第四章 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,然后才能进行关闭。

你可能感兴趣的:(第四章 UEFI 中的 Protocol)