作者: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 时,一般需要以下三个步骤:
- 通过 gBS->OpenProtocol(或者 HandleProtocol、LocateProtocol)找出 Protocol 对象。
- 使用这个 Protocol 提供的服务
- 通过 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,然后才能进行关闭。