IOMMU源码分析
相关参考
- Intel IOMMU Introduction
- DMAR表组织结构
- Intel® Virtualization Technology for Directed I/O (官方文档)
- Intel-IOMMU.txt 内核文档
- iommu & intel-iommu实现 和 intel IOMMU driver analysis
相关类图
本文目标
- 理解Intel下IOMMU实现的原理,配合Intel® Virtualization Technology for Directed I/O 的代码实现。
- 设备是如何分到各个IOMMU_GROUP上的?
- 有了IOMMU的后,DMA操作的接口发生了什么概变?
- 解答 Intel IOMMU Introduction 中提到的问题。
- 有了IOMMU后,中断的处理是如何分发和获取的?
基础知识
关于DMAR的相关寄存器
在学习 10th Generation Intel® Core™ Processors Datasheet Volume 2 of 2 中我们了解了地址表信息
baiy@baiy-ThinkPad-E470c:testacpi$ sudo cat /proc/iomem # 参考
......
fed90000-fed90fff : dmar0
fed91000-fed91fff : dmar1
fee00000-fee00fff : Local APIC
.....
baiy@baiy-ThinkPad-E470c:testacpi$ cat dmar.dsl # 参考VT-d 第8章
......
[030h 0048 2] Subtable Type : 0000 [Hardware Unit Definition]
[032h 0050 2] Length : 0018
[034h 0052 1] Flags : 00
[035h 0053 1] Reserved : 00
[036h 0054 2] PCI Segment Number : 0000 ### PCI的domain地址
[038h 0056 8] Register Base Address : 00000000FED90000
......
[048h 0072 2] Subtable Type : 0000 [Hardware Unit Definition]
[04Ah 0074 2] Length : 0020
[04Ch 0076 1] Flags : 01
[04Dh 0077 1] Reserved : 00
[04Eh 0078 2] PCI Segment Number : 0000 ### PCI的domain地址
[050h 0080 8] Register Base Address : 00000000FED91000 # 参考VT-d 10章
......
中间有部分存储了dmar的地址(这里是00000000FED90000 和 00000000FED91000) 与ACPI的硬件地址一致,这个地址映射了VT-d的相关寄存器
这些寄存器描述在 : Intel® Virtualization Technology for Directed I/O 第10章
在 Intel® Virtualization Technology for Directed I/O 第8章 描述了DMAR;配合 DMAR表组织结构 进行学习
The system BIOS is responsible for detecting the remapping hardware functions in the platform and
for locating the memory-mapped remapping hardware registers in the host system address space.
The BIOS reports the remapping hardware units in a platform to system software through the DMA
Remapping Reporting (DMAR) ACPI table described below系统BIOS负责检测平台中硬件功能的映射 和 将硬件寄存器映射系统地址空间(物理地址)。BIOS通过DMAR ACPI表将这些信息传递给操作系统
代码汇总
IOMMU_TABLE
注:Linux 解析ACPI表的方法非常巧妙, 将所有解析表封装成一个函数,然后注册一个回调接口,全部完成;666
// arch/x86/include/asm/iommu_table.h
struct iommu_table_entry {
initcall_t detect;
initcall_t depend;
void (*early_init)(void); /* No memory allocate available. */
void (*late_init)(void); /* Yes, can allocate memory. */
#define IOMMU_FINISH_IF_DETECTED (1<<0)
#define IOMMU_DETECTED (1<<1)
int flags;
};
该结构体:系统在初始化时,通过 __IOMMU_INIT 系列宏去注册一些列在 ”.iommu_table“段的结构体
然后在 ./arch/x86/mm/init_64.c 中
mem_init
pci_iommu_alloc();
// 在__IOMMU_INIT宏注释中说明:添加到这个表的字段需要系统去进行排序
// 根据depend依赖关系去排序,然后分别初始化
sort_iommu_table(__iommu_table, __iommu_table_end);
for (p = __iommu_table; p < __iommu_table_end; p++) { // 去遍历排序后的表,分别初始化
if (p && p->detect && p->detect() > 0) { // 调用detect接口 ******
p->flags |= IOMMU_DETECTED;
if (p->early_init) p->early_init(); // 调用early_init接口,这里未实现
IOMMU_INIT_POST(detect_intel_iommu);
detect_intel_iommu
dmar_table_detect(); // 会从acpi获取dmar信息, ACPI的不了解,可以用系统中打印出acpi表
dmar_walk_dmar_table((struct acpi_table_dmar *)dmar_tbl,
&validate_drhd_cb);
dmar_walk_remapping_entries // 这个函数从ACPI表中找出了对应回调接口,然后调用回调
// // 遍历时,只有ACPI_DMAR_TYPE_HARDWARE_UNIT类型有回调接口,调用回调去初始化
cb->cb[iter->type](iter, cb->arg[iter->type]);
dmar_validate_one_drhd // ACPI_DMAR_TYPE_HARDWARE_UNI 的回调函数
只是检测下 是否是支持VT-D的有效的DRHD,cap和ecap至少要支持一个。
pci_request_acs(); -> pci_acs_enable = 1;
x86_init.iommu.iommu_init = intel_iommu_init; // 重点接口
acpi_put_table(dmar_tbl); // dmar_table_detect()的释放ACPI接口
注:打印dmar的acpi表信息(详细可参考 ACPI)
sudo apt-get install -y iasl acpica-tools
mkdir -p testacpi && cd testacpi
acpidump > acpidump.out # 将ACPI表二进制打印到文件
acpixtract -a acpidump.out # 解析acpi表,生成各个dat文件
iasl -d dmar.dat # iasl会解析acpi 二进制表,生成xxx.dsl描述文件
dmar表的获取-dmar_tbl全局变量(重点)
dmar_table_detect
|- status = acpi_get_table(ACPI_SIG_DMAR, 0, &dmar_tbl);
// DMAR.dsl
[000h 0000 4] Signature : "DMAR" [DMA Remapping table]
[004h 0004 4] Table Length : 000001E0
[008h 0008 1] Revision : 01
[009h 0009 1] Checksum : 5A
[00Ah 0010 6] Oem ID : "INTEL "
[010h 0016 8] Oem Table ID : "S2600WF "
[018h 0024 4] Oem Revision : 00000001
[01Ch 0028 4] Asl Compiler ID : "INTL"
[020h 0032 4] Asl Compiler Revision : 20091013
[024h 0036 1] Host Address Width : 2D
[025h 0037 1] Flags : 03
[026h 0038 10] Reserved : 00 00 00 00 00 00 00 00 00 00
这个刚好对应 全局变量 dmar_tbl 中的信息 ***********
/*******************************************************************************
*
* Master ACPI Table Header. This common header is used by all ACPI tables
* except the RSDP and FACS.
*
******************************************************************************/
struct acpi_table_header {
char signature[ACPI_NAME_SIZE]; /* ASCII table signature */
u32 length; /* Length of table in bytes, including this header */
u8 revision; /* ACPI Specification minor version number */
u8 checksum; /* To make sum of entire table == 0 */
char oem_id[ACPI_OEM_ID_SIZE]; /* ASCII OEM identification */
char oem_table_id[ACPI_OEM_TABLE_ID_SIZE]; /* ASCII OEM table identification */
u32 oem_revision; /* OEM revision number */
char asl_compiler_id[ACPI_NAME_SIZE]; /* ASCII ASL compiler vendor ID */
u32 asl_compiler_revision; /* ASL compiler version */
};
struct acpi_table_dmar {
struct acpi_table_header header; /* Common ACPI table header */
u8 width; /* Host Address Width */
u8 flags;
u8 reserved[10];
};
struct acpi_table_header * __initdata dmar_tbl;
dmar表解析 dmar_validate_one_drhd
配合附录中 ACPI的DMAR数据 和 DMAR表组织结构 进行查看学习
这部分主要看: DRHD(DMA Remapping Hardware Unit Definition) 表
DRHD表主要包括两方面的信息,
一是提供VT-d重定向硬件寄存器基地址,为系统软件访问VT-d硬件寄存器提供入口(各个偏移量所指向的具体寄存器在VT-d的spec中有详细的约定,即VT-d硬件的具体实现);
另一个是该VT-d重定向硬件所管辖的硬件,由Segment Number和Device Scope两个区域来定义。Device Scope结构体由Device Scope Entry组成,每个Device Scope Entry可以用来指明一个PCI endpoint device,一个PCI sub-hierarchy,或者其他设备,如I/O xAPIC或者HPET。
struct acpi_dmar_header {
u16 type;
u16 length;
};
struct acpi_dmar_hardware_unit { // DRHD表结构体
struct acpi_dmar_header header;
u8 flags;
u8 reserved;
u16 segment;
u64 address; /* Register Base Address */
};
/* 判断ACPI是否枚举出dmar的drhd配置, 且是否有效 */
/* 检查下 VT-d重定向硬件寄存器基地址(参考基础知识-DMAR寄存器)的cap或ecap寄存器是否存在 */
dmar_validate_one_drhd(entry = ACPI_DMAR_TYPE_HARDWARE_UNI对应ACPI表信息, args=NULL)
addr = early_ioremap(drhd->address, VTD_PAGE_SIZE);
cap = dmar_readq(addr + DMAR_CAP_REG);
ecap = dmar_readq(addr + DMAR_ECAP_REG);
early_iounmap(addr, VTD_PAGE_SIZE);
check....
入口部分
./arch/x86/kernel/pci-dma.c
iommu_setup // grub参数
rootfs_initcall(pci_iommu_init);
pci_iommu_init
dma_debug_add_bus(&pci_bus_type);
x86_init.iommu.iommu_init(); => intel_iommu_init
/* 这个IOMMU_INIT_POST(detect_intel_iommu) 未实现late_init,不用看 */
/*
for (p = __iommu_table; p < __iommu_table_end; p++) {
if (p && (p->flags & IOMMU_DETECTED) && p->late_init)
p->late_init(); // 未实现
}
*/
初始化部分
Copy Intel IOMMU Introduction
intel_iommu_init
|-> dmar_table_init -> parse_dmar_table -> dmar_walk_dmar_table //重点分析
|-> dmar_dev_scope_init
|-> dmar_acpi_dev_scope_init -> dmar_acpi_insert_dev_scope //重点分析
|-> dmar_pci_bus_add_dev -> dmar_insert_dev_scope
|-> bus_register_notifier
|-> dmar_init_reserved_ranges // init RMRR
|-> init_no_remapping_devices // init no remapping devices
|-> init_dmars //重点分析
|-> dma_ops = &intel_dma_ops
|-> iommu_device_sysfs_add, iommu_device_set_ops, iommu_device_register
|-> bus_set_iommu(&pci_bus_type, &intel_iommu_ops)
|-> bus_register_notifier(&pci_bus_type, &device_nb)
dmar_table_init-解析DMAR的ACPI表
关于 dmar_table_init -> parse_dmar_table -> dmar_walk_dmar_table 这条路和IOMMU TABLE部分解析方式一样,这里不具体分析代码
// 用acpi去回调这些所有的接口
struct dmar_res_callback cb = {
.print_entry = true,
.ignore_unhandled = true,
.arg[ACPI_DMAR_TYPE_HARDWARE_UNIT] = &drhd_count,
.cb[ACPI_DMAR_TYPE_HARDWARE_UNIT] = &dmar_parse_one_drhd,
.cb[ACPI_DMAR_TYPE_RESERVED_MEMORY] = &dmar_parse_one_rmrr,
.cb[ACPI_DMAR_TYPE_ROOT_ATS] = &dmar_parse_one_atsr,
.cb[ACPI_DMAR_TYPE_HARDWARE_AFFINITY] = &dmar_parse_one_rhsa,
.cb[ACPI_DMAR_TYPE_NAMESPACE] = &dmar_parse_one_andd,
};
// https://www.intel.com/content/dam/www/public/us/en/documents/guides/txt-enabling-guide.pdf
dmar_tbl = tboot_get_dmar_table(dmar_tbl);
// 和IOMMU_TABLE一样,都是获取ACPI,去调用各个回调接口
dmar_table_detect();
ret = dmar_walk_dmar_table(dmar, &cb);
dmar_parse_one_drhd 函数 (重点)
调试:内核启动过程中打印 pr_info(“DMAR dmar0/1: reg_base_addr %llx ver %d:%d cap %llx ecap %llx\n”) 就是在这个函数中调用了 alloc_iommu 的结果
dmar_drhd_units全局存 dmar_drhd_unit 的结构体
# 先看一个drhd全不结构:
================== header===================================
[030h 0048 2] Subtable Type : 0000 [Hardware Unit Definition]
[032h 0050 2] Length : 0018
[034h 0052 1] Flags : 00
[035h 0053 1] Reserved : 00
[036h 0054 2] PCI Segment Number : 0000
[038h 0056 8] Register Base Address : 00000000FED90000
===================end header=================================
===================dev scope==============================
[040h 0064 1] Device Scope Type : 01 [PCI Endpoint Device]
[041h 0065 1] Entry Length : 08
[042h 0066 2] Reserved : 0000
[044h 0068 1] Enumeration ID : 00
[045h 0069 1] PCI Bus Number : 00
[046h 0070 2] PCI Path : 02,00
===================dev scope==============================
struct acpi_dmar_hardware_unit {
struct acpi_dmar_header header;
u8 flags;
u8 reserved;
u16 segment;
u64 address; /* Register Base Address */
}drhd;
struct dmar_drhd_unit {
struct list_head list; /* list of drhd units */
struct acpi_dmar_header *hdr; /* ACPI header */
u64 reg_base_addr; /* register base address*/
struct dmar_dev_scope *devices;/* target device array */
int devices_cnt; /* target device count */
u16 segment; /* PCI domain */
u8 ignored:1; /* ignore drhd */
u8 include_all:1;
u8 gfx_dedicated:1; /* graphic dedicated */
struct intel_iommu *iommu;
}dmaru;
// drivers/iommu/intel/dmar.c
dmar_parse_one_drhd(DRHD, &drhd_count)
drhd = (struct acpi_dmar_hardware_unit *)header // drhd 是 ACPI表中DRHD的地址
dmaru = dmar_find_dmaru(drhd); //dmar_drhd_units全局存drhd的链表,此时未初始化=false
// 分配一个dmaru描述 + DRHD大小,并将DRHD结构 拷贝到dmaru结构体底部
dmaru = kzalloc(sizeof(*dmaru) + header->length, GFP_KERNEL);
..... // init dmar unit结构
|->dmaru->devices = dmar_alloc_dev_scope // 解析 ACPI中DMAR的DRHD dev scope
|->alloc_iommu(dmaru) // *** 重点,每一个dmaru都分配一套struct intel_iommu的结构
struct intel_iommu *iommu; //
map_iommu(iommu, drhd->reg_base_addr); // 映射VT-D中的寄存器表
dmaru->iommu = iommu; // 这就是下边UML图
|->dmar_register_drhd_unit(dmaru)
dmar_drhd_units 全局链表中添加自己的dmaru结构
调试小知识:
seq_id是个bitmap,其实对应就是dmar%d。 在alloc_iommu->dmar_alloc_seq_id 中去赋值的 sprintf(iommu->name, “dmar%d”, iommu->seq_id); 然后后边有打印: reg_base_addr就是 每一个dmar对应VT-D的寄存器表基地址,cap和ecap的值是VT-D中寄存器的值。
[ 0.136135] DMAR: DRHD base: 0x000000d37fc000 flags: 0x0 [ 0.136143] DMAR: dmar0: reg_base_addr d37fc000 ver 1:0 cap 8d2078c106f0466 ecap f020de [ 0.136144] DMAR: DRHD base: 0x000000e0ffc000 flags: 0x0 [ 0.136149] DMAR: dmar1: reg_base_addr e0ffc000 ver 1:0 cap 8d2078c106f0466 ecap f020de [ 0.136150] DMAR: DRHD base: 0x000000ee7fc000 flags: 0x0 [ 0.136155] DMAR: dmar2: reg_base_addr ee7fc000 ver 1:0 cap 8d2078c106f0466 ecap f020de [ 0.136156] DMAR: DRHD base: 0x000000fbffc000 flags: 0x0 [ 0.136160] DMAR: dmar3: reg_base_addr fbffc000 ver 1:0 cap 8d2078c106f0466 ecap f020de [ 0.136161] DMAR: DRHD base: 0x000000aaffc000 flags: 0x0 [ 0.136167] DMAR: dmar4: reg_base_addr aaffc000 ver 1:0 cap 8d2078c106f0466 ecap f020de [ 0.136168] DMAR: DRHD base: 0x000000b87fc000 flags: 0x0 [ 0.136172] DMAR: dmar5: reg_base_addr b87fc000 ver 1:0 cap 8d2078c106f0466 ecap f020de [ 0.136173] DMAR: DRHD base: 0x000000c5ffc000 flags: 0x0 [ 0.136176] DMAR: dmar6: reg_base_addr c5ffc000 ver 1:0 cap 8d2078c106f0466 ecap f020de [ 0.136177] DMAR: DRHD base: 0x0000009d7fc000 flags: 0x1 [ 0.136181] DMAR: dmar7: reg_base_addr 9d7fc000 ver 1:0 cap 8d2078c106f0466 ecap f020de
dmar_parse_one_rmrr
RMRR(Reserved Memory Region Reporting)表 :RMRR表用于表示BIOS或者UEFI为了DMA的使用而保留的一些系统物理内存,这些内存从操作系统的角度来看其属性为Reserved Memory,因为有一些比较传统的设备(比如USB、UMA显卡等)可能会需要用到一些固定的,或者专用的系统内存,这时候就需要BIOS或UEFI为其保留。
全局链表 dmar_rmrr_units 存放了ACPI中的 acpi_dmar_reserved_memory 预留内存单元结构体
[068h 0104 2] Subtable Type : 0001 [Reserved Memory Region]
[06Ah 0106 2] Length : 0020
[06Ch 0108 2] Reserved : 0000
[06Eh 0110 2] PCI Segment Number : 0000
[070h 0112 8] Base Address : 00000000BBDE2000
[078h 0120 8] End Address (limit) : 00000000BBE01FFF
[080h 0128 1] Device Scope Type : 01 [PCI Endpoint Device]
[081h 0129 1] Entry Length : 08
[082h 0130 2] Reserved : 0000
[084h 0132 1] Enumeration ID : 00
[085h 0133 1] PCI Bus Number : 00
[086h 0134 2] PCI Path : 14,00
...
dmar_parse_one_rmrr
这部分和drhd的初始化类似,根据ACPI 初始化自己rmrr的结构,并添加到链表中
**
dmar_parse_one_atsr
dmar_parse_one_rhsa
dmar_parse_one_andd (namespace,好像可以没有)
这三个不考虑,可选项,根据 DMAR表组织结构 去分析
dmar_dev_scope_init(可以跳过)
dmar_dev_scope_init
// 判断ACPI中是否支持dmar_drhd_unit
// 从全局dmar_drhd_units链表中是否有 dmar_drhd_unit 的结构体
if (list_empty(&dmar_drhd_units))
dmar_dev_scope_status = -ENODEV;
dmar_acpi_dev_scope_init(); // *** 重要函数,但找了几台机器,都没这玩意namespace,跳过
// 给PCI总线注册 BUS_NOTIFY_ADD_DEVICE 和 BUS_NOTIFY_REMOVED_DEVICE 通知接口
// **** 这部分也很关键,设备起来后,添加驱动,都需要走这个
bus_register_notifier(&pci_bus_type, &dmar_pci_bus_nb);
dmar_init_reserved_ranges
init_dmars() 重点函数
// 初始化每个iommu,并分配root_entry
for_each_drhd_unit(drhd)
g_num_of_iommus // g_num_of_iommus = drhd 硬件dmar的个数
for_each_active_iommu(iommu, drhd) // 每个drhd都有intel_iommu对象,存放在drhd->iommu
g_iommus[iommu->seq_id] = iommu; // g_iommus 根据seq_id存放了intel_iommu对象
// 注:iommu分配和seq_id初始化在 alloc_iommu 接口实现
intel_iommu_init_qi // 参考VT-D 6.5 什么寄存器缓存啥的??? TBD
iommu_init_domains(iommu); // 重点:
ndomains = cap_ndoms(iommu->cap); // 判断当前drhd支持多少个domains, cap[2:0]表示
iommu->domain_ids; // 对支持的域建立bitmap
// 难点:三级指针, 最终格式如下边的图片
size = (ALIGN(ndomains, 256) >> 8) * sizeof(struct dmar_domain **);
iommu->domains = kzalloc(size, GFP_KERNEL);
size = 256 * sizeof(struct dmar_domain *);
iommu->domains[0] = kzalloc(size, GFP_KERNEL);
init_translation_status(iommu); // 判断GSTS_REG[30]寄存器是否使能DMAR,修改iommu->flags
translation_pre_enabled(iommu); // 同上,验证下环境
iommu_alloc_root_entry(iommu); // 给每个intel_iommu分配root_entry,大小1页大小
copy_translation_tables(iommu); // 暂时跳过,后边详解
intel_svm_alloc_pasid_tables(iommu); // 支持PASID,以后看的时候补充
iommu_set_root_entry(iommu); // 更新iommu的entry地址
iommu_init_domains 的结构描述:
copy_translation_tables 说明
这里有个说明: ecap[24] 和 Root Table Address Register[11]都为0,旧版本支持扩展的空间,新版本已不支持,直接为0
struct root_entry {
u64 lo;
u64 hi;
};
new_ext == ext == 0;
rtaddr_reg = Root Table Address Register 的值
// old_rt_phys 和 old_rt 旧的Root Table Address
old_rt_phys = rtaddr_reg & VTD_PAGE_MASK;
old_rt = memremap(old_rt_phys, PAGE_SIZE, MEMREMAP_WB);
ctxt_tbls=分配成256大小的指针数组=> void * ctxt_tbls[256]
for (bus = 0; bus < 256; bus++) {
copy_context_table(iommu, &old_rt[bus], ctxt_tbls, bus, ext);
}
set_ops
初始化操作接口
for_each_active_iommu(iommu, drhd) {
iommu_device_sysfs_add(&iommu->iommu, NULL,
intel_iommu_groups,
"%s", iommu->name);
iommu_device_set_ops(&iommu->iommu, &intel_iommu_ops);
iommu_device_register(&iommu->iommu);
}
初始化一个sysfs/classs/iommu/dmarx 接口, 并关联起相关的接口
dma_ops = &intel_dma_ops;
更新dma操作接口,dma_alloc_coherent 后期分配内存就不走通用的接口了,走这里了。
bus_set_iommu
建议先复习下 内核通知链
bus_set_iommu(&pci_bus_type, &intel_iommu_ops);
bus->iommu_ops = ops; // 给pci_bus_type注册 iommu_ops
iommu_bus_init(bus, ops);
nb->notifier_call = iommu_bus_notifier;
// 给pci_bus_type在注册一个notifier 通知链
// 内核通知链里分析过:device_add会触发一次notifier
bus_register_notifier(bus, call_func:iommu_bus_notifier);
bus_for_each_dev(bus, NULL, &cb, add_iommu_group);
对pci_bus_type下的设备都调用一次 add_iommu_group(pci_dev, &cb)
cb->ops = intel_iommu_ops
// 在给pci_bus_type注册一个notifier通知
bus_register_notifier(&pci_bus_type, call_func:device_notifier;
add_iommu_group(重点难点 )
先简单整理下代码调用关系
注:iommu & intel-iommu实现 中开始对这部分代码有了比较详细的跟踪
add_iommu_group
const struct iommu_ops *ops = cb->ops; // cb->ops = intel_iommu_ops
intel_iommu_ops->add_device(dev); // 这就是对每个PCI设备都添加组的最终操作
intel_iommu_add_device(dev)
// 根据PCI设备寻找 对应PCI总线域的DRHD的intel_iommu结构
// 根据dmar_drhd_unit找到当前device属于哪个 intel_iommu
iommu = device_to_iommu(dev, &bus, &devfn);
segment = pci_domain_nr(pdev->bus); // 每个PCI总线的domain,也就是segment
当前PCI设备的segment 肯定会落在drhd的描述中,说明该PCI设备属于匹配的drhd下
在对应drhd下找到匹配的设备,返回intel_iommu
iommu_device_link(&iommu->iommu, dev); // 将该设备链接到sysfs下
// 获取或者分配设备的iommu_group,并分配响应的iommu_domain
// *** 重点***
group = iommu_group_get_for_dev(dev);
iommu_group_get(dev); // dev->iommu_group第一次肯定为NULL,
group = ops->device_group(dev); // 调用 intel_iommu 的 pci_device_group
// 给当前group分配的 domain, 调用 intel_iommu_domain_alloc
group->domain = group->default_domain = __iommu_domain_alloc
// 上边是配合和查找group,然后当然是将设备添加到group了
iommu_group_add_device(group, dev)
// 在sysfs下创建iommu_groups
sysfs_create_link(&dev->kobj, &group->kobj, "iommu_group");
// 很关键:以后直接从dev获取group即可
dev->iommu_group = group;
// intel_iommu_attach_device 将设备挂到domain上,对其进行页表管理
__iommu_attach_device(group->domain, dev);
// 给组内兄弟喊一声:来新人了,发红包
blocking_notifier_call_chain(,IOMMU_GROUP_NOTIFY_ADD_DEVICE,);
// [关键打印信息] iommu: Adding device 0000:00:00.0 to group 0
iommu_group_put(group);
domain的分配
__iommu_domain_alloc // 初始化dmar_domain和iommu_domain
bus->iommu_ops->domain_alloc(type);
iommu_group_get_for_dev 和 iommu_group_add_device
这个函数分组关系比较复杂,具 intel IOMMU driver analysis 和 IOMMU group and PCIe ACS问题 描述
有几种情况可以从现有设备获取设备IOMMU组。 例如,如果一个网桥支持ACS,则需要转到上游总线。. 多功能设备的所有功能设备也需要共享同一IOMMU组
这部分牵扯到PCIE的ACS功能,这部分跳过,只要知道: GROUP是虚拟化分组的最小单位,所以SR-IOV设备要把pcie分到不同的分组中,才能用。
iommu_group_get_for_dev
ops->device_group(dev);
pci_device_group
pci_device_group
iommu_group_alloc(); // 这里只看创建group
iommu_group_add_device
dev->iommu_group = group;
__iommu_attach_device(group->domain, dev);
附录
附录1 Intel(R) Core(TM) i5-6200U CPU @ 2.30GHz的ACPI DMAR
baiy@baiy-ThinkPad-E470c:testacpi$ cat dmar.dsl
/*
* Intel ACPI Component Architecture
* AML/ASL+ Disassembler version 20180105 (64-bit version)
* Copyright (c) 2000 - 2018 Intel Corporation
*
* Disassembly of dmar.dat, Sun Nov 29 21:34:36 2020
*
* ACPI Data Table [DMAR]
*
* Format: [HexOffset DecimalOffset ByteLength] FieldName : FieldValue
*/
[000h 0000 4] Signature : "DMAR" [DMA Remapping table]
[004h 0004 4] Table Length : 000000A8
[008h 0008 1] Revision : 01
[009h 0009 1] Checksum : DE
[00Ah 0010 6] Oem ID : "LENOVO"
[010h 0016 8] Oem Table ID : "TP-R0D "
[018h 0024 4] Oem Revision : 00000830
[01Ch 0028 4] Asl Compiler ID : "PTEC"
[020h 0032 4] Asl Compiler Revision : 00000002
[024h 0036 1] Host Address Width : 26
[025h 0037 1] Flags : 01
[026h 0038 10] Reserved : 00 00 00 00 00 00 00 00 00 00
[030h 0048 2] Subtable Type : 0000 [Hardware Unit Definition]
[032h 0050 2] Length : 0018
[034h 0052 1] Flags : 00
[035h 0053 1] Reserved : 00
[036h 0054 2] PCI Segment Number : 0000
[038h 0056 8] Register Base Address : 00000000FED90000
[040h 0064 1] Device Scope Type : 01 [PCI Endpoint Device]
[041h 0065 1] Entry Length : 08
[042h 0066 2] Reserved : 0000
[044h 0068 1] Enumeration ID : 00
[045h 0069 1] PCI Bus Number : 00
[046h 0070 2] PCI Path : 02,00
[048h 0072 2] Subtable Type : 0000 [Hardware Unit Definition]
[04Ah 0074 2] Length : 0020
[04Ch 0076 1] Flags : 01
[04Dh 0077 1] Reserved : 00
[04Eh 0078 2] PCI Segment Number : 0000
[050h 0080 8] Register Base Address : 00000000FED91000
[058h 0088 1] Device Scope Type : 03 [IOAPIC Device]
[059h 0089 1] Entry Length : 08
[05Ah 0090 2] Reserved : 0000
[05Ch 0092 1] Enumeration ID : 02
[05Dh 0093 1] PCI Bus Number : F0
[05Eh 0094 2] PCI Path : 1F,00
[060h 0096 1] Device Scope Type : 04 [Message-capable HPET Device]
[061h 0097 1] Entry Length : 08
[062h 0098 2] Reserved : 0000
[064h 0100 1] Enumeration ID : 00
[065h 0101 1] PCI Bus Number : 00
[066h 0102 2] PCI Path : 1F,00
[068h 0104 2] Subtable Type : 0001 [Reserved Memory Region]
[06Ah 0106 2] Length : 0020
[06Ch 0108 2] Reserved : 0000
[06Eh 0110 2] PCI Segment Number : 0000
[070h 0112 8] Base Address : 00000000BBDE2000
[078h 0120 8] End Address (limit) : 00000000BBE01FFF
[080h 0128 1] Device Scope Type : 01 [PCI Endpoint Device]
[081h 0129 1] Entry Length : 08
[082h 0130 2] Reserved : 0000
[084h 0132 1] Enumeration ID : 00
[085h 0133 1] PCI Bus Number : 00
[086h 0134 2] PCI Path : 14,00
[088h 0136 2] Subtable Type : 0001 [Reserved Memory Region]
[08Ah 0138 2] Length : 0020
[08Ch 0140 2] Reserved : 0000
[08Eh 0142 2] PCI Segment Number : 0000
[090h 0144 8] Base Address : 00000000BD000000
[098h 0152 8] End Address (limit) : 00000000BF7FFFFF
[0A0h 0160 1] Device Scope Type : 01 [PCI Endpoint Device]
[0A1h 0161 1] Entry Length : 08
[0A2h 0162 2] Reserved : 0000
[0A4h 0164 1] Enumeration ID : 00
[0A5h 0165 1] PCI Bus Number : 00
[0A6h 0166 2] PCI Path : 02,00
Raw Table Data: Length 168 (0xA8)
0000: 44 4D 41 52 A8 00 00 00 01 DE 4C 45 4E 4F 56 4F // DMAR......LENOVO
0010: 54 50 2D 52 30 44 20 20 30 08 00 00 50 54 45 43 // TP-R0D 0...PTEC
0020: 02 00 00 00 26 01 00 00 00 00 00 00 00 00 00 00 // ....&...........
0030: 00 00 18 00 00 00 00 00 00 00 D9 FE 00 00 00 00 // ................
0040: 01 08 00 00 00 00 02 00 00 00 20 00 01 00 00 00 // .......... .....
0050: 00 10 D9 FE 00 00 00 00 03 08 00 00 02 F0 1F 00 // ................
0060: 04 08 00 00 00 00 1F 00 01 00 20 00 00 00 00 00 // .......... .....
0070: 00 20 DE BB 00 00 00 00 FF 1F E0 BB 00 00 00 00 // . ..............
0080: 01 08 00 00 00 00 14 00 01 00 20 00 00 00 00 00 // .......... .....
0090: 00 00 00 BD 00 00 00 00 FF FF 7F BF 00 00 00 00 // ................
00A0: 01 08 00 00 00 00 02 00 // ........