PCI總線設備遍歷

在梳理內核驅動程序時,發現一個問題,當注入內核驅動程序時,內核會根據驅動結構體(比如: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設備。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章