Chinaunix首页 | 论坛 | 博客
  • 博客访问: 3526
  • 博文数量: 1
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 10
  • 用 户 组: 普通用户
  • 注册时间: 2020-11-10 16:46
文章分类
文章存档

2023年(1)

我的朋友
最近访客

分类: LINUX

2023-01-13 11:19:36

原文地址:iommu passthrough分析 作者:lvyilong316

iommu passthrough分析

——lvyilong316

 

我们经常在OS/proc/cmdline配置中看到诸如intel_iommu=on iommu=pt的配置,其中intel_iommu=on很容易理解,其作用就是是否使能iommu,这里我们主要介绍一下 iommu=pt,并借此对iommu中的一些流程进行梳理。以下相关代码取自linux kernel4.9.

iommu中的关键概念

  在介绍具体流程之前,我们先介绍一下iommu相关代码中的几个重要概念。

struct inte_iommu

struct inte_iommu就是iommu硬件在驱动层所对应的概念,即硬件上一个iommu单元就会对应这样一个结构。

iommummu一样是个硬件单元,通常是实现在北桥之中(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就叫做IOMMUARM中叫SMMU

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 ServicesPCIe ACS

如果不希望P2P直接通信则可以使用PCIeACS特性。ACSPCIe的访问控制服务,控制PCIe数据流向的。ACS可以将peer-to-peer转发的功能关闭,强制将其下所有设备通信送到RootComplexACS可以应用于PCIe Switch以及带有VFPF等所有具有调度功能的节点。所以iommu分组的依据就是ACS这个从其设备分组实现函数pci_device_group中也可以看出。


点击(此处)折叠或打开

  1. struct iommu_group *pci_device_group(struct device *dev)
  2. {
  3.     struct pci_dev *pdev = to_pci_dev(dev);
  4.     struct group_for_pci_data data;
  5.     struct pci_bus *bus;
  6.     struct iommu_group *group = NULL;
  7.     u64 devfns[4] = { 0 };

  8.     if (WARN_ON(!dev_is_pci(dev)))
  9.         return ERR_PTR(-EINVAL);

  10.     /*
  11.      * Find the upstream DMA alias for the device. A device must not
  12.      * be aliased due to topology in order to have its own IOMMU group.
  13.      * If we find an alias along the way that already belongs to a
  14.      * group, use it.
  15.      */
  16.     if (pci_for_each_dma_alias(pdev, get_pci_alias_or_group, &data))
  17.         return data.group;

  18.     pdev = data.pdev;

  19.     /*
  20.      * Continue upstream from the point of minimum IOMMU granularity
  21.      * due to aliases to the point where devices are protected from
  22.      * peer-to-peer DMA by PCI ACS. Again, if we find an existing
  23.      * group, use it.
  24.      */
  25.     for (bus = pdev->bus; !pci_is_root_bus(bus); bus = bus->parent) {
  26.         if (!bus->self)
  27.             continue;

  28.         if (pci_acs_path_enabled(bus->self, NULL, REQ_ACS_FLAGS))
  29.             break;

  30.         pdev = bus->self;

  31.         group = iommu_group_get(&pdev->dev);
  32.         if (group)
  33.             return group;
  34.     }

  35.     /*
  36.      * Look for existing groups on device aliases. If we alias another
  37.      * device or another device aliases us, use the same group.
  38.      */
  39.     group = get_pci_alias_group(pdev, (unsigned long *)devfns);
  40.     if (group)
  41.         return group;

  42.     /*
  43.      * Look for existing groups on non-isolated functions on the same
  44.      * slot and aliases of those funcions, if any. No need to clear
  45.      * the search bitmap, the tested devfns are still valid.
  46.      */
  47.     group = get_pci_function_alias_group(pdev, (unsigned long *)devfns);
  48.     if (group)
  49.         return group;

  50.     /* No shared group found, allocate new */
  51.     group = iommu_group_alloc();
  52.     if (IS_ERR(group))
  53.         return NULL;

  54.     return group;
  55. }


这个函数的核心逻辑在于pci_acs_path_enabledPCIe设备向上通往PCIe根节点的路径上,所有downstream portmulti-function device都要具有ACS特性,若某个downstream portmulti-functionACS特性关闭,则下面的所有设备都必须归到同一个iommu group,否则该PCIe设备就可以独立成一个iommu group。另外PCI总线上的设备都归一个iommu group{BANNED}最佳后同一个iommu group中所有的设备将会共享一个IOVA地址空间

因此,一个iommu group里可能有一个或多个设备。设备透传的时候一个group里面的设备必须都给一个虚拟机,不能给不同的VM,也不能部分被分配到给虚拟机。

struct dmar_domain

dmar_domain里面存储的是iova->hpa的转换页表(即一个IOVA映射空间,一个dmar_domain可以为多个或者一个设备服务。

struct iommu_domain

iommu_domain作为dmar_domain的成员,主要存放iommu核心层的通用数据信息,如iommu_ops,同时作为groupdmar_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)可以参考下图。

 

iommu初始化

intel iommu的发现是从IOMMU_INIT_POST(detect_intel_iommu)这个地方开始的,其大体流程如下图,中间过程不再详细展开。

其中较为复杂的是init_dmars这个函数的作用从它的名字也基本能看出来就是对dma remapping 做一些初始化的工作。具体的比如把每个drhd关联到struct intel_iommu,假设系统当中如果有ndma硬件则系统会创建一个大小为n* sizeof(struct  intel_iommu*)g_iommu数组,首先通过intel_iommu_init_qi 为每个iommu初始化Invalidation Translation Caches 机制;其次通过iommu_init_domains 为每个intel_iommu分配domain_idsdmar_domains,同时为每个intel_iommu分配root_entryroot_table的基址,然后写到基址寄存器RTADDR_REG当中。

这个函数的关键是引入了我们要讨论的iommu_pass_through,其中关键有以下几行代码。


点击(此处)折叠或打开

  1. static int __init init_dmars(void)
  2. {
  3. //...
  4.     if (iommu_pass_through)
  5.         iommu_identity_mapping |= IDENTMAP_ALL;

  6.     if (iommu_identity_mapping) {
  7.         ret = si_domain_init(hw_pass_through);
  8.         if (ret)
  9.             goto free_iommu;
  10.     }
  11.     /*
  12.      * If pass through is not set or not enabled, setup context entries for
  13.      * identity mappings for rmrr, gfx, and isa and may fall back to static
  14.      * identity mapping if iommu_identity_mapping is set.
  15.      */
  16.     if (iommu_identity_mapping) {
  17.         ret = iommu_prepare_static_identity_mapping(hw_pass_through);
  18.         if (ret) {
  19.             pr_crit("Failed to setup IOMMU pass-through\n");
  20.             goto free_iommu;
  21.         }
  22.     }
  23. //...
  24. }


在讨论pass_through前我们先注意一下上面bus_set_iommu(&pci_bus_type, &intel_iommu_ops),这个函数会给pci bus关联相应的iommu_ops,在设备进行dma操作,或者设备热插拔时使用到的iommu操作都是通过这里注册的回调函数实现的。

iommu_pass_through

   终于到我们今天的正题,上节函数中的iommu_pass_through变量即我们在/proc/cmdline中的iommu=pt选项,他是在iommu_setup函数中被赋值的:


点击(此处)折叠或打开

  1. if (!strncmp(p, "pt", 2))
  2.         iommu_pass_through = 1;


并且我们看到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是通过kernelcmdline人为设置iommu=pt来实现的。

si_domain_init创建一个全局的dmar_domain(你可以理解它存储了所有的图当中address translation的页表)si表示的是static即静态的,之所以说是静态的是因为si_domain会把每个node上的内存提前建立好iovahpamapping

阅读(1505) | 评论(0) | 转发(0) |
0

上一篇:没有了

下一篇:没有了

给主人留下些什么吧!~~