IOMMU_GROUP创建流程

内核的iommu驱动在drivers/iommu下,因为虚拟化相关的设计与硬件体系架构强相关,所以iommu的驱动包含软件提取的通用框架层代码和各体系结构相关代码,包括intel/amd/arm等。

本文简单梳理了intel的iommu主线流程,内核基于linux 5.14.13。

一、驱动框架的相关入口一般都是各种initcall函数,在内核初始化的最后阶段调用。intel的iommu的入口在IOMMU_INIT_POST(detect_intel_iommu);

在这个接口里,会检测当前的硬件是否是intel的iommu,如果是则执行后续的初始化。

int __init detect_intel_iommu(void)
{
	int ret;
	struct dmar_res_callback validate_drhd_cb = {
		.cb[ACPI_DMAR_TYPE_HARDWARE_UNIT] = &dmar_validate_one_drhd,
		.ignore_unhandled = true,
	};

	down_write(&dmar_global_lock);
	ret = dmar_table_detect();
	if (!ret)
		ret = dmar_walk_dmar_table((struct acpi_table_dmar *)dmar_tbl,
					   &validate_drhd_cb);
	if (!ret && !no_iommu && !iommu_detected &&
	    (!dmar_disabled || dmar_platform_optin())) {
		iommu_detected = 1;
		/* Make sure ACS will be enabled */
		pci_request_acs();
	}

#ifdef CONFIG_X86
	if (!ret) {
		x86_init.iommu.iommu_init = intel_iommu_init;
		x86_platform.iommu_shutdown = intel_iommu_shutdown;
	}

#endif

	if (dmar_tbl) {
		acpi_put_table(dmar_tbl);
		dmar_tbl = NULL;
	}
	up_write(&dmar_global_lock);

	return ret ? ret : 1;
}

可以看到,内核阶段是根据acpi表的内容进行判断的,如果acpi初始化了相关内容,且内核配置打开iommu(!no_iommu),则配置x86.init_iommu.iommu_init = intel_iommu_init.

这里只是配置了intel的iommu_init接口,下面看调用它的位置以及这个接口具体做了什么。

二、在arch/x86/kernel/pci-dma.c中,调用了x86.init_iommu_iommu_init()接口。

static int __init pci_iommu_init(void)
{
	struct iommu_table_entry *p;

	x86_init.iommu.iommu_init();

	for (p = __iommu_table; p < __iommu_table_end; p++) {
		if (p && (p->flags & IOMMU_DETECTED) && p->late_init)
			p->late_init();
	}

	return 0;
}
/* Must execute after PCI subsystem */
rootfs_initcall(pci_iommu_init);

这也是一个initcall函数,想必IOMMU_INIT_POST的优先级比rootfs_initcall的优先级要高了。

三、intel_iommu_init()接口做了什么,如何为每个pci设备创建了iommu_group和iommu_domain.

int __init intel_iommu_init(void)
{
    /* ............ */
	ret = init_dmars();

    /* ............ */

    bus_set_iommu(&pci_bus_type, &intel_iommu_ops);

   /* ............ */
}

其实为每个pci设备创建iommu_group和iommu_domain的过程就在bus_set_iommu接口里。内核启动过程中会有相关日志打印,每个设备都会有"Adding device to iommu_group 12"这样的日志信息。

bus_set_iommu()接口又继续调用了iommu_bus_init()接口,一共做了三件重要的事:

1、pci_bus_type->iommu_ops = intel_iommu_ops;     iommu_ops是一组核心接口,不同体系架构的都会注册不同的iommu_ops。

2、为pci bus注册了一个notifier,notifier_call是iommu_bus_notifier。(有效事件是BUS_NOTIFY_ADD_DEVICE,为热插入设备执行iommu_probe_device)。

3、bus_iommu_probe接口,遍历当前pci bus挂载的冷启动设备,执行probe_iommu_group:1)创建iommu_group并添加到group_list中;2)然后为group_list中的所有group申请domain,创建direct mapping。

实际分析代码,iommu_probe_device接口的操作与probe_iommu_group接口中为每个设备做的操作是一致的。这也是符合预期的,热插拔和冷启动的设备执行同样的动作。

这两个接口都会调用__iommu_probe_device()。

四、__iommu_probe_device做了什么

static int __iommu_probe_device(struct device *dev, struct list_head *group_list)
{
	const struct iommu_ops *ops = dev->bus->iommu_ops;

	/* ....... */
	iommu_dev = ops->probe_device(dev);   //intel_iommu_probe_device
	if (IS_ERR(iommu_dev)) {
		ret = PTR_ERR(iommu_dev);
		goto out_module_put;
	}

	dev->iommu->iommu_dev = iommu_dev;

	group = iommu_group_get_for_dev(dev);  //创建iommu_group, Adding to iommu_group **,是在这里打印的
	if (IS_ERR(group)) {
		ret = PTR_ERR(group);
		goto out_release;
	}
	iommu_group_put(group);

	if (group_list && !group->default_domain && list_empty(&group->entry))
		list_add_tail(&group->entry, group_list);        //添加到group_list链表中

	iommu_device_link(iommu_dev, dev);   //创建sys目录下的链接关系
	/* ...... */
}

iommu_group_get_for_dev() ----> ops->device_group() = intel_iommu_device_group() ----> pci_device_group()

在pci_device_group()接口里,会牵涉到一个重要的特性ACS。如果设备不支持ACS,那么挂在同一个总线下的所有设备都必须使用一个iommu_group,这是从安全性考虑的。只有通过ACS禁止掉了P2P,主机才会认为设备是安全的,可以分配给不通的虚机或地址空间使用,看起来这部分工作是内核的软件来控制的。

这部分代码还有一些其他的alias逻辑,作者只是分析了ACS相关的内容。

#define REQ_ACS_FLAGS   (PCI_ACS_SV | PCI_ACS_RR | PCI_ACS_CR | PCI_ACS_UF)

因此设备侧如果需要扩展总线和多级设备,也需要配置每一级port和device设备的ACS特性,具体使能的字段如上定义。

struct iommu_group *pci_device_group(struct device *dev)
{
	struct pci_dev *pdev = to_pci_dev(dev);
	struct group_for_pci_data data;
	struct pci_bus *bus;
    /*  ...... */
	/*
	 * Continue upstream from the point of minimum IOMMU granularity
	 * due to aliases to the point where devices are protected from
	 * peer-to-peer DMA by PCI ACS.  Again, if we find an existing
	 * group, use it.
	 */
	for (bus = pdev->bus; !pci_is_root_bus(bus); bus = bus->parent) { //依次遍历上一级总线
		if (!bus->self)
			continue;

		if (pci_acs_path_enabled(bus->self, NULL, REQ_ACS_FLAGS)) //当前总线port使能了REQ_ACS_FLAGS,查找失败,break退出
			break;

		pdev = bus->self;

		group = iommu_group_get(&pdev->dev);   //当前总线已经分配了group,使用该group并立即返回
		if (group)
			return group;
	}

    /*  ...... */

	return iommu_group_alloc();
}
EXPORT_SYMBOL_GPL(pci_device_group);

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值