Trusted Firmware-A 加载 U-Boot 过程详解

本文档详细描述了 Trusted Firmware-A (TFA) 如何加载 U-Boot 的完整过程,基于 bitmain/bm1684 平台的实现。

1. 引导流程概述

TFA 采用分阶段引导的方式,从 BL1 到 BL2,再到 BL31,最终跳转到 BL33(U-Boot)。整个流程如下:

  1. BL1:从 SPI Flash 加载,初始化基本硬件,然后加载 BL2 到安全 SRAM
  2. BL2:加载 BL31(EL3 运行时)和 BL33(U-Boot,非安全)到指定内存位置
  3. BL31:初始化 EL3 运行时环境,然后跳转到 BL33(U-Boot)
  4. BL33:U-Boot 启动,继续引导操作系统

2. 关键组件和文件

2.1 主要代码文件

  • bl2/bl2_main.c:BL2 的主函数,协调镜像加载过程
  • bl2/bl2_image_load_v2.c:实现 bl2_load_images() 函数,负责加载 BL31 和 BL33
  • plat/bitmain/bm1684/bm_bl2_mem_params_desc.c:定义 BL33(U-Boot)的加载参数
  • plat/bitmain/bm1684/include/platform_def.h:定义内存布局和地址偏移
  • bl31/bl31_main.c:BL31 主函数,负责初始化并跳转到 BL33

2.2 内存布局

根据 platform_def.h 的定义,相关内存布局如下:

内存区域 基地址 大小 用途
NS_DRAM_BASE 0x300000000 256MB 非安全 DRAM 基地址
NS_IMAGE_OFFSET 0x308000000 - U-Boot 加载地址(偏移 128MB)
NS_IMAGE_MAX_SIZE 0x8000000 128MB U-Boot 最大大小
BL31_BASE 0x300000000 256KB BL31 加载地址

3. 详细加载过程

3.1 BL2 加载 BL33(U-Boot)

  1. BL2 主函数执行

    • bl2_main() 函数中,调用 bl2_load_images() 加载后续镜像
  2. 获取镜像加载信息

    • bl2_load_images() 调用 plat_get_bl_image_load_info() 获取镜像加载信息
    • 对于 BM1684 平台,此函数会将镜像描述符复制到安全位置
  3. 遍历并加载镜像

    • 遍历 bl2_mem_params_descs 数组中的每个镜像
    • 对于 BL31(安全镜像)和 BL33(非安全镜像),执行加载操作
  4. BL33 加载配置
    bm_bl2_mem_params_desc.c 中,BL33 的配置如下:

    { .image_id = BL33_IMAGE_ID,
      SET_STATIC_PARAM_HEAD(ep_info, PARAM_EP, VERSION_2,
                            entry_point_info_t, NON_SECURE | EXECUTABLE),
      .ep_info.pc = NS_IMAGE_OFFSET,
    
      SET_STATIC_PARAM_HEAD(image_info, PARAM_EP, VERSION_2, image_info_t,
                            0),
      .image_info.image_base = NS_IMAGE_OFFSET,
      .image_info.image_max_size = NS_IMAGE_MAX_SIZE,
    
      .next_handoff_image_id = INVALID_IMAGE_ID,
    }
    
  5. 执行加载

    • 调用 load_auth_image() 加载 BL33 镜像到 NS_IMAGE_OFFSET 地址
    • 执行平台特定的后处理操作 bl2_plat_handle_post_image_load()
  6. 准备跳转参数

    • 调用 plat_get_next_bl_params() 获取传递给下一个镜像的参数
    • 刷新参数到内存,确保下一个镜像可以访问
  7. 跳转到 BL31

    • BL2 完成加载后,跳转到 BL31 执行

3.2 BL31 跳转到 BL33(U-Boot)

  1. BL31 初始化

    • bl31_main() 函数中,执行平台初始化、运行时服务初始化等操作
  2. 准备跳转到下一个镜像

    • 调用 bl31_prepare_next_image_entry() 准备跳转到 BL33
  3. 获取 BL33 入口信息

    • 调用 bl31_plat_get_next_image_ep_info(NON_SECURE) 获取 BL33 的入口点信息
    • 验证入口点信息的安全性状态
  4. 配置 EL3 退出

    • 调用 cm_init_my_context(next_image_info) 初始化上下文
    • 调用 cm_prepare_el3_exit_ns() 准备从 EL3 退出到非安全世界
  5. 执行 ERET 指令

    • 执行 ERET 指令,跳转到 BL33(U-Boot)的入口点
    • U-Boot 开始执行

4. 关键函数调用链

4.1 BL2 加载流程

bl2_main()
  └── bl2_load_images()
      ├── plat_get_bl_image_load_info()
      │   └── get_bl_load_info_from_mem_params_desc()
      ├── load_auth_image(BL31_IMAGE_ID, ...)
      ├── load_auth_image(BL33_IMAGE_ID, ...)
      ├── plat_get_next_bl_params()
      │   └── get_next_bl_params_from_mem_params_desc()
      ├── plat_flush_next_bl_params()
      └── return next_bl_ep_info (指向 BL31)

4.2 BL31 跳转流程

bl31_main()
  ├── bl31_platform_setup()
  ├── runtime_svc_init()
  ├── bl31_prepare_next_image_entry()
  │   ├── bl31_get_next_image_type() (返回 NON_SECURE)
  │   ├── bl31_plat_get_next_image_ep_info(NON_SECURE)
  │   ├── cm_init_my_context(next_image_info)
  │   └── cm_prepare_el3_exit_ns()
  └── 执行 ERET 跳转到 BL33

5. 配置和定制

5.1 内存布局配置

U-Boot 的加载地址和大小可以通过修改 platform_def.h 中的以下宏来调整:

  • NS_IMAGE_OFFSET:U-Boot 的加载地址
  • NS_IMAGE_MAX_SIZE:U-Boot 的最大大小

5.2 镜像加载配置

BL33 的加载参数可以在 bm_bl2_mem_params_desc.c 中修改,包括:

  • 入口点地址
  • 镜像基地址
  • 镜像最大大小
  • 安全状态标记

6. 调试和排错

6.1 常见问题

  1. U-Boot 加载失败

    • 检查 NS_IMAGE_OFFSET 是否正确
    • 确认 U-Boot 镜像大小不超过 NS_IMAGE_MAX_SIZE
    • 检查 SPI Flash 中的 U-Boot 镜像是否正确
  2. 跳转失败

    • 检查 BL33 的入口点地址是否正确
    • 确认 BL31 到 BL33 的参数传递是否正确
  3. 内存冲突

    • 确保 BL31 和 BL33 的内存区域不重叠
    • 检查其他组件是否使用了相同的内存区域

6.2 调试技巧

  • 在 BL2 和 BL31 中添加调试信息,输出镜像加载的详细过程
  • 使用 JTAG 调试器跟踪引导过程
  • 检查 U-Boot 的反汇编代码,确认入口点逻辑正确

7. 总结

TFA 加载 U-Boot 的过程是一个精心设计的多阶段引导流程,确保了系统的安全性和可靠性。通过 BL2 加载 BL33 到指定内存位置,然后由 BL31 负责安全地跳转到 U-Boot,整个过程实现了从安全世界到非安全世界的平稳过渡。

这种设计不仅提供了安全的引导环境,还为 U-Boot 和后续的操作系统启动做好了充分准备,是现代 ARM 系统引导架构的重要组成部分。

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