第四章 UEFI 中的 Protocol

作者: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è)步驟:

  1. 通過(guò) gBS->OpenProtocol(或者 HandleProtocol、LocateProtocol)找出 Protocol 對(duì)象。
  2. 使用這個(gè) Protocol 提供的服務(wù)
  3. 通過(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)閉。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容