在 Zephyr 中针对 ESP32 系列的 SOC 都可以有两种引导流程:
- 无 mcuboot: ROM-> ESP Bootloader-> Zephyr.bin(App)
- 有 mcuboot: ROM-> MCUboot -> Zephyr.bin(App)
本文着重分析无 mcuboot 部分。
ROM 链接到标题
在 esp32c3 中 ROM 被称为"First stage bootloader", ROM 被固化在 esp32c3 上,芯片上电时从 ROM 开始运行。被 ROM 引导的 image,其 elf 文件需要用 esptool.py 的 elf2image 进行转化,该工具将 elf 转化为 header+segment+segment….+segment 的形式。ROM 引导时先读取 header, header 中包含每个 segment 的信息,按照 segmentd 的信息将 bin 文件内对应 segment 的内容拷贝到内存或是做 mmu 映射。header 中包含入口地址,拷贝 segment 完成后就跳到入口地址执行。
Image 格式和 header 格式参考:
- https://docs.espressif.com/projects/esptool/en/latest/esp32c3/advanced-topics/firmware-image-format.html
- https://docs.espressif.com/projects/esp-idf/zh_CN/latest/esp32c3/api-reference/system/app_image_format.html
ROM 除了引导 ESP Bootloader 外,也支持直接引导应用 image,参考 https://github.com/espressif/esp32c3-direct-boot-example/blob/main/README.md ROM 被固化在芯片内,没有源代码。
ESP Bootloader 链接到标题
在 esp32c3 中ESP Bootloader 被称为"Second stage bootloader", 被 ESP Bootloader 引导的 image 也要使用 esptool.py 的 elf2image 进行转化。ESP Bootloader 在启动后默认从 flash 的 0x8000 偏移地址处读取分区表,从分区表中选出要引导的程序拷贝到内存或是做 mmu 映射,这一过程和 ROM 一致。
被 ESP Bootloader 引导到 zephyr image 需要通过 elf2image 进行转化。
无 mcuboot 流程 链接到标题
构建 链接到标题
使用 west build -b esp32c3_zgp zephyr_sample/
进行无 mcuboot 的构建,当不使用 mcuboot 时,构建 esp32c3 的 zephyr image 会默认选择这种方式,Zephyr 构建会采用下面几步:
- 构建
modules/hal/espressif/components/bootloader
,并转为 ROM 可引导的 bin 文件 - 构建
modules/hal/espressif/partition_table
生成分区表 bin - 构建 zephyr app,生成 elf
- 将 zephyr.elf 并转化为 bootloader 可引导到 bin 文件
以上第 3 步为正常的 zephyr 构建,1,2,4 步由 zephyr/soc/riscv/esp32c3/CMakeLists.txt
进行描述处理 CONFIG_BOOTLOADER_ESP_IDF
控制的部分描述
if ( CONFIG_BOOTLOADER_ESP_IDF )
include ( ExternalProject )
## we use hello-world project, but I think any can be used.
set ( espidf_components_dir ${ESP_IDF_PATH}/components )
set ( espidf_prefix ${CMAKE_BINARY_DIR}/esp-idf )
set ( espidf_build_dir ${espidf_prefix}/build )
# 生成 bootloader
ExternalProject_Add (
EspIdfBootloader
PREFIX ${espidf_prefix}
SOURCE_DIR ${espidf_components_dir}/bootloader/subproject
BINARY_DIR ${espidf_build_dir}/bootloader
CONFIGURE_COMMAND
${CMAKE_COMMAND} -G${CMAKE_GENERATOR}
-S ${espidf_components_dir}/bootloader/subproject
-B ${espidf_build_dir}/bootloader -DSDKCONFIG=${espidf_build_dir}/sdkconfig
-DIDF_PATH=${ESP_IDF_PATH} -DIDF_TARGET=${CONFIG_SOC}
-DPYTHON_DEPS_CHECKED=1
-DCMAKE_C_COMPILER=${CMAKE_C_COMPILER}
-DCMAKE_CXX_COMPILER=${CMAKE_CXX_COMPILER}
-DCMAKE_ASM_COMPILER=${CMAKE_ASM_COMPILER}
-DCMAKE_SYSTEM_NAME=${CMAKE_SYSTEM_NAME}
-DPYTHON=${PYTHON_EXECUTABLE}
BUILD_COMMAND
${CMAKE_COMMAND} --build .
INSTALL_COMMAND "" # This particular build system has no install command
)
# 生成分区表
ExternalProject_Add (
EspPartitionTable
SOURCE_DIR ${espidf_components_dir}/partition_table
BINARY_DIR ${espidf_build_dir}
CONFIGURE_COMMAND ""
BUILD_COMMAND
${PYTHON_EXECUTABLE}${ESP_IDF_PATH}/components/partition_table/gen_esp32part.py -q
--offset 0x8000 --flash-size ${esptoolpy_flashsize}MB ${ESP_IDF_PATH}/components/partition_table/partitions_singleapp.csv ${espidf_build_dir}/partitions_singleapp.bin
INSTALL_COMMAND ""
)
set_property ( TARGET bintools PROPERTY disassembly_flag_inline_source )
# app 的依赖于 bootloader 和分区表
add_dependencies ( app EspIdfBootloader EspPartitionTable )
# 指定烧写时使用的 bootloader
board_finalize_runner_args ( esp32 "--esp-flash-bootloader=${espidf_build_dir}/bootloader/bootloader.bin" )
# 指定烧写时使用的分区表
board_finalize_runner_args ( esp32 "--esp-flash-partition_table=${espidf_build_dir}/partitions_singleapp.bin" )
# 指定烧写时分区表的地址
board_finalize_runner_args ( esp32 "--esp-partition-table-address=0x8000" )
endif ( )
if ( CONFIG_MCUBOOT OR CONFIG_BOOTLOADER_ESP_IDF )
# 将 zephyr.elf 用 esptool 转化成 ROM/Bootloader 可引导的 zephyr.bin,也就是前面说的加头和分段
# 当编译的是 mcuboot 时,mcuboot 可以被 ROM 引导
if ( CONFIG_BUILD_OUTPUT_BIN )
set_property ( GLOBAL APPEND PROPERTY extra_post_build_commands
COMMAND ${PYTHON_EXECUTABLE}${ESP_IDF_PATH}/components/esptool_py/esptool/esptool.py
ARGS --chip esp32c3 elf2image --flash_mode dio --flash_freq 40m --flash_size ${esptoolpy_flashsize}MB
-o ${CMAKE_BINARY_DIR}/zephyr/${CONFIG_KERNEL_BIN_NAME}.bin
${CMAKE_BINARY_DIR}/zephyr/${CONFIG_KERNEL_BIN_NAME}.elf )
endif ( )
# 如果编译的是 mcuboot,指定烧写的 bootloader 为 mcuboot
if ( CONFIG_MCUBOOT )
board_finalize_runner_args ( esp32 "--esp-flash-bootloader=${CMAKE_BINARY_DIR}/zephyr/${CONFIG_KERNEL_BIN_NAME}.bin" )
endif ( )
endif ( )
esp32c3 构建使用的 ld 文件为 zephyr/soc/riscv/esp32c3/linker.ld
构建为 app 的情况下使用的 ld 为 zephyr/soc/riscv/esp32c3/default.ld
, 其 memory region 为
MEMORY
{
mcuboot_hdr ( RX ) : org = 0x0, len = 0x20
metadata ( RX ) : org = 0x20, len = 0x20
ROM ( RX ) : org = 0x40, len = 4194304-0x40
iram0_0_seg ( RX ) : org = ( 0x4037C000 + 0x4000 ) , len = 0x403D0000 - ( 0x4037C000-0x3FC7C000 ) - ( 0x3FC7C000 + 0x4000 )
irom0_0_seg ( RX ) : org = 0x42000020, len = ( 4194304-0x20 )
drom0_0_seg ( R ) : org = 0x3C000040, len = 4194304-0x40
dram0_0_seg ( RW ) : org = ( 0x3FC7C000 + 0x4000 ) , len = 0x403D0000 - ( 0x4037C000-0x3FC7C000 ) - ( 0x3FC7C000 + 0x4000 )
rtc_iram_seg ( RWX ) : org = 0x50000000, len = 0x2000
IDT_LIST ( RW ) : org = 0x3ebfe010, len = 0x2000
}
对于非 mcuboot 引导的镜像 mcuboot_hdr, metadata, ROM, IDT_LIST 均无用,剩余对应 flash map:
- iram0_0_seg:内部指令 RAM
- irom0_0_seg:外部 flash,指令,通过 MMU 映射到 Flash
- drom0_0_seg: 外部 flash,数据,通过 MMU 映射到 Flash
- dram0_0_seg:内部数据 RAM
- rtc_iram_seg: RTC 使用内部指令/数据 RAM
对应到 soc spec 中的信息:
构建后的 zephyr.bin 可以用 esptool dump 出每个 segment 的信息
frank@DESKTOP-OCT9105:/mnt/g/project/v3.4.0$ /mnt/g/project/v3.4.0/modules/hal/espressif/components/esptool_py/esptool/esptool.py --chip esp32c3 image_info build/zephyr/zephyr.bin
esptool.py v4.5
Image version: 1
Entry point: 4038620a
8 segments
Segment 1: len 0x0001c load 0x00000020 file_offs 0x00000018 [PADDING]
Segment 2: len 0x00eac load 0x3fc9e898 file_offs 0x0000003c [DRAM,BYTE_ACCESSIBLE]
Segment 3: len 0x0069c load 0x3fc9f744 file_offs 0x00000ef0 [DRAM,BYTE_ACCESSIBLE]
Segment 4: len 0x0ea9c load 0x40380000 file_offs 0x00001594 [IRAM]
Segment 5: len 0x07a84 load 0x3c000040 file_offs 0x00010038 [DROM]
Segment 6: len 0x018a0 load 0x4038ea9c file_offs 0x00017ac4 [IRAM]
Segment 7: len 0x06ca4 load 0x00000000 file_offs 0x0001936c [PADDING]
Segment 8: len 0x17510 load 0x42010020 file_offs 0x00020018 [IROM]
Checksum: ac ( valid )
Validation Hash: 19c68846341fcc4a9bd19d9913b26b302a4aae622ef2b3bd50247e2c8f8b9a17 ( valid )
烧写 链接到标题
构建的结果均放到 build 中
- bootloader: build/esp-idf/build/bootloader/bootloader.bin
- 分区表: build/esp-idf/build/partitions_singleapp.bin
- app: build/zephyr/zephyr.bin
west flash 执行烧写时,依赖于 board_finalize_runner_args 加入的参数,会使用 esptool 将 bootloader,分区表和 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 0x0 /mnt/g/project/v3.4.0/build/esp-idf/build/bootloader/bootloader.bin 0x8000 /mnt/g/project/v3.4.0/build/esp-idf/build/partitions_singleapp.bin 0x10000 /mnt/g/project/v3.4.0/build/zephyr/zephyr.bin
引导流程 链接到标题
- ROM 从 0 地址读取 Bootloader,根据 header 对 Bootloader 进行加载,bootloader 运行在芯片内部的 iram。
- Bootloader 读取分区表,根据分区表中的信息,读取 app,按照 app 的 header 进行片内 sram 的 text 和 data 拷贝,并建立 flash mmu,跳转到 app 的入口地址进行执行,app 运行在 flash 上。