4. Memory Read/Write TLP:MRd/MWr TLP格式、地址对齐与数据载荷、Completion(Cpl/CplD)与Non-Posted事务

Memory Read(MRd)和 Memory Write(MWr)是 PCIe 中最常用的 TLP 类型,用于 RC(Root Complex)与 EP(Endpoint)之间的数据交换。理解它们的 TLP 格式、地址对齐规则以及事务分类(Posted vs Non-Posted)是驱动开发与 FPGA 调试的基础。

4.1 MRd/MWr TLP 格式与字段解析

MRd 和 MWr 同属 Memory 空间访问类 TLP,其通用格式由 TLP Prefix、TLP Header、Data Payload(仅 MWr 和 CplD 携带)和 TLP Digest(可选)组成。对于 32 位或 64 位地址,Header 长度分别为 3 DW(12 字节)或 4 DW(16 字节)。

关键字段速查:
  • Fmt[2:0]:决定 Header 长度与是否携带数据。MRd 为 000(3DW无数据)或 001(4DW无数据);MWr 为 010(3DW有数据)或 011(4DW有数据)。
  • Type[4:0]:Memory 请求固定为 00000。
  • TC[2:0]:Traffic Class,用于 QoS 区分。
  • TD:TLP Digest 存在位。
  • EP:Poisoned 指示,标记数据载荷损坏。
  • Length[9:0]:数据载荷长度,单位 DW。MRd 的 Length 表示请求读取的 DW 数;MWr 表示实际写入的 DW 数。
  • Requester ID:Bus/Device/Function 号,标识请求者。
  • Tag[7:0]:事务标签,用于匹配请求与 Completion。
  • Address[31:0] 或 [63:0]:目标内存地址,必须按 Length 对齐。
MRd/MWr TLP Header 结构(3DW Header 示例) MRd/MWr TLP Header 结构(3DW Header,32位地址) Fmt+Type TC TD EP Attr Length[9:0] DW0 Requester ID Tag Last DW 1st DW DW1 Address[31:2] DW2 说明: • 对于 64 位地址(4DW Header),DW2 为高32位地址,DW3 为低32位地址。 • MWr 在 Header 之后紧跟 Data Payload;MRd 无 Data Payload,但需要等待 Completion 返回数据。

4.2 地址对齐与数据载荷规则

PCIe 协议对 Memory 请求的地址对齐有严格约束,违反对齐会导致 UR(Unsupported Request)或数据错误。驱动和 FPGA 逻辑必须遵守以下规则:

请求类型 地址对齐要求 数据载荷约束 典型违规场景
MRd 起始地址必须按 Length 对齐(以 DW 为单位) Length 字段表示请求读取的 DW 数,最大 1024 DW 读取 4 字节但地址未 4 字节对齐
MWr 起始地址必须按 Data Payload 大小对齐 实际数据长度必须等于 Length 字段值,且为 1~1024 DW 写入 8 字节但地址为 0x1002(未 8 字节对齐)
原子操作 地址必须按操作数大小对齐(4B/8B) 固定长度,不可拆分 —
实战提示:在 FPGA 端实现 BAR 空间时,建议将地址译码逻辑设计为“忽略低地址位”模式。例如,若 BAR 空间粒度为 64 字节,则地址低 6 位可忽略,仅检查高 26 位是否命中。这能有效避免因地址未对齐导致的 TLP 被硬件丢弃。

4.3 Non-Posted 事务与 Completion(Cpl/CplD)

PCIe 事务分为 Posted 和 Non-Posted 两类:

  • Posted 事务:发送端发出后无需等待接收端确认,典型代表为 MWr 和 Message。优点是延迟低,但发送端无法直接获知对端是否成功接收。
  • Non-Posted 事务:发送端发出请求后,必须等待接收端返回 Completion(Cpl 或 CplD)才能确认操作完成。典型代表为 MRd、I/O 读写、配置读写。

Completion 是 Non-Posted 事务的响应,分为两种:

Completion 类型 携带数据 典型用途
Cpl(Completion without Data) 否 用于 I/O 写完成、配置写完成,或 MRd 出错时返回错误状态
CplD(Completion with Data) 是 用于 MRd 成功完成,返回请求的数据载荷

Completion 的 Header 中包含 Requester ID 和 Tag,用于与原始请求匹配。RC 端驱动通过维护 Tag 表来跟踪未完成的 Non-Posted 请求,超时未收到 Completion 则触发错误处理。

Non-Posted 事务:MRd + CplD 交互流程 Non-Posted 事务:MRd + CplD 交互流程 RC(Root Complex) EP(Endpoint) MRd (Tag=0x5A, Addr=0x1000, Len=4) CplD (Tag=0x5A, Data=0xDEADBEEF...) 关键点: • RC 发出 MRd 后,必须等待 EP 返回 CplD,期间不能释放 Tag 0x5A。 • 若 EP 返回 Cpl(无数据),表示读请求被拒绝或出错,RC 需检查 Completion Status 字段。

4.4 Completion 超时与错误处理

驱动开发中,Completion 超时是最常见的调试痛点。PCIe 协议定义了 Completion Timeout 机制:

  • RC 发出 Non-Posted 请求后,启动定时器,等待匹配的 Completion。
  • 若超时未收到 Completion,RC 认为事务失败,记录 Uncorrectable Error(UR 或 CA)。
  • FPGA 端必须确保在协议规定时间内(通常为 50μs ~ 50ms,取决于设备类型)返回 Completion,否则 RC 可能触发系统错误或驱动崩溃。
FPGA 调试警告:在 FPGA 中实现 MRd 处理逻辑时,务必检查请求的地址是否落在已实现的 BAR 范围内。若地址未命中,必须返回 Cpl 且 Completion Status = UR(Unsupported Request),而不是忽略请求。忽略请求会导致 RC 端 Completion 超时,进而上报 PCIe 致命错误,可能触发系统重启。

4.5 实战:RC 端驱动发送 MRd 并等待 CplD

以下代码片段展示了 Linux 内核驱动中通过 ioread32 触发 MRd 事务的典型流程。注意,ioread32 是阻塞调用,底层由 PCIe 控制器完成 MRd 发送与 CplD 接收。

#include <linux/io.h>
#include <linux/pci.h>

/* 假设已通过 pci_iomap 映射 BAR0 到 virt_addr */
void __iomem *bar0_virt;

/* 从 EP 的 BAR0 偏移 0x1000 处读取 4 字节 */
u32 read_val = ioread32(bar0_virt + 0x1000);

/* 底层硬件自动完成:
 * 1. 构造 MRd TLP:Requester ID = RC 的 BDF,Tag 由控制器分配
 * 2. 发送 MRd 到 EP
 * 3. 等待 CplD 返回
 * 4. 将 CplD 中的 Data Payload 写入 read_val
 * 5. 释放 Tag
 */

对于需要读取超过 4 字节的场景,可使用 ioread32_rep 或 memcpy_fromio,它们会生成多个 MRd 事务(或单个长 MRd,取决于硬件实现)。

4.6 本章小结

  • MRd 和 MWr 是 PCIe 内存访问的核心 TLP,其 Header 格式决定了地址宽度、数据长度和事务属性。
  • 地址对齐是硬性约束,驱动和 FPGA 必须严格遵循,否则会导致 UR 或数据错误。
  • MRd 属于 Non-Posted 事务,必须等待 Completion(CplD 或 Cpl)才能完成;MWr 属于 Posted 事务,无需等待。
  • Completion 超时是调试中的常见问题,FPGA 端必须及时、正确地响应所有 Non-Posted 请求。