本文會對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) {