iommu分析之---intel iommu初始化


intel 的iommu 是iommu框架的一個實現案例。
由於intel 的iommu 實現得比arm smmv3復雜得多,里面概念也多,所以針對intel 實現的iommu 案例的初始化部分進行一些講解,本文針對4.19內核。
Intel IOMMU的初始化函數在哪調用的呢?
它的初始化函數是:

int __init intel_iommu_init(void)
{
	int ret = -ENODEV;
	struct dmar_drhd_unit *drhd;
	struct intel_iommu *iommu;

	/* VT-d is required for a TXT/tboot launch, so enforce that */
	force_on = tboot_force_iommu();

那這個函數是在常見的模塊初始化里面調用的么?
事實上,它的調用鏈是這樣的,

int __init detect_intel_iommu(void)//caq:IOMMU_INIT_POST調用這個,有amd的,ibm大型機的
{//caq:此時還沒有memory allocator
	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();//caq:bios/uefi提供的dmar table
	if (!ret)
		ret = dmar_walk_dmar_table((struct acpi_table_dmar *)dmar_tbl,
					   &validate_drhd_cb);
	if (!ret && !no_iommu && !iommu_detected && !dmar_disabled) {
		iommu_detected = 1;//caq:返回正常就設定這個標志位
		/* Make sure ACS will be enabled */
		pci_request_acs();
	}

#ifdef CONFIG_X86
	if (!ret)
		**x86_init.iommu.iommu_init = intel_iommu_init;//caq:pci_iommu_init 會統一執行iommu_init**
#endif

	if (dmar_tbl) {
		acpi_put_table(dmar_tbl);//caq:釋放資源
		dmar_tbl = NULL;
	}
	up_write(&dmar_global_lock);

	return ret ? ret : 1;
}

在 detect_intel_iommu 函數中,有一個關鍵行:

x86_init.iommu.iommu_init = intel_iommu_init;//caq:pci_iommu_init 會統一執行iommu_init
然后在 pci_iommu_init 會統一執行 iommu_init 。如下:

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

	**x86_init.iommu.iommu_init();//caq:執行前面 detect_intel_iommu 里面對 x86_init.iommu 賦值的指針。**

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

	return 0;
}

如下的調用鏈:

kernel_init ->
	kernel_init_freeable ->
		do_one_initcall ->
			pci_iommu_init ->
				x86_init.iommu.iommu_init

以上就是intel 的iommu 初始化調用鏈分析。

常見問題:
1、intel怎么管理那么多iommu硬件?
使用一個全局的 dmar_drhd_units 鏈表來管理對應的iommu硬件,dmar_drhd_unit 的list 成員串接在這個全局鏈表中。

crash> list -H dmar_drhd_units -s dmar_drhd_unit.segment,devices_cnt
ffff8b83ff078780
  segment = 0
  devices_cnt = 0
ffff8b83ff078060
  segment = 0
  devices_cnt = 0
ffff8b83ff065680---------管理了三個設備
  segment = 0
  devices_cnt = 3
ffff8b83ff078ae0
  segment = 0
  devices_cnt = 0
ffff8b83ff078c60
  segment = 0
  devices_cnt = 0
ffff8b83ff078480
  segment = 0
  devices_cnt = 0
ffff8b83ff072180--------------管理了8個設備
  segment = 0
  devices_cnt = 8
ffff8b83ff078240
  segment = 0
  devices_cnt = 0

我們可以看到,bios/uefi至少枚舉了8個iommu的硬件,其中兩個intel_iommu設備 分別管理的3個和8個設備。

2、是不是devices_cnt就是所有的設備呢?
實際單板上,pci設備就遠遠不止11個設備,那么其他設備怎么辦呢?devices_cnt 為0,不代表它就不管理設備,相反,還得繼續看include_all標志,
事實上,這個在如下函數有體現:

struct dmar_drhd_unit *
dmar_find_matched_drhd_unit(struct pci_dev *dev)//caq:pci設備找歸屬的drhd_unit 硬件
{
	struct dmar_drhd_unit *dmaru;
	struct acpi_dmar_hardware_unit *drhd;

	dev = pci_physfn(dev);

	rcu_read_lock();
	for_each_drhd_unit(dmaru) {
		drhd = container_of(dmaru->hdr,
				    struct acpi_dmar_hardware_unit,
				    header);

		if (dmaru->**include_all **&&
		    drhd->segment == pci_domain_nr(dev->bus))//caq:include_all是segment內的,不是整個系統的
			goto out;

		if (dmar_pci_device_match(dmaru->devices,//caq:在對應的unit 的devices 數組中找到了dev
					  dmaru->devices_cnt, dev))
			goto out;
	}
	dmaru = NULL;//caq:否則返回NULL
out:
	rcu_read_unlock();

	return dmaru;
}

也就是說,通過一個dmar硬件,支持include_all屬性,將很多設備收納了其中,比如看的其他一個服務器的打印:

crash> dmar_drhd_unit.iommu,include_all,devices_cnt ffff8d1240078360
  iommu = 0xffff8d42ff043500
  include_all = 1 '\001'
  devices_cnt = 0
crash> intel_iommu.name 0xffff8d42ff043500
  name = "dmar7\000\000\000\000\000\000\000"
上面可以看到,dmar7的 devices_cnt  為0,但是真實的管理設備個數卻很多,如下:
[root@localhost devices]# pwd
/sys/class/iommu/dmar7/devices
[root@localhost devices]# ll |wc -l
281

