para dispositivo crear como c linux linux-kernel linux-device-driver embedded-linux

crear - ¿Cómo escribir un controlador de dispositivo Linux simple?



como crear un driver para linux (4)

No sé si entendí tu pregunta correctamente. Como señaló m-ric, hay controladores maestros y esclavos.

Por lo general, los controladores maestros están más atados al hardware, es decir, generalmente manipulan los registros IO o hacen algo de IO asignado a la memoria.

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

Así que supongo que desea UTILIZAR las instalaciones SPI de omap4 para implementar un controlador de dispositivo esclavo (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 verlo, pero tiene más código de inicialización, para ALSA, GPIO, parámetros del módulo). Intenté separar el código que trata con SPI (tal vez olvidé algo, pero de todos modos debería 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 en 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 utilizar McSPI, GPIO con bits desordenados 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 agregar 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

De modo que se crea un dispositivo maestro McSPI para la instalación de hardware omap3 McSPI4.

Espero que ayude.

Necesito escribir desde cero un controlador de dispositivo de personaje SPI Linux para omap4. Conozco algunos conceptos básicos para escribir controladores de dispositivos. Pero, no sé cómo comenzar a escribir el controlador de dispositivo específico de la plataforma desde cero.

He escrito algunos controladores de carga básicos, y pensé que escribir un controlador de dispositivo SPI sería similar. Los controladores de 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 spi-omap2-mcspi.c código spi-omap2-mcspi.c como referencia para tener una idea de cómo empezar a desarrollar un controlador SPI desde cero.

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


Primero, comience por escribir un módulo kernel genérico. Hay múltiples lugares para buscar información, pero este enlace me pareció muy útil. Después de haber analizado todos los ejemplos allí especificados, puede comenzar a escribir su propio Módulo de controladores Linux.

Tenga en cuenta que no saldrá impune copiando y pegando el código de ejemplo y esperando que funcione, no. Kernel API a veces puede cambiar y los ejemplos no funcionarán. Los ejemplos proporcionados deben considerarse como una guía sobre cómo hacer algo. Dependiendo de la versión del kernel que esté utilizando, debe modificar el ejemplo para poder trabajar.

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

Editar 1:

No estoy completamente familiarizado con la implementación de SPI de Linux, pero comenzaría por observar la función omap2_mcspi_probe () en el archivo drivers / spi / spi-omap2-mcspi.c. Como puede ver allí, registra sus métodos para el controlador maestro de SPI de Linux utilizando esta API: Linux / include / linux / spi / spi.h. A diferencia del controlador de char, las principales funciones aquí son * _transfer () funciones. Consulte las descripciones de las estructuras en el archivo spi.h para obtener más detalles. Además, eche un vistazo a this API de controlador de dispositivo alternativa también.


Supongo que tu OMAP4 Linux usa uno de los dispositivos arch/arm/boot/dts/{omap4.dtsi,am33xx.dtsi} device-tree, por lo que compila drivers/spi/spi-omap2-mcspi.c (si no sabes acerca de device-tree, léelo ). Entonces:

  • el controlador maestro SPI está hecho,
  • lo más probable es que se registre con drivers/spi/spi.c marco de núcleo SPI de Linux 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 controlador maestro? Llama a spi_register_master() .

Maestro SPI, esclavo SPI?

Por favor, consulte Documentation/spi/spi_summary . El documento hace referencia al controlador del controlador (maestro) y los controladores del protocolo (esclavo). Según su descripción, entiendo que quiere escribir un controlador de Protocolo / Dispositivo .

Protocolo SPI?

Para entender eso, necesitas la hoja de datos de tu dispositivo esclavo, te dirá:

  • el modo SPI entendido por su dispositivo,
  • el protocolo que espera en el autobús.

A diferencia de i2c, SPI no define un protocolo o un apretón de manos, los fabricantes de chips SPI tienen que definir los propios. Por lo tanto, consulte la hoja de datos.

Modo SPI

Desde 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, verifique 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. Comprendería que un controlador de dispositivo flash SPI es diferente de un controlador de dispositivo SPI FPGA . Lamentablemente, no hay tantos controladores de dispositivo SPI. Para encontrarlos:

$ cd linux $ git grep "spi_new_device/|spi_add_device"


Ejemplo mínimo file_operations ejecutable

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

Kernel module 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://.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");

Programa de prueba shell de Userland :

#!/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 está claro para qué llamadas del sistema se están convocando para cada uno de esos comandos. (o también strace usar strace y descubrirlo :-)).

Las otras file_operations son un poco más complicadas, aquí hay algunos ejemplos adicionales:

Comience con modelos de software de hardware simplificado en emuladores

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

  • no siempre puedes tener tu mano sobre un hardware dado fácilmente
  • las API de hardware pueden ser complicadas
  • es difícil ver cuál es el estado interno del hardware

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

QEMU, por ejemplo, tiene un dispositivo PCI educativo integrado 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 dispositivo. He hecho un controlador simple para que esté disponible aquí .

A continuación, puede poner printf o usar GDB en QEMU como para cualquier otro programa, y ​​ver exactamente lo que está sucediendo.

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