PCI Avanzado

13
1 Programación de dispositivos sobre buses estándares 2 Bibliografía La información presentada en estas transparencias puede ser consultada y ampliada en los siguientes libros: { Para consultar información sobre el bus PCI: Linux Device Drivers 3ª edición, capítulo 12. Understanding Linux Network Internals, capítulo 6. { Para consultar información sobre algunas de las funciones mencionadas en las transparencias: Linux Device Drivers 3ª edición, capítulos 8 y 9. NOTA: Las imágenes utilizadas en las transparencias han sido tomadas de Linux Device Drivers 3ª edición.

Transcript of PCI Avanzado

Page 1: PCI Avanzado

1

Programación de dispositivos sobre buses estándares

2

Bibliografía

La información presentada en estas transparencias puede ser consultada y ampliada en los siguientes libros:

Para consultar información sobre el bus PCI:Linux Device Drivers 3ª edición, capítulo 12.Understanding Linux Network Internals, capítulo 6.

Para consultar información sobre algunas de las funciones mencionadas en las transparencias:

Linux Device Drivers 3ª edición, capítulos 8 y 9.

NOTA: Las imágenes utilizadas en las transparencias han sido tomadas de LinuxDevice Drivers 3ª edición.

Page 2: PCI Avanzado

3

El bus PCIDesarrollado por Intel, convertido a estándar abierto.

Admite espacio de direcciones de memoria y de E/S

VersionesDatos y direcciones de 32 o 64 bitsFrecuencias disponibles: 33, 66 o 133 (PCI-X) MHz

Características de velocidad superiores a buses anteriores (ISA, MCA, EISA, etc…)

ISA: 16MB/sEISA: 33MB/sPCI 32bits, 66MHz: 264MB/s

Establece mecanismos de configuración automática de los dispositivos PCI

4

Operación en el bus PCICapacidad de bus mastering por cualquiera de los dispositivos que lo soporten.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 PCI.Permite el acceso directo de los dispositivos PCI maestros a la memoria principal.

Interrupciones: 4 líneas de interrupción (de INTA hasta INTD) que pueden redirigirse por el puente PCI a una o varias IRQ’s.Todos los dispositivos PCI con interrupciones deben soportar interrupciones compartidas.

Otros pines indican al puente la presencia de dispositivos conectados al bus y la potencia eléctrica consumida.

Page 3: PCI Avanzado

5

Jerarquía del bus PCIHasta 256 buses

Hasta 32 dispositivos por bus

Hasta 8 funciones por dispositivo

Identificación de cada función de dispositivo en el bus:

Hardware: dirección de 16 bits

Kernel de Linux: La dirección hardware se almacena en variables de tipo structpci_dev

Por ejemplo, una tarjeta puede ser una capturadora de video y una tarjeta gráfica VGA

Dispositivo 2

Dispositivo 0

Dispositivo 1

NOTA: Cada función se comporta como un dispositivo independiente dentro de la tarjeta. A partir de ahora utilizaremos el término dispositivo para referirnos a las funciones.

3 bits5 bits8 bits

NºFUNCION

NºDISPOSITIVONº BUS

Puente PCI

6Espacios de direcciones PCI:Direcciones de Memoria y E/S

Existen 3 espacios de direcciones:Memoria, entrada/salida y configuración.

Espacios de direcciones de memoria y E/SExiste 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 vistas en la práctica 6: inb, inw, inl, outb, outw, outl.

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

Page 4: PCI Avanzado

7

Puente PCI

CPU

RAM

Dispositivo 1 Dispositivo 2

Supongamos que ambos dispositivos utilizan 256 bytes del espacio de direcciones de memoria.

256 MB RAM

Dispositivo 1

Dispositivo 2

0x0

0x0FFFFFFF

0x1000A0000x1000A0FF

0x1000B0000x1000B0FF

Espacio de Memoria

Durante el arranque del sistema, los dispositivos podrían ser mapeadoscomo muestra la figura.

Espacios de direcciones PCI:Direcciones de Memoria y E/S – Ejemplo (I)

8Espacios de direcciones PCI:Direcciones de Memoria y E/S – Ejemplo (II)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 = inw( DIR1 ); // Leemos un valor del puerto DIR1outw( 40, DIR2 ); // Escribimos en el puerto DIR2

Supongamos que un dispositivo PCI utiliza un rango de direcciones de memoria que va desde la dirección BASE hasta BASE+50.

u8 *pbase = BASE;u8 valor = ioread8(BASE); // Leemos un byte de BASEu8 valor2 = ioread8(pbase+3); // Leemos un byte de BASE+3iowrite8( 0, BASE+5 ); // Escribimos 0 en BASE+5iowrite8( 1, pbase+6 ); // Escribimos 1 en BASE+6*pbase = valor;

Esta operación es válida en x86 pero no es portable a otras arquitecturas. Además, en el caso de E/S mapeada en memoria, hay que evitar que el compilador modifique el orden en el que se realizan las lecturas y escrituras en memoria.

Page 5: PCI Avanzado

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 todos 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 especie de base de datos con los dispositivos encontrados.

Cuando el driver accede al dispositivo, este ya ha sido debidamente configurado: tan sólo tiene que consultar la configuración leyendo los registros de 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

Page 6: PCI Avanzado

11Espacio de configuración:Acceso 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). Existen macros definidas para cada registro de configuración.val: dirección donde se guardará el valor leído o 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);

