【UEFI基础】Protocol介绍

简要说明

Protocol是UEFI中的一个重要概念(事实上《UEFI SPEC》中有超过70%的内容都是在讲Protocol),下面简单说明下:

1. 首先,非常重要的一点,Protocol不是什么特殊的东西,它就是一个结构体,比如说下面是一个用于存储设备访问的Protocol:

///
///  This protocol provides control over block devices.
///
struct _EFI_BLOCK_IO_PROTOCOL {
  ///
  /// The revision to which the block IO interface adheres. All future
  /// revisions must be backwards compatible. If a future version is not
  /// back wards compatible, it is not the same GUID.
  ///
  UINT64              Revision;
  ///
  /// Pointer to the EFI_BLOCK_IO_MEDIA data for this device.
  ///
  EFI_BLOCK_IO_MEDIA  *Media;

  EFI_BLOCK_RESET     Reset;
  EFI_BLOCK_READ      ReadBlocks;
  EFI_BLOCK_WRITE     WriteBlocks;
  EFI_BLOCK_FLUSH     FlushBlocks;

};

2. 其次,Protocol不是UEFI BIOS一开始就可以用的。我们知道UEFI BIOS启动时分为不同的阶段,SEC-PEI-DXE-BDS等等。而Protocol需要等到DXE阶段才可以使用(不需要特别在意DXE阶段的哪个点开始,基本上我们开发时写的DXE模块都可以使用)。

3. UEFI框架下提供了函数来存取Protocol。

4. Protocol的作用跟普通的结构体没有区别,如果存放的是数据就作为存储用,如果存放的是函数指针就用作特定代码执行。

5. UEFI下将大部分的设备初始化和其它功能代码都包装成了一个个的Protocol,所以要学习UEFI,Protocol是必经之路。

Protocol的实现说明

首先一张图说明:

这里需要关注的图中红框部分的内容。

这里实际上是两种链表,一种是Handle的链表,一种是Protocol的链表。Handle其实就是一个不会重复的整型数字,而Protocol在之前已经说过就是一个结构体。各个Handle连接在一起构成一个链表,每个Handle上可以附着若干个不会重复的Protocol。

上述的两种链表交织成了一张网,这张网被称为“Handle Database”。

这个Handle Database会在DXE最开始的地方初始化起来,之后通过接口可以扩展,搜寻等等操作。

关于具体的代码实现这里不再介绍,可以参考DXE阶段开始的代码(DxeMain.c)。

Protocol的使用

在UEFI Boot Service中提供了如下的函数用来操作Protocol:

  //
  // Protocol Handler Services
  //
  EFI_INSTALL_PROTOCOL_INTERFACE    InstallProtocolInterface;
  EFI_REINSTALL_PROTOCOL_INTERFACE  ReinstallProtocolInterface;
  EFI_UNINSTALL_PROTOCOL_INTERFACE  UninstallProtocolInterface;
  EFI_HANDLE_PROTOCOL               HandleProtocol;
  VOID                              *Reserved;
  EFI_REGISTER_PROTOCOL_NOTIFY      RegisterProtocolNotify;
  EFI_LOCATE_HANDLE                 LocateHandle;
  EFI_LOCATE_DEVICE_PATH            LocateDevicePath;
  EFI_INSTALL_CONFIGURATION_TABLE   InstallConfigurationTable;

  //
  // Open and Close Protocol Services
  //
  EFI_OPEN_PROTOCOL                 OpenProtocol;
  EFI_CLOSE_PROTOCOL                CloseProtocol;
  EFI_OPEN_PROTOCOL_INFORMATION     OpenProtocolInformation;

  //
  // Library Services
  //
  EFI_PROTOCOLS_PER_HANDLE          ProtocolsPerHandle;
  EFI_LOCATE_HANDLE_BUFFER          LocateHandleBuffer;
  EFI_LOCATE_PROTOCOL               LocateProtocol;
  EFI_INSTALL_MULTIPLE_PROTOCOL_INTERFACES    InstallMultipleProtocolInterfaces;
  EFI_UNINSTALL_MULTIPLE_PROTOCOL_INTERFACES  UninstallMultipleProtocolInterfaces;

它们可以分为几种不同的类型:

1. 安装和卸载接口,就是这里的IntallXXX,ReinstallXXX,UninstallXXX,IntallMultipleXXX,UninstallMultipleXXX等。

