[轉]老男孩讀pcie


轉自

http://www.ssdfans.com/blog/2017/08/03/%E8%80%81%E7%94%B7%E5%AD%A9%E8%AF%BBpcie%E4%B9%8B%E4%B8%80%EF%BC%9A%E4%BB%8Epcie%E9%80%9F%E5%BA%A6%E8%AF%B4%E8%B5%B7/

老男孩讀PCIe之一:從PCIe速度說起

從今天開始,老男孩要開始講PCIe了。對我來說,這是個很大的挑戰:首先,我自己本身,對PCIe並沒有做到胸有成竹,我的PCIe知識也只是停留在理論階段,我並沒有實際做過任何有關PCIe的東西;其次,我要把PCIe講得深入淺出,讓讀者輕易接受,我覺得很難,根本原因就是我還沒有做到胸有PCIe;最后,我的文章都會通過ssdfans公眾號推出(還沒有關注的同學,趕快關注),很多讀者都是PCIe高手,班門弄斧,我深感壓力。但盡管如此,我還是決定出發,我自己努力學習,盡我最大能力把我學到的東西分享給大家,也希望各位PCIe大拿們,幫忙斧正文章的錯誤,我希望不是我一個人,而是和各位大拿們一起,帶領大家攀上PCIe的高峰。

 

我是從事SSD工作的,為什么要講PCIe,原因很簡單,現在很多SSD都開始使用PCIe接口。那為什么SSD要用PCIe接口?因為它快,比SATA快。它究竟有多快?我們今天首先從PCIe接口的速度開始我們的PCIe之旅。

 

PCIe發展到現在,從PCIe 1.0,PCIe 2.0,到現在的PCIe 3.0,速度一代比一代快。

 

Link Width這一行,我們看到X1,X2,X4…,這是什么意思?這是指PCIe連接的通道數(Lane)。就像高速一樣,有單根道,有2根道的,有4根道的,不過像8根道或者更多道的公路不常見,但PCIe是可以最多32條道的。

 

 兩個設備之間的PCIe連接,叫做一個Link,如下圖所示:

 

 

從A到B,之間是個雙向連接,車可以從A駛向B,同時,車也可以從B駛向A,各行其道。兩個PCIe設備之間,有專門的發送和接收通道,數據可以同時往兩個方向傳輸,PCIe spec稱這種工作模式為雙單工模式(dual-simplex),可以理解為全雙工模式。

 

SATA是什么工作模式呢?

 

 

和PCIe一樣,SATA也有獨立的發送和接收通道,但與PCIe工作模式不一樣:同一時間,只有一條道可以進行數據傳輸,也就是說,你在一條道上發送數據,另外一條道上不能接收數據,反之亦然。這種工作模式應該是半雙工模式。PCIe猶如我們的手機,雙方可以同時講話,而SATA就是對講機了,一個人在說話,另外一個人就只能聽不能說。

 

回到前面PCIe帶寬那張表,上面的帶寬,比如PCIe3.0x1,帶寬為2GB/s,是指雙向帶寬,即讀寫帶寬。如果單指讀或者寫,該值應該減半,即1GB/s的讀速度或者寫速度。

 

我們來看看表里面的帶寬是怎么算出來的。

 

PCIe是串行總線,PCIe1.0的線上比特傳輸速率為2.5Gb/s,物理層使用8/10編碼,即8比特的數據,實際在物理線路上是需要傳輸10比特的,因此:

PCIe1.0 x 1的帶寬=2.5Gb/s x 2(雙向通道))/ 10bit = 0.5GB/s

這是單條Lane的帶寬,有幾條Lane,那么整個帶寬就0.5GB/s乘以Lane的數目。

 

PCIe2.0的線上比特傳輸速率在PCIe1.0的基礎上翻了一倍,為5Gb/s,物理層同樣使用8/10編碼,所以:

PCIe2.0 x 1的帶寬=5Gb/s x 2(雙向通道))/ 10bit = 1GB/s

同樣,有多少條Lane,帶寬就是1GB/s乘以Lane的數目。

 

PCIe3.0的線上比特傳輸速率沒有在PCIe2.0的基礎上翻倍,不是10Gb/s,而是8Gb/s,但物理層使用的是128/130編碼進行數據傳輸,所以:

PCIe3.0 x 1的帶寬=8Gb/s x 2(雙向通道))/ 8bit = 2GB/s

同樣,有多少條Lane,帶寬就是2GB/s乘以Lane的數目。

 

由於采用了128/130編碼,128比特的數據,只額外增加了2bit的開銷,有效數據傳輸比率增大,雖然線上比特傳輸率沒有翻倍,但有效數據帶寬還是在PCIe2.0的基礎上做到翻倍。

 

這里值得一提的是,上面算出的數據帶寬已經考慮到8/10或者128/130編碼,因此,大家在算帶寬的時候,沒有必要再考慮線上編碼的問題了。

 

和SATA單通道不同,PCIe連接可以通過增加通道數擴展帶寬,彈性十足。通道數越多,速度越快。不過,通道數越多,成本越高,占用更多空間,還有就是更耗電。因此,使用多少通道,應該在性能和其他因素之間進行一個綜合考慮。單考慮性能的話,PCIe最高帶寬可達64GB/s,PCIe 3.0 x 32對應的帶寬,很恐怖的一個數據。不過,現有的PCIe接口SSD,一般最多使用4通道,如PCIe3.0x4,雙向帶寬為8GB/s,讀或者寫帶寬為4GB/s。

 

 

幾個GB/s的傳輸速度,讀寫小電影那是杠杠的。

