作者:Maxwell Li
日期:2017/12/06
未經(jīng)作者允許,禁止轉(zhuǎn)載本文任何內(nèi)容。如需轉(zhuǎn)載請(qǐng)留言。
[TOC]
Protocol 是服務(wù)器與客戶端之間的一種約定,雙方根據(jù)這種約定互通信息。UEFI 中的 Protocol 引入了面向?qū)ο蟮乃枷耄河?struct 來(lái)模擬 class;用函數(shù)指針(Protocol 的成員變量)模擬成員函數(shù),此函數(shù)的第一參數(shù)必須指向 Protocol 的指針,用來(lái)模擬 this 指針。
Protocol 是一個(gè)大的結(jié)構(gòu)類型,包含很多數(shù)據(jù)信息。最主要的是 GUID,其次是一個(gè)指向 GUID 的全局指針變量,例如 _EFI_BLOCK_IO_PROTOCOL 就有一個(gè) gEfiBlockIoProtocolGuid 全局指針。如果要在應(yīng)用程序或者驅(qū)動(dòng)中使用這個(gè) GUID(例如 gEfiBlockIoProtocolGuid),那么必須要在 .inf 文件的 [Protocols] 塊中列出對(duì)應(yīng)的 GUID,以便預(yù)處理時(shí)將其包含在生成的 AutoGen.c 中供全局使用。
struct _EFI_BLOCK_IO_PROTOCOL {
UINT64 Revision; // Protocol 版本號(hào)必須向后兼容,否則必須給未來(lái)版本定義不同的 GUID。每個(gè) Protocol 必須有一個(gè)不同的 GUID
EFI_BLOCK_IO_MEDIA *Media; // 指針指向這個(gè)設(shè)備
EFI_BLOCK_RESET Reset; // 重置復(fù)位信號(hào)
EFI_BLOCK_READ ReadBlocks; // 讀 Protocol 服務(wù)
EFI_BLOCK_WRITE WriteBlocks; // 寫 Protocol 服務(wù)
EFI_BLOCK_FLUSH FlushBlocks; // 清楚緩存服務(wù)
};
extern EFI_GUID gEfiBlockIoProtocolGuid;
結(jié)構(gòu)體 _EFI_BLOCK_IO_PROTOCOL 有兩個(gè)成員變量和四個(gè)成員函數(shù)(函數(shù)指針)。以 ReadBlocks 為例來(lái)看成員函數(shù)的聲明,代碼如下所示:
/** 從地址 Lba 開(kāi)始的塊讀取 BufferSize 字節(jié)到緩沖區(qū)
@retval EFI_SUCCESS 數(shù)據(jù)從設(shè)備正確讀出
@retval EFI_DEVICE_ERROR 設(shè)備出現(xiàn)錯(cuò)誤
@retval EFI_NO_MEDIA 設(shè)備中沒(méi)有介質(zhì)
@retval EFI_MEDIA_CHANGED MediaId 與當(dāng)前設(shè)備不服
@retval EFI_BAD_BUFFER_SIZE 緩沖區(qū)大小不是塊的整數(shù)倍
@retval EFI_INVALID_PARAMETER 讀取的塊中包含無(wú)效塊;或緩沖區(qū)未對(duì)齊
**/
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, // 讀取的起始?jí)K邏輯地址
IN UINTN BufferSize, // 讀取的字節(jié)數(shù),必須是塊大小的整數(shù)倍
OUT VOID *Buffer, // 目的緩沖區(qū),調(diào)用者負(fù)責(zé)該緩沖區(qū)的創(chuàng)建與刪除
);
*This 指向 EFI_BLOCK_IO_PROTOCOL 對(duì)象自己的指針。
4.1 Protocol 在 UEFI 內(nèi)核中的表示
EFI_HANDLE 是指向某種對(duì)象的指針,UEFI 用它來(lái)表示某個(gè)對(duì)象。UEFI 掃描總線后,會(huì)為每個(gè)設(shè)備建立一個(gè) Controller 用于控制設(shè)備。所有設(shè)備的驅(qū)動(dòng)以 Protocol 的形式安裝到 Controller,這個(gè) Controller 就是 EFI_HANDLE 對(duì)象。
當(dāng) .efi 文件加載到內(nèi)存中時(shí),UEFI 會(huì)為改文件建立一個(gè) Image 對(duì)象,這個(gè) Image 對(duì)象也是一個(gè) EFI_HANDLE 對(duì)象。在 UEFI 內(nèi)部,EFI_HANDLE 被理解為 IHANDLE,數(shù)據(jù)結(jié)構(gòu)如下:
/// IHANDLE - 包含了 Protocols 鏈表
typedef struct{
UINTN Signature; // 表明 Handle 的類別
LIST_ENTRY AllHandles; // 所有 IHANDLE 組成的鏈表
LIST_ENTRY Protocols; // 此 Handle 的 Protocol 鏈表
UINTN LocateRequest;
UINT64 Key;
}IHANDLE
每個(gè) IHANDLE 中都有一個(gè) Protocol 鏈表,存放屬于自己的 Protocol。所有的 IAHDNLE 通過(guò) AllHandles 鏈接起來(lái)。
4.2 使用 Protocol 服務(wù)
Boot Service 中提供了幾種 Protocol 服務(wù),如下所示:
Boot Service 中的 Protocol 服務(wù)
| Protocol | 功能 |
|---|---|
| OpenProtocol | 打開(kāi) Protocol |
| HandleProtocol | 打開(kāi) Protocol,OpenProtocol 的簡(jiǎn)化版 |
| LocateProtocol | 找出系統(tǒng)中指定 Protocol 的第一個(gè)實(shí)例 |
| LocateHandleBuffer | 找出支持指定 Protocol 的所有 Handle。系統(tǒng)負(fù)責(zé)分配內(nèi)存,調(diào)用者負(fù)責(zé)釋放內(nèi)存 |
| LocateHandle | 找出支持指定 Protocol 的所有 Handle。調(diào)用者負(fù)責(zé)分配和釋放內(nèi)存 |
| OpenProtocolInformation | 返回指定 Protocol 的打開(kāi)信息 |
| ProtocolsPerHandle | 找出指定 Handle上 安裝的所有 Protocol |
| CloseProtocol | 關(guān)閉Protocol |
使用 Protocol 時(shí),一般需要以下三個(gè)步驟:
- 通過(guò) gBS->OpenProtocol(或者 HandleProtocol、LocateProtocol)找出 Protocol 對(duì)象。
- 使用這個(gè) Protocol 提供的服務(wù)
- 通過(guò) gBS->CloseProtocol 關(guān)閉 Protocol。
4.2.1 OpenProtocol 服務(wù)
OpenProtocol 用于查詢指定的 Handle 中是否支持指定的 Protocol,如果支持,則打開(kāi)該 Protocol,否則返回錯(cuò)誤代碼。
OpenProtocol 服務(wù)函數(shù)原型:
/** gBS->OpenProtocol
打開(kāi) Procotol
@retval EFI_SUCCESS 成功打開(kāi) Protocol,打開(kāi)信息添加到 Protocol 的打開(kāi)列表中
@retval EFI_UNSUPPORTED Handle 不支持 Protocol
@retval EFI_INVALID_PARAMETER 無(wú)效參數(shù)
@retval EFI_ACCESS_DENIED Attribute 不被當(dāng)前環(huán)境支持
@retval EFI_ALREADY_STARTED Protocol 已經(jīng)被同一 AgenHandle 打開(kāi)
**/
typedef EFI_STATUS(EFIAPI *EFI_OPEN_PROTOCOL) (
IN EFI_HANDLE Handle, //指定的 Handle,將查詢并打開(kāi)此 Handle 中安裝的 Protocol
IN EFI_GUID *Protocol, //要打開(kāi)的 Protocol(指向該 Protocol GUID 的指針)
OUT VOID **Interface, OPTIONAL // 返回打開(kāi)的 Protocol 對(duì)象
IN EFI_HANDLE AgentHandle, // 打開(kāi)此 Protocol 的 Image
IN EFI_HANDLE ControllerHandle, // 使用此 Protocol 的控制器
IN UINT32 Attributes // 打開(kāi) Protocol 的方式
);
Handle 是 Protocol 的提供者,如果 Handle 的 Protocols 鏈表中有該 Protocol, Protocol 對(duì)象的指針寫到 *Interface,并返回 EFI_SUCCESS,否則返回 EFI_UNSUPPORTED。
- 驅(qū)動(dòng)中調(diào)用 OpenProtocol:ControllerHandle 是擁有該驅(qū)動(dòng)的控制器,請(qǐng)求使用該 Protocol;AgentHandle 是擁有該 EFI_DRIVER_BINDING_PROTOCOL 對(duì)象的 Handle。EFI_DRIVER_BINDING_PROTOCOL 是 UEFI 驅(qū)動(dòng)開(kāi)發(fā)一定會(huì)用到的一個(gè) Protocol,負(fù)責(zé)驅(qū)動(dòng)的安裝與卸載。
- 應(yīng)用程序調(diào)用 OpenProtocol:AgentHandle 是該應(yīng)用程序的 Handle,也就是 UefiMain 的第一個(gè)參數(shù),ControllerHandle 此時(shí)可以忽略。
Attributes 可以取以下六種值,即有六種打開(kāi) 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)打開(kāi),則被同一控制器再次打開(kāi)的時(shí)候?qū)?huì)失敗
#define EFI_OPEN_PROTOCOL_EXCLUSIVE 0x00000020
//若Protocol已經(jīng)打開(kāi),則再次打開(kāi)就會(huì)失敗
4.2.2 HandleProtocol 服務(wù)
OpenProtocol 功能比較強(qiáng)大,但是使用比較復(fù)雜。為了方便使用 Protocol,啟動(dòng)服務(wù)提供了 HandleProtocol 以簡(jiǎn)化打開(kāi) Protocol。
HandleProtocol 服務(wù)函數(shù)原型:
/**gBS->HandleProtocol
查詢指定的 Handle 中是否支持指定的 Protocol,如果支持,打開(kāi)該 Protocol
@retval EFI_SUCCESS 成功打開(kāi)指定的 Protocol
@retval EFI_UNSUPPORTED 指定的設(shè)備(Handle)不支持該 Protocol
@retval EFI_INVALID_PARAMETER 參數(shù) Handle、Protocol 或 Interface 是 NULL
typedef EFI_STATUS(EFIAPI *EFI_HANDLE_PROTOCOL) (
IN EFI_HANDLE Handle, // 查詢?cè)?Handle 是否支持 Protocol
IN EFI_GUID *Protocol, // 待查詢的 Protocol
OUT VOID **Interface // 返回待查詢的 Protocol
);
HandleProtocol 內(nèi)部其實(shí)仍調(diào)用了 OpenProtocol,實(shí)現(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 服務(wù)
LocateProtocol 服務(wù)可以從 UEFI 內(nèi)核中按順序搜索 HANDLE 鏈表,找出指定 Protocol 的第一個(gè)實(shí)例。
LocateProtocol 服務(wù)函數(shù)原型:
/** gBS->LocateProtocol
從 UEFI 內(nèi)核中找出匹配 Protocol 和 Registration 的第一個(gè)實(shí)例
@retval EFI_SUCCESS 成功找到匹配的 Protocol
@retval EFI_NOT_FOUND 系統(tǒng)中無(wú)法找到匹配的 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)中第一個(gè)匹配到的Protocol實(shí)例
);
4.2.4 LocateHandleBuffer 服務(wù)
LocateHandleBuffer 服務(wù)可以找出支持某個(gè) Protocol 的所有設(shè)備,
LocateHandleBuffer 服務(wù)函數(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)中沒(méi)有發(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ù)庫(kù)中找出指定 Protocol 的 Handle)。
4.2.5 LocateHandle 服務(wù)
LocateHandle 服務(wù)的功能是找出支持某個(gè) Protocol 的所有設(shè)備,與 LocateHandleBuffer 相同。兩者區(qū)別在于 LocateHandle 需要調(diào)用者負(fù)責(zé)管理 Buffer 數(shù)組占用的內(nèi)存。
LocateHandle 服務(wù)函數(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 服務(wù)
ProtocolsPerHandle 服務(wù)用于獲得指定設(shè)備所支持的所有 Protocol。這些 Protocol 的 GUID 通過(guò) ProtocolBuffer 返回給調(diào)用者,UEFI 負(fù)責(zé)分配內(nèi)存給 ProtocolBuffer,調(diào)動(dòng)者負(fù)責(zé)釋放該內(nèi)存。
ProtocolsPerHandle 服務(wù)函數(shù)原型:
/** gBS->ProtocolsPerHandle
返回指定設(shè)備所支持的所有 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, // 找出這個(gè) Handle 上所有的 Protocol
OUT EFI_GUID ***ProtocolBuffer, // 返回 Protocol GUID 數(shù)組
OUT UINTN *ProtocolBufferCount // 返回 Protocol 的數(shù)目
);
4.2.7 OpenProtocolInformation 服務(wù)
OpenProtocolInformation 服務(wù)用于獲得指定設(shè)備上指定 Protocol 的打開(kāi)信息。
OpenProtocolInformation 服務(wù)函數(shù)原型:
typedef EFI_STATUS(EFIAPI *EFI_OPEN_PROTOCOL_INFORMATION) (
IN EFI_HANDLE Handle, // 設(shè)備句柄
IN EFI_GUID *Protocol, // 待查詢的 Protocol
OUT EFI_OPEN_PROTOCOL_INFORMATION_ENTRY **EntryBuffer, // 打開(kāi)信息通過(guò)此數(shù)組返回
OUT UINTN *EntryCount // EntryBuffer 數(shù)組元素個(gè)數(shù)
);
返回的打開(kāi)信息包括使用者句柄(AgentHandle)、控制器句柄(ControllerHandle)、打開(kāi)屬性和打開(kāi)個(gè)數(shù)。
4.2.8 CloseProtocol 服務(wù)
CloseProtocol 服務(wù)用于關(guān)閉打開(kāi)的 Protocol。
CloseProtocol 服務(wù)函數(shù)原型:
typedef EFI_STATUS(EFIAPI *EFI_CLOSE_PROTOCOL) (
IN EFI_HANDLE Handle, // 設(shè)備句柄
IN EFI_GUID *Protocol, // 要關(guān)閉的 Protocol 對(duì)象
IN EFI_HANDLE AgentHandle, // 關(guān)閉該 Protocol 的 Image
IN EFI_HANDLE ControllerHandle // 使用該 protocol 的控制器
);
通過(guò) HandleProtocol 和 LocateHandle 打開(kāi)的 Protocol 因?yàn)闆](méi)有指定 AgentHandle,所以無(wú)法關(guān)閉。若一定要關(guān)閉,則要調(diào)用 OpenProtocolInformation 獲得 AgentHandle 和 ControllerHandle,然后才能進(jìn)行關(guān)閉。