1. Key data structure PCI devices have three address spaces: PCI I/O space, PCI storage space, and PCI configuration space. The CPU can access all address spaces on the PCI device, where I/O space and storage space are provided to the device driver, while the configuration space is used by the PCI initialization code in the Linux kernel. At boot time, the kernel is responsible for initializing all PCI devices, configuring all PCI devices, including the interrupt number and the I/O base address, and listing all the found PCI devices in file/proc/pci, along with the parameters and attributes of those devices. Linux drivers typically use structures (structs) to represent a device, whereas a variable in a struct represents a specific device that holds all the information associated with that device. Good drivers should be able to drive multiple devices of the same kind, each with a secondary device number to differentiate, if the use of structural data to represent all the devices that can be driven by this driver, then you can simply use the array subscript to represent the secondary device number. In the PCI driver, the following key data structures play a very central role: ? pci_driver This data structure in the file include/linux/pci.h, which is the Linux kernel version 2.4 after the new PCI device driver added, the most important is to identify the device id_table structure, and functions for detecting devices probe () and unloading devices remove (): struct pci_driver { struct list_head node; Char *name;&nbs P 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 *d EV, U32 state); int (*suspend) (struct Pci_dev *dev, u32 State); int (*resume) (Struc T pci_dEV *dev); int (*enable_wake) (struct Pci_dev *dev, u32 State, int enable);}; ? pci_dev This data structure is also in the file include/linux/pci.h, it describes in detail a PCI device almost all the hardware information, including the manufacturer ID, device ID, various resources, etc.: struct Pci_dev { struct List_head global_list; struct list_head bus_list; struct Pci_bus *bus ; struct Pci_bus *subordinate; void *sysdata; Stru CT proc_dir_entry *procent; unsigned int devfn; unsigned short vendor; unsigned short device; unsigned short subsystem_vendor; unsigned short   ;subsystem_device; unsigned int class; U8 hdr_type; &nbs P U8 rom_base_reg; struct pci_driver *driver; void *driver_data; U64 DMA_MASK;&NBSp U32 current_state; unsigned short Vendor_compatible[device_c ount_compatible]; unsigned short device_compatible[device_count_compatible]; unsigned int & nbsp irq; struct resource resource[device_count_resource]; struct resource dma_resource[ device_count_dma]; struct Resource irq_resource[device_count_irq]; char & nbsp;name[80]; Char slot_name[8]; int active; &NB Sp int ro; unsigned short regs; int (*prepare) (struct Pci_dev *dev); &nbs P Int (*activate) (struct Pci_dev *dev); int (*deactivate) (struct Pci_dev *dev);}; 2. When implementing a PCI device driver in a modular manner, the basic framework typically implements at least the following parts: Initializing the device module, the device open module, the data read and write and control module, the interrupt processing module, the device release module, and the device unload module. Here is a typical PCI device driver basic framework, it is not difficult to understand how these several key modules are organized. static struct pci_device_id demo_pCI_TBL [] __initdata = { {pci_vendor_id_demo, pci_device_id_demo, pci_any_id, PCI_ANY_ID , 0, 0, demo}, {0,}};struct demo_card { unsigned int magic; struct DEM O_card *next; }static void demo_interrupt (int irq, void *dev_id, struct pt_regs *regs) { }static St Ruct file_operations demo_fops = { owner: this_module, read: & nbsp Demo_read, write: demo_write, IOCTL: &nbs P demo_ioctl, mmap: Demo_mmap, Open: Demo_open, release: demo_release } ; static struct Pci_driver demo_pci_driver = { Name: Demo_module_name,   ;   Id_table: DEMO_PCI_TBL, probe: demo_probe,   ; Remove: demo_remove };static int __init demo_init_module (void) { }sta tic void __exit demo_cleanup_module (void) { pci_unregister_driver (&demo_pci_driver);} Module_init (Demo_init_module); Module_exit (demo_cleanup_module); The above code gives a typical PCI device driver framework, is a relatively fixed mode. It is important to note that the functions or data structures associated with loading and unloading modules are preceded by __init, __exit, and so on, to differentiate them from normal functions. After constructing such a framework, the next task is how to complete the various functional modules within the framework. 3. Initializing the device module under the Linux system, to complete the initialization of a PCI device, the following work is required:? Check if the PCI bus is supported by the Linux kernel; Check that the device is plugged in to the bus slot and, if it is, save information such as the location of the slots it occupies. ? Read the information in the configuration header to provide the driver with the use. When the Linux kernel starts and completes the initialization of all PCI devices, such as scanning, logging in, and allocating resources, the topology of all PCI devices in the system is established, and the following code is typically called when the PCI driver needs to initialize the device: static int __init demo_init_module (void) { if (!pci_present ()) Return-enode v; if (!pci_register_driver (&demo_pci_driver)){ Pci_unregister_driver (&demo_pci_driver); return-enodev; } return 0;} The driver first calls the function pci_present () to check if the PCI bus has been supported by the Linux kernel, and if the system supports the PCI bus structure, the return value of this function is 0, and if the driver gets a non-0 return value when calling this function, Then the driver has to abort its mission. In the kernel before 2.4, you need to call the Pci_find_device () function manually to find the PCI device, but after 2.4 it is better to call the Pci_register_driver () function to register the driver for the PCI device, and you need to provide a pci_ Driver structure, the probe detection routines given in this structure will be responsible for the detection of hardware. static int __init demo_probe (struct pci_dev *pci_dev, const struct pci_device_id *pci_id) { struct DEMO_ Card *card; if (Pci_enable_device (Pci_dev)) return-eio;   ; if (Pci_set_dma_mask (Pci_dev, Demo_dma_mask)) { return-enodev; }&N Bsp if (card = kmalloc (sizeof (struct demo_card), gfp_kernel) = = NULL) { Print K (kern_err "Pci_demo:out of Memory\n "); return-enomem; } memset (card, 0, sizeof (*card)); &nbs p; card->iobase = Pci_resource_start (Pci_dev, 1); Card->pci_dev = pci_dev; C ard->pci_id = pci_id->device; CARD->IRQ = pci_dev->irq; Card->next = devs; Card->magic = demo_card_magic; Pci_set_master (Pci_dev); & nbsp; Request_region (Card->iobase, Card_names[pci_id->driver_data]); return 0;} 4. Open the device module in this module to achieve the main application interruption, check read and write mode, and apply for control of the device, and so on. In the application of control, non-blocking method encountered busy return, otherwise the process actively accept scheduling, go to sleep state, waiting for other processes to release control of the device. static int Demo_open (struct inode *inode, struct file *file) { REQUEST_IRQ (CARD->IRQ, &de Mo_interrupt, sa_shirq, Card_names[pci_id->driver_data], card)) { if (File->f_mode & Fmode_read) {   } if (File->f_mode & Fmode_write) { }  &NBSP ; Down (&card->open_sem); while (Card->open_mode & File->f_mode) { if (File->f_flags & O_nonblock) { up (&card->open_sem); return-ebusy; &NB Sp Else { CARD->OPEN_MOD E |= f_mode & (Fmode_read | Fmode_write); up (&card->open_sem); &NBS p; mod_inc_use_count; &NBSP ; } }}5. Data read-Write and Control information module The PCI device driver can provide an interface to the application to control the hardware through the function Demo_ioctl () in the DEMO_FOPS structure. For example, it can be used from the I/OThe register reads a data and transmits it to the user space: static int demo_ioctl (struct inode *inode, struct file *file, unsigned int cmd, u nsigned long Arg) { switch (CMD) { Case demo_rdata:& nbsp val = inl (card->iobae + 0x10); return 0; } & nbsp &NBSP: In fact, in the demo_fops can also implement such operations as Demo_read (), Demo_mmap (), the Linux kernel source in the driver directory provides a lot of device driver source code, find there to find a similar example. In addition to the I/O directives, access to peripheral I/O memory is available in the way resources are accessed. The operation of these memory can be done by re-mapping the I/O memory as normal memory, on the other hand, the device can transfer the data through DMA to the system memory by means of the bus master DMA. 6. Interrupt Processing module The PC has a limited number of interrupts, with only 0~15 interrupt numbers, so most external devices are requesting interrupt numbers in the form of a share. When an interrupt occurs, the interrupt handler is first responsible for identifying the interrupt and then doing further processing. static void Demo_interrupt (int irq, void *dev_id, struct pt_regs *regs) { struct Demo_card *card = (struct dem O_card *) dev_id; u32 status; Spin_loCK (&card->lock); status = INL (Card->iobase + glob_sta); if (! ( Status & Int_mask) { Spin_unlock (&card->lock); & nbsp return; } outl (Status & Int_mask, Card->iobase + glob_sta); Spin _unlock (&card->lock); }7. Release device module Release the device module is primarily responsible for releasing control of the device, freeing up memory and interrupts, and so on, doing exactly the opposite of opening the device module: static int demo_release (struct inode *inode, struct file *file ) { card->open_mode &= (Fmode_read | Fmode_write); wake_up (&card->open_wait); up (&CARD->OPEN_SEM); FREE_IRQ (CARD->IRQ, card); Mod_dec_use_count; }8. Uninstalling the device module the Unload device module is relative to the initialization device module, which is relatively simple to implement, mainly called function pci_unregister_drIver () Unregister the device driver from the Linux kernel: static void __exit demo_cleanup_module (void) { Pci_unregister_driver ( &demo_pci_driver);}
PCI driver analysis for Linux