IOMMU源码分析

相关参考

相关类图

IOMMU图表

本文目标

基础知识

关于DMAR的相关寄存器

在学习 10th Generation Intel® Core™ Processors Datasheet Volume 2 of 2 中我们了解了地址表信息

  1. baiy@baiy-ThinkPad-E470c:testacpi$ sudo cat /proc/iomem # 参考
  2. ......
  3. fed90000-fed90fff : dmar0
  4. fed91000-fed91fff : dmar1
  5. fee00000-fee00fff : Local APIC
  6. .....
  7. baiy@baiy-ThinkPad-E470c:testacpi$ cat dmar.dsl # 参考VT-d 第8章
  8. ......
  9. [030h 0048 2] Subtable Type : 0000 [Hardware Unit Definition]
  10. [032h 0050 2] Length : 0018
  11. [034h 0052 1] Flags : 00
  12. [035h 0053 1] Reserved : 00
  13. [036h 0054 2] PCI Segment Number : 0000 ### PCI的domain地址
  14. [038h 0056 8] Register Base Address : 00000000FED90000
  15. ......
  16. [048h 0072 2] Subtable Type : 0000 [Hardware Unit Definition]
  17. [04Ah 0074 2] Length : 0020
  18. [04Ch 0076 1] Flags : 01
  19. [04Dh 0077 1] Reserved : 00
  20. [04Eh 0078 2] PCI Segment Number : 0000 ### PCI的domain地址
  21. [050h 0080 8] Register Base Address : 00000000FED91000 # 参考VT-d 10章
  22. ......

中间有部分存储了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表将这些信息传递给操作系统

image.png

代码汇总

参考:英特尔IOMMU驱动程序分析

IOMMU_TABLE

注:Linux 解析ACPI表的方法非常巧妙, 将所有解析表封装成一个函数,然后注册一个回调接口,全部完成;666

  1. // arch/x86/include/asm/iommu_table.h
  2. struct iommu_table_entry {
  3. initcall_t detect;
  4. initcall_t depend;
  5. void (*early_init)(void); /* No memory allocate available. */
  6. void (*late_init)(void); /* Yes, can allocate memory. */
  7. #define IOMMU_FINISH_IF_DETECTED (1<<0)
  8. #define IOMMU_DETECTED (1<<1)
  9. int flags;
  10. };
  11. 该结构体:系统在初始化时,通过 __IOMMU_INIT 系列宏去注册一些列在 ”.iommu_table“段的结构体
  12. 然后在 ./arch/x86/mm/init_64.c
  13. mem_init
  14. pci_iommu_alloc();
  15. // 在__IOMMU_INIT宏注释中说明:添加到这个表的字段需要系统去进行排序
  16. // 根据depend依赖关系去排序,然后分别初始化
  17. sort_iommu_table(__iommu_table, __iommu_table_end);
  18. for (p = __iommu_table; p < __iommu_table_end; p++) { // 去遍历排序后的表,分别初始化
  19. if (p && p->detect && p->detect() > 0) { // 调用detect接口 ******
  20. p->flags |= IOMMU_DETECTED;
  21. if (p->early_init) p->early_init(); // 调用early_init接口,这里未实现
  22. IOMMU_INIT_POST(detect_intel_iommu);
  23. detect_intel_iommu
  24. dmar_table_detect(); // 会从acpi获取dmar信息, ACPI的不了解,可以用系统中打印出acpi表
  25. dmar_walk_dmar_table((struct acpi_table_dmar *)dmar_tbl,
  26. &validate_drhd_cb);
  27. dmar_walk_remapping_entries // 这个函数从ACPI表中找出了对应回调接口,然后调用回调
  28. // // 遍历时,只有ACPI_DMAR_TYPE_HARDWARE_UNIT类型有回调接口,调用回调去初始化
  29. cb->cb[iter->type](iter, cb->arg[iter->type]);
  30. dmar_validate_one_drhd // ACPI_DMAR_TYPE_HARDWARE_UNI 的回调函数
  31. 只是检测下 是否是支持VT-D的有效的DRHDcapecap至少要支持一个。
  32. pci_request_acs(); -> pci_acs_enable = 1;
  33. x86_init.iommu.iommu_init = intel_iommu_init; // 重点接口
  34. acpi_put_table(dmar_tbl); // dmar_table_detect()的释放ACPI接口

注:打印dmar的acpi表信息(详细可参考 ACPI)

  1. sudo apt-get install -y iasl acpica-tools
  2. mkdir -p testacpi && cd testacpi
  3. acpidump > acpidump.out # 将ACPI表二进制打印到文件
  4. acpixtract -a acpidump.out # 解析acpi表,生成各个dat文件
  5. iasl -d dmar.dat # iasl会解析acpi 二进制表,生成xxx.dsl描述文件

dmar表的获取-dmar_tbl全局变量(重点)

  1. dmar_table_detect
  2. |- status = acpi_get_table(ACPI_SIG_DMAR, 0, &dmar_tbl);
  3. // DMAR.dsl
  4. [000h 0000 4] Signature : "DMAR" [DMA Remapping table]
  5. [004h 0004 4] Table Length : 000001E0
  6. [008h 0008 1] Revision : 01
  7. [009h 0009 1] Checksum : 5A
  8. [00Ah 0010 6] Oem ID : "INTEL "
  9. [010h 0016 8] Oem Table ID : "S2600WF "
  10. [018h 0024 4] Oem Revision : 00000001
  11. [01Ch 0028 4] Asl Compiler ID : "INTL"
  12. [020h 0032 4] Asl Compiler Revision : 20091013
  13. [024h 0036 1] Host Address Width : 2D
  14. [025h 0037 1] Flags : 03
  15. [026h 0038 10] Reserved : 00 00 00 00 00 00 00 00 00 00
  16. 这个刚好对应 全局变量 dmar_tbl 中的信息 ***********
  17. /*******************************************************************************
  18. *
  19. * Master ACPI Table Header. This common header is used by all ACPI tables
  20. * except the RSDP and FACS.
  21. *
  22. ******************************************************************************/
  23. struct acpi_table_header {
  24. char signature[ACPI_NAME_SIZE]; /* ASCII table signature */
  25. u32 length; /* Length of table in bytes, including this header */
  26. u8 revision; /* ACPI Specification minor version number */
  27. u8 checksum; /* To make sum of entire table == 0 */
  28. char oem_id[ACPI_OEM_ID_SIZE]; /* ASCII OEM identification */
  29. char oem_table_id[ACPI_OEM_TABLE_ID_SIZE]; /* ASCII OEM table identification */
  30. u32 oem_revision; /* OEM revision number */
  31. char asl_compiler_id[ACPI_NAME_SIZE]; /* ASCII ASL compiler vendor ID */
  32. u32 asl_compiler_revision; /* ASL compiler version */
  33. };
  34. struct acpi_table_dmar {
  35. struct acpi_table_header header; /* Common ACPI table header */
  36. u8 width; /* Host Address Width */
  37. u8 flags;
  38. u8 reserved[10];
  39. };
  40. 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。

  1. struct acpi_dmar_header {
  2. u16 type;
  3. u16 length;
  4. };
  5. struct acpi_dmar_hardware_unit { // DRHD表结构体
  6. struct acpi_dmar_header header;
  7. u8 flags;
  8. u8 reserved;
  9. u16 segment;
  10. u64 address; /* Register Base Address */
  11. };
  12. /* 判断ACPI是否枚举出dmardrhd配置, 且是否有效 */
  13. /* 检查下 VT-d重定向硬件寄存器基地址(参考基础知识-DMAR寄存器)的capecap寄存器是否存在 */
  14. dmar_validate_one_drhd(entry = ACPI_DMAR_TYPE_HARDWARE_UNI对应ACPI表信息, args=NULL)
  15. addr = early_ioremap(drhd->address, VTD_PAGE_SIZE);
  16. cap = dmar_readq(addr + DMAR_CAP_REG);
  17. ecap = dmar_readq(addr + DMAR_ECAP_REG);
  18. early_iounmap(addr, VTD_PAGE_SIZE);
  19. check....

入口部分

  1. ./arch/x86/kernel/pci-dma.c
  2. iommu_setup // grub参数
  3. rootfs_initcall(pci_iommu_init);
  4. pci_iommu_init
  5. dma_debug_add_bus(&pci_bus_type);
  6. x86_init.iommu.iommu_init(); => intel_iommu_init
  7. /* 这个IOMMU_INIT_POST(detect_intel_iommu) 未实现late_init,不用看 */
  8. /*
  9. for (p = __iommu_table; p < __iommu_table_end; p++) {
  10. if (p && (p->flags & IOMMU_DETECTED) && p->late_init)
  11. p->late_init(); // 未实现
  12. }
  13. */

初始化部分

  1. Copy Intel IOMMU Introduction
  2. intel_iommu_init
  3. |-> dmar_table_init -> parse_dmar_table -> dmar_walk_dmar_table //重点分析
  4. |-> dmar_dev_scope_init
  5. |-> dmar_acpi_dev_scope_init -> dmar_acpi_insert_dev_scope //重点分析
  6. |-> dmar_pci_bus_add_dev -> dmar_insert_dev_scope
  7. |-> bus_register_notifier
  8. |-> dmar_init_reserved_ranges // init RMRR
  9. |-> init_no_remapping_devices // init no remapping devices
  10. |-> init_dmars //重点分析
  11. |-> dma_ops = &intel_dma_ops
  12. |-> iommu_device_sysfs_add, iommu_device_set_ops, iommu_device_register
  13. |-> bus_set_iommu(&pci_bus_type, &intel_iommu_ops)
  14. |-> 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部分解析方式一样,这里不具体分析代码

  1. // 用acpi去回调这些所有的接口
  2. struct dmar_res_callback cb = {
  3. .print_entry = true,
  4. .ignore_unhandled = true,
  5. .arg[ACPI_DMAR_TYPE_HARDWARE_UNIT] = &drhd_count,
  6. .cb[ACPI_DMAR_TYPE_HARDWARE_UNIT] = &dmar_parse_one_drhd,
  7. .cb[ACPI_DMAR_TYPE_RESERVED_MEMORY] = &dmar_parse_one_rmrr,
  8. .cb[ACPI_DMAR_TYPE_ROOT_ATS] = &dmar_parse_one_atsr,
  9. .cb[ACPI_DMAR_TYPE_HARDWARE_AFFINITY] = &dmar_parse_one_rhsa,
  10. .cb[ACPI_DMAR_TYPE_NAMESPACE] = &dmar_parse_one_andd,
  11. };
  12. // https://www.intel.com/content/dam/www/public/us/en/documents/guides/txt-enabling-guide.pdf
  13. dmar_tbl = tboot_get_dmar_table(dmar_tbl);
  14. // 和IOMMU_TABLE一样,都是获取ACPI,去调用各个回调接口
  15. dmar_table_detect();
  16. 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 的结构体

  1. # 先看一个drhd全不结构:
  2. ================== header===================================
  3. [030h 0048 2] Subtable Type : 0000 [Hardware Unit Definition]
  4. [032h 0050 2] Length : 0018
  5. [034h 0052 1] Flags : 00
  6. [035h 0053 1] Reserved : 00
  7. [036h 0054 2] PCI Segment Number : 0000
  8. [038h 0056 8] Register Base Address : 00000000FED90000
  9. ===================end header=================================
  10. ===================dev scope==============================
  11. [040h 0064 1] Device Scope Type : 01 [PCI Endpoint Device]
  12. [041h 0065 1] Entry Length : 08
  13. [042h 0066 2] Reserved : 0000
  14. [044h 0068 1] Enumeration ID : 00
  15. [045h 0069 1] PCI Bus Number : 00
  16. [046h 0070 2] PCI Path : 02,00
  17. ===================dev scope==============================
  18. struct acpi_dmar_hardware_unit {
  19. struct acpi_dmar_header header;
  20. u8 flags;
  21. u8 reserved;
  22. u16 segment;
  23. u64 address; /* Register Base Address */
  24. }drhd;
  25. struct dmar_drhd_unit {
  26. struct list_head list; /* list of drhd units */
  27. struct acpi_dmar_header *hdr; /* ACPI header */
  28. u64 reg_base_addr; /* register base address*/
  29. struct dmar_dev_scope *devices;/* target device array */
  30. int devices_cnt; /* target device count */
  31. u16 segment; /* PCI domain */
  32. u8 ignored:1; /* ignore drhd */
  33. u8 include_all:1;
  34. u8 gfx_dedicated:1; /* graphic dedicated */
  35. struct intel_iommu *iommu;
  36. }dmaru;
  37. // drivers/iommu/intel/dmar.c
  38. dmar_parse_one_drhd(DRHD, &drhd_count)
  39. drhd = (struct acpi_dmar_hardware_unit *)header // drhd 是 ACPI表中DRHD的地址
  40. dmaru = dmar_find_dmaru(drhd); //dmar_drhd_units全局存drhd的链表,此时未初始化=false
  41. // 分配一个dmaru描述 + DRHD大小,并将DRHD结构 拷贝到dmaru结构体底部
  42. dmaru = kzalloc(sizeof(*dmaru) + header->length, GFP_KERNEL);
  43. ..... // init dmar unit结构
  44. |->dmaru->devices = dmar_alloc_dev_scope // 解析 ACPI中DMAR的DRHD dev scope
  45. |->alloc_iommu(dmaru) // *** 重点,每一个dmaru都分配一套struct intel_iommu的结构
  46. struct intel_iommu *iommu; //
  47. map_iommu(iommu, drhd->reg_base_addr); // 映射VT-D中的寄存器表
  48. dmaru->iommu = iommu; // 这就是下边UML图
  49. |->dmar_register_drhd_unit(dmaru)
  50. dmar_drhd_units 全局链表中添加自己的dmaru结构

image.png
调试小知识:

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 预留内存单元结构体

  1. [068h 0104 2] Subtable Type : 0001 [Reserved Memory Region]
  2. [06Ah 0106 2] Length : 0020
  3. [06Ch 0108 2] Reserved : 0000
  4. [06Eh 0110 2] PCI Segment Number : 0000
  5. [070h 0112 8] Base Address : 00000000BBDE2000
  6. [078h 0120 8] End Address (limit) : 00000000BBE01FFF
  7. [080h 0128 1] Device Scope Type : 01 [PCI Endpoint Device]
  8. [081h 0129 1] Entry Length : 08
  9. [082h 0130 2] Reserved : 0000
  10. [084h 0132 1] Enumeration ID : 00
  11. [085h 0133 1] PCI Bus Number : 00
  12. [086h 0134 2] PCI Path : 14,00
  13. ...
  14. dmar_parse_one_rmrr
  15. 这部分和drhd的初始化类似,根据ACPI 初始化自己rmrr的结构,并添加到链表中

**
dmar_parse_one_atsr
dmar_parse_one_rhsa
dmar_parse_one_andd (namespace,好像可以没有)
这三个不考虑,可选项,根据 DMAR表组织结构 去分析

dmar_dev_scope_init(可以跳过)
  1. dmar_dev_scope_init
  2. // 判断ACPI中是否支持dmar_drhd_unit
  3. // 从全局dmar_drhd_units链表中是否有 dmar_drhd_unit 的结构体
  4. if (list_empty(&dmar_drhd_units))
  5. dmar_dev_scope_status = -ENODEV;
  6. dmar_acpi_dev_scope_init(); // *** 重要函数,但找了几台机器,都没这玩意namespace,跳过
  7. // 给PCI总线注册 BUS_NOTIFY_ADD_DEVICE 和 BUS_NOTIFY_REMOVED_DEVICE 通知接口
  8. // **** 这部分也很关键,设备起来后,添加驱动,都需要走这个
  9. bus_register_notifier(&pci_bus_type, &dmar_pci_bus_nb);

dmar_init_reserved_ranges

init_dmars() 重点函数
  1. // 初始化每个iommu,并分配root_entry
  2. for_each_drhd_unit(drhd)
  3. g_num_of_iommus // g_num_of_iommus = drhd 硬件dmar的个数
  4. for_each_active_iommu(iommu, drhd) // 每个drhd都有intel_iommu对象,存放在drhd->iommu
  5. g_iommus[iommu->seq_id] = iommu; // g_iommus 根据seq_id存放了intel_iommu对象
  6. // 注:iommu分配和seq_id初始化在 alloc_iommu 接口实现
  7. intel_iommu_init_qi // 参考VT-D 6.5 什么寄存器缓存啥的??? TBD
  8. iommu_init_domains(iommu); // 重点:
  9. ndomains = cap_ndoms(iommu->cap); // 判断当前drhd支持多少个domains, cap[2:0]表示
  10. iommu->domain_ids; // 对支持的域建立bitmap
  11. // 难点:三级指针, 最终格式如下边的图片
  12. size = (ALIGN(ndomains, 256) >> 8) * sizeof(struct dmar_domain **);
  13. iommu->domains = kzalloc(size, GFP_KERNEL);
  14. size = 256 * sizeof(struct dmar_domain *);
  15. iommu->domains[0] = kzalloc(size, GFP_KERNEL);
  16. init_translation_status(iommu); // 判断GSTS_REG[30]寄存器是否使能DMAR,修改iommu->flags
  17. translation_pre_enabled(iommu); // 同上,验证下环境
  18. iommu_alloc_root_entry(iommu); // 给每个intel_iommu分配root_entry,大小1页大小
  19. copy_translation_tables(iommu); // 暂时跳过,后边详解
  20. intel_svm_alloc_pasid_tables(iommu); // 支持PASID,以后看的时候补充
  21. iommu_set_root_entry(iommu); // 更新iommu的entry地址

iommu_init_domains 的结构描述:
image.png
image.png
image.png

copy_translation_tables 说明

这里有个说明: ecap[24] 和 Root Table Address Register[11]都为0,旧版本支持扩展的空间,新版本已不支持,直接为0

  1. struct root_entry {
  2. u64 lo;
  3. u64 hi;
  4. };
  5. new_ext == ext == 0
  6. rtaddr_reg = Root Table Address Register 的值
  7. // old_rt_phys 和 old_rt 旧的Root Table Address
  8. old_rt_phys = rtaddr_reg & VTD_PAGE_MASK;
  9. old_rt = memremap(old_rt_phys, PAGE_SIZE, MEMREMAP_WB);
  10. ctxt_tbls=分配成256大小的指针数组=> void * ctxt_tbls[256]
  11. for (bus = 0; bus < 256; bus++) {
  12. copy_context_table(iommu, &old_rt[bus], ctxt_tbls, bus, ext);
  13. }

set_ops

初始化操作接口
  1. for_each_active_iommu(iommu, drhd) {
  2. iommu_device_sysfs_add(&iommu->iommu, NULL,
  3. intel_iommu_groups,
  4. "%s", iommu->name);
  5. iommu_device_set_ops(&iommu->iommu, &intel_iommu_ops);
  6. iommu_device_register(&iommu->iommu);
  7. }

初始化一个sysfs/classs/iommu/dmarx 接口, 并关联起相关的接口
image.png

dma_ops = &intel_dma_ops;

更新dma操作接口,dma_alloc_coherent 后期分配内存就不走通用的接口了,走这里了。

bus_set_iommu

建议先复习下 内核通知链

  1. bus_set_iommu(&pci_bus_type, &intel_iommu_ops);
  2. bus->iommu_ops = ops; // 给pci_bus_type注册 iommu_ops
  3. iommu_bus_init(bus, ops);
  4. nb->notifier_call = iommu_bus_notifier;
  5. // 给pci_bus_type在注册一个notifier 通知链
  6. // 内核通知链里分析过:device_add会触发一次notifier
  7. bus_register_notifier(bus, call_func:iommu_bus_notifier);
  8. bus_for_each_dev(bus, NULL, &cb, add_iommu_group);
  9. pci_bus_type下的设备都调用一次 add_iommu_group(pci_dev, &cb)
  10. cb->ops = intel_iommu_ops
  11. // 在给pci_bus_type注册一个notifier通知
  12. bus_register_notifier(&pci_bus_type, call_func:device_notifier;

add_iommu_group(重点难点 )

先简单整理下代码调用关系
注:iommu & intel-iommu实现 中开始对这部分代码有了比较详细的跟踪

  1. add_iommu_group
  2. const struct iommu_ops *ops = cb->ops; // cb->ops = intel_iommu_ops
  3. intel_iommu_ops->add_device(dev); // 这就是对每个PCI设备都添加组的最终操作
  4. intel_iommu_add_device(dev)
  5. // 根据PCI设备寻找 对应PCI总线域的DRHD的intel_iommu结构
  6. // 根据dmar_drhd_unit找到当前device属于哪个 intel_iommu
  7. iommu = device_to_iommu(dev, &bus, &devfn);
  8. segment = pci_domain_nr(pdev->bus); // 每个PCI总线的domain,也就是segment
  9. 当前PCI设备的segment 肯定会落在drhd的描述中,说明该PCI设备属于匹配的drhd
  10. 在对应drhd下找到匹配的设备,返回intel_iommu
  11. iommu_device_link(&iommu->iommu, dev); // 将该设备链接到sysfs下
  12. // 获取或者分配设备的iommu_group,并分配响应的iommu_domain
  13. // *** 重点***
  14. group = iommu_group_get_for_dev(dev);
  15. iommu_group_get(dev); // dev->iommu_group第一次肯定为NULL,
  16. group = ops->device_group(dev); // 调用 intel_iommu 的 pci_device_group
  17. // 给当前group分配的 domain, 调用 intel_iommu_domain_alloc
  18. group->domain = group->default_domain = __iommu_domain_alloc
  19. // 上边是配合和查找group,然后当然是将设备添加到group了
  20. iommu_group_add_device(group, dev)
  21. // 在sysfs下创建iommu_groups
  22. sysfs_create_link(&dev->kobj, &group->kobj, "iommu_group");
  23. // 很关键:以后直接从dev获取group即可
  24. dev->iommu_group = group;
  25. // intel_iommu_attach_device 将设备挂到domain上,对其进行页表管理
  26. __iommu_attach_device(group->domain, dev);
  27. // 给组内兄弟喊一声:来新人了,发红包
  28. blocking_notifier_call_chain(,IOMMU_GROUP_NOTIFY_ADD_DEVICE,);
  29. // [关键打印信息] iommu: Adding device 0000:00:00.0 to group 0
  30. iommu_group_put(group);

domain的分配

  1. __iommu_domain_alloc // 初始化dmar_domain和iommu_domain
  2. bus->iommu_ops->domain_alloc(type);

image.png
iommu_group_get_for_dev 和 iommu_group_add_device
这个函数分组关系比较复杂,具 intel IOMMU driver analysisIOMMU group and PCIe ACS问题 描述

有几种情况可以从现有设备获取设备IOMMU组。 例如,如果一个网桥支持ACS,则需要转到上游总线。. 多功能设备的所有功能设备也需要共享同一IOMMU组

这部分牵扯到PCIE的ACS功能,这部分跳过,只要知道: GROUP是虚拟化分组的最小单位,所以SR-IOV设备要把pcie分到不同的分组中,才能用。

  1. iommu_group_get_for_dev
  2. ops->device_group(dev);
  3. pci_device_group
  4. pci_device_group
  5. iommu_group_alloc(); // 这里只看创建group
  6. iommu_group_add_device
  7. dev->iommu_group = group;
  8. __iommu_attach_device(group->domain, dev);

附录

附录1 Intel(R) Core(TM) i5-6200U CPU @ 2.30GHz的ACPI DMAR

  1. baiy@baiy-ThinkPad-E470c:testacpi$ cat dmar.dsl
  2. /*
  3. * Intel ACPI Component Architecture
  4. * AML/ASL+ Disassembler version 20180105 (64-bit version)
  5. * Copyright (c) 2000 - 2018 Intel Corporation
  6. *
  7. * Disassembly of dmar.dat, Sun Nov 29 21:34:36 2020
  8. *
  9. * ACPI Data Table [DMAR]
  10. *
  11. * Format: [HexOffset DecimalOffset ByteLength] FieldName : FieldValue
  12. */
  13. [000h 0000 4] Signature : "DMAR" [DMA Remapping table]
  14. [004h 0004 4] Table Length : 000000A8
  15. [008h 0008 1] Revision : 01
  16. [009h 0009 1] Checksum : DE
  17. [00Ah 0010 6] Oem ID : "LENOVO"
  18. [010h 0016 8] Oem Table ID : "TP-R0D "
  19. [018h 0024 4] Oem Revision : 00000830
  20. [01Ch 0028 4] Asl Compiler ID : "PTEC"
  21. [020h 0032 4] Asl Compiler Revision : 00000002
  22. [024h 0036 1] Host Address Width : 26
  23. [025h 0037 1] Flags : 01
  24. [026h 0038 10] Reserved : 00 00 00 00 00 00 00 00 00 00
  25. [030h 0048 2] Subtable Type : 0000 [Hardware Unit Definition]
  26. [032h 0050 2] Length : 0018
  27. [034h 0052 1] Flags : 00
  28. [035h 0053 1] Reserved : 00
  29. [036h 0054 2] PCI Segment Number : 0000
  30. [038h 0056 8] Register Base Address : 00000000FED90000
  31. [040h 0064 1] Device Scope Type : 01 [PCI Endpoint Device]
  32. [041h 0065 1] Entry Length : 08
  33. [042h 0066 2] Reserved : 0000
  34. [044h 0068 1] Enumeration ID : 00
  35. [045h 0069 1] PCI Bus Number : 00
  36. [046h 0070 2] PCI Path : 02,00
  37. [048h 0072 2] Subtable Type : 0000 [Hardware Unit Definition]
  38. [04Ah 0074 2] Length : 0020
  39. [04Ch 0076 1] Flags : 01
  40. [04Dh 0077 1] Reserved : 00
  41. [04Eh 0078 2] PCI Segment Number : 0000
  42. [050h 0080 8] Register Base Address : 00000000FED91000
  43. [058h 0088 1] Device Scope Type : 03 [IOAPIC Device]
  44. [059h 0089 1] Entry Length : 08
  45. [05Ah 0090 2] Reserved : 0000
  46. [05Ch 0092 1] Enumeration ID : 02
  47. [05Dh 0093 1] PCI Bus Number : F0
  48. [05Eh 0094 2] PCI Path : 1F,00
  49. [060h 0096 1] Device Scope Type : 04 [Message-capable HPET Device]
  50. [061h 0097 1] Entry Length : 08
  51. [062h 0098 2] Reserved : 0000
  52. [064h 0100 1] Enumeration ID : 00
  53. [065h 0101 1] PCI Bus Number : 00
  54. [066h 0102 2] PCI Path : 1F,00
  55. [068h 0104 2] Subtable Type : 0001 [Reserved Memory Region]
  56. [06Ah 0106 2] Length : 0020
  57. [06Ch 0108 2] Reserved : 0000
  58. [06Eh 0110 2] PCI Segment Number : 0000
  59. [070h 0112 8] Base Address : 00000000BBDE2000
  60. [078h 0120 8] End Address (limit) : 00000000BBE01FFF
  61. [080h 0128 1] Device Scope Type : 01 [PCI Endpoint Device]
  62. [081h 0129 1] Entry Length : 08
  63. [082h 0130 2] Reserved : 0000
  64. [084h 0132 1] Enumeration ID : 00
  65. [085h 0133 1] PCI Bus Number : 00
  66. [086h 0134 2] PCI Path : 14,00
  67. [088h 0136 2] Subtable Type : 0001 [Reserved Memory Region]
  68. [08Ah 0138 2] Length : 0020
  69. [08Ch 0140 2] Reserved : 0000
  70. [08Eh 0142 2] PCI Segment Number : 0000
  71. [090h 0144 8] Base Address : 00000000BD000000
  72. [098h 0152 8] End Address (limit) : 00000000BF7FFFFF
  73. [0A0h 0160 1] Device Scope Type : 01 [PCI Endpoint Device]
  74. [0A1h 0161 1] Entry Length : 08
  75. [0A2h 0162 2] Reserved : 0000
  76. [0A4h 0164 1] Enumeration ID : 00
  77. [0A5h 0165 1] PCI Bus Number : 00
  78. [0A6h 0166 2] PCI Path : 02,00
  79. Raw Table Data: Length 168 (0xA8)
  80. 0000: 44 4D 41 52 A8 00 00 00 01 DE 4C 45 4E 4F 56 4F // DMAR......LENOVO
  81. 0010: 54 50 2D 52 30 44 20 20 30 08 00 00 50 54 45 43 // TP-R0D 0...PTEC
  82. 0020: 02 00 00 00 26 01 00 00 00 00 00 00 00 00 00 00 // ....&...........
  83. 0030: 00 00 18 00 00 00 00 00 00 00 D9 FE 00 00 00 00 // ................
  84. 0040: 01 08 00 00 00 00 02 00 00 00 20 00 01 00 00 00 // .......... .....
  85. 0050: 00 10 D9 FE 00 00 00 00 03 08 00 00 02 F0 1F 00 // ................
  86. 0060: 04 08 00 00 00 00 1F 00 01 00 20 00 00 00 00 00 // .......... .....
  87. 0070: 00 20 DE BB 00 00 00 00 FF 1F E0 BB 00 00 00 00 // . ..............
  88. 0080: 01 08 00 00 00 00 14 00 01 00 20 00 00 00 00 00 // .......... .....
  89. 0090: 00 00 00 BD 00 00 00 00 FF FF 7F BF 00 00 00 00 // ................
  90. 00A0: 01 08 00 00 00 00 02 00 // ........