PEI阶段资源依然十分有限,内存到了PEI后期才被初始化。其主要功能是为DXE准备执行环境,将需要传递给DXE的信息组成HOB(Handoff Block)列表,最终将控制权交给DXE。执行流程如下:
以OVMF为例,从SEC阶段分析得知,PEI入口函数是PeiCore,位于:
edk2\MdeModulePkg\Core\Pei\PeiMain\PeiMain.c
接下来进入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
)
{
1. 判断传入第三个参数 Data ,执行 PEI 核心阶段特定操作。
_ModuleEntryPoint(SecCoreData, PpiList) -> ProcessModuleEntryPointList (SecCoreData, PpiList, NULL) -> PeiCore (SecCoreData, PpiList, Context);
Data != NULL时,需要进行数据的一系列修复等复杂操作(按照上面流程看目前不确定为什么Data会存在!=NULL的状态,猜测可能跟recovery有关)
Data == NULL (我们后续直接讨论这种状态)时,直接进入 PEI Core第一阶段。
2. 创建PEI私有数据结构实例,类型为PEI_CORE_INSTANCE实例为PrivateData
ZeroMem (&PrivateData, sizeof (PEI_CORE_INSTANCE));
PrivateData.Signature = PEI_CORE_HANDLE_SIGNATURE;
gPs是一个类型为EFI_PEI_SERVICES的全局指针
CopyMem (&PrivateData.ServiceTableShadow, &gPs, sizeof (gPs));
4. 缓存指向位于临时内存或永久内存中的 PEI Services Table的指针,如下:
PrivateData.Ps = &PrivateData.ServiceTableShadow;(ps 指向ServiceTableShadow的指针)
4.通过SetPeiServicesTablePointer()保存Ps,以便可以在任何地方检索它。由全局的gPeiServices维护:
SetPeiServicesTablePointer ((CONST EFI_PEI_SERVICES **)&PrivateData.Ps);
5.通过ProcessLibraryConstructorList (),传入ps初始化 PEI Core链接的库
ProcessLibraryConstructorList (NULL, (CONST EFI_PEI_SERVICES **)&PrivateData.Ps);
6.初始化 PEI Core Services
InitializeMemoryServices (&PrivateData, SecCoreData, OldCoreData);
7.初始化PEI核心私有数据缓冲区。
初始化ppi 服务 -> InitializePpiServices (&PrivateData, OldCoreData);
初始化 安全服务 ->InitializeSecurityServices (&PrivateData.Ps, OldCoreData);
初始化调度程序的数据成员 ->InitializeDispatcherData (&PrivateData, OldCoreData, SecCoreData);
安装PEI加载PPI文件 ->InitializeImageServices (&PrivateData, OldCoreData);
8.如果 SEC 提供了 PpiList,则对其进行处理。
如果PpiList != NULL
则-> ProcessPpiListFromSec ((CONST EFI_PEI_SERVICES **) &PrivateData.Ps, PpiList);
9.呼叫 PEIM dispatcher,进行PEIM调度
PeiDispatcher (SecCoreData, &PrivateData);
10.检查是否在非 S3 恢复引导路径上调用了 InstallPeiMemory 服务。
if (PrivateData.HobList.HandoffInformationTable->BootMode != BOOT_ON_S3_RESUME) {
ASSERT(PrivateData.PeiMemoryInstalled == TRUE);
}
11.查找 DXE IPL PPI
PeiServicesLocatePpi (&gEfiDxeIplPpiGuid, 0, NULL, (VOID **)&TempPtr.DxeIpl );
12.输入 DxeIpl 加载 Dxe 内核。准备HOB (Hand-off Block)列表(PrivateData.HobList),进入DXE入口
TempPtr.DxeIpl->Entry ( TempPtr.DxeIpl, &PrivateData.Ps, PrivateData.HobList );
}
从功能上讲,PEI可分为两部分:
PEI Foundation 是一个用每个处理器结构编译成一个简单可执行的二进制文件。主要包括基础服务 (Core Services包) 和 流程。
具体如下:
PEIM(PEI Module) 表示代码和/或数据单元。 它抽象了特定领域的逻辑,类似于 DXE 驱动程序。
每个 PEI 模块 (PEIM) 都存储在一个文件中。 它由以下部分组成:
PEIM 的可执行部分可以是位置相关的或位置无关的代码。如果 PEIM 的可执行部分是位置相关代码,则必须在 PEIM 映像中提供重定位信息,以允许 FV 存储软件将映像重定位到与编译时不同的位置。
PEIM 的典型布局如下:
PEIM的编译遵从UEFI,一般由:
inf 文件 + c文件 + h文件 + uni文件组成,编译生成 -> efi格式的二进制文件
比如 UhciPei.efi 和 UsbBusPei.efi ,其中 UhciPei是设备可执行文件,而 UsbBusPei.efi 是给其他PEIM提供接口。
edk2\MdeModulePkg\Bus\Pci\UhciPei\UhciPei.inf
PEIM入口函数是 UhcPeimEntry
/**
初始化 Usb Host Controller.
@param FileHandle 被调用文件的 Handle
@param PeiServices 描述可能的 PEI 服务列表。
@retval EFI_SUCCESS 已成功安装。
@retval EFI_OUT_OF_RESOURCES 无法分配内存资源。
**/
EFI_STATUS
EFIAPI
UhcPeimEntry (
IN EFI_PEI_FILE_HANDLE FileHandle,
IN CONST EFI_PEI_SERVICES **PeiServices
)
PEI 调度员的工作是以有序的方式将控制权移交给 PEIM。
PEI阶段操作包括调用PEI Foundation,有序调度所有PEIM,发现并调用下一个阶段,如图13.6所示。 在 PEI 基金会初始化期间,PEI 基金会初始化内部吗数据区域和函数,这些区域和函数是为 PEIM 提供公共 PEI 服务所需的。 在 PEIM 调度期间,PEI Dispatcher 遍历固件卷并根据闪存文件系统定义发现 PEIM。 如果满足以下条件,PEI Dispatcher 将调度 PEIM:
■ PEIM 尚未被调用。
■ PEIM 文件格式正确。
■ PEIM 值得信赖。
■ PEIM 的依赖性要求已得到满足
PEI Dispatcher 调度: PeiCore -> PeiDispatcher
edk2\MdeModulePkg\Core\Pei\Dispatcher\Dispatcher.c
/**
进行 PEIM 调度。
@param SecCoreData 指向包含有关 PEI 内核操作环境信息的数据结构,
例如临时 RAM 的大小和位置、堆栈位置和 BFV 位置。
@param Private 指向调用者传入的私有数据的指针
**/
VOID
PeiDispatcher (
IN CONST EFI_SEC_PEI_HAND_OFF *SecCoreData,
IN PEI_CORE_INSTANCE *Private
)
在一个peim 在执行时,有可能会用到其他peim的功能。所以要等其他peim先执行后,并install ppi至 ppi database中,该peim才能使用,该ppi也才能继续执行下去,,这就是dependency。而这些关系记录放在fv的一个特殊的section中,一个peim有可能会需要很多其他peim先执行才能执行,所以表示方法用boolean运算符 and, or, 和sequencing .
特定于依赖表达式和局部函数原型的数据在:
edk2\MdeModulePkg\Core\Pei\Dependency\Dependency.c
PEI 先验文件 (The PEI a priori file)是一种特殊文件,它可以可选地存在于固件卷中,其主要目的是为平台的固件设计提供更大程度的灵活性。具体而言
通过PeiServices,PEIM可以使用PEI阶段的系统服务,通过这些服务,PEIM可以访问PEI内核。
PEI Service Table定义类型为EFI_PEI_SERVICES
// 为 EFI_PEI_SERVICE 声明前向引用数据结构。
typedef struct _EFI_PEI_SERVICES EFI_PEI_SERVICES;
struct _EFI_PEI_SERVICES {
// PEI 服务表的表头。
EFI_TABLE_HEADER Hdr;
// PPI服务: 管理 PPI 促进PEIM模块间的调用。在临时 RAM 中维护接口被安装和被跟踪信息的数据库。
EFI_PEI_INSTALL_PPI InstallPpi; // 通过 GUID 在 PEI PPI 数据库中安装一个接口
EFI_PEI_REINSTALL_PPI ReInstallPpi; // 通过 GUID 重新安装 PEI PPI 数据库中的接口
EFI_PEI_LOCATE_PPI LocatePpi; // 找到一个给定的命名 PPI
EFI_PEI_NOTIFY_PPI NotifyPpi; // 此函数安装一个通知服务,以便在安装或重新安装给定接口时回调。
// 启动模式服务:管理系统的启动模式 (S3, S5, 正常引导、系统的诊断等 等)
EFI_PEI_GET_BOOT_MODE GetBootMode; // 该服务使 PEIM 能够确定启动模式的当前值。
EFI_PEI_SET_BOOT_MODE SetBootMode; // 此服务使 PEIM 能够更新启动模式变量。
// HOB 服务: 建立叫 Hand-Off Blocks的数据结构,用于传递信息给整个框架中的下一个阶段(DXE)
EFI_PEI_GET_HOB_LIST GetHobList; // 获取指向 HOB 列表的指针。
EFI_PEI_CREATE_HOB CreateHob; // 将新的 HOB 添加到 HOB 列表
//
// Firmware Volume 服务: 在 Flash 的 FV 中扫描各 FFS 寻找 PEIM 和其它固件文件
EFI_PEI_FFS_FIND_NEXT_VOLUME2 FfsFindNextVolume; // 按索引搜索固件卷
EFI_PEI_FFS_FIND_NEXT_FILE2 FfsFindNextFile; // 在固件卷中搜索下一个匹配文件
EFI_PEI_FFS_FIND_SECTION_DATA2 FfsFindSectionData; // 在指定文件中搜索下一个匹配部分
// PEI Memory 服务: 在固定存储器被发现前后,提供存储器管理服务程序集
EFI_PEI_INSTALL_PEI_MEMORY InstallPeiMemory; // 此函数将找到的内存配置注册到 PEI Foundation
EFI_PEI_ALLOCATE_PAGES AllocatePages; // 该服务的目的是发布一个接口,允许 PEIM 分配由 PEI Foundation 管理的内存范围。
EFI_PEI_ALLOCATE_POOL AllocatePool; // 池分配服务。 在发现永久内存之前,该池将在临时内存的堆中分配。
// 一般来说,临时内存中堆的大小不超过64K,因此最大可以分配的池大小为64K
EFI_PEI_COPY_MEM CopyMem; // 将源缓冲区复制到目标缓冲区,并返回目标缓冲区
EFI_PEI_SET_MEM SetMem; // 服务用指定的值填充缓冲区
// 状态码服务: 统一的进展和错误代码报告服务程序,用来从 80 口或串口丢简单字符调试用。
EFI_PEI_REPORT_STATUS_CODE ReportStatusCode; // 状态码报告器的核心版本
// 重启服务: 提供统一的方式重启系统
EFI_PEI_RESET_SYSTEM ResetSystem; // 重置系统的核心版本
// (以下接口通过发布PEIM安装) I/O Abstractions
EFI_PEI_CPU_IO_PPI *CpuIo; // 这个默认的 EFI_PEI_CPU_IO_PPI 安装实例在 PeiCore 初始化时分配给 EFI_PEI_SERVICE.CpuIo。
EFI_PEI_PCI_CFG2_PPI *PciCfg; // 这个 EFI_PEI_PCI_CFG2_PPI 的默认实例在 PeiCore 初始化时分配给 EFI_PEI_SERVICE.PciCfg。
// 未来安装服务
EFI_PEI_FFS_FIND_BY_NAME FfsFindFileByName; // 通过文件名在卷中查找文件
EFI_PEI_FFS_GET_FILE_INFO FfsGetFileInfo; // 返回特定文件的相关信息
EFI_PEI_FFS_GET_VOLUME_INFO FfsGetVolumeInfo; // 返回有关特定固件的信息卷,包括其名称、类型、属性、起始地址和大小。
EFI_PEI_REGISTER_FOR_SHADOW RegisterForShadow; // 当 PEI Foundation 发现永久内存时,此例程使 PEIM 能够注册自己的映射
EFI_PEI_FFS_FIND_SECTION_DATA3 FindSectionData3; // 在指定文件中搜索下一个匹配部分
EFI_PEI_FFS_GET_FILE_INFO2 FfsGetFileInfo2; // 返回特定文件的相关信息
EFI_PEI_RESET2_SYSTEM ResetSystem2; // 重置整个平台
EFI_PEI_FREE_PAGES FreePages; // 释放内存页面
};
EFI_PEI_SERVICES 在代码中的实例是 gPs,如下
edk2\MdeModulePkg\Core\Pei\PeiMain\PeiMain.c
///
/// Pei service instance
///
EFI_PEI_SERVICES gPs = {
{
PEI_SERVICES_SIGNATURE,
PEI_SERVICES_REVISION,
sizeof (EFI_PEI_SERVICES),
0,
0
},
PeiInstallPpi,
PeiReInstallPpi,
PeiLocatePpi,
PeiNotifyPpi,
PeiGetBootMode,
PeiSetBootMode,
PeiGetHobList,
PeiCreateHob,
PeiFfsFindNextVolume,
PeiFfsFindNextFile,
PeiFfsFindSectionData,
PeiInstallPeiMemory,
PeiAllocatePages,
PeiAllocatePool,
(EFI_PEI_COPY_MEM)CopyMem,
(EFI_PEI_SET_MEM)SetMem,
PeiReportStatusCode,
PeiResetSystem,
&gPeiDefaultCpuIoPpi,
&gPeiDefaultPciCfg2Ppi,
PeiFfsFindFileByName,
PeiFfsGetFileInfo,
PeiFfsGetVolumeInfo,
PeiRegisterForShadow,
PeiFfsFindSectionData3,
PeiFfsGetFileInfo2,
PeiResetSystem2,
PeiFreePages,
};
gPs在PEI Foundation 中用私有数据结构 PrivateData里面的 Ps 存储。参考 2.1 代码流程分析步骤2和3。
PrivateData类型为 PEI_CORE_INSTANCE
// PEI_CORE_INSTANCE 的前向声明
typedef struct _PEI_CORE_INSTANCE PEI_CORE_INSTANCE;
// Pei Core 私有数据结构实例
struct _PEI_CORE_INSTANCE {
UINTN Signature;
// 指向ServiceTableShadow
EFI_PEI_SERVICES *Ps;
PEI_PPI_DATABASE PpiData;
// 包含 FFS 且可由 PeiCore 调度的 FV 的计数。
UINTN FvCount;
// 包含 FFS 且可由 PeiCore 调度的 FV 的最大数量。
UINTN MaxFvCount;
// 指向具有 MaxFvCount 条目数的缓冲区的指针。 每个条目对应一个 FV,其中包含 FFS,可以由 PeiCore 调度。
PEI_CORE_FV_HANDLE *Fv;
// 指向具有 MaxUnknownFvInfoCount 条目数的缓冲区的指针。 每个条目都是针对一个无法被 PeiCore 调度的 FV。
PEI_CORE_UNKNOW_FORMAT_FV_INFO *UnknownFvInfo;
UINTN MaxUnknownFvInfoCount;
UINTN UnknownFvInfoCount;
// 指向由 CurrentPeimFvCount 指定的 PEI_CORE_FV_HANDLE 中的缓冲区 FvFileHandlers 的指针。
EFI_PEI_FILE_HANDLE *CurrentFvFileHandles;
UINTN AprioriCount;
UINTN CurrentPeimFvCount;
UINTN CurrentPeimCount;
EFI_PEI_FILE_HANDLE CurrentFileHandle;
BOOLEAN PeimNeedingDispatch;
BOOLEAN PeimDispatchOnThisPass;
BOOLEAN PeimDispatcherReenter;
EFI_PEI_HOB_POINTERS HobList;
BOOLEAN SwitchStackSignal;
BOOLEAN PeiMemoryInstalled;
VOID *CpuIo;
EFI_PEI_SECURITY2_PPI *PrivateSecurityPpi;
EFI_PEI_SERVICES ServiceTableShadow;
EFI_PEI_PPI_DESCRIPTOR *XipLoadFile;
EFI_PHYSICAL_ADDRESS PhysicalMemoryBegin;
UINT64 PhysicalMemoryLength;
EFI_PHYSICAL_ADDRESS FreePhysicalMemoryTop;
UINTN HeapOffset;
BOOLEAN HeapOffsetPositive;
UINTN StackOffset;
BOOLEAN StackOffsetPositive;
// 用于迁移在内存前阶段分配的内存页面的信息。
HOLE_MEMORY_DATA MemoryPages;
PEICORE_FUNCTION_POINTER ShadowedPeiCore;
CACHE_SECTION_DATA CacheSection;
// 用于在固定地址加载模块功能以缓存将放置 Runtime 代码、启动时间代码和 PEI 内存的顶部地址。
//请注意,此字段与 Ps 之间的偏移量不应更改,因为也许用户可以通过使用 Ps 的偏移量来获取此顶部地址。
EFI_PHYSICAL_ADDRESS LoadModuleAtFixAddressTopAddress;
// 该字段定义为在固定地址加载模块功能以跟踪 PEI 代码内存范围使用情况。
// 它是一个位映射数组,其中每个位都指示相应的内存页是否可用。
UINT64 *PeiCodeMemoryRangeUsageBitMap;
// 该字段指向shadowed image读取功能
PE_COFF_LOADER_READ_FILE ShadowedImageRead;
UINTN TempPeimCount;
// 指向具有 TempPeimCount 条目数的临时缓冲区的指针。
EFI_PEI_FILE_HANDLE *TempFileHandles;
// 指向具有 TempPeimCount 条目数的临时缓冲区的指针。
EFI_GUID *TempFileGuid;
// PeiTempMem 和 Stack 不涵盖 Temp Memory Range。
// 这些内存范围将被迁移到物理内存中。
HOLE_MEMORY_DATA HoleData[HOLE_MAX_NUMBER];
};
PPI - ( PEIM to PEIM Interface ) 顾名思义,PPI就是PEIM之间沟通的接口。
PEI Foundation 协助 PEIM 相互交流,并提供接口以允许 PEIM 注册 PPI 并在另一个 PEIM 安装 PPI 时得到通知(回调)。
PPI的数据结构由一个 GUID 和 一个指针组成:
//PEI Ppi 服务列表描述符
#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
// PEIM 用来向 PEI Foundation.描述可用服务的数据结构。
typedef struct {
// 该字段是一组标志,描述了这个导入的表条目的特征。
UINTN Flags;
// 命名接口的 EFI_GUID 的地址。
EFI_GUID *Guid;
// 指向 PPI 的指针。 它包含安装服务所需的信息。
VOID *Ppi;
} EFI_PEI_PPI_DESCRIPTOR;
// PPI Functions
EFI_PEI_INSTALL_PPI InstallPpi;
EFI_PEI_REINSTALL_PPI ReInstallPpi;
EFI_PEI_LOCATE_PPI LocatePpi;
EFI_PEI_NOTIFY_PPI NotifyPpi;
原型就是 InstallPpi
/**
该函数通过 GUID 在 PEI PPI 数据库中安装一个接口。
该服务的目的是发布一个接口,其他方可以使用该接口调用其他 PEIM。
@param PeiServices 指向 PEI Foundation发布的 EFI_PEI_SERVICES 表的间接指针。
@param PpiList 指向 PEI PPI 描述符列表的指针。
@retval EFI_SUCCESS 如果 PpiList 中的所有 PPI 都已成功安装。
@retval EFI_INVALID_PARAMETER 如果 PpiList 是 NULL 指针
如果 PpiList 中的任何 PPI 无效
@retval EFI_OUT_OF_RESOURCES 如果没有更多的内存资源来安装 PPI
**/
EFI_STATUS
EFIAPI
PeiInstallPpi (
IN CONST EFI_PEI_SERVICES **PeiServices,
IN CONST EFI_PEI_PPI_DESCRIPTOR *PpiList
)
{
1. 通过 GUID 在 PEI PPI 数据库中安装一个接口。
把PPI interface 从头部向尾部插入到PPI database 的PPI LIST 数组中。
2. 使用 ProcessNotify 为新安装的 PPI 处理任何回调级别通知。
当PPI notify的级别为callback level 时:
1. 插入该PPI后,然后检查一下是否有与之GUID相同的PPI Notify被notify过。
2. 如果有,则调用该PPI notify的notify function完成相应的功能。
当PPI notify的级别为dispatch level 时:
1. 在每个PEIM 被执行后去检查是否有与之GUID相同的PPI被 install过。
2. 如果有,则调用该PPI notify的notify function完成相应的功能。
}
原型就是 ReInstallPpi
/**
此函数通过 GUID 重新安装 PEI PPI 数据库中的接口。
该服务的目的是发布一个接口,其他方可以使用该接口将协议数据库中的同名接口替换为不同的接口。
@param PeiServices 指向 PEI Foundation发布的 EFI_PEI_SERVICES 表的间接指针。
@param OldPpi 指向旧 PEI PPI 描述符的指针。
@param NewPpi 指向新 PEI PPI 描述符的指针。
@retval EFI_SUCCESS 如果操作成功
@retval EFI_INVALID_PARAMETER 如果 OldPpi 或 NewPpi 为 NULL
@retval EFI_INVALID_PARAMETER 如果 NewPpi 无效
@retval EFI_NOT_FOUND 如果 PPI 不在数据库中
**/
EFI_STATUS
EFIAPI
PeiReInstallPpi (
IN CONST EFI_PEI_SERVICES **PeiServices,
IN CONST EFI_PEI_PPI_DESCRIPTOR *OldPpi,
IN CONST EFI_PEI_PPI_DESCRIPTOR *NewPpi
)
{
1. 通过 GUID 重新安装 PEI PPI 数据库中的接口。
遍历PPI LIST 数组,检测到对应的GUID,重新插入到PPI LIST 数组。
2. 使用 ProcessNotify 为新安装的 PPI 处理任何回调级别通知。
当PPI notify的级别为callback level 时:
1. 插入该PPI后,然后检查一下是否有与之GUID相同的PPI Notify被notify过。
2. 如果有,则调用该PPI notify的notify function完成相应的功能。
当PPI notify的级别为dispatch level 时:
1. 在每个PEIM 被执行后去检查是否有与之GUID相同的PPI被 install过。
2. 如果有,则调用该PPI notify的notify function完成相应的功能。
}
原型就是 LocatePpi
/**
找到给定的命名 PPI。
@param PeiServices 指向 PEI Foundation发布的 EFI_PEI_SERVICES 表的间接指针。
@param Guid 指向 PPI 的 GUID 的指针。
@param Instance 要发现的实例编号。
@param PpiDescriptor 指向找到的描述符的指针。 如果不为 NULL,
返回指向描述符的指针(包括标志等)
@param Ppi 指向找到的 PPI 的指针
@retval EFI_SUCCESS 如果 PPI 在数据库中
@retval EFI_NOT_FOUND 如果 PPI 不在数据库中
**/
EFI_STATUS
EFIAPI
PeiLocatePpi (
IN CONST EFI_PEI_SERVICES **PeiServices,
IN CONST EFI_GUID *Guid,
IN UINTN Instance,
IN OUT EFI_PEI_PPI_DESCRIPTOR **PpiDescriptor,
IN OUT VOID **Ppi
)
{
1. 在数据库中搜索 GUID PPI 的匹配实例。
但是PPI notify是无法被locate到
2. 返回结果
}
原型就是 NotifyPpi
/**
此函数安装一个通知服务,以便在安装或重新安装给定接口时回调。
该服务的目的是发布一个接口,其他方可以使用该接口来调用可能稍后实现的其他 PPI。
@param PeiServices 指向 PEI Foundation 发布的 EFI_PEI_SERVICES 表的间接指针。
@param NotifyList 指向要通知的描述符列表的指针。
@retval EFI_SUCCESS 如果成功
@retval EFI_OUT_OF_RESOURCES 如果数据库中没有空间
@retval EFI_INVALID_PARAMETER 如果不是一个好的描述符
**/
EFI_STATUS
EFIAPI
PeiNotifyPpi (
IN CONST EFI_PEI_SERVICES **PeiServices,
IN CONST EFI_PEI_NOTIFY_DESCRIPTOR *NotifyList
)
{
1. 把PPI notify 插入PPI database 的PPI LIST 数组中
插入顺序是从PPI LIST的尾部向头部插入
2. 使用 ProcessNotify 为所有以前安装的 PPI 处理任何回调级别通知。
当PPI notify的级别为callback level 时:
1. 插入该PPI后,然后检查一下是否有与之GUID相同的PPI Notify被notify过。
2. 如果有,则调用该PPI notify的notify function完成相应的功能。
当PPI notify的级别为dispatch level 时:
1. 在每个PEIM 被执行后去检查是否有与之GUID相同的PPI被 install过。
2. 如果有,则调用该PPI notify的notify function完成相应的功能。
}
当一个PEIM需要提供功能给其它PEIM使用时,需要:
参考 PEI编程练习 - Ppi练习
BootMode为启动时的引导方式,uefi和legacy是两种不同的引导方式,uefi是新式的BIOS,legacy是传统BIOS。
HOB (Hand-Off Blocks )传输信息的载体。相比于其他Phase之间的联系,Pei到DXE之间联系比较薄弱,PEI一些初始化硬件、内存的数据等,DXE需要知道,HOB便作为桥梁应运而生。分为两个阶段:
HOB在PEI到DXE传送信息的过程遵循one Producer to one Consumer的模式,即在PEI阶段,一个PEIM创建一个HOB,在DXE阶段,一个DXE Driver使用那个HOB并且把HOB相关的信息传送给其他的需要这些信息的DXE组件。
HOB 列表最初由 HOB 生产者阶段构建。 HOB 列表是在存在、初始化和测试的内存中创建的。
HOB list是在PEI Phase被建立的,它存在于已经present,initialized,tested的Memory中。一旦最初的HOB List被创建,物理内存就不能被remapped, interleaved, 或者被后来的程序moved。
Hoblist 存储在PrivateData ,在PEI末阶段传入DXE。所有HOB类型如下:
// Union of all the possible HOB Types.
typedef union {
EFI_HOB_GENERIC_HEADER *Header;
EFI_HOB_HANDOFF_INFO_TABLE *HandoffInformationTable;
EFI_HOB_MEMORY_ALLOCATION *MemoryAllocation;
EFI_HOB_MEMORY_ALLOCATION_BSP_STORE *MemoryAllocationBspStore;
EFI_HOB_MEMORY_ALLOCATION_STACK *MemoryAllocationStack;
EFI_HOB_MEMORY_ALLOCATION_MODULE *MemoryAllocationModule;
EFI_HOB_RESOURCE_DESCRIPTOR *ResourceDescriptor;
EFI_HOB_GUID_TYPE *Guid;
EFI_HOB_FIRMWARE_VOLUME *FirmwareVolume;
EFI_HOB_FIRMWARE_VOLUME2 *FirmwareVolume2;
EFI_HOB_FIRMWARE_VOLUME3 *FirmwareVolume3;
EFI_HOB_CPU *Cpu;
EFI_HOB_MEMORY_POOL *Pool;
EFI_HOB_UEFI_CAPSULE *Capsule;
UINT8 *Raw;
} EFI_PEI_HOB_POINTERS;
其中 EFI_HOB_GUID_TYPE 允许PEIM将私有数据传递给DXE驱动程序 (自定义HOB时候会用到的类型,通常用于自己写一些HOB信息。)
// HobType of EFI_HOB_GENERIC_HEADER.
#define EFI_HOB_TYPE_HANDOFF 0x0001
#define EFI_HOB_TYPE_MEMORY_ALLOCATION 0x0002
#define EFI_HOB_TYPE_RESOURCE_DESCRIPTOR 0x0003
#define EFI_HOB_TYPE_GUID_EXTENSION 0x0004
#define EFI_HOB_TYPE_FV 0x0005
#define EFI_HOB_TYPE_CPU 0x0006
#define EFI_HOB_TYPE_MEMORY_POOL 0x0007
#define EFI_HOB_TYPE_FV2 0x0009
#define EFI_HOB_TYPE_LOAD_PEIM_UNUSED 0x000A
#define EFI_HOB_TYPE_UEFI_CAPSULE 0x000B
#define EFI_HOB_TYPE_FV3 0x000C
#define EFI_HOB_TYPE_UNUSED 0xFFFE
#define EFI_HOB_TYPE_END_OF_HOB_LIST 0xFFFF
// 描述 HOB 内数据的格式和大小。 所有 HOB 都必须包含此通用 HOB 标头。
typedef struct {
// 标识 HOB 数据结构类型。
UINT16 HobType;
// HOB 的字节长度。
UINT16 HobLength;
// 该字段必须始终设置为零。
UINT32 Reserved;
} EFI_HOB_GENERIC_HEADER;
// EFI_HOB_HANDOFF_INFO_TABLE 中的版本值。
#define EFI_HOB_HANDOFF_TABLE_VERSION 0x0009
// 包含 HOB 生产者阶段使用的一般状态信息。
// 此 HOB 必须是 HOB 列表中的第一个。
typedef struct {
/// HOB 通用标头。 Header.HobType = EFI_HOB_TYPE_HANDOFF.
EFI_HOB_GENERIC_HEADER Header;
// 与 PHIT HOB 定义有关的版本号。
// 这个值是四个字节的长度,当它与 4 字节的 BootMode 组合时提供一个 8 字节对齐的条目。
UINT32 Version;
// 在 HOB 生产者阶段确定的系统引导模式。
EFI_BOOT_MODE BootMode;
// 分配给 HOB 生产者阶段使用的内存的最高地址位置。
//此地址必须对齐 4 KB 以满足 UEFI 的页面限制。
EFI_PHYSICAL_ADDRESS EfiMemoryTop;
// 分配给 HOB 生产者阶段使用的内存的最低地址位置。
EFI_PHYSICAL_ADDRESS EfiMemoryBottom;
// 当前可供 HOB 生产者阶段使用的空闲内存的最高地址位置。
EFI_PHYSICAL_ADDRESS EfiFreeMemoryTop;
// 可供 HOB 生产者阶段使用的空闲内存的最低地址位置。
EFI_PHYSICAL_ADDRESS EfiFreeMemoryBottom;
// HOB 列表的结尾。
EFI_PHYSICAL_ADDRESS EfiEndOfHobList;
} EFI_HOB_HANDOFF_INFO_TABLE;
PeiCore -> InitializeMemoryServices -> PeiCoreBuildHobHandoffInfoTable
必须遵循以下的规则:
每个HOB必须以一个HOB generic header开头(EFI_HOB_GENERIC_HEADER)。此要求允许用户在跳过其余部分时找到他们感兴趣的 HOB。
HOBs可以包含boot services data,在DXE Phase结束之前,PEI和DXE都可以调用。
HOBs可以被DXE重新安置在系统内存上,每个HOB都不能包含指向HOB List中其他数据的指针,也不能指向其他的HOB,这个Table必须可以被Copied而不需要任何内部指针的调整。
所有的HOB在长度上必须是8 bytes的倍数。此要求符合 Itanium® 处理器系列的对齐限制。
PHIT HOB必须总是在8 byte处开始。由于此要求和此列表中的要求 #4,所有 HOB 都将从 8 字节边界开始。
增加的HOB总是被加到HOB List的最后,而且只能在PEI Phase(HOB Producer Phase)增加,DXE Phase(HOB Consumer Phase)不能。
HOBs不能被删除。每个HOB的generic header中都会描述这个HOB的长度,这样下一个HOB就很容易被找到。
如下:
edk2\MdeModulePkg\Core\Pei\Hob\Hob.c
/**
构建切换信息表 HOB
@param BootMode - 当前引导模式
@param MemoryBegin - 起始内存地址。
@param MemoryLength - 内存长度。
@return EFI_SUCCESS 总是成功初始化 HOB。
**/
EFI_STATUS
PeiCoreBuildHobHandoffInfoTable (
IN EFI_BOOT_MODE BootMode,
IN EFI_PHYSICAL_ADDRESS MemoryBegin,
IN UINT64 MemoryLength
)
{
EFI_HOB_HANDOFF_INFO_TABLE *Hob;
EFI_HOB_GENERIC_HEADER *HobEnd;
Hob = (VOID *)(UINTN)MemoryBegin;
HobEnd = (EFI_HOB_GENERIC_HEADER*) (Hob+1);
Hob->Header.HobType = EFI_HOB_TYPE_HANDOFF;
Hob->Header.HobLength = (UINT16) sizeof (EFI_HOB_HANDOFF_INFO_TABLE);
Hob->Header.Reserved = 0;
HobEnd->HobType = EFI_HOB_TYPE_END_OF_HOB_LIST;
HobEnd->HobLength = (UINT16) sizeof (EFI_HOB_GENERIC_HEADER);
HobEnd->Reserved = 0;
Hob->Version = EFI_HOB_HANDOFF_TABLE_VERSION;
Hob->BootMode = BootMode;
Hob->EfiMemoryTop = MemoryBegin + MemoryLength;
Hob->EfiMemoryBottom = MemoryBegin;
Hob->EfiFreeMemoryTop = MemoryBegin + MemoryLength;
Hob->EfiFreeMemoryBottom = (EFI_PHYSICAL_ADDRESS) (UINTN) (HobEnd + 1);
Hob->EfiEndOfHobList = (EFI_PHYSICAL_ADDRESS) (UINTN) HobEnd;
return EFI_SUCCESS;
}
PEI Phase(HOB Producer Phase)肯定包含一个指向PHIT HOB(这是HOB List的开始)的指针,然后遵循以下的步骤:
确定NewHobSize,即确定要创建的HOB的大小(以Byte为单位)。
确定是否有足够的空闲内存分配给新的HOB(NewHobSize <= (PHIT->EfiFreeMemoryTop - PHIT->EfiFreeMemoryBottom))。
在(PHIT->EfiFreeMemoryBottom)处构建HOB。
设置PHIT->EfiFreeMemoryBottom = PHIT->EfiFreeMemoryBottom + NewHobSize 。
/**
将新的 HOB 添加到 HOB 列表。
@param PeiServices 指向 PEI 基金会发布的 EFI_PEI_SERVICES 表的间接指针。
@param Type 新 HOB 的类型。
@param Length 要分配的新 HOB 的长度。
@param Hob 指向新 HOB 的指针。
@return EFI_SUCCESS 成功创建 HOB。
@retval EFI_INVALID_PARAMETER 如果 Hob 为 NULL
@retval EFI_NOT_AVAILABLE_YET 如果 HobList 仍然不可用。
@retval EFI_OUT_OF_RESOURCES 如果没有更多内存来增长 Hoblist。
**/
EFI_STATUS
EFIAPI
PeiCreateHob (
IN CONST EFI_PEI_SERVICES **PeiServices,
IN UINT16 Type,
IN UINT16 Length,
IN OUT VOID **Hob
)
{
EFI_STATUS Status;
EFI_HOB_HANDOFF_INFO_TABLE *HandOffHob;
EFI_HOB_GENERIC_HEADER *HobEnd;
EFI_PHYSICAL_ADDRESS FreeMemory;
Status = PeiGetHobList (PeiServices, Hob);
if (EFI_ERROR(Status)) {
return Status;
}
HandOffHob = *Hob;
//
// Check Length to avoid data overflow.
//
if (0x10000 - Length <= 0x7) {
return EFI_INVALID_PARAMETER;
}
Length = (UINT16)((Length + 0x7) & (~0x7));
FreeMemory = HandOffHob->EfiFreeMemoryTop -
HandOffHob->EfiFreeMemoryBottom;
if (FreeMemory < Length) {
DEBUG ((EFI_D_ERROR, "PeiCreateHob fail: Length - 0x%08x\n", (UINTN)Length));
DEBUG ((EFI_D_ERROR, " FreeMemoryTop - 0x%08x\n", (UINTN)HandOffHob->EfiFreeMemoryTop));
DEBUG ((EFI_D_ERROR, " FreeMemoryBottom - 0x%08x\n", (UINTN)HandOffHob->EfiFreeMemoryBottom));
return EFI_OUT_OF_RESOURCES;
}
*Hob = (VOID*) (UINTN) HandOffHob->EfiEndOfHobList;
((EFI_HOB_GENERIC_HEADER*) *Hob)->HobType = Type;
((EFI_HOB_GENERIC_HEADER*) *Hob)->HobLength = Length;
((EFI_HOB_GENERIC_HEADER*) *Hob)->Reserved = 0;
HobEnd = (EFI_HOB_GENERIC_HEADER*) ((UINTN) *Hob + Length);
HandOffHob->EfiEndOfHobList = (EFI_PHYSICAL_ADDRESS) (UINTN) HobEnd;
HobEnd->HobType = EFI_HOB_TYPE_END_OF_HOB_LIST;
HobEnd->HobLength = (UINT16) sizeof (EFI_HOB_GENERIC_HEADER);
HobEnd->Reserved = 0;
HobEnd++;
HandOffHob->EfiFreeMemoryBottom = (EFI_PHYSICAL_ADDRESS) (UINTN) HobEnd;
return EFI_SUCCESS;
}
PEI编程练习 - hob简单练习
内存发现是 PEI 阶段的重要架构事件。 当 PEIM 成功发现、初始化和测试了连续范围的系统 RAM 时,它会将这个 RAM 报告给 PEI Foundation。 当 PEIM 退出时,PEI Foundation将临时 RAM 的 PEI 使用迁移到真实系统 RAM,这涉及以下两个任务:
一旦这个过程完成,PEI Foundation 就会安装一个架构 PPI 来通知任何感兴趣的 PEIM 已经安装了真正的系统内存。此通知允许回调在安装内存之前运行的 PEIM,以便它们可以在实际系统内存中完成必要的任务,例如为 DXE 的下一阶段构建 HOB。
状态代码类型由代码类型 (EFI_STATUS_CODE_TYPE) 和严重性 ( EFI_STATUS_CODE_VALUE) 组成.
位于: edk2\MdePkg\Include\Pi\PiStatusCode.h
PeiResetSystem2 -> PeiServicesLocatePpi (
&gEfiPeiReset2PpiGuid,
0,
NULL,
(VOID **)&Reset2Ppi
);
if (!EFI_ERROR (Status)) {
Reset2Ppi->ResetSystem (ResetType, ResetStatus, DataSize, ResetData);
return;
}
PeiResetSystem -> PeiServicesLocatePpi (
&gEfiPeiResetPpiGuid,
0,
NULL,
(VOID **)&ResetPpi
);
if (!EFI_ERROR (Status)) {
return ResetPpi->ResetSystem (PeiServices);
}
PeiServices在PeiCore初始化阶段发布的EFI_PEI_CPU_IO_PPI支持的默认版本。
EFI_PEI_CPU_IO_PPI由某些平台或特定于芯片组的PEIM安装,这些PEIM抽象了处理器可见的I/O操作。当PeiCore启动时,EFI_PEI_CPU_IO_PPI的默认版本将被分配给PeiServices表。
这个默认的EFI_PEI_CPU_IO_PPI安装实例分配给EFI_PEI_SERVICE。PeiCore初始化时的CpuIo
edk2\MdeModulePkg\Core\Pei\CpuIo\CpuIo.c
// 这个默认的 EFI_PEI_CPU_IO_PPI 安装实例在 PeiCore 初始化时分配给 EFI_PEI_SERVICE.CpuIo。
EFI_PEI_CPU_IO_PPI gPeiDefaultCpuIoPpi = {
{
PeiDefaultMemRead,
PeiDefaultMemWrite
},
{
PeiDefaultIoRead,
PeiDefaultIoWrite
},
PeiDefaultIoRead8,
PeiDefaultIoRead16,
PeiDefaultIoRead32,
PeiDefaultIoRead64,
PeiDefaultIoWrite8,
PeiDefaultIoWrite16,
PeiDefaultIoWrite32,
PeiDefaultIoWrite64,
PeiDefaultMemRead8,
PeiDefaultMemRead16,
PeiDefaultMemRead32,
PeiDefaultMemRead64,
PeiDefaultMemWrite8,
PeiDefaultMemWrite16,
PeiDefaultMemWrite32,
PeiDefaultMemWrite64
};
PeiServices在PeiCore初始化阶段发布的EFI_PEI_PCI_CFG2_PPI支持的默认版本。EFI_PEI_PCI_CFG2_PPI由支持PCI根桥的PEIM安装。当PeiCore启动时,EFI_PEI_PCI_CFG2_PPI的默认版本将被分配给PeiServices表
edk2\MdeModulePkg\Core\Pei\PciCfg2\PciCfg2.c
// 这个 EFI_PEI_PCI_CFG2_PPI 的默认实例在 PeiCore 初始化时分配给 EFI_PEI_SERVICE.PciCfg。
EFI_PEI_PCI_CFG2_PPI gPeiDefaultPciCfg2Ppi = {
PeiDefaultPciCfg2Read,
PeiDefaultPciCfg2Write,
PeiDefaultPciCfg2Modify
};
ROM固件(Flash Device binary image)由一个或多个Firmware volume(FV)构成,每个FV里存放了FFS Image(EFI Firmware File system),FFS Image则由多个EFI Section构成,EFI Section包含了PE32/PE32+/Coff Image文件。
S3 恢复(保存到 RAM 恢复)上的 PEI 阶段在几个方面有所不同
正常启动时从 PEI 阶段开始的基本方法。 区别如下:
Recovery (恢复)
在查看特定平台时,可以综合所有有关 PEI 的概念。下面的列表代表一个带有所有相关系统组件的 865 系统。
下图 显示了同样的系统,其中包括实际的硅组件。
下图提供了同一系统的理想化版本。后图中的组件具有相应的 PEIM 来抽象组件的初始化和服务。对于这些组件中的每一个,可以交付一个到多个 PEI 模块,以抽象特定组件的行为。这些组件的示例可以包括:
参考: