在 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 ) ;
- bootloader/mcuboot/boot/zephyr/main.c,
- 根据 header 和签名信息对 app 进行校验
- bootloader/mcuboot/boot/zephyr/single_loader.c
FIH_CALL ( boot_image_validate, fih_rc, _fa_p, &_hdr ) ;
- bootloader/mcuboot/boot/zephyr/single_loader.c
- 校验通过后从 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
- bootloader/mcuboot/boot/zephyr/main.c
被 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 字节以后才是实际的程序镜像。