Powerenv 上的 PCI Express I/O 虛擬化資源¶
Wei Yang <weiyang@linux.vnet.ibm.com>
Benjamin Herrenschmidt <benh@au1.ibm.com>
Bjorn Helgaas <bhelgaas@google.com>
2014 年 8 月 26 日
本文件描述了 PowerKVM 上 PCI MMIO 資源大小調整和分配的硬體需求,以及通用 PCI 程式碼如何處理此需求。前兩部分描述了可分割槽端點的概念以及 P8 (IODA2) 上的實現。接下來的兩部分討論了在 IODA2 上啟用 SRIOV 的注意事項。
1. 可分割槽端點介紹¶
可分割槽端點 (PE) 是一種將與裝置或一組裝置相關的各種資源分組的方式,以提供分割槽之間的隔離(即,DMA、MSI 等的過濾),並提供一種凍結導致錯誤的裝置以限制錯誤資料傳播可能性的機制。
因此,在硬體中,有一個 PE 狀態表,其中包含每 PE 的一對“凍結”狀態位(一個用於 MMIO,一個用於 DMA,它們一起設定但可以獨立清除)。
當 PE 被凍結時,所有方向的儲存都被丟棄,所有載入都返回全 1 的值。MSI 也被阻止。還有更多狀態可以捕獲導致凍結的錯誤的詳細資訊等,但這不是關鍵。
有趣的部分是各種 PCIe 事務(MMIO、DMA 等)如何與其對應的 PE 匹配。
以下部分粗略描述了我們在 P8 (IODA2) 上的內容。請記住,這都是按 PHB(PCI 主機橋)進行的。每個 PHB 都是一個完全獨立的硬體實體,它複製了整個邏輯,因此有自己的一組 PE 等。
2. P8 (IODA2) 上可分割槽端點的實現¶
P8 每個 PHB 最多支援 256 個可分割槽端點。
入站
對於 DMA、MSI 和入站 PCIe 錯誤訊息,我們有一個表(在記憶體中,但由晶片在硬體中訪問),它提供了 PCIe RID(匯流排/裝置/功能)與 PE 編號之間的直接對應關係。我們稱之為 RTT。
對於 DMA,我們然後為每個 PE 提供一個完整的地址空間,該空間可以包含兩個“視窗”,具體取決於 PCI 地址位 59 的值。每個視窗都可以配置為透過“TCE 表”(IOMMU 轉換表)重新對映,該表具有各種可配置的特性,此處未描述。
對於 MSI,我們在地址空間中有兩個視窗(一個在 32 位空間的頂部,另一個更高),透過地址和 MSI 值的組合,將觸發每個橋的 2048 箇中斷之一。中斷控制器描述符表中也有一個 PE#,它與從 RTT 獲得的 PE# 進行比較,以“授權”裝置發出該特定中斷。
錯誤訊息只使用 RTT。
出站。那是棘手的部分。
像其他 PCI 主機橋一樣,Power8 IODA2 PHB 支援從 CPU 地址空間到 PCI 地址空間的“視窗”。有一個 M32 視窗和十六個 M64 視窗。它們具有不同的特性。首先是它們的共同點:它們將 CPU 地址空間的可配置部分轉發到 PCIe 匯流排,並且必須是大小的自然對齊的 2 的冪。其餘的不同
M32 視窗
大小限制為 4GB。
丟棄地址的最高位(高於大小),並將其替換為可配置的值。這通常用於生成 32 位 PCIe 訪問。我們從 FW 在啟動時配置該視窗,並且不從 Linux 觸控它;它通常設定為將 CPU 地址空間的 2GB 部分轉發到 PCIe 0x8000_0000..0xffff_ffff。(注意:頂部 64KB 實際上是為 MSI 保留的,但此時這不是問題;我們只需要確保 Linux 不在那裡分配任何東西,但 M32 邏輯忽略了這一點,如果我們嘗試,它會轉發到該空間)。
它被分成 256 個大小相等的段。晶片中的一個表將每個段對映到 PE#。這允許將 MMIO 空間的部分分配給 PE,粒度為段。對於 2GB 視窗,段粒度為 2GB/256 = 8MB。
現在,這是我們今天在 Linux 中使用的“主”視窗(不包括 SR-IOV)。我們基本上使用強制橋 MMIO 視窗到段對齊/粒度的技巧,以便可以將橋後面的空間分配給 PE。
理想情況下,我們希望能夠將 PE 中的各個功能分開,但這將意味著使用完全不同的地址分配方案,其中各個功能 BAR 可以“分組”以適應一個或多個段。
M64 視窗
大小必須至少為 256MB。
不轉換地址(PCIe 上的地址與 PowerBus 上的地址相同)。有一種方法也可以設定 PowerBus 未傳遞的最高 14 位,但我們不使用它。
可以配置為分段。當未分段時,我們可以為整個視窗指定 PE#。當分段時,一個視窗有 256 個段;但是,沒有將段對映到 PE# 的表。段號是 PE#。
支援重疊。如果一個地址被多個視窗覆蓋,則有一個定義的順序來確定哪個視窗適用。
我們有程式碼(與 M32 相關的東西相比相當新)利用了這一點來處理 64 位空間中的大型 BAR
我們配置一個 M64 視窗來覆蓋由 FW 為 PHB 分配的整個地址空間區域(大約 64GB,忽略 M32 的空間,它來自不同的“保留”)。我們將其配置為分段。
然後我們做與 M32 相同的事情,使用橋對齊技巧,以匹配那些巨大的段。
由於我們無法重新對映,因此我們還有兩個額外的約束
我們在分配 64 位空間之後進行 PE# 分配,因為我們使用的地址直接確定 PE#。然後,我們更新同時使用 32 位和 64 位空間的裝置的 M32 PE#,或將剩餘的 PE# 分配給僅 32 位的裝置。
我們無法在硬體中“分組”段,因此如果一個裝置最終使用多個段,我們最終會得到多個 PE#。有一種硬體機制可以使凍結狀態級聯到“伴隨” PE,但這僅適用於 PCIe 錯誤訊息(通常用於凍結交換機,它會凍結其所有子項)。所以我們在 SW 中做。在這種情況下,我們失去了一點 EEH 的有效性,但這是我們發現的最好的。因此,當任何 PE 凍結時,我們會凍結該“域”的其他 PE。因此,我們引入了“主 PE”的概念,它用於 DMA、MSI 等,而“輔助 PE”用於剩餘的 M64 段。
我們希望研究在“單個 PE”模式下使用額外的 M64 視窗來覆蓋特定 BAR,以解決其中的一些問題,例如,對於具有非常大的 BAR 的裝置,例如 GPU。這是有道理的,但我們還沒有這樣做。
3. 在 PowerKVM 上使用 SR-IOV 的注意事項¶
SR-IOV 背景
PCIe SR-IOV 功能允許單個物理功能 (PF) 支援多個虛擬功能 (VF)。PF 的 SR-IOV 功能中的暫存器控制 VF 的數量以及它們是否已啟用。
當啟用 VF 時,它們會像正常的 PCI 裝置一樣出現在配置空間中,但 VF 配置空間標頭中的 BAR 是不尋常的。對於非 VF 裝置,軟體使用配置空間標頭中的 BAR 來發現 BAR 大小併為其分配地址。對於 VF 裝置,軟體使用PF SR-IOV 功能中的 VF BAR 暫存器來發現大小並分配地址。VF 的配置空間標頭中的 BAR 是隻讀零。
當在 PF SR-IOV 功能中程式設計 VF BAR 時,它會設定所有相應 VF(n) BAR 的基地址。例如,如果 PF SR-IOV 功能被程式設計為啟用八個 VF,並且它有一個 1MB VF BAR0,則該 VF BAR 中的地址設定一個 8MB 區域的基址。此區域被分成八個連續的 1MB 區域,每個區域都是其中一個 VF 的 BAR0。請注意,即使 VF BAR 描述了一個 8MB 區域,對齊要求也適用於單個 VF,即本例中為 1MB。
有幾種在 PE 中隔離 VF 的策略
M32 視窗:有一個 M32 視窗,它被分成 256 個大小相等的段。可能的最佳粒度是具有 1MB 段的 256MB 視窗。1MB 或更大的 VF BAR 可以對映到此視窗中的單獨 PE。每個段都可以透過查詢表單獨對映到 PE,因此這非常靈活,但是當所有 VF BAR 的大小相同時,它的效果最佳。如果它們的大小不同,則整個視窗必須足夠小,以使段大小與最小的 VF BAR 匹配,這意味著較大的 VF BAR 跨越多個段。
非分段 M64 視窗:非分段 M64 視窗完全對映到單個 PE,因此它只能隔離一個 VF。
單分段 M64 視窗:分段 M64 視窗可以像 M32 視窗一樣使用,但是段不能單獨對映到 PE(段號是 PE#),因此沒有那麼多的靈活性。具有多個 BAR 的 VF 必須位於多個 PE 的“域”中,這不如單個 PE 的隔離效果好。
多個分段 M64 視窗:與往常一樣,每個視窗被分成 256 個大小相等的段,段號是 PE#。但是,如果我們使用多個 M64 視窗,則可以將它們設定為不同的基地址和不同的段大小。如果我們有每個都有 1MB BAR 和 32MB BAR 的 VF,我們可以使用一個 M64 視窗來分配 1MB 段,並使用另一個 M64 視窗來分配 32MB 段。
最後,計劃使用 M64 視窗來實現 SR-IOV,這將在接下來的兩節中進行更多描述。對於給定的 VF BAR,我們需要有效地保留整個 256 個段(256 * VF BAR 大小),並將 VF BAR 定位在 M64 視窗內的空閒段/PE 範圍的開頭。
目標當然是能夠為每個 VF 提供一個單獨的 PE。
IODA2 平臺有 16 個 M64 視窗,用於將 MMIO 範圍對映到 PE#。每個 M64 視窗定義一個 MMIO 範圍,該範圍被分成 256 個段,每個段對應於一個 PE。
我們決定利用此 M64 視窗將 VF 對映到單獨的 PE,因為 SR-IOV VF BAR 的大小都相同。
但是這樣做會引入另一個問題:total_VF 通常小於 M64 視窗段的數量,因此如果我們直接將一個 VF BAR 對映到一個 M64 視窗,則 M64 視窗的某些部分將對映到另一個裝置的 MMIO 範圍。
IODA 支援 256 個 PE,因此分段視窗包含 256 個段,因此如果 total_VF 小於 256,則會出現圖 1.0 中的情況,其中 M64 視窗的段 [total_VF, 255] 可能會對映到其他裝置上的某些 MMIO 範圍
0 1 total_VFs - 1 +------+------+- -+------+------+ | | | ... | | | +------+------+- -+------+------+ VF(n) BAR space 0 1 total_VFs - 1 255 +------+------+- -+------+------+- -+------+------+ | | | ... | | | ... | | | +------+------+- -+------+------+- -+------+------+ M64 window Figure 1.0 Direct map VF(n) BAR space我們當前的解決方案是分配 256 個段,即使 VF(n) BAR 空間不需要那麼多,如圖 1.1 所示
0 1 total_VFs - 1 255 +------+------+- -+------+------+- -+------+------+ | | | ... | | | ... | | | +------+------+- -+------+------+- -+------+------+ VF(n) BAR space + extra 0 1 total_VFs - 1 255 +------+------+- -+------+------+- -+------+------+ | | | ... | | | ... | | | +------+------+- -+------+------+- -+------+------+ M64 window Figure 1.1 Map VF(n) BAR space + extra分配額外的空間可確保整個 M64 視窗將分配給此 SR-IOV 裝置,並且沒有空間可供其他裝置使用。請注意,這僅擴充套件了在軟體中保留的空間;仍然只有 total_VF 個 VF,它們僅響應段 [0, total_VF - 1]。硬體中沒有任何東西響應段 [total_VF, 255]。
4. 通用 PCI 程式碼的含義¶
PCIe SR-IOV 規範要求 VF(n) BAR 空間的基址與單個 VF BAR 的大小對齊。
在 IODA2 中,MMIO 地址確定 PE#。如果地址位於 M32 視窗中,我們可以透過更新將段轉換為 PE# 的表來設定 PE#。類似地,如果地址位於未分段的 M64 視窗中,我們可以為視窗設定 PE#。但如果它在分段的 M64 視窗中,則段號是 PE#。
因此,控制 VF 的 PE# 的唯一方法是更改 VF BAR 中 VF(n) BAR 空間的基址。如果 PCI 核心分配 VF(n) BAR 空間所需的精確空間量,則 VF BAR 值是固定的並且無法更改。
另一方面,如果 PCI 核心分配額外的空間,則只要整個 VF(n) BAR 空間保留在核心分配的空間內,就可以更改 VF BAR 值。
理想情況下,段大小將與單個 VF BAR 大小相同。然後每個 VF 將在其自己的 PE 中。VF BAR(以及 PE#)是連續的。如果 VF0 位於 PE(x) 中,則 VF(n) 位於 PE(x+n) 中。如果我們分配 256 個段,則 VF0 的 PE# 有 (256 - numVF) 個選擇。
如果段大小小於 VF BAR 大小,則需要多個段來覆蓋 VF BAR,並且 VF 將位於多個 PE 中。這是可能的,但隔離效果不是很好,並且它減少了 PE# 選擇的數量,因為 VF(n) BAR 空間將佔用 (numVF * n) 個段,而不是僅佔用 numVF 個段。這意味著沒有那麼多可用於調整 VF(n) BAR 空間基址的可用段。