在梳理內核驅動程序時,發現一個問題,當注入內核驅動程序時,內核會根據驅動結構體(比如:struct pci_driver)中的總線屬性(pci_driver->bus)來找到與驅動程序在同一總線上相對應的驅動設備。
因此,本文主要對pci設備在內核中的注入時間,以及注入過程來進行分析。查閱資料,得知關於不同CPU架構下的PCI設備的注入是以arch/cpu_type/pci/目錄下的文件爲入口來開始執行的。通過對其中的文件進行分析,最後執行的關鍵入口函數爲pci_bus_add_devices()函數。
void pci_bus_add_devices(const struct pci_bus *bus)
{
struct pci_dev *dev;
struct pci_bus *child;
list_for_each_entry(dev, &bus->devices, bus_list) {
//這裏,說明一下struct pci_bus結構體。
//struct pci_bus {
// ...
// struct pci_bus *parent; //pci總線的父節點,比如:PCI總線可以掛載在另一條PCI總線上。
// ...
// struct list_head devices; //pci總線上的設備鏈表,該鏈表中主要連接的結構體對象爲struct pci_dev。
// ...
// struct list_head slots; //pci總線上的PCI槽鏈表
// ...
// struct resource *resource[PCI_BRIDGE_RESOURCE_NUM]; //pci總線可用的資源,即I/O地址空間
// struct list_head resources;
// ...
// struct pci_ops *ops;
// ...
// struct device *bridge; //pci總線所使用的橋片
// struct device dev;
// ...
// unsigned int ia_added:1;
//};
if (pci_dev_is_added(dev))
//如果pci設備已經被添加,則遍歷下一個pci設備。
continue;
pci_bus_add_device(dev);
//如果沒有,則將pci設備添加到pci總線當中。
}
list_for_each_entry() {
if (!pci_dev_is_added(dev))
continue;
child = dev->subordinate;
if (child)
pci_bus_add_devices(child);
}
}
通過對上述函數的分析,可以知道此時,PCI設備信息已經被添加到struct pci_dev結構體當中,且已經存放到pci_bus的屬性devices所指向的鏈表結構中。而pci_bus的初始化主要通過pcibios_scanbus()函數來完成。
//根據傳參,可以知道該函數需要PCI控制器,這也就與CPU手冊上所描述的PCI控制器相對應
static void pcibios_scanbus(struct pci_controller *hose)
{
...
LIST_HEAD(resources);
struct pci_bus *bus;
struct pci_host_bridge *bridge;
...
bridge = pci_alloc_host_bridge(0);
//申請PCI主橋pci_host_bridge結構體對象,並對其進行初始化
...
pci_add_resource_offset(&resources, hose->mem_resource, hose->mem_offset);
pci_add_resource_offset(&resources, hose->io_resource, hose->io_offset);
pci_add_resource(&resources, hose->busn_resource);
list_splice_init(&resources, &bridge->windows);
//將PCI總線可用的I/O資源添加到resources鏈表中。並將該resources鏈表與pci_host_bridge中的windows屬性關聯。這裏resources爲struct resource類型的全局變量。
bridge->dev.parent = NULL;
...
ret = pci_scan_root_bus_bridge(bridge);
//該函數主要對上述申請的橋片進行註冊,註冊函數爲pci_register_host_bridge(bridge)。並對bridge結構體中的屬性進行初始化並在內核中創建相關的文件。
if (ret) {
pci_free_host_bridge(bridge);
return;
}
hose->bus = bus = bridge->bus;
...
list_for_each_entry(child, &bus->children, node)
pcie_bus_configure_settingd(child);
...
pci_bus_add_devices(bus);
}
int pci_scan_root_bus_bridge(struct pci_host_bridge *bridge)
{
...
struct pci_bus *b;
...
ret = pci_register_host_bridge(bridge);
//註冊pci總線所使用的橋片,並對其中的屬性(比如:bus)進行初始化。
...
b = bridge->bus;
...
max = pci_scan_child_bus(b);
//發現設備,並添加設備信息。其實際執行的函數爲pci_scan_child_bus_extend()函數。
...
return 0;
}
static unsigned int pci_scan_child_bus_extend(struct pci_bus *bus, unsigned int avaliable_buses)
{
...
for (devfn = 0; devfn < 256; devfn += 8) {
nr_devs = pci_scan_slot(bus, devfn);
//該函數用來判斷每個PCI槽中的設備個數。其中關鍵的執行函數爲pci_scan_single_device()函數。
if (jailhouse_paravirt() && nr_devs == 0) {
for (fn = 1; fn < 8; fn++) {
dev = pci_scan_single_device(bus, devfn + fn);
if (dev)
dev->multifunction = 1;
}
}
}
...
}
struct pci_dev *pci_scan_single_device(struct pci_bus *bus, int devfn)
{
struct pci_dev *dev;
dev = pci_get_slot(bus, devfn);
//獲取PCI槽設備
if (dev) {
pci_dev_put(dev);
return dev;
}
dev = pci_scan_device(bus, devfn);
//獲取PCI總線上的設備。獲取的方法主要通過bus找到pci_host_bridge結構體,即橋片的相關信,隨後通過橋片的信息來獲取到設備的vendor_id以及device_id。執行這一過程的函數主要由pci_bus_read_dev_vendor_id()函數來完成。
if (!dev)
return NULL;
pci_device_add(dev, bus);
return dev;
}
綜上,便是PCI設備信息在初始化階段的添加過程,在分析過程中,可見PCI控制器是包含所有PCI設備信息的關鍵描述,隨後是PCI橋片,PCI控制器中的信息會被存儲到所對應的橋片信息當中。因爲PCI設備都是通過橋片來與處理進行訪問,而PCI設備與橋片之間又是通過總線來連接,因此在初始完橋片之後,內核會初始該橋片的總線結構體,最後通過該總線反向獲取PCI控制器中的信息,從而添加PCI設備。