博客 / 詳情

返回

UEFI-PEI 階段的深層介紹

一、PEI Core 與 PEIMs

PEI Core:是PEI階段的核心引擎,負責尋找,加載和執行各種PEIM。PEI Core首先首先運行一個小的固定的SEC階段的代碼,這段代碼通常使用CPU緩存作為臨時內存來運行。一旦找到並初始化了真正的系統內存,PEI核心就會把執行權交給永久內存管理器,從而結束使用CAR。

PEIMs:這些是實際執行硬件初始化任務的功能模塊。執行芯片組、內存控制器、橋接器和核心主板組件的最低限度初始化。關鍵任務是初始化 DRAM,使其可用於後續的啓動階段。創建 Hand-Off Blocks (HOB) 列表,這是一個數據結構,用於保存所有已發現的硬件狀態、配置信息和資源映射,並將這些信息傳遞給下一個啓動階段——DXE 階段。

下面對這兩部分的流程進行更具體的介紹:

PEI Core 主要是提供一個基本的執行環境然後找到並執行 PEIMs,它才是真正幹活的。

關鍵職責 具體任務 對應的概念/API
A. 環境建立 在 SEC 階段交接後,立即建立 PEI Core 自身的堆棧和數據結構(最初在 CAR 中)。 CAR (Cache As RAM)
B. 服務提供 提供 PEI 階段的基本服務(如內存分配、HOB 創建)。 PEI Services Table
C. PEIM 調度 搜索固件存儲空間 (FV) 以發現待執行的 PEIM 映像,並按順序執行它們。 FV (Firmware Volume)
D. 內存轉換 一旦 DRAM 被初始化,它將所有 PEI 結構從 CAR 遷移到永久內存中。 PeiServicesInstallPeiMemory()
E. 階段交接 所有 PEIMs 執行完畢後,構建最終的 HOB 列表,並跳轉到 DXE Core。 DXE IPL (Initial Program Load)

PEIMs 是實際的工作執行者,它們通過“提供 PPI”(PEIM-to-PEIM Interface)和“註冊 Notify”機制來完成工作和進行協作。

關鍵職責 具體任務 對應的機制
A. 硬件初始化 初始化芯片組、時鐘、電源管理等關鍵硬件。 PCHInitPeim, MemoryInitPeim
B. 資源發現 找到並配置系統內存 (DRAM),並報告給 PEI Core。 Memory Init PEIM
C. 數據發佈 創建並安裝 PPI (PEI Protocol Interface),讓其他 PEIM 知道某個服務或數據已可用。 PeiServicesInstallPpi()
D. 階段準備 收集硬件數據,創建 HOB,將配置信息傳遞給 DXE。 PeiServicesCreateHob()

偽代碼:

// -------------------------------------------------------------------------
// 階段 0: 接管和臨時環境建立 (運行在 CAR/Cache As RAM)
// -------------------------------------------------------------------------
Function PeiCoreMain(SecCoreData, HandoffData)
    // 1. 設置 PEI Services Table
    PeiServicesTable = InitializePeiServicesTable()
    
    // 2. 初始化臨時堆棧和臨時堆 (基於CAR)
    InitializePeiCoreStack(SecCoreData.StackBase)
    InitializePeiTemporaryMemoryHeap() 
    
    // 3. 創建第一個 HOB: Handoff Information HOB
    CreateHob(HOB_TYPE_HANDOFF, HandoffData)
    
    // 4. 初始化 PEIM 調度器
    PeimDispatcher = InitializePeimDispatcher()
    
    // 5. 開始 PEIM 主循環
    Call PeimExecutionLoop(PeimDispatcher, PeiServicesTable)
    
    // 6. 退出 PEI 階段
    Call PeiCoreExit() 

End Function


