4. IOMMU 与 SMMU 架构概览
在 Linux 内核的 DMA 映射体系中,IOMMU(Input/Output Memory Management Unit)是连接设备与主存之间的地址转换与保护单元。它让设备发出的 DMA 地址不再等同于物理地址,而是先经过一层“设备视角的虚拟地址”转换。ARM 体系中的 IOMMU 实现称为 SMMU(System Memory Management Unit),其架构与 x86 的 VT-d 类似,但在表结构、Stream 标识和 Stage 划分上有自己的规范。理解 SMMU 的 Stream Table、Context Descriptor 以及 Stage-1/Stage-2 两级转换,是后续配置设备 DMA 映射、排查 SMMU fault 的基础。
一、IOMMU 的核心作用
没有 IOMMU 时,设备驱动申请 DMA 缓冲区后,直接把物理地址写入设备寄存器,设备用该物理地址访问内存。这种方式简单,但存在三个明显问题:一是设备只能访问物理连续或经过散列/聚集(scatter-gather)描述的内存;二是设备可以访问任意物理地址,缺乏隔离;三是虚拟化场景下,Guest 的物理地址与 Host 物理地址不一致,设备无法直接使用。
IOMMU 引入后,设备发出的地址称为 IOVA(I/O Virtual Address),IOMMU 将其转换为物理地址(PA)。由此带来四项能力:
- 地址转换:把不连续的物理页映射为设备可见的连续 IOVA,简化驱动对缓冲区的管理。
- 内存保护:设备只能访问页表中已映射的区域,越界访问触发 fault,而不是静默破坏内存。
- 设备隔离:不同设备使用不同的页表,互不干扰,为容器、虚拟机提供隔离基础。
- 虚拟化支持:Stage-2 转换让 Guest 物理地址映射到 Host 物理地址,设备可直通给虚拟机。
二、SMMU 在 ARM 体系中的位置
在 ARM 架构中,SMMU 通常位于设备与内存控制器之间,或集成在互连(Interconnect)中。设备通过总线发起带 Stream ID 的访问请求,SMMU 根据 Stream ID 查找对应的转换表,完成地址转换后再访问内存。SMMU 的输入侧是设备事务,输出侧是内存事务,因此它既能看到设备的读写请求,也能对请求进行拦截和记录。
ARM SMMU 规范经历了多个版本:SMMUv1、SMMUv2 主要面向 Stage-1 转换;SMMUv3 重新设计了寄存器接口和命令队列,支持更高效的 Stage-1/Stage-2 组合,并引入 Stream Table、Context Descriptor、CD 表等结构。Linux 内核中分别对应 arm-smmu 与 arm-smmu-v3 驱动。
三、Stream Table 与 Context Descriptor
SMMU 收到设备请求时,首先根据请求携带的 Stream ID 在 Stream Table 中查找对应的 STE(Stream Table Entry)。Stream ID 通常由设备树或 ACPI 中的 iommus 属性描述,代表“哪个设备”或“哪个设备功能”。STE 中保存了该 Stream 的配置,包括是否启用 Stage-1、是否启用 Stage-2、以及指向 Context Descriptor 的指针。
Context Descriptor(CD)是 SMMUv3 中描述单个转换上下文的结构,主要字段包括:
| 字段 | 含义 | 典型用途 |
|---|---|---|
| TTBR0 / TCR0 | Stage-1 页表基址与转换控制 | 设备 IOVA 到 IPA 的映射 |
| VTTBR / VTCR | Stage-2 页表基址与转换控制 | IPA 到 PA 的映射,虚拟化场景 |
| ASID | 地址空间标识 | 区分不同进程或设备的 Stage-1 上下文 |
| VMID | 虚拟机标识 | 区分不同 Guest 的 Stage-2 上下文 |
| Mair / SCTLR | 内存属性与系统控制 | 控制缓存、共享性、访问权限 |
一个 Stream ID 可以对应一个 CD,也可以多个 Stream ID 共享同一个 CD。共享 CD 意味着这些设备使用相同的地址空间,适合同一驱动管理的多个队列或功能。若 STE 中未启用 Stage-1,则设备地址直接进入 Stage-2;若两者都未启用,则 SMMU 处于 bypass 模式,设备地址直接作为物理地址使用。
四、Stage-1 与 Stage-2 转换
Stage-1 转换把设备发出的 IOVA 转换为 IPA(Intermediate Physical Address,中间物理地址)。在非虚拟化场景中,IPA 通常就等于 PA,因此 Stage-1 是主要的转换层。Stage-2 转换把 IPA 转换为 PA,主要用于虚拟化:Guest 操作系统认为自己在管理物理地址,实际上这些地址还要经过 Hypervisor 控制的 Stage-2 页表才能落到真实内存。
两级转换可以单独使用,也可以级联。级联时,SMMU 先查 Stage-1 页表得到 IPA,再查 Stage-2 页表得到 PA。Linux 内核中,iommu_map() 和 iommu_unmap() 负责维护这些页表;DMA API 如 dma_map_single() 在启用 IOMMU 时会调用 IOMMU 后端建立映射,返回设备可用的 DMA 地址。
# 查看系统中 SMMU 设备与 Stream ID 映射
ls /sys/bus/platform/devices/ | grep -i smmu
cat /sys/kernel/debug/iommu/arm-smmu-v3/streams
# 典型设备树片段:描述设备的 Stream ID
iommu@... {
compatible = "arm,smmu-v3";
#iommu-cells = <1>;
};
device@... {
iommus = <&smmu 0x1234>;
};
五、常见误区与排查思路
误区一:认为所有设备都默认经过 SMMU。 实际上,只有设备树或 ACPI 中正确声明了 iommus 属性,且 SMMU 驱动成功探测,设备才会被纳入 SMMU 转换。未声明的设备仍使用物理地址直通。
误区二:把 Stream ID 当成设备号。 Stream ID 是总线事务中携带的标识,可能由硬件固定,也可能由固件配置。同一个物理设备的不同功能可能使用不同 Stream ID。
误区三:忽略 Stage-2 的权限。 在虚拟化场景中,即使 Stage-1 映射正确,Stage-2 页表若未映射或权限不足,设备访问仍会触发 fault。
排查 SMMU fault 时,应优先确认三件事:设备是否绑定了正确的 Stream ID;STE 是否指向了有效的 CD;CD 中的页表基址和转换模式是否与当前场景匹配。内核日志中通常会出现 arm-smmu-v3 相关的 fault 记录,包含 Stream ID、地址和 fault 类型,可据此定位是 Stage-1 还是 Stage-2 的问题。
理解 SMMU 的关键在于建立“设备地址空间”这一视角:Stream Table 决定“谁”,Context Descriptor 决定“用哪套页表”,Stage-1/Stage-2 决定“怎么转”。三者组合起来,才构成完整的设备地址转换路径。