標籤:核心 kernel pci 驅動
水平有限,錯誤難免 ^_^
參考資料:
1) 《Linux核心原始碼情景分析》
2) Linux核心原始碼(2.6.32)。
本文只討論比較簡單的軟硬體設定情境。
系統中的第一條PCI匯流排(即主PCI匯流排),掛在“宿主—PCI橋”上。
CPU通過“宿主——PCI橋”就可以訪問主PCI匯流排了。
PC機中通常只有一個“宿主—PCI橋”。但是,通過引入其他類型的PCI橋,可以將更多的匯流排(可以是PCI匯流排,也可以是ISA匯流排)串連到主PCI匯流排上來。這樣一來,系統中就可以有多條匯流排存在了。
下層PCI匯流排也可以進一步通過PCI橋,將更下一層的PCI匯流排串連進來。
在上層匯流排看來,PCI橋也是串連到本匯流排上的一個裝置。
主PCI匯流排的編號是0,其他的pci匯流排編號則從1開始依次遞增。
每一條PCI匯流排,可以掛接32個PCI匯流排介面晶片。每個PCI裝置(注意,這裡的裝置概念後面需要進一步解釋)都是通過一個PCI匯流排介面晶片串連到PCI主線上。
這樣的話,每條PCI匯流排,最大支援32個裝置。裝置編號0~31
裝置可以固化在主板上,也可以做成一個PCI介面卡,通過一個PCI插槽串連到系統中。
這樣的話,每個PCI插槽對應一個PCI匯流排介面晶片。
上面說了,這裡提到的裝置的概念需要進一步解釋。這裡就來說說吧。
PCI匯流排上的一個裝置,可以包含1~8個功能,編號是0~7。每個功能稱為一個邏輯裝置。
有些裝置可能只包含一個邏輯裝置。例如,一塊網卡,上面可能就一個邏輯裝置。
在Linux核心角度來看,這些邏輯裝置才是最終的裝置。
這樣的話,每條PCI匯流排,最大支援32*8個邏輯裝置,即256個邏輯裝置。
通過lspci命令,可以查看系統中的所有pci裝置(邏輯裝置)。
下面是此命令的輸出結果的一行,顯示了一塊網卡的資訊:1號匯流排,1號裝置,0號功能。
01:01.0 Ethernet controller: Realtek Semiconductor Co., Ltd. RTL-8139/8139C/8139C+ (rev 10)
下面看看Linux核心中pci相關的部分代碼
下面的pci_subsys_init是核心啟動過程中會調用的pci子系統的初始化函數。
subsys_initcall(pci_subsys_init)將他指定為子系統級的初始化函數。
我們這裡只看最簡單的情況,因此忽略掉那幾個條件編譯項。
這樣的話,我們從pci_legacy_init函數看起。
int __init pci_subsys_init(void){#ifdef CONFIG_X86_NUMAQpci_numaq_init();#endif#ifdef CONFIG_ACPIpci_acpi_init();#endif#ifdef CONFIG_X86_VISWSpci_visws_init();#endifpci_legacy_init();pcibios_fixup_peer_bridges();pcibios_irq_init();pcibios_init();return 0;}subsys_initcall(pci_subsys_init);
下面是pci_legacy_init的代碼,實際關鍵的代碼就是
調用pcibios_scan_root(0)掃描0號pci匯流排,
調用pci_bus_add_devices(pci_root_bus),將0號pci匯流排上掃描到的裝置添加到系統中。
static int __init pci_legacy_init(void){if (!raw_pci_ops) {printk("PCI: System does not support PCI\n");return 0;}if (pcibios_scanned++)return 0;printk("PCI: Probing PCI hardware\n");pci_root_bus = pcibios_scan_root(0);if (pci_root_bus)pci_bus_add_devices(pci_root_bus);return 0;}
再看pcibios_scan_root函數。
while結構應該是在遍曆已經探測到的匯流排。首次執行裡,自然什麼都遍曆不到。
因此,最終進入 pci_scan_bus_parented,進而進入pci_scan_child_bus去掃描匯流排上的裝置(這裡只關注核心部分,因此跳得有點快^_^)。
struct pci_bus * __devinit pcibios_scan_root(int busnum){struct pci_bus *bus = NULL;struct pci_sysdata *sd;while ((bus = pci_find_next_bus(bus)) != NULL) {if (bus->number == busnum) {/* Already scanned */return bus;}}/* Allocate per-root-bus (not per bus) arch-specific data.* TODO: leak; this memory is never freed.* It's arguable whether it's worth the trouble to care.*/sd = kzalloc(sizeof(*sd), GFP_KERNEL);if (!sd) {printk(KERN_ERR "PCI: OOM, not probing PCI bus %02x\n", busnum);return NULL;}sd->node = get_mp_bus_to_node(busnum);printk(KERN_DEBUG "PCI: Probing PCI hardware (bus %02x)\n", busnum);bus = pci_scan_bus_parented(NULL, busnum, &pci_root_ops, sd);if (!bus)kfree(sd);return bus;}
pci_scan_child_bus的核心代碼如下:
for (devfn = 0; devfn < 0x100; devfn += 8)pci_scan_slot(bus, devfn);
可見共掃描32個插槽,每個插槽掃描8個功能,即8個邏輯裝置。
這裡的devfn,是devicefunction的縮寫,即“裝置功能”的意思。
相當於是裝置號與功能號的組合,高位是裝置號,低3-bit是功能號。
再來看pci_scan_slot函數。先掃描出一個邏輯裝置,如果是多功能裝置,再掃描出其餘的邏輯裝置。
針對掃描出的每一個邏輯裝置,建立一個struct pci_dev結構,掛入所屬的pci匯流排(每條匯流排由一個struct pci_bus結構表示)的devices鏈表中。
具體實現,看pci_scan_single_device函數的實現就知道了。
struct pci_dev結構中,有一個devfn欄位,其值就來自pci_scan_single_device傳入的參數devfn + fn。
因此,他是裝置號與功能號的組合:高位是裝置號,低3-bit是功能號。
int pci_scan_slot(struct pci_bus *bus, int devfn){int fn, nr = 0;struct pci_dev *dev;dev = pci_scan_single_device(bus, devfn);if (dev && !dev->is_added)/* new device? */nr++;if (dev && dev->multifunction) {for (fn = 1; fn < 8; fn++) {dev = pci_scan_single_device(bus, devfn + fn);if (dev) {if (!dev->is_added)nr++;dev->multifunction = 1;}}}/* only one slot has pcie device */if (bus->self && nr)pcie_aspm_init_link_state(bus->self);return nr;}
好了,邏輯裝置掃描完了,每一個邏輯裝置也都掛入了所屬的pci匯流排的devices鏈表中了。
再回到pci_legacy_init函數來看,掃描完邏輯裝置,調用pci_bus_add_devices(pci_root_bus)將邏輯裝置添加到系統中。
pci_bus_add_devices的核心是調用pci_bus_add_device函數。
接下來的核心調用鏈是:
device_add --) bus_probe_device --) device_attach --) bus_for_each_drv --) __device_attach --) driver_probe_device --) really_probe --) dev->bus->probe(即pci_bus_type.probe,也即pci_device_probe)
pci_device_probe最終會調用相應的pci裝置驅動(由struct pci_driver結構表示)的probe函數。
總之,從上層來說,device_add函數,用於向系統添加一個裝置,此函數會嘗試將被添加的邏輯裝置與系統中的驅動程式進行一次匹配。
另外,當一個pci驅動被載入到核心中時,也會將系統中尚未匹配驅動程式的邏輯裝置與此驅動進行一次匹配嘗試(調用鏈pci_register_driver --) __pci_register_driver --) driver_register --) bus_add_driver --) driver_attach --) bus_for_each_dev --) __driver_attach --) driver_probe_device)。
pci裝置學習筆記