高通Android UEFI XBL 代碼流程分析
背景
之前學習的lk階段點亮LCD的流程算是比較經典,但是高通已經推出了很多種基於UEFI方案的啟動架構。
所以需要對這塊比較新的技術進行學習。在學習之前,有必要了解一下高通UEFI啟動流程。
原文(有刪改):https://blog.csdn.net/Ciellee/article/details/113519478
參考文檔:80_P2484_117_B_UEFI_With_XBL_On_MSM8998_SDM660_SDM
總覽
先來看下SDM660芯片冷啟動的流程。可以看出,在設備上電后,先跑的是 APPS PBL,接着運行XBL SEC、XBL Loader,通過Loader引出XBL CORE APPSBL,最后進入HLOS。
我們來看下這幾個涉及的模塊大概功能:
1、Application primary boot loader (APPS PBL)
PBL 啟動時,CPU只開啟了第一個核心 CPU Core 0,運行固件在ROM中,這部分是高通寫死在芯片中的固件,外部開發人員是無法修改這部份的。
主要功能為:
(1)系統安全環境的初始化,以確保后續的XBL中的APPS 能夠正常運行。
(2)根據boot gpio的配置選擇從什么設備啟動操作系統(如 Nand,USB等)。
(3)通過檢測GPIO判斷是否進入Emergency Download mode,用戶可以通過FILE來下載完整的系統鏡像。
(4)通過L2 TCM來加載XBL1 ELF,OCIMEM 和 RPM CodeRAM 代碼。
2、Extensible boot loader (XBL)
從XBL開始,跑的就是我們編譯下載進eMMC/UFS的系統鏡像了,在XBL中主要是初始化相關的硬件環境,及代碼安全環境。
(1)初始化 Buses、DDR、Clocks、CDT,啟動QSEE,QHEE,RPM_FW, XBL core images。
(2)使能memory dump through USB and Sahara(系統死機時memory dump),看門狗,RAM dump to SD support等功能。
(3)初始化 USB驅動,USB充電功能,溫升檢測,PMIC驅動初始化,和 DDR training模塊。
3、XBL core (UEFI or LK,ABL)
XBL core,就是之前的bootloader,主要功能就是初始化display驅動,提供fastboot功能,引導進入HLOS kernel操作系統。
注意,在ABL中,同樣也只有CPU Core0在工作,其他的CPU核以是在進入HLOS Kernel后才開始初始化啟用的。
本文中,我們重點關注的是Extensible boot loader (XBL),主要來學學UEFI XBL架構,及UEFI XBL代碼流程。
一、UEFI XBL
代碼目錄分析
UEFI XBL代碼路徑位於:BOOT.XF.1.4\boot_images\
# BOOT.XF.1.4\boot_images ArmPkg ----> ARM 架構相關的Protocols ArmPlatformPkg ----> ARM 開發板相關的UEFI代碼 BaseTools ----> 編譯EDK和EDK2相關的工具,如 EmbeddedPkg ----> FatPkg IntelFrameworkModulePkg IntelFrameworkPkg MdeModulePkg MdePkg QcomPkg ----> 高通定制的相關pkg,如display和usb充電都在里面 ShellPkg ----> UEFI shell 環境
UEFI代碼運行流程
從圖中可以看出,UEFI代碼運行流程為:
SEC(安全驗證)--->PEI(EFI前期初始化)--->DXE(驅動執行環境)--->BDS(啟動設備選擇)--->UEFI Loader(操作系統加載前期)--->RT(Run Time)
。
接下來,我們根據這個流程來分析下UEFI代碼。
SEC (安全驗證)
SEC的匯編代碼入口位於:
BOOT.XF.1.4\boot_images\QcomPkg\XBLCore\AARCH64\ModuleEntryPoint.masm
的 _ModuleEntryPoint
中
入口匯編代碼分析
分析看看ModuleEntryPoint.masm 這個文件
該匯編代碼中,主要工作為:
1、關閉所有中斷
2、關閉MMU和Caches
3、關閉TLB緩存表
4、獲得當前運行的安全環境:EL1、EL2、EL3
5、初始化ELX 安全環境
6、使能 Cache
7、初始化棧
8、調用 CEntryPoint,傳參 _StackBase
(0x80C00000)、_StackSize
(0x00040000)
#include <AsmMacroIoLibV8.h> #include <Base.h> #include <Library/PcdLib.h> #include <AutoGen.h> AREA |.text|,ALIGN=8,CODE,READONLY # BOOT.XF.1.4\boot_images\QcomPkg\XBLCore\AARCH64\ModuleEntryPoint.masm IMPORT CEntryPoint // 導入CEntryPoint()函數 EXPORT _ModuleEntryPoint // 輸出 _ModuleEntryPoint段 IMPORT InitStackCanary // 導入InitStackCanary()函數 初始化棧 IMPORT ArmDisableInterrupts // 導入ArmDisableInterrupts()函數 禁用arm 中斷 IMPORT ArmDisableCachesAndMmu // 導入ArmDisableCachesAndMmu()函數 禁用cache, mmu IMPORT ArmWriteCptr IMPORT ArmWriteHcr IMPORT ArmWriteVBar EXPORT _StackBase // 輸出棧起始地址,起始地址為:0x80C00000 EXPORT _StackSize // 輸出棧大小,棧大小為 0x00040000,256k EXPORT CNTFRQ // 輸出時鍾頻率,19200000 //定義於: BOOT.XF.1.4\boot_images\QcomPkg\Sdm660Pkg\Common\Sdm660Pkg_Loader.dsc _StackBase dcq FixedPcdGet64(PcdPrePiStackBase) _StackSize dcq FixedPcdGet64(PcdPrePiStackSize) CNTFRQ dcq FixedPcdGet32(PcdArmArchTimerFreqInHz) _ModuleEntryPoint mov x0, #0 // 1、關閉所有中斷 /* First ensure all interrupts are disabled */ bl ArmDisableInterrupts // 2、關閉MMU和Caches /* Ensure that the MMU and caches are off */ bl ArmDisableCachesAndMmu // 3、關閉TLB緩存表 /* Invalidate Instruction Cache and TLB */ bl ArmInvalidateInstructionCache bl ArmInvalidateTlb // 4、獲得當前運行的安全環境:EL1、EL2、EL3 /* Get current EL in x0 */ EL1_OR_EL2_OR_EL3(x0) // CurrentEL : 0xC = EL3; 8 = EL2; 4 = EL1 // This only selects between EL1 and EL2 and EL3, else we die. // Provide the Macro with a safe temp xreg to use. //mrs x0, CurrentEL cmp x0, #0xC // 比較 x0寄存器是否為 0xc,如果是跳轉到 標簽3 beq %F3 cmp x0, #0x8 // 比較 x0寄存器是否為 0x8,如果是跳轉到 標簽2 beq %F2 cmp x0, #0x4 // 比較 x0寄存器是否為 0x4 bne 、 // We should never get here // EL1 code starts here 1 beq _Start 2 beq _Start // 如果當前是 EL2,直接跳轉到_Start /* Do not trap any access to Floating Point and Advanced SIMD in EL3、*/ /* Note this works only in EL3, x0 has current EL mode */ 3 mov x0, #0 bl ArmWriteCptr // 如果當前是 EL3,直接跳轉到ArmWriteCptr // msr cptr_el3, x0 // EL3 Coprocessor Trap Reg (CPTR) // 5、初始化ELX 安全環境 _SetupELx mov x0, #0x30 /* RES1 */ // x0 = 0x30 orr x0, x0, #(1 << 0) /* Non-secure bit */ // 使能第0位為1 orr x0, x0, #(1 << 8) /* HVC enable */ // 使能第8位為1 orr x0, x0, #(1 << 10) /* 64-bit EL2 */ // 使能第10位為1 msr scr_el3, x0 // 配置通用寄存器 scr_el3 為- msr cptr_el3, xzr /* Disable copro、traps to EL3 */ ldr x0, CNTFRQ //msr cntfrq_el0, x0 msr sctlr_el2, xzr .......省略一部分代碼....... // 6、使能 Cache _EnableCache #ifdef PRE_SIL LoadConstantToReg (FixedPcdGet32(PcdSkipEarlyCacheMaint), x0) cmn x0, #0 b.ne _PrepareArguments #endif bl ArmInvalidateDataCache bl ArmEnableInstructionCache bl ArmEnableDataCache // 7、初始化棧 _PrepareArguments /* Initialize Stack Canary */ bl InitStackCanary // 8、調用 CEntryPoint,傳參 _StackBase(0x80C00000)、_StackSize(0x00040000) /* x0 = _StackBase and x1 = _StackSize */ ldr x0, _StackBase /* Stack base arg0 */ ldr x1, _StackSize /* Stack size arg1 */ bl CEntryPoint
初始化C運行環境
前面匯編代碼中主要目的是初始化C運行環境,初始化棧,以便可以調C代碼運行。
SEC的C代碼入口位於: BOOT.XF.1.4\boot_images\QcomPkg\XBLCore\Sec.c
的 CEntryPoint
中
/** Entry point @param StackBase pointer to the stack base @param StackSize stack size **/ VOID CEntryPoint (IN VOID *StackBase,IN UINTN StackSize){ UefiDebugModeEntry(); // 如果支待jtag調試的話,此處會循環等待,直到debug指向的地址匹配 TargetEarlyInit() Main (StackBase, StackSize); //進入main函數,傳參 _StackBase(0x80C00000)、_StackSize(0x00040000) }
接下來,我們進入 main函數分析下:
VOID Main (IN VOID *StackBase, IN UINTN StackSize){ // 1、獲得fdf文件所在的地址,fdf可以說是UEFI的配置文件, // 在fdf文件中包含所有的inf文件所在路徑,及相關的bmp圖片資源路徑,以及相關的cfg配置文件路徑。 // ## FD Base offset (refer to .fdf for FD size) UefiFdBase = FixedPcdGet64(PcdEmbeddedFdBaseAddress); // 0x80200000 SecHeapMemBase = UefiFdBase + SEC_HEAP_MEM_OFFSET; // 0x300000 HobStackSize = StackSize; // 2、初始化棧 InitStackCanary(); // 3、啟動定時器周期計數 StartCyclCounter (); // 4、初始化UART,主要是serial port端口初始化,及 serial buffer初始化 /* Start UART debug output */ UartInit(); // 5、打印"UEFI Start" 串口信息 PrintUefiStartInfo(); // 6、初始化CPU異常處理入口 InitializeCpuExceptionHandlers (NULL); // 7、打印從開機到現在的時間差 PrintTimerDelta(); // 8、如果支持的話,啟動程序流預測 /* Enable program flow prediction, if supported */ ArmEnableBranchPrediction (); // 9、/* Initialize Info Block */ UefiInfoBlkPtr = InitInfoBlock (UefiFdBase + UEFI_INFO_BLK_OFFSET); UefiInfoBlkPtr->StackBase = StackBase; UefiInfoBlkPtr->StackSize = StackSize; // 10、初始化 RAM 分區表,起始地址0x80000000,內存大小512M,檢查地址是否非法,是否可正常訪問 InitRamPartitionTableLib (); ValidateFdRegion(UefiFdBase); //TODO: Move this to ACPI-specific location InitializeFBPT(); /* Get nibble from random value to adjust SEC heap */ SecHeapAslrVal = AslrAdjustRNGVal(ASLR_HEAP_RNG_BITS); // 11、初始化hoblist,有關hob可參考:https://blog.csdn.net/z190814412/article/details/85330324 InitHobList(SecHeapMemBase,SEC_HEAP_MEM_SIZE - (SecHeapAslrVal*ASLR_HEAP_ALIGN), UefiInfoBlkPtr); /* Add the FVs to the hob list */ BuildFvHob (PcdGet64(PcdFlashFvMainBase), PcdGet64(PcdFlashFvMainSize)); // 12、打印RAM 分區信息 /* Should be done after we have setup HOB for memory allocation */ PrintRamPartitions (); // 13、初始化cache Status = EarlyCacheInit (UefiFdBase, UEFI_FD_SIZE); // 14、加載並解析 uefiplat.cfg平台配置文件,/* Load and Parse platform cfg file, cache re-initialized per cfg file */ Status = LoadAndParsePlatformCfg(); // 15、更新系統內存區相關信息 /* Add information from all other memory banks */ Status = UpdateSystemMemoryRegions(); Status = InitCacheWithMemoryRegions(); // 16、初始化所有的共享庫 /* All shared lib related initialization */ // 初始化的lib源碼位於 BOOT.XF.1.4\boot_images\QcomPkg\Sdm660Pkg\Library\ // 配置文件位於 BOOT.XF.1.4\boot_images\QcomPkg\Sdm660Pkg\LA\Sdm660Pkg_Core.dsc Status = InitSharedLibs(); InitDbiDump(); // 17、獲得DXE Heap堆內存信息,/* Look for "DXE Heap" memory region in config file */ Status = GetMemRegionInfoByName("DXE Heap", &DxeHeapMemInfo); /* Get nibble from random value to adjust DXE heap */ DxeHeapAslrVal = AslrAdjustRNGVal(ASLR_HEAP_RNG_BITS); /* Re-initialize HOB to point to the DXE Heap in CFG */ ReInitHobList(DxeHeapMemInfo.MemBase, DxeHeapMemInfo.MemSize - (DxeHeapAslrVal*ASLR_HEAP_ALIGN), UefiInfoBlkPtr); // 18、初始化分頁池緩存區 /* Now we have access to bigger pool, move pre-pi memory allocation pool to it */ ReInitPagePoolBuffer (); // 19、創建Stack、CPU Hob信息 BuildStackHob ((EFI_PHYSICAL_ADDRESS)StackBase, HobStackSize); BuildCpuHob (PcdGet8 (PcdPrePiCpuMemorySize), PcdGet8 (PcdPrePiCpuIoSize)); // 20、打印早期信息 DisplayEarlyInfo(); AddMemRegionHobs (); /* Start perf here, after timer init, start at current tick value */ InitPerf(); /* SEC phase needs to run library constructors by hand */ ExtractGuidedSectionLibConstructor (); LzmaDecompressLibConstructor (); ZlibDecompressLibConstructor (); /* Build HOBs to pass up our Version of stuff the DXE Core needs to save space */ BuildPeCoffLoaderHob (); BuildExtractSectionHob ( &gLzmaCustomDecompressGuid, LzmaGuidedSectionGetInfo, LzmaGuidedSectionExtraction ); BuildExtractSectionHob ( &gZlibDecompressGuid, ZlibGuidedSectionGetInfo, ZlibGuidedSectionDecompress ); /* Check PRODMODE flag */ ProdmodeInfo = PRODMODE_ENABLED; /* Build HOB to pass up prodmode info for security applications */ BuildGuidDataHob (&gQcomProdmodeInfoGuid, &ProdmodeInfo, sizeof(BOOLEAN)); UefiStartTime = ConvertTimerCountms(gUEFIStartCounter); BuildGuidDataHob (&gEfiStartTimeHobGuid, &UefiStartTime, sizeof(UINT32)); /* Assume the FV that contains the SEC (our code) also contains a compressed FV */ DecompressFirstFv (); /* Any non-critical initialization */ TargetLateInit(); /* Build memory allocation HOB for FV2 type Need to remove for decompressed image */ BuildMemHobForFv(EFI_HOB_TYPE_FV2); /* Load the DXE Core and transfer control to it */ LoadDxeCoreFromFv (NULL, 0); /* DXE Core should always load and never return */ ASSERT (FALSE); CpuDeadLoop(); }
1、獲得fdf文件所在的地址,
在fdf文件中包含所有的inf文件所在路徑,及相關的bmp圖片資源路徑,以及相關的cfg配置文件路徑。
fdf可以說是UEFI的配置文件。
2、初始化棧
3、啟動定時器周期計數
4、初始化UART,主要是serial port端口初始化,及 serial buffer初始化
5、打印"UEFI Start" 串口信息
6、初始化CPU異常處理入口
7、打印從開機到現在的時間差
8、如果支持的話,啟動程序流預測 /* Enable program flow prediction, if supported */
9、Initialize Info Block
10、初始化 RAM 分區表,起始地址0x80000000,內存大小512M,檢查地址是否非法,是否可正常訪問
11、初始化hoblist,有關hob可參考:https://blog.csdn.net/z190814412/article/details/85330324
12、打印RAM 分區信息
13、初始化cache
14、加載並解析 uefiplat.cfg平台配置文件
15、更新系統內存區相關信息 /* Add information from all other memory banks */
16、初始化所有的共享庫 /* All shared lib related initialization */
初始化的lib源碼位於 BOOT.XF.1.4\boot_images\QcomPkg\Sdm660Pkg\Library
配置文件位於 BOOT.XF.1.4\boot_images\QcomPkg\Sdm660Pkg\LA\Sdm660Pkg_Core.dsc
17、獲得DXE Heap堆內存信息,/* Look for “DXE Heap” memory region in config file */
18、初始化分頁池緩存區
19、創建Stack、CPU Hob信息
20、打印早期信息
PEI (EFI前期初始化)
BOOT.XF.1.4\boot_images\MdeModulePkg\Core\Pei\PeiMain\PeiMain.c
的 PeiCore
函數中
DXE (驅動執行環境)
DXE的加載位置在:
BOOT.XF.1.4\boot_images\EmbeddedPkg\Library\PrePiLib\PrePiLib.c
的 LoadDxeCoreFromFv
中
DXE的入口代碼位於:
BOOT.XF.1.4\boot_images\MdeModulePkg\Core\Dxe\DxeMain\DxeMain.c
的 DxeMain
中
BDS (啟動設備選擇)
代碼位於:
BOOT.XF.1.4\boot_images\QcomPkg\Drivers\BdsDxe\BdsEntry.c
的 BdsEntry
中
代碼位於:
BOOT.XF.1.4\boot_images\QcomPkg\Drivers\QcomBds\QcomBds.c
的 BdsEntry
中
XBL Loader
代碼位於:
BOOT.XF.1.4\boot_images\QcomPkg\XBLLoader\boot_loader.c
RT(Run Time)
代碼位於:BOOT.XF.1.4\boot_images\MdeModulePkg\Core\RuntimeDxe\Runtime.c
的 RuntimeDriverInitialize
中
若在頁首無特別聲明,本篇文章由 Schips 經過整理后發布。
博客地址:https://www.cnblogs.com/schips/