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

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

減少 PCI 錯誤舊版中斷情況的因應措施。

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

摘要

在使用者空間中,PCI Bus 驅動程式庫必須要停用舊版層級的觸發中斷功能,直到裝置中斷服務後再繼續,以防止相同 IRQ 持續喚醒 Bus Driver 的 IRQ 執行緒。為了達成這個目標,我們需要讓裝置驅動程式庫通知 Bus 驅動程式庫已準備好服務新的中斷,並重新啟用舊版中斷產生作業。

提振精神

大多數的現代化 PCI 裝置是透過 PCI 設定空間中的選用 MSI 或 MSI-X 功能控制,透過訊息信號中斷 (MSI) 運作。這些中斷事件只適用於特定裝置,由核心控點和 MsiInterruptDispatcher 之間的直接對應管理。每個 MSI 僅提供給單一裝置,可從驅動程式庫角度視為標準系統中斷。

不過,PCI 舊版中斷作業會透過所有 PCI 裝置共用的一組中斷線運作,且依照 PCI 韌體規格定義的 ACPI 等系統韌體表格詳細說明。這些中斷的情況會依照規格觸發,並處於有效狀態。當中斷情形觸發時,系統軟體的責任是系統軟體,判斷哪個裝置負責中斷服務,使其能夠提供服務並釋放線路。在核心 PCI 匯流排驅動程式 (kPCI) 中,所有舊版中斷點都會由核心中所有的 PciInterruptDispatcher 註冊的共用中斷處理常式進行處理。這個處理常式會判斷哪部裝置產生中斷,並發出適當的中斷物件信號。裝置無法產生幹擾的功能。驅動程式庫下次在調度工具上等候時,Unmask 掛鉤會取消遮罩並重新啟用裝置的舊版中斷產生能力。

透過 Userspace PCI Bus 驅動程式 (uPCI) 所有此機器已移至使用者空間。uPCI 本身現在會操作低負載 IRQ 工作站,藉此判斷裝置負責的裝置,並向裝置的驅動程式庫程式進行互動的虛擬中斷。但是,由於中斷程度是以等級為基礎,如果我們缺少裝置的驅動程式庫,或是特定驅動程式庫無法正確處理中斷,那麼幹擾將不斷觸發。但是,如果 uPCI 停用裝置的中斷,讓驅動程式庫可以處理,則我們沒有任何現有方法可供 PCI 裝置驅動程式庫重新啟動。目前尚無法通知 uPCI 匯流排驅動程式,裝置驅動程式庫在提供的虛擬中斷處已呼叫 zx_interrupt_waitzx_port_wait,因此匯流驅動程式不知道何時應重新啟用裝置的中斷。

設計

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

  1. 知道他們是 PCI 驅動程式,並直接呼叫 PciProtocol 方法的驅動程式。
  2. 讓中斷方式使用中斷的驅動程式,避免使用 PciProtocol 方法。

使用 PCI_IRQ_MODE_LEGACY 為共用線觸發舊版中斷設定時,Bus 驅動程式庫會負責通知正確的裝置。與 kPCI 驅動程式庫類似,在向驅動程式庫告知有可用的中斷情形時,公車會停用裝置的舊版中斷功能,以便有效遮蓋。我們將新增 PCI 通訊協定方法,讓裝置驅動程式庫要求重新啟用/取消遮蓋中斷狀態。如果驅動程式可能會與在某些設定中採用舊版中斷功能的裝置互動,則需要呼叫此呼叫,但針對僅採用 MSI 的裝置不需要變更。這不會造成任何不必要的干擾,且為符合前述第一項使用需求的需求。

這與 Linux 處理使用者空間 I/O 中斷情形的情況類似。

針對第二次用量,我們會建立替代的舊版 IRQ 模式 PCI_IRQ_MODE_LEGACY_ACKLESS。這個模式不會由 ConfigureIrqMode() 選取,因此需要根據這個獨特需求的駕駛人特別選取。在本手冊中,如果有裝置的中斷功能設定,將受到監控,以便瞭解每秒的中斷次數是否超過設定的數字。發生這種情況時,裝置無法產生幹擾的功能就會停用。運作方式與 Linux 對啟動中斷的處理方式類似。

實作

即可按照順序進行變更,而不會受到遷移或 CQ 疑慮。

  1. 修改 pci_configure_irq_mode 以新增排除參數,以儲存專為不受干擾模式影響的驅動程式所選擇的 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 斑斕紋和 Fuchsia.dev 中的 PCI 中斷情形。

效能

遇到的大多數 PCI 裝置都會使用 MSI 運作。仍在使用舊版中斷情形的裝置類型通常僅限於舊型硬體、效能低落的整合 SOC 裝置、不支援 MSI 的模擬環境,以及很少使用中斷事件的裝置。

想要處理舊版中斷情形的驅動程式會根據這個新的 PCI 通訊協定方法,在中斷處理處理常式中新增額外的管道寫入作業,此方法需要從驅動程式庫開發主機 Proxy 寫入 uPCI。如要剖析這項呼叫,請為呼叫本身進行基準測試,或在 Zircon 匯總基準中檢查管道寫入的匯總費用。

安全性考量

無。

隱私權注意事項

無。

測試

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

說明文件

PCI 需要展開文件,說明中斷模式的作業理論。另外,您也可以在 zx_interrupt_waitzx_port_wait 說明文件中註明需要pci_legacy_interrupt_ack這項特性。

缺點、替代方案和未知

缺點

大多數驅動程式偏好只使用 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();
  }
}

如果省略確認程式碼,此驅動程式庫仍可與 MSI 搭配使用,但在舊版模式下第一次使用之後,就沒有任何干擾。改善 PCI 驅動程式的測試架構,將有助於在開發期間找出這類錯誤的更佳解決方案。

考慮改用的替代方案

將未處理的干擾標示為惡意並停用中斷功能

與 Linux 類似,我們只需針對依序出現的中斷幹擾設定門檻,這樣只要達到這個門檻,我們可以停用或忽略中斷線,直到重新啟動為止。這種方法的其中一個主要問題是,當 Linux 處理共用中斷的時,它會透過核心中的硬式 IRQ 處理常式呼叫處理常式鏈,然後再於所有處理常式執行完畢時確認中斷。這可確保所有驅動程式庫中斷處理常式都在確認前執行,因此只有在沒有處理常式正確處理中斷時,才會發生嚴重的中斷情形。當 Zircon 中的 uPCI 驅動程式庫和裝置驅動程式結束程序時,我們可以指示使用者喚醒其 irq 處理執行緒,但我們無法得知這些驅動程式已可完成。在常見情況下,這會導致嚴重中斷,具體取決於驅動程式庫的 IRQ 執行緒排定及處理特定中斷條件的速度。不過,與常見案例中的確認提案相比,這種方法仍會導致更多嚴重的中斷情形。

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

考慮到新增一個方法,用於處理任何類型的中斷等待,有效 pci_interrupt_wait 可避免中斷時需要額外的條件。

很遺憾,這會導致 PCI 中斷,需要與其他中斷類型不同的處理。為了享有最好的功能,我相信將任何中斷物件與任何其他中斷物件使用相同的介面,都極具價值,且驅動程式庫製作者必須能繼續使用 zx_interrupt_waitzx_port_bindzx_port_waitzx_object_wait_async。系統中的大多數驅動程式都使用具有多個中斷的 IRQ 通訊埠組合,或是多個用來處理多個後端 (UART、PCI、USB) 的組合,因此務必不要違反中斷物件周圍的介面。

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

我們可保留 kPCI 的概念,並將這項工作委派給其,讓 PCI 裝置驅動程式庫與核心之間的中斷處理。PciInterruptDispatcher遺憾的是,這是使用者空間 PCI 驅動程式庫與 Zircon 核心之間大量的耦合。

  1. 每個專用 InterruptDispatcher 都必須能寫入 PCI 裝置的控制註冊系統,以停用舊版中斷功能。在理想情況下,這需要使用 VMO,而不是我們採用 MSI 的方法,或是要提供給任何系統呼叫建立此物件的位址。我們無法這麼做,因為調度工具必須知道對應的裝置。
  2. 中斷停用是控制註冊系統中的一個位元,使用者空間匯流排驅動程式庫會在公車和裝置初始化期間頻繁修改,而如果發生任何待處理的中斷,就會帶來競爭狀況的嚴重風險。
  3. 如果裝置本身使用衍生的 InterruptDispatcher,我們仍需處理在共用線條上觸發其中斷的情形。由於裝置不再使用匯流排處理中斷處理,這表示我們必須在核心中維持與 kPCI 的 SharedIrqHandler 類似的邏輯。
  4. 我們現在也需要再次透過核心,瞭解 PCI 舊版 IRQ 轉送資料表從 ACPI / 主機板檔案而來。這項作業會在使用者空間和 ACPI 中處理。

除非我們願意確認 PCI 是需要一些自訂核心程式碼,以及約 2 個額外的系統呼叫來執行其工作,否則目前我沒有認為使用此方法的合理做法。

先前的圖片和參考資料

Zircon 是研究所面臨的一項問題,在使用者空間中完全進行處理是 Zircon 問題。

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

    PCIDriverKit > IOPCI 裝置

  2. 大部分的 Linux PCI 中斷會在核心本身中處理。共用中斷情形是由驅動程式註冊的處理常式鏈結。觸發中斷時,核心會依序呼叫每個處理常式,直到其中一個處理常式處理中斷為止。如果共用中斷線有足夠嚴重的中斷情形,且任何控點未處理的中斷情形,核心就會停用中斷。

    Linux 啟動中斷

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

    使用者空間 I/O 教學指南

  3. 大部分的 Windows PCI 驅動程式都是以核心模式驅動程式架構 (KMDF) 建構而成。中斷處理常式稱為核心中斷分派和驅動程式,會在 IRQ 環境執行的處理常式進行呼叫。

    幹擾服務日常安排簡介