我們在 淺談Linux PCI設備驅動(dòng)(一)中(以下簡(jiǎn)稱(chēng) 淺談(一) )介紹了PCI的配置寄存器組,而Linux PCI初始化就是使用了這些寄存器來(lái)進(jìn)行的。后面我們會(huì )舉個(gè)例子來(lái)說(shuō)明Linux PCI設備驅動(dòng)的主要工作內容(不是全部?jì)热?,這里只做文字性的介紹而不會(huì )涉及具體代碼的分析,因為要分析代碼的話(huà),基本就是對 Linux內核源代碼情景分析(下冊)第八章的解讀,讀者若想分析代碼,可以參考該書(shū)的內容,我們這里就不去深入分析這些代碼了。
Linux PCI設備驅動(dòng)代碼必須掃描系統中所有的PCI總線(xiàn),尋找系統中所有的PCI設備(包括PCI-PCI橋設備)。系統中的每條PCI總線(xiàn)都有個(gè)編號number,根PCI總線(xiàn)的編號為0。系統當前存在的所有根總線(xiàn)(因為可能存在不止一個(gè)Host/PCI橋,那么就可能存在多條根總線(xiàn)) 都通過(guò)其pci_bus結構體中的node成員鏈接成一個(gè)全局的根總線(xiàn)鏈表,其表頭由struct list_head類(lèi)型的全局變量pci_root_buses來(lái)描述,我們在/linux-2.4.18/linux/drivers/pci/pci.c的38行可以看到如下定義:
LIST_HEAD(pci_root_buses);
而根總線(xiàn)下面的所有下級總線(xiàn)則都通過(guò)其pci_bus結構體中的node成員鏈接到其父總線(xiàn)的children鏈表中。這樣,通過(guò)這兩種PCI總線(xiàn)鏈表,Linux內核就將所有的pci_bus結構體以一種倒置樹(shù)的方式組織起來(lái)。
另外,每個(gè)PCI設備都由一個(gè)pci_dev結構體表示,每個(gè)pci_dev結構體都同時(shí)連入兩個(gè)隊列,一方面通過(guò)其成員global_list掛入一個(gè)總的pci_dev結構隊列(隊列頭是pci_devices);同時(shí)又通過(guò)成員bus_list掛入其所在總線(xiàn)的pci_dev結構隊列devices(隊列頭是pci_bus.devices,即該pci設備所在的pci總線(xiàn)的devices隊列),并且使指針bus(指pci_dev結構體里的bus成員)指向代表著(zhù)其所在總線(xiàn)的pci_bus結構。如果具體的設備是PCI-PCI橋,則還要使其指針subordinate指向代表著(zhù)另一條PCI總線(xiàn)的pci_bus結構。同樣我們在/linux-2.4.18/linux/drivers/pci/pci.c的39行可以看到如下定義:
LIST_HEAD(pci_devices);
對于PCI設備鏈表,我們可以通過(guò)圖1來(lái)理解。
注:該圖摘自Linux設備驅動(dòng)開(kāi)發(fā)詳解 第21章 PCI設備驅動(dòng)。

圖1 Linux PCI設備鏈表
而對于我們在淺談(一)中貼出的圖1的PCI系統結構示意圖,Linux內核中對應的數據結構如這里的圖2所示。

