Zephyr 下 esp32c3 构建引导之 ESP Bootloader 一文中提到 Zephyr 对 esp32c3 也支持从 MCUBoot 引导:

ROM-> MCUboot -> Zephyr.bin ( App )

ROM 在前文已经说明,本文着重分析 MCUboot 的构建引导部分。

构建 链接到标题

使用 west -v build -b esp32c3_zgp --sysbuild zephyr_sample/ -- -DBOARD_ROOT=/mnt/g/project/v3.4.0/zephyr_sample/ -Dmcuboot_BOARD_ROOT=/mnt/g/project/v3.4.0/zephyr_sample/ 构建,会编译出 mcuboot 和 app 两部分 image. 主要流程如下:

  • 构建 mcuboot, mcuboot 按照 ROM 引导的格式加头,分出 segment
  • 构建 zephyr app,生成 elf
  • 将 zephyr.elf 转化为 zephyr.bin, Zephyr 原生加入了 mcuboot 的头,因此只用做 objcopy
  • 对 zephyr.bin 进行签名转化为 zephyr.signed.bin

下面描述使用–sysbuild 时会自动加入 mcuboot 进行构建的过程:

zephyr/scripts/zephyr_module.py 导入 sysbuild 的 cmake

sysbuild_cmake += process_sysbuildcmake ( module.project, module.meta )

zephyr/scripts/west_commands/build.py do_run->self._run_cmake

config_sysbuild = config_getboolean ( 'sysbuild', False )
  if self.args.sysbuild or ( config_sysbuild and not self.args.no_sysbuild ) :
      cmake_opts.extend ( ['-S{}'.format ( SYSBUILD_PROJ_DIR ) ,
                  '-DAPP_DIR: PATH={}'.format ( self.source_dir )] )
  else:
    # self.args.no_sysbuild == True or config sysbuild False
    cmake_opts.extend ( ['-S{}'.format ( self.source_dir )] )

读取 share/sysbuild 下面的 CMakeList.txt 并执行:

SYSBUILD_PROJ_DIR = pathlib.Path ( __file__ ) .resolve ( ) .parent.parent.parent \
    / pathlib.Path ( 'share/sysbuild' )

zephyr/share/sysbuild/CMakeLists.txt:

#执行 zephyr app 的构建
# This adds the primary application to the build.
ExternalZephyrProject_Add (
        APPLICATION ${app_name}
        SOURCE_DIR ${APP_DIR}
        APP_TYPE MAIN
 )
list ( APPEND IMAGES "${app_name}" )
set ( DEFAULT_IMAGE "${app_name}" )

# 添加 bootloader 子目前的 CMakeList
add_subdirectory ( bootloader )

zephyr/share/sysbuild/bootloader/CMakeLists.txt, 执行 mcuboot 的构建:

# Include MCUboot if enabled.
if ( SB_CONFIG_BOOTLOADER_MCUBOOT )
  set ( image mcuboot )
  ExternalZephyrProject_Add (
          APPLICATION ${image}
          SOURCE_DIR ${ZEPHYR_MCUBOOT_MODULE_DIR}/boot/zephyr/
          APP_TYPE BOOTLOADER
  )
# MCUBoot default configuration is to perform a full chip erase.
# Placing MCUBoot first in list to ensure it is flashed before other images.
set ( IMAGES ${image}${IMAGES} PARENT_SCOPE )

set_config_string ( ${image} CONFIG_BOOT_SIGNATURE_KEY_FILE "${SB_CONFIG_BOOT_SIGNATURE_KEY_FILE}" )
endif ( )

在 zephyr/share/sysbuild/bootloader/Kconfig 中默认开启了 SB_CONFIG_BOOTLOADER_MCUBOOT。 mcuboot 构建后会被 zephyr/soc/riscv/esp32c3/CMakeLists.txt 转化为 ROM 可引导的的 bin 文件。

使用–sysbuild 构建 zephyr app 时,会使用 zephyr_sample/boards/riscv/esp32c3_zgp/Kconfig.sysbuild 下的配置:

  choice BOOTLOADER
      default BOOTLOADER_MCUBOOT
  endchoice

  choice BOOT_SIGNATURE_TYPE
      default BOOT_SIGNATURE_TYPE_NONE
  endchoice

由于 BOOTLOADER_MCUBOOT 被启用,BOOTLOADER_ESP_IDF 将被关闭,参考 zephyr/Kconfig.zephyr:

  config BOOTLOADER_ESP_IDF
      bool "ESP-IDF bootloader support"
      depends on SOC_FAMILY_ESP32 && !BOOTLOADER_MCUBOOT && !MCUBOOT
      default    y

关闭 BOOTLOADER_ESP_IDF 的情况下,zephyr app 的 bin 文件是由 elf 直接 objcopy 出来,zephyr/CmakeList.txt 会指定对其进行签名:

  if ( CONFIG_BOOTLOADER_MCUBOOT )
    get_target_property ( signing_script zephyr_property_target SIGNING_SCRIPT )
    if ( NOT signing_script )
      set_target_properties ( zephyr_property_target PROPERTIES SIGNING_SCRIPT ${CMAKE_CURRENT_LIST_DIR}/cmake/mcuboot.cmake )
    endif ( )
  endif ( )

  # Include signing script, if set
  get_target_property ( signing_script zephyr_property_target SIGNING_SCRIPT )
  if ( signing_script )
    message ( STATUS "Including signing script: ${signing_script}" )

    include ( ${signing_script} )
  endif ( )

签名的时候会对 bin 的前 32 个字节进行 mcuboot header 填充,并将签名的结果写到 bin 的末尾,生成 zephyr.sign.bin

烧写 链接到标题

构建的结果均放到 build 中

  • bootloader: build/mcuboot/zephyr/zephyr.bin
  • app: build/zephyr_sample/zephyr/zephyr.signed.bin

west flash 执行烧写时,依赖于 board_finalize_runner_args 加入的参数,会使用 esptool 将 bootloader 和 zephyr.sign.bin 分开烧写

 runners.esp32: /usr/bin/python3 /mnt/g/project/v3.4.0/modules/hal/espressif/components/esptool_py/esptool/esptool.py --chip auto --baud 921600 --before default_reset --after hard_reset write_flash -u --flash_mode dio --flash_freq 40m --flash_size detect 0x0 /mnt/g/project/v3.4.0/mcuboot-build/mcuboot/zephyr/zephyr.bin
 runners.esp32: /usr/bin/python3 /mnt/g/project/v3.4.0/modules/hal/espressif/components/esptool_py/esptool/esptool.py --chip auto --baud 921600 --before default_reset --after hard_reset write_flash -u --flash_mode dio --flash_freq 40m --flash_size detect 0x10000 /mnt/g/project/v3.4.0/mcuboot-build/zephyr_sample/zephyr/zephyr.signed.bin

引导流程 链接到标题

  • ROM 从 0 地址读取 mcuboot,根据 header 对 mcuboot 进行加载,mcuboot 运行在芯片内部的 iram
  • mcuboot 编译期已从 dts 将分区表信息固化在 mcuboot 中
  • mcuboot 从 slot0 的地方读出 header
    • bootloader/mcuboot/boot/zephyr/main.c, FIH_CALL ( boot_go, fih_rc, &rsp ) ;
  • 根据 header 和签名信息对 app 进行校验
    • bootloader/mcuboot/boot/zephyr/single_loader.c FIH_CALL ( boot_image_validate, fih_rc, _fa_p, &_hdr ) ;
  • 校验通过后从 header 后面读取 metadata,根据 metadata 中各段的信息进行 text 和 data 的拷贝,建立 flash mmu,根据 metadata 中入口地址跳转执行,app 运行在 flash 上
    • bootloader/mcuboot/boot/zephyr/main.c do_boot->start_cpu0_image
    • bootloader/mcuboot/boot/espressif/port/esp_loader.c start_cpu0_image->esp_app_image_load

被 mcuboot 引导 app 二进制镜像结构,在 zephyr/soc/riscv/esp32c3/default.ld 定义

    mcuboot_hdr ( RX ) : org = 0x0, len = 0x20
    metadata ( RX ) : org = 0x20, len = 0x20
    ROM ( RX ) : org = 0x40, len = FLASH_SIZE - 0x40

最开始 32 字节为 mcuboot header,编译期只是预留这部分空间,在签名时进行改写

    .mcuboot_header :
    {
      QUAD ( 0x0 )
      QUAD ( 0x0 )
      QUAD ( 0x0 )
      QUAD ( 0x0 )
 }> mcuboot_hdr

mcuboot header 后面是 32 字节的 metadata,为 esp32c3 内部 sram 使用情况和入口地址提供信息,这是 esp32c3 的特殊流程

    .metadata :
    {
      /* Magic byte for load header */
      LONG ( 0xace637d3 )

      /* Application entry point address */
      KEEP ( * ( .entry_addr ))

      /* IRAM metadata:
   - - Destination address ( VMA ) for IRAM region
   - - Flash offset ( LMA ) for start of IRAM region
   - - Size of IRAM region
       */
      LONG ( ADDR ( .iram0.text ))
      LONG ( LOADADDR ( .iram0.text ))
      LONG ( SIZEOF ( .iram0.text ))

      /* DRAM metadata:
   - - Destination address ( VMA ) for DRAM region
   - - Flash offset ( LMA ) for start of DRAM region
   - - Size of DRAM region
       */
      LONG ( ADDR ( .dram0.data ))
      LONG ( LOADADDR ( .dram0.data ))
      LONG ( LOADADDR ( .dummy.dram.data ) + SIZEOF ( .dummy.dram.data ) - LOADADDR ( .dram0.data ))
 }> metadata

64 字节以后才是实际的程序镜像。