iommu系列之概念解释篇

本文会对iommu中的一些容易引起疑惑的概念进行阐述,内核版本为4.19.
先上简写:

  • DMAR - DMA remapping
  • DRHD - DMA Remapping Hardware Unit Definition
  • RMRR - Reserved memory Region Reporting Structure
  • ZLR - Zero length reads from PCI devices
  • IOVA - IO Virtual address.
  • SMMU -System Memory Management Unit,就是arm的iommu

1、为什么会有一个iommu_group的概念,直接将device和iommu_domain关联不香吗?
假设我们通过iommu提供设备的DMA能力,当发起dma_map的时候,设备设置了streamid, 但是多个设备的streamid有可能是一样的
那么这时候修改其中一个设备的页表体系,就影响了相同streamid的其他设备。
所以,修改页表的最小单位不是设备,而是streamid。这个streamid大家可能觉得比较抽象,先知道这个是用来索引的就行。
因此,为了避免这种情况,增加了一个iommu_group的概念,iommu_group代表共享同一个streamid的一组device(表述在/sys/kernel/iommu_group中)。
有了iommu_group, 设备发起dma_map操作时,会定位streamid和iommu_group, group定位了iommu_device和iommu_domain,
iommu_domain定位了asid。group 里面的设备既然公用一套iova的页表,那么只能透传给一个虚机,不能分开透传。
一个iommu_group里面既可能只有一个device,也可能有多个device。

arm smmu-v3中的 iommugroup 类型为2类

//caq:arm中针对iommu,有两类group,如果是pci的设备,用一个默认group,否则用系统的默认group,注意是两类,不是两个,个数可以建很多个。
static struct iommu_group *arm_smmu_device_group(struct device *dev)
{
	struct iommu_group *group;

	/*
	 * We don't support devices sharing stream IDs other than PCI RID
	 * aliases, since the necessary ID-to-device lookup becomes rather
	 * impractical given a potential sparse 32-bit stream ID space.
	 */
	if (dev_is_pci(dev))
		group = pci_device_group(dev);//caq:pci 的group
	else
		group = generic_device_group(dev);//caq:generic  的group

	return group;
}

2、resv_regions
有些保留的区域,是不能dma映射的,将这些区域管理起来,避免映射。
对于arm smmu-v3来说,保存区域为 MSI_IOVA_BASE,长度为 MSI_IOVA_LENGTH,还有保留类型为IOMMU_RESV_MSI,它是硬件的 msi 区域 。
对于intel 来说,保留区域为 IOMMU_RESV_DIRECT 和 IOMMU_RESV_MSI类型的 IOAPIC_RANGE_START 区域。

3、iommu_group 和dev 之间的关系