在此,順便來算算PCIe3.0x4理論上最大的4K IOPS。PCIe3.0x4理論最大讀或者寫的速度為4GB/s,不考慮協議開銷,每秒可以傳輸4GB/4K個4K大小的IO,該值為1M,即理論上最大IOPS為1000K。因此,一個SSD,不管你底層用什么介質,flash還是3d xpoint,接口速度就這么塊,最大IOPS是不可能超過這個值的。

 

PCIe是從PCI發展過來的,PCIe的”e”是express的簡稱,快的意思。PCIe怎么就能比PCI(或者PCI-X)快呢?PCIe在物理傳輸上,跟PCI有着本質的區別:PCI使用並口傳輸數據,而PCIe使用的是串口傳輸。我PCI並行總線,單個時鍾周期可以傳輸32bit或者64bit,怎么就比不了你單個時鍾周期傳輸1個bit數據的串行總線呢?

在實際時鍾頻率比較低的情況下,並口因為可以同時傳輸若干比特,速率確實比串口快。隨着技術的發展,數據傳輸速率要求越來越快,要求時鍾頻率也越來越快,但是,並行總線時鍾頻率不是想快就能快的。

 

在發送端,數據在某個時鍾沿傳出去(左邊時鍾第一個上升沿),在接收端,數據在下個時鍾沿(右邊時鍾第二個上升沿)接收。因此,要在接收端能正確采集到數據,要求時鍾的周期必須大於數據傳輸的時間(從發送端到接收端,flight time)。受限於數據傳輸時間(該時間還隨着數據線長度的增加而增加),因此時鍾頻率不能做得太高。另外,時鍾信號在線上傳輸的時候,也會存在相位偏移(clock skew ),影響接收端的數據采集;還有,並行傳輸,接收端必須等最慢的那個bit數據到了以后,才能鎖住整個數據 (signal skew)。

 

PCIe使用串行總線進行數據傳輸就沒有這些問題。它沒有外部時鍾信號,它的時鍾信息通過8/10編碼或者128/130編碼嵌入在數據流,接收端可以從數據流里面恢復時鍾信息,因此,它不受數據在線上傳輸時間的限制,你導線多長都沒有問題,你數據傳輸頻率多快也沒有問題;沒有外部時鍾信號,自然就沒有所謂的clock skew問題;由於是串行傳輸,只有一個bit傳輸,所以不存在signal skew問題。但是,如果使用多條lane傳輸數據(串行中又有並行,哈哈),這個問題又回來了,因為接收端同樣要等最慢的那個lane上的數據到達才能處理整個數據。

 

 

老男孩讀PCIe之二:PCIe拓撲結構

計算機網絡的拓撲結構是引用拓撲學中研究與大小、形狀無關的點、線關系的方法,把網絡中的計算機和通信設備抽象為一個點,把傳輸介質抽象為一條線,由點和線組成的幾何圖形就是計算機網絡的拓撲結構。”

 

計算機網絡的最主要的拓撲結構有總線型拓撲拓撲、拓撲、拓撲、混合型拓撲以及網狀拓撲。

 

PCI采用的是總線型拓撲結構,一條PCI總線上掛着若干個PCI終端設備或者PCI橋設備,大家共享該條PCI總線,哪個人想說話,必須獲得總線使用權,然后才能發言。下面是一個基於PCI的傳統計算機系統:

  

北橋下面的那根PCI總線,掛載了以太網設備、SCSI設備、南橋以及其他設備,他們共享那條總線,某個設備只有獲得總線使用權才能進行數據傳輸。

 

而PCIe則采用樹形拓撲結構,一個簡單而又典型的PCIe拓撲結構如下: 

 

 

整個PCIe拓撲結構是一個樹形結構,Root Complex(RC)是樹的根。RC為CPU代言,與整個計算機系統其它部分通訊,比如CPU通過它訪問內存,通過它訪問PCIe系統中的設備。

CPU像皇上一樣高高在上,而RC好比皇上身邊當紅的太監,皇上想叫下面的人做點事情,通過太監傳達;下面的人也是通過太監,向皇上反應一些情況。不過,這個太監不尋常,它是有根(root)的。

RC的內部實現很復雜,PCIe Spec也沒有規定RC該做什么,還是不該做什么。我們也不需要知道那么多,只需清楚:它一般實現了一條內部PCIe總線(BUS 0),以及通過若干個PCIe bridge,擴展出一些PCIe Port,如下圖所示: 

 

 

PCIe Endpoint,就是PCIe終端設備,比如PCIe SSD,PCIe網卡等等,而Legacy Endpoint,接口是PCIe,但是內部的行為卻和傳統的PCI或者PCI-x一樣(比如支持IO空間)。這些Endpoint可以直接連在RC上,也可以通過Switch連到PCIe總線上。Switch用於擴展鏈路,提供更多的端口用以連接Endpoint。拿USB打比方,我們計算機主板上提供的USB口有限,如果你要連接很多USB設備,比如無線網卡、無線鼠標、USB攝像頭、USB打印機、U盤等等,這時候不夠用,怎么辦?我會去找馬雲,向它買個USB HUB,下面這個就不錯: 

 

Switch擴展了PCIe端口,靠近RC的那個端口,我們叫上游端口(upstream port),而分出來的其他端口,我們叫下游端口(downstream port)。一個Switch只有一個上游端口,可以擴展出若干個下游端口。下游端口可以直接連接Endpoint,也可以連接Switch,擴展出更多的PCIe端口。 

 

 

對每個Switch來說,它下面的Endpoint或者Switch,都是歸他管的:上游下來的數據,它需要甄別數據是傳給它下面哪個設備,然后進行轉發;下面設備向RC傳數據,也要通過Switch代為轉發的。因此,Switch的作用就是擴展PCIe端口,並為掛在它上面的設備(endpoint 或者switch)提供路由和轉發服務。

