IOMMU是如何划分PCI device group的?

IOMMU的一个主要作用就是将IO设备发出的请求地址IOVA(I/O Virtual Address)转化为物理内存地址,如果没有IOMMU,那么所有的IO设备都将使用相同的物理地址空间访问物理内存。引入IOMMU后,就会引入IOVA这个地址空间,IO设备可以通过IOVA虚拟地址访问物理内存。

在虚拟化引入之前,IOMMU主要有两个功能:

  1. 创建IOVA到HPA的映射,让设备能够访问任意物理内存。比如,有些I/O设备的寻址空间只有4G,但是平台的物理内存大于4G,为了让这样的I/O设备能够访问4G空间以上的内存,就需要建立一个IOVA -> HPA的映射。
  2. 通过IOVA -> HPA的映射,创建内存连续的DMA访问,提高系统性能。比如,内核分配2个不相邻的4KB内存页,若没有IOMMU,需要建立两次DMA操作才能访问到这两个内存页,若有IOMMU,则可以将这两个不相邻的物理内存页映射到相邻的IOVA上,这样设备只需要建立一次DMA操作,即可访问到这两个不相邻的物理内存页。

除了以上功能,虚拟化对IOMMU的主要应用就是对设备的隔离。在引入PCIe之前,传统的PCI总线对设备的隔离是很难实现的,因为传统的PCI总线采用的是总线仲裁机制,同一时间只有一个PCI设备独占整个PCI总线,设备发出的TLP包不包含requester ID,导致RC(Root Complex)或者其他接收设备无法分辨出接收到的TLP包来自哪个设备,也就无法达到设备区分和隔离的目的。虽然PCI-X在一定程度上引入了requester ID,但是有些规则还是不够完善,还做不到完全的隔离。

对于PCIe架构而言,PCIe设备发出的所有TLP包都会包含一个requester ID(即PCI设备的Bus、Device和Function Number),这个ID可以唯一地辨别一个PCI设备,TLP的接收设备可以使用这个ID来查找IOVA的地址转换页表,这样PCIe设备就可以使用虚拟地址(IOVA)访问物理内存。这时候,对于透传给虚拟机的PCI设备,软件需要做的就是将PCIe设备所在的虚拟机的虚拟机物理地址GPA(Guest Physical Address)到主机物理地址HPA(Host Physical Address)的映射告知IOMMU,当PCIe设备发生DMA的时候,IOMMU选择目标虚拟机的GPA->HPA的映射表对地址进行转换,这样就能够让透传的PCI设备只能访问到虚拟机的物理地址空间,即分配给虚拟机的物理内存,达到设备隔离的目的。

device group指的是从IOMMU角度看能够进行隔离的最小设备集。device group的划分规则包括:

  1. device group中所有的设备将会共享一个IOVA地址空间。对于传统PCI总线上的设备而言,TLP中不包含Requester ID,无法进行设备区分,所以整个传统PCI总线上的设备都将被划分到同一个device group上。PCIe设备发出的TLP带有requester ID,所以可以进行设备区分,也就是可以使用独立的IOVA地址空间。
  2. 从设备发出的TLP是否都能够到达IOMMU,如果设备发出的TLP可以不经过IOMMU,IOMMU就无法对TLP中包含的地址进行转换,即IOMMU无法控制设备的访问地址,无法达到隔离的目的。

PCIe ACS(Access Control Service) Extended Capability是PCIe标准中引入的用于控制对接收到的TLP(Transaction Layer Packets)进行正常的路由(即向上提交),阻塞或者是重定向转发的特性,该特性可以实现PCIe设备peer-to-peer的数据传输,即设备之间的数据交互可以不经过IOMMU。ACS位于Root Complex,downstream port或者Muti-Function Devices(包括支持SR-IOV特性的PCIe设备),downstream port经常以PCI bridge的形式表现出来。

例如,可以在连接PCIe Root Port的PCI Bridge中找到了ACS Capability。

IOMMU是如何划分PCI device group的?_第1张图片

带有SR-IOV功能的网卡也可能提供ACS特性

IOMMU是如何划分PCI device group的?_第2张图片

总的来说,iommu device group的划分总体来说按照以下两个规则 :

  1. 一条传统PCI总线或者PCI-X总线上的PCI设备都划分到同一个device group。
  2. 从上往下看,若某一个PCIe downstream port或者multif-function device开启了ACS特性,该PCIe port或者multi-function下面的所有设备也都划分到同一个device group。否则每个PCIe设备都划分为一个独立的device group。

比如,以下面的拓扑图为例

IOMMU是如何划分PCI device group的?_第3张图片

  • 红框中的PCI Express-PCI/PCI-X Bridge下面挂的设备都将被划分到同一个iommu device group中。
  • PCIe switch的某个downstream port开启了ACS特性,则该PCIe switch port可能将接收到的PCIe设备发出的TLP包转发到该switch port下面的其他设备,所以为了保证完全的隔离,只能将该switch port下面的所有设备都归到同一个device group中。
  • 系统中其余的PCIe switch port,root port或PCIe Endpoint device都没有开启ACS特性,所以其余的PCIe设备,每个设备都可以各自独立为一个iommu device group。

内核函数pci_device_group()用于对pci设备进行device group的划分,对于一个pci device而言,其主体结构如下所示:

struct iommu_group *pci_device_group(struct device *dev)
{
    	struct pci_dev *pdev = to_pci_dev(dev);

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

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

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

		pdev = bus->self;
		group = iommu_group_get(&pdev->dev);
		if (group)
			return group;
	}

	return iommu_group_alloc();
}

函数会从PCI设备往上直到root(包括各种桥)进行检测,检测设备是否支持ACS(Access Control Service)特性,如果一路上都没有开启ACS特性,则调用iommu_group_alloc()为该设备添加一个新的iommu device group。如果设备位于传统的PCI bus(不是PCIe)上,则当该传统PCI bus上已经有设备分配了iommu device group,则将会调用pci_for_each_dma_alias()函数,将该设备直接添加到目标iommu device group中。

 

 

你可能感兴趣的:(x86架构,计算机架构,虚拟化,iommu,pci-e,虚拟机)