2. Linux DMA API 概览:核心接口与使用场景

在摄像头驱动中,DMA(Direct Memory Access)是连接图像传感器与系统内存的关键数据通路。Linux 内核提供了一套标准化的 DMA API,用于管理设备与内存之间的数据传输。本章将系统介绍三个最核心的 DMA 接口:dma_alloc_coherent、dma_map_single 和 dma_map_sg,并分析它们在摄像头驱动中的典型应用场景。

2.1 DMA API 设计哲学

Linux DMA API 的设计目标是在不同架构(ARM、x86、RISC-V 等)和不同总线(PCIe、USB、AMBA 等)之间提供统一的抽象层。其核心原则包括:

  • 缓存一致性管理:解决 CPU 缓存与设备 DMA 操作之间的数据一致性问题。
  • 地址转换:将设备可见的物理地址(或 IOMMU 映射地址)与内核虚拟地址进行映射。
  • 内存区域划分:区分可 DMA 内存与普通内存,确保分配的内存满足设备寻址能力。
关键概念:DMA 地址 ≠ 物理地址。在存在 IOMMU/SMMU 的系统中,DMA 地址是设备侧看到的地址,经过硬件转换后映射到物理内存。

2.2 核心 API 对比总览

API 函数 内存类型 缓存一致性 适用场景 典型摄像头用途
dma_alloc_coherent 一致性内存(Coherent) 硬件保证 控制结构、描述符环 ISP 配置参数、帧描述符
dma_map_single 流式内存(Streaming) 软件管理(sync) 单缓冲区传输 单帧图像数据、小尺寸控制包
dma_map_sg 流式内存(Scatter-Gather) 软件管理(sync) 多段分散缓冲区 多平面 YUV 帧、大尺寸图像分块
选择建议:对于摄像头驱动中频繁传输的图像数据(如 1080p@30fps 的 YUV 帧),优先使用 dma_map_sg 配合分散/聚集 DMA 引擎,避免大块连续物理内存的分配压力。

2.3 dma_alloc_coherent:一致性 DMA 内存

dma_alloc_coherent 分配一段在 CPU 和设备之间保持缓存一致性的内存区域。硬件保证 CPU 对这段内存的写操作立即可被 DMA 设备看到,反之亦然。

// 原型
void *dma_alloc_coherent(struct device *dev, size_t size,
                         dma_addr_t *dma_handle, gfp_t flag);

// 摄像头驱动中的典型用法:分配 ISP 配置描述符
struct isp_config_desc *desc;
dma_addr_t desc_dma;

desc = dma_alloc_coherent(isp_dev, sizeof(*desc),
                          &desc_dma, GFP_KERNEL);
if (!desc) {
    dev_err(isp_dev, "Failed to allocate coherent descriptor\n");
    return -ENOMEM;
}

// 填充描述符内容(CPU 写,设备立即可见)
desc->frame_width  = 1920;
desc->frame_height = 1080;
desc->format       = V4L2_PIX_FMT_NV12;
// 将 DMA 地址写入硬件寄存器
writel(desc_dma, isp->regs + ISP_DESC_ADDR);
注意事项:一致性内存分配开销较大,且可能占用稀缺的连续物理内存。在摄像头驱动中,仅用于控制路径(如描述符环、寄存器影子副本),不应直接用于图像数据缓冲区。

2.4 dma_map_single:流式单缓冲区映射

dma_map_single 将一段已分配的内存(通常来自 kmalloc 或 vmalloc)映射为 DMA 可访问的流式缓冲区。与一致性内存不同,流式映射需要显式调用 dma_sync_single_for_cpu / dma_sync_single_for_device 来维护缓存一致性。

// 原型
dma_addr_t dma_map_single(struct device *dev, void *cpu_addr,
                          size_t size, enum dma_data_direction dir);

// 摄像头驱动中的典型用法:映射单帧元数据缓冲区
struct frame_metadata *meta;
dma_addr_t meta_dma;

meta = kmalloc(sizeof(*meta), GFP_KERNEL);
if (!meta)
    return -ENOMEM;

meta_dma = dma_map_single(cam_dev, meta, sizeof(*meta),
                          DMA_BIDIRECTIONAL);
if (dma_mapping_error(cam_dev, meta_dma)) {
    kfree(meta);
    return -EIO;
}

// 启动 DMA 传输前同步
dma_sync_single_for_device(cam_dev, meta_dma,
                           sizeof(*meta), DMA_BIDIRECTIONAL);

// 传输完成后同步回 CPU
dma_sync_single_for_cpu(cam_dev, meta_dma,
                        sizeof(*meta), DMA_BIDIRECTIONAL);

2.5 dma_map_sg:分散/聚集映射