3、如何查看一個設備歸屬的iommu?
除此之外,真正讓一個device跟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) {
				/* For a VF use its original BDF# not that of the PF
				 * which we used for the IOMMU lookup. Strictly speaking
				 * we could do this for all PCI devices; we only need to
				 * get the BDF# from the scope table for ACPI matches. */
				if (pdev && pdev->is_virtfn)//caq:如果是vf 設備,則 拿 pf
					goto got_pdev;

				*bus = drhd->devices[i].bus;//caq:取bus 號
				*devfn = drhd->devices[i].devfn;//caq:取 device和func 號
				goto out;
			}

........

			ptmp = to_pci_dev(tmp);
			if (ptmp->subordinate &&//caq:范圍查找
			    ptmp->subordinate->number <= pdev->bus->number &&
			    ptmp->subordinate->busn_res.end >= pdev->bus->number)
				goto got_pdev;
		}

		if (pdev && drhd->include_all) {
		got_pdev:
			*bus = pdev->bus->number;
			*devfn = pdev->devfn;
			goto out;
		}
	}
	iommu = NULL;
 out:
	rcu_read_unlock();

	return iommu;
}

從如上函數可以看出,跟iommu關聯的設備數,可能會遠遠大於在drhd中看到的devices_cnt,事實上,這個devices_cnt很容易讓人誤解。

4、dmar_disabled 的意思?
但是需要注意的是,如果intel_iommu=on 沒有設置,且 CONFIG_INTEL_IOMMU_DEFAULT_ON 這個config沒有開啟,則會導致 dmar_disabled 為1.

#ifdef CONFIG_INTEL_IOMMU_DEFAULT_ON
int dmar_disabled = 0;//caq:默認是否開啟取決於 CONFIG_INTEL_IOMMU_DEFAULT_ON 是否配置
#else
int dmar_disabled = 1;
#endif /*CONFIG_INTEL_IOMMU_DEFAULT_ON*/

這個 dmar_disabled 的值會影響 int __init intel_iommu_init(void) 函數的行為,

int __init intel_iommu_init(void)
{
.....
if (no_iommu || dmar_disabled) {//caq: 
		/*
		 * We exit the function here to ensure IOMMU's remapping and
		 * mempool aren't setup, which means that the IOMMU's PMRs
		 * won't be disabled via the call to init_dmars(). So disable
		 * it explicitly here. The PMRs were setup by tboot prior to
		 * calling SENTER, but the kernel is expected to reset/tear
		 * down the PMRs.
		 */
		if (intel_iommu_tboot_noforce) {
			for_each_iommu(iommu, drhd)
				iommu_disable_protect_mem_regions(iommu);
		}

		/*
		 * Make sure the IOMMUs are switched off, even when we
		 * boot into a kexec kernel and the previous kernel left
		 * them enabled
		 */
		intel_disable_iommus();//caq:導致intel_iommu的功能被關閉,也就是調用 iommu_disable_translation
		goto out_free_dmar;
	}
.....

所以我們經常能看到硬件上是有很多drhd的硬件,但是軟件上並沒有使能,當然也就沒有注冊到 iommu框架中的,iommu_device_register不會被調用,
全局的 iommu_device_list 只是空的,留下寂寞空虛冷。

而如果軟件上使能了的話,iommu_device_list 就會保留有當前期active的 iommu device,如下:

crash> list -H iommu_device_list -s iommu_device.ops-----------//caq:注意,這個鏈表只有active的成員。
ffff9994ff0709c8
  ops = 0xffffffff952ebcc0
ffff99a53f043dc8
  ops = 0xffffffff952ebcc0
crash> dis -l 0xffffffff952ebcc0
0xffffffff952ebcc0 <intel_iommu_ops>:   adcb   $0xff,-0x6b72(%rdi)

非active的iommu,不會展示在sysfs中,如下案例:

具備iommu功能的硬件打印:
crash> list -H dmar_drhd_units -s dmar_drhd_unit.segment,devices_cnt,include_all,ignored
ffff8d1240078e40
  segment = 0
  devices_cnt = 0
  include_all = 0 '\000'
  ignored = 1 '\001'
ffff8d1240078240
  segment = 0
  devices_cnt = 1
  include_all = 0 '\000'
  ignored = 0 '\000'
ffff8d1240065000
  segment = 0
  devices_cnt = 3
  include_all = 0 '\000'
  ignored = 0 '\000'
ffff8d1240078780
  segment = 0
  devices_cnt = 0
  include_all = 0 '\000'
  ignored = 1 '\001'
ffff8d12400783c0
  segment = 0
  devices_cnt = 0
  include_all = 0 '\000'
  ignored = 1 '\001'
ffff8d1240078960
  segment = 0
  devices_cnt = 0
  include_all = 0 '\000'
  ignored = 1 '\001'
ffff8d1240072fc0
  segment = 0
  devices_cnt = 8
  include_all = 0 '\000'
  **ignored = 1** '\001'//caq:雖然 devices_cnt  不為0,但是依然被ignored。
ffff8d1240078360
  segment = 0
  devices_cnt = 0
  include_all = 1 '\001'
  ignored = 0 '\000'
除掉這些ignored為1的,可以看到active的iommu設備,
angus@jm212:/sys/class/iommu$ pwd
/sys/class/iommu
angus@jm212:/sys/class/iommu$ ls
dmar4  dmar5  dmar7


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM