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 物理地址,设备可直通给虚拟机。
IOMMU 的本质是“为设备建立一套独立的地址空间”,DMA 映射 API 的工作就是维护这套地址空间中的页表项。

二、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 驱动。

ARM SMMU 地址转换架构 设备 / DMA 引擎 发出 IOVA + Stream ID SMMU Stream Table 查找 Context Descriptor 选择 内存 / DDR 物理地址 PA Stream Table 每个 Stream ID 对应一个 STE STE 指向 Context Descriptor 或直接指向 Stage-2 表 Context Descriptor Stage-1 页表基址 (TTBR0) Stage-2 页表基址 (VTTBR) ASID / VMID / 转换模式 Stage-1 转换 IOVA → IPA 设备视角虚拟地址 到中间物理地址 Stage-2 转换 IPA → PA 中间物理地址 到真实物理地址 Stage-1 与 Stage-2 可单独启用,也可级联形成完整转换链

三、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 决定“怎么转”。三者组合起来,才构成完整的设备地址转换路径。