Iii. PCI driver Program Implementation
1. Key Data Structure
There are three address spaces on the PCI device: pci I/O space, PCI storage space, and PCI configuration space. The CPU can access all the address spaces on the PCI device. The I/O space and storage space are provided to the device driver, and the configuration space is initialized by the PCI in the Linux kernel. Code . At startup, the kernel initializes all PCI devices and configures all PCI devices, including the interrupt number and I/O base address, and list all the PCI devices found in the file/proc/PCI, as well as the parameters and properties of these devices.
A Linux driver usually uses a structure (struct) to represent a device, while a variable in the structure represents a specific device, which stores all information related to the device. A good driver should be able to drive multiple devices of the same type. Each device is differentiated by a device number. If structured data is used to represent all the devices that can be driven by the driver, then, the array subscript can be used to represent the sub-device number.
In the PCI driver, the following key data structures play a very core role:
• Pci_driver
The data structure is in the file include/Linux/PCI. h, this is added to the new PCI device driver after Linux kernel version 2.4. The most important one is to identify the id_table structure of the device and the probe () function used to detect the device () and remove ():
Struct pci_driver {
Struct list_head node;
Char * Name;
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, u32 State );
INT (* suspend) (struct pci_dev * Dev, u32 State );
INT (* resume) (struct pci_dev * Dev );
INT (* enable_wake) (struct pci_dev * Dev, u32 state, int enable );
};
• Pci_dev
The data structure is also in the File Include/Linux/PCI. H. It describes almost all the hardware information of a PCI device, including the vendor ID, device ID, and various resources:
Struct pci_dev {
Struct list_head global_list;
Struct list_head bus_list;
Struct pci_bus * bus;
Struct pci_bus * subordinate;
Void * sysdata;
Struct 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;
U8 rom_base_reg;
Struct pci_driver * driver;
Void * driver_data;
U64 dma_mask;
U32 current_state;
Unsigned short vendor_compatible [device_count_compatible];
Unsigned short device_compatible [device_count_compatible];
Unsigned int IRQ;
Struct resource [device_count_resource];
Struct resource dma_resource [device_count_dma];
Struct resource irq_resource [device_count_irq];
Char name [80];
Char slot_name [8];
Int active;
Int ro;
Unsigned short regs;
INT (* prepare) (struct pci_dev * Dev );
INT (* activate) (struct pci_dev * Dev );
INT (* deactivate) (struct pci_dev * Dev );
};
2. Basic Framework
When using a module to implement the driver of a PCI device, we usually need to implement at least the following: initialize the device module, device Open Module, data read/write and control module, interrupt processing module, device release module, and device uninstall module. The following describes the basic framework of a typical PCI device driver. It is not difficult to understand how these key modules are organized.
/* Specify which PCI devices the driver applies */
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 ,}
};
/* Description of the data structure of a specific PCI device */
Struct demo_card {
Unsigned int magic;
/* Use a linked list to save all similar PCI devices */
Struct demo_card * next;
/*...*/
}
/* Interrupt processing module */
Static void demo_interrupt (int irq, void * dev_id, struct pt_regs * regs)
{
/*...*/
}
/* Device File Operation Interface */
Static struct file_operations demo_fops = {
Owner: this_module,/* Device Module of demo_fops */
Read: demo_read,/* Read Device operation */
Write: demo_write,/* Write Device operations */
IOCTL: demo_ioctl,/* control device operations */
MMAP: demo_mmap,/* Memory re ing operation */
Open: demo_open,/* open a device */
Release: demo_release/* delete a device */
/*...*/
};
/* Device module information */
Static struct pci_driver demo_pci_driver = {
Name: demo_module_name,/* Device Module name */
Id_table: demo_pci_tbl,/* List of devices that can be driven */
Probe: demo_probe,/* Find and initialize the device */
Remove: demo_remove/* uninstall the device module */
/*...*/
};
Static int _ init demo_init_module (void)
{
/*...*/
}
Static void _ exit demo_cleanup_module (void)
{
Pci_unregister_driver (& demo_pci_driver );
}
/* Loading driver module entry */
Module_init (demo_init_module );
/* Uninstall driver module entry */
Module_exit (demo_cleanup_module );
The above Code provides a typical PCI device driver framework, which is a relatively fixed mode. Note that _ init, _ exit, and other flags must be added before functions or data structures related to the module to distinguish them from common functions. After constructing such a framework, the next step is to complete the functional modules in the framework.
3. initialize the device module
In Linux, to initialize a PCI device, complete the following tasks:
• Check whether the PCI bus is supported by the Linux kernel;
• Check whether the device is inserted in the bus slot, and if so, save information such as the location of the slot it occupies.
• Read the information in the configuration header for the driver.
When the Linux kernel starts and initializes all PCI devices, such as scanning, logging on, and allocating resources, the topology of all PCI devices in the system is established, after that, when the PCI driver needs to initialize the device, the following code is generally called:
Static int _ init demo_init_module (void)
{
/* Check whether the system supports PCI bus */
If (! Pci_present ())
Return-enodev;
/* Register the hardware driver */
If (! Pci_register_driver (& demo_pci_driver )){
Pci_unregister_driver (& demo_pci_driver );
Return-enodev;
}
/*...*/
Return 0;
}
The driver first calls the pci_present () function to check whether the PCI bus is supported by the Linux kernel. If the system supports the PCI bus structure, the return value of this function is 0, if the driver gets a non-zero return value when calling this function, the driver must abort its own task. In kernels earlier than 2.4, You need to manually call the pci_find_device () function to find the PCI device. But after 2.4, the better way is to call the pci_register_driver () function to register the driver of the PCI device, in this case, you need to provide a pci_driver structure. The probe detection routine provided in this structure will be responsible for hardware detection.
Static int _ init demo_probe (struct pci_dev * pci_dev, const struct pci_device_id * pci_id)
{
Struct demo_card * card;
/* Start the PCI device */
If (pci_enable_device (pci_dev ))
Return-EIO;
/* Device dma id */
If (pci_set_dma_mask (pci_dev, demo_dma_mask )){
Return-enodev;
}
/* Dynamically apply for memory in the kernel space */
If (card = kmalloc (sizeof (struct demo_card), gfp_kernel) = NULL ){
Printk (kern_err "pci_demo: out of memory \ n ");
Return-enomem;
}
Memset (card, 0, sizeof (* card ));
/* Read PCI configuration information */
Card-> iobase = pci_resource_start (pci_dev, 1 );
Card-> pci_dev = pci_dev;
Card-> pci_id = pci_id-> device;
Card-> IRQ = pci_dev-> IRQ;
Card-> next = Devs;
Card-> magic = demo_card_magic;
/* Set it to the bus master DMA mode */
Pci_set_master (pci_dev );
/* Apply for I/O resources */
Request_region (card-> iobase, 64, card_names [pci_id-> driver_data]);
Return 0;
}
4. Open the device module
In this module, the application is interrupted, the read/write mode is checked, and the application is used to control the device. When you apply for control, the process is not blocked and returns. Otherwise, the process takes the initiative to accept scheduling, enters the sleep state, and waits for other processes to release control of the device.
Static int demo_open (struct inode * inode, struct file * file)
{
/* Application interrupted, register the interrupt handling program */
Request_irq (card-> IRQ, & demo_interrupt, sa_shirq,
Card_names [pci_id-> driver_data], card )){
/* Check the read/write mode */
If (file-> f_mode & fmode_read ){
/*...*/
}
If (file-> f_mode & fmode_write ){
/*...*/
}
/* Apply for control of the device */
Down (& Card-> open_sem );
While (card-> open_mode & file-> f_mode ){
If (file-> f_flags & o_nonblock ){
/* Nonblock mode, return-ebusy */
Up (& Card-> open_sem );
Return-ebusy;
} Else {
/* Wait for scheduling to obtain control */
Card-> open_mode | = f_mode & (fmode_read | fmode_write );
Up (& Card-> open_sem );
/* Increase the number of device opens by 1 */
Mod_inc_use_count;
/*...*/
}
}
}
5. data read/write and control information module
The PCI device driver can use the demo_ioctl () function in the demo_fops structure to provide an interface for the application to control the hardware. For example, you can read a data from the I/O register and transmit it to the user space:
Static int demo_ioctl (struct inode * inode, struct file * file,
Unsigned int cmd, unsigned long Arg)
{
/*...*/
Switch (CMD ){
Case demo_rdata:
/* Read 4 bytes of data from the I/O port */
Val = INL (card-> iobae + 0x10 );
/* Transmit the read data to the user space */
Return 0;
}
/*...*/
}
In fact, operations such as demo_read () and demo_mmap () can be implemented in demo_fops. The driver directory in the Linux kernel source code provides many device drivers.Source code You can find similar examples. In terms of resource access, in addition to I/O commands, there are also access to peripheral I/O memory. These memory operations can be performed by re- ing the I/O memory as normal memory, or by bus master DMA) in this way, the device transmits data to the system memory through DMA.
6. interrupt handling module
The interrupted resources of the PC are limited, only 0 ~ Therefore, most external devices apply for the interrupt number in the form of sharing. When an interrupt occurs, the interrupt handler identifies the interrupt and then performs further processing.
Static void demo_interrupt (int irq, void * dev_id, struct pt_regs * regs)
{
Struct demo_card * card = (struct demo_card *) dev_id;
U32 status;
Spin_lock (& Card-> lock );
/* Identify interruptions */
Status = INL (card-> iobase + glob_sta );
If (! (Status & int_mask ))
{
Spin_unlock (& Card-> lock );
Return;/* not for us */
}
/* Tell the device that the device has been interrupted */
Outl (Status & int_mask, card-> iobase + glob_sta );
Spin_unlock (& Card-> lock );
/* Further processing, such as updating the DMA buffer pointer */
}
7. Release the device module
Releasing a device module is mainly responsible for releasing control of the device, releasing the occupied memory and interrupting the device, and doing the same thing as opening the device module:
Static int demo_release (struct inode * inode, struct file * file)
{
/*...*/
/* Release control of the device */
Card-> open_mode & = (fmode_read | fmode_write );
/* Wake up other processes waiting for control */
Wake_up (& Card-> open_wait );
Up (& Card-> open_sem );
/* Release interruption */
Free_irq (card-> IRQ, card );
/* Increase the number of device opens by 1 */
Mod_dec_use_count;
/*...*/
}
8. Uninstall the device module
The device uninstall module corresponds to the device initialization module and is relatively simple to implement. The main function is to call the pci_unregister_driver () function to log out the device driver from the Linux kernel:
Static void _ exit demo_cleanup_module (void)
{
Pci_unregister_driver (& demo_pci_driver );
}
________________________________________
Back to Top
Iv. Summary
PCI bus is not only a widely used computer bus standard, but also a computer bus with the strongest compatibility and most comprehensive functions. As a new operating system, Linux has an incalculable development prospect. It also makes it possible to interconnect the PCI bus with various new devices. Since the Linux source code is open, it is easier to write drivers for any device connected to the PCI bus. This article describes how to compile the PCI driver in Linux. The kernel version is 2.4.
Read the full text
Category:View comments by default