每個Switch內部,也是有一根內部PCIe總線的,然后通過若干個Bridge,擴展出若干個下游端口,如下圖所示:

 

 

最后小結一下:

PCIe采用的是樹形拓撲結構,RC是樹的根,或者主干,它為CPU代言,與PCIe系統其它部分通訊,一般為通訊的發起者;Switch是樹枝,樹枝上有葉子(Endpoint),也可節外生枝,Switch上連Switch,歸根結底,是為了連接更多的Endpoint。Switch為它下面的Endpoint或Switch提供路由轉發服務;Endpoint是樹葉,諸如SSD,網卡,顯卡等等,實現某些特定功能(function)。我們還看到有所謂的Bridge,用以將PCIe總線轉換成PCI總線,或者反過來,不是我們要講的重點,忽略之。PCIe與采用總線共享式通訊方式的PCI不同,PCIe采用點到點(Endpoint to Endpoint)通訊方式,每個設備獨享通道帶寬,速度和效率都比PCI好。

最后,以一個實際的計算機系統例子結束本文: 

 

 

 

老男孩讀PCIe之三:PCIe分層結構

絕大多數的總線或者接口,都是采用分層實現的。PCIe也不例外,它的層次結構如下:

 

PCIe定義了下三層(彩色部分):事務層(Transaction Layer),數據鏈路層(Data Link Layer)和物理層(Physical Layer),每層職能是不同的,且下層是為上層服務的。分層設計的一個好處:如果層次分得夠好,接口版本升級時,硬件設計可能只需要改動某一層,其它層次可以保持不動。

 

PCIe傳輸的數據從上到下,都是以packet的形式傳輸的,每個packet都是有其固定的格式的。

事務層的主要職責是創建(發送)或者解析(接收)TLP (Transaction Layer packet),流量控制,QoS,事務排序等。

數據鏈路層的主要職責是創建(發送)或者解析(接收)DLLP(Data Link Layer packet),Ack/Nak協議(鏈路層檢錯和糾錯),流控,電源管理等。

物理層的主要職責是處理所有的Packet數據物理傳輸,發送端數據分發到各個Lane傳輸(stripe),接收端把各個Lane上的數據匯總起來(De-stripe),每個Lane上加擾(Scramble,目的是讓0和1分布均勻,去除信道的電磁干擾EMI)去擾(De-scramble),以及8/10或者128/130編碼解碼,等等。

今天只講個大概,這三層以后還會專門細講。這里先貼個這三層的細節圖,自己看:

 

 

 數據從上到下,一層一層打包,上層打包完的數據,作為下層的原始數據,再打包。就像人穿衣服一樣,穿了內衣穿襯衫,穿了襯衫穿外套。

 

 

 

紅色的是TLP的格式,Data是事務層上層給的數據,事務層給它頭上加個Header,然后尾巴上再加個CRC校驗,就構成了一個TLP;這個TLP下傳到數據鏈路層,又被數據鏈路層頭上加了個包序列號,尾巴再加個CRC校驗,構成一個DLLP;然后DLLP下傳到物理層,頭上加個Start,尾巴加個End符號,把這些數據分派到各個Lane上,然后每個Lane上加擾碼,經8/10或128/130編碼,最后通過物理傳輸介質傳輸給接收方。

接收方物理層是最先接收到這些數據的,然后執行逆操作;在數據鏈路層,校驗序列號和LCRC,如果沒錯,剝掉序列號和LCRC,往事務層走;如果校驗出差,通知對方重傳;在事務層,校驗ECRC,有錯,數據拋棄,沒錯,去掉ECRC,獲得數據。整個過程猶如脫衣睡覺,外套脫了,襯衫脫了,內衣也脫了,光溜溜鑽進被窩。

 

 

和PCI數據裸奔不同,PCIe的數據是穿有衣服的。PCIe數據以packet的形式傳輸,比起PCI冷冰冰的數據,PCIe的數據是鮮活有生命的。

每個Endpoint都需要實現這三層,每個Switch的每個Port也是需要實現這三層的:

 

 

上圖中,如果RC要與EP1通信,中間要經歷怎樣的一個過程?

 

 

如果把前述的數據發送和接收過程,我們通俗的叫做穿衣脫衣,那么,RC與EP1數據傳輸過程中,則存在好幾次這樣穿衣脫衣過程:RC跟數據穿好衣服,發送給Switch的上游端口,A為了知道該筆數據發送給誰,就需要脫掉該數據的衣服,找到里面的地址信息。衣服脫光后,Switch發現它是往EP1的,又幫它換了身新衣服,發送給端口B。B又不嫌麻煩的脫掉它的衣服,換上新衣服,最后發送給EP1。

 

Switch的主要功能是轉發數據,為什么還需要實現事務層?Switch必須實現這三層,因為數據的目的地信息是在TLP中的,如果不實現這一層,就無法知道目的地址,也就無法實現數據尋址路由。

 

今天就到這,關於這三層,以后還會展開講,敬請期待。

 

老男孩讀PCIe之四:TLP類型

Host與PCIe設備之間,或者PCIe設備與設備之間,數據傳輸都是以Packet形式進行的。事務層根據上層(軟件層或者應用層)請求(Request)的類型、目的地址和其它相關屬性,把這些請求打包,產生TLP,也就是Transaction Layer Packet。然后這些TLP往下,經歷數據鏈路層,物理層,最終到達目標設備。

 