2. 获取和关闭接口,比如HandleProtocol,LocateHandle等等。

3. 其它辅助接口,比如OpenProtocolInformation,RegisterProtocolNotify等,其中RegisterProtocolNotify注册了一个回调函数,当指定的Protocol被安装时,这个回调函数就会被执行。

之后会有实际的代码来介绍如何使用这些接口。

几种特殊的Protocol

UEFI中的Protocol有一些比较特殊的类型,本节将介绍这些Protocol。

Architectural Protocol

UEFI规定了一些Protocol,这些Protocol在UEFI BIOS运行的过程中会安装,且一定需要被安装,如果没有被安装的话,系统就会报错。

这些Protocol如下所示:

//
// DXE Core Global Variables for all of the Architectural Protocols.
// If a protocol is installed mArchProtocols[].Present will be TRUE.
//
// CoreNotifyOnArchProtocolInstallation () fills in mArchProtocols[].Event
// and mArchProtocols[].Registration as it creates events for every array
// entry.
//
EFI_CORE_PROTOCOL_NOTIFY_ENTRY  mArchProtocols[] = {
  { &gEfiSecurityArchProtocolGuid,         (VOID **)&gSecurity,      NULL, NULL, FALSE },
  { &gEfiCpuArchProtocolGuid,              (VOID **)&gCpu,           NULL, NULL, FALSE },
  { &gEfiMetronomeArchProtocolGuid,        (VOID **)&gMetronome,     NULL, NULL, FALSE },
  { &gEfiTimerArchProtocolGuid,            (VOID **)&gTimer,         NULL, NULL, FALSE },
  { &gEfiBdsArchProtocolGuid,              (VOID **)&gBds,           NULL, NULL, FALSE },
  { &gEfiWatchdogTimerArchProtocolGuid,    (VOID **)&gWatchdogTimer, NULL, NULL, FALSE },
  { &gEfiRuntimeArchProtocolGuid,          (VOID **)&gRuntime,       NULL, NULL, FALSE },
  { &gEfiVariableArchProtocolGuid,         (VOID **)NULL,            NULL, NULL, FALSE },
  { &gEfiVariableWriteArchProtocolGuid,    (VOID **)NULL,            NULL, NULL, FALSE },
  { &gEfiCapsuleArchProtocolGuid,          (VOID **)NULL,            NULL, NULL, FALSE },
  { &gEfiMonotonicCounterArchProtocolGuid, (VOID **)NULL,            NULL, NULL, FALSE },
  { &gEfiResetArchProtocolGuid,            (VOID **)NULL,            NULL, NULL, FALSE },
  { &gEfiRealTimeClockArchProtocolGuid,    (VOID **)NULL,            NULL, NULL, FALSE },
  { NULL,                                  (VOID **)NULL,            NULL, NULL, FALSE }
};

这些Protocol都是UEFI或者系统必须的最基础的Protocol,比如说这里的gEfiBdsArchProtocolGuid对应的Protocol,它是BDS阶段的如果,在DXEMain.c中有如下的代码:

  //
  // Transfer control to the BDS Architectural Protocol
  //
  gBds->Entry (gBds);

使DXE阶段过渡到BDS阶段。

Device Path Protocol

Device Path Protocol是一种纯数据的结构体,它表示的是一个设备的可编程路径,可以简称就是Device Path(后面就直接省略掉Protocol)。

这种说法比较抽象,而且这里说的“设备”也并不一定需要是真实的设备,它可以是虚拟设备,甚至可以是一个文件。

Device Path的具体说明有在其它的文章中介绍,这里不做具体的说明。

这里简单介绍一下它的结构体:

/**
  This protocol can be used on any device handle to obtain generic path/location 
  information concerning the physical device or logical device. If the handle does 
  not logically map to a physical device, the handle may not necessarily support 
  the device path protocol. The device path describes the location of the device 
  the handle is for. The size of the Device Path can be determined from the structures 
  that make up the Device Path.
**/
typedef struct {
  UINT8 Type;       ///< 0x01 Hardware Device Path.
                    ///< 0x02 ACPI Device Path.
                    ///< 0x03 Messaging Device Path.
                    ///< 0x04 Media Device Path.
                    ///< 0x05 BIOS Boot Specification Device Path.
                    ///< 0x7F End of Hardware Device Path.
                    
  UINT8 SubType;    ///< Varies by Type
                    ///< 0xFF End Entire Device Path, or
                    ///< 0x01 End This Instance of a Device Path and start a new
                    ///< Device Path.
                    
  UINT8 Length[2];  ///< Specific Device Path data. Type and Sub-Type define
                    ///< type of data. Size of data is included in Length.
                    
} EFI_DEVICE_PATH_PROTOCOL;

它的结构非常的简单,是一个可变长的结构体。

成员包括了一个基本的头部(分为类型,子类型和长度三部分),以及之后的具体类型所需要包含的成员。

Device Path有一个非常重要的作用就是标记对应Handle的属性。

举一个简单的例子,现在有两个硬盘,那么它们都有一个_EFI_BLOCK_IO_PROTOCOL(见开头),然而我们想访问其中一个特定的硬盘,如何找到这个硬件,就可以依赖于Device Path。

以硬盘的Device Path举例,它的类型是HARDWARE_DEVICE_PATH,子类型是HW_CONTROLLER_DP,因此它的Device Path中包含如下的部分:

///
/// Controller Device Path.
///
typedef struct {
  EFI_DEVICE_PATH_PROTOCOL        Header;
  ///
  /// Controller number.
  ///
  UINT32                          ControllerNumber;
} CONTROLLER_DEVICE_PATH;

而两个不同的硬盘,其中的ControllerNumber可能是不同的(根据不同的硬件配置),因此就可以确定到底使用哪个Device Path,最终获取到正确的_EFI_BLOCK_IO_PROTOCOL,大致流程如下:

1. 调用LocateHandleBuffer获取到所有安装了_EFI_BLOCK_IO_PROTOCOL的Handle;

2. 遍历所有的Handle;

3. 根据上述Handle获取到Device Path Protocol,根据这个Device  Path Protocol就能够确定该Handle是否是我们要找的那个Handle;

4. 通过找到的Handle,调用HandleProtocol来到对应的_EFI_BLOCK_IO_PROTOCOL,然后就可以使用这个Protocol来访问硬盘。

以上是使用Device Path Protocol的一个示例。

当然Device Path Protocol的用法还有很多,这个就需要自己摸索了。

EFI_DRIVER_BINDING_PROTOCOL

该类Protocol用在一种叫做UEFI Driver Model类型的驱动中。

这种驱动在DXE阶段安装,但实际使用是在BDS阶段。

这种Protocol的结构如下:

///
/// This protocol provides the services required to determine if a driver supports a given controller. 
/// If a controller is supported, then it also provides routines to start and stop the controller.
///
struct _EFI_DRIVER_BINDING_PROTOCOL {
  EFI_DRIVER_BINDING_SUPPORTED  Supported;
  EFI_DRIVER_BINDING_START      Start;
  EFI_DRIVER_BINDING_STOP       Stop;
  
  ///
  /// The version number of the UEFI driver that produced the
  /// EFI_DRIVER_BINDING_PROTOCOL. This field is used by
  /// the EFI boot service ConnectController() to determine
  /// the order that driver's Supported() service will be used when
  /// a controller needs to be started. EFI Driver Binding Protocol
  /// instances with higher Version values will be used before ones
  /// with lower Version values. The Version values of 0x0-
  /// 0x0f and 0xfffffff0-0xffffffff are reserved for
  /// platform/OEM specific drivers. The Version values of 0x10-
  /// 0xffffffef are reserved for IHV-developed drivers.
  ///
  UINT32                        Version;
  
  ///
  /// The image handle of the UEFI driver that produced this instance
  /// of the EFI_DRIVER_BINDING_PROTOCOL.
  ///
  EFI_HANDLE                    ImageHandle;
  
  ///
  /// The handle on which this instance of the
  /// EFI_DRIVER_BINDING_PROTOCOL is installed. In most
  /// cases, this is the same handle as ImageHandle. However, for
  /// UEFI drivers that produce more than one instance of the
  /// EFI_DRIVER_BINDING_PROTOCOL, this value may not be
  /// the same as ImageHandle.  
  ///
  EFI_HANDLE                    DriverBindingHandle;
};

下面是一个例子:

1. 声明:

//
// Simple Network Protocol Driver Global Variables
//
EFI_DRIVER_BINDING_PROTOCOL gSimpleNetworkDriverBinding = {
  SimpleNetworkDriverSupported,
  SimpleNetworkDriverStart,
  SimpleNetworkDriverStop,
  0xa,
  NULL,
  NULL
};

2. 安装:

EFI_STATUS
EFIAPI
InitializeSnpNiiDriver (
  IN EFI_HANDLE       ImageHandle,
  IN EFI_SYSTEM_TABLE *SystemTable
  )
{
  return EfiLibInstallDriverBindingComponentName2 (
           ImageHandle,
           SystemTable,
           &gSimpleNetworkDriverBinding,
           ImageHandle,
           &gSimpleNetworkComponentName,
           &gSimpleNetworkComponentName2
           );
}

上述代码在Snp.c中,整个模块就是在DXE阶段安装了若干个Protocol(其它的不是很重要)。

这里的EFI_DRIVER_BINDING_PROTOCOL通常是不会通过HandleProtocol等函数去获取的,而是使用下面的接口(也在UEFI Boot Service中):

  //
  // DriverSupport Services
  //
  EFI_CONNECT_CONTROLLER            ConnectController;
  EFI_DISCONNECT_CONTROLLER         DisconnectController;

当调用ConnectController的时候,代码首先会去执行EFI_DRIVER_BINDING_PROTOCOL中的XXXSupported函数,判断传入的Handle是否符合该Protocol执行的要求,如果符合就执行XXXStart函数,否则就直接退出;而DisconnectController会去执行Protocol中的XXXStop函数。

EFI_DRIVER_BINDING_PROTOCOL或者说UEFI Driver Model实现了一种动态的方式来执行代码以完成初始化。

所以EFI_DRIVER_BINDING_PROTOCOL也是比较特殊的一种Protocol。

自己编写Protocol

下面简单的编写一个自己的Protocol,并使用该Protocol。

具体的代码可以在vUDK2017: https://github.com/tianocore/edk2.git Tag vUDK2017.找到。

编写Protocol并安装

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

/**
*  @Package     : BeniPkg
*  @FileName    : HelloWorldProtocol.h
*  @Date        : 20190211
*  @Author      : Wei Jiang
*  @Version     : 0.1
*  @Description :
*    This file describes a protocol for test.
*
*  @History:
*    20190211: Initialize.
*
*  This program and the accompanying materials
*  are licensed and made available under the terms and conditions of the BSD License
*  which accompanies this distribution. The full text of the license may be found at
*  http://opensource.org/licenses/bsd-license.php
*
*  THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS,
*  WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED.
**/

#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_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 } }

否则在其它代码中就无法使用gEfiHelloWorldProtocolGuid。

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

/**
*  @Package     : BeniPkg
*  @FileName    : ProtocolServer.c
*  @Date        : 20190211
*  @Author      : Wei Jiang
*  @Version     : 0.1
*  @Description :
*    Install EFI_HELLO_WORLD_PROTOCOL.
*
*  @History:
*    20190211: Initialize.
*
*  This program and the accompanying materials
*  are licensed and made available under the terms and conditions of the BSD License
*  which accompanies this distribution. The full text of the license may be found at
*  http://opensource.org/licenses/bsd-license.php
*
*  THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS,
*  WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED.
**/

#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;
}

观察Protocol的安装,可以看到这里使用的参数:

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的时候异常挂死;

3. 最后就是对Protocol的使用:

/**
*  @Package     : BeniPkg
*  @FileName    : ProtocolConsumer.c
*  @Date        : 20190211
*  @Author      : Wei Jiang
*  @Version     : 0.1
*  @Description :
*    Use EFI_HELLO_WORLD_PROTOCOL.
*
*  @History:
*    20190211: Initialize.
*
*  This program and the accompanying materials
*  are licensed and made available under the terms and conditions of the BSD License
*  which accompanies this distribution. The full text of the license may be found at
*  http://opensource.org/licenses/bsd-license.php
*
*  THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS,
*  WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED.
**/

#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;
}

关于实现就比较简单了,只是调用LocateProtocol来获取Protocol,然后使用即可。

以上就是对Protocol的一个实现以及简单的使用,对应的模块是ProtocolServer.inf和ProtocolConsumer.inf。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值