Linux内核分析之内存管理-16

韩乔落

第16章:IOMMU 输入输出内存管理单元

基于 Linux 6.12.38 源码


16.1 IOMMU 概述

16.1.1 什么是 IOMMU

IOMMU (Input/Output Memory Management Unit) 是一种硬件组件,用于管理设备对系统内存的访问。它类似于 CPU 的 MMU,但服务于 I/O 设备而非 CPU。

IOMMU 核心功能:

功能 描述
DMA 地址转换 将设备看到的 IOVA (I/O Virtual Address) 转换为物理地址
内存隔离 防止恶意设备访问任意内存区域
scatter-gather 支持 支持不连续物理内存的 DMA 操作
中断重映射 提供 MSI/MSI-X 中断重映射功能

16.1.2 IOMMU 与 CPU MMU 对比

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
┌─────────────────────────────────────────────────────────────────┐
│ CPU MMU (内存管理单元) │
├─────────────────────────────────────────────────────────────────┤
│ │
│ CPU ──→ 虚拟地址 (VA) ──→ MMU ──→ 物理地址 (PA) ──→ 系统内存 │
│ │
│ - 进程地址空间隔离 │
│ - 页表管理 (PGD → PUD → PMD → PTE) │
│ - TLB 缓存 │
│ │
├─────────────────────────────────────────────────────────────────┤
│ IOMMU (IO 内存管理单元) │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 设备 ──→ IOVA ──→ IOMMU ──→ 物理地址 (PA) ──→ 系统内存 │
│ │
│ - 设备 DMA 地址转换 │
│ - 设备隔离 (防止恶意 DMA) │
│ - IOTLB 缓存 │
│ - PASID 支持 (多个地址空间) │
│ │
└─────────────────────────────────────────────────────────────────┘

16.1.3 主要 IOMMU 硬件实现

硬件 描述 Linux 驱动位置
Intel VT-d Intel x86 平台的 IOMMU (VT-x for I/O) drivers/iommu/intel/
AMD-Vi AMD x86 平台的 IOMMU drivers/iommu/amd/
ARM SMMU ARM 平台 SMMU v2/v3 drivers/iommu/arm/arm-smmu-v3/
virtio-iommu 虚拟化环境 IOMMU drivers/iommu/virtio-iommu.c

16.2 IOMMU 核心数据结构

16.2.1 iommu_device 结构

位置: include/linux/iommu.h:682

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/**
* struct iommu_device - IOMMU 硬件实例的核心表示
* @list: 用于 iommu-core 维护已注册 iommu 的链表
* @ops: 指向此 iommu 的 iommu-ops
* @fwnode: firmware 节点
* @dev: 用于 sysfs 处理的 struct device
* @singleton_group: 用于只有一个组的驱动程序
* @max_pasids: 支持的 PASID 数量
*/
struct iommu_device {
struct list_head list;
const struct iommu_ops *ops;
struct fwnode_handle *fwnode;
struct device *dev;
struct iommu_group *singleton_group;
u32 max_pasids;
};

16.2.2 iommu_domain 结构

位置: include/linux/iommu.h:208

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
/**
* struct iommu_domain - IOMMU 域表示
*
* IOMMU 域表示一个设备地址空间,用于隔离不同设备的 DMA 地址空间
*/
struct iommu_domain {
unsigned type; /* 域类型 */
const struct iommu_domain_ops *ops; /* 域操作 */
const struct iommu_dirty_ops *dirty_ops;/* 脏页跟踪操作 */
const struct iommu_ops *owner; /* 所属驱动 */
unsigned long pgsize_bitmap; /* 支持的页大小位图 */
struct iommu_domain_geometry geometry; /* 地址空间几何信息 */
struct iommu_dma_cookie *iova_cookie; /* DMA API cookie */
int (*iopf_handler)(struct iopf_group *); /* IOPF 处理器 */
void *fault_data; /* fault 数据 */
union {
struct {
iommu_fault_handler_t handler; /* 传统 fault 处理器 */
void *handler_token;
};
struct { /* IOMMU_DOMAIN_SVA 专用 */
struct mm_struct *mm; /* 关联的进程地址空间 */
int users; /* 用户计数 */
struct list_head next; /* mm->iommu_mm->sva_domains */
};
};
};

16.2.3 iommu_domain_geometry

1
2
3
4
5
6
7
8
9
10
11
/**
* struct iommu_domain_geometry - 域地址空间几何信息
* @aperture_start: 可映射的第一个地址
* @aperture_end: 可映射的最后一个地址
* @force_aperture: DMA 仅允许在可映射范围内
*/
struct iommu_domain_geometry {
dma_addr_t aperture_start; /* 可映射地址范围起始 */
dma_addr_t aperture_end; /* 可映射地址范围结束 */
bool force_aperture; /* 是否强制限制 DMA 在可映射范围内 */
};

16.2.4 iommu_group 结构

位置: drivers/iommu/iommu.c:47

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/**
* struct iommu_group - IOMMU 组
*
* IOMMU 组是一组设备的集合,这些设备共享相同的 IOMMU 域
*/
struct iommu_group {
struct kobject kobj;
struct kobject *devices_kobj;
struct list_head devices; /* 组内的设备列表 */
struct xarray pasid_array; /* PASID 数组 */
struct mutex mutex;
void *iommu_data;
void (*iommu_data_release)(void *iommu_data);
char *name;
int id;
struct iommu_domain *default_domain; /* 默认域 */
struct iommu_domain *blocking_domain; /* 阻塞域 */
struct iommu_domain *domain; /* 当前域 */
struct list_head entry;
unsigned int owner_cnt;
void *owner;
};