根據軟件層的不同請求,事務層產生四種不同的TLP請求:

  1. Memory
  2. IO
  3. Configuration
  4. Message

 

前三種分別用於訪問內存空間、IO空間、配置空間,這三種請求在PCI或者PCI-X時代就有了;最后的Message請求是PCIe新加的。在PCI或者PCI-X時代,像中斷、錯誤以及電源管理相關信息,都是通過邊帶信號(sideband signal)進行傳輸的,但PCIe干掉了這些邊帶信號線,所有的通訊都是走帶內信號,即通過Packet傳輸,因此,過去一些由邊帶信號線傳輸的數據,比如中斷信息、錯誤信息等,現在就交由Message來傳輸了。

 

我們知道,一個設備的物理空間,可以通過內存映射(Memory map)的方式映射到Host的主存,有些空間還可以映射到Host的IO空間(如果Host存在IO空間的話)。但新的PCIe設備(區別於Legacy PCIe設備)只支持內存映射,之所以還存在訪問IO空間的TLP,完全是為了照顧那些老設備。以后IO映射的方式會逐漸取消,為減輕學習壓力,我們以后看到IO 相關的東西,大可或略之。

 

所有的配置空間(Configuration)的訪問,都是Host發起的,確切的說是RC發起的,往往只在上電枚舉和配置階段會發起Configuration的訪問,這樣的TLP很重要,但不是常態; Message也是一樣,只有有中斷,或者有錯誤等情況下,才會有Message TLP,屬非主流。PCIe線上主流傳輸的是Memory訪問相關的TLP,Host與device,或者device與device之間,數據都是在彼此的Memory之間(拋掉IO)交互,因此,這種TLP是我們最常見的。

這四種請求,如果需要對方響應的,我們叫做Non-Posted的TLP;如果不期望對方給響應的,我們稱之為Posted TLP。Post,有”郵政”的意思,我們只管把信投到郵箱,能不能到達對方,就取決於郵遞員了。Posted TLP,就是不指望對方回復(信能不能收到都是個問題);Non-Posted TLP,就是要求對方務必回復。

 

哪些TLP是Posted,哪些又是non-posted的呢?像Configuration和IO訪問,無論讀寫,都是Non-posted的,這樣的請求必須得到設備的響應;Message TLP是Posted;Memory Read必須是Non-posted,我讀你數據,你不返回數據(返回數據也是響應),那肯定不行的。所以,Memory Read必須得到響應。而Memory Write是Posted,我數據傳給你,無需回復,這樣Host或者Device可以不等對方回復,趁早把下一筆數據寫下去,這樣一定程度上提高了寫的性能。有人會擔心如果沒有得到對方的響應,發送者就沒有辦法知道數據究竟有沒有成功寫入,就有丟數據的風險。雖然這個風險存在(概率很小),但數據鏈路層提供了ACK/NAK機制,一定程度上能保證TLP正確交互,因此能很大程度減小數據寫失敗的可能。

Request Type

Non-Posted or Posted

Memory Read

Non-Posted

Memory Write

Posted

Memory Read Lock

Non-Posted

IO Read

Non-Posted

IO Write

Non-Posted

Configuration Read (Type 0 and Type 1)

Non-Posted

Configuration Write (Type 0 and Type 1)

Non-Posted

Message

Posted

 

所以,只要記住只有Memory WriteMessage兩種TLPPosted就可以了。

Memory Read Lock是歷史的遺留物,Native PCIe設備已經拋棄了這個,存在的意義完全是為了兼容Legacy PCIe設備。和IO一樣,我們以后也忽略。能不看的就不看,PCIe東西本來就多,不要被這些過時沒用的東西擋着我們學習的道路。

Configuration一欄,看到有Type 0和Type 1。我們在之前的拓撲結構中,看到除了Endpoint之外,還有Switch,他們都是PCIe設備,但配置種類不同,因此用Type 0和Type 1區分

 

Request Type

Non-Posted or Posted

Memory Read

Non-Posted

Memory Write

Posted

Configuration Read (Type 0 and Type 1)

Non-Posted

Configuration Write (Type 0 and Type 1)

Non-Posted

Message

Posted

 

這樣,Request TLP是不是清爽點?

 

對Non-Posted的Request,是一定需要對方響應的,對方是通過返回一個Completion TLP來作為響應的。對Read Request,響應者通過Completion TLP返回請求者所需的數據,這種Completion TLP包含有效數據;對Write Request(現在只有Configuration Write了)來說,響應者通過Completion TLP告訴請求者執行狀態,這樣的Completion TLP不含有效數據。

因此,PCIe里面所有的TLP = Request TLP + Completion TLP。

TLP Packet Type

Abbreviated Name

Memory Read

MRd

Memory Write

MWr

Configuration Read(Type 0 and Type 1)

CfgRd0, CfgRd

Configuration Write(Type 0 and Type 1)

CfgWr0,CfgWr1

Message Request with Data

MsgD

Message Request without Data

Msg

Completion with Data

CplD

Completion without Data

Cpl

 

看個Memory Read的例子:

 

 

例子中,Switch B下面的某個Endpoint想讀Host內存的數據,因此,它在事務層上生成一個Memory Read TLP,該MRd一路向上,翻過B,越過A,最終到達RC。RC收到該Request,就到內存中取該Endpoint所需的數據,RC通過Completion with Data TLP(CplD)返回數據,原路返回,直到Endpoint。

一個TLP,最多只能攜帶4KB有效數據,因此,上例子,如果Endpoint需要讀16KB的數據,RC必須返回4個CplD給Endpoint。注意,Endpoint只需發1個MRd就可以了。

 

再看個Memory Write的例子:

 

 

