3. PCIe DMA传输模型

PCIe DMA(Direct Memory Access)是高性能设备与系统内存之间批量数据交换的核心机制。理解DMA读/写操作流程、Descriptor Ring(描述符环)以及Scatter-Gather(SG)列表,是定位DMA传输异常与链路重训练问题的基础。

3.1 PCIe DMA读操作流程

DMA读操作是指设备主动从系统内存(Host Memory)读取数据到设备本地缓冲区。整个过程由设备端的DMA引擎发起,无需CPU逐字节干预。

步骤 发起方 操作描述 PCIe事务类型
1 设备DMA引擎 从Descriptor Ring获取读描述符,解析源地址、长度 本地总线读
2 设备DMA引擎 构造MRd(Memory Read Request)TLP,发送至RC MRd(32/64位地址)
3 RC(Root Complex) 地址转换、访问系统内存控制器,读取数据 系统总线事务
4 RC 构造CplD(Completion with Data)TLP,返回数据 CplD
5 设备DMA引擎 接收CplD,将数据写入本地FIFO/缓冲区 本地总线写
6 设备DMA引擎 更新描述符状态(完成位),写回Doorbell或中断通知驱动 MWr / INTx / MSI
调试要点:DMA读超时通常表现为CplD未返回。检查链路是否进入重训练、RC端地址映射是否正确、以及Max Payload Size是否匹配。
关键寄存器:DMA读操作涉及设备端的DMA控制寄存器、源地址寄存器(SAR)、目标地址寄存器(DAR)、传输长度寄存器(BYTE_COUNT)以及描述符环基址寄存器。
PCIe DMA读操作流程图 ① 设备DMA引擎 ② 构造MRd TLP ③ RC接收请求 ④ 内存控制器读 ⑤ 返回CplD TLP ⑥ 设备接收数据 ⑦ 更新描述符 ⚠ 常见异常点: • MRd TLP中地址越界 → RC返回UR/CA完成状态 • CplD超时 → 链路重训练或RC拥塞

3.2 PCIe DMA写操作流程

DMA写操作是指设备将本地缓冲区中的数据主动写入系统内存。与读操作不同,写操作不需要等待Completion,属于“Posted”事务,因此延迟更低,但也需要更谨慎的错误处理。

步骤 发起方 操作描述 PCIe事务类型
1 设备DMA引擎 从Descriptor Ring获取写描述符,解析目标地址、长度 本地总线读
2 设备DMA引擎 从本地FIFO/缓冲区读取数据 本地总线读
3 设备DMA引擎 构造MWr(Memory Write Request)TLP,携带数据和地址 MWr(Posted)
4 RC 接收MWr,将数据写入系统内存 系统总线事务
5 设备DMA引擎 更新描述符状态,写Doorbell或发送中断 MWr / MSI
重要:DMA写操作是Posted事务,不会返回Completion。这意味着设备无法直接知道数据是否成功写入内存。驱动必须依赖描述符环的状态更新或中断来确认传输完成。如果链路在MWr传输过程中发生重训练,数据可能丢失且无法自动重传。
PCIe DMA写操作流程图 ① 获取写描述符 ② 读取本地数据 ③ 构造MWr TLP ④ RC写入内存 ⑤ 更新描述符 ⑥ 触发中断 ⚠ 写操作异常场景: • 链路重训练期间MWr丢失 → 数据空洞,无Completion通知 • RC端地址不可写 → 设备侧无法感知,需靠超时机制检测 • 写缓冲区满(FC Credits耗尽) → MWr被流控,设备等待

3.3 Descriptor Ring机制

Descriptor Ring(描述符环)是驱动与设备之间传递DMA传输任务的环形队列。它解决了“驱动如何告诉设备要传输什么数据”以及“设备如何告诉驱动传输完成”这两个核心问题。

3.3.1 描述符环结构

描述符环由一组固定大小的描述符条目组成,每个条目包含以下关键字段:

字段 偏移 大小 描述
SRC_ADDR 0x00 8字节 源地址(读操作时为系统内存地址,写操作时为设备地址)
DST_ADDR 0x08 8字节 目标地址
BYTE_COUNT 0x10 4字节 传输字节数
CONTROL 0x14 2字节 控制标志(中断使能、链式标志、方向等)
STATUS 0x16 2字节 完成状态(设备写入:成功/失败/重试)
NEXT_PTR 0x18 8字节 下一个描述符指针(链式模式使用)
环管理关键寄存器:驱动通过写设备寄存器来通知设备新描述符可用。典型寄存器包括:Ring Base Address(环基址)、Ring Size(环大小)、Producer Index(生产者索引,驱动写)、Consumer Index(消费者索引,设备更新)。

3.3.2 描述符环工作流程

  1. 驱动准备:在系统内存中分配描述符环,将环基址写入设备寄存器。
  2. 驱动提交:填充描述符条目,更新Producer Index,写Doorbell寄存器通知设备。
  3. 设备消费:设备读取Producer Index,从环中取出描述符,执行DMA传输。
  4. 设备完成:设备更新描述符的STATUS字段,递增Consumer Index。
  5. 驱动回收:驱动轮询Consumer Index或等待中断,回收已完成描述符。
Descriptor Ring 环形队列结构 D0 D1 D2 D3 D4 D5 D6 D7 D8 D9 Producer Index (驱动写) Consumer Index (设备更新) 环空条件:Producer == Consumer;环满条件:(Producer + 1) % Size == Consumer

3.4 Scatter-Gather列表

Scatter-Gather(SG)列表是描述符环的扩展,用于解决物理内存不连续的问题。当驱动分配的DMA缓冲区在物理地址上不连续时,SG列表允许将一次逻辑传输拆分为多个物理上连续的片段。

3.4.1 SG列表结构

每个SG条目描述一个物理连续的内存块:

字段 大小 描述
Address 8字节 物理地址(必须页对齐或按设备要求对齐)
Length 4字节 该片段的字节长度
Flags 2字节 控制位(Last Segment标记、中断使能等)
Reserved 2字节 保留字段,通常置0

3.4.2 SG列表与描述符环的配合

在实际实现中,描述符环中的每个描述符可以指向一个SG列表(链式模式),也可以直接将SG条目嵌入描述符中(内联模式)。

  • 链式模式:描述符中的NEXT_PTR指向下一个SG条目链表,适合大块不连续内存。
  • 内联模式:描述符内包含固定数量的SG条目(通常2~4个),适合小粒度分散内存。
调试陷阱:SG列表中的地址必须是总线地址(经过IOMMU或SWIOTLB映射后的地址),而非CPU虚拟地址。驱动在构建SG列表时,必须使用dma_map_sg()等API进行地址转换。未正确映射的SG地址会导致DMA访问错误(UR/CA完成状态)。
Scatter-Gather列表与DMA传输映射 SG Entry 0: Addr=0x1000, Len=4KB SG Entry 1: Addr=0x3000, Len=2KB SG Entry 2: Addr=0x5000, Len=8KB SG Entry 3: Addr=0xA000, Len=1KB 物理页 0x1000 (4KB) 物理页 0x2000 (4KB) 0x3000 (2KB) 物理页 0x5000 (4KB) 物理页 0x6000 (4KB) 0xA000 🔍 SG列表调试要点: 1. 检查每个SG条目的地址是否在IOMMU映射范围内 2. 确认SG条目的Length不超过Max Payload Size × Max Read Request Size的限制 3. 最后一个SG条目必须设置Last Segment标志,否则设备会无限等待

3.5 DMA传输模型对比总结

特性 DMA读操作 DMA写操作
事务类型 Non-Posted(需要CplD) Posted(无Completion)
延迟 较高(等待CplD返回) 较低(无需等待)
错误检测 通过CplD状态(UR/CA/CRS) 需超时或描述符状态轮询
链路重训练影响 CplD超时,可重试 MWr可能丢失,需驱动层恢复
典型应用 从内存读取数据到设备(如网卡收包) 从设备写入数据到内存(如存储写盘)
实战建议:在调试DMA异常时,首先通过PCIe分析仪或内核tracepoint(如pciehp、dma_api)确认TLP是否正常发出。然后检查描述符环的Producer/Consumer Index是否一致。最后验证SG列表的地址映射是否正确。这三个步骤覆盖了90%的DMA传输问题。