16.2.5 dev_iommu 结构

位置: include/linux/iommu.h:732

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
* struct dev_iommu - 每设备 IOMMU 数据集合
*/
struct dev_iommu {
struct mutex lock;
struct iommu_fault_param __rcu *fault_param; /* fault 报告数据 */
struct iommu_fwspec *fwspec; /* firmware 规格数据 */
struct iommu_device *iommu_dev; /* 关联的 IOMMU 设备 */
void *priv; /* IOMMU 驱动私有数据 */
u32 max_pasids; /* 设备可消耗的 PASID 数量 */
u32 attach_deferred:1; /* 延迟附加 */
u32 pci_32bit_workaround:1; /* 32 位 DMA 限制 */
u32 require_direct:1; /* 需要直接映射区域 */
u32 shadow_on_flush:1; /* 刷新时同步影子表 */
};

16.2.6 iommu_mm_data 结构

位置: include/linux/iommu.h:1017

1
2
3
4
5
6
7
8
9
/**
* struct iommu_mm_data - mm_struct 与 IOMMU 的集成数据
* @pasid: 分配给此 mm 的 PASID
* @sva_domains: 使用此 mm 的 SVA 域列表
*/
struct iommu_mm_data {
u32 pasid; /* 分配给此 mm 的 PASID */
struct list_head sva_domains; /* SVA 域列表 */
};

16.3 IOMMU 域类型

16.3.1 域类型定义

位置: include/linux/iommu.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
/* 域特性标志 */
#define __IOMMU_DOMAIN_PAGING (1U << 0) /* 支持 iommu_map/unmap */
#define __IOMMU_DOMAIN_DMA_API (1U << 1) /* DMA-API 使用的域 */
#define __IOMMU_DOMAIN_PT (1U << 2) /* 恒等映射域 */
#define __IOMMU_DOMAIN_DMA_FQ (1U << 3) /* DMA-API 使用 flush queue */
#define __IOMMU_DOMAIN_SVA (1U << 4) /* 共享进程地址空间 */
#define __IOMMU_DOMAIN_PLATFORM (1U << 5) /* 平台特定 */
#define __IOMMU_DOMAIN_NESTED (1U << 6) /* 用户管理地址空间嵌套 */

/*
* 域类型定义:
*
* IOMMU_DOMAIN_BLOCKED - 所有 DMA 被阻塞,用于设备隔离
* IOMMU_DOMAIN_IDENTITY - DMA 地址就是系统物理地址 (1:1 映射)
* IOMMU_DOMAIN_UNMANAGED - DMA 映射由 IOMMU-API 用户管理,用于 VM
* IOMMU_DOMAIN_DMA - DMA-API 实现内部使用
* IOMMU_DOMAIN_DMA_FQ - 同上,但使用批量 TLB 失效
* IOMMU_DOMAIN_SVA - DMA 地址是共享的进程地址
* IOMMU_DOMAIN_PLATFORM - 遗留域,新驱动不应使用
*/
#define IOMMU_DOMAIN_BLOCKED (0U)
#define IOMMU_DOMAIN_IDENTITY (__IOMMU_DOMAIN_PT)
#define IOMMU_DOMAIN_UNMANAGED (__IOMMU_DOMAIN_PAGING)
#define IOMMU_DOMAIN_DMA (__IOMMU_DOMAIN_PAGING | __IOMMU_DOMAIN_DMA_API)
#define IOMMU_DOMAIN_DMA_FQ (__IOMMU_DOMAIN_PAGING | __IOMMU_DOMAIN_DMA_API | __IOMMU_DOMAIN_DMA_FQ)
#define IOMMU_DOMAIN_SVA (__IOMMU_DOMAIN_SVA)
#define IOMMU_DOMAIN_PLATFORM (__IOMMU_DOMAIN_PLATFORM)
#define IOMMU_DOMAIN_NESTED (__IOMMU_DOMAIN_NESTED)

16.3.2 域类型特性对比

域类型 描述 地址转换 用途
BLOCKED 所有 DMA 被阻塞 N/A 设备隔离
IDENTITY 1:1 恒等映射 IOVA = PA 直通模式
UNMANAGED 用户管理映射 IOVA → PA 虚拟化
DMA 内核 DMA API IOVA → PA 通用设备
DMA_FQ DMA + Flush Queue IOVA → PA 高性能设备
SVA 共享进程地址 VA = IOVA → PA 设备直接访问进程内存
NESTED 嵌套页表 GVA → GPA → PA 虚拟化嵌套

16.4 IOMMU 域操作

16.4.1 iommu_ops 结构

