4. 缓存管理基础

在摄像头驱动中,DMA 负责把图像数据从传感器接口搬运到系统内存,而 CPU 往往通过 Cache 访问同一块缓冲区。只要 Cache 与内存之间出现“副本不一致”,驱动就会遇到花屏、旧帧、数据错位甚至随机崩溃。理解 Cache 一致性、写回/写直达策略、内存屏障以及 DMA 与 Cache 的交互,是设计稳定摄像头数据通路的前提。

一、核心原理:为什么会出现 Cache 一致性问题

现代 SoC 中,CPU 访问内存通常经过多级 Cache,而 DMA 控制器一般直接访问物理内存(或经过 IOMMU/SMMU 映射后的内存),并不感知 CPU Cache 的内容。于是同一块图像缓冲区可能同时存在三个“视图”:

  • CPU Cache 中的副本:CPU 读写时看到的最新数据。
  • 物理内存中的主副本:DMA 实际读写的目标。
  • 设备侧 FIFO/寄存器:传感器或 CSI 控制器内部的临时数据。

当 CPU 写入了新配置或新帧数据但尚未写回内存时,DMA 读到的仍是旧内存;当 DMA 把新图像写入内存后,CPU Cache 中可能仍保留旧行,CPU 读到的就是过期数据。这就是 DMA 与 Cache 交互问题的根源。

关键结论:DMA 与 CPU 共享缓冲区时,必须显式处理 Cache 的“写回”和“无效化”,否则一致性无法自动保证。

二、写回与写直达策略

Cache 的写策略决定 CPU 写数据时如何同步到内存:

  • 写直达(Write-Through):CPU 每次写 Cache 的同时也写入内存。实现简单、一致性强,但写内存带宽压力大,摄像头高帧率场景下容易成为瓶颈。
  • 写回(Write-Back):CPU 只写 Cache,并标记为脏行,直到该行被替换或显式清理时才写回内存。带宽利用率高,但 DMA 读取前必须确保脏行已写回。

多数应用处理器对普通内存采用写回策略,因此驱动在启动 DMA 前通常需要执行 clean(写回脏行)或 flush(写回并无效化)。

策略 写内存时机 一致性风险 典型适用
写直达 每次写 Cache 同步写内存 较低,但带宽开销大 小缓冲、控制寄存器
写回 脏行替换或显式清理时 DMA 前需 clean/flush 大帧缓冲、图像数据

三、内存屏障:顺序比速度更重要

即使 Cache 操作正确,CPU 和编译器仍可能重排访存指令。例如驱动先写 DMA 描述符,再写寄存器启动 DMA;若启动寄存器的写被提前,DMA 可能读到未完成的描述符。内存屏障用于约束访存顺序:

  • 编译屏障:barrier(),阻止编译器重排。
  • 写屏障:wmb(),保证之前的写先于之后的写完成。
  • 读屏障:rmb(),保证之前的读先于之后的读完成。
  • 全屏障:mb(),读写均有序。
  • DMA 专用屏障:dma_wmb()、dma_rmb(),用于设备与 CPU 之间的共享内存。
/* 典型顺序:先填描述符,再启动 DMA */
desc->addr = dma_addr;
desc->len  = frame_size;
dma_wmb();                 /* 确保描述符先于寄存器写可见 */
writel(desc_phys, DMA_DESC_REG);
writel(START_BIT, DMA_CTRL_REG);

四、DMA 与 Cache 的交互:四类典型问题

摄像头驱动中,DMA 与 Cache 的交互可归纳为四种方向,处理方式各不相同:

  1. CPU 写、DMA 读:如配置描述符、填充元数据。DMA 启动前需 dma_sync_single_for_device() 或 clean 操作,把脏行写回内存。
  2. DMA 写、CPU 读:如采集完成的图像帧。CPU 读取前需 dma_sync_single_for_cpu() 或 invalidate,丢弃旧 Cache 行。
  3. 双向共享:如环形缓冲区的读写指针。需在每次交接点执行同步,并配合屏障。
  4. 非一致性架构:部分 SoC 的 DMA 不参与 Cache 一致性,必须由驱动显式维护;一致性架构(如带 CCI 的 ARM)可减少部分操作,但仍需正确使用 DMA API。
CPU 读写图像缓冲 DMA 控制器 搬运 CSI 数据 CPU Cache 脏行 / 有效行 物理内存 帧缓冲 / 描述符 CSI / Sensor 图像数据源 读/写 clean / flush DMA 读写 数据流 invalidate DMA 前 clean 写回,DMA 后 invalidate 丢弃旧行,屏障保证顺序

五、案例场景:一帧图像从采集到显示的缓存路径

以 MIPI CSI 摄像头采集一帧 1080p 图像为例,典型流程如下:

1. 驱动分配 DMA 一致性缓冲区或普通缓冲区
2. CPU 填充 DMA 描述符(地址、长度、控制位)
3. dma_wmb() 保证描述符先于启动寄存器可见
4. 启动 DMA,CSI 数据写入物理内存
5. DMA 完成中断触发
6. dma_sync_single_for_cpu() 使 CPU 侧 Cache 无效化
7. CPU 读取图像数据,进行 ISP 处理或送显
8. 若 CPU 修改了缓冲区,再次 dma_sync_single_for_device()

若第 6 步缺失,CPU 可能读到 Cache 中的旧帧;若第 3 步缺失,DMA 可能读到未完成的描述符,导致地址错乱。两者都是摄像头驱动调试中常见的问题。

使用 dma_alloc_coherent() 分配的一致性内存可避免大部分 Cache 维护操作,但会牺牲部分性能;对高带宽图像帧,通常采用流式 DMA 映射加显式同步。

六、误区与复盘

  • 误区一:认为 DMA 会自动与 Cache 保持一致。只有硬件一致性架构且正确配置时才成立,普通 DMA 必须由驱动维护。
  • 误区二:只做 invalidate 不做 clean。CPU 写过的脏数据未写回,DMA 读到旧值,配置类数据尤其明显。
  • 误区三:忽略屏障。描述符与启动寄存器之间没有顺序保证,偶发失败难以复现。
  • 误区四:同步范围过大。对整个大缓冲反复 clean/invalidate 会显著增加 CPU 开销,应按帧或按描述符粒度同步。
  • 误区五:在中断上下文中执行耗时同步。大范围 Cache 操作可能引起延迟抖动,影响高帧率采集。
调试建议:若出现“第一帧正常、后续花屏”或“偶发地址异常”,优先检查 DMA 前后的 clean/invalidate 与内存屏障是否成对出现。

总结来看,摄像头驱动的缓存管理可以归纳为一条主线:明确数据流向,判断 CPU 与 DMA 谁写谁读,再选择 clean、invalidate 或两者兼有,并用内存屏障锁定顺序。写回与写直达策略决定了同步成本,而 DMA API 与屏障则是把“可能出错”变成“确定正确”的关键工具。