【UEFI基础】EFI_HANDLE

EFI_HANDLE

EFI_HANDLE其实就是一个指针:

///
/// A collection of related interfaces.
///
typedef VOID                      *EFI_HANDLE;

下面简单介绍在EDK代码中可能的用法。

ImageHandle

一个普通的DXE模块的入口(以Snp.c举例):

EFI_STATUS
EFIAPI
InitializeSnpNiiDriver (
  IN EFI_HANDLE       ImageHandle,
  IN EFI_SYSTEM_TABLE *SystemTable
  );

这里的SystemTable不解释,但是ImageHandle又是什么呢?

这需要了解DXE Dispatcher里面的操作。

代码要从DxeMain.c中说起:

DxeMain()函数中有如下的代码:

  //
  // Initialize the DXE Dispatcher
  //
  PERF_START (NULL,"CoreInitializeDispatcher", "DxeMain", 0) ;
  CoreInitializeDispatcher ();
  PERF_END (NULL,"CoreInitializeDispatcher", "DxeMain", 0) ;

  //
  // Invoke the DXE Dispatcher
  //
  PERF_START (NULL, "CoreDispatcher", "DxeMain", 0);
  CoreDispatcher ();
  PERF_END (NULL, "CoreDispatcher", "DxeMain", 0);

在CoreDispatcher()函数里面,就会运行各个DXE模块。

最终模块运行起来的代码是:

    Image->Status = Image->EntryPoint (ImageHandle, Image->Info.SystemTable);

这里的ImageHandle是下面的函数传进来的:

EFI_STATUS
EFIAPI
CoreStartImage (
  IN EFI_HANDLE  ImageHandle,
  OUT UINTN      *ExitDataSize,
  OUT CHAR16     **ExitData  OPTIONAL
  )

而这个函数就在CoreDispatcher()中:

        Status = CoreStartImage (DriverEntry->ImageHandle, NULL, NULL);

所以我们实际要找的ImageHandle是DriverEntry的一部分。

原本它的值是NULL:

      //
      // Load the DXE Driver image into memory. If the Driver was transitioned from
      // Untrused to Scheduled it would have already been loaded so we may need to
      // skip the LoadImage
      //
      if (DriverEntry->ImageHandle == NULL && !DriverEntry->IsFvImage) {
        DEBUG ((DEBUG_INFO, "Loading driver %g\n", &DriverEntry->FileName));
        Status = CoreLoadImage (
                        FALSE,
                        gDxeCoreImageHandle,
                        DriverEntry->FvFileDevicePath,
                        NULL,
                        0,
                        &DriverEntry->ImageHandle
                        );

在CoreLoadImage()中会有赋值的操作,再跟进去这个函数:

  Status = CoreLoadImageCommon (
             BootPolicy,
             ParentImageHandle,
             FilePath,
             SourceBuffer,
             SourceSize,
             (EFI_PHYSICAL_ADDRESS) (UINTN) NULL,
             NULL,
             ImageHandle,
             NULL,
             EFI_LOAD_PE_IMAGE_ATTRIBUTE_RUNTIME_REGISTRATION | EFI_LOAD_PE_IMAGE_ATTRIBUTE_DEBUG_IMAGE_INFO_TABLE_REGISTRATION
             );

还需要继续跟进CoreLoadImageCommon()函数:

  //
  // Success.  Return the image handle
  //
  *ImageHandle = Image->Handle;

这里的Image->Handle又是什么?

  //
  // Allocate a new image structure
  //
  Image = AllocateZeroPool (sizeof(LOADED_IMAGE_PRIVATE_DATA));

Image其实就是各个被加载的模块的一个数据表现。

  //
  // Install the protocol interfaces for this image
  // don't fire notifications yet
  //
  Status = CoreInstallProtocolInterfaceNotify (
             &Image->Handle,
             &gEfiLoadedImageProtocolGuid,
             EFI_NATIVE_INTERFACE,
             &Image->Info,
             FALSE
             );

如果一个模块被顺利加载了,就会安装Protocol。上面就是安装一个LoadedImageProtocol的代码。

在这个函数中:

  //
  // If caller didn't supply a handle, allocate a new one
  //
  Handle = (IHANDLE *)*UserHandle;
  if (Handle == NULL) {
Handle = AllocateZeroPool (sizeof(IHANDLE));
  ……
  if (!EFI_ERROR (Status)) {
    //
    // Return the new handle back to the caller
    //
*UserHandle = Handle;

可以看到ImageHandle的最终赋值。

Handle的类型是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;

而ImageHandle的类型其实是VOID*:

///
/// A collection of related interfaces.
///
typedef VOID                      *EFI_HANDLE;

由于空指针能够只想任何结构体,所以这里也没有问题。

从IHANDLE的结构体可以看出,一个ImageHandle连接着所有的Handle,以及它下挂的所有Protocol。

总结:

ImageHandle是DXE模块对应的数据结构中的一个参数,在该模块运行的过程中,会在这个ImageHandle上安装一系列的接口(就是Protocol)。而这个ImageHandle本身会在整个UEFI运行过程中被放在一个EFI_HANDLE数据库中。

因此在某个DXE模块中实现的接口(Protocol),可以通过一系列的函数,先找到ImageHandle,再通过它找到对应的Protocol,然后在其它的地方调用这些Protocol。

这里举一个例子,下面是代码,可以在Shell下运行(可以在https://code.csdn.net/jiangwei0512/edk2-udk2017.git的BeniTest3.c中看到具体的代码):

  // Get a handle, just find one, use it free.
  //
  Status = gBS->LocateHandleBuffer (
                 ByProtocol,
                 &gEfiSimpleTextOutProtocolGuid,
                 NULL,
                 &Handles,
                 &Buffer
                 );
  if (EFI_ERROR (Status)) {
    Print (L"LocateHandleBuffer failed. - %r\r\n", Status);
    goto Error;
  }

  if (0 < Handles) {
    Status = gBS->HandleProtocol (
                   Buffer[0],
                   &gEfiSimpleTextOutProtocolGuid,
                   (VOID **)&Protocol
                   );
    if (!EFI_ERROR (Status)) {
      Print (L"Hanlde: 0x%08x.\r\n", Buffer[0]);
    }
  }

这里就是获取到一个Handle,然后打印它的值,结果如下:

查看这个位置的值:

前面4个字节就是IHANDLE的Signature。

所以实际上的Handle就只是指向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;

可以看到AllHandles这个链表,它其实是将所有的Handle连接在了一起,而Protocols也是个链表,它表示了所有安装在该Handle上的Protocols,最终就形成了如下的样子:

 Controller

同样以Snp.c举例,其中包含着叫做UEFI Driver Model的东西,它的入口也有一个类型为EFI_HANDLE的参数:

EFI_STATUS
EFIAPI
SimpleNetworkDriverStart (
  IN EFI_DRIVER_BINDING_PROTOCOL    *This,
  IN EFI_HANDLE                     Controller,
  IN EFI_DEVICE_PATH_PROTOCOL       *RemainingDevicePath
  );

这个函数是通过gBS->ConnectController()来调用的:

  //
  //Perform Connect
  //
  HandleCount = 0;
  while (1) {
    OldHandleCount = HandleCount;
    Status = gBS->LocateHandleBuffer (
                    AllHandles,
                    NULL,
                    NULL,
                    &HandleCount,
                    &HandleBuffer
                    );
    if (EFI_ERROR (Status)) {
      break;
    }

    if (HandleCount == OldHandleCount) {
      break;
    }

    for (Index = 0; Index < HandleCount; Index++) {
      gBS->ConnectController (HandleBuffer[Index], NULL, NULL, TRUE);
    }
  }

以上代码在BDS阶段执行,这里只是截取了一种形式,即获取DXE阶段安装的所有ImageHandle并作为参数传入。

实际上还有其它的方式,不过上面这种应该是最全面的了。

下面就需要看一下ConnectController()函数的实现,这个函数也是在DxeMain.c中初始化的:

  (EFI_CONNECT_CONTROLLER)                      CoreConnectController,                    // ConnectController
  //
  // Connect all drivers to ControllerHandle
  // If CoreConnectSingleController returns EFI_NOT_READY, then the number of
  // Driver Binding Protocols in the handle database has increased during the call
  // so the connect operation must be restarted
  //
  do {
    ReturnStatus = CoreConnectSingleController (
                     ControllerHandle,
                     DriverImageHandle,
                     AlignedRemainingDevicePath
                     );
  } while (ReturnStatus == EFI_NOT_READY);

而在CoreConnectSingleController()函数中:

          Status = DriverBinding->Start (
                                    DriverBinding,
                                    ControllerHandle,
                                    RemainingDevicePath
                                    );

这里的ControllerHandle就是gBS->ConnectController()函数中传入的第一个参数。

也就是说ControllerHandle可以是ImageHandle。

当然由于并不是所有的DXE模块都有安装UEFIDriver Model所有并不是所有ImageHandle都是ControllerHandle。

另外还需要说明,UEFI Driver Model对应一个EFI_DRIVER_BINDING_PROTOCOL:

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

也就是SimpleNetworkDriverStart()函数的第一个参数。

这里还需要说一下SimpleNetworkDriverSupported()函数。它是一个条件判断函数,只有其中的函数满足了要求,才会运行对应的Start()函数,比如:

EFI_STATUS
EFIAPI
SimpleNetworkDriverSupported (
  IN EFI_DRIVER_BINDING_PROTOCOL    *This,
  IN EFI_HANDLE                     Controller,
  IN EFI_DEVICE_PATH_PROTOCOL       *RemainingDevicePath
  )
{
  EFI_STATUS                                Status;
  EFI_NETWORK_INTERFACE_IDENTIFIER_PROTOCOL *NiiProtocol;
  PXE_UNDI                                  *Pxe;

  Status = gBS->OpenProtocol (
                  Controller,
                  &gEfiDevicePathProtocolGuid,
                  NULL,
                  This->DriverBindingHandle,
                  Controller,
                  EFI_OPEN_PROTOCOL_TEST_PROTOCOL
                  );
  if (EFI_ERROR (Status)) {
    return Status;
  }

  Status = gBS->OpenProtocol (
                  Controller,
                  &gEfiNetworkInterfaceIdentifierProtocolGuid_31,
                  (VOID **) &NiiProtocol,
                  This->DriverBindingHandle,
                  Controller,
                  EFI_OPEN_PROTOCOL_BY_DRIVER
                  );

这里的Controller需要安装了DevicePathProtocol和NetworkInterfaceIdentifierProtocol这两个Protocol。

因此就需要在之前的某个模块中,比如某个网卡模块,它需要安装了这两个协议,然后才能使网卡模块对应的EFI_HANDLE之上安装SNP这样的网络传输协议。

有个问题?

网卡运行起来后安装NetworkInterfaceIdentifierProtocol是没有问题的,但是DevicePathProtocol需要在什么时候安装呢?PCI扫描的时候?

可能也是在网卡驱动运行的时候吧……

需要有源代码才好。

据我所知,网卡驱动也是UEFI Driver Model。

说到DevicePathProtocol,就需要提供了另一种gBS->ConnectController()的调用方式了:

  Status = gBS->LocateDevicePath (
                  &gEfiDevicePathProtocolGuid,
                  &TempPciDevicePath,
                  &PciDeviceHandle
                  );
  if (EFI_ERROR (Status)) {
    return Status;
  }
  gBS->ConnectController (PciDeviceHandle, NULL, MyDevicePath, FALSE);

这里就是根据安装有DevicePathProtocol的EFI_HANDLE来ConnectController。

追踪代码可以看到在PCI扫描的时候有:

RegisterPciDevice (
  IN  EFI_HANDLE          Controller,
  IN  PCI_IO_DEVICE       *PciIoDevice,
  OUT EFI_HANDLE          *Handle      OPTIONAL
  )
{
  EFI_STATUS          Status;
  VOID                *PlatformOpRomBuffer;
  UINTN               PlatformOpRomSize;
  UINT8               PciExpressCapRegOffset;
  EFI_PCI_IO_PROTOCOL *PciIo;
  UINT8               Data8;
  BOOLEAN             HasEfiImage;

  //
  // Install the pciio protocol, device path protocol
  //
  Status = gBS->InstallMultipleProtocolInterfaces (
                  &PciIoDevice->Handle,
                  &gEfiDevicePathProtocolGuid,
                  PciIoDevice->DevicePath,
                  &gEfiPciIoProtocolGuid,
                  &PciIoDevice->PciIo,
                  NULL
                  );
  if (EFI_ERROR (Status)) {
    return Status;
  }

所以在PCI扫描的时候,也会为设备创建EFI_HANDLE并安装对应的DevicePathProtocol。

总结:

ControllerHandle有两个主要来源,一个是ImageHandle,另外一个就是PCI扫描的时候创建的EFI_HANDLE。

其它还有没有不是很确定了。比如ChildHandle,不知道是不是也有一部分可以做ControllerHandle?

/** @file This sample application bases on HelloWorld PCD setting to print "UEFI Hello World!" to the UEFI Console. Copyright (c) 2006 - 2018, Intel Corporation. All rights reserved.<BR> SPDX-License-Identifier: BSD-2-Clause-Patent **/ #include <Uefi.h> #include <Library/PcdLib.h> #include <Library/UefiLib.h> #include <Library/UefiApplicationEntryPoint.h> // // String token ID of help message text. // Shell supports to find help message in the resource section of an application image if // .MAN file is not found. This global variable is added to make build tool recognizes // that the help string is consumed by user and then build tool will add the string into // the resource section. Thus the application can use '-?' option to show help message in // Shell. // GLOBAL_REMOVE_IF_UNREFERENCED EFI_STRING_ID mStringHelpTokenId = STRING_TOKEN (STR_HELLO_WORLD_HELP_INFORMATION); /** The user Entry Point for Application. The user code starts with this function as the real entry point for the application. @param[in] ImageHandle The firmware allocated handle for the EFI image. @param[in] SystemTable A pointer to the EFI System Table. @retval EFI_SUCCESS The entry point is executed successfully. @retval other Some error occurs when executing this entry point. **/ EFI_STATUS EFIAPI UefiMain ( IN EFI_HANDLE ImageHandle, IN EFI_SYSTEM_TABLE *SystemTable ) { UINT32 Index; Index = 0; // // Three PCD type (FeatureFlag, UINT32 and String) are used as the sample. // if (FeaturePcdGet (PcdHelloWorldPrintEnable)) { for (Index = 0; Index < PcdGet32 (PcdHelloWorldPrintTimes); Index++) { // // Use UefiLib Print API to print string to UEFI console // Print ((CHAR16 *)PcdGetPtr (PcdHelloWorldPrintString)); } } return EFI_SUCCESS; } /**UefiMain ( IN EFI_HANDLE ImageHandle, IN EFI_SYSTEM_TABLE *SystemTable ) { print(L"hello uefi\n"); return EFI_SUCCESS; }**/
最新发布
03-22
<think>好的,我现在需要帮用户分析他们提供的这段UEFI应用程序代码。首先,我得仔细阅读代码,理解它的结构和功能。看起来这是一个基于EDK II的简单“Hello World”程序,但用户可能在尝试修改或学习如何开发UEFI应用。 首先,用户提供的代码有两个部分:一个是正式的UefiMain函数,另一个是被注释掉的另一个UefiMain实现。这可能是个错误,因为重复定义函数会导致编译失败。我需要指出这一点,并解释正确的结构。 接下来,原代码使用了PCD(平台配置数据库)来控制打印行为和次数。FeaturePcdGet检查是否启用打印,PcdGet32获取打印次数,PcdGetPtr获取字符串。这些是EDK II中常用的配置方法,用户可能需要了解PCD的作用和使用方式。 被注释掉的代码部分直接使用print函数输出字符串,这可能是一个更简单的示例,但不符合原代码利用PCD的设计。需要比较这两种方法的区别,说明为何原代码采用PCD,比如灵活性、可配置性等。 然后,代码中的GLOBAL_REMOVE_IF_UNREFERENCED宏用于帮助构建工具识别未引用的变量,避免被优化掉,确保帮助信息能正确嵌入资源段。用户可能对这个宏的作用不太清楚,需要解释。 此外,代码结构包括必要的头文件和入口点声明。UEFI应用的入口点通常是UefiMain,通过Library/UefiApplicationEntryPoint.h定义。需要强调入口点的正确声明方式。 用户可能的疑问点包括:PCD的工作原理,如何配置不同的PCD类型(FeatureFlag、UINT32、String),以及如何在实际项目中修改这些配置。此外,他们可能对代码中的字符串处理(CHAR16指针转换)和Print函数的使用有疑问。 最后,需要指出被注释的代码存在的语法问题,比如重复的函数定义,并建议正确的做法。同时,总结这段代码的教学目的,即展示PCD的使用和UEFI应用的基本结构,帮助用户理解如何扩展和调试自己的UEFI应用。</think>这个代码片段是一个基于EDK II框架的UEFI应用程序示例,包含正式实现和注释掉的实验代码。以下是逐步解析: 一、代码结构分析 $$#include <Uefi.h> \quad // UEFI基础头文件$$ $$#include <Library/PcdLib.h> \quad // PCD操作库$$ $$#include <Library/UefiLib.h> \quad // UEFI输出库$$ $$#include <Library/UefiApplicationEntryPoint.h> \quad // 入口点声明$$ 二、核心函数解析 1. 合法入口函数实现: ```c EFI_STATUS EFIAPI UefiMain( IN EFI_HANDLE ImageHandle, // 镜像句柄 IN EFI_SYSTEM_TABLE *SystemTable // 系统表指针 ) { UINT32 Index = 0; if (FeaturePcdGet(PcdHelloWorldPrintEnable)) { for (Index = 0; Index < PcdGet32(PcdHelloWorldPrintTimes); Index++) { Print((CHAR16 *)PcdGetPtr(PcdHelloWorldPrintString)); // Unicode字符串输出 } } return EFI_SUCCESS; } ``` 2. 被注释的错误实现(语法错误示例): ```c /**UefiMain (...) { print(L"hello uefi\n"); // 错误1:函数重复定义 return EFI_SUCCESS; // 错误2:print应为Print }**/ ``` 三、关键技术点 1. PCD使用三重奏: - $FeaturePcdGet()$ 检查特性开关 - $PcdGet32()$ 获取32位整数值 - $PcdGetPtr()$ 获取字符串指针 2. 资源管理: $$GLOBAL\_REMOVE\_IF\_UNREFERENCED\ EFI\_STRING\_ID$$ 此宏保证帮助信息即使未被显式引用,仍会保留在资源段中 四、工作流程 $$ \begin{aligned} 1.\ & \text{检查PcdHelloWorldPrintEnable标志位} \\ 2.\ & \text{获取PcdHelloWorldPrintTimes循环次数} \\ 3.\ & \text{循环输出PcdHelloWorldPrintString指定内容} \\ 4.\ & \text{返回EFI\_SUCCESS(0)} \end{aligned} $$ 五、对比分析 | 合法实现 | 注释代码 | |---------|---------| | 使用PCD控制行为 | 硬编码输出 | | 支持动态配置 | 固定输出 | | 符合EDK II规范 | 语法错误 | | 可扩展性强 | 无法编译 | 建议开发时: 1. 保持单一入口函数 2. 优先使用PCD进行配置 3. 正确使用Print()而非print 4. 注意Unicode字符串处理(CHAR16*类型) 这个示例展示了UEFI应用开发的核心模式:通过系统表访问服务、使用PCD进行灵活配置、遵循EDK II编码规范。被注释的代码则演示了常见的新手错误,具有对比教学意义。
评论 27
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值