Cómo escribir un controlador de dispositivo Linux simple?


Necesito escribir un controlador de dispositivo de caracteres SPI Linux para omap4 desde cero. Conozco algunos conceptos básicos de escritura de controladores de dispositivos. Pero, no se cómo empezar a escribir controlador de dispositivo específico de la plataforma desde cero.

He escrito algunos controladores char básicos, y pensé que escribir controlador de dispositivo SPI sería similar a él. Los controladores Char tienen una estructura file_operations que contiene las funciones implementadas en el controlador.

struct file_operations Fops = {
    .read = device_read,
    .write = device_write,
    .ioctl = device_ioctl,
    .open = device_open,
    .release = device_release,  /* a.k.a. close */
};

Ahora, estoy pasando por spi-omap2-mcspi.código c como referencia para tener una idea para empezar a desarrollar SPI driver desde cero.

Pero, no veo funciones como abrir, leer, escribir, etc. No sé de dónde empieza el programa.

Author: Sagar Jain, 2014-03-25

4 answers

Primero comience escribiendo un módulo de kernel genérico. Hay varios lugares para buscar información, pero encontré que este enlace es muy útil. Después de haber pasado por todos los ejemplos especificados allí, puede comenzar a escribir su propio Módulo de Controlador de Linux.

Tenga en cuenta que no se saldrá con la suya con solo copiar y pegar el código de ejemplo y esperar que funcione, no. La API del núcleo a veces puede cambiar y los ejemplos no funcionarán. Examples provided there should be looked at as a guía cómo hacer algo. Dependiendo de la versión del kernel que esté utilizando, debe modificar el ejemplo para que funcione.

Considere usar las funciones proporcionadas por la plataforma TI tanto como pueda, porque eso realmente puede hacer mucho trabajo por usted, como solicitar y habilitar los relojes, autobuses y fuentes de alimentación necesarios. Si recuerdo correctamente, puede usar las funciones para adquirir rangos de direcciones mapeados de memoria para el acceso directo a los registros. Tengo que mencionar que tengo mala experiencia con TI siempre funciones porque no liberan/limpian correctamente todos los recursos adquiridos, por lo que para algunos recursos tuve que llamar a otros servicios del núcleo para liberarlos durante la descarga del módulo.

Editar 1:

No estoy completamente familiarizado con la implementación SPI de Linux, pero empezaría por mirar la función omap2_mcspi_probe() en drivers/spi/spi-omap2-mcspi.archivo c. Como puede ver allí, registra sus métodos para Linux master SPI driver usando esta API: Linux/include/linux/spi / spi.h. In en contraste con el controlador char, las funciones principales aquí son las funciones * _transfer (). Mira las descripciones de estructuras en spi.h archivo para más detalles. También, echa un vistazo a esta API de controlador de dispositivo alternativo, también.

 50
Author: Nenad Radulovic,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-03-27 21:30:18

Asumo que su linux OMAP4 usa uno de arch/arm/boot/dts/{omap4.dtsi,am33xx.dtsi} device-tree, por lo que compila drivers/spi/spi-omap2-mcspi.c (si no conoce el device-tree, lea esto). Entonces:

  • el controlador maestro SPI está hecho,
  • (muy probablemente) se registra con Linux SPI core framework drivers/spi/spi.c,
  • (probablemente) funciona bien en su OMAP4.

En realidad no necesita preocuparse por el controlador maestro para escribir su controlador de dispositivo esclavo . ¿Cómo sé que spi-omap2-mcspi.c es un maestro conductor? Llama spi_register_master().

¿Amo del SPI, esclavo del SPI ?

Por favor refiérase a Documentation/spi/spi_summary. El documento se refiere a Controlador de controlador (maestro) y Controladores de protocolo (esclavo). Por su descripción, entiendo que desea escribir un protocolo /Controlador de dispositivo.

Protocolo SPI ?

Para entender eso, necesita la hoja de datos de su dispositivo esclavo, le dirá:

  • el modo SPI entendido por su dispositivo,
  • el protocolo se espera en el bus.

Al contrario de i2c, SPI no define un protocolo o handshake, los fabricantes de chips SPI tienen que definir el suyo propio. Así que revisa la hoja de datos.

Modo SPI

De include/linux/spi/spi.h:

 * @mode: The spi mode defines how data is clocked out and in.
 *  This may be changed by the device's driver.
 *  The "active low" default for chipselect mode can be overridden
 *  (by specifying SPI_CS_HIGH) as can the "MSB first" default for
 *  each word in a transfer (by specifying SPI_LSB_FIRST).

De nuevo, revise la hoja de datos de su dispositivo SPI.

¿Un ejemplo de controlador de dispositivo SPI?

Para darle un ejemplo relevante, necesito saber su tipo de dispositivo SPI. Usted entendería que un controlador de dispositivo flash SPI es diferente de un controlador de dispositivo SPI FPGA . Desafortunadamente no hay tantos controladores de dispositivos SPI por ahí. Para encontrarlos:

