4. BL2阶段详解:BL2的职责、平台配置、加载并验证后续镜像

好,我们进入BL2阶段。这是整个ATF启动流程里我个人觉得最“承上启下”的一环。BL1把接力棒交给你,你得把后面所有镜像都安排好——说白了,BL2就是那个“总调度+安检员”。

4.1 BL2的核心职责

BL2运行在EL3,但它不像BL1那样只做极简初始化。它的任务更复杂:

  • 加载并验证后续镜像:BL31(EL3运行时固件)、BL32(可选的安全OS)、BL33(非安全侧Bootloader,比如U-Boot)。
  • 管理平台配置:从FIP(Firmware Image Package)里解析镜像,并处理平台特定的硬件信息。
  • 建立安全上下文:为BL31准备好运行环境,包括内存布局和参数传递。

嗯,这里要注意:BL2本身也是被BL1验证过的。所以整个信任链是连续的——BL1验BL2,BL2验后面的所有镜像。

信任链关键点:BL2是第一个“主动验证者”。它不再依赖BL1的验证逻辑,而是用自己的代码去验后面的镜像。一旦BL2被攻破,整个安全启动就完了。

4.2 平台配置:BL2怎么知道该做什么?

BL2不是写死的。它通过平台描述文件(比如plat/<platform>/下的代码)来适配不同硬件。我刚开始接触ATF时,最头疼的就是理清这些配置关系。

核心配置项包括:

配置项 作用 常见值
BL2_BASE BL2在SRAM中的基地址 0x04020000
BL2_LIMIT BL2可用的最大地址 0x04030000
BL31_BASE BL31加载地址 0x04003000
BL32_BASE BL32加载地址(可选) 0x06000000
BL33_BASE BL33加载地址 0x80000000

这些值通常在include/plat/<platform>/platform_def.h里定义。我曾经在一个项目里因为BL2_BASE和BL31_BASE重叠,导致BL31加载时直接覆盖了BL2的代码——嗯,死机是必然的。

避坑指南:我曾经在调试一块新板子时,发现BL2加载BL31后系统就挂。查了两天才发现是BL31_BASE设在了BL2的代码段内。记住:BL2和BL31不能有地址重叠,除非你明确知道自己在做什么。

4.3 加载并验证后续镜像

BL2从FIP里读取镜像。FIP是一个打包文件,里面包含了所有需要验证的镜像和它们的元数据(比如哈希值、签名)。

流程大致如下:

  1. 解析FIP:找到每个镜像的偏移和大小。
  2. 加载到内存:把镜像从存储介质(比如eMMC、NOR Flash)读到SRAM或DRAM。
  3. 验证签名:用公钥验签,确保镜像没被篡改。
  4. 传递控制权:跳转到BL31入口。

代码层面,核心函数是load_auth_image()。它封装了加载和验证两个步骤。我习惯把它理解为“先读进来,再检查一下,不行就停”。

// 伪代码示意
int load_auth_image(unsigned int image_id,
                    image_info_t *image_data,
                    entry_point_info_t *entry_point) {
    // 1. 从FIP加载镜像到内存
    int err = load_image(image_id, image_data);
    if (err) return err;

    // 2. 验证镜像签名和哈希
    err = auth_image(image_id, image_data);
    if (err) {
        ERROR("Image %u authentication failed!\n", image_id);
        panic();
    }

    // 3. 设置入口点
    entry_point->pc = image_data->image_base;
    return 0;
}

你想想看,如果验证失败,BL2会直接调用panic()——系统就停在这里了。这是故意的:安全启动宁可死机,也不运行未经验证的代码。

4.4 BL2与后续镜像的关系

BL2加载完所有镜像后,会跳转到BL31。但跳转前,它会把一些关键信息通过bl31_params结构体传过去。这个结构体包含了:

  • BL32和BL33的入口地址
  • 平台特定的配置数据
  • 内存布局信息

我见过不少新手在这里犯错:BL2传的参数不对,导致BL31启动后找不到BL33。说白了,BL2就像一个交接员,交接清单必须准确。

调试技巧:如果你在BL31启动后卡住,先检查BL2传给BL31的参数。可以在BL2的bl2_main()里加打印,把bl31_params的内容dump出来。我曾经靠这个办法半小时定位了一个参数对齐问题。

4.5 BL2阶段的SVG流程图

下面这张图展示了BL2从启动到跳转BL31的完整流程。我建议你对照代码看,会更清楚。

BL2阶段核心流程 BL2入口 (bl2_main) 初始化平台 解析FIP 加载并验证BL31 加载并验证BL32 加载并验证BL33 跳转到BL31 BL31开始执行 说明:BL32为可选镜像,若不存在则跳过;所有验证失败都会触发panic()

4.6 平台适配的常见陷阱

最后聊几个我踩过的坑:

  • FIP打包顺序:BL2解析FIP时,镜像顺序必须和代码里定义的image_id一致。我遇到过FIP里BL31和BL32顺序反了,结果BL2把BL32当BL31加载——直接死机。
  • 内存对齐:BL2加载镜像时,目标地址必须按16字节对齐。有些平台要求更严格,比如64字节对齐。不满足对齐条件,验证会失败。
  • 公钥存储:BL2用的公钥通常烧在OTP或eFuse里。如果公钥没烧对,所有镜像都验不过。我曾经在生产线上遇到过一批芯片OTP写入失败,排查了很久才发现是硬件问题。

总结一下:BL2是安全启动的“总闸门”。它做对了一切,后面的镜像才能安全运行。做平台适配时,多花时间检查地址、对齐和FIP结构,能省去大量调试时间。