淺談Linux PCI裝置驅動(二)
我們在 淺談Linux PCI裝置驅動(一)中(以下簡稱 淺談(一) )介紹了PCI的配置暫存器組,而Linux PCI初始化就是使用了這些暫存器來進行的。後面我們會舉個例子來說明Linux PCI裝置驅動的主要工作內容(不是全部內容),這裡只做文字性的介紹而不會涉及具體程式碼的分析,因為要分析程式碼的話,基本就是對 Linux核心原始碼情景分析(下冊)第八章的解讀,讀者若想分析程式碼,可以參考該書的內容,我們這裡就不去深入分析這些程式碼了。
Linux PCI裝置驅動程式碼必須掃描系統中所有的PCI匯流排,尋找系統中所有的PCI裝置(包括PCI-PCI橋裝置)。系統中的每條PCI匯流排都有個編號number,根PCI匯流排的編號為0。系統當前存在的所有根匯流排(因為可能存在不止一個Host/PCI橋,那麼就可能存在多條根匯流排) 都通過其pci_bus結構體中的node成員連結成一個全域性的根匯流排連結串列,其表頭由struct list_head型別的全域性變數pci_root_buses來描述,我們在/linux-2.4.18/linux/drivers/pci/pci.c的38行可以看到如下定義:
LIST_HEAD(pci_root_buses);
而根匯流排下面的所有下級匯流排則都通過其pci_bus結構體中的node成員連結到其父匯流排的children連結串列中。這樣,通過這兩種PCI匯流排連結串列,Linux核心就將所有的pci_bus結構體以一種倒置樹的方式組織起來。
另外,每個PCI裝置都由一個pci_dev結構體表示,每個pci_dev結構體都同時連入兩個佇列,一方面通過其成員global_list掛入一個總的pci_dev結構佇列(佇列頭是pci_devices);同時又通過成員bus_list掛入其所在匯流排的pci_dev結構佇列devices(佇列頭是pci_bus.devices,即該pci裝置所在的pci匯流排的devices佇列),並且使指標bus(指pci_dev結構體裡的bus成員)指向代表著其所在匯流排的pci_bus結構。如果具體的裝置是PCI-PCI橋,則還要使其指標subordinate指向代表著另一條PCI匯流排的pci_bus結構。同樣我們在/linux-2.4.18/linux/drivers/pci/pci.c的39行可以看到如下定義:
LIST_HEAD(pci_devices);
對於PCI裝置連結串列,我們可以通過圖1來理解。
注:該圖摘自Linux裝置驅動開發詳解 第21章 PCI裝置驅動。
圖1 Linux PCI裝置連結串列
而對於我們在淺談(一)中貼出的圖1的PCI系統結構示意圖,Linux核心中對應的資料結構如這裡的圖2所示。
圖2 Linux核心PCI資料結構
Linux PCI初始化程式碼從PCI匯流排0開始掃描,它通過讀取"Vendor ID"和"Device ID"來試圖發現每一個插槽上的裝置。如果發現了一個PCI-PCI橋,則建立一個pci_bus資料結構並且連入到由pci_root_buses指向的pci_bus和pci_dev資料結構組成的樹中。PCI初始化程式碼通過裝置類程式碼0x060400來判斷一個PCI裝置是否是PCI-PCI橋。然後,Linux核心開始構造這個橋裝置另一端的PCI匯流排和其上的裝置。如果還發現了橋裝置,就以同樣的步驟來進行構建。這個處理過程稱之為深度優先演算法。PCI-PCI橋橫跨在兩條匯流排之間,暫存器PCI_PRIMARY_BUS和PCI_SECONDARY_BUS的內容就說明了其上下兩端的匯流排號,其中PCI_SECONDARY_BUS就是該PCI-PCI橋所連線和控制的匯流排,而PCI_SUBORDINATE_BUS則說明自此以下、在以此為根的子樹中最大的匯流排號是什麼。
我們可以在/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 */
由於在列舉階段做的是深度優先掃描,所以子樹中的匯流排號總是連續遞增的。當CPU往I/O暫存器0xCF8中寫入一個綜合地址以後,從0號匯流排開始,每個PCI-PCI橋會把綜合地址中的匯流排號與自身的匯流排號相比,如果相符就用邏輯裝置號在本總線上尋訪目標裝置;否則就進一步把這個匯流排號與PCI_SUBORDINATE_BUS中的內容相比,如果目標匯流排號落在當前子樹範圍中,就把綜合地址傳遞給其下的各個次層PCI-PCI橋,要不然就不予理睬。這樣,最終就會找到目標裝置。當然,這個過程只是在PCI裝置的配置階段需要這樣做,一旦配置完成,CPU就直接通過有關的匯流排地址訪問目標裝置了。
PCI-PCI橋要想正確傳遞對PCI I/O,PCI Memory或PCI Configuration地址空間的讀和寫請求,必須知道下列資訊:
(1)Primary Bus Number(主匯流排號)
該PCI-PCI橋所處的PCI匯流排稱為主匯流排。
(2)Secondary Bus Number(子匯流排號)
該PCI-PCI橋所連線的PCI匯流排稱為子匯流排/次匯流排號。
(3)Subordinate Bus Number
PCI匯流排的下屬PCI匯流排的匯流排編號最大值。有點繞,看後面的分析就明白了。
PCI I/O 和 PCI Memory 視窗
PCI橋的配置暫存器與一般的PCI裝置不同。一般PCI裝置可以有6個地址區間,外加一個ROM區間,代表著裝置上實際存在的儲存器或暫存器區間。而PCI橋,則本身並不一定有儲存器或暫存器區間,但是卻有三個用於地址過濾的區間。每個地址過濾區間決定了一個地址視窗,從CPU一側發出的地址,如果落在PCI橋的某個視窗內,就可以穿過PCI橋而到達其所連線的總線上。此外,PCI橋的命令暫存器中還有”memory access enable”和”I/O access enable ”的兩個控制位,當這兩個控制位為0時,這些視窗就全都關上了。在未完成對PCI匯流排的初始化之前,還沒有為PCI裝置上的各個區間分配合適的匯流排地址時,正是因為這兩個控制位為0,才不會對CPU一側造成干擾。例如,對於淺談(一)的 PCI系統示意圖,僅當讀和寫請求中的PCI I/O或PCI memory地址屬於SCSI或Ethernet裝置時,PCI-PCI橋才將這些總線上的請求從PCI匯流排0傳遞到PCI匯流排1。這種過濾機制可以避免地址在系統中沒必要的繁衍。為了做到這點,每個PCI-PCI橋必須正確地被設定好它所負責的PCI I/O或PCI memory的起始地址和大小。當一個讀或寫請求地址落在其負責的範圍之內,這個請求將被對映到次級的PCI總線上。系統中的PCI-PCI橋一旦設定完畢,如果Linux中的裝置驅動程式存取的PCI I/O和PCI memory地址落在在這些視窗之內,那麼這些PCI-PCI橋就是透明的。這是個很重要的特性,使得Linux PCI裝置驅動程式開發者的工作容易些。
問題是配置一個PCI-PCI橋的時候,並不知道這個PCI-PCI橋的subordinate bus number。那麼就不知道該PCI橋下面是否還有其他的PCI-PCI橋。即使你知道,也不清楚如何對它們賦值。解決方法是利用上述的深度掃描演算法來掃描每個匯流排。每當發現PCI-PCI橋就對它進行賦值。當發現一個PCI-PCI橋時,可以確定它的secondary bus number。然後我們暫時先將其subordinate bus number賦值為0xFF。緊接著,開始掃描該PCI-PCI橋的downstream橋。這個過程看起來有點複雜,下面的例子將給出清晰的解釋:
圖3 配置PCI系統 第一步
PCI-PCI橋編號--第一步
以圖3的拓撲結構為例,掃描時首先發現的橋是Bridge1。Bridge 1的downstream PCI匯流排號碼被賦值1。自然該橋的secondary bus number也是1。其subordinate bus number暫時賦值為0xFF。上述賦值的含義是所有型別1的含有PCI匯流排1或更高(<255)的號碼的PCI配置地址將被Bridge 1傳遞到PCI匯流排1上。如果PCI匯流排號是1,Bridge 1還負責將配置地址的型別轉換成型別0(對於這裡說的型別0和型別1,請參考淺談(一))。否則,就不做轉換。上述動作就是開始掃描匯流排1時Linux PCI初始化程式碼所完成的對匯流排0的配置工作。
圖4 配置PCI系統 第二步
PCI-PCI橋編號--第二步
由於Linux PCI裝置驅動使用深度優先演算法進行掃描,所以初始化程式碼開始掃描匯流排1。從而Bridge 2被發現。因為在Bridge 2下面發現不再有PCI-PCI橋,所以Bridge 2的subordinate bus number是2,等於它的secondary bus number。圖4顯示了在這個時刻匯流排和PCI-PCI橋的賦值情況。
圖5 配置PCI系統 第三步
PCI-PCI橋編號--第三步
Linux PCI裝置驅動程式碼從匯流排2的掃描中回來接著進行掃描匯流排1,發現Bridge 3。它的primary bus number被賦值為1,secondary bus number為3。因為匯流排3上還發現了PCI-PCI橋,所以Bridge 3的subordinate bus number暫時賦值0xFF。圖5顯示了這個時刻系統配置的狀態。到目前為止,含有匯流排號1,2,3的型別1的PCI配置都可以正確地傳送到相應的總線上。
圖6 配置PCI系統 第四步
PCI-PCI橋編號--第四步
現在Linux開始掃描PCI匯流排3,Bridge 3的downstream。PCI匯流排3上有另外一個PCI-PCI橋,Bridge 4。因此Bridge 4的primary bus number的值為3,secondary bus number為4。由於Bridge 4下面沒有別的橋裝置,所以Bridge 4的subordinate bus number為4。然後回到PCI-PCI Bridge 3。這時就將Bridge 3的subordinate bus number從0xFF改為4,表示匯流排4是從Bridge 3往下走的最遠的PCI-PCI橋。最後,Linux PCI裝置驅動程式碼將4以同樣的道理賦值給Bridge 1的subordinate bus number。圖6反映了系統最後的狀態。
原文連結 http://blog.csdn.net/linuxdrivers/article/details/5917478