第四章 UEFI 中的 Protocol

作者: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 時社露,一般需要以下三個步驟:

  1. 通過 gBS->OpenProtocol(或者 HandleProtocol挟阻、LocateProtocol)找出 Protocol 對象。
  2. 使用這個 Protocol 提供的服務
  3. 通過 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巴帮,然后才能進行關閉溯泣。

?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市榕茧,隨后出現(xiàn)的幾起案子垃沦,更是在濱河造成了極大的恐慌,老刑警劉巖雪猪,帶你破解...
    沈念sama閱讀 217,907評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件栏尚,死亡現(xiàn)場離奇詭異,居然都是意外死亡只恨,警方通過查閱死者的電腦和手機译仗,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,987評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來官觅,“玉大人纵菌,你說我怎么就攤上這事⌒莸樱” “怎么了咱圆?”我有些...
    開封第一講書人閱讀 164,298評論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長功氨。 經(jīng)常有香客問我序苏,道長,這世上最難降的妖魔是什么捷凄? 我笑而不...
    開封第一講書人閱讀 58,586評論 1 293
  • 正文 為了忘掉前任忱详,我火速辦了婚禮,結果婚禮上跺涤,老公的妹妹穿的比我還像新娘匈睁。我一直安慰自己,他們只是感情好桶错,可當我...
    茶點故事閱讀 67,633評論 6 392
  • 文/花漫 我一把揭開白布航唆。 她就那樣靜靜地躺著,像睡著了一般院刁。 火紅的嫁衣襯著肌膚如雪糯钙。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,488評論 1 302
  • 那天,我揣著相機與錄音超营,去河邊找鬼鸳玩。 笑死,一個胖子當著我的面吹牛演闭,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播颓帝,決...
    沈念sama閱讀 40,275評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼米碰,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了购城?” 一聲冷哼從身側響起吕座,我...
    開封第一講書人閱讀 39,176評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎瘪板,沒想到半個月后吴趴,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,619評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡侮攀,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,819評論 3 336
  • 正文 我和宋清朗相戀三年锣枝,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片兰英。...
    茶點故事閱讀 39,932評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡撇叁,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出畦贸,到底是詐尸還是另有隱情陨闹,我是刑警寧澤,帶...
    沈念sama閱讀 35,655評論 5 346
  • 正文 年R本政府宣布薄坏,位于F島的核電站趋厉,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏胶坠。R本人自食惡果不足惜君账,卻給世界環(huán)境...
    茶點故事閱讀 41,265評論 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望涵但。 院中可真熱鬧杈绸,春花似錦、人聲如沸矮瘟。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,871評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽澈侠。三九已至劫侧,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背烧栋。 一陣腳步聲響...
    開封第一講書人閱讀 32,994評論 1 269
  • 我被黑心中介騙來泰國打工写妥, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人审姓。 一個月前我還...
    沈念sama閱讀 48,095評論 3 370
  • 正文 我出身青樓珍特,卻偏偏與公主長得像,于是被迫代替她去往敵國和親魔吐。 傳聞我的和親對象是個殘疾皇子扎筒,可洞房花燭夜當晚...
    茶點故事閱讀 44,884評論 2 354

推薦閱讀更多精彩內(nèi)容