位置: include/linux/iommu.h:559

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
/**
* struct iommu_ops - iommu 操作和能力
*/
struct iommu_ops {
/* 能力检查 */
bool (*capable)(struct device *dev, enum iommu_cap);
void *(*hw_info)(struct device *dev, u32 *length, u32 *type);

/* 域分配 */
struct iommu_domain *(*domain_alloc)(unsigned iommu_domain_type);
struct iommu_domain *(*domain_alloc_user)(
struct device *dev, u32 flags, struct iommu_domain *parent,
const struct iommu_user_data *user_data);
struct iommu_domain *(*domain_alloc_paging)(struct device *dev);
struct iommu_domain *(*domain_alloc_sva)(struct device *dev,
struct mm_struct *mm);

/* 设备探测 */
struct iommu_device *(*probe_device)(struct device *dev);
void (*release_device)(struct device *dev);
void (*probe_finalize)(struct device *dev);
struct iommu_group *(*device_group)(struct device *dev);

/* 保留区域 */
void (*get_resv_regions)(struct device *dev, struct list_head *list);

/* 设备特性 */
int (*dev_enable_feat)(struct device *dev, enum iommu_dev_features f);
int (*dev_disable_feat)(struct device *dev, enum iommu_dev_features f);

/* 页面响应 */
void (*page_response)(struct device *dev, struct iopf_fault *evt,
struct iommu_page_response *msg);

/* 默认域类型 */
int (*def_domain_type)(struct device *dev);

/* PASID 移除 */
void (*remove_dev_pasid)(struct device *dev, ioasid_t pasid,
struct iommu_domain *domain);

const struct iommu_domain_ops *default_domain_ops;
unsigned long pgsize_bitmap;
struct module *owner;
struct iommu_domain *identity_domain;
struct iommu_domain *blocked_domain;
struct iommu_domain *release_domain;
struct iommu_domain *default_domain;
u8 user_pasid_table:1;
};

16.4.2 iommu_domain_ops 结构

位置: include/linux/iommu.h:642

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
/**
* struct iommu_domain_ops - 域特定操作
*/
struct iommu_domain_ops {
/* 附加/分离设备 */
int (*attach_dev)(struct iommu_domain *domain, struct device *dev);
int (*set_dev_pasid)(struct iommu_domain *domain, struct device *dev,
ioasid_t pasid);

/* 页面映射/解除映射 */
int (*map_pages)(struct iommu_domain *domain, unsigned long iova,
phys_addr_t paddr, size_t pgsize, size_t pgcount,
int prot, gfp_t gfp, size_t *mapped);
size_t (*unmap_pages)(struct iommu_domain *domain, unsigned long iova,
size_t pgsize, size_t pgcount,
struct iommu_iotlb_gather *iotlb_gather);

/* TLB 刷新 */
void (*flush_iotlb_all)(struct iommu_domain *domain);
int (*iotlb_sync_map)(struct iommu_domain *domain, unsigned long iova,
size_t size);
void (*iotlb_sync)(struct iommu_domain *domain,
struct iommu_iotlb_gather *iotlb_gather);
int (*cache_invalidate_user)(struct iommu_domain *domain,
struct iommu_user_data_array *array);

/* 地址转换 */
phys_addr_t (*iova_to_phys)(struct iommu_domain *domain, dma_addr_t iova);

/* 域特性 */
bool (*enforce_cache_coherency)(struct iommu_domain *domain);
int (*enable_nesting)(struct iommu_domain *domain);
int (*set_pgtable_quirks)(struct iommu_domain *domain,
unsigned long quirks);

/* 释放域 */
void (*free)(struct iommu_domain *domain);
};

16.4.3 IOMMU 核心操作函数

位置: include/linux/iommu.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
/* 域分配和释放 */
extern struct iommu_domain *iommu_domain_alloc(const struct bus_type *bus);
struct iommu_domain *iommu_paging_domain_alloc(struct device *dev);
extern void iommu_domain_free(struct iommu_domain *domain);

/* 设备附加/分离 */
extern int iommu_attach_device(struct iommu_domain *domain, struct device *dev);
extern void iommu_detach_device(struct iommu_domain *domain, struct device *dev);
extern struct iommu_domain *iommu_get_domain_for_dev(struct device *dev);
extern struct iommu_domain *iommu_get_dma_domain(struct device *dev);

/* 页面映射/解除映射 */
extern int iommu_map(struct iommu_domain *domain, unsigned long iova,
phys_addr_t paddr, size_t size, int prot, gfp_t gfp);
extern size_t iommu_unmap(struct iommu_domain *domain, unsigned long iova,
size_t size);
extern size_t iommu_unmap_fast(struct iommu_domain *domain,
unsigned long iova, size_t size,
struct iommu_iotlb_gather *iotlb_gather);
extern ssize_t iommu_map_sg(struct iommu_domain *domain, unsigned long iova,
struct scatterlist *sg, unsigned int nents,
int prot, gfp_t gfp);

/* 地址转换 */
extern phys_addr_t iommu_iova_to_phys(struct iommu_domain *domain, dma_addr_t iova);

/* fault 处理 */
extern void iommu_set_fault_handler(struct iommu_domain *domain,
iommu_fault_handler_t handler, void *token);
extern int report_iommu_fault(struct iommu_domain *domain, struct device *dev,
unsigned long iova, int flags);

/* 组操作 */
extern int iommu_attach_group(struct iommu_domain *domain, struct iommu_group *group);
extern void iommu_detach_group(struct iommu_domain *domain, struct iommu_group *group);
extern struct iommu_group *iommu_group_alloc(void);
extern struct iommu_group *iommu_group_get(struct device *dev);
extern void iommu_group_put(struct iommu_group *group);
extern int iommu_group_id(struct iommu_group *group);

/* 保留区域 */
extern void iommu_get_resv_regions(struct device *dev, struct list_head *list);
extern void iommu_put_resv_regions(struct device *dev, struct list_head *list);

