SMMU架构概览:内部模块划分、数据通路与控制通路
好,咱们正式开始聊SMMU的内部结构。说实话,很多搞系统软件的同学,看了好几遍SMMU的spec,还是搞不清它到底是怎么工作的。我当年也一样,总觉得这东西就是个黑盒子——把虚拟地址扔进去,物理地址出来,中间发生了什么?不清楚。
这一节,我就带你把这个黑盒子拆开。咱们不聊那些虚的,直接看它里面到底有哪些模块,数据是怎么流的,控制信号又是怎么走的。
SMMU内部模块划分
一个典型的SMMU,内部可以分成这么几个核心模块。我习惯把它们分成三组:前端处理、核心转换、后端接口。
| 模块分组 | 模块名称 | 核心职责 |
|---|---|---|
| 前端处理 | TBU(Translation Buffer Unit) | 接收设备发起的地址转换请求,做第一级缓存查找 |
| 前端处理 | TCU(Translation Control Unit) | 全局控制逻辑,管理配置、异常、同步 |
| 核心转换 | TLB(Translation Lookaside Buffer) | 缓存最近用过的页表转换结果,分多级 |
| 核心转换 | PTW(Page Table Walker) | 硬件页表遍历引擎,TLB miss时自动查内存 |
| 后端接口 | CD(Configuration Database) | 存放Stream Table、Context Descriptor等配置数据 |
| 后端接口 | Fault/Event Handler | 处理转换异常、记录事件日志 |
嗯,这里要注意:TBU和TCU的划分,在ARM的SMMU架构里是一个关键设计点。TBU通常靠近设备端,TCU靠近内存端。多TBU可以挂在一个TCU下,形成1:N的关系。我在一个项目中就遇到过,一个TCU挂了4个TBU,结果TBU之间的缓存一致性没处理好,踩了个大坑。
数据通路:地址转换的完整路径
数据通路,说白了就是地址转换请求从哪进、从哪出。我画了一张图,你看一眼就明白了。
你看这个流程,其实很直观:
- 设备发起请求,带上设备ID(Stream ID)和虚拟地址(VA)。
- TBU先查自己的TLB。如果命中,直接返回物理地址(PA)。
- 如果TLB miss,PTW开始干活——它根据页表基地址,一级一级往下查。
- 查到结果后,写回TLB,同时把PA返回给设备。
个人经验:我在调试一个PCIe网卡驱动时,发现DMA老是报错。最后定位到是TBU的TLB没命中,PTW又因为页表配置错误一直死循环。你想想看,硬件遍历页表时如果遇到非法项,它不会像软件那样优雅地返回错误,而是直接触发一个同步异常。这个异常如果没被正确处理,系统就hang了。
控制通路:配置、同步与异常处理
数据通路是“干活”的,控制通路是“管人”的。控制通路主要处理三件事:配置初始化、TLB无效化、异常上报。
1. 配置初始化
系统启动时,软件需要写SMMU的寄存器,告诉它:
- Stream Table放在内存的哪个位置
- 每个Stream ID对应哪个Context Descriptor
- 页表级别(2级还是3级?)
- 缓存策略(是否要bypass?)
这些配置都通过MMIO寄存器写入。我记得有一次,我在配置SMMU时忘了设置SMMU_CR0的ENABLE位,结果设备发起的请求全部被SMMU拦截了,系统直接panic。嗯,这种低级错误,犯过一次就再也不会忘了。
2. TLB无效化
操作系统在切换进程、修改页表时,必须通知SMMU:你缓存的那些转换结果,可能已经过期了。这个过程叫TLB invalidate。
控制通路里有一个专门的命令队列(Command Queue),软件往里面写命令,SMMU硬件异步执行。常见的命令有:
TLBI_EL2:无效化所有TLB项TLBI_VAA:按虚拟地址无效化TLBI_ASID:按ASID无效化
避坑指南:我曾经在一个多核系统上,多个CPU同时往命令队列里写TLB无效化命令,结果队列满了,后面的命令被丢弃。设备继续使用过期的TLB项,读到了错误的数据。这个bug查了我整整两天。解决方案是:在写命令队列前,先检查队列是否满,或者使用同步方式(SMMU_S_INV_ALL)。
3. 异常上报
当SMMU遇到以下情况时,会触发异常:
- 页表项权限不匹配(比如写操作但页表是只读)
- 地址越界
- Stream ID未配置
异常信息会被记录在事件队列(Event Queue)中。软件需要定期轮询或通过中断处理这些事件。我建议你在调试阶段,把事件队列的中断打开,这样一旦有异常,系统能第一时间告诉你。
数据通路与控制通路的交互
这两个通路不是完全独立的。举个例子:
- 当PTW在遍历页表时,如果发现页表项被标记为“无效”,它会通知控制通路,控制通路再往事件队列里写一条记录。
- 当软件执行TLB无效化时,控制通路会暂停TBU的请求处理,等所有正在进行的转换完成后,再执行无效化操作。
这种交互,说白了就是握手协议。我在项目中见过一个设计缺陷:控制通路在发无效化命令时,没有等数据通路的所有pending请求完成,结果导致部分请求用了旧的TLB项。这个问题的根因,就是两个通路的同步逻辑没做好。
核心要点:
- 数据通路追求低延迟,TLB命中时一个周期就能出结果
- 控制通路追求正确性,宁可慢一点,也不能出错
- 两个通路通过状态寄存器和中断进行通信
小结
这一节我们把SMMU的内部模块和数据流捋了一遍。你记住几个关键点:
- TBU负责接收请求,TLB负责缓存,PTW负责查内存
- 控制通路管配置、无效化和异常
- 两个通路之间需要做好同步,否则就会出各种诡异的问题