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 对齐。
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) | 固定长度,不可拆分 | — |
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 则触发错误处理。
4.4 Completion 超时与错误处理
驱动开发中,Completion 超时是最常见的调试痛点。PCIe 协议定义了 Completion Timeout 机制:
- RC 发出 Non-Posted 请求后,启动定时器,等待匹配的 Completion。
- 若超时未收到 Completion,RC 认为事务失败,记录 Uncorrectable Error(UR 或 CA)。
- FPGA 端必须确保在协议规定时间内(通常为 50μs ~ 50ms,取决于设备类型)返回 Completion,否则 RC 可能触发系统错误或驱动崩溃。
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 请求。