UEFI之PEI阶段

文章目录

      • 一、PEI 简介
      • 二、相关名词解释
      • 三、PEI 流程
        • 3.1 PEI执行顺序
        • 3.2 PeiMain.inf
        • 3.3 函数PeiCore()
      • 四、PEIM
        • 4.1 PEIM简介
        • 4.2 PEIM分析
      • 五、PPI
        • 5.1 PPI 简介
        • 5.2PPI 例子

一、PEI 简介

PEI (Pre-EFI Initialization)阶段资源依然十分有限,内存到了PEI后期才被初始化。其主要任务是为DXE准备执行环境。需要将传递到DXE的信息组成HOB(Handoff Block)列表,最终将控制权交到DXE手中。

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

  • PEI Core(PEI Foundation):负责PEI基础服务和流程。
  • PEIM(Pre-EFI Initialize Module)派遣器:主要功能是根据Flash文件系统的定义遍历固件卷FV,找出所有PEIM。并根据PEIM之间的依赖关系按顺序执行PEIM。PEI阶段对系统的初始化主要是由PEIM完成的,不同的PEIM之间可以通过PPI(PEIM—to-PEIM Interface) 完成数据传输。

在UEFI启动过程中主要完成以下任务

  1. 芯片组初始化
  2. 内存初始化
  3. UEFI环境初始化
  4. 将代码运行环境切换到内存 (取消 CAR,重新启用缓存)
  5. 启动DXEIPL(DXE Initial Program Loader)

UEFI之PEI阶段_第1张图片

通过PeiServices,PEIM可以使用PEI阶段提供的系统服务,通过这些系统服务,PEIM可以访问PEI内核。PEIM之间的通信通过PPI(PEIM-to-PEIM Interfaces)完成。每个PPI都有一个GUID。

二、相关名词解释

  1. PEI Core:PEI内核,负责提供PEI阶段的基础服务和执行流程,存储在BFV区域。
  2. PEIM(Pre-EFI Initialize Module):独立的模块,每一个都负责一项具体的初始化工作,存储在FVs区域。
  3. PPI:(PEIM to PEIM Interface),每个PEIM中都包含的一个结构体,有函数指针和GUID,可以让一个PEIM调用另一个PEIM。
  4. PEI Dispatcher:PEI Core的一部分,用来寻找BFV中存储的PEIM并启动它们。
  5. PEI Service:PEI Core提供给所有PEIM使用的基础服务。
  6. HOB:Hand-off block,是PEI阶段向DXE传递系统信息的手段。在PEI阶段构建一些HOB结构来存放系统状态数据,然后将其作为参数传给DXE入口函数,DXE Core会根据HOB列表来初始化UEFI系统服务。在HOB List中的第一个HOB必须是PHIT HOB(Phase Handoff Information Table),最后一个HOB必须是End of HOB List HOB。中间的HOB列表用来存放信息。HOB在PEI到DXE传送信息的过程遵循one Producer to one Consumer的模式,即在PEI阶段,一个PEIM创建一个HOB,在DXE阶段,一个DXE Driver使用那个HOB并且把HOB相关的信息传送给其他的需要这些信息的DXE组件。

三、PEI 流程

从SEC阶段分析得知,PEI入口函数是PeiCore,过程是:_ModuleEntryPoint(SecCoreData, PpiList) -> ProcessModuleEntryPointList (SecCoreData, PpiList, NULL) -> PeiCore (SecCoreData, PpiList, Context);

函数PeiCore()位于:MdeModulePkg/Core/Pei/PeiMain/PeiMain.c

3.1 PEI执行顺序

以龙芯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的初始化。

3.2 PeiMain.inf


