UEFI BIOS —— PEI阶段分析

UEFI BIOS —— PEI阶段分析_第1张图片

PEI(Pre-EFI Initialization) -预先EFI初始化)阶段

一、PEI 主要功能

PEI阶段资源依然十分有限,内存到了PEI后期才被初始化。其主要功能是为DXE准备执行环境,将需要传递给DXE的信息组成HOB(Handoff Block)列表,最终将控制权交给DXE。执行流程如下:
UEFI BIOS —— PEI阶段分析_第2张图片

二、PEI 代码流程分析

UEFI BIOS —— PEI阶段分析_第3张图片

以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 Core分析

从功能上讲,PEI可分为两部分:

  1. PEI Foundation (PEI 内核):负责PEI基础服务和流程。
  2. Core Dispatcher (PEIM 派遣器) —— PEI Module派遣器
    找出系统中所有的 PEIM,并根据 PEIM 之间的依赖关系按顺序执行 PEIM 。
    PEI阶段对系统初始化主要是由 PEIM 完成的。

框架如下:
UEFI BIOS —— PEI阶段分析_第4张图片

3.1 PEI Foundation

PEI Foundation 是一个用每个处理器结构编译成一个简单可执行的二进制文件。主要包括基础服务 (Core Services包) 和 流程。

  • 基础服务 (Core Services)包括PEI及后面phase要用到的各种Services, 比如Status Code, HOBs,Memory Services,Boot Mode Services等。

具体如下:

  1. 成功调度Pre-EFI初始化模块(PEIMs)
  2. 维护引导模式
  3. 永久内存初始化
  4. 调用驱动程序执行环境(DXE)加载程序PEI

3.2 PEIM

PEIM(PEI Module) 表示代码和/或数据单元。 它抽象了特定领域的逻辑,类似于 DXE 驱动程序。

  1. PEIM 可包含处理器,芯片组,设备,或者其它特殊平台功能的可执行二进制文件。
    • 特定于平台的 PEIM
    • 特定于处理器的 PEIM
    • 芯片组专用 PEIM
    • PEI CIS——规定的架构 PEIM
    • 其他 PEIM
  2. PEIM可抽象成可提供PEIM或PEI Foundation与PEIM或者硬件间通信的接口。
  3. PEIM是一个单独编译成的二进制模块,绝大部分驻留在ROM中并且没有被压缩。
    可能有一小部分PEIM为了性能原因在RAM中运行,这些以压缩格式放在ROM中。
    放在ROM中的PEIM是内置式可执行的模块,这些模块可能是由位置独立代码或者位置相关代码与重定位信息一起组成的。

每个 PEI 模块 (PEIM) 都存储在一个文件中。 它由以下部分组成:

  • 标准标题
  • 就地执行代码/数据部分
  • 可选的搬迁信息
  • 身份验证信息(如果存在)

3.2.1 PEIM 布局

PEIM 的可执行部分可以是位置相关的或位置无关的代码。如果 PEIM 的可执行部分是位置相关代码,则必须在 PEIM 映像中提供重定位信息,以允许 FV 存储软件将映像重定位到与编译时不同的位置。
PEIM 的典型布局如下:
UEFI BIOS —— PEI阶段分析_第5张图片

3.2.2 PEIM入口

PEIM的编译遵从UEFI,一般由:
inf 文件 + c文件 + h文件 + uni文件组成,编译生成 -> efi格式的二进制文件

比如 UhciPei.efiUsbBusPei.efi ,其中 UhciPei是设备可执行文件,而 UsbBusPei.efi 是给其他PEIM提供接口。

edk2\MdeModulePkg\Bus\Pci\UhciPei\UhciPei.inf
UEFI BIOS —— PEI阶段分析_第6张图片
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
  )

3.3 PEI Dispatcher

PEI 调度员的工作是以有序的方式将控制权移交给 PEIM。
PEI阶段操作包括调用PEI Foundation,有序调度所有PEIM,发现并调用下一个阶段,如图13.6所示。 在 PEI 基金会初始化期间,PEI 基金会初始化内部吗数据区域和函数,这些区域和函数是为 PEIM 提供公共 PEI 服务所需的。 在 PEIM 调度期间,PEI Dispatcher 遍历固件卷并根据闪存文件系统定义发现 PEIM。 如果满足以下条件,PEI Dispatcher 将调度 PEIM:
■ PEIM 尚未被调用。
■ PEIM 文件格式正确。
■ PEIM 值得信赖。
■ PEIM 的依赖性要求已得到满足

