惠伟: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