公司动态

手撕ARM64启动栈(三):QEMU + TF-A(ATF) BL1 核心职责——`bl1_main()` 逐函数拆解与 BL2 加载

📅 2026/7/26 8:57:23
手撕ARM64启动栈(三):QEMU + TF-A(ATF) BL1 核心职责——`bl1_main()` 逐函数拆解与 BL2 加载
系列文章目录手撕ARM64启动栈一QEMU TF-A → OP-TEE → U-Boot → Linux 全链路总览手撕ARM64启动栈二QEMU TF-A(ATF) BL1 执行上下文——GDB 实测复位到 BL2手撕ARM64启动栈三QEMU TF-A(ATF) BL1 核心职责——bl1_main()逐函数拆解与 BL2 加载手撕ARM64启动栈四QEMU TF-A(ATF) BL2 详解文章目录系列文章目录[手撕ARM64启动栈一QEMU TF-A → OP-TEE → U-Boot → Linux 全链路总览](https://blog.csdn.net/zheng_wuling/article/details/162657151?spm1011.2124.3001.6209) [手撕ARM64启动栈二QEMU TF-A(ATF) BL1 执行上下文——GDB 实测复位到 BL2](https://blog.csdn.net/zheng_wuling/article/details/162769218?spm1011.2124.3001.6209) [手撕ARM64启动栈三QEMU TF-A(ATF) BL1 核心职责——bl1_main() 逐函数拆解与 BL2 加载](https://blog.csdn.net/zheng_wuling/article/details/162847153?spm1011.2124.3001.6209) [手撕ARM64启动栈四QEMU TF-A(ATF) BL2 详解](https://blog.csdn.net/zheng_wuling/article/details/162975843?spm1011.2124.3001.6209)1. 引言2. bl1_main() 调用序列总览3. 逐函数拆解每个函数的职责3.1 plat_setup_early_console() / bl1_early_platform_setup()初始化控制台GDB 实测UARTCR.UARTEN 从 0 变 13.2 bl1_plat_arch_setup() / bl1_arch_setup()建立地址转换并设定下一级架构状态GDB 实测SCTLR_EL3.M 从 0 变 13.3 crypto_mod_init() / auth_mod_init()初始化校验框架3.4 bl1_platform_setup()注册镜像加载的 IO 后端4. 加载 BL2GDB 实测desc-image_info 从取到描述符到镜像已搬运5. 本篇小结日志与代码的对应关系1. 引言上一篇讲了 BL1 启动瞬间的执行上下文复位向量、el3_entrypoint_common、异常向量表以及 BL1 退出前交给 BL2 的最后状态。本篇回到bl1_main()bl1/bl1_main.c:50内部逐个拆解从打印欢迎信息到加载 BL2 之间调用的每一个平台初始化函数的功能。本篇结论基于TF_A_TRUSTED_BOARD_BOOTn见第一篇配置矩阵。涉及关键词ATF / TF-A / ARM Trusted Firmware、QEMU virt aarch64、BL1、bl1_main、OPTEE、OP-TEE。2.bl1_main()调用序列总览关键调用序列对照bl1/bl1_main.c的bl1_main()voidbl1_main(void){plat_setup_early_console();bl1_early_platform_setup();// 初始化串口之后才能打印bl1_plat_arch_setup();// 建页表、开 MMUNOTICE(FIRMWARE_WELCOME_STR);// Booting Trusted FirmwareNOTICE(BL1: %s\n,build_version_string);// BL1: v2.14.0(debug):v2.14.0NOTICE(BL1: %s\n,build_message);// BL1: Built : ...INFO(BL1: RAM %p - %p\n,...);// 打印 BL1 可见的 Trusted RAM 范围bl1_arch_setup();// 设 SCR_EL3.RW1下一级 EL 用 AArch64crypto_mod_init();// 初始化 crypto 框架auth_mod_init();// 初始化认证框架bl1_plat_mboot_init();// 度量启动初始化bl1_platform_setup();// 调 plat_qemu_io_setup()注册 FIP/semihosting IO 后端image_idbl1_plat_get_next_image_id();// QEMU virt 恒返回 BL2_IMAGE_IDif(image_idBL2_IMAGE_ID)bl1_load_bl2();// 正常启动加载 BL2elseNOTICE(BL1-FWU: *******FWU Process Started*******\n);// 固件升级分支...bl1_prepare_next_image(image_id);// 构造 BL2 的 entry_point_info上一篇已展开}bl1_plat_get_next_image_id()在QEMU 平台使用的是plat/common/plat_bl1_common.c里的弱符号默认实现恒定返回BL2_IMAGE_ID——所以永远走if加载 BL2。真实平台可以重写该函数如检测按键/GPIO/寄存器等返回非BL2_IMAGE_ID值以进入else的 FWU固件升级/恢复流程。bl1_main()调用序列流程图如下3. 逐函数拆解每个函数的职责bl1_main()里这串调用绝大多数是 TF-A 定义的平台移植钩子platform hook——框架规定了调用时机和函数签名具体实现交给各平台。其中bl1_early_platform_setup/bl1_plat_arch_setup/bl1_platform_setup是移植时必须实现的强制钩子官方《Porting Guide》trusted-firmware-a/docs/porting-guide.rst逐个给了职责定义。下面逐个说明每个钩子设计上负责什么。3.1plat_setup_early_console()/bl1_early_platform_setup()初始化控制台bl1_early_platform_setup()是 BL1 的早期平台初始化钩子。官方 porting guideporting-guide.rst:1653-1673原文Function : bl1_early_platform_setup() [mandatory]Argument : void Return : voidThis function executes with the MMU and data caches disabled. It is only calledby the primary CPU.On Arm standard platforms, this function:Enables a secure instance of SP805 to act as the Trusted Watchdog.Initializes a UART (PL011 console), which enables access to theprintffamily of functions in BL1.Enables issuing of snoop and DVM (Distributed Virtual Memory) requests tothe CCI slave interface corresponding to the cluster that includes theprimary CPU.——trusted-firmware-a/docs/porting-guide.rst:1653-1673即在 MMU 和数据 cache 关闭的状态下执行只由主 CPU 调用在 Arm 标准平台上负责使能 Trusted Watchdog、初始化一个 UARTPL011 控制台从而让 BL1 能用printf、以及使能 CCI 的 snoop/DVM 请求。也就是说它的核心职责是在 BL1 打印第一条日志之前把串口控制台拉起来。plat_setup_early_console()bl1_main.c:55则是它之前一个可选的最早期控制台钩子由EARLY_CONSOLE编译开关控制。QEMU 平台的bl1_early_platform_setup()plat/qemu/common/qemu_bl1_setup.c:52-60做两件事调qemu_console_init()初始化控制台并记录 BL1 可见的 Trusted RAM 范围voidbl1_early_platform_setup(void){/* Initialize the console to provide early debug support */qemu_console_init();/* Allow BL1 to see the whole Trusted RAM */bl1_tzram_layout.total_baseBL_RAM_BASE;bl1_tzram_layout.total_sizeBL_RAM_SIZE;}qemu_console_init()plat/qemu/common/qemu_console.c:14-22调用console_pl011_register()注册一个 PL011 UART 作启动运行时控制台。至此串口初始化完成BL1 从这里开始就能通过串口输出 log 了。GDB 实测UARTCR.UARTEN从 0 变 1执行前0x300—— PL011 复位默认值TXEbit8/RXEbit9默认打开但UARTENbit0默认关闭此时串口硬件还不能收发。Step Over 执行完这一行后0x301——UARTEN位被置 1串口正式使能。执行完bl1_early_platform_setup()UARTCR0x301表明已经把 UART 拉起其中$SCTLR_EL30x30c5183aM位/bit0 为0表明MMU还没enable。3.2bl1_plat_arch_setup()/bl1_arch_setup()建立地址转换并设定下一级架构状态bl1_plat_arch_setup()是平台相关的架构初始化钩子。官方 porting guideporting-guide.rst:1675-1689原文Function : bl1_plat_arch_setup() [mandatory]Argument : void Return : voidThis function performs any platform-specific and architectural setup that theplatform requires. Platform-specific setup might include configuration ofmemory controllers and the interconnect.In Arm standard platforms, this function enables the MMU.This function helps fulfill requirement 2 above.——trusted-firmware-a/docs/porting-guide.rst:1675-1689即执行平台特定和架构相关的设置平台特定部分可能包括内存控制器和互连interconnect的配置。QEMU 平台的实现plat/qemu/common/qemu_bl1_setup.c:73-90为建页表 开 MMUvoidbl1_plat_arch_setup(void){constmmap_region_tbl_regions[]{MAP_BL1_TOTAL,MAP_BL1_RO,{0}};setup_page_tables(bl_regions,plat_qemu_get_mmap());enable_mmu_el3(0);}GDB 实测SCTLR_EL3.M从 0 变 1执行前上一节截图同一时刻0x30c5183aM位bit0为0——MMU 尚未开启此时的取指/取数都是物理地址直通。Step Over 执行完这一行后0x30cd183fM位变为1——页表已建好MMU 正式生效。bl1_arch_setup()bl1/aarch64/bl1_arch_setup.c:15-19是平台无关的通用代码负责架构相关的 EL3 初始化当前实现是把下一级异常级别设为 AArch64voidbl1_arch_setup(void){/* Set the next EL to be AArch64 */write_scr_el3(read_scr_el3()|SCR_RW_BIT);}即置SCR_EL3.RW1告诉硬件下一级 ELBL2 所在的 S-EL1用 AArch64 指令集。3.3crypto_mod_init()/auth_mod_init()初始化校验框架这两个函数是 TF-A **可信启动Trusted Board Boot**认证框架的入口本质是初始化 crypto / 认证框架为后面加载镜像时的验签做准备。本文配置下 secure boot 关闭TF_A_TRUSTED_BOARD_BOOTnsecure boot 打开后它们才会真正初始化校验框架这里暂不关注留到后续 secure boot 相关文档详细介绍。3.4bl1_platform_setup()注册镜像加载的 IO 后端bl1_platform_setup()是完整平台初始化钩子。官方 porting guideporting-guide.rst:1691-1706原文Function : bl1_platform_setup() [mandatory]Argument : void Return : voidThis function executes with the MMU and data caches enabled. It is responsiblefor performing any remaining platform-specific setup that can occur after theMMU and data cache have been enabled.In Arm standard platforms, this function initializes the storage abstractionlayer used to load the next bootloader image.This function helps fulfill requirement 4 above.——trusted-firmware-a/docs/porting-guide.rst:1691-1706即在 MMU 和数据 cache 使能之后执行负责在 MMU/dcache 打开后才能进行的剩余平台特定设置在 Arm 标准平台上这个函数初始化用于加载下一个 bootloader 镜像的存储抽象层。QEMU 平台的实现plat/qemu/common/qemu_bl1_setup.cvoidbl1_platform_setup(void){plat_qemu_io_setup();}plat_qemu_io_setup()plat/qemu/common/qemu_io_storage.c注册 FIP 和 semihosting 两套 IO 后端的 policy 表规定每个镜像先从哪里读、失败后退到哪里: BL1 加载 BL2 时先尝试 FIP失败后回退到 semihosting。4. 加载 BL2bl1_main()调用序列里最后、也是最关键的一步是加载 BL2staticvoidbl1_load_bl2(void){descbl1_plat_get_image_desc(BL2_IMAGE_ID);INFO(BL1: Loading BL2\n);errload_auth_image(BL2_IMAGE_ID,info);NOTICE(BL1: Booting BL2\n);}对应日志INFO: BL1: Loading BL2 NOTICE: BL1: Booting BL2load_auth_image()common/bl_common.c:198-208内部按编译期开关分两条路径auth_mod_verify_img()做签名验证的那条路径由TRUSTED_BOARD_BOOT开关控制本篇配置下是n走的是纯搬运路径load_image()staticintload_auth_image_internal(unsignedintimage_id,image_info_t*image_data){#ifTRUSTED_BOARD_BOOTif(dyn_is_auth_disabled()0){returnload_auth_image_recursive(image_id,image_data);}#endifreturnload_image(image_id,image_data);// ← 本例实际执行这一行}签名验证相关的load_auth_image_recursive()/auth_mod_verify_img()留到后续 secure boot 专题文档展开本篇只关注load_image()这条路径。从bl1_load_bl2()起先经load_auth_image()→load_auth_image_internal()转发到load_image()TBBn 时无任何校验分支再进入load_image()common/bl_common.c:73-153内部找到镜像源 → 打开 → 读大小 → 检查大小 → 搬运数据到内存GDB 实测desc-image_info从取到描述符到镜像已搬运断点打在bl1_main.c:176info desc-image_info;bl1_load_bl2()内部desc bl1_plat_get_image_desc(BL2_IMAGE_ID);刚执行完的下一行WATCH 里把局部变量desc整个展开字段值说明desc0xe0ee080 bl2_img_desc静态描述符表里 BL2 那一项的地址非堆/栈临时对象desc-image_id0x1即BL2_IMAGE_IDdesc-state0x0IMAGE_STATE_RESET尚未开始加载desc-image_info.image_base0xe05b000BL2 将被搬运到的目标物理地址desc-image_info.image_max_size0x35000分配给 BL2 的最大空间对应BL2_BASE BL31_BASE - 0x35000platform_def.hdesc-image_info.image_size0x0此刻还没有加载大小未知desc-ep_info.pc0xe05b000与image_base相同——BL2 的入口地址就是它被搬运到的起始地址image_base 0xe05b000正是上一篇《执行上下文》断点 4/5 里next_bl_ep-pc/ELR_EL3最终被写入的那个值——两篇的实测数据在这里完全对得上本篇看到的是BL2 要被搬到哪上一篇看到的是BL1 ERET 跳到哪同一个地址。5. 本篇小结日志与代码的对应关系日志行 / 关键事实代码位置在 QEMU virt 上是否真实动作NOTICE: Booting Trusted Firmwarebl1_main.c:78—NOTICE: BL1: v2.14.0(debug):v2.14.0bl1_main.c:79—串口可以打印字符bl1_early_platform_setup()→qemu_console_init()qemu_console.c:14-22PL011 0x09000000plat_setup_early_console()本身因EARLY_CONSOLE0是空函数✅ 真实寄存器配置BL1 页表建立、MMU 开启bl1_plat_arch_setup()qemu_bl1_setup.c:73-90✅ 真实软件页表非内存控制器SCR_EL3.RW1bl1_arch_setup()bl1_arch_setup.c:15-19✅ 真实单条寄存器写load_auth_image()实际行为common/bl_common.cload_auth_image_internal()❌ TBBn 时等价于load_image()无校验NOTICE: BL1: Booting BL2bl1_load_bl2()末尾数据搬运完成至此BL1 阶段全部分析完成上一篇讲执行上下文复位向量、异常向量表、退出前状态本篇讲核心职责–逐函数拆解——串口初始化mmu页表建立与使能BL2镜像加载等。综合两篇,BL1 的主要职责已经清晰:上电复位后完成必要的底层初始化(硬件寄存器、EL3 异常向量表),建立 C 运行时环境与调用栈;随后进入 bl1_main() 执行平台相关初始化(串口、MMU 等;crypto/认证框架在本配置 TBBn 下为空);最后加载并跳转到 BL2,完成使命。下一篇开始进入BL2阶段分析BL2的详细流程其实到这里我们就可以知道ARM TF-A已将BL1流程(复位入口、C 运行时、bl1_main() 主序列)框架写好,芯片相关部分抽象成 bl1_early_platform_setup、bl1_plat_arch_setup、bl1_platform_setup 等平台钩子。如果需要移植到自己真实芯片,只要实现对应的钩子(串口、MMU、加载 BL2 的 IO 后端),BL1 就能跑起来并加载 BL2。