12Espacio de configuración:Acceso desde el driver - Ejemplos

Supongamos que la variable struct pci_dev *dispositivo contiene un valor que identifica a un dispositivo PCI concreto del bus (el kernel proporciona dicho valor al drivera 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_read_config_word( dispositivo, 0x4, &command )) {

/* Error en la lectura del espacio de configuracion */}

Para escribir el valor 3 en el registro IRQ Line (dirección 0x3c):if (pci_write_config_byte( dispositivo, 0x3c, 3 )) {

/* Error en la escritura del espacio de configuracion */}

El fichero <linux/pci.h> define macros con las direcciones de cada uno de los registros del espacio de configuración.

Page 7: PCI Avanzado

13Espacio de configuración:Identificación del tipo de dispositivo

Vendor ID: Identificador del fabricanteLos valores son establecidos por el PCI SIG (Special Interest Group). Por ejemplo, el valor 0x8086 corresponde a Intel0xFFFF indica que no hay dispositivo conectado

Device ID: Identificador del dispositivoSu 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ónSus 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. Ejemplos: sin clase (00h), almacenamiento (01h), redes (02h), vídeo (03h)

Subclase (byte medio): Indica la función específica del dispositivo.Ejemplos: 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.

Macros definidas para el acceso a los registros anteriores: PCI_VENDOR_ID, PCI_DEVICE_ID, PCI_CLASS_PROG, PCI_CLASS_DEVICE (incluye la Clase base y la Subclase)

14Espacio de configuración:Registros de dirección base (I)

Un dispositivo PCI puede tener hasta 6 regiones de direcciones de memoria o de Entrada/SalidaLa 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 BARconsecutivos para especificar el rango.Si la memoria admite prebúsqueda la CPU puede guardarla en la caché y el acceso desde el driver se puede realizar 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 bits01 : Reservado10 : Dirección de 64 bits11 : Reservado

Prefetchable (admite prebúsqueda):0 : nonprefetchable1 : prefetchable

Memoria

10Dirección Base

0131 2E/S

Page 8: PCI Avanzado

15Espacio de configuración:Registros 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 bar

unsigned 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 memoriaIORESOURCE_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, o las macros PCI_BASE_ADDRESS_0 hasta PCI_BASE_ADDRESS_5.

Fichero de cabecera para los flags: <linux/ioports.h>

