一 相關技術與研究
1997年,PCI SIG制定了第一個PCI熱插拔規範,其中定義了支援熱插拔所必需的平台、板卡和軟體元素。PCI SIG推出了標準熱插拔控制器規範(SHPC SPEC),其中明確了熱插拔的標準使用模式和嚴格的寄存器組要求,並且允許作業系統供應商在平台特定的軟體之外提供熱插拔支援,逐步完成了熱插拔標準制定工作,進入技術的全面推廣階段。 2002年以後,Intel把熱插拔作為一種天然屬性賦予新推出的PCI Express規範,PCI Express熱插拔總結了五年來工業標準的經驗,具有如下特點: 基於SHPC模式並對其進行了功能擴充; PCI Express從設計上就把熱插拔寄存器整合進入了其標準的效能寄存器組(而在SHPC 1.0中,熱插拔寄存器是附加的); 提供給作業系統一個統一的熱插拔硬體寄存器介面,賦予了作業系統進行原生熱插拔(繞過了傳統基於BIOS的熱插拔方法)的能力; 通過在基本的體系機構層次定義熱插拔必須的硬體要求,完善了一種標準的使用模式; 提供了規格參數來保證OEM產品的低成本而高平台可靠性。
具有完整功能的PCI Express熱插拔系統在平台硬體和韌體支援之外,還必須有作業系統以及裝置驅動程式的支援。
在各大晶片廠商紛紛推出支援PCI熱插拔的產品的同時,Microsoft,Novell,SCO等公司在他們相關的作業系統中也都包括了支援熱插拔的技術, Novell NetWare 4.11&5和SCO UnixWare 7以及更新版本支援完整PCI熱插拔(熱替換和熱添加)。Microsoft Windows NT 4.0利用Compaq 伺服器支援盤(SSD) for NT 4.0,提供了PCI熱替換支援,進入2000年後,Microsoft Windows 2000也內建了對PCI熱插拔技術的全面軟體支援。對於GNU/Linux,從2001年1月份的核心2.4版本開始,PCI 熱插拔開始成為其標準特性之一,到2.6 版本,熱插拔功能已經被整合進入核心裝置模型。現在,幾乎所有的Linux發行版本,包括RedHat, Debian和 United Linux,都對PCI 熱插拔提供了良好支援,並分別有所擴充。BSD分支在這方面起步較晚,OpenBSD在2004年三月份的3.6版本中才出現了不含裝置驅動的裝置熱插拔參考架構,而FreeBSD 到5.3版本為止,尚未提供對PCI 熱插拔的支援。
作為2002年出現的規範,各個通用作業系統對PCI Express的支援才剛剛起步。在微軟公司的 Windows 2000, Windows XP, and Windows Server 2003中,PCI Express裝置被當作PCI裝置進行熱插拔,其他PCI Express的進階功能可以由韌體啟用。在Windows Longhorn中,才提供對PCI Express進階特性的原生支援。GNU/Linux在核心版本2.6.9中開始專門提供對於PCI Express 熱插拔的支援,中間曆經軟體架構的改動,逐漸發展為以核心版本2.6.10以及核心版本2.6.12下兩種不同的PCI Express熱插拔驅動架構。本文主要討論2.6.10所採用的PCI Express熱插拔驅動架構,並在最後給出2.6.12的新特性。
回頁首 三 PCI Express 熱插拔的軟體支援
根據規範,一個完整的 Native PCI Express 熱插拔系統需要幾方面的相互配合,分別為硬體元素、韌體元素和軟體元素。 硬體元素是指主板匯流排系統的電氣特性方面的支援,包括熱插拔控制器(Hot-Plug Controller)、卡槽電源切換邏輯(Card Slot Power Switching Logic)、板卡重設邏輯(Card Reset Logic)、電源指示燈(Power Indicator)、提示按鈕(Attention Button)和板卡存在檢測引腳(Card Present Detect Pins)等等;韌體元素是指主板BIOS必須對熱插拔提供的支援,要實現Native PCI Express熱插拔,韌體必須提供OSHP方法或ACPI _OSC方法之一;軟體元素是指作業系統操綜合使用PCI Express 熱插拔所必須提供的功能組件。 軟體元素
為了操縱平台的硬體元素,提供PCI Express熱插拔服務,我們必須實現表1所示的主要熱插拔軟體元素。 PCI Express服務模型
PCIE熱插拔(PCIEHP)子系統中的標準熱插拔系統驅動程式通過檢查PCI Express效能資料結構中的插槽效能寄存器(Slot Capabilities register),來擷取硬體的熱插拔組件資訊。這些寄存器所能反映的資訊如下: 插槽是否支援熱插拔 裝置是否支援不通知軟體的突然拔出操作 提示燈、控制器等硬體元素是否存在
一旦軟體完成了PCI Express裝置熱插拔功能的開啟和配置工作,熱插拔行為(例如移出或插入請求,電源故障)就可以向系統熱插拔處理機制提交系統中斷和電源管理事件。這種熱插拔處理機制由作業系統獨立提供,圖1展示了PCI Express Native熱插拔的服務模型,並展示了它與傳統的基於ACPI的模型之間的區別。 圖1 PCI Express熱插拔服務模型比較
回頁首 四Linux 2.6.10 PCIE 熱插拔子系統程式碼分析 熱插拔架構
為了提供PCI Express熱插拔服務,Linux 2.6.10 PCIE熱插拔(PCIEHP)子系統必須實現使用者操作介面、熱插拔服務程式和標準熱插拔系統驅動等熱插拔軟體元素,並使軟體的行為符合熱插拔標準使用模式[4]。另外,一些特定的熱插拔功能必須與支援熱插拔的裝置驅動結合在一起發揮作用。
PCIEHP核內部分的主體是一個核心線程,其控制邏輯以模組的方式載入入核心,負責監控PCI Express匯流排上的熱插拔事件,並做相關處理;核外部分是一個使用者空間的指令碼,從核心中調用,並根據核心傳回的資訊執行後續處理過程。
1 熱插拔驅動程式生命週期
從PCIEHP子系統啟動,到子系統被卸載期間,PCIEHP完全接管系統中PCI Express插槽熱插拔事件。
PCIEHP子系統啟動時,首先要開啟使用者態守護進程。然後初始化通知機制,開啟PCI Express熱插拔事件處理核心線程,為作業系統中各匯流排資料結構分別預初始化一個熱插拔插槽列表。接著,根據核心編譯時間是否指定ACPI式資源管理,初始化裝置資源管理方式。最後,根據裝置標識,在系統中註冊PCI Express熱插拔驅動程式,並將其綁定到匯流排上所有可能掛載熱插拔插槽的PCIE橋接裝置,然後初始化對應的熱插拔控制器,分配資源,開啟定時的中斷輪詢機制。
卸載PCIEHP子系統,首先釋放熱插拔控制器全域列表中每個控制器所佔用的資源,釋放熱插拔插槽全域列表中每個插槽所佔用的資源,終止通知機制的運行,結束PCI Express熱插拔事件處理核心線程;接著,根據核心編譯時間是否指定ACPI式資源管理,釋放相關裝置佔用的系統資源;然後,登出PCI Express熱插拔驅動程式;在處理完當前熱插拔事件後,終止核外守護進程。
在PCIEHP載入後,為了在熱插拔執行過程中追蹤其狀態變化,我們把熱插拔插槽的狀態抽象如下: 靜態 (STATIC STATE ),
正常工作或者閒置時段, 開啟前閃爍提示態(BLINKINGON STATE)
發出開啟請求到確認之前的時段 關閉前閃爍提示態 (BLINKINGOFF STATE)
發出關閉請求到確認之前的時段 開啟態 (POWERON STATE)
執行開啟操作的時段 關閉態(POWEROFF STATE)
執行關閉操作的時段
各個插槽狀態之間的轉換關係如圖2所示: 圖2 插槽狀態轉換圖
2 使用者控制指令碼/sbin/hotplug和核內外通訊機制
在核心熱插拔處理完畢後,它會在核內調用一個使用者態指令碼hotplug,這個指令碼將根據核心提供的資訊進行後續處理工作。它是裝置熱插拔通告的使用者空間部分,它接收核心傳出的熱插拔操作類型和環境變數,處理裝置掛載和卸載操作。通常,hotplug會根據裝置類型調用一個策略指令碼,針對裝置和系統當前參數進行後續的配置工作。例如,對於網卡,可能會呼叫指令碼指定IP地址,網關和網域名稱伺服器等等,對於存放裝置,可能會調用mount命令來載入它到檔案系統內。
核心傳送給hotplug的資訊包含熱插拔操作類型(例如PCI),並且在一個環境變數中提供本次操作相關資訊: 行為種類:添加或刪除 對象裝置所屬PCI 類、子類和編程介面 對象製造商標誌和裝置標誌 對象匯流排地址、插槽地址和功能函數編號
核外的後續配置工作涉及如下檔案:
/etc/hotplug/pci.rc/ect/hotplug/pci.Agent/etc/rc.d/init.d/hotplug
在核心中,由kernel/kmod.c中的函數
int call_usermodehelper (char *path, char **argv, char **envp, int wait)來開啟使用者態指令碼/sbin/hotplug。在參數表中,path表示了所啟動的核外應用程式的路徑,argv是應用程式的參數表,envp是環境變數列表,wait則指出了是否同步等待應用程式執行完畢再返回執行結果的狀態。
3 PCIE 熱插拔模組構成
為了使用PCI Express Native Hotplug,我們必須在編譯的時候開啟對應的功能模組。
在核心配置中,PCIE Hotplug對應的開關為HOTPLUG_PCI_PCIE,它依賴於HOTPLUG_PCI。如果你的主板支援PCI Express Native Hotplug,可以選擇Y;如果你只是想把這個驅動作為模組來編譯,那麼選擇M,此模組叫做pciehp,在原始碼\dirver\pci\hotplug\Kconfig檔案中,你可以看到:
config HOTPLUG_PCI_PCIEtristate "PCI Express Hotplug driver"depends on HOTPLUG_PCI
完整的pciehp模組功能涉及到如下幾個檔案pci_hotplug_core.c, pciehp_core.c,pciehp_ctrl.c,pciehp_pci.c pciehp_hpc.c,另外根據是否開啟了ACPI,包含pciehprm_acpi.c 或者pciehprm_nonacpi.c,在原始碼\dirver\pci\hotplug\Makefile檔案中,你可以看到:
pci_hotplug-objs:=pci_hotplug_core.opciehp-objs:=pciehp_core.o\pciehp_ctrl.o\pciehp_pci.o\pciehp_hpc.oifdef CONFIG_ACPI_BUSpciehp-objs += pciehprm_acpi.oelsepciehp-objs += pciehprm_nonacpi.oendif
代碼的主要任務就是在所有支援熱插拔的PCIE橋上載入熱插拔驅動程式,監控熱插拔事件,並根據類型,如是熱插拔事件、熱拔出事件還是電源故障等分別予以處理。 熱插拔驅動的載入
熱插拔驅動程式的載入所進行的主要工作是開啟並初始化PCIE熱插拔的核心線程,這部分代碼位於/driver/pci/hotplug/pciehp_core.c中。入口函數為:
static int __init pcied_init(void)
#ifdef CONFIG_HOTPLUG_PCI_PCIE_POLL_EVENT_MODEpciehp_poll_mode = 1;#endifretval = pcie_start_thread();if (retval)goto error_hpc_init;retval = pciehprm_init(PCI);if (!retval) {retval = pci_register_driver(&pcie_driver);dbg("pci_register_driver = %d\n", retval);info(DRIVER_DESC " version: " DRIVER_VERSION "\n");}
pcied_init中所涉及的關鍵函數分析如下:
retval = pcie_start_thread();
初始化並開啟通知機制:
pciehp_event_start_thread()啟動事件監控處理線程
然後初始化slot列表,系統中每個bus給一個slot列表。
struct pci_func *pciehp_slot_list[256]; 都設為NULL
retval = pciehprm_init(PCI);
原型:int pciehprm_init(enum php_ctlr_type ctlr_type)
初始化資源(區別兩種情況:acpi和非acpi的) 在非acpi的初始化方式下,調用空函數legacy_pciehprm_init_pci();在pcihprm_nonacpi.h中,定義了irq_info,irq_routing_table兩個結構。在acpi初始化方式下,通過pciehprm_acpi_scan_pci()在acpi樹下遍曆搜尋PCI裝置。不論acpi和非acpi的,都要求php_ctlr_type為PCI。
retval = pci_register_driver(&pcie_driver);
註冊並初始化PCI橋熱插拔驅動程式模組。
把裝置驅動加入登入裝置驅動列表,即使在期間沒有相應裝置出現驅動程式仍然保持有效。
int count = 0;/* initialize common driver fields */
用來泛化之,可以把drv->driver看作為drv的基類資訊
drv->driver.name = drv->name;drv->driver.bus = &pci_bus_type;drv->driver.probe = pci_device_probe;drv->driver.remove = pci_device_remove;drv->driver.kobj.ktype = &pci_driver_kobj_type;pci_init_dynids(&drv->dynids);count = driver_register(&drv->driver);
注意:這裡是如何從基類drv->driver一般裝置的驅動向子類drv這個pci裝置的驅動逆向關聯的--使用CONTAINER宏
pcie_driver為PCIE驅動程式對象,定義在 pciehp_core.c中
static struct pci_driver pcie_driver = {//驅動名稱定義為"pciehp".name=PCIE_MODULE_NAME, // id_table指定探測函數probe所應用的範圍,這裡在表中指定為所有的PCI橋裝置.id_table=pcied_pci_tbl, //probe為指定的裝置探測函數.probe=pcie_probe, };static struct pci_device_id pcied_pci_tbl[] = {{//此處選擇所有PCI橋.class = ((PCI_CLASS_BRIDGE_PCI << 8) | 0x00), .class_mask =~0,.vendor = PCI_ANY_ID,.device = PCI_ANY_ID,.subvendor = PCI_ANY_ID,.subdevice = PCI_ANY_ID,},
裝置探測pcie_probe:
static int pcie_probe(struct pci_dev *pdev, const struct pci_device_id *ent)in pcie-core.c
已經確定了pdev就是pcie_driver 所匹配的PCI橋裝置,而且它在pcie_driver中所對應的裝置特徵號是*ent,就可以對其進行進一步的初始化和探測。
具體行為如下: 綁定熱插拔插槽 設定了控制器及其狀態 註冊中斷處理函數 讀寫配置頭,然後分配資源,最後對插槽檢測,掛接 熱插拔事件的監控處理線程
熱插拔驅動程式對於熱插拔事件的輪詢和通知採用非同步機制,對熱插拔功能組件的操縱是通過讀寫相關寄存器組進行的。主要功能函數關係請參見圖3: 圖3 插槽熱插拔事件處理函數關係
圖3中主要函數的功能介紹如下:
A插槽事件監控線程
作為熱插拔活動最直接的資訊,插槽事件由硬體操作共置位相關寄存器組,系統軟體可以通過定時輪詢或者中斷方式擷取事件資訊,執行對應的事件預先處理函數。插槽事件如下: 熱插拔命令到達 插槽鎖狀態改變 適配卡存在狀態改變 電源出現故障
php_ctlr->int_poll_timer.function = &int_poll_timeout 其中php_ctlr是熱插拔控制器狀態php_ctlr_state_s類型,它定義於pciehp_hpc.h中,記錄當前熱插拔控制器重要狀態,被用作HPC(controller)的控制器控制代碼;熱插拔