UEFI的Handle和Protocol简单理解

EFI_HANDLE

EFI_HANDLE是EFI(Ext Firmware Interface)规范中的一个重要念,用于标识和管理在系统中的各种和实体.UEFI通过扫描总线,为每一个设备建立一个Controller对象,用于控制各个设备,所有该设备的驱动都是以Protocol的形式安装在这个Controller中的,这个Controller就是一个EFI_HANDLE指针指向的对象。 当我们将一个.efi文件加载到内存中,UEFI也会为该文件建立一个Image对象,这个Image对象也是一个EFI_HANDLE对象。 在UEFI内部,EFI_HANDLE被理解为IHANDLE,这个IHANDLE包含了Protocols的链表,存放属于自己的Protocol,然后ALLHANDLE将所有的IHANDLE连接起来。

  • EFI_HANDLE 是一个指针指向某种对象

EFI_HANDLE 是一个指针,应用如下:
1、UEFI会为每一个连接的设备建立一个EFI_HANDLE指针指向的对象(controller)
2、当一个.efi文件被加载的时候,UEF也会为这个文件建立一个EFI_HANDLE 指向的对象(imageHandle)
从上面的应用中可知code里面广泛使用的controller和imagehandle都是指的是EFI_HANDLE这个指针指向的对象

  • EFI_HANDLE指向对象实际上市一个名为IHANDLE的结构体
  • 设备驱动会以protocol形式安装到Handle上面

HANDLE架构

在这里插入图片描述
在这里插入图片描述

EFI_HANDLE代码举例跟踪

EFI_HANDLE的定义如下:

typedef VOID                      *EFI_HANDLE;

    EFI_HANDLE是一个 void *类型的数据,在C语言中这表示无明确类型的指针,就是说可以进行任意转换。符合我们前述的类型为指针、应用广泛。

EFI_HANDLE指向的这段内存就是我们之前提到的对象,这个如下:

EFI_HANDLE     *UserHandle;
IHANDLE             *Handle;
Handle = NULL;
// ... 
Handle = (IHANDLE *)*UserHandle;

转换成了 IHANDLE Handle = (IHANDLE )EFI_HANDLE的形式,即指向的对象为IHANDLE

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是一个非常标准且常见的结构体形式,我们所说的handle的实质也就是IHANDLE结构体。

展开上面结构体,关注一下LIST_ENTRY,这个成员是构成handle&protocol关系的重要联系,详细了解这个成员的结构对于理解handle&protocol有着很重要的帮助,结构如下:

typedef struct _LIST_ENTRY LIST_ENTRY;

///
/// _LIST_ENTRY structure definition.
///
struct _LIST_ENTRY {
  LIST_ENTRY  *ForwardLink;
  LIST_ENTRY  *BackLink;
};

这是一个简单的双向链表节点结构,链表的每一个节点连接的都是LIST_ENTRY结构体,也就是连接只存在于LIST_ENTRY结构体之间,而包含了LIST_ENTRY结构体的IHANDLE结构就能够通过其成员LIST_ENTRY AllHandles结构体产生相互连接。换句话说AllHandles能够形成一个链表,将IHANDLE按照某种方式串在一起,我们将形成的这个链表就称为Handledatabase链表

Handledatabase

    Handle database是由Handles(链表)和Protocols(链表)组成的,是全局的,可以被任何UEFI Image访问。在执行完ExitBootServices()之后,Handle database就不存在了。
    Handledatabase链表是一个环形的链表,其中头结点是一个空节点gHandleList,每次有新的IHANDLE结构体需要添加到链表中,就在头结点后面进行插入。Handledatabase就是我们需要强调掌握的第一个链表。
    总的来说,在UEFI固件中,Handle Database只有一份,它是存储系统中所有Handle信息的唯一位置。Handle Database中的信息是根据系统中的设备和驱动程序动态生成的,每当系统中添加或移除设备时,Handle Database中的信息就会相应地发生变化。

Protocol

    protocol是服务器和客户端之间的一种约定,双方依据这种约定互通信息。服务器提供服务,客户端使用服务。

1)UEFI中的protocol是一组API(既函数接口),用来实现特定接口

  • 结构体,既protocol中函数接口的定义
  • GUID(既protocol的名字)

2)protocol在UEFI内核中的表示:

    当我们将一个.efi文件加载到内存中,UEFI也会为改文件建立一个image对象(也是一个EFI_HADLE对象),在UEFI内部被理解为INHANDLE,每个INHANDLE中都有一个Protocl链表,存放属于自己的protocl。

提示:每个INHANDLE中有有一个protocol链表,存放属于自己的protocl。所有的IHANDLE通过ALLHandles链接起来。如下图展示了IHANDLE内的Protocal是如何被组织起来的。IHANDLE的Protocols是一个双向链表,链表中的每一个元素是PROTOCOL_INTERFACE,通过PROTOCOL_INTERFACE的protocol指针可以得到这个Protocal的GUID,通过interface指针可以得到这个Protocal的实例。如下
在这里插入图片描述

3)protocol使用:

编写Protocal并安装

1、首先需要定义一个Protocal,通常会在独立的Package的Include/Protocol目录下创建一个头文件,这里是HelloWorldProtocol.h:


#ifndef __BENI_HELLO_WORLD_PROTOCOL_H__
#define __BENI_HELLO_WORLD_PROTOCOL_H__
 
#include <Uefi.h>
 
#define EFI_HELLO_WORLD_PROTOCOL_GUID \
  {0x038f1af5, 0x1c8d, 0x408f, { 0xab, 0x25, 0x30, 0xae, 0xb5, 0x96, 0x5d, 0x6e }}
 
typedef struct _EFI_HELLO_WORLD_PROTOCOL EFI_HELLO_WORLD_PROTOCOL;
 
/**
  Print "Hello Wrold".
  @param[in]  This                  A pointer to the EFI_HELLO_WORLD_PROTOCOL instance.
  @retval  EFI_SUCCESS              Always return EFI_SUCCESS after print.
**/
typedef
EFI_STATUS
(EFIAPI *HELLO) (
  IN  EFI_HELLO_WORLD_PROTOCOL      *This
  );
 
struct _EFI_HELLO_WORLD_PROTOCOL {
  UINTN                   Version;
  HELLO                   Hello;
};
 
extern EFI_GUID gEfiHelloWorldProtocolGuid;
 
#endif // __BENI_HELLO_WORLD_PROTOCOL_H__

上上述代码中重要的有两个部分,一个就是Protocol,这里定义了一个包含一个整型和一个函数指针的Protocol;另一个是一个EFI_GUID,之前的说明中一直没有提到,它其实是对应Protocol的标记,前面提到的UEFI Boot Service中的Protocol处理接口需要通过这个EFI_GUID来操作Protocol。因为EFI_GUID是唯一的,所以能够对应特定的Protocol。

还有就是函数的入参只有一个类型为EFI_HELLO_WORLD_PROTOCOL的this指针,这个位置需要注意,这个this指针就是指向使用函数的protocol结构体自身。而且,所有的protocol的service函数的首个参数,都是指向自己结构体的指针(这是必须的)

不仅在头文件中需要声明EFI_GUID,这里就是gEfiHelloWorldProtocolGuid,还需要在dec文件中定义该EFI_GUID:

[Protocols]
  # // Include/Protocol/HelloWorldProtocol.h
  # // {038F1AF5-1C8D-408F-AB25-03AEB5965D6E}
  gEfiHelloWorldProtocolGuid        = { 0x038f1af5, 0x1c8d, 0x408f, { 0xab, 0x25, 0x30, 0xae, 0xb5, 0x96, 0x5d, 0x6e } }

2、之后就是EFI_HELLO_WORLD_PROTOCOL的实现和安装:

#include <Uefi.h>
 
#include <Library/UefiDriverEntryPoint.h>
#include <Library/UefiBootServicesTableLib.h>
#include <Library/MemoryAllocationLib.h>
#include <Library/DebugLib.h>
 
#include <Protocol/HelloWorldProtocol.h>
 
/**
  Print "Hello Wrold".
  @param[in]  This                  A pointer to the EFI_HELLO_WORLD_PROTOCOL instance.
  @retval  EFI_SUCCESS              Always return EFI_SUCCESS after print.
**/
EFI_STATUS
EFIAPI
Hello (
  IN  EFI_HELLO_WORLD_PROTOCOL      *This
  )
{
  DEBUG ((EFI_D_ERROR, "Hello World\n"));
 
  return EFI_SUCCESS;
}
 
/**
  Main entry of the driver.
  @param[in]  ImageHandle           Image handle for this driver.
  @param[in]   SystemTable          Pointer to the System Table.
  @retval  EFI_SUCCESS              Driver executed successfully.
  @retval  Others                   Error happened.
**/
EFI_STATUS
EFIAPI
ProtocolServerEntry (
  IN  EFI_HANDLE                    ImageHandle,
  IN  EFI_SYSTEM_TABLE              *SystemTable
  )
{
  EFI_STATUS                   Status;
  EFI_HELLO_WORLD_PROTOCOL     *Protocol;
 
  Protocol = AllocatePool (sizeof (EFI_HELLO_WORLD_PROTOCOL));
  if (NULL == Protocol) {
    DEBUG ((EFI_D_ERROR, "[BENI][%a][%d]: Out of resource.", __FUNCTION__, __LINE__));
    return EFI_OUT_OF_RESOURCES;
  }
 
  Protocol->Version = 0x01;
  Protocol->Hello   = Hello;
 
  Status = gBS->InstallProtocolInterface (
                    &ImageHandle,
                    &gEfiHelloWorldProtocolGuid,
                    EFI_NATIVE_INTERFACE,
                    Protocol
                    );
  if (EFI_ERROR (Status)) {
    DEBUG ((EFI_D_ERROR, "[BENI]Install EFI_HELLO_WORLD_PROTOCOL failed. - %r\n", Status));
    FreePool (Protocol);
    return Status;
  }
 
  return EFI_SUCCESS;
}

由上面的安装代码,了解各个使用参数的定义:
ImageHandle:这个就是用来存放Protocol的EFI_HANDLE,当然也可以使用初始值为NULL的新的Handle,不过方便起见就使用了原有的;

gEfiHelloWorldProtocolGuid:这个就是用来标记EFI_HELLO_WORLD_PROTOCOL的EFI_GUID;

EFI_NATIVE_INTERFACE:这是Protocol的属性,类型是EFI_INTERFACE_TYPE,目前只有这一个值;

Protocol:就是本模块实现的Protocol实例,这里需要注意它的类型是XXX_PROTOCOL指针,有时候可能一时疏忽写成了&Protocol,就会导致使用该Protocol的时候异常挂死;

每一次protocol的插入都会新建一个对应的PROTOCOL-INTERFACE,
当我们日常在使用InstallProtocolInterface进行所谓的安装protocol的时候,实际上是生成一个 protocol interface 并且在该interface上安装我们自己定义的能够实现功能的 protocol 实例

3、对Protocol的使用:
要使用Protocol服务,首先要根据GUID找到Protocol对象,Boot Service中提供了几种Protocol服务,如下表所示:
在这里插入图片描述
介绍完了上述几种Protocol服务,接下来我们了解一下使用Protocol服务的几个操作步骤,使用protocol服务的一般有以下三步:

第一步:通过gBS->OpenProtocol(或者HandleProtocol、LocateProtocol)找出Protocol的对象。

第二步:使用这个Protocol提供的服务。

第三步:通过gBS->CloseProtocol关闭打开的Protocol。

代码实现如下:

#include <Uefi.h>
 
#include <Library/UefiDriverEntryPoint.h>
#include <Library/UefiBootServicesTableLib.h>
#include <Library/DebugLib.h>
 
#include <Protocol/HelloWorldProtocol.h>
 
/**
  Main entry of the driver.
  @param[in]  ImageHandle           Image handle for this driver.
  @param[in]   SystemTable          Pointer to the System Table.
  @retval  EFI_SUCCESS              Driver executed successfully.
  @retval  Others                   Error happened.
**/
EFI_STATUS
EFIAPI
ProtocolConsumerEntry (
  IN  EFI_HANDLE                    ImageHandle,
  IN  EFI_SYSTEM_TABLE              *SystemTable
  )
{
  EFI_STATUS                   Status;
  EFI_HELLO_WORLD_PROTOCOL     *Protocol;
 
  Status = gBS->LocateProtocol (&gEfiHelloWorldProtocolGuid, NULL, (VOID **)&Protocol);
  if (EFI_ERROR (Status)) {
    DEBUG ((EFI_D_ERROR, "[BENI]Locate EFI_HELLO_WORLD_PROTOCOL failed. - %r\n", Status));
    return Status;
  }
 
  DEBUG ((EFI_D_ERROR, "Protocol Version: 0x%08x\n", Protocol->Version));
  Status = Protocol->Hello (Protocol);
  if (EFI_ERROR (Status)) {
    DEBUG ((EFI_D_ERROR, "[BENI]Protocol->Hello failed. - %r\n", Status));
    return Status;
  }
 
  return EFI_SUCCESS;

提示:Install和locate protocol写代码的三个步骤:
在这里插入图片描述

Handle和Protocal的联系

1、Handle是系统中的一个唯一标识符,表示一个UEFI服务。每个Handle都可以提供一个或多个协议接口。

2、Protocol是一个标准接口,定义了访问某种特定功能的方法和属性。在UEFI中,协议可以被多个Handle实现,每个Handle可以提供一个或多个协议接口。

3、Handle和Protocol之间的关系是多对多的。一个Handle可以提供多个协议接口,每个协议接口都属于不同的协议。同样,一个协议可以被多个Handle实现,每个Handle都提供一个或多个协议接口。

4、在UEFI中,使用HandleProtocol函数可以通过Handle访问一个协议接口。HandleProtocol函数需要指定要访问的协议的GUID和Handle,它会返回一个指向协议接口的指针。通过HandleProtocol函数,可以访问一个Handle提供的所有协议接口。

总的来说,Handle和Protocol是UEFI中的两个重要概念,它们之间的关系是多对多的。Handle表示一个UEFI服务,可以提供多个协议接口,而Protocol是一个标准接口,可以被多个Handle实现。通过HandleProtocol函数可以通过Handle访问一个协议接口。

参考链接:
Handle & Protocol
protocl

  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值