DMAR表 + iommu


 

話說,盤古開天的時候,設備訪問內存(DMA)就只接受物理地址,所以CPU要把一個地址告訴設備,就只能給物理地址。但設備的地址長度還比CPU的總線長度短,所以只能分配低地址來給設備用。所以CPU這邊的接口就只有dma=dma_alloc(dev, size),分配了物理地址,然后映射為內核的va,然后把pa作為dma地址,CPU提供給設備,設備訪問這個dma地址,就得到內存里面的那個數據了。

后來設備做強了,雖然地址總線不長,但可以帶一個頁表,把它能訪問的有限長度的dma地址轉換為和物理空間一樣長的物理地址。這樣就有了dma=dma_map(dev, va)。這樣,其實我們對同一個物理地址就有三個地址的概念了:CPU看到的地址va,設備看到的地址dma,和總線看到的pa。

設備帶個頁表,這不就是mmu嗎?於是,通用的iommu的概念(硬件上)就被發明了。所以dma_map(dev, va),在有iommu的設備上,就變成了對iommu的通用頁表操作。iova=iommu_alloc(), iommu_map(domain, iova, pa);

這里我們發現了兩個新概念,一個是iova,這個很好理解,就是原來的dma地址了(io的va嘛),另一個是domain,這本質是一個頁表,為什么要把這個頁表獨立封裝出來,這個我們很快會看到的。

我這個需要提醒一句,iommu用的頁表,和mmu用的頁表,不是同一個頁表,為了容易區分,我們把前者叫做iopt,后者叫pt。兩者都可以翻譯虛擬地址為物理地址,物理地址是一樣的,都是pa,而對於va,前者我們叫iova,后者我們叫va。

 

又到了后來,人們需要支持虛擬化,提出了VFIO的概念,需要在用戶進程中直接訪問設備,那我們就要支持在用戶態直接發起DMA操作了,用戶態發起DMA,它自己在分配iova,直接設置下來,要求iommu就用這個iova,那我內核對這個設備做dma_map,也要分配iova。這兩者沖突怎么解決呢?

dma_map還可以避開用戶態請求過的va空間,用戶態的請求沒法避開內核的dma_map的呀。

VFIO這樣解決:默認情況下,iommu上會綁定一個default_domain,它具有IOMMU_DOMAIN_DMA屬性,原來怎么弄就怎么弄,這時你可以調用dma_map()。但如果你要用VFIO,你就要先detach原來的驅動,改用VFIO的驅動,VFIO就給你換一個domain,這個domain的屬性是IOMMU_DOMAIN_UNMANAGED,之后你愛用哪個iova就用那個iova,你自己保證不會沖突就好,VFIO通過iommu_map(domain, iova, pa)來執行這種映射。

等你從VFIO上detach,把你的domain刪除了,這個iommu就會恢復原來的default_domain,這樣你就可以繼續用你的dma API了。

這種情況下,你必須給你的設備選一種應用模式,非此即彼。

很多設備,比如GPU,沒有用VFIO,也會自行創建unmanaged的domain,自己管理映射,這個就變成一個通用的接口了。

 

好了,這個是Linux內核的現狀(截止到4.20)。如果基於這個現狀,我們要同時讓用戶態和內核態都可以做mapping的話,唯一的手段是使用unmanaged模式,然后va都從用戶態分配(比如通過mmap),然后統一用iommu_map完成這個映射。

 

但實際上,Linux的這個框架,已經落后於硬件的發展了。因為現在大部分IOMMU,都支持多進程訪問。比如我有兩個進程同時從用戶態訪問設備,他們自己管理iova,這樣,他們給iommu提供的iova就可能是沖突的。所以,IOMMU硬件同時支持多張iopt,用進程的id作為下標(對於PCIE設備來說,就是pasid了)。

這樣,我們可以讓內核使用pasid=0的iopt,每個用戶進程用pasid=xxx的iopt,這樣就互相不會沖突了。

 

為了支持這樣的應用模式,ARM的Jean Philipse做了一套補丁,為domain增加pasid支持。他的方法是domain上可以bind多個pasid,bind的時候給你分配一個io_mm,然后你用iommu_sva_map()帶上這個io_mm來做mapping。

