深入PCI與PCIe之二:軟體篇
我們前一篇文章(深入PCI與PCIe之一:硬體篇 - 知乎專欄)介紹了PCI和PCIe的硬體部分。本篇主要介紹PCI和PCIe的軟體界面和UEFI對PCI的支持。
PCI/PCIe軟體界面
1。配置空間
PCI spec規定了PCI設備必須提供的單獨地址空間:配置空間(configuration space),前64個位元組(其地址範圍為0x00~0x3F)是所有PCI設備必須支持的(有不少簡單的設備也僅支持這些),此外PCI/PCI-X還擴展了0x40~0xFF這段配置空間,在這段空間主要存放一些與MSI或者MSI-X中斷機制和電源管理相關的Capability結構。
前文提到過,PCI配置空間和內存空間是分離的,那麼如何訪問這段空間呢?我們首先要對所有的PCI設備進行編碼以避免衝突,通常我們是以三段編碼來區分PCI設備,即Bus Number, Device Number和Function Number,以後我們簡稱他們為BDF。有了BDF我們既可以唯一確定某一PCI設備。不同的晶元廠商訪問配置空間的方法略有不同,我們以Intel的晶元組為例,其使用IO空間的CF8h/CFCh地址來訪問PCI設備的配置寄存器:
CF8h: CONFIG_ADDRESS。PCI配置空間地址埠。
CFCh: CONFIG_DATA。PCI配置空間數據埠。
CONFIG_ADDRESS寄存器格式:
31 位:Enabled位。
23:16 位:匯流排編號。
15:11 位:設備編號。
10: 8 位:功能編號。
7: 2 位:配置空間寄存器編號。
1: 0 位:恆為「00」。這是因為CF8h、CFCh埠是32位埠。
如上,在CONFIG_ADDRESS埠填入BDF,即可以在CONFIG_DATA上寫入或者讀出PCI配置空間的內容。
PCIe規範在PCI規範的基礎上,將配置空間擴展到4KB。原來的CF8/CFC方法仍然可以訪問所有PCIe設備配置空間的頭255B,但是該方法訪問不了剩下的(4K-255)配置空間。怎麼辦呢?Intel提供了另外一種PCIe配置空間訪問方法:通過將配置空間映射到Memory map IO(MMIO)空間,對PCIe配置空間可以像對內存一樣進行讀寫訪問了。如圖
這樣再加上PCI板子上的RAM或者ROM,整個PCIe Device空間如下圖:
MMIO這段空間有256MB,因為按照PCIe規範,支持最多256個buses,每個Bus支持最多32個PCI devices,每個device支持最多8個function,也就是說:佔用內存的最大值為:256 * 32 * 8 * 4K = 256MB。在台式機上我們很多時候覺得佔用256MB空間太浪費(造成4G以下memory可用空間變少,雖然實際memory可以映射到4G以上,但對32位OS影響很大),PCI Bus也沒有那麼多,所以可以設置成最低64MB,即最多64個Bus。那麼這個256MB的MMIO空間在在哪裡呢?我們以Intel的Haswell平台為例:
其中PCIEXBAR就是這個MMIO的起始位置,在4G下面佔據64MB/128MB/256MB空間(4G以上部分不在本文範圍內,我們今後會詳細介紹固件中的內存布局),其具體位置可以由平台進行設置,設置寄存器一般在Root complex(下文簡稱RC)中。
如果大家忘記RC,可以參考前文硬體部分的典型PCIe框圖。
RC是PCIe體系結構的一個重要組成部件,也是一個較為混亂的概念。RC的提出與x86處理器系統密切相關,PCIe匯流排規範中涉及的RC也以x86處理器為例進行說明,而且一些在PCIe匯流排規範中出現的最新功能也在Intel的x86處理器系統中率先實現。事實上,只有x86處理器才存在PCIe匯流排規範定義的「標準RC」,而在多數處理器系統,並不含有在PCIe匯流排規範中涉及的,與RC相關的全部概念。
在x86處理器系統中,RC內部集成了一些PCI設備、RCRB(RC Register Block)和Event Collector等組成部件。其中RCRB由一系列的寄存器組成的大雜燴,而僅存在於x86處理器中;而Event Collector用來處理來自PCIe設備的錯誤消息報文和PME消息報文。RCRB的訪問基地址一般在LPC設備寄存器上設置。
如果將RC中的RCRB、內置的PCI設備和Event Collector去除,該RC的主要功能與PCI匯流排中的Host Bridge類似,其主要作用是完成存儲器域到PCI匯流排域的地址轉換。但是隨著虛擬化技術的引入,尤其是引入MR-IOV技術之後,RC的實現變得異常複雜。
2。BAR空間
現在我們來看看在配置空間里具體有些什麼。我們以一個一般的type 0(非Bridge)設備為例:
其中Device ID和Vendor ID是區分不同設備的關鍵,OS和UEFI在很多時候就是通過匹配他們來找到不同的設備驅動(Class Code有時也起一定作用)。為了保證其唯一性,Vendor ID應當向PCI特別興趣小組(PCI SIG)申請而得到。
我們重點來了解一下這些Base Address Registers(BAR)。BAR是PCI配置空間中從0x10 到 0x24的6個register,用來定義PCI需要的配置空間大小以及配置PCI設備佔用的地址空間。
每個PCI設備在BAR中描述自己需要佔用多少地址空間,UEFI通過所有設備的這些信息構建一張完整的關係圖,描述系統中資源的分配情況,然後在合理的將地址空間配置給每個PCI設備。
BAR在bit0來表示該設備是映射到memory還是IO,bar的bit0是readonly的,也就是說,設備寄存器是映射到memory還是IO是由設備製造商決定的,其他人無法修改。
下圖是BAR寄存器的結構,分別是Memory和IO:
BAR通過將某些位設置為只讀,且0來表示需要的地址空間大小,比如一個PCI設備需要佔用1MB的地址空間,那麼這個BAR就需要實現高12bit是可讀寫的,而20-4bit是只讀且位0。地址空間大小的計算方法如下:
a.向BAR寄存器寫全1
b.讀回寄存器裡面的值,然後clear 上圖中特殊編碼的值,(IO 中bit0,bit1, memory中bit0-3)。
c.對讀回來的值去反,加一就得到了該設備需要佔用的地址內存空間。
這樣我們就可以在構建一張大表,用於記錄所有PCI設備所需要的空間。這也是PCI枚舉的主要任務之一。另外別忘記設置Command寄存器enable這些BARs。
3。PCI橋設備
PCI橋在PCI設備樹中起到呈上起下的作用。一個PCI-to-PCI橋它的配置空間如下:
注意其中的三組綠色的BUS Number和多組黃色的BASE/Limit對,它決定了橋和橋下面的PCI設備子樹相應/被分配的Bus和各種資源大小和位置。這些值都是由PCI枚舉程序來設置的。
4。Capabilities結構
PCI-X和PCIe匯流排規範要求其設備必須支持Capabilities結構。在PCI匯流排的基本配置空間中,包含一個Capabilities Pointer寄存器,該寄存器存放Capabilities結構鏈表的頭指針。在一個PCIe設備中,可能含有多個Capability結構,這些寄存器組成一個鏈表,其結構如圖:
PCIe的各種特性如Max Payload、Complete Timeout(CTO)等等都通過這個鏈錶鏈接在一起,Capabilities ID由PCIe spec規定。鏈表的好處是如果你不關心這個Capabilities(或不知道怎麼處理),直接跳過,處理關心的即可,兼容性比較好。另外擴展性也強,新加的功能不會固定放在某個位置,淘汰的功能刪掉即好。
5。PCI枚舉
PCI枚舉是個不斷遞歸調用發現新設備的過程,PCI枚舉簡單來說主要包括下面幾個步驟:
A. 利用深度優先演算法遍歷整個PCI設備樹。從Root Complex出發,尋找設備和橋。發現橋後設置Bus,會發現一個PCI設備子樹,遞歸回到A)
B. 遞歸的過程中通過讀取BARs,記錄所有MMIO和IO的需求情況並予以滿足。
C. 設置必要的Capabilities
在整個過程結束後,一顆完整的資源分配完畢的樹就建立好了。
6。地址解碼
在PCI匯流排中定義了兩種「地址解碼」方式,一個是正向解碼,一個是負向解碼。當訪問Bus N時,其下的所有PCI設備都將對出現在地址周期中的PCI匯流排地址進行解碼。如果這個地址在某個PCI設備的BAR空間中命中時,這個PCI設備將接收這個PCI匯流排請求。這個過程也被稱為PCI匯流排的正向解碼,這種方式也是大多數PCI設備所採用的解碼方式。
但是在PCI匯流排上的某些設備,如PCI-to-(E)ISA橋(或LPC)並不使用正向解碼接收來自PCI匯流排的請求, PCI BUS N上的匯流排事務在三個時鐘周期後,沒有得到任何PCI設備響應時(即匯流排請求的PCI匯流排地址不在這些設備的BAR空間中),PCI-to-ISA橋將被動地接收這個數據請求。這個過程被稱為PCI匯流排的負向解碼。可以進行負向解碼的設備也被稱為負向解碼設備。
在PCI匯流排中,除了PCI-to-(E)ISA橋可以作為負向解碼設備,PCI橋也可以作為負向解碼設備,但是PCI橋並不是在任何時候都可以作為負向解碼設備。在絕大多數情況下,PCI橋無論是處理「來自上游匯流排(upstream)」,還是處理「來自下游匯流排(downstream)」的匯流排事務時,都使用正向解碼方式。如圖:
在某些特殊應用中,PCI橋也可以作為負向解碼設備。PCI匯流排規定使用負向解碼的PCI橋,其Base Class Code寄存器為0x06,Sub Class Code寄存器為0x04,而Interface寄存器為0x01;使用正向解碼方式的PCI橋的Interface寄存器為0x00。
如筆記本在連接Dock插座時,也使用了PCI橋。因為在大多數情況下,筆記本與Dock插座是分離使用的,而且Dock插座上連接的設備多為慢速設備,此時用於連接Dock插座的PCI橋使用負向解碼。在該橋管理的設備並不參與處理器系統對PCI匯流排的枚舉過程。當筆記本插入到Dock之後,系統軟體並不需要重新枚舉Dock中的設備並為這些設備分配系統資源,而僅需要使用負向解碼PCI橋管理好其下的設備即可,從而極大降低了Dock對系統軟體的影響。
UEFI對PCI/PCIe的支持
UEFI對於PCI匯流排的支持包括以下三個方面:
1) 提供分配PCI設備資源的協議(Protocol)。
2) 提供訪問PCI設備的協議(Protocol)。
3) 提供PCI枚舉器,枚舉PCI匯流排上的設備以及分配設備所需的資源。
4) 提供各種Lib,方便驅動程序訪問PCI/PCIe配置空間或者MMIO/IO空間。
1.PCI驅動
UEFI BIOS提供了兩個主要的模塊來支持PCI匯流排,一個是PCI Host Bridge控制器驅動,另一個是PCI匯流排驅動。
PCI Host Bridge控制器驅動是跟特定的平台硬體綁定的。根據系統實際I/O空間和memory map,為PCI設備指定I/O空間和Memory空間的範圍,並且產生PCI Host Bridge Resource Allocation 協議(Protocol)供PCI匯流排驅動使用。該驅動還對HostBridge控制器下所有RootBridge設備產生句柄(Handle),該句柄上安裝了PciRootBridgeIoProtocol。PCI匯流排驅動則利用PciRootBridgeIo Protocol枚舉系統中所有PCI設備,發現並獲得PCI設備的Option Rom,並且調用PCI Host Bridge Resource Allocation 協議(Protocol)分配PCI設備資源。PCI Host Bridge Resource Allocation協議的實現是跟特定的芯和平台相結合的,畢竟只有平台所有者才知道資源從哪裡來和有多少。每一個PCI HostBridge Controller下面可以接一個或者多個PCI root bridges,PCI Root Bridge會產生PCI local Bus。正如我們前文舉得例子,如Intel志強第三代四路伺服器,共四顆CPU,每個CPU都被劃分了共享但區隔的Bus, PCI I/O, PCI Memory範圍,其構成可以表示成如下圖:
其他情況可見上文。PCI設備驅動不會使用PCI Root Bridge I/O協議訪問PCI設備,而是會使用PCI匯流排驅動為PCI設備產生的PCI IO Protocol來訪問PCI設備的IO/MEMORY空間和配置空間。PCI Root Bridge I/O協議(Protocol)是安裝在RootBridge設備的句柄上(handle),同時在該handle上也會有表明RootBridge設備的DevicePath協議(Protocol),如下圖所示
PCI匯流排驅動在BDS階段會枚舉整個PCI設備樹並分配資源(BUS,MMIO和IO等),它還會在不同的枚舉點調用Notify event通知平台,平台的Hook可以掛接在這些點上做些特殊的動作。具體各種點的定義請參閱UEFI spec。
PCI bus驅動在這裡:tianocore/edk2
2。PCI Lib
在MdePackage下有很多PCI lib。有Cf8/CFC形式訪問配置空間的,有PCIe方式訪問的。都有些許不同。注意Cf8/CFC只能訪問255以內的,而PCIe方式訪問的要配置正確PCIe base address PCD。
結語
本篇沒有介紹下列內容,以後有機會再補。
1. Non-transparent bridge
2. LPC
3. 各種PCIe的feature
4. MSI中斷處理
如果你還覺得意猶未盡,仔細思考一下下面這些問題並找找資料有助於你更深入了解PCI/PCIe
1. 前文說過,PCIe的速度和Lane的數目是在Training的時候由Root Port和EndPoint協調得到的。那這個Training的過程發生在什麼時候呢? (提示,Hard Strap,Soft Strap, Wait for BIOS/Bifurcation)。
2. UEFI PCI Bus枚舉發生在BDS階段,很靠後。那我們如果在晶元初始化階段需要對PCI設備MMIO空間的寄存器甚至Bridge後面的設備做些設置,該怎麼辦呢?
歡迎大家關注本專欄和用微信掃描下方二維碼加入微信公眾號"UEFIBlog",在那裡有最新的文章。同時歡迎大家給本專欄和公眾號投稿!
推薦閱讀: