系列文章:
- 《【Android SDM660开机流程】- UEFI XBL 代码流程分析》
- 《【Android SDM660源码分析】- 01 – 如何创建 UEFI XBL Protocol DXE_DRIVER 驱动及UEFI_APPLICATION 应用程序》
- 《【Android SDM660源码分析】- 02 – UEFI XBL QcomChargerApp充电流程代码分析》
- 《【Android SDM660源码分析】- 03 – UEFI XBL GraphicsOutput BMP图片显示流程》
- 《【Android SDM660源码分析】- 04 – UEFI ABL LinuxLoader 代码分析》
先来看下SDM660芯片冷启动的流程,可以看出,在设备上电后,先跑的是 APPS PBL,接着运行XBL SEC、XBL Loader,通过Loader引出XBL CORE APPSBL,最后进入HLOS。

我们来看下这几个涉及的模块大概功能:
-
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 代码。 -
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模块。 -
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代码流程。
1.1 boot_images代码目录
UEFI XBL代码路径位于:SDM660_Androidsnapdragon-amssBOOT.XF.1.4boot_images
# SDM660_Androidsnapdragon-amssBOOT.XF.1.4boot_images
ArmPkg ----> ARM 架构相关的Protocols
ArmPlatformPkg ----> ARM 开发板相关的UEFI代码
BaseTools ----> 编译EDK和EDK2相关的工具,如
EmbeddedPkg ---->
FatPkg
IntelFrameworkModulePkg
IntelFrameworkPkg
MdeModulePkg
MdePkg
QcomPkg ----> 高通定制的相关pkg,如display和usb充电都在里面
ShellPkg ----> UEFI shell 环境
1.2 UEFI代码运行流程
从图中可以看出,UEFI代码运行流程为:
SEC(安全验证)--->PEI(EFI前期初始化)--->DXE(驱动执行环境)--->BDS(启动设备选择)--->UEFI Loader(操作系统加载前期)--->RT(Run Time)。
接下来,我们根据这个流程来分析下UEFI代码。

