2. DMA基础原理

DMA(Direct Memory Access,直接存储器访问)是PCIe设备实现高性能数据传输的核心机制。它允许外设绕过CPU,直接与系统内存交换数据,从而大幅降低CPU负载并提升吞吐量。理解DMA控制器的工作流程以及总线主控/从控模式,是调试PCIe驱动的基础。

2.1 DMA控制器核心工作流程

一个典型的DMA传输事务由以下阶段构成,涉及CPU、DMA控制器、PCIe总线与系统内存的协同操作:

  1. 驱动初始化:CPU分配DMA缓冲区(物理连续或通过IOMMU映射),并将缓冲区物理地址/长度写入DMA控制器的描述符寄存器。
  2. 启动传输:CPU写DMA控制器的“启动”位或门铃寄存器,触发DMA引擎开始工作。
  3. 总线主控读/写:DMA控制器作为PCIe总线主控(Bus Master),发起Memory Read/Write TLP(事务层包)直接访问系统内存。
  4. 传输完成:DMA控制器完成指定长度的数据传输后,更新状态寄存器,并可选地触发MSI-X中断通知CPU。
  5. 驱动轮询/中断处理:CPU通过中断服务例程或轮询状态位,确认传输完成并回收/处理数据。
关键点: 在PCIe语境下,DMA控制器本质上是“智能外设”,它代替CPU执行内存访问,但必须遵循PCIe总线协议中的主控/从控角色划分。
DMA控制器工作流程 CPU 初始化 写描述符/门铃 DMA 引擎启动 总线主控读/写内存 更新状态/发MSI-X CPU 中断处理 驱动回收数据 流程说明: ① CPU通过MMIO写描述符地址/长度 → ② 写门铃启动DMA → ③ DMA引擎作为总线主控直接访问内存 ④ 传输完成后更新状态寄存器并触发MSI-X中断 → ⑤ CPU在中断上下文中处理完成事件

2.2 总线主控模式 vs 从控模式

在PCIe总线体系中,每个设备(Endpoint)可以扮演两种角色:总线主控(Bus Master)和从控(Target/Slave)。这两种模式决定了设备能否主动发起内存访问。

对比维度 总线主控模式 (Bus Master) 从控模式 (Target/Slave)
角色定义 设备主动发起PCIe Memory Read/Write TLP 设备被动响应CPU发起的配置/MMIO访问
典型用途 DMA传输、数据批量搬运 寄存器访问、状态查询、控制命令下发
地址空间 访问系统物理内存(需DMA地址映射) 访问设备自身的BAR空间(MMIO)
性能特征 高带宽、低延迟、不占用CPU 受限于CPU发起的事务数量
驱动配置 必须设置PCIe命令寄存器的“Bus Master Enable”位 默认使能,无需额外配置
调试关注点 DMA地址映射正确性、TLP格式、完成超时 BAR空间偏移、寄存器位域定义
常见陷阱: 驱动加载后若未设置 pci_set_master(),设备将无法发起DMA传输,导致数据一直卡在设备侧。调试时务必检查PCIe配置空间中的Bus Master Enable位是否置1。

2.3 从控模式下的MMIO访问路径

当CPU需要配置DMA控制器时,通过从控模式访问设备的BAR空间。下图展示了CPU写DMA描述符的典型路径:

从控模式MMIO访问路径 CPU Root Complex PCIe Switch Endpoint (DMA控制器) 从控模式路径: CPU → Root Complex → PCIe Switch → Endpoint(BAR空间)。CPU使用 writel() 写入描述符地址/长度,设备被动接收。

2.4 主控模式下的DMA传输路径

当DMA控制器启动后,它作为总线主控直接访问系统内存,无需CPU介入。下图展示了DMA写内存(Device-to-Memory)的典型路径:

主控模式DMA传输路径 Endpoint (DMA) PCIe Switch Root Complex 系统内存 DMA写请求 Memory Write TLP 地址翻译/路由 数据写入 主控模式关键特征: ① 设备主动发起TLP,目标地址是系统物理内存(非BAR空间) ② 驱动必须通过 pci_map_single() 或 dma_map_single() 获取设备可见的DMA地址

2.5 DMA地址映射与一致性

在PCIe系统中,设备看到的地址空间与CPU看到的物理地址空间可能不同(尤其存在IOMMU/SMMU时)。驱动必须使用DMA API进行地址转换:

/* 典型DMA映射流程 */
struct device *dev = &pdev->dev;
dma_addr_t dma_handle;
void *cpu_addr;

/* 1. 分配DMA缓冲区(一致性或流式) */
cpu_addr = dma_alloc_coherent(dev, size, &dma_handle, GFP_KERNEL);

/* 2. 将DMA地址写入设备描述符寄存器 */
writel(lower_32_bits(dma_handle), bar + DMA_DESC_ADDR_LO);
writel(upper_32_bits(dma_handle), bar + DMA_DESC_ADDR_HI);

/* 3. 启动DMA传输 */
writel(1, bar + DMA_START);

/* 4. 传输完成后释放 */
dma_free_coherent(dev, size, cpu_addr, dma_handle);
调试建议: 当DMA传输异常时,首先检查 dma_handle 是否在设备地址范围内(如32位设备只能访问4GB以下内存)。使用 lspci -vvv 查看设备的 Address 能力,确认DMA地址映射无误。

2.6 主控/从控模式切换的驱动实现

在Linux内核中,使能总线主控模式通过以下API完成:

/* 使能总线主控(必须调用) */
pci_set_master(pdev);

/* 禁用总线主控 */
pci_clear_master(pdev);

/* 检查当前状态 */
if (pdev->is_busmaster)
    dev_info(&pdev->dev, "Bus master enabled\n");

从控模式无需显式使能,但驱动必须正确映射BAR空间:

/* 映射BAR0为MMIO区域 */
bar0 = pci_ioremap_bar(pdev, 0);
if (!bar0) {
    dev_err(&pdev->dev, "Failed to map BAR0\n");
    return -ENOMEM;
}

/* 通过MMIO访问设备寄存器(从控模式) */
u32 val = ioread32(bar0 + DMA_STATUS);
iowrite32(desc_addr, bar0 + DMA_DESC_ADDR);

2.7 主控与从控模式对比总结

特性 主控模式 (DMA传输) 从控模式 (MMIO访问)
发起方 PCIe设备 (Endpoint) CPU (Root Complex)
目标地址 系统物理内存 设备BAR空间
TLP类型 Memory Read/Write Memory Read/Write (由CPU发起)
驱动配置 pci_set_master() + DMA映射 pci_ioremap_bar()
性能 高吞吐,CPU零拷贝 低吞吐,CPU参与每次访问
调试难点 地址映射错误、TLP完成超时 BAR偏移错误、字节序问题