Realtek8168網卡時pci接口的網卡,其驅動程序就是一個PCI設備的驅動程序實例,我們一起看看其流程。
1. 首先,初始化模塊調用static inline int pci_register_driver(struct pci_driver *driver)函數來注冊設備驅動,這個函數的參數是struct pci_driver *driver,對應於r8168,就是
static struct pci_driver rtl8168_pci_driver = {
.name = MODULENAME,
.id_table = rtl8168_pci_tbl,
.probe = rtl8168_init_one,
.remove = __devexit_p(rtl8168_remove_one),
#if LINUX_VERSION_CODE > KERNEL_VERSION(2,6,11)
.shutdown = rtl8168_shutdown,
#endif
#ifdef CONFIG_PM
.suspend = rtl8168_suspend,
.resume = rtl8168_resume,
#endif
};
這個結構體把這個設備驅動所支持的設備(rtl8168_pci_tbl),探測函數(rtl8168_init_one)等都定義好,后面我們將需要用到rtl8168_pci_tbl,rtl8168_init_one兩部分內容來匹配,是否系統中的設備,看是否有設備可以跟這個驅動匹配
2. pci_register_driver 函數調用__pci_register_driver來完成任務,而__pci_register_driver則重新封裝了要注冊的驅動為PCI總線的,即
int __pci_register_driver(struct pci_driver *drv, struct module *owner)
{
……
drv->driver.bus = &pci_bus_type;
……
drv->driver.kobj.ktype = &pci_driver_kobj_type;
……
}
接下來就是調用設備驅動模型的函數,把我們要注冊的驅動掛載到PCI總線的設備隊列上,並掃描PCI總線的設備隊列,查看是否有設備可以匹配這個驅動,這跟usb設備驅動的掛載是一致的,只是這里掛載的是PCI總線,usb掛載的是USB總線,大致的流程是
driver_register()---àbus_add_driver()----àdriver_attach()--à__driver_attach()--àdriver_probe_device()---àdev->bus->probe(),即最后還是調用了2.中的pci_bus_type結構體中的probe成員函數,即static int pci_device_probe(struct device * dev)
3. static int pci_device_probe(struct device * dev)函數的參數dev就是遍歷了PCI總線上的設備鏈表,一一進行匹配來完成的,因為我們調用__driver_attach()的方式是bus_for_each_dev(drv->bus, NULL, drv, __driver_attach);
4. static int pci_device_probe(struct device * dev)通過兩個宏轉換to_pci_driver,to_pci_dev,獲得需要匹配的設備和驅動,調用static int __pci_device_probe(struct pci_driver *drv, struct pci_dev *pci_dev)函數進行匹配
5. __pci_device_probe函數首先通過設備驅動中的rtl8168_pci_tbl表,跟從設備獲得vendorID,productID進行比較,看是否一致,如果一致,就返回這個表的地址;如果沒有一致的,就表明,這個設備跟這個驅動不匹配,就不需要繼續進行下面的操作了,直接退出
6. 如果第5步發現了一致的設備表,就表明有設備ID一致,需要進一步探測,接下來就要調用我們設備驅動程序中的探測函數,進行更具體的探測了,即pci_call_probe(drv, pci_dev, id)---à drv->probe(dev, id),到這里,就開始調用我們的設備驅動中的探測函數了。
7. static int __devinit rtl8168_init_one(struct pci_dev *pdev, const struct pci_device_id *ent)函數是r8168的探測函數,其調用rtl8168_init_board(pdev, &dev, &ioaddr)來完成跟PCI設備驅動相關的探測。
8. static int __devinit rtl8168_init_board(struct pci_dev *pdev, struct net_device **dev_out, void __iomem **ioaddr_out)函數調用pci_enable_device函數來使能PCI設備,只有使能成功的PCI設備,才能正常使用。
9. 調用pci_set_mwi函數判斷設備是否支持memory-write-invalidate 功能
10. 調用pci_find_capability函數來判斷設備是否有電源管理功能.
11. 調用pci_resource_flags函數來判斷PCI是內存映射模式,還是IO模式
12. 調用pci_resource_len函數來判斷內存空間是否小於設備所需要的內存空間,如果小於,明顯出錯
13. 調用pci_request_regions函數通知內核,當前PCI將使用這些內存地址,其他設備不能再使用了
14. 調用pci_set_master(pdev)函數,設置設備具有獲得總線的能力,即調用這個函數,使設備具備申請使用PCI總線的能力。
15. 調用ioremap函數把剛剛申請的物理內存,映射成虛擬內存,因為進程使用的都是虛擬內存地址,而不是物理內存地址。
16. 把ioremap映射的虛擬內存返回給調用函數。
17. 到此,跟PCI相關的初始化都完成了,設備即可正常工作了
