59
1 Raouf Senhadji Navarro [email protected] Curso de verano “Software libre a fondo” Creación de drivers para dispositivos PCI en Linux Ignacio García Vargas [email protected]

Driver Pci Linux

Embed Size (px)

Citation preview

1

Raouf Senhadji [email protected]

Curso de verano “Software libre a fondo”

Creación de drivers para dispositivos PCI en Linux

Ignacio García [email protected]

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),)

obj­m   := $(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 );

}

59

Bibliografía

• Linux Device Drivers (Third Edition), Jonathan Corbet, Alessandro Rubini, Greg Kroah-Hartman, O'Reilly

• Understanding the Linux Kernel (Third Edition), Daniel P. Bovet, Marco Cesati, O'Reilly

• Linux Cross-Reference (http://lxr.linux.no)