[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     //函数入口

3.3 函数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 ()

  • 主要的调度循环会在已知的FV区域中搜索PEIM,并尝试调度它们。
  • 如果有任何PEIM被成功调度并完成了该部分工作,就会在OldCoreData->HobList中添加一个HOB,然后此循环会从Bfv重新开始搜索,从而确定是否存在运行条件已被满足的新的PEIM。
  • 如果FV中的PEIM存放顺序完美遵循了依赖关系,每个PEIM被发现时都满足运行条件,则该循环就只会运行一次。

在每次PEIM成功运行时,调用PeiCheckAndSwitchStack (SecCoreData, Private); 来检查内存是否已被初始化,若是则进行栈切换,流程如下:

  1. 在将堆栈从临时内存切换到永久内存之前,计算临时内存中的堆和堆栈使用情况以输出调试用的信息。
  2. 为PEI代码预分配内存范围。
  3. 在物理内存的底部保留新堆栈的空间,此空间不小于临时内存中堆栈的大小。
  4. 分别计算临时内存和新永久内存之间的栈偏移量和堆偏移量
  5. 构建保存永久内存堆栈信息的HOB。
  6. 缓存SecCoreData中的信息,以免在栈切换过程中丢失。
  7. 计算永久内存中栈内的HandOffTable和PrivateData新地址。
  8. 调用TemporaryRamSupportPpi的TemporaryRamMigration函数,它将临时内存复制到永久内存并切换运行环境。调用该函数之后,代码运行使用的堆栈都位于永久内存中。
  9. 调用MigrateMemoryPages将之前分配的内存也迁移到永久内存中。
  10. 重新启动PeiCore。
    (返回到PeiMain.c的代码中)判断OldCoreData是否为空,不为空,表明这是在内存初始化后进入PEI Core。
  11. 读取PeiCore模块代码并存入永久内存中。
  12. 获取指向PEI服务列表的指针。初始化PEI服务,使其常驻内存(此时为永久内存),可以被随时调用。
  13. 再次启动PeiDispatcher,调度剩余的PEIM进行初始化工作。完成后返回结束标记。
  14. 调用PeiServicesLocatePpi来定位DXE IPL PPI,此PPI用来获取DXE阶段入口地址。如果没有找到则报告EFI_ERROR_CODE和EFI_SW_PEI_CORE_EC_DXEIPL_NOT_FOUND,并调用CpuDeadLoop函数,启动过程停止。
  15. 进入DXE入口并传递HOB列表。
  16. PEI阶段结束。

四、PEIM

4.1 PEIM简介

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

PEIM 可包含处理器、芯片组、设备或者其它特殊平台功能的可执行二进制文件。

  • 特定于平台的 PEIM
  • 特定于处理器的 PEIM
  • 芯片组专用 PEIM
  • PEI CIS——规定的架构 PEIM
  • 其他 PEIM

PEIM可抽象成可提供PEIM或PEI Foundation与PEIM或者硬件间通信的接口。
PEIM是一个单独编译成的二进制模块,绝大部分驻留在ROM中并且没有被压缩。
可能有一小部分PEIM为了性能原因在RAM中运行,这些以压缩格式放在ROM中。
放在ROM中的PEIM是内置式可执行的模块,这些模块可能是由位置独立代码或者位置相关代码与重定位信息一起组成的。
每个 PEI 模块 (PEIM) 都存储在一个文件中。 它由以下部分组成:

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

4.2 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

5.1 PPI 简介

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

  • InstallPpi() 安装PPI到PEI foundation,protocol install完后是放到Handle Datebase里面
  • LocatePpi() 根据PPI名字GUID从PEI foundation找Interface
  • NotifyPpi() PPI里的function不会在派发时就执行,会有一个判定条件,通知系统这个PPI会在某个PPI被安装时才执行。

5.2PPI 例子

  1. xxxx.h
GLOBAL_REMOVE_IF_UNREFERENCED EFI_GUID gEfiXxxPpiGuid = { 0x56A9F5C8, 0x9A19, 0x854C, { 0x01, 0xA6, 0x13, 0x35, 0xDE, 0x25, 0x49, 0xFC } };
  1. XxxSource.c

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;
}
  1. XxxSource.inf
[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
  1. XxxUse.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();

  1. XxxUse.inf
[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

你可能感兴趣的:(UEFI学习,服务器,linux,uefi,固件)