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 的典型数据流关系:
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)适用于大尺寸、多平面的图像帧传输,是摄像头高性能驱动的首选。