// -------------------------------------------------------------------------
// 階段 1: PEIM 調度與執行主循環
// -------------------------------------------------------------------------
Function PeimExecutionLoop(Dispatcher, Services)
    
    // 循環條件: 只要在 Firmware Volume (FV) 中還能找到未執行的 PEIM
    While (Dispatcher.FindNextPeimImage() != NULL)
        CurrentPeim = Dispatcher.FindNextPeimImage()

        // 1. 加載 PEIM 代碼
        CurrentPeim.Image = Services.FfsFindPeim(CurrentPeim.FilePath)
        
        // 2. 執行 PEIM 的入口點 (EntryPoint)
        // 實際的硬件初始化和 PPI 安裝都在這裏發生
        Status = CurrentPeim.EntryPoint(CurrentPeim.Image, Services)
        
        If (Status == EFI_SUCCESS)
            MarkPeimAsExecuted(CurrentPeim)
            
            // 3. 檢查是否有內存初始化發生
            If (Services.IsMemoryInstalled() == TRUE)
                // 內存初始化 PEIM 已經運行,進入關鍵的轉換流程
                Call PeimMemoryTransition() 
                // 由於內存轉換,代碼和數據可能已被遷移,需要重新啓動循環
                // 通常會重新設置 PEI Core 的環境指針
                Return // 退出當前循環,進入下一個 PEI 階段
            End If
        Else
            Log(ERROR, "PEIM failed to execute: " + CurrentPeim.Name)
        End If
    End While
    
End Function


// -------------------------------------------------------------------------
// 階段 2: 內存轉換 (最關鍵的一步)
// -------------------------------------------------------------------------
Function PeimMemoryTransition()
    
    // 1. 發現新安裝的永久內存信息
    ResourceHob = Services.GetHobByType(HOB_TYPE_RESOURCE_DESCRIPTOR)
    
    // 2. 告訴 PEI Services Table 內存已安裝
    Services.PeiServicesInstallPeiMemory() 
    
    // 3. 將 PEI Core 自身的數據、PEIMs 和 HOB 列表
    //    從臨時的 CAR 空間遷移到新的永久內存中
    MigratePeiCoreDataToDRAM()
    
    // 4. 更新 Services Table 和所有 PEIMs 的指針
    UpdatePointersToNewDRAMLocations()
    
    // 5. 重新進入 PeimExecutionLoop,尋找需要二次執行的 PEIMs (Post-Memory PEIMs)
    Call PeimExecutionLoop(...)
    
End Function


// -------------------------------------------------------------------------
// 階段 3: 退出 PEI 階段
// -------------------------------------------------------------------------
Function PeiCoreExit()
    
    // 1. 確保所有 HOB 都已創建,生成最終的 HOB 列表
    FinalizeHobList()
    
    // 2. 查找並加載 DXE IPL PEIM (Initial Program Load)
    DxeIplPeim = FindPeim("DxeIpl.inf")
    
    // 3. 執行 DXE IPL PEIM,其工作是加載 DXE Core 映像
    //    並最終將控制權交給 DXE Core
    DxeIplPeim.EntryPoint(HobListAddress) 
    
    // 永遠不會返回 (Never Returns), 因為控制權已轉交給 DXE Core
    
End Function
  • PEI Core 負責整個流程的框架和調度(PeiCoreMain)。

  • PEIM 在 PeimExecutionLoop 中被加載和執行,完成實際的硬件配置,是真正幹活的。

  • 內存轉換是分水嶺。 PeimMemoryTransition 標誌着系統從使用 CPU 緩存運行(CAR)過渡到使用真正的 DRAM 內存。這是整個 PEI 階段最關鍵的一步。

  • HOB 是最終階段結果和目的。最終的 HOB 列表是 PEI 階段工作的產物,作為參數傳遞給 DXE 階段。

二、PEIMs 在 EDKII 中的體現

第一節中我們提到,PEI 會搜索固件卷(FV)中待執行的的 PEIM 鏡像(Imaga),並按照順序執行。這裏可以先簡單把 FV 理解為文件夾,文件夾中存放着特定的功能性程序,也就是 PEIM 鏡像。下面舉個例子介紹 PEIMs 在EDKII 工程中的形式。

在 EDKII 中,PEIM 本質上就是一個模塊(Module),其通常被編譯為.efi適用於 PEI 的 PE/COFF 可執行文件,且最終被放入 FV 中,一般是 FV/PEIFV 或 FV/MAINFV

有關 EDKII 工程結構的介紹異步此博客。一般情況下,PEIM 的源代碼位於某個 Package 的目錄中,典型路徑(以 MdeModulePkg 中的示例為例):

edk2/
 └── MdeModulePkg/
      └── Universal/
          └── PCD/
              └── Pei/
                  ├── Pcd.inf     ← PEIM 的 INF 文件(最重要)
                  ├── Pcd.c       ← 源碼
                  └── Pcd.h

➡️ 所有 PEIM 模塊的特徵是:INF 文件中有:

[Defines]
  MODULE_TYPE = PEIM