# for d in /sys/kernel/iommu_groups/*/devices/*; do n=${d#*/iommu_groups/*}; n=${n%%/*}; printf 'IOMMU Group %s ' "$n"; lspci -nns "${d##*/}"; done;
IOMMU Group 0 00:00.0 Host bridge [0600]: Intel Corporation Sky Lake-E DMI3 Registers [8086:2020] (rev 07)
IOMMU Group 10 00:05.2 System peripheral [0880]: Intel Corporation Device [8086:2025] (rev 07)
IOMMU Group 11 00:05.4 PIC [0800]: Intel Corporation Device [8086:2026] (rev 07)
IOMMU Group 12 00:08.0 System peripheral [0880]: Intel Corporation Sky Lake-E Ubox Registers [8086:2014] (rev 07)
IOMMU Group 13 00:08.1 Performance counters [1101]: Intel Corporation Sky Lake-E Ubox Registers [8086:2015] (rev 07)
IOMMU Group 14 00:08.2 System peripheral [0880]: Intel Corporation Sky Lake-E Ubox Registers [8086:2016] (rev 07)
IOMMU Group 15 00:11.0 Unassigned class [ff00]: Intel Corporation C620 Series Chipset Family MROM 0 [8086:a1ec] (rev 09)
IOMMU Group 15 00:11.5 SATA controller [0106]: Intel Corporation C620 Series Chipset Family SSATA Controller [AHCI mode] [8086:a1d2] (rev 09)
IOMMU Group 16 00:14.0 USB controller [0c03]: Intel Corporation C620 Series Chipset Family USB 3.0 xHCI Controller [8086:a1af] (rev 09)
IOMMU Group 16 00:14.2 Signal processing controller [1180]: Intel Corporation C620 Series Chipset Family Thermal Subsystem [8086:a1b1] (rev 09)
IOMMU Group 17 00:16.0 Communication controller [0780]: Intel Corporation C620 Series Chipset Family MEI Controller #1 [8086:a1ba] (rev 09)
IOMMU Group 17 00:16.1 Communication controller [0780]: Intel Corporation C620 Series Chipset Family MEI Controller #2 [8086:a1bb] (rev 09)
IOMMU Group 17 00:16.4 Communication controller [0780]: Intel Corporation C620 Series Chipset Family MEI Controller #3 [8086:a1be] (rev 09)
IOMMU Group 18 00:17.0 SATA controller [0106]: Intel Corporation C620 Series Chipset Family SATA Controller [AHCI mode] [8086:a182] (rev 09)
IOMMU Group 19 00:1c.0 PCI bridge [0604]: Intel Corporation C620 Series Chipset Family PCI Express Root Port #1 [8086:a190] (rev f9)
IOMMU Group 1 00:04.0 System peripheral [0880]: Intel Corporation Sky Lake-E CBDMA Registers [8086:2021] (rev 07)
IOMMU Group 20 00:1c.1 PCI bridge [0604]: Intel Corporation C620 Series Chipset Family PCI Express Root Port #2 [8086:a191] (rev f9)
IOMMU Group 21 00:1c.2 PCI bridge [0604]: Intel Corporation C620 Series Chipset Family PCI Express Root Port #3 [8086:a192] (rev f9)
IOMMU Group 22 00:1c.3 PCI bridge [0604]: Intel Corporation C620 Series Chipset Family PCI Express Root Port #4 [8086:a193] (rev f9)
IOMMU Group 23 00:1c.5 PCI bridge [0604]: Intel Corporation C620 Series Chipset Family PCI Express Root Port #6 [8086:a195] (rev f9)

从打印可以看出,/sys/kernel/iommu_groups/ 这个kset 下有所有的iommu_group 信息,然后 对应的某个group下目前暴露两个信息,一个是归属在这个group的设备,一个是他的reserved_regions.

root@G3:/sys/kernel/iommu_groups# ls
0  10  12  14  16  18  2   21  23  25  27  29  30  32  34  36  38  4   41  43  45  47  49  50  52  54  56  58  6   61  63  65  67  69  70  72  74  76  78  8   81  83  85  87  89  90  92  94  96
1  11  13  15  17  19  20  22  24  26  28  3   31  33  35  37  39  40  42  44  46  48  5   51  53  55  57  59  60  62  64  66  68  7   71  73  75  77  79  80  82  84  86  88  9   91  93  95
root@G3:/sys/kernel/iommu_groups# cd 2
root@G3:/sys/kernel/iommu_groups/2# ls
devices  reserved_regions  type
root@G3:/sys/kernel/iommu_groups/2# ll
total 0
drwxr-xr-x  3 root root    0 10月 25 09:23 ./
drwxr-xr-x 99 root root    0 10月 25 03:40 ../
drwxr-xr-x  2 root root    0 10月 29 10:43 devices/
-r--r--r--  1 root root 4096 10月 30 10:15 reserved_regions
-r--r--r--  1 root root 4096 10月 30 10:15 type
root@G3:/sys/kernel/iommu_groups/2# ls devices/
0000:00:04.1
root@G3:/sys/kernel/iommu_groups/2# pwd
/sys/kernel/iommu_groups/2
root@G3:/sys/kernel/iommu_groups/2# ls devices/
0000:00:04.1
root@G3:/sys/kernel/iommu_groups/2# ls reserved_regions
reserved_regions
root@G3:/sys/kernel/iommu_groups/2# cat reserved_regions
0x00000000fee00000 0x00000000feefffff msi
root@G3:/sys/kernel/iommu_groups/2# ls ../3/
devices/          reserved_regions  type
root@G3:/sys/kernel/iommu_groups/2# ls ../3/devices/
0000:00:04.2
root@G3:/sys/kernel/iommu_groups/2# cat ../3/reserved_regions/
cat: ../3/reserved_regions/: Not a directory
root@G3:/sys/kernel/iommu_groups/2# cat ../3/reserved_regions
0x00000000fee00000 0x00000000feefffff msi

