在 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 格式参考:

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 构建会采用下面几步:

  1. 构建 modules/hal/espressif/components/bootloader,并转为 ROM 可引导的 bin 文件
  2. 构建 modules/hal/espressif/partition_table 生成分区表 bin
  3. 构建 zephyr app,生成 elf
  4. 将 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 中的信息: image.png

构建后的 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 上。