陷阱一:MMU与Cache一致性陷阱

驱动中直接操作物理地址,结果Cache和DDR数据不一致,系统直接死机。

这问题我见过太多次了。说实话,刚入行那会儿我自己也踩过这个坑。今天咱们就把这个「经典陷阱」彻底聊透。

问题是怎么发生的?

海思芯片默认开启了MMU和Cache。CPU读写内存时,其实先跟Cache打交道。Cache里存了一份数据副本,DDR里是另一份。

正常情况下,硬件会自动同步。但驱动里一旦直接操作物理地址——比如用映射一段内存,或者DMA直接搬运数据——就容易出乱子。

举个例子:

// 错误示范:直接写物理地址
unsigned long *p = (unsigned long *)0x92000000;
*p = 0x12345678;  // 数据进了Cache,没进DDR

// DMA外设去读0x92000000
// 结果:读到的是旧数据,系统崩溃

为什么会这样?因为CPU写的是Cache,DMA读的是DDR。两边数据不一致,系统不崩才怪。

⚠️ 核心原因:CPU通过Cache访问内存,外设(DMA、GPU等)直接访问物理地址。两者路径不同,数据不同步。

我在项目中遇到的真实案例

之前做一款安防摄像头,海思Hi3516方案。图像采集模块用DMA把数据搬到内存,CPU再做算法处理。

一开始跑demo没问题,一上压力测试就花屏、死机。查了三天,最后发现是Cache没刷。

DMA写完了数据,CPU从Cache里读到的还是旧数据。你想想看,算法处理的是垃圾数据,结果能对吗?

解决方案:三种常用方法

嗯,这里要注意,不同场景用不同方法。我总结了三招:

场景 方法 适用性
驱动中临时访问物理地址 使用 简单直接,适合少量寄存器操作
DMA与CPU共享内存 使用DMA一致性API(dma_alloc_coherent 推荐,自动处理Cache同步
已有内存区域需要同步 手动刷Cache(flush_dcache_range等) 灵活,但容易遗漏

方法一:使用

我个人习惯,如果是映射一段寄存器空间,直接用。它告诉MMU:这段地址不走Cache,或者走写合并(Write Combine)模式。

void __iomem *regs = ioremap_wc(0x92000000, 0x1000);
writel(0x12345678, regs + 0x00);  // 直接写物理内存
// 外设读取时,数据已经在DDR里了

注意:ioremap默认是ioremap_nocache,但海思某些平台ioremap可能带Cache属性。我建议显式指定ioremap_wcioremap_nocache,别偷懒。

方法二:DMA一致性API

这是最稳妥的做法。用dma_alloc_coherent分配的内存,驱动框架会自动处理Cache同步。

dma_addr_t dma_handle;
void *cpu_addr = dma_alloc_coherent(dev, size, &dma_handle, GFP_KERNEL);

// CPU和外设都可以直接访问
// 不需要手动刷Cache
// 性能可能略低,但安全第一

我在项目中用过这个方案。虽然比普通内存分配慢一点,但省心啊。你想想看,系统死机和性能损失,哪个更严重?

💡 小技巧:如果对性能要求高,可以用dma_alloc_noncoherent配合手动刷Cache。但一定要在正确的位置刷,别漏了。

方法三:手动刷Cache

有时候你手里已经有一段内存了,没法用一致性API。那就手动刷Cache。

// 在DMA启动前,刷CPU写入的数据到DDR
flush_dcache_range(start, end);

// 在DMA完成后,让CPU从DDR重新读取
invalidate_dcache_range(start, end);

我曾经在这个地方栽过跟头。刷Cache的顺序搞反了,数据反而被覆盖了。记住一个口诀:

  • CPU写 → 外设读:先刷Cache(flush)
  • 外设写 → CPU读:先无效化Cache(invalidate)

说白了,就是保证谁先访问,谁拿到的是最新数据。

避坑指南

我曾经见过一个同事,在中断处理函数里直接操作物理地址,没做任何Cache处理。结果系统跑几分钟就挂一次。

排查时发现,中断处理里用了readl读寄存器。但readl本身会绕过Cache,所以寄存器操作没问题。问题出在他用同样的方式读DMA缓冲区——这就踩坑了。

所以我的建议是:

  • 明确区分「寄存器空间」和「内存空间」
  • 寄存器用readl/writel系列,没问题
  • 内存缓冲区一定要用一致性API或手动刷Cache
  • 别混用,别想当然

海思平台的特殊性

海思芯片的MMU实现和ARM公版略有差异。我遇到过一个问题:ioremap映射后的地址,在某些内核版本下默认是Write-Back模式。这意味着你写寄存器时,数据可能先留在Cache里。

嗯,这很坑。寄存器操作要求立即生效,Write-Back会导致外设读到旧值。

解决办法:用ioremap_nocache,或者映射后调用set_memory_uc设置属性。

📌 关键总结:
  1. MMU+Cache是海思驱动的标配,别想着关掉它
  2. 物理地址操作必须考虑Cache一致性
  3. 优先用DMA一致性API,省心省力
  4. 手动刷Cache时,注意flush和invalidate的顺序
  5. 海思平台ioremap默认属性可能不同,显式指定更安全

好了,这个陷阱就聊到这儿。下次遇到驱动死机,先检查Cache一致性——八成是这个问题。