2011年10月21日 星期五

Boot Manager

Boot Manager會按照定義在Global NVRAM Variable的順序去Load UEFI Driver以及UEFI Application (包括UEFI OS Boot Loader). BIOS必須要使用Global NVRAM Variable裡面的Boot Order去boot. BIOS可以在Boot Order List裡面加入Boot Option或是移除Boot Option. 如果在Boot的過程中有發現一些條件成立的話, BIOS可以在Boot Manager裡面實作Add的功能.
UEFI的Boot順序如下:
1. Boot Order List可以從Global NVRAM Variable讀出. 如果修改此Variable的話, 那麼只能Reset之後才會有作用. Boot Order List會定義一串NVRAM Variable, 每個NVRAM Variable包含了Load的資訊. 每個NVRAM Variable定義了Boot Option的名稱, 可以顯示給User.
2. NVRAM Variable也包含了指標指到Hardware Device以及Hardware Device的File, 此File包含了UEFI image可以被Load.
3. NVRAM Variable可能還包含OS分割區以及目錄.
NVRAM Variable也可以包含Load Option, 直接載入UEFI Image. BIOS並不會知道Load Option會包甚麼東西. 高階的軟體可以寫到Global NVRAM Variable去設定BIOS boot的行為.

3.1 Firmware Boot Manager
Boot Manager會負責決定甚麼東西該載入, 並且和User互動來決定該做甚麼事.

3.1.1 Boot Manager Programming
透過SetVariable()可以修改Load Option Variable. 每個Load Option Entry會存在在Boot#### Variable或Driver#### Variable. ####是唯一的Option Number, 以十六進制表示(0000~FFFF). Load Option會依照Option Number的順序排列. DriverOrder放Driver####, BootOrder放Boot####. 例如, 若要加入一個新的Boot Option, 那麼新的Boot####就會被加入. 接著新的Boot####的Option Number也會加到BootOrder Variable裡面, 此時BootOrder會被重寫.

3.1.2 Load Option Processing
Boot Manager必須要呼叫LoadImage(), 需支援最新的EFI_SIMPLE_FILE_SYSTEM_PROTOCOL以及EFI_LOAD_FILE_PROTOCOL來處理Load Option. 如果LoadImage()成功的話, Boot Manager需要再呼叫StartImage()之前, 透過SetWatchdogTimer()把WatchDog Timer叫起來. 如果Boot Image無法透過LoadImage()載入的話, 那麼Boot Manager就需要去檢查Default Application去Boot. 搜尋Default Application去Boot會發生在Removable以及Fixed Media之間.

3.1.3 Load Options
每個Load Option Variable包含了一個EFI_LOAD_OPTION. 可以透過SetVariable()去建立一個Load Option. 當建立了一個新的Load Option, 所有未定義的參數都需要設為0. 如果Load Option是LOAD_OPTION_ACTIVE的話, Boot Manager會利用Load Option的Device Path去嘗試Boot.

3.4.1.2 Non-removable Media Boot Behavior
在Non-emovable Media的裝置上, 通常會使用"目錄"以及"檔案"來表示FilePath, 讓Platform可以根據此FilePath去達到Boot的動作. 然而BootOrder Variable裡面只有Boot####可以參考, 而且Boot####並無法表示Boot Device的Time Out, 或是指定的檔案是否存在, 或是Boot Variable是否有效, 我們需要有一個預設的行為來處理這些情況的發生.
這個預設的行為包含了讓Boot Manager搜尋Non-removable Media是否有支援EFI_SIMPLE_FILE_SYSTEM_PROTOCOL或是EFI_BLOCK_IO_PROTOCOL. 一般而言Boot Manager會去搜尋所有的媒體裝置, 然而Platform的策略應該會去限制, 只搜尋連接在系統的裝置. 可以選擇這樣的限制來實作.
如果Device的EFI系統分區上有支援EFI_SIMPLE_FILE_SYSTEM_PROTOCOL的話, 那麼BIOS就會去試著從這個EFI系統分區上去Boot, Boot的方式是去執行\EFI\BOOT\BOOTxxx.EFI.
如果Device不支援EFI_SIMPLE_FILE_SYSTEM_PROTOCOL但是有支援EFI_BLOCK_IO_PROTOCOL的話, 那麼就必須要呼叫EFI Boot Service ConnectController去Connect這個Device, 其中DriverImageHandle需要設為NULL, RemainingDevicePath的Recursive flag設為TRUE. BIOS就會試著去Boot此Device的Child.

2011年9月23日 星期五

Boot Menu

1. 在Driver.c中的DxeMain(), 首先會去Locate三個Protocol, 分別是EFI_HII_CONFIG_ROUTING_PROTOCOL, EFI_HII_DATABASE_PROTOCOL以及EFI_FORM_BROWSER2_PROTOCOL.

2. 找到gST->ConsoleInHandle的EFI_SIMPLE_TEXT_INPUT_EX_PROTOCOL.

