1. 總線、設備和驅動
1.1 簡單介紹
????????? Linux設備模型中三個很重要的概念就是總線、設備和驅動,即bus,device和driver。它們分別對應的數據結構分別為struct bus_type,struct device和struct device_driver。
????總線是處理器與一個或多個設備之間的通道,在設備模型中,所有的設備都通過總線相連。在最底層,Linux系統中的每一個設備都用device結構的一個實例來表示。而驅動則是使總線上的設備能夠完成它應該完成的功能。
????在系統中有多種總線,如PCI總線、SCSI總線等。系統中的多個設備和驅動是通過總線讓它們聯系起來的。在bus_type中兩個很重要的成員就是struct kset drivers和struct kset devices。它分別代表了連接在這個總線上的兩個鏈,一個是設備鏈表,另一個則是設備驅動鏈表。也就是說,通過一個總線描述符,就可以找到掛載到這條總線上的設備,以及支持該總線的不同的設備驅動程序。
1.2 總線、設備與驅動的綁定
????????? 在系統啟動時,它會對每種類型的總線創建一個描述符,并將使用該總線的設備鏈接到該總線描述符的devices鏈上來。也即是說在系統初始化時,它會掃描連接了哪些設備,并且為每個設備建立一個struce device變量,然后將該變量鏈接到這個設備所連接的總線的描述符上去。另一方面,每當加載了一個設備驅動,則系統也會準備一個struct device_driver結構的變量,然后再將這個變量也鏈接到它所在總線的描述符的drivers鏈上去。
????對于設備來說,在結構體struct device中有兩個重要的成員,一個是struct bus_type *bus,另一個是struct device_driver *driver。bus成員就表示該設備是鏈接到哪一個總線上的,而driver成員就表示當前設備是由哪個驅動程序所驅動的。對于驅動程序來說,在結構體struct device_driver中也有兩個成員,struct bus_type *bus和struct list_head devices,這里的bus成員也是指向這個驅動是鏈接到哪個總線上的,而devices這個鏈表則是表示當前這個驅動程序可以去進行驅動的那些設備。一個驅動程序可以支持一個或多個設備,而一個設備則只會綁定給一個驅動程序。
????對于device與device_driver之間建立聯系的方式,主要有兩種方式。第一種,在計算機啟動的時候,總線開始掃描連接在其上的設備,為每個設備建立一個struct device變量并鏈接到該總線的devices鏈上,然后開始初始化不同的驅動程序,驅動程序到它所在的總線的devices鏈上去遍歷每一個還沒有被綁定給某個驅動的設備,然后再查看是否能夠支持這種設備,如果它能夠支持這種設備,則將這個設備與這個驅動聯系起來。即,將這個設備的device變量加到驅動的devices鏈上,同時讓struct device中的device_driver指向當前這個驅動。第二種則是熱插拔。也即是在系統運行時插入了設備,此時內核會去查找在該bus鏈上注冊了的device_driver,然后再將設備與驅動聯系起來。設備與驅動根據什么規則聯系起來,它們是如何被聯系起來的代碼我們將在后面的章節進行詳細的描述。
1.3 PCI總線
????PCI是一種在CPU與I/O設備之間進行高速數據傳輸的一種總線。有很多設備都是使用PCI總線的,網卡就是其中之一。我們在前面講了那些總線、設備與驅動方面的知識,原因就在于網卡是連接到PCI總線上,所以PCI總線、網卡設備以及網卡驅動就成了我們研究網卡的一個很重要的線索,尤其是在網絡的鏈路層部分。下圖顯示了在一個系統中PCI設備的一個框圖:
? ?? ???PCI子系統聲明了一個bus_type結構,為pci_bus_type。它就是PCI總線的描述符。在這個變量上,鏈接了PCI設備以及支持PCI設備的驅動程序。
1.4 PCI設備與驅動
???????PCI設備通常由一組參數唯一地標識,它們被vendorID,deviceID和class nodes所標識,即設備廠商,型號等,這些參數保存在pci_device_id結構中。每個PCI設備都會被分配一個pci_dev變量,內核就用這個數據結構來表示一個PCI設備。
???????所有的PCI驅動程序都必須定義一個pci_driver結構變量,在該變量中包含了這個PCI驅動程序所提供的不同功能的函數,同時,在這個結構中也包含了一個device_driver結構,這個結構定義了PCI子系統與PCI設備之間的接口。在注冊PCI驅動程序時,這個結構將被初始化,同時這個pci_driver變量會被鏈接到pci_bus_type中的驅動鏈上去。
? ? ? ? 在pci_driver中有一個成員struct pci_device_id *id_table,它列出了這個設備驅動程序所能夠處理的所有PCI設備的ID值。
1.5 PCI設備與驅動的綁定過程
???????下面描述一下對于PCI設備與驅動綁定的過程。首先在系統啟動的時候,PCI總線會去掃描連接到這個總線上的設備,同時為每一個設備建立一個pci_dev結構,在這個結構中有一個device成員,并將這些pci_dev結構鏈接到PCI總線描述符上的devices鏈。如下圖所示:
????第二步是當PCI驅動被加載時,pci_driver結構體將被初始化,這一過程在函數pci_register_driver中:
????drv->driver.bus = &pci_bus_type;
??? drv->driver.probe = pci_device_probe;
????最后會調用driver_register(&drv->driver)將這個PCI驅動掛載到總線描述符的驅動鏈上。同時在注冊的過程中,會根據pci_driver中的id_table中的ID值去查看該驅動支持哪些設備,將這些設備掛載到pci_driver中的devices鏈中來。如下圖所示:
????對于不同的設備,可能驅動程序也不一樣,因此,對于上圖中的Dev3,可能就需要另外一個驅動程序來對其進行驅動。所以當加載了Dev3的驅動程序時,其示意圖如下圖所示:
????上面這三個示意圖就描述了總線、設備以及驅動在系統中是如何進行相互聯系的。前面對于驅動注冊這些函數的描述較為簡單,因為網卡是一個PCI設備,因此在后面具體地講到網卡注冊時再來詳細地講解和PCI相關的注冊等函數。
1.6 小結
????本部分主要講解了總線、設備以及驅動方面的一些知識,由于網卡是一個PCI設備,因此具體地講到了一點PCI總線、PCI設備及相應的PCI驅動方面的知識,但是由于PCI本身就是很大的一個子系統,因此這里不可能對其進行詳細地講解,在后面對網卡的分析中,將對網卡中涉及到的和PCI相關的部分進行講解。
?
2. 網卡在PCI層的注冊
2.1 數據結構
?????? 前面第一章講了總線、設備以及驅動方面的關系,也講到了大多數網卡設備實際上是一個PCI設備。因此,本章就講解網卡設備在注冊時是如何注冊到PCI總線上去的。在這里,以Intel的E100網卡驅動進行講解。
???????前面講到每個PCI設備都由一組參數唯一地標識,這些參數保存在結構體pci_device_id中,如下所示:
struct pci_device_id {? ? ? ? __u32 vendor, device;? ? ? ? ? ? ? ? /* Vendor and device ID or PCI_ANY_ID*/? ? ? ? __u32 subvendor, subdevice;? ? ? ? /* Subsystem ID's or PCI_ANY_ID */? ? ? ? __u32 class, class_mask;? ? ? ? /* (class,subclass,prog-if) triplet */? ? ? ? kernel_ulong_t driver_data;? ? ? ? /* Data private to the driver */};
? ? ? ? 每個PCI設備驅動都有一個pci_driver變量,它描述了一個PCI驅動的信息,如下所示:
struct pci_driver {? ? ? ? struct list_head node;? ? ? ? char *name;? ? ? ? const struct pci_device_id *id_table;? ? ? ? /* must be non-NULL for probe to be called */? ? ? ? int??(*probe)??(struct pci_dev *dev, const struct pci_device_id *id);? ? ? ? /* New device inserted */? ? ? ? void (*remove) (struct pci_dev *dev);? ? ? ? /* Device removed (NULL if not a hot-plug capable driver) */? ? ? ? int??(*suspend) (struct pci_dev *dev, pm_message_t state);? ? ? ? /* Device suspended */? ? ? ? int??(*suspend_late) (struct pci_dev *dev, pm_message_t state);? ? ? ? int??(*resume_early) (struct pci_dev *dev);? ? ? ? int??(*resume) (struct pci_dev *dev);? ? ? ?? ?? ?? ?? ?? ???/* Device woken up */? ? ? ? int??(*enable_wake) (struct pci_dev *dev, pci_power_t state, int enable);? ?/* Enable wake event */? ? ? ? void (*shutdown) (struct pci_dev *dev);? ? ? ? struct pci_error_handlers *err_handler;? ? ? ? struct device_driver? ? ? ? driver;? ? ? ? struct pci_dynids dynids;? ? ? ? int multithread_probe;};
? ? ? ? 每個PCI驅動中都有一個id_table成員變量,記錄了當前這個驅動所能夠進行驅動的那些設備的ID值。
? ? ? ? 對于E100網卡驅動來說,它的pci_driver變量定義為:
static struct pci_driver e100_driver = {? ? ? ? .name =? ?? ?? ?DRV_NAME,? ? ? ? .id_table =? ???e100_id_table,? ? ? ? .probe =? ?? ???e100_probe,? ? ? ? .remove =? ?? ? __devexit_p(e100_remove),#ifdef CONFIG_PM? ? ? ? /* Power Management hooks */? ? ? ? .suspend =? ?? ?e100_suspend,? ? ? ? .resume =? ?? ? e100_resume,#endif? ? ? ? .shutdown =? ???e100_shutdown,? ? ? ? .err_handler = &e100_err_handler,};
? ? ? ? 里面e100_id_table就表示該E100驅動所能夠支持的PCI設備的ID號,其定義為:
#define INTEL_8255X_ETHERNET_DEVICE(device_id, ich) {/? ? ? ? PCI_VENDOR_ID_INTEL, device_id, PCI_ANY_ID, PCI_ANY_ID, /? ? ? ? PCI_CLASS_NETWORK_ETHERNET << 8, 0xFFFF00, ich }static struct pci_device_id e100_id_table[] = {? ? ? ? INTEL_8255X_ETHERNET_DEVICE(0x1029, 0),? ? ? ? INTEL_8255X_ETHERNET_DEVICE(0x1030, 0),? ? ? ? …? ? ? ? { 0, }};
? ? ? ? 當PCI層檢測到一個PCI設備能夠被某PCI驅動所支持時(這是通過函數pci_match_one_device來進行檢測的),就會調用這個PCI驅動上的probe函數,在該函數中會對該特定的PCI設備進行一些具體的初始化等操作。比如對于E100設備驅動來說,其probe函數為e100_probe。在這個函數中,會對網卡設備進行初始化。
? ? ? ? e100_probe主要就涉及到網卡設備net_device的初始化,我們現在先來關注一下從網卡注冊一直到調用e100_probe這一個過程的整個流程。
2.2 E100初始化
??????? E100驅動程序的初始化是在函數e100_init_module()中的,如下:
static int __init e100_init_module(void){? ? ? ? if(((1 << debug) - 1) & NETIF_MSG_DRV) {? ? ? ? ? ? ? ? printk(KERN_INFO PFX "%s, %s/n", DRV_DESCRIPTION, DRV_VERSION);? ? ? ? ? ? ? ? printk(KERN_INFO PFX "%s/n", DRV_COPYRIGHT);? ? ? ? }? ? ? ? return pci_register_driver(&e100_driver);}
? ? ? ? 在這個函數中,調用了pci_register_driver()函數,對e100_driver這個驅動進行注冊。
2.3 PCI注冊
???????在前面我們已經看到,PCI的注冊就是將PCI驅動程序掛載到其所在的總線的drivers鏈,同時掃描PCI設備,將它能夠進行驅動的設備掛載到driver上的devices鏈表上來,這里,我們將詳細地查看這整個流程的函數調用關系。
? ? ? ? pci_register_driver()->__pci_register_driver()? //注意:這可是2個不同的函數
/*** __pci_register_driver - register a new pci driver* @drv: the driver structure to register* @owner: owner module of drv* @mod_name: module name string* * Adds the driver structure to the list of registered drivers.* Returns a negative value on error, otherwise 0. * If no error occurred, the driver remains registered even if * no device was claimed during registration.*/? ? ? ? int __pci_register_driver(struct pci_driver *drv, struct module *owner, const char *mod_name);??????? //在函數中有幾個初始化語句:? ? ? ? drv->driver.name = drv->name;? ? ? ? drv->driver.bus = &pci_bus_type;? ? ? ? drv->driver.owner = owner;? ? ? ? drv->driver.mod_name = mod_name;
? ? ? ? 即是將PCI設備中的driver變量的總線指向pci_bus_type這個總線描述符,同時設置驅動的名字等。
? ? ? ? pci_bus_type定義如下:
struct bus_type pci_bus_type = {? ? ? ? .name? ? ? ? ? ? ? ? = "pci",? ? ? ? .match? ? ? ? ? ? ? ? = pci_bus_match,? ? ? ? .uevent? ? ? ? ? ? ? ? = pci_uevent,? ? ? ? .probe? ? ? ? ? ? ? ? = pci_device_probe,? ? ? ? .remove? ? ? ? ? ? ? ? = pci_device_remove,? ? ? ? .suspend? ? ? ? = pci_device_suspend,? ? ? ? .suspend_late? ? ? ? = pci_device_suspend_late,? ? ? ? .resume_early? ? ? ? = pci_device_resume_early,? ? ? ? .resume? ? ? ? ? ? ? ? = pci_device_resume,? ? ? ? .shutdown? ? ? ? = pci_device_shutdown,? ? ? ? .dev_attrs? ? ? ? = pci_dev_attrs,};
? ? ? ? 然后再調用函數driver_register(&drv->driver);通過這個函數將這個PCI驅動中的struct device_driver driver成員變量注冊到系統中去。
? ? ? ? pci_register_driver()->__pci_register_driver()->driver_register()
? ? ? ? driver_register()代碼如下:
/***? ? ? ? driver_register - register driver with bus*? ? ? ? @drv:? ? ? ? driver to register**? ? ? ? We pass off most of the work to the bus_add_driver() call,*? ? ? ? since most of the things we have to do deal with the bus*? ? ? ? structures.**? ? ? ? The one interesting aspect is that we setup @drv->unloaded*? ? ? ? as a completion that gets complete when the driver reference*? ? ? ? count reaches 0.*/int driver_register(struct device_driver * drv){? ? ? ? if ((drv->bus->probe && drv->probe) ||? ? ? ?? ???(drv->bus->remove && drv->remove) ||? ? ? ?? ???(drv->bus->shutdown && drv->shutdown)) {? ? ? ? ? ? ? ? printk(KERN_WARNING "Driver '%s' needs updating - please use bus_type methods/n", drv->name);? ? ? ? }? ? ? ? klist_init(&drv->klist_devices, NULL, NULL);? ? ? ? init_completion(&drv->unloaded);? ? ? ? return bus_add_driver(drv);}
? ? ? ? klist_init()是為設備驅動的klist_devices成員進行初始化,這個klist_devices是一個對鏈表進行操作的包裹結構,它會鏈接這個驅動能夠支持的那些設備。
? ? ? ? 最后就調用bus_add_driver()函數。這個函數的功能就是將這個驅動加到其所在的總線的驅動鏈上。
? ? ? ? pci_register_driver()->__pci_register_driver()->driver_register()->bus_add_driver()->driver_attach()
? ? ? ? 在bus_add_driver()函數中,最重要的是調用driver_attach()函數,其定義如下:
/***? ? ? ? driver_attach - try to bind driver to devices.*? ? ? ? @drv:? ? ? ? driver.**? ? ? ? Walk the list of devices that the bus has on it and try to*? ? ? ? match the driver with each one.??If driver_probe_device()*? ? ? ? returns 0 and the @dev->driver is set, we've found a*? ? ? ? compatible pair.*/int driver_attach(struct device_driver * drv){? ? ? ? return bus_for_each_dev(drv->bus, NULL, drv, __driver_attach);}
? ? ? ? 該函數遍歷這個驅動所在的總線上的所有設備,然后將這些設備與當前驅動進行匹配,以檢測這個驅動是否能夠支持某個設備,也即是將設備與驅動聯系起來。
? ? ? ? bus_for_each_dev函數是掃描在drv->bus這個總線上的所有設備,然后將每個設備以及當前驅動這兩個指針傳遞給__driver_attach函數。
? ? ? ? pci_register_driver()->__pci_register_driver()->driver_register()->bus_add_driver()->driver_attach()->__driver_attach()
? ? ? ? __driver_attach()函數是將驅動與設備聯系起來的函數。
static int __driver_attach(struct device * dev, void * data){? ? ? ? struct device_driver * drv = data;? ? ? ? /*? ? ? ???* Lock device and try to bind to it. We drop the error? ? ? ???* here and always return 0, because we need to keep trying? ? ? ???* to bind to devices and some drivers will return an error? ? ? ???* simply if it didn't support the device.? ? ? ???*? ? ? ???* driver_probe_device() will spit a warning if there? ? ? ???* is an error.? ? ? ???*/? ? ? ? if (dev->parent)? ? ? ? /* Needed for USB */? ? ? ? ? ? ? ? down(&dev->parent->sem);? ? ? ? down(&dev->sem);? ? ? ? if (!dev->driver)? ? ? ? ? ? ? ? driver_probe_device(drv, dev);? ? ? ? up(&dev->sem);? ? ? ? if (dev->parent)? ? ? ? ? ? ? ? up(&dev->parent->sem);? ? ? ? return 0;}
? ? ? ? 在函數中有兩條語句:
? ? ? ? if (!dev->driver)? ? ? ? ? ? ? ? driver_probe_device(drv, dev);
? ? ? ? 也即是判斷當前設備是否已經注冊了一個驅動,如果沒有注冊驅動,則調用driver_probe_device()函數。
? ? ? ? pci_register_driver()->__pci_register_driver()->driver_register()->bus_add_driver()->driver_attach()->__driver_attach()->driver_probe_device()
? ? ? ? 如下:
/*** driver_probe_device - attempt to bind device & driver together* @drv: driver to bind a device to* @dev: device to try to bind to the driver** First, we call the bus's match function, if one present, which should* compare the device IDs the driver supports with the device IDs of the* device. Note we don't do this ourselves because we don't know the* format of the ID structures, nor what is to be considered a match and* what is not.** This function returns 1 if a match is found, an error if one occurs* (that is not -ENODEV or -ENXIO), and 0 otherwise.** This function must be called with @dev->sem held.??When called for a* USB interface, @dev->parent->sem must be held as well.*/int driver_probe_device(struct device_driver * drv, struct device * dev){? ? ? ? struct stupid_thread_structure *data;? ? ? ? struct task_struct *probe_task;? ? ? ? int ret = 0;? ? ? ? if (!device_is_registered(dev))? ? ? ? ? ? ? ? return -ENODEV;? ? ? ? if (drv->bus->match && !drv->bus->match(dev, drv))? ? ? ? ? ? ? ? goto done;? ? ? ? pr_debug("%s: Matched Device %s with Driver %s/n",? ? ? ? ? ? ? ???drv->bus->name, dev->bus_id, drv->name);? ? ? ? data = kmalloc(sizeof(*data), GFP_KERNEL);? ? ? ? if (!data)? ? ? ? ? ? ? ? return -ENOMEM;? ? ? ? data->drv = drv;? ? ? ? data->dev = dev;? ? ? ? if (drv->multithread_probe) {? ? ? ? ? ? ? ? probe_task = kthread_run(really_probe, data,? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ???"probe-%s", dev->bus_id);? ? ? ? ? ? ? ? if (IS_ERR(probe_task))? ? ? ? ? ? ? ? ? ? ? ? ret = really_probe(data);? ? ? ? } else? ? ? ? ? ? ? ? ret = really_probe(data);done:? ? ? ? return ret;}? ? ? ?
? ? ? ? 該函數首先會調用總線上的match函數,以判斷當前的PCI驅動能否支持該PCI設備,如果可以,則繼續往后面執行。
? ? ? ? drv->bus->match函數也即是pci_bus_type中的match成員變量,它為pci_bus_match函數。
? ? ? ? pci_register_driver()->__pci_register_driver()->driver_register()->bus_add_driver()->driver_attach()->__driver_attach()->driver_probe_device()->pci_bus_match()
/*** pci_bus_match - Tell if a PCI device structure has a matching PCI device id structure* @dev: the PCI device structure to match against* @drv: the device driver to search for matching PCI device id structures* * Used by a driver to check whether a PCI device present in the* system is in its list of supported devices. Returns the matching* pci_device_id structure or %NULL if there is no match.*/static int pci_bus_match(struct device *dev, struct device_driver *drv){? ? ? ? struct pci_dev *pci_dev = to_pci_dev(dev);? ? ? ? struct pci_driver *pci_drv = to_pci_driver(drv);? ? ? ? const struct pci_device_id *found_id;? ? ? ? found_id = pci_match_device(pci_drv, pci_dev);? ? ? ? if (found_id)? ? ? ? ? ? ? ? return 1;? ? ? ? return 0;}
? ? ? ? pci_bus_match函數的作用就是將PCI設備與PCI驅動進行比較以檢查該驅動是否能夠支持這個設備。在函數的最前面是兩個宏to_pci_dev和to_pci_driver。因為在函數執行的過程中,雖然最開始傳進來的是pci_driver結構與pci_dev結構,但是在執行的時候卻取了這兩個結構體中的device_driver和device成員變量,所以現在就要通過這兩個成員變量找到之前對應的pci_driver和pci_dev結構的地址。
#define? ? ? ? to_pci_dev(n) container_of(n, struct pci_dev, dev)
#define? ? ? ? to_pci_driver(drv) container_of(drv,struct pci_driver, driver)
? ? ? ? 這兩個宏在<Linux Device Driver> 3rd書上有相應的講解,這里也就是找到E100的pci_driver:e100_driver以及該網卡設備的pci_dev結構。現在就要對它們進行比較以看它們之間是否能夠聯系起來。這是通過函數pci_match_device實現的。
? ? ? ? pci_register_driver()->__pci_register_driver()->driver_register()->bus_add_driver()->driver_attach()->__driver_attach()->driver_probe_device()->pci_bus_match()->pci_match_device()
/*** pci_match_device - Tell if a PCI device structure has a matching PCI device id structure* @drv: the PCI driver to match against* @dev: the PCI device structure to match against** Used by a driver to check whether a PCI device present in the* system is in its list of supported devices.??Returns the matching* pci_device_id structure or %NULL if there is no match.*/const struct pci_device_id *pci_match_device(struct pci_driver *drv,? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?? ?? ?struct pci_dev *dev){? ? ? ? struct pci_dynid *dynid;? ? ? ? /* Look at the dynamic ids first, before the static ones */? ? ? ? spin_lock(&drv->dynids.lock);? ? ? ? list_for_each_entry(dynid, &drv->dynids.list, node) {? ? ? ? ? ? ? ? if (pci_match_one_device(&dynid->id, dev)) {? ? ? ? ? ? ? ? ? ? ? ? spin_unlock(&drv->dynids.lock);? ? ? ? ? ? ? ? ? ? ? ? return &dynid->id;? ? ? ? ? ? ? ? }? ? ? ? }? ? ? ? spin_unlock(&drv->dynids.lock);? ? ? ? return pci_match_id(drv->id_table, dev);}
? ? ? ? pci_match_one_driver函數的作用是將一個PCI設備與PCI驅動進行比較,以查看它們是否相匹配。如果相匹配,則返回匹配的pci_device_id結構體指針。
? ? ? ? 此時,如果該PCI驅動已經找到了一個可以想符的PCI設備,則返回,然后再退回到之前的driver_probe_device函數中。在該函數最后將調用really_probe函數。將device_driver與device結構體指針作為參數傳遞到這個函數中。下面幾行是調用驅動或者總線的probe函數來掃描設備。
? ? ? ? pci_register_driver()->__pci_register_driver()->driver_register()->bus_add_driver()->driver_attach()->__driver_attach()->driver_probe_device()->really_probe()
? ? ? ? 在函數really_probe()中:
? ? ? ? if (dev->bus->probe) {? ? ? ? ? ? ? ? ret = dev->bus->probe(dev);? ? ? ? ? ? ? ? if (ret)? ? ? ? ? ? ? ? ? ? ? ? goto probe_failed;? ? ? ? } else if (drv->probe) {? ? ? ? ? ? ? ? ret = drv->probe(dev);? ? ? ? ? ? ? ? if (ret)? ? ? ? ? ? ? ? ? ? ? ? goto probe_failed;? ? ? ? }
? ? ? ? 此時的dev->bus為pci_bus_type,其probe函數則對應為:pci_device_probe。
? ? ? ? pci_register_driver()->__pci_register_driver()->driver_register()->bus_add_driver()->driver_attach()->__driver_attach()->driver_probe_device()->really_probe()->pci_device_probe()
? ? ? ? 同樣,在該函數中會獲得當前的PCI設備的pci_dev結構體指針以及PCI驅動程序的pci_driver結構體指針。分別使用宏to_pci_dev和to_pci_driver。最后則調用函數__pci_device_probe。在該函數中還會調用函數pci_call_probe,這是最后的函數
? ? ? ? pci_register_driver()->__pci_register_driver()->driver_register()->bus_add_driver()->driver_attach()->__driver_attach()->driver_probe_device()->really_probe()->pci_device_probe()->__pci_device_probe()->pci_call_probe()
? ? ? ? 在函數pci_call_probe里有一條語句:
static int pci_call_probe(struct pci_driver *drv, struct pci_dev *dev,? ? ? ? ? ? ? ? ? ? ? ?? ?const struct pci_device_id *id){? ? ? ? int error;/*??省略??*/? ? ? ? error = drv->probe(dev, id);
? ? ? ? 在此處就調用了pci_driver的probe函數,對于這里的E100驅動來說,它的probe函數是最開始注冊的e100_probe函數,在該函數中會完成對網卡設備net_device的初始化等操作。
? ? ? ? pci_register_driver()->__pci_register_driver()->driver_register()->bus_add_driver()->driver_attach()->__driver_attach()->driver_probe_device()->really_probe()->pci_device_probe()->__pci_device_probe()->pci_call_probe()->e100_probe()
? ? ? ? 到這里,我們對網卡驅動的PCI層的初始化分析就告一個段落了,剩下的部分就是網卡驅動對網卡設備本身的初始化等操作。
2.4 函數調用流程圖
?????? 在這里,為網卡在PCI層的注冊畫了一個函數調用的流程圖,能夠更直觀地展現網卡從注冊到調用其自身的網卡初始化的這一個函數調用過程。
?
?
總結
以上是生活随笔為你收集整理的PCI驱动的注册和初始化的全部內容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。