UEFI实战DXE驱动相关

Posted jiangwei0512

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了UEFI实战DXE驱动相关相关的知识,希望对你有一定的参考价值。

简介

DXE阶段运行的驱动有两种类型,这里要说的不是Boot Service Driver和Runtime Service Driver(还不是很清除如何自己实现一个Runtime Service Driver以及如何使用,以后有机会的话再写吧)。

而是根据是否满足UEFI Driver Model来区分,一种是普通的Driver,一种就是满足UEFI Driver Model的驱动。

简单的说,前者是再编写驱动的时候就主动去寻找设备并初始化它,而后者是系统服务自己根据设备来寻到到合适的驱动然后初始化。前者的实现一般在驱动运行的时候就直接完成了,而后者需要先注册驱动,然后再后续(通常是BDS阶段)通过调用系统服务来完成,这个系统复位就是EFI_BOOT_SERVICES.ConnectController()。

下图是两种类型执行的简单图示:

(示例代码可以在vUDK2017: https://github.com/tianocore/edk2.git Tag vUDK2017.找到)

实现

两种类型驱动的代码结构上类似,主要的区别是驱动的入口做了什么。

下面是一个驱动的inf文件的定义部分:

[Defines]
  INF_VERSION                    = 0x00010005
  BASE_NAME                      = DxeDriverInBds
  FILE_GUID                      = 04687443-0174-498F-A2F9-08F3A5363F84
  MODULE_TYPE                    = UEFI_DRIVER
  VERSION_STRING                 = 1.0
  ENTRY_POINT                    = DxeDriverEntry

最后一行是C代码的入口,对于普通的驱动,这个入口里面就是初始化设备的函数。

而对于符合UEFI Driver Model的驱动来说,它只是简单的安装了一个Protocol,以SnpDxe.inf模块为例:

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

  return EfiLibInstallDriverBindingComponentName2 (
           ImageHandle,
           SystemTable,
           &gSimpleNetworkDriverBinding,
           ImageHandle,
           &gSimpleNetworkComponentName,
           &gSimpleNetworkComponentName2
           );

这里的重点在于gSimpleNetworkDriverBinding这个Protocol,它的形式如下:

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

所有的符合UEFI Driver Model的驱动都会安装一个如上结构的Protocol,在《UEFI Spec》里面有对该类型Protocol的详细介绍。

简单来说,就是DXE阶段安装了一大堆这种Protocol,然后gBS->ConnectController的时候,首先会执行xxxSupported()函数,如果返回的是EFI_SUCCESS,则会继续执行xxxStart()函数,而这个函数中就包含设备初始化所需要的代码。

还是以SnpDxe.inf模块为例,它的xxxSupported()函数如下:

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
                  );

  if (EFI_ERROR (Status)) 
    if (Status == EFI_ALREADY_STARTED) 
      DEBUG ((EFI_D_INFO, "Support(): Already Started. on handle %p\\n", Controller));
    
    return Status;
  

  DEBUG ((EFI_D_INFO, "Support(): UNDI3.1 found on handle %p\\n", Controller));

  //
  // check the version, we don't want to connect to the undi16
  //
  if (NiiProtocol->Type != EfiNetworkInterfaceUndi) 
    Status = EFI_UNSUPPORTED;
    goto Done;
  
  //
  // Check to see if !PXE structure is valid. Paragraph alignment of !PXE structure is required.
  //
  if ((NiiProtocol->Id & 0x0F) != 0) 
    DEBUG ((EFI_D_NET, "\\n!PXE structure is not paragraph aligned.\\n"));
    Status = EFI_UNSUPPORTED;
    goto Done;
  

  Pxe = (PXE_UNDI *) (UINTN) (NiiProtocol->Id);

  //
  //  Verify !PXE revisions.
  //
  if (Pxe->hw.Signature != PXE_ROMID_SIGNATURE) 
    DEBUG ((EFI_D_NET, "\\n!PXE signature is not valid.\\n"));
    Status = EFI_UNSUPPORTED;
    goto Done;
  

  if (Pxe->hw.Rev < PXE_ROMID_REV) 
    DEBUG ((EFI_D_NET, "\\n!PXE.Rev is not supported.\\n"));
    Status = EFI_UNSUPPORTED;
    goto Done;
  

  if (Pxe->hw.MajorVer < PXE_ROMID_MAJORVER) 

    DEBUG ((EFI_D_NET, "\\n!PXE.MajorVer is not supported.\\n"));
    Status = EFI_UNSUPPORTED;
    goto Done;

   else if (Pxe->hw.MajorVer == PXE_ROMID_MAJORVER && Pxe->hw.MinorVer < PXE_ROMID_MINORVER) 
    DEBUG ((EFI_D_NET, "\\n!PXE.MinorVer is not supported."));
    Status = EFI_UNSUPPORTED;
    goto Done;
  
  //
  // Do S/W UNDI specific checks.
  //
  if ((Pxe->hw.Implementation & PXE_ROMID_IMP_HW_UNDI) == 0) 
    if (Pxe->sw.EntryPoint < Pxe->sw.Len) 
      DEBUG ((EFI_D_NET, "\\n!PXE S/W entry point is not valid."));
      Status = EFI_UNSUPPORTED;
      goto Done;
    

    if (Pxe->sw.BusCnt == 0) 
      DEBUG ((EFI_D_NET, "\\n!PXE.BusCnt is zero."));
      Status = EFI_UNSUPPORTED;
      goto Done;
    
  

  Status = EFI_SUCCESS;
  DEBUG ((EFI_D_INFO, "Support(): supported on %p\\n", Controller));

Done:
  gBS->CloseProtocol (
        Controller,
        &gEfiNetworkInterfaceIdentifierProtocolGuid_31,
        This->DriverBindingHandle,
        Controller
        );

  return Status;

大致的流程如下:

1. 当扫描的这个设备的时候(设备用Controller表示),先判断它是否安装了DevicePathProtocol,没有就表示这个设备还没有准备好(或者说不是设备),后面的xxxStart()不用执行;

2. 然后判断NetworkInterfaceIdentifierProtocol是否安装,这个是网卡驱动一定会装的Protocol,Snp驱动底层的操作需要依赖于它,所以一定要安装,如果没有就不会执行后面的操作;

3. 判断NetworkInterfaceIdentifierProtocol是否满足要求,如果不满足则不会执行xxxStart()函数。

如果以上条件都满足,就可以认为该设备是一个网卡,然后这个驱动就会被执行,而之前获取到的DevicePathProtocol和NetworkInterfaceIdentifierProtocol就会成为操作正确设备的基础。

几个问题

下面说明几个常会问到的问题。

DXE驱动是在哪里开始的?

驱动被调用的基本函数是LoadImage()和StartImage(),它们是Boot Service,所以可以在DXE和BDS阶段的大部分地方调用。

在DxeMain.c里面的DxeMain()函数,里面的CoreDispatcher()就是用来执行各个驱动的,除非自己写代码,否则DXE驱动都会在这个位置执行。

UEFI Driver Model类型的驱动是在哪里执行的?

之前已经提到,ConnectController()函数里面会执行驱动的xxxSupported()函数,对应的真正的调用位置如下:

  do 

    //
    // Loop through the sorted Driver Binding Protocol Instances in order, and see if
    // any of the Driver Binding Protocols support the controller specified by
    // ControllerHandle.
    //
    DriverBinding = NULL;
    DriverFound = FALSE;
    for (Index = 0; (Index < NumberOfSortedDriverBindingProtocols) && !DriverFound; Index++) 
      if (SortedDriverBindingProtocols[Index] != NULL) 
        DriverBinding = SortedDriverBindingProtocols[Index];
        PERF_START (DriverBinding->DriverBindingHandle, "DB:Support:", NULL, 0);
        Status = DriverBinding->Supported(
                                  DriverBinding,
                                  ControllerHandle,
                                  RemainingDevicePath
                                  );
        PERF_END (DriverBinding->DriverBindingHandle, "DB:Support:", NULL, 0);
        if (!EFI_ERROR (Status)) 
          SortedDriverBindingProtocols[Index] = NULL;
          DriverFound = TRUE;

          //
          // A driver was found that supports ControllerHandle, so attempt to start the driver
          // on ControllerHandle.
          //
          PERF_START (DriverBinding->DriverBindingHandle, "DB:Start:", NULL, 0);
          Status = DriverBinding->Start (
                                    DriverBinding,
                                    ControllerHandle,
                                    RemainingDevicePath
                                    );
          PERF_END (DriverBinding->DriverBindingHandle, "DB:Start:", NULL, 0);

          if (!EFI_ERROR (Status)) 
            //
            // The driver was successfully started on ControllerHandle, so set a flag
            //
            OneStarted = TRUE;
          
        
      
    
   while (DriverFound);

在BDS阶段,有不少位置可以调用,这里不一一说明。

另外,在UEFI  Shell下有一个connect命令:

配合load命令可以手动到导入驱动和初始化设备。

如果有一个普通的DXE驱动以来与一个UEFI Driver Model类型的驱动要怎么处理?

之前一直以为普通的DXE驱动只能在DXE阶段运行,但是实际上普通的DXE驱动也可以在BDS阶段运行,只要写好依赖就行。

比如某个驱动依赖与gEfiPciIoProtocolGuid,而这个Protocol是在BDS阶段的PCIE扫描的时候安装的,只要在INF中加上这个依赖,那么它就会在BDS阶段运行,比如DxeDriverInBds.inf驱动:

[Defines]
  INF_VERSION                    = 0x00010005
  BASE_NAME                      = DxeDriverInBds
  FILE_GUID                      = 04687443-0174-498F-A2F9-08F3A5363F84
  MODULE_TYPE                    = UEFI_DRIVER
  VERSION_STRING                 = 1.0
  ENTRY_POINT                    = DxeDriverInBdsEntry

#
# The following information is for reference only and not required by the build tools.
#
#  VALID_ARCHITECTURES           = IA32 X64 IPF EBC
#

[Sources.common]
  DxeDriverInBds.c
  DxeDriverInBds.h

[Packages]
  MdePkg/MdePkg.dec
  OemPkg/OemPkg.dec

[LibraryClasses]
  UefiLib
  BaseLib
  BaseMemoryLib
  MemoryAllocationLib
  UefiBootServicesTableLib
  UefiRuntimeServicesTableLib
  UefiDriverEntryPoint
  DebugLib

[Guids]

[Protocols]
  gEfiPciIoProtocolGuid                  ## CONSUMES

[Depex]
  gEfiPciIoProtocolGuid

它增加了对PciIoProtocol的依赖,对应的代码如下:

EFI_STATUS
EFIAPI
DxeDriverInBdsEntry (
  IN EFI_HANDLE           ImageHandle,
  IN EFI_SYSTEM_TABLE     *SystemTable
  )

  EFI_STATUS              Status;
  EFI_PCI_IO_PROTOCOL     *PciIo;
  EFI_HANDLE              *HandleArray;
  UINTN                   HandleArrayCount;
  UINTN                   Index;
  UINT16                  VendorId;
  UINT16                  DeviceId;
  
  DEBUG ((EFI_D_ERROR, "[beni]DxeDriverInBdsEntry start.\\n"));
  
  Status = gBS->LocateHandleBuffer (
                  ByProtocol,
                  &gEfiPciIoProtocolGuid,
                  NULL,
                  &HandleArrayCount,
                  &HandleArray
                  );
  if (EFI_ERROR (Status)) 
    DEBUG ((EFI_D_ERROR, "[beni]LocateHandleBuffer failed. - %r\\n", Status));
    return Status;
  

  for (Index = 0; Index < HandleArrayCount; Index++) 
    Status = gBS->HandleProtocol (
                    HandleArray[Index],
                    &gEfiPciIoProtocolGuid,
                    (VOID **)&PciIo
                    );
    if (!EFI_ERROR (Status)) 
      Status = PciIo->Pci.Read (
                      PciIo,
                      EfiPciIoWidthUint16,
                      0x00,
                      1,
                      &VendorId
                      );
      if (EFI_ERROR (Status)) 
        continue;
      
      
      Status = PciIo->Pci.Read (
                      PciIo,
                      EfiPciIoWidthUint16,
                      0x02,
                      1,
                      &DeviceId
                      );
      if (EFI_ERROR (Status)) 
        continue;
      

      DEBUG ((EFI_D_ERROR, "[beni]VendorId: 0x%04x, DeviceId: 0x%04x.\\n", VendorId, DeviceId));
    
  
  
  DEBUG ((EFI_D_ERROR, "[beni]DxeDriverInBdsEntry end.\\n"));

  if (NULL != HandleArray) 
    FreePool (HandleArray);
    HandleArray = NULL;
  
  
  return EFI_SUCCESS;

QEMU下运行的串口打印如下:

[beni]DxeDriverInBdsEntry start.
[beni]VendorId: 0x8086, DeviceId: 0x1237.
[beni]VendorId: 0x8086, DeviceId: 0x7000.
[beni]VendorId: 0x8086, DeviceId: 0x7010.
[beni]VendorId: 0x8086, DeviceId: 0x7113.
[beni]VendorId: 0x1234, DeviceId: 0x1111.
[beni]VendorId: 0x8086, DeviceId: 0x100E.
[beni]DxeDriverInBdsEntry end.

可以看到它能够正常运行,能够在BDS阶段运行并找到PciIoProtocol。

BDS阶段如何调用到普通的DXE驱动?

接前一个问题。

因为BDS阶段调用到了普通的DXE驱动,那么到底是在什么地方调用的呢?

查看串口信息:

Terminal - Mode 0, Column = 80, Row = 25
Terminal - Mode 1, Column = 80, Row = 50
Terminal - Mode 2, Column = 100, Row = 31
[2J[01;01H[=3h[2J[01;01H[2J[01;01H[=3h[2J[01;01H
InstallProtocolInterface: 09576E91-6D3F-11D2-8E39-00A0C969723B 7101C18
[2J[01;01H[=3h[2J[01;01H[2J[01;01H[=3h[2J[01;01H
InstallProtocolInterface: 387477C1-69C7-11D2-8E39-00A0C969723B 71013C0
InstallProtocolInterface: DD9E7534-7762-4698-8C14-F58517A625AA 71014A0
InstallProtocolInterface: 387477C2-69C7-11D2-8E39-00A0C969723B 71013D8
InstallProtocolInterface: D3B36F2B-D551-11D4-9A46-0090273FC14D 0
InstallProtocolInterface: D3B36F2C-D551-11D4-9A46-0090273FC14D 0
InstallProtocolInterface: D3B36F2D-D551-11D4-9A46-0090273FC14D 0
[2J[01;01H[=3h[2J[01;01H[2J[01;01H[=3h[2J[01;01H[0m[35m[40m
Loading driver 04687443-0174-498F-A2F9-08F3A5363F84
InstallProtocolInterface: 5B1B31A1-9562-11D2-8E3F-00A0C969723B 70FE0C0
Loading driver at 0x000047CB000 EntryPoint=0x000047CB374 DxeDriverInBds.efi
InstallProtocolInterface: BC62157E-3E33-4FEC-9920-2D3B36D750DF 70FEC98
[beni]DxeDriverInBdsEntry start.

从上面的Loading driver可以看到,调用的函数是gDS->Dispatch()。

追代码可以看到,对于OVMF里面,调用的路径如下:

MdeModulePkg\\Universal\\BdsDxe\\BdsDxe.inf --->
BdsEntry() --->
EfiBootManagerConnectAllDefaultConsoles() --->
EfiBootManagerConnectConsoleVariable() --->
EfiBootManagerConnectDevicePath()

以上是关于UEFI实战DXE驱动相关的主要内容,如果未能解决你的问题,请参考以下文章

UEFI学习-创建一个dxe driver-UDK2017

UEFI.源码分析.DXE的异步事件服务.第一部分.事件驱动

UEFI.源码分析.DXE的异步事件服务.第一部分.事件驱动

UEFI.源码分析.DXE的异步事件服务.第二部分.任务优先级

UEFI.源码分析.DXE的异步事件服务.第二部分.任务优先级

UEFI.源码分析.DXE的异步事件服务.第三部分.定时器与时钟中断