ECB-BF532/uClinux driver char

From Wiki-linuxencaja
Jump to: navigation, search

Contents

Introducción

La filosofía Unix "todo es un archivo", implica que los controladores de dispositivos son de tal manera que se le permite al usuario el acceso a funciones del kernel que controlan cierto número de componentes/periféricos.

Los drivers en Linux son de 3 tipos:

  • Controladores de Carácter:

Ampliamente utilizados, utilizados tanto para tareas simples como sofisticadas. Abarca desde hacer encender/apagar un led hasta controladores USB

  • Controladores de Bloque:

Utilizados mayormente para almacenamiento ya que hacen interfaz con el sistema de archivos y se utilizan para cargar particiones de discos. Se diferencian de los controladores tipo carácter ya que el acceso a los datos suele ser protegido por el kernel

  • Controladores de Red:

Este tipo tiene una estructura diferente y están estrechamente relacionados con la interconexión de redes y su integración en el kernel. Utiliza puertos y conectores (sockets) para rutear paquetes de datos desde y hacia el sistema.


Al usuario No se le permite ejecutar código del kernel o tener acceso a los dispositivos de entrada/salida de forma directa. Cuando esta funcionalidad es requerida se debe hacer por medio de una aplicación que debe usar el driver del dispositivo. Es altamente recomendable escribir un driver para cada dispositivo necesario dada la facilidad y la posibilidad de reutilización del código.

Drivers en Linux

Un driver puede ser modular o puede estar enlazado directamente dentro del kernel. Para poder utilizar correctamente primero el driver debe ejecutar una función de registro en el kernel durante el proceso de inicialización del módulo.

Un controlador de dispositivo debe contener los siguientes componentes:

  • Una tabla de operaciones del archivo (fops: file operations)
  • Un número mayor
  • Tipo de Dispositivo (Bloque o Carácter)
  • Nombre

Existen unos archivos especiales llamados nodos de dispositivo (device nodes), que son creados en archivos del sistema y contienen la clase, el número mayor y el número menor de cada dispositivo en particular. Estos nodos permiten acceder al dispositivo, y convenientemente al ser accedidos por medio de los devices nodes (creados con el comando mknod) son puestos en el directorio /dev

La tabla de operaciones de archivo (fops) son funciones que permiten aumentar las capacidades del kernel al manejar los dispositivos.

Número Mayor

El número Mayor es el enlace principal entre el dispositivo, el controlador del kernel y el software del usuario. El usuario abre un dispositivo representado por un archivo de nodo de dispositivo (creado por mknod). Este archivo especial al ser creado fue dotado de una clase (b=bloque, c=caracter) y un número mayor.

El número mayor se refiere a un controlador de dispositivo que se registró en el kernel como el servicio de esa clase particular de dispositivo .

Para visualizar los drivers que están registrados en el sistema se utiliza el comando:

  1. cat /proc/devices

De lo cual se debe obtener información del siguiente tipo:

  1. (Major Number/ Device Name )
  2.  
  3. Character devices:          
  4.   1 mem                     
  5.   2 pty                     
  6.   3 ttyp                    
  7.   4 ttyS                    
  8.   5 cua                     
  9.   7 vcs                     
  10.  10 misc                    
  11.  14 sound                   
  12. 128 ptm                     
  13. 136 pts                     
  14. 254 pcmcia                  
  15.  
  16. Block devices:              
  17.   1 ramdisk                 
  18.   2 fd                      
  19.   3 ide0                    
  20.  22 ide1

Note que se puede utilizar el mismo número mayor para controladores de clase distinta (tipo caracter o bloque).

Para mayor referencia existe una gran lista con los dispositivos soportados y sus respectivos números mayores en:

  • linux-2.6.x/Documentation/devices.txt

Número Menor

Este número menor tambien es especificado por el archivo especial creado por mknod. El controlador principal es referenciado por el número mayor, pero el número menor puede ser utilizado para obtener una instancia particular del dispositivo controlado por el driver.

