把描述項、DMA 資料與完成通知接起來

DMA
Direct Memory Access,裝置直接存取記憶體

選擇步驟後,圖中對應的元件與下方解說會加上重點標示。完整圖與所有解說會持續顯示;也可用方向鍵切換步驟。

由上往下看本例的所有權交接。同步動作依平台及 DMA API 決定;圖中的先後關係不替平台補出額外的快取保證。

DMA
Direct Memory Access,裝置直接存取記憶體
軟體發布描述項,裝置寫入資料與旗標,再由主機依平台同步條件接手。軟體發布描述項,裝置寫入資料與旗標,再由主機依平台同步條件接手。驅動程式/主機端裝置:執行工作平台可供 DMA 的記憶體寫入描述項及資料依平台規則發布後通知裝置裝置寫入結果資料依適用排序寫完成旗標主機依平台同步條件接手結果

作者的簡化工作週期。PCIe 交易排序與 CPU/快取同步各有責任,兩者共同決定資料何時可用。

DMA(Direct Memory Access,直接記憶體存取)讓裝置發起記憶體交易。要讓一次工作正確完成,軟體還需交接緩衝區所有權,並處理平台要求的同步。

前情提要:Chapter 5 已建立 Request/Completion 的概念。本章沿著軟體準備工作、裝置寫入資料、軟體接手結果的過程,區分 PCIe 排序與 CPU/快取同步的責任。

閱讀路線:DMA 位址與映射背景 → 描述項發布 → Base Table 2-42 的交易排序 → 封包屬性與主機接手條件。

  1. 先取得裝置可用的位址

    本例已具備適用的映射及裝置位址範圍;CPU 指標與 DMA 位址依平台規則處理。

    由平台的 DMA 映射機制取得裝置可使用的位址,確認方向、範圍與緩衝區生命週期。

    Linux 6.12 DMA API HOWTO:CPU and DMA addresses、Types of DMA mappings。

  2. 發布描述項,再交給裝置

    此處的同步須使用平台與作業系統定義的介面;PCIe 排序表本身不定義 CPU 的全部記憶體模型。

    先準備描述項與資料,再依平台要求執行記憶體及 I/O 同步,最後通知裝置有新工作。

    Linux 6.12 memory barriers:dma_wmb/dma_rmb/dma_mb、Kernel I/O barrier effects。

  3. 依適用交易排序寫資料與旗標

    RO 是 Relaxed Ordering,允許在規定條件下放寬排序。能力允許與這兩筆封包是否實際使用 RO,需分別查明。

    裝置先寫結果資料,再以另一筆交易寫完成旗標;判讀這兩筆交易時,保留 TC、Requester ID 與 RO 等適用條件。

    TC
    Traffic Class,流量類別

    Base 6.0 §2.4.1/Table 2-42,pp.222–224;§2.2.6.4–§2.2.6.5,pp.160–161。

  4. 主機取得所有權後讀取結果

    No Snoop 是封包的快取窺探屬性,不能單靠它指定平台的快取一致性行為。

    主機確認完成條件,再依 DMA 映射與快取規則同步並讀取資料。封包已發出、旗標已被觀察與 CPU 可用資料,是同一工作週期中的不同階段。

    Base 6.0 §2.2.6.5,p.161;Linux 6.12 DMA API 與 memory barriers 的相關段落。

圖中元件

裝置端的 DMA

本圖由裝置把結果寫到平台提供的 DMA 緩衝區。

基本責任

DMA 描述誰發起資料搬移;實際 PCIe 仍使用適用的 Memory Request 等交易。

1. DMA 是另一種特殊的 PCIe 封包嗎?

Direct Memory Access(DMA,直接記憶體存取)描述的是裝置搬移資料的方式。放到本章的 PCIe 案例裡,裝置讀主記憶體會發出 MRd,裝置寫主記憶體會發出一般 MWr;封包仍使用 Chapter 5 的交易種類。CPU 先準備工作,再由裝置發出交易,不需要 CPU 逐筆把 Payload 搬進裝置 Register。

從裝置角度命名 DMA 方向
目的Endpoint 的動作資料如何移動
裝置取得主記憶體中的輸入資料發出 MRd,保留 Requester ID 與 Tag。平台以 CplD 把資料回傳給裝置。
裝置把結果放回主記憶體發出帶 Payload 的 MWr。資料隨 Posted Request 向平台移動,沒有普通 Write Completion。
CPU 要求裝置開始處理CPU 經平台寫裝置的通知 Register。這筆 MMIO 寫入是通知,不等於結果資料已搬完。
MMIO
Memory-Mapped I/O,記憶體映射輸入輸出

