UEFI Training – UEFI PEI 阶段详细解析

本文将结合EDK2源代码,深入分析了UEFI固件启动过程中PEI(Pre-EFI Initialization)阶段的完整工作流程。PEI阶段是UEFI启动规范中的关键环节,负责在SEC阶段之后进行早期系统初始化,包括内存发现、PPI(PEIM-to-PEIM Interface)管理、PEIM模块调度等核心功能,最终将控制权移交给DXE阶段。它解决了在系统资源极度受限(如无有效内存)的情况下进行初始化的挑战,为后续的UEFI引导过程奠定基础。

1. PEI 阶段概述

PEI阶段是UEFI PI(Platform Initialization)规范定义的第二个启动阶段,位于SEC(Security)阶段和DXE(Driver Execution Environment)阶段之间。PEI阶段在SEC阶段提供了基本的初始化后(如CPU和临时SRAM),接管控制权,其核心职责包括:

1.早期系统初始化:在非常有限的资源(如CPU内部缓存或临时SRAM)下,执行最基本的硬件初始化,例如发现并初始化可用的系统内存。

2.建立PEIM运行时环境:初始化内存服务、PPI(PEIM-to-PEIM Interface)管理、文件系统服务等,为后续PEIM模块的执行提供必要的环境。

3.PEIM模块调度与执行:从发现的FV(Firmware Volume)中按照特定顺序调度和执行PEIM(PEI Module)模块,这些模块负责更具体的硬件初始化和功能设置。

4.构建Hand-Off Block (HOB) 列表:收集在PEI阶段发现和初始化的所有系统信息,将其组织成HOB列表,以便传递给后续的DXE阶段使用。

5.控制权移交:最终将控制权移交给DxeIpl(DXE Initial Program Loader),由其加载DXE Core并进入DXE阶段。

2. PEI Core 入口点和初始化

2.1 PEI Core 入口点

PEI Core的入口点定义为PeiCore函数,这是从SEC阶段转换到PEI阶段的关键接口。 这个函数通常被认为是PEI阶段的起点。

PeiCore函数接收来自SEC阶段的重要参数,这些参数提供了PEI Core运行所需的初始环境信息:

  • SecCoreDataPtr:指向SEC_PEI_CORE_DATA结构体的指针,该结构体包含了PEI Core运行环境的关键信息,例如:
  • 临时RAM的大小和位置:在永久内存可用之前,PEI Core和早期PEIMs将在此临时内存中执行。
  • 堆栈位置:指示PEI Core的初始堆栈。
  • BFV(Boot Firmware Volume)位置:指示包含PEI模块的固件卷的起始地址。
  • PpiList:SEC阶段安装的PPI描述符列表。SEC阶段可能会安装一些基本的PPI,例如EFI_PEI_TEMPORARY_RAM_SUPPORT_PPI,用于在临时内存和永久内存之间进行数据迁移。
  • Data:指向旧的核心数据的指针,用于处理PEI Core重新进入的情况。当PEI Core首次启动时(Pre-Memory PEI),此参数通常为NULL。当发现并初始化永久内存后,PEI Core会重新加载并再次执行,此时Data参数将指向前一次执行的状态数据,允许PEI Core从临时内存模式过渡到永久内存模式(Post-Memory PEI)。

2.2 PEI 服务表初始化

PEI Core维护一个全局的PEI服务表gPs(通常通过EFI_PEI_SERVICES结构体暴露),提供标准的PEI服务接口。 该服务表是PEIMs与PEI Core交互的主要途径,它包含了PEI阶段所需的所有核心服务,确保PEIMs能够以标准化的方式请求资源和执行操作:

  • PPI管理服务
    • InstallPpi:安装一个新的PPI实例,供其他PEIMs发现和使用。
    • ReInstallPpi:重新安装一个已存在的PPI,通常用于更新其实现。
    • LocatePpi:查找并获取一个特定PPI的实例。
    • NotifyPpi:注册一个回调函数,当某个特定的PPI被安装时触发。
  • 启动模式管理
    • GetBootMode:获取当前的系统启动模式(如正常启动、S3恢复、制造模式等)。
    • SetBootMode:设置当前的系统启动模式。
  • HOB管理
    • GetHobList:获取当前构建的HOB列表的头指针。
    • CreateHob:创建一个新的HOB,用于在PEI和DXE之间传递数据。
  • 文件系统服务
    • FfsFindNextVolume:在固件中查找下一个固件卷。
    • FfsFindNextFile:在固件卷中查找下一个文件(PEIM)。
  • 内存管理服务
    • InstallPeiMemory:安装永久内存。
    • AllocatePages:分配物理内存页面。
    • AllocatePool:从堆中分配内存池。
  • 状态报告和重置服务
    • ReportStatusCode:报告系统状态和错误代码,便于调试。
    • ResetSystem:执行系统复位。

2.3 PEI Core 两阶段执行模式

PEI Core支持两阶段执行模式,根据是否存在旧的核心数据(OldCoreData)来判断当前的执行状态: 这种设计是PEI阶段的关键特性,因为它允许PEI Core在发现永久内存前后以不同的方式运行,有效利用可用资源。

  • 第一阶段(临时内存模式 / Pre-Memory PEI)
    • 当OldCoreData为NULL时,表示系统首次进入PEI Core。
    • 此时系统内存尚未完全初始化,PEI Core及其早期PEIMs将在CPU缓存、或少量临时SRAM中运行。
    • 在此阶段,PEI Core会初始化PrivateData结构,并专注于内存发现和初始化等核心任务。
    • 此阶段的目标是尽可能快地发现并初始化主系统内存。
  • 第二阶段(永久内存模式 / Post-Memory PEI)
    • 当发现并初始化永久内存后,PEI Core会重新加载到永久内存中,并再次执行(OldCoreData非NULL)。
    • 在此阶段,PEI Core会进行内存迁移、指针修正,以确保所有数据结构和执行上下文都在新的、更快速的永久内存中。
    • 此阶段继续调度剩余的PEIMs,它们可以利用完整的内存功能和更丰富的PEI服务。
    • 通过在永久内存中运行,PEI Core和PEIMs的执行性能得到显著提升。

3. 内存管理和堆栈切换

PEI阶段的内存管理是其最重要的功能之一,它需要从有限的临时内存过渡到完整的系统内存。

3.1 内存服务初始化

PEI Core通过InitializeMemoryServices函数初始化内存管理服务。 这包括设置临时内存布局、初始化HOB(Hand-Off Block)列表的管理基础设施等。HOB列表是PEI阶段用于收集和传递系统配置信息给DXE阶段的关键数据结构。在这个阶段,PEI Core会创建第一个HOB——EFI_PEI_HOB_CPU,记录CPU相关信息。

3.2 堆栈切换机制

当永久内存可用时,PEI Core通过PeiCheckAndSwitchStack函数执行堆栈切换操作。 这是一个关键步骤,因为它将执行上下文从有限且可能较慢的临时RAM堆栈迁移到更充裕、更快的永久内存堆栈。该过程包括:

  1. 计算新堆栈大小和偏移量:根据需要计算在永久内存中分配的新堆栈的地址和大小。
  2. 构建堆栈HOB描述符:创建一个EFI_PEI_HOB_RESOURCE_DESCRIPTOR或类似类型的HOB,描述新的堆栈区域,以便DXE阶段能够了解和利用这部分内存。
  3. 使用TemporaryRamSupportPPI进行内存迁移:如果SEC阶段安装了EFI_PEI_TEMPORARY_RAM_SUPPORT_PPI,PEI Core会利用此PPI提供的服务,将关键数据(如PEI Core的私有数据、当前堆栈内容等)从临时RAM安全地迁移到永久内存中。
  4. 更新所有指针和数据结构:由于内存地址发生了变化,所有指向旧临时RAM地址的指针都需要被修正,指向新的永久内存地址,这包括PEI服务表、PPI描述符、HOB列表中的指针等。
  5. 切换到新的堆栈继续执行:通过汇编指令或特定的C语言构造,将CPU的堆栈指针(SP寄存器)更新为新的永久内存堆栈地址,从而在新的堆栈上继续执行。

4. PEI 调度器(PeiDispatcher)

PEI调度器是PEI阶段的核心,负责发现、评估依赖并执行各个PEIM模块。

4.1 调度器初始化

PEI调度器通过InitializeDispatcherData函数进行初始化, 设置调度器的数据成员,并初始化或重新初始化FV(Firmware Volume)处理。这包括扫描固件卷以发现其中包含的PEIM文件。