3. 檢查BootMenu的Protocol是否已安裝, 如果還未安裝, 表示正在Post階段, Setup還沒初始化, 這時候需要先註冊Event, 等EFI_HII_PLATFORM_SETUP_FORMSET安裝後, 再呼叫BmInitSetupMenu(), 註冊完之後順便安裝BootMenu的Protocol.

4.1. (XP版)在SetupBootMenu.c中, BmInitSetupMenu()首先會透過BmLoadBootOptions()去載入所有Boot Option. 接著呼叫BmInitSetupMenuStrings()以及BmInitSetupMenuForm(). 最後透過HiiLibCreatePackageListDriverHandle()建立HII Driver Handle, 透過HiiLibCreatePackageListFromPackages()建立HII Package List.

4.2. (MB3版)在BootIii.c中, BmInitSetupMenu()首先會透過CreateHiiDriverHandle()建立HII Driver Handle. 接著呼叫PreparePackageList()以及NewPackageList()去安裝VFR Form. 接著Locate SCT_BDS_SERVICES_PROTOCOL. 透過Protocol呼叫GetBootList().
4.2.1. BootManager會先InitializeLoadOptions(), Load Option又可分成兩種, 一種是Boot, 另一種是Driver.

5. 如果Setup Menu的Boot Page沒有被執行, 那麼BootManager就會來執行BmInitMenu().

