4. IOMMU硬件架构
IOMMU 并不是一个单一寄存器块,而是由“根表/上下文表解析单元 + 多级页表遍历器 + IOTLB + 设备页缓存 + 中断重映射单元 + 故障记录单元”共同构成的硬件子系统。Intel VT-d、AMD-Vi 与 ARM SMMU 在功能目标上高度一致:把设备发出的 DMA 地址(IOVA)翻译成物理地址(PA),并施加访问权限与中断隔离。但三者在表结构命名、寄存器模型、缓存层次和失效命令上差异明显,理解这些差异是跨平台直通工程的基础。
一、核心原理:三种架构的翻译路径
三者都采用“设备标识 → 地址空间标识 → 多级页表 → 物理地址”的翻译链,但入口结构不同。Intel VT-d 使用 Root Table 与 Context Table 两级入口:Bus/Device/Function 先定位 Root Entry,再定位 Context Entry,Context Entry 指向第二级页表基址。AMD-Vi 使用 Device Table 与 I/O Page Table:Device ID 直接索引 Device Table Entry(DTE),DTE 中保存页表根指针与权限位。ARM SMMU 使用 Stream Table:Stream ID 索引 Stream Table Entry(STE),STE 再指向 CD Table(Context Descriptor),由 CD 提供页表基址与 ASID。
翻译粒度上,VT-d 与 AMD-Vi 以 4KB 为基本页,支持 2MB/1GB 大页;SMMU 支持 4KB、16KB、64KB 三种粒度,并区分 Stage 1(VA→IPA)与 Stage 2(IPA→PA)。VT-d 的二级翻译对应 GPA→HPA,AMD-Vi 通过 DTE 中的模式位支持嵌套。SMMU 的 Stage 1/Stage 2 组合最灵活,也最复杂,常用于虚拟化与设备直通并存场景。
二、寄存器模型与失效命令
VT-d 的寄存器以 MMIO 形式暴露,核心包括 RTADDR(根表基址)、CCMD(上下文缓存失效)、IOTLB_INV(IOTLB 失效)、FSTS(故障状态)与 FRCD(故障记录)。失效命令通过 CCMD 寄存器写入,需等待 IOTLB_INV 完成后再继续,否则可能读到旧翻译。AMD-Vi 的核心寄存器是 DEV_TAB_BASE(设备表基址)、CMD_BUF_BASE(命令缓冲)、EVENT_LOG_BASE(事件日志)。失效通过命令缓冲写入 INVALIDATE_IOMMU_PAGES 或 INVALIDATE_DEVTAB_ENTRY,硬件异步完成。SMMU 的寄存器包括 SMMU_STRTAB_BASE、SMMU_CMD 队列基址、SMMU_EVENTQ 基址。失效命令为 CMD_TLBI_NH_ASID、CMD_TLBI_EL2 等,粒度更细,支持按 ASID、按 VA 范围失效。
工程上最易踩坑的是“失效顺序”:先改页表,再发失效命令,最后等待完成。若顺序颠倒,设备可能继续使用旧映射,导致 DMA 写入错误内存。VT-d 的 IOTLB_INV 是同步等待,AMD-Vi 与 SMMU 是异步队列,需要轮询完成状态或依赖中断。
三、缓存与 TLB 结构对比
| 对比项 | Intel VT-d | AMD-Vi | ARM SMMU |
|---|---|---|---|
| 入口结构 | Root + Context Table | Device Table | Stream Table + CD Table |
| 页表层级 | 二级页表(SLPT) | I/O Page Table | Stage1 / Stage2 |
| 页粒度 | 4KB / 2MB / 1GB | 4KB / 2MB / 1GB | 4KB / 16KB / 64KB |
| IOTLB | 有,按域失效 | 有,按域失效 | 有,按 ASID/VMID 失效 |
| 页表遍历缓存 | 有(PWC) | 有(PWC) | 有(Walk Cache) |
| 失效方式 | 同步等待 | 异步命令队列 | 异步命令队列 |
| 中断重映射 | 支持 | 支持 | 支持(MSI 重映射) |
四、案例与排障场景
某 KVM 直通场景中,虚拟机启动后网卡 DMA 偶发写入错误内存。排查发现:VFIO 在更新页表后未正确等待 VT-d 的 IOTLB_INV 完成,导致设备短暂使用旧映射。修复方式是在 iommu_flush_iotlb_all() 后增加完成轮询。另一个场景是 SMMU 平台上直通设备无法识别,原因是 Stream ID 与设备树中的 iommus 属性不匹配,STE 未正确指向 CD,翻译直接 bypass。检查 /sys/kernel/debug/iommu/ 下的 SMMU 状态可快速定位。
常见误区:认为“IOMMU 开启后设备一定隔离”。实际上若 DTE/STE 配置为 bypass 模式,或页表权限位为可写全通,设备仍可访问任意物理内存。必须同时检查入口表项与页表权限,不能只看 IOMMU 是否使能。
调试建议:VT-d 用 dmesg | grep -i DMAR 查看故障记录;AMD-Vi 用 dmesg | grep -i AMD-Vi 查看事件日志;SMMU 用 dmesg | grep -i SMMU 与 debugfs 事件队列。三者都建议在直通前先用 iommu=pt 或 arm-smmu.disable_bypass=0 验证基础翻译是否正常。
五、工程选型与配置要点
跨平台直通工程中,建议抽象出统一的“IOMMU 操作层”:初始化入口表、建立映射、失效缓存、注册故障处理。VT-d 与 AMD-Vi 的差异主要在寄存器偏移与失效命令格式,SMMU 则需额外处理 Stage1/Stage2 与 ASID 分配。配置时优先使用内核已有的 IOMMU 驱动与 VFIO 框架,避免直接操作寄存器。若必须裸写,务必先阅读对应架构的规范章节,确认表项对齐、内存屏障与失效完成条件。
# 查看 IOMMU 分组与设备归属
ls /sys/kernel/iommu_groups/
# 查看 VT-d 故障记录
dmesg | grep -i "DMAR"
# 查看 SMMU 事件
cat /sys/kernel/debug/iommu/smmu/eventq
# 绑定设备到 VFIO
echo 0000:01:00.0 > /sys/bus/pci/drivers/vfio-pci/bind
总结来看,三种架构在“入口表 → 页表 → 缓存 → 失效”这条主线上一致,差异集中在表结构命名、失效同步方式和页粒度支持。掌握这些差异后,直通工程中的翻译失败、DMA 越界与中断异常都能更快定位到具体表项或缓存状态。