3.3.1 PeiDispatcher

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
  )

3.3.2 Dependency

在一个peim 在执行时,有可能会用到其他peim的功能。所以要等其他peim先执行后,并install ppi至 ppi database中,该peim才能使用,该ppi也才能继续执行下去,,这就是dependency。而这些关系记录放在fv的一个特殊的section中,一个peim有可能会需要很多其他peim先执行才能执行,所以表示方法用boolean运算符 and, or, 和sequencing .

  1. PEI调度器 (PEI Dispatcher)本质上是一个在PEI Foundation中实现的状态机。PEI调度器评估检查FV中的Pre-EFI初始化模块(PEIMs)中的依赖表达式(inf -> [Depex])。
  2. 依赖关系表达式是PPIs之间的逻辑结合。在PEIM使用PPIs之前,这些表达式用来描述该PPIs是否是可用的。为了评估依赖关系表达式给PEIM,PEI调度器查阅PEI Foundation中的PPI database来决定哪一个PPI可以被install。如果该PPI被install过了,那么相关的依赖关系表达式就会评估为TRUE,这样就可以告诉PEI调度器该PPI可以在PEIM中使用。在这一点上,PEI Foundation就是以一个为TRUE的依赖关系表达式来将控制权转给PEIM。
  3. 一旦PEI调度器评估了所有查找出的FV里的PEIM,并且没有更多的PEIM可以被调度(依赖没有从FALSE变成TRUE),PEI调度器将会退出。这个时候PEI调度器不能唤起任何额外的PEIM。之后PEI Foundation重新从PEI调度器中获得控制权,并唤起DXE IPL PPI将执行控制权传递给DXE阶段。

特定于依赖表达式和局部函数原型的数据在:
edk2\MdeModulePkg\Core\Pei\Dependency\Dependency.c

The PEI a priori file

PEI 先验文件 (The PEI a priori file)是一种特殊文件,它可以可选地存在于固件卷中,其主要目的是为平台的固件设计提供更大程度的灵活性。具体而言

  1. 先验文件通过规定一系列需要按规定顺序调度的模块来补充PEI的依赖关系表达机制。
  2. 平台中存在的每个固件卷最多可以有一个PEI先验文件。先验文件具有已知的 GUID 文件名 PEI_APRIORI_FILE_NAME_GUID,使 PEI Foundation 调度行为能够找到先验文件(如果存在)。文件的内容应包含格式为 PEI_APRIORI_FILE_CONTENTS 的数据,可能有零条目。
  3. 每次 PEI Dispatcher 发现固件卷时,它首先查找先验文件。先验文件中枚举的 PEIM 必须与先验文件本身存在于同一固件卷中;不允许跨卷映射。 PEI Foundation 将按照此文件中的顺序调用 PEI_APRIORI_FILE_CONTENTS 中列出的 PEIM。
  4. 如果没有先验文件,仅仅因为它们的依赖表达式而执行的 PEIM 是弱排序的。这意味着执行顺序在引导之间或平台之间不是完全确定的。在某些情况下,需要确定性的执行顺序。
  5. PEI 先验文件使用以下两种实现方法提供了 PEIM 的确定性执行顺序。所有 PEI Foundation 实现都必须支持先验模型,但它不排除额外的先验调度方法,只要后一种模型对备用先验模块列表使用不同的机制和/或文件名 GUID。

3.4 PeiServices

通过PeiServices,PEIM可以使用PEI阶段的系统服务,通过这些服务,PEIM可以访问PEI内核。

  • PEI Foundation 在系统中建立一个可让所有 PEIM 可视化的系统表,这个表被命名为
  • Services Table。一个PEI 服务程序可以被定义为一个函数,命令,或者其它功能,当需要初始化哪种服务时,PEI Foundation 就能把它表现出来。
  • 因为直到PEI后阶段有固定存储器,所以PEI阶段能被创建的服务程序的范围不能像PEI后阶段一样丰富。
  • 因为在编译的时候不知道PEI Foundation 和其临时内存的位置,所以一个指向PEI Service Table的指针被放进每个PEIM和PPI中。

