RFC-0070:支援舊版服務中斷的 PCI 通訊協定

RFC-0070:PCI 通訊協定變更,支援舊版中斷
狀態已接受
區域
  • Kernel
說明

緩解虛假 PCI 舊版中斷問題。

問題
Gerrit 變更
作者
審查人員
提交日期 (年-月-日)2020-01-17
審查日期 (年-月-日)2020-02-25

摘要

在使用者空間中,PCI 匯流排驅動程式庫必須能夠停用舊版層級觸發的中斷,直到裝置中斷服務為止,以免相同的 IRQ 持續喚醒匯流排驅動程式的 IRQ 執行緒。為此,我們需要讓裝置驅動程式庫通知匯流排驅動程式庫,表示已準備好處理新的中斷,並重新啟用舊版中斷產生功能。

提振精神

大多數現代 PCI 裝置都是透過訊息訊號中斷 (MSI) 運作,並透過 PCI 設定空間中的選用 MSI 或 MSI-X 功能控制。這些中斷是特定裝置專屬,且由核心控制碼和 MsiInterruptDispatcher 之間的直接對應關係管理。每個 MSI 只會提供給單一裝置,且從驅動程式庫角度來看,可以視為標準系統中斷。

不過,PCI 舊版中斷是透過一組在所有 PCI 裝置間共用的中斷線路運作,並詳列於系統韌體表格 (例如 ACPI),如 PCI 韌體規格所定義。這些中斷是依規格層級觸發,且為低電位有效。中斷觸發時,系統軟體有責任判斷哪個裝置負責中斷,以便提供服務並釋放線路。在核心 PCI 匯流排驅動程式 (kPCI) 中,這項作業是由所有舊版中斷處理,這些中斷具有向核心中所有 PciInterruptDispatcher 註冊的共用中斷處理常式。這個處理常式接著會判斷哪個裝置產生中斷,並發出適當的中斷物件信號。裝置產生中斷的能力隨即遭到停用。下次驅動程式庫等待分派器時,Unmask 勾點會取消遮罩,並重新啟用裝置的舊版中斷產生能力。

有了 Userspace PCI Bus Driver (uPCI),所有這些機制都已移至使用者空間。uPCI 本身現在會運作低負擔的 IRQ 工作站,判斷負責的裝置,並發出裝置驅動程式庫互動的虛擬中斷信號。不過,由於中斷是以層級為基礎,如果裝置缺少驅動程式庫,或是特定驅動程式庫無法正確處理中斷,中斷就會持續觸發。但如果 uPCI 停用裝置的中斷,以便驅動程式庫可以處理,則 PCI 裝置驅動程式庫沒有現有方法可重新啟用中斷。目前無法通知 uPCI 匯流排驅動程式,裝置驅動程式庫已在提供的虛擬中斷上呼叫 zx_interrupt_waitzx_port_wait,因此匯流排驅動程式不知道何時應重新啟用裝置的中斷。

設計

我們需要為 PCI 驅動程式中的兩種不同中斷用法設計。

  1. 驅動程式知道自己是 PCI 驅動程式,並直接呼叫 PciProtocol 方法。
  2. 驅動程式以防止使用 PciProtocol 方法的方式使用中斷。

使用 PCI_IRQ_MODE_LEGACY 設定的舊版中斷觸發共用線路時,匯流排驅動程式庫會負責通知正確的裝置。與 kPCI 驅動程式庫類似,當 Bus 向驅動程式庫發出中斷可供服務的信號時,會停用裝置的舊版中斷,我們會新增 PCI 通訊協定,讓裝置驅動程式庫要求重新啟用/取消遮罩中斷。如果驅動程式可能會與使用舊版中斷的裝置互動,就必須進行這項呼叫,但如果裝置只使用 MSI,則不需要進行任何變更。這樣就不會產生偽中斷,且符合上述第一種使用方式的需求。

這與 Linux 處理 Userspace I/O 中斷的方式類似。

第二種用法是建立替代的舊版 IRQ 模式 PCI_IRQ_MODE_LEGACY_ACKLESSConfigureIrqMode() 不會選取這個模式,駕駛人必須符合這項特殊規定,才能選取這個模式。系統會監控以這種方式設定中斷的裝置,查看每秒中斷次數是否超過設定的次數。如果發生這種情況,裝置產生中斷的能力就會遭到停用。這與 Linux 處理啟動中斷的方式類似。

