2. SMMU核心架构:TBU、TCU与地址翻译流程
好,咱们直接进入正题。SMMU 内部到底长什么样?很多人看了几百页的 ARM 架构手册,还是搞不清 TBU 和 TCU 到底怎么分工。我刚开始接触 SMMU 时也犯过这毛病——以为它就是个黑盒子,IO 设备发个地址进去,出来就是物理地址。其实内部拆开看,结构相当清晰。
2.1 SMMU 的两大核心模块:TBU 与 TCU
ARM 把 SMMU 拆成了两个逻辑部分:TBU(Translation Buffer Unit) 和 TCU(Translation Control Unit)。说白了,一个管数据缓存,一个管控制逻辑。
| 模块 | 全称 | 核心职责 | 硬件位置 |
|---|---|---|---|
| TBU | Translation Buffer Unit | TLB 缓存、页表 walk 请求发起 | 靠近 IO 设备端(分散部署) |
| TCU | Translation Control Unit | 页表基址管理、配置寄存器、一致性协议 | 集中式控制单元 |
我在一个项目中遇到过这样的场景:芯片里挂了 8 个 PCIe EP 设备,每个设备都配了一个 TBU。结果调试时发现某个 TBU 的 TLB 老是 miss,性能上不去。查了半天,原来是 TCU 里配的页表基址寄存器写错了——TBU 本身没错,是控制层给了错误指令。所以,理解这两者的分工,对定位问题至关重要。
2.2 TBU 微架构:TLB 与页表 Walk 引擎
TBU 内部其实是个小型的“翻译引擎”。它主要包含三部分:
- TLB 阵列:缓存最近用过的页表项。我习惯把它理解成“快表”,命中就直接出结果。
- 页表 Walk 引擎:TLB miss 时,自动发起多级页表查询。这个引擎会按页表层级一级一级往下读内存。
- 请求队列:缓存等待翻译的 IO 请求。当 TLB miss 且 walk 引擎忙时,请求先排队。
关键点:TBU 不存储页表本身,它只负责“查”和“缓存”。页表数据存在系统内存里,TCU 告诉 TBU 页表基址在哪。
你想想看,如果每个 TBU 都自己维护一份完整的页表,那内存开销得多大?所以 ARM 的设计思路是:TBU 只管跑腿,TCU 管方向。
2.3 TCU 微架构:配置与一致性管家
TCU 是 SMMU 的“大脑”。它负责:
- Stream Table 管理:每个 IO 设备(Stream)对应一个配置条目,里面存着页表基址、转换粒度、权限等信息。
- 全局配置寄存器:比如 SMMU_CR0、SMMU_CR1,控制整个 SMMU 的使能、安全模式、异常处理策略。
- TLB 一致性协议:当软件修改了页表,TCU 负责向所有 TBU 广播 TLB 无效化命令。
个人经验:调试 TLB 一致性问题时,我建议先检查 TCU 的 SMMU_TLBIVAL 寄存器是否写对了。曾经有个项目,软件工程师只 invalidate 了本地的 TBU,忘了广播给其他 TBU,结果数据错乱查了两天。
2.4 地址翻译流程概览
好,现在我们把 TBU 和 TCU 串起来,看看一次完整的地址翻译是怎么走的。我画了一张流程图,方便你对照理解。
流程其实不复杂,我拆成几步讲:
- IO 设备发起 DMA:设备发一个 IOVA(IO Virtual Address)给 TBU。
- TBU 查 TLB:如果命中,直接返回物理地址,走快速路径。
- TLB Miss 触发 Walk:没命中?TBU 的 walk 引擎开始工作。它先问 TCU 要这个 Stream 的页表基址和配置。
- 多级页表查询:Walk 引擎按页表层级(比如 4 级页表)逐级读取内存。每一级都从内存中取出下一级的基址。
- 更新 TLB 并返回:找到最终物理地址后,写入 TLB 缓存,同时把结果返回给 IO 设备。
注意:页表 Walk 是串行的。如果页表层级深(比如 4 级),一次 miss 就要读 4 次内存。我在调试一个 NVMe 控制器时,发现随机读性能差,就是因为 TLB 命中率太低,每次都要走 4 级 walk。后来通过调整页表大小(用 2MB 大页),性能直接翻倍。
2.5 TBU 与 TCU 的交互协议
TBU 和 TCU 之间怎么通信?ARM 定义了一套内部接口协议,我简单列一下关键信号:
| 信号方向 | 信号名 | 含义 |
|---|---|---|
| TBU → TCU | REQ_STREAMID | 请求查询某个 Stream 的配置 |
| TCU → TBU | CFG_TTBR | 返回页表基址(Translation Table Base Register) |
| TCU → TBU | CFG_TCR | 返回页表控制寄存器(粒度、层级等) |
| TCU → TBU | TLBI_CMD | 广播 TLB 无效化命令 |
嗯,这里要注意:TCU 返回的配置是缓存在 TBU 内部的。也就是说,TBU 不会每次翻译都去问 TCU——它会把 Stream 配置缓存下来。只有 Stream 配置被修改时,TCU 才会发无效化命令。
2.6 微架构设计的权衡
实际芯片设计时,TBU 和 TCU 的部署方式有很多变种。我见过三种常见方案:
- 全集中式:一个 TCU 带一个 TBU,所有设备共享。适合设备少、性能要求不高的场景。
- 全分布式:每个设备配独立的 TBU,TCU 集中管理配置。适合高性能场景,但芯片面积大。
- 混合式:几个设备共享一个 TBU,TCU 做全局协调。这是大多数 SoC 的选择。
我的建议:调试时先搞清楚你的芯片用的是哪种拓扑。我曾经在混合式架构上吃过亏——两个设备共享一个 TBU,结果一个设备改了页表,另一个设备的 TLB 没被 invalidate,数据全乱了。后来在 TCU 侧加了 Stream 隔离才解决。
2.7 小结
这一章我们把 SMMU 的核心架构拆开了。TBU 管缓存和 walk,TCU 管配置和一致性。地址翻译的路径其实很直观:TLB 命中走快道,miss 走慢道。理解了这个流程,后面讲页表管理和 TLB 结构时,你就能知道每个环节的瓶颈在哪。