$ cd linux 
$ git grep "spi_new_device\|spi_add_device"
 19
Author: m-ric,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 11:47:32

No se si entendí su pregunta correctamente. Como señaló m-ric, hay conductores maestros y conductores esclavos.

Por lo general, los controladores maestros están más vinculados al hardware, es decir, por lo general manipulan los registros de E / s o hacen algo de E / s mapeado en memoria.

Para algunas arquitecturas ya soportadas por el kernel de linux (como omap3 y omap4) los controladores maestros ya están implementados (McSPI).

Así que asumo que desea utilizar esas instalaciones SPI de omap4 para implementar un dispositivo esclavo driver (su protocolo, para comunicarse con su dispositivo externo a través de SPI).

He escrito el siguiente ejemplo para BeagleBoard-xm (omap3). El código completo está en https://github.com/rslemos/itrigue/blob/master/alsadriver/itrigue.c (vale la pena una vista, pero tiene más código de inicialización, para ALSA, GPIO, parámetros de módulo). He tratado de separar el código que se ocupa de SPI (tal vez me olvidé de algo, pero de todos modos usted debe tener la idea):

#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/spi/spi.h>

/* MODULE PARAMETERS */
static uint spi_bus = 4;
static uint spi_cs = 0;
static uint spi_speed_hz = 1500000;
static uint spi_bits_per_word = 16;

/* THIS IS WHERE YOUR DEVICE IS CREATED; THROUGH THIS YOU INTERACT WITH YOUR EXTERNAL DEVICE */
static struct spi_device *spi_device;


/* SETUP SPI */

static inline __init int spi_init(void) {
    struct spi_board_info spi_device_info = {
        .modalias = "module name",
        .max_speed_hz = spi_speed_hz,
        .bus_num = spi_bus,
        .chip_select = spi_cs,
        .mode = 0,
    };

    struct spi_master *master;

    int ret;

    // get the master device, given SPI the bus number
    master = spi_busnum_to_master( spi_device_info.bus_num );
    if( !master )
        return -ENODEV;

    // create a new slave device, given the master and device info
    spi_device = spi_new_device( master, &spi_device_info );
    if( !spi_device )
        return -ENODEV;

    spi_device->bits_per_word = spi_bits_per_word;

    ret = spi_setup( spi_device );
    if( ret )
        spi_unregister_device( spi_device );

    return ret;
}

static inline void spi_exit(void) {
    spi_unregister_device( spi_device );
}

Para escribir datos a su dispositivo:

spi_write( spi_device, &write_data, sizeof write_data );

El código anterior es independiente de la implementación, es decir, podría usar McSPI, GPIO bit-banged o cualquier otra implementación de un dispositivo maestro SPI. Esta interfaz se describe en linux/spi/spi.h

Para que funcione en BeagleBoard-XM tuve que añadir lo siguiente a la línea de comandos del kernel:

omap_mux=mcbsp1_clkr.mcspi4_clk=0x0000,mcbsp1_dx.mcspi4_simo=0x0000,mcbsp1_dr.mcspi4_somi=0x0118,mcbsp1_fsx.mcspi4_cs0=0x0000

Para que se cree un dispositivo maestro McSPI para la instalación de hardware omap3 McSPI4.

Espero que eso ayude.

 11
Author: rslemos,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-06-24 01:32:14

Minimal runnable file_operations example

Este ejemplo no interactúa con ningún hardware, pero ilustra la API de kernel file_operations más simple con debugfs.

Módulo del núcleo fops.c :

#include <asm/uaccess.h> /* copy_from_user, copy_to_user */
#include <linux/debugfs.h>
#include <linux/errno.h> /* EFAULT */
#include <linux/fs.h> /* file_operations */
#include <linux/kernel.h> /* min */
#include <linux/module.h>
#include <linux/printk.h> /* printk */
#include <uapi/linux/stat.h> /* S_IRUSR */

static struct dentry *debugfs_file;
static char data[] = {'a', 'b', 'c', 'd'};

static int open(struct inode *inode, struct file *filp)
{
    pr_info("open\n");
    return 0;
}

/* @param[in,out] off: gives the initial position into the buffer.
 *      We must increment this by the ammount of bytes read.
 *      Then when userland reads the same file descriptor again,
 *      we start from that point instead.
 * */
static ssize_t read(struct file *filp, char __user *buf, size_t len, loff_t *off)
{
    ssize_t ret;

    pr_info("read\n");
    pr_info("len = %zu\n", len);
    pr_info("off = %lld\n", (long long)*off);
    if (sizeof(data) <= *off) {
        ret = 0;
    } else {
        ret = min(len, sizeof(data) - (size_t)*off);
        if (copy_to_user(buf, data + *off, ret)) {
            ret = -EFAULT;
        } else {
            *off += ret;
        }
    }
    pr_info("buf = %.*s\n", (int)len, buf);
    pr_info("ret = %lld\n", (long long)ret);
    return ret;
}