這種情況下,你不再需要和dma api隔離了,因為他會自動用pasid=0(實際硬件不一定是這樣的接口,這只是比喻)的iopt來做dma api,用其他pasid來做用戶態。這時你也不再需要unmanaged的domain了。你繼續用dma的domain,然后bind一個pasid上去即可。

 

但Jean這個補丁上傳的時候正好遇到Intel的Scalable Virtual IO的補丁在上傳,Intel要用這個特性來實現更輕量級的VFIO。原來的VFIO,是整個設備共享給用戶態的,有了pasid這個概念,我可以基於pasid分配資源,基於pasid共享給用戶態。但Jean的補丁要求使用的時候就要bind一個pasid上來。但VFIO是要分配完設備,等有進程用這個設備的時候才能提供pasid。

為了解決這個問題,Jean又加了一個aux domain的概念,你可以給一個iommu創建一個primary domain,和多個aux domain。那些aux domain可以晚點再綁定pasid上來。

 

后面這個變化,和前面的接口是兼容的,對我們來說都一樣,我們只要有pasid用就可以了。

 

 

 

一些關鍵詞:

  • DMAR - DMA重映射
  • DRHD - DMA重映射硬件​​單元定義
  • RMRR - 預留內存區域報告結構
  • ZLR - 從PCI設備讀取零長度
  • IOVA - IO虛擬地址。

基本的東西

ACPI枚舉並列出平台中的不同DMA引擎,以及PCI設備與DMA引擎控制它們之間的設備范圍關系。


什么是RMRR?

BIOS控制一些設備,例如USB設備執行PS2仿真。用於這些設備的存儲區域在e820映射中標記為保留。當我們打開DMA轉換時,DMA到這些區域將失敗。因此,BIOS使用RMRR指定這些區域以及需要訪問這些區域的設備。 OS希望為這些區域設置單位映射,以便這些設備訪問這些區域。


IOVA是如何產生的?

表現良好的驅動程序在向需要執行DMA的設備發送命令之前調用pci_map _ *()調用。完成DMA並且不再需要映射后,設備將執行pci_unmap _ *()調用以取消映射該區域。

Intel IOMMU驅動程序為每個域分配一個虛擬地址。每個PCIE設備都有自己的域(因此保護)。 p2p網橋下的設備與p2p網橋下的所有設備共享虛擬地址(due to transaction id aliasing for p2p bridges)。

IOVA生成非常通用。我們使用與vmalloc()相同的技術,但這些不是全局地址空間,而是針對每個域分開。不同的DMA引擎可以支持不同數量的域。

我們還為每個映射分配了保護頁面,因此我們可以嘗試捕獲可能發生的任何溢出。

 

硬件結構

先看下一個典型的X86物理服務器視圖:

在多路服務器上我們可以有多個DMAR Unit(這里可以直接理解為多個IOMMU硬件), 每個DMAR會負責處理其下掛載設備的DMA請求進行地址翻譯。例如上圖中, PCIE Root Port (dev:fun) (14:0)下面掛載的所有設備的DMA請求由DMAR #1負責處理, PCIE Root Port (dev:fun) (14:1)下面掛載的所有設備的DMA請求由DMAR #2負責處理, 而DMAR #3下掛載的是一個Root-Complex集成設備[29:0],這個設備的DMA請求被DMAR #3承包, DMAR #4的情況比較復雜,它負責處理Root-Complex集成設備[30:0]以及I/OxAPIC設備的DMA請求。這些和IOMMU相關的硬件拓撲信息需要BIOS通過ACPI表呈現給OS,這樣OS才能正確驅動IOMMU硬件工作。

關於硬件拓撲信息呈現,這里有幾個概念需要了解一下:

  1. DRHD: DMA Remapping Hardware Unit Definition 用來描述DMAR Unit(IOMMU)的基本信息

  2. RMRR: Reserved Memory Region Reporting 用來描述那些保留的物理地址,這段地址空間不被重映射

  3. ATSR: Root Port ATS Capability 僅限於有Device-TLB的情形,Root Port需要向OS報告支持ATS的能力

  4. RHSA: Remapping Hardware Static Affinity Remapping親和性,在有NUMA的系統下可以提升DMA Remapping的性能