16.4.4 IOMMU 保护标志

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/* IOMMU 保护标志 */
#define IOMMU_READ (1 << 0) /* 可读 */
#define IOMMU_WRITE (1 << 1) /* 可写 */
#define IOMMU_CACHE (1 << 2) /* DMA 缓存一致性 */
#define IOMMU_NOEXEC (1 << 3) /* 不可执行 */
#define IOMMU_MMIO (1 << 4) /* MMIO 访问 (如 MSI doorbells) */
#define IOMMU_PRIV (1 << 5) /* 特权级访问 */

/*
* IOMMU_PRIV 标志说明:
* 当总线硬件包含特权级作为其访问类型标记的一部分,
* 并且某些设备能够发出标记为 "supervisor" 或 "user" 的事务时,
* IOMMU_PRIV 标志请求其他给定的权限标志仅应用于较高级别的访问,
* 并且非特权事务应具有尽可能少的访问权限。
*/

16.5 IOMMU IOTLB 管理

16.5.1 iommu_iotlb_gather 结构

位置: include/linux/iommu.h:345

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/**
* struct iommu_iotlb_gather - 待刷新 IOTLB 范围信息
*
* @start: 要刷新范围的 IOVA 起始地址
* @end: 要刷新范围的 IOVA 结束地址 (包含)
* @pgsize: 执行刷新的间隔
* @freelist: 在 sync 后要释放的已移除页面
* @queued: 指示刷新将被排队
*
* 此结构旨在通过多次调用 ->unmap() 函数更新,
* 最终在 ->iotlb_sync() 或 ->iotlb_flush_all() 中传递
*/
struct iommu_iotlb_gather {
unsigned long start;
unsigned long end;
size_t pgsize;
struct list_head freelist;
bool queued;
};

16.5.2 IOTLB 刷新辅助函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
/* 初始化 gather 结构 */
static inline void iommu_iotlb_gather_init(struct iommu_iotlb_gather *gather)
{
*gather = (struct iommu_iotlb_gather) {
.start = ULONG_MAX,
.freelist = LIST_HEAD_INIT(gather->freelist),
};
}

/* 检查范围是否不相交 */
static inline bool iommu_iotlb_gather_is_disjoint(
struct iommu_iotlb_gather *gather, unsigned long iova, size_t size)
{
unsigned long start = iova, end = start + size - 1;
return gather->end != 0 &&
(end + 1 < gather->start || start > gather->end + 1);
}

/* 基于地址的 TLB 失效收集 */
static inline void iommu_iotlb_gather_add_range(
struct iommu_iotlb_gather *gather, unsigned long iova, size_t size)
{
unsigned long end = iova + size - 1;
if (gather->start > iova)
gather->start = iova;
if (gather->end < end)
gather->end = end;
}

/* 基于页的 TLB 失效收集 */
static inline void iommu_iotlb_gather_add_page(
struct iommu_domain *domain, struct iommu_iotlb_gather *gather,
unsigned long iova, size_t size)
{
if ((gather->pgsize && gather->pgsize != size) ||
iommu_iotlb_gather_is_disjoint(gather, iova, size))
iommu_iotlb_sync(domain, gather);
gather->pgsize = size;
iommu_iotlb_gather_add_range(gather, iova, size);
}

/* 检查是否排队 */
static inline bool iommu_iotlb_gather_queued(struct iommu_iotlb_gather *gather)
{
return gather && gather->queued;
}

16.5.3 TLB 刷新函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/* 刷新整个域的 TLB */
static inline void iommu_flush_iotlb_all(struct iommu_domain *domain)
{
if (domain->ops->flush_iotlb_all)
domain->ops->flush_iotlb_all(domain);
}

/* 同步 TLB 刷新 */
static inline void iommu_iotlb_sync(struct iommu_domain *domain,
struct iommu_iotlb_gather *iotlb_gather)
{
if (domain->ops->iotlb_sync)
domain->ops->iotlb_sync(domain, iotlb_gather);
iommu_iotlb_gather_init(iotlb_gather);
}

16.6 SVA (共享虚拟地址)

16.6.1 SVA 概述

SVA (Shared Virtual Addressing) 允许设备使用与 CPU 相同的虚拟地址空间。

SVA 核心概念:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
┌─────────────────────────────────────────────────────────────────┐
│ 传统 DMA 模型 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 进程 VA ──→ CPU MMU ──→ PA │
│ │
│ 设备 ──→ IOVA ──→ IOMMU ──→ PA ──→ (可能通过 DMA 映射) │
│ │
│ 问题: 需要维护两套地址空间,需要显式 DMA 映射 │
│ │
├─────────────────────────────────────────────────────────────────┤
│ SVA 模式 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 进程 VA ──→ CPU MMU ──→ PA │
│ │ │
│ └── 设备 ──→ IOVA (=VA) ──→ IOMMU ──→ PA │
│ │
│ 优势: 设备直接使用进程虚拟地址,无需额外映射 │
│ 通过 PASID 区分不同进程的地址空间 │
│ │
└─────────────────────────────────────────────────────────────────┘

16.6.2 PASID (Process Address Space ID)

PASID 定义:

1
2
3
4
#define IOMMU_NO_PASID            (0U)   /* 无 PASID (传统 DMA) */
#define IOMMU_FIRST_GLOBAL_PASID (1U) /* 全局 PASID 起始范围 */
#define IOMMU_PASID_INVALID (-1U) /* 无效 PASID */
typedef unsigned int ioasid_t;

PASID 作用:

  1. 地址空间隔离: 允许单个设备同时访问多个进程的地址空间
  2. 并发访问: 设备可以同时为多个进程执行 DMA 操作
  3. PASID 表: IOMMU 维护 PASID 表,每个 PASID 指向不同的页表

16.6.3 iommu_sva 结构

位置: include/linux/iommu.h:1009

1
2
3
4
5
6
7
8
/**
* struct iommu_sva - 设备-mm 绑定句柄
*/
struct iommu_sva {
struct iommu_attach_handle handle;
struct device *dev;
refcount_t users;
};

16.6.4 SVA 操作函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/* 绑定设备到进程地址空间 */
struct iommu_sva *iommu_sva_bind_device(struct device *dev,
struct mm_struct *mm);
void iommu_sva_unbind_device(struct iommu_sva *handle);

/* 获取 PASID */
u32 iommu_sva_get_pasid(struct iommu_sva *handle);

/* PASID 附加/分离 (扩展) */
int iommu_attach_device_pasid(struct iommu_domain *domain,
struct device *dev, ioasid_t pasid,
struct iommu_attach_handle *handle);
void iommu_detach_device_pasid(struct iommu_domain *domain,
struct device *dev, ioasid_t pasid);

/* 全局 PASID 分配 */
ioasid_t iommu_alloc_global_pasid(struct device *dev);
void iommu_free_global_pasid(ioasid_t pasid);

16.6.5 mm_struct PASID 操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
/* 初始化 PASID */
static inline void mm_pasid_init(struct mm_struct *mm)
{
/*
* 在 dup_mm() 期间,新的 mm 会从旧 mm memcpy,这会导致新的 mm
* 和旧 mm 指向同一个 iommu_mm 实例。当其中一个释放时,
* iommu_mm 实例被释放,导致另一个 mm 出现 use-after-free/double-free。
* 因此需要将新 mm 的 iommu_mm 指针清零。
*/
mm->iommu_mm = NULL;
}

/* 检查 PASID 是否有效 */
static inline bool mm_valid_pasid(struct mm_struct *mm)
{
return READ_ONCE(mm->iommu_mm);
}

/* 获取 ENQCMD PASID */
static inline u32 mm_get_enqcmd_pasid(struct mm_struct *mm)
{
struct iommu_mm_data *iommu_mm = READ_ONCE(mm->iommu_mm);
if (!iommu_mm)
return IOMMU_PASID_INVALID;
return iommu_mm->pasid;
}

/* 释放 PASID */
void mm_pasid_drop(struct mm_struct *mm);

16.7 IOMMU Fault 处理

16.7.1 IOMMU Fault 类型

1
2
3
4
5
6
7
8
9
10
/* 通用 fault 类型,可扩展为 IRQ 重映射 fault */
enum iommu_fault_type {
IOMMU_FAULT_PAGE_REQ = 1, /* 页请求 fault */
};

/* Fault 权限 */
#define IOMMU_FAULT_PERM_READ (1 << 0) /* 读 */
#define IOMMU_FAULT_PERM_WRITE (1 << 1) /* 写 */
#define IOMMU_FAULT_PERM_EXEC (1 << 2) /* 执行 */
#define IOMMU_FAULT_PERM_PRIV (1 << 3) /* 特权 */

16.7.2 IOMMU Fault 结构

位置: include/linux/iommu.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
/**
* struct iommu_fault_page_request - 页请求数据
* @flags: 编码字段有效性和是否为组中最后一页
* @pasid: 进程地址空间 ID
* @grpid: 页请求组索引
* @perm: 请求的页权限 (IOMMU_FAULT_PERM_*)
* @addr: 页地址
* @private_data: 设备特定私有信息
*/
struct iommu_fault_page_request {
#define IOMMU_FAULT_PAGE_REQUEST_PASID_VALID (1 << 0)
#define IOMMU_FAULT_PAGE_REQUEST_LAST_PAGE (1 << 1)
#define IOMMU_FAULT_PAGE_RESPONSE_NEEDS_PASID (1 << 2)
u32 flags;
u32 pasid;
u32 grpid;
u32 perm;
u64 addr;
u64 private_data[2];
};

/**
* struct iommu_fault - 通用 fault 数据
* @type: fault 类型
* @prm: 当 @type 为 %IOMMU_FAULT_PAGE_REQ 时的页请求消息
*/
struct iommu_fault {
u32 type;
struct iommu_fault_page_request prm;
};

16.7.3 页响应代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/**
* enum iommu_page_response_code - fault 处理器返回状态
* @IOMMU_PAGE_RESP_SUCCESS: fault 已处理,页表已填充,重试访问
* @IOMMU_PAGE_RESP_FAILURE: 一般错误,删除后续 fault
* @IOMMU_PAGE_RESP_INVALID: 无法处理此 fault,不重试访问
*/
enum iommu_page_response_code {
IOMMU_PAGE_RESP_SUCCESS = 0,
IOMMU_PAGE_RESP_INVALID,
IOMMU_PAGE_RESP_FAILURE,
};

/**
* struct iommu_page_response - 通用页响应信息
* @pasid: 进程地址空间 ID
* @grpid: 页请求组索引
* @code: 响应代码
*/
struct iommu_page_response {
u32 pasid;
u32 grpid;
u32 code;
};

16.7.4 IOPF (I/O Page Fault) 队列

位置: include/linux/iommu.h:139

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
/**
* struct iopf_group - I/O Page Fault 组
* @last_fault: 组中最后一个 fault
* @faults: 此组的 fault 列表
* @fault_count: fault 数量
* @pending_node: iommu_fault_param::faults 的链表节点
* @work: fault 处理工作
* @attach_handle: 附加句柄
* @fault_param: 设备的 fault 数据参数
* @node: 提供者用于在自己的列表上挂钩组的节点
* @cookie: 组标识符
*/
struct iopf_group {
struct iopf_fault last_fault;
struct list_head faults;
size_t fault_count;
struct list_head pending_node;
struct work_struct work;
struct iommu_attach_handle *attach_handle;
struct iommu_fault_param *fault_param;
struct list_head node;
u32 cookie;
};

/**
* struct iopf_fault - IO Page Fault
* @fault: IOMMU fault 数据
* @list: 待处理列表的节点
*/
struct iopf_fault {
struct iommu_fault fault;
struct list_head list;
};

/**
* struct iopf_queue - IO Page Fault 队列
* @wq: fault 工作队列
* @devices: 附加到此队列的设备
* @lock: 保护设备列表
*/
struct iopf_queue {
struct workqueue_struct *wq;
struct list_head devices;
struct mutex lock;
};

16.7.5 IOPF 队列操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/* 分配/释放队列 */
struct iopf_queue *iopf_queue_alloc(const char *name);
void iopf_queue_free(struct iopf_queue *queue);

/* 设备管理 */
int iopf_queue_add_device(struct iopf_queue *queue, struct device *dev);
void iopf_queue_remove_device(struct iopf_queue *queue, struct device *dev);
int iopf_queue_flush_dev(struct device *dev);

/* fault 处理 */
int iommu_report_device_fault(struct device *dev, struct iopf_fault *evt);
void iopf_group_response(struct iopf_group *group,
enum iommu_page_response_code status);
void iopf_free_group(struct iopf_group *group);

/* 部分 fault 处理 */
int iopf_queue_discard_partial(struct iopf_queue *queue);

16.8 DMA-IOMMU 实现

16.8.1 DMA-IOMMU 概述

DMA-IOMMU 层为通用 DMA API 提供后端支持,自动管理设备的 IOMMU 域和 IOVA 地址空间。

位置: drivers/iommu/dma-iommu.c

架构:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
┌─────────────────────────────────────────────────────────────────┐
│ 设备驱动 │
│ dma_map_single() / dma_map_sg() / dma_alloc_coherent() │
└─────────────────────────────────────────────────────────────────┘


┌─────────────────────────────────────────────────────────────────┐
│ DMA 映射层 │
│ (drivers/iommu/dma-iommu.c) │
└─────────────────────────────────────────────────────────────────┘


┌─────────────────────────────────────────────────────────────────┐
│ IOMMU 核心 │
│ (drivers/iommu/iommu.c) │
└─────────────────────────────────────────────────────────────────┘


┌─────────────────────────────────────────────────────────────────┐
│ IOMMU 硬件驱动 │
│ Intel VT-d / AMD-Vi / ARM SMMU / virtio-iommu │
└─────────────────────────────────────────────────────────────────┘

16.8.2 DMA-IOMMU 核心函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
/* DMA 域分配 */
struct iommu_domain *iommu_dma_domain_alloc(struct bus_type *bus);
int iommu_dma_init_domain(struct iommu_domain *domain, dma_addr_t base,
u64 size, struct device *dev);

/* MSI cookie */
int iommu_get_msi_cookie(struct iommu_domain *domain, dma_addr_t base);

/* scatter-gather 映射 */
int iommu_dma_map_sg(struct device *dev, struct scatterlist *sg,
int nents, enum dma_data_direction dir,
unsigned long attrs);
void iommu_dma_unmap_sg(struct device *dev, struct scatterlist *sg,
int nents, enum dma_data_direction dir,
unsigned long attrs);

/* 单页映射 */
dma_addr_t iommu_dma_map_page(struct device *dev, struct page *page,
unsigned long offset, size_t size,
enum dma_data_direction dir,
unsigned long attrs);
void iommu_dma_unmap_page(struct device *dev, dma_addr_t dma_handle,
size_t size, enum dma_data_direction dir,
unsigned long attrs);

/* 一致性内存 */
void *iommu_dma_alloc_coherent(struct device *dev, size_t size,
dma_addr_t *dma_handle, gfp_t gfp,
unsigned long attrs);
void iommu_dma_free_coherent(struct device *dev, size_t size,
void *cpu_addr, dma_addr_t dma_handle,
unsigned long attrs);

16.8.3 IOVA 管理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/* IOVA 域 */
struct iova_domain {
struct rbroot rbroot; /* IOVA 红黑树 */
spinlock_t iova_rbtree_lock; /* 树锁 */
unsigned long granule; /* IOVA 粒度 */
unsigned long start_pfn; /* 起始 PFN */
unsigned long dma_32bit_pfn; /* 32 位 DMA 限制 */
struct iova_rcache *rcaches; /* 缓存 */
};

/* IOVA 分配 */
struct iova *alloc_iova(struct iova_domain *iovad, unsigned long size,
unsigned long limit_pfn,
bool size_aligned);
void free_iova(struct iova_domain *iovad, struct iova *iova);

/* IOVA 缓存 */
void init_iova_rcaches(struct iova_domain *iovad);
void free_iova_rcaches(struct iova_domain *iovad);
void put_iova_domain(struct iova_domain *iovad);

16.9 IOMMU 保留区域

16.9.1 保留区域类型

位置: include/linux/iommu.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/* 保留区域类型 */
enum iommu_resv_type {
/* 必须始终 1:1 映射的内存区域 */
IOMMU_RESV_DIRECT,
/*
* 广告为 1:1 但在某些条件下通常被认为是可放宽的内存区域,
* 例如在设备分配用例中 (USB, Graphics)
*/
IOMMU_RESV_DIRECT_RELAXABLE,
/* 任意的 "永不映射或给设备" 的地址范围 */
IOMMU_RESV_RESERVED,
/* 硬件 MSI 区域 (未翻译) */
IOMMU_RESV_MSI,
/* 软件管理的 MSI 翻译窗口 */
IOMMU_RESV_SW_MSI,
};

16.9.2 iommu_resv_region 结构

位置: include/linux/iommu.h:285

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/**
* struct iommu_resv_region - 保留内存区域描述符
* @list: 链表指针
* @start: 区域的系统物理起始地址
* @length: 区域长度 (字节)
* @prot: IOMMU 保护标志 (READ/WRITE/...)
* @type: 保留区域的类型
* @free: 释放关联内存分配的回调
*/
struct iommu_resv_region {
struct list_head list;
phys_addr_t start;
size_t length;
int prot;
enum iommu_resv_type type;
void (*free)(struct device *dev, struct iommu_resv_region *region);
};

16.9.3 保留区域操作

1
2
3
4
5
6
7
8
9
10
/* 获取/释放保留区域 */
extern void iommu_get_resv_regions(struct device *dev, struct list_head *list);
extern void iommu_put_resv_regions(struct device *dev, struct list_head *list);
extern int iommu_get_group_resv_regions(struct iommu_group *group,
struct list_head *head);

/* 分配保留区域 */
extern struct iommu_resv_region *
iommu_alloc_resv_region(phys_addr_t start, size_t length, int prot,
enum iommu_resv_type type, gfp_t gfp);

16.10 IOMMU 设备特性

16.10.1 设备特性枚举

位置: include/linux/iommu.h

1
2
3
4
5
6
7
8
9
10
11
/**
* enum iommu_dev_features - 每设备 IOMMU 特性
* @IOMMU_DEV_FEAT_SVA: 共享虚拟地址
* @IOMMU_DEV_FEAT_IOPF: I/O Page Fault,如 PRI 或 Stall
*
* 设备驱动使用 iommu_dev_enable_feature() 启用特性
*/
enum iommu_dev_features {
IOMMU_DEV_FEAT_SVA,
IOMMU_DEV_FEAT_IOPF,
};

16.10.2 设备特性操作

1
2
3
4
5
6
/* 启用/禁用设备特性 */
int iommu_dev_enable_feature(struct device *dev, enum iommu_dev_features f);
int iommu_dev_disable_feature(struct device *dev, enum iommu_dev_features f);

/* IOMMU 能力查询 */
bool device_iommu_capable(struct device *dev, enum iommu_cap cap);

16.10.3 IOMMU 能力

1
2
3
4
5
6
7
8
enum iommu_cap {
IOMMU_CAP_CACHE_COHERENCY, /* 支持 IOMMU_CACHE */
IOMMU_CAP_NOEXEC, /* 支持 IOMMU_NOEXEC */
IOMMU_CAP_PRE_BOOT_PROTECTION, /* 固件使用的保护 */
IOMMU_CAP_ENFORCE_CACHE_COHERENCY, /* enforce_cache_coherency() 有效 */
IOMMU_CAP_DEFERRED_FLUSH, /* 支持延迟 flush */
IOMMU_CAP_DIRTY_TRACKING, /* 支持脏页跟踪 */
};

16.11 IOMMU 脏页跟踪

16.11.1 脏页跟踪概述

IOMMU 脏页跟踪用于虚拟化场景,记录设备通过 IOMMU 修改的页面,用于迁移或增量备份。

16.11.2 iommu_dirty_ops

位置: include/linux/iommu.h:371

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
* struct iommu_dirty_ops - 域特定脏页跟踪操作
* @set_dirty_tracking: 启用/禁用 iommu 域上的脏页跟踪
* @read_and_clear_dirty: 遍历 IOMMU 页表查找脏 PTE,
* 将其编组到位图中,位表示一个页。
* 读取脏 PTE 位并从 IO 页表中清除。
*/
struct iommu_dirty_ops {
int (*set_dirty_tracking)(struct iommu_domain *domain, bool enabled);
int (*read_and_clear_dirty)(struct iommu_domain *domain,
unsigned long iova, size_t size,
unsigned long flags,
struct iommu_dirty_bitmap *dirty);
};

16.11.3 iommu_dirty_bitmap 结构

位置: include/linux/iommu.h:358

1
2
3
4
5
6
7
8
9
/**
* struct iommu_dirty_bitmap - 脏 IOVA 位图状态
* @bitmap: IOVA 位图
* @gather: 待刷新 IOTLB 范围信息
*/
struct iommu_dirty_bitmap {
struct iova_bitmap *bitmap;
struct iommu_iotlb_gather *gather;
};

16.11.4 脏页跟踪操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/* 初始化脏页位图 */
static inline void iommu_dirty_bitmap_init(struct iommu_dirty_bitmap *dirty,
struct iova_bitmap *bitmap,
struct iommu_iotlb_gather *gather)
{
if (gather)
iommu_iotlb_gather_init(gather);
dirty->bitmap = bitmap;
dirty->gather = gather;
}

/* 记录脏页 */
static inline void iommu_dirty_bitmap_record(struct iommu_dirty_bitmap *dirty,
unsigned long iova,
unsigned long length)
{
if (dirty->bitmap)
iova_bitmap_set(dirty->bitmap, iova, length);
if (dirty->gather)
iommu_iotlb_gather_add_range(dirty->gather, iova, length);
}

16.12 嵌套 IOMMU (虚拟化)

16.12.1 嵌套 IOMMU 概述

嵌套 IOMMU 用于虚拟化场景,允许客户机 (Guest) 管理自己的 IOMMU 页表,形成两级地址转换:

1
2
3
4
5
GVA (Guest Virtual Address)
↓ (Guest Page Table)
GPA (Guest Physical Address)
↓ (Nested IOMMU + Host Stage-2)
HPA (Host Physical Address)

16.12.2 嵌套域操作

1
2
3
4
5
6
7
8
9
10
11
/* 启用嵌套 */
int iommu_enable_nesting(struct iommu_domain *domain);

/* 用户分配域 (支持嵌套) */
struct iommu_domain *(*domain_alloc_user)(
struct device *dev, u32 flags, struct iommu_domain *parent,
const struct iommu_user_data *user_data);

/* 用户缓存失效 */
int (*cache_invalidate_user)(struct iommu_domain *domain,
struct iommu_user_data_array *array);

16.13 IOMMU 调试和监控

16.13.1 IOMMU DebugFS

1
2
3
4
#ifdef CONFIG_IOMMU_DEBUGFS
extern struct dentry *iommu_debugfs_dir;
void iommu_debugfs_setup(void);
#endif

16.13.2 IOMMU 跟踪事件

IOMMU 提供了多个跟踪点用于调试:

1
2
3
4
5
6
7
8
# 查看 IOMMU 跟踪事件
trace-cmd list | grep iommu

# 常见跟踪点:
# iommu_map - IOVA 映射
# iommu_unmap - IOVA 解除映射
# iommu_attach_device - 设备附加
# iommu_detach_device - 设备分离

16.14 架构特定实现

16.14.1 Intel VT-d

驱动位置: drivers/iommu/intel/

核心文件:

  • iommu.c - 核心 IOMMU 实现
  • dmar.c - DMAR (DMA Remapping) 表解析
  • pasid.c - PASID 管理
  • svm.c - Shared Virtual Memory (SVM) 支持

关键特性:

  • 支持 5 级页表
  • Scalable Mode (Scalable Mode Translation)
  • PASID 支持 (最多 2^20 - 1 个 PASID)
  • PRS (Page Request Services) for IOPF

16.14.2 AMD-Vi

驱动位置: drivers/iommu/amd/

核心文件:

  • iommu.c - 核心 IOMMU 实现
  • init.c - 初始化
  • pasid.c - PASID 管理
  • io_pgtable.c - IO 页表实现

关键特性:

  • 支持 IOMMU v1 和 v2
  • GDTR (Guest DTE Root) for nested
  • PPR (Peripheral Page Request) for IOPF

16.14.3 ARM SMMU v3

驱动位置: drivers/iommu/arm/arm-smmu-v3/

核心文件:

  • arm-smmu-v3.c - SMMUv3 驱动
  • arm-smmu-v3-sva.c - SVA 实现

关键特性:

  • 支持两阶段转换 (Stage-1 + Stage-2)
  • SVA (SMMUv3.1+)
  • 支持 PCIe 和平台设备
  • 最多 2^20 - 1 PASID

16.15 本章小结

本章介绍了 Linux 6.12 的 IOMMU 子系统:

核心概念

  1. IOMMU 基础: DMA 地址转换、内存隔离、scatter-gather 支持
  2. 核心结构: iommu_device、iommu_domain、iommu_group、dev_iommu
  3. 域类型: BLOCKED、IDENTITY、UNMANAGED、DMA、DMA_FQ、SVA、NESTED

主要功能

  1. 域操作: 分配/释放域、设备附加/分离、映射/解除映射
  2. IOTLB 管理: TLB 刷新、批量失效
  3. SVA: 共享虚拟地址、PASID 支持、设备-mm 绑定
  4. Fault 处理: IOPF 队列、fault 响应

高级特性

  1. DMA-IOMMU: DMA API 的 IOMMU 后端实现、IOVA 管理
  2. 脏页跟踪: 虚拟化场景的页面修改跟踪
  3. 嵌套 IOMMU: 虚拟化两级地址转换

架构支持

  1. Intel VT-d: DMAR、PASID、SVM
  2. AMD-Vi: IOMMU v1/v2、PPR
  3. ARM SMMU v3: 两阶段转换、SVA

相关章节:

  • 第4章: 虚拟地址空间 - mm_struct 结构
  • 第5章: 页表管理 - CPU MMU 页表
  • 第6章: 虚拟内存区域 (VMA) - VMA 管理
  • 第9章: 缺页异常处理 - CPU 页面 fault
  • Title: Linux内核分析之内存管理-16
  • Author: 韩乔落
  • Created at : 2026-01-20 21:39:59
  • Updated at : 2026-02-24 14:05:09
  • Link: https://jelasin.github.io/2026/01/20/Linux内核分析之内存管理-16/
  • License: This work is licensed under CC BY-NC-SA 4.0.
Comments
On this page
Linux内核分析之内存管理-16