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[] 成员就包含了这两种地址。
物理地址与虚拟地址映射关系 CPU 虚拟地址空间 0xFFFF8000_00000000 MMU 页表转换 物理内存 0x00000000_9FC00000 DMA 设备 直接访问物理地址 虚拟地址 物理地址 DMA 读写 ⚠ 关键差异 • 驱动代码使用虚拟地址访问缓冲区 • 硬件寄存器配置使用物理/DMA 地址

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()。
  • 适用场景: 视频帧数据、音频流、网络数据包等大块数据。
  • 优势: 内存分配灵活,可复用系统内存池,适合高频大数据量传输。
流式 DMA vs 一致性 DMA 工作流程 一致性 DMA dma_alloc_coherent 分配一致内存 CPU 直接访问 无需同步 DMA 传输 硬件自动一致 释放 dma_free_coherent 流式 DMA 分配常规内存 dma_map_single sync_for_device DMA 传输 sync_for_cpu dma_unmap_single

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 一致性
};