4.2 主调度循环

PeiDispatcher函数实现了PEI阶段的核心调度逻辑, 包含一个复杂的多层嵌套循环结构,旨在高效且有序地调度PEIMs,同时处理复杂的依赖关系和动态行为:

  • 外层循环: 
    • 这是最外层的循环,它持续执行直到没有需要调度的PEIM,或者在当前一轮中没有任何PEIM被成功调度。
    • 这种设计允许调度器处理延迟调度的PEIMs,以及那些需要等待其他PEIM安装特定PPI后才能满足依赖的PEIMs。它确保了只要有PEIM等待调度,调度器就会尝试执行。
    • 该循环还支持在发现新FV后重新扫描。
  • FV遍历循环: 
    • 这个循环遍历所有已发现的固件卷(FV)。
    • 对于每个FV,调度器会检查它是否具有对应的EFI_PEI_FIRMWARE_VOLUME_PPI实例。这个PPI提供了访问FV内容的接口,例如查找PEIM文件。
  • PEIM调度循环: 
    • PEIM_STATE_NOT_DISPATCHED:尚未被调度。
    • PEIM_STATE_DISPATCHED:已成功调度并执行。
    • PEIM_STATE_DONE:已完成执行。
    • PEIM_STATE_REGISTER_FOR_SHADOW:已注册影子模式,等待被复制到永久内存中执行。
    • 遍历当前FV中的所有PEIM。
    • 对于每个PEIM,调度器会检查其依赖关系(Dependency Expression,DEPEX)和当前的调度状态。PEIM的常见调度状态包括:
    • 如果PEIM的依赖关系满足,并且它尚未被调度,调度器将调用该PEIM的入口点函数,执行其初始化逻辑。

4.3 PEIM 发现和 Apriori 排序

调度器使用DiscoverPeimsAndOrderWithApriori函数发现FV中的PEIM,并根据Apriori文件重新排序。 

  • Apriori文件是一个特殊的FV文件,它包含了一系列PEIM的GUID列表。这些GUID定义了平台希望PEIM被调度的优先顺序
  • Apriori文件的作用是允许平台固件设计者强制特定的PEIM在其他PEIM之前执行,即便其DEPEX可能尚未完全满足(或者没有DEPEX)。这对于关键的早期初始化模块(如内存初始化PEIM、FVs发现PEIM)至关重要,确保它们能够优先执行以建立必要的基础设施。

4.4 依赖关系评估

DepexSatisfied函数负责评估PEIM的依赖关系(DEPEX), 判断是否可以调度特定的PEIM:

  1. Apriori文件中的PEIM:如果PEIM的GUID在Apriori文件中列出,则其依赖关系自动满足,调度器将优先执行它。这是一种强制调度机制。
  2. 无DEPEX段的PEIM:如果PEIM的固件文件头中没有DEPEX段(Dependency Expression Section),则假设该模块可以立即执行,因为它没有声明外部依赖。
  3. 有DEPEX段的PEIM:如果存在DEPEX段,调度器会解析并评估DEPEX表达式。DEPEX表达式使用了一种简单的后缀表示法,由一系列操作码和GUID组成,用于表示对特定PPI的安装(EFI_PEI_PPI_DESCRIPTOR_PPI)或通知(EFI_PEI_PPI_DESCRIPTOR_NOTIFY)的依赖。例如,一个DEPEX表达式可能表示“如果PPI_A已安装且PPI_B未安装PPI_C已安装,则该PEIM可以执行”。只有当表达式评估结果为真时,PEIM才会被调度执行。

5. Shadow 模式和性能优化

Shadow模式(影子模式)是PEI阶段的一种重要性能优化机制,它允许PEIM在系统内存可用后,从较慢的Flash存储或临时RAM复制到更快的永久内存中执行。

5.1 Shadow 注册机制

PEI Core支持PEIM的Shadow模式,允许PEIM在早期启动阶段(当永久内存不可用时)注册自己,以便在发现永久内存后被复制到内存中执行。 PeiRegisterForShadow函数提供了这一功能。PEIM通常在其入口点注册此功能,表明它希望在永久内存中运行以获得更好的性能。

5.2 Shadow 执行

