陷阱3:DMA缓冲区对齐——Cache Line引发的血案

做海思驱动开发,DMA传输是个绕不开的坎。我见过太多工程师,明明DMA配置得妥妥当当,数据就是不对。查来查去,最后发现是缓冲区没对齐Cache Line。

说白了,这就是个「你以为你传了,其实没传对」的问题。今天咱们就把它彻底讲透。

问题到底出在哪?

海思芯片用的是ARM架构,CPU和DMA之间有个Cache。Cache Line通常是64字节。什么意思呢?CPU读写内存,不是按字节来的,而是按64字节一块儿读的。

你想想看,如果DMA缓冲区起始地址不是64的倍数,会怎样?

举个例子。你申请了一个缓冲区,地址是0x10000004。DMA往这个地址写了数据。CPU去读的时候,发现Cache里没有,就去内存加载。但加载的是0x10000000到0x1000003F这一整块。

问题来了——DMA只写了0x10000004开始的几个字节,但CPU加载的是整块Cache Line。这块数据里,有一部分是DMA写的新数据,有一部分是内存里的旧数据。CPU拿到的是新旧混杂的数据,你说能不乱吗?

⚠️ 核心矛盾:DMA直接操作内存,CPU通过Cache操作内存。两者步调不一致,数据就打架。

我在项目中踩过的坑

记得有一次做海思3559的摄像头采集驱动。图像数据通过DMA从ISP传到内存,再交给CPU做算法处理。图像总是出现条纹,偶尔还有花屏。

我排查了整整两天。时钟、同步信号、DMA配置……全查了一遍。最后用printk打印缓冲区地址,发现每次分配的地址都不一样,而且大部分都不是64字节对齐的。

嗯,问题就在这儿。malloc分配的地址,没法保证对齐。海思的DMA控制器可不管你这些,它只管往你给的地址写数据。结果就是Cache Line错位,数据一塌糊涂。

怎么解决?两种方案

解决思路其实就两条:要么让缓冲区对齐,要么让Cache和DMA同步。

方案一:强制对齐分配

我个人习惯用这个方案,简单粗暴,效果直接。

/* 方法1:使用posix_memalign */
void *buf;
int ret = posix_memalign(&buf, 64, size);  // 64字节对齐
if (ret != 0) {
    printk("DMA buffer alignment failed\n");
    return -ENOMEM;
}

/* 方法2:使用dma_alloc_coherent(海思推荐) */
dma_addr_t dma_handle;
void *cpu_addr = dma_alloc_coherent(dev, size, &dma_handle, GFP_KERNEL);
if (!cpu_addr) {
    printk("DMA coherent allocation failed\n");
    return -ENOMEM;
}

注意看第二个方法。dma_alloc_coherent是海思SDK里最常用的。它分配的内存既保证Cache Line对齐,又自动处理了Cache一致性。说白了,你只管用,底层的事它帮你搞定。

💡 我的建议:在海思平台上,优先用dma_alloc_coherent。它比posix_memalign多做了Cache一致性处理,省心很多。

方案二:手动Cache操作

有时候你没法用dma_alloc_coherent,比如缓冲区是上层传下来的。那就得手动刷Cache。

/* DMA传输前:把CPU可能修改过的数据刷回内存 */
dma_map_single(dev, buf, size, DMA_FROM_DEVICE);

/* DMA传输后:让CPU重新从内存加载数据 */
dma_unmap_single(dev, dma_handle, size, DMA_FROM_DEVICE);

这里有个坑,我提醒一下。很多人只记得传输后unmap,忘了传输前map。结果就是CPU写了一半的数据,DMA直接拿去用了,数据还是旧的。

⚠️ 常见错误:只做unmap不做map。DMA传输前一定要先map,把Cache里的脏数据刷出去。

性能问题也不容忽视

除了数据错乱,对齐问题还会导致性能骤降。为什么?

你想想看,如果缓冲区没对齐,DMA传输时可能触发多次Cache Line的加载和回写。本来一次能搞定的事,现在要分好几次。吞吐量直接掉一半。

我在海思3516上做过测试:

缓冲区对齐情况 DMA传输速率 CPU占用率
64字节对齐 800 MB/s 12%
32字节对齐 520 MB/s 28%
未对齐(随机地址) 310 MB/s 45%

看到没?没对齐的情况下,性能直接腰斩。CPU还得多干活,去处理Cache一致性问题。说白了就是两头亏。

避坑指南

我曾经吃过不少亏,总结了几条经验:

  • 分配时就对齐——别指望后面再补救。dma_alloc_coherent一步到位。
  • 检查地址——调试时加个打印,看看缓冲区地址是不是64的倍数。不是的话,赶紧改。
  • 别混用——同一个缓冲区,别一会儿用DMA操作,一会儿用CPU操作。非要混用的话,记得map/unmap。
  • 注意海思平台差异——不同海思芯片的Cache Line大小可能不同。3559是64字节,3516也是64字节,但有些老芯片可能是32字节。查手册确认。
📌 核心要点:DMA缓冲区对齐不是可选项,是必选项。不对齐,轻则数据错乱,重则系统崩溃。在海思平台上,养成用dma_alloc_coherent的习惯,能省掉90%的DMA相关bug。

好了,这一章就讲到这里。DMA对齐问题说难不难,说简单也不简单。关键是要理解Cache和DMA的工作机制,知道它们为什么打架,怎么劝架。下一章咱们聊聊中断处理中的竞态问题,那个坑更深,到时候再细说。