PCIe 是一種序列匯流排(serial bus)。在 PCIe 協議上,所有操作都以封包(packet)的形式完成。舉例來說,假設 CPU 想要將一些資料寫入裝置。它將命令轉送到 PCIe Bridge,然後 PCIe bridge 會建立一個封包、之中寫入位址和資料、然後以序列方式轉送到目標。目標裝置收到封包後,再進行解析並執行之。
PCIe 所連接兩端以封包的形式來溝通,以將資訊從傳輸端傳送到接收端。而 PCIe 是一個多層協定,由事務層(Transaction)、資料交換層(Data Link)和實體層(Physical)構成。傳輸時,每當封包經過下一層級後,會將原本的封包加上此層的封包所需的附加資訊。接收時則相反,封包由實體層步步拆解至事務層的封包,
在本章節中,我們接針對事務層(Transaction Layer)進行更仔細的探討。
事務層是協定的最上層,主要職責是 TLP(Transaction Layer Packet) 的組裝和拆卸。 TLP 用於例如讀取和寫入以及特定類型的事件。交易層還負責管理 TLP 的 credit-based flow control。
每個封包都具有一個唯一的 id,使回應的封包能夠定位到正確的傳輸者。封包格式還支援不同形式的定址,根據事務類型可以分為 Memory、I/O、Configuration 和 Message 四種。其中 Message 方式主要目的是消除目前在實作中使用的各種 sideband 訊號,其支援把所有舊有(PCI)的 sideband 訊號轉變為 inband 訊號,例如中斷、電源管理的請求等。
從系統軟體的角度,事務層(Transaction layer)的運作原理是我們理解 PCIe 重要的一環。在事務層,封包被接收。而封包可以有許多類型:比如讀寫記憶體、讀寫 I/O、表示完成訊息等。這一類封包在標準中稱為「事務層封包(Transaction Layer Packet, TLP)」。
PCIe 是 32 bits 的 bus,因此每 32 bits 的資料在 PCIe 上可稱為 double word(以下簡稱 DW)。每個 TLP 一般由 header (3-4 個 DW)和 data payload (0-1023 個 DW)組成。
為求容易理解,這裡暫時忽略可選的 TLP Prefix 和 TLP Digest。詳情請翻閱 PCIe Specification 的第二章 Transaction Layer Specification。
Header 的格式有許多種,但第一個 DW 會是相同的。Fmt
一欄位標示 header 的格式,比如 header 大小以及是否有 data payload 等。結合 Type
則描述了 TLP 對應的操作。詳情請見 PCI Express® Base Specification Revision 5.0 的 Table 2-3 Fmt[2:0] and Type[4:0] Field Encodings
。
由於 PCIe 事務的類型複雜,要仔細闡述 PCIe 所支援的所有封包格式並不容易。這裡我們僅以舉例方式說明,過程會省略部分的特例與細節,只是嘗試讓讀者對 PCIe 傳輸的流程有基本的了解。如果需要完整的認識,請務必閱讀規格書。
假設 CPU 嘗試以 32 位元尋址方式,對 PCIe 周邊的實體位址 fdaff040
寫入 32 bits 的資料 0x12345678
。
在此案例中,現代平台常見的設計是 CPU 首先在自己的匯流排上執行寫入操作,而由於連接到 CPU 匯流排的記憶體控制器(memory controller) 直接連接到 PCIe 匯流排。因此,記憶體控制器能再藉由 Root Complex 產生一個記憶體寫入的封包,表明將資料寫入特定位址。封包經過 PCIe port 到達周邊裝置(中間可能有額外的網路,比如 switch 等)後,裝置對封包做解析並執行要求的寫入操作。
在封包部分。可由 4 個 DW 組成,具體內容如下所示。
這裡我們可以再觀察封包的更多細節:
Fmt
和 Type
結合在一起,表示這是一個記憶體寫入請求TD
為零,表示 TLP 資料上沒有 TLP Digest(額外的 CRC 資料)Length
為 0x001,表示該 TLP 具有 1 個 DW payloadRequest ID
欄位表示該封包的傳送者之 ID 為零,也就是 Root ComplexTag
在此案例下是未使用的欄位。發送者可以在此處放置任何內容,而其他人都應忽略它。後面會再探討這有甚麼用途1st BE
欄位(1st Double-Word Byte Enable)可以用來選擇第一個 data DW 中的四個 bytes 之有效性。以本例來說,設定為 0xf 表示四個 bytes 都有效,應被寫入Last BE
在 Length 為 1 時必須為零,因為第一個 DW 和最後一個 DW 是相同的Address
表示資料寫入的起始位址
0x3f6bfc10 << 2 == 0xfdaff040
Data DW0
表示所要寫入的資料 0x12345678
附帶一提,由於 PCIe 是使用 Big Endian,如果 CPU 是使用 Little Endian,則在其在軟體會需要寫入 0x78563412
。
假設 CPU 嘗試對 PCIe 周邊的實體位址 fdaff040
讀出 32 bits 的資料時又是如何呢? 在讀取操作上,這會比寫入稍微複雜一下,因為會涉及兩個封包:
在 PCIe 術語中,我們有一個 Requester 和一個 Completer,在此案例中分別是 Host 和 PCIe 周邊。和上一個例子類似,CPU 在與記憶體控制器共享的匯流排上執行寫入操作,讓記憶體控制器藉 Root Complex 產生透過 PCIe bus 發送的 TLP。
在封包部分。這個讀取請求的封包由 3 個 DW 組成,內容如下。
大體上欄位和前述的寫操作相似。
Fmt
和 Type
接合表示一個讀取請求Request ID
欄位表示該封包的傳送者之 ID 為零,在讀取上此欄位至關重要,因為它告訴 Completer 應該將回應發送到哪裡Tag
在讀取請求中很重要。當 Completer 回應時,它會將此值複製到 Completion TLP 中。這讓 Request 知道完成的封包是對應之前送出的哪個請求Length
為 1 表示要讀取一個 DWAddress
表示從哪個位址讀取關於 Tag
再額外說明。在 PCIe 上,單一裝置被允許發送多個請求,Requester 可以根據自身管理方式設定之,只要確保所有未完成請求的 Tag
都是唯一的即可。
當週邊收到 Read TLP 時,無論它是否能夠確實完成該請求,它都必須以某種 Completion TLP 做回應。若以成功讀取的結果為例,裝置從其內部讀取資料,然後將結果傳回給 Requester,封包會如下所示:
這個封包表示「告訴 Requester 0x0000,對於 0x0100 的請求中,其中 tag 標記為 0x0c 的一個之答案是 0x12345678。
Fmt
與 Type
分別是 0x2
和 0xa
,結合在一起表示這是一個帶有資料的 Complete TLPLength
值為 0x001,表示該 TLP 具有 1 個 DW 的資料
Byte Count
所代表是包含目前的 Completion TLP 所剩餘需要傳輸的 byte 數量。在本例中,單個 TLP 就完成請求的情況中,其值為 4(bytes)Lower Address
是 TLP 所讀取的起始位址之最低 7 個 bits。以本例來說 0x40(0xfdaff040 & 0x7f
)。這個欄位在多個 Completion TLP 時非常有用Completer ID
代表此封包的傳送者是 0x0100
Requester ID
表示封包的接收者 ID 為 0,也就是 Root ComplexStatus
為 0 表示是一個成功的 Completion,其他值就用來不同類型的拒絕/失敗BCM
欄位除了封包源自具有 PCI-X 的 bridge 的情況,始終是零Data DW0
就是所得到的資料比較上述的讀寫案例,我們會發現一個明顯的差異: 讀操作無需等待額外的 Completion TLP,而寫操作需等待直到收到 Completion TLP。對於後者,這表示在收到 Completion TLP 之前,Requester 必須保留與 request 有關的資訊。甚至 CPU 開始讀取動作時,CPU 需持續等待直到成功讀取來自 PCI 裝置的資料。
PCI/PCIe 的術語上,這種發出請求即結束的操作稱為 Posted transaction,而由請求和完成組成的操作則是 non-Posted transaction。顯而易見,non-Posted transaction 容易造成更大的延遲。請參閱 PCIe 的規範手冊以得知關於 Posted/Non-Posted 的更多細節。
Max Payload size
ECRC