該例子中,Host想往某個Endpoint寫入數據,因此RC在其事務層生成一個Memory Write TLP(要寫的數據在該TLP中),翻過A,越過B,直到目的地。前面說過Memory Write TLP是Posted的,因此,Endpoint收到數據后,是不需要返回Completion TLP(如果這個時候返回Completion TLP,反而是畫蛇添足)。

同樣的,由於一個TLP只能攜帶    4KB數據,因此Host想往Endpoint上寫入16KB數據,RC必須發送4個MWr TLP。

 

老男孩讀PCIe之五:TLP結構

無論Request TLP,還是作為回應的Completion TLP,它們模樣都差不多:

TLP主要由三部分組成:Header,Data和CRC。TLP都是生於發送端的事務層(Transaction Layer),終於接收端的事務層。

每個TLP都有一個Header,跟動物一樣,沒有頭就活不了,所以TLP可以沒手沒腳,但不能沒有頭。事務層根據上層請求內容,生成TLP Header。Header內容包括發送者的相關信息、目標地址(該TLP要發給誰)、TLP類型(前面提到的諸如Memory read,Memory Write之類的)、數據長度(如果有的話)等等。

Data Payload域,用以放有效載荷數據。該域不是必須的,因為並不是每個TLP都必須攜帶數據的,比如Memory Read TLP,它只是一個請求,數據是由目標設備通過Completion TLP返回的。后面我們會整理哪些TLP需要攜帶數據,哪些TLP不帶數據的。前面也提到,一個TLP最大載重是4KB,數據長度大於4KB的話,就需要分幾個TLP傳輸。

ECRC(End to End CRC)域,它對之前的Header和Data(如果有的話)生成一個CRC,在接收端然后根據收到的TLP,重新生成Header和Data(如果有的話)的CRC,和收到的CRC比較,一樣則說明數據在傳輸過程中沒有出錯,否則就有錯。它也是可選的,可以設置不加CRC。

 

Data域和CRC域沒有什么好說的,有花頭的是Header域,我們要深入其中看看。

一個Header大小可以是3DW,也可以是4DW。以4DW的Header為例,TLP的Header長下面樣子:

 

 

紅色區域為所有TLP Header公共部分,所有Header都有這些;其它則是跟具體的TLP相關。

 

稍微解釋一下:

Fmt:Format, 表明該TLP是否帶有數據,Header是3DW還是4DW;

Type:TLP類型,上一節提到的,Memory Read, Memory Write, Configuration Read, Configuration Write, Message和Completion,等等;

R Reserved,為0;

TC: Traffic Class,TLP也分三六九等,優先級高的先得到服務。這里是3比特,說明可以分為8個等級,0-7,TC默認是0,數字越大,優先級越高;

Attr: Attrbiute, 屬性,前后共三個bit,先不說;

TH: TLP Processing Hints,先不說;

TD: TLP Digest,之前說ECRC可選,如果這個這個bit置起來,說明該TLP包含ECRC,接收端應該做CRC校驗;

EP: Poisoned data, 有毒的數據,遠離,哈哈;

AT: Address Type,地址種類,先不說;

Length: Payload數據長度,10個bit,最大1024,單位DW,所以TLP最大數據長度是4KB; 該長度總是DW的整數倍,如果TLP的數據不是DW的整數倍(不是4Byte的整數倍),則需要用到下面兩個域:

Last DW BE 1st DW BE

 

我覺得,到目前為止,對於Header,我們只需知道它大概有什么內容,沒有必要記住每個域是什么。

 

這里重點講講Fmt和Type,看看不同的TLP(精簡版的,Native PCIe設備所有)其Fmt和Type應該怎樣編碼:

 

 

 

TLP

Fmt

Type

Comment

Memory Read Request

000=3DW,no data

001=4DW,no data

0 0000

3DW或者4DW是指Header大小,Memory Read不帶數據

Memory Write Request

010=3DW,with data

011=4DW,with data

0 0000

Memory Write必須帶數據

Configuration Type 0 Read Request

000=3DW,no data

0 0100

讀Endpoint的Configuration,不帶數據,Header總是3DW

Configuration Type 0 Write Request

010=3DW,with data

0 0100

寫Endpoint的Configuration,帶數據,Header總是3DW

Configuration Type 1 Read Request

000=3DW,no data

0 0101

讀Switch的Configuration,不帶數據,Header總是3DW

Configuration Type 1 Write Request

010=3DW,with data

0 0101

寫Switch的Configuration,帶數據,Header總是3DW

Message Request

001 = 4DW, no data

1 0rrr

Message的Header總是4DW

Message Request with Data

011 = 4DW, with data

1 0rrr

Message的Header總是4DW

Completion

000=3DW,no data

0 1010

Completion的Header總是3DW

Completion with Data

010=3DW,with data

0 1010

Completion的Header總是3DW

 

從上可以看出,Configuration和Completion 的TLP(以C打頭的TLP),其Header大小總是3字節;Message TLP的Header總是4字節;而Memory相關的TLP取決於地址空間的大小,地址空間小於4GB的,Header大小為3DW,大於4GB的,Header大小則為4DW。

上面介紹了幾個TLP Header的通用部分,下面分別介紹具體TLP的Header。

 

  • Memory TLP

有兩個重要的東西在前面沒有提到,那就是TLP的源和目標,即,該TLP是哪里產生的,它要到哪里去,它們都包含在Header里面的。因為不同的TLP類型,尋址方式不同,因此要具體TLP具體來看這兩個東西。

 

 

 

