BIOS知识枝桠—— Protocol
UEFI的Protocol
- Protocol的概念
- Protocol的數據結構
- Protocol的實現
- Protocol的使用
- 其他類別的Protocol
- Device Path Protocol
- EFI Driver Binding Protocol
本文為參閱UEFI原理與編程第四章及他人博客后寫的融合怪,由于UEFI spec第八章起都是Protocol的,覺得有必要寫,侵刪
Protocol的概念
在計算機通信中,Protocol是網絡協議的簡稱,網絡協議是通信計算機雙方必須共同遵從的一組約定,是為了使數據在網絡上從源到達目的,網絡通信的參與方必須遵循相同的規則,這套規則稱為協議(protocol),典型的Protocol有HTTP、FTP、TCP、IP等,通過Http完成了文件的傳輸。
在UEFI中Protocol同樣是重要的概念之一,Protocol提供了一種在UEFI應用程序以及UEFI驅動之間的通信方式。通過Protocol,用戶可以使用驅動提供的服務,以及系統提供的其他服務。從本質上說是一種調用者與被調用者之間的“約定”。而這種“約定”在軟件開發領域有另一個更形象化的名字叫接口(Interface)。為了做到二進制間的互操作,那么參與操作的雙方(調用者與被調用者)都必須做出一定的讓步,這個讓步就是雙方必須遵循實現商量好的調用方法(接口),而這種事先約定的接口就是protocol的定義。Protocol引入了C++的面向對象的思想來設計管理,相當于C++的class,用 struct 來模擬 class,用函數指針(Protocol的成員變量)模擬成員函數,此種函數的第一參數必須是指向Protocol的指針,用來模擬this指針。
DXE驅動之間通過Protocol通信,Protocol是一種特殊的結構體,每個Protocol對應一個GUID,利用系統BootService的OpenProtocol,并根據GUID來打開對應的protocol,進而使用這個Protocol提供的服務。Protocol不是UEFI BIOS一開始就可以用的。UEFI BIOS啟動時分為不同的階段,SEC-PEI-DXE-BDS等等。而Protocol需要等到DXE階段才可以使用(不需要特別在意DXE階段的哪個點開始,基本上開發時寫的DXE模塊都可以使用)。UEFI框架下提供了函數來存取Protocol,大部分的設備初始化和其它功能代碼也都被包裝成了一個個的Protocol。Protocol的作用跟普通的結構體沒有區別,如果存放的是數據就作為存儲用,如果存放的是函數指針就用作特定代碼執行。
Protocol的數據結構
Protocol不是很復雜的東西,直觀來說,它就是一個結構體, Protocol的作用跟普通的結構體沒有區別,如果存放的是數據就作為存儲用,如果存放的是函數指針就用作特定代碼執行, 同時 UEFI框架下提供了函數來存取Protocol。UEFI下將大部分的設備初始化和其它功能代碼都包裝成了一個個的Protocol,比如說下面是一個用于存儲設備訪問的Protocol:
// @f ile MdePkg/Include/Protocol/Blocklo .h /// 通過這個Protocol可以控制塊設備 struct _EFI_BLOCK_IO_PROTOCOL {//////Protocol版本號,Protocol必須保證向后兼容///如果沒有向后兼容,必須給未來的版本定義不同的GUID,也就是必須定義一個不同的Protocol///UINT64 Revision;////// Pointer to the EFI_BLOCK_IO_MEDIA data for this device.///EFI_BLOCK_IO_MEDIA *Media; //指針指向這個設備EFI_BLOCK_RESET Reset; //重置復位信號EFI_BLOCK_READ ReadBlocks; //讀Protocol服務EFI_BLOCK_WRITE WriteBlocks; //寫Protocol服務EFI_BLOCK_FLUSH FlushBlocks; //清除緩存服務 }; extern EFI_GUID gEfiBlockloProtocolGuid; //導出該Protocol每個Protocol必須有一個唯一的GUID,例如在 Blocklo.h 中定義了 Blocklo 的 GUID,如下所示:
#define EFI_BLOCK_IO_PROTOCOL_GUID\{\0x964e5b21, 0x6459, 0xlld2, {0x8e, 0x39,0x0, OxaO, 0xc9, 0x69, 0x72, 0x3b }\} typedef struct _EFI_BLOCK_IO_PROTOCOL EFI_BLOCK_IO_PROTOCOL;結構體EFI_BLOCK_IO_PROTOCOL有兩個成員變量和4個成員函數(當然,從C語言的角度來看,“成員函數”這樣的叫法不準確,它實際上也是一個成員變量,只是這個變量是函數指針而已)。gEfiBlockIoProtocolGuid ({ 0x964e5b21, 0x6459,0x11d2,{ 0x8e, 0x39, 0x0, 0xa0, 0xc9, 0x69, 0x72, 0x3b} })是一種標示符,標示了 EFI_BLOCK_IO_PROTOCOL。
下面是 EFI_BLOCK_IO_PROTOCOL的ReadBlocks服務的函數原型:
/**從地址Lba開始的塊讀取Buffersize字節到緩沖區@retval EFI_SUCCESS 數據從設備正確讀出@retval EFI_DEVICE_ERROR 設備出現錯誤@retval EFI_NO_MEDIA 設備中沒有介質@retval EFI_MEDIA_CHANGED Mediald與當前設備不符@retval EFI_BAD_BUFFER_SIZE 緩沖區大小不是塊的整數倍 @retval EFI_INVALID_PARAMETER 要讀取的塊中包含無效塊;或緩沖區未對齊 **/ typedef EFI_STATUS(EFIAPI *EFI_BLOCK_READ)(IN EFI_BLOCK_IO_PROTOCOL *This, //This 指針,指向調用上下文IN UINT32 Mediald, // media IdIN EFI_LBA Lba, //要讀取的啟始塊邏輯地址IN UINTN BufferSize, //要讀取的字節數,必須是塊大小的整數倍OUT VOID *Buffer //目的緩沖區,調用者負責該緩沖區的創建與刪除}它的第一個參數,指向EFI_BLOCK_IO_PROTOCOL對象自己的This指針,這是成員函數區別于一般函數的重要特征。通常,計算機中有許多不同的塊設備,每個塊設備都有一個EFI_BLOCK_IO_PROTOCOL的實例,This指針就是指向這個實例,用于告訴成員函數我們正在操作哪個設備。This指針是Protocol成員函數的一個重要特征,與C++成員函數this指針的區別是,C++的this指針由編譯器自動加人,而Protocol成員函數的This指針需手工添加。
Protocol的實現
這里需要關注的圖中紅框部分的內容。
這里實際上是兩種鏈表,一種是Handle的鏈表,一種是Protocol的鏈表。Handle其實就是一個不會重復的整型數字,而Protocol在之前已經說過就是一個結構體。各個Handle連接在一起構成一個鏈表,每個Handle上可以附著若干個不會重復的Protocol。
上述的兩種鏈表交織成了一張網,這張網被稱為“Handle Database”。
這個Handle Database會在DXE最開始的地方初始化起來,之后通過接口可以擴展,搜尋等等操作。
下面認識一下EFI_HANDLE。typedef VOID *EFI_HANDLE;
EFI_HANDLE是指向某種對象的指針,UEFI用它來表示某個對象。UEFI掃描總線后,會為每個設備建立一個Controller對象,用于控制設備,所有該設備的驅動以Protocol的形式安裝到這個Controller中,這個Controller就是一個EFI_HANDLE對象。當我們將一個.efi文件加載到內存中時,UEFI也會為該文件建立一個Image對象(此Image非圖像的意思),這個Image對象也是一個EFI_HANDLE對象。在UEFI內部,EFI_HANDLE被理解為IHANDLE, IHANDLE的數據結構如代碼所示。
每 個 IHANDLE中 都 有 一 個Protocol鏈 表 , 存 放 屬 于 自 己 的 Protocol。所 有 的IHANDLE通過AllHandles鏈接起來。上圖展示了 IHANDLE內的Protocol是如何被組織起來的。IHANDLE的Protocols是一個雙向鏈表,鏈表中每一個元素是PROTOCOL_INTERFACE ,通過 PROTOCOL_INTERFACE 的 Protocol 指針可以得到這個 Protocol 的GUID,通過Interface指針可以得到這個Protocol的實例。
Protocol的使用
在UEFI Boot Service中提供了如下的函數用來操作Protocol:
| InstallProtocolInterface | Boot | 在設備句柄上安裝protocol接口 |
| UninstallProtocolInterface | Boot | 從設備句柄中移除protocol接口 |
| ReinstallProtocolInterface | Boot | 在設備句柄上重新安裝protocol接口。 |
| RegisterProtocolNotify | Boot | 注冊一個事件,該事件在為指定protocol安裝接口時發出信號。 |
| LocateHandle | Boot | 返回支持指定protocol的句柄數組。 |
| HandleProtocol | Boot | 查詢句柄以確定它是否支持指定的protocol。 |
| LocateDevicePath | Boot | 在支持指定protocol的設備路徑上定位所有設備,并返回最接近該路徑的設備的句柄。 |
| OpenProtocol | Boot | 向使用protocol接口的代理列表中添加元素 |
| CloseProtocol | Boot | 從使用protocol接口的代理列表中刪除元素。 |
| OpenProtocolInformation | Boot | 檢索當前正在使用protocol接口的代理的列表。 |
| ConnectController | Boot | 使用一組優先規則來找到管理控制器的最佳驅動程序集。 |
| DisconnectController | Boot | 通知一組驅動程序停止管理控制器。 |
| ProtocolsPerHandle | Boot | 檢索安裝在句柄上的protocol列表。返回緩沖區被自動分配 |
| LocateHandleBuffer | Boot | 從符合搜索條件的句柄數據庫中檢索句柄列表。返回緩沖區被自動分配。 |
| LocateProtocol | Boot | 找到句柄數據庫中支持請求protocol的第一個句柄。 |
| InstallMultipleProtocolInterfaces | Boot | 將一個或多個protocol接口安裝到句柄上。 |
| UninstallMultipleProtocolInterfaces | Boot | 從句柄中卸載一個或多個protocol接口 |
具體可以查看UEFI Spec 6.3節
它們可以分為幾種不同的類型:
安裝和卸載接口,就是這里的IntallXXX,ReinstallXXX,UninstallXXX,IntallMultipleXXX,UninstallMultipleXXX等。
獲取和關閉接口,比如HandleProtocol,LocateHandle等等。
其它輔助接口,比如OpenProtocolInformation,RegisterProtocolNotify等,其中RegisterProtocolNotify注冊了一個回調函數,當指定的Protocol被安裝時,這個回調函數就會被執行
其他類別的Protocol
參考博客:https://blog.csdn.net/jiangwei0512/article/details/86996846
UEFI中的Protocol有一些比較特殊的類型,本節將介紹這些Protocol。
Architectural Protocol
UEFI規定了一些Protocol,這些Protocol在UEFI BIOS運行的過程中會安裝,且一定需要被安裝,如果沒有被安裝的話,系統就會報錯。
這些Protocol如下所示:
// // DXE Core Global Variables for all of the Architectural Protocols. // If a protocol is installed mArchProtocols[].Present will be TRUE. // // CoreNotifyOnArchProtocolInstallation () fills in mArchProtocols[].Event // and mArchProtocols[].Registration as it creates events for every array // entry. // EFI_CORE_PROTOCOL_NOTIFY_ENTRY mArchProtocols[] = {{ &gEfiSecurityArchProtocolGuid, (VOID **)&gSecurity, NULL, NULL, FALSE },{ &gEfiCpuArchProtocolGuid, (VOID **)&gCpu, NULL, NULL, FALSE },{ &gEfiMetronomeArchProtocolGuid, (VOID **)&gMetronome, NULL, NULL, FALSE },{ &gEfiTimerArchProtocolGuid, (VOID **)&gTimer, NULL, NULL, FALSE },{ &gEfiBdsArchProtocolGuid, (VOID **)&gBds, NULL, NULL, FALSE },{ &gEfiWatchdogTimerArchProtocolGuid, (VOID **)&gWatchdogTimer, NULL, NULL, FALSE },{ &gEfiRuntimeArchProtocolGuid, (VOID **)&gRuntime, NULL, NULL, FALSE },{ &gEfiVariableArchProtocolGuid, (VOID **)NULL, NULL, NULL, FALSE },{ &gEfiVariableWriteArchProtocolGuid, (VOID **)NULL, NULL, NULL, FALSE },{ &gEfiCapsuleArchProtocolGuid, (VOID **)NULL, NULL, NULL, FALSE },{ &gEfiMonotonicCounterArchProtocolGuid, (VOID **)NULL, NULL, NULL, FALSE },{ &gEfiResetArchProtocolGuid, (VOID **)NULL, NULL, NULL, FALSE },{ &gEfiRealTimeClockArchProtocolGuid, (VOID **)NULL, NULL, NULL, FALSE },{ NULL, (VOID **)NULL, NULL, NULL, FALSE } };這些Protocol都是UEFI或者系統必須的最基礎的Protocol,比如說這里的gEfiBdsArchProtocolGuid對應的Protocol,它是BDS階段的如果,在DXEMain.c中有如下的代碼:
//// Transfer control to the BDS Architectural Protocol//gBds->Entry (gBds);使DXE階段過渡到BDS階段。
Device Path Protocol
Device Path Protocol是一種純數據的結構體,它表示的是一個設備的可編程路徑,可以簡稱就是Device Path(后面就直接省略掉Protocol)。這種說法比較抽象,而且這里說的“設備”也并不一定需要是真實的設備,它可以是虛擬設備,甚至可以是一個文件。
Device Path的具體說明有在其它的文章中介紹,這里不做具體的說明。
簡單介紹一下它的結構體:
它的結構非常的簡單,是一個可變長的結構體。成員包括了一個基本的頭部(分為類型,子類型和長度三部分),以及之后的具體類型所需要包含的成員。Device Path有一個非常重要的作用就是標記對應Handle的屬性。
舉一個簡單的例子,現在有兩個硬盤,那么它們都有一個_EFI_BLOCK_IO_PROTOCOL(見開頭),然而我們想訪問其中一個特定的硬盤,如何找到這個硬件,就可以依賴于Device Path。
以硬盤的Device Path舉例,它的類型是HARDWARE_DEVICE_PATH,子類型是HW_CONTROLLER_DP,因此它的Device Path中包含如下的部分:
/// /// Controller Device Path. /// typedef struct {EFI_DEVICE_PATH_PROTOCOL Header;////// Controller number.///UINT32 ControllerNumber; } CONTROLLER_DEVICE_PATH;而兩個不同的硬盤,其中的ControllerNumber可能是不同的(根據不同的硬件配置),因此就可以確定到底使用哪個Device Path,最終獲取到正確的_EFI_BLOCK_IO_PROTOCOL,大致流程如下:
以上是使用Device Path Protocol的一個示例,當然Device Path Protocol的用法還有很多,可以參考UEFI Spec第九章。
EFI Driver Binding Protocol
總結
以上是生活随笔為你收集整理的BIOS知识枝桠—— Protocol的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: what is Personnel
- 下一篇: 选择分支结构