圖2 Linux內核PCI數據結構
Linux PCI初始化代碼從PCI總線(xiàn)0開(kāi)始掃描,它通過(guò)讀取"Vendor ID"和"Device ID"來(lái)試圖發(fā)現每一個(gè)插槽上的設備。如果發(fā)現了一個(gè)PCI-PCI橋,則創(chuàng )建一個(gè)pci_bus數據結構并且連入到由pci_root_buses指向的pci_bus和pci_dev數據結構組成的樹(shù)中。PCI初始化代碼通過(guò)設備類(lèi)代碼0x060400來(lái)判斷一個(gè)PCI設備是否是PCI-PCI橋。然后,Linux核心開(kāi)始構造這個(gè)橋設備另一端的PCI總線(xiàn)和其上的設備。如果還發(fā)現了橋設備,就以同樣的步驟來(lái)進(jìn)行構建。這個(gè)處理過(guò)程稱(chēng)之為深度優(yōu)先算法。PCI-PCI橋橫跨在兩條總線(xiàn)之間,寄存器PCI_PRIMARY_BUS和PCI_SECONDARY_BUS的內容就說(shuō)明了其上下兩端的總線(xiàn)號,其中PCI_SECONDARY_BUS就是該PCI-PCI橋所連接和控制的總線(xiàn),而PCI_SUBORDINATE_BUS則說(shuō)明自此以下、在以此為根的子樹(shù)中最大的總線(xiàn)號是什么。
我們可以在/linux-2.4.18/linux/include/linux/pci.h看到如下定義:
112: /* Header type 1 (PCI-to-PCI bridges) */
113: #define PCI_PRIMARY_BUS 0x18 /* Primary bus number */
114: #define PCI_SECONDARY_BUS 0x19 /* Secondary bus number */
115: #define PCI_SUBORDINATE_BUS 0x1a /* Highest bus number behind the bridge */
由于在枚舉階段做的是深度優(yōu)先掃描,所以子樹(shù)中的總線(xiàn)號總是連續遞增的。當CPU往I/O寄存器0xCF8中寫(xiě)入一個(gè)綜合地址以后,從0號總線(xiàn)開(kāi)始,每個(gè)PCI-PCI橋會(huì )把綜合地址中的總線(xiàn)號與自身的總線(xiàn)號相比,如果相符就用邏輯設備號在本總線(xiàn)上尋訪(fǎng)目標設備;否則就進(jìn)一步把這個(gè)總線(xiàn)號與PCI_SUBORDINATE_BUS中的內容相比,如果目標總線(xiàn)號落在當前子樹(shù)范圍中,就把綜合地址傳遞給其下的各個(gè)次層PCI-PCI橋,要不然就不予理睬。這樣,最終就會(huì )找到目標設備。當然,這個(gè)過(guò)程只是在PCI設備的配置階段需要這樣做,一旦配置完成,CPU就直接通過(guò)有關(guān)的總線(xiàn)地址訪(fǎng)問(wèn)目標設備了。
PCI-PCI橋要想正確傳遞對PCI I/O,PCI Memory或PCI Configuration地址空間的讀和寫(xiě)請求,必須知道下列信息:
(1)Primary Bus Number(主總線(xiàn)號)
該PCI-PCI橋所處的PCI總線(xiàn)稱(chēng)為主總線(xiàn)。
(2)Secondary Bus Number(子總線(xiàn)號)
該PCI-PCI橋所連接的PCI總線(xiàn)稱(chēng)為子總線(xiàn)/次總線(xiàn)號。
(3)Subordinate Bus Number
PCI總線(xiàn)的下屬PCI總線(xiàn)的總線(xiàn)編號最大值。有點(diǎn)繞,看后面的分析就明白了。
PCI I/O 和 PCI Memory 窗口
PCI橋的配置寄存器與一般的PCI設備不同。一般PCI設備可以有6個(gè)地址區間,外加一個(gè)ROM區間,代表著(zhù)設備上實(shí)際存在的存儲器或寄存器區間。而PCI橋,則本身并不一定有存儲器或寄存器區間,但是卻有三個(gè)用于地址過(guò)濾的區間。每個(gè)地址過(guò)濾區間決定了一個(gè)地址窗口,從CPU一側發(fā)出的地址,如果落在PCI橋的某個(gè)窗口內,就可以穿過(guò)PCI橋而到達其所連接的總線(xiàn)上。此外,PCI橋的命令寄存器中還有”memory access enable”和”I/O access enable ”的兩個(gè)控制位,當這兩個(gè)控制位為0時(shí),這些窗口就全都關(guān)上了。在未完成對PCI總線(xiàn)的初始化之前,還沒(méi)有為PCI設備上的各個(gè)區間分配合適的總線(xiàn)地址時(shí),正是因為這兩個(gè)控制位為0,才不會(huì )對CPU一側造成干擾。例如, 對于淺談(一)的 PCI系統示意圖 ,僅當讀和寫(xiě)請求中的PCI I/O或PCI memory地址屬于SCSI或Ethernet設備時(shí),PCI-PCI橋才將這些總線(xiàn)上的請求從PCI總線(xiàn)0傳遞到PCI總線(xiàn)1。這種過(guò)濾機制可以避免地址在系統中沒(méi)必要的繁衍。為了做到這點(diǎn),每個(gè)PCI-PCI橋必須正確地被設置好它所負責的PCI I/O或PCI memory的起始地址和大小。當一個(gè)讀或寫(xiě)請求地址落在其負責的范圍之內,這個(gè)請求將被映射到次級的PCI總線(xiàn)上。系統中的PCI-PCI橋一旦設置完畢,如果Linux中的設備驅動(dòng)程序存取的PCI I/O和PCI memory地址落在在這些窗口之內,那么這些PCI-PCI橋就是透明的。這是個(gè)很重要的特性,使得Linux PCI設備驅動(dòng)程序開(kāi)發(fā)者的工作容易些。
問(wèn)題是配置一個(gè)PCI-PCI橋的時(shí)候,并不知道這個(gè)PCI-PCI橋的subordinate bus number。那么就不知道該PCI橋下面是否還有其他的PCI-PCI橋。即使你知道,也不清楚如何對它們賦值。解決方法是利用上述的深度掃描算法來(lái)掃描每個(gè)總線(xiàn)。每當發(fā)現PCI-PCI橋就對它進(jìn)行賦值。當發(fā)現一個(gè)PCI-PCI橋時(shí),可以確定它的secondary bus number。然后我們暫時(shí)先將其subordinate bus number賦值為0xFF。緊接著(zhù),開(kāi)始掃描該PCI-PCI橋的downstream橋。這個(gè)過(guò)程看起來(lái)有點(diǎn)復雜,下面的例子將給出清晰的解釋?zhuān)?/span>