實作

您可以在訂單中進行變更,不必擔心遷移或 CQ 問題。

  1. 修改 pci_configure_irq_mode,為與中斷模式無關的驅動程式新增 out 參數,以儲存所選 IRQ 模式,並更新現有的呼叫端。
  2. 新增通訊協定方法 pci_legacy_interrupt_ack (或直接使用 pci_interrupt_ack),重新啟用裝置的舊版中斷,並傳回 ZX_OK,或是在裝置未設定為使用舊版中斷時傳回 ZX_ERR_BAD_STATE
  3. 更新 pci_configure_irq_mode 的現有呼叫端和 PCI_IRQ_MODE_LEGACY 的使用者,以便在處理中斷時使用新的通訊協定方法。
  4. 更新以抽象方式處理中斷的驅動程式,並確保使用 PCI_IRQ_MODE_LEGACY_NOACK 而不是 PCI_IRQ_MODE_LEGACY
  5. 當所有驅動程式都移轉完畢後,讓 uPCI IRQ 工作者停用裝置的舊版中斷產生功能,藉此發出裝置驅動程式庫的虛擬中斷訊號。
  6. PciProtocol banjo 和 Fuchsia.dev 中,詳細記錄 PCI 中斷的使用情形。

效能

遇到的大多數 PCI 裝置都會使用 MSI。仍使用舊版中斷的裝置類型通常僅限於舊版硬體、效能需求較低的整合式 SOC 裝置、不支援 MSI 的模擬環境,以及極少使用中斷的裝置。

如要處理舊版中斷,驅動程式會將額外的管道寫入中斷處理常式,這是因為新的 PCI 通訊協定方法需要從驅動程式庫開發主機 Proxy 寫入 uPCI。您可以透過基準化呼叫本身,或在 Zircon 匯總基準中檢查管道寫入的匯總成本,來分析這項資料。

安全性考量

無。

隱私權注意事項

無。

測試

CQ/CI 中的現有整合和端對端測試會驗證中斷功能在變更後是否仍正常運作,而新的單元測試則會驗證 pci_configure_irq_mode 通訊協定方法的變更作業。

說明文件

PCI 說明文件需要擴充,以說明中斷模式的運作理論。此外,請注意 pci_legacy_interrupt_ackzx_interrupt_waitzx_port_wait 說明文件中可能有所不同。

缺點、替代方案和未知事項

缺點

大多數驅動程式會偏好只使用 MSI / MSI-X 中斷模式,完全不需要使用這個 API,因此系統只會要求可能遇到使用舊版中斷的裝置的驅動程式處理這種情況,而不是所有驅動程式,這項變更的影響範圍較小。不過,如果驅動程式庫是為特定裝置設定所編寫,則可能會遇到問題,也就是驅動程式只會收到第一個中斷,如果無法確認,就會發生問題。如果驅動程式支援多種裝置,就可能會出現這個問題。

如果有多個後端,情況也會變得較為複雜。舉例來說,我們的 xHCI 驅動程式庫有類似下列的內容。如果其 PCI 支援涉及舊版中斷,則可能必須類似以下內容:

// Initialize the proper setup and obtain an interrupt
if (pci_.is_valid()) {
  pci_init();
} else {
  mmio_init();
}

do {
  // Wait loop on the interrupt
  // Handle the interrupt

  if (mode_ == XHCI_MODE_PCI && irq_mode_ == PCI_IRQ_MODE_LEGACY)
    status = pci_.LegacyInterruptAck();
  }
}

如果省略 ACK 程式碼,這個驅動程式庫在 MSI 模式下可以正常運作,但在舊版模式下,第一次中斷後就不會再收到任何中斷。改善 PCI 驅動程式的測試架構,有助於在開發期間找出這類錯誤,進而提供更完善的解決方案。

考慮的替代方案

將過多未處理的中斷標示為虛假,並停用中斷

與 Linux 類似,我們可以為連續的偽中斷設定閾值,如果達到閾值,我們可以停用或忽略該中斷行,直到重新啟動為止。這個方法的一大問題是,Linux 處理共用中斷時,會在所有處理常式完成後,透過核心中的硬體 IRQ 處理常式呼叫處理常式鏈,然後確認中斷。這可確保所有驅動程式庫中斷處理常式都在確認前執行,因此只有在沒有處理常式正確處理中斷時,才會發生虛假中斷。在 Zircon 中使用 uPCI 驅動程式庫,且裝置驅動程式不在程序外時,我們可以發出信號,喚醒 irq 處理執行緒,但無法得知執行緒是否已完成執行。在一般情況下,這會導致虛假中斷,具體取決於驅動程式庫的 IRQ 執行緒排程速度,以及處理特定中斷情況的速度。不過,在一般情況下,這種做法仍會導致比 ACK 提案更多的偽中斷。

新增 PCI 通訊協定方法,以等待中斷

其中一個考慮的選項是新增方法,以處理任何類型的中斷等待,有效避免中斷時需要額外的條件。pci_interrupt_wait

遺憾的是,這會導致 PCI 中斷需要以不同於其他中斷的方式處理。據我所知,將任何中斷物件設為與其他中斷物件相同的介面,具有許多價值,且驅動程式庫作者必須能夠繼續使用 zx_interrupt_waitzx_port_bindzx_port_waitzx_object_wait_async。系統中的大多數驅動程式都有 IRQ 連接埠和多個中斷的組合,或多個後端 (UART、PCI、USB) 要處理,因此請務必不要違反中斷物件周圍的介面。

在衍生的 InterruptDispatcher 中處理這個問題

我們可以保留 kPCI 的 PciInterruptDispatcher 概念,並將這項工作委派給 kPCI,讓 PCI 裝置驅動程式庫和核心之間的插斷處理作業保持不變。很遺憾,使用者空間 PCI 驅動程式庫和 Zircon 核心之間存在大量耦合。

  1. 每個專門的 InterruptDispatcher 都必須能夠寫入 PCI 裝置的控制暫存器,才能停用舊版中斷。這理想上需要 VMO,與我們的 MSI 方法類似,或是為建立這個物件的任何系統呼叫提供位址。Dispatcher 必須知道對應的裝置,因此無法規避這項限制。
  2. 中斷停用是控制暫存器中的單一位元,使用者空間匯流排驅動程式庫會在匯流排和裝置初始化期間頻繁修改,如果存在任何待處理的中斷,就會造成嚴重的競爭條件風險。
  3. 如果我們在裝置本身使用衍生 InterruptDispatcher,仍需處理共用線路上的中斷觸發者。由於裝置不再透過匯流排處理中斷,這表示我們需要在核心中保留類似 kPCI 的邏輯。SharedIrqHandler
  4. 我們現在也需要再次透過核心,從 ACPI / 主機板檔案中,深入瞭解 PCI 舊版 IRQ 路由表。這項作業現在會在使用者空間和 ACPI 中處理。

除非我們願意承認 PCI 是需要一些自訂核心程式碼,以及約 2 個額外系統呼叫才能運作的特殊驅動程式庫,否則我認為這個方法目前沒有合理的發展方向。

既有技術和參考資料

在我的研究中,完全在使用者空間處理這項問題是 Zircon 獨有的問題。

  1. OSX 的 DriverKit 使用 IOInterruptDispatchSource,與核心中的中斷設定和處理作業搭配運作。此外,PCIDriverKit 僅支援 MSI 和 MSI-X 中斷模式。

    PCIDriverKit > IOPCIDevice

  2. 大多數 Linux PCI 中斷都會在核心本身處理。共用中斷具有驅動程式向其註冊的處理常式鏈結。中斷觸發時,核心會依序呼叫每個處理常式,直到其中一個處理常式處理完中斷為止。如果共用中斷線有足夠的偽中斷,且任何控制代碼都未處理,核心就會停用中斷。

    Linux 開機中斷

    Linux 也透過 Userspace I/O (UIO) 介面支援簡單的使用者空間驅動程式。這可讓驅動程式庫在提供的 /dev/uioX sysfs 節點上執行封鎖 read(),等待中斷。觸發中斷時,中斷會停用,但驅動程式庫可以對 sysfs 節點發出 write() 呼叫,重新啟用中斷。

    使用者空間 I/O HOWTO

  3. 大多數 Windows PCI 驅動程式都是使用核心模式驅動程式架構 (KMDF) 建構。中斷處理常式會做為核心中斷調度的一部分呼叫,而驅動程式會註冊在 IRQ 環境中執行的處理常式。

    中斷服務常式簡介