UEFI中的Protocol
1. 基本概念
1.1 什么是protocol?
Protocol是UEFI中的一个重要概念(事实上《UEFI SPEC》中有超过70%的内容都是在讲Protocol),下面简单说明下。
从表现形式上看,它本身就是一个结构体。就是一个类,用来将driver或服务封装成类,结构里面定义了访问一个实例的数据成员和成员函数。
从功能上来说,Protocol在UEFI中的角色相当于UEFI的接口函数。实际上,Proctol是UEFI环境的提供者何使用者之间的一种约定,双方根据这个协定进行通信。
UEFI的代码时使用面向过程的C语言写的,但是proctol(service、driver)时使用面向对象的思想设计和管理的。UEFI的使用者可以通过GUID和相应的访问函数,获得对象的指针,然后使用该指针获得对象(driver、service)提供的服务,实现需要的功能。
- 使用struct来模拟类。
- 用函数指针(struct中的成员)来模拟类中的的成员函数,此种成员函数的额一个参数必需是指向Protocol实例d的指针,用来模拟this指针。
1.2 哪些对象对提供protocol?
- 启动服务和运行时服务。使用者通过protocol可以获得安装、卸载、内存和时间服务等。启动服务和运行服务的指针可以通过系统表找到。
- 各类driver。使用者可以通过protocol访问设备。
1.3 Protocol的组成
Protocol其实就是一个大的结构体类型,它包含有很多数据信息。下面通过BlockIO为例来说明一个协议。
typedef
EFI_STATUS
(EFIAPI *EFI_BLOCK_RESET) (
IN EFI_BLOCK_IO_PROTOCOL * This,
IN BOOLEAN ExtendedVerification
);
这是一个Protocol成员函数原型。第一个参数指向Protocol。之后又利用同样的方法定义了几个其他的函数。
///
/// This protocol provides control over block devices.
///
struct _EFI_BLOCK_IO_PROTOCOL {
///
/// The revision to which the block IO interface adheres. All future
/// revisions must be backwards compatible. If a future version is not
/// back wards compatible, it is not the same GUID.
///
UINT64 Revision;
///
/// Pointer to the EFI_BLOCK_IO_MEDIA data for this device.
///
EFI_BLOCK_IO_MEDIA *Media;
EFI_BLOCK_RESET Reset;
EFI_BLOCK_READ ReadBlocks;
EFI_BLOCK_WRITE WriteBlocks;
EFI_BLOCK_FLUSH FlushBlocks;
};
extern EFI_GUID gEfiBlockIoProtocolGuid;
这里又定义了一个大的结构类型EFI_BLOCK_IO_PROTOCOL,这就是一个Protocol接口原型。注意到这个结构几乎是由之前提及的指针函数所构成的。因此,一个protocol的组成包括:
-
上面提到的结构体内部的成员,也是对外访问接口。
-
GUID,一个128bit的ID,该Procotol的唯一标识符或名称。这样一来每个Protocol都有了自己的名字,那么使用者可以方便的通过指明不同的GUID来得到不同的Protocol。如果要在应用程序或者驱动中使用这个GUID(如gEfiBlockIoProtocolGuid),那么必须要在.inf文件的[Protocols]中声明,以便预处理时将其包含在生成的AutoGen.c中供全局使用。
- 一个指向GUID的全局指针变量,如gEfiBlockIoProtocolGuid。他的变量名的命名规则是统一的,即g + Efi + Protocol 名称 + ProtocolGuid。这样一来,程序员就不需记住相应GUID具体的值,而只在需要GUID作为参数的时候,使用这个全局变量即可,这有点类似Windows下的UUID宏。
1.4 Protocol和handle的关系
UEFI 驱动模型使用句柄代表设备,每个设备对应有自己的句柄,句柄由一个或多个协议组成,句柄包含指向这些协议的指针。UEFI 驱动和应用程序可以产生句柄,并在句柄上装载协议。
协议是一个以128 位的GUID(Globally Unique Identifier,全局唯一标识符)命名的结构体,类似于面向对象中的类,它是一些指针和数据结构体,或者说是规范定义的接口函数指针的集合,协议用来存放设备句柄所代表的设备提供的一类服务,服务的具体功能在设备驱动中实现。
开发者首先找到指定设备句柄上挂载的指定协议,再通过协议提供的接口访问设备驱动中实现服务的功能函数即可对设备进行操作。通过对句柄和协议的实现,UEFI 驱动程序模型抽象了驱动的安装和使用过程。图1-4 所示为设备句柄和协议的结构图。
- handle是指向某种对象的指针,UEFI用句柄来代表某个对象
typedef VOID *EFI_HANDLE;
UEFI扫描总线后,会为每个设备创建一个controller对象(用于控制设备)。所有设备的驱动以protocol的方式安装到这个controller中,这个controller就是一个UEFI_HANDLE对象。
除了设备外,一个.efi被加载到内存后,UEFI也会为该文件创建一个Image对象,这个对象也是一个UEFI_HANDLE对象。
在UEFI内核,UEFI_HANDLE被理解为IHANDLE。
2.HANDLE的结构
IHANDLE的结构如下:
///
/// IHANDLE - contains a list of protocol handles
///
typedef struct {
UINTN Signature;
/// All handles list of IHANDLE
LIST_ENTRY AllHandles;
/// List of PROTOCOL_INTERFACE's for this handle
LIST_ENTRY Protocols;
UINTN LocateRequest;
/// The Handle Database Key value when this handle was last created or modified
UINT64 Key;
} IHANDLE;
每个ihandle都有一个Protocols成员,存放属于自己的Protocol。所有的handle通过AllHandles链接起来.
而一个ihandle中的Protocol是如何组织的?是通过 LIST_ENTRY 类型的双向链表。链表中的而每个元素都是一个PROTOCOL_INTERFACE,通过PROTOCOL_INTERFACE的protocol指针可以获得protocol的GUID, 而通过PROTOCOL_INTERFACE内的interface指针就可以获得Protocol实例。
1.5 device driver和protocol
如下图所示是驱动加载过程中驱动镜像句柄安装的协议。
从图可知,一个驱动至少包含2个协议:
- 镜像加载协议EFI_LOADED_IMAGE_PROTOCOL 是驱动的入口点,用于加载驱动。
- 驱动绑定协议EFI_DRIVER_BINDING_PROTOCOL 实现驱动的启动和停止,三个接口函数:Supported()、Start()和Stop()。Supported()函数用来验证驱动与给定的设备句柄是否匹配。Start()函数负责驱动和句柄的连接,即将抽象I/O 功能的协议安装到设备句柄上。相对应的,Stop()函数则会强制停止驱动对一个设备句柄的管理和控制,并卸载设备句柄在Start()中安装的所有协议。
此外,还有一些协议是可选的:驱动配置协议、驱动诊断协议、组件命名协议。
总结
协议就是一类设备驱动或应用程序提供的接口合集。
- 从语言角度将,Protocol就是包含了各种属性和函数指针的结构体;
- 从功能角度讲,Procotol就是提供者和使用者对服务方式的一种约定;
- 从组织形式上看,Procotol安装在Image 对象的句柄中。
2.协议的使用
使用Protocol,一般需要如下3个步骤:
- 通过gBS->OpenProtocol(或者HandleProtocol、LocateProtocol)找出Protocol的对象。
- 使用这个Protocol提供的服务。
- 通过gBS->CloseProtocol关闭打开的Protocol。
2.1 OpenProtocol服务
OpenProtocol用于查询指定的Handle中是否支持指定的Protocol,如果支持的话就打开,不支持的话就返回错误的代码。OpenProtocol服务的函数原型如下所示:
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链表中有该Potocol,Protocol对象的指针写到*Interface,并返回EFI_SUCCESS;否则 返回EFI_UNSUPPORTED。
如果在驱动中调用OpenProtocol(),就是请求使用Protocol的控制器ControllerHandle。AgentHandle是拥有负责驱动安装和卸载的EFI_DRIVER_BINDING_PROTOCOL对象的Handle。如果调用OpenProtocol的是应用程序,那么AgentHandle是该应用对应的Handle,也就main函数的第一个参数,ControllerHandle此时可以忽略.
打开一个BlockIO示例:
2.2 HandleProtocol服务
这也是一个打开Protocol的服务,只是这是一个简化版本的OpenProtocol,它只含有三个参数,木有提供AgentHandle、ControllerHandle和Attributes,在调用的时候AgentHandle使用gDxeCoreImageHandle,ControllerHandle使用NULL值,Attribute使用EFI_OPEN_PROTOCOL_BY_HANDLE_PROTOCOL。以下是HandleProtocol的服务的函数原型:
EFI_STATUS EFIAPI CoreHandleProtocol (
IN EFI_HANDLE UserHandle,//查询该Handle是否支持Protocol
IN EFI_GUID *Protocol, //带查询的Protocol
OUT VOID **Interface //返回带查询的Protocol
)