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之间的缓存一致性没处理好,踩了个大坑。

数据通路:地址转换的完整路径

数据通路,说白了就是地址转换请求从哪进、从哪出。我画了一张图,你看一眼就明白了。

SMMU数据通路与模块关系图 设备发起请求 TBU TLB PTW 内存/页表 返回物理地址 数据通路流程说明: 1. 设备发起地址转换请求 → TBU接收 2. TBU查询TLB:命中则直接返回物理地址 3. TLB未命中 → PTW启动硬件页表遍历 → 从内存读取页表 4. 转换结果写入TLB,同时返回给设备

你看这个流程,其实很直观:

  1. 设备发起请求,带上设备ID(Stream ID)和虚拟地址(VA)。
  2. TBU先查自己的TLB。如果命中,直接返回物理地址(PA)。
  3. 如果TLB miss,PTW开始干活——它根据页表基地址,一级一级往下查。
  4. 查到结果后,写回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负责查内存
  • 控制通路管配置、无效化和异常
  • 两个通路之间需要做好同步,否则就会出各种诡异的问题