Post on 24-Oct-2015
1
Raouf Senhadji Navarroraouf@us.es
Curso de verano “Software libre a fondo”
Creación de drivers para dispositivos PCI en Linux
Ignacio García Vargasiggv@us.es
2
Contenido
● Características generales del bus PCI
● Jerarquía del bus PCI
● Espacio de direcciones PCI
Espacios de memoria y E/S
Espacio de configuración
● Regitrar un driver PCI
● Pequeños ejemplos
● Driver ff: Un ejemplo de driver PCI
● Bibliografía
3
Características generales del bus PCI (I)
• Desarrollado por Intel y convertido a estándar abierto
• Versiones:– Datos y direcciones de 32 o 64 bits– Frecuencias disponibles: 33, 66 o 133 (PCI-X) Mhz
• Características de velocidad superiores a buses anteriores:– ISA: 16 MB/s– EISA: 33 MB/s– PCI 32bits, 66Mhz: 264MB/s
• Establece mecanismos de configuración automática para los dispositivos
4
Características generales de bus PCI (II)• Admite espacios de direcciones de memoria y de E/S
• Capacidad de bus mastering por parte de cualquier dispositivo que lo soporte
• Puente PCI:– Conecta el procesador/cache/memoria al bus PCI
– Permite el acceso directo de la CPU a las direcciones de memoria o E/S de los dispositivos
– Permite el acceso directo de los dispositivos maestros a la memoria principal
• Interrupciones:– Dispone de 4 líneas de interrupción (de INTA a INTD)
– Todos los dispositivos con interrupciones deben soportar interrupciones compartidas
• Hay otros pines que indican al puente si existe algún dispositivo conectado a una ranura, el tipo de potencia consumida, etc.
5
Jerarquía del bus PCI
Hasta 256 buses
Hasta 8 funciones por dispositivo
● Localización de perifericos en el bus PCI: Hardware: dirección de 16 bits
Kernel de Linux:
Soporta dominios PCI (cada dominio contiene hasta 256 buses)
Se accede al dispositivo a través de variables struct pci_dev
Dispositivo 2
Dispositivo 0
Dispositivo 1
NOTA: Utilizaremos el término dispositivo para referirnos a las funciones
3 bits5 bits8 bits
Nº FUNCION
Nº DISPOSITIVONº BUS
PuentePCI
Hasta 32 dispositivos por bus
Por ejemplo, una tarjeta puede ser una capturadora de vídeo y una tarjeta gráfica VGA
Puente PCI-PCI
6
Espacios de direcciones PCIEspacios de memoria y E/S (I)
• Existen 3 espacios de direcciones: Memoria, entrada/salida y configuración
• Espacios de direcciones de memoria y E/S– Existe un espacio de memoria y un espacio de E/S, ambos son
compartidos por todos los dispositivos del bus y por la CPU– Cada dispositivo utiliza un rango diferente de direcciones de memoria o
de E/S (dos dispositivos no pueden usar la misma dirección de memoria o de E/S)
– Se accede a los dispositivos usando las direcciones pertenecientes a su rango
– El driver puede acceder al espacio de E/S con las funciones inb, inw,inl, outb, outw, outl, etc. (definidas en <asm/io.h>)
– El driver puede acceder al espacio de memoria con las siguientes funciones, definidas en <asm/io.h>:
unsigned int ioread8(void *direccion);unsigned int ioread16(void *direccion);unsigned int ioread32(void *direccion);void iowrite8(u8 valor, void *direccion);void iowrite16(u16 valor, void *direccion);void iowrite32(u32 valor, void *direccion);
7
Espacios de direcciones PCIEspacios de memoria y E/S (II)
• El bus PCI almacena la información utilizando el formato little-endian, por lo que en aquellas arquitectura que usen otro formato es necesario cambiarlo. Pueden usarse las siguientes funciones:#include <asm/byteorder.h>u32 cpu_to_le32 (u32 data) u16 cpu_to_le16 (u16 data)u32 le32_to_cpu (u32 data) u16 le16_to_cpu (u16 data)Convierten un valor del formato usado por la CPU a little-endian y viceversa.
• Acceso a direcciones de E/S mapeada en memoria:– Hay que asegurar que se accede siempre a la dirección mapeada en
memoria y no a un valor almacenado en un registro interno. Se usan variables volatile (las funciones definidas lo garantizan)
– Es necesario asegurar el orden en que se realizan los accesos evitando optimizaciones. Para ello se utilizan los memory barriers:#include <asm/system.h>void rmb(void) Garantiza que tras la función se han completado todas
las lecturas pendientesvoid wmb(void) Garantiza que tras la función se han completado todas
las escrituras pendientesvoid mb(void) Garantiza que se han completado todas las lecturas y
escrituras pendientes
8
Espacios de direcciones PCI:Espacios de memoria y E/S – Ejemplo
• Supongamos que un dispositivo PCI tiene dos registros de 16 bits en las direcciones de E/S (o puertos de E/S) DIR1 y DIR2:
u16 valor;valor = le32_to_cpu(inw( DIR1 )); // Leemos un valor del puerto DIR1outw( cpu_to_le32(valor), DIR2 ); // Escribimos en el puerto DIR2
• Supongamos que un dispositivo PCI utiliza el rango de direcciones de memoria virtual [base : base+3] para configurar transferencias de datos:
iowrite8(operacion,pbase); // Preparamos una transferencia ...
iowrite8(direccion,pbase+1);
iowrite8(tamanyo,pbase+2); // *(base+2)=tamanyo;
wmb(); // Garantiza que se han completado las escrituras anteriores
iowrite8(comando,pbase+3); // Comenzamos la transfencia
Esta operación es válida en x86 pero no es portable a otras arquitecturas
9
• Espacio de configuración:– Contiene los registros de configuración del dispositivo– Cada dispositivo tiene su propio espacio de configuración– Para acceder al espacio de configuración de un dispositivo, es necesario
especificar el dispositivo (nº bus, nº dispositivo, nº función) y la dirección del registro de configuración
• Durante el arranque, la BIOS o el kernel de Linux configura los dispositivos PCI accediendo a sus espacios de configuración:– Los rangos de direcciones de memoria y de E/S de cada dispositivo se
mapean en los espacios de direcciones de memoria y E/S de la CPU– Se asigna un número de interrupción a cada dispositivo– El kernel crea una “base de datos” con los dispositivos encontrados.
Cada dispositivo se modela con un struct pci_dev• Cuando el driver accede al dispositivo, éste ya ha sido
debidamente configurado: tan sólo tiene que consultar dicha configuración
Espacios de direcciones PCI:Espacio de Configuración
10
Espacio de configuración
• El espacio de configuración (256 bytes) se dividide en dos regiones:– Cabecera de 64 bytes
• Los primeros 16 bytes son iguales para todos los tipos de dispositivos• El resto depende del campo Header Type (en la figura se muestra el
tipo 00h)
– 192 bytes dependientes del dispositivo
Registros obligatorios
11
Espacio de configuraciónAcceso desde el driver
• Funciones para leer del espacio de configuración de un dispositivo:
• Funciones para escribir en el espacio de configuración de un dispositivo:
• Parámetros:– dev: dispositivo a cuyo espacio de configuración se va acceder– where: dirección de configuración a la que se quiere acceder (véase la
figura de la transparencia anterior) – val: dirección donde se guardará el valor leído o el valor que se quiere
escribir• Valor devuelto:
– Devuelven 0 si la operación se ha realizado correctamente; sino, devuelven un código de error
• Fichero de cabecera: <linux/pci.h> Este fichero contiene todas las funciones relacionadas con el PCI que veremos en las transparencias
int pci_read_config_byte(struct pci_dev *dev, int where, u8 *val);int pci_read_config_word(struct pci_dev *dev, int where, u16 *val);int pci_read_config_dword(struct pci_dev *dev, int where, u32 *val);
int pci_write_config_byte(struct pci_dev *dev, int where, u8 val);int pci_write_config_word(struct pci_dev *dev, int where, u16 val);int pci_write_config_dword(struct pci_dev *dev, int where, u32 val);
12
Espacio de configuraciónAcceso desde el driver - Ejemplos
• Supongamos que la variable struct pci_dev *dispositivo contiene información de un dispositivo PCI concreto (el kernel proporciona dicha información al driver a través de la función probe, que se estudiará más adelante).
• Para leer el registro Cache Line (dirección 0xc) debemos escribir:if (pci_read_config_byte( dispositivo, 0xc, &cache_line )) {
/* Error en el acceso al espacio de configuracion */}
• Para leer el registro Command (dirección 0x4) debemos escribir:if (pci_write_config_word( dispositivo, 0x4, command )) {
/* Error en el acceso al espacio de configuracion */}
• Para escribir el valor 3 en el registro IRQ Line (dirección 0x3c):if (pci_read_config_byte( dispositivo, 0x3c, &irq )) {
/* Error en el acceso al espacio de configuracion */}
• El fichero <linux/pci.h> define macros con las direcciones de cada uno de los registros del espacio de configuración: PCI_CACHE_LINE_SIZE, PCI_COMMAND, PCI_INTERRUPT_LINE, ...
13
Espacio de configuración:Identificación del tipo de dispositivo (I)
• Vendor ID: Identificador del fabricante– Los valores son establecidos por el PCI SIG (Special Interest Group). Por
ejemplo, el valor 0x8086 corresponde a Intel– 0xFFFF indica que no hay dispositivo conectado
• Device ID: Identificador del dispositivo– Su valor lo establece el fabricante (no necesita ser registrado por el PCI SIG)– El par (VendorID, DeviceID) identifica al dispositivo hardware
• Class Code: Indica la clase a la que pertenece el dispositivo según su función– Sus valores son definidos por el PCI SIG. Se divide en tres campos de un byte:
• Clase base (byte alto): Indica la clase genérica a la que pertenece el dispositivo. Por ejemplo: sin clase (00h), almacenamiento (01h), redes (02h), controladores de display (03h)
• Subclase (byte medio): Indica la función específica del dispositivo. Por ejemplo: controlador SCSI (0100h), controlador IDE (0101h), adaptador token ring (0201h), adaptador VGA (0300h)
• Interface de programación (byte bajo): Define la interface de programación a nivel de registro. Por ejemplo: puerto paralelo (070000h), puerto paralelo bidireccional (070001h), puerto paralelo ECP 1.X (010002h), etc.
14
Espacio de configuraciónIdentificación del tipo de dispositivo (II)
• Revision ID: Puede ser considerado como una extensión del Device ID• Subsystem Vendor ID (Subvendor ID): Identificador del fabricante
de la tarjeta donde reside el dispositivo PCI (son asignados por el PCI-SIG)
• Subsystem Device ID (Subdevice ID): Identificador de la tarjeta donde reside el dispositivo PCI (su valor lo establece el fabricante)
• Macros definidas para el acceso a los registros anteriores: – PCI_VENDOR_ID– PCI_DEVICE_ID– PCI_CLASS_PROG (interfaz de programación)– PCI_CLASS_DEVICE (incluye la Clase base y la Subclase)– PCI_REVISION_ID– PCI_SUBSYSTEM_VENDOR_ID– PCI_SUBSYSTEM_ID
15
Espacio de configuraciónRegistros de dirección base (I)
• Un dispositivo PCI puede tener hasta 6 regiones de direcciones de memoria o de Entrada/Salida
• La dirección base de cada región está definida por un registro BAR (Base Address Register)
– En el caso de que la dirección de memoria sea de 64 bits, se usan dos registros BAR consecutivos para especificar el rango
– Si la memoria admite prebúsqueda los datos pueden guardarse en caché y el acceso puede realizarse como si fuera memoria RAM. En caso contrario, se comporta como E/S mapeada en memoria
– El tamaño de cada región también puede consultarse con los registros BAR
0X XXDirección Base
02 1331/63 4
Tipo: 00 : Dirección de 32 bits 01 : Reservado 10 : Dirección de 64 bits 11 : ReservadoPrefetchable (admite prebúsqueda): 0 : nonprefetchable 1 : prefetchable
Memoria
10Dirección Base
0131 2E/S
16
Espacio de configuraciónRegistros de dirección base (II)
• Toda la información relativa a las diferentes regiones de direcciones de memoria o E/S se obtiene con las siguientes funciones:unsigned long pci_resource_start(struct pci_dev *dev, int bar);
Devuelve la primera dirección de la región indicada por el parámetro barunsigned long pci_resource_end(struct pci_dev *dev, int bar);
Devuelve la última dirección de la región indicada por el parámetro barunsigned long pci_resource_len(struct pci_dev *dev, int bar);
Devuelve el tamaño de la región indicada por el parámetro barunsigned long pci_resource_flags(struct pci_dev *dev, int bar);
Devuelve los flags asociados a la región indicada por el parámetro bar:IORESOURCE_IO : Es una región de direcciones de E/SIORESOURCE_MEM: Es una región de direcciones de memoria
IORESOURCE_PREFETCH: La región de memoria admite prebúsqueda
• Parámetros:– dev: dispositivo a cuyo espacio de configuración se quiere acceder.– bar: indica el número de registro BAR que define la región. Puede ser un
valor del 0 al 5.• Fichero de cabecera para los flags: <linux/ioports.h>
17
Espacio de configuraciónRegistros de dirección base (III) – Ejemplo
Supongamos que tenemos la variable disp debidamente inicializada para identificar a un dispositivo PCI del bus:
struct pci_dev *disp;...inicio = pci_resource_start( disp, 0 );tam = pci_resource_len( disp, 0 );if (pci_resource_flags( disp, 0) & IORESOURCE_MEM) { /* Memoria */
if (!request_mem_region( inicio, tam, “midispositivo”)) {/* Error */
}if ((base = ioremap( inicio, tam )) == NULL) {
/* Error */}iowrite8( 0x3f, base ); ...
}else { /* E/S */if (request_region(inicio, tam, “midispositivo”) != NULL) {
outb(0x3f,inicio);...
18
Espacio de configuraciónReserva de rangos de direcciones
Las siguientes funciones permiten la reserva y uso de los recursos de memoria y E/S del dispositivo PCI:
● struct resource *request_region(unsigned long start, unsigned long n, char *name);
Solicita al kernel el uso de n puertos de E/S a partir de la dirección start para el dispositivo name. Devuelve NULL en caso de que los puertos solicitados no estén disponibles. Está en <linux/ioport.h>
• struct resource *request_mem_region(unsigned long start, unsigned long len, char *name);
Solicita al kernel la asignación de len bytes de memoria a partir de la dirección start para el dispositivo name. Devuelve NULL en caso de error. Está en <linux/ioport.h>
● void *ioremap(unsigned long phys_addr, unsigned long size);
Asigna un rango de direcciones de memoria virtual al rango de direcciones físicas [phys_addr, phys_addr+size]. Devuelve la dirección base virtual. Está en<asm/io>
/proc/ioports
/proc/iomem
19
Espacio de configuraciónReserva de rango de direcciones - Ejemplo
Supongamos que tenemos la variable disp debidamente inicializada para identificar a un dispositivo PCI del bus:
struct pci_dev *disp;...inicio = pci_resource_start( disp, 0 );tam = pci_resource_len( disp, 0 );if (pci_resource_flags( disp, 0 ) & IORESOURCE_MEM) {
if (!request_mem_region( inicio, tam, “midispositivo”)) {/* Error */
}if ((base = ioremap( inicio, tam )) == NULL) {
/* Error */}iowrite8( 0x3f, base );
...}else {
if (request_region(inicio, tam, “midispositivo”) != NULL) {outb(0x3f,inicio);...
20
Espacio de configuraciónLiberación de rangos de direcciones
• Las siguientes funciones permiten liberar los recursos de memoria y E/S del dispositivo PCI:– void release_region(unsigned long start, unsigned long n);
Libera la región de E/S especificada
– void release_mem_region(unsigned long start, unsigned long len);
Libera la región de memoria indicada.
– void iounmap(void * virtual_addr);
Libera el rango de direcciones de memoria virtuales asignadas con ioremap
• Los puertos de entrada/salida reservados pueden consultarse en:/proc/ioports
• Los rangos de memoria reservados pueden consultarse en:/proc/iomem
21
Espacio de configuraciónInterrupciones PCI
• Registros:– IRQ Line: Indica el número de interrupción asociada al dispositivo
(este valor puede ser diferente al número de interrupción utilizado por el kernel)
– IRQ Pin: Indica que línea de interrupción (INTA, INTB, INTC, INTD) está conectada al dispositivo (el valor 0 indica que el dispositivo no usa interrupciones)
• Driver:– Puede acceder a los registros utilizando las funciones vistas
anteriormente.• PCI_INTERRUPT_LINE: dirección del registro IRQ Line• PCI_INTERRUPT_PIN: dirección del registro IRQ Pin
Ejemplo:if (pci_read_config_byte(dev, PCI_INTERRUPT_PIN, &irq_pin)) {/* Error */ }
– El número de interrupción del dispositivo se encuentra en el campo irq de la estructura pci_dev (no se debe utilizar el valor del registro IRQ Line)Ejemplo: mi_irq = dev->irq;
22
Registrar un driver PCILa estructura pci_device_id (I)
• En el proceso de registro, el driver PCI debe indicar al kernel qué tipo de dispositivos maneja
• Para especificar el tipo de dispositivo PCI se utiliza la estructura struct pci_device_id, con los siguientes campos:__u32 vendor;__u32 device;
Indican el Vendor ID y el Device ID del tipo de dispositivo. El valor PCI_ANY_ID indica cualquier vendor y device ID__u32 class;__u32 class_mask;
Se utilizan para indicar la clase de dispositivo PCI. El campo class_mask se utiliza para seleccionar parte de los bytes en los que se divide el campo Class Code__u32 subvendor;__u32 subdevice;
Indican el Subsystem Vendor ID y Subsystem Device ID del dispositivo. El valor PCI_ANY_ID indica cualquier subvendor y subdevice IDkernel_ulong_t driver_data;
Este campo es para el uso interno del driver. El driver puede utilizarlo para guardar información asociada al tipo de dispositivo especificado en la estructura
23
Registrar un driver PCILa estructura pci_device_id (II)
• El driver debe definir un array con todos los tipos de dispositivos PCI que maneja (el final del array se marca con un elemento relleno con ceros).
• Las siguientes macros se utilizan para inicializar el array de tipos dispositivos PCI:– PCI_DEVICE( vendor, device )
Permite inicializar un struct pci_device_id especificando el vendor y device ID. Los campos subvendor y subdevice se inicializan a PCI_ANY_ID.
– PCI_DEVICE_CLASS( clase, mascara )Inicializa un struct pci_device_id especificando la clase. El resto de campos se inicializan con el valor PCI_ANY_ID.
• Ejemplo:struct pci_device_id tb_ejemplo[] = {
{PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82810_IG1)},{PCI_DEVICE_CLASS( 0x020100, ~0 ), .driver_data = indice_datos },
{ } /* Fin del array */ };
• La tabla de tipos de dispositivos debe ser exportada al espacio del usuario para permitir la carga de módulos y el hotplug:
– Ejemplo: MODULE_DEVICE_TABLE( pci, tb_ejemplo );
24
Registrar un driver PCILa estructura pci_driver (I)
• La estructura struct pci_driver contiene toda la información que el kernel necesita para registrar el driver. Consta de los siguientes campos (entre otros):– const char *name;
Nombre del driver (aparece en /sys/bus/pci/drivers/)– const struct pci_device_id *id_table;
Puntero a un vector de estructuras pci_device_id que indica los tipos de dispositivos que maneja el driver. El último elemento del vector debe tener todos los campos a cero
– int (*probe) (struct pci_dev *dev, const struct pci_device_id *id);
Puntero a la función probe del driver. El kernel llama a esta función cuando encuentra un dispositivo en el bus que debe ser controlado por el driver. El kernel le pasa a la función el dispositivo encontrado y su tipo en los parámetros dev e id, respectivamente. El valor devuelto es:
0 si se ha inicializado el dispositivo correctamente -ENODEV si el dispositivo no es manejado por el driver un código de error en cualquier otro caso
25
Registrar un driver PCILa estructura pci_driver (II)
• Estructura struct pci_driver (continuación):– void (*remove) (struct pci_dev *dev);
Puntero a la función remove del driver. El kernel llama a esta función cuando el dispositivo es eliminado del sistema
– int (*suspend) (struct pci_dev *dev, u32 state);– int (*resume) (struct pci_dev *dev);
Las funciones anteriores se encargan de suspender y activar el dispositivo. Ambas funciones son opcionales
• El driver PCI debe registrarse durante la inicialización del módulo con la función:– int pci_register_driver ( struct pci_driver *drv );
Entre otras acciones, llama a la función probe del driver para cada dispositivo manejado por el driver. Devuelve un código de error, o cero si el registro se ha realizado sin problemas
• El driver PCI debe eliminarse durante la descarga del módulo con la función:– void pci_unregister_driver ( struct pci_driver *drv );
Entre otras acciones, llama a la función remove para cada dispositivo manejado por el driver
26
Registrar un driver PCILa función probe: inicialización del dispositivo
• El driver PCI debe realizar los siguientes pasos generales durante la inicialización del dispositivo:
– Habilitar el dispositivo:int pci_enable_device( struct pci_dev *dev );
Despierta al dispositivo si está en estado suspendido. Asigna la IRQ y establece las regiones de E/S y memoria en caso de que no lo haya hecho la BIOS. Devuelve cero o un código de error
– Solicitar recursos de E/S y/o memoria: request_region, request_mem_region, ioremap
– Acceder si es necesario al espacio de configuración del dispositivo
– Registrar el manejador de interrupciones en caso de que sea necesario
27
Registrar un driver PCILa función remove: cierre del dispositivo
• El driver PCI debe realizar los siguientes pasos generales cuando deje de manejar el dispositivo
– Deshabilitar las interrupciones
– Liberar el manejador de interrupciones
– Liberar los recursos de E/S y/o memoria:release_region, release_mem_region, iounmap
– Deshabilitar el dispositivo:void pci_disable_device( struct pci_dev *dev );Deshabilita el dispositivo e informa al sistema de que el dispositivo no está en uso
28
Registrar un driver PCIUna visión general
• Durante el arranque del sistema, el kernel configura todos los dispositivos PCI y almacena información de cada uno de ellos en estructuras pci_dev
• Un módulo que implemente un driver para un dispositivo PCI debe registrar el driver PCI en la función de inicialización del módulo. El módulo le indica al kernel qué tipo de dispositivos PCI maneja (p.e. mediante el VendorID, DeviceID)
• El kernel busca dispositivos libres que puedan ser manejados por el módulo y llama a la función probe del módulo tantas veces como dispositivos encuentre (una vez por cada dispositivo). En cada llamada le pasa a la función probe un valor de tipo struct pci_dev* para que la función pueda acceder al espacio de configuración del dispositivo
• La función probe debe habilitar el dispositivo y leer su configuración: número de interrupción, rango de direcciones de memoria o de E/S que utiliza (registros BAR), etc.
• Después de ejecutar la función probe, el módulo ya puede manejar los dispositivos PCI accediendo a sus direcciones de E/S (inb, outb, etc.) o a sus direcciones de memoria (ioread8, iowrite8, etc.)
29
Registrar un driver PCIUna visión general – Ejemplo (I)
#include <linux/pci.h>
static const struct pci_device_id ej_ids [] = {{PCI_DEVICE( PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82810_IG1 ),
.driver_data = 2 },{PCI_DEVICE( PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82810_IG3 ),
.driver_data = 1 },...{ /*fin*/ }
};
static int ej_probe( struct pci_dev *dev, const struct pci_device_id *id );static void ej_remove( struct pci_dev *dev );
static struct pci_driver ej_pci_driver = {.name = “midriver",.id_table = ej_ids,.probe = ej_probe,.remove = ej_remove,
};
Continúa…
Definimos los tipos de dispositivos que maneja el driver. El campo driver_datapodría contener un indice a una estructura de datos interna
30
Registrar un driver PCIUna visión general – Ejemplo (II)
static int __init ej_inicio_modulo(void) { ...
return pci_register_driver( &ej_pci_driver );}
...
static void __exit ej_fin_modulo(void){ ...
pci_unregister_driver( &ej_pci_driver );}
Continúa ...
Cuando se carga el módulo (por ejemplo, con insmod) registramos el driver en el kernel
Cuando se descarga el módulo (por ejemplo, con rmmod) deshacemos el registro del driver
31
Registrar un driver PCIUna visión general – Ejemplo (III)
static int ej_probe( struct pci_dev *dev, const struct pci_device_id *id){ ...
if (pci_enable_device( dev )) { /* error */ }...dir_inicio = pci_resource_start( dev, 0 );len = pci_resource_len( dev, 0 );request_region( dir_inicio, len, “midriver” ); ...... mis_datos[id->driver_data] ......request_irq( dev->irq, ... );...numero_mayor = register_chrdev_region(0,2,dispositivo);...
} Continúa ...
Si hay dispositivos del tipo que maneja el driver, el kernel llama a la función probe para cada uno de ellos
El kernel le pasa el dispositivoy el elemento del campo id_tables
32
Registrar un driver PCIUna visión general – Ejemplo (IV)
static void ej_remove( struct pci_dev *dev ){ ...
uregister_chrdev_region(0,2);... free_irq( dev->irq, ... );...release_region( dir_inicio, len, “midriver” );...pci_disable_device( dev );
}
...
module_init( ej_inicio_modulo );module_exit( ej_fin_modulo );
El kernel llama a la función remove para cada dispositivo manejado por el driver
El kernel le pasa el dispositivo
Si un dispositivo se elimina del sistema, el kernel llama a la función remove correspondiente
33
Registrar un driver PCOtras funciones
• El driver puede almacenar información privada en una estructura pci_dev con las siguientes funciones:
void *pci_get_drvdata( struct pci_dev *dev ) Devuelve el contenido del campo driver_data asociado a dev
void pci_set_drvdata( struct pci_dev *dev, void *data ) Asigna data al campo driver_data asociado a dev
• Ejemplo:
int probe( struct pci_dev *dev, struct pci_device_id *id ) {struct mi_disp *d = kmalloc( sizeof( struct mi_disp ), GFP_KERNEL );… /* Inicializamos la estructura struct mi_disp apuntada por d */ …pci_set_drvdata( dev, d );…
}void remove( struct pci_dev *dev ) {
struct mi_disp *d = pci_get_drvdata( dev );kfree( d );…
}
El driver puede almacenar toda la información que necesite para manejar al dispositivo PCI dev: semáforos, colas de espera, variables, etc.
Esto es especialmente útil cuando el driver maneja múltiples dispositivos PCI
34
Pequeños ejemplos¿Qué dispositivos PCI tenemos?
• lspci00:1d.0 USB Controller: Intel Corporation 82801G (ICH7 Family) USB UHCI #1 (rev 01)
• scanpci -vpci bus 0x0000 cardnum 0x1d function 0x00: vendor 0x8086 device 0x27c8Intel Corporation 82801G (ICH7 Family) USB UHCI #1CardVendor 0x103c card 0x30aa (Hewlett-Packard Company, Card unknown)STATUS 0x0280 COMMAND 0x0005CLASS 0x0c 0x03 0x00 REVISION 0x01BIST 0x00 HEADER 0x80 LATENCY 0x00 CACHE 0x00BASE4 0x00006021 addr 0x00006020 I/OMAX_LAT 0x00 MIN_GNT 0x00 INT_PIN 0x01 INT_LINE 0x0a
• cat /sys/bus/pci/devices/0000:00:1d.0/irq18
• cat /proc/interrupts18: 439356 0 IO-APIC-fasteoi ehci_hcd:usb1, uhci_hcd:usb2
• cat /proc/ioports6020-603f : 0000:00:1d.0 6020-603f : uhci_hcd
35
Pequeños ejemplosBuscar dispositivos sin asignar – Conocimientos previos
Makefile
MODULO = listado_pci
ifneq ($(KERNELRELEASE),)
objm := $(MODULO).o
else
KERNELDIR ?= /lib/modules/$(shell uname r)/build
PWD := $(shell pwd)
default:
$(MAKE) C $(KERNELDIR) M=$(PWD) modules
endif
Compilación: make
Instalación del módulo: insmod listado_pci.ko
Desintalación del módulo: rmmod listado_pci
Visualización de mensajes del kernel: dmesg
36
Pequeños ejemplosBuscar dispositivos sin asignar – Problema
#include <linux/init.h>#include <linux/module.h> Macros: PCI_DEVICE y PCI_ANY_ID#include <linux/pci.h> Campos de pci_dev: bus y devfn
static struct pci_device_id ids[] = {...}
static int listado_probe( struct pci_dev *dev,
const struct pci_device_id *id );
static void listado_remove( struct pci_dev *dev );
static struct pci_driver listado_drv = { .name = ... .id_table = ... .probe = ... .remove = ... };
static int __init inicio_modulo (void) { return pci_register_driver( &listado_drv );}
static void __exit fin_modulo (void) { pci_unregister_driver( &listado_drv );}
module_init(inicio_modulo);module_exit(fin_modulo);
37
Pequeños ejemplosBuscar dispositivos sin asignar - Solución
#include <linux/init.h>#include <linux/module.h>#include <linux/pci.h>
static struct pci_device_id ids[]={
{ PCI_DEVICE( PCI_ANY_ID, PCI_ANY_ID ) }, {} };static int listado_probe( struct pci_dev *dev, const struct pci_device_id *id );static void listado_remove( struct pci_dev *dev );
static struct pci_driver listado_drv = { .name = "listado_pci", .id_table = ids, .probe = listado_probe, .remove = listado_remove};static int listado_probe( struct pci_dev *dev,
const struct pci_device_id *id ) { printk( "Encontrado dispositivo sin modulo asignado en:
%02x:%02x.%x\n",dev>bus>number, dev>devfn>>3,
dev>devfn & 0x07);
return 0;}static void listado_remove( struct pci_dev *dev ) {}static int __init inicio_modulo (void) {return pci_register_driver( &listado_drv );}static void __exit fin_modulo (void) { pci_unregister_driver( &listado_drv );}module_init(inicio_modulo);
module_exit(fin_modulo);
38
Driver ff: Un ejemplo de driver PCI• En una tarjeta de prototipo PCI basada en FPGA se ha
diseñado un dispositivo sencillo (una simple FIFO, ff) para fines docentes
• El driver para el dispositivo ff tiene las siguientes características:
– Permite escribir y leer datos de la FIFO (/dev/ff)
• Implementa las funciones open, close, read, write y ioctl (reset, fija umbrales para las interrupciones, lee el número de datos en la FIFO, etc.)
• Las transferencias pueden realizarse tanto por DMA como por operaciones push/pop de la CPU (parámetro del módulo)
• Maneja las siguientes fuentes de interrupción:
– FIFO llenándose ha superado un determinado umbral
– FIFO vaciándose ha superado un derminado umbral
– Fin de DMA– Permite leer el contenido de la FIFO sin modificarla (/dev/ffv)
• Implementa los métodos open, close, read y ioctl (puede verse la FIFO completa o sólo los elementos almacenados)
39
Driver ff: Un ejemplo de driver PCIRegistrar y liberar – La funciones probe y remove
static int probe(struct pci_dev *dev, const struct pci_device_id *id)
{
int res;
if ((res = pci_enable_device(dev)))
return res;
if ((res = ff_init( &ffdev, dev ))) {
pci_disable_device(dev);
return res; }
return 0;
}
static void remove(struct pci_dev *dev) {
ff_cleanup( &ffdev );
pci_disable_device(dev);
}
40
Driver ff: Un ejemplo de driver PCIRegistrar e inicializar (I)
static int ff_init( struct ff_dev *dev, struct pci_dev* pcidev )
{ ...
// Leemos configuracion pci y reservamos espacios de direcciones
if ((res = ff_init_pciparams( dev, pcidev )))
goto error_pciparams;
// Inicializamos el hardware
// Reset y deshabilitacion de las interrupciones
OUTL_PCI( FF_HW_RESET, FF_HW_CONTROL_P(dev) ); ...
// Inicializacion de los campos de la estructura dev
dev>fifo_empty = 1; ...
// Reservamos el MAJOR number
if (ff_major) { devn = MKDEV(ff_major, ff_minor);
res = register_chrdev_region(devn, FF_NR_DEVS, FF_NAME);
} else { res = alloc_chrdev_region(&devn, ff_minor, FF_NR_DEVS,FF_NAME ); ff_major = MAJOR(devn); }
Continúa ...
41
Driver ff: Un ejemplo de driver PCIRegistrar e inicializar (II)
if (res < 0) {
printk(KERN_WARNING "ff: no se puede obtener el numero mayor %d\n", ff_major);
goto error_pciparams;
}
if ((res = ff_setup_cdev( dev )))
goto error_chrdev_region;
return 0;
error_chrdev_region:
devn = MKDEV( ff_major, ff_minor );
unregister_chrdev_region( devn, FF_NR_DEVS );
error_pciparams:
ff_cleanup_pciparams( dev );
return res;
}
42
Driver ff: Un ejemplo de driver PCILiberar
static void ff_cleanup ( struct ff_dev *dev )
{
ff_del_cdev( dev );
unregister_chrdev_region( MKDEV(ff_major,ff_minor), FF_NR_DEVS );
kfree( dev>rd_buffer );
kfree( dev>wr_buffer );
ff_cleanup_pciparams( dev );
}
43
Driver ff: Un ejemplo de driver PCIRegistrar los dispositivos de caracteres
static int ff_setup_cdev ( struct ff_dev *dev )
{...
// Registramos el dispositivo de caracteres ff
if (ff_dma)
cdev_init( &dev>cdev, &ff_fops );
else
cdev_init( &dev>cdev, &ff_p_fops );
err = cdev_add( &dev>cdev, devno, 1 );
if (err) return err;
// Registramos el dispositivo de caracteres ffv
devno = MKDEV( ff_major, ff_minor + 1 );
cdev_init(&dev>v_cdev, &ff_v_fops);
err = cdev_add( &dev>v_cdev, devno, 1 );
if (err) { // Marcamos v_cdev para indicar que no se ha creado
dev>v_cdev.ops = NULL; }
return 0;}
44
Driver ff: Un ejemplo de driver PCIEliminar registro de los dispositivos de caracteres
static void ff_del_cdev ( struct ff_dev *dev )
{
// Eliminamos los dispositivos de caracteres
cdev_del( &dev>cdev );
if (dev>v_cdev.ops != NULL)
cdev_del( &dev>v_cdev );
}
45
Driver ff: Un ejemplo de driver PCIInicialización y reserva de recursos PCI (I)
static int ff_init_pciparams ( struct ff_dev *dev,
struct pci_dev *pcidev )
{
int res = 0;
dev>pci_dev = pcidev;
dev>base_con = pci_resource_start( pcidev, FF_BARCON );
dev>tam_con = pci_resource_len( pcidev, FF_BARCON );
if (!request_region( dev>base_con, dev>tam_con, FF_NAME )) {
dev>tam_con = 0; dev>base_con = 0; return EBUSY;}
dev>base_vis = pci_resource_start( pcidev, FF_BARVIS );
dev>tam_vis = pci_resource_len( pcidev, FF_BARVIS );
if (!request_mem_region( dev>base_vis, dev>tam_vis, FF_NAME )) {
dev>tam_vis = 0; dev>base_vis = 0; return EBUSY;}
Continúa ...
46
Driver ff: Un ejemplo de driver PCIInicialización y reserva de recursos PCI (II)
dev>vis_ptr = ioremap( dev>base_vis, dev>tam_vis );
if (dev>vis_ptr == NULL) {return ENOMEM;}
dev>base_dma = pci_resource_start( pcidev, FF_BARDMA );
dev>tam_dma = pci_resource_len( pcidev, FF_BARDMA );
if (!request_region( dev>base_dma, dev>tam_dma, FF_NAME )) {
dev>tam_dma = 0; dev>base_dma = 0; return EBUSY;}
dev>irq = pcidev>irq;
return res;
}
47
Driver ff: Un ejemplo de driver PCILiberación de recursos PCI
static void ff_cleanup_pciparams ( struct ff_dev *dev )
{
if (dev>base_con || dev>tam_con) {
release_region( dev>base_con, dev>tam_con );
}
if (dev>base_vis || dev>tam_vis) {
if (dev>vis_ptr != NULL)
iounmap( dev>vis_ptr );
release_mem_region( dev>base_vis, dev>tam_vis );
}
if (dev>base_dma || dev>tam_dma) {
release_region( dev>base_dma, dev>tam_dma );
}
}
48
Driver ff: Un ejemplo de driver PCILa operación open del dispositivo ff (I)
int ff_open(struct inode *inode, struct file *filp)
{
struct ff_dev *dev; /* Informacion del dispositivo */
int res;
dev = container_of(inode>i_cdev, struct ff_dev, cdev);
filp>private_data = dev; /* para el resto de metodos */
/* Solo permitimos un proceso en escritura y otro en lectura
if (down_interruptible(&dev>open_sem))
return ERESTARTSYS;
if (FF_OPEN_BUSY(dev,filp>f_mode)) {
up( &dev>open_sem );
return EBUSY;
} Continúa ...
49
Driver ff: Un ejemplo de driver PCILa operación open del dispositivo ff (II)
if (dev>nreaders + dev>nwriters == 0) {
// Registramos el manejador de interrupcion
res = request_irq( dev>irq, ff_interrupt, SA_SHIRQ, FF_NAME, dev );
if (res) {
dev>irq );
up(&dev>open_sem);
return res;}
dev>ff_timer.data = (unsigned long)dev;
dev>ff_timer.expires = jiffies + ff_timeout_to_poll;
add_timer( &dev>ff_timer );
OUTL_PCI(INL_PCI(FF_HW_CONTROL_P(dev)) | FF_HW_INT_LOW_EN | FF_HW_INT_HIGH_EN, FF_HW_CONTROL_P(dev));
}
if (filp>f_mode & FMODE_READ) dev>nreaders++;
if (filp>f_mode & FMODE_WRITE) dev>nwriters++;
up(&dev>open_sem);
return 0; } Continúa ...
50
Driver ff: Un ejemplo de driver PCILa operación close del dispositivo ff (I)
int ff_release(struct inode *inode, struct file *filp)
{
struct ff_dev *dev = filp>private_data;
down(&dev>open_sem);
if (filp>f_mode & FMODE_READ)
dev>nreaders;
if (filp>f_mode & FMODE_WRITE)
dev>nwriters;
if (dev>nreaders + dev>nwriters == 0) {
/* DMA ocupado en una transferencia lanzada por un write */
if (wait_event_interruptible( dev>dma_q, !dev>dma_busy )) {
up(&dev>open_sem);
return ERESTARTSYS;
}
51
Driver ff: Un ejemplo de driver PCILa operación close del dispositivo ff (II)
// Liberamos el manejador de interrupcion en el ultimo close
OUTL_PCI(INL_PCI(FF_HW_CONTROL_P(dev)) & ~FF_HW_INT_LOW_EN & ~FF_HW_INT_HIGH_EN, FF_HW_CONTROL_P(dev));
free_irq( dev>irq, dev );
del_timer_sync( &dev>ff_timer );
}
up(&dev>open_sem);
return 0;
}
52
ssize_t ff_read ( struct file *filp, char __user *buf,
size_t count,loff_t *f_pos)
{
struct ff_dev *dev = filp>private_data;
u32 fifo_count;
dma_addr_t bus_addr;
if (count % FF_ITEM_SIZE) return EINVAL;
if (down_interruptible( &dev>read_sem )) return ERESTARTSYS;
while (dev>fifo_empty) {
up(&dev>read_sem);
if (filp>f_flags & O_NONBLOCK) return EAGAIN;
if (wait_event_interruptible(dev>read_q, !dev>fifo_empty))
return ERESTARTSYS;
if (down_interruptible(&dev>read_sem)) return ERESTARTSYS;
} Continúa ...
Driver ff: Un ejemplo de driver PCILa operación read del dispositivo ff mediante DMA (I)
53
Driver ff: Un ejemplo de driver PCILa operación read del dispositivo ff mediante DMA (II)
down_write( &dev>dma_sem );
// Preparamos la transferencia por DMA
if (dev>dma_busy) {
if (wait_event_interruptible( dev>dma_q, !dev>dma_busy )) {
up_write( &dev>dma_sem );
up( &dev>read_sem );
return ERESTARTSYS;}
}
// Comprobamos la cantidad de datos en la FIFO
fifo_count = INL_PCI(FF_HW_USAGE_P(dev)) & FF_HW_USAGE_MSK;
fifo_count *= FF_ITEM_SIZE;
if (count > fifo_count)
count = fifo_count;
if (count > ff_buffer_size)
count = ff_buffer_size;Continúa ...
54
Driver ff: Un ejemplo de driver PCILa operación read del dispositivo ff mediante DMA (III)
dev>dma_dir = DMA_FROM_DEVICE;
dev>dma_size = count;
bus_addr = dma_map_single( &dev>pci_dev>dev, dev>rd_buffer, count, dev>dma_dir );
if (dma_mapping_error(bus_addr)) {
up_write( &dev>dma_sem );
up( &dev>read_sem );
return EFAULT;}
dev>dma_addr = bus_addr;
dev>dma_busy = 1;
// Inicia DMA
OUTL_PCI( bus_addr, FF_HW_DMA_ADDR_P(dev) );
OUTL_PCI( (count/FF_ITEM_SIZE) | FF_HW_DMA_RD | FF_HW_DMA_INT_EN,
FF_HW_DMA_OPER_P(dev));
OUTL_PCI( (count/FF_ITEM_SIZE) | FF_HW_DMA_RD | FF_HW_DMA_INT_EN | FF_HW_DMA_ON,FF_HW_DMA_OPER_P(dev) );
Continúa ...
55
Driver ff: Un ejemplo de driver PCILa operación read del dispositivo ff mediante DMA (IV)
up_write( &dev>dma_sem );
if (wait_event_interruptible( dev>dma_q, !dev>dma_busy )) {
up( &dev>read_sem );
return ERESTARTSYS;
}
/* Copiamos los datos al usuario */
if (copy_to_user( buf, dev>rd_buffer, count )) {
up( &dev>read_sem );
return EFAULT;
}
up( &dev>read_sem );
return count;
}
56
Driver ff: Un ejemplo de driver PCIEl gestor de interrupción (I)
irqreturn_t ff_interrupt( int irq, void *dev_id, struct pt_regs *regs ) {
struct ff_dev *dev = dev_id;
u32 status;
u32 status_dma;
unsigned long flags;
spin_lock_irqsave( &dev>data_lock, flags );
status = INL_PCI(FF_HW_USAGE_P(dev));
status_dma = INL_PCI( FF_HW_DMA_OPER_P(dev) );
if (!(status & (FF_HW_USAGE_INT_LOW | FF_HW_USAGE_INT_HIGH)) &&
!(status_dma & FF_HW_DMA_TC))
return IRQ_NONE;
dev>fifo_empty = (status & FF_HW_USAGE_EMPTY) != 0;
dev>fifo_full = (status & FF_HW_USAGE_FULL) != 0; Continúa ...
57
Driver ff: Un ejemplo de driver PCIEl gestor de interrupción (II)
if (status & FF_HW_USAGE_INT_LOW) { // Superado umbral llenandose
wake_up_interruptible( &dev>read_q );}
if (status & FF_HW_USAGE_INT_HIGH) { // Superado umbral vaciandose
wake_up_interruptible( &dev>write_q );}
if (status_dma & FF_HW_DMA_TC) { // Fin de DMA
dev>dma_busy = 0;
dma_unmap_single( &dev>pci_dev>dev, dev>dma_addr,
dev>dma_size, dev>dma_dir );
OUTL_PCI( 0, dev>base_dma + FF_HW_DMA_OPER_PORT );
wake_up_interruptible( &dev>dma_q );
}
spin_unlock_irqrestore( &dev>data_lock, flags );
// Reprogramamos el timer
mod_timer( &dev>ff_timer, jiffies + ff_timeout_to_poll );
return IRQ_HANDLED;
}
58
Driver ff: Un ejemplo de driver PCILa función de time-out
void ff_timeout(unsigned long data)
{
struct ff_dev *dev = (struct ff_dev*)data;
unsigned long flags;
u32 status;
spin_lock_irqsave( &dev>data_lock, flags );
status = INL_PCI(FF_HW_USAGE_P(dev));
dev>fifo_empty = (status & FF_HW_USAGE_EMPTY) != 0;
dev>fifo_full = (status & FF_HW_USAGE_FULL) != 0;
if (!dev>fifo_empty) wake_up_interruptible( &dev>read_q );
if (!dev>fifo_full) wake_up_interruptible( &dev>write_q );
spin_unlock_irqrestore( &dev>data_lock, flags );
mod_timer( &dev>ff_timer, jiffies + ff_timeout_to_poll );
}