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)以及描述符环基址寄存器。
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传输过程中发生重训练,数据可能丢失且无法自动重传。
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 描述符环工作流程
- 驱动准备:在系统内存中分配描述符环,将环基址写入设备寄存器。
- 驱动提交:填充描述符条目,更新Producer Index,写Doorbell寄存器通知设备。
- 设备消费:设备读取Producer Index,从环中取出描述符,执行DMA传输。
- 设备完成:设备更新描述符的STATUS字段,递增Consumer Index。
- 驱动回收:驱动轮询Consumer Index或等待中断,回收已完成描述符。
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完成状态)。
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传输问题。