作者:Maxwell Li
日期:2017/12/06
未經(jīng)作者允許疗杉,禁止轉載本文任何內(nèi)容椒振。如需轉載請留言般又。
[TOC]
Protocol 是服務器與客戶端之間的一種約定适秩,雙方根據(jù)這種約定互通信息。UEFI 中的 Protocol 引入了面向?qū)ο蟮乃枷耄河?struct 來模擬 class纷宇;用函數(shù)指針(Protocol 的成員變量)模擬成員函數(shù)夸盟,此函數(shù)的第一參數(shù)必須指向 Protocol 的指針,用來模擬 this 指針像捶。
Protocol 是一個大的結構類型上陕,包含很多數(shù)據(jù)信息。最主要的是 GUID拓春,其次是一個指向 GUID 的全局指針變量释簿,例如 _EFI_BLOCK_IO_PROTOCOL 就有一個 gEfiBlockIoProtocolGuid 全局指針。如果要在應用程序或者驅(qū)動中使用這個 GUID(例如 gEfiBlockIoProtocolGuid)硼莽,那么必須要在 .inf 文件的 [Protocols] 塊中列出對應的 GUID庶溶,以便預處理時將其包含在生成的 AutoGen.c 中供全局使用。
struct _EFI_BLOCK_IO_PROTOCOL {
UINT64 Revision; // Protocol 版本號必須向后兼容懂鸵,否則必須給未來版本定義不同的 GUID偏螺。每個 Protocol 必須有一個不同的 GUID
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 gEfiBlockIoProtocolGuid;
結構體 _EFI_BLOCK_IO_PROTOCOL 有兩個成員變量和四個成員函數(shù)(函數(shù)指針)。以 ReadBlocks 為例來看成員函數(shù)的聲明匆光,代碼如下所示:
/** 從地址 Lba 開始的塊讀取 BufferSize 字節(jié)到緩沖區(qū)
@retval EFI_SUCCESS 數(shù)據(jù)從設備正確讀出
@retval EFI_DEVICE_ERROR 設備出現(xiàn)錯誤
@retval EFI_NO_MEDIA 設備中沒有介質(zhì)
@retval EFI_MEDIA_CHANGED MediaId 與當前設備不服
@retval EFI_BAD_BUFFER_SIZE 緩沖區(qū)大小不是塊的整數(shù)倍
@retval EFI_INVALID_PARAMETER 讀取的塊中包含無效塊套像;或緩沖區(qū)未對齊
**/
typedef EFI_STATUS(EFIAPI *EFI_BLOCK_READ)(
IN EFI_BLOCK_IO_PROTOCOL *This, // This 指針,指向調(diào)用上下文
IN UINT32 MediaId, // media Id
IN EFI_LBA Lba, // 讀取的起始塊邏輯地址
IN UINTN BufferSize, // 讀取的字節(jié)數(shù)终息,必須是塊大小的整數(shù)倍
OUT VOID *Buffer, // 目的緩沖區(qū)夺巩,調(diào)用者負責該緩沖區(qū)的創(chuàng)建與刪除
);
*This 指向 EFI_BLOCK_IO_PROTOCOL 對象自己的指針贞让。
4.1 Protocol 在 UEFI 內(nèi)核中的表示
EFI_HANDLE 是指向某種對象的指針,UEFI 用它來表示某個對象柳譬。UEFI 掃描總線后喳张,會為每個設備建立一個 Controller 用于控制設備。所有設備的驅(qū)動以 Protocol 的形式安裝到 Controller征绎,這個 Controller 就是 EFI_HANDLE 對象蹲姐。
當 .efi 文件加載到內(nèi)存中時,UEFI 會為改文件建立一個 Image 對象人柿,這個 Image 對象也是一個 EFI_HANDLE 對象柴墩。在 UEFI 內(nèi)部,EFI_HANDLE 被理解為 IHANDLE凫岖,數(shù)據(jù)結構如下:
/// IHANDLE - 包含了 Protocols 鏈表
typedef struct{
UINTN Signature; // 表明 Handle 的類別
LIST_ENTRY AllHandles; // 所有 IHANDLE 組成的鏈表
LIST_ENTRY Protocols; // 此 Handle 的 Protocol 鏈表
UINTN LocateRequest;
UINT64 Key;
}IHANDLE
每個 IHANDLE 中都有一個 Protocol 鏈表江咳,存放屬于自己的 Protocol。所有的 IAHDNLE 通過 AllHandles 鏈接起來哥放。
4.2 使用 Protocol 服務
Boot Service 中提供了幾種 Protocol 服務歼指,如下所示:
Boot Service 中的 Protocol 服務
Protocol | 功能 |
---|---|
OpenProtocol | 打開 Protocol |
HandleProtocol | 打開 Protocol,OpenProtocol 的簡化版 |
LocateProtocol | 找出系統(tǒng)中指定 Protocol 的第一個實例 |
LocateHandleBuffer | 找出支持指定 Protocol 的所有 Handle甥雕。系統(tǒng)負責分配內(nèi)存踩身,調(diào)用者負責釋放內(nèi)存 |
LocateHandle | 找出支持指定 Protocol 的所有 Handle。調(diào)用者負責分配和釋放內(nèi)存 |
OpenProtocolInformation | 返回指定 Protocol 的打開信息 |
ProtocolsPerHandle | 找出指定 Handle上 安裝的所有 Protocol |
CloseProtocol | 關閉Protocol |
使用 Protocol 時社露,一般需要以下三個步驟:
- 通過 gBS->OpenProtocol(或者 HandleProtocol挟阻、LocateProtocol)找出 Protocol 對象。
- 使用這個 Protocol 提供的服務
- 通過 gBS->CloseProtocol 關閉 Protocol峭弟。
4.2.1 OpenProtocol 服務
OpenProtocol 用于查詢指定的 Handle 中是否支持指定的 Protocol附鸽,如果支持,則打開該 Protocol瞒瘸,否則返回錯誤代碼坷备。
OpenProtocol 服務函數(shù)原型:
/** gBS->OpenProtocol
打開 Procotol
@retval EFI_SUCCESS 成功打開 Protocol,打開信息添加到 Protocol 的打開列表中
@retval EFI_UNSUPPORTED Handle 不支持 Protocol
@retval EFI_INVALID_PARAMETER 無效參數(shù)
@retval EFI_ACCESS_DENIED Attribute 不被當前環(huán)境支持
@retval EFI_ALREADY_STARTED Protocol 已經(jīng)被同一 AgenHandle 打開
**/
typedef EFI_STATUS(EFIAPI *EFI_OPEN_PROTOCOL) (
IN EFI_HANDLE Handle, //指定的 Handle情臭,將查詢并打開此 Handle 中安裝的 Protocol
IN EFI_GUID *Protocol, //要打開的 Protocol(指向該 Protocol GUID 的指針)
OUT VOID **Interface, OPTIONAL // 返回打開的 Protocol 對象
IN EFI_HANDLE AgentHandle, // 打開此 Protocol 的 Image
IN EFI_HANDLE ControllerHandle, // 使用此 Protocol 的控制器
IN UINT32 Attributes // 打開 Protocol 的方式
);
Handle 是 Protocol 的提供者省撑,如果 Handle 的 Protocols 鏈表中有該 Protocol, Protocol 對象的指針寫到 *Interface俯在,并返回 EFI_SUCCESS丁侄,否則返回 EFI_UNSUPPORTED。
- 驅(qū)動中調(diào)用 OpenProtocol:ControllerHandle 是擁有該驅(qū)動的控制器朝巫,請求使用該 Protocol;AgentHandle 是擁有該 EFI_DRIVER_BINDING_PROTOCOL 對象的 Handle石景。EFI_DRIVER_BINDING_PROTOCOL 是 UEFI 驅(qū)動開發(fā)一定會用到的一個 Protocol劈猿,負責驅(qū)動的安裝與卸載拙吉。
- 應用程序調(diào)用 OpenProtocol:AgentHandle 是該應用程序的 Handle,也就是 UefiMain 的第一個參數(shù)揪荣,ControllerHandle 此時可以忽略筷黔。
Attributes 可以取以下六種值,即有六種打開 Protocol 的方式:
#define EFI_OPEN_PROTOCOL_BY_HANDLE_PROTOCOL 0x00000001
#define EFI_OPEN_PROTOCOL_GET_PROTOCOL 0x00000002
#define EFI_OPEN_PROTOCOL_TEST_PROTOCOL 0x00000004
#define EFI_OPEN_PROTOCOL_BY_CHILD_CONTROLLER 0x00000008
#define EFI_OPEN_PROTOCOL_BY_DRIVER 0x00000010
//若已經(jīng)打開仗颈,則被同一控制器再次打開的時候?qū)?#define EFI_OPEN_PROTOCOL_EXCLUSIVE 0x00000020
//若Protocol已經(jīng)打開佛舱,則再次打開就會失敗
4.2.2 HandleProtocol 服務
OpenProtocol 功能比較強大,但是使用比較復雜挨决。為了方便使用 Protocol请祖,啟動服務提供了 HandleProtocol 以簡化打開 Protocol。
HandleProtocol 服務函數(shù)原型:
/**gBS->HandleProtocol
查詢指定的 Handle 中是否支持指定的 Protocol脖祈,如果支持肆捕,打開該 Protocol
@retval EFI_SUCCESS 成功打開指定的 Protocol
@retval EFI_UNSUPPORTED 指定的設備(Handle)不支持該 Protocol
@retval EFI_INVALID_PARAMETER 參數(shù) Handle、Protocol 或 Interface 是 NULL
typedef EFI_STATUS(EFIAPI *EFI_HANDLE_PROTOCOL) (
IN EFI_HANDLE Handle, // 查詢該 Handle 是否支持 Protocol
IN EFI_GUID *Protocol, // 待查詢的 Protocol
OUT VOID **Interface // 返回待查詢的 Protocol
);
HandleProtocol 內(nèi)部其實仍調(diào)用了 OpenProtocol盖高,實現(xiàn)如下:
EFI_STATUS EFIAPI CoreHandleProtocol (
IN EFI_HANDLE UserHandle,
IN EFI_GUID *Protocol,
OUT VOID **Interface
)
{
return CoreOpenProtocol (
UserHandle,
Protocol,
Interface,
gDxeCoreImageHandle,
NULL,
EFI_OPEN_PROTOCOL_BY_HANDLE_PROTOCOL
);
}
AgentHandle 使用 gDxeCoreImageHandle慎陵,ControllerHandle 使用 NULL,Attributes 使用 EFI_OPEN_PROTOCOL_BY_HANDLE_PROTOCOL喻奥。
4.2.3 LocateProtocol 服務
LocateProtocol 服務可以從 UEFI 內(nèi)核中按順序搜索 HANDLE 鏈表席纽,找出指定 Protocol 的第一個實例。
LocateProtocol 服務函數(shù)原型:
/** gBS->LocateProtocol
從 UEFI 內(nèi)核中找出匹配 Protocol 和 Registration 的第一個實例
@retval EFI_SUCCESS 成功找到匹配的 Protocol
@retval EFI_NOT_FOUND 系統(tǒng)中無法找到匹配的 Protocol
@retval EFI_INVALID_PARAMETER Interface 為空
**/
typedef EFI_STATUS(EFIAPI *EFI_LOCATE_PROTOCOL)(
IN EFI_GUID *Protocol, // 待查詢的Protocol
IN VOID *Registration, OPTIONAL // 可選參數(shù)撞蚕,從 RegisterProtocolNotify() 中獲得的 Key
OUT VOID **Interface // 返回系統(tǒng)中第一個匹配到的Protocol實例
);
4.2.4 LocateHandleBuffer 服務
LocateHandleBuffer 服務可以找出支持某個 Protocol 的所有設備润梯,
LocateHandleBuffer 服務函數(shù)原型:
/** gBS->LocateHandleBuffer
獲得所有支持指定 Protocol 的 HANDLE。Buffer 數(shù)組由系統(tǒng)分配诈豌,由調(diào)用者釋放
@retval EFI_SUCCESS 成功返回仆救。Buffer 返回 Handle 數(shù)組,NoHandles 返回 Handle 數(shù)目
@retval EFI_NOT_FOUND 系統(tǒng)中沒有發(fā)現(xiàn)支持指定 Protocol 的 Handle
@retval EFI_OUT_OF_RESOURCES 資源耗盡
@retval EFI_INVALID_PARAMETER NoHandles 或 Buffer 參數(shù)非法
**/
typedef EFI_STATUS(EFIAPI *EFI_LOCATE_HANDLE_BUFFER)(
IN EFI_LOCATE_SEARCH_TYPE SearchType, // 查找方式
IN EFI_GUID *Protocol OPTIONAL, // 指定的 Protocol
IN VOID *SearchKey OPTIONAL, // PROTOCOL_NOTIFY 類型
IN OUT UINTN *NoHandles, // 返回找到的 Handle 數(shù)量
OUT EFI_HANDLE **Buffer // 分配 Handle 數(shù)組并返回
);
SearchType 有三種:AllHandles(查找系統(tǒng)中所有 Handle)矫渔、ByRegisterNotify(從 RegisterProtocolNotify 中找出匹配 SearchKey 的 Handle)彤蔽、ByProtocol(從系統(tǒng) Handle 數(shù)據(jù)庫中找出指定 Protocol 的 Handle)。
4.2.5 LocateHandle 服務
LocateHandle 服務的功能是找出支持某個 Protocol 的所有設備庙洼,與 LocateHandleBuffer 相同顿痪。兩者區(qū)別在于 LocateHandle 需要調(diào)用者負責管理 Buffer 數(shù)組占用的內(nèi)存。
LocateHandle 服務函數(shù)原型:
/** gBS->LocateHandle
獲得所有支持指定 Protocol 的 HANDLE油够。
@retval EFI_SUCCESS 成功返回
@retval EFI_BUFFER_TOO_SMALL 提供的 Buffer 太小
**/
typedef EFI_STATUS(EFIAPI *EFI_LOCATE_HANDLE)(
IN EFI_LOCATE_SEARCH_TYPE SearchType, // 查找方式
IN EFI_GUID *Protocol OPTIONAL, // 指定的 Protocol
IN VOID *SearchKey OPTIONAL, // PROTOCOL_NOTIFY 類型
IN OUT UINTN *BufferSize, // 返回找到的 Handle 數(shù)量
OUT EFI_HANDLE **Buffer // 分配 Handle 數(shù)組并返回
);
4.2.6 ProtocolsPerHandle 服務
ProtocolsPerHandle 服務用于獲得指定設備所支持的所有 Protocol蚁袭。這些 Protocol 的 GUID 通過 ProtocolBuffer 返回給調(diào)用者,UEFI 負責分配內(nèi)存給 ProtocolBuffer石咬,調(diào)動者負責釋放該內(nèi)存揩悄。
ProtocolsPerHandle 服務函數(shù)原型:
/** gBS->ProtocolsPerHandle
返回指定設備所支持的所有 Protocol
@retval EFI_SUCCESS 成功返回 Handle 上安裝的所有 Protocol
@retval EFI_OUT_OF_RESOURCES 資源耗盡
@retval EFI_INVALID_PARAMETER 參數(shù)非法或不是有效的 Handle
**/
typedef EFI_STATUS(EFIAPI *EFI_PROTOCOLS_PER_HANDLE)(
IN EFI_HANDLE Handle, // 找出這個 Handle 上所有的 Protocol
OUT EFI_GUID ***ProtocolBuffer, // 返回 Protocol GUID 數(shù)組
OUT UINTN *ProtocolBufferCount // 返回 Protocol 的數(shù)目
);
4.2.7 OpenProtocolInformation 服務
OpenProtocolInformation 服務用于獲得指定設備上指定 Protocol 的打開信息。
OpenProtocolInformation 服務函數(shù)原型:
typedef EFI_STATUS(EFIAPI *EFI_OPEN_PROTOCOL_INFORMATION) (
IN EFI_HANDLE Handle, // 設備句柄
IN EFI_GUID *Protocol, // 待查詢的 Protocol
OUT EFI_OPEN_PROTOCOL_INFORMATION_ENTRY **EntryBuffer, // 打開信息通過此數(shù)組返回
OUT UINTN *EntryCount // EntryBuffer 數(shù)組元素個數(shù)
);
返回的打開信息包括使用者句柄(AgentHandle)鬼悠、控制器句柄(ControllerHandle)删性、打開屬性和打開個數(shù)亏娜。
4.2.8 CloseProtocol 服務
CloseProtocol 服務用于關閉打開的 Protocol。
CloseProtocol 服務函數(shù)原型:
typedef EFI_STATUS(EFIAPI *EFI_CLOSE_PROTOCOL) (
IN EFI_HANDLE Handle, // 設備句柄
IN EFI_GUID *Protocol, // 要關閉的 Protocol 對象
IN EFI_HANDLE AgentHandle, // 關閉該 Protocol 的 Image
IN EFI_HANDLE ControllerHandle // 使用該 protocol 的控制器
);
通過 HandleProtocol 和 LocateHandle 打開的 Protocol 因為沒有指定 AgentHandle蹬挺,所以無法關閉维贺。若一定要關閉,則要調(diào)用 OpenProtocolInformation 獲得 AgentHandle 和 ControllerHandle巴帮,然后才能進行關閉溯泣。