6. 在Menu.c中, BmInitMenu()首先會執行BootMenu.c的BmInitBootMenu()去初始化Boot Menu畫面的設定值. BmInitMenu()也會執行AppMenu.c的BmInitAppMenu()去初始化App Menu畫面的設定值. 接著透過BootOption.c的BmLoadBootOptions()去Load所有Boot Option. 接著執行BmInitAppMenu()和BmLoadAppOptions(). 最後透過HiiLibCreatePackageListDriverHandle()建立HII Driver Handle, 透過HiiLibCreatePackageListFromPackages()建立HII Package List (這段Code類似SetupBootMenu的部分, 但在這裡只有安裝String而已, 沒有安裝Form.

6.1 當BootOption.c執行BmLoadBootOption()時, 會透過BDS_SERVICES_PROTOCOL->GetBootList()去取得Boot List的起點.

7. 最後透過BmDrawMenu()去畫BootMenu. 用BmInteractMenu()去接受Keyboard按下的鍵來做反應.

2011年7月14日 星期四

Hii Setup Menu

1. 建立INF file, 如Sample.inf. 給一組GUID並設定為BS_DRIVER.
2. 建立一個Header file - Vfr.h, 並設定一組Formset GUID. 另外須設定一個Form Id.
3. 建立一個VFR file - Sample.vfr, 並將Formset GUID設定進來.
4. 建立Sample.c, 這裡有一些步驟須進行:
    a. 先宣告一個extern的陣列資料, 命名規則是以VFR檔名+Bin[].
    b. 宣告一個extern的字串陣列資料, 命名規則是以INF檔名+Strings[].
    c. 須設定一組GUID, 此GUID須宣告在DEF file, 之後會安裝給HII Protocol.
    d. 進入DxeMain, 首先要Locate EFI_HII_DATABASE_PROTOCOL.
    e. 再來Locate EFI_HII_CONFIG_ROUTING_PROTOCOL.
    f. 把Bin[]與Strings[]與Formset GUID包成EFI_HII_PACKAGE_LIST_HEADER.
    g. 利用HiiLibCreatePackageListDriverHandle產生一個Driver Handle.
    h. 將產生出來的Driver Handle以及EFI_HII_PACKAGE_LIST_HEADER,
        加到HII Database裡面去. 並產生一個HII Handle.
    i. 安裝EFI_HII_CONFIG_ACCESS_PROTOCOL到Driver Handle裡面去.
       此Protocol包含了三個Callback Function. 包含:
       (1). ExtractConfig. 進入Setup Menu時會呼叫.
       (2). RouteConfig. 離開Setup Menu時會呼叫.
       (3). Callback. 頁面切換時會呼叫.
    j. 將SCT_FORM_SET_PROTOCOL的資料填入, 並安裝給Driver Handle.

2011年6月28日 星期二

Device Path and Image Information of the OS Loader

下列的程式片段會秀出OS Loader自己取得Device Path以及File Path的步驟. 首先會呼叫到HandleProtocol()去取得LOADED_IMAGE_PROTOCOL的介面. 其中ImageHandle會透過Dispater傳給OS Loader. 再來會呼叫HandleProtocol()去取得DEVICE_PATH_PROTOCOL的介面給OS Loader使用. 這兩個呼叫是用來將OS Loader的Device Path, File Path以及其他Image的資訊傳給OS Loader自己.

BS->HandleProtocol(
             ImageHandle,
             &LoadedImageProtocol,
             LoadedImage);

BS->HandleProtocol(
             LoadedImage->DeviceHandle,
             &DevicePathProtocol,
             &DevicePath);

Print (
   L"Image device : %s\n",
   DevicePathToStr (DevicePath));

Print (
   L"Image file : %s\n",
   DevicePathToStr (LoadedImage->FilePath));

Print (
   L"Image Base : %X\n",
   LoadedImage->ImageBase);

Print (
   L"Image Size : %X\n",
   LoadedImage->ImageSize);

2011年6月20日 星期一

SMM Mode

SMM是CPU一個獨有的運作模式, 當CPU SMI Pin被觸發時, 就會進入到SMM Mode, 以程式而言, 它會取得Vector去看位址是多少, 而這個位址所指到的記憶體就稱為SMM RAM, 所有有關SMM的Driver都會放在這塊RAM裡面, 當產生中斷進入SMM時, 會去判斷發生中斷的人是誰, 然後去執行相關的Function, 當POST結束之後, 這塊SMM RAM就會被Lock起來, 避免Run Time時有人會去破壞.

在UEFI的架構中, SMM Driver是一個個獨立的Driver, EFI會有Dispatcher來負責執行Driver, 當執行過程中發現Driver是SMM Driver時, 這時會呼叫Kernel的Function, 將此Driver搬到SMM RAM裡面.

SMM Driver可以從Entry Point發現與DXE Driver的不同, SMM的Entry Point會先去Locate一個叫EFI_SMM_BASE_PROTOCOL Protocol, 透過此Protocol來知道目前是否在SMM Mode下, 如果不是在SMM Mode下的話, 程式會先去註冊(Register), 將這支SMM Driver的FV位址以及大小Register起來, 如此一來系統就會依照這些資訊將此SMM Driver搬到SMM RAM裡面去, 搬完之後系統會產生一次SMI, 此時會再進入一次SMM Driver的Entry Point, 第二次進入Entry Point透過EFI_SMM_BASE_PROTOCOL.InSmm ()就會知道目前是在SMM Mode下, 第二次就要透過EFI_SMM_BASE_PROTOCOL.RegisterCallback ()註冊Callback Function. 等下一次SMI觸發時, 就可以進入到Callback Function去執行.

ACPI Enable/Disable

ACPI在OS起來之後, 會去Locate DSDT Table, 在Locate之前, 會先找到FACS Table (Firmware ACPI Control Structure), FACE Table裡面有一個欄位, 會記載Software SMI的Port是多少, 以及ACPI Enable/Disable的Software SMI Number是多少, 如此一來就可以透過Out SMI_Port, ACPI_Number的方式去發SMI來執行ACPI Enable/Disable的Callback Function.

上述的動作可以參考Platform\PlatformSmm的Driver.

S3 Save

在系統進入S3之前, 有一些Chipset的內容需要儲存到記憶體內, 所以需要有一支Driver來處理這些儲存的動作, 在系統進入S3之前, 這些動作會在SMM中完成.

上述的動作可以參考Platform\PlatformS3的Driver.

要做這些儲存的動作, 會透過一種稱為"Boot Script"的命令去完成, Boot Script會在你S3回來後, 把一些已經初始化好的值回填到暫存器內, 如此一來系統才能夠S3回來之後還能夠繼續正常工作. "Boot Script"是EFI的Protocol - EFI_BOOT_SCRIPT_SAVE_PROTOCOL, 而回填的動作是在PEI完成的, 所以會透過EFI_PEI_BOOT_SCRIPT_EXECUTER_PPI.Execute來完成.
當從S3回到OS, POST過程並不會經過DXE Driver, 但會經過PEI Driver, 所以會在PEI Driver上偵測目前的Boot Mode是否為S3, 如果是S3的話, 就透過Boot Script將值填回暫存器內.

EFI OS Loaders

這個段落會討論一些特別的考慮有關OS Loader. OS Loader是一種特別的EFI程式, 負責轉換系統, 從firmware的環境轉到OS的環境. 為了要達成這樣的任務, 有一些重要的步驟需要掌握:

1. OS Loader必須要決定它會從哪裡載入. 這個決定可以允許OS Loader可以從相同的地方取得一些額外的檔案.

2. OS Loader必須要知道OS存在哪裡. 一般而言, OS會在HDD的分割區裡. OS存在的分割區可能無法符合EFI環境能夠使用的檔案系統.  在這個情況下, OS Loader只能夠像Block Device一樣只使用Block I/O來存取分割區. OS Loader接著會需要實做或載入File System Driver去存取在OS分割區上的檔案.

3. OS Loader必須要建立實體記憶體的記憶體對映(memory map of physical memory), 使OS Kernel可以知道記憶體要管理的東西. 系統中一些實體記憶體必須要保留一些區段讓OS Kernel無法存取, 所以OS loader必須使用EFI API去取得系統目前的記憶體對映.

4. OS會有一些儲存Boot Path的選項以及Boot Option, 用環境變數的形式存在非揮發的儲存裝置(nonvolatile storage)裡. OS Loader可能會需要使用到這些儲存在非揮發儲存裝置的環境變數, 此外, OS Loader可能會需要將這些環境變數傳給OS Kernel.

5. 下一個步驟是去呼叫ExitBootServices(), 這個呼叫可以由OS Loader或試OS Kernel來完成, 要特別注意的是必須要在呼叫這個Function之前, 要取得目前記憶體對映(Current Memory Map). 一旦呼叫了ExitBootServices(), 就再也沒有EFI Boot Service可以使用了. 在某些點, 呼叫ExitBootServices()之前或之後, OS Loader就會將控制權交給OS Kernel.

6. 最後, 呼叫ExitBootServices()之後, EFI Boot Service就無法再使用, 這表示一旦OS Kernel取得系統的控制權之後, OS Kernel就只會呼叫EFI Runtime Service. 下列可以找到OS Loader程式的完整列表. 在下列的程式片段中並沒有完成任何的錯誤檢查. OS Loader的範例程式呼叫了幾種EFI Library來做一些簡單的實作. 下列會秀出OS Loader自己的Device Path以及File Path. 它也秀出了OS Loader存在的記憶體位址, 以及使用了多少Bytes. 接著它載入了OSKERNEL.BIN到記憶體, 而OSKERNEL.BIN是從OS Loader相同的目錄中取得的.
接著會秀出數個Block Device的第一個Block. 第一個Block是Floppy Drive的FAT12 File System. 第二個是HDD的MBR(Master Boot Record). 第三個則是相同HDD的大FAT32分割區. 第四個是相同HDD的小FAT16分割區.
最後的步驟是秀出所有System Configuration Table, 系統目前記憶體對映的指標以及列出所有系統的環境變數.  然後最後OS Loader呼叫ExitBootServices().

2011年6月17日 星期五

Protocols You Should Know

這個章節描述的是EFI會經常使用到的Protocol. 不管是要建立Device Driver, EFI pre-OS應用程式, 或者是Platform Firmware都應該要知道. 在這裡會有一些例子說明Protocol, 從最耳熟能響的"Hello world"開始. 這個測試會列在這裡是因為它最簡單. 它並不會與其他EFI Library的function有任何相依性. 所以在執行時並不會產生連結到EFI Library. 這個測試程式會使用Entry Point傳進來的SystemTable, 去取得EFI console device的控制權. 要在Console Output Device(通常是螢幕)上顯示一段訊息, 可以透過SIMPLE_TEXT_OUTPUT_INTERFACE Protocol的OutputString()來顯示訊息. 而Console Input Device(通常是鍵盤)可以等待使用者按下鍵盤, 可以透過SIMPLE_INPUT_INTERFACE Protocol的WaitForEvent()以及WaitForKey Event來達成. 一旦按鍵按下, 程式就會離開.

#include "efi.h"
EFI_STATUS
InitializeHelloApplication (
IN EFI_HANDLE ImageHandle,
IN EFI_SYSTEM_TABLE *SystemTable
)
{
  UINTN Index;


  //
  // Send a message to the ConsoleOut device.
  //


  SystemTable->ConOut->OutputString (
  SystemTable->ConOut,
  L"Hello application started\n\r");


  //
  // Wait for the user to press a key.
  //


  SystemTable->ConOut->OutputString (
  SystemTable->ConOut,
  L"\n\r\n\r\n\rHit any key to exit\n\r");


  SystemTable->BootServices->WaitForEvent (
  1,
  &(SystemTable->ConIn->WaitForKey),
  &Index);


  SystemTable->ConOut->OutputString (
  SystemTable->ConOut,L"\n\r\n\r");


  //
  // Exit the application.
  //


  return EFI_SUCCESS;
}


要執行EFI的程式, 需要在EFI Shell的command line打入程式的名稱, 下面的例子可以知道如何在EFI Shell中執行程式.
Example:
Shell> hello
Hello application started
Hit any key to exit this image

2011年4月25日 星期一

Pseudo Code

三種不同形式的Driver, 在這裡會說明有關Start() function的演算法. Driver的Start() function實作方式會影響到Supported() function的實作方式.

EFI_DRIVER_BINDING_PROTOCOL的所有Service需要共同合作才能確保所有資源的開啟與配置可以在Stop()中釋放掉.
第一種演算法是一個簡單的Device Driver, 它並不會建立任何額外的Handles. 它只會將一個或多個Protocol加到一個已經存在的Handle.
第二種是一個簡單的Bus Driver, 它會在第一次呼叫Start()的時候, 建立所有的Child Handle. 它不會為了Bus Controller附加任何額外的Protocol到Handle裡面.
第三種是一個比較進階的Bus Driver, 它可以在呼叫Start()時候只建立一個Child Handle. 也可以一次建立所有的Child Handle. 同樣地, 它不會為了Bus Controller附加任何額外的Protocol到Handle裡面.

Device Driver

1. 用OpenProtocol()去開啟所有需要的Protocol. 如果這個Driver允許開啟的Protocol可以和其他Driver共用. 那麼它就應該使用EFI_OPEN_PROTOCOL_BY_DRIVER參數去開啟. 如果Driver不允許開啟的Protocol和其他Driver共用的話, 那麼它就應該使用EFI_OPEN_PROTOCOL_BY_DRIVER | EFI_OPEN_PROTOCOL_EXCLUSIVE的參數去開啟. 相同的參數可以用在Supported().

2. 如果在第一步時, 呼叫任何一個OpenProtocol()後, 發生錯誤的話, 那麼就必須要透過CloseProtocol()關掉所有在第一步開啟的Protocol, 並且將錯誤的狀態碼回傳回去.

3. 忽略傳入的參數RemainingDevicePath.

4. 初始化ControllerHandle指定的Device. 如果有錯誤發生, 那麼就必須要透過CloseProtocol()關掉所有在第一步開啟的Protocol, 並且將EFI_DEVICE_ERROR回傳回去.

5. 配置以及初始化所有這個Driver會需要用到的資料結構, 以便管理ControllerHandle指定的Device. 這包含了Public Protocol的空間以及任何額外Private Data Structure的空間, 這些資料都是與ControllerHandle有關連的. 如果有錯誤發生, 那麼就必須要透過CloseProtocol()關掉所有在第一步開啟的Protocol, 並且將EFI_OUT_OF_RESOURCES回傳回去.

6. 使用InstallProtocolInterface()去安裝所有新的Protocol介面到ControllerHandle. 如果有錯誤發生, 那麼就必須要透過CloseProtocol()關掉所有在第一步開啟的Protocol, 並且將InstallProtocolInterface()的錯誤回傳回去.

7. 完成之後回傳EFI_SUCCESS.

Bus Driver在第一次呼叫Start()時, 會建立所有的Child Handles.

1. 用OpenProtocol()去開啟所有需要的Protocol. 如果這個Driver允許開啟的Protocol可以和其他Driver共用. 那麼它就應該使用EFI_OPEN_PROTOCOL_BY_DRIVER參數去開啟. 如果Driver不允許開啟的Protocol和其他Driver共用的話, 那麼它就應該使用EFI_OPEN_PROTOCOL_BY_DRIVER | EFI_OPEN_PROTOCOL_EXCLUSIVE的參數去開啟. 相同的參數可以用在Supported().

2. 如果在第一步時, 呼叫任何一個OpenProtocol()後, 發生錯誤的話, 那麼就必須要透過CloseProtocol()關掉所有在第一步開啟的Protocol, 並且將錯誤的狀態碼回傳回去.

3. 忽略傳入的參數RemainingDevicePath.

4. 初始化ControllerHandle指定的Device. 如果有錯誤發生, 那麼就必須要透過CloseProtocol()關掉所有在第一步開啟的Protocol, 並且將EFI_DEVICE_ERROR回傳回去.

5. 搜尋Bus Controller所指定ControllerHandle的所有Child Device.

6. 如果Bus需要它, 配置資源給Bus Controller所指定ControllerHandle的所有Child Device.

7. 針對每個ControllerHandle的Child C開始FOR迴圈

8. 配置以及初始化這個Driver需要管理Child Device的所有資料結構. 這包含了與Child C有關的Public Protocol的空間以及任何額外Private Data Structure的空間, 如果有錯誤發生, 那麼就必須要透過CloseProtocol()關掉所有在第一步開啟的Protocol, 並且將EFI_OUT_OF_RESOURCES回傳回去.

9. 如果Bus Driver建立了Device Path給Child Device, 接著會根據附加在ControllerHandle的Device Path建立Device Path給Child C.

10. 初始化Child Device C. 如果有錯誤發生, 那麼就必須要透過CloseProtocol()關掉所有在第一步開啟的Protocol, 並且返回EFI_DEVICE_ERROR錯誤.

11. 建立一個Handle給C, 並安裝一個Protocol Interface給Child Device C. 這可能會包含EFI_DEVICE_PATH_PROTOCOL.

12. 呼叫OpenProtocol()並傳入EFI_OPEN_PROTOCOL_BY_CHILD_CONTROLLER參數來代表Child C.

13. 結束迴圈.

14. 返回EFI_SUCCESS.

2011年4月24日 星期日

Hot Plug Events

在以前, System Firmware在pre-boot的階段並沒有Hot Plug Events的需求. 然而, 例如像USB這種高階的Bus, 使用者會經常的插拔裝置. 要確保在EFI Driver Model內能夠描述這些Bus的種類是很重要的. Bus的Bus Driver要能夠支援這些Hot Plag Events. 對於這種類型的Bus, 有一些Platform的管理必須要轉成這種Bus Driver. 例如, 當一個鍵盤要插入USB Bus, 使用者會希望鍵盤是可以動的. USB Bus Driver要能夠偵測Add的Event發生, 並且建立一個Child Handle給Keyboard device.
然而, 因為Driver無法連接到Controller(除非呼叫ConnectController()), 所以鍵盤無法啟動. 當Add的Event發生, 要讓鍵盤啟動就必須仰賴USB Bus Driver去呼叫ConnectController(). 此外, 當Remove的Event發生時, USB Bus Driver也要能夠呼叫DisconnectController().

Hot Plug Events也會影響Device Driver. 例如USB, 在沒有任何通知下可以移除Device. 這代表USB Device Driver的Stop() function必須要和系統中不再存在的Device關掉Drver. 所以任何不能完成的I/O請求必須在不能接觸Hardware的情況下被清除.

一般而言, 增加支援Hot Plug Events的功能會大大地增加Bus Driver與Device Driver之間的複雜性.

下面有兩個例子, 第一個例子會去察看Controller Handle, 第二個例子會去測試Device Path.

extern EFI_GUID gEfiDriverBindingProtocolGuid;
EFI_HANDLE gMyImageHandle;
EFI_HANDLE DriverImageHandle;
EFI_HANDLE ControllerHandle;
EFI_DRIVER_BINDING_PROTOCOL *DriverBinding;
EFI_DEVICE_PATH_PROTOCOL *RemainingDevicePath;

//
// Use the DriverImageHandle to get the Driver Binding Protocol
// instance
//

//
// 透過DriverImageHandle取得Driver Binding Protocol的指標, 並放到DriverBinding內.
//

Status = gBS->OpenProtocol (
                             DriverImageHandle,
                             &gEfiDriverBindingProtocolGuid,
                             &DriverBinding,
                             gMyImageHandle,
                             NULL,
                             EFI_OPEN_PROTOCOL_HANDLE_PROTOCOL);

if (EFI_ERROR (Status)) {
  return Status;
}

//
// EXAMPLE #1
//
// Use the Driver Binding Protocol instance to test to see if
// the driver specified by DriverImageHandle supports the
// controller specified by ControllerHandle
//

//
// 取得Driver Binding Protocol的instance後,
// 就可以透過DriverBinding去測試, DriverImageHandle的Driver是否
// 有支援ControllerHandle的Controller.
//
Status = DriverBinding->Supported (
                                             DriverBinding,
                                             ControllerHandle,
                                             NULL);

//
// 如果成功的話, 表示Driver支援Controller, 執行Start() function.
//

if (!EFI_ERROR (Status)) {
  Status = DriverBinding->Start (
                                              DriverBinding,
                                              ControllerHandle,
                                              NULL);
}
return Status;

//
// EXAMPLE #2
//
// The RemainingDevicePath parameter can be used to initialize
// only the minimum devices required to boot. For example,
// maybe we only want to initialize 1 hard disk on a SCSI
// channel. If DriverImageHandle is a SCSI Bus Driver, and
// ControllerHandle is a SCSI Controller, and we only want to
// create a child handle for PUN=3 and LUN=0, then the
// RemainingDevicePath would be SCSI(3,0)/END. The following
// example would return EFI_SUCCESS if the SCSI driver supports
// creating the child handle for PUN=3, LUN=0. Otherwise it
// would return an error.
//

//
// RemainingDevicePath參數可以用來初始化最少Device Boot的需求.
// 例如, 我們可能只需要初始化SCSI上的一個HDD. 如果DriverImageHandle是
// 一個SCSI Bus Driver, 而ControllerHandle是一個SCSI Controller的話, 那麼
// 我們就只需要去建立一個PUN = 3以及LUN = 0的Child Handle. 它的
// RemainingDevicePath就可以寫成SCSI(3,0)/END. 下面的例子, 如果
// SCSI Driver支援PUN = 3以及LUN = 0的Child Handle的話, 那麼就會返回
// EFI_SUCCESS, 否則返回error.
//

Status = DriverBinding->Supported (
                                             DriverBinding,
                                             ControllerHandle,
                                             RemainingDevicePath);
if (!EFI_ERROR (Status)) {
  Status = DriverBinding->Start (
  DriverBinding,
  ControllerHandle,
  RemainingDevicePath);
}
return Status;

2011年4月23日 星期六

Platform Components

在EFI Driver Model的架構之下, 從Controller連接或斷線到Driver的動作都是在Platform Firmware的控制下. 通常都會實做為EFI Boot Manager的一個部分, 但也可能實做在其他地方. Platform Firmware會使用ConnectController()以及DisconnectController()來決定哪一個Controller可以開始, 哪個不行. 如果Platform希望完成Diagnostic測試或安裝OS, 那麼它可能會去連接Driver到所有可能boot的Device. 如果Platform希望boot到預先安裝好的OS, 那麼它可以只連接一個Driver到有安裝OS的Device.

EFI Driver Model同時支援Boot Service的ConnectController()以及DisconnectController()的運作模式. 此外, 因為背負boot責任的Platform Component需要Console Device的Device Path幫忙以及Boot Option的幫忙, 所有EFI Driver Model的Service以及Protocol的複雜使用都會與Device Path一起做最佳化處理.

Platform也可以選擇要不要產生一個叫做Platform Driver Override的Protocol, 此Protocol類似Bus Specific Driver Override Protocol, 但是Platform Driver Override Protocol有較高的優先權. 此Protocol賦予了Platform Firmware最高的優先權, 可以讓Platform Firmware有權力決定哪一個Driver連接到哪一個Controller. Platform Driver Override Protocol會附加在系統的Handle之中. 通常Boot Service的ConnectController()會讓此Protocol可以使用.

2011年4月22日 星期五

Bus Drivers

從EFI Driver Model的觀點來看, Bus Driver和Device Driver其實是差不多的. 唯一的差別在於Bus Driver會建立新的Device Handle給自己在Bus上找到的Child Controller使用. 所以Bus Driver會比Device Driver來得複雜些. Device Driver在設計與實作上就會比較簡單.

Bus Driver主要有兩種. 第一種會呼叫一次Start()就建立Handle給所有的Child Controller. 每個Child Controller都有Handle. 第二種則是透過多次呼叫Start()的方式, 建立一個個的Handle給Child Controller. 第二種Bus Driver對於Fast Boot非常有用. 它可以建立少量的Handle甚至一個Handle. 通常在Bus上會花很多時間在列舉Child的裝置(例如SCSI), 而這種方式可以節省許多時間.

下圖秀出了Bus Controller在Start()呼叫前與呼叫後的樹狀結構. 進來到Bus Controller的虛線代表Bus Controller的Parent Controller. 如果Bus Controller是一個Hos Bus Controller的話, 那麼它就不會有Parent Controller.

節點A, B, C, D以及E表示Bus Controller的Child Controller. 每次呼叫Start(), Bus Driver可以支援只建立一個Child, 而且可以選擇先建立Child C, 以及Child E, 接著是剩下的Child A, Child B和Child D. Driver Binding Protocol的Supported(), Start()以及Stop() function有足夠的彈性能夠這樣做.

Bus Driver必須要安裝Protocol介面到每個Child Handle之中. 在最精簡的情況下, Bus Driver必須要安裝一個可操作Bus Service的I/O Protocol介面到Child Controller之中. 如果Bus Driver建立了一個代表實體的Child Handle, 那麼Bus Driver也必須要安裝一個Device Path Protocol到Child Handle裡面.

Bus Driver可以選擇性地安裝Bus Specific Driver Override Protocol到每一個Child Handle. 這個Protocol會使用在Driver連接到Child Handle時, 呼叫Boot Service ConnectController(), 會根據定義好的一些優先權規則, 找到最適合的Driver安裝到Controller上. Bus Specific Driver Override Protocol比一般的Driver搜尋演算法有更高的優先權, 但比Platform Override低.

PCI Bus Driver會給儲存在PCI controller option ROM的Driver比較高的優先權, 優先權會比儲存在Platform其他位置的Driver還高.

例如: XYZ Bus Driver建立的Child Device Handle支援Bus Specific Driver Override機制.

Child Device Handle
  1. EFI_DEVICE_PATH_PROTOCOL
  2. EFI_XYZ_IO_PROTOCOL
  3. EFI_BUS_SPECIFIC_DRIVER_OVERRIDE_PROTOCOL (可選擇性地安裝)

2011年4月21日 星期四

Device Drivers

Device Driver並不允許建立任何新的Device Handle. 而是安裝外掛的Protocol介面到一個已經存在的Device Handle. 最常見的例子就是Device Driver安裝I/O介面到Device Handle. 而這個Device Handle是由Bus Driver所產生的. EFI OS會透過此I/O介面Boot到OS.

有一些I/O介面的例子, 包含Simple Text Output, Simple Input, Block I/O以及Simple Network Protocol等.

例如: Device Handle在Device Driver連接之前有:
  1. EFI_DEVICE_PATH_PROTOCOL
  2. EFI_XYZ_IO_PROTOCOL
在Device Driver連接到Device Handle之後會有:
  1. EFI_DEVICE_PATH_PROTOCOL
  2. EFI_PCI_HOST_BRIDGE_IO_PROTOCOL
  3. EFI_BLOCK_IO_PROTOCOL
上述的例子可以看到, Device Handle是屬於XYZ Bus建立出來的Child Handle, 所以它包含了XYZ Bus所支援的XYZ I/O Protocol. 同時它也包含了XYZ Bus Driver所配置的Device Path Protocol. 然而並不是所有的Device Handle都需要Device Path Protocol. 在系統中代表實體裝置的Device Handle才需要Device Path Protocol. 虛擬裝置的Device Handle是不需要Device Path Protocol的. 連接到Device Handle的Device Driver必須要在自己的Image Handle上安裝Driver Binding Protocol. Driver Binding Protocol包含了三個function, 分別為Supported(), Start()以及Stop(). 其中Supported()是用來測試看說Driver是否有支援Controller. 以上面的例子而言, Driver會檢查Device Handle是否有支援Device Path Protocol以及XYZ I/O Protocol. 如果Driver的Supported() function有通過的話, 那麼接著Driver就可以透過Driver的Start() function去連接Controller. Start() function才是真正加入外掛I/O protocol到Device Handle的地方. 以上述的例子而言, 就可以看到Device Handle有被加入了Block I/O Protocol.

為了要符合對稱性, Driver Binding Protocol也有Stop() function. 可以強迫Driver停止管理Device Handle. Stop()之後, Device Driver就會將Start() function中安裝的所有Protocol全部uninstall掉.

EFI Driver Binding Protocol的Support(), Start()以及Stop()的三個function需要能夠讓Boot Service的OpenProtocol()可以取得Protocol的介面, 同時也要能夠讓Boot Service的CloseProtocol()釋放掉Protocol的介面.

OpenProtocol()以及CloseProtocol()可以更新System Firmware所管理的Handle Database. 如此一來就可以追蹤哪一個Driver正在使用Protocol的介面. 透過Handle Database的資訊就可以取得Driver與Controller的相關資訊. Boot Service的OpenProtocolInformation()可用來取得目前正在使用特殊Protocol介面的Component(元件)列表.

2011年4月20日 星期三

Host Bus Controllers

在Driver的Entry Point中, 並不允許接觸Hardware(直接呼叫Hardware function). 所以當Driver Image被載入和啟動後, Driver在系統中只是被動者, Driver會一直等待, 直到系統告知Driver要管理一個或是多個控制器(Controller).

例如: EFI Boot Manager是負責管理Driver連接到Controller的工作.

然而, 在做這些連接動作之前, 必須要先收集這些Controller的資訊給Driver, 以方便管理. 而這些Controller的源頭就是Host Bus Controller. Firmware Component並不受EFI Driver Model的限制, 它會產生Host Bus Controller, 而Host Bus Controller會提供I/O介面. 這種EFI Driver並不屬於EFI Driver Model的一種. (這個部分很抽象...)

在EFI中, 每個Host Bridge可用Device Handle來表示, Device Handle包含了一個Device Path Protocol, 另一個Protocol提供了I/O的運作, Host Bus可以透過這些I/O來完成某些事.

例如: PCI Host Bus Controller支援PCI Host Bridge I/O Protocol. PCI Host Bridge是一個Device Handle. 此時Device Handle包含了EFI_DEVICE_PATH_PROTOCOL以及EFI_PCI_HOST_BRIDGE_IO_PROTOCOL.

PCI Bus Driver會連接到PCI Host Bridge, 並針對系統中的每個PCI Device建立出對應的Child Handle. 接著PCI Device Driver就會連接到這些Child Handle, 並產生I/O介面, 讓EFI OS可以透過此I/O介面Boot進去.

之後會再介紹不同形式的EFI Driver Model.

2011年4月19日 星期二

Driver Initialization

Driver Image這種類型的檔案, 通常會存放在儲存裝置中, 例如: ROM, flash, HDD, floppy, CD-ROM, 甚至是可以放在網路(雲端技術); 一旦在這些裝置中找到Driver Image的話, 那麼就可以使用Boot Service的LoadImage(), 將Driver Image載入到System Memory.

例如: gBS->LoadImage (
               TRUE,
               ImageHandle,
               DevicePath,
               NULL,
               0,
               &FileImageHandle);
上面程式的解釋應該為, 把DevicePath上的Driver Image載入到System Memory之後, 取得Handle存放在FileImageHandle裡面.

LoadImage()會將PE/COFF格式的Driver Image載入到System Memory. 載入之後就會產生一組Handle, 此Handle會被掛上一個Loaded Image Protocol. 此時Driver還未真正開始啟動. 上面的所有動作都只是把Driver Image放到System Memory等待啟動而已.

這時候的Image Handle還不算是Driver Image Handle.

要啟動Driver, 需要透過Boot Service的StartImage(). 所有的EFI Application和EFI Driver都可以在EFI相容的系統中做載入(Loaded)和啟動(Started)的動作. 為什麼可以做到相容呢? 原因就是因為寫EFI Driver時必需要遵守EFI Driver Model的規定.

甚麼樣的規定需要遵守呢?
首先, 在Driver的Entry Point內不能夠直接接觸到任何Hardware, 也就是說不能呼叫任何有關Hardware的function.
在Driver的Entry Point內只能安裝Protocol到Driver自己的Image Handle. 遵守EFI Driver Model的Driver需要安裝Driver Binding Protocol到Driver自己的Image Handle. Driver也可以選擇性地決定要不要安裝其他Protocol, 例如: Driver Configuration Protocol, Driver Diagnostics Protocol或是Component Name Protocol. 此外, 如果Driver希望自己是可以被卸載的, 那麼就可以更新Loaded Image Protocol所提供的Unload() function.
最後, 當Boot Service的ExitBootServices()被呼叫時, 如果Driver需要完成任何特殊的動作, Driver可以建立一個Event, 此Event包含了Notification function. 當Boot Service的ExitBootServices()被呼叫的時候, 可以觸發此Event.

一個包含Driver Binding Protocol的Image Handle就稱為Driver Image Handle.
.