Cómo crear un controlador de dispositivo PCI para Windows NT
1. Estructuras de datos clave
Hay tres espacios de direcciones en los dispositivos PCI: espacio de E/S PCI, espacio de almacenamiento PCI y espacio de configuración PCI: la CPU puede Acceda a todo el espacio de direcciones en un dispositivo PCI, donde los controladores de dispositivo utilizan el espacio de E/S y el espacio de almacenamiento, y el código de inicialización PCI en el kernel de Linux utiliza el espacio de configuración. El kernel es responsable de inicializar todos los dispositivos PCI en el momento del arranque, configurar todos los dispositivos PCI (incluidos los números de interrupción y las direcciones base de E/S) y enumerar todos los dispositivos PCI encontrados junto con sus parámetros y propiedades en el archivo /proc/pci.
Los controladores de Linux generalmente usan una estructura para representar un dispositivo, y las variables en la estructura representan un dispositivo específico y guardan toda la información sobre el dispositivo. Un buen conductor debería poder controlar varios dispositivos del mismo tipo, cada uno diferenciado por un número de subdispositivo. Si los datos de estructura se utilizan para representar todos los dispositivos que el controlador puede controlar, entonces los números de los subdispositivos se pueden representar simplemente mediante una serie de subíndices.
Las siguientes estructuras de datos clave desempeñan un papel central en los controladores PCI:
pci_driver
Esta estructura de datos se encuentra en el archivo include/linux/pci.h Se agregó después de la versión 2.4 del kernel de Linux para los nuevos controladores de dispositivos PCI.
struct pci_driver {
struct list_head node;
char *name;
Este es el primer dispositivo agregado después de la versión 2.4 del kernel de Linux El conductor también es el más importante. const struct pci_device_id *id_table;
int (*probe) (struct pci_dev *dev, const struct pci_device_id *id);
void (*remove) (struct pci_dev *dev) ;
int (*save_state) (struct pci_dev *dev, estado u32);
int (*suspend) (struct pci_dev *dev, estado u32); p>int (*resume) (struct pci_dev *dev);
int (*enable_wake) (struct pci_dev *dev, estado u32, int enable
}); p> p>
pci_dev
Esta estructura de datos también se incluye en el archivo /linux/pci.h, que describe en detalle casi toda la
información de hardware del PCI. dispositivo, incluido el ID del proveedor, el ID del dispositivo, diversos recursos, etc.
:
struct pci_dev {
struct list_head global_list
struct list_head bus_list
struct pci_bus *bus; p>struct pci_bus *subordinate;
void *sysdata;
struct proc_dir_entry * procent; proveedor;
dispositivo corto sin firmar
subsistema_vendor corto sin firmar
dispositivo de subsistema corto sin firmar
clase int sin firmar;
u8 hdr_type;
u8 rom_base_reg;
struct pci_driver *driver;
void *driver_data;
u64 dma_mask;
u32 current_state;
proveedor corto sin firmar_compatible[DEVICE_COUNT_COMPATIBLE];
dispositivo corto sin firmar_compatible[DEVICE_COUNT_COMPATIBLE]
recurso de recurso de estructura sin firmar; [DEVICE_COUNT_RESOURCE];
recurso dma_resource[DEVICE_COUNT_DMA];
recurso irq_recurso[DEVICE_COUNT_IRQ]
nombre de carácter[ 80];
char slot_name[8];
int activo;
int ro;
registros cortos sin firmar
int (* preparar)(struct pci_dev *dev);
int (*activate)(struct pci_dev *dev);
int (*deactivate)(struct pci_dev *dev);
};
2. Marco básico
Al implementar un controlador de dispositivo PCI de forma modular, generalmente es necesario implementar al menos las siguientes partes: módulo de dispositivo de inicialización , módulo de apertura de dispositivos, módulo de control y lectura/escritura de datos, módulo de procesamiento de interrupciones, módulo de liberación de dispositivos, módulo de descarga de dispositivos. A continuación se proporciona el marco básico de un controlador de dispositivo PCI típico y no es difícil ver cómo están organizados estos módulos clave.
/* Indique a qué dispositivos PCI se aplica el controlador*/
static struct pci_device_id demo_pci_tbl [] __initdata = {
{PCI_VENDOR_ID_DEMO, PCI_DEVICE_ID_DEMO, p>
p>
PCI_ANY_ID, PCI_ANY_ID, 0, 0, DEMO},
{0,}
};
/* Describe el dispositivo PCI específico Estructura de datos*/
struct demo_card {
unsigned int magic;
/* Utilice una lista vinculada para guardar todos los dispositivos PCI del mismo tipo*/
struct demo_card *next;
/* ...*/
}
/* Módulo de procesamiento de interrupciones */
static void demo_interrupt(int irq, void *dev_id, struct pt_regs *regs)
{
/* ...*/
}
/* Interfaz de operación de archivos del dispositivo*/
static struct file_operatives demo_fops = {
propietario: THIS_MODULE, /* El módulo del dispositivo a a qué demo_fops pertenece*/
leer: demo_read, /* leer operación del dispositivo*/
escribir: demo_write, /* escribir operación del dispositivo*/
ioctl: demo_ioctl, /* operación del dispositivo de control*/
mmap: demo_mmap, /* Operación de reasignación de memoria*//* Cargar entrada del módulo controlador*/
module_init(demo_init_module);
/* Descargar entrada del módulo del controlador */
module_exit(demo_cleanup_module);
El código anterior proporciona el marco de un controlador de dispositivo PCI típico en un modo relativamente fijo. Tenga en cuenta que las funciones o estructuras de datos relacionadas con la carga y descarga de módulos tienen el prefijo __init, __exit y otros marcadores para distinguirlas de las funciones ordinarias. Después de construir dicho marco, la siguiente tarea es cómo completar el marco de varios módulos funcionales.
3. Inicialice el módulo del dispositivo
En Linux, para inicializar un dispositivo PCI, debe realizar las siguientes operaciones:
Compruebe si el kernel de Linux es compatible. el bus PCI;
p>
Compruebe si el dispositivo está conectado a una ranura del bus y, en caso afirmativo, guarde la posición de la ranura que ocupa.
Lea la información en el encabezado de configuración y proporciónela al conductor.
Cuando el kernel de Linux inicia y completa las operaciones de inicialización, como escanear, iniciar sesión y asignar recursos para todos los dispositivos PCI, establecerá la topología de todos los dispositivos PCI en el sistema. El controlador necesita inicializar el dispositivo, generalmente llama al siguiente código:
static int __init demo _init_module (void)
{
/* Compruebe si el sistema admite el bus PCI*/ p>
if (!pci_present())
return -ENODEV;
/* Registrar controlador de hardware*/
if (!pci_ Register_driver (&demo_pci_driver)) {
pci_unregister_driver(&demo_pci_driver);
return -ENODEV;
}
/* ...*/
pci_set_master(pci_dev);
/* Solicitar recursos de E/S*/
request_region(card->iobase, 64 , card_names[pci_id->driver_data ]);
return 0;
}
4. Abra el módulo del dispositivo
El principal. La función de este módulo es solicitar interrupción y verificar el modo de lectura/escritura y aplicar el control del dispositivo. Cuando solicita el control, vuelve al modo sin bloqueo cuando está ocupado. De lo contrario, el proceso acepta activamente la programación, entra en estado de suspensión y espera a que otros procesos liberen el control del dispositivo.
static int demo_open(struct inode *inode, struct file *file)
{
/* Solicitar una interrupción y registrar un controlador de interrupciones*/ p >
request_irq(card->irq, &demo_interrupt, SA_SHIRQ,
card_names[pci_id->driver_data], card)) {
/* Comprobar lectura/escritura modo */
if(archivo->f_mode & FMODE_READ ) {
/* ...*/
}
si ( file->f_mode & FMODE_WRITE ) {
/* ...*/
}
/* Solicitar dispositivo de control*/
abajo(&card->open_sem);
while(card->open_mode & file->f_mode) {
if (file->f_flags & O_NONBLOCK) { p >
/* Modo NO BLOQUEO, regresa -EBUSY *//
Arriba (&card->open_sem);
regresa -EBUSY;
} else {
/* Esperando a que la programación tome el control*/
card->open_mode |= f_mode &(FMODE_READ | FMODE_WRITE);
up( &card- >open_sem);
/* El recuento de dispositivos abiertos aumenta en 1 */
MOD_INC_USE_COUNT;
/* ...*/
}
}
}
5. Módulo de lectura/escritura de datos e información de control
El controlador del dispositivo PCI puede pasar la estructura demo_fops La función demo_ioctl() proporciona una interfaz para que las aplicaciones controlen el hardware. Por ejemplo, los datos se pueden leer desde un registro de E/S y transferirse al espacio de usuario:
static int demo_ioctl(struct inode *inode, struct file *file, unsigned int
cmd , argumento largo sin firmar)
{
/* ...*/
switch(cmd) {
case DEMO_RDATA:
/* Leer 4 bytes de datos del puerto de E/S*/
val = inl(card->iobae + 0x10);
/* Transferir los datos leídos en el espacio de usuario*/
return 0;
}
/* ...*/
} p>
De hecho, demo_read(), demo_mmap() y otras operaciones también se pueden implementar en demo_fops. El código fuente de muchos controladores de dispositivos se proporciona en el directorio de controladores del código fuente del kernel de Linux; allí puede encontrar ejemplos similares; En términos de cómo se accede a los recursos, además de las instrucciones de E/S, también se puede acceder a la memoria de E/S periférica.
Por un lado, estas memorias se pueden operar reasignando la memoria de E/S y operándola como memoria normal. Por otro lado, el dispositivo también puede usar el bus maestro DMA para permitir que el dispositivo transfiera datos a la memoria del sistema a través de DMA. .
6. Módulo de procesamiento de interrupciones
Los recursos de interrupción de la PC son relativamente limitados, con solo 0 ~ 15 números de interrupción, por lo que la mayoría de los dispositivos externos solicitan interrupciones en forma de disfrute sexual. Número. Cuando ocurre una interrupción, el controlador de interrupciones es primero responsable de identificar la interrupción y luego procesarla más.
static void demo_interrupt(int irq, void *dev_id, struct pt_regs *regs)
{
struct demo_card *card = (struct demo_card *)dev_id;
estado u32;
spin_lock(&card->lock);
/* Identificar interrupción*/
estado = inl(tarjeta ->iobase + GLOB_STA);
if (!(status & INT_MASK))
{
spin_unlock(&card->lock);
return; /* no para nosotros */
}
//* decirle al dispositivo que se ha recibido una interrupción */
outl( status &INT_MASK, card->iobase + GLOB_STA);
spin_unlock(& card->lock);
/* Otro procesamiento posterior, como actualizar el puntero del búfer DMA* /
}
7. Liberar el módulo del dispositivo
El módulo de liberación del dispositivo es el principal responsable de liberar el control del dispositivo, liberar la memoria ocupada y las interrupciones, etc.
Su función es opuesta a abrir el módulo del dispositivo:
static int demo_release(struct inode *inode, struct file * file)
{
/* ..
/* Libera el control del dispositivo */
card->open_mode &= (FMODE_READ | FMODE_WRITE);
/*Despierta y. esperar para tomar el control Otros procesos con el derecho */
wake_up(&card->open_wait);
up(&card->open_sem);
/ * interrupciones gratuitas */
free_irq(card->irq, card);
/* incrementa el recuento de aperturas del dispositivo en 1 */
MOD_ DEC_USE_COUNT;
/* ...
}
8. Desinstalar el módulo del dispositivo
El módulo de desinstalación del dispositivo corresponde al módulo de inicialización del dispositivo y es relativamente sencillo de implementar. El objetivo principal es Se llama a la función pci_unregister_driver() para cancelar el registro del controlador del dispositivo del kernel de Linux:
static void ___Demo_Cleanup_MOD_DEC_USE_COUNT;
MOD_ DEC_USE_COUNT;<. /p>
/* ..... . salir demo_cleanup_module (void)
{
pci_unregister_driver(&demo_pci_driver);
}
Resumen
El bus PCI no solo es un estándar de bus informático ampliamente utilizado, sino también uno de los buses informáticos más compatibles y con más funciones. Como nuevo sistema operativo, Linux tiene enormes perspectivas de desarrollo y también ofrece la posibilidad de interconectar el bus PCI con varios dispositivos nuevos. Debido a que el código fuente de Linux es abierto, es relativamente fácil escribir controladores para cualquier dispositivo conectado al bus PCI. Este artículo describe cómo compilar controladores PCI para la versión 2.4 del kernel de Linux.