BIOS通過在ACPI表中提供一套DMA Remapping Reporting Structure 信息來表述物理服務器上的IOMMU拓撲信息, 這樣OS在加載IOMMU驅動的時候就知道如何建立映射關系了

VT-d-----DMAR表組織結構

  在系統上電的時候,BIOS/UEFI負責檢測並初始化重定向硬件(即VT-d硬件),為其分配相應的物理地址, 並且以ACPI表中的DMAR(DMA Remapping Reporting)表的形式告知VT-d硬件的存在。
  DMAR的格式如下所示,先是標准的APCI表的表頭,然后是Host Address Width表示該系統中支持的物理地址寬度;標志字節Flag表示VT-d硬件支持的一些功能,最后是Remapping Structures,即一堆有組織的結構體用來描述VT-d硬件的功能。

 

Remapping Structures是一堆以Type和Length為開始的結構體,Type的類型可能有下面五種:

 

  BIOS/UEFI負責在初始化系統的時候將這些結構體以類型為順序有序地組織起來(同種類型的結構體可能有多個,也可能壓根就不存在)。第一個結構體必須是DRHD(DMA Remapping Hardware Unit Definition)結構體。

1. DRHD(DMA Remapping Hardware Unit Definition)表

一個DMAR結構體用於唯一定義系統中存在的一個VT-d重定向硬件。其結構體如下所示:

  主要包括兩方面的信息,一是提供VT-d重定向硬件寄存器基地址,為系統軟件訪問VT-d硬件寄存器提供入口(各個偏移量所指向的具體寄存器在VT-d的spec中有詳細的約定,即VT-d硬件的具體實現);另一個是該VT-d重定向硬件所管轄的硬件,由Segment Number和Device Scope兩個區域來定義。Device Scope結構體由Device Scope Entry組成,每個Device Scope Entry可以用來指明一個PCI endpoint device,一個PCI sub-hierarchy,或者其他設備,如I/O xAPIC或者HPET。
 

2. RMRR(Reserved Memory Region Reporting)表

  RMRR表用於表示BIOS或者UEFI為了DMA的使用而保留的一些系統物理內存,這些內存從操作系統的角度來看其屬性為Reserved Memory,因為有一些比較傳統的設備(比如USB、UMA顯卡等)可能會需要用到一些固定的,或者專用的系統內存,這時候就需要BIOS或UEFI為其保留。

 

  該表中,主要包括兩方面信息,即保留的內存的范圍(Reserved Memory Region Base Address和Reserved Memory Region Limit Address)和針對的物理設備(Segment Number和Device Scope)。
 

3. ATSR(Root Port ATS Capability Reporting)表

  ATS是Address Translation Services的意思,它是PCIe Capability的一種,用於表示PCIe設備是否支持經過PCIe Root Port翻譯過的地址。ATSR表只適用於那種PCIe設備支持Device-TLB的系統中,即PCIe設備帶有地址轉換加速功能。一個ATSR表表示一個支持ATS功能的PCIe Root-Port,其結構如下所示:

 

主要包括兩方面信息:Segment Number用於定位PCIe Root-Port;Device Scope用於定位位於該PCIe Root-Port下面的設備。
 

4. RHSA(Remapping Hardware Status Affinity)表

  RHSR表適用於NUMA(Non-Uniform Memory)系統(即不同的CPU Socket都可能會單獨連接一些內存條,不同的CPU Socket對同一物理內存的訪問路徑可能是不同的),並且系統中的VT-d重定向硬件分布於不同的Node上。該表用於表示VT-d重定向硬件從屬於哪個Domain。

 

5. ANDD(ACPI Name-space Device Declaration)表

一個ANDD表用於表示一個以ACPI name-space規則命名,並且可發出DMA請求的設備。ANDD可以和前面提到的Device Scope Entry結合一起時候。

 

 其中ACPI Device Number,相當於在該VT-d硬件管轄范圍內的以ACPI name-space規則命名的硬件ID號,前面Device Scope Entry值需要這個ID號,就可以找到該ANDD表,並從該表的ACPI Object Name區域找到具體的設備。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM