1. 课程导入

在ARM64服务器、嵌入式与可信计算平台中,固件启动链路通常由两段关键软件共同完成:一段是负责平台初始化、设备枚举与操作系统加载的UEFI固件,另一段是负责底层安全监控、电源管理、异常处理与安全世界切换的ATF(Arm Trusted Firmware)。很多工程师在单独理解UEFI或单独理解ATF时并不困难,但一旦进入真实项目,就会遇到“UEFI如何把控制权交给ATF”“BL31如何返回EL2/EL1”“PSCI调用如何穿过SMC进入ATF”“启动参数如何跨阶段传递”等具体问题。本课程的目标,正是把这两套体系从概念到代码、从启动时序到调试手段完整打通。

一、学习目标:从“知道”到“能对接”

本课程不是单纯讲UEFI规范,也不是单纯讲ATF源码,而是围绕“对接”这一工程动作展开。学完本课程后,你应当能够独立完成以下任务:

  • 说清ARM64启动过程中BL1、BL2、BL31、BL33各阶段的职责与交接关系;
  • 理解UEFI在BL33阶段作为非安全世界引导加载器的角色定位;
  • 掌握PSCI、SMC/HVC、SMC Calling Convention在UEFI与ATF之间的调用路径;
  • 能够阅读并修改ATF的plat层、BL31入口与UEFI的DXE/PEI关键代码;
  • 能够使用FVP、QEMU或真实开发板复现启动流程并定位对接故障;
  • 能够设计启动参数传递、共享内存、日志与异常处理的联调方案。
核心判断标准:当系统能从BL1一路启动到UEFI Shell,并且UEFI能通过PSCI成功完成CPU_ON、SYSTEM_OFF、SYSTEM_RESET等调用,才算真正完成了一次可验证的UEFI与ATF对接。

二、核心概念:先建立一张“启动地图”

ARM64可信固件启动流程通常被划分为多个Boot Loader阶段。BL1位于芯片内部ROM或可信SRAM,负责最基础的平台初始化与BL2加载;BL2负责加载BL31、BL32和BL33;BL31是ATF的运行时固件,常驻EL3,负责安全监控、PSCI服务与世界切换;BL33通常就是UEFI固件,运行在非安全世界,最终加载操作系统。理解这张地图,是后续所有代码分析和调试工作的前提。

需要特别注意的是,UEFI与ATF并不是简单的“前后关系”。在启动早期,BL31先被加载并初始化,随后BL33才被跳转执行;但在运行期,UEFI又可以通过SMC指令主动调用BL31提供的服务。因此二者既是启动阶段的上下游,又是运行阶段的服务调用关系。这种双重关系,正是对接工程中最容易混淆的地方。

ARM64 启动阶段与 UEFI / ATF 对接关系 BL1 ROM / 可信SRAM BL2 加载BL31/BL32/BL33 BL31 (ATF) EL3 安全监控 / PSCI BL33 (UEFI) 非安全世界引导 运行期调用关系(UEFI → ATF) UEFI PSCI Client SMC / HVC 指令 BL31 PSCI Service 关键对接点与调试观测位置 BL31 入口与 plat 初始化 BL33 跳转与启动参数 PSCI / SMC 调用路径 异常向量与日志输出 共享内存与通信协议 FVP / QEMU 验证环境

三、整体框架:三条主线贯穿全课程

为了避免知识碎片化,本课程用三条主线组织内容。第一条是启动时序线,从BL1到BL33,重点分析每一阶段的入口、参数、内存布局与跳转条件。第二条是服务调用线,围绕PSCI、SMC Calling Convention、共享内存和中断路由,分析UEFI如何请求ATF服务。第三条是工程调试线,包括日志分级、串口输出、FVP模型、JTAG调试、异常回溯与常见启动挂死定位。

主线 核心问题 关键对象 典型产出
启动时序线 控制权如何从BL1交到UEFI? BL1/BL2/BL31/BL33、入口地址、启动参数 启动流程图、内存布局图、跳转日志
服务调用线 UEFI如何调用ATF的PSCI服务? SMC/HVC、PSCI、共享内存、EL3异常向量 调用时序图、参数表、返回码分析
工程调试线 启动失败时如何快速定位? 串口日志、FVP、JTAG、异常等级、栈回溯 调试记录、故障树、修复补丁

四、案例场景:一次典型的“卡在BL31之后”问题

假设你在FVP平台上构建了ATF和UEFI,串口只输出到“BL31: Initializing runtime services”后就没有后续日志。此时不要急于修改UEFI代码,而应按启动链路逐段排查。首先确认BL31是否成功完成plat初始化并跳转到BL33入口;其次检查BL33的加载地址与入口地址是否与ATF的plat参数一致;然后确认UEFI早期串口是否已经初始化,因为很多“无日志”其实是UEFI还没走到串口驱动。最后再检查PSCI版本、CPU数、共享内存基址等参数是否匹配。

# 典型排查命令示例(FVP环境)
./FVP_Base_RevC-2xAEMvA \
  -C pctl.startup=0.0.0.0 \
  -C bp.secure_memory=1 \
  -C cluster0.NUM_CORES=4 \
  -a BL1.bin@0x0 \
  -a BL2.bin@0x10000 \
  -a BL31.bin@0x20000 \
  -a BL33.bin@0x40000

# 观察串口输出中的关键阶段
NOTICE:  BL1: Booting BL2
NOTICE:  BL2: Booting BL31
NOTICE:  BL31: v2.10
NOTICE:  BL31: Initializing runtime services
NOTICE:  BL31: Preparing for EL3 exit to normal world
调试口诀:先看BL1,再看BL2,再看BL31,最后看BL33。每一阶段都要确认“入口地址、参数、内存、日志”四件事。不要跳过BL31直接怀疑UEFI。

五、学习路径与常见误区

建议按“概念→时序→代码→调试→实战”的顺序推进。先建立BL1到BL33的整体地图,再深入BL31入口与PSCI实现,随后阅读UEFI的PSCI客户端与DXE阶段代码,最后在FVP或开发板上完成一次完整启动与调用验证。每学完一个模块,都应回到启动日志中找对应证据,避免只停留在源码阅读。

常见误区有三个:第一,把UEFI当成ATF的“下一阶段”而忽略运行期SMC调用;第二,只关注代码不关注内存布局和入口参数,导致跳转失败却找不到原因;第三,遇到问题直接改UEFI,而不先确认BL31是否已经正确初始化。复盘时建议记录每次启动的完整日志、镜像加载地址、PSCI返回码和异常等级变化,这些信息往往比单步调试更有效。

本课程后续所有实验都建议保留完整串口日志,并按“BL1→BL2→BL31→BL33”分段标注。没有日志的调试,等于在黑暗中修电路。

当你能够把启动时序、服务调用和调试手段三条线合在一起看,UEFI与ATF的对接就不再是零散的知识点,而是一条可以验证、可以复现、可以修复的工程链路。接下来的章节将沿着这条链路,从BL1的第一条指令开始,逐步走到UEFI加载操作系统的最后一跳。