ECB-BF532/uClinux driver char
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:
cat /proc/devices
De lo cual se debe obtener información del siguiente tipo:
(Major Number/ Device Name )
Character devices:1 mem2 pty3 ttyp4 ttyS5 cua7 vcs10 misc14 sound128 ptm136 pts254 pcmciaBlock devices:1 ramdisk2 fd3 ide022 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.
/** Basic Character mode driver*/#include <linux/module.h>#include <linux/kernel.h>#include <linux/init.h>#include <linux/fs.h>#include <linux/uaccess.h>#define SCMD_DEV "scmd_basic"/* small buffer used by our demonstration driver */#define SCMD_SIZE 32static char scmd_data[SCMD_SIZE];
/* Para un número mayor estático scmd_major = 250, para el caso dinámico debe ser scmd_major = 0 */static int scmd_major = 250;
/* Tabla de operaciones, cuando el valor es NULL el kernel identifica que este driver tipo caracter no puede realizarpeticiones con el sistema de archivos (leer, escribir,ioctl, etc) */struct file_operations scmd_driver_fops = {
NULL
};
static int scmd_open(struct inode *inode, struct file *file);
static int scmd_release(struct inode *inode, struct file *file);
static ssize_t scmd_read(struct file *filep, char *buf, size_t count, loff_t * f_pos);
static ssize_t scmd_write(struct file *filep, const char *buf, size_t count, loff_t * f_pos);
/* Esta función permite registrar el driver como tipo carácter */static int __init scmd_init(void){
int ret;
int i;
/* set up the initial data */for ( i = 0 ; i < SCMD_SIZE ; i++ )
{scmd_data[i] = '0'+i;
}/*Referencias necesarias en la tabla de operaciones de archivo (fops) para las funciones principales */scmd_driver_fops.read = scmd_read;
scmd_driver_fops.write = scmd_write;
scmd_driver_fops.open = scmd_open;
scmd_driver_fops.release = scmd_release;
ret = register_chrdev(scmd_major, SCMD_DEV,&scmd_driver_fops);
printk(" Driver scmd_basic registered .. ret %d \n",ret);
if ( ret > 0 ) {
scmd_major = ret;
ret = 0;
}return ret;
}module_init(scmd_init);
/* Este código permite remover el driver */static void __exit scmd_exit(void){
unregister_chrdev(scmd_major, SCMD_DEV);
printk(KERN_INFO SCMD_DEV ": unregistered char device \n");
}module_exit(scmd_exit);
/* La funcion "Open" es utilizada para configurar el proceso para acceder al dispositivo. Normalmente realiza las siguiente operaciones:*Configura una estructura de datos privados para guardar el estado del driver*Crea área en memoria para ser utilizada por el driver*Administrar los bloqueos y semáforos para controlar el acceso al dispositivoLa funcion "Open" debe retornar 0 para indicar que las operaciones han sido exitosas ó valor negativo cuando sucede algún error.Los parámetros de la función Open son:struct inode *inode:Se refiere al nodo del dispositivo encontrado en el sistema de archivosstruct file *file: Es una estructura creada por el kernel para mantener el estado de nuestro uso del dispositivo en el controlador de dispositivo*/static unsigned long scmd_is_open;
static int scmd_open(struct inode *inode, struct file *file)
{// allow only one userif(test_and_set_bit(0, &scmd_is_open))
return -EBUSY;
// Activate the system//scmd_start();/* This is used by subsystems that don't want seekable file descriptors */return nonseekable_open(inode, file);
}/*Esta es la función de cierre/liberación del controlador */static int scmd_release(struct inode *inode, struct file *file)
{// release the driver for othersclear_bit(0, &scmd_is_open);
// we are donereturn 0;
}/*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 seleccionadoLos parámetros para las funciones de leer/escribir son casi iguales:*filep - Estructura del controlador alojada en el kernel*buf - Buffer del espacio de usuario*count - El número de bytes a transferir*lofft - Offset del primer byte de datos a transferirEl 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*//*La función "read" transfiere 'count' número de carácteres del dispositivo hacia el buffer 'buf' en el espacio de usuario*/static ssize_t scmd_read(struct file *filep, char *buf, size_t count, loff_t * f_pos){
int pos;
int size;
pos = *f_pos;
size = SCMD_SIZE;
/* check limits */if (pos >= size) pos = size-1;
if ((pos + count) >= size) count = (size - pos) - 1;
/* copy count from kernel buffer to user space buffer *//* copy_to_user returns 0 on success */if (copy_to_user(buf,&scmd_data[pos],count))
return -EFAULT;
pos += count;
//filep->f_pos = pos; // Note this is deprecated*f_pos = pos; // new for 2.6 Kernel
return count;
}/*La función de "write" transfiere 'count' caracteres del espacio de usuario hacia el dispositivo*/static ssize_t scmd_write(struct file *filep, const char *buf, size_t count, loff_t * f_pos){
int pos;
int size;
pos = *f_pos;
size = SCMD_SIZE;
/* check limits */if (pos >= size) pos = size;
if ((pos + count) >= size) count = (size - pos) - 1;
/* copy count from a user space buffer to the kernel buffer *//* copy_from_user returns 0 on success */if (copy_from_user(&scmd_data[pos],buf,count))
return -EFAULT;
pos += count;
//filep->f_pos = pos; // Note Deprecated*f_pos = pos; // new for 2.6 Kernel
return count;
}/*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.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.El kernel se encarga automáticamente de cualquier página de intercambio necesaria para asignar el espacio de datos del usuario.Estas funciones están definidas en la librería: #include <linux/uaccess.h>*/
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"
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
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
## basic Makefile## place this in linux-2.6.x/drivers/char/test## bypass the config system for now# CONFIG_SCMD_DRIVER = y# add the driverobj-$(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"
## modify linux-2.6.x/drivers/char/Makefile# add the test sub directory note the trailing /## dummy config statement to be removed later#CONFIG_DRIVER_TEST = y## trigger the inclusion of the test directoryobj-$(CONFIG_DRIVER_TEST) += test/
##
Primera prueba
cd linux-2.6.x/
make > make.out
grep simple_driver make.outCC 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:
config DRIVER_TEST
tristate "Driver Test"help
Some Test Drivers
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:
## Test Driver configuration#config SCMD_DRIVER
tristate "Basic Test Driver"depends on DRIVER_TEST
help
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:
make