16Espacio de configuración:Registros de dirección base – 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, PCI_BASE_ADDRESS_0 );tam = pci_resource_end( disp, PCI_BASE_ADDRESS_0 ) – inicio + 1;if (pci_resource_flags(disp,PCI_BASE_ADDRESS_0) & IORESOURCE_MEM) {

if (!request_mem_region( inicio, tam, “midispositivo”)) {/* Error */

}if ((base = ioremap( inicio, tam )) == NULL) {

/* Error */}iowrite8( 0x3f, base ); // *base = 0x3f; estado = ioread16( base + 2 );

...

}else {if (request_region(inicio, tam, “midispositivo”) != NULL) {

outb(0x3f,inicio);

...

Espacio de direcciones de memoria

Espacio de direcciones de E/S

Esta operación es válida en x86 pero no es portable a otras arquitecturas

Page 9: PCI Avanzado

17Espacio de configuración:Preparando el acceso a memoria o E/S

Las siguientes funciones utilizadas en el ejemplo anterior pueden ser consultadas en los capítulos 8 y 9 de Linux Device Drivers 3ª edición.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 startpara el dispositivo name. Devuelve NULL en caso de que los puertos solicitados no estén disponibles. <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. <linux/ioport.h>

void *ioremap(unsigned long phys_addr, unsigned long size);

Asigna un rango de direcciones de memoria virtuales al rango de direcciones físicas [phys_addr, phys_addr+size]. Devuelve la dirección base virtual. <asm/io>

18

Puente PCI

CPU

RAM

Dispositivo 1 Dispositivo 2

256 MB RAM

Dispositivo 1

Dispositivo 2

0x0

0x0FFFFFFF

0x1000A0000x1000A0FF

0x1000B0000x1000B0FF

Direcciones físicas

256 MB RAM

Dispositivo 2

Dispositivo 1

0x0

0x0FFFFFFF

0x900000000x900000FF

0xA00000000xA00000FF

Direcciones virtuales

Direcciones virtuales asignadas por ioremapa las direcciones físicas

Espacio de configuración:Preparando el acceso a memoria o E/S – Ejemplo

Sistema de paginación

Page 10: PCI Avanzado

19Espacio de configuración:Liberando los rangos de direcciones

Las siguientes funciones son complementarias a las mencionadas en la transparencia 17 (ser consultadas en los capítulos 8 y 9 de Linux Device Drivers3ª edición).

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.

20Espacio de configuración:Interrupciones PCI

Registros:IRQ Line: Indica el número de interrupción asociada al dispositivo.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:Accede a los registros utilizando las funciones vistas anteriormente.

PCI_INTERRUPT_LINE: dirección del registro IRQ LinePCI_INTERRUPT_PIN: dirección del registro IRQ Pin

Ejemplo:resultado = pci_read_config_byte(dev, PCI_INTERRUPT_LINE, &mi_irq);

if (resultado) {

/* error en la lectura del espacio de configuración */

}

Page 11: PCI Avanzado

21Driver PCI:Registrando el driver (I)

En el proceso de registro, el driver PCI debe indicar al kernel qué tipo de dispositivosmaneja.Para especificar el tipo de dispositivo PCI se utiliza la estructura struct pci_device_id, con los siguientes campos:

__u32 vendor;__u32 device;

Indica el Vendor ID y el Device ID del tipo de dispositivo. Para indicar cualquier tipo de identificador se usa el valor PCI_ANY_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; (SIN COMENTAR)__u32 subdevice; (SIN COMENTAR)kernel_ulong_t driver_data; (SIN COMENTAR)

Ejemplo:struct pci_device_id scsi_id = {.vendor = PCI_ANY_ID, .device = PCI_ANY_ID,

.subvendor = PCI_ANY_ID, .subdevice = PCI_ANY_ID, .class = 0x020100,

.class_mask = ~0};

Adaptador Token Ring

22Driver PCI:Registrando el driver (II)

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 (debe ser único).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.

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.

Para registrar el driver desde el módulo de linux, se utiliza la siguiente función:int pci_register_driver( struct pci_driver *drv );

En el momento de descargar el driver, es necesario deshacer el registro:void pci_unregister_driver( struct pci_driver *drv );

Page 12: PCI Avanzado

23Driver PCI:Registrando el driver: una visión general

Durante el arranque del sistema, el kernel configura todos los dispositivos PCI accediendo a sus registros de configuración y guarda una base de datos con todos los dispositivos del sistema.

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 structpci_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.)

24Driver PCI:Registrando el driver – 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 )},{ /*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,

};

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…

Definimos los tipos de dispositivos que maneja el driver. La macro PCI_DEVICE sirve para inicializar un struct pci_device_id.

El último elemento del array debe ser rellenado con ceros

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.

Page 13: PCI Avanzado

25Driver PCI:Registrando el driver – Ejemplo (II)

static int ej_probe( struct pci_dev *dev, const struct pci_device_id *id ){ ...

if (pci_enable_device( dev )) { /* error */ }...resultado = pci_read_config_byte( dev,

PCI_INTERRUPT_LINE, &mi_irq );...dir_inicio = pci_resource_start( dev, PCI_BASE_ADDRESS_0 ); ...numero_mayor = register_chrdev(0,”midriver”,&operaciones);...

}

static void ej_remove( struct pci_dev *dev ){ ...

pci_disable_device(dev);}

...

module_init( ej_inicio_modulo );module_exit( ej_fin_modulo );

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 dispositivo y el elemento del campo id_tables

La función probe se encarga de inicializar el hardware del dispositivo, activar interrupciones, registrar el dispositivo, etc.

Si un dispositivo se elimina del sistema, el kernelllama a la función remove correspondiente, indicándole el dispositivo.

La función pci_enable_device se encarga de despertar al dispositivo, entre otras cosas. Devuelve un código de error.