2. PCI Express 埠匯流排驅動程式指南 HOWTO¶
- 作者:
Tom L Nguyen tom.l.nguyen@intel.com 2004 年 11 月 3 日
- 版權:
© 2004 Intel Corporation
2.1. 關於本指南¶
本指南描述了 PCI Express 埠匯流排驅動程式的基礎知識,並提供瞭如何使服務驅動程式註冊/登出 PCI Express 埠匯流排驅動程式的資訊。
2.2. 什麼是 PCI Express 埠匯流排驅動程式¶
PCI Express 埠是一種邏輯 PCI-PCI 橋結構。PCI Express 埠有兩種型別:根埠和交換機埠。根埠從 PCI Express 根複合體發起 PCI Express 鏈路,交換機埠將 PCI Express 鏈路連線到內部邏輯 PCI 匯流排。交換機埠的次級匯流排代表交換機的內部路由邏輯,稱為交換機的上行埠。交換機的下行埠從交換機的內部路由匯流排橋接到代表來自 PCI Express 交換機的下行 PCI Express 鏈路的匯流排。
PCI Express 埠可以提供多達四種不同的功能,在本文件中稱為服務,具體取決於其埠型別。PCI Express 埠的服務包括原生熱插拔支援 (HP)、電源管理事件支援 (PME)、高階錯誤報告支援 (AER) 和虛擬通道支援 (VC)。這些服務可以由一個複雜的驅動程式處理,也可以單獨分發並由相應的服務驅動程式處理。
2.3. 為什麼要使用 PCI Express 埠匯流排驅動程式?¶
在現有 Linux 核心中,Linux 裝置驅動程式模型允許單個物理裝置只能由一個驅動程式處理。PCI Express 埠是具有多個不同服務的 PCI-PCI 橋裝置。為了保持一個清晰簡單的解決方案,每個服務可以有自己的軟體服務驅動程式。在這種情況下,多個服務驅動程式將爭奪單個 PCI-PCI 橋裝置。例如,如果 PCI Express 根埠原生熱插拔服務驅動程式首先載入,它就會宣告一個 PCI-PCI 橋根埠。因此,核心不會為該根埠載入其他服務驅動程式。換句話說,使用當前驅動程式模型,無法讓多個服務驅動程式在 PCI-PCI 橋裝置上同時載入和執行。
要實現多個服務驅動程式同時執行,需要一個 PCI Express 埠匯流排驅動程式,它管理所有已填充的 PCI Express 埠,並根據需要將所有提供的服務請求分發給相應的服務驅動程式。使用 PCI Express 埠匯流排驅動程式的一些主要優點如下所示:
允許多個服務驅動程式在 PCI-PCI 橋埠裝置上同時執行。
允許服務驅動程式以獨立的階段式方法實現。
允許一個服務驅動程式在多個 PCI-PCI 橋埠裝置上執行。
管理和分配 PCI-PCI 橋埠裝置的資源給請求的服務驅動程式。
2.4. 配置 PCI Express 埠匯流排驅動程式與服務驅動程式¶
2.4.1. 將 PCI Express 埠匯流排驅動程式支援包含到核心中¶
是否包含 PCI Express 埠匯流排驅動程式取決於核心配置中是否包含 PCI Express 支援。當核心中啟用 PCI Express 支援時,核心將自動將 PCI Express 埠匯流排驅動程式作為核心驅動程式包含在內。
2.4.2. 啟用服務驅動程式支援¶
PCI 裝置驅動程式是基於 Linux 裝置驅動程式模型實現的。所有服務驅動程式都是 PCI 裝置驅動程式。如上所述,一旦核心載入了 PCI Express 埠匯流排驅動程式,就無法載入任何服務驅動程式。為了滿足 PCI Express 埠匯流排驅動程式模型,需要對現有服務驅動程式進行一些微小的更改,這些更改不會影響現有服務驅動程式的功能。
服務驅動程式需要使用下面顯示的兩個 API 將其服務註冊到 PCI Express 埠匯流排驅動程式(參見 5.2.1 和 5.2.2 節)。重要的是,服務驅動程式在呼叫這些 API 之前,要初始化標頭檔案 /include/linux/pcieport_if.h 中包含的 pcie_port_service_driver 資料結構。否則將導致身份不匹配,從而阻止 PCI Express 埠匯流排驅動程式載入服務驅動程式。
2.4.2.1. pcie_port_service_register¶
int pcie_port_service_register(struct pcie_port_service_driver *new)
此 API 替換了 Linux 驅動程式模型的 pci_register_driver API。服務驅動程式應始終在模組初始化時呼叫 pcie_port_service_register。請注意,服務驅動程式載入後,不再需要呼叫 pci_enable_device(dev) 和 pci_set_master(dev) 等函式,因為這些呼叫由 PCI 埠匯流排驅動程式執行。
2.4.2.2. pcie_port_service_unregister¶
void pcie_port_service_unregister(struct pcie_port_service_driver *new)
pcie_port_service_unregister 替換了 Linux 驅動程式模型的 pci_unregister_driver。當模組退出時,服務驅動程式總是會呼叫它。
2.4.2.3. 示例程式碼¶
下面是初始化埠服務驅動程式資料結構的服務驅動程式示例程式碼。
static struct pcie_port_service_id service_id[] = { {
.vendor = PCI_ANY_ID,
.device = PCI_ANY_ID,
.port_type = PCIE_RC_PORT,
.service_type = PCIE_PORT_SERVICE_AER,
}, { /* end: all zeroes */ }
};
static struct pcie_port_service_driver root_aerdrv = {
.name = (char *)device_name,
.id_table = service_id,
.probe = aerdrv_load,
.remove = aerdrv_unload,
.suspend = aerdrv_suspend,
.resume = aerdrv_resume,
};
下面是註冊/登出服務驅動程式的示例程式碼。
static int __init aerdrv_service_init(void)
{
int retval = 0;
retval = pcie_port_service_register(&root_aerdrv);
if (!retval) {
/*
* FIX ME
*/
}
return retval;
}
static void __exit aerdrv_service_exit(void)
{
pcie_port_service_unregister(&root_aerdrv);
}
module_init(aerdrv_service_init);
module_exit(aerdrv_service_exit);
2.5. 可能的資源衝突¶
由於 PCI-PCI 橋埠裝置的所有服務驅動程式都允許同時執行,下面列出了幾種可能的資源衝突及其建議的解決方案。
2.5.1. MSI 和 MSI-X 向量資源¶
一旦在裝置上啟用 MSI 或 MSI-X 中斷,它將保持此模式直到再次停用。由於同一 PCI-PCI 橋埠的服務驅動程式共享相同的物理裝置,如果單個服務驅動程式啟用或停用 MSI/MSI-X 模式,可能會導致不可預測的行為。
為避免這種情況,所有服務驅動程式均不允許切換其裝置上的中斷模式。PCI Express 埠匯流排驅動程式負責確定中斷模式,這對於服務驅動程式來說應該是透明的。服務驅動程式只需知道分配給 struct pcie_device 結構體中 irq 欄位的向量 IRQ,該 IRQ 在 PCI Express 埠匯流排驅動程式探測每個服務驅動程式時傳入。服務驅動程式應使用 (struct pcie_device*)dev->irq 呼叫 request_irq/free_irq。此外,中斷模式儲存在 struct pcie_device 結構體的 interrupt_mode 欄位中。
2.5.2. PCI 記憶體/IO 對映區域¶
PCI Express 電源管理 (PME)、高階錯誤報告 (AER)、熱插拔 (HP) 和虛擬通道 (VC) 的服務驅動程式訪問 PCI Express 埠上的 PCI 配置空間。在所有情況下,訪問的暫存器都是相互獨立的。此補丁假定所有服務驅動程式都將表現良好,並且不會覆蓋其他服務驅動程式的配置設定。
2.5.3. PCI 配置暫存器¶
每個服務驅動程式都在自己的能力結構上執行 PCI 配置操作,除了 PCI Express 能力結構,該結構在包括服務驅動程式在內的許多驅動程式之間共享。RMW 能力訪問器 (pcie_capability_clear_and_set_word(), pcie_capability_set_word(), 和 pcie_capability_clear_word()) 保護一組選定的 PCI Express 能力暫存器:
鏈路控制暫存器
根控制暫存器
鏈路控制 2 暫存器
對這些暫存器的任何更改都應使用 RMW 訪問器執行,以避免由於併發更新而引起的問題。有關受保護暫存器的最新列表,請參閱 pcie_capability_clear_and_set_word()。