SearchUser loginSyndicate |
Qué hacer y qué no hacer en los controladores de dispositivo Linux (es)Submitted by kernel-labs on Mon, 2006-09-25 07:02.
Javier M. García López, nuestro más estrecho colaborador, nos ha cedido esta magnífica traducción del conocido documento de KernelJanitors, HOWTO: Linux Device Driver Dos and Don'ts. Es un documento muy interesante que no podemos dejar de leer.
Una guía para escribir controladores de dispotivos robustos para Linux. Version 1.1 Índice
Licencia , autor y versiónCopyright (c) 2003 de Intel Corporation.Este material puede ser distribuido, con la única restricción de respetar los términos y condiciones de la Open Publication Licence, v1.0 o posterior. La última versión está actualmente disponible en: http://www.opencontent.org/openpub. Autor: Tariq Shureih Versión: 1.1 Fecha de Actualización: Date: 2004/03/18 21:42:47 IntroducciónEste documento no es realmente un HOWTO de controladores de dispositivo – hay libros por ahí sobre cómo escribir controladores de dispositivo.Escribir un controlador de dispositivo del kernel es tan sencillo como escribir tres líneas de código, o tan complejo, que puede requerir entender cómo Linux maneja el hardware en las diferentes arquitecturas que soporta, así como entender conceptos sobre el PC (todas las referencias de este documento son relativas a la arquitectura x86 aunque no especificamente). Lo que este documento aborda, es lo que debes y no debes hacer al escribir controladores de dispositivo para el kernel Linux. Estos consejos estan basados en la lista TODO del proyecto Kernel-Janitor. Incluyendo los conceptos introducidos por las publicaciones de las especificaciones de "Hardened Drivers“ (nota del traductor: "Blindando Controladores“) en "http://hardeneddrivers.sf.net, también presentes en este documento. Para más información sobre el proyecto Kernel-Janitor, en siguiente enlace "http://kernel-janitor.sourceforge.net/". 1.Resumen1.1. ¿Por qué este documento?Quería recopilar la información que aprendí cuando comencé con el desarrollo del kernel, en un único documento y quizás hacer un guía para novatos y/o gente que buscaba esos pequeños trucos que son necesarios para escribir controladores de dispositivo robustos.Este documento es básicamente una sencilla guía para conocer métodos y técnicas al escribir un controlador de dispositovo para Linux y puede servir como documentación adicional a otra disponible. 1.2. ¿Qué es un "Controlador de Dispositivo Blindado?La respuesta a esta pregunta depente de a quién se la pregunte. Para algunos, un controlador de dispositivo blindado es un controlador de dispositivo estable, fiable, "instrumental“ y de alta disponibilidad. En un intento previo de definir un controlador de dispositivo blindado, fue descrito como aquel que contempla tres niveles:
Para evitar confusión con intentos anteriores de especificar lo que es un "Controlador Blindado“ este documento se referirá a un controlador ideal como un "Controlador Robusto". 1.3. "Controladores de Dispositivo Robustos"Un controlador de dispositivo robusto es realmente un ejemplo de código robusto, sin errores y mantenible a nivel del kernel.Al tiempo que Linux evoluciona, las características de lo que es un controlador robusto también varían, pero las siguientes caracterésticas permanecen:
Tal y como se mencionó anteriormente, éstas son caracteristcas generales que deben tenerse en cuenta para unos controladores de dispositivos mas robustos. 2.0. ¿Dónde empezar?Si eres nuevo desarrollando controladores de dispositivo para Linux sería mejor empezar por el libro de O'Reilly "Linux Device Drivers“, porlinux-kernel-source/Documentation/driver-model y por algunos ejemplos básicos del propio kernel (como linux/drivers/char/nvram.c).
También se puede conseguir mucha información en "www.kernelnewbies.org". Una vez que has escrito tu primer driver y has adquirido lo básico (compilar, cargar, descargar, consultar, operaciones sobre ficheros, etc.), puedes echar un vistazo a este documento de nuevo y asi darte cuenta de algunas cosas que puede que hayas dejado pasar, o que te hubieran ayudado con tu driver. Tanto si estas migrando un driver a otra plataforma, o si estas experimentando escribiendo controladores nuevos, este documento te ayudará en puntos que requieren especial atención, o tareas comunes al llevar controladores a diferentes plataformas que necesitarías conocer. Espero que este documento te evite algunos "errores“ comunes y te enseñe el camino correcto que llevarán a tu controlador a los más altos niveles de rendimiento. 3.0. Vale estoy preparado ...Comencemos examinando aspectos sobre la robustez del código de un controlador de dispositivo y las diferentes técnicas para conseguir gran eficiencia y estabilidad.3.1. Manejo eficiente, comunicación y recuperación de erroresSe pueden seguir unas formas simples y probadas de manejar errores y fallos en el código:
3.2. Al día con las interfaces/Apis del kernelEste documento fue escrito mientras se estaba desarrollando la version 2.6 del kernel Linux. Durante el desarrollo de la versión y las diferentes versiones 2.5.x, varios conceptos, interfaces y subsistemas fueron introducidos, modificados o eliminados.Tu nuevo, o modificado controlador necesita estar actualizado con respecto a estos cambios para funcionar mejor y para evitar posibles efectos no contemplados que afecten a la estabilidad, o a la degradación de la calidad. Este documento aborda al máximo, de acuerdo con la habilidad del autor, todas las áreas de importancia y en consonancia al proyecto Janitor del kernel, la lista de desarrollo del kernel y la experiencia personal en lo que respecta a la contribución al kernel y añade experiencias de desarrolladores experimentados. 3.2.1 Cambios en las interfaces de los módulos
3.2.2 Sysfs y el nuevo modelo de controladoresReferencia:linux-kernel-source/Documentation/driver-model/Referencia: linux-kernel-source/Documentation/filesystems/sysfs.txtReferencia: linux-kernel-source/Documentation/kobject.txtSysfs fue diseñado con sencillez e implementación uniforme en el espacio del kernel. No se simplifican las interfaces de usuario a objetos del kernel, sin embargo se trata de mantener la misma estructura aunque dividiendo la información en /proc que se había convertido en algo muy confuso. Primero asegurate de leer "kobject.txt", mencionado mas arriba. Sysfs esta fuertemente unido a kobject. En esta sección trataré de mostrar la forma simple más rápida de usar Sysfs, y me centraré en los detalles que parecen menos documentados o escondidos. Sysfs esta siempre compilado (kernel 2.5 y posteriores) y puede ser accedido con: mount -t sysfs sysfs /sysDonde /sys es el punto de montaje y se crea con: mkdir /sysEstoy escribiendo un nuevo controlador, ¿cómo lo añado a Sysfs? Estoy llevando un controlador a linux 2.5 y sysfs, ¿qué hago? La idea es exponer los atributos de tu dispositivo, o controlador de bus, o posiblemente una clase (una colección de objetos similares o dispositivos lógicamente agrupados bajo el mismo subdirectorio sysfs tal como "input". Un ratón puede ser PS/2 o USB pero sigue siendo un dispsitivo de entrada y pertenece a la clase "input" ) a través de ficheros creados bajo /sys. Estos atributos corresponden a la funcionalidad específica de tu dispositivo y el espacio de usuario puede acceder escribiendo directamente sobre estos ficheros.La tarea más común es probablemente portar controladores existentes a sysfs o escribir un nuevo controlador que aproveche las ventajas de sysfs. Pensando en que estos dispositivo pertenecen a un bus especifico como PCI o USB y que probablemente también pertenecen a una clase de dispositivos comunes en funcionalidad pero no de bus (puedes tener un raton PS/2 y otro USB; ambos son de la clase "input“ pero cada uno se conecta a un bus diferente, el trabajo de convertir los buses, las clases, etcétera, está hecho o casi hecho. La clase de red todavía no esta terminada en el momento de escribir este documento). Cuando escribes un nuevo controlador para un dispositivo PCI y reservas tu estructura pci_dev *pdev, por ejemplo, en tu estructura privada de dispositivos hay un puntero a struct pci_driver *driver el cual tiene empotrado struct device dev dentro. Esto es proporcionado por sysfs para el controlador de dispositivo. Para acceder a la instancia del dispositivo desde el controlador debes acceder a xxx_driver->pdev->dev.
struct xxx_driver {Entonces tu dispositivo es descubierto por el bus (en este caso pci; durante el proceso de registro el subsistema pci automáticamente registra tu controlador de dispositivo en sysfs y crea los ficheros correspondientes en esa estructura bajo /sys/bus/pci/drivers/xxx_driver/, que es en realidad un enlace simbólico a /sys/devices/pci0/device_address).Bajo esos directorios los atributos del controlador son accesibles. Hay ficheros por defecto que pueden corresponder a características no implementadas pero todos los dispositivos tienen nombre, potencia, clase, fabricante, irq, ... y hay caracteristicas específicas del dispositivo que necesitarás implementar. Estas características son accesibles bajo el directorio de la instancia del dispositivo y no bajo el disrectorio del controlador. Ejemplo básico 1: Ejemplo de cómo se puede crear un controlador de una clase ya existente. Es un controlador “vacío”, un driver tipo carácter, que he creado bajo la clase “input” como un dispositivo be bus de sistema. Este controlador tiene la estructura habitual file_operations fops: open, close, seek, etc. Este registra un dispositivo de caracteres en /dev/mydriver con valor major 42 y minor 0. Normalmente se registra con: Para el sysfs ya he declarado: struct device_driver mydriver = {Nota: se ha hecho un gran esfuerzo en la comunidad del kernel para usar el estilo C99 en la declaracion de estructuras. El ejemplo de arriba muestra como puedes hacerlo y como al mismo tiempo también cumples con el estilo de codificacion de linux. También quiero poder “preguntar” al controlador por el flag de debug y poder activarlo haciendo lo siguiente: echo "1" > /sys/class/input/drivers/mydriver/debug // poniendo 1 se activa y 0 se desactivaEl sistema de ficheros sysfs da dos metodos (“show” y “store”) para leer y escribir información de atributos. Implemento dos funciones: static ssize_t show_debug (struct device_driver *d, char * buf)Hay algunos comentarios importantes de linux/Documentation/filesystems/sysfs.txt a continuación:- sysfs reserva memoria de tamaño (PAGE_SIZE) y la pasa al método. - la memoria reservada será siempre de tamaño PAGE_SIZE bytes. En el i386 es 4096. - las funciones show() deben revolver el número de bytes mostrados en este buffer. Este es el valor de snprintf(). - las funciones store() deben revolver el número de bystes usados. Esto se puede hacer usando strlen(). Nota: El sysfs no permite cambiar el nombe del controlador de dispositovo así que no crees funciones para hacerlo :) Ya tengo la estructura (sysfs) device_driver y las funciones. Ahora necesito registrar mis atributos. Sysfs "linux/device.h" nos da macros para simplificar este proceso: Simplemente declaras: DRIVER_ATTR(debug, 0644, show_debug, store_debug);Después tenemos que crear los ficheros asociados con los atributos, para dispositivos llamamos a: driver_create_file(&mydriver, &driver_attr_debug);donde: mydriver es la estructura device_driver.driver_attr es el prefijo de la función.debug es el nombre del atributo que se añade a las declaraciones de las funciones show_ y store_ .Claro, también está: driver_remove_file(&mydriver, &driver_attr_debug);para descargar el controlador. Una pregunta muy normal es "¿dónde pongo las funciones para el resto del código?". La respuesta es, que no tienes que registrar atributos o crear ficheros hasta que el controlador ha terminado la inicialización y está completamente cargado. En la instanciación del controlador de dispositivo habitualmente llamas a estas funciones cuando la instancia se ha registrado sin errores. Lo mismo para borrar ficheros. Ejemplo básico 2: Añadir atributos a un controlador ya existente. En este ejemplo voy a usar el controlador de dispositivo “tulip” actualmente implementado en el kernel. Este ejemplo asume que el desarrollador quiere añadir un nuevo atributo que es especifico del driver para el hardware tulip y no uno común, o por defecto que venga con el pci-sysfs. El nombre del atributo es “ctrl” y en realidad es un atributo “vacío”, que no hace nada. Pero sirve como ejemplo. Los prototipos de las tres nuevas funciones son: static ssize_t show_ctrl (struct device *d, char *ctrl_buf); // método ShowTambién declaro una variable global: static int tulip_ctrl = 0;La implementaciones de las funciones son: static ssize_t show_ctrl (struct device *d, char *ctrl_buf)Hay que añadir los atributos y crear los ficheros para estos en la estructura del controlador de tulip. El controlador de tulip esta en: /sys/buc/pci/drivers/tulip/<device_id>/.Quiero que mi atributo "ctrl" esté en la estrutura arriba mencionada. ¿Recuerdas lo que comenté más arriba sobre la estructura pci_dev que tenía la estructura del dispositivo dentro? Ahora podemos utilizarla en la estrutura de dispositivo de sysfs. Nota: el prototipo de attr_reg, tiene un puntero a la estructura tulip_private que es algo como esto: struct tulip_private {Dentro de la estructura tulip_private, se puede ver el puntero a la estructura pci_dev que es algo como: struct pci_dev {Entonces tenemos acceso a la entrada del dispositivo en sysfs y podemos acceder de la siguiente forma: static void attr_reg(struct tulip_private *device) {Pero todavía no hemos terminado. No hemos declarado las macros para los atributos y tampoco colocamos las llamadas para attr_reg y device_remove_file en el código tulip todavía. Suelo colocar la llamada a la macro DEVICE_ATTR justo después de los prototipos de las funciones. Recuerda que las macros declaran tus estructuras de atributos pero nada se crea hasta que no llamas a device_create_file. También recuerda que tu no debes llamar a attr_reg hasta que la instancia del controlador se haya inicilizado y cargado completamente. Código: (pricipio del fichero después de los includes en la declaracion de prototipos) static ssize_t show_ctrl (struct device *d, char *pm_buf);En la función tulip_init_one he añadido una llamada a attr_reg: [cortado de un patch]Por supuesto no olvides borrar los ficheros cuando se descarge la instancia del controlador: static void __devexit tulip_remove_one (struct pci_dev *pdev)Cuando compiles y carges el controlador podrás encontrar la entrada de "ctrl" en: /sys/bus/pci/driver/tulip/0:11.0/ctrl.Recuerda que el ejemplo implementa un atributo específico del controlador y eso significa que no es un atributo común y que sólo estará disponible (en este ejemplo) si el dispositivo lo proporciona y no lo proporciona el controlador genérico. 3.2.3 Cli() y Sti()Desde linux-2.5.x y también para linux-2.6.x, las siguientes funciones (macros) están obsoletas y por tanto están en desuso:cli(), sti(), save_flags(), save_flags_cli and restore_flags().Esto es especialmente importante para sistemas SMP. Los controladores tendrán que utilizar las funciones primitivas de bloqueo y hacer uso de los semáforos que proporciona el kernel. Esto se detalla en linux/Documentation/cli-sti-removal.txtUna cosa que hay que tener en mente cuando se convierte un controlador existente para que sea apto en sistemas SMP al manejar interrupciones y sincronización de bloqueos: En la mayoría de los casos no es un simple buscar y cambiar de viejas llamadas a las nuevas. Cuando se trata con interrupciones y bloqueos necesitas analizar el código del controlador en conjunto y comprender el objetivo de esas llamadas. Mirando en linux/include/linux/interrupt.h:Las viejas macros para sistemas mono-procesador se cambian por las nuevas hasta que se adapta todo el código. #if !CONFIG_SMPEstas se eliminarán en cuando aparareca la versión oficial de linux-2.6. Tanto si tu controlador es mono-procesador (UP) o multi-procesador (SMP), es más apropiado usar el correspondiente código para proteger (bloquear) las secciones críticas, manejar las interrupciones dentro de tu controlador, y usar la apropiada sincronización. Los spinlocks aportan mejoras de rendimiento siendo así más rápidos de obtener. Los actuales funciones (macros) para los spinlock están definidos en: linux/include/linux/spinlock.h
Algunos apuntes sobre la nueva API:
3.3. Gestionando recursosEn esta sección miraremos las diferentes formas de reserva de recursos, inicialización de variables, llamadas a funciones de balanceo y las funciones adecuadas de cadenas para controladores.3.3.1. Funciones de cadenas
3.3.2. Declaración e inicialización de variables
3.3.3. Funciones de balanceo/repartoEs importante asegurarse que de todas las llamadas a funciones para reserva o liberación de recursos están balanceadas y coinciden unas con otras sobre todo durante caidas. Las llamadas habituales son kmalloc junto con kfree y de igual modo kfree_skbs al librerar skbs.Áreas comunes que necesitan atención son:
3.4. Operaciones de E/S3.4.1. Acceso al espacio de E/SEstas funciones son:Lecturas/Escrituras I/O de tipo byte: unsigned inb(unsigned port); //lecturaunsigned inw(unsigned port); //lecturaLecturas/Escrituras I/O de tipo long: unsigned inl(unsigned port); //lecturaLas correspondientes versiones para cadenas son: void insb(unsigned port,void *addr,unsigned long count); //lecturaEjemplo: __asm__( "mov $0, %al\n\t" "outb %al, $0x70");En su lugar debe ser: outb(0x00, 0x70);3.4.2. Acceso a la memoria mapeada de E/SLas funciones para esto son:unsinged readb(address);Ejemplo: *(u8 *) (IOBASE+IER) = 0x80;En su lugar debe ser: writeb(0x80, IOBASE+IER);Nota: debes utilizar la opción de compilación -O2 para que estas funciones (macros en realidad) se expandan.
3.5. Lo que obviamente no debes
printer-friendly version | login to post comments
|