当永久内存可用且满足条件时,调度器会处理已注册的Shadow PEIM:

  1. 调度器会将已注册的PEIM的图像从固件存储或临时RAM加载并复制到永久内存中。
  2. 更新PEIM的状态从PEIM_STATE_REGISTER_FOR_SHADOW到PEIM_STATE_DONE(或者一个中间状态表示已shadowed)。
  3. PEI Core会再次调用该PEIM的入口点,但这一次是通过永久内存中的地址调用。PEIM可以检测到它是以shadowed模式执行,并进行相应的初始化或状态调整。这使得PEIM可以利用永久内存的更高访问速度来提升性能。

5.3 PEI Core 自身的 Shadow

PEI Core本身也支持shadow操作,通过ShadowPeiCore函数实现。  这意味着PEI Core自身的大部分代码和数据结构也可以从临时RAM或Flash复制到永久内存中。这进一步提高了PEI Core自身的执行性能,尤其是在永久内存可用之后,使得后续的PEIM调度和系统初始化更为高效。

6. 延迟调度机制

PEI Core支持延迟调度机制,允许PEIM注册在指定条件满足或延迟后执行的回调函数。 

6.1 延迟调度表

延迟调度表维护了所有注册的延迟调度条目。每个条目包含一个回调函数和触发该回调的条件(例如,等待某个PPI的安装,或者达到某个特定的时间点)。

6.2 延迟调度执行

DelayedDispatchDispatcher函数负责处理延迟调度请求, 定期检查和执行到期的调度条目。这种机制对于那些不立即需要执行,或者需要等待特定硬件状态或资源准备就绪才能运行的PEIMs非常有用。例如,某个PEIM可能需要等待一个特定的I/O控制器初始化完成才能进行配置,此时就可以利用延迟调度。

7. PPI 管理系统

PPI(PEIM-to-PEIM Interface)是PEI阶段最重要的模块间通信机制。它类似于DXE阶段的协议(Protocol),允许不同的PEIMs相互发现并利用对方提供的服务。

7.1 PPI 安装和通知

PEI阶段通过PPI机制实现模块间通信。PEI Core提供了完整的PPI管理服务,包括:

  • 安装PPI (InstallPpi):当一个PEIM完成某个功能并希望将其服务暴露给其他PEIM时,它会安装一个PPI实例。
  • 重新安装PPI (ReInstallPpi):用于更新或替换一个已安装的PPI实例,常用于PEIM在不同阶段提供不同版本服务的情况。
  • 定位PPI (LocatePpi):PEIM可以通过GUID来查找并获取一个已安装的PPI实例,从而调用其提供的服务。
  • 通知机制 (NotifyPpi):PEIM可以注册一个回调函数,当某个特定的PPI被安装时,该回调函数会被自动触发。这使得PEIM能够响应其他模块的功能可用性变化。

7.2 内存发现 PPI

当PEI Core成功发现并初始化永久内存后,它会安装Memory Discovered PPI(EFI_PEI_MEMORY_DISCOVERED_PPI)。 这个PPI的安装是一个重要的里程碑,它表明系统现在有了可用的主内存。安装此PPI会触发所有注册了对Memory Discovered PPI通知的PEIM的回调函数,使这些PEIM能够进行在永久内存可用后才能执行的操作,例如加载更多的固件模块到内存中,或者执行内存敏感的初始化任务。

8. 向 DXE 阶段的转换

PEI阶段的最终目标是为DXE阶段的启动做好准备,并顺利将控制权移交给DXE Core。

8.1 DXE IPL 定位

PEI阶段的最后步骤是定位DXE IPL(Initial Program Loader)PPI(EFI_PEI_DXE_IPL_PPI)。 这个PPI是DXE阶段的入口点,由一个特殊的PEIM(通常是DxeIpl PEIM)提供。它负责加载并初始化DXE Core以及其核心服务。

8.2 控制权移交

成功定位DXE IPL PPI后,PEI Core会调用其Entry函数,传递PEI服务指针最终构建的HOB列表作为参数。 

  • PEI服务指针:虽然DXE阶段有自己的服务表,但在过渡初期,可能还需要一些PEI服务的支持。
  • HOB列表:这是最重要的传递参数。HOB(Hand-Off Block)列表是一个链表结构,包含了PEI阶段收集到的所有关于系统硬件、内存布局、CPU信息、已加载PEIM、ACPI表、SMBIOS表等关键信息。DXE Core会解析这个HOB列表,并根据其中的信息初始化DXE运行时环境和DXE服务。一旦控制权移交给DXE IPL,PEI阶段的任务就宣告完成。

