2. DMA基础原理
DMA(Direct Memory Access,直接存储器访问)是PCIe设备实现高性能数据传输的核心机制。它允许外设绕过CPU,直接与系统内存交换数据,从而大幅降低CPU负载并提升吞吐量。理解DMA控制器的工作流程以及总线主控/从控模式,是调试PCIe驱动的基础。
2.1 DMA控制器核心工作流程
一个典型的DMA传输事务由以下阶段构成,涉及CPU、DMA控制器、PCIe总线与系统内存的协同操作:
- 驱动初始化:CPU分配DMA缓冲区(物理连续或通过IOMMU映射),并将缓冲区物理地址/长度写入DMA控制器的描述符寄存器。
- 启动传输:CPU写DMA控制器的“启动”位或门铃寄存器,触发DMA引擎开始工作。
- 总线主控读/写:DMA控制器作为PCIe总线主控(Bus Master),发起Memory Read/Write TLP(事务层包)直接访问系统内存。
- 传输完成:DMA控制器完成指定长度的数据传输后,更新状态寄存器,并可选地触发MSI-X中断通知CPU。
- 驱动轮询/中断处理:CPU通过中断服务例程或轮询状态位,确认传输完成并回收/处理数据。
关键点: 在PCIe语境下,DMA控制器本质上是“智能外设”,它代替CPU执行内存访问,但必须遵循PCIe总线协议中的主控/从控角色划分。
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描述符的典型路径:
2.4 主控模式下的DMA传输路径
当DMA控制器启动后,它作为总线主控直接访问系统内存,无需CPU介入。下图展示了DMA写内存(Device-to-Memory)的典型路径:
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偏移错误、字节序问题 |