Linux下的PCI驱动编程

PCI设备上有三种地址空间:PCI的I/O空间、PCI的存储空间和PCI的配置空间。CPU可以访问PCI设备上的所有地址空间,其中I/O空间和存储空间提供给设备驱动程序使用,而配置空间则由Linux内核中的PCI初始化代码使用。内核在启动时负责对所有PCI设备进行初始化,配置好所有的PCI设备,包括中断号以及I/O基址,并在文件/proc/pci中列出所有找到的PCI设备,以及这些设备的参数和属性。

Linux驱动程序通常使用结构(struct)来表示一种设备,而结构体中的变量则代表某一具体设备,该变量存放了与该设备相关的所有信息。好的驱动程序都应该能驱动多个同种设备,每个设备之间用次设备号进行区分,如果采用结构数据来代表所有能由该驱动程序驱动的设备,那么就可以简单地使用数组下标来表示次设备号。

在PCI驱动程序中,下面几个关键数据结构起着非常核心的作用:
1)pci_driver:
1)      这个数据结构在文件include/linux/pci.h里,这是Linux内核版本2.4之后为新型的PCI设备驱动程序所添加的,其中最主要的是用于识别设备的id_table结构,以及用于检测设备的函数probe()和卸载设备的函数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);
};
其中name 是驱动程序名称;id_table指向一个与驱动程序相关的设备ID表的指针。大多数驱动程序应当用MODULE_DEVICE_TABLE(pci,…)将该设备ID表导出。在调用prob( )时设成NULL 以让系统检测到所有的pci设备。
代码中是这样定义的:MODULE_DEVICE_TABLE(pci, sil_pci_tbl);
probe 指向设备检测函数probe( ) 的指针。该函数将在pci设备ID与设备ID表匹配且还没有被其它驱动程序处理时(一般在对已存在的设备执行pci_register_driver或以后又有新设备插入时)被调用。调用时传入一个指向struct pci_driver结构的指针和与设备匹配的设备ID表做参数。若成功(驱动程序检测到pci设备)则返回0,否则返回一个负的错误代码。这个函数总是在上下文之间调用的,因此可以进入睡眠状态的
remove指向一个设备卸载函数remove( )的指针。该函数在pci设备被卸载时(如在注销设备驱动程序或者手动拔出该设备)被调用。同probe一样,该函数也是可以睡眠的。
2)pci_dev:
1)      这个数据结构也在文件include/linux/pci.h里,它详细描述了一个PCI设备几乎所有的
硬件信息,包括厂商ID、设备ID、各种资源等:
struct pci_dev {
       struct list_head global_list;     /* node in list of all PCI devices */
       struct list_head bus_list;   /* node in per-bus list */
       struct pci_bus      *bus;             /* bus this device is on */
       struct pci_bus      *subordinate;      /* bus this device bridges to */
 
       void              *sysdata;      /* hook for sys-specific extension */
       struct proc_dir_entry *procent;     /* device entry in /proc/bus/pci */
 
       unsigned int devfn;           /* encoded device & function index */
       unsigned short    vendor;
       unsigned short    device;
       unsigned short    subsystem_vendor;
       unsigned short    subsystem_device;
       unsigned int class;            /* 3 bytes: (base,sub,prog-if) */
       u8          hdr_type;      /* PCI header type (`multi' flag masked out) */
       u8          rom_base_reg;   /* which config register controls the ROM */
 
       struct pci_driver *driver;  /* which driver has allocated this device */
       u64        dma_mask;  /* Mask of the bits of bus address this
                                      device implements.  Normally this is
                                      0xffffffff.  You only need to change
                                      this if your device has broken DMA
                                      or supports 64-bit transfers.  */
 
       pci_power_t     current_state;  /* Current operating state. In ACPI-speak,
                                      this is D0-D3, D0 being fully functional,
                                      and D3 being off. */
 
       struct     device    dev;              /* Generic device interface */
 
       /* device is compatible with these IDs */
       unsigned short vendor_compatible[DEVICE_COUNT_COMPATIBLE];
       unsigned short device_compatible[DEVICE_COUNT_COMPATIBLE];
 
       int          cfg_size;       /* Size of configuration space */
 
       /*
        * Instead of touching interrupt line and base address registers
        * directly, use the values stored here. They might be different!
        */
       unsigned int irq;
struct resource resource[DEVICE_COUNT_RESOURCE]; /* I/O and memory regions + expansion ROMs */
 
       /* These fields are used by common fixups */
       unsigned int transparent:1;     /* Transparent PCI bridge */
       unsigned int multifunction:1;/* Part of multi-function device */
       /* keep track of device state */
       unsigned int is_enabled:1;       /* pci_enable_device has been called */
       unsigned int is_busmaster:1; /* device is busmaster */
       unsigned int no_msi:1;     /* device may not use msi */
 
       u32        saved_config_space[16]; /* config space saved at suspend time */
       struct bin_attribute *rom_attr; /* attribute descriptor for sysfs ROM entry */
       int rom_attr_enabled;             /* has display of the rom attribute been enabled? */
       struct bin_attribute *res_attr[DEVICE_COUNT_RESOURCE]; /* sysfs file for resources */
};
 
             同加载和卸载模块相关的函数或数据结构都要在前面加上__init、__exit等
标志符,以使同普通函数区分开来。static int __init sil_init(void)
{
       return pci_module_init(&sil_pci_driver);
}
              驱动程式通过pci_module_init向内核注册自己(我们有时会看到pci_register_driver函数,其实他们是同一个,在内核代码中会看到,只是个简单的#define):
 pci_module_init(&sil_pci_driver);
 调用函数后,如果pci_device_id数组中标识的设备存在于系统中,并且该设备恰好还没有驱动程式,则该驱动程式会被安装。
           注册驱动程式成功后,sil_init_one会被调用,在这个函数中,我们能通过插入一些打印输出语句看到PCI的设置地址空间和I/O地址区域的一些情况。
  
            pci_enable_device和pci_disable_device
在一个pci设备可以被使用之前,必须调用pci_enable_device进行激活,该函数会调用底层代码激活PCI设备上的I/O和内存,使之可用。而pci_disable_device所做的事情刚好相反,告诉系统该PCI设备不再使用,
同时,禁用相关的一些资源。

相关推荐