将晦涩难懂的技术讲的通俗易懂
分类: LINUX
2022-09-30 17:05:04
我们经常在OS的/proc/cmdline配置中看到诸如intel_iommu=on iommu=pt的配置,其中intel_iommu=on很容易理解,其作用就是是否使能iommu,这里我们主要介绍一下 iommu=pt,并借此对iommu中的一些流程进行梳理。以下相关代码取自linux kernel4.9.
在介绍具体流程之前,我们先介绍一下iommu相关代码中的几个重要概念。
l struct inte_iommu
struct inte_iommu就是iommu硬件在驱动层所对应的概念,即硬件上一个iommu单元就会对应这样一个结构。
iommu和mmu一样是个硬件单元,通常是实现在北桥之中(Root Complex中),现在北桥通常被集成进SOC中,所以iommu通常都放在SOC内部了。根据iommu的作用,iommu通常在硬件上也叫DMA Remapping Unit(简称DMAR)。
在SOC上可能有一个或多个IOMMU硬件单元,例如多路服务器上可能集成有多个IOMMU硬件单元。每个IOMMU硬件单元负责管理挂载到它所在的PCIe Root Port下所有设备的DMA请求。BIOS会将平台上的IOMMU信息通过ACPI协议报告给操作系统,再由操作系统来初始化和管理这些硬件设备。
不同芯片厂商的实现大同小异,Intel的芯片上叫VT-d (Virtualization Technology for Directed I/O ),AMD就叫做IOMMU,ARM中叫SMMU。
l struct iommu_group
iommu_group就是对iommu硬件单元管理的设备进行分组,每个分组称作一个iommu_group。那么为什么要对设备进行分组呢?
我们知道PCIe允许peer-to-peer通信,即PCIe设备发出的数据包可以不一直往上提交到PCIe的根节点,而是在中间的PCIe Switch直接进行转发,转发到其他PCIe设备上。不经过根节点这样就会导致IOMMU无法控制到这种peer-to-peer的数据传输,达不到设备隔离的目的。例如p2p的两个设备一个在虚拟机内,或一个在物理机上或者另一个虚拟机内,不经过IOMMU的话,地址完全乱了。
Access Control Services(PCIe ACS)
如果不希望P2P直接通信则可以使用PCIe的ACS特性。ACS是PCIe的访问控制服务,控制PCIe数据流向的。ACS可以将peer-to-peer转发的功能关闭,强制将其下所有设备通信送到RootComplex。ACS可以应用于PCIe Switch以及带有VF的PF等所有具有调度功能的节点。所以iommu分组的依据就是ACS。这个从其设备分组实现函数pci_device_group中也可以看出。
点击(此处)折叠或打开
这个函数的核心逻辑在于pci_acs_path_enabled,从PCIe设备向上通往PCIe根节点的路径上,所有downstream port和multi-function device都要具有ACS特性,若某个downstream port或multi-function的ACS特性关闭,则下面的所有设备都必须归到同一个iommu group,否则该PCIe设备就可以独立成一个iommu group。另外PCI总线上的设备都归一个iommu group。{BANNED}最佳后同一个iommu group中所有的设备将会共享一个IOVA地址空间。
因此,一个iommu group里可能有一个或多个设备。设备透传的时候一个group里面的设备必须都给一个虚拟机,不能给不同的VM,也不能部分被分配到给虚拟机。
l struct dmar_domain
dmar_domain里面存储的是iova->hpa的转换页表(即一个IOVA映射空间),一个dmar_domain可以为多个或者一个设备服务。
l struct iommu_domain
iommu_domain作为dmar_domain的成员,主要存放iommu核心层的通用数据信息,如iommu_ops,同时作为group和dmar_domain的关联,一个iommu_domain里面可以有多个iommu_group,然后每个iommu_group通过iommu_domain{BANNED}最佳终找到dmar_domain进行转换。
这里有个问题,一个iommu_group的设备属于一个IOVA地址空间,而一个dmar_domain(或iommu_domain)对应一个IOVA地址空间,那是不是一个iommu_group对应一个dmar_domain呢?其实不是的,注意一个iommu_group的设备属于一个IOVA地址空间,并不是表示一个iommu_group的设备一定要独占一个IOVA地址空间,只能说IOVA地址空间的{BANNED}最佳小粒度是iommu_group,但多个iommu_group是可以共享一个IOVA地址空间的,我们接下来讨论的全局si_domain就是一个例子。关于在硬件上设备如何如何使用bdf号找到相应的iova转换表(domain)可以参考下图。
intel iommu的发现是从IOMMU_INIT_POST(detect_intel_iommu)这个地方开始的,其大体流程如下图,中间过程不再详细展开。
其中较为复杂的是init_dmars,这个函数的作用从它的名字也基本能看出来就是对dma remapping 做一些初始化的工作。具体的比如把每个drhd关联到struct intel_iommu,假设系统当中如果有n个dma硬件则系统会创建一个大小为n* sizeof(struct intel_iommu*)的g_iommu数组,首先通过intel_iommu_init_qi 为每个iommu初始化Invalidation Translation Caches 机制;其次通过iommu_init_domains 为每个intel_iommu分配domain_ids和dmar_domains,同时为每个intel_iommu分配root_entry即root_table的基址,然后写到基址寄存器RTADDR_REG当中。
这个函数的关键是引入了我们要讨论的iommu_pass_through,其中关键有以下几行代码。
点击(此处)折叠或打开
在讨论pass_through前我们先注意一下上面bus_set_iommu(&pci_bus_type, &intel_iommu_ops),这个函数会给pci bus关联相应的iommu_ops,在设备进行dma操作,或者设备热插拔时使用到的iommu操作都是通过这里注册的回调函数实现的。
终于到我们今天的正题,上节函数中的iommu_pass_through变量即我们在/proc/cmdline中的iommu=pt选项,他是在iommu_setup函数中被赋值的:
点击(此处)折叠或打开
并且我们看到iommu_pass_through被设置时,iommu_identity_mapping也会被设置。其实从字面一次就可以看出,所谓iommu_pass_through就是地址直接透传,即不再经过IOVA的地址转换,而直接使用物理地址(hpa),而iommu_identity_mapping的字面意思也很好理解,就是一致性映射。
其实这里面还有一个变量:hw_pass_through ,hw_pass_through表示iommu硬件是否有直通能力,是通过读iommu硬件的ecap来获取的,而iommu_pass_through是通过kernel的cmdline人为设置iommu=pt来实现的。
si_domain_init创建一个全局的dmar_domain(你可以理解它存储了所有的上图当中address translation的页表),si表示的是static即静态的,之所以说是静态的是因为si_domain会把每个node上的内存提前建立好iova到hpa的mapping。