IOMMU(三)-初始化

2021-04-28 10:19:19 浏览数 (1)

惠伟:IOMMU(一)-简单介绍​zhuanlan.zhihu.com

惠伟:IOMMU(二)-从配置说起​zhuanlan.zhihu.com

BIOS收集IOMMU相关的信息,通过ACPI中的特定表组织数据,放置在内存中,等操作系统接管硬件后,它会加载驱动,驱动再详细解析ACPI表中的信息。

BIOS

首先是DMA Remapping Reporting Structure,它后面紧跟下表这些结构。

对应代码宏定义如下:

代码语言:javascript复制
enum acpi_dmar_type {
	ACPI_DMAR_TYPE_HARDWARE_UNIT = 0,
	ACPI_DMAR_TYPE_RESERVED_MEMORY = 1,
	ACPI_DMAR_TYPE_ROOT_ATS = 2,
	ACPI_DMAR_TYPE_HARDWARE_AFFINITY = 3,
	ACPI_DMAR_TYPE_NAMESPACE = 4,
	ACPI_DMAR_TYPE_RESERVED = 5	/* 5 and greater are reserved */
};

总的来说就是软件看来有几个IOMMU单元,每个单元负责哪些pci设备。详细见qemu代码build_dmar_q35,AcpiTableDmar就是DMA Remapping Reporting Structure,它后面就是DRHD,DRHD就是IOMMU硬件单元,DeviceScope紧跟着DRHD,就是这个DRHD负责处理的那些pci设备。如果DRHD支持device iotlb,还要上报ATSR。重点说一下RMRR,因为以前在HP ProLiant DL360 Gen9机器做DPDK开发时碰到过问题(Device is ineligible for IOMMU domain attach due to platform RMRR requirement),HP的服务器带外管理和正常的流量共用一个网卡,这个网卡只能把流量DMA到一个firmware规定的物理内存区域(RMRR),如果不在这个区域firmware就拿不到流量,把这个网卡绑定到vfio-pci后,网卡DMA的目的地址是dpdk进程的虚拟地址,虚拟地址对应着大页内存的物理地址,大概率和firmware上报的RMRR不同,所以vfio-pci就报错不让绑定,解决办法就是升级firmware,在BIOS中disable 网卡shared memory功能。

驱动

内核编译的时候生成IOMMU相关的数据结构,所有IOMMU厂商注册自己的IOMMU硬件的detect/depend/early_init/late_init函数,intel注册了detect_intel_iommu来检测自己的IOMMU,其它函数都为NULL,finish为0表示检测到自己就不用再检测其它IOMMU硬件了。

代码语言:javascript复制
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;
};
#define __IOMMU_INIT(_detect, _depend, _early_init, _late_init, _finish)
	static const struct iommu_table_entry				
		__iommu_entry_##_detect __used				
	__attribute__ ((unused, __section__(".iommu_table"),		
			aligned((sizeof(void *)))))	
	= {_detect, _depend, _early_init, _late_init,			
	   _finish ? IOMMU_FINISH_IF_DETECTED : 0}
#define IOMMU_INIT_POST(_detect)					
	__IOMMU_INIT(_detect, pci_swiotlb_detect_4gb,  NULL, NULL, 0)
IOMMU_INIT_POST(detect_intel_iommu);

内核启动后从ACPI中获取DMAR table,调用到detect_intel_iommu,它只检测了类型为ACPI_DMAR_TYPE_HARDWARE_UNIT的数据,也就是IOMMU硬件单元,还尝试读了IOMMU硬件的capability和extended capability,如果都成功给iommu_init符值intel_iommu_init。

代码语言:javascript复制
struct acpi_table_header * __initdata dmar_tbl;
static acpi_size dmar_tbl_size;
start_kernel
  └─mm_init
      └─mem_init
          └─pci_iommu_alloc
              ├─sort_iommu_table
              ├─check_iommu_entries
              └─detect_intel_iommu
                  ├─dmar_table_detect
                  |   └─acpi_get_table_with_size
                  |       └─dmar_walk_remapping_entries
                  |           └─dmar_validate_one_drhd
                  ├─dmar_walk_dmar_table
                  ├─pci_request_acs
                  └─x86_init.iommu.iommu_init = intel_iommu_init

intel_iommu_init横空出世,那就得看什么地方调用到它。detect_intel_iommu执行时还没有memory allocator,所以干的活很简单,但intel_iommu_init执行时memory allocator已经形成,所以intel_iommu_init就大量分配内存建立iommu的数据结构,主要是struct dmar_drhd_unit和struct intel_iommu。

代码语言:javascript复制
struct list_head dmar_drhd_units;
static LIST_HEAD(dmar_rmrr_units);
pci_iommu_init
  └─intel_iommu_init
      ├─dmar_table_init
      ├─parse_dmar_table
      |   ├─dmar_table_detect
      |   └─dmar_walk_remapping_entries
      |       ├─dmar_parse_one_drhd
      |       |  ├─dmar_find_dmaru
      |       |  ├─dmar_alloc_dev_scope//分配好多空内存
      |       |  ├─alloc_iommu
      |       |  |   └─map_iommu
      |       |  └─dmar_register_drhd_unit
      |       ├─dmar_parse_one_rmrr
      |       ├─dmar_parse_one_atsr
      |       ├─dmar_parse_one_rhsa
      |       └─dmar_parse_one_andd
      ├─dmar_dev_scope_init
      | ├─dmar_acpi_dev_scope_init
      | |     └─for(dev_scope_num in acpi table)
      | |           ├─acpi_bus_get_device
      | |           └─dmar_acpi_insert_dev_scope//给上面注释中指的内存空间中写dev/bus
      | └─for_each_pci_dev(dev)//把非acpi上报的dev上搞到iommu中来
      |     ├─dmar_alloc_pci_notify_info
      |     ├─dmar_pci_bus_add_dev
      |     |   ├─for_each_drhd_unit//找到dev的hrhd然后加入
      |     |   |    └─dmar_insert_dev_scope
      |     |   ├─dmar_iommu_notify_scope_dev
      |     |   └─intel_irq_remap_add_device
      |     └─dmar_free_pci_notify_info
      ├─init_dmars//后面单独分析
      ├─bus_set_iommu//后面单独分析
      └─for_each_iommu
             └─iommu_enable_translation

单独分析init_dmars,有点复杂。

代码语言:javascript复制
init_dmars
  ├─for_each_iommu
  |   ├─intel_iommu_init_qi//软件给硬件通过这块内存提交任务,硬件清自己的iotlb缓存
  |   |   └─dmar_enable_qi
  |   |       └─__dmar_enable_qi//写硬件寄存器
  |   ├─iommu_init_domains//分配了很多struct dmar_domain
  |   └─iommu_alloc_root_entry
  ├─for_each_active_iommu
  |   ├─iommu_flush_write_buffer
  |   └─iommu_set_root_entry//写硬件寄存器
  ├─si_domain_init
  |   ├─alloc_domain(DOMAIN_FLAG_STATIC_IDENTITY)//额外创建一个domain
  |   ├─for_each_online_node//在这个domain中dma地址和物理地址一一对应
  |   |   └─for_each_mem_pfn_range
  |   |       └─iommu_domain_identity_map
  |   |           └─__domain_mapping
  |   └─for_each_rmrr_units//rmrr内存在这个domain中一一对应
  |      └─for_each_active_dev_scope
  |           └─iommu_domain_identity_map
  └─for_each_iommu
      ├─iommu_flush_write_buffer
      └─dmar_set_interrupt//iommu硬件自己的中断
          ├─dmar_alloc_hwirq
          └─request_irq(irq, dmar_fault)

有四种domain,init_dmars中用到了IOMMU_DOMAIN_IDENTITY,这个类型的domain只能有一个,kvm和dpdk会用到IOMMU_DOMAIN_UNMANAGED,一个qemu或者一个dpdk进程一个domain。IOMMU_DOMAIN_BLOCKED和IOMMU_DOMAIN_DMA是内核用到,它和iommu group有关系,一个group对应一个domain,一个group有可能有多个dev,这个和pci硬件结构有关系,详见函数pci_device_group。

代码语言:javascript复制
/*
 * This are the possible domain-types
 *
 *	IOMMU_DOMAIN_BLOCKED	- All DMA is blocked, can be used to isolate
 *				  devices
 *	IOMMU_DOMAIN_IDENTITY	- DMA addresses are system physical addresses
 *	IOMMU_DOMAIN_UNMANAGED	- DMA mappings managed by IOMMU-API user, used
 *				  for VMs
 *	IOMMU_DOMAIN_DMA	- Internally used for DMA-API implementations.
 *				  This flag allows IOMMU drivers to implement
 *				  certain optimizations for these domains
 */

单独分析bus_set_iommu,这个函数中有个default domain,如果内核参数iommu=pt,这个default domain就是唯一类型为IOMMU_DOMAIN_IDENTITY的si,如果内核参数iommu=nopt就是类型为IOMMU_DOMAIN_DMA的domain,内核参数没有iommu,默认为IOMMU_DOMAIN_IDENTITY。

代码语言:javascript复制
bus_set_iommu
  └─iommu_bus_init
      ├─bus_iommu_probe//处理bus上的设备
      |   ├─bus_for_each_dev//给dev分配group,
      |   |   └─probe_iommu_group
      |   |        └─__iommu_probe_device
      |   |             ├─intel_iommu_probe_device
      |   |             └─iommu_group_get_for_dev
      |   |                   └─pci_device_group//真正决定group的函数
      |   └─for_each_group
      |        ├─probe_alloc_default_domain//给内核管理的dev分配default domain
      |        ├─iommu_group_create_direct_mappings//对系统保留区分建立mapping,如dev和ioapic的关系
      |        |   └─iommu_create_device_direct_mappings
      |        |       ├─list_for_each_entry
      |        |       |   ├─iommu_iova_to_phys
      |        |       |   └─iommu_map
      |        |       └─iommu_flush_iotlb_all
      |        ├─__iommu_group_dma_attach
      |        |     └─dmar_insert_one_dev_info//创建这个dev在IOMMU中的转换表
      |        └─__iommu_group_dma_finalize
      |              └─intel_iommu_probe_finalize
      |                  └─iommu_dma_init_domain//给dev分配iova并且flush到硬件上
      └─bus_register_notifier(iommu_bus_notifier)//用于处理hotplug的设备

初始化工作准备了设备动态运行时要用到的数据和函数,后面就得分析设备动态运行时做了什么操作,调用了哪些函数。

参考文献

http://liujunming.top/2020/02/12/RMRR-related-notes/

https://support.hpe.com/hpesc/public/docDisplay?docId=emr_na-c04781229

https://lists.linuxfoundation.org

0 人点赞