UEFI的Handle和Protocol简单理解

UEFI的Handle和Protocol简单理解3 对 Protocol 的使用 要使用 Protocol 服务 首先要根据 GUID 找到 Protocol 对象 BootService 中提供了几种 Protocol 服务 如下表所示 介绍完了上述几种 Prot

大家好,欢迎来到IT知识分享网。

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

免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://haidsoft.com/154726.html

(0)
上一篇 2025-02-23 21:45
下一篇 2025-02-23 22:00

相关推荐

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注微信