3.4.1 PEI Service Table 定义

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;           // 释放内存页面
};

3.4.2 PEI Service Table实例 gPs

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。

3.4.3 PrivateData 定义

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];
};

3.4.4 PPI

PPI - ( PEIM to PEIM Interface ) 顾名思义,PPI就是PEIM之间沟通的接口。
PEI Foundation 协助 PEIM 相互交流,并提供接口以允许 PEIM 注册 PPI 并在另一个 PEIM 安装 PPI 时得到通知(回调)。

PPI结构如下图:
UEFI BIOS —— PEI阶段分析_第7张图片

3.4.4.1 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;

3.4.4.2 核心PPI Services

  // PPI Functions
  EFI_PEI_INSTALL_PPI             InstallPpi;
  EFI_PEI_REINSTALL_PPI           ReInstallPpi;
  EFI_PEI_LOCATE_PPI              LocatePpi;
  EFI_PEI_NOTIFY_PPI              NotifyPpi;
PeiServicesInstallPpi

原型就是 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完成相应的功能。
}
PeiServicesReInstallPpi

原型就是 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完成相应的功能。
}
PeiServicesLocatePpi

原型就是 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. 返回结果
}
PeiServicesNotifyPpi

原型就是 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完成相应的功能。
}

3.4.4.3 PPI实例

当一个PEIM需要提供功能给其它PEIM使用时,需要:

  1. 使用 PeiServicesInstallPpi ( 或者 PeiServicesReInstallPpi) 来 Install 一个 PPI 到PPI database中。
  2. 可以使用 PeiServicesNotifyPpi注册Notify,在 PeiServicesInstallPpi 执行完成时,会调用 Notify
  3. 其它 PEIM 要想使用这个 PPI, 就使用 PeiServicesLocatePpi 通 过GUID 找到这个PPI。

参考 PEI编程练习 - Ppi练习

3.4.5 BootMode

BootMode为启动时的引导方式,uefi和legacy是两种不同的引导方式,uefi是新式的BIOS,legacy是传统BIOS。
UEFI BIOS —— PEI阶段分析_第8张图片

3.4.6 HOB

HOB (Hand-Off Blocks )传输信息的载体。相比于其他Phase之间的联系,Pei到DXE之间联系比较薄弱,PEI一些初始化硬件、内存的数据等,DXE需要知道,HOB便作为桥梁应运而生。分为两个阶段:

  1. HOB producer phase (HOB 生产者阶段) 是创建 HOB 和 HOB 列表的预引导阶段。(于 PEI phase)
  2. HOB consumer phase (HOB 消费者阶段)是预引导阶段,HOB 列表传递到该阶段然后被消费。(于 PEI & DXE phase)

HOB在PEI到DXE传送信息的过程遵循one Producer to one Consumer的模式,即在PEI阶段,一个PEIM创建一个HOB,在DXE阶段,一个DXE Driver使用那个HOB并且把HOB相关的信息传送给其他的需要这些信息的DXE组件。

3.4.6.1 HOB LIST

HOB 列表最初由 HOB 生产者阶段构建。 HOB 列表是在存在、初始化和测试的内存中创建的。
HOB list是在PEI Phase被建立的,它存在于已经present,initialized,tested的Memory中。一旦最初的HOB List被创建,物理内存就不能被remapped, interleaved, 或者被后来的程序moved。

  • 在PEI Phase 中,使用的数据存储区就叫做HOBs,PEI可以使用HOB向DXE传递信息。
  • 在PEI Phase 中,HOBs是可读可写的,在DXE Phase中,只能读。
  • HOB是系列的连续的内存结构体 (所有的Hob放在一段连续的内存里面),可以认为其由三部分构成:
    • 第一部分,是 Phase Handoff Information Table (PHIT) 头,它描述了HOB的起始地址、总的内存使用以及期间发现的启动模式;
    • 第二部分是各个Hob列表,DXE阶段会根据这一部分获取上关资源;
    • 第三部分是结束部分。
      UEFI BIOS —— PEI阶段分析_第9张图片
      这些在内存中的HOBs的连续的表被称为HOB List。
      HOB 生产者阶段内存映射和使用:
      UEFI BIOS —— PEI阶段分析_第10张图片

