第四章 UEFI 中的 Protocol

作者:Maxwell Li
日期:2017/12/06
未经作者允许,禁止转载本文任何内容。如需转载请留言。


[TOC]

Protocol 是服务器与客户端之间的一种约定,双方根据这种约定互通信息。UEFI 中的 Protocol 引入了面向对象的思想:用 struct 来模拟 class;用函数指针(Protocol 的成员变量)模拟成员函数,此函数的第一参数必须指向 Protocol 的指针,用来模拟 this 指针。

Protocol 是一个大的结构类型,包含很多数据信息。最主要的是 GUID,其次是一个指向 GUID 的全局指针变量,例如 _EFI_BLOCK_IO_PROTOCOL 就有一个 gEfiBlockIoProtocolGuid 全局指针。如果要在应用程序或者驱动中使用这个 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 有两个成员变量和四个成员函数(函数指针)。以 ReadBlocks 为例来看成员函数的声明,代码如下所示:

/** 从地址 Lba 开始的块读取 BufferSize 字节到缓冲区
  @retval EFI_SUCCESS           数据从设备正确读出
  @retval EFI_DEVICE_ERROR      设备出现错误
  @retval EFI_NO_MEDIA          设备中没有介质
  @retval EFI_MEDIA_CHANGED     MediaId 与当前设备不服
  @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                  MediaId,     // media Id
  IN EFI_LBA                 Lba,         // 读取的起始块逻辑地址
  IN UINTN                   BufferSize,  // 读取的字节数,必须是块大小的整数倍
  OUT VOID                   *Buffer,     // 目的缓冲区,调用者负责该缓冲区的创建与删除
  );

*This 指向 EFI_BLOCK_IO_PROTOCOL 对象自己的指针。

4.1 Protocol 在 UEFI 内核中的表示

EFI_HANDLE 是指向某种对象的指针,UEFI 用它来表示某个对象。UEFI 扫描总线后,会为每个设备建立一个 Controller 用于控制设备。所有设备的驱动以 Protocol 的形式安装到 Controller,这个 Controller 就是 EFI_HANDLE 对象。

当 .efi 文件加载到内存中时,UEFI 会为改文件建立一个 Image 对象,这个 Image 对象也是一个 EFI_HANDLE 对象。在 UEFI 内部,EFI_HANDLE 被理解为 IHANDLE,数据结构如下:

/// 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 找出系统中指定 Protocol 的第一个实例
LocateHandleBuffer 找出支持指定 Protocol 的所有 Handle。系统负责分配内存,调用者负责释放内存
LocateHandle 找出支持指定 Protocol 的所有 Handle。调用者负责分配和释放内存
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 服务函数原型:

/** gBS->OpenProtocol
打开 Procotol
  @retval EFI_SUCCESS              成功打开 Protocol,打开信息添加到 Protocol 的打开列表中
  @retval EFI_UNSUPPORTED         Handle 不支持 Protocol
  @retval EFI_INVALID_PARAMETER   无效参数
  @retval EFI_ACCESS_DENIED       Attribute 不被当前环境支持
  @retval EFI_ALREADY_STARTED     Protocol 已经被同一 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。

  • 驱动中调用 OpenProtocol:ControllerHandle 是拥有该驱动的控制器,请求使用该 Protocol;AgentHandle 是拥有该 EFI_DRIVER_BINDING_PROTOCOL 对象的 Handle。EFI_DRIVER_BINDING_PROTOCOL 是 UEFI 驱动开发一定会用到的一个 Protocol,负责驱动的安装与卸载。
  • 应用程序调用 OpenProtocol:AgentHandle 是该应用程序的 Handle,也就是 UefiMain 的第一个参数,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
//若已经打开,则被同一控制器再次打开的时候将会失败
#define EFI_OPEN_PROTOCOL_EXCLUSIVE               0x00000020
//若Protocol已经打开,则再次打开就会失败

4.2.2 HandleProtocol 服务

OpenProtocol 功能比较强大,但是使用比较复杂。为了方便使用 Protocol,启动服务提供了 HandleProtocol 以简化打开 Protocol。

HandleProtocol 服务函数原型:

/**gBS->HandleProtocol
查询指定的 Handle 中是否支持指定的 Protocol,如果支持,打开该 Protocol
  @retval EFI_SUCCESS            成功打开指定的 Protocol
  @retval EFI_UNSUPPORTED        指定的设备(Handle)不支持该 Protocol
  @retval EFI_INVALID_PARAMETER  参数 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 内部其实仍调用了 OpenProtocol,实现如下:

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 内核中按顺序搜索 HANDLE 链表,找出指定 Protocol 的第一个实例。

LocateProtocol 服务函数原型:

/** gBS->LocateProtocol
  从 UEFI 内核中找出匹配 Protocol 和 Registration 的第一个实例
  @retval EFI_SUCCESS             成功找到匹配的 Protocol
  @retval EFI_NOT_FOUND           系统中无法找到匹配的 Protocol
  @retval EFI_INVALID_PARAMETER   Interface 为空
**/
typedef EFI_STATUS(EFIAPI *EFI_LOCATE_PROTOCOL)(
  IN EFI_GUID *Protocol,             // 待查询的Protocol
  IN VOID *Registration, OPTIONAL    // 可选参数,从 RegisterProtocolNotify() 中获得的 Key
  OUT VOID **Interface               // 返回系统中第一个匹配到的Protocol实例
);

