PCI設備
外圍設備互連(PCI)是一種將系統中外部設備以結構化與可控制方式連接到起來的總線標準,包括系統部件連接的電氣特性及行爲。本章將詳細討論Linux核心對系統中的PCI總線與設備的初始化過程。
圖6.1是一個基於PCI的系統示意圖。PCI總線和PCI-PCI橋接器在連接系統中設備到上起關鍵作用,在這個系統中CPU和視頻設備被連到PCI bus 0上,它是系統中的主幹PCI總線。而PCI-PCI橋接器這個特殊PCI設備將主幹總線PCI bus 0與下級總線PCI bus 1連接到一起。PCI標準術語中,PCI bus 1是PCI-PCI橋接器的downstream而PCI bus 0是此橋接器的up-stream。SCSI和以太網設備通過二級PCI總線連接到這個系統中。而在物理實現上,橋接器和二級PCI總線被集成到一塊PCI卡上。而PCI-ISA橋接器用來支持古老的ISA設備,圖中有一個高級I/O控制芯片來控制鍵盤、鼠標及軟盤設備。
6.1 PCI地址空間
CPU和PCI設備需要存取在它們之間共享的內存空間。這塊內存區域被設備驅動用來控制PCI設備並在CPU與PCI設備之間傳遞信息。最典型的共享內存包括設備的控制與狀態寄存器。這些寄存器用來控制設備並讀取其 信息。例如PCI SCSI設備驅動可以通過讀取其狀態寄存器,找出已準備好將一塊數據寫入SCSI磁盤的SCSI設備。同時還可以在設備加電後,通過對控制寄存器寫入信息來啓動設備。
CPU的系統內存可以被用作這種共享內存,但是如果採用這種方式,則每次PCI設備訪問此內存塊時,CPU將被迫停止工作以等待PCI設備完成此操作。這種方式將共享內存限制成每次只允許一個系統設備訪問。該策略會大大降低系統性能。但如果允許系統外設不受限制地訪問主存也不是好辦法。它的危險之處在於一個有惡意行爲的設備將使整個系統置於不穩定狀態。
外設有其自身的內存空間。CPU可以自由存取此空間,但設備對系統主存的訪問將處於DMA(直接內存訪問)通道的嚴格控制下。ISA設備需要存取兩個地址空間:ISA I/O(輸入輸出)和ISA內存。而PCI設備需要訪問三種地址空間:PCI I/O、PCI內存和PCI配置空間。CPU則可以訪問所有這些地址空間。PCI I/O和 PCI內存由設備驅動程序使用而PCI配置空間被Linux 核心中的PCI初始化代碼使用。
Alpha AXP處理器並不能象訪問系統地址空間那樣隨意訪問這些地址空間,它只能通過輔助芯片組來存取這些 地址空間,如PCI配置空間。Alpha AXP處理器使用稀疏地址映射策略來從系統巨大的虛擬內存中"竊取"一部分並將其映射到PCI地址空間。
6.2 PCI 配置頭
圖6.2 PCI配置頭
系統中每個PCI設備,包括PCI-PCI橋接器在內,都有一個配置數據結構,它通常位於PCI配置地址空間中。PCI配置頭允許系統來標識與控制設備。配置頭在PCI配置空間的位置取決於系統中PCI設備的拓撲結構。例如將一個PCI視頻卡插入不同的PCI槽,其配置頭位置會變化。但對系統沒什麼影響,系統將找到每個PCI設備與橋接器並使用它們配置頭中的信息來配置其寄存器。
典型的辦法是用PCI槽相對主板的位置來決定其PCI配置頭在配置空間中的偏移。比如主板中的第一個PCI槽的PCI配置頭位於配置空間偏移0處,而第二個則位於偏移256處(所有PCI配置頭長度都相等,爲256字節),其它槽可以由此類推。系統還將提供一種硬件相關機制以便PCI設置代碼能正確的辨認出對應PCI總線上所有存在的設備的PCI配置頭。通過PCI配置頭中的某些域來判斷哪些設備存在及哪些設備不存在(這個域叫廠商標誌域: Vendor Identification field)。對空PCI槽中這個域的讀操作將得到一個值爲0xFFFFFFFF的錯誤信息。
圖6.2給出了256字節PCI配置頭的結構,它包含以下域:
- 廠商標識(Vendor Identification)
- 用來唯一標識PCI設備生產廠家的數值。Digital的PCI廠商標識爲0x1011而Intel的爲0x8086。
- 設備標識(Device Identification)
- 用來唯一標識設備的數值。Digital 21141快速以太設備的設備標識爲0x0009。
- 狀態(Status)
- 此域提供PCI標準定義中此設備的狀態信息。
- 命令(Command)
- 通過對此域的寫可以控制此設備,如允許設備訪問PCI I/O內存。
- 分類代碼(Class Code)
- 此域標識本設備的類型。對於每種類型的視頻,SCSI等設備都有標準的分類代碼。如SCSI設備分類代碼爲0x0100。
- 基地址寄存器(Base Address Registers)
- 這些寄存器用來決定和分配此設備可以使用的PCI I/O與PCI內存空間的類型,數量及位置。
- 中斷引腳(Interrupt Pin)
- PCI卡上的四個物理引腳可以將中斷信號從插卡上帶到PCI總線上。這四個引腳標準的標記分別爲A、B、C及D。中斷引腳域描敘此PCI設備使用的引腳號。通常特定設備都是採用硬連接方式。這也是系統啓動時,設備總使用相同中斷引腳的原因。中斷處理子系統用它來管理來自該設備的中斷。
- 中斷連線(Interrupt Line)
- 本設備配置頭中的中斷連線域用來在PCI初始化代碼、設備驅動以及Linux中斷處理子系統間傳遞中斷處理過程。雖然本域中記錄的這個數值對於設備驅動毫無意義。但是它可以將中斷處理過程從PCI卡上正確路由到Linux操作系統中相應的設備驅動中斷處理代碼中。在interrupt一章中將詳細描敘Linux中斷處理過程。
6.3 PCI I/O和PCI內存地址
這兩個地址空間用來實現PCI設備和Linux核心中設備驅動程序之間的通訊。例如DEC21141快速以太網設備的內部寄存器被映射到PIC I/O空間上時,其對應的Linux設備驅動可以通過對這些寄存器的讀寫來控制此設備。PCI視頻卡通常使用大量的PCI內存空間來存儲視頻信息。
在PCI系統建立並通過用PCI配置頭中的命令域來打開這些地址空間前,系統決不允許對它們進行存取。值得注意的是只有PCI配置代碼讀取和寫入PCI配置空間,Linux設備驅動只讀寫PCI I/O和PCI內存地址。
6.4 PCI-ISA 橋接器
這種橋接器通過將PCI I/O和PCI內存空間的存取轉換成對ISA I/O和ISA內存的存取來支持古老的ISA設備。市場上許多主板中同時包含幾個ISA總線槽和PCI槽。但今後對ISA設備的向後兼容支持將逐漸減弱,最終主板上只會有PCI槽。早期的Intel 8080 PC就將ISA設備的ISA地址空間固定了下來。即使在價值5000美圓的Alpha AXP 系統中其ISA軟盤控制器地址也和最早IBM PC上的相同。PCI標準將PCI I/O和PCI內存的低端部分保留給系統中的ISA外設,另外還使用PCI-ISA橋接器實現從PCI內存訪問到ISA內存訪問的轉換。6.5 PCI-PCI 橋接器
PCI-PCI橋接器是一種將系統中所有PCI總線連接起來的特殊PCI設備。在簡單系統中只存在一條PCI總線,由於受電氣特性的限制,它所連接的PCI設備個數有限。引入PCI-PCI橋接器後系統可以使用更多的PCI設備。對於高性能服務器這是非常重要的。Linux提供了對PCI-PCI橋接器的全面支持。
6.5.1 PCI-PCI橋接器:PCI I/O和PCI 內存窗口
PCI-PCI橋接器將PCI I/O和PCI內存讀寫請求中的一個子集向下傳送。例如在圖6.1中,如果來自PCI 總線0請求是對SCSI或以太設備所擁有的PCI I/O或PCI內存的讀寫,則此PCI-PCI橋接器將只需把請求從總線0傳遞到PCI總線1上;所有其它PCI I/O和內存地址都將被它忽略。這個過濾使得這些地址信息不會在整個系統中擴散。爲了實現這點,PCI-PCI橋接器必須編程爲有某個PCI I/O及PCI內存基址和上限,只有在這個地址範圍內的PCI地址訪問才能從主幹總線傳遞到二級總線。一旦系統中的PCI-PCI橋接器被設置成這樣,則只要當Linux設備驅動程序通過這個窗口訪問PCI I/O和PCI內存空間時,此PCI-PCI橋接器就將變得透明。這樣也給Linux PCI設備驅動編寫者提供了方便。我們在稍後的討論中將看到Linux對PCI-PCI橋接器非常巧妙的配置。6.5.2 PCI-PCI橋接器:PCI配置循環及PCI總線編號方式
爲了讓CPU上運行的PCI初始化代碼能訪問位於分支PCI總線上的設備,必須爲橋接器提供某種機制以便它可以決定是否將配置循環從主幹接口傳遞到其二級接口。循環是出現在PCI總線上的一個地址。PCI 標準定義了兩種PCI配置尋址格式;類型0和類型1;它們分別如圖6.3及6.4所示。類型0 PCI配置循環不包含總線序號,同時在此PCI總線上對應於這個PCI配置地址的所有PCI設備都會來對它們進行解釋。類型0 配置循環的11 位到31位用來進行PCI設備選擇。有種設計方式是讓每位代表系統中一個不同的設備。這時11位對應PCI槽0中的PCI設備而12位標識槽1中的設備等等,如此類推。另外一種方式是直接將設備的槽號寫入到位31到11中。系統使用哪種機制依賴於系統PCI內存控制器。
類型1 PCI配置循環包含一個PCI總線序號,同時這種配置循環將被除橋接器外的所有PCI設備所忽略。所有發現類型1 配置循環的PCI-PCI橋接器把它們看到的地址傳遞到各自的下級PCI總線。至於PCI-PCI橋接器是否忽略類型1 配置循環或將其傳遞到PCI總線則依賴於PCI-PCI橋接器的配置方式。每個PCI-PCI橋接器都擁有一個主幹總線接口序號以及一個二級總線接口序號。主幹總線是那個離CPU最近的PCI總線而二級總線是離它稍遠的PCI總線。任何PCI-PCI橋接器還包含一個從屬總線序號,這是所有二級總線接口所橋接的PCI總線中序號最大的那個。或者說這個從屬總線序號是PCI-PCI橋接器向下連接中PCI總線的最大序號。當PCI-PCI橋接器看到類型1 PCI配置循環時它將進行如下操作:
如果此總線序號不在橋接器的二級總線序號和從屬總線序號之間則忽略掉它。 如果此總線序號與橋接器的二級總線序號相同則將其轉換成類型0 配置命令。 如果此總線序號位於橋接器的二級總線序號與從屬總線序號之間則將它不作改變的傳遞到二級總線接口中。所以如果想尋址PCI-PCI配置例4中總線3上的設備1,我們繼續從CPU中產生一個類型1 配置命令。橋接器1將其傳遞給總線1。橋接器2雖然忽略它但會將其轉換成一個類型0 配置命令並送到總線3上,在那裏設備1將作出相應反應。
PCI配置中總線序號由操作系統來分配。但是序號分配策略必須遵循對系統中所有PCI-PCI橋接器都正確的描敘:
“位於PCI-PCI橋接器後所有的PCI總線必須位於二級總線序號和從屬總線序號之間”。
如果這個規則被打破,則PCI-PCI橋接器將不能正確的傳遞與轉換類型1 PCI配置循環,同時系統將找不到或者不能正確地初始化系統中的PCI設備。爲了滿足這個序號分配策略,Linux以特殊的順序配置這些特殊的設備。PCI-PCI總線序號分配一節詳細描敘了Linux的PCI橋接器與總線序號分配策略。
6.6 Linux PCI 初始化過程
Linux中的PCI初始化代碼邏輯上可分成三個部分:
PCI 設備驅動這個僞設備驅動程序將從總線0開始搜索PCI系統並定位系統中所有的PCI設備與橋接器。它將建立起一個描敘系統拓撲結構的數據結構鏈表。另外它還爲所有的橋接器進行編號。 PCI BIOS這個軟件層提供了在bib-pci-bios定義中描敘的服務。即使Alpha AXP沒有BIOS服務,Linux核心也將爲它提供具有相同功能的代碼。 PCI Fixup系統相關補丁代碼將整理PCI初始化最後階段的一些系統相關事物。6.6.1 Linux 核心PCI數據結構
圖6.5 Linux核心PCI數據結構
Linux核心初始化PCI系統時同時也建立了反應系統中真實PCI拓撲的數據結構。 圖6.5顯示了圖6.1所標識的PCI示例系統中數據結構間關係。每個PCI設備(包括PCI-PCI橋接器)用一個pci_dev數據結構來描敘。每個PCI總線用一個pci_bus數據結構來描敘。這樣系統中形成了一個PCI總線樹,每棵樹上由一些子PCI設備組成。由於PCI總線僅能通過PCI-PCI橋接器(除了主幹PCI總線0)存取,所以pci_bus結構中包含一個指向PCI-PCI橋接器的指針。這個PCI設備是PCI總線的父PCI總線的子設備。
在圖6.5中沒有顯示出來的是一個指向系統中所有PCI設備的指針,pci_devices。系統中所有的PCI設備將其各自的pci_dev數據結構加入此隊列中。這個隊列被Linux核心用來迅速查找系統中所有的PCI設備。
6.6.2 PCI設備驅動
PCI設備驅動根本不是真正的設備驅動,它僅是在系統初始化時由操作系統調用的一些函數。PCI初始化代碼將掃描系統中所有的PCI總線以找到系統中所有的PCI設備(包括PCI-PCI橋接器)。
它通過PCI BIOS代碼來檢查當前PCI總線的每個插槽是否已被佔用。如果被佔用則它建立一個pci_dev數據結構來描敘此設備並將其連接到已知PCI設備鏈表中(由pci_devices指向)。
首先PCI初始化代碼掃描PCI總線0。它將試圖讀取對每個PCI槽中可能的PCI設備廠商標誌與設備標誌域。當發現槽被佔用後將建立一個pci_dev結構來描敘此設備。所有這些PCI初始化代碼建立的pci_dev結構(包括PCI-PCI橋接器)將被連接到一個單向鏈表pci_devices中。
如果這個PCI設備是一個PCI-PCI橋接器則建立一個pci_bus結構並將其連接到由pci_root指向的pci_dev結構和pci_bus樹中。PCI初始化代碼通過類別代碼0x060400來判斷此PCI設備是否是一個PCI-PCI橋接器。然後Linux 核心代碼將配置此PCI-PCI橋接器下方的PCI設備。如果有更多的橋接器被找到則進行同樣的配置。顯然這個過程使用了深度優先搜索算法;系統中PCI拓撲將在進行廣度映射前先進行深度優先映射。圖6.1中Linux將在配置PCI總線0上的視頻設備前先配置PCI設備1上的以太與SCSI設備。
由於Linux優先搜索從屬的PCI總線,它必須處理PCI-PCI橋接器二級總線與從屬總線序號。在下面的pci-pci總線序號分配中將進行詳細討論。
配置PCI-PCI橋接器 - 指定PCI總線序號
爲了讓PCI-PCI橋接器可以傳遞PCI I/O、PCI內存或PCI配置地址空間,它們需要如下內容:
Primary Bus Number:主幹總線序號位於PCI-PCI橋接器上方的總線序號Secondary Bus Number:二級總線序號位於PCI-PCI橋接器下方的總線序號Subordinate Bus Number:從屬總線序號在橋接器下方可達的最大總線序號PCI I/O and PCI Memory Windows:PCI I/O與PCI內存窗口對於PCI-PCI橋接器下方所有PCI I/O地址空間與PCI內存地址空間的窗口基址和大小。配置任一PCI-PCI橋接器時我們對此橋接器的從屬總線序號一無所知。不知道是否還有下一級橋接器存在,同時也不知道指派給它們的序號是什麼。但可以使用深度優先遍歷算法來對掃描出指定PCI-PCI橋接器連接的每條總線,同時將它們編號。當找到一個PCI-PCI橋接器時,其二級總線被編號並且將臨時從屬序號0xff指派給它以便對其所有下屬PCI-PCI橋接器進行掃描與指定序號。以上過程看起來十分複雜,下面將提供一個實例以幫助理解。
- PCI-PCI 橋接器序號分配:步驟1
- 考慮圖6.6所顯示的拓撲結構,第一個被掃描到的橋接器將是橋1。所以橋1下方的總線將被編號成總線1,同時橋1被設置爲二級總線1且擁有臨時總線序號0xff。這意味着所有PCI總線序號爲1或以上的類型1 PCI配置地址將被通過橋1傳遞到PCI總線1上。如果其總線序號爲1則此配置循環將被轉換成類型0 配置循環,對於其它序號不作轉換。這正是Linux PCI初始化代碼所需要的按序訪問及掃描 PCI總線1。
圖6.7 配置PCI系統:第二部分 - PCI-PCI 橋接器序號分配:第二步
- 由於Linux使用深度優先算法,初始化代碼將繼續掃描PCI總線1。在此處它將發現一個PCI-PCI橋接器2。除此橋接器2外再沒有其它橋接器存在,因此它被分配給從屬總線序號2,這正好和其二級接口序號相同。圖6.7畫出了此處的PCI-PCI橋接器與總線的編號情況。
圖6.8 配置PCI系統:第三部分 - PCI-PCI 橋接器序號分配:步驟三
- PCI初始化代碼將繼續掃描總線1並發現另外一個PCI-PCI橋接器,橋3。橋3的主幹總線接口序號被設置成1,二級總線接口序號爲3,同時從屬總線序號爲0xff。圖6.8給出了系統現在的配置情況。 帶總線序號1、2或者3的類型1 PCI配置循環將被髮送到正確的PCI總線。
圖6.9 配置PCI系統:第四部分 - PCI-PCI 橋接器序號分配:步驟四
- Linux開始沿PCI總線3向下掃描PCI-PCI橋接器。PCI總線3上有另外一個PCI-PCI橋接器(橋4), 橋4的主幹總線序號被設置成3,二級總線序號爲4。由於它是此分支上最後一個橋接器所以它的從屬總線接口序號爲4。初始化代碼將重新從PCI-PCI橋接器3開始並將其從屬總線序號設爲4。 最後PCI初始化代碼將PCI-PCI橋接器1的從屬總線序號設置爲4。圖6.9給出了最後的總線序號分配情況。
6.6.3 PCI BIOS 函數
PCI BIOS函數是一組適用於所有平臺的標準過程。在Intel和Alpha AXP系統上沒有區別。雖然在CPU控制下可以用它們對所有PCI地址空間進行訪問。但只有Linux核心代碼和設備驅動才能使用它們。
6.6.4 PCI 補丁代碼
在Alpha AXP平臺上的PCI補丁代碼所作工作量要大於Intel平臺。
基於Intel的系統在系統啓動時就已經由系統BIOS完成了PCI系統的配置。Linux只需要完成簡單的映射配置. 非Intel系統將需要更多的配置:
爲每個設備分配PCI I/O及PCI內存空間。爲系統中每個PCI-PCI橋接器配置PCI I/O和PCI內存地址窗口。爲這些設備產生中斷連線值;用來控制設備的中斷處理。下一節將描敘這些代碼的工作過程。
確定設備所需PCI I/O和PCI內存空間的大小
系統要查詢每個PCI設備需要多少PCI I/O於PCI內存地址空間。爲了完成這項工作,每個基地址寄存器將被寫上全1並讀取出來。設備將把不必要的地址位設爲0從而有效的定義所需地址空間。有兩類基本的基地址寄存器,一類標識設備寄存器必須駐留的地址空間;另一類是PCI I/O或PCI內存空間。此寄存器的0位來進行類型的區分。圖6.10給出了對應於PCI內存和PCI I/O兩種不同類型的基地址寄存器。
確定某個基地址寄存器所需地址空間大小時,先向此寄存器寫入全1再讀取此寄存器,設備將在某些位填上0來形成一個二進制數表示所需有效地址空間。
以初始化DEC 21142 PCI快速以太設備爲例,它將告訴系統需要0x100字節的PCI I/O空間或者PCI內存空間。於是初始化代碼爲其分配空間。空間分配完畢後,就可以在那些地址上看到21142的控制與狀態寄存器。
爲PCI-PCI橋接器與設備分配PCI I/O與PCI內存
象所有內存一樣,PCI I/O和PCI內存空間是非常有限甚至匱乏。非Intel系統的PCI補丁代碼(或者Intel 系統的BIOS代碼)必須爲每個設備分配其所要求的內存。PCI I/O和PCI內存必須以自然對齊方式分配給每個設備。比如如果一個設備要求0xB0大小的PCI I/O空間則它必須和一個0xB0倍數的地址對齊。除此以外,對於任何指定橋接器,其PCI I/O和PCI內存基址必須以在1M字節邊界上以4K字節方式對齊。所以在橋接器下方的設備的地址空間必須位於任意指定設備上方的PCI-PCI橋接器的內存範圍內。進行有效的空間分配是一件比較困難的工作。Linux使用的算法依賴於由PCI設備驅動程序建立的描敘PCI設備的總線/設備樹,每個設備的地址空間按照PCI I/O內存順序的升序來分配。同時再次使用遍歷算法來遍歷由PCI初始化代碼建立的pci_bus和 pci_dev結構。從根PCI總線開始(由pci_boot指向)PCI補丁代碼將完成下列工作:
- 使當前全局PCI I/O和內存的基址在4K,邊界在1M上對齊。
- 對於當前總線上的每個設備(按照PCI I/O內存需要的升序排列)
- 在PCI I/O和PCI內存中爲其分配空間
- 爲全局PCI I/O和內存基址同時加上一個適當值
- 授予設備對PCI I/O和PCI內存的使用權
- 爲對於當前總線下方的所有總線循環分配空間。注意這將改變全局PCI I/O和內存基址。
- 使當前全局PCI I/O和內存的基址和邊界分別在4K和1M對齊,以便確定當前PCI-PCI橋接器所需的PCI I/O和PCI內存基址及大小。
- 對此PCI-PCI橋接器編程,將其PCI I/O和PCI內存基址及界限連接到總線上。
- 打開PCI-PCI橋接器上的PCI I/O和PCI內存訪問橋接功能。這時在此橋接器主幹PCI總線上位於此橋接器PCI I/O和PCI內存地址窗口中的任何PCI I/O或者PCI內存地址將被橋接到二級PCI總線上。
以圖6.1中的PCI系統爲例,PCI補丁代碼將以如下方式設置系統:
對齊PCI基址PCI I/O基址爲0x4000而PCI內存基址爲0x100000。這樣允許PCI-ISA橋接器將此地址以下的地址轉換成ISA地址循環。視頻設備我們按照它的請求從當前PCI內存基址開始分配0x200000字節給它,這樣可以在邊界上對齊。PCI內存基址被移到0x400000同時PCI I/O基址保持在0x4000。PCI-PCI 橋接器現在我們將穿過PCI-PCI橋接器來分配PCI內存,注意此時我們無需對齊這些基址,因爲它們已經自然對齊。- 以太網設備
- 它需要0xB0字節的PCI I/O和PCI內存空間。這些空間從PCI I/O地址0x4000和PCI內存地址 0x400000處開始。PCI內存基址被移動到0x4000B0同時PCI I/O基址移動到0x40B0。
- SCSI 設備
- 它需要0x1000字節PCI內存,所以它將在自然對齊後從從0x401000處開始分配空間。PCI I/O基址仍然在0x40B0而PCI內存基址被移動到0x402000。