【UEFI基础】BIOS模块执行的优先级

综述

BIOS下主要通过两种方式来确定一般模块的优先级,一种是fdf文件中指定的优先级,另一种是inf文件中指定的优先级。需要注意这里使用了“一般模块”的说法,因为有些模块(尤其是PEI_CORE,DXE_CORE类型的模块)总是会先执行的,事实上就是因为这些优先执行的模块在控制一般模块的优先级。

fdf中的优先级

APRIORI

fdf中的优先级通过特殊的标识来说明,下面是一个例子:

[FV.DXEFV]
# 中间略

APRIORI DXE {
  INF  MdeModulePkg/Universal/DevicePathDxe/DevicePathDxe.inf
  INF  MdeModulePkg/Universal/PCD/Dxe/Pcd.inf
  # AmdSevDxe must be loaded before TdxDxe. Because in SEV guest AmdSevDxe
  # driver performs a MemEncryptSevClearMmioPageEncMask() call against the
  # PcdPciExpressBaseAddress range to mark it shared/unencrypted.
  # Otherwise #VC handler terminates the guest for trying to do MMIO to an
  # encrypted region (Since the range has not been marked shared/unencrypted).
  INF  OvmfPkg/AmdSevDxe/AmdSevDxe.inf
  INF  OvmfPkg/TdxDxe/TdxDxe.inf
!if $(SMM_REQUIRE) == FALSE
  INF  OvmfPkg/QemuFlashFvbServicesRuntimeDxe/FvbServicesRuntimeDxe.inf
!endif
}

这里的APRIORI就指定了需要优先执行的模块。

在编译时这个部分会被组成一个Firmware File,上例中可以从DXEFV中找到这个Firmware File,下面是该文件中的实际数据:

在这里插入图片描述

这些数据实际上是一个个的GUID,来自包含模块中的inf文件中的FILE_GUID

[Defines]
  INF_VERSION                    = 0x00010005
  BASE_NAME                      = DevicePathDxe
  MODULE_UNI_FILE                = DevicePathDxe.uni
  FILE_GUID                      = 9B680FCE-AD6B-4F3A-B60B-F59899003443 # Firmware File中包含的GUID
  MODULE_TYPE                    = DXE_DRIVER
  VERSION_STRING                 = 1.0
  ENTRY_POINT                    = DevicePathEntryPoint

而这个Firmware File本身也有一个GUID:

在这里插入图片描述

这个GUID实际上是固定的,定义在MdePkg\Include\Guid\Apriori.h中:

#define EFI_APRIORI_GUID \
  { \
    0xfc510ee7, 0xffdc, 0x11d4, {0xbd, 0x41, 0x0, 0x80, 0xc7, 0x3c, 0x88, 0x81 } \
  }
extern EFI_GUID gAprioriGuid;

这个gAprioriGuid将会在代码中进一步使用,来获取上面提到的APRIORI文件中的GUID,以确定哪些模块需要优先执行。

代码处理gAprioriGuid

相关代码可以在edk2\MdeModulePkg\Core\Dxe\DxeMain.inf中的CoreDispatcher()找到:

    //
    // Read the array of GUIDs from the Apriori file if it is present in the firmware volume
    //
    AprioriFile = NULL;
    Status      = Fv->ReadSection (
                        Fv,
                        &gAprioriGuid,
                        EFI_SECTION_RAW,
                        0,
                        (VOID **)&AprioriFile,
                        &SizeOfBuffer,
                        &AuthenticationStatus
                        );
    if (!EFI_ERROR (Status)) {
      AprioriEntryCount = SizeOfBuffer / sizeof (EFI_GUID);
    } else {
      AprioriEntryCount = 0;
    }

    //
    // Put drivers on Apriori List on the Scheduled queue. The Discovered List includes
    // drivers not in the current FV and these must be skipped since the a priori list
    // is only valid for the FV that it resided in.
    //

    for (Index = 0; Index < AprioriEntryCount; Index++) {
      for (Link = mDiscoveredList.ForwardLink; Link != &mDiscoveredList; Link = Link->ForwardLink) {
        DriverEntry = CR (Link, EFI_CORE_DRIVER_ENTRY, Link, EFI_CORE_DRIVER_ENTRY_SIGNATURE);
        if (CompareGuid (&DriverEntry->FileName, &AprioriFile[Index]) &&
            (FvHandle == DriverEntry->FvHandle))
        {
          CoreAcquireDispatcherLock ();
          DriverEntry->Dependent = FALSE;
          DriverEntry->Scheduled = TRUE;
          InsertTailList (&mScheduledQueue, &DriverEntry->ScheduledLink);
          CoreReleaseDispatcherLock ();
          DEBUG ((DEBUG_DISPATCH, "Evaluate DXE DEPEX for FFS(%g)\n", &DriverEntry->FileName));
          DEBUG ((DEBUG_DISPATCH, "  RESULT = TRUE (Apriori)\n"));
          break;
        }
      }
    }

代码也非常的简单:

  • 获取GUID。
  • 遍历GUID。
  • 遍历找到的所有模块,与指定的GUID匹配,如果匹配到了就放到mScheduledQueue

以上只是第一步,即存放优先模块,在edk2\MdeModulePkg\Core\Dxe\Dispatcher\Dispatcher.c文件的头部对应如下的说明:

Step #1 - When a FV protocol is added to the system every driver in the FV
is added to the mDiscoveredList. The SOR, Before, and After Depex are
pre-processed as drivers are added to the mDiscoveredList. If an Apriori
file exists in the FV those drivers are addeded to the
mScheduledQueue. The mFvHandleList is used to make sure a
FV is only processed once.

主要是这一句:

If an Apriori file exists in the FV those drivers are addeded to the mScheduledQueue.

在执行时:

EFI_STATUS
EFIAPI
CoreDispatcher (
  VOID
  )
{
  // 其它次要代码已经略去
  do {
    //
    // Drain the Scheduled Queue
    //
    while (!IsListEmpty (&mScheduledQueue)) {
      // 获取模块
      DriverEntry = CR (
                      mScheduledQueue.ForwardLink,
                      EFI_CORE_DRIVER_ENTRY,
                      ScheduledLink,
                      EFI_CORE_DRIVER_ENTRY_SIGNATURE
                      );
      // 加载模块
      Status = CoreLoadImage (
                  FALSE,
                  gDxeCoreImageHandle,
                  DriverEntry->FvFileDevicePath,
                  NULL,
                  0,
                  &DriverEntry->ImageHandle
                  );
      // 执行之后移除魔魁啊
      DriverEntry->Scheduled   = FALSE;
      DriverEntry->Initialized = TRUE;
      RemoveEntryList (&DriverEntry->ScheduledLink);
      if (DriverEntry->IsFvImage) {
        //
        // Produce a firmware volume block protocol for FvImage so it gets dispatched from.
        //
        Status = CoreProcessFvImageFile (DriverEntry->Fv, DriverEntry->FvHandle, &DriverEntry->FileName);
      } else {
        // 执行模块
        Status = CoreStartImage (DriverEntry->ImageHandle, NULL, NULL);
      }

      ReturnStatus = EFI_SUCCESS;
    }
  } while (ReadyToRun);

这里有两个循环,第二个while循环中就是先执行mScheduledQueue中的模块。对应edk2\MdeModulePkg\Core\Dxe\Dispatcher\Dispatcher.c文件的头部的说明:

Step #2 - Dispatch. Remove driver from the mScheduledQueue and load and
start it. After mScheduledQueue is drained check the
mDiscoveredList to see if any item has a Depex that is ready to
be placed on the mScheduledQueue.

主要对应第一句:

Dispatch. Remove driver from the mScheduledQueue and load and start it.

inf中的优先级

并不是所有的模块都可以包含依赖关系,有些模块的依赖关系即使写了也会被忽略,在《edk-ii-inf-specification.pdf》中有如下的说明:

  • If the Module is a Library, then a [Depex] section is optional.
    If the Module is a Library with a MODULE_TYPE of BASE, the generic (i.e., [Depex]) and generic with only architectural modifier entries (i.e., [Depex.IA32]) are not permitted. It is permitted to have a Depex section if one ModuleType modifier is specified (i.e., [Depex.common.PEIM).
  • If the ModuleType is USER_DEFINED , then a [Depex] section is optional. If a PEI, SMM or DXE DEPEX section is required, the user must specify a ModuleType of PEIM to generate a PEI_DEPEX section, a ModuleType of DXE_DRIVER to generate a DXE_DEPEX section, or a ModuleType of DXE_SMM_DRIVER to generate an SMM_DEPEX section.
  • If the ModuleType is SEC, UEFI_APPLICATION, UEFI_DRIVER, PEI_CORE, SMM_CORE or DXE_CORE, no [Depex] sections are permitted and all library class [Depex] sections are ignored.
  • Module types PEIM, DXE_DRIVER, DXE_RUNTIME_DRIVER, DXE_SMM_DRIVER require a DXE_SAL_DRIVER and [Depex] section unless the dependencies are specified by a PEI_DEPEX , DXE_DEPEX or SMM_DEPEX in the [Binaries] section.

生成depex文件

inf中有一个Section包含了依赖关系,下面是一个例子(来自beni\BeniPkg\Dxe\DxeDriverInBds\DxeDriverInBds.inf):

[Depex]
  gEfiPciIoProtocolGuid

也就是说要执行本模块,前提是gEfiPciIoProtocolGuid这个GUID已经安装。

在编译模块时,会生成一个特定的文件(通过edk2\BaseTools\Source\Python\AutoGen\GenDepex.py执行相关操作,它也是AutoGen的一部分),名称格式是“模块名.depex”,本例就是DxeDriverInBds.depex:

在这里插入图片描述

上图高亮的就是gEfiPciIoProtocolGuid这个GUID。不过这里还是有几点需要说明:

  • 首先GUID前面有一个02,它表示的是OPCODE,在edk2\BaseTools\Source\Python\AutoGen\GenDepex.py可以看到更多的OPCODE:
    Opcode = {
        "PEI"   : {
            DEPEX_OPCODE_PUSH  :   0x02,
            DEPEX_OPCODE_AND   :   0x03,
            DEPEX_OPCODE_OR    :   0x04,
            DEPEX_OPCODE_NOT   :   0x05,
            DEPEX_OPCODE_TRUE  :   0x06,
            DEPEX_OPCODE_FALSE :   0x07,
            DEPEX_OPCODE_END   :   0x08
        },

        "DXE"   : {
            DEPEX_OPCODE_BEFORE:   0x00,
            DEPEX_OPCODE_AFTER :   0x01,
            DEPEX_OPCODE_PUSH  :   0x02,
            DEPEX_OPCODE_AND   :   0x03,
            DEPEX_OPCODE_OR    :   0x04,
            DEPEX_OPCODE_NOT   :   0x05,
            DEPEX_OPCODE_TRUE  :   0x06,
            DEPEX_OPCODE_FALSE :   0x07,
            DEPEX_OPCODE_END   :   0x08,
            DEPEX_OPCODE_SOR   :   0x09
        },

02表示的是DEPEX_OPCODE_PUSH,03表示的是DEPEX_OPCODE_AND,08表示的是DEPEX_OPCODE_END

  • 这里还有第二个GUID,对应gEfiPcdProtocolGuid
  ## Include/Protocol/PiPcd.h
  gEfiPcdProtocolGuid = { 0x13a3f0f6, 0x264a, 0x3ef0, { 0xf2, 0xe0, 0xde, 0xc5, 0x12, 0x34, 0x2f, 0x34 } }

不确定为什么要包含这个GUID,并且当[Depex]下只有TRUE的时候,也有这个GUID:

在这里插入图片描述

另外值得注意的一点是,在fdf文件中,也会将这个PCD模块包含在APRIORI中,因为PCD是一种基本的模式,为了所有的模块都能够支持PCD,有这样的依赖也是可以理解的。

将depex文件包含到BIOS二进制中

通过查看fdf可以知道depex是如何被包含的,主要是通过Rules这种[Section],比如一个DXE_DRIVER,它生成的ffs文件结构遵循的Rule如下:

[Rule.Common.DXE_DRIVER]
  FILE DRIVER = $(NAMED_GUID) {
    DXE_DEPEX    DXE_DEPEX Optional      $(INF_OUTPUT)/$(MODULE_NAME).depex
    PE32     PE32                    $(INF_OUTPUT)/$(MODULE_NAME).efi
    UI       STRING="$(MODULE_NAME)" Optional
    VERSION  STRING="$(INF_VERSION)" Optional BUILD_NUM=$(BUILD_NUMBER)
    RAW ACPI  Optional               |.acpi
    RAW ASL   Optional               |.aml
  }

即首先是一个depex文件,然后是一个efi文件,如下所示:

在这里插入图片描述

代码处理

模块中没有一个特定的GUID(像gAprioriGuid)来指定依赖,不过depex本来就是ffs中的一部分,所以是可以读出来的,在一个描述模块的结构体中有成员来表示这个depex(以DXE为例):

typedef struct {
  // 其它略
  VOID                             *Depex;		// 描述依赖关系
  UINTN                            DepexSize;	// depex的大小
} EFI_CORE_DRIVER_ENTRY;

所以在得到模块的时候我们就已经能够知道它依赖的GUID了,而这些模块通过一个全局变量mDiscoveredList构成了一个链表,后续会通过遍历这个链表来进行各种操作。

真正处理依赖关系的代码同样在CoreDispatcher()

EFI_STATUS
EFIAPI
CoreDispatcher (
  VOID
  )
{
  // 其它次要代码已经略去
  do {
    //
    // Drain the Scheduled Queue
    //
    while (!IsListEmpty (&mScheduledQueue)) {
      // 首次执行的时候,执行fdf中的优先模块
      // 后面的操作又会往mScheduledQueue里面放更多的模块,又会继续执行
    }
    //
    // Search DriverList for items to place on Scheduled Queue
    //
    ReadyToRun = FALSE;
    for (Link = mDiscoveredList.ForwardLink; Link != &mDiscoveredList; Link = Link->ForwardLink) {
      DriverEntry = CR (Link, EFI_CORE_DRIVER_ENTRY, Link, EFI_CORE_DRIVER_ENTRY_SIGNATURE);

      if (DriverEntry->DepexProtocolError) {
        //
        // If Section Extraction Protocol did not let the Depex be read before retry the read
        //
        // 会将满足依赖的模块继续放入mScheduledQueue
        Status = CoreGetDepexSectionAndPreProccess (DriverEntry);
      }

      if (DriverEntry->Dependent) {
        if (CoreIsSchedulable (DriverEntry)) {
          CoreInsertOnScheduledQueueWhileProcessingBeforeAndAfter (DriverEntry);
          ReadyToRun = TRUE;
        }
      }
    }
  } while (ReadyToRun);

总体来说,对于依赖关系的处理如下:

在这里插入图片描述

其它

开始的时候说过依赖关系主要有两种,实际上还是有一些衍生的方式。比如在inf中的优先级中有提到depex文件,它可以有不同的用法,在inf中包含[Depex]可以生成depex文件,也可以手动生成(通过edk2\BaseTools\Source\Python\AutoGen\GenDepex.py),然后直接放到fdf中来为某个文件指定依赖,这种情况对于在fdf中直接包含efi文件时时很有用的,下面是一个例子:

FILE DRIVER = 5BBA83E5-F027-4ca7-BFD0-16358CC9E123 {
    SECTION PE32 = $(PLATFORM_FEATURES_PATH)/Icc/IccOverClocking/IccOverClocking.efi
    SECTION DXE_DEPEX = $(PLATFORM_FEATURES_PATH)/Icc/IccOverClocking/IccOverClocking.depex
    SECTION UI = "IccOverClocking"
  }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值