一、Protocol的定义及结构
Protocol从功能上来说类似于UEFI提供的接口函数,是提供服务者和调用服务者之间的一种约定,双方根据这种约定互通信息。Protocol引入了面向对象的思想:
1、用struct来模拟class
2、用函数指针(Protocol的成员变量)模拟成员函数,此种函数的第一种参数必须指向Protocol的指针,用来模拟this指针。
Protocol的结构如下:
Protocol有GUID、Protocol接口的结构体、Protocol服务(即Protocol接口函数)组成。UEFI通过GUID和相应的访问函数,获得对象的指针,然后使用该指针获得对象所提供的服务,实现所需的功能。
以EFI_BLOCK_IO_PROTOCOL为例介绍Protocol的结构,其数据结构代码为:
// 这个协议用来控制块设备
struct _EFI_BLOCK_IO_PROTOCOL {
// Protocol版本号,Protocol必须向后兼容,如果不能兼容,就必须为未来的版本定义不同的GUID,也就是定义一个不同的Protocol
UINT64 Revision;
// 指向该设备EFI_BLOCK_IO_MEDIA数据的指针
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;
其中,ReadBlocks的函数原型为:
/**
从地址为Lba开始的块读取BufferSize字节到缓冲区
@param This Indicates a pointer to the calling context.
@param MediaId Id of the media, changes every time the media is replaced.
@param Lba The starting Logical Block Address to read from
@param BufferSize Size of Buffer, must be a multiple of device block size.
@param Buffer A pointer to the destination buffer for the data. The caller is
responsible for either having implicit or explicit ownership of the buffer.
@retval EFI_SUCCESS 数据从设备正确读出
@retval EFI_DEVICE_ERROR 设备出现错误
@retval EFI_NO_MEDIA 设备中没有介质
@retval EFI_MEDIA_CHANGED Media与当前设备不符.
@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, //mediaID
IN EFI_LBA Lba, //要读取的起始块逻辑地址
IN UINTN BufferSize, //要读取的字节数
OUT VOID *Buffer //目的缓冲区
);
第一个参数为是指向EFI_BLOCK_IO_PROTOCOL对象自己的This指针,这是成员函数区别于一般函数的重要特征。This指针指向正在操作的块设备的实例,告诉成员函数在操作哪个设备。与C++成员函数This指针的区别是,C++的this指针由编译器自动生成,而Protocol成员函数的This指针需要手动添加。
二、Protocol的简单使用
1、编写UEFI DXE Drrive代码MyHelloWorldDXEProtocol.c
需要使用InstallProtocolInterface()向设备的句柄注册一个新的Protocol实例
/**
Installs a protocol interface on a device handle. If the handle does not exist, it is created and added
to the list of handles in the system. InstallMultipleProtocolInterfaces() performs
more error checking than InstallProtocolInterface(), so it is recommended that
InstallMultipleProtocolInterfaces() be used in place of
InstallProtocolInterface()
在设备句柄上安装协议接口(接口就是Protocol实例),如果句柄不存在,就会创建该句柄并将其添加到系统的句柄列表中。InstallMultipleProtocolInterfaces() 比 InstallProtocolInterface() 执行更多的错误检查,因此建议使用 InstallMultipleProtocolInterfaces() 代替 InstallProtocolInterface()
@param[in, out] Handle A pointer to the EFI_HANDLE on which the interface is to be installed.
@param[in] Protocol The numeric ID of the protocol interface.
@param[in] InterfaceType Indicates whether Interface is supplied in native form.
@param[in] Interface A pointer to the protocol interface.
@retval EFI_SUCCESS The protocol interface was installed.
@retval EFI_OUT_OF_RESOURCES Space for a new handle could not be allocated.
@retval EFI_INVALID_PARAMETER Handle is NULL.
@retval EFI_INVALID_PARAMETER Protocol is NULL.
@retval EFI_INVALID_PARAMETER InterfaceType is not EFI_NATIVE_INTERFACE.
@retval EFI_INVALID_PARAMETER Protocol is already installed on the handle specified by Handle.
**/
typedef
EFI_STATUS
(EFIAPI *EFI_INSTALL_PROTOCOL_INTERFACE)(
IN OUT EFI_HANDLE *Handle, //输出参数,指向要安装接口的handle
IN EFI_GUID *Protocol, //protocol接口的GUID
IN EFI_INTERFACE_TYPE InterfaceType, //表示接口是否以本地形式提供
IN VOID *Interface //指向Protocol接口的指针
);
完整代码:
#include <uefi.h>
#include <Library/UefiLib.h>
#include <Library/BaseLib.h>
#include <Library/DebugLib.h>
#include <Library/BaseMemoryLib.h>
#include <Library/UefiDriverEntryPoint.h>
#include <Pi/PiBootMode.h>
#include <Pi/PiHob.h>
#include <Library/HobLib.h>
#include <Library/UefiBootServicesTableLib.h>
EFI_GUID gMyHelloWorldPEIGUID = { 0xbdb38129, 0x4d65, 0x39f4, { 0x72, 0x12, 0x68, 0xcf, 0x5a, 0x19, 0xa, 0xf8 }};
typedef struct _EFI_MYHELLOWORLD_PROTOCOL EFI_MYHELLOWORLD_PROTOCOL;
typedef EFI_STATUS (EFIAPI *EFI_PRINT_MSG)( EFI_MYHELLOWORLD_PROTOCOL *This,IN CHAR16 *Msg);//定义函数指针
//定义这个Protocol
struct _EFI_MYHELLOWORLD_PROTOCOL {
UINT64 Revision; //版本号
EFI_PRINT_MSG PrintMsg; //Protocol的成员函数,实际上是函数指针
};
//成员函数的具体实现
EFI_STATUS EFIAPI
PrintHelloWorldMsg(IN EFI_MYHELLOWORLD_PROTOCOL *This, IN CHAR16 *Msg){//手动定义This指针
EFI_STATUS Status = EFI_SUCCESS;
DEBUG ((EFI_D_ERROR, "[MyHelloWorldProtocol] PrintHelloWorldMsg %s..\n",Msg));
Print ((EFI_D_ERROR, "[MyHelloWorldProtocol] PrintHelloWorldMsg %s..\n",Msg));
return Status;
}
static EFI_MYHELLOWORLD_PROTOCOL gMyHelloWorldProtocol ;
EFI_STATUS
EFIAPI
MyHelloWorldDXEProtocolEntry(
IN EFI_HANDLE ImageHandle,
IN EFI_SYSTEM_TABLE *SystemTable
)
{
EFI_STATUS Status = EFI_SUCCESS;
DEBUG ((EFI_D_ERROR , "[MyHelloWorldProtocol] MyHelloWorldDXEProtocolEntry Start..\n"));
gMyHelloWorldProtocol.PrintMsg = PrintHelloWorldMsg;
gMyHelloWorldProtocol.Revision = 1;
//注册Protocol实例
Status = gBS->InstallProtocolInterface(&ImageHandle,
&gMyHelloWorldPEIGUID, //Protocol的GUID
EFI_NATIVE_INTERFACE,
&gMyHelloWorldProtocol //指向protocol的指针
);
if (!EFI_ERROR(Status)){
DEBUG ((EFI_D_ERROR ,"[MyHelloWorldProtocol] MyHelloWorldDXEProtocolEntry Installed Protocol Successfully..\n"));
}else{
DEBUG ((EFI_D_ERROR ,"[MyHelloWorldProtocol] MyHelloWorldDXEProtocolEntry Installed Protocol Failly..\n"));
}
DEBUG ((EFI_D_ERROR , "[MyHelloWorldProtocol] MyHelloWorldDXEProtocolEntry End..\n"));
return Status;
}
2、编写 UEFI DXE Driver的inf文件
1、编写UEFI Application代码MyHelloWorldAppProtocol.c
在使用protocol的时候需要使用LocateProtocol查找到已经注册的protocol实例,其代码原型为
/**
Returns the first protocol instance that matches the given protocol.
返回与给定protocol匹配的第一个protocol实例
@retval EFI_SUCCESS A protocol instance matching Protocol was found and returned in
Interface.
@retval EFI_NOT_FOUND No protocol instances were found that match Protocol and
Registration.
@retval EFI_INVALID_PARAMETER Interface is NULL.
Protocol is NULL.
**/
typedef
EFI_STATUS
(EFIAPI *EFI_LOCATE_PROTOCOL)(
IN EFI_GUID *Protocol, //给出要查找的protocol,即指向protocol的GUID
IN VOID *Registration OPTIONAL, //可选参数,用于指定特定的protocol实例,不需要指定的话可设置为NULL
OUT VOID **Interface //输出参数,指向一个指针,指针指向找到的protocol实例
);
完整代码如下:
#include <uefi.h>
#include <Library/UefiLib.h>
#include <Library/BaseLib.h>
#include <Library/DebugLib.h>
#include <Library/BaseMemoryLib.h>
#include <Library/UefiBootServicesTableLib.h>
EFI_GUID gMyHelloWorldPEIGUID = { 0xbdb38129, 0x4d65, 0x39f4, { 0x72, 0x12, 0x68, 0xcf, 0x5a, 0x19, 0xa, 0xf8 }};
typedef struct _EFI_MYHELLOWORLD_PROTOCOL EFI_MYHELLOWORLD_PROTOCOL;
typedef EFI_STATUS (EFIAPI *EFI_PRINT_MSG)( EFI_MYHELLOWORLD_PROTOCOL *This,IN CHAR16 *Msg);
struct _EFI_MYHELLOWORLD_PROTOCOL {
UINT64 Revision;
EFI_PRINT_MSG PrintMsg;
};
static EFI_MYHELLOWORLD_PROTOCOL * gMyHelloWorldProtocol = NULL;
EFI_STATUS
EFIAPI
MyHelloWorldAppProtocolEntry(
IN EFI_HANDLE ImageHandle,
IN EFI_SYSTEM_TABLE *SystemTable
)
{
EFI_STATUS Status = EFI_SUCCESS;
DEBUG ((EFI_D_ERROR , "[MyHelloWorldProtocol] MyHelloWorldAppProtocolEntry Start..\n"));
Print (L"[MyHelloWorldProtocol] MyHelloWorldAppProtocolEntry Start..\n");
Status = gBS->LocateProtocol(&gMyHelloWorldPEIGUID,NULL,(VOID **)&gMyHelloWorldProtocol);//寻找protocol实例
if (EFI_ERROR(Status)){
Print(L"[MyHelloWorldProtocol] Locate Protocol gMyHelloWorldProtocol %r \n",Status);
return Status;
}
gMyHelloWorldProtocol->PrintMsg(gMyHelloWorldProtocol,L"Hello World App Success.....\n");//利用protocol提供的接口函数实现所需要的功能
DEBUG ((EFI_D_ERROR, "[MyHelloWorldProtocol] MyHelloWorldAppProtocolEntry End..\n"));
Print (L"[MyHelloWorldProtocol] MyHelloWorldAppProtocolEntry End ... \n");
return Status;
}
2、编写 inf文件MyHelloWorldAppProtocol.inf
编译包,并将fd文件加载到qemu中运行
运行结果如下:
E:\UEFIWorkSpace\edk2>qemurun.bat | findstr MyHelloWorldProtocol
WARNING: Image format was not specified for 'HDD_BOOT.img' and probing guessed raw.
Automatically detecting the format is dangerous for raw images, write operations on block 0 will be restricted.
Specify the 'raw' format explicitly to remove the restrictions.
[MyHelloWorldProtocol] MyHelloWorldDXEProtocolEntry Start..
[MyHelloWorldProtocol] MyHelloWorldDXEProtocolEntry Installed Protocol Successfully..
[MyHelloWorldProtocol] MyHelloWorldDXEProtocolEntry End..
[MyHelloWorldProtocol] MyHelloWorldAppProtocolEntry Start..
[MyHelloWorldProtocol] MyHelloWorldAppProtocolEntry Start..
[MyHelloWorldProtocol] PrintHelloWorldMsg Hello World App Success.....
[MyHelloWorldProtocol] MyHelloWorldAppProtocolEntry End..
[MyHelloWorldProtocol] MyHelloWorldAppProtocolEntry End ...
总结
Protocol的使用:
1、找到Protocol实例:每个protocol都有一个唯一的GUID,根据这个GUID,利用LocateProtocol()找到Protocol 实例。
2、利用Protocol提供的接口函数实现所需要的功能。
3、使用CloseProtocol()关闭打开的Protocol实例。(这本文中未关闭)
Protocol的产生:
1、定义Protocol的结构体,并实现其中的函数指针所指向的函数
2、利用InstallProtocolInterface()向设备的handle注册Protocol实例,其它组件就可以发现提供的服务。