UEFI中的Protocol
一、Protocol是什么
Protocol是服务器端和客户端之间的一种约定,在软件编程上称为接口,服务器端和客户端通过这个约定信息的互通。服务器端和客户端在UEFI中都是可执行的二进制文件,为了实现这些二进制文件之间的互通,C/S双方共同作出的让步,使用Protocol进行双方的交互。
二、Protocol是干什么的,为什么要有Protocol
UEFI的中文含义是“可扩展固件接口”,所谓可扩展的含义就是可以在系统完成后(编译为binary)之后,再次为系统增加新的功能,而不用重新rebuild整个系统。为了支持不同二进制组件运行时相互通信,不同组件可以相互调用之间的功能,同时各个组件相互之间调用时的统一编程接口以便方便组件厂商对于组件的开发等要求。Protocol用以实现这些功能的同时满足服务器端和客户端之间的通信。而通信至少要具备以下几种功能:
(1)互操作。A组件*可以调用B组件实现的函数。反过来也一样。
(2)数据传递。双方可以通过某种方法(sharedmemory, pipes and etc)互相交换数据。
(3)可探测。某组件必须具备探测另一个组件是否存在的能力。
(4)组件的开发必须是独立的。开发A组件不需要B组件的源代码,反之亦然。
三、Protocol的组成
Protocol其实就是一个大的结构类型,它包含有很多数据信息。最主要的是GUID,每一个Protocol都有一个定义好的、唯一的标识符,这个标识符用GUID来表达。这样程序员可以方便的通过指明不同的GUID来得到不同的Protocol。其次是一个指向GUID的全局指针变量,比如EFI_BLOCK_IO_PROTOCOL,就有一个gEfiBlockIoProtocolGuid的全局指针,他是EFI_GUID *结构的。这样做事实上完全是为了方便,他的变量名的命名规则是统一的,即g + Efi + Protocol 名称 + ProtocolGuid。这样一来,程序员就不需记住相应GUID具体的值,而只在需要GUID作为参数的时候,使用这个全局变量就行了。如果要在应用程序或者驱动中使用这个GUID(如gEfiBlockIoProtocolGuid),那么必须要在.inf文件的[Protocols]中什么以便预处理时将其包含在生成的AutoGen.c*全局使用。以下是以块设备的Protocol为例描述protocol的数据结构中的成员变量,通过这个Protocol可以控制块设备。
struct _EFI_BLOCK_IO_PROTOCOL {
UINT64 Revision; //必须保证向后兼容的Protocol版本号,若木有向后兼容的话就必须给未来的版本号定义一个新的GUID,相当于定义了一个新的Protocol
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; //导出该Protocol
四、Protocol在内核中的表示
了解Protocol在什么位置之前我们要先了解下EFI_HANDLE。
typedef VOID *EFI_HANDLE; //相关接口的一个集合
这个EFI_HANDLE是指向某种对象的指针,UEFI用其来表示某个对象的。UEFI通过扫描总线,为每一个设备建立一个Controller对象,用于控制各个设备,所有该设备的驱动都是以Protocol的形式安装在这个Controller中的,这个Controller就是一个EFI_HANDLE指针指向的对象。 当我们将一个.efi文件加载到内存中,UEFI也会为该文件建立一个Image对象,这个Image对象也是一个EFI_HANDLE对象。 在UEFI内部,EFI_HANDLE被理解为IHANDLE,这个IHANDLE包含了Protocols的链表,存放属于自己的Protocol,然后ALLHANDLE将所有的IHANDLE连接起来。
五、Protocol服务的使用
要使用Protocol服务,首先要根据GUID找到Protocol对象,Boot Service中提供了几种Protocol服务,如下表所示:
表1 Boot Service中提供的Protocol服务
OpenProtocol |
打开Protocol |
HandleProtocol |
打开Protocol,PenProtocol的简化版本 |
LocateProtocol |
找出系统中指定Protocol的第一个实例 |
LocateHandleBuffer |
找出支持指定Protocol的左右Handle。系统负责分配内存,调用者负责释放内存 |
LocateHandle |
找出支持指定Protocol的左右Handle,调用者负责分配和释放内存 |
OpenProtocolInformation |
返回指定Protocol的打开信息 |
ProtocolsPerHandle |
找出指定Handle上安装的所有Protocol |
CloseProtocol |
关闭Protocol |
介绍完了上述几种Protocol服务,接下来我们了解一下使用Protocol服务的几个操作步骤,使用protocol服务的一般有以下三步:
第一步:通过gBS->OpenProtocol(或者HandleProtocol、LocateProtocol)找出Protocol的对象。
第二步:使用这个Protocol提供的服务。
第三步:通过gBS->CloseProtocol关闭打开的Protocol。
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是Protocol的提供者,如果Handle的Protocols链表中有该Potocol,Protocol对象的指针写到*Interface,并返回EFI_SUCCESS;否则 返回EFI_UNSUPPORTED。
如果在驱动中调用OpenProtocol(),就是请求使用Protocol的控制器ControllerHandle。AgentHandle是拥有负责驱动安装和卸载的EFI_DRIVER_BINDING_PROTOCOL对象的Handle。如果调用OpenProtocol的是应用程序,那么AgentHandle是该应用对应的Handle,也就main函数的第一个参数,ControllerHandle此时可以忽略。
对于Attributes可以取以下5种值,即有五种打开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已经打开,则再次打开就会失败
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
)
3、LocateProtocol服务
这个服务是从内核中找出指定Protocol的第一个实例,它会按顺序搜索HANDLE链表,返回找到的第一个该Protocol的实例。以下是LocateProtocol的服务的函数原型:
Typedef EFI_STATUS LocateProtocol (
IN EFI_GUID *Protocol, //带查询的Protocol
IN VOID *Registration OPTIONAL, //可选参数,从RegisterProtocolNotify中获得的Key
OUT VOID **Interface //返回系统中第一个匹配到的Protocol实例
);
4、LocateHandleBuffer服务
这个服务的功能是找出支持某个Protocol的所有设备。该服务的函数原型如下:
typedef EFI_STATUS LocateHandleBuffer (
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)。NoHandles是找到的HANDLE的数量, Buffer数组由UEFI复杂分配,由用户负责释放。
5、LocateHandle服务
这个服务的功能是找出支持某个Protocol的所有设备,它的作用于上述的LocateHandleBuffer提供的服务是一样的,两者之间最大的区别在于LocateHandle需有调用者负责管理Buffer数组占用的内存。该服务的函数原型如下:
typedef EFI_STATUS LocateHandleBuffer (
IN EFI_LOCATE_SEARCH_TYPE SearchType, //查找方法
IN EFI_GUID *Protocol OPTIONAL, //指定的Protocol
IN VOID *SearchKey OPTIONAL, //PROTOCOL_NOTIFY类型
IN OUT UINTN *Buffersize, //返回找到的Handle的Buffer大小
OUT EFI_HANDLE **Buffer //返回找到的Handle
);
6、其他使用Protocol的服务
(1)、ProtocolsPerHandle服务
这个服务是用于获得指定设备所支持的所有Protocol。这些Protocol的GUID通过ProtocolBuffer返回给调用者,UEFI负责分配内存给ProtocolBuffer,调用者负责释放该内存。其函数原型如下:
Typedef EFI_STATUS ProtocolsPerHandle (
IN EFI_HANDLE Handle, //找出这个Handle上所有的Protocol
OUT EFI_GUID ***ProtocolBuffer, //返回Protocol GUID的数组
OUT UINTN *ProtocolBufferCount //返回Protocol的数目
);
(2)、OpenProtocolInformation服务
这个服务用于获得指定设备上指定Protocol的打开信息,其函数原型如下:
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数组元素个数
);
7、Close Protocol服务
这个服务的作用是关闭打开的Protocol。通过HandleProtocol和LocateHandle打开的Protocol因为没有指定AgentHandle,所以无法关闭。若一定要关闭,则要调用OpenProtocolInformation获得AgentHandle和ControllerHandle,然后才能进行关闭。它的函数原型如下:
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的控制器
);