3.4.6.2 HOB TYPE

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信息。)

3.4.6.3 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;

UEFI BIOS —— PEI阶段分析_第11张图片

PHIT 初始化

PeiCore -> InitializeMemoryServices -> PeiCoreBuildHobHandoffInfoTable

构造HOB

必须遵循以下的规则:

  1. 每个HOB必须以一个HOB generic header开头(EFI_HOB_GENERIC_HEADER)。此要求允许用户在跳过其余部分时找到他们感兴趣的 HOB。

  2. HOBs可以包含boot services data,在DXE Phase结束之前,PEI和DXE都可以调用。

  3. HOBs可以被DXE重新安置在系统内存上,每个HOB都不能包含指向HOB List中其他数据的指针,也不能指向其他的HOB,这个Table必须可以被Copied而不需要任何内部指针的调整。

  4. 所有的HOB在长度上必须是8 bytes的倍数。此要求符合 Itanium® 处理器系列的对齐限制。

  5. PHIT HOB必须总是在8 byte处开始。由于此要求和此列表中的要求 #4,所有 HOB 都将从 8 字节边界开始。

  6. 增加的HOB总是被加到HOB List的最后,而且只能在PEI Phase(HOB Producer Phase)增加,DXE Phase(HOB Consumer Phase)不能。

  7. 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;
}
增加一个新的HOB到HOB List中

PEI Phase(HOB Producer Phase)肯定包含一个指向PHIT HOB(这是HOB List的开始)的指针,然后遵循以下的步骤:

  1. 确定NewHobSize,即确定要创建的HOB的大小(以Byte为单位)。

  2. 确定是否有足够的空闲内存分配给新的HOB(NewHobSize <= (PHIT->EfiFreeMemoryTop - PHIT->EfiFreeMemoryBottom))。

  3. 在(PHIT->EfiFreeMemoryBottom)处构建HOB。

  4. 设置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;
}
hob简单练习

PEI编程练习 - hob简单练习

3.4.7 Memory

内存发现是 PEI 阶段的重要架构事件。 当 PEIM 成功发现、初始化和测试了连续范围的系统 RAM 时,它会将这个 RAM 报告给 PEI Foundation。 当 PEIM 退出时,PEI Foundation将临时 RAM 的 PEI 使用迁移到真实系统 RAM,这涉及以下两个任务:

  1. PEI Foundation 必须将 PEI 堆栈使用从临时 RAM 切换到永久系统内存。
  2. PEI Foundation 必须将 PEIM(包括 HOB)分配的简单堆迁移到真实系统 RAM。

一旦这个过程完成,PEI Foundation 就会安装一个架构 PPI 来通知任何感兴趣的 PEIM 已经安装了真正的系统内存。此通知允许回调在安装内存之前运行的 PEIM,以便它们可以在实际系统内存中完成必要的任务,例如为 DXE 的下一阶段构建 HOB。

  • InitializeMemoryServices () : 初始化内存服务。
  • PeiInstallPeiMemory () : 该功能向PEI Foundation注册发现的内存配置。
  • PeiAllocatePages () : 该服务的目的是发布一个接口,允许PEIMs分配由PEI Foundation管理的内存范围。
  • PeiAllocatePool () : 内存池分配服务。在发现永久内存之前,将在临时内存中为池分配堆。通常,临时内存中的堆大小不会超过64K,因此可以分配的最大池大小是64K。

3.4.8 StatusCode

状态代码类型由代码类型 (EFI_STATUS_CODE_TYPE) 和严重性 ( EFI_STATUS_CODE_VALUE) 组成.
位于: edk2\MdePkg\Include\Pi\PiStatusCode.h

  • PeiReportStatusCode () : 状态代码报告程序的核心版本

3.4.9 Reset

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);
  }

3.4.10 IO

CpuIo

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
};

PciCfg2

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
};

3.4.11 FV

  • FD:固件设备,指任何可以存储固件的设备或设备的集合,它存储代码和数据。
  • FV:固件卷,指在FD上一个连续的部分,我们可以把它看成一个逻辑设备,因为我们代码真正操作的是FV,而非FD。我们经常提到的FFS的概念也是以FV的形式存在,它描述了FV中的文件组织方式。FV之于FD,类似于thread之于package。
  • FF:固件文件,指在FV中组织代码和数据的一个集合。我们在PEI和DXE阶段都要Dispatch Modules,这个Modules指的就是FF。

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文件。

3.4.12 S3返回

S3 恢复(保存到 RAM 恢复)上的 PEI 阶段在几个方面有所不同
正常启动时从 PEI 阶段开始的基本方法。 区别如下:

  • 内存子部分恢复到其预睡眠状态,而不是初始化。
  • 操作系统拥有的系统内存不被PEI Foundation 或PEIM 使用。
  • DXE 阶段不会在恢复时分派,因为它会损坏内存。
  • 通常分派DXE 阶段的PEIM 改为使用特殊的硬件保存表将基本硬件恢复到引导配置。 恢复硬件后,PEIM 将控制权交给操作系统提供的恢复向量。
  • 正常引导期间的 DXE 和后续阶段会在 UEFI PI 保留内存或固件卷区域中保存足够的信息,以便将硬件恢复到操作系统可以用来恢复设备的状态。 此保存的信息位于硬件保存表中。

3.4.13 Recovery

Recovery (恢复)

  • Recovery 是当固件被破坏时重新建立一个系统固件设备的过程。这个错误(失效)可能是由各种机制引起的。大多数FV在非易失性存储设备中以块的形式被管理。当一个Block 或 semantically bound blocks正在更新时系统掉电,则存储器可能会变得无效。另一方面,固件设备可能会被错误的程序或错误的硬件损坏。假设 PEI 存在于容错 Block 中,它可以支持Recovery模式调度。
  • PEIM 可以检查“强制恢复”跳线以检测是否需要Recovery。 PEI Foundation 可能会发现特定 PEIM 未正确验证或FV已损坏。
  • Recovery 后的概念是有足够的系统固件被保留,以至以便系统可以引导到可以读取从所选外围设备丢失的数据的副本的点,然后使用该数据重新编程固件卷。
  • Recovery 固件的保存是 FV 存储管理方式的一项功能。在 UEFI PI flash 文件系统中,Recovery 所需的 PEIM 被标记为此类。FV 存储体系结构必须保留标记的项目,方法是使它们不可更改(可能有硬件支持)或使用容错更新过程保护它们。
  • 在发现 Recovery 模式之前,PEI Dispatcher 将正常运行。如果 PEI 调度程序遇到已损坏的 PEIM(例如,通过接收不正确的无用信息),它必须将引导模式更改为 Recovery。一旦设置为 Recovery,其他 PEIM 不得将其状态。 PEI Dispatcher 发现系统处于 Recovery 模式后,它将自行重新启动,仅调度 Recovery 所需的那些 PEIM。 PEIM 也有可能检测到灾难性状况或作为强制 Recovery 检测 PEIM, 并通知 PEI 调度程序它需要继续进行 Recovery 调度。当 PEIM 在 Recovery 介质上找到FV 且 DXE Foundation 从该FV启动时,Recovery 调度完成。该 DXE FV中的驱动程序可以执行恢复过程。

系统实例

在查看特定平台时,可以综合所有有关 PEI 的概念。下面的列表代表一个带有所有相关系统组件的 865 系统。
下图 显示了同样的系统,其中包括实际的硅组件。
UEFI BIOS —— PEI阶段分析_第12张图片

下图提供了同一系统的理想化版本。后图中的组件具有相应的 PEIM 来抽象组件的初始化和服务。对于这些组件中的每一个,可以交付一个到多个 PEI 模块,以抽象特定组件的行为。这些组件的示例可以包括:

  • Pentium® 4 处理器 PEIM:初始化和 CPU I/O 服务
  • PCI 配置 PEIM:PCI 配置 PPI
  • ICH PEIM:ICH 初始化和 SMBUS PPI
  • 内存初始化PEIM:通过SMBUS PPI读取SPD,初始化内存控制器,上报PEI内核可用内存
  • 平台 PEIM:创建闪存模式,检测启动模式
  • DXE IPL:启动 DXE、调用 S3 或恢复流的通用服务
    UEFI BIOS —— PEI阶段分析_第13张图片

参考:

  • 《Platform Initialization (PI) Specification》
  • 《UEFI原理与编程》
  • EDKII
  • 《Beyond BIOS》

你可能感兴趣的:(UEFI,uefi,c)