PCIE開發筆記(一)簡介篇
這是一個系列筆記,將會陸續進行更新。
最近接觸到一個項目,需要使用PCIE協議,項目要求完成一個pcie板卡,最終可以通過電腦進行通信,完成電腦發送的指令。這當中需要完成硬件部分,使用FPGA板實現,同時需要編寫Windows下的驅動編寫。初次接觸到PCIE協議,網絡上的相關教程不夠清晰,讓人看了之后不知所以然,不適合完全沒有基礎的人學習(就是我這樣的人)。經過較長時間閱讀相關文檔,其中也走了不少彎路,最后對PCIE的IP核使用有了一定的了解,所以想寫下這篇筆記,一來方便以后自己溫習,而來幫助其他新入門的同學,避免一些不必要的彎路。
因為各種的PCIE設備的設計與使用都是依據PCIE協議的,所以首先我們需要對PCIE協議有一個大致的了解,了解的深度即不要太大(因為相關協議的文檔長達數千也,而且有些你可能就不會用),也不能太淺,不然當你閱讀Xilinx的PCIE的集成核時會一頭霧水,因為你會不了解其中的一些寄存機,結構。
首先你需要下載這兩個文檔《PCI_Express_Base_Specification_Revision》,《PCI Express System Architecture》。第一個文檔是將PCIE設備進行通信時包的格式,以及設備中的寄存器的含義和使用,可以看做是一本工具書,當你開發時關於接口,包格式,寄存器問題是隨時可以查閱的文檔,沒有必要去細讀它。第二個是非常有必要去讀的一個文檔,有一個減縮版可以讓你快速對整個體系有一個了解。
我們的開發學習筆記就從第二本的內容開始,對pcie有一個大體的了解。首先我們都知道在電腦中有很多設備使用pcie總線,例如顯卡,網卡,硬盤。
首先我們簡單介紹一下PCIE,PCIE是一種串行通信協議。在低速情況下,並行結構絕對是一種非常高效的傳輸方式,但是當傳輸速度非常高,並行傳輸的致命性缺點就出現了。因為時鍾在高速的情況下,因為每一位在傳輸線路上不可能嚴格的一致,並行傳輸的一個字節中的每個位不會同時到達接受端就被放大了。而串行傳輸一位一位傳輸就不會出現這個問題。串行的優勢就出現了,串行因為不存在並行的這些問題,就可以工作在非常高的頻率下,用頻率的提升掩蓋它的劣勢。
PCIE使用一對差分信號來傳輸一位信號,當D+比D-信號高時,傳輸的是邏輯1,反之為0,當相同時不工作。同時PCIE系統沒有時鍾線。
下面了解一下pcie總線的拓撲結構。
Fig.1 PCIE拓撲結構
從Fig1可以看出這個拓撲結構,CPU連接到根聚合體(Root Complex),RC負責完成從CPU總線域到外設域的轉換,並且實現各種總線的聚合。將一部分CPU地址映射到內存,一部分地址映射到相應的相應的設備終端(比如板卡)。
pcie設備有兩大類,一種是root port,另一種Endpoint。從字面意思可以了解這兩類的作用,root port相當於一個根節點,將多個endpoint設備連接在一個節點,同時它完成數據的路由。上圖中的Switch就是一個root port設備。而endpoint就是最終數據的接受者,命令的執行者。
這里我們就對pcie總線在計算機結構中的位置有一個大致的了解,下面我們對pcie數據的傳輸方式進行一個簡單的介紹。pcie數據的傳輸方式類似於TCP/IP的方式,將數據按數據包的格式進行傳輸,同時對結構進行分層。
Fig.2 PCIE Device layers
Fig.3 Detailed Block Diagram of PCI Express Device's Layer
PCIE的設備都具有這幾個結構,每個結構的作用不同。我們首先說明數據傳輸時候的流程,PCIE協議傳輸數據是以數據包的形式傳輸。
首先說明在發送端,設備核或者應用軟件產生數據信息,交由PCI Express Core Logic Interface將數據格式轉換TL層可以接受的格式,TL層產生相應的數據包。然后數據包被存儲在緩沖buffer中,准備傳輸給下一層數據鏈層(Data Link Layer)。數據鏈層將上一層傳來的數據包添加一些額外的數據用來給接收端進行一些必要的數據正確性檢查。然后物理層將數據包編碼,通過多條鏈路使用模擬信號進行傳輸。
在接收端,接收端設備在物理層解碼傳輸的數據,並將數據傳輸至上一層數據鏈層,數據鏈層將入站數據包進行正確性檢查,如果沒有錯誤就將數據傳輸至TL層,TL層將數據包緩沖buffer,之后PCI Express Core Logic Interface將數據包轉換成設備核或者軟件能夠處理的數據。
我們使用IP核進行開發時,這三個層都已經寫好了。所以我們的主要的任務就是寫出fig.2中PCI Express Core Logic Interface,從他的字面我們就可以明白他的作用,就是一個接口,將數據從Device Core輸出的數據格式轉換IP核TL層接受的數據格式。
Fig.4 pcie數據包的處理
在TLP包傳輸的過程中會發生數據包的組裝和拆解。
TLP包的組裝
當數據從軟件層或者設備核傳來之后,TL層添加ECRC,
在DLL層在前段添加序列數字,在后面添加DLL層的CRC,
在物理層添加幀頭和幀未。
Fig.5 TLP Assembly
TLP的拆解是一個反過程。如Fig.6
Fig.6 TLP Disassembly
到這里筆記(一)就結束了。
PCIE開發筆記(二)TLP類型介紹篇
我們在學習筆記(一)中對PCIE協議有一個大致的了解,我們從他的拓撲結構可以看出PCIE設備是以peer to peer結構連接在一起的。並且點到點之間是以數據包的形式傳輸的。這篇筆記我們就對數據包進行一個大致的講解。
我們上一篇說到,PCIE在邏輯上分為三層,分別是
1.TL層,對應數據包為TLP
2.數據鏈層(Data Link Layer),對應數據包為DLLP
3.物理層(PHY Layer),對應數據包為PLP
DLLP和PLP只會在相鄰的兩個設備之間傳遞,不會傳遞給第三個設備。
這里我們把重點着重放在TL層產生的TLP數據包。
我們首先給TLP數據包進行一個分類,主要可以分為以下四類:
1.與memory有關
2.與IO有關
3.與配置(configuration)有關
4.與消息(message)有關
configuration TLP是用來對PCIE設備進行配置專用的數據包,例如可以修改設備寄存器的值。
詳細如table 1所示。
table.1
同時我們還可以從數據包從發送方到達接受方之后接受方是否返回一個數據包,將TLP分為兩類:
1.Posted 接受方不返回數據包
2.Non_posted 接收方返回數據包
數據包相應的對應關系如table 2所示。
table.2
我們可以從它們的字面意思很輕易的理解它們。(configuration數據包是用來配置PCIE設備的專門的數據包,message是用來傳遞中斷,錯誤信息,電源管理信息的專用數據包)
如何去辨別TLP的類型那?他們的差別主要在TLP Header中,TLP Header有兩種格式,一種長度為3DW,一種為4DW。在TLP Header的Byte 0中有Fmt和Type兩個部分,他們一起來表示TLP的類型。不同的類型長度不一樣,詳細參照table.3。
fig.1
table.3
下面我們詳細的介紹它們。
1.Non-Posted Read Transactions操作
請求者(Requester)請求一個操作,數據包可以是MRd,IORd,CfgRd0,CfgRd1。當接受者(Completer)接受之后,完成響應操作,之后返回一個數據包,可能是CplD或者Cpl。
fig.2
2. Non-Posted Locked Read Transaction操作
請求者(Requester)請求一個操作,數據包是MRdLk.當接受者(Completer)接受之后,完成響應操作,之后返回一個數據包,可能是CplDLk或者CplLk。在Requester接受到Completion之前,數據包傳遞路徑鎖定。
fig.3
3.Non-Posted Write Transactions 操作
請求者(Requester)請求一個操作,數據包是IOWr,CfgWr0,CfgWr1。當接受者(Completer)接受之后,完成響應操作,之后返回一個數據包,是Cpl。
fig.4
4.Posted Memory Write Transactions 操作
請求者(Requester)請求一個操作,數據包是MWr.當接受者(Completer)接受之后,不做任何反應。
fig.5
5.Posted Message Transactions 操作
請求者(Requester)請求一個操作,數據包是Msg,MsgD.當接受者(Completer)接受之后,不做任何反應。
fig.6
下面我們舉一些實際的例子:
1.CPU讀取一個PCIE設備的memory
在PCIE的拓撲結構中,有一個非常重要的結構,它就是Root Complex(RC)結構。顧名思義,它負責將幾個不同的總線協議聚合在一起,如內存的DDR總線,處理器的前端總線Front Side Bus(FSB)。在PCIE中,CPU的操作實際是由RC代替完成的,所以一定程度上也可以講RC代表CPU。
所以當CPU想要訪問Endpoint時
Step1:CPU讓RC產生一個MRd,經過Switch A,Switch B(第一篇講到過Switch),到達Endpoint。
Step2: Endpoint 接受數據包,進行數據讀取。
Step3:Endpoint返回一個帶有數據的Completion.
Step4: RC接受數據包,給CPU。
fig.7
其他類型就不再一一贅述了。這里TLP的分類就告一段落。
PCIE開發筆記(三)TLP路由篇
前兩篇我們對TLP有一個大致的了解,現在有一個問題擺在我們面前,當一個設備想和另一個設備進行通信時,TLP是怎么找到這條路徑,從而進行傳播的,這就是路由問題。
上一篇我們講過PLLP,PLP只在臨近的兩個設備之間傳播,所以不存在路由問題。而TLP會在整個拓撲結構中傳播,所以存在這個問題。
要路由首先要能明確的表示一個地址,寄存器,或者一個設備。
所以要介紹幾個概念。
首先介紹一下地址空間(Address Space)這個概念。
系統將一部分地址分配給內存(System Memory),一部分(MMIO,Prefetchable Memory Devices)分配給外設(如PCIE外設)。IO接口也是這個樣子。這樣就可以用一個統一的方式命名系統的存儲空間。這稱之為地址空間。
fig.1
同時,在整個系統初始化之后,每個設備會有一個設備號,總線號,功能號(Device number,ID Bus Number,Function Number),這樣也可以唯一的確定一個設備。這些信息存儲在設備的configurection頭里面。
TLP路由總共有三種方式:
1.Address Routing 根據地址路由
2.ID Routing 根據ID路由
3.Implicit Routing 隱式路由
不同類型的TLP的路由方式不一樣,具體如table.1.
table.1
如果我們仔細思考會發現這樣的路由方式是非常合理的
在PCIE拓撲結構中能夠進行路由的結構只有Switch和RC。所以我們有必要介紹一下它們。 Switch就是一個多端口設備,用來連接多個設備。Switch可以理解為一個雙層橋結構,其中還包含一個虛擬總線連接這個雙層橋。其中每個橋設備(Bridge)一端連接到一個外部PCIE設備,一段連接到虛擬總線。對Switch結構進行Configuration配置是使用Type 1。
Fig.1
對於需要進行路由的設備,當它接受到一個TLP時,首先會判斷這個TLP是不是發送給它自己的,如果是,接受它,如果不是,那就繼續路由轉發。
現在我們一一介紹:
1.Address Routing
當PCIE設備想訪問內存(system memory)時,或者CPU想訪問PCIE設備的memory時,使用一個含有地址請求包,這個時候就是Address Routing方式。
Fig.2
當一個Endpoint設備接受一個TLP之后,設備會首先檢查它的Header中的Fmt和Type,如果屬於Address Routing,檢查是3DW還是4DW地址。然后設備將會對比設備的Configuration中的Base Address Register和TLP頭中的地址,如果相同,就接受這個TLP,不相同就拒絕。
Fig.3
當一個Switch設備接受一個TLP之后,首先檢查是否為Address Routing,如果是,那它就對比TLP中目的地址和自己的Configuration中的Base Address Register,如果相同,就自己接受這個數據包,如果不同,它會去檢查是否符合它下游設備的Base/Limit Register地址范圍。
Fig.4
關於Switch補充下列幾點:
- 如果TLP中的地址符合它下游的任意一個Base/Limit Register地址范圍,它就會將數據包向下游傳遞。
- 如果傳遞到下游的TLP不在下游設備的BAR(Base Address Register)或者Base/Limit Register。那么下游就向上游傳遞一個unspported請求。
- 向上游傳播的TLP永遠向上游傳播,除非TLP中的地址符合Switch的BAR或者某個下游分支。
2.ID Routing
當進行Configuration Write/Read,或者發送Message時使用ID,都將使用ID Routing方式。
Fig.5
當一個Endpoint設備接受到一個ID Routing TLP之后,它會對比它在初始化時得到的Bus#,Dev#,Fn#,如果相同, 就接受,不接受就拒絕這個消息。如Fig.5所示。當系統Reset之后,所有設備的ID都變為0,並且不接受任何TLP,直到Configuration Write TLP到達,設備獲取ID,再接受TLP。
當一個Switch設備接受一個TLP時,它會首先判斷這個TLP的ID的自己的ID是否相同,如果相同,它就內部接受這個TLP。如果不相同,它就檢查它是否和它的下有設備的ID是否相同。
Fig.6
關於Switch補充下列幾點:
- 如果TLP的ID和它的下游任意接口的configuration中的Secondary-Subordinate Register符合,它就向下游傳播這個包。
- 如果傳遞到下游的TLP不和下游設備自身的ID符合。那么下游就向上游傳遞一個unspported請求。
- 向上游傳播的TLP會一直向上游傳播,除非TLP是傳遞給Switch或者某個分支的。
3.Implicit Routing
只有Message使用Implicit Routing。詳細信息見Table.2。
Table.2
Fig.7
當一個Endpoint設備接受到一個Implicit Routing TLP之后,它只會簡單的檢查這個TLP是不是適合自己,然后去接受。
當一個Switch設備接受一個TLP時,它會考慮這個TLP接受的端口,並根據它的TLP頭判斷這個TLP是不是合理的。例如
- Switch設備接受一個從上游來的Broadcast Message之后,它會轉發給自己所有的下游連接,當Switch從下游設備接受一個Broadcast Message之后,它會把它當做一個畸形TLP。
- Switch設備在下游端口接受到一個向RC傳播的TLP,它會把這個TLP傳播到所有的上游接口,因為它知道RC一定在它的上游。反之,如果從它的上游接受一個這樣的TLP,那顯然是一個錯誤的TLP。
- 如果Switch接受一個TLP之后,TLP表示應該在他的接受者處停止,那么Switch就接受這個消息,不再轉發。
到這里三種路由方式就介紹完成了。
PCIE開發筆記(四)PCIE系統configuration和設備枚舉篇
在上一篇我們介紹了路由方式。其中有ID Routing這種方式。但是我們應該有一個疑惑,就是設備它的Bus Number,Device Number,Function Number是怎樣得到的。
首先我們可以排除一種情況,就是這些ID不可能是硬編碼在設備中的,應為PCIE拓撲結構千變萬化,如果使用硬編碼那就與這種情況矛盾。實際上在一個PCIE系統在Power On或者Reset之后,會經歷一個初始化和設備枚舉的過程,這個枚舉過程結束之后就會得到他的所有ID。
現在我們就開始介紹PCIE設備是怎么被發現的,整個拓撲結構是怎么建立的。
首先,在一個PCIE Device中支持最高8個Function,那什么是Function?一個設備它可以同時具有幾個功能,每個功能對用一個Function,且每個Function必須擁有一個Configuration來配置他的必要屬性。且如果一個設備只有一個Function,那么他的Function Number必須為0,如果這個設備是一個多Function的設備,那么它的第一個Function Number必須為0,其余的Function Number可以不必按照順序遞增。
我們再介紹幾個概念,Rrimary Bus代表的一個橋設備直接相連的上游Bus Number。Secondary Bus代表的一個橋設備直接相連的下游Bus Number。Sub Bus Number代表這個橋下游最遠的Bus Number。
在設備啟動之后整個系統的拓撲結構是未知的,只有RC內部總線是已知的,命名為Bus 0,這是硬件編碼在芯片當中的。
Fig.1
那么系統是怎么識別一個Function是否存在哪?我們以Type 0結構的Configuration為例,有Device ID和Vendor ID,它們都是硬編碼在芯片當中,不同的設備有着不同的ID,其中值FFFFh保留 ,任何設備不能使用。當RC發出一個Configuration讀請求時,如果返回的不是FFFFh,那么系統就認為存在這樣的設備,如果返回的為FFFFh那么系統就認為不存在這個設備。
Fig.2
在上一篇中我們說到過,這個系統中,只有RC能進行Configuration Write操作,否則整個體系會發生混亂。
那么RC是怎么產生一個Configuration Write或者Configuration Read操作的?我們都知道RC只是代替CPU進行操作的,那么CPU怎么樣才能讓RC產生一個這樣的操作那?有幾種想法可以實現,例如,我們把所有Configuration空間映射到系統的Memory Space中,但是如果系統存在大量的設備,那么將占據大量的Memory Space,這樣是不高效的。所以一個非常聰明的方法被提出(另外一種方法這里就不提了)。
系統將IO Space中從0CF8h-0CFBh這32bit划分為Configuration Address Port,將0CFCh-0CFFh這32bit划分為Configuration Data Port。
Configuration Address Port有一下定義:
Fig.3
- Bit0-1是硬編碼進芯片的。
- Bit2-7定義Target dword,表明要寫的comfiguration space的位置。Configuration Space總計有64個Dword。
- Bit8-23為目標的ID。
- Bit31為使能位,為一時使能工作。
當CPU想要進行一個Configuration Write時,它只需在Configuration Address Port寫進它的地址,和Target dwor。在Configuration Data Port寫入數據,使能就完成一個配置寫操作了。當CPU從Configuration Data Port進行讀操作就完成配置讀操作了。
以前說到Configuration 分為Type 0和Type 1兩類。這里我們說明一下它們的區別。
首先它們的TLP Header不一樣,但是它們最大的不同如下:
當CPU產生一個Configuration Write時,如果寫的目的Bus等於RC的Bus,那RC會直接產生一個Type 0,然后Bus中的某個設備接受它。如果不相同,那么它將產生一個Type 1,並且繼續向下傳播。下游的橋接受后,它會是首先對比目的Bus和自己的Secondary Bus,如果相同的話,它會將這個TLP從Type 1轉化為Type 0,然后Bus上的某個設備接受它,如果Secondary Bus<目的Bus,這個橋設備不對這個TLP進行類型轉換,繼續向下傳播,直到目的Bus=Secondary Bus再完成類型轉換。**
Fig.4
現在我們開始介紹如何進行設備枚舉。
在系統上電或者Reset之后,設備會有一個初始化的過程,這個過程中設備的寄存器都是無意義的,當初始化之后,所有寄存器數據穩定並且有意義。這是才可以進行Configuration和設備的枚舉。
Fig.5
在系統完成初始化之后,整個系統如Fig.3所示,只有RC中的Bus被硬編碼為Bus 0。這時枚舉程序開始工作,首先他將要做的是:
1.枚舉程序將要探測Bus 0下面有幾個設備,PCIE允許每個總線上最多存在32個Device。上面我們已經介紹了怎么探測一個設備是否存在,這時RC將要產生一個Configuration Read TLP,目的ID為Bus 0,Device 0,Function 0,讀取Vendor ID,如果返回的不是FFFFh,那表明存在Device 0,Function 0。跳到下一步。如果返回為FFFFh,那就表明一定不存在Device 0。那么程序就開始探測是否存在Bus 0,Device 1,Function 0。
Fig.6
2.上一步探測到Device 0(Fig.2中的A)存在。在設備的Header Type field中存在Header Type Register,Capability Register。這兩個Register表明Device的一些特性。Header Type Register的Bit 7表明他是否是一個多功能設備(Multi-function Device)。Capability Register的Bit4-7表明這個設備的類型,詳細如Table.1。假設現在枚舉程序開始讀取Bus 0,Device 0,Function 0的這兩個寄存器,返回Header Type數據為00000001b,表明這是一個單Function,橋設備說明它還有下游設備即存在Bus 1。
Fig.7 Header Type Register
Fig.8 Capability Register
Table.1
3.現在程序進行一系列Configuration Write操作,將A設備(Fig.2)的Primary Bus Number Register=0,Secondary Bus Number Register=1,Subordinate Bus Number Register=1.現在橋A就可以感知他的下游總線為Bus 1,下游最遠的總線為Bus 1.
4.程序更新RC的相關寄存器。
5.程序讀取A的Capability Register,得到Device/Port Type Field=0100b,表明這是RC的一個Root Port。
6.程序必須執行Depth-first Search。也就是說在探測Bus 0上的其他設備,程序應該首先探測Device A下面的所有設備。
7.程序讀取Bus 1,Device 0,Function 0的Vendor ID,一個有效的值返回,表示存在這個設備C。
8.讀取Header Type field,返回00000001,表明C是一個橋,且C是一個單功能設備。
9.C的Capability Register Device/Port Type Field=0101b,表明這是一個Switch的上行端口。
10.程序對C重復像步驟3一樣的操作。
11.同時更新Host/bridge和橋A的Subordinate Bus Number=2。
12.繼續Depth-first Search,發現設備D。讀取相關寄存器,得知D為Switch的下游接口橋設備。得知Bus 3的存在。對D的寄存器修改,更新上游設備。
13.對Bus 3 上的設備進行探測,發現Bus 3,Device 0。讀寄存器,發現為Endpoint多功能設備。
14.結束Depth-first Search,回滾到Bus 2,繼續探測設備,然后執行Depth-first Search。
15.重復上述過程完成所有設備遍歷。
同時還有一個問題擺在面前,一個Function是怎么知道它自己的Bus Number和Device Number的。一個Function一定知道自己的Function Number的,Device的設計者在設計的時候會以某種方式例如硬編碼寄存器方式告訴它自己的Function Number。而Bus Number和Device Number不可能以這種方式實現,因為設備它不同的位置它的ID不一樣。當一個Configuration TLP到達一個Completer后,Completer會以某種方式記錄TLP Header中的目的ID,也就是自己的ID,從而獲得自己的ID。
當以上說有的操作完成之后,這個體系的ID都已經建立,那么所有的Configuration都可以正確的進行傳播,然后其他程序才會正常的進行工作,例如,我們知道Address Routing這種方式,當采用這種方式進行工作時,需要首先對Base Address Register進行Configuration,才能正常的路由。而如果沒有建立一個正確的ID體系是無法進行Configuration的。關於BAR寄存器的相關設置稍后在講解。
隨后我將介紹大致介紹基於WinDriver的驅動程序和PIO這一簡單的PCIE設備。最近比較忙,當我有足夠時間的話,我會一並寫出發布。
PCIE開發筆記(五)PCIE設備熱插拔篇
我們主要介紹PCI_E設備的熱插拔(Hot Plug)功能。
熱插拔是一個非常重要的功能,很多系統需要熱插拔功能從而盡最大可能減少系統停機的時間。
PCI設備需要額外的控制邏輯去控制PCI板卡,來完成例如上電,復位,時鍾,以及指示器顯示。PCI_E相較於PCI設備具有原生的熱插拔功能(native feature),而不需要去設置額外的設備去實現熱插拔功能。
PCI_E的熱插拔功能需要在熱插拔控制器(Hot Plug Controller)的協助下完成,該控制器用來控制一些必要的控制信號。這些控制器存在在相互獨立的根節點(Root)或者開關(Switch)中,與相應的端口(port)相連。同時PCI_E協議為該控制器定義了一些必要的寄存器。這些控制器在熱插拔軟件控制下,使着相連的端口的控制信號有序地變化。
一個控制器必須實現以下功能。
- 置位或者不置位與PCI_E設備相連的PERST#復位信號。
- 給PCI_E設備上電或者斷電。
- 選擇性地打開或者關閉用來表示當前設備狀態的指示器(例如LED指示燈)。
- 檢測PCI_E設備插入的插槽(slot)發生的事件(例如移去一個設備),並將這些事件通過中斷方式報告給軟件。
PCI_E設備和PCI設備通過一種稱作無意外(no surprises)方式實現熱插拔。用戶不允許在未告知系統軟件的情況下插入或者移除一個PCI_E設備。用戶告知軟件將要插入或者移除一個設備之后,軟件將進行相關操作,之后告知用戶是否可以進行安全的進行這個操作(通過相應的指示器)。然后用戶才可以進行接下來的操作。
同時PCI_E設備也可以通過突然意外(surprise removal)的方式移除設備。它通過兩根探測引腳(PRSNT1#,PRSNT2#)來實現,兩個引腳如Fig1,Fig2所示。這兩個引腳比其余的引腳更短,那么在用戶移除設備的時候這兩根信號會首先斷開,系統會迅速的檢測到,並迅速做出反應,從而安全移除設備。
Fig1
Fig.2
在上面簡單的接受之后我們開始介紹實現熱插拔必備部分:
- 軟件部分
- 硬件部分
首先介紹軟件部分。
下表將詳細介紹實現熱插拔必須的軟件,以及它們的層次結構。
- User Interface 用戶接口
操作系統提供給用戶調用,來請求關閉一個設備或者打開一個剛剛插入設備。
- Hot-Plug Service
一個用來處理操作系統發起的請求的服務程序,主要包括例如提供插槽的標識符、打開或者關閉設備、打開或者關閉指示器、返回當前某個插槽的狀態(ON or OFF)。
- Standardized Hot-Plug System Driver
由主板提供,接受來自Hot-Plug Service的請求,控制熱插拔控制器(Hot Plug Controller)完成響應請求 。
- Device Driver
對於一些比較特殊的設備,完成熱插拔需要設備的驅動設備來協作。比如,當一個設備移除之后,要將設備的驅動程序設備為靜默狀態,不再工作。
硬件部分
下表詳細的介紹了實現熱插拔必須的硬件部分。
- Hot-Plug Controller 熱插拔控制器
用來接收處理Hot-Plug System Driver發出的指令,一個控制器連接一個支持熱插拔的端口(port),PCI_E協議為控制器定義了標准軟件接口。
- Card Slot Power Switching Logic
在熱插拔控制器的控制下完成PCI_E設備的上電與斷電。
- Card Reset Logic
在熱插拔控制器的控制下對與PCI_E設備連接的PERST#復位信號置1或者置0。
- Power Indicator電源指示器
每一個插槽分配一個指示器,由熱插拔控制器控制,指示當前插槽是否上電。
- Attention Button
每一個插槽分配一個按鈕,當用戶請求一個熱插拔操作時,按壓這個按鈕。
- Attention Indicator
每一個插槽分配一個指示器。指示器用來引起操作者的注意,表明存在一個熱插拔問題,或者熱插拔失敗,由熱插拔控制器控制。如圖Fig.3就是一個服務器硬盤的示意圖。
Fig.3
- Card Present Detect Pins
總共有兩種卡存在信號,PRSENT1#,PRSNT2#。作用上面我們已經介紹過了。
完成上述必要部分的介紹之后,我們開始介紹PCI_E設備熱插拔實現框架,了解上述各個部分是如何連接,相互協作的。同時與PCI設備的熱插拔進行對比。
首先我們介紹PCI設備的熱插拔框架,PCI是共享總線結構,即一條總線上連接多個設備,在實現熱插拔過程就需要額外的邏輯電路,如圖Fig.4,系統存在一個總的Hot-Plug Controller,在控制器里面存在各個插槽對應的控制器,控制器在Hot-Plug System Driver的控制下完成熱插拔過程。
Fig.4
而PCI_E是點對點(Peer To Peer)拓撲結構,同時原生支持熱插拔功能,這就決定它的系統框架不同於PCI。如圖Fig.5,熱插拔控制器分散存在於每個根聚合點(Root complex)或者開關(Switch)中,每個端口(Port)對應一個控制器,不再需要一個單獨額外的控制器。用戶通過調用用戶接口來一層一層實現最后的熱插拔功能。
Fig.5
下面我們開始介紹PCI_E設備的實現熱插拔的具體過程。(這里假設操作系統完成對一個新插入的設備的配置,我們以前的教程有提到過)。
熱插拔過程可以分為這幾個過程(以移除為例):
- 用戶要移除某個設備,通過物理方式(例如按下按鈕)或者軟件方式告知操作系統。
- 操作系統接收到請求之后進行一些必要的操作(例如完成現在正在進行的寫操作,並禁止接收新的操作),然后通過物理(例如指示燈)或者軟件的方式告知用戶你的請求是否可以滿足。
- 如果第二步操作系統告知用戶可以進行請求的操作的話,Hot-Plug System Driver將插槽(slot)關閉。通過控制根聚合點(Root complex)或者開關(Switch)中相應的寄存器完成插槽狀態的轉換。具體的寄存器介紹稍后介紹。
- 用戶移除相應的設備。
首先介紹打開或者關閉插槽的過程。
首先了解兩個狀態:
- 插槽開狀態(On)
該狀態有如下特點:
\1. 插槽已經上電。
\2. 參考時鍾REFCLK已經打開。
\3. 插槽連接狀態處在活躍狀態(active),或者由於電源管理(Active State Power Management)處於待命狀態(L0s or L1)。
\4. 復位信號PERST#置0。
- 插槽關狀態(Off)
該狀態有如下特點:
\1. 插槽斷電
\2. 參考時鍾REFCLK已經關閉。
\3. 連接狀態處於靜默狀態(inactive)。相應信號線處於高組態。
\4. 復位信號PERST#置1。
下面我們介紹關閉和打開插槽的具體流程。
關閉插槽過程:
\1. 將鏈路鏈接關閉。端口在物理層(Physical Later)將相關信號設置為高阻態。
\2. 將插槽的復位信號PERST#置1。
\3. 關閉插槽的參考時鍾REFCLK 。
\4. 將插槽斷電。
打開插槽過程:
\1. 將插槽上電。
\2. 打開插槽的參考時鍾REFCLK。
\3. 將復位信號PERST#置0。
一旦插槽上電,參考時鍾打開,復位信號置0之后,鏈接兩端將會進行鏈路訓練和初始化,之后就可以收發TLP了。
下面我們將詳細介紹設備移除和設備插入過程:
- 移除設備:
移除方式可以以物理方式和軟件方式兩種方式進行。
當以物理方式時:
\1. 當通過物理方式告知系統將要移除設備。操作者需要按下插槽中Attention Button。熱插拔控制器偵測到這個事件之后,給根聚合點傳遞一個中斷。
\2. Hot Plug Service調用Hot-Plug System Driver,讀取現在插槽的狀態。之后Hot Plug Service向Hot-Plug System Driver發送一個請求,要求Hot-Plug Controller控制Power Indicator從常亮轉向閃爍。操作者可以在五秒內再次按下Attention Button中止移除。
\3. 當第二步讀取卡槽狀態,卡移除請求驗證成功,並且熱插拔軟件允許該操作之后,Power Indicator將持續閃爍。當設備正在進行一些非常重要的操作時,熱插拔軟件可能不允許該操作。如果軟件不允許該操作時,軟件將會拒絕該操作,並讓Hot-PlugController將Power Indicator停止閃爍,保持常亮。
\4. 如果該操作被允許,Hot Plug Service命令Device Driver靜默,完成已經接收的操作,並不在進行接受或發出任何請求。
\5. 軟件操作鏈接兩側端口的Link Control Register關閉兩側設備的數據通道。
\6. 軟件通過Hot-Plug Controller將插槽關閉。
\7. 在斷電成功之后,軟件通過Hot-Plug Controller將Power Indicator電源指示器關閉,此時操作者知道設備此時可以安全的移除設備了。
8.操作者將卡扣(Mechanical Retention Latch,將設備固定在主板上的設備,可選設備,上面有一個傳感器,檢測卡扣是否發開,如圖Fig.5)打開,將所有連接線(如備用電源線)斷開此時卡移除掉。此步驟與硬件有關,可選。
Fig.5
\9. 操作系統將分配給設備的內存空間(Memory Space),IO空間(IO Space),中斷線回收再利用。
當以軟件方式移除時與物理方式基本相同,但是前兩步替換為以下步驟:
操作者發出一個與設備相連的物理插槽號(Physical Slot Number)的移除請求。然后軟件顯示一個信息要求操作者確認,此時Power Indicator開始由常亮轉向閃爍。
- 插入設備:
插入也分為物理方式和軟件方式,設備插入過程是移除的一個反過程,我們這里假設插入的插槽已經斷電。
物理方式具體過程如下:
- 操作者將設備插入,關上卡扣。如果卡扣存在傳感器的話,Hot-Plug Controller將會感知到卡扣已經關閉,這是控制器將會將備用信號和備用電源連接到插槽。
- 操作者通過按壓Attention Button來通知Hot-Plug Seriver。同時引起狀態寄存器相應位置位,並引發中斷事件,發往根聚合點。然后,軟件讀取插槽狀態,辨識請求。
- Hot-Plug Service向Hot-Plug System Driver發出請求要求Hot Plug Controller閃爍插槽的Power Indicator,提示現在不能再移除設備了。操作者樂意在閃爍開始5秒內再次按壓Attention Button來取消插入請求。
- 當設備允許請求時,Power Indicator將保持閃爍。可能因為設備安全因素禁止插入某些插槽,熱插拔軟件將不允許該操作。如果軟件不允許該操作時,軟件將會拒絕該操作,並讓Hot-Plug Controller將Power Indicator熄滅。同時建議系統用消息或者日志的方式記錄禁止的原因。
- Hot-Plug Service向Hot-Plug System Driver發送命令,將相應卡槽打開。
- 一旦插槽打開,軟件命令將Power Indicator打開。
- 一旦鏈路訓練完成,操作系統開始進行設備枚舉,分配Bus Number,Device Number,Function Number,並配置相應配置空間(configuration space)。
- 操作系統根據設備信息加載相應的驅動程序。
- 操作系統調用驅動程序執行設備的初始化代碼,設置配置空間相應的命令寄存器,使能設備,完成初始化。
軟件過程可以類比,到此這個熱插拔過程介紹就完成了。
下面介紹PCI_E建議的用戶接口和相應寄存器介紹。
首先介紹軟件之間的接口。協議沒有詳細的定義這些接口,但是它定義了一些基礎的類型和它的內容。Hot-Plug Service和Hot-Plug System Driver之間的接口如下:
- Query Hot-Plug system Driver
輸入:none 輸出: Hot-Plug System Driver控制的邏輯插槽ID
功能:詢問Hot-Plug System Driver控制的的插槽,並返回其邏輯插槽ID。
- Set Slot Status
輸入:邏輯插槽ID、新的狀態、新的Attention Indicator狀態、新的Power Indicator
狀態
輸出:請求完成狀態
功能:用來控制slot和與之相關的Indicator。
- Query Slot Status
輸入:邏輯插槽ID 輸出:插槽狀態 設備電源需求
功能:返回插槽狀態,Hot-Plug System Driver返回相比相應信息。
下面介紹熱插拔控制器的可編程接口。PCI_E協議已經在配置空間中定義了相關寄存器。Hot-Plug System Driver通過控制Fig.6紅圈內有相關寄存器來控制熱插拔控制器,進而實現熱插拔。
Fig.6
- Slot Capabilities Register 這個寄存器主要表明設備存在哪些指示器,傳感器。存在在插槽和設備的配置空間中。硬件必須要初始化這個寄存器以表明設備實現哪些硬件了。軟件通過讀該寄存器來獲取硬件信息。Fig.7是該寄存器示意圖,Table.2是詳細描述。
Fig.7
Table.1
- Slot Control 該寄存器作為軟件接口,可以控制一系列熱插拔事件。比如那個事件的發生可以引起中斷。
Fig.8
Table.2
- Slot Status and Event Management 熱插拔控制器監控一系列事件,將這些事件報告給Hot-Plug System Driver 。軟件通過讀取這個寄存器來判斷那個事件引起中斷。寄存器中改變的位必須通過軟件清零,
以探測之后發生的事件。
Fig.9
Table.3
這里熱插拔篇就結束了,之后會更新有關熱插拔的電氣要求。
PCIE開發筆記(六)PCIE設備中斷篇
使用Xilinx IP核進行PCIE開發學習筆記(六)PCIE設備中斷篇
中斷(Interrupt)是一個非常重要的機制,在各中系統中都普遍存在,通過中斷,我們可以為CPU減負,從而可以節約CPU的時鍾周期,讓CPU去做一些其他重要的事情。只在一個事件(Event)發生之后,通過引發中斷的方式來調度CPU來完成中斷處理。
下面我們介紹PCI_E協議中的中斷。
首先,中斷功能是PCIE設備的一個可選功能。
在PCIE設備中,實現中斷有兩種方式。
- Native PCI Express Interrupt Delivery PCIE協議在2.2版本之后排除了使用額外的信號線來傳遞中斷的方式,取而代之的是一種稱之為MSI(Message Signaled Interrupt)的方法。它通過發送TLP包的方式來告知中斷。但是應該注意的是,不要被字面意思蒙蔽,這個TLP包的類型不是Message類型,而是一個簡單的存儲器寫類型的TLP包。同時我們可以通過寫TLP包的目的地址來判別這是一個MSI數據包還是一個普通的存儲器寫數據包。因為MSI的目的地址是系統特意分配給它的。
- Legacy PCI Interrupt Delivery 這種是PCI協議支持的中斷方式,PCI_E協議為了兼容性也允許這種方式。它通過額外的信號線(INTx)來傳遞中斷信號。
我們在整個系統中闡述着兩種方式。
- 如果一個原生PCIE設備(Native PCI Express function)需要中斷功能,那它必須使用MSI這種方式。
- 對於一個傳統的終端設備(Legacy Endpoint device)必須支持MSI,對INTx型的可以選擇性支持,因為有些設備(boot devices)在開機boot時間只能使用傳統的中斷,而MSI只有在開機成功設備驅動載入以后才能使用。
- 對於PCI_E TO PCI橋設備,必須支持INTx類型中斷。
Fig.1
- MSI型中斷
MSI型中斷通過向根聚合點發送一個存儲器寫操作來完成中斷。MSI Capability register提供觸發這個中斷的所有信息。這個寄存器是由Configuration 軟件來完成初始化的。寄存器提供一下信息:
- 目標地址(Target memory address)。
- 寫操作的數據內容。
- 可以使用的消息數(Message Number)。
下面我們具體介紹MSI Capability register,每個設備通過這個寄存器表明它對MSI的支持。每個原生PCI_E設備(native PCI_E function)必須在它的配置空間(configuration space)中實現這個獨一無二的寄存器。同時存在兩種類型的MSI Capability register。
- 64-bit地址格式寄存器,所有原生設備(Native PCI_E device)必須實現。
- 32-bit地址格式寄存器。老式設備可選(Legacy device)。
32-bit MSI Capability register
64-bit MSI Capability register
下面分別介紹每個字段含義:
Capability ID
這個字段被硬件編寫為05h,只讀。表明這個寄存器的類型。
Pointer To Next New Capability
這個字段用來指向下一個新的Capability寄存器的地址(相當於鏈表結構),如果這個是最后一個Capability寄存器,那就寫00h。這個字段也是只讀,硬編碼的寄存器。
Message Control Register
Message Control Register
字段含義:
Message Address Register
寄存器的低兩位是硬件置0的,使地址強制與字對齊。
原生設備必須具有高32位地址寄存器。當Message Control register的第七位置1時出現高32 寄存器。如果出現高32位寄存器,那么它和低32位寄存器連接組成一個64位地址寄存器。當第七位置位0,那寄存器就是一個單純的32位寄存器。
Message Data Register
系統軟件通過向這個16位的可讀寫寄存器寫入數據來告知設備一個基本的數據格式(Base Message Data Pattern)。當設備需要產生一個中斷請求時,設備向Message Address Register中的地址寫入一個32位數據。數據有以下格式:
- 高16位總是設置為0。
- 低16位由Message Address Register提供。如果有多個中斷同時發生,那在該寄存器內數據上做相依修改,后面將會詳細介紹。
- MSI配置過程
- 在初始化過程,配置軟件掃描總線,尋找設備。當軟件發現一個設備(Device Function),配置軟件讀取能力列表指針(Capabilities List Pointer),獲得第一個能力寄存器。
- 軟件搜索能力寄存器列表,找到MSI Capability register(ID為05h)。查看設備是否支持MSI中斷,不支持則退出,支持則進入下一步。
- 軟件對設備的Message Address register賦值。用於請求中斷時存儲器寫地址的目的地址。
- 軟件檢測Message Control寄存器中的Multiple Message Capable字段,查看設備准備請求多少消息。這里每個消息(Message)對應設備中發生的一個事件(Event)。
- 軟件向設備分配消息,消息數等於或着小於設備的請求消息數。軟件最小應該給設備分配一個消息。
- 軟件向設備的Message Data寄存器寫入Base Message Data Pattern。
- 軟件設置Message Control register中的MSI Enable bit,允許設備產生中斷。
- MSI中斷產生
設備通過向處理器發送一個存儲器寫數據包來產生中斷。這個數據包的數據負載為1DW。它的目的地址和數據負荷上面已經介紹過。
關鍵點如下:
- Format 字段必須是11b,表明這是一個4DW包頭,帶數據載荷的數據包。對遺留的設 備(legacy device),標頭可以使10b。
- Header Attribute Bits必須為0。
- Length Field必須為01h。
- First BE field必須為0011h。Last BE field必須為0000h。
- Address field從MSI Capability register中獲得。
- 數據載荷的低16位從MSI Capability register中的data field獲得。
可以參考Fig.2。
Fig.2
混合消息:
如果設備請求了多個消息(message),並且軟件也給它分配了多個消息,那么當設備准備發送一個不同的中斷,那么它需要適當修改數據包的數據載荷。
我們舉一個例子,假設4個消息分配給設備,Message Data register的值被設置為0500h,同時Message Address register值為0A000000h。那么,當四個消息所對應的時間的任意一個發生,那個設備將會產生一個向0A000000H地址的存儲器寫數據包,其中的數據載荷分別為00000500h,00000501h,00000502h,0000503h。也就是數據包數據載荷的高16位補零,低16位根據Message Data register的值按順序增加。
- Legacy PCI Interrupt Delivery 中斷
這種方式是采用中斷信號線INTX來實現,在PCI協議中規定,每個設備支持對多四個中斷信號,INTA,INTB,INTC,INTD。中斷是電平觸發的,當信號線為低電平,這引發中斷。一旦產生中斷,則該信號線則一直保持低電平,直到驅動程序清除這個中斷請求。這些信號線可以連接在一起。在設備的配置空間里(Configuration Space)有一組寄存器(Interrupt Pin)表明一個功能(Function)使用哪個中斷信號。
Fig.3
所有的中斷信號都與系統的可編程中斷路由器(Programmable Interrupt Router)相連,這個路由器有四個中斷輸入接口。設備與這個路由器的中斷輸入接口怎么相連是系統的設計者在設計系統的時候就已經決定了,用戶是不能改變的。操作系統通過一個中斷路由表來得知。路由表記錄每個中斷向量(IRQ)與每個設備的中斷信號的連接情況。例如Fig.4,表會記錄device 0 INTA、Device 1 INTA、Device 2 INTA與IRQ10相連。
基於與每個設備相連的INTX信號的路由關系,在設備啟動之后,配置軟件將會給每個設備一個Interrupt Line Number,並修改設備的配置空間。這個值將會告訴每個設備的設備驅動當這個設備發生中斷時,哪個中斷向量將會被報告。當設備產生一個中斷,CPU將會接收一個與Interrupt Line register中對應的中斷向量(一個中斷向量對應一個程序入口)。CPU將會使用這個向量來檢索中斷服務表(Interrupt Service Table,用來存儲中斷向量和程序入口的對應關系)來獲取與這個設備的驅動相關的中斷服務程序。
Fig.4
在Fig.4中我們可以看到有幾個不同的設備連接在一起,共用一個Interrupt Line Number。那么當設備產生中斷之后,CPU接收到中斷向量之后如何判斷哪個設備產生的中斷,進而調用相應的中斷服務程序?
在系統啟動之后,會根據路由描述表來建立中斷服務表。以IRQ10為例,系統首先掃描到Device 0 INTA,那么它將中斷服務列表IRQ10的中斷服務程序入口登記為Device 1 INTA的,如果之后發現Device 1 INTA也使用IRQ10,那么它就會覆蓋掉上一步,將入口登記為Device 1 INTA的,並將Device1的中斷服務程序的最后指向Device 0 INTA的服務程序,依次類推,形成一個設備鏈。那么之后中斷到來之后,會首先調用最后掃描到的設備的中斷服務程序,該程序通過查詢中斷請求位來判斷該設備是否引發中斷,如果沒有那就沿着設備鏈找到下一個設備,繼續查詢,直到最后找到引發中斷的設備。
相關寄存器
設備應該實現一個設備特定的寄存器,這個寄存器可以並映射到Memory或者IO地址中。在這個寄存器中應該有這個一位,當一個設備產生中斷之后,設備將在這位設置為1,驅動通過讀這位來判斷有一個中斷等待處理,當清理這個位之后,中斷就失效了。
同時在設備的配置空間中有兩個寄存器。
Fig.5
Interrupt Disable 當該位置位時,中斷失效。
Fig.6
Interrupt Status 該位在中斷發生,並等待解決時置位。只讀。
到這里PCI-E中兩種中斷方式的相關內容介紹完畢。還有虛擬INTx消息可以查閱相關資料了解一下。
PCIE開發筆記(七)DLL介紹(上)
在之前的文章中,我們已經知道PCIE協議可以划分為三個層,每個層的作用與分工不同。在接下來的幾篇文章中,我將詳細介紹PCIE協議中三個層的詳細作用。
圖1 PCIE結構分層
一個良好的結構分層可以使整個系統功能簡明。明確每個部分的工作是什么,使得每個層只專注自己應該做的工作,同時也使得當問題出現之后能夠馬上定位出問題出現的位置。
如果你了解TCP/IP網絡協議,那么你就不難發現它好像是PCIE的一個升級版,增加了更多的層次。同時PCIE又好像是TCP/IP協議的一個濃縮版,保留了達到目的的必要層次。如果你仔細思考這些層的划分,你就不難想到, 其實他們的相似並非偶然,而是進行一個功能比較完善的通信所必須的,同時又因為兩者的目的的不同,而產生一些必然的差異。
- TL層(Transaction Layer):TL層着眼於數據包在整個拓撲結構中的傳輸,完成數據包的路由工作,同時進行一些數據包傳輸優先級的控制,從而滿足一些特定要求的數據包的傳輸。
- DLL層(Data Link Layer):DLL層主要作用是確保一個數據包能夠正確地從一個鏈路的發送端傳輸到鏈路的接收端。
- PL層(Physical Layer):PL層的主要作用是探測一個鏈路的兩端是否連接能夠工作的設備。在探測到鏈路兩端存在設備之后,對鏈路進行訓練,使得鏈路能夠在正確的頻率和位寬下工作。
那DLL層如何確保一個數據包正確的傳輸?
首先,我們必須對“正確的傳輸”有如下定義:
- 接收方接收的數據包與發送方的數據包完全一致的,沒有任何改變。
- 在圖1中,接收方DLL層向上傳遞給TL層的數據包的順序必須和發送方發送的順序嚴格一致(至於什么原因后面會講到)。
圖2 ACK/NAK DLLP格式
DLL層使用ACK/NAK協議保證數據包的正確傳輸。如圖2所示,發送方(Transmit)發送一個數據包之后,接收方(Receiver)會返回一個ACK/NAK DLLP(Data Link Layer Packet),並且在DLLP中包含一個序列號(Sequence Number)信息,來具體確認那一個TLP傳輸是否成功。ACK(Acknowledgement)表示成功,NAK(Negative Acknowledgment)表示失敗。
圖3 ACK/NAK協議
圖3展示了DLL層中實現ACK/NAK協議的更多細節。對於每一個通過Link由Device A發送到Device B的TLP數據包,Device B會通過TLP的LCRC(Link Cyclic Redundancy Check)來判斷TLP數據包是否存在錯誤。在檢查錯誤之后,Device B通過返回一個ACK或NAK DLLP數據包來告知Device A是否接受成功。Device A接收一個ACK DLLP則確認Device B正確地接受一個或者多個數據包,接收一個NAK DLLP則確認Device B錯誤地接受一個數據包(同時它也隱形的表示一些TLP發送成功,這個在下一篇中說明)。當Device A接收到NAK DLLP之后,Device A設備將會再次發送相關數據包。
圖4 ACK/NAK原理圖
下面我們就比照圖4詳細介紹ACK/NAK協議如何實現整個過程的。
首先介紹原理圖里面幾個部分:
- Replay Buffer:Replay Buffer:主要用來存儲TLP數據包,其中包括序列號(Sequence Number)和LCRC。所有的TLP按照發送的順序儲存,后進入的TLP的序列號總是大於先進入的序列號。 當Device A接收到ACK DLLP之后表明Device B接收TLP成功,Device A就會從Replay Buffer中去掉相應的TLP。如果收到一個NAK數據包,則再次發送整個Buffer。
- NEX_TRANSMIT_SEQ****Counter:這個計數器主要用來產生Sequence Number,這個值將會賦給下一個傳輸的TLP。這是一個32_bit計數器,當計數器到達最大值4095后回滾到0。
- LCRC Generator:LCRC產生器,用來為TLP產生一個32位的LCRC。Device B使用它來判斷接受的TLP是否有問題。
- REPLAY_NUM Count: 這是一個2_bit的計數器,主要是用來統計Device A收到NAK和REPLAY_TIMER 時間溢出次數(time out)的計數器。當該計數器溢出時,表明這個物理鏈路可能出現問題,DLL層將會觸發PL層進行一個物理鏈路重訓練(Physical Layer Link-retrain)。整個DLL層不再進行數據包的傳輸,直到重訓練完成之后。REPLAY_NUM在系統復位或者DLL層處於不活躍狀態時初始化為00b。當Device A再次發送Buffer之后,其中一些TLP被確認被接收(通過ACK或者NAK DLLP)時,則復位為00b。
- REPLAY_TIMER Counter:REPLAY_TIMER只要是用來測量從Device A發送任意TLP之后到它接收到一個與之對應的ACK/NAK的數據包之間的時間。這個計數器從發送的TLP的最后一刻開始計數。當且僅當Device A接收到一個ACK並且與之對應的TLP在Replay Buffer 中還存在時,這個計數器歸0,如果此時Buffer中還有其他未經NAK/ACK確認的TLP,則計數器重新開始計時,如果不存在未發送的TLP,就保持0。當Device A接收到NAK時,計數器歸零並保持,直到DLL再次發送數據包。當PL進行鏈路訓練時,不計數。該計數器的作用主要是用來決定什么時候進行數據重發包,使保證這個DLL層向前運行,而不是卡在某個環節(例如當TLP已經發送出去,但是始終接收不到返回的ACK/NAK DLLP,或者始終接收到錯誤的ACK/NAK DLLP)。
- ACKD_SEQ Count:一個12_bit的計數器,用來保存上一次接受到的ACK/NAK DLLP中的Sequence Number。當接受到新的ACK/NAK DLLP,就使用其中的AckNak_Seq_Num字段更新這個計數器。ACKD_SEQ可以和NEXT_TRANSMIT_SEQ一起來判斷整個Buffer是否滿。當滿足:
(NEXT_TRANSMIT_SEQ - ACKD_SEQ)mod 4096>2048
表明兩個計數器之間的間距過大,DLL層將會拒絕接收TL層傳輸的新的TLP數據包,同時一個致命的錯誤將會報告。
- DLLP CRC Check: Device A接收到一個返回的DLLP之后,會對它進行CRC檢查,如果是一個沒有錯誤的DLLP,那個將會接收它。如果是錯誤的,將什么措施都不采取,將這個DLLP丟棄。整個流程如圖5所示。
圖5 接受DLLP錯誤檢查流程
- Receive Buffer:該緩沖區用來短暫的存儲帶有TLP CRC和Sequence Number 的TLP數據包,然后傳遞給上游進行檢查,如果沒有錯誤,那么TLP將傳遞給TL層。如果存在錯誤,那么Device B將會丟棄這個TLP,並計划准備發送一個NAK DLLP數據包。
- LCRC Error Check:用來檢查接收到的TLP是否存在錯誤。
- NEXT_RCV_SEQ Count:一個12_bit的計數器,用來保存下一個期望接收到的數據包的Sequence Number。這個計數器在Device B復位或者DLL層處於休眠狀態時初始化為0。當Device A接收到一個期望的TLP之后,並且傳遞給TL層之后,該寄存器進行加1。當計數器溢出后返回0。當接收到錯誤TLP之后,寄存器不遞增。
- Sequence Number Check:在CRC檢查之后,將會對比接收到的TLP的Sequence Number位是否和NEXT_RCV_SEQ一致。
當TLP的Sequence Number等於NEXT_RCV_SEQ時,這個TLP將會被接收,並傳遞給TL層進行后續處理。同時NEXT_RCV_SEQ計數器進行加一。Device B將繼續接收進入的TLP,不返回ACK DLLP直到ACKNAK_LATENCY_TIMER溢出或者是超出預設值。
當TLP的Sequence Number小於NEXT_RCV_SEQ時,並且兩者之間的差距小於2048,說明這個TLP數據包之前已經接收過,那么Device B將會丟棄這個TLP,並計划返回一個ACK DLLP。
當TLP的Sequence Number大於NEXT_RCV_SEQ時,那么這個TLP將會被丟棄,同時計划返回一個NAK DLLP。
- NAK_SCHEDULED Flag:當Device B計划返回一個NAK DLLP時,這個位將會被置1。當Device B接收到再次發送的與返回的NAK DLLP有關的第一個TLP時,該位將清零。當該位被置1時,Device B對后續的TLP不做處理,直接丟棄。
- ACKNAK_LATENCY_TIMER:ACKNAK_LATENCY_TIMER是用來記錄第一個ACK DLLP計划發出到當前時間的長度。Device B當該值溢出或者超出預設值時,實際發出返回一個ACK DLLP。
這里我們要區別一下計划發出與實際發出之間的區別,計划發出是表明Device將要發出一個但是實際上還沒有發出,實際發出就是真正有一個DLLP從鏈路發出。使用ACKNAK_LATENCY_TIMER的原因主要是想要減輕ACK DLLP占用鏈路帶寬,具體方法后面介紹。
- ACK/NAK DLLP Generator: 這一部分主要是用來產生ACK/NAK DLLP。其中產生的DLLP中的ACKNAK_RCV_SEQ=NEXT_RCV_SEQ-1。
在詳細介紹ACK/NAK原理圖中的幾個部分之后,下面介紹這個工作流程。分為發送端(Device A)和接收端(Device B)。
- 發送端工作流程
圖6 發送方工作流程
圖4中,在Device A接收到來自TL層傳遞的TLP之后,會首先在TLP數據包的前段添加Sequence Number,在數據包后添加CRC,之后將數據包放入Buffer中。於此同時會進行兩個判斷,
- (NEXT_TRANSMIT_SEQ - ACKD_SEQ)mod 4096>2048
- 判斷Buffer是否已經寫滿
這兩個判斷B將說明DLL接收來自TLP數據包是否過多,如果過多將會阻止TL再傳遞數據包。然后Device A發送Buffer中的數據包。等待Device B返回的ACK/NAK DLLP。
當接收到Device B返回的DLLP數據包之后,會首先按照圖5對DLLP進行錯誤檢查。檢查無誤之后按照圖6流程進行相應的流程。對於接收到的DLLP,不管是ACK/NAK,都將進行圖6紅框內操作。
除了圖6所示的方式引起Replay Buffer的再次發送,另外一種引起再次發送的方式就是REPLAY_TIMER 計數器到達預設值。
- 接收端工作流程
圖7 接收方工作流程
圖7中顯示Device B接收數據包之后的工作流程,首先會進行褐色方框內的檢查,方框中工作主要是物理層進行的工作,檢查給數據包接收是否出現問題,以及這個數據包是否是一個被丟棄的數據包。這里不做過多講述。重點介紹紅框里面的內容。
在完成褐色框的檢查之后,數據包會首先進行CRC錯誤檢查,如果檢查不通過,說明接收的TLP數據包存在錯誤,那么將會判斷NAK_SCHEDULED是否置位,如果未置位,那個將該位置1,同時立即返回一個NAK DLLP,然后丟棄這個TLP。(這里NAK_SCHEDULED的作用體現出來了,你可以結合前面的內容想想它的作用)
如果檢查通過之后,將判斷Sequence Number是否等於NEXT_RCV_SEQ,如果兩者相等那就將接收到的TLP進行處理,傳遞給TL層,同時將NEXT_RCV_SEQ加一,清除NAK_SCHEDULED標志位。如果不等,將判斷
(NEXT_RCV_SEQ - Sequence Number)mod 4096<2048(判斷兩者大小)是否成立。
如果成立,說明該TLP Device B之前已經接收過,是一個重復發送的TLP,那么Device A計划發送一個ACK DLLP。如果不成立,說明Sequence Number大於NEXT_RCV_SEQ ,則說明TLP有部分TLP數據包丟失,應該立即返回NAK DLLP,同時置位NAK_SCHEDULED。
如果本篇看完后還是一頭霧水,請看下篇。
PCIE開發筆記(八)DLL介紹(下)
在PCIE開發筆記(七)DLL介紹(上)中我們大致介紹了DLL工作的流程,但是對於一些重要的細節,和一些常見的情況還沒有介紹,這樣會使讀者對整個流程沒有一個直觀的了解,現在我將就幾個常見的情況進一步介紹。
圖1 ACK/NAK DLLP
在《DLL介紹(上)》中我們大致介紹了ACK/NAK協議的工作原理,其中ACK/NAK DLLP扮演着非常重要的角色,所以很有必要在這里重新詳細地介紹這兩個DLLP。如圖1所示,DLLP有以下幾個部分:
- Type字段 Byte 0 bit7:0 :表明該DLLP是ACK還是NAK,對於ACK DLLP 該字節為0000 0000。對於NAK DLLP該字節為0001 0000。
- AckNak_Seq_Num字段: 該字段主要是提供給發送端來表明那些TLP發送成功或者失敗。AckNak_Seq_Num=NEXT_RCV_SEQ(After Incrementing) -1 這里有一個需要注意的細節!!!!在上一篇我們說到一個,當Device B(接收端)接收一個TLP數據包,如果其Sequence Number等於NEXT_RCV_SEQ,那么NEXT_RCV_SEQ將自行加一。如果此時Device B需要返回一個ACK DLLP,那么此時公式中的NEXT_RCV_SEQ為自行加一后的值。其他的所有情況,不存在NEXT_RCV_SEQ值變化,所以不需要考慮這個細節。協議做這樣的定義使非常的巧妙,在下面你將會體會到它的巧妙。
- 16-bit CRC: 用來校驗
圖2 發送端接收DLLP
ACK和NAK DLLP表示的意義不同。以圖二為例,當發送端接收一個Sequence Number為5的ACK DLLP,則表示接收端已經接收序號為5和更早的TLP,在此之后發送端將會從Replay Buffer中刪除這些數據包。當發送端接收到一個序列號為6的NAK DLLP,則表示接收端沒有成功接收序列號為7的TLP,但是於此同時間接的表明序號6及更早的TLP已經成功接收。也就是說不論接收到ACK/NAK DLLP,總是能夠證明接收端已經成功接收若干TLP。
接收端並不需要對每一個TLP都要返回一個DLLP,這樣可以有效減小ACK DLLP對鏈路帶寬的占用。
從上一篇我們可以看到,發送端不會發送兩個序列號不同的NAK DLLP。例如在圖2,如果接收端發現序號4 TLP出現錯誤,那個它將發送一個NAK,如果此后發現 5 TLP錯誤,那個接收方不會再發送一個NAK,直到它成功接收到序號4 TLP。也就是說不可能有兩個NAK同時在傳播。
當發送端正在再次發送Buffer中的TLP時,如果此時發送端接收到ACK/NAK DLLP,那么允許發送端先完成這個再次發送,之后再去處理接收的DLLP(盡管這可能不是一種高效的機制)。如果發送端接收一個ACK之后有接收一個新的ACK,那么含有較早序列號的ACK將會被丟棄。
結合一些實例講述整個流程。
實例一:接收到ACK DLLP,着重點在Device A
圖3 接收ACK DLLP之后的行為
在上一篇中我已經以流程圖的形式說明了這個流程,這里我就用圖3簡要的再闡述一下整個流程。
- Device A發送序列號為3、4、5、6、7 TLP,TLP3是第一個發送的,TLP7是最后發送的
- Device B按順序接收TLP 3、4、 5。TLP 6,7還正在發送途中。
- Device B將會對TLP3、4、5進行錯誤檢查,確認成功接收了這幾個TLP之后,它將返回一個序列號為5的ACK,表明接收了TLP3、4、5。
- Device A接收到ACK 5。
- Device A消除Buffer中TLP3、4、5。
- 當Device B接收到TLP6、7。則重復步驟3-4。
我們可以這樣總結,當Device A接收到一個ACK DLLP數據包之后,Device A需要做如下工作:
- 使用DLLP數據包中的AckNak_Seq_Num[11:0]更新ACKD_SEQ寄存器。
- 將計數器REPLAY_NUM和REPLAY_TIMER重置為0。
- 消除Buffer中序列號為AckNak_Seq_Num[11:0]及更早的TLP。
實例2 接收到NAK ,着重點在Device A
圖4 接收NAK DLLP之后的行為
如圖4所示,簡述整個流程。
- Device A發送TLP 4094、4095、0、1、2。其中TLP 4094是第一個TLP,TLP 2是最后一個TLP。
- Device B按順序接收TLP 4094、4095、0。TLP1、2還在傳輸途中。
- Device B成功接收TLP4094,TLP4094不存在問題。同時將NEXT_RCV_SEQ增加至4095。
- Device B接收到含有CRC錯誤的TLP 4095。
- Device B返回一個序列號為4094的NAK DLLP。
- Device A接收到這個NAK 4094,阻塞DLL繼續接收TL層傳遞的新的TLP,直到再次發送結束。
- Device A去除Buffer中已經接收的TLP,本例為TLP 4094。
- Device A再次發送TLP 4095、0、1、2。但是不從Buffer中去除這些TLP。
我們可以這樣總結,當Device A接收到一個NAK DLLP數據包之后,Device A需要做如下工作:
- 消除Buffer中序列號為AckNak_Seq_Num[11:0]及更早的TLP。
- 使用DLLP數據包中的AckNak_Seq_Num[11:0]更新ACKD_SEQ寄存器。
- 只有當NAK DLLP中AckNak_Seq_Num[11:0]在當前ACKD_SEQ之后,才將REPLAY_NUM重置為0。(確保Device A是在向前傳輸TLP,而不是在原地踏步)同時將REPLAY_TIMER置零。
- 再次發送整個Buffer中的TLP。
每次Device A接收到NAK DLLP,Device A將會再次發送Buffer中的TLP,同時阻止DLL的Buffer接收來自TL層傳遞的TLP,直到再次發送這個過程結束。在Device A再次發送過程中,Device A接收到ACK/NAK DLLP,那么允許Device A先完成這個再次發送,完成之后再去處理接收的DLLP(盡管這可能不是一種高效的機制)。每次再次發送時,Device A使用REPLAY_NUM來記錄再次發送的次數,也就是說接收NAK之后,再次發送這個Buffer中的TLP,如果再次發送的TLP沒有一個被Device B接收,那么REPLAY_NUM繼續加1,並再次再次發送整個Buffer,如果再次發送的TLP有部分被Device B成功接收,那么Device A將REPLAY_NUM置零。如果Device A再次發送Buffer四次,而Device B每次一個TLP都沒有接收成功,則表示整個鏈路存在一些問題,那么DLL層停止工作,並通知PL層進行鏈路訓練。
引起DLL再次發送Buffer有兩中情況:
- Device A接收到NAK DLLP。
- Device A Replay Timer計數器溢出。如果Device A發送TLP以后,但是遲遲沒有接收到返回的ACK/NAK DLLP,進而Device A什么措施都不選取顯然是非常不合理的。Device A遲遲沒有接收到返回的ACK/NAK DLLP的原因可能是DLLP在鏈路傳播途中丟失,或者途中DLLP變化,被Device A接收到之后進行CRC檢查發現錯誤而將它丟棄,或者Device B出現問題而不能發送DLLP。因此很有必要引入一個機制避免這種情況發生。Device A通過設置一個時間計數器,如果長時間接收不到DLLP,那么Device A再次發送整個Buffer,來解決這種情況的發生。
綜合實例一,二。我們可以看出,無論是ACK/NAK DLLP,都表明AckNak_Seq_Num[11:0]及更早的TLP已經成功的被Device B 成功的接受。這樣做事很巧妙地。我們從上一篇可以看出,Device A對NAK DLLP處理只是在ACK DLLP處理的基礎上多了一個再次發送Buffer的部分,這樣大大簡化了電路。
實例3 返回一個ACK DLLP,着重點在Device B
圖5 Device B返回ACK DLLP的情況
如圖5所示,簡述整個流程。
- Device A發送TLP 4094、4095、0、1、2。其中TLP 4094是第一個發送的,TLP 2是最后發送的。
- Device B按順序接收到TLP 4094、4095、0、1。NEXT_RCV_SEQ增加至2。TLP 2還在發送中。
- Device B進行CRC檢查,發送一個ACK DLLP,序列號為1。
- Device B將TLP 4094、4095、0、1傳遞給TL層。
- Device B最終接收TLP 2。重復步驟2-4。
我們可以這樣總結,Device B主要是做如下工作:
- 進行CRC檢查,TLP正確並將TLP傳遞給TL層。
- 對比TLP的Sequence Number和NEXT_RCV_SEQ,發現相同,說明是Device B想接收的TLP。遞增NEXT_RCV_SEQ。這一步保證Device B按順序接收TLP。
- 清除NAK_SCHEDULED。
- 在ACKNAK_LATENCY_TIMER溢出時,返回ACK DLLP,它的序列號為溢出時的NEXT_RCV_SEQ -1 。
實例4 返回一個NAK DLLP,着重點在Device B
圖6 Device B返回NAK DLLP的情況
如圖6所示,簡述整個流程。
- Device A發送TLP 4094、4095、0、1、2。
- Device B按順序接收TLP 4094、4095、0。TLP 1、2還在鏈路上傳遞。
- Device B正確無誤地接收TLP 4094,並將它傳遞至TL層。並將NEXT_RCV_SEQ增加至4095。
- Device B發現TLP 4095存在CRC錯誤。那么設置NAK_SCHEDULED為1,同時返回一個NAK DLLP,序號為4094。NEXT_RCV_SEQ不增加。
- Device B丟棄TLP 4095。
- Device B依舊丟棄TLP 0,盡管它是一個正確的TLP。當TLP 1、2到達時同樣也丟棄。
- Device B不為TLP 0、1、2發送ACK DLLP。因為NAK_SCHEDULED被設置為1。
- Device A接收到NAK 4094。
- Device A不再接收來自TL的TLP。
- Device A將TLP 4094從Buffer中清楚。
- Device A再次發送TLP 4095、0、1、2。
- 再次發送的TLP 4095、0、1、2依次到達Device B。
- 在確認接收的TLP無錯誤之后,Device B發現TLP 4095為再次發送的TLP,因為TLP的Sequence Number等於NEXT_RCV_SEQ。清楚NAK_SCHEDULED。
- Device B將這些TLP按順序傳遞給TL層。
我們可以這樣總結,Device B主要是做如下工作:
- 進行CRC檢查,發現TLP出現TLP錯誤。表明接收TLP失敗,那么設置NAK_SCHEDULED位。同時返回一個NAK DLLP,序列號為NEXT_RCV_SEQ -1。
- 如果CRC檢查通過,那么查看TLP的Sequence Number是否等於NEXT_RCV_SEQ。如果相等,則表明是Device B期望接收的TLP。如果小於,則表明這個TLP Device B之前已經接收,那么丟棄這個數據包,並將NAK_SCHEDULED置零。
- 如果大於,說明存在TLP丟失。那么如果此時NAK_SCHEDULED為0,就將NAK_SCHEDULED置1,返回一個NAK DLLP。如果NAK_SCHEDULED為1,那么保持原狀,不返回NAK DLLP。
綜合實例一,二。我們可以總結,出現以下情況后會立即返回一個NAK,並將NAK_SCHEDULED置1:首先返回NAK DLLP的一個大前提是NAK_SCHEDULED=0,然后出現以下任意情況
- PL層發現接受的TLP有問題。
- TLP CRC檢查不通過。
- TLP Sequence Number大於NEXT_RCV_SEQ,丟失TLP。
當TLP通過CRC,並且Sequence Number小於等於NEXT_RCV_SEQ。則Device B僅僅將NAK_SCHEDULED清零。
當以下條件滿足,Device A將返回一個ACK DLLP:
- DLL層處於工作狀態。
- 結合成功的TLP已經傳遞給TLP。但是還沒有返回一個ACK DLLP。
- AckNak_LATENCY_TIMER溢出或者到達設置的值。
- 用於傳輸ACK DLLP的鏈路已經處於L0狀態。
- 用於傳輸ACK DLLP的鏈路當前沒有傳輸其他數據包。
- NAK_SCHEDULED=0。
當NAK_SCHEDULED=1時,Device A不會返回任何DLLP,直到它被清0。
從上面的總結我們可以看出返回一個ACK DLLP更像是一個無意識的行為,只要這些條件滿足就自動返回一個ACK DLLP。而返回NAK DLLP目的性更加明顯,一旦需要返回NAK DLLP時,只需要置位NAK_SCHEDULED就馬上返回。“NAK的立即發送”可以隨時打斷“ ACK的等待條件滿足再發送”,而不會產生任何問題。
到這里我們對ACK/NAK協議介紹已經結束了。
下面我們介紹更多發生錯誤的實例,來體現ACK/NAK協議保證TLP的正確傳輸。
實例1:傳輸途中丟失TLP。
圖6 丟失TLP
- Device A按順序發送發送TLP 4094、4095、0、1、2.
- Device B接收到TLP 4094、4095、0。並返回ACK 0。並將這些TLP傳遞給TL層。同時遞增NEXT_RCV_SEQ至1。
- Device A接收到ACK 0,清楚Buffer中的TLP 4094、4095、0。
- TLP 1在傳輸途中丟失。
- TLP 2到達Device B。Device B對比TLP 2的Sequence Number和NEXT_RCV_SEQ發現 TLP 2的Sequence Number較大。說明TLP存在丟失。
- Device B丟棄TLP 2。返回NAK 0(NEXT_RCV_SEQ-1)。
- Device A接收到NAK 0。再次發送TLP 1、2。
- TLP 1、2被Device B正確的接收,並傳遞給TL層。
實例2:傳輸途中丟失ACK DLLP
圖7 丟失ACK DLLP
- Device A按順序發送TLP 4094、4095、0、1、2。
- Device B接收到TLP 4094、4095、0。並返回ACK 0。並將這些TLP傳遞給TL層。同時遞增NEXT_RCV_SEQ至1。
- ACK 0丟失。TLP 4094、4095、0仍舊保存在Buffer中。
- TLP 1、2緊跟到達Device B,並被成功接收。NEXT_RCV_SEQ增加至3。
- Device B返回ACK 2,並將TLP 1、2傳遞給TL層。
- ACK 2到達Device A。
- Device A清除Buffer中的TLP 4094、4095、0、1、2。
在這個事例中,如果ACK 0存在CRC錯誤,后續流程也是一樣的。
如果返回的ACK 2也丟失或者存在CRC錯誤,那么后續將不會再有ACK/NAK DLLP返回,那么Device A的REPLAY_TIMER將會溢出,導致Device A再次發送Buffer中的TLP。最終Device B將它們全部接收,並返回ACK 2。整個傳輸任務成功完成。
實例3:傳輸途中丟失ACK DLLP
圖8 丟失ACK DLLP
- Device A按順序發送TLP 4094、4095、0、1、2。
- Device B接收到TLP 4094、4095、0。並返回ACK 0。並將這些TLP傳遞給TL層。同時遞增NEXT_RCV_SEQ至1。
- ACK 0丟失。TLP 4094、4095、0仍舊保存在Buffer中。
- TLP 1、2緊跟到達Device B。TLP 1並被成功接收,NEXT_RCV_SEQ增加至2。TLP 1被傳遞至TL層。
- TLP 2存在錯誤。NEXT_RCV_SEQ值保持在2。
- Device B返回NAK 1,丟棄TLP 2。
- NAK 1到達Device A。
- Device A清楚TLP 4094、4095、0、1。
- Device A再次發送TLP 2。
- TLP 2到達Device B,此時NEXT_RCV_SEQ為2。
- Device B接收這個正確的TLP 2,並將它傳遞給TL層。NEXT_RCV_SEQ增加至3。
- 如果此時ACKNAK_LATENCY_TIMER正好溢出,Device B返回ACK 2。
- Device A接收ACK 2,清楚Buffer中的TLP 2。
到此DLL層主要功能介紹結束。
深入PCI與PCIe之一:硬件篇
PCI總線和設備樹是X86硬件體系內很重要的組成部分,幾乎所有的外圍硬件都以這樣或那樣的形式連接到PCI設備樹上。雖然Intel為了方便各種IP的接入而提出IOSF總線,但是其主體接口(primary interface)還依然是PCIe形式。我們下面分成兩部分介紹PCI和他的繼承者PCIe(PCI express):第一部分是歷史沿革和硬件架構;第二部分是軟件界面和UEFI中的PCI/PCe。
自PC在1981年被IBM發明以來,主板上都有擴展槽用於擴充計算機功能。現在最常見的擴展槽是PCIe插槽,實際上在你看不見的計算機主板芯片內部,各種硬件控制模塊大部分也是以PCIe設備的形式掛載到了一顆或者幾顆PCI/PCIe設備樹上。固件和操作系統正是通過枚舉設備樹們才能發現絕大多數即插即用(PNP)設備的。那究竟什么是PCI呢?
PCI/PCIe的歷史
在我們看PCIe是什么之前,我們應該要了解一下PCIe的祖先們,這樣我們才能對PCIe的一些設計有了更深刻的理解,並感嘆計算機技術的飛速發展和工程師們的不懈努力。
1. ISA (Industry Standard Architecture)
2. MCA (Micro Channel Architecture)
3. EISA (Extended Industry Standard Architecture)
4. VLB (VESA Local Bus)
5. PCI (Peripheral Component Interconnect)
6. PCI-X (Peripheral Component Interconnect eXtended)
7. AGP (Accelerated Graphics Port)
8. PCI Express (Peripheral Component Interconnect Express)
科技的每一步前進都是為了解決前一代中出現的問題,這里的問題就是速度。作為擴展接口,它主要用於外圍設備的連接和擴展,而外圍設備吞吐速度的提高,往往會倒推接口速度的提升。第一代ISA插槽出現在第一代IBM PC XT機型上(1981),作為現代PC的盤古之作,8位的ISA提供了4.77MB/s的帶寬(或傳輸率)。到了1984年,IBM就在PC AT上將帶寬提高了幾乎一倍,16位ISA第二代提供了8MB/s的傳輸率。但其對傳輸像圖像這種數據來說還是杯水車薪。
IBM自作聰明在PS/2產品線上引入了MCA總線,迫使其他幾家PC兼容機廠商聯合起來搗鼓出來EISA。因為兩者都期待兼容ISA,導致速度沒有多大提升。真正的高速總線始於VLB,它綁定自己的頻率到了當時486 CPU內部總線頻率:33MHz。而到了奔騰時代,內部總線提高到了66MHz,給VLB帶來了嚴重的兼容問題,造成致命一擊。
Intel在1992年提出PCI(Peripheral Component Interconnect)總線協議,並召集其它的小伙伴組成了名為 PCI-SIG (PCI Special Interest Group)(PCI 特殊興趣組J)的企業聯盟。從那以后這個組織就負責PCI和其繼承者們(PCI-X和PCIe的標准制定和推廣。
不得不點贊下這種開放的行為,相對IBM當時的封閉,合作共贏的心態使得PCI標准得以廣泛推廣和使用。有似天雷勾動地火,統一的標准撩撥起了外圍設備制造商的創新,從那以后各種各樣的PCI設備應運而生,豐富了PC的整個生態環境。
PCI總線標准初試啼聲就提供了133MB/s的帶寬(33MHz時鍾,每時鍾傳送32bit)。這對當時一般的台式機已經是超高速了,但對於服務器或者視頻來說還是不夠。於是AGP被發明出來專門連接北橋與顯卡,而為服務器則提出PCI-X來連接高速設備。
2004年,Intel再一次帶領小伙伴革了PCI的命。PCI express(PCIe,注意官方寫法是這樣,而不是PCIE或者PCI-E)誕生了,其后又經歷了兩代,現在是第三代(gen3,3.0),gen4有望在2017年公布,而gen5已經開始起草中。
下面這個大表列出所有的速度比較。其中一些x8,x16的概念后面細節部分有介紹。
從下面的主頻變化圖中,大家可能注意到更新速度越來越快。
PCI和PCIe架構
1。PCI架構
一個典型的桌面系統PCI架構如下圖:
如圖,桌面系統一般只有一個Host Bridge用於隔離處理器系統的存儲器域與PCI總線域,並完成處理器與PCI設備間的數據交換。每個Host Bridge單獨管理獨立的總線空間,包括PCI Bus, PCI I/O, PCI Memory, and PCI
Prefetchable Memory Space。桌面系統也一般只有一個Root Bridge,每個Root Bridge管理一個Local Bus空間,它下面掛載了一顆PCI總線樹,在同一顆PCI總線樹上的所有PCI設備屬於同一個PCI總線域。一顆典型的PCI總線樹如圖:
從圖中我們可以看出 PCI 總線主要被分成三部分:
1. PCI 設備。符合 PCI 總線標准的設備就被稱為 PCI 設備,PCI 總線架構中可以包含多個 PCI 設備。圖中的 Audio、LAN 都是一個 PCI 設備。PCI 設備同時也分為主設備和目標設備兩種,主設備是一次訪問操作的發起者,而目標設備則是被訪問者。
2. PCI 總線。PCI 總線在系統中可以有多條,類似於樹狀結構進行擴展,每條 PCI 總線都可以連接多個 PCI 設備/橋。上圖中有兩條 PCI 總線。
3. PCI 橋。當一條 PCI 總線的承載量不夠時,可以用新的 PCI 總線進行擴展,而 PCI 橋則是連接 PCI 總線之間的紐帶。
服務器的情況要復雜一點,舉個例子,如Intel志強第三代四路服務器,共四顆CPU,每個CPU都被划分了共享但區隔的Bus, PCI I/O, PCI Memory范圍,其構成可以表示成如下圖:
可以看出,只有一個Host Bridge,但有四個Root Bridge,管理了四顆單獨的PCI樹,樹之間共享Bus等等PCI空間。
在某些時候,當服務器連接入大量的PCI bridge或者PCIe設備后,Bus數目很快就入不敷出了,這時就需要引入Segment的概念,擴展PCI Bus的數目。如下例:
如圖,我們就有了兩個Segment,每個Segment有自己的bus空間,這樣我們就有了512個Bus數可以分配,但其他PCI空間因為只有一個Host Bridge所以是共享的。會不會有更復雜的情況呢? 在某些大型服務器上,會有多個Host bridge的情況出現,這里我們就不展開了。
PCI標准有什么特點嗎?
1. 它是個並行總線。在一個時鍾周期內32個bit(后擴展到64)同時被傳輸。引腳定義如下:
地址和數據在一個時鍾周期內按照協議,分別一次被傳輸。
2. PCI空間與處理器空間隔離。PCI設備具有獨立的地址空間,即PCI總線地址空間,該空間與存儲器地址空間通過Host bridge隔離。處理器需要通過Host bridge才能訪問PCI設備,而PCI設備需要通過Host bridge才能主存儲器。在Host bridge中含有許多緩沖,這些緩沖使得處理器總線與PCI總線工作在各自的時鍾頻率中,彼此互不干擾。Host bridge的存在也使得PCI設備和處理器可以方便地共享主存儲器資源。處理器訪問PCI設備時,必須通過Host bridge進行地址轉換;而PCI設備訪問主存儲器時,也需要通過Host bridge進行地址轉換。
深入理解PCI空間與處理器空間的不同是理解和使用PCI的基礎。
3.擴展性強。PCI總線具有很強的擴展性。在PCI總線中,Root Bridge可以直接連出一條PCI總線,這條總線也是該Root bridge所管理的第一條PCI總線,該總線還可以通過PCI橋擴展出一系列PCI總線,並以Root bridge為根節點,形成1顆PCI總線樹。在同一條PCI總線上的設備間可以直接通信,並不會影響其他PCI總線上設備間的數據通信。隸屬於同一顆PCI總線樹上的PCI設備,也可以直接通信,但是需要通過PCI橋進行數據轉發。
2。PCIe架構
PCI后期越來越不能適應高速發展的數據傳輸需求,PCI-X和AGP走了兩條略有不同的路徑,PCI-x不斷提高時鍾頻率,而AGP通過在一個時鍾周期內傳輸多次數據來提速。隨着頻率的提高,PCI並行傳輸遇到了干擾的問題:高速傳輸的時候,並行的連線直接干擾異常嚴重,而且隨着頻率的提高,干擾(EMI)越來越不可跨越。
亂入一個話題,經常有朋友問我為什么現在越來越多的通訊協議改成串行了,SATA/SAS,PCIe,USB,QPI等等,經典理論不是並行快嗎?一次傳輸多個bit不是效率更高嗎?從PCI到PCIe的歷程我們可以一窺原因。
PCIe和PCI最大的改變是由並行改為串行,通過使用差分信號傳輸(differential transmission),如圖
相同內容通過一正一反鏡像傳輸,干擾可以很快被發現和糾正,從而可以將傳輸頻率大幅提升。加上PCI原來基本是半雙工的(地址/數據線太多,不得不復用線路),而串行可以全雙工。綜合下來,如果如果我們從頻率提高下來得到的收益大於一次傳輸多個bit的收益,這個選擇就是合理的。我們做個簡單的計算:
PCI傳輸: 33MHz x 4B = 133MB/s
PCIe 1.0 x1: 2.5GHz x 1b = 250MB/s (知道為什么不是2500M / 8=312.5MB嗎?)
速度快了一倍!我們還得到了另外的好處,例如布線簡單,線路可以加長(甚至變成線纜連出機箱!),多個lane還可以整合成為更高帶寬的線路等等。
PCIe還在很多方面和PCI有很大不同:
1. PCI是總線結構,而PCIe是點對點結構。一個典型的PCIe系統框圖如下:
一個典型的結構是一個root port和一個endpoint直接組成一個點對點連接對,而Switch可以同時連接幾個endpoint。一個root port和一個endpoint對就需要一個單獨的PCI bus。而PCI是在同一個總線上的設備共享同一個bus number。過去主板上的PCI插槽都公用一個PCI bus,而現在的PCIe插槽卻連在芯片組不同的root port上。
2. PCIe的連線是由不同的lane來連接的,這些lane可以合在一起提供更高的帶寬。譬如兩個1lane可以合成2lane的連接,寫作x2。兩個x2可以變成x4,最大直到x16,往往給帶寬需求最大的顯卡使用。
3. PCI配置空間從256B擴展為4k,同時提供了PCIe memory map訪問方式,我們在軟件部分會詳細介紹。
4.PCIe提供了很多特殊功能,如Complete Timeout(CTO),MaxPayload等等幾十個特性,而且還在隨着PCIe版本的進化不斷增加中,對電源管理也提出了單獨的State(L0/L0s/L1等等)。這些請參見PCIe 3.0 spec,本文不再詳述。
5. 其他VC的內容,和固件理解無關,本文不再提及。INT到MSI的部分會在將來介紹PC中斷系統時詳細講解。
PCIe 1.0和2.0采用了8b/10b編碼方式,這意味着每個字節(8b)都用10bit傳輸,這就是為什么2.5GHz和5GHz時鍾,每時鍾1b數據,結果不是312.5MB/s和625MB/s而是250MB/s和500MB/s。PCIe 3.0和4.0采用128b/130b編碼,減小了浪費(overhead),所以才能在8GHz時鍾下帶寬達到1000MB/s(而不是800MB/s)。即將於今年發布的PCIe 4.0還會將頻率提高一倍,達到16GHz,帶寬達到2GB/s每Lane。
后記
對於一般用戶來說,PCIe對用戶可見的部分就是主板上大大小小的PCIe插槽了,有時還和PCI插槽混在一起,造成了一定的混亂,其實也很好區分:
如圖,PCI插槽都是等長的,防呆口位置靠上,大部分都是純白色。PCIe插槽大大小小,最小的x1,最大的x16,防呆口靠下。各種PCIe插槽大小如下:
常見問題:
Q:我主板上沒有x1的插槽,我x1的串口卡能不能插在x4的插槽里。
A: 可以,完全沒有問題。除了有點浪費外,串口卡也將已x1的方式工作。
Q:我主板上只有一個x16的插槽,被我的顯卡占據了。我還有個x16的RAID卡可以插在x8的插槽內嗎?
A: 你也許會驚訝,但我的答案同樣是:可以!你的RAID卡將以x8的方式工作。實際上來說,你可以將任何PCIe卡插入任何PCIe插槽中! PCIe在鏈接training的時候會動態調整出雙方都可以接受的寬度。最后還有個小問題,你根本插不進去!呵呵,有些主板廠商會把PCIe插槽尾部開口,方便這種行為,不過很多情況下沒有。這時怎么辦?你懂的。。。。
Q: 我的顯卡是PCIe 3.0的,主板是PCIe2.0的,能工作嗎?
A: 可以,會以2.0工作。反之,亦然。
Q: 我把x16的顯卡插在主板上最長的x16插槽中,可是benchmark下來卻說跑在x8下,怎么回事?!
A: 主板插槽x16不見得就連在支持x16的root port上,最好詳細看看主板說明書,有些主板實際上是x8。有個主板原理圖就更方便了。
Q: 我新買的SSD是Mini PCIe的,Mini PCIe是什么鬼?
A: Mini PCIe接口常見於筆記本中,為54pin的插槽。多用於連接wifi網卡和SSD,注意不要和mSATA弄混了,兩者完全可以互插,但大多數情況下不能混用(除了少數主板做了特殊處理),主板設計中的防呆設計到哪里去了!請仔細閱讀主板說明書。另外也要小心不要和m.2(NGFF)搞混了,好在卡槽大小不一樣。
PCI系列二: 深入PCI與PCIe之二:軟件篇 - 知乎專欄
歡迎大家關注本專欄和用微信掃描下方二維碼加入微信公眾號"UEFIBlog",在那里有最新的文章。同時歡迎大家給本專欄和公眾號投稿!
用微信掃描二維碼加入UEFIBlog公眾號
編輯於 2017-10-28
深入PCI與PCIe之二:軟件篇
我們前一篇文章(深入PCI與PCIe之一:硬件篇 - 知乎專欄)介紹了PCI和PCIe的硬件部分。本篇主要介紹PCI和PCIe的軟件界面和UEFI對PCI的支持。
PCI/PCIe軟件界面
1。配置空間
PCI spec規定了PCI設備必須提供的單獨地址空間:配置空間(configuration space),前64個字節(其地址范圍為0x000x3F)是所有PCI設備必須支持的(有不少簡單的設備也僅支持這些),此外PCI/PCI-X還擴展了0x400xFF這段配置空間,在這段空間主要存放一些與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",在那里有最新的文章。同時歡迎大家給本專欄和公眾號投稿!
用微信掃描二維碼加入UEFIBlog公眾號
編輯於 2017-10-28
PCIe的通道是怎么分分合合的?詳解PCIe bifurcation
隨着AMD新一代CPU的發布,PCIe 4.0 (Gen4)也進入了人們的視線。然而Intel隨后宣傳PCIe 4.0對消費市場用處不大,AMD則反諷Intel吃不到葡萄說葡萄酸。正在吃瓜群眾搬板凳看熱鬧的時間,一件事情正在發生。PCIe的標准制定組織,PCI-SIG(Peripheral Component Interconnect Special Interest Group)發布了PCIe 6.0(Gen6)的標准!對,你沒有看錯,不是5.0而是6.0。實際上5.0剛剛發布,如果我們看最近這個組織的活動軌跡:
也許是哪里來的莫名其妙王子用親吻喚醒了她,在蟄伏了7年之后,PCI-SIG加速以翻一番的速度發布新標准,從2017年的Gen4,2018年的Gen5,到2019的Gen6,這個節奏簡直瘋狂。如果說Intel現在的節奏是Tock、Tock、Tock...,那PCIe就是打了雞血般的Tick、Tick和Tick!這讓主板廠商情何以堪?吃瓜群眾也一臉懵懂,到底我是不是該升級呢?
盡管我不認為我們在2021年底之前能看到任何的PCIe 6.0的設備,但PCIe標准的高歌猛進讓人們更加關注PCIe這個現代計算機的脊柱總線,也是好事一件。我已經有兩篇文章介紹PCIe的基本知識:
老狼:深入PCI與PCIe之一:硬件篇zhuanlan.zhihu.com老狼:深入PCI與PCIe之二:軟件篇zhuanlan.zhihu.com
PCIe Lane(通道)
我在前面文章介紹過,PCIe是串行總線,通過使用差分信號傳輸(differential transmission),如圖
相同內容通過一正一反鏡像傳輸,干擾可以很快被發現和糾正,從而可以將傳輸頻率大幅提升。加上PCI原來基本是半雙工的(地址/數據線太多,不得不復用線路),而串行可以全雙工。
這樣一對差分信號組成一個PCIe Lane,也叫做x1通道。把n組綁定在一起,可以讓PCIe設備大幅提高傳輸帶寬。如M.2接口的NVMe SSD一般用四組,四個Lane,也就是x4;而最耗帶寬的顯卡一般要用16組,就是x16。注意這個n應該是2的冪,所以不存在奇數組或者x10等組合。
PCIe通道的組合和差分
現在PCIe的設備越來越多,Intel的台式機/筆記本平台為了讓主板廠商能夠靈活滿足客戶的需求,在CPU和南橋PCH后面都提供了不少PCIe通道:
這是個Haswell的例子,比較老,但和現在系統沒有本質區別(現在南橋換成PCIe Gen3了)。CPU一般提供PCIe x16通道給顯卡,南橋則提供更多的通道,但因為要經過DMI這個小水管,一般不建議鏈接顯卡等需要高帶寬的設備。
計算機用戶多種多樣,有的用戶需要插兩組顯卡,有的則需要很多x1的插槽。為了給主板廠商提供靈活的空間,芯片廠商通過一種叫做bifurcation(分叉)的方式讓主板廠商可以靈活配置,組合或者拆分PCIe通道,來做出滿足細分市場的產品來。
PCIe初始化一般分為:
1.bifurcation。
2.Root Port Training。根據信號完整性的不同,盡管Root port支持PCIe Gen3/4,但主板走線有問題,有干擾,可能只能Training出Gen2,甚至Gen1的速度來。信號完整性可以參考我的這篇文章:老狼:芯片中的數學——均衡器EQ和它在高速外部總線中的應用
\3. PCI枚舉。
\4. PCI/PCIe的各種特性(Feature)設置,如CTO等等。
作為初始化的第一步,bifurcation的重要性自不待言。它決定了各個設備和PCIe插槽的通道寬度。它一般有三種方式:Hard Strap,Soft Strap或者Wait for BIOS。
Hard Strap
所謂Hard,是指這種方式是硬件連線,不能后期修改。在酷睿桌面CPU后面的PCIe通道通常采用這些方式。我們來看個例子,下面所有內容來自Intel官網,7代i5的Datasheet[1]
注意紅框部分
我們可以看到這種bifurcation,CPU后面的PCIe是一個x16,還是兩個x8,亦或1個x8家兩個x4,取決於CFG信號。
主板廠商根據自己主板樣式,如提供了一個顯卡插槽,則把CFG[6:5]信號都連高電平,就是一個x16;如果提供兩個顯卡插槽,則把CFG[6:5]信號連接一高一低,就是兩個x8,即兩個PCIe顯卡就降成x8使用;還有些廠商喜歡把NVMe的m.2連接到CPU后面,來提高存儲速度,則可以把CFG[6:5]信號都連低電平,則是1個x8連接顯卡,兩個x4來連接M.2 SSD。
這種bifurcation一旦確定,就不能更改,除非重新布線。
Soft Strap
所謂Soft,就是軟件可以修改。PCH下PCIe root port一般是這種方式。這種配置一般儲存在BIOS Image前面的discription中,可以通過工具修改:
這種修改一般和BIOS程序無關,修改后直接燒錄BIOS即可。當然BIOS在Image沒有被鎖定的情況下,可以重新修改這個區域,但修改后需要重新啟動才可以生效。
主板廠商一般根據自己的主板設計情況,在燒錄BIOS的時候就用軟件改好了相應的值,BIOS一般沒有界面去修改這個值。
Wait For BIOS
這種方式是純BIOS設置,也就是在PCIe Training之前,通過BIOS對相關PCIe root complex的寄存器進行設置來確定通道寬度。
這種方式一般用於至強系列CPU,它們在CPU后面提供高達40個Lanes的支持:
為什么是44個Lane?
如圖中,我們數一下,一共是44個Lane,不是說40個Lane嗎?其實P0的lane是給DMI用的,如果在多路情況下,除了第一個Socket,其他CPU才可以把它用起來。
這么多Lane,因為最高一個設備只支持x16,所以分為幾組。一般一組是一個PCIe device,分為4個function,在bifurcation之后,如果該Function輪空,需要我們禁掉該function來省電。
這種方式是最靈活的方式,它賦予至強CPU的用戶極大的靈活性,一般會有配置界面來配置:
結論
這許多細節也許比較枯燥,但只有了解現象后面的本質,我們才能夠更深刻地理解計算機是怎么工作的。
最后來解決一下有些同學的實際問題,有的同學主板只有一個X16的槽,但想要插兩個顯卡(想想什么情況下需要),怎么辦呢?可以借助一種叫做bifurcation卡的PCIe卡:
現在你知道為什么叫做bifurcation了吧,顧名思義,真的就是分叉啊。
歡迎大家關注本專欄和用微信掃描下方二維碼加入微信公眾號"UEFIBlog",在那里有最新的文章。
用微信掃描二維碼加入UEFIBlog公眾號
參考
發布於 2019-06-21
一張圖對PCIe進行掃盲(史無前例的好文章)
博觀而約取,厚積而薄發
寫在開始的話,不知道看了多少資料才總結出一點知識,能輸入已經很不容易,何況想要輸出,有十能輸出一二就算不錯了。所以這個過程中真的很難堅持下來,何況沒有實際項目作為載體,真的不知道實際運用中這些知識夠不夠用,這只是基於我之前的經驗,認為一個全新的硬件模塊中需要掌握的部分。
1 首先要了解這個硬件的用途,物理接口,pin定義。
2 要知道需要做什么樣的配置才能使得設備達到我們的預期。
3 設備工作中最重要的就是三個模塊,正確識別,注冊中斷以及終端處理函數,數據傳輸。
完成以上三步一個硬件模塊的bringup就完成了。本文忽略了大部分細節,只為了用最短的篇幅描述好PCIe。
MSI、MSI-X中斷和TLP這里不詳細展開去說,必要的時候會帶出來。
首先用一張圖來直觀的呈現出要了解PCIe,我們需要知道的一些基本概念。
用華為hisi的芯片來描述PCIe在linux中的驅動注冊過程,可以參考本文:https://blog.csdn.net/chengch512/article/details/52635736?depth_1-utm_source=distribute.pc_relevant.none-task&utm_source=distribute.pc_relevant.none-task
參考文章鏈接:
https://www.cnblogs.com/szhb-5251/p/11620310.html
https://www.cnblogs.com/yangxingsha/p/11551472.html
https://blog.csdn.net/kunkliu/article/details/94380357
https://www.sohu.com/a/300238384_505795
https://blog.csdn.net/buyi_shizi/article/details/51068609
https://blog.csdn.net/abcamus/article/details/74157026
https://blog.csdn.net/abcamus/article/details/74157026
一個PCIe設備能夠被識別,大致需要經過以下步驟:
上述過程的詳細描述可以參考這個鏈接:
https://blog.csdn.net/maxwell2ic/article/details/90759280
https://blog.csdn.net/yijingjijng/article/details/48196531
上面的描述我們也能看到,一個設備之所以能夠被檢測並正確的識別,需要很多個過程,首先這個設備能夠從物理上被識別到。而識別到這個設備的第一個關鍵步驟就是物理層上能夠檢測到。在設備中按照OSI模型,都會有多個層次結構。對於PCIe同樣也不例外,下圖就是一個PCIe的層次結構。
此處只簡單描述下PHY層(也就是物理層)中的鏈路訓練狀態機(LTSSM,Link Training and Status State Mechine)。
主要包含五類狀態:
1 鏈路訓練狀態(Link Training State)
Detect
Polling
Configuration
2 重訓練狀態(Re-Training(Recovery)State)
Recovery
3 軟件驅動功耗管理狀態(Software Driven Power Managment State)
L2
L0
L1
4 活動狀態功耗管理狀態(Active-State Power Management State,ASPM State)
L1
L0s
5 其它狀態(Other State)
From Configuration or Recovery :Disabled\External Loopback。
From Recovery: Hot Reset。
具體我們可以通過這張圖來了解一下LTSSM:
這一部分更為詳細的描述,我們可以通過這個鏈接來看:https://blog.csdn.net/kunkliu/article/details/94594501
以上是在理論層面上全面的了解了PCIe,具體我們在使用的過程中,需要通過軟件的方式來實現,從而能夠正確的被上層應用使用。
pci驅動在linux中的描述如下:
Once the driver knows about a PCI device and takes ownership, the
driver generally needs to perform the following initialization:
Enable the device
Request MMIO/IOP resources
Set the DMA mask size (for both coherent and streaming DMA)
Allocate and initialize shared control data (pci_allocate_coherent())
Access device configuration space (if needed)
Register IRQ handler (request_irq())
Initialize non-PCI (i.e. LAN/SCSI/etc parts of the chip)
Enable DMA/processing engines
When done using the device, and perhaps the module needs to be unloaded,
the driver needs to take the follow steps:
Disable the device from generating IRQs
Release the IRQ (free_irq())
Stop all DMA activity
Release DMA buffers (both streaming and coherent)
Unregister from other subsystems (e.g. scsi or netdev)
Release MMIO/IOP resources
Disable the device