Por ejemplo, un driver serial puede controlar varios puertos seriales, en este caso el número menor puede dirigir el driver a un puerto en particular. En algunos casos el número menor puede referirse a operaciones completamente diferentes de lectura/escritura

Driver Simple

El driver más básico consiste en hacer que se pueda registrar en el kernel correctamente y dejar todo dispuesto para luego agregar la información y funciones que sean necesarias

Código

El siguiente código tiene la estructura básica de un driver para Linux. A pesar de que no contiene un código para ejecutar alguna funcionalidad tiene las funciones necesarias para ser registrado en el kernel.

  1. /*
  2.  * Basic Character mode driver
  3.  */
  4.  
  5. #include <linux/module.h>
  6. #include <linux/kernel.h>
  7. #include <linux/init.h>
  8. #include <linux/fs.h>
  9. #include <linux/uaccess.h>
  10.  
  11. #define SCMD_DEV "scmd_basic"
  12.  
  13. /* small buffer used by our demonstration driver */
  14. #define SCMD_SIZE 32
  15. static char scmd_data[SCMD_SIZE];
  16.  
  17.  
  18. /* Para un número mayor estático scmd_major = 250, para el caso dinámico debe ser scmd_major = 0 */
  19. static int scmd_major = 250;
  20.  
  21.  
  22. /* Tabla de operaciones, cuando el valor es NULL el kernel identifica que este driver tipo caracter no puede realizar 
  23. peticiones con el sistema de archivos (leer, escribir,ioctl, etc) */
  24. struct file_operations scmd_driver_fops = {
  25.     NULL
  26. };
  27.  
  28.  
  29. static int scmd_open(struct inode *inode, struct file *file);
  30. static int scmd_release(struct inode *inode, struct file *file);
  31. static ssize_t scmd_read(struct file *filep, char *buf, size_t  count, loff_t * f_pos);
  32. static ssize_t scmd_write(struct file *filep, const char *buf, size_t  count, loff_t * f_pos);
  33.  
  34.  
  35. /* Esta función permite registrar el driver como tipo carácter */
  36. static int __init scmd_init(void){                                 
  37.       int ret;
  38.       int i;
  39.  
  40.       /* set up the initial data */                  
  41.       for ( i = 0 ; i < SCMD_SIZE ; i++ ) 
  42.       {          
  43.           scmd_data[i] = '0'+i;                       
  44.       }                                              
  45.       /*Referencias necesarias en la tabla de operaciones de archivo (fops) para las funciones principales */
  46.       scmd_driver_fops.read = scmd_read;
  47.       scmd_driver_fops.write = scmd_write;
  48.       scmd_driver_fops.open = scmd_open;
  49.       scmd_driver_fops.release = scmd_release;
  50.  
  51.  
  52.       ret = register_chrdev(scmd_major, SCMD_DEV,&scmd_driver_fops);            
  53.       printk(" Driver scmd_basic registered .. ret %d \n",ret);      
  54.       if ( ret > 0 ) {                                               
  55.           scmd_major = ret;                                          
  56.        ret = 0;                                                          
  57.       }                                                              
  58.       return ret;                                                    
  59.   }
  60. module_init(scmd_init);
  61.  
  62.  
  63. /* Este código permite remover el driver */
  64. static void __exit scmd_exit(void){
  65.     unregister_chrdev(scmd_major, SCMD_DEV);
  66.     printk(KERN_INFO SCMD_DEV ": unregistered char device \n");
  67. }
  68. module_exit(scmd_exit);
  69.  
  70. /* La funcion "Open" es utilizada para configurar el proceso para acceder al dispositivo. Normalmente realiza las siguiente operaciones:
  71.  *Configura una estructura de datos privados para guardar el estado del driver
  72.  *Crea área en memoria para ser utilizada por el driver
  73.  *Administrar los bloqueos y semáforos para controlar el acceso al dispositivo
  74.  
  75.  La funcion "Open" debe retornar 0 para indicar que las operaciones han sido exitosas ó valor negativo cuando sucede algún error.
  76.  
  77. Los parámetros de la función Open son:
  78.  struct inode *inode:Se refiere al nodo del dispositivo encontrado en el sistema de archivos
  79.  struct file *file: Es una estructura creada por el kernel para mantener el estado de nuestro uso del dispositivo en el controlador de dispositivo
  80. */
  81. static unsigned long scmd_is_open;
  82. static int scmd_open(struct inode *inode, struct file *file)
  83. {
  84.         // allow only one user
  85.         if(test_and_set_bit(0, &scmd_is_open))
  86.                 return -EBUSY;
  87.         // Activate the system
  88.         //scmd_start();
  89.  
  90. 	/* This is used by subsystems that don't want seekable file descriptors */
  91.         return nonseekable_open(inode, file);
  92. }
  93.  
  94.  
  95. /*Esta es la función de cierre/liberación del controlador */
  96. static int scmd_release(struct inode *inode, struct file *file)
  97. {
  98.         // release the driver for others
  99.         clear_bit(0, &scmd_is_open);
  100.  
  101.         // we are done
  102.         return 0;
  103. }
  104.  
  105.  
  106. /*
  107.  Las funciones de lectura y escritura son utilizadas para leer y escribir datos en el dispositivo, el sistema de llamada del kernel agrega dos parámetros extras (filep and loff_t). En este ejemplo el driver del dispositivo va a leer y escribir un pequeño buffer en el espacio del kernel para simular una transferencia de datos hacia un dispositivo seleccionado 
  108.  
  109. Los parámetros para las funciones de leer/escribir son casi iguales:
  110. *filep    - Estructura del controlador alojada en el kernel 
  111. *buf      - Buffer del espacio de usuario 
  112. *count    - El número de bytes a transferir
  113. *lofft    - Offset del primer byte de datos a transferir 
  114.  
  115. El valor que retorna corresponde a la cuenta actual de los datos escritos o leídos del dispositivo. En el caso de lectura cuando se retorna 0, indica que no existena más datos. En el caso de escritura si el valor retornado es 0 significa que ningún dato fue escrito
  116. */
  117.  
  118. /*
  119. La función "read" transfiere 'count' número de carácteres del dispositivo hacia el buffer 'buf' en el espacio de usuario 
  120. */
  121. static ssize_t scmd_read(struct file *filep, char *buf, size_t  count, loff_t * f_pos){                             
  122.      int pos;                                                            
  123.      int size;                                                           
  124.  
  125.      pos = *f_pos;                                                       
  126.      size = SCMD_SIZE;                                                   
  127.  
  128.      /* check limits */                                                  
  129.      if (pos >= size) pos = size-1;                                      
  130.      if ((pos + count) >= size) count = (size - pos) - 1;                
  131.  
  132.      /* copy count from kernel buffer to user space buffer */            
  133.      /* copy_to_user returns 0 on success */                             
  134.      if (copy_to_user(buf,&scmd_data[pos],count))                        
  135.         return -EFAULT;                                                  
  136.      pos += count;                                                       
  137.      //filep->f_pos = pos; // Note this is deprecated                            
  138.      *f_pos = pos;  // new for 2.6 Kernel                                
  139.      return count;                                                       
  140.   }                    
  141.  
  142.  
  143. /*
  144. La función de "write" transfiere 'count' caracteres del espacio de usuario hacia el dispositivo
  145.  */
  146. static ssize_t scmd_write(struct file *filep, const char *buf, size_t  count, loff_t * f_pos){                      
  147.      int pos;                                                      
  148.      int size;                                                     
  149.  
  150.      pos = *f_pos;                                                  
  151.      size = SCMD_SIZE;                                             
  152.  
  153.      /* check limits */                                            
  154.      if (pos >= size) pos = size;                                  
  155.      if ((pos + count) >= size) count = (size - pos) - 1;          
  156.  
  157.      /* copy count from a user space buffer to the kernel buffer */
  158.      /* copy_from_user returns 0 on success */                    
  159.      if (copy_from_user(&scmd_data[pos],buf,count))               
  160.         return -EFAULT;                                           
  161.      pos += count;                                                
  162.      //filep->f_pos = pos;  // Note Deprecated                    
  163.      *f_pos = pos; // new for 2.6 Kernel                          
  164.      return count;                                                
  165. }
  166.  
  167.  
  168. /*
  169. El kernel de Linux ofrece un par de funciones para llevar a cabo la transferencia de datos entre el espacio de usuario y el espacio del núcleo.
  170. Estas funciones proporcionan un chequeo que el espacio del usuario es válido. Las funciones retornarán el número de bytes no-copiados si detecta un error en el conteo (count) o en el buffer (buf). El controlador debe retornar como un error en el código de usuario.
  171.  
  172. El kernel se encarga automáticamente de cualquier página de intercambio necesaria para asignar el espacio de datos del usuario.
  173. Estas funciones están definidas en la librería: #include <linux/uaccess.h>
  174. */

Configurar y Construir

Una vez obtenido el código del driver se procederá a agregarlo a las fuentes del kernel y al sistema de construcción del kernel. El procedimiento es el siguiente:

  • Crear un nuevo directorio para el driver
  • Agregar las fuentes al nuevo directorio
  • Crear un archivo Makefile en el nuevo directorio
  • Modificar el Makefile principal para incluir el nuevo sub-directorio
  • Agregar una opción de configuración en el antiguo Kconfig
  • Crear un nuevo archivo Kconfig
  • Probar el funcionamiento
  • Probar el driver dentro de la tarjeta ECB-BF532

Crear nuevo directorio

El nuevo driver se guardará en el directorio "test"

  1.  mkdir -p linux-2.6.x/drivers/char/test

Copiar las fuentes en el nuevo directorio

Se copia el código del driver en este directorio

  1. linux-2.6.x/drivers/char/test/simple_driver.c

Crear un Makefile en el nuevo directorio

El nuevo Makefile se creará en el directorio drivers/char/test

  1. #                                            
  2. # basic Makefile                             
  3. #                                            
  4. # place this in linux-2.6.x/drivers/char/test
  5. #                                            
  6. # bypass the config system for now
  7. # CONFIG_SCMD_DRIVER = y                       
  8. # add the driver 
  9. obj-$(CONFIG_SCMD_DRIVER) += simple_driver.o

Agregar el directorio "test" al Makefile raíz

El Makefile en el directorio drivers/char es modificado para agregar la referencia al nuevo directorio "test"

  1.  #                                                 
  2.  #  modify linux-2.6.x/drivers/char/Makefile       
  3.  #  add the test sub directory note the trailing / 
  4.  #                                                 
  5.  # dummy config statement to be removed later      
  6.  #CONFIG_DRIVER_TEST = y                            
  7.  #
  8.  # trigger the inclusion of the test directory     
  9.  obj-$(CONFIG_DRIVER_TEST) += test/                
  10.  #                                                 
  11.  #

Primera prueba

  1.  cd linux-2.6.x/
  2.  make > make.out
  3.  grep simple_driver make.out
  4.   CC      drivers/char/test/simple_driver.o

Con esto ya se tiene el driver listo para ser incluido en el menu de configuración de Linux

Agregar Opciones de Configuración

Agregar al archivo Kconfig en el directorio del controlador: "drivers/char/Kconfig" y el siguiente código:

  1. config DRIVER_TEST                           
  2.      tristate "Driver Test"                      
  3.         help                                 
  4.           Some Test Drivers 
  5. source "drivers/char/test/Kconfig"

Crear el archivo Kconfig en el directorio: "linux-2.6.x/drivers/char/test/Kconfig". Agregue el siguiente código:

  1. #                                            
  2. # Test Driver configuration                  
  3. #                                            
  4. config SCMD_DRIVER                           
  5.        tristate "Basic Test Driver"          
  6.        depends on DRIVER_TEST                
  7.        help                                  
  8.          A basic demo driver

Ahora basta con ejecutar: make menuconfig e intentar habilitar el nuevo driver. Luego de seleccionarlo se procede a crear la imagen del kernel:

  1. make

Fuentes

Personal tools
Namespaces

Variants
Actions
Navigation
Toolbox