4、总线地址是个什么概念
以pci总线举例,BAR读取到的是PCI地址空间中的地址,不等同于CPU认识的内存地址。虽然在x86上如果没有开启IOMMU时,它们的值一般是相同的,但是对于其他构架的CPU如PowerPC就可以是不一样的。
所以正确的使用BAR空间的方法:

pciaddr=pci_resource_start(pdev,1);
if(pciaddr!=NULL)
{
ioremap(pciaddr,xx_SIZE);
}

而不是下面这样错误的方法:

pci_read_config_dword(pdev,1,&pciaddr);
ioremap(pciaddr,xx_SIZE);

对于内核态cpu的地址来说,它只关心内核态 虚拟地址 通过mmu 转为 物理地址,在设备驱动通知设备做dma操作的时候,直接给设备传递没有经过dma_map的地址,是会有问题的。

5、如何确认iommu的硬件加载情况

[root@localhost dma]# dmesg -T|grep -i dmar
[Sat Oct  9 08:56:54 2021] ACPI: DMAR 000000006fffd000 00200 (v01 DELL   PE_SC3   00000001 DELL 00000001)----------linux启动时扫描acpi的table
[Sat Oct  9 08:57:01 2021] DMAR: Host address width 46
[Sat Oct  9 08:57:01 2021] DMAR: DRHD base: 0x000000d37fc000 flags: 0x0
[Sat Oct  9 08:57:01 2021] DMAR: dmar0: reg_base_addr d37fc000 ver 1:0 cap 8d2078c106f0466 ecap f020df
[Sat Oct  9 08:57:01 2021] DMAR: DRHD base: 0x000000e0ffc000 flags: 0x0
[Sat Oct  9 08:57:01 2021] DMAR: dmar1: reg_base_addr e0ffc000 ver 1:0 cap 8d2078c106f0466 ecap f020df
[Sat Oct  9 08:57:01 2021] DMAR: DRHD base: 0x000000ee7fc000 flags: 0x0
[Sat Oct  9 08:57:01 2021] DMAR: dmar2: reg_base_addr ee7fc000 ver 1:0 cap 8d2078c106f0466 ecap f020df
[Sat Oct  9 08:57:01 2021] DMAR: DRHD base: 0x000000fbffc000 flags: 0x0
[Sat Oct  9 08:57:01 2021] DMAR: dmar3: reg_base_addr fbffc000 ver 1:0 cap 8d2078c106f0466 ecap f020df
[Sat Oct  9 08:57:01 2021] DMAR: DRHD base: 0x000000aaffc000 flags: 0x0
[Sat Oct  9 08:57:01 2021] DMAR: dmar4: reg_base_addr aaffc000 ver 1:0 cap 8d2078c106f0466 ecap f020df
[Sat Oct  9 08:57:01 2021] DMAR: DRHD base: 0x000000b87fc000 flags: 0x0
[Sat Oct  9 08:57:01 2021] DMAR: dmar5: reg_base_addr b87fc000 ver 1:0 cap 8d2078c106f0466 ecap f020df
[Sat Oct  9 08:57:01 2021] DMAR: DRHD base: 0x000000c5ffc000 flags: 0x0
[Sat Oct  9 08:57:01 2021] DMAR: dmar6: reg_base_addr c5ffc000 ver 1:0 cap 8d2078c106f0466 ecap f020df
[Sat Oct  9 08:57:01 2021] DMAR: DRHD base: 0x0000009d7fc000 flags: 0x1
[Sat Oct  9 08:57:01 2021] DMAR: dmar7: reg_base_addr 9d7fc000 ver 1:0 cap 8d2078c106f0466 ecap f020df
[Sat Oct  9 08:57:01 2021] DMAR: RMRR base: 0x000000402f8000 end: 0x000000482fffff
[Sat Oct  9 08:57:01 2021] DMAR: RMRR base: 0x0000006ef60000 end: 0x0000006ef62fff
[Sat Oct  9 08:57:01 2021] DMAR: ATSR flags: 0x0
[Sat Oct  9 08:57:01 2021] DMAR: ATSR flags: 0x0
//caq:下面是ir部分的开始打印,见 ir_parse_one_ioapic_scope 函数:
[Sat Oct  9 08:57:01 2021] DMAR-IR: IOAPIC id 12 under DRHD base  0xc5ffc000 IOMMU 6//caq:注意,ir是interrupt remapping 的简写,
[Sat Oct  9 08:57:01 2021] DMAR-IR: IOAPIC id 11 under DRHD base  0xb87fc000 IOMMU 5//caq:最后的数字是 intel_iommu.seq_id
[Sat Oct  9 08:57:01 2021] DMAR-IR: IOAPIC id 10 under DRHD base  0xaaffc000 IOMMU 4//caq: IOAPIC id是指 enumeration_id
[Sat Oct  9 08:57:01 2021] DMAR-IR: IOAPIC id 18 under DRHD base  0xfbffc000 IOMMU 3
[Sat Oct  9 08:57:01 2021] DMAR-IR: IOAPIC id 17 under DRHD base  0xee7fc000 IOMMU 2
[Sat Oct  9 08:57:01 2021] DMAR-IR: IOAPIC id 16 under DRHD base  0xe0ffc000 IOMMU 1
[Sat Oct  9 08:57:01 2021] DMAR-IR: IOAPIC id 15 under DRHD base  0xd37fc000 IOMMU 0
[Sat Oct  9 08:57:01 2021] DMAR-IR: IOAPIC id 8 under DRHD base  0x9d7fc000 IOMMU 7
[Sat Oct  9 08:57:01 2021] DMAR-IR: IOAPIC id 9 under DRHD base  0x9d7fc000 IOMMU 7
[Sat Oct  9 08:57:01 2021] DMAR-IR: HPET id 0 under DRHD base 0x9d7fc000
[Sat Oct  9 08:57:01 2021] DMAR-IR: Queued invalidation will be enabled to support x2apic and Intr-remapping.
[Sat Oct  9 08:57:01 2021] DMAR-IR: Enabled IRQ remapping in x2apic mode---------------关键打印,表明irq的remapping 的工作模式是 x2apic 还是 xapic
[Sat Oct  9 08:57:02 2021] DMAR: [Firmware Bug]: RMRR entry for device 1a:00.0 is broken - applying workaround

