本文旨在簡單介紹一下UEFI中驅動程序的加載方式(這里涉及的模塊指的是符合UEFI Driver Model的模塊):
在UEFI中,當一個驅動模塊被加載時,在模塊入口點只會安裝EFI_DRIVER_BINDING_PROTOCOL等,而不會去執行驅動程序的初始化(這一點與Linux中不同,在Linux中,當我們在驅動模塊的入口點調用driver_register()來注冊驅動的時候,會在driver_register()的里面調用總線的match(),驅動與設備匹配成功之后緊接着就會調用驅動程序的probe()函數來執行驅動的初始化),而在UEFI中,只有當我們在Boot Manager中調用gBS->ConnectController()時才會去匹配並初始化驅動程序。
下面我們設想一個硬件結構,以此為例來描述一下UEFI的驅動加載過程:
硬件結構:CPU內部有一個PCI Host Bridge,PCI Host Bridge下面有一個Root Bridge,USB Host Controller(EHCI)掛在Root Bridge管理的PCI總線上,再外接一個USB KeyBoard(USB鍵盤)。
目標:現在我們要通過USB鍵盤輸入字符。
涉及的設備驅動程序:pci host bridge driver、pci bus driver、usb host driver(EHCI)、usb bus driver、usb keyboard driver。
UEFI相關背景知識:
1. POST過程中,DXE內核會Load所有的模塊,模塊會執行他們的入口函數;
2. 大部分設備驅動程序(這里指pci bus driver、usb host driver、usb bus driver、usb keyboard driver)在入口函數只做一件事(安裝EFI_DRIVER_BINDING_PROTOCOL);
3. pci host bridge driver有點不一樣,它在入口函數會:
-> 創建host bridge的handle並在handle上安裝EFI_PCI_HOST_BRIDGE_RESOURCE_ALLOCATION_PROTOCOL;
-> 創建root bridge的handle並在handle上安裝EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL;
4. EFI_BOOT_SERVICES.LocateHandleBuffer()能夠獲取系統中所有安裝某種Protocol的handle;
5. EFI_BOOT_SERVICES.ConnectController()會把系統中所有的EFI_DRIVER_BINDING_PROTOCOL都找出來,並執行EFI_DRIVER_BINDING_PROTOCOL->Support()來匹配Device和Driver;匹配成功之后,ConnectController()會接着調用EFI_DRIVER_BINDING_PROTOCOL->Start()來執行驅動程序的初始化。
6. 關於Handle與Protocol:一個Handle上可以安裝多個不同的Protocol,不同的Handle上可以安裝同一個Protocol的不同實例(類似一個二維鏈表)
下面的代碼模擬執行所有硬件相關的初始化:
//第一步:查找系統中的PCI Root Bridge,並加載PCI總線驅動 // 獲取系統中所有安裝EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL的handle;即找出系統中所有的pci root bridge gBS->LocateHandleBuffer ( ByProtocol, &gEfiPciRootBridgeIoProtocolGuid, NULL, &NumHandles, &HandleBuffer); // 查找並加載pci bus driver // PCI bus driver的Support()通過判斷device handle上是否安裝有EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL來匹配; for (Index = 0; Index < NumHandles; Index++) { //循環root bridge Status = gBS->ConnectController ( HandleBuffer [Index], NULL, NULL, FALSE); } // pci bus driver會枚舉出所有的pci device,為每個pci device創建handle,並在handle上安裝EFI_PCI_IO_PROTOCOL; //第二步:加載EHCI driver,初始化USB Host Controller // 獲取系統中所有安裝EFI_PCI_IO_PROTOCOL的handle(即找出系統中所有的pci device); Status = gBS->LocateHandleBuffer ( ByProtocol, &gEfiPciIoProtocolGuid, NULL, &HandleCount, &Handles ); // 因為我們只關心EHCI,所以通過判斷pci device的class code找到EHCI for (Index = 0; Index < HandleCount; Index++) { Status = gBS->HandleProtocol ( Handles[Index], &gEfiPciIoProtocolGuid, (VOID **) &PciIo ); if (!EFI_ERROR (Status)) { // 讀取pci配置空間 Status = PciIo->Pci.Read (PciIo, EfiPciIoWidthUint8, 0x09, 3, &Class); if (!EFI_ERROR (Status) &&((PCI_CLASS_SERIAL == Class[2]) && (PCI_CLASS_SERIAL_USB == Class[1]))) { // pci device 是 EHCI // 查找並加載驅動 // EHCI driver的support()通過判斷device handle是否安裝了EFI_PCI_IO_PROTOCOL以及class code是否正確來匹配 Status = gBS->ConnectController ( Handles[Index], NULL, DevicePath, FALSE ); } } } // EHCI driver的start()會為handle安裝EFI_USB_HC2_PROTOCOL;表明這是一個USB Host Controller //第三步:加載USB總線驅動 // 獲取系統中所有安裝EFI_USB_HC2_PROTOCOL的handle(即找出系統中所有的USB Host Controller); Status = gBS->LocateHandleBuffer ( ByProtocol, &gEfiUsb2HcProtocolGuid, NULL, &UsbHcHandlesCount, &UsbHcHandles); // 查找並加載usb bus driver // usb bus driver的support通過判斷device handle是否安裝了EFI_USB_HC2_PROTOCOL來匹配 if (!EFI_ERROR (Status)) { for (i = 0; i < UsbHcHandlesCount; i++) { gBS->ConnectController (UsbHcHandles [i], NULL, NULL, TRUE); } } // usb bus driver的start()會枚舉所有的USB device,為每個device創建device handle,並安裝EFI_USB_IO_PROTOCOL(用來表明這是一個USB設備); // 當usb keyboard device被枚舉之后,usb bus driver會調用EFI_BOOT_SERVICES.ConnectController()查找他的驅動;
// usb keyboard driver的support()會判斷device handle是否安裝了EFI_USB_IO_PROTOCOL以及Interface描述符的class、subclass、protocol來匹配; // usb keyboard driver的start()會在keyboard的device handle上安裝EFI_SIMPLE_TEXT_INPUT_PROTOCOL。 //第四步:調用EFI_SIMPLE_TEXT_INPUT_PROTOCOL接收鍵盤的輸入 while (TRUE) { // 調用EFI_SIMPLE_TEXT_INPUT_PROTOCOL gST->ConIn->ReadKeyStroke (gST->ConIn, &Key); if (Key.ScanCode == CONFIG_SYSTEM_ERROR_MANAGER_MENU_RESUME_KEY) { // 用戶輸入正確的按鍵 return; } }
總結:UEFI中通過Protocol來標識device handle的類型(當然底層驅動也通過Protocol向上層驅動提供接口)。UEFI中的驅動程序的分層很清晰,由底向上依次依賴。驅動程序的初始化由POST過程控制,便於控制和理解(Linux中則由各個子系統控制,以USB系統為例:UEFI必須先初始化Host Controller Driver,然后初始化USB Bus Driver,最后初始化USB Device Driver;而在Linux中,不存在這種依賴關系)