Endpoint 與沿途 Port 的 BME 必須允許相應的上游要求,這是 Chapter 4 的啟用條件。但 BME=1 不會替驅動程式配置緩衝區、不會驗證描述項,也不等於取得任意主記憶體的存取權。

PCIe 與 DMA 背景來源

Base 6.0 §2.1.1.1(p.116)、§2.2.7.1(pp.162–164)及 Table 7-4 的 BME 定義(p.919)。DMA 位址觀點另見 Linux 6.12:CPU and DMA addresses。

2. CPU 手上的指標,可以直接寫進 DMA 描述項嗎?

必須先確認那個數值屬於哪個位址空間。Linux 6.12 的 DMA 指引區分 CPU 虛擬位址、CPU 實體位址與裝置使用的 DMA 位址;平台可能透過 IOMMU 或 Host Bridge 映射它們。IOMMU 是處理 I/O 存取位址轉譯與保護的平台機制,本章只借用這個背景,不假設所有系統都採同一種硬體。

本例的 DMA 映射假設
觀察者/位置本例的值應如何使用
CPU 程式指標 p透過 CPU 的映射存取緩衝區;不把 p 當作裝置位址。
CPU 實體位址0x90001000–0x90001FFF本例假設的主記憶體範圍。
03:00.0 的 DMA 位址0x40001000–0x40001FFF平台已建立有效且權限相符的映射,描述項使用這組位址。
裝置 BAR00x80004000–0x80007FFFCPU 存取裝置介面用;不是這個主記憶體緩衝區。

例如,裝置讀 DMA 位址 0x40001080,本例平台會轉到 CPU 實體位址 0x90001080。兩個基底相差多少只是這個案例的配置,不能推成通用的加減公式。更不能因為 BAR0 已正確配置,就認為主記憶體 DMA 映射也已存在。

案例中的映射在交易期間保持有效,裝置可表示這段位址,且沒有其他工作重複使用同一緩衝區。若這些前提改變,單看合法的 TLP Header 也無法證明存取一定成功。

TLP
Transaction Layer Packet,交易層封包
來源與推演界線

Linux 6.12 Dynamic DMA mapping Guide 用於區分三種位址。Base 6.0 §2.2.7.1 的 64-bit 位址產生說明(p.164)要求驅動程式確保目標落在裝置可到達的範圍。表內數值、映射範圍與存取權限全部是作者假設,未引用某款 IOMMU 的暫存器編碼。

3. 描述項先寫好,再寫「可以開始」,為什麼仍要同步?

假設描述項包含三項資料:緩衝區位址 0x40001000、長度 256 bytes,以及表示裝置可以開始取用的 OWN 旗標。這個 OWN 是作者為案例命名的欄位,不是 PCIe 共通欄位。裝置介面另行規定:只有取得 OWN 後才讀取其餘欄位。

若 CPU 在原始程式中依序寫入位址、長度、OWN,並不能只靠這個書寫順序,證明裝置必定按同一順序觀察到更新。Base 6.0 §2.4.2 明確指出:裝置以單筆讀取觀察到正在更新的記憶體時,更新順序與粒度不由一般 PCIe 讀取規則保證。一次讀完描述項,也不等於取得原子的完整快照。

本例的所有權交接條件
階段必須建立的關係仍須由誰提供保證
CPU 準備裝置看到 OWN 之前,應先能看到對應的位址與長度。平台的記憶體同步介面與驅動程式。
CPU 通知描述項發布後,才讓裝置收到工作通知。適用的 MMIO 存取與順序規則。
裝置執行依約取得所有權後讀取欄位,完成前不讓 CPU 重用緩衝區。本例的裝置介面與工作協定。
CPU 收回確認裝置交還所有權,完成必要同步後,才讀取或重用結果。裝置狀態定義與平台同步介面。
MMIO
Memory-Mapped I/O,記憶體映射輸入輸出

以 Linux 6.12 的 consistent memory 為例,dma_wmb()/dma_rmb() 用來建立 CPU 與 DMA 裝置之間共享記憶體存取的順序;這些 barrier 本身不提供 MMIO 順序保證。Coherent allocation 也不能省略必要的 memory barrier。若使用 streaming mapping,則還要依 DMA API 處理 CPU 與裝置之間的同步。這些介面各有適用前提,不應濃縮成「加一個 barrier 就全部解決」。

