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.
.