1.3 SEC (安全验证)
SEC的汇编代码入口位于:
amssBOOT.XF.1.4boot_imagesQcomPkgXBLCoreAARCH64ModuleEntryPoint.masm的 _ModuleEntryPoint中
1.3.1 ModuleEntryPoint.masm 代码分析
该汇编代码中,主要工作为:
- 关闭所有中断
- 关闭MMU和Caches
- 关闭TLB缓存表
- 获得当前运行的安全环境:EL1、EL2、EL3
- 初始化ELX 安全环境
- 使能 Cache
- 初始化栈
- 调用 CEntryPoint,传参 _StackBase(0x80C00000)、_StackSize(0x00040000)
# amssBOOT.XF.1.4boot_imagesQcomPkgXBLCoreAARCH64ModuleEntryPoint.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
//定义于: amssBOOT.XF.1.4boot_imagesQcomPkgSdm660PkgCommonSdm660Pkg_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
1.3.2 XBLCoreSec.c 代码分析
前面汇编代码中主要目的是初始化C运行环境,初始化栈,以便可以调C代码运行。
SEC的C代码入口位于:
amssBOOT.XF.1.4boot_imagesQcomPkgXBLCoreSec.c的 CEntryPoint 中
/** Entry point
@param StackBase pointer to the stack base
@param StackSize stack size
**/
VOID CEntryPoint (IN VOID *StackBase,IN UINTN StackSize)
接下来,我们进入 main函数分析下
- 获得fdf文件所在的地址,fdf可以说是UEFI的配置文件,
在fdf文件中包含所有的inf文件所在路径,及相关的bmp图片资源路径,以及相关的cfg配置文件路径。 - 初始化栈
- 启动定时器周期计数
- 初始化UART,主要是serial port端口初始化,及 serial buffer初始化
- 打印"UEFI Start" 串口信息
- 初始化CPU异常处理入口
- 打印从开机到现在的时间差
- 如果支持的话,启动程序流预测 /* Enable program flow prediction, if supported */
- Initialize Info Block
- 初始化 RAM 分区表,起始地址0x80000000,内存大小512M,检查地址是否非法,是否可正常访问
- 初始化hoblist,有关hob可参考:https://blog.csdn.net/z190814412/article/details/85330324
- 打印RAM 分区信息
- 初始化cache
- 加载并解析 uefiplat.cfg平台配置文件
- 更新系统内存区相关信息 /* Add information from all other memory banks */
- 初始化所有的共享库 /* All shared lib related initialization */
初始化的lib源码位于 amssBOOT.XF.1.4boot_imagesQcomPkgSdm660PkgLibrary
配置文件位于 amssBOOT.XF.1.4boot_imagesQcomPkgSdm660PkgLASdm660Pkg_Core.dsc - 获得DXE Heap堆内存信息,/* Look for “DXE Heap” memory region in config file */
- 初始化分页池缓存区
- 创建Stack、CPU Hob信息
- Display 早期初始化
- 开启耗费的时间统计,用于计算性能 /* Start perf here, after timer init, start at current tick value */
- 加载且将CPU交给 DXE Core。
VOID Main (IN VOID *StackBase, IN UINTN StackSize)
1.3.3 LoadAndParsePlatformCfg() 加载并解析 uefiplat.cfg平台配置文件
主要工作流程如下:
- 初始化相关全局变量
- 加载并解析
uefiplat.cfg配置文件#define UEFIPLATCFG_FILE "uefiplat.cfg"
文件内容保存在 CfgBuffer 中,解析器描述符保存在MemParserDesc 中
在cfg文件中包含了内存相关的信息及系统相关的配置,amssBOOT.XF.1.4boot_imagesQcomPkgSdm660PkgLAlat.cfg - 解析
uefiplat.cfg中的[Config]区域 - 解析
uefiplat.cfg中的[MemoryMap]区域 - 解析
uefiplat.cfg中的[RegisterMap]区域,内容保存在mMemRegions中 - 解析
uefiplat.cfg中的[ConfigParameters]区域
内容保存在ConfigTable中,ConfigTableEntryCount表示其内容的数量 - 解析
uefiplat.cfg中的[ChipIDConfig]区域
# amssBOOT.XF.1.4boot_imagesQcomPkgXBLCoreUefiPlatCfg.c
EFI_STATUS EFIAPI LoadAndParsePlatformCfg ( VOID )
## Default app to boot in platform BDS init
DefaultChargerApp = "QcomChargerApp"
DefaultBDSBootApp = "LinuxLoader"
1.3.4 DisplayEarlyInfo() 显示模块早期初始化
在 DisplayEarlyInfo 中主要工作就是解析 UEFI version 版本号,然后根据版本号加载对应的镜像,接着打印系统启动的路径。
主要流程为:
- 获取
UefiPlatCfg.c中的UEFI CORE字段信息,PlatConfigFileName="uefiplatLA.cfg" - 获取固件版本号,定义在
boot_imagesQcomPkgSdm660PkgLASdm660Pkg_Core.dsc中
gEfiMdeModulePkgTokenSpaceGuid.PcdFirmwareVersionString|L"4.2" - 根据固件版本号查找对应的image镜像
Append Image version string component - 打印UEFI固件版本号
- 选UEFI启动类型,判断顺序为
UFS > EMMC > SPI
# amssBOOT.XF.1.4boot_imagesQcomPkgXBLCoreFwVersion.c
VOID DisplayEarlyInfo(VOID)
1.3.5 LoadDxeCoreFromFv() 加载DXE Core
先查找DXE_CORE的文件地址,接着调用LoadDxeCoreFromFfsFile 加载 EntryPoint函数
# amssBOOT.XF.1.4boot_imagesEmbeddedPkgLibraryPrePiLibPrePiLib.c
EFI_STATUS EFIAPI LoadDxeCoreFromFv(IN UINTN *FvInstance, OPTIONAL IN UINTN StackSize)
} else {
Status = FfsAnyFvFindFirstFile (EFI_FV_FILETYPE_DXE_CORE, &VolumeHandle, &FileHandle);
}
return LoadDxeCoreFromFfsFile (FileHandle, StackSize);
}
在 LoadDxeCoreFromFfsFile() 中最终调用运行 EntryPoint函数。
SwitchStack (
(SWITCH_STACK_ENTRY_POINT)(UINTN)EntryPoint,
Hob,
NULL,
TopOfStack
);
1.4 DXE (驱动执行环境)
DXE的加载位置在:
amssBOOT.XF.1.4boot_imagesEmbeddedPkgLibraryPrePiLibPrePiLib.c的 LoadDxeCoreFromFv中
在前面1.3中,我们分析,CPU跳转到了ENTRY_POINT函数 DxeMain()中
# amssBOOT.XF.1.4boot_imagesMdeModulePkgCoreDxeDxeMain.inf
[Defines]
INF_VERSION = 0x00010005
BASE_NAME = DxeCore
MODULE_UNI_FILE = DxeCore.uni
FILE_GUID = D6A2CB7F-6A18-4e2f-B43B-9920A733700A
MODULE_TYPE = DXE_CORE
VERSION_STRING = 1.0
ENTRY_POINT = DxeMain
我们接下来看下DxeMain的主要工作。
DXE的入口代码位于:
amssBOOT.XF.1.4boot_imagesMdeModulePkgCoreDxeDxeMainDxeMain.c的 DxeMain中
主要工作流程为:
- 初始化CPU异常处理情况。
- 初始化内存、事件等相关基础服务, 主要目的是初始化UEFI代码基础环境(这一大段代码有点看不懂,跳过)
- 初始化DXE 调度器相关
VOID EFIAPI DxeMain (IN VOID *HobStart)
1.5 BDS (启动设备选择)
代码位于:
amssBOOT.XF.1.4boot_imagesQcomPkgDriversBdsDxeBdsEntry.c 的 BdsEntry 中
其主要工作为:
- 注册按键事件,按下按键后会回调到
HotkeyEvent()函数,最终调用到HotkeyCallback()函数中,解析其中的key scancode - 平台BDS初始化
在其中会打印显示版本号,平台版本信息等等
调用LaunchDefaultBDSApps ()加载默认APP
调用SetupPlatformSecurity()初始化secureboot安全环境
挂载efisp分区
调用ReadAnyKey()循环检测音量下键是否按下,从而更新对应的启动项 - 初始化所有
DriverOptionList上的 驱动协议。 - 根据选择的启动方式,启动对应的的系统
# amssBOOT.XF.1.4boot_imagesQcomPkgDriversBdsDxeBdsEntry.c
VOID EFIAPI BdsEntry(IN EFI_BDS_ARCH_PROTOCOL *This)
1.6 RT(Run Time)
代码位于:
amssBOOT.XF.1.4boot_imagesMdeModulePkgCoreRuntimeDxeRuntime.c的 RuntimeDriverInitialize 中
参考:
《80_P2484_117_B_UEFI_With_XBL_On_MSM8998_SDM660_SDM.pdf》
https://blog.csdn.net/u014089131/article/details/73617716
https://blog.csdn.net/jlgcumt/article/details/104842586
https://blog.csdn.net/jlgcumt/article/details/104814931
https://blog.csdn.net/jlgcumt/article/details/104836900
https://blog.csdn.net/jlgcumt/article/details/104790690
https://blog.csdn.net/jlgcumt/article/details/104756673





