2、SMMU固件架构:ATF、UEFI、OPTEE中的SMMU驱动框架,固件与内核的交互模型
这一章我们来聊聊固件侧的SMMU。很多人觉得SMMU是内核管的事,跟固件没关系。其实不然。我做过几个项目,都是因为固件侧SMMU配置没搞对,导致内核一启动就卡死,连log都看不到。说白了,固件才是SMMU的"第一责任人"。
为什么这么说?因为系统上电后,内核还没跑起来,SMMU就得先初始化好。谁来做?ATF、UEFI、OPTEE这些固件。它们负责把SMMU的全局配置、StreamID映射、安全态隔离这些基础工作做完,然后才把接力棒交给内核。
2.1 三大固件中的SMMU驱动框架
先看一张整体架构图,帮你建立全局认知。
2.2 ATF中的SMMU驱动
ATF运行在EL3,是系统里权限最高的固件。它负责SMMU的早期初始化。我习惯把ATF的SMMU工作分成三个阶段:
- 冷启动初始化:配置SMMU全局寄存器,比如SMMU_CR0、SMMU_CR1、SMMU_CR2。设置StreamID的位数、队列基地址等。
- 安全态隔离:把安全世界的StreamID和非安全世界的StreamID分开。防止非安全世界通过DMA访问安全内存。
- 运行时服务:通过SMC接口响应内核的SMMU配置请求。
ATF中SMMU相关的关键代码路径:
/* ATF SMMU驱动核心接口 */
void smmu_init(uintptr_t base_addr)
{
/* 1. 复位SMMU */
mmio_write_32(base_addr + SMMU_CR0, 0);
/* 2. 配置StreamID映射表基地址 */
mmio_write_64(base_addr + SMMU_STRTAB_BASE, strtab_pa);
mmio_write_32(base_addr + SMMU_STRTAB_BASE_CFG, cfg);
/* 3. 使能SMMU */
mmio_setbits_32(base_addr + SMMU_CR0, SMMU_CR0_SMMUEN);
/* 4. 等待使能完成 */
while (!(mmio_read_32(base_addr + SMMU_CR0) & SMMU_CR0_SMMUEN));
}
2.3 UEFI中的SMMU配置
UEFI的角色跟ATF不一样。它不直接操作SMMU寄存器(除非做DMA保护),而是负责生成描述SMMU拓扑的ACPI表。最关键的就是IORT表。
IORT表里描述了:
- SMMU硬件实例的数量和基地址
- 每个SMMU支持的StreamID范围
- 设备到SMMU的映射关系
- 中断信息(比如EVENTQ、PRIQ、CMDQSync中断)
内核启动时,通过IORT表知道SMMU长什么样、怎么用。如果IORT表写错了,内核要么找不到SMMU,要么配错中断号。嗯,我遇到过IORT表里中断号写反了,结果SMMU报错内核收不到中断,DMA事务全挂。
| IORT表节点类型 | 描述 | 常见配置错误 |
|---|---|---|
| ITS Group节点 | 描述GIC ITS信息 | ITS ID与硬件不匹配 |
| SMMUv3节点 | SMMU硬件实例信息 | 基地址错误、中断号错误 |
| Named Component节点 | 设备到SMMU的映射 | StreamID映射关系写错 |
| Root Complex节点 | PCIe RC到SMMU的映射 | RID范围配置错误 |
2.4 OPTEE中的SMMU管理
OPTEE运行在安全世界(S-EL1或S-EL2)。它有自己的SMMU配置需求。比如安全应用(TA)要访问硬件外设,就需要通过OPTEE配置SMMU。
OPTEE的SMMU管理有几个特点:
- SPMC(Secure Partition Manager Core):在FF-A架构下,SPMC负责管理安全分区的SMMU配置。
- 内存隔离:安全世界的DMA必须走独立的SMMU Stream,不能和非安全世界混用。
- FF-A接口:通过FF-A的Memory Management Descriptor来传递SMMU配置信息。
我记得有个项目,安全摄像头要通过OPTEE访问DDR。一开始没配SMMU,结果安全摄像头DMA直接写穿了非安全内存。后来在OPTEE里加了SMMU配置,把安全摄像头的StreamID映射到安全内存区域,问题才解决。
2.5 固件与内核的交互模型
固件和内核怎么配合?我总结了几种交互方式:
| 交互方式 | 调用方 | 用途 | 性能开销 |
|---|---|---|---|
| SMC调用 | 内核 → ATF | SMMU全局配置、安全态切换 | 高(约5000 cycles) |
| HVC调用 | 内核 → Hypervisor | 虚拟化SMMU配置 | 中(约2000 cycles) |
| 共享内存 | 双向 | 传递Stream Table、CD Table | 低(约100 cycles) |
| Command Queue | 内核 → SMMU硬件 | TLB维护、STE配置 | 低(约50 cycles) |
这里有个关键点:内核尽量不走SMC。为什么?因为SMC要陷入EL3,开销太大。内核应该直接操作SMMU的Command Queue来维护TLB和配置STE。只有在需要改全局配置或切换安全态时,才走SMC。
2.6 交互模型的实际案例
拿一个实际场景来说。假设系统里有个NVMe SSD,通过SMMU做IOMMU。整个交互流程是这样的:
- 上电阶段:ATF初始化SMMU,配置全局寄存器,设置StreamID映射。
- UEFI阶段:UEFI生成IORT表,描述NVMe控制器到SMMU的映射关系。
- 内核启动:内核解析IORT表,找到SMMU实例和NVMe控制器的StreamID。
- 驱动加载:内核SMMU驱动分配STE和CD,通过Command Queue配置到硬件。
- 运行时:NVMe驱动发起DMA,SMMU自动完成地址翻译。TLB miss时,内核通过Command Queue做TLB维护。
这个流程里,固件只参与前两步。后面全是内核和SMMU硬件直接交互。为什么这样设计?说白了,就是性能。EL3的SMC调用太慢了,能不走就不走。
嗯,这一章的内容就到这里。固件侧的SMMU配置,说白了就是"初始化+描述+交接"。初始化交给ATF,描述交给UEFI,安全隔离交给OPTEE,运行时管理交给内核。各司其职,才能让SMMU高效工作。