同步規則的閱讀入口

Base 6.0 §2.4.2(p.228)說明 PCIe 不保證的更新順序與粒度。平台補充採 Linux 6.12 memory barriers 的 dma_* barrier 與 Kernel I/O barrier effects 段落,以及 DMA mapping Guide 的 Types of DMA mappings、Using Streaming DMA mappings。此處是責任對照,不是可直接套到任意驅動程式的初始化程式碼。

4. 資料寫入後再寫完成旗標,兩筆 TLP 能互換順序嗎?

TLP
Transaction Layer Packet,交易層封包

換成裝置產生結果。假設同一 Function 先以一筆 MWr 寫入 64 bytes 的資料 D,再以另一筆 MWr 把完成旗標 F 設為 1。D 位於 0x40001000,F 位於 0x40001FF0;兩者都在本例有效的 DMA 映射內。此時問題是:後送出的 F 能不能越過先送出的 D?

查 Table 2-42 時,欄代表先發出的交易,列代表後發出的交易。D 與 F 都是 Posted Request,因此看 A2。相同 TC、沒有適用的放寬條件時,A2a 規定後面的 Posted Request 不得越過前面的 Posted Request。本例便不能讓 F 在 PCIe 路徑中越過 D。

TC
Traffic Class,流量類別
改變條件後,能對交易順序下什麼結論?
D 與 F 的條件適用規則判讀
同一 Function、TC0;F 的 RO=0、IDO=0A2aF 不得越過 D。
同一 TC;F 的 RO=1,且路徑允許這種放寬A2b 及相關限制允許越過,不表示一定會越過;不能再用 A2a 證明 D 在前。
D 使用 TC0、F 使用 TC1Table 2-42 的適用邊界不同 TC 之間沒有這張表提供的順序保證。
只把 No Snoop 從 0 改成 1§2.2.6.5、§2.4.1No Snoop 不改變交易排序;但快取一致性的條件已不同。

表中的 Yes、No、Y/N 不是「成功、失敗、不確定」。Yes 表示為避免 deadlock,後者必須能越過前者;No 表示禁止越過;Y/N 表示規格容許實作選擇。也因此,「PCIe 有強排序」不等於所有種類的 TLP 都按同一總順序排隊。

回到 CPU:A2a 只回答兩筆交易在適用路徑內能否互相越過。CPU 是否以正確的映射和同步方式觀察 F、再使用 D,仍要滿足上一節的交接條件。不能拿一條封包排序規則代替整套記憶體使用協定。

排序表應該連同哪些條文閱讀?

§2.4.1 Table 2-42(p.222)給出欄列與符號;A2a/A2b(p.223)給出 Posted 對 Posted 的條件。A2b 的註解指出部分使用方式會禁止 RO-enabled Posted Request 越過先前 Posted Request,因此 RO=1 是可能放寬的條件,並非必定超車。不同 TC 的界線見 p.222;No Snoop 不影響排序見 p.224。案例排除另有規則的 Flow-Through IDE。

資料寫完就送中斷,軟體一定能先看到資料嗎?

沿用前節的資料 D,現在把後續動作換成 MSI-X(Message Signaled Interrupts Extended,以記憶體寫入通知中斷)交易。先判斷兩筆 PCIe 寫入的順序,再檢查平台如何讓 CPU 讀到結果。這兩段需要各自的證據。

MSI-X
Message Signaled Interrupts Extension,表格式訊息中斷

查閱順序是:先在 §6.1.4 確認 MSI-X 使用 Memory Write,且 RO 與 No Snoop 必須為 0;IDO 在啟用條件成立時可設為 1,本例明確設為 0。再到 §2.4.1 的 Table 2-42,以先發的 D 找欄、後發的 MSI-X 找列,讀 A2a 與 A2b。最後回 §6.1.4 的 implementation note,查資料與中斷使用不同 TC 時的同步要求。(Base 6.0,文件頁/PDF 頁 222–223、661。)

平台補充:若本例的驅動使用 Linux,資料與所有權仍依該緩衝區的映射方式及裝置介面處理;例如 consistent memory 的 DMA barrier 與 MMIO 存取有不同適用範圍。這部分參考 Linux 6.12 memory barriers 與 DMA mapping Guide,不是 Base 6.0 對所有作業系統的規定。

5. 設定欄位允許 RO,是否代表每個封包都用了 RO?

不是。設定空間決定允許使用的行為;真正的 TLP Attribute 才描述這筆交易。本例延續 PCIe Capability 位於 0x70,因此 Device Control 位於 0x78。假設這個 16-bit Register 讀回 0x2810:

本例 Device Control 的能力使用許可
欄位由 0x2810 取得的值能證明的事情
bit 4:Enable Relaxed Ordering1符合條件的交易可以設定 RO;不強迫每筆設定。
bit 11:Enable No Snoop1仍須符合 No Snoop 使用條件,不能讓所有交易一律設為 1。
bits 14:12:Max_Read_Request_Size010b最大讀取要求為 512 bytes,與 Chapter 5 的 DMA 讀取案例相容。

接著看 Figure 2-29:Attr[1] 是 RO、Attr[0] 是 No Snoop、Attr[2] 是 ID-Based Ordering(IDO)。它們在 Non-Flit Header 裡並不全部相鄰,必須再對 Figure 2-5 的實際位置;不可直接把這三個 bits 當成連續的欄位取值。

RO=1 表示適用的順序限制可以放寬,不是「這筆資料比較不重要」。No Snoop=1 表示該交易不期待硬體強制快取一致性;Device Control 的許可還要求發送端能保證目標位址沒有存在系統任何 cache 中。它不是清除 CPU cache 的命令。反過來,No Snoop=0 也不會替軟體完成描述項所有權交接。

IDO 另有獨立的 Request/Completion 啟用控制及身分條件;本章固定為零,避免把不同 Requester 或 PASID 的例外混入同一 Function 的基本案例。到了中斷章,還會看到 MSI/MSI-X 的 RO 與 No Snoop 必須為零,因此「Enable=1」也不能凌駕特定交易的禁止條件。

PASID
Process Address Space ID,程序位址空間識別碼
MSI
Message Signaled Interrupt,訊息中斷
從設定到封包的查詢順序

先依 Chapter 2 找到 PCIe Capability,再看 Figure 7-25(p.959)、Table 7-21 的 bits 4、11、14:12(pp.960–962)。封包屬性看 Figure 2-29/Table 2-12(p.160)、Table 2-13(p.161),最後回到 Figure 2-5(p.120)。IDO 控制與限制見 §2.2.6.4(p.160);MSI 屬性限制見 §6.1.4(p.661)。

6. 把排序與平台同步放回同一案例

裝置以一筆 MRd 讀完整描述項,是否保證所有欄位來自同一個更新時間點?

沒有。單一 TLP 不會自動把正在變動的資料變成原子快照。Base 6.0 §2.4.2(p.228)甚至說明可能觀察到部分更新;需要裝置介面與平台共同建立有效的所有權及同步方式。

在同一 TC 中,先寫 D,再以 RO=1 寫 F;觀察到 D 在前,就能證明所有實作都必須如此嗎?

不能。容許重排並不要求每次重排。這次觀察只能說明這次的結果,不能把「允許」改寫成「禁止」。要證明保序,應檢查適用的欄位與路徑限制。(A2b,p.223。)

Device Control 的 Enable No Snoop=1,可以省掉所有 cache 與 barrier 的考量嗎?

不行。它只提供有條件的發送許可,而且 No Snoop 不改變交易順序。軟體仍須遵循平台的記憶體介面;欄位含義見 Table 7-21(p.961)。

BAR0 與 BME 都正確,裝置仍把 CPU 指標 p 當成 DMA 位址使用,問題解決了嗎?

沒有。BAR0 解決裝置資源的存取,BME 控制要求的發送許可;兩者都不負責把 p 轉成這個裝置可用的 DMA 位址。請回到 位址對照表 確認描述項實際交給裝置的是哪一種值。

7. 接著讓軟體知道有工作完成

資料與狀態已按介面約定交接後,裝置還需要通知軟體處理。Chapter 7 會從 Capability 找到 MSI-X Table,設定一個 entry,並追蹤遮罩、Pending 與中斷寫入。

版本與來源

PCIe 規則以 Base 6.0 為準;平台背景固定使用 Linux 6.12 的 DMA mapping Guide 與 memory barriers 文件。這些補充文件不是 IOMMU 硬體規格,也不使本章案例成為 Linux 驅動程式範本。查核日期:2026-10-28。