陷阱一:MMU与Cache一致性陷阱
驱动中直接操作物理地址,结果Cache和DDR数据不一致,系统直接死机。
这问题我见过太多次了。说实话,刚入行那会儿我自己也踩过这个坑。今天咱们就把这个「经典陷阱」彻底聊透。
问题是怎么发生的?
海思芯片默认开启了MMU和Cache。CPU读写内存时,其实先跟Cache打交道。Cache里存了一份数据副本,DDR里是另一份。
正常情况下,硬件会自动同步。但驱动里一旦直接操作物理地址——比如用
举个例子:
// 错误示范:直接写物理地址
unsigned long *p = (unsigned long *)0x92000000;
*p = 0x12345678; // 数据进了Cache,没进DDR
// DMA外设去读0x92000000
// 结果:读到的是旧数据,系统崩溃
为什么会这样?因为CPU写的是Cache,DMA读的是DDR。两边数据不一致,系统不崩才怪。
我在项目中遇到的真实案例
之前做一款安防摄像头,海思Hi3516方案。图像采集模块用DMA把数据搬到内存,CPU再做算法处理。
一开始跑demo没问题,一上压力测试就花屏、死机。查了三天,最后发现是Cache没刷。
DMA写完了数据,CPU从Cache里读到的还是旧数据。你想想看,算法处理的是垃圾数据,结果能对吗?
解决方案:三种常用方法
嗯,这里要注意,不同场景用不同方法。我总结了三招:
| 场景 | 方法 | 适用性 |
|---|---|---|
| 驱动中临时访问物理地址 | 使用 |
简单直接,适合少量寄存器操作 |
| DMA与CPU共享内存 | 使用DMA一致性API(dma_alloc_coherent) | 推荐,自动处理Cache同步 |
| 已有内存区域需要同步 | 手动刷Cache(flush_dcache_range等) | 灵活,但容易遗漏 |
方法一:使用
我个人习惯,如果是映射一段寄存器空间,直接用
void __iomem *regs = ioremap_wc(0x92000000, 0x1000);
writel(0x12345678, regs + 0x00); // 直接写物理内存
// 外设读取时,数据已经在DDR里了
注意:ioremap默认是ioremap_nocache,但海思某些平台ioremap可能带Cache属性。我建议显式指定ioremap_wc或ioremap_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
// 性能可能略低,但安全第一
我在项目中用过这个方案。虽然比普通内存分配慢一点,但省心啊。你想想看,系统死机和性能损失,哪个更严重?
方法三:手动刷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设置属性。
- MMU+Cache是海思驱动的标配,别想着关掉它
- 物理地址操作必须考虑Cache一致性
- 优先用DMA一致性API,省心省力
- 手动刷Cache时,注意flush和invalidate的顺序
- 海思平台ioremap默认属性可能不同,显式指定更安全
好了,这个陷阱就聊到这儿。下次遇到驱动死机,先检查Cache一致性——八成是这个问题。