UEFI基础EFI_HANDLE

Posted jiangwei0512

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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?

以上是关于UEFI基础EFI_HANDLE的主要内容,如果未能解决你的问题,请参考以下文章

UEFI 基础教程 (零) - 目录

UEFI 基础教程 (零) - 目录

UEFI 基础教程 (零) - 目录

UEFI 基础教程 (零) - 目录

UEFI 基础教程 (零) - 目录

UEFI基础UEFI事件介绍