9. 错误处理和状态报告

在固件启动过程中,错误处理和状态报告对于调试和系统稳定性至关重要。

9.1 状态码报告

PEI Core在关键节点报告状态码,包括初始化开始、PEIM调度状态、错误发生等, 便于调试和系统监控。这主要通过ReportStatusCode PPI(EFI_PEI_REPORT_STATUS_CODE_PPI)来实现。PEIMs可以调用此PPI的服务来报告各种事件、进展或错误,这些信息可以被固件内的日志机制或外部调试工具捕获。

10. Hand-Off Block (HOB) 列表

Hand-Off Block (HOB) 列表是PEI阶段创建并传递给DXE阶段的关键数据结构。它是一个由多个独立的HOB组成的链表,每个HOB都包含特定类型的信息。

  • 定义和目的:HOB是小型、自描述的数据结构,用于在PEI阶段收集和封装系统信息。其主要目的是将PEI阶段发现的所有硬件配置、内存布局、CPU状态、已加载模块等信息,以一种标准化的格式传递给DXE Core。
  • 类型:HOB有多种类型,例如:
    • EFI_PEI_HOB_CPU:描述CPU架构和特征。
    • EFI_PEI_HOB_MEMORY_ALLOCATION:描述内存区域的分配和类型(如临时RAM、永久RAM、保留内存)。
    • EFI_PEI_HOB_RESOURCE_DESCRIPTOR:描述系统资源,如PCI设备、内存范围等。
    • EFI_PEI_HOB_GUID_EXTENSION:允许PEIMs以自定义的GUID扩展方式传递特定数据。
    • EFI_PEI_HOB_FIRMWARE_VOLUME:描述已发现的固件卷。
  • 重要性:DXE Core在启动时会遍历和解析HOB列表,利用这些信息来构建其自身的DXE环境,例如初始化DXE内存服务、创建DXE协议数据库、加载DXE驱动等。没有完整的HOB列表,DXE Core将无法正确地理解系统状态并进行初始化。

11. PEIM 的结构与生命周期

PEIM(PEI Module)是PEI阶段执行的基本功能单元。它们是独立的、可执行的模块,通常存储在固件卷中。

  • 结构:一个PEIM通常是一个遵循UEFI PEI规范的PE32+镜像,包含一个入口点(Entry Point)函数。
  • 入口点:每个PEIM都有一个由EFI_PEIM_ENTRYPOINT宏定义的入口点函数(例如PeimEntry),这是PEI Core调度器执行该PEIM时调用的函数。该入口点函数通常接收EFI_PEI_FILE_HANDLEEFI_PEI_SERVICES作为参数,允许PEIM访问其自身的文件内容和PEI Core提供的服务。
  • 生命周期
  1. 发现:PEI调度器在固件卷中发现PEIM。
  2. DEPEX评估:调度器评估PEIM的依赖关系,以确定是否可以执行。
  3. 加载与执行:如果依赖满足,PEIM被加载到内存中(临时RAM或永久内存),并调用其入口点函数。
  4. 功能实现:PEIM在其入口点函数中执行其特定的初始化任务,例如初始化硬件、安装PPI、创建HOB等。
  5. 完成:PEIM完成其任务后,返回控制权给PEI调度器。一个PEIM可以多次被调用(例如,在非Shadowed和Shadowed模式下)。

12. 关键 PPI 详解

在PEI阶段,除了前面提到的通用PPI管理服务外,还有一些具有特定重要作用的PPI,它们在固件启动流程中扮演着关键角色:

12.1 EFI_PEI_TEMPORARY_RAM_SUPPORT_PPI (GUID: C474D92F-34C1-417A-B962-79383626D8E4)

  • 目的:这个PPI由SEC阶段(或非常早期的PEIM)安装,提供在临时RAM永久内存之间进行数据迁移的服务。它是实现PEI Core两阶段执行模式和堆栈切换的关键。
  • 作用:在永久内存被发现并初始化后,PEI Core会调用此PPI的服务,将自身和已加载的PEIM数据从临时RAM复制到永久内存中,以提高执行效率。此PPI可能提供如TemporaryRamMigration这样的服务接口。

