天天看點

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裝置不再使用,

同時,禁用相關的一些資源。

繼續閱讀