UEFI——Protocol的使用

一、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实例,其它组件就可以发现提供的服务。

  • 5
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值