1. MTK平台内存架构概览

各位同学,咱们今天聊聊MTK SoC的内存架构。说实话,搞驱动开发这么多年,我踩过最多的坑就是内存相关的。你想想看,一个系统跑着跑着突然卡死,或者DMA传着传着数据就错了,十有八九是内存访问没搞对。

我个人习惯,拿到一个新平台,第一件事就是先把内存层次结构摸清楚。为什么?因为不同的内存区域,访问速度能差好几个数量级。你写驱动的时候,数据放哪儿、怎么搬,直接决定了性能。

1.1 内存层次结构总览

MTK SoC的内存架构,说白了就是一套金字塔结构。从最顶层到最底层,速度越来越慢,容量越来越大。

层级 类型 典型容量 访问延迟 功耗
L1 Cache SRAM 32KB - 64KB 1-2 cycles 极高
L2 Cache SRAM 256KB - 1MB 10-20 cycles
内部SRAM SRAM 几百KB 20-50 cycles
DRAM DDR 几GB 100-300 cycles

嗯,这里要注意,Cache是硬件自动管理的,但SRAM和DRAM是我们驱动工程师需要直接打交道的。

1.2 L1/L2 Cache:速度的王者

Cache这玩意儿,说白了就是CPU的"贴身秘书"。它把最常用的数据放在离CPU最近的地方。我在项目中遇到过,有个同事写了个视频解码驱动,性能死活上不去。后来一查,发现关键数据全在DRAM里,每次访问都要等上百个时钟周期。

为什么会这样?因为Cache的命中率太低了。你想想看,CPU访问L1 Cache只要1-2个周期,访问DRAM要200个周期,差了100倍!

关键点:Cache是透明的,但驱动开发者必须了解它的行为。比如DMA传输时,如果CPU和DMA同时访问同一块内存,Cache一致性就是个大问题。

我曾经在一个音频驱动项目里,DMA把数据写到了DRAM,但CPU读的时候发现数据不对。查了两天才发现,CPU读的是Cache里的旧数据,根本没去DRAM拿新的。这就是典型的Cache一致性问题。

1.3 内部SRAM:小而美的"快存"

内部SRAM,我习惯叫它"片内快存"。它不像Cache那样自动管理,而是由我们手动分配使用。容量不大,一般几百KB,但速度比DRAM快得多。

我个人建议,把那些对延迟极度敏感的数据放在SRAM里。比如中断处理函数里要用的变量、实时性要求高的音频缓冲区。

实战技巧:在MTK平台上,内部SRAM通常有专门的地址区间。比如MT6789平台上,SRAM基地址是0x00100000,大小256KB。你可以用ioremap把它映射到内核虚拟地址空间。

// 内部SRAM映射示例
#define SRAM_BASE 0x00100000
#define SRAM_SIZE (256 * 1024)

void *sram_virt = ioremap(SRAM_BASE, SRAM_SIZE);
if (!sram_virt) {
    pr_err("SRAM映射失败!\n");
    return -ENOMEM;
}

// 在SRAM中分配关键数据
struct critical_data *data = (struct critical_data *)sram_virt;
data->flag = 0x55AA;  // 初始化标志

1.4 DRAM:大容量的"仓库"

DRAM就是咱们常说的DDR内存。容量大,几GB起步,但访问延迟也大。你想想看,从DRAM读一个数据,要经过内存控制器、总线、Cache,最后才到CPU,不慢才怪。

不过,DRAM也有它的优势——便宜、容量大。大部分数据,比如视频帧、图片、应用代码,都放在DRAM里。

避坑指南:我曾经在调试一个4K视频播放驱动时,发现帧率始终上不去。后来用性能分析工具一看,DDR带宽被占满了。原来有个模块在不停地做DMA传输,把DDR总线堵死了。解决办法是把一些不紧急的数据放到SRAM里,或者用DMA的优先级控制。

1.5 不同内存区域的访问延迟对比

咱们用个具体的例子来说明。假设CPU要读取一个32位整数:

  • L1 Cache命中:约1-2ns,几乎不耗电
  • L2 Cache命中:约5-10ns,功耗适中
  • 内部SRAM:约10-20ns,功耗略高
  • DRAM:约50-100ns,功耗最低

你看,从L1到DRAM,延迟差了50倍以上。所以写驱动的时候,一定要想清楚:这个数据该放哪儿?

1.6 功耗特性:性能与功耗的博弈

搞嵌入式的人都知道,功耗是个大问题。Cache虽然快,但功耗也高。SRAM次之,DRAM最省电。

我记得有个项目是做智能手表,电池只有300mAh。为了省电,我们把大部分数据都放在DRAM里,只有最关键的几个变量放在SRAM里。结果续航从8小时提升到了12小时。

但代价是什么?系统响应变慢了。用户按一下按钮,要等几百毫秒才有反应。这就是典型的性能与功耗的博弈。

我的建议:在MTK平台上,你可以通过mtk_mem_profile这个工具来查看当前内存的使用情况和功耗分布。我一般会在驱动初始化时,根据当前的工作模式(高性能/低功耗)动态调整数据存放的位置。

1.7 实战中的内存选择策略

说了这么多理论,咱们来点实际的。在MTK平台上写驱动,怎么选内存?

  1. 中断处理函数:数据放SRAM,因为中断里不能等
  2. 音频/视频流:数据放DRAM,用DMA传输,CPU只管配置
  3. 关键状态变量:放SRAM,保证原子性和低延迟
  4. 大块数据缓冲区:放DRAM,用Cacheable映射,利用Cache加速

嗯,这里要注意,Cacheable映射虽然快,但会引入一致性问题。如果你用DMA往DRAM写数据,CPU读的时候一定要做Cache刷新操作。

// DMA传输后的Cache刷新
dma_sync_single_for_cpu(dev, dma_addr, size, DMA_FROM_DEVICE);
// 或者更粗暴的方式
__flush_dcache_area(virt_addr, size);

我个人习惯,在DMA传输完成后,总是显式地做一次Cache刷新。虽然会损失一点性能,但能避免很多诡异的bug。

1.8 本章小结

好了,咱们把MTK的内存架构捋了一遍。记住几个关键点:

  • Cache最快但不可控,SRAM又快又可控但容量小,DRAM慢但容量大
  • 写驱动时,根据数据的实时性要求和访问频率来选择存放位置
  • DMA传输时,一定要注意Cache一致性问题
  • 功耗和性能是矛盾的,要根据应用场景做取舍

下一章,咱们会深入讲解DMA传输的实战技巧。到时候我会拿一个真实的摄像头驱动案例,手把手教你怎么配置DMA、怎么处理Cache一致性。敬请期待!