虛擬化中裝置直通的實現
裝置直通的實現大致分為四個部分,分別是直通裝置發現,虛擬PCI配置空間,中斷重對映,DMA重對映。
直通裝置發現
如何讓虛擬機器發現直通裝置
作業系統初始化時會進行PCI裝置的裝置列舉,裝置列舉從根節點HOST-PCI橋(Header Type為1的PCI裝置),首先探測匯流排0上的各個裝置。每當發現一個橋裝置,當將它為根節點往下探測,如此反覆直到所有裝置都被探測完畢。
探測的方法很直接,從0到PCI_SLOTMAX列舉device number,再結合所在匯流排的bus number與預設的function number(0)組合成bdf,通過將bdf寫入CONFIG_ADDRESS(0xcf8埠),然後向CONFIG_DATA中寫入值便可以讀該個PCI插槽上的PCI裝置。通過讀取PCI裝置"Vendor ID"和"Device ID"能夠知道這個PCI裝置是否存在。如果發現該裝置一個PCI-PCI橋,則建立一個pci_bus資料結構並且連入到由pci_root_buses指向的pci_bus和pci_dev資料結構組成的樹中。
直通裝置發現的關鍵步驟
截獲作業系統對PCI匯流排的訪問(CONFIG_ADDRESS,CONFIG_DATA)
HV將0xcf8埠與0xcfc埠從VMCS中的IO點陣圖中關閉,作業系統對這兩個埠的訪問將下陷到hypervisor中。
將直通裝置記錄在pci_vdevs陣列中
HV需要對每個虛擬機器模擬對應的PCI匯流排,記錄當前給虛擬機器模擬的PCI裝置,當給UOS assign一個直通裝置時,需要給這個裝置建立一個vdev結構體,並將其加入到UOS對應的pci_vdevs陣列中,當作業系統對PCI裝置進行列舉時,能夠發現到這個直通裝置。
虛擬PCI配置空間
裝置直通的一個關鍵點是讓虛擬機器能夠訪問裝置的真實IO空間,它的關鍵是虛擬機器對裝置PCI配置空間的訪問。
PCI配置空間介紹
CONFIG_ADDRESS
x86平臺上作業系統通過IO埠0xCF8-0xCFF訪問PCI裝置,前32位是CONFIG_ADDRESS,後32位是CONFIG_DATA,CONFIG_ADDRESS中包括BDF和register number,可以索引到PCI裝置上的暫存器。
預定義頭部
- Header Type 決定PCI裝置型別,共有三種型別:普通PCI裝置,PCI橋,CardBus橋,每種PCI裝置的配置空間結構都不相同,上圖展示的是普通PCI裝置的配置空間。
- Base Address Registers,基地址暫存器,它記錄PCI暫存器或者裝置RAM在I/O埠(或者實體地址空間)的地址。
- Capabilities Pointer,capabilities list的頭指標
- Interrupt Pin,Interrupt Line,裝置中斷引腳與中斷線
為什麼要虛擬PCI配置空間
PCI配置空間包括預定義頭部(predefined header region)與裝置相關部分(device dependent region)。預定義頭部除了包括vendor id,device id,type之外,還包括了六個bar register。PCI裝置有自己的板上儲存空間,這些儲存空間對映的系統軟體的地址空間中,它們具體的地址就存在於bar register中。由於虛擬機器不能夠直接訪問實體記憶體,所以它也不能夠直接訪問儲存了實體地址的bar register。
裝置相關部分包括了PCI MSI中斷資訊,MSI通過在PCI配置空間中儲存中斷物件與中斷號,能夠繞過IOAPIC,直接向LAPIC發起中斷。如果虛擬機器可以直接讀寫真實裝置的裝置相關部分,它將有能力向其他核或者其他虛擬機器發起中斷。這是不可行的。
因此虛擬機器監控器需要針對直通裝置虛擬出PCI配置空間。
如何虛擬PCI配置空間
- HV 啟動時深度優先掃描並記錄所有的PCI匯流排與裝置
- 關閉I/O bitmap讓虛擬機器對PCI裝置的埠訪問產生下陷(正如前文介紹,虛擬機器通過CONFIG_ADDRSS與CONFIG_DATA兩個埠訪問PCI裝置)。
- 建立轉換表,報告虛擬的PCI BAR給虛擬機器,當虛擬機器通過IO埠訪問PCI裝置(作業系統只能通過埠0xCF8,0xCFC訪問PCI裝置配置空間)時,HV可以截獲操作並通過轉換表把I/O請求轉發到裝置的I/O地址空間。
- 虛擬機器不能直接讀寫PCI配置空間,但是可以直接讀寫PCI板上記憶體。由於HV為虛擬機器寫入BAR暫存器的GPA建立了第二階段頁表對映,當虛擬機器訪問BAR暫存器上的GPA指向的地址時,第二階段翻譯能夠將GPA翻譯成PCI裝置真正對映的實體地址。於是虛擬機器能夠訪問直通裝置的板上記憶體而無需VMM處理。
中斷重對映
為了避免虛擬機器對外設傳送惡意中斷,從而對主機或者虛擬機器進行攻擊。硬體廠商引入了中斷重對映機制,在外設與CPU之間加了一個硬體中斷重對映單元。當接收到來自外設的中斷時,硬體重對映單元會對中斷請求的來源進行有效性驗證,然後以中斷號為索引查詢中斷重對映表,代替外設向目標cpu傳送中斷。中斷重對映表由虛擬機器進行設定與安裝。
VT-d對中斷重對映的支援
下面以Intel VT-d技術為例介紹硬體對於中斷重對映的支援。
為了支援中斷重對映,需要對中斷源進行升級,包括(I/O APIC,MSI,MSI-X),讓中斷重對映硬體能夠從中斷訊息中提出中斷重對映表的索引。因此VT-d設計了可重對映的中斷訊息格式。
中斷重對映硬體能夠通過handle與subhandle計算出中斷在中斷重對映表的索引值。
為了讓中斷重對映硬體知道中斷重對映表的位置,需要在HV初始化的時候分配一塊區域作為中斷重對映表,並將該區域寫入中斷重對映表地址暫存器。
中斷重對映表裡的中斷重對映條目(IRTE)如下
MSI中斷髮送流程
先介紹MSI中斷的傳送流程
- PCIe 裝置在傳送 MSI/MSI-X中斷請求之前,系統軟體需要合理設定PCIe裝置MSI/MSI-X Capability 暫存器,使 Message Address暫存器的值為0xFEExx00y,同時合理地設定 Message Data暫存器Vector欄位。
- PCIe裝置提交MSI/MSI-X中斷請求時,需要向0xFEExx00y地址寫Message Data暫存器中包含的資料,並以儲存器寫TLP的形式傳送到RC。當橋片收到這個TLP後,發現這個TLP的目 的地址在系統匯流排Interrupts儲存器空間中,則將PCIe匯流排的儲存器寫請求轉換為系統匯流排Interrupt Message匯流排事務,並在系統匯流排上廣播。
- 系統匯流排上的CPU,根據APIC ID資訊,選擇是否接收這個Interrupt Message匯流排事務,並進入中斷狀態,之後該CPU將直接從這個匯流排事務中獲得中斷向量號,執行相應的中斷服 務例程,而不需要從APIC中斷控制器獲得中斷。
設定中斷重對映表
以MSI中斷為例,為了之後使用MSI中斷,作業系統啟動時需要對PCI配置空間中的MSI capability進行設定,包括:
- 讀取裝置的訊息控制暫存器的Mulitple Message Capable欄位獲得裝置支援的訊息數量以及是否支援64bit訊息地址。然後使能對應的enable bit。
- 分配base message data pattern以及Message Address。
- 最後使能MSI enable bit並關閉其它的中斷選項。
當虛擬機器設定直通裝置的MSI(X)資訊時,將觸發VM-Exit,此時HV可以設定中斷重對映表,並向Message Address中寫入可重對映的中斷訊息。
當在Message Address中寫入可重對映的中斷訊息後,裝置傳送MSI中斷時將傳送這個中斷訊息到匯流排上,VT-d硬體能夠讀懂這個訊息,並利用可重對映的中斷訊息索引出中斷重對映條目,將其傳送給LAPIC。
DMA重對映
DMA重對映的實現介紹正在整理中。
參考資料
https://projectacrn.github.io/2.4/developer-guides/hld/hv-dev-passthrough.html#hv-device-passthrough
https://kernelgo.org/armv8-virt-guide.html
https://kernelgo.org/vfio-insight.html
https://github.com/minosproject/minos
PCIE_Base_Specification_Revision_4_0_Version 1_0.pdf
vt-directed-io-spec.pdf
https://zhuanlan.zhihu.com/p/326412992
https://luohao-brian.gitbooks.io/interrupt-virtualization/content/vt-d-dma-remapping-fen-xi.html
https://zhuanlan.zhihu.com/p/194244760