2. PCIe体系结构基础

要真正理解 PCIe DMA 传输与数据搬移,必须先建立 PCIe 的体系结构视图。DMA 并不是“设备直接读写内存”这么简单的一句话,它依赖 PCIe 的拓扑寻址、分层协议封装、事务层读写请求、数据链路层的可靠传输、物理层的链路训练,以及配置空间和枚举流程所建立的地址与资源映射。只有把这些层次串起来,才能解释 DMA 描述符为什么能被设备正确读取、数据为什么能按预期写入主机内存。

一、PCIe 拓扑:从 Root Complex 到端点设备

PCIe 采用树状拓扑。最上层是 Root Complex(RC),它连接 CPU 与内存子系统,并产生配置空间访问和内存访问。RC 下方可以连接 Switch 或直接连接 Endpoint(EP)。Switch 用于扩展端口,内部包含多个虚拟 PCI-PCI 桥,实现下游设备的路由。Endpoint 是实际功能设备,例如 NVMe 控制器、网卡、FPGA 加速卡。还有一种特殊设备叫 PCIe-to-PCI 桥,用于兼容旧总线。

拓扑中的每个设备都有唯一的 BDF(Bus、Device、Function)编号。DMA 访问时,设备发出的内存读写请求会携带目标地址,由 RC 或 Switch 根据地址路由到主机内存。理解拓扑的意义在于:DMA 的地址空间、设备可见范围和路由路径都由拓扑与配置决定,而不是设备随意访问。

PCIe 拓扑与分层协议 Root Complex (RC) CPU / 内存 / 配置空间发起者 PCIe Switch 虚拟 PCI-PCI 桥 / 路由 Endpoint (NVMe) BDF 01:00.0 Endpoint (FPGA) BDF 02:00.0 Endpoint (NIC) BDF 03:00.0 PCIe 分层协议(发送方向) 事务层 TLP 数据链路层 DLLP 物理层 PLP 链路 / 差分对 读写请求 / 完成 ACK/NAK / 流控 编码 / 串并转换 Lane 0~N

二、分层协议:事务层、数据链路层、物理层

PCIe 协议分为三层,发送时逐层封装,接收时逐层解封装。事务层负责生成 TLP(Transaction Layer Packet),包括 Memory Read、Memory Write、Configuration Read/Write、Completion 等类型。DMA 的核心就是设备发起 Memory Read/Write TLP,或者主机向设备发起读写。事务层还负责 QoS、流量类别和部分错误报告。

数据链路层负责 TLP 的可靠传输。它为 TLP 添加序列号和 LCRC,接收端校验后返回 ACK/NAK。如果出错则重传。数据链路层还产生 DLLP,用于流控信用和电源管理。物理层负责将数据包转换为差分信号,完成链路训练、通道对齐、加扰和解扰。物理层决定链路宽度和速率,例如 x4、x8、Gen3、Gen4。

关键理解:DMA 性能不仅取决于设备本身,还受链路宽度、速率、TLP 大小、流控信用和重传影响。一个 Memory Write TLP 如果频繁被 NAK 重传,实际带宽会明显下降。

三、配置空间与枚举流程

每个 PCIe 功能都有配置空间,标准配置空间为 256 字节,扩展配置空间为 4KB。前 64 字节包含 Vendor ID、Device ID、Command、Status、BAR 等。BAR(Base Address Register)用于声明设备需要的地址空间类型和大小。系统枚举时,RC 通过配置读写扫描总线,读取设备 ID,分配总线号、内存空间和 I/O 空间,并写入 BAR 和 Command 寄存器。

枚举流程大致如下:首先扫描 Bus 0,发现桥或设备;为下游总线分配 Bus Number;读取 BAR 并分配地址;使能 Memory Space 和 Bus Master Enable。只有 Bus Master Enable 置位后,设备才能发起 DMA 访问。很多 DMA 不工作的原因,正是这个位没有使能,或者 BAR 地址与系统内存冲突。

# 查看 PCIe 设备与 BAR 分配(Linux)
lspci -tv
lspci -vv -s 01:00.0 | grep -E "BAR|BusMaster|Control"

# 关键寄存器位
Command Register:
  bit 1  Memory Space Enable
  bit 2  Bus Master Enable
  bit 6  Parity Error Response

# 枚举后典型输出
01:00.0 Non-Volatile memory controller: ...
        Region 0: Memory at 0xfe000000 (64-bit, non-prefetchable)
        Control: I/O- Mem+ BusMaster+ ...

四、案例与误区复盘

假设一块 FPGA 加速卡通过 PCIe x8 Gen3 接入,主机枚举后分配 BAR0 为 0xfe000000,大小为 1MB。驱动映射 BAR0 后配置 DMA 描述符基地址,并置位 Bus Master Enable。设备随后发起 Memory Read 读取描述符,再发起 Memory Write 将数据写入主机内存。如果枚举阶段 BAR 未正确分配,驱动读到的 BAR 为 0,DMA 描述符地址就会错误,设备可能读到全 0 或触发 UR(Unsupported Request)。

层次 核心对象 DMA 相关作用 常见问题
拓扑 RC / Switch / EP 决定路由与地址可见性 BDF 冲突、链路未训练
事务层 TLP 发起 Memory Read/Write UR、CA、完成超时
数据链路层 DLLP / ACK / NAK 保证 TLP 可靠传输 重传、流控信用不足
物理层 Lane / 差分信号 决定带宽与误码率 链路降速、误码
配置空间 BAR / Command 地址分配与 Bus Master 使能 BAR 未分配、Bus Master 未开

常见误区:只关注 DMA 寄存器配置,忽略枚举与 BAR 分配;或者认为链路训练是自动完成的,不检查链路状态。实际调试中,应先确认链路已训练到预期宽度和速率,再确认 BAR 和 Bus Master Enable,最后才检查 DMA 描述符与数据一致性。

总结来说,PCIe DMA 是建立在拓扑、分层协议、配置空间和枚举流程之上的系统工程。事务层负责“发什么请求”,数据链路层负责“可靠送达”,物理层负责“信号传输”,配置空间和枚举负责“地址与权限”。把这四者串起来,后续的 DMA 描述符设计、地址映射和性能调优才有稳固基础。