圖3 配置PCI系統 第一步
PCI-PCI橋編號--第一步
以圖3的拓撲結構為例,掃描時(shí)首先發(fā)現的橋是Bridge1。Bridge 1的downstream PCI總線(xiàn)號碼被賦值1。自然該橋的secondary bus number也是1。其subordinate bus number暫時(shí)賦值為0xFF。上述賦值的含義是所有類(lèi)型1的含有PCI總線(xiàn)1或更高(<255)的號碼的PCI配置地址將被Bridge 1傳遞到PCI總線(xiàn)1上。如果PCI總線(xiàn)號是1,Bridge 1 還負責將配置地址的類(lèi)型轉換成類(lèi)型0(對于這里說(shuō)的類(lèi)型0和類(lèi)型1,請參考淺談(一))。否則,就不做轉換。上述動(dòng)作就是開(kāi)始掃描總線(xiàn)1時(shí)Linux PCI初始化代碼所完成的對總線(xiàn)0的配置工作。

圖4 配置PCI系統 第二步
PCI-PCI橋編號--第二步
由于Linux PCI設備驅動(dòng)使用深度優(yōu)先算法進(jìn)行掃描,所以初始化代碼開(kāi)始掃描總線(xiàn)1。從而Bridge 2被發(fā)現。因為在Bridge 2下面發(fā)現不再有PCI-PCI橋,所以Bridge 2的subordinate bus number是2,等于它的secondary bus number。圖4顯示了在這個(gè)時(shí)刻總線(xiàn)和PCI-PCI橋的賦值情況。

圖5 配置PCI系統 第三步
PCI-PCI橋編號--第三步
Linux PCI設備驅動(dòng)代碼從總線(xiàn)2的掃描中回來(lái)接著(zhù)進(jìn)行掃描總線(xiàn)1,發(fā)現Bridge 3。它的primary bus number被賦值為1,secondary bus number為3。因為總線(xiàn)3上還發(fā)現了PCI-PCI橋,所以Bridge 3的subordinate bus number暫時(shí)賦值0xFF。圖5顯示了這個(gè)時(shí)刻系統配置的狀態(tài)。到目前為止,含有總線(xiàn)號1,2,3的類(lèi)型1的PCI配置都可以正確地傳送到相應的總線(xiàn)上。

圖6 配置PCI系統 第四步
PCI-PCI橋編號--第四步
現在Linux開(kāi)始掃描PCI總線(xiàn)3,Bridge 3的downstream。PCI總線(xiàn)3上有另外一個(gè)PCI-PCI橋,Bridge 4。因此Bridge 4的primary bus number的值為3,secondary bus number為4。由于Bridge 4下面沒(méi)有別的橋設備,所以Bridge 4的subordinate bus number為4。然后回到PCI-PCI Bridge 3。這時(shí)就將Bridge 3的subordinate bus number從0xFF改為4,表示總線(xiàn)4是從Bridge 3往下走的最遠的PCI-PCI橋。最后,Linux PCI設備驅動(dòng)代碼將4以同樣的道理賦值給Bridge 1的subordinate bus number。圖6反映了系統最后的狀態(tài)。
注:淺談Linux PCI設備驅動(dòng)(二)暫時(shí)的整體結構就是這樣了,后續可能還會(huì )有些細節上的修補和添加。在此強烈推薦想學(xué)LinuxPCI設備驅動(dòng)的朋友結合《Linux內核源代碼情景分析下冊》第八章和《Linux設備驅動(dòng)開(kāi)發(fā)詳解》第21章 來(lái)學(xué)習。感謝您關(guān)注本文。
聯(lián)系客服