對一個PCIe設備來說,它開放給Host訪問的設備空間首先會映射到Host的內存空間,Host如果想訪問設備的某個空間,TLP Header當中的地址應該設置為該訪問空間在Host內存的映射地址。如果Host內存空間小於4GB,則Memory讀寫TLP的Header大小為3DW,大於4GB,則為4DW。那是因為,對4GB內存空間,32bit的地址用1DW就可以表示,該地址位於Byte8-11;而4GB以上的內存空間,需要2DW表示地址,該地址位於Byte8-15。

 

該TLP經過Switch的時候,Switch會根據地址信息,把該TLP轉發到目標設備。之所以能唯一的找到目標設備,那是因為不同的Endpoint設備空間會映射到Host內存空間的不同位置。

 

關於TLP路由,后面還會專門講。

Memory TLP的目標是通過內存地址告知的,而源是通過”Requester ID”告知。每個設備在PCIe系統中都有唯一的ID,該ID由總線(Bus)、設備(Device)、功能(Function)三者唯一確定。這個后面也會專門講,這里只需知道一個PCIe組成有唯一的ID,不管是RC,Switch還是Endpoint。

 

  • Configuration TLP

Endpoint和Switch的配置(Configuration)格式不一樣,分別為Type 0和 Type 1來表示。配置可以認為是一個Endpoint或者Switch的一個標准空間,這段空間在初始化時也需要映射到Host的內存空間。與設備的其他空間不同,該空間是標准化的,即不管哪個廠家生產的設備,都需要有這么段空間,而且哪個地方放什么東西,都是協議規定好的,Host按協議訪問這部分空間。由於每個設備ID唯一,而其Configuration又是固定好的,因此,Host訪問PCIe設備的配置空間,只需指定目標設備的ID就可以了,不需要內存地址。

 

下面是訪問Endpoint的配置空間的TLP Header (Type 0):

 

 

Bus Number + Device + Function就唯一決定了目標設備; Ext Reg Number + Register Number相當於配置空間的偏移。找到了設備,然后指定了配置空間的偏移,就能找到具體想訪問的配置空間的某個位置。

 

  • Message TLP

Message TLP用以傳輸中斷、錯誤、電源管理等信息,取代PCI時代的邊帶信號傳輸。Message TLP的Header 大小總是4DW。

 

 

Message Code來指定該Message的類型,具體如下:

 

 

不同的Message Code,最后兩個DW的意義也不同,這里不展開。

 

  • Completion TLP

有non-posted request TLP,才有Completion TLP。有因才有果。前面看到,Requester 的TLP當中都有Requester ID和Tag,來告訴接收者發起者是誰。那么響應者的目標地址就很簡單,照抄發起者的源地址就可以了。因此,Completion TLP的Header如下:

 

 

Completion TLP,一方面,可以返回請求者的數據,比如作為Memory或者Configuration Read的響應;另一方面,還可以返回該事務(Transaction)的狀態,因此,在Completion TLP的Header里面有一個Completion Status,用以返回事務狀態:

 

 

 

老男孩讀PCIe之六:配置和地址空間

每個PCIe設備,有這么一段空間,Host軟件可以讀取它獲得該設備的一些信息,也可以通過它來配置該設備,這段空間就叫做PCIe的配置空間。不同於每個設備的其它空間,PCIe設備的配置空間是協議規定好的,哪個地方放什么內容,都是有定義的。PCI或者PCI-X時代就有配置空間的概念,那時的配置空間如下:

 

 

整個配置空間就是一系列寄存器的集合,其中Type 0是Endpoint的配置,Type 1是Bridge(PCIe時代就是Switch)的配置,都由兩部分組成:64 Bytes的Header+192Bytes的Capability結構,后者是設備告訴Host它有多牛逼,都會什么絕活。

 

進入PCIe時代,PCIe能耐更大,192 Bytes不足以羅列它的絕活。為了保持后向兼容,又要不把絕活落下,怎么辦?很簡單,我擴展后者的空間,整個配置空間由256 Bytes擴展成4KB,前面256 Bytes保持不變:

 

 

PCIe有什么能耐(Capability)我們不看,我們先挑軟柿子捏,先看看只占64 Bytes的Configuration Header。 

 

 

像Device ID,Vendor ID,Class Code和Revision ID,是只讀寄存器,PCIe設備通過這些寄存器告訴Host軟件,這是哪個廠家的設備、設備ID是多少、以及是什么類型的(網卡?顯卡?橋?)設備。

 

其它的我們暫時不看,我們看看重要的BAR(Base Address Register)。

對Endpoint Configuration(Type 0),提供了最多6個BAR,而對Switch(Type 1)來說,只有2個。BAR是干什么用的?

 

每個PCIe設備,都有自己的內部空間,這部分空間如果開放給Host(軟件或者CPU)訪問,那么Host怎樣才能往這部分空間寫入數據,或者讀數據呢?

 

我們知道,CPU只能直接訪問Host內存(Memory)空間(或者IO空間,我們不考慮),不對PCIe等外設直接操作。怎么辦?記得皇帝身邊那個有根的太監嗎?Root Complex,RC。RC可以為CPU分憂。

 

解決辦法是:CPU如果想訪問某個設備的空間,由於它不能(或者不屑)親自跟那些PCIe外設打交道,因此叫太監RC去辦。比如,如果CPU想讀PCIe外設的數據,先叫RC通過TLP把數據從PCIe外設讀到Host內存,然后CPU從Host內存讀數據;如果CPU要往外設寫數據,則先把數據在內存中准備好,然后叫RC通過TLP寫入到PCIe設備。完美!

 

 

上圖例子中,最左邊虛線的表示CPU要讀Endpoint A的數據,RC則通過TLP(經歷Switch)數據交互獲得數據,並把它寫入到系統內存中,然后CPU從內存中讀取數據(紫色箭頭所示),從而CPU間接完成對PCIe設備數據的讀取。

 

具體實現就是上電的時候,系統把PCIe設備開放的空間(系統軟件可見)映射到內存空間,CPU要訪問該PCIe設備空間,只需訪問對應的內存空間。RC檢查該內存地址,如果發現該內存空間地址是某個PCIe設備空間的映射,就會觸發其產生TLP,去訪問對應的PCIe設備,讀取或者寫入PCIe設備。

 

一個PCIe設備,可能有若干個內部空間(屬性可能不一樣,比如有些可預讀,有些不可預讀)需要映射到內存空間,設備出廠時,這些空間的大小和屬性都寫在Configuration BAR寄存器里面,然后上電后,系統軟件讀取這些BAR,分別為其分配對應的系統內存空間,並把相應的內存基地址寫回到BAR。(BAR的地址其實是PCI總線域的地址,CPU訪問的是存儲器域的地址,CPU訪問PCIe設備時,需要把總線域地址轉換成存儲器域的地址。)

 

 

 

如上圖例子,一個Native PCIe Endpoint,只支持Memory Map,它有兩個不同屬性的內部空間要開放給系統軟件,因此,它可以分別映射到系統內存空間的兩個地方;還有一個Legacy Endpoint,它既支持Memory Map,還支持IO Map,它也有兩個不同屬性的內部空間,分別映射到系統內存空間和IO空間。

 

來個例子,看一下一個PCIe設備,系統軟件是如何為其分配映射空間的。

 

 

上電時,系統軟件首先會讀取PCIe設備的BAR0,得到數據:

 

 

然后系統軟件往該BAR0寫入全1,得到:

 

 

BAR寄存器有些bit是只讀的,是PCIe設備在出廠前就固定好的bit,寫全1進去,如果值保持不變,就說明這些bit是廠家固化好的,這些固化好的bit提供了這塊內部空間的一些信息:

 

怎么解讀?低12沒變,表明該設備空間大小是4KB(2的12次方),然后低4位表明了該存儲空間的一些屬性(IO映射還是內存映射,32bit地址還是64bit地址,能否預取?做過單片機的人可能知道,有些寄存器只要一讀,數據就會清掉,因此,對這樣的空間,是不能預讀的,因為預讀會改變原來的值),這些都是PCIe設備在出廠前都設置好的,提供給系統軟件的信息。

 

然后系統軟件根據這些信息,在系統內存空間找到這樣一塊地方來映射這4KB的空間,把分配的基地址寫入到BAR0:

 

 

從而最終完成了該PCIe空間的映射。一個PCIe設備可能有若干個內部空間需要開放出來,系統軟件依次讀取BAR1,BAR2。。。,直到BAR5,完成所有內部空間的映射。

 

上面主要講了Endpoint的BAR,Switch也有兩個BAR,今天不打算講,下節講TLP路由,再回過頭來講。繼續說配置空間。

 

前面說每個PCIe設備都有一個配置空間,其實這樣說是不准確的,而是每個PCIe設備至少有一個配置空間。一個PCIe設備,它可能具有多個功能(function),比如既能當硬盤,還能當網卡。每個功能對應一個配置空間。

 

在一個PCIe拓撲結構里,一條總線下面可以掛幾個設備,而每個設備可以具有幾個功能,如下所示:

 

 

因此,在整個PCIe系統中,只要知道了Bus+Device+Function,就能找到對應的Function。尋址基本單元是功能(function),它的ID就由Bus+Device+Function組成 (BDF)。一個PCIe系統,可以最多有256條Bus,每條Bus上可以掛最多32個Device,而每個Device最多又能實現8個Function,而每個Function對應着4KB的配置空間。上電的時候,這些配置空間都是需要映射到Host的內存空間,因此,需要占用內存空間是:256*32*8*4KB =256MB。在這個動輒4GB、8GB內存的時代,256MB算不了什么。

 

系統軟件是如何讀取Configuration空間呢?不能通過BAR中的地址,為什么?別忘了BAR是在Configuration中的,你首先要讀取Configuration,才能得到BAR。前面不是系統為所有可能的Configuration預留了256MB內存空間嗎?系統軟件想訪問哪個Configuration,只需指定相應Function對應的內存空間地址,RC發現這個地址是Configuration映射空間,就會產生相應的Configuration Read TLP去獲得相應Function的Configuration。

再回想一下前面介紹的Configuration Read TLP的Header格式:

 

 

Bus Number + Device + Function就唯一決定了目標設備; Ext Reg Number + Register Number相當於配置空間的偏移。找到了設備,然后指定了配置空間的偏移,就能找到具體想訪問的配置空間的某個位置。

 

結束前,強調一下,只有RC才能發起Configuration的訪問請求,其他設備是不允許對別的設備進行Configuration讀寫的。

老男孩讀PCIe之七:TLP的路由

一個TLP,是怎樣經歷千山萬水,最后順利抵達目的地呢?

 

 

今天就以上圖的簡單拓撲結構為例,討論一個TLP是怎樣從發起者到達接收者,即TLP路由問題。

 

PCIe共有三種路由方式:基於地址(Address)路由,基於設備ID(Bus number + Device number + Function Number)路由,還有就是隱式(Implicit)路由。

 

不同類型的TLP,其尋址方式也不同,下表總結了每種TLP對應的路由方式:

 

TLP類型

路由方式

Memory Read/Write TLP

地址路由

Configuration Read/Write TLP

ID路由

Completion TLP

ID路由

Message TLP