6、iommu_domain 与 iommu 硬件之间的关系:
以intel 为例,我们先来看 iommu_domain 的数据结构:

struct dmar_domain {//caq:这个直接继承 iommu_domain
	int	nid;			/* node id */

	unsigned	iommu_refcnt[DMAR_UNITS_SUPPORTED];//caq:每个domain 在 对应intel_iommu硬件中的引用计数
					/* Refcount of devices per iommu *///caq:说明一个domain 可以包含多个iommu,特别是vm场景


	u16		iommu_did[DMAR_UNITS_SUPPORTED];//这个domain 在 对应intel_iommu 中的id
					/* Domain ids per IOMMU. Use u16 since
					 * domain ids are 16 bit wide according
					 * to VT-d spec, section 9.3 */

	bool has_iotlb_device;//caq:表示这个Domain里是否有具备IO-TLB的设备
	struct list_head devices;	/* all devices' list */
	struct iova_domain iovad;	/* iova's that belong to this domain *///caq:属于这个dmar_domain的iova_domain

	struct dma_pte	*pgd;		/* virtual address *///caq:指向了IOMMU页表的基地址是IOMMU页表的入口
	int		gaw;		/* max guest address width */

	/* adjusted guest address width, 0 is level 2 30-bit */
	int		agaw;//caq:0代表level 2,

	int		flags;		/* flags to find out type of domain *///caq:dmain 的类型也存放在这

	int		iommu_coherency;/* indicate coherency of iommu access *///caq:一致性
	int		iommu_snooping; /* indicate snooping control feature*///caq:是否嗅探总线的控制feature
	int		iommu_count;	/* reference count of iommu */
	int		iommu_superpage;/* Level of superpages supported:
					   0 == 4KiB (no superpages), 1 == 2MiB,
					   2 == 1GiB, 3 == 512GiB, 4 == 1TiB *///caq:各种大页的标志,0就是代表普通4k页
	u64		max_addr;	/* maximum mapped address *///caq:最大映射的addr

	struct iommu_domain domain;	/* generic domain data structure for
					   iommu core */
};

iommu_refcnt 成员的数组形式,对于 DOMAIN_FLAG_VIRTUAL_MACHINE 类型的 ,一个iommu_domain 可以有多个 iommu的硬件,而对于
其他类型,则一般是一对一的关系。

7.iommu 设备的 attr,是指该iommu 设备抽象出来的属性,比如是否支持二级转换,pgsize 的值等

enum iommu_attr {
	DOMAIN_ATTR_GEOMETRY,
	DOMAIN_ATTR_PAGING,
	DOMAIN_ATTR_WINDOWS,
	DOMAIN_ATTR_FSL_PAMU_STASH,
	DOMAIN_ATTR_FSL_PAMU_ENABLE,
	DOMAIN_ATTR_FSL_PAMUV1,
	DOMAIN_ATTR_NESTING,	/* two stages of translation */
	DOMAIN_ATTR_MAX,
};

8.iommu的 reserve type,是对dev 保留不需要映射的区域的一个类型划分。

/* These are the possible reserved region types */
enum iommu_resv_type {
	/* Memory regions which must be mapped 1:1 at all times */
	IOMMU_RESV_DIRECT,//caq:1:1 直接映射
	/* Arbitrary "never map this or give it to a device" address ranges */
	IOMMU_RESV_RESERVED,//caq:保留的,不要映射
	/* Hardware MSI region (untranslated) */
	IOMMU_RESV_MSI,//caq:硬件的 msi 区域
	/* Software-managed MSI translation window */
	IOMMU_RESV_SW_MSI,//caq:软件保留的msi 区域
};

9.对于iommu设备,虽然抽象出一个iommu_device,但是intel 对这个的实现是 分开的,dmar_rmrr_unit 用来描述一个iommu的硬件部分,而用 intel_iommu 来实现iommu_device,
当然, intel_iommu 不能是空中楼阁,它会有一个指向 dmar_rmrr_unit 的指针,如下示例:

struct intel_iommu {
	void __iomem	*reg; /* Pointer to hardware regs, virtual addr *///caq:这个是寄存器组的虚拟地址,通过ioremap而来
......
        struct **iommu_device **iommu;  /* IOMMU core code handle *///caq:intel_iommu首先是一个 iommu_device,核心的是ops
	struct dmar_drhd_unit *drhd;//caq:也有一个指向 dmar_drhd_unit 的指针,1对1------------------intel对iommu
};

10、如果没有iommu,能透传设备给虚机么?
一般来说,在没有IOMMU的情况下,设备必须访问真实的物理地址HPA,而虚机可见的是GPA,
如果让虚机填入真正的HPA,那样的话相当于虚机可以直接访问物理地址,会有安全隐患。
所以针对没有IOMMU的情况,不能用透传的方式,对于设备的直接访问都会有VMM接管,这样就不会对虚机暴露HPA。

11、iova_domain 是干嘛的
iova就是va针对dma领域的一个虚拟地址的抽象,那怎么管理这些地址呢?比如哪些地址被映射过,就需要一个记录的地方,

/* holds all the iova translations for a domain */
struct iova_domain {//caq:用一棵红黑树来记录iova->hpa的地址翻译关系
	spinlock_t	iova_rbtree_lock; /* Lock to protect update of rbtree *///caq:全局锁
	struct rb_root	rbroot;		/* iova domain rbtree root */
	struct rb_node	*cached_node;	/* Save last alloced node *///caq:最后申请的一个node
	struct rb_node	*cached32_node; /* Save last 32-bit alloced node *///caq:最后申请的32bit一个node
	unsigned long	granule;	/* pfn granularity for this domain *///caq:pfn粒度
	unsigned long	start_pfn;	/* Lower limit for this domain *///caq:域内起始的pfn
	unsigned long	dma_32bit_pfn;
	struct iova	anchor;		/* rbtree lookup anchor */
	struct iova_rcache rcaches[IOVA_RANGE_CACHE_MAX_SIZE];	/* IOVA range caches *///caq:iova的rang先从这个cache 中申请
//caq:数组内是2的N次方大小的range
	iova_flush_cb	flush_cb;	/* Call-Back function to flush IOMMU
					   TLBs */

