前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >IOMMU(三)-初始化

IOMMU(三)-初始化

作者头像
惠伟
发布2022-04-28 17:42:24
1K0
发布2022-04-28 17:42:24
举报
文章被收录于专栏:虚拟化笔记虚拟化笔记

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/pipermail/iommu/2009-June/001605.html

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2021-04-27,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • BIOS
  • 驱动
  • 参考文献
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档