4. DMA地址映射原理:IOMMU/SMMU的作用、流式映射与一致性映射的区别
DMA(Direct Memory Access)地址映射是PCIe驱动开发中最容易出错的环节之一。设备使用总线地址(Bus Address)访问内存,而CPU使用虚拟地址(VA)或物理地址(PA)。在没有IOMMU/SMMU的情况下,设备直接访问物理内存,存在安全性和兼容性问题。本章深入剖析DMA地址映射的核心机制。
4.1 IOMMU/SMMU:DMA重映射的硬件基础
IOMMU(I/O Memory Management Unit)在x86平台称为VT-d,在ARM平台称为SMMU(System Memory Management Unit)。它的核心作用是将设备发起的DMA请求中的总线地址转换为系统物理地址,并提供访问权限控制。
关键概念: 没有IOMMU时,设备可以访问任意物理内存地址,存在安全漏洞;启用IOMMU后,设备只能访问驱动显式映射的DMA区域。
4.2 流式映射(Streaming DMA Mapping)
流式映射是最常用的DMA映射方式,适用于数据在设备和内存之间单向或短暂传输的场景。驱动在每次DMA操作前建立映射,操作完成后解除映射。
4.2.1 核心API与使用流程
// 流式映射:从设备读取数据(设备 -> 内存)
dma_addr_t dma_map_single(struct device *dev, void *cpu_addr,
size_t size, enum dma_data_direction dir);
// 流式解映射
void dma_unmap_single(struct device *dev, dma_addr_t dma_handle,
size_t size, enum dma_data_direction dir);
// 典型使用模式
void *buffer = kmalloc(BUF_SIZE, GFP_KERNEL);
dma_addr_t dma_handle = dma_map_single(dev, buffer, BUF_SIZE, DMA_FROM_DEVICE);
if (dma_mapping_error(dev, dma_handle)) {
// 映射失败处理
}
// 启动DMA操作...
dma_unmap_single(dev, dma_handle, BUF_SIZE, DMA_FROM_DEVICE);
kfree(buffer);
4.2.2 流式映射的关键特性
| 特性 | 说明 |
|---|---|
| Cache一致性 | 驱动需要手动处理Cache一致性(通过dma_map_*和dma_unmap_*隐式完成) |
| 映射生命周期 | 短生命周期,每次DMA操作前后建立/销毁 |
| 内存所有权 | 映射期间设备拥有内存访问权,CPU不能访问(或需谨慎访问) |
| 适用场景 | 网络数据包、块设备I/O、一次性数据传输 |
| 性能开销 | 每次映射/解映射涉及TLB刷新和Cache维护,频繁操作有开销 |
最佳实践: 流式映射完成后,必须调用 dma_unmap_* 释放映射。遗漏解映射会导致IOMMU页表泄漏,最终耗尽DMA地址空间。
4.3 一致性映射(Consistent DMA Mapping / Coherent Mapping)
一致性映射也称为连贯映射,适用于设备和CPU频繁共享同一块内存的场景(如描述符环、控制寄存器)。映射建立后,CPU和设备看到的是一致的内存视图,无需手动维护Cache一致性。
4.3.1 核心API与使用流程
// 一致性映射分配与映射
dma_addr_t dma_alloc_coherent(struct device *dev, size_t size,
dma_addr_t *dma_handle, gfp_t flag);
// 释放一致性映射
void dma_free_coherent(struct device *dev, size_t size,
void *cpu_addr, dma_addr_t dma_handle);
// 典型使用模式
dma_addr_t dma_handle;
void *desc_ring = dma_alloc_coherent(dev, RING_SIZE,
&dma_handle, GFP_KERNEL);
if (!desc_ring) {
// 分配失败处理
}
// 初始化描述符环...
// 设备可以直接访问 dma_handle 对应的内存
// CPU 可以直接读写 desc_ring 指针
// 无需手动调用 dma_map/unmap
dma_free_coherent(dev, RING_SIZE, desc_ring, dma_handle);
4.3.2 一致性映射的关键特性
| 特性 | 说明 |
|---|---|
| Cache一致性 | 硬件保证一致性(通常通过非Cacheable映射或硬件Cache Coherent协议) |
| 映射生命周期 | 长生命周期,从分配开始到驱动卸载结束 |
| 内存所有权 | CPU和设备可以同时访问,无需同步 |
| 适用场景 | 描述符环(Descriptor Ring)、DMA控制块、状态寄存器 |
| 性能开销 | 分配时一次性开销较大,但运行时无Cache维护开销 |
4.4 流式映射 vs 一致性映射:对比总结
| 对比维度 | 流式映射 (Streaming) | 一致性映射 (Coherent) |
|---|---|---|
| Cache一致性维护 | 由驱动在map/unmap时隐式处理 | 硬件自动保证 |
| 映射建立开销 | 低(每次操作建立/销毁) | 高(分配时建立,长期保持) |
| 运行时开销 | 每次map/unmap有TLB/Cache刷新 | 无额外运行时开销 |
| 内存使用效率 | 高(按需映射,释放后回收) | 低(长期占用DMA地址空间) |
| 典型用途 | 数据缓冲区(Data Buffer) | 控制结构(Descriptor Ring) |
| 错误处理 | 需检查dma_mapping_error() | 需检查分配返回值是否为NULL |
4.5 常见故障与排查要点
故障高发区:
- DMA地址未检查: dma_map_single 返回后未调用 dma_mapping_error() 检查,导致使用无效地址。
- 方向错误: DMA_FROM_DEVICE 和 DMA_TO_DEVICE 混淆,导致数据损坏或Cache不一致。
- 解映射遗漏: 流式映射未解映射导致IOMMU页表泄漏,最终DMA分配失败。
- 一致性内存访问越界: dma_alloc_coherent 分配的内存大小与实际使用不匹配。
- IOMMU未启用: 某些平台需要显式启用IOMMU,否则dma_map_*返回物理地址。
4.6 调试手段:查看DMA映射状态
# 查看IOMMU设备映射(x86 VT-d)
# cat /sys/kernel/debug/iommu/intel/dmar_translation
# 查看设备DMA地址空间使用情况
# cat /sys/kernel/debug/dma-api/dump
# 使用 ftrace 跟踪 DMA API 调用
# echo dma:dma_map_single > /sys/kernel/debug/tracing/set_event
# echo dma:dma_unmap_single >> /sys/kernel/debug/tracing/set_event
# cat /sys/kernel/debug/tracing/trace_pipe
调试建议: 在驱动开发阶段,可以启用内核的DMA API调试功能(CONFIG_DMA_API_DEBUG=y),它会自动检测常见的DMA映射错误,如双重解映射、方向错误等。