IOMMU是如何劃分PCI device group的?

河馬虛擬化發表於2021-01-05

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。

帶有SR-IOV功能的網路卡也可能提供ACS特性

總的來說,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。

比如,以下面的拓撲圖為例

  • 紅框中的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中。

 

 

相關文章