SMMU 架构与落地方案(四):Linux 的 IOMMU + SMMU 架构
在前几篇我们讲清楚了 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 中:
-
分配 context descriptor
-
分配 CD table
-
选择 Stage1 / Stage2
-
写入 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()
内部做:
-
分配页表(ARM LPAE 格式)
-
写入 PTE
-
刷新 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 翻译
↓
访问物理地址
更多推荐




所有评论(0)