综述
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"
}