3、SMMU寄存器详解:SMMUv3关键寄存器配置实战

说实话,SMMUv3的寄存器数量不少,但真正在固件开发中天天打交道的,就那么几个。我刚开始接触SMMU时,面对几百页的Spec也是一头雾水。后来踩了几个坑才明白——抓住SMMU_CR0、SMMU_SMRn、SMMU_CBn这三类寄存器,你就抓住了SMMU配置的命脉。

今天咱们就一个一个掰开揉碎了讲。我会结合我实际项目中的配置经验,告诉你每个寄存器该怎么配、配错了会出什么妖蛾子。

核心观点:SMMUv3的配置逻辑可以概括为“全局使能 → 流匹配 → 上下文绑定”。SMMU_CR0管全局,SMMU_SMRn管“谁进来”,SMMU_CBn管“怎么走”。
SMMUv3 关键寄存器配置流程 SMMU_CR0 全局控制寄存器 SMMU_SMRn 流匹配寄存器组 SMMU_CBn 上下文寄存器组 配置顺序说明: ① 先配 SMMU_CR0 使能全局功能(如 SMMU_EN、PTM_EN) ② 再配 SMMU_SMRn 定义哪些设备/流需要走 SMMU ③ 最后配 SMMU_CBn 设置每个流的页表基址、ASID、缓存策略

3.1 SMMU_CR0:全局控制寄存器

SMMU_CR0是整个SMMU的“总开关”。你想想看,如果这个寄存器没配好,后面配再多都是白搭。我见过一个同事,调试了三天SMMU不工作,最后发现是SMMU_CR0的SMMU_EN位没置1——这种低级错误,说出来都是泪。

SMMU_CR0的关键位域:

位域 名称 说明 我的建议
[0] SMMU_EN SMMU全局使能,0=禁用,1=启用 最后一步再置1,先配好其他寄存器
[1] PTM_EN 页表内存访问使能 必须置1,否则页表走不了
[13] EVTGEN_EN 事件生成使能 调试阶段建议开启
[16] ATS_EN ATS(地址转换服务)使能 PCIe设备需要时开启
[19] MSI_EN MSI中断使能 建议开启,用于错误上报
我的配置习惯:我会先写一个“影子寄存器”变量,把所有位拼好,最后一次性写入SMMU_CR0。这样做的好处是——不会出现“配到一半设备开始乱走”的情况。你想想,如果SMMU_EN先置1了,但SMRn还没配,那进来的流量怎么处理?大概率是走Default流,然后触发错误。

实际代码中,我一般这样配:

// 伪代码:SMMU_CR0 配置示例
uint32_t cr0_val = 0;

// 先关掉SMMU,防止配置过程中有流量乱入
cr0_val &= ~(1 << 0);  // SMMU_EN = 0

// 配置其他功能位
cr0_val |= (1 << 1);   // PTM_EN = 1
cr0_val |= (1 << 13);  // EVTGEN_EN = 1
cr0_val |= (1 << 19);  // MSI_EN = 1

// 写入寄存器
mmio_write_32(SMMU_CR0_BASE + 0x00, cr0_val);

// 确认写入成功
uint32_t readback = mmio_read_32(SMMU_CR0_BASE + 0x00);
assert(readback == cr0_val);

// 最后再使能SMMU
cr0_val |= (1 << 0);   // SMMU_EN = 1
mmio_write_32(SMMU_CR0_BASE + 0x00, cr0_val);
我曾经踩过的坑:有一次我在配置SMMU_CR0时,没有做readback确认。结果发现SMMU根本没使能上——因为那个平台的SMMU_CR0寄存器有写保护,需要先解锁。从那以后,我每次配完关键寄存器都会读回来确认一下。别嫌麻烦,这步能救你命。

3.2 SMMU_SMRn:流匹配寄存器组

SMMU_SMRn的作用,说白了就是“谁的话我听,谁的话我不听”。每个SMRn寄存器对应一条流匹配规则,决定哪些设备发出的请求需要经过SMMU转换。

SMMU_SMRn的结构:

位域 名称 说明
[31:16] VMID 虚拟机ID,用于虚拟化场景
[15:0] SID Stream ID,设备标识

每个SMRn寄存器还配有一个SMMU_S2CRn(Stream to Context Register),用来指定匹配到的流该走哪个上下文(Context Bank)。

我个人的习惯是:先规划好SID分配表,再动手配寄存器。比如:

// SID分配规划示例
// SID 0x0000 - 0x00FF: 保留给内部设备
// SID 0x0100 - 0x01FF: PCIe RC (Root Complex)
// SID 0x0200 - 0x02FF: 显示控制器
// SID 0x0300 - 0x03FF: 多媒体引擎

// 配置SMR0:匹配PCIe设备,映射到Context Bank 0
mmio_write_32(SMMU_SMR_BASE + 0x00 * 8, 0x00000100);  // SMR0: SID=0x0100, mask=0xFF00
mmio_write_32(SMMU_S2CR_BASE + 0x00 * 8, 0x00000001); // S2CR0: 指向CB0

// 配置SMR1:匹配显示控制器,映射到Context Bank 1
mmio_write_32(SMMU_SMR_BASE + 0x01 * 8, 0x00000200);  // SMR1: SID=0x0200, mask=0xFF00
mmio_write_32(SMMU_S2CR_BASE + 0x01 * 8, 0x00000002); // S2CR1: 指向CB1
避坑指南:SMRn的匹配规则是“先匹配先服务”。如果你配了两条规则都能匹配同一个SID,那么只有第一条生效。我曾经遇到过一个问题:一个PCIe EP设备的SID是0x0180,结果被SMR0(匹配0x0100/0xFF00)匹配到了,但我本来想让它走SMR2的专用规则。后来我把专用规则放在了前面,问题就解决了。

3.3 SMMU_CBn:上下文寄存器组

SMMU_CBn是真正干活的寄存器。每个Context Bank包含一组寄存器,用来描述一个地址空间的转换规则。包括页表基址、ASID(Address Space ID)、缓存策略、异常处理等。

一个Context Bank的关键寄存器:

寄存器 偏移 说明
CBARn 0x00 Context Bank属性寄存器,配置VA大小、页表格式等
TTBR0n 0x08 转换表基址寄存器0(Stage 1页表)
TTBR1n 0x10 转换表基址寄存器1(Stage 1页表,高地址空间)
ACTLRn 0x18 辅助控制寄存器,TLB预取、缓存策略等
MAIRn 0x20 内存属性间接寄存器,定义页表中的属性编码

配Context Bank时,我最关注的是TTBR0n和ACTLRn。TTBR0n决定了页表在哪,ACTLRn决定了TLB怎么玩。

// 配置Context Bank 0
// 假设页表已经准备好,物理地址为0x8000_0000

// 1. 配置CBAR:VA_SIZE=48bit,页表格式为4KB粒度
mmio_write_32(SMMU_CB0_BASE + 0x00, 0x0000002F);  // CBAR: VA=48bit, TG=4KB

// 2. 配置TTBR0:页表基址
mmio_write_64(SMMU_CB0_BASE + 0x08, 0x0000000080000000ULL);  // TTBR0

// 3. 配置MAIR:定义内存属性
// Attr0: Normal Memory, Outer/Inner Write-Back
// Attr1: Device-nGnRE
mmio_write_32(SMMU_CB0_BASE + 0x20, 0x000004FF);  // MAIR

// 4. 配置ACTLR:开启TLB预取,使能合并写
mmio_write_32(SMMU_CB0_BASE + 0x18, 0x00000003);  // ACTLR: TLB_prefetch=1, merge_write=1
我曾经踩过的坑:有一次我配完Context Bank后,设备访问内存总是报错。查了两天才发现——TTBR0的地址没有按页表粒度对齐。SMMU要求TTBR0的地址必须与页表粒度对齐(比如4KB页表,地址必须4KB对齐)。我当时用的是0x8000_0100,结果低12位不为0,SMMU直接罢工了。从那以后,我每次配TTBR0都会检查地址对齐。

3.4 实战:完整配置一个设备流

好了,理论说完了,咱们来一个完整的配置流程。假设我们要为一个PCIe网卡配置SMMU:

  1. Step 1:读取设备SID(从PCIe配置空间或设备树中获取)
  2. Step 2:分配一个空闲的SMRn和Context Bank
  3. Step 3:配置SMMU_CR0(先关SMMU_EN)
  4. Step 4:配置SMRn和S2CRn(绑定SID到Context Bank)
  5. Step 5:配置Context Bank(页表、ASID、缓存策略)
  6. Step 6:使能SMMU_CR0(置SMMU_EN=1)
  7. Step 7:验证配置(发送测试请求,检查是否正常转换)
关键检查点:配置完成后,一定要检查SMMU_SMCR(SMMU Stream Match Configuration Register)的SMR_COUNT,确认当前生效的SMR数量是否正确。如果SMR_COUNT为0,说明你的SMRn没配上去。