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 空間基址的可用段。