4.2.4 LocateHandleBuffer 服务

LocateHandleBuffer 服务可以找出支持某个 Protocol 的所有设备,

LocateHandleBuffer 服务函数原型:

/** gBS->LocateHandleBuffer
获得所有支持指定 Protocol 的 HANDLE。Buffer 数组由系统分配,由调用者释放
  @retval EFI_SUCCESS            成功返回。Buffer 返回 Handle 数组,NoHandles 返回 Handle 数目
  @retval EFI_NOT_FOUND          系统中没有发现支持指定 Protocol 的 Handle
  @retval EFI_OUT_OF_RESOURCES   资源耗尽
  @retval EFI_INVALID_PARAMETER  NoHandles 或 Buffer 参数非法
**/
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 数量
  OUT EFI_HANDLE **Buffer                 // 分配 Handle 数组并返回
);

SearchType 有三种:AllHandles(查找系统中所有 Handle)、ByRegisterNotify(从 RegisterProtocolNotify 中找出匹配 SearchKey 的 Handle)、ByProtocol(从系统 Handle 数据库中找出指定 Protocol 的 Handle)。

4.2.5 LocateHandle 服务

LocateHandle 服务的功能是找出支持某个 Protocol 的所有设备,与 LocateHandleBuffer 相同。两者区别在于 LocateHandle 需要调用者负责管理 Buffer 数组占用的内存。

LocateHandle 服务函数原型:

/** 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 数量
  OUT EFI_HANDLE **Buffer                 // 分配 Handle 数组并返回
);

4.2.6 ProtocolsPerHandle 服务

ProtocolsPerHandle 服务用于获得指定设备所支持的所有 Protocol。这些 Protocol 的 GUID 通过 ProtocolBuffer 返回给调用者,UEFI 负责分配内存给 ProtocolBuffer,调动者负责释放该内存。

ProtocolsPerHandle 服务函数原型:

/** gBS->ProtocolsPerHandle
  返回指定设备所支持的所有 Protocol
  @retval EFI_SUCCESS            成功返回 Handle 上安装的所有 Protocol
  @retval EFI_OUT_OF_RESOURCES   资源耗尽
  @retval EFI_INVALID_PARAMETER  参数非法或不是有效的 Handle
**/
typedef EFI_STATUS(EFIAPI *EFI_PROTOCOLS_PER_HANDLE)(
  IN EFI_HANDLE  Handle,               // 找出这个 Handle 上所有的 Protocol
  OUT EFI_GUID   ***ProtocolBuffer,    // 返回 Protocol GUID 数组
  OUT UINTN      *ProtocolBufferCount  // 返回 Protocol 的数目
);

4.2.7 OpenProtocolInformation 服务

OpenProtocolInformation 服务用于获得指定设备上指定 Protocol 的打开信息。

OpenProtocolInformation 服务函数原型:

typedef EFI_STATUS(EFIAPI *EFI_OPEN_PROTOCOL_INFORMATION) (
  IN EFI_HANDLE                           Handle,        // 设备句柄
  IN EFI_GUID                             *Protocol,     // 待查询的 Protocol
  OUT EFI_OPEN_PROTOCOL_INFORMATION_ENTRY **EntryBuffer, // 打开信息通过此数组返回
  OUT UINTN                               *EntryCount    // EntryBuffer 数组元素个数
);

返回的打开信息包括使用者句柄(AgentHandle)、控制器句柄(ControllerHandle)、打开属性和打开个数。

4.2.8 CloseProtocol 服务

CloseProtocol 服务用于关闭打开的 Protocol。

CloseProtocol 服务函数原型:

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,所以无法关闭。若一定要关闭,则要调用 OpenProtocolInformation 获得 AgentHandle 和 ControllerHandle,然后才能进行关闭。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 220,295评论 6 512
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,928评论 3 396
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 166,682评论 0 357
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 59,209评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,237评论 6 397
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,965评论 1 308
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,586评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,487评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 46,016评论 1 319
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,136评论 3 340
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,271评论 1 352
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,948评论 5 347
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,619评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,139评论 0 23
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,252评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,598评论 3 375
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,267评论 2 358

推荐阅读更多精彩内容