/* Similar to read, but with one notable difference:
 * we must return ENOSPC if the user tries to write more
 * than the size of our buffer. Otherwise, Bash > just
 * keeps trying to write to it infinitely. */
static ssize_t write(struct file *filp, const char __user *buf, size_t len, loff_t *off)
{
    ssize_t ret;

    pr_info("write\n");
    pr_info("len = %zu\n", len);
    pr_info("off = %lld\n", (long long)*off);
    if (sizeof(data) <= *off) {
        ret = 0;
    } else {
        if (sizeof(data) - (size_t)*off < len) {
            ret = -ENOSPC;
        } else {
            if (copy_from_user(data + *off, buf, len)) {
                ret = -EFAULT;
            } else {
                ret = len;
                pr_info("buf = %.*s\n", (int)len, data + *off);
                *off += ret;
            }
        }
    }
    pr_info("ret = %lld\n", (long long)ret);
    return ret;
}

/*
Called on the last close:
http://stackoverflow.com/questions/11393674/why-is-the-close-function-is-called-release-in-struct-file-operations-in-the-l
*/
static int release(struct inode *inode, struct file *filp)
{
    pr_info("release\n");
    return 0;
}

static loff_t llseek(struct file *filp, loff_t off, int whence)
{
    loff_t newpos;

    pr_info("llseek\n");
    pr_info("off = %lld\n", (long long)off);
    pr_info("whence = %lld\n", (long long)whence);
    switch(whence) {
        case SEEK_SET:
            newpos = off;
            break;
        case SEEK_CUR:
            newpos = filp->f_pos + off;
            break;
        case SEEK_END:
            newpos = sizeof(data) + off;
            break;
        default:
            return -EINVAL;
    }
    if (newpos < 0) return -EINVAL;
    filp->f_pos = newpos;
    pr_info("newpos = %lld\n", (long long)newpos);
    return newpos;
}

static const struct file_operations fops = {
    /* Prevents rmmod while fops are running.
     * Try removing this for poll, which waits a lot. */
    .owner = THIS_MODULE,
    .llseek = llseek,
    .open = open,
    .read = read,
    .release = release,
    .write = write,
};

static int myinit(void)
{
    debugfs_file = debugfs_create_file("lkmc_fops", S_IRUSR | S_IWUSR, NULL, NULL, &fops);
    return 0;
}

static void myexit(void)
{
    debugfs_remove_recursive(debugfs_file);
}

module_init(myinit)
module_exit(myexit)
MODULE_LICENSE("GPL");

Userland shell test program :

#!/bin/sh

mount -t debugfs none /sys/kernel/debug

insmod /fops.ko
cd /sys/kernel/debug/lkmc_fops

## Basic read.
cat f
# => abcd
# dmesg => open
# dmesg => read
# dmesg => len = [0-9]+
# dmesg => close

## Basic write

printf '01' >f
# dmesg => open
# dmesg => write
# dmesg => len = 1
# dmesg => buf = a
# dmesg => close

cat f
# => 01cd
# dmesg => open
# dmesg => read
# dmesg => len = [0-9]+
# dmesg => close

## ENOSPC
printf '1234' >f
printf '12345' >f
echo "$?"
# => 8
cat f
# => 1234

## seek
printf '1234' >f
printf 'z' | dd bs=1 of=f seek=2
cat f
# => 12z4

También debe escribir un programa en C que ejecute esas pruebas si no tiene claro qué llamadas al sistema se están llamando para cada uno de esos comandos. (o también podría usar strace y averiguar :-)).

Los otros file_operations están un poco más involucrados, aquí hay algunos ejemplos más:

Comience con modelos de software de hardware simplificado en emuladores

El desarrollo real del hardware del dispositivo es "difícil" porque:

  • usted no siempre puede conseguir su mano en un hardware dado fácilmente
  • las API de hardware pueden ser complicadas
  • es difícil para ver cuál es el estado interno del hardware

Emuladores como QEMU nos permiten superar todas esas dificultades, simulando simulación de hardware simplificada en software.

QEMU por ejemplo, tiene un dispositivo PCI educativo incorporado llamado edu, que expliqué más adelante en: ¿Cómo agregar un nuevo dispositivo en el código fuente de QEMU? y es una buena manera de comenzar con los controladores de dispositivos. He hecho un controlador simple para él disponible aquí.

Puedes luego ponga printf o use GDB en QEMU como para cualquier otro programa, y vea exactamente lo que está pasando.

También hay un modelo OPAM SPI para su caso de uso específico: https://github.com/qemu/qemu/blob/v2.7.0/hw/ssi/omap_spi.c

 4
Author: Ciro Santilli 新疆改造中心 六四事件 法轮功,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-08-06 13:02:01