对于摄像头驱动中常见的多平面帧格式(如 NV12 的 Y 平面和 UV 平面分离存储),dma_map_sg 可以将多个不连续的物理内存段组织成一个分散/聚集列表,由 DMA 引擎一次性完成传输。

// 原型
int dma_map_sg(struct device *dev, struct scatterlist *sg,
               int nents, enum dma_data_direction dir);

// 摄像头驱动中的典型用法:映射 NV12 双平面帧
struct scatterlist sg[2];
struct frame_buffer *buf;
int nents;

// 初始化两个 scatterlist 条目:Y 平面和 UV 平面
sg_init_table(sg, 2);
sg_set_page(&sg[0], virt_to_page(buf->y_plane), buf->y_size, 0);
sg_set_page(&sg[1], virt_to_page(buf->uv_plane), buf->uv_size, 0);

nents = dma_map_sg(cam_dev, sg, 2, DMA_FROM_DEVICE);
if (nents <= 0) {
    dev_err(cam_dev, "Failed to map SG list\n");
    return -EIO;
}

// 遍历映射后的 SG 列表,将每个段的 DMA 地址写入硬件描述符
for_each_sg(sg, s, nents, i) {
    hw_desc[i].dma_addr = sg_dma_address(s);
    hw_desc[i].length   = sg_dma_len(s);
}

2.6 核心 API 关系与数据流

下面的 SVG 图展示了摄像头驱动中三种 DMA API 的典型数据流关系:

摄像头驱动 DMA API 数据流关系图 CPU 内存空间 kmalloc / vmalloc 页分配器 DMA 设备 摄像头 ISP DMA 引擎 dma_alloc_coherent dma_map_single dma_map_sg 数据流说明: 1. dma_alloc_coherent:直接从一致性内存池分配,CPU 和设备共享缓存一致性视图。 2. dma_map_single:将已有内存映射为流式 DMA 缓冲区,需手动同步缓存。 3. dma_map_sg:将多个分散内存段组织为 SG 列表,由硬件 DMA 引擎批量传输。 摄像头驱动中,dma_map_sg 常用于多平面帧(Y/U/V)的零拷贝传输。

2.7 缓存同步策略对比

API 类型 同步方式 CPU 写后 DMA 完成后 性能开销
dma_alloc_coherent 硬件自动 无需操作 无需操作 中等(分配时)
dma_map_single 显式 sync dma_sync_single_for_device dma_sync_single_for_cpu 低(每次 sync)
dma_map_sg 显式 sync dma_sync_sg_for_device dma_sync_sg_for_cpu 低(批量 sync)

2.8 摄像头驱动中的典型选择矩阵

根据摄像头驱动的不同功能模块,推荐使用的 DMA API 如下:

  • ISP 配置描述符:dma_alloc_coherent — 需要低延迟、高可靠性的控制路径。
  • 单帧元数据(如曝光统计):dma_map_single — 小尺寸、非频繁传输。
  • 多平面图像帧(YUV420、NV12):dma_map_sg — 大尺寸、高吞吐、分散物理内存。
  • RAW 原始帧(Bayer 格式):dma_map_sg — 连续大块内存,但可通过 SG 避免碎片。
  • 硬件描述符环(Ring Buffer):dma_alloc_coherent — 需要 CPU 和硬件同时访问的循环队列。
性能优化提示:在摄像头驱动中,dma_map_sg 配合 dmaengine_prep_slave_sg 可以实现零拷贝帧传输,避免 memcpy 带来的 CPU 开销。对于 4K@60fps 的摄像头数据流,这是保证帧率不丢帧的关键。

2.9 常见错误与调试

错误场景 现象 根因 解决方案
DMA 地址未对齐 硬件传输异常 分配的内存地址不满足设备对齐要求 使用 dma_set_coherent_mask 或 dma_set_mask 设置对齐
缓存一致性问题 图像数据花屏 流式映射后未调用 sync 在 DMA 前后添加 dma_sync_* 调用
SG 列表映射失败 dma_map_sg 返回 0 设备不支持分散/聚集或 IOMMU 映射失败 检查 dma_get_required_mask 和 IOMMU 配置
内存泄漏 驱动卸载后内存未释放 忘记调用 dma_free_coherent / dma_unmap_sg 在 remove 路径中确保对称释放

2.10 本章小结

Linux DMA API 为摄像头驱动提供了三种核心内存管理模型:

  • 一致性内存(dma_alloc_coherent)适用于控制路径,硬件保证缓存一致性。
  • 流式单缓冲区(dma_map_single)适用于小尺寸、低频次的数据传输。
  • 流式分散/聚集(dma_map_sg)适用于大尺寸、多平面的图像帧传输,是摄像头高性能驱动的首选。