12.2 EFI_PEI_RESET_SYSTEM_PPI (GUID: 62ADF58C-CC8B-4241-94E0-F04CCF11D369)

  • 目的:提供系统复位(reboot)功能。
  • 作用:任何PEIM在发现不可恢复的错误或需要重新启动系统以应用某些配置时,都可以通过定位此PPI并调用其服务来执行系统复位操作。这是早期启动阶段进行错误恢复或状态重置的唯一可靠机制。

12.3 EFI_PEI_STALL_PPI (GUID: E2C0637B-7CC8-479E-BD8F-44F4191F4B98)

  • 目的:提供一个简单的延迟(stall)服务,允许PEIM在非常早期且缺乏计时器服务时暂停执行一段时间。
  • 作用:在还没有可用的精确计时器(如HPET)时,此PPI可以提供微秒或毫秒级的忙等待(busy-wait)延迟,用于等待某些硬件状态变化或完成初始化。

12.4 EFI_PEI_MEMORY_DISCOVERED_PPI (GUID: F894643C-6597-400A-AE00-18D3540C6885)

  • 目的标记永久内存已被发现并初始化
  • 作用:当PEI Core成功配置并安装了主系统内存后,它会安装此PPI。这是一个关键的事件信号。其他PEIMs可以注册为此PPI的通知,一旦此PPI被安装,这些PEIM就可以安全地分配和使用永久内存,并执行那些必须在内存可用后才能进行的初始化任务。此PPI的安装也通常是PEI阶段从"Pre-Memory"过渡到"Post-Memory"的关键标志。

12.5 EFI_PEI_REPORT_STATUS_CODE_PPI (GUID: D2F75078-D11A-4836-8E67-08CEE232ACB0)

  • 目的:提供一种机制来报告系统状态、进度和错误信息。
  • 作用:PEIMs可以使用此PPI来输出启动过程中的调试信息、错误代码、警告或重要事件。这些状态码可以被固件内的日志模块捕获并存储,或通过调试端口输出,对于固件开发和故障排查至关重要。

12.6 EFI_PEI_DXE_IPL_PPI (GUID: C057F715-140F-455F-912A-B9EE6A8C2807)

  • 目的:作为从PEI阶段过渡到DXE阶段的接口。
  • 作用:这是PEI阶段最后被定位和调用的PPI。它由负责加载DXE Core的PEIM安装。PEI Core通过调用此PPI提供的服务,将HOB列表等关键信息传递给DXE IPL,从而启动DXE阶段。

12.7 gEfiEndOfPeiSignalPpiGuid (GUID: 4B605638-3850-413A-9E0E-26920C061C83)

  • 目的:一个信号PPI,表示PEI阶段即将完成
  • 作用:这个PPI在PEI Core即将完成所有PEIM的调度,并准备将控制权移交给DXE IPL之前安装。它不是一个提供服务的PPI,而是一个纯粹的通知事件。任何PEIM如果需要在PEI阶段结束前执行一些清理、最终状态保存或特殊操作,可以注册为此PPI的通知。这确保了在进入DXE阶段之前,所有PEI阶段的必要工作都已完成。它为PEIMs提供了一个在PEI生命周期结束时执行“最后一次机会”的机制。

13. 结语

PEI阶段是UEFI固件启动过程中的基石,其复杂的调度机制、细致的内存管理和灵活的模块间通信系统,为后续的DXE阶段奠定了坚实的基础。通过精心设计的多层循环调度算法、严谨的依赖关系评估、创新的Shadow机制和灵活的延迟调度等特性,PEI阶段能够高效地管理早期系统初始化过程,确保系统的稳定性和性能

 
 

版权声明:
作者:bin
链接:https://ay123.net/mystudy/1887/
来源:爱影博客
文章版权归作者所有,未经允许请勿转载。

THE END
分享
二维码
海报
UEFI Training – UEFI PEI 阶段详细解析
本文将结合EDK2源代码,深入分析了UEFI固件启动过程中PEI(Pre-EFI Initialization)阶段的完整工作流程。PEI阶段是UEFI启动规范中的关键环节,负责在SEC阶段……
<<上一篇
下一篇>>
文章目录
关闭
目 录