IOMMU的一個主要作用就是將IO裝置發出的請求地址IOVA(I/O Virtual Address)轉化為物理記憶體地址,如果沒有IOMMU,那麼所有的IO裝置都將使用相同的物理地址空間訪問物理記憶體。引入IOMMU後,就會引入IOVA這個地址空間,IO裝置可以透過IOVA虛擬地址訪問物理記憶體。

在虛擬化引入之前,IOMMU主要有兩個功能:

建立IOVA到HPA的對映,讓裝置能夠訪問任意物理記憶體。比如,有些I/O裝置的定址空間只有4G,但是平臺的物理記憶體大於4G,為了讓這樣的I/O裝置能夠訪問4G空間以上的記憶體,就需要建立一個IOVA -> HPA的對映。

透過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的劃分規則包括:

device group中所有的裝置將會共享一個IOVA地址空間。對於傳統PCI總線上的裝置而言,TLP中不包含Requester ID,無法進行裝置區分,所以整個傳統PCI總線上的裝置都將被劃分到同一個device group上。PCIe裝置發出的TLP帶有requester ID,所以可以進行裝置區分,也就是可以使用獨立的IOVA地址空間。

從裝置發出的TLP是否都能夠到達IOMMU,如果裝置發出的TLP可以不經過IOMMU,IOMMU就無法對TLP中包含的地址進行轉換,即IOMMU無法控制裝置的訪問地址,無法達到隔離的目的。

但是PCIe的另外一個peer-to-peer傳輸特性卻給裝置隔離帶來的麻煩,peer-to-peer特性是指PCIe裝置發出的資料包可以不一直往上提交到PCIe樹的根節點,而是在中間的PCIe switch直接進行轉發,轉發到其他PCIe裝置上。這樣就會導致IOMMU無法控制到這種peer-to-peer的資料傳輸,達不到裝置隔離的目的。比如,為了效能最佳化,裝置驅動可以透過配置,直接將網絡卡接收到的資料包直接轉發給儲存卡,讓儲存卡將裝置直接寫到儲存裝置上,省去中間CPU和網絡卡、儲存卡的互動。

PCIe ACS(Access Control Service) Extended Capability是PCIe標準中引入的用於控制對接收到的TLP(Transaction Layer Packets)進行正常的路由(即向上提交),阻塞或者是重定向轉發的特性。透過控制該特性,可以讓PCIe裝置之間不發生peer-to-peer的資料傳輸,而是像傳統的PCI匯流排一樣,讓裝置發出的資料包都向上提交到PCIe裝置樹的根上,這樣就可以確保PCIe裝置發出的資料包都能夠被IOMMU截獲(可以理解為IOMMU也是位於PCIe裝置樹的根上)。ACS位於Root Complex,downstream port或者Muti-Function Devices(包括支援SR-IOV特性的PCIe裝置),downstream port經常以PCI bridge的形式表現出來。所以,為了確保PCIe裝置能夠安全地隔離,需要確保PCIe裝置到PCIe樹根節點的路徑上,所有可能執行peer-to-peer轉發的downstream port、multi-function devices都需要透過ACS特性將peer-to-peer轉發的功能關閉。

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

IOMMU是如何劃分PCI device group的?

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

IOMMU是如何劃分PCI device group的?

總的來說,iommu device group的劃分總體來說按照以下兩個規則 :

一條傳統PCI匯流排或者PCI-X總線上的PCI裝置都劃分到同一個device group。

從PCIe裝置往上看,在裝置通往PCIe樹根節點的路徑上,所有的PCIe downstream port和multi-function device都需要透過ACS特性,將peer-to-peer轉發的功能關閉。若某個PCIe downstream port或multi-function未透過ACS特性關閉peer-to-peer特性,則下面的所有裝置都必須歸到同一個iommu group,否則該PCIe裝置就可以獨立成一個iommu group。

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

IOMMU是如何劃分PCI 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都透過ACS特性的控制確保接收到的TLP包都往上傳遞給root complex,所以其餘的PCIe裝置,每個裝置都可以各自獨立為一個iommu device group。

核心函式pci_device_group()用於對pci裝置進行device group的劃分,對於一個pci device而言,其主體結構如下所示:

/*

* To consider a PCI device isolated, we require ACS to support Source

* Validation, Request Redirection, Completer Redirection, and Upstream

* Forwarding。 This effectively means that devices cannot spoof their

* requester ID, requests and completions cannot be redirected, and all

* transactions are forwarded upstream, even as it passes through a

* bridge where the target device is downstream。

*/

#define REQ_ACS_FLAGS (PCI_ACS_SV | PCI_ACS_RR | PCI_ACS_CR | PCI_ACS_UF)

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

}

/*

* Look for existing groups on device aliases。 If we alias another

* device or another device aliases us, use the same group。

*/

group

=

get_pci_alias_group

pdev

unsigned

long

*

devfns

);

if

group

return

group

/*

* Look for existing groups on non-isolated functions on the same

* slot and aliases of those funcions, if any。 No need to clear

* the search bitmap, the tested devfns are still valid。

*/

group

=

get_pci_function_alias_group

pdev

unsigned

long

*

devfns

);

if

group

return

group

return

iommu_group_alloc

();

}

宏定義REQ_ACS_FLAGS中定義了ACS特性中的一些特性,確保資料包在PCIe裝置樹種都能夠往上提交。pci_device_group函式會呼叫pci_acs_path_enabled()函式,檢查該裝置到root complex的路徑上,所有的裝置是都都開啟的REQ_ACS_FLAGS中定義的特性,如果都定義了,則說明該裝置能夠獨立成一個iommu_group。如果裝置位於傳統的PCI bus(不是PCIe)上,則當該傳統PCI bus上已經有裝置分配了iommu device group,則將會呼叫pci_for_each_dma_alias()函式,將該裝置直接新增到目標iommu device group中。