地址路由或者ID路由或者隱式路由

 

下面分別講講這幾種路由方式。

  • 地址路由

 

前面提到,Switch負責路由和TLP的轉發,而路由信息是存儲在Switch的Configuration空間的,因此,很有必要先理解Switch的Configuration。

 

 

BAR0和BAR1沒有什么好說,跟前一節講的Endpoint的BAR意義一樣,存放Switch內部空間在Host內存空間映射基址。

 

Switch有一個上游端口(靠近RC)和若干個下游端口,每個端口其實是一個Bridge,都是有一個Configuration的,每個Configuration描述了其下面連接設備空間映射的范圍,分別由Memory Base和Memory Limit來表示。對上游端口,其Configuration描述的地址范圍是它下游所有設備的映射空間范圍,而對每個下游端口的Configuration,描述了連接它端口設備的映射空間范圍。大家看看下面這張圖,理解一下我剛才說的。(Range由Memory Base和Memory Limit限定)

 

 

 

前面我們看到,Memory Read 或者Memory Write TLP的Header里面都有一個地址信息,該地址是PCIe設備內部空間在內存中的映射地址。

 

 

 

  • 當一個Endpoint收到一個Memory Read或者Memory Write TLP,它會把TLP Header中的地址跟它Configuration當中的所有BAR寄存器比較,如果TLP Header中的地址落在這些BAR的地址空間,那么它就認為該TLP是發給它的,於是接收該TLP,否則就忽略。

 

 

 

  • 當一個Switch上游端口收到一個Memory Read或者Memory Write TLP,它首先把TLP Header中的地址跟它自己Configuration當中的所有BAR寄存器比較,如果TLP Header當中的地址落在這些BAR的地址空間,那么它就認為該TLP是發給它的,於是接收該TLP(這個過程與Endpoint的處理方式一樣);如果不是,然后看這個地址是否落在其下游設備的地址范圍內(是否在memory base 和memory limit之間),如果是,說明該TLP是發給它下游設備的,因此它要完成路由轉發;如果地址不落在下游設備的地方范圍內,說明該TLP不是發給它下面設備的,因此不接受該TLP。

 

 

剛才的描述是針對TLP從Upstream流到Downstream的路由。如果是TLP從下游往上走呢?

 

它首先把TLP Header中的地址跟它自己Configuration當中的所有BAR寄存器比較,如果TLP Header當中的地址落在這些BAR的地址空間,那么它就認為該TLP是發給它的,於是接收該TLP(跟前面描述一樣);如果不是,然后看這個地址是否落在其下游設備的地址范圍內(是否在memory base 和memory limit之間),如果是,這個時候不是接受,而是拒絕;相反,如果地址不落在下游設備的地方范圍內,Switch則把該TLP傳上去。

 

  • ID路由

在一個PCIe拓撲結構中,由ID = Bus number+Device number+Function Number(BDF)能唯一找到某個設備的某個功能。這種按設備ID號來尋址的方式叫做ID路由。Configuration TLP和Completion TLP(以C打頭的TLP)按ID路由,Message在某些情況下也是ID路由。

使用ID路由的TLP,其TLP Header中含有BDF信息:

 

 

當一個Endpoint收到一個這樣的TLP,它用自己的ID和收到TLP Header中的BDF比較,如果是給自己的,就收下TLP,否則就拒絕。

如果是一個Switch收到這樣的一個TLP,怎么處理?我們再回頭去看看Switch的Configuration Header。

 

 

看三個寄存器:Subordinate Bus Number,Secondary Bus Number和Primary Bus Number,看下圖就知道這幾個寄存器是什么意思:

 

 

對一個Switch來說,每個Port靠近RC(上游)的那根Bus叫做Primary Bus,其Number寫在其Configuration Header中的Primary Bus Number寄存器;每個Port下面的那根Bus叫做Secondary Bus,其Number寫在其Configuration Header中的Secondary Bus Number寄存器;對上游端口,Subordinate Bus是其下游所有端口連接的Bus 編號最大的那個Bus,Subordinate Bus Number寫在每個Port的Configuration Header中的Subordinate Bus Number寄存器。

 

當一個Switch收到一個基於ID尋址的TLP,首先檢查TLP中的BDF是否與自己的ID匹配,如匹配,說明該TLP是給自己的,收下;否則,則檢查該TLP中的Bus Number是否落在Secondary Bus Number和Subordinate Bus Number之間,如果是,說明該TLP是發給其下游設備的,然后轉發到對應的下游端口;如果其他情況,則拒絕這些TLP。

 

 

  • 隱式路由

 

只有Message TLP才支持隱式路由。在PCIe總線中,有些Message是與RC通信的,RC是該TLP的發送者或者接收者,因此沒有必要明明白白的指定地址或者ID,而是采用”你懂的”的方式進行路由,這種路由方式為隱式路由。Message TLP還支持地址路由和ID路由,但以隱式路由為主。

 

Message TLP的Header總是4DW,如下圖所示:

 

 

Type字段,低三位,用rrr表示的,指明該Message的路由方式,具體如下:

 

 

當一個Endpoint收到一個Message TLP,檢查TLP Header,如果是RC的廣播Message(011b)或者該Message終結於它(100b),它就接受該Message。

 

當一個Switch收到一個Message TLP,檢查TLP Header,如果是RC的廣播Message(011b),則往它每個下游端口復制該Message然后轉發;如果該Message終結於它(100b),則接受該TLP;如果下游端口收到發給RC的message,往上游端口轉發便是。

 

上面說的是Message使用隱式路由的情況。如果是地址路由或者ID路由,Message TLP的路由跟別的TLP一樣,不贅述。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM