PEI (Pre-EFI Initialization)阶段资源依然十分有限,内存到了PEI后期才被初始化。其主要任务是为DXE准备执行环境。需要将传递到DXE的信息组成HOB(Handoff Block)列表,最终将控制权交到DXE手中。
从功能上讲,PEI可分为两部分:
在UEFI启动过程中主要完成以下任务
通过PeiServices,PEIM可以使用PEI阶段提供的系统服务,通过这些系统服务,PEIM可以访问PEI内核。PEIM之间的通信通过PPI(PEIM-to-PEIM Interfaces)完成。每个PPI都有一个GUID。
从SEC阶段分析得知,PEI入口函数是PeiCore,过程是:_ModuleEntryPoint(SecCoreData, PpiList) ->
ProcessModuleEntryPointList (SecCoreData, PpiList, NULL) ->
PeiCore (SecCoreData, PpiList, Context);
函数PeiCore()位于:MdeModulePkg/Core/Pei/PeiMain/PeiMain.c
以龙芯UEFI为例:在FD固件镜像中的FV_PEIFV区域,存放了以下内容:
LsRefCodePkg/SampleCode/Desktop/Script/Loongson.fdf
#
# PEI Phase modules
#
INF MdeModulePkg/Core/Pei/PeiMain.inf
INF MdeModulePkg/Universal/PCD/Pei/Pcd.inf
INF MdeModulePkg/Core/DxeIplPeim/DxeIpl.inf
INF LsRefCodePkg/SampleCode/Desktop/Ls3aPlatformTable/Pei/PeiLs3aPlatformTableInit.inf
INF LsRefCodePkg/SampleCode/Desktop/Ls7aPlatformTable/Pei/PeiLs7aPlatformTableInit.inf
INF LsRefCodePkg/SampleCode/Desktop/PlatformInitPei/PlatformInitPei.inf
每个inf文件代表一个module,以上module一起完成UEFI在PEI阶段的工作。在PEI阶段,最重要的工作是CPU、内存、桥片和Platform的初始化。
[Defines]
INF_VERSION = 0x00010005
BASE_NAME = PeiCore
MODULE_UNI_FILE = PeiCore.uni
FILE_GUID = 52C05B14-0B98-496c-BC3B-04B50211D680
MODULE_TYPE = PEI_CORE
VERSION_STRING = 1.0
ENTRY_POINT = PeiCore //函数入口
/**
在从 SEC 到 PEI 的转换过程中,PeiMain 模块的主入口调用该例程。
在 PEI 核心中切换堆栈后,它将使用旧的核心数据重新启动。
@param SecCoreDataPtr 指向包含有关 PEI core的操作信息的数据结构环境,
例如临时 RAM 的大小和位置、堆栈位置和BFV 位置。
@param PpiList 指向由 PEI core最初安装的一个或多个 PPI 描述符的列表。
一个空的 PPI 列表由一个带有结束标记 EFI_PEI_PPI_DESCRIPTOR_TERMINATE_LIST 的描述符组成。
作为其初始化阶段的一部分,PEI Foundation会将这些 SEC-hosted PPI 添加到其 PPI 数据库中,
以便 PEI Foundation和任何模块都可以利用这些早期 PPI 中的相关服务调用和/或代码
@param Data 指向用于初始化核心数据区域的旧核心数据的指针。
如果为NULL,则首先进入PeiCore。
**/
VOID
EFIAPI
PeiCore (
IN CONST EFI_SEC_PEI_HAND_OFF *SecCoreDataPtr,
IN CONST EFI_PEI_PPI_DESCRIPTOR *PpiList,
IN VOID *Data
)
{
// Retrieve context passed into PEI Core
OldCoreData = (PEI_CORE_INSTANCE *) Data;
SecCoreData = (EFI_SEC_PEI_HAND_OFF *) SecCoreDataPtr;
// 判断传入第三个参数 Data ,执行 PEI 核心阶段特定操作。第一次Data == NULL ,直接进入 PEI Core第一阶段。
// Perform PEI Core phase specific actions.
if (OldCoreData == NULL) {
// If OldCoreData is NULL, means current is the first entry into the PEI Core before memory is available.
ZeroMem (&PrivateData, sizeof (PEI_CORE_INSTANCE));
//创建PEI私有数据结构实例,类型为PEI_CORE_INSTANCE实例为PrivateData
PrivateData.Signature = PEI_CORE_HANDLE_SIGNATURE;
//gPs是一个类型为EFI_PEI_SERVICES的全局指针
CopyMem (&PrivateData.ServiceTableShadow, &gPs, sizeof (gPs));
}
......
// Cache a pointer to the PEI Services Table that is either in temporary memory or permanent memory
//缓存指向位于临时内存或永久内存中的 PEI Services Table的指针
PrivateData.Ps = &PrivateData.ServiceTableShadow;
// Save PeiServicePointer so that it can be retrieved anywhere.
//保存Ps,以便可以在任何地方检索它。由全局的gPeiServices维护
SetPeiServicesTablePointer ((CONST EFI_PEI_SERVICES **)&PrivateData.Ps);
// Initialize libraries that the PEI Core is linked against
//初始化 PEI Core链接的库
ProcessLibraryConstructorList (NULL, (CONST EFI_PEI_SERVICES **)&PrivateData.Ps);
// Initialize PEI Core Services
//初始化 PEI Core Services
InitializeMemoryServices (&PrivateData, SecCoreData, OldCoreData);
if (OldCoreData == NULL) {
// Initialize PEI Core Private Data Buffer
//初始化PEI核心私有数据缓冲区
PrivateData.PpiData.PpiListPtrs
PrivateData.Fv
PrivateData.Fv[0].PeimState
PrivateData.Fv[0].FvFileHandles
PrivateData.UnknownFvInfo
PrivateData.CurrentFvFileHandles
PrivateData.FileGuid
PrivateData.FileHandles
}
//初始化ppi 服务
InitializePpiServices (&PrivateData, OldCoreData);
// Complete PEI Core Service initialization
// 完成PEI Core服务初始化
InitializeSecurityServices (&PrivateData.Ps, OldCoreData); //初始化 安全服务
InitializeDispatcherData (&PrivateData, OldCoreData, SecCoreData); //初始化调度程序的数据成员
InitializeImageServices (&PrivateData, OldCoreData); //安装PEI加载PPI文件
// Perform PEI Core Phase specific actions
if (OldCoreData == NULL) {
// Report Status Code EFI_SW_PC_INIT
REPORT_STATUS_CODE (
EFI_PROGRESS_CODE,
(EFI_SOFTWARE_PEI_CORE | EFI_SW_PC_INIT)
);
// If SEC provided the PpiList, process it.
//如果 SEC 提供了 PpiList,对其进行处理
if (PpiList != NULL) {
ProcessPpiListFromSec ((CONST EFI_PEI_SERVICES **) &PrivateData.Ps, PpiList);
}
}
// Call PEIM dispatcher 进行PEIM调度
PeiDispatcher (SecCoreData, &PrivateData);
if (PrivateData.HobList.HandoffInformationTable->BootMode != BOOT_ON_S3_RESUME) {
// Check if InstallPeiMemory service was called on non-S3 resume boot path.
ASSERT(PrivateData.PeiMemoryInstalled == TRUE);
}
// Lookup DXE IPL PPI 查找
Status = PeiServicesLocatePpi (
&gEfiDxeIplPpiGuid,
0,
NULL,
(VOID **)&TempPtr.DxeIpl
);
// Enter DxeIpl to load Dxe core.
//准备HOB (Hand-off Block)列表(PrivateData.HobList),进入DXE入口
DEBUG ((EFI_D_INFO, "DXE IPL Entry\n"));
Status = TempPtr.DxeIpl->Entry (
TempPtr.DxeIpl,
&PrivateData.Ps,
PrivateData.HobList
);
}
MdeModulePkg/Core/Pei/Dispatcher/Dispatcher.c
函数PeiDispatcher ()
在每次PEIM成功运行时,调用PeiCheckAndSwitchStack (SecCoreData, Private); 来检查内存是否已被初始化,若是则进行栈切换,流程如下:
PEIM(Pre-EFI Initialize Module)表示代码和/或数据单元。 它抽象了特定领域的逻辑,类似于 DXE 驱动程序。
PEIM 可包含处理器、芯片组、设备或者其它特殊平台功能的可执行二进制文件。
PEIM可抽象成可提供PEIM或PEI Foundation与PEIM或者硬件间通信的接口。
PEIM是一个单独编译成的二进制模块,绝大部分驻留在ROM中并且没有被压缩。
可能有一小部分PEIM为了性能原因在RAM中运行,这些以压缩格式放在ROM中。
放在ROM中的PEIM是内置式可执行的模块,这些模块可能是由位置独立代码或者位置相关代码与重定位信息一起组成的。
每个 PEI 模块 (PEIM) 都存储在一个文件中。 它由以下部分组成:
每个PEIM都是独立的Module,并且可能分布在固件中的不同位置。为了便于被定位和调度,具有统一定义的入口函数_ModuleEntryPoint(),位于MdePkg/Library/PeimEntryPoint/PeimEntryPoint.c,代码如下:
EFI_STATUS
EFIAPI
_ModuleEntryPoint (
IN EFI_PEI_FILE_HANDLE FileHandle,
IN CONST EFI_PEI_SERVICES **PeiServices
)
{
if (_gPeimRevision != 0) {
// Make sure that the PEI spec revision of the platform is >= PEI spec revision of the driver
ASSERT ((*PeiServices)->Hdr.Revision >= _gPeimRevision);
}
// Call constructor for all libraries
ProcessLibraryConstructorList (FileHandle, PeiServices);
// Call the driver entry point
return ProcessModuleEntryPointList (FileHandle, PeiServices);
}
而PEI Core可以理解为最先启动的PEIM,然后通过PPI来调度其他的PEIM。
在PEIM Dispatcher寻找可启动的PEIM时,会先在每一个FV上定位Apriori文件,然后读取文件内容来查找PEIM的GUID,确保Apriori文件中的PEIM被首先调用。
在龙芯架构UEFI固件Loongson.fdf中,可以看到[FV.PEIFV]
中有以下内容:
APRIORI PEI {
INF MdeModulePkg/Universal/PCD/Pei/Pcd.inf
!if $(CAPSULE_ENABLE)
INF LsRefCodePkg/Universal/Capsule/CapsulePei/CapsulePei.inf
!endif
}
说明PCD模块是PEI Dispatcher首先启动的PEIM。然后才对[FV.PEIFV]
中定义的PEI 阶段的其他模块进行调用。最终调用DXE IPL的PPI来启动DXE,此时DXE也类似于一个PEIM
。
MdeModulePkg/Core/DxeIplPeim/DxeIpl.inf
[Defines]
INF_VERSION = 0x00010005
BASE_NAME = DxeIpl
MODULE_UNI_FILE = DxeIpl.uni
FILE_GUID = 86D70125-BAA3-4296-A62F-602BEBBB9081
MODULE_TYPE = PEIM //类型
VERSION_STRING = 1.0
ENTRY_POINT = PeimInitializeDxeIpl //入口函数
[Sources]
DxeIpl.h
DxeLoad.c
[Sources.Ia32]
X64/VirtualMemory.h
X64/VirtualMemory.c
Ia32/DxeLoadFunc.c
Ia32/IdtVectorAsm.nasm
Ia32/IdtVectorAsm.asm
Ia32/IdtVectorAsm.S
[Sources.X64]
X64/VirtualMemory.h
X64/VirtualMemory.c
X64/DxeLoadFunc.c
[Sources.IPF]
Ipf/DxeLoadFunc.c
[Sources.EBC]
Ebc/DxeLoadFunc.c
[Sources.ARM, Sources.AARCH64]
Arm/DxeLoadFunc.c
[Sources.LOONGARCH64]
LoongArch/DxeLoadFunc.c
[Packages]
MdePkg/MdePkg.dec
MdeModulePkg/MdeModulePkg.dec
PPI — ( PEIM to PEIM Interface ) 顾名思义,PPI就是PEIM之间沟通的接口。
PEI Foundation 协助 PEIM 相互交流,并提供接口以允许 PEIM 注册 PPI 并在另一个 PEIM 安装 PPI 时得到通知(回调)。
MdePkg/Include/Pi/PiPeiCis.h
// PEI Ppi Services List Descriptors
#define EFI_PEI_PPI_DESCRIPTOR_PIC 0x00000001
#define EFI_PEI_PPI_DESCRIPTOR_PPI 0x00000010
#define EFI_PEI_PPI_DESCRIPTOR_NOTIFY_CALLBACK 0x00000020
#define EFI_PEI_PPI_DESCRIPTOR_NOTIFY_DISPATCH 0x00000040
#define EFI_PEI_PPI_DESCRIPTOR_NOTIFY_TYPES 0x00000060
#define EFI_PEI_PPI_DESCRIPTOR_TERMINATE_LIST 0x80000000
/// The data structure through which a PEIM describes available services to the PEI Foundation.
typedef struct {
/// This field is a set of flags describing the characteristics of this imported table entry.
/// All flags are defined as EFI_PEI_PPI_DESCRIPTOR_***, which can also be combined into one.
UINTN Flags; //描述该PPI的特性
/// The address of the EFI_GUID that names the interface.
EFI_GUID *Guid; //根据该GUID定位到PPI
/// A pointer to the PPI. It contains the information necessary to install a service.
VOID *Ppi; //PPI实际内容
} EFI_PEI_PPI_DESCRIPTOR;
几个重要的PPI Services
GLOBAL_REMOVE_IF_UNREFERENCED EFI_GUID gEfiXxxPpiGuid = { 0x56A9F5C8, 0x9A19, 0x854C, { 0x01, 0xA6, 0x13, 0x35, 0xDE, 0x25, 0x49, 0xFC } };
Defind_Struct_PPI XxxPpi = {
L"v1.0",
Get_Test,
Set_Test
};
EFI_STATUS
EFIAPI
Get_Test (
VOID
)
{
EFI_STATUS Status;
pr_info("Get_Test!!!\n");
return EFI_SUCCESS;
}
EFI_STATUS
EFIAPI
Set_Test (
VOID
)
{
EFI_STATUS Status;
pr_info("Set_Test!!!\n");
return EFI_SUCCESS;
}
EFI_STATUS
EFIAPI
XxxSourceInstall (
IN EFI_PEI_FILE_HANDLE FileHandle,
IN CONST EFI_PEI_SERVICES **PeiServices
)
{
EFI_STATUS Status;
EFI_PEI_PPI_DESCRIPTOR *XxxPpiDesc;
XxxPpiDesc = (EFI_PEI_PPI_DESCRIPTOR *) AllocateZeroPool (sizeof (EFI_PEI_PPI_DESCRIPTOR));
ASSERT (XxxPpiDesc != NULL);
if (XxxPpiDesc == NULL) {
return EFI_OUT_OF_RESOURCES;
}
// Initialize the PPI
XxxPpiDesc->Flags = EFI_PEI_PPI_DESCRIPTOR_PPI | EFI_PEI_PPI_DESCRIPTOR_TERMINATE_LIST;
XxxPpiDesc->Guid = &gEfiXxxPpiGuid;
XxxPpiDesc->Ppi = &XxxPpi;
// Install PPI
Status = PeiServicesInstallPpi (XxxPpiDesc);
ASSERT_EFI_ERROR (Status);
return Status;
}
[Defines]
INF_VERSION = 0x00010005
BASE_NAME = ChipSourcePpi
FILE_GUID = 5ca46eca-ca55-4a0c-9427-582d17a75cc2
MODULE_TYPE = PEIM
VERSION_STRING = 1.0
ENTRY_POINT = XxxSourceInstall
[Sources]
XxxSource.c
EFI_STATUS
EFIAPI
XxxInitEntry (
IN EFI_PEI_FILE_HANDLE FileHandle,
IN CONST EFI_PEI_SERVICES **PeiServices
)
{
EFI_STATUS Status;
Defind_Struct_PPI *Xxx_Ppi = NULL;
Status = PeiServicesLocatePpi (
&gEfiXxxPpiGuid,
0,
NULL,
(VOID **)&Xxx_Ppi
);
ASSERT_EFI_ERROR (Status);
Xxx_Ppi->Get_Test();
Xxx_Ppi->Set_Test();
[Defines]
INF_VERSION = 0x00010005
BASE_NAME = CustomedPpi
FILE_GUID = b64672cb-7d0f-4de7-8adb-7c9bebbed5fd
MODULE_TYPE = PEIM
VERSION_STRING = 1.0
ENTRY_POINT = XxxInitEntry
[Sources]
XxxUse.c
[Ppis]
gEfiXxxPpiGuid