UEFI中的protocol
简介
1.我们先看protocol的翻译,protocol:礼仪;外交礼节;条约草案;议定书;(协议或条约的)附件;(数据传递的)协议,规程,规约。我把它理解为协议,作为服务端和客户端约定好的协议,服务端和客户端按照协议互通信息,使用服务的是客户端,提供服务的是服务端。
UEFI的英文翻译过来应该叫“可扩展固件接口”。这个名词中最重要的事实上是“可扩展”这三个字。为了两个参与操作的进程之间的操作以及代码的保密性,类似于做交易的双方,为了代码的安全,双方约定好交易地点时间(接口),双方遵循这个协议进行交易。而这种事先的约定的接口就是protocol的定义。而这种定义尤以.h文件的形式加以实现。
2.在语言方面c语言作为一门面向过程的语言为了也能使用对象这个概念,用struct来模拟class,成员变量存入函数指针模拟成员函数,此函数的第一参数必须指向Protocol的指针,用来模拟this指针。
3.Protocol不是UEFI BIOS一开始就可以用的。我们知道UEFI BIOS启动时分为不同的阶段,SEC-PEI-DXE-BDS等等。而Protocol需要等到DXE阶段才可以使用(不需要特别在意DXE阶段的哪个点开始,基本上我们开发时写的DXE模块都可以使用)
4.GUID是唯一的id,一个唯一的标识符,方便使用这个唯一的标识符操控对应的protocol。如果要在应用程序或者驱动中使用这个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
5.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连接起来。
DECLARE_HANDLE(HDC);
展开
#define DECLARE_HANDLE(name) struct name##__ { int unused; };
用HDC替换上面的name来看看,我们就得到了下面的一段代码:
struct HDC__
{
int unused;
};
typedef struct HDC__ *HDC
typedef struct name##__ *name
handle与遥控器类似,可以通过遥控器操控换台,但是内部的结构怎么做到的没有权限接触,不会通过遥控器伤害或者了解电视内部的结构。
HANDLE不过是一个指向void型,即无类型的指针,嗯,目前的指针是32位的吧.其实也不能说HANDLE是一种指针,它只充当一种索引的作用.HANDLE是固定的,它不会变,但是对象的地址会变,当对象在内存中的位置发生改变后,我们不能通过之前的对象指针找到对象,怎么办呢?嗯,这时候HANDLE的用处就出来了,HANDLE就是用来记录对象的最新地址的。 在UEFI内部,EFI_HANDLE被理解为IHANDLE,这个IHANDLE包含了Protocols的链表,存放属于自己的Protocol,然后ALLHANDLE将所有的IHANDLE连接起来。
Windows是一个以虚拟内存为基础的操作系统。在这种系统环境下,Windows内存管理器经常在内存中来回移动对象,依此来满足各种应用程序的内存需要。对象被移动之后,我们还可以通过指针来操作对象吗?显然是不能的,为此我们使用一个第三方变量,既然地址不是唯一不变的,我们将这个变动的值赋值给一个变量,当地址变动,变量的值也相应改变,所以我们只需要调动这个变量即可。Windows内存管理器在移动对象在内存中的位置后,把对象新的地址告知这个句柄地址来保存。这样我们只需记住这个句柄地址就可以间接地知道对象具体在内存中的哪个位置。这个地址是在对象装载(Load)时由系统分配给的,当系统卸载时(Unload)又释放给系统。句柄地址(稳定)→记载着对象在内存中的地址→对象在内存中的地址(不稳定)→实际对象。但是,必须注意的是程序每次从新启动,系统不能保证分配给这个程序的句柄还是原来的那个句柄,而且绝大多数情况的确不一样的。
使用步骤
使用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此时可以忽略.
2.HandleProtocol服务
EFI_STATUS EFIAPI CoreHandleProtocol (
IN EFI_HANDLE UserHandle,//查询该Handle是否支持Protocol
IN EFI_GUID *Protocol, //带查询的Protocol
OUT VOID **Interface //返回带查询的Protocol
)
这也是一个打开Protocol的服务,只是这是一个简化版本的OpenProtocol,它只含有三个参数,木有提供AgentHandle、ControllerHandle和Attributes,在调用的时候AgentHandle使用gDxeCoreImageHandle,ControllerHandle使用NULL值,Attribute使用EFI_OPEN_PROTOCOL_BY_HANDLE_PROTOCOL。以下是HandleProtocol的服务的函数原型:
3、LocateProtocol服务
Typedef EFI_STATUS LocateProtocol (
IN EFI_GUID *Protocol, //带查询的Protocol
IN VOID *Registration OPTIONAL, //可选参数,从RegisterProtocolNotify中获得的Key
OUT VOID **Interface //返回系统中第一个匹配到的Protocol实例
);
这个服务是从内核中找出指定Protocol的第一个实例,它会按顺序搜索HANDLE链表,返回找到的第一个该Protocol的实例。以下是LocateProtocol的服务的函数原型:
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的控制器
);