The article on building the ESP Bootloader under Zephyr for ESP32-C3 mentions that Zephyr also supports booting from MCUBoot:
ROM-> MCUboot -> Zephyr.bin ( App )
As previously explained about the ROM, this article focuses on analyzing the booting part of MCUboot.
Building
Using 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/
to build will compile both mcuboot and app image. The main process is as follows:
-
Build mcuboot, mcuboot adds a header according to the ROM boot format and segments it. -
Build the zephyr app, generating an elf file. -
Convert zephyr.elf to zephyr.bin, Zephyr natively includes the mcuboot header, so only objcopy is needed. -
Sign zephyr.bin to convert it to zephyr.signed.bin.
The following describes the process when using –sysbuild, which automatically includes mcuboot in the build:
zephyr/scripts/zephyr_module.py imports the cmake for sysbuild.
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 )] )
Read share/sysbuild
below CMakeList.txt and execute:
SYSBUILD_PROJ_DIR = pathlib.Path ( __file__ ) .resolve ( ) .parent.parent.parent \
/ pathlib.Path ( 'share/sysbuild' )
zephyr/share/sysbuild/CMakeLists.txt:
# Execute the build of 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}" )
# Add the bootloader's current CMakeList
add_subdirectory ( bootloader )
In zephyr/share/sysbuild/bootloader/CMakeLists.txt, execute the build of 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 ( )
In zephyr/share/sysbuild/bootloader/Kconfig, SB_CONFIG_BOOTLOADER_MCUBOOT
is enabled by default. After building mcuboot, it will be converted into a ROM bootable bin file by zephyr/soc/riscv/esp32c3/CMakeLists.txt
.
When building the zephyr app using –sysbuild, the configuration from zephyr_sample/boards/riscv/esp32c3_zgp/Kconfig.sysbuild
will be used:
choice BOOTLOADER
default BOOTLOADER_MCUBOOT
endchoice
choice BOOT_SIGNATURE_TYPE
default BOOT_SIGNATURE_TYPE_NONE
endchoice
Since BOOTLOADER_MCUBOOT is enabled, BOOTLOADER_ESP_IDF will be disabled, as referenced in zephyr/Kconfig.zephyr:
config BOOTLOADER_ESP_IDF
bool "ESP-IDF bootloader support"
depends on SOC_FAMILY_ESP32 && !BOOTLOADER_MCUBOOT && !MCUBOOT
default y
With BOOTLOADER_ESP_IDF disabled, the bin file of the zephyr app is directly objcopy from the elf, and zephyr/CmakeList.txt will specify to sign it:
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 ( )
During signing, the first 32 bytes of the bin will be filled with the mcuboot header, and the result of the signature will be written to the end of the bin, generating zephyr.sign.bin.
Flashing
The results of the build are placed in the build directory:
-
bootloader: build/mcuboot/zephyr/zephyr.bin -
app: build/zephyr_sample/zephyr/zephyr.signed.bin
west flash
during flashing relies on the parameters added by board_finalize_runner_args
, and will use esptool to flash the bootloader and zephyr.sign.bin separately.
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
Boot Process
-
ROM reads mcuboot from address 0, loading mcuboot based on the header, mcuboot runs in the chip’s internal iram. -
mcuboot has already solidified the partition table information from the dts during compilation. -
mcuboot reads the header from slot0. -
bootloader/mcuboot/boot/zephyr/main.c, FIH_CALL ( boot_go, fih_rc, &rsp ) ;
-
Verify the app based on the header and signature information. -
bootloader/mcuboot/boot/zephyr/single_loader.c FIH_CALL ( boot_image_validate, fih_rc, _fa_p, &_hdr ) ;
-
After verification, read the metadata from behind the header, copy the text and data according to the information in the metadata, establish the flash mmu, and jump to execute based on the entry address in the metadata, the app runs in 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
The binary image structure of the app guided by mcuboot is defined in 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
The first 32 bytes are the mcuboot header, which is only reserved during compilation, and rewritten during signing.
.mcuboot_header :
{
QUAD ( 0x0 )
QUAD ( 0x0 )
QUAD ( 0x0 )
QUAD ( 0x0 )
}> mcuboot_hdr
Following the mcuboot header is a 32-byte metadata, which provides information about the usage of esp32c3’s internal sram and the entry address, which is a special process for 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
Only after 64 bytes does the actual program image begin.