	iova_entry_dtor entry_dtor;	/* IOMMU driver specific destructor for
					   iova entry */

	struct iova_fq __percpu *fq;	/* Flush Queue *///caq:下面这部分都是关于flush相关的

	atomic64_t	fq_flush_start_cnt;	/* Number of TLB flushes that
						   have been started */

	atomic64_t	fq_flush_finish_cnt;	/* Number of TLB flushes that
						   have been finished */

	struct timer_list fq_timer;		/* Timer to regularily empty the
						   flush-queues *///caq:定时器用来刷掉fq中的cmd
	atomic_t fq_timer_on;			/* 1 when timer is active, 0
						   when not */
};

很显然,不同的iommu_domain都有这么一个iova_domain,两者是1:1的关系。
一般来说,当 iommu_domain 的类型是 IOMMU_DOMAIN_DMA 的话,从iommu_domain索引 iova_domain是放在 iova_cookie

//caq:从驱动的dma api请求过来,完成iova的分配
static dma_addr_t iommu_dma_alloc_iova(struct iommu_domain *domain,
		size_t size, dma_addr_t dma_limit, struct device *dev)
{
	struct iommu_dma_cookie *cookie = domain->iova_cookie;
	struct iova_domain *iovad = &cookie->iovad;//caq:iova_domain 在此

11、dev怎么关联对应的 iommu 设备呢?
不同的arch有不同的实现,对于arm smmuv3来说,

//caq:将设备dev关联到 iommu_domain
static int arm_smmu_attach_dev(struct iommu_domain *domain, struct device *dev)
{
	int ret = 0;
	struct arm_smmu_device *smmu;
	struct arm_smmu_domain *smmu_domain = to_smmu_domain(domain);//caq:从iommu_domain 中得到arm_smmu_domain
	struct arm_smmu_master_data *master;
	struct arm_smmu_strtab_ent *ste;

	if (!dev->iommu_fwspec)
		return -ENOENT;

**	master = dev->iommu_fwspec->iommu_priv;**
**	smmu = master->smmu;**

对于intel来说,比较复杂,可以参照 device_to_iommu 函数的实现:

//caq:bus和devfn是出参
static struct intel_iommu *device_to_iommu(struct device *dev, u8 *bus, u8 *devfn)
{
	struct dmar_drhd_unit *drhd = NULL;
	struct intel_iommu *iommu;
	struct device *tmp;
	struct pci_dev *ptmp, *pdev = NULL;
	u16 segment = 0;
	int i;

	if (iommu_dummy(dev))//caq:该设备没有关联归属iommu,
		return NULL;

	if (dev_is_pci(dev)) {//caq:pci设备,也就是他的bus type 是pci_bus_type
		struct pci_dev *pf_pdev;

		pdev = to_pci_dev(dev);

#ifdef CONFIG_X86
		/* VMD child devices currently cannot be handled individually */
		if (is_vmd(pdev->bus))//caq:VMD:Intel® Volume Management Device
			return NULL;
#endif

		/* VFs aren't listed in scope tables; we need to look up
		 * the PF instead to find the IOMMU. */
		pf_pdev = pci_physfn(pdev);**//caq:以 pf 为dev,在drhd管理的dev中,是看不到vf的**
		dev = &pf_pdev->dev;
		segment = pci_domain_nr(pdev->bus);//caq:segment其实就是bus归属的domain
	} else if (has_acpi_companion(dev))
		dev = &ACPI_COMPANION(dev)->dev;

	rcu_read_lock();
	for_each_active_iommu(iommu, drhd) {//caq:除掉ignored 的所有 intel_iommu
		if (pdev && segment != drhd->segment)
			continue;

		for_each_active_dev_scope(drhd->devices,
					  drhd->devices_cnt, i, tmp) {
			if (tmp == dev) {

参考资料:
http://www.intel.com/content/dam/www/public/us/en/documents/product-specifications/vt-directed-io-spec.pdf

水平有限,如果有错误,请帮忙提醒我。如果您觉得本文对您有帮助,可以点击下面的 推荐 支持一下我。版权所有,需要转发请带上本文源地址,博客一直在更新,欢迎 关注 。
原文地址:https://www.cnblogs.com/10087622blog/p/15468851.html