【UEFI基础】BIOS模块执行的优先级
综述
BIOS下主要通过两种方式来确定一般模块的优先级,一种是fdf文件中指定的优先级,另一种是inf文件中指定的优先级。需要注意这里使用了“一般模块”的说法,因为有些模块(尤其是PEI_CORE,DXE_CORE类型的模块)总是会先执行的,事实上就是因为这些优先执行的模块在控制一般模块的优先级。
fdf中的优先级
APRIORI
fdf中的优先级通过特殊的标识来说明,下面是一个例子:
[FV.DXEFV]
# 中间略APRIORI DXE {INF MdeModulePkg/Universal/DevicePathDxe/DevicePathDxe.infINF 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.infINF OvmfPkg/TdxDxe/TdxDxe.inf
!if $(SMM_REQUIRE) == FALSEINF OvmfPkg/QemuFlashFvbServicesRuntimeDxe/FvbServicesRuntimeDxe.inf
!endif
}
这里的APRIORI
就指定了需要优先执行的模块。
在编译时这个部分会被组成一个Firmware File,上例中可以从DXEFV中找到这个Firmware File,下面是该文件中的实际数据:
这些数据实际上是一个个的GUID,来自包含模块中的inf文件中的FILE_GUID
:
[Defines]INF_VERSION = 0x00010005BASE_NAME = DevicePathDxeMODULE_UNI_FILE = DevicePathDxe.uniFILE_GUID = 9B680FCE-AD6B-4F3A-B60B-F59899003443 # Firmware File中包含的GUIDMODULE_TYPE = DXE_DRIVERVERSION_STRING = 1.0ENTRY_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.hgEfiPcdProtocolGuid = { 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).depexPE32 PE32 $(INF_OUTPUT)/$(MODULE_NAME).efiUI STRING="$(MODULE_NAME)" OptionalVERSION STRING="$(INF_VERSION)" Optional BUILD_NUM=$(BUILD_NUMBER)RAW ACPI Optional |.acpiRAW 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//// 会将满足依赖的模块继续放入mScheduledQueueStatus = 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.efiSECTION DXE_DEPEX = $(PLATFORM_FEATURES_PATH)/Icc/IccOverClocking/IccOverClocking.depexSECTION UI = "IccOverClocking"}