在前几篇我们讲清楚了 SMMU 的硬件能力:StreamID、Context Bank、Stage1/Stage2、TLB、Broadcast 等。

这一篇我们站在 Linux 软件实现 的角度,看清:

Linux 是如何把 SMMU 抽象成一个可复用、可隔离、可虚拟化的 IOMMU 子系统?


一、三层架构总览

Linux 的 IOMMU 软件栈,本质可以压缩成三层:

┌──────────────────────────────┐
│ ① 上层使用者层                                                  │
│ DMA API / VFIO / KVM                                       │
└──────────────┬───────────────┘
                                       │
┌──────────────▼───────────────┐
│ ② IOMMU Core 核心层                                       │
│ iommu_group / iommu_domain                           │
└──────────────┬───────────────┘
                                       │
┌──────────────▼───────────────┐
│ ③ SMMU 驱动层                                                  │
│ arm-smmu-v3.c                                                    │
│ iommu.c                                                               │
└──────────────┬───────────────┘
                                       │
                                 SMMU 硬件

三层分工非常清晰:

层级 解决问题
上层 谁要用 IOMMU?
核心层 如何抽象“设备隔离”和“页表空间”?
驱动层 如何把抽象变成 SMMU 寄存器配置?

二、第一层:上层使用者(谁在用 IOMMU?)

Linux 中并不是 SMMU 主动工作,而是别人要求它工作

典型调用者有三类:

1. DMA API

内核驱动写:

dma_map_single(dev, cpu_addr, size, DMA_TO_DEVICE);

这行代码背后发生了什么?

路径大致是:

dma_map_xxx
        ↓
dma-iommu.c
        ↓
iommu_map()
        ↓
SMMU 驱动 map()

DMA API 并不知道什么是 SMMU。

它只知道:

我要把 CPU 物理地址 → 映射成设备可访问的 IOVA

真正的页表建立是在 IOMMU 核心层 + SMMU 驱动层完成。

 

2. VFIO

当设备被绑定到 vfio-pci

/dev/vfio/vfio

用户态通过 ioctl 操作 IOMMU。

例如:

  • 创建 IO address space

  • 绑定 device

  • 映射用户空间内存

VFIO 的核心目标是:

把 IOMMU 控制权从“内核驱动”转交给“用户空间虚拟机管理程序”。

也就是说:

  • DMA API = 内核进行映射

  • VFIO = 用户自己控制映射

 

3. KVM(Stage2)

在 ARM 虚拟化场景:

  • Host 管理 Stage1

  • KVM 管理 Stage2

SMMU 支持嵌套时:

  • Guest DMA → Stage1 → IPA

  • Stage2 → HPA

但 Linux 不允许 Guest 直接控制 SMMU。

原因很简单:

SMMU 是系统安全边界的一部分。

 

三、第二层:IOMMU Core 核心抽象

这一层才是 Linux IOMMU 的灵魂。

核心文件:

  • drivers/iommu/iommu.c

这一层解决两个关键问题:

 

1. iommu_group —— 设备隔离单位

关键结构体:

struct iommu_group

作用:

表示“一组必须共享 IOVA 空间的设备”

为什么需要 group?

因为硬件拓扑限制:

  • PCIe 不支持 ACS

  • 存在 DMA alias

  • 桥设备共享事务路径

Linux 会把它们放进同一个 group。

group 的语义:

  • 同一个 group 共享同一个 IOMMU domain

  • group 内设备可以互相 DMA

 

group 是怎么分配的?

对于 PCI 设备:

路径在:

drivers/pci/iommu.c

核心逻辑是:

  • 检查 ACS

  • 检查 DMA alias

  • 检查桥结构

如果不能完全隔离 → 必须同 group。

这就是:

为什么 VFIO 直通要求整组设备一起分配。

 

2. iommu_domain —— IOVA 地址空间

关键结构:

struct iommu_domain

它代表:

一张 IO 页表

domain 提供:

iommu_map()
iommu_unmap()

每个 domain:

  • 独立 IOVA 空间

  • 独立页表

  • 独立 TLB

 

domain 和 group 的关系

device → group → domain → SMMU context

一个 group 同一时刻只能 attach 一个 domain。

attach 路径:

iommu_attach_group()

内部:

  • 调用驱动的 attach_dev()

 

特殊 domain

Linux 默认有两个:

  • identity_domain(直通映射)

  • blocked_domain(完全禁止 DMA)

这两个是“安全默认状态”。

 

四、第三层:SMMU 驱动(arm-smmu-v3.c)

现在进入真正硬件相关部分。

核心文件:

  • drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c

这一文件完成:

把 iommu_domain 抽象,翻译成 SMMU 硬件配置。

 

1. 设备绑定流程

当设备 attach 到 domain 时:

iommu_attach_group()
         ↓
arm_smmu_attach_dev()

在 arm-smmu-v3.c 中:

  1. 分配 context descriptor

  2. 分配 CD table

  3. 选择 Stage1 / Stage2

  4. 写入 SMMU CD registers

关键概念:

  • StreamID

  • STE(Stream Table Entry)

  • CD(Context Descriptor)

SMMU v3 使用:

StreamID → STE → CD → TTBR

 

2. 映射流程

当调用:

iommu_map(domain, iova, paddr)

驱动执行:

arm_smmu_map()

内部做:

  1. 分配页表(ARM LPAE 格式)

  2. 写入 PTE

  3. 刷新 TLB

页表格式与 CPU 页表类似,但完全独立。

 

3. TLB 维护

SMMU v3 支持:

  • Command Queue

  • Broadcast TLB invalidation

驱动会:

  • 往 CMDQ 写 TLBI 命令

  • 等待 completion

这就是:

为什么 iommu_unmap() 不是简单删 PTE。


五、从调用到硬件的完整链路

一次 DMA 映射全过程:

驱动调用 dma_map_single()
     ↓
dma-iommu.c
     ↓
iommu_map()
     ↓
arm_smmu_map()
     ↓
写 SMMU 页表
     ↓
TLB invalidate
     ↓
设备 DMA 访问 IOVA
     ↓
SMMU 翻译
     ↓
访问物理地址

 

 

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