2. DMA 基础概念:物理地址与虚拟地址、DMA 寻址能力、流式 DMA 与一致性 DMA 的区别
在 V4L2 驱动中,DMA(Direct Memory Access)是摄像头数据从硬件传输到系统内存的核心机制。理解 DMA 的基础概念,是正确分配缓冲区、保证缓存一致性的前提。本节将深入剖析物理地址与虚拟地址的映射关系、DMA 寻址能力限制,以及两种 DMA 传输模型的本质区别。
核心认知: 驱动运行在虚拟地址空间,而 DMA 硬件直接操作物理地址。两者之间的“翻译”工作由 MMU 和 IOMMU/SMMU 完成,但缓存一致性问题需要驱动开发者显式处理。
2.1 物理地址与虚拟地址:DMA 的“语言”差异
CPU 通过 MMU(Memory Management Unit)访问虚拟地址,而 DMA 控制器直接访问物理地址。这种差异是驱动开发中许多问题的根源。
- 虚拟地址(VA): 进程和内核使用的地址空间,由 MMU 映射。驱动中
kmalloc()、vmalloc()返回的都是虚拟地址。 - 物理地址(PA): 硬件总线(如 AXI、PCIe)上实际存在的地址。DMA 引擎、摄像头控制器只能识别物理地址。
- 总线地址(BA): 在某些架构(如带有 IOMMU 的 ARM64)中,外设看到的地址可能与 CPU 物理地址不同,称为总线地址或 DMA 地址。
V4L2 实践提示: 在调用
dma_alloc_coherent() 或 dma_map_single() 时,驱动需要同时获取虚拟地址(供 CPU 访问缓冲区内容)和 DMA 地址(传递给硬件寄存器)。struct vb2_buffer 中的 planes[] 成员就包含了这两种地址。
2.2 DMA 寻址能力:设备能“看到”多少内存?
并非所有 DMA 设备都能访问整个物理地址空间。DMA 寻址能力由硬件设计决定,通常受限于地址线宽度或总线协议。
| 寻址能力 | 地址范围 | 典型设备 | 驱动处理方式 |
|---|---|---|---|
| 32-bit DMA | 0 ~ 4 GB | 旧式 USB 控制器、低端摄像头 | 使用 DMA_BIT_MASK(32),限制缓冲区分配在低 4GB |
| 64-bit DMA | 0 ~ 16 EB | 现代 PCIe 设备、高端图像传感器 | 使用 DMA_BIT_MASK(64),可分配任意物理地址 |
| 30-bit DMA | 0 ~ 1 GB | 某些嵌入式 SoC 内置外设 | 使用 DMA_BIT_MASK(30),需要 ZONE_DMA 内存 |
| IOMMU/SMMU 映射 | 由 IOMMU 定义 | ARM64 平台外设 | 设备看到 IOVA,驱动使用 dma_map_* 接口 |
常见错误: 如果设备 DMA 寻址能力为 32 位,但驱动分配了高于 4GB 的物理缓冲区,DMA 传输将静默失败或损坏数据。务必在 probe 中调用
dma_set_mask_and_coherent() 声明设备能力。
// 正确设置 DMA 掩码的示例
static int my_camera_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
int ret;
// 先尝试 64 位,失败则降级到 32 位
ret = dma_set_mask_and_coherent(dev, DMA_BIT_MASK(64));
if (ret) {
ret = dma_set_mask_and_coherent(dev, DMA_BIT_MASK(32));
if (ret) {
dev_err(dev, "DMA mask setup failed\n");
return ret;
}
dev_info(dev, "Using 32-bit DMA addressing\n");
} else {
dev_info(dev, "Using 64-bit DMA addressing\n");
}
// ... 后续初始化
}
2.3 流式 DMA 与一致性 DMA:两种缓存管理模型
DMA 传输涉及 CPU 缓存(Cache)与物理内存之间的数据一致性。Linux 内核提供了两种 DMA 映射模型,分别适用于不同的使用场景。
2.3.1 一致性 DMA(Coherent DMA)
一致性 DMA 保证 CPU 和 DMA 设备在任何时刻看到的缓冲区内容是一致的,无需驱动手动刷新缓存。
- 分配方式:
dma_alloc_coherent() - 特点: 分配的内存是 Cache 一致性的(通常通过禁用该区域的 Cache 或使用硬件一致性协议实现)。
- 适用场景: 控制结构、描述符环、状态标志等频繁由 CPU 和设备共同访问的小型缓冲区。
- 代价: 分配和释放开销较大,且可能占用稀缺的一致内存区域。
2.3.2 流式 DMA(Streaming DMA)
流式 DMA 允许驱动在传输前后通过显式的同步操作来管理缓存一致性,适用于大数据块传输。
- 映射方式:
dma_map_single()/dma_map_sg() - 特点: 使用常规内存,传输前调用
dma_sync_single_for_device(),传输后调用dma_sync_single_for_cpu()。 - 适用场景: 视频帧数据、音频流、网络数据包等大块数据。
- 优势: 内存分配灵活,可复用系统内存池,适合高频大数据量传输。
2.4 两种 DMA 模型的对比与选择
| 对比维度 | 一致性 DMA | 流式 DMA |
|---|---|---|
| 分配函数 | dma_alloc_coherent() |
dma_map_single() / dma_map_sg() |
| 缓存同步 | 自动(硬件保证) | 手动(驱动调用 sync 函数) |
| 内存类型 | 特殊一致内存(可能非 Cache 或硬件一致) | 常规内核内存(可 Cache) |
| 分配开销 | 较高(分配/释放慢) | 较低(仅映射/取消映射) |
| 适合大小 | 小缓冲区(< 1 页) | 大缓冲区(视频帧、网络包) |
| V4L2 典型用途 | 描述符环、状态寄存器 | 视频帧缓冲区(vb2 队列) |
| 错误风险 | 低(一致性由内核保证) | 高(忘记 sync 导致数据损坏) |
V4L2 驱动最佳实践: 在
vb2_ops 的 buf_prepare 回调中,使用流式 DMA 映射视频缓冲区;在 start_streaming 中分配一致性 DMA 用于描述符环。这样既保证了大块数据的传输效率,又确保了控制路径的可靠性。
2.5 缓存一致性问题根源:CPU Cache 与 DMA 的冲突
现代 CPU 使用多级 Cache 来加速内存访问。当 DMA 设备直接写入物理内存时,如果 CPU Cache 中仍然保存着该地址的旧数据,就会产生不一致。
- 场景一(DMA 写入): 设备将视频帧写入物理地址 A,但 CPU Cache 中地址 A 的数据尚未失效。CPU 读取时将得到旧数据。
- 场景二(DMA 读取): CPU 修改了缓冲区内容(写入 Cache),但修改尚未写回物理内存。DMA 设备读取时将得到旧数据。
硬件加速方案: 某些 SoC 提供硬件 Cache 一致性协议(如 ARM 的 ACE/CHI 总线),允许 DMA 设备直接窥探 CPU Cache。这种情况下驱动可以使用一致性 DMA 而无需软件同步,但需要设备树中正确配置
dma-coherent 属性。
// 设备树中声明 DMA 一致性支持的示例
camera: camera@1a00000 {
compatible = "vendor,camera";
reg = <0x1a00000 0x1000>;
interrupts = <0 45 4>;
dma-coherent; // 声明硬件支持 DMA 一致性
};