3. SMMU编程模型:寄存器空间、命令队列、事件队列、PRI队列
SMMU的编程模型是系统软件与硬件交互的核心接口。理解其寄存器空间布局、队列机制以及中断处理流程,是进行SMMU初始化和动态管理的基础。本章将深入剖析SMMU的四大编程要素:寄存器空间、命令队列(Command Queue)、事件队列(Event Queue)和PRI队列(Page Request Interface Queue)。
3.1 SMMU寄存器空间
SMMU的寄存器空间分为全局寄存器(Global Registers)和上下文寄存器(Context Registers)两大部分。全局寄存器控制SMMU的整体行为,而上下文寄存器则管理具体的Stream Table Entry(STE)和配置。
| 寄存器类别 | 偏移范围 | 主要功能 | 关键寄存器示例 |
|---|---|---|---|
| 全局寄存器 | 0x0000 - 0x0FFF | SMMU全局控制、状态、ID、队列基址 | SMMU_CR0, SMMU_CR1, SMMU_IDR0, SMMU_GBPA |
| 上下文寄存器 | 0x1000 - 0xFFFF | 每个Stream的STE配置、CD表基址、TTB | STE0_Config, STE0_S1ContextPtr |
| 命令队列寄存器 | 0x2000 - 0x2FFF | 命令队列基址、生产者/消费者指针 | SMMU_CMDQ_BASE, SMMU_CMDQ_PROD, SMMU_CMDQ_CONS |
| 事件队列寄存器 | 0x3000 - 0x3FFF | 事件队列基址、生产者/消费者指针 | SMMU_EVTQ_BASE, SMMU_EVTQ_PROD, SMMU_EVTQ_CONS |
| PRI队列寄存器 | 0x4000 - 0x4FFF | PRI队列基址、生产者/消费者指针 | SMMU_PRIQ_BASE, SMMU_PRIQ_PROD, SMMU_PRIQ_CONS |
编程要点: 全局寄存器通常通过MMIO(Memory-Mapped I/O)访问,且必须遵循ARM架构规定的对齐和访问顺序。在修改控制寄存器(如SMMU_CR0)时,建议先读取、修改、再写回,并轮询状态位确认生效。
3.2 命令队列(Command Queue)
命令队列是软件向SMMU下发控制指令的通道。它是一个环形缓冲区(Ring Buffer),由软件(生产者)写入命令,SMMU硬件(消费者)读取并执行。常见的命令包括:TLBI(TLB无效化)、SYNC(同步屏障)、PREFETCH(预取)等。
命令队列操作流程
- 初始化: 设置SMMU_CMDQ_BASE寄存器,指定队列基址和大小。
- 写入命令: 软件将命令写入队列的当前生产者指针位置。
- 更新生产者指针: 写入SMMU_CMDQ_PROD寄存器,通知硬件有新命令。
- 硬件消费: SMMU读取命令并执行,更新消费者指针SMMU_CMDQ_CONS。
- 同步等待: 软件通过发送SYNC命令并轮询消费者指针,确保之前命令执行完毕。
关键命令格式(CMD_PREFETCH_CONFIG):
// 命令字格式(128位)
// Word 0: [31:0] 命令类型 & 标志位
// Word 1: [31:0] StreamID
// Word 2: [31:0] SubstreamID
// Word 3: [31:0] 保留
// 示例:发送TLBI_ASID命令
#define CMD_TLBI_ASID 0x3
#define CMD_TLBI_ASID_ASID 0x1
void smmu_send_tlbi_asid(uint32_t asid) {
uint64_t cmd[2] = {0};
cmd[0] = CMD_TLBI_ASID | (asid << 12);
// 写入命令队列并更新PROD指针
writeq(cmd[0], cmq_base + prod_offset);
writeq(cmd[1], cmq_base + prod_offset + 8);
writel(prod + 2, smmu_base + SMMU_CMDQ_PROD);
}
3.3 事件队列(Event Queue)
事件队列是SMMU向软件报告错误、异常或状态信息的通道。当SMMU检测到配置错误、地址翻译错误(如F_TRANSLATION)、权限错误(如F_PERMISSION)时,会将事件记录写入事件队列,并可选地触发中断。
常见事件类型
- F_TRANSLATION: 地址翻译失败,通常由无效的STE或CD表引起。
- F_PERMISSION: 访问权限冲突,如写只读页面。
- F_ACCESS: 访问了标记为不可访问的页面。
- F_ADDR_SIZE: 输入地址超出了配置的地址空间范围。
- C_BAD_STREAMID: 无效的StreamID。
调试建议: 在开发阶段,建议将事件队列中断使能,并在中断处理函数中打印事件记录。通过分析事件队列中的错误码和StreamID,可以快速定位配置错误或设备驱动问题。
3.4 PRI队列(Page Request Interface Queue)
PRI队列是SMMUv3引入的用于支持PCIe PRI(Page Request Interface)协议的通道。当设备访问的页面不在内存中(或未被映射)时,SMMU可以通过PRI队列向软件发送页面请求,软件响应后分配物理页面并更新页表,然后通过命令队列发送响应。
PRI队列工作流程
| 步骤 | 发起方 | 操作描述 | 关键寄存器/命令 |
|---|---|---|---|
| 1 | 设备 | 发起DMA访问,触发缺页 | PCIe PRI Request |
| 2 | SMMU | 将PRI请求写入PRI队列 | SMMU_PRIQ_PROD |
| 3 | 软件 | 从PRI队列读取请求,分配内存 | SMMU_PRIQ_CONS |
| 4 | 软件 | 更新页表,映射物理页面 | 页表操作 |
| 5 | 软件 | 发送PRI响应命令 | CMD_PRI_RESP |
| 6 | SMMU | 通知设备页面已就绪 | PCIe PRI Response |
PRI响应命令格式:
// CMD_PRI_RESP 命令结构
// Word 0: [31:0] 命令类型 (0x4)
// Word 1: [31:0] StreamID
// Word 2: [31:0] SubstreamID
// Word 3: [31:0] 页面请求组ID & 响应状态
// 示例:响应PRI请求,允许访问
void smmu_pri_response(uint32_t sid, uint32_t ssid, uint32_t prg_id) {
uint64_t cmd[2] = {0};
cmd[0] = (0x4) | (sid << 32);
cmd[1] = (ssid << 32) | (prg_id & 0xFFFF) | (0x1 << 16); // 状态=成功
// 写入命令队列
writeq(cmd[0], cmq_base + prod_offset);
writeq(cmd[1], cmq_base + prod_offset + 8);
writel(prod + 2, smmu_base + SMMU_CMDQ_PROD);
}
3.5 队列管理与中断机制
SMMU的每个队列(命令、事件、PRI)都支持独立的中断配置。通过SMMU_IRQ_CTRL寄存器可以控制每个队列的中断使能,而SMMU_IRQ_STATUS寄存器则用于查询中断状态。
中断处理最佳实践
- 命令队列: 通常使用轮询模式(SYNC命令后轮询CONS指针),避免中断开销。
- 事件队列: 必须使能中断,以便及时捕获错误。
- PRI队列: 建议使能中断,确保低延迟响应设备缺页请求。
- 中断亲和性: 将SMMU中断绑定到特定CPU核心,避免中断风暴。
性能优化: 对于命令队列,可以采用批处理模式:一次性写入多个命令,然后统一更新PROD指针,减少MMIO访问次数。对于事件队列,建议在中断处理函数中一次性读取多个事件记录,提高处理效率。