有關模塊的概念後續會補充,前面章節中有關於MdeModulePkg的介紹以及單獨編譯某模塊的方法,還有模塊的配置描述文件inf文件的簡單介紹,感興趣可以移步!

PEIM 是如何被編譯的呢?首先在 EDKII 工程中,我們最終的目的是編譯某個平台的固件文件,比如虛擬機使用的 ovmf.fd,其平台描述文件位於OvmfPkg/OvmfPkgX64.dsc。具體內容點擊此處在附錄中有介紹。這個平台描述文件會定義哪些 PEIM 模塊會被編譯。fdf文件OvmfPkg/OvmfPkgX64.fdf描述了最終的固件鏡像是如何佈局的,因此PEIM 是否進入固件由平台 FDF 文件決定。

fdf文件的介紹後續會另外補充!

例如 OvmfPkgX64.dsc 內會有:

[MdeModulePkg/Universal/PCD/Pei/Pcd.inf]

只要在 .dsc 裏被包含,構建系統就會編譯它。

編譯 ovmf 固件鏡像時

build -a X64 -t GCC5 -p OvmfPkg/OvmfPkgX64.dsc

編譯操作自動會:找到所有 INF(包括 PEIM),生成 .obj,鏈接出 PEIM 的 .efi(PEI 可執行)。

EDK II 的編譯輸出統一放在 Build/ 目錄中。比如

Build/OvmfX64/DEBUG_GCC5/X64/MdeModulePkg/Universal/PCD/Pei/Pcd/OUTPUT/Pcd.efi

PEIM 在最終固件鏡像(FV)裏:

Build/OvmfX64/DEBUG_GCC5/FV/
 ├── OVMF.fd
 ├── OVMF_CODE.fd
 ├── OVMF_VARS.fd
 └── PEIFV.Fv 			# efi 文件會被工具打包進 .FV 文件。

可以查看 FDF 文件,例如 OvmfPkgX64.fdf:

FV = PEIFV {
    INF MdeModulePkg/Universal/PCD/Pei/Pcd.inf
}

這表示 Pcd.inf (一個 PEIM)會放入該 Firmware Volume。

三、PEI Services Table 介紹

在 PEI Core 以及偽代碼中涉及到 PEI Services Table 這裏簡單介紹一下。

PEI Services Table 本質上是一個指向一系列函數指針的結構體。它允許 PEIMs 訪問底層 PEI Core 實現的服務,而無需知道這些服務的具體位置和內部細節。通俗來説就相當於執行具體任務的 PEIM 能夠通過 PEI Services Table 調用 PEI Core 中的函數。

PEI Services Table 結構通常包含多個部分,其中最重要的就是核心服務輔助服務的指針:

  • 核心服務 (Core Services): 最基礎的服務,在整個 PEI 階段都可用。
    • AllocatePages / AllocatePool: 內存分配。
    • CreateHob: 創建 Hand-Off Block,將數據傳遞給 DXE。
    • InstallPpi: 安裝 PPI (PEIM-to-PEIM Interface),用於 PEIM 間通信。
    • LocatePpi: 查找其他 PEIM 已經安裝的 PPI。
  • 啓動服務 (Boot Services): 與啓動流程管理相關的服務。
    • FfsFindNextPeim: 用於在 Firmware Volume (FV) 中查找下一個要執行的 PEIM。
    • NotifyPpi: 註冊一個通知函數,當某個 PPI 被安裝時觸發。

如上文中的偽代碼所示,這個服務表在 PEI Core 啓動時(在 SEC 階段交接之後)被初始化,最初的實現可能非常精簡,使用的都是臨時內存(CAR/Cache As RAM)。當 PEI Core 加載並執行一個 PEIM 時,它會將 PEI Services Table 的地址作為參數傳遞給該 PEIM 的 EntryPoint 函數。偽代碼如下,對應上文偽代碼中的 41 行。

// PEIM Entry Point 函數的簽名
Function PeimEntryPoint(
    IN PeiFileHandle,
    IN **PeiServicesTablePointer  // 這裏的雙指針指向服務表
)
    // PEIM 現在可以使用這個表來調用服務
    Status = PeiServicesTablePointer->CreateHob(...)
    Status = PeiServicesTablePointer->InstallPpi(...)
End Function

四、HOB的詳細介紹,PEI 到 DXE階段

HOB 的主要任務是將 PEI 階段發現、初始化和配置的所有系統信息,以一種標準化的格式,安全可靠地傳遞給接下來運行的 DXE 階段。PEI 階段創建的 HOB 並不是一個單一的數據塊,而是一個由多個獨立 HOB 描述符串聯而成的鏈表,稱為 HOB 列表 (HOB List)

核心作用

  • 狀態傳遞: 記錄內存、CPU、芯片組等硬件的狀態和配置。
  • 資源映射: 提供系統內存和 I/O 資源的詳細布局,供 DXE 階段的內存管理器和驅動程序使用。
  • 服務註冊: 記錄 PEI 階段可能提供的某些特殊服務或數據。

每個 HOB 都是由一個標準化的 HOB 描述符頭部 (EFI_HOB_GENERIC_HEADER) 開始,緊接着是該 HOB 特定類型的數據。

字段 描述
Type HOB 的類型(見下文)。
Length 整個 HOB 結構的長度(包括頭部和數據)。
Reserved 保留字段。

HOB 列表中的每一個塊都屬於一個特定的類型,用於傳遞特定的信息。以下是幾種最關鍵的 HOB 類型:

HOB 類型 (Type) 結構名稱 (例如) 傳遞的信息
EFI_HOB_TYPE_RESOURCE_DESCRIPTOR EFI_HOB_RESOURCE_DESCRIPTOR 內存和 I/O 資源的詳細列表。 這是 DXE 內存管理器建立內存映射的基礎。
EFI_HOB_TYPE_MEMORY_ALLOCATION EFI_HOB_MEMORY_ALLOCATION 記錄 PEI 階段已分配的內存塊(例如,用於存儲 PEI 堆棧、PEI 核心、或特定的數據結構)。
EFI_HOB_TYPE_HANDOFF EFI_HOB_HANDOFF_INFO_TABLE 列表的第一個 HOB。 包含 PEI 和 DXE 之間的手遞手信息,例如:啓動模式、SEC 階段的堆棧基址等。
EFI_HOB_TYPE_CPU EFI_HOB_CPU CPU 特定的信息,例如:CPU 的最大地址空間、處理器數量等。
EFI_HOB_TYPE_GUID_EXTENSION EFI_HOB_GUID_TYPE 允許平台或 OEM 定義自定義數據,通過 GUID 識別,用於傳遞非標準化的信息。

在 PEI 階段,當一個 PEIM (PEI Module) 完成初始化或發現某個資源時,它會調用 PEI 服務表中的 API 來創建 HOB,將其添加到 HOB 列表的末尾。

主要的 HOB 創建服務是:

  • PeiServicesCreateHob(): 用於創建任何類型的 HOB。

  • PeiServicesInstallPeiMemory(): 專用於創建第一個內存資源 HOB,標誌着永久內存初始化完成。

HOB 如何從 PEI 傳遞到 DXE,這是 HOB 機制的核心價值:

  1. PEI 結束: PEI 核心在完成所有 PEIM 的執行後,會調用 PeiCoreExitTransition() 函數。
  2. Handoff Block 準備: 在退出前,PEI 核心確保 HOB 列表已經最終化。
  3. 跳轉到 DXE: PEI 核心最後會執行一個特殊的 PEIM,該 PEIM會加載 DXE 核心的映像,並使用特殊的跳轉指令(通常是 JMPCALL)進入 DXE 階段的入口點 (DxeCoreEntry)。
  4. 傳遞參數: 在跳轉時,HOB 列表的起始地址會被作為參數(通常通過 CPU 寄存器)傳遞給 DXE 核心。

DXE 階段如何使用 HOB。DXE 核心是 HOB 列表的主要消費者

  1. 初始化 DXE 核心: DXE 核心的入口點接收到 HOB 列表的地址。它首先解析這個列表,建立其內存管理器
  2. 建立內存映射: 它利用 EFI_HOB_TYPE_RESOURCE_DESCRIPTOR HOB 中包含的內存信息,來建立自己的內存映射,區分可用內存和已佔用內存。
  3. 提取配置信息: DXE 驅動和 DXE 服務可以使用 EfiGetSystemConfigurationTable() 或直接遍歷 HOB 列表來查找特定的配置信息或自定義數據。
  4. 銷燬(邏輯上): 一旦 DXE 核心完成了初始化並建立了自己的資源管理機制,HOB 列表的歷史使命就完成了。它所佔據的內存通常會被重新聲明為 DXE 階段可用的系統內存。

Steady Progress

user avatar
0 位用戶收藏了這個故事!

發佈 評論

Some HTML is okay.