Skip to content

arch/xtensa/esp32: Implement board_boot_image for mcuboot/nxboot support#18373

Draft
aviralgarg05 wants to merge 14 commits intoapache:masterfrom
aviralgarg05:esp32-boot-image-impl
Draft

arch/xtensa/esp32: Implement board_boot_image for mcuboot/nxboot support#18373
aviralgarg05 wants to merge 14 commits intoapache:masterfrom
aviralgarg05:esp32-boot-image-impl

Conversation

@aviralgarg05
Copy link
Contributor

@aviralgarg05 aviralgarg05 commented Feb 9, 2026

Note: Please adhere to Contributing Guidelines.

Summary

This PR implements board_boot_image() for ESP32 so bootloader-managed images (for example mcuboot / nxboot) can be chain-booted through BOARDIOC_BOOT_IMAGE.

Main changes:

  1. Add ESP32 board-side board_boot_image() implementation in /boards/xtensa/esp32/common/src/esp32_boot_image.c.
  2. Parse the ESP32 app load header and explicitly load RAM-resident segments before handoff:
    • IRAM
    • DRAM
    • LP RTC IRAM
    • LP RTC DRAM
  3. Handoff from IRAM stub by disabling interrupts and jumping to image entry.
  4. Keep legacy/simple image formats unsupported in this path (-ENOTSUP) for ESP32.
  5. Keep partition-side API surface minimal (remove unused offset ioctl extension from this series).

Impact

  • Enables ESP32 BOARDIOC_BOOT_IMAGE handoff for location-fixed bootloader image formats used by mcuboot / nxboot.
  • Ensures RAM sections are loaded in chain-boot flow (normally done by ROM bootloader on cold boot).
  • ESP32-only impact.

Testing

Static checks

  • tools/checkpatch.sh -c -u -m -g upstream/master..HEAD : pass

Build coverage for boot configurations

Validated build paths with board_boot_image enabled:

  • BOOT_MINIBOOT: pass
  • BOOT_NXBOOT + NXBOOT_BOOTLOADER: pass
  • BOOT_MCUBOOT + MCUBOOT_BOOTLOADER: pass

QEMU runtime validation (MCUboot path)

Base defconfig and reproducible option toggles:

./tools/configure.sh esp32-devkitc:qemu_openeth
kconfig-tweak -e ESP32_QEMU_IMAGE
kconfig-tweak -e ESP32_MERGE_BINS
kconfig-tweak -e ESP32_IGNORE_CHIP_REVISION_CHECK
make olddefconfig
make -j8

QEMU run command:

~/.local/espressif-qemu-xtensa/qemu/bin/qemu-system-xtensa \
  -nographic \
  -machine esp32 \
  -drive file=nuttx.merged.bin,if=mtd,format=raw \
  -nic user,model=open_eth

Observed key boot output:

[esp32] [INF] *** Booting MCUboot build ... ***
[esp32] [INF] Loading image 0 - slot 0 from flash, area id: 1
...
NuttShell (NSH) NuttX-12.12.0
nsh>

Fixes #17641

@github-actions github-actions bot added Arch: xtensa Issues related to the Xtensa architecture Board: xtensa Size: XL The size of the change in this PR is very large. Consider breaking down the PR into smaller pieces. Size: M The size of the change in this PR is medium labels Feb 9, 2026
@aviralgarg05 aviralgarg05 force-pushed the esp32-boot-image-impl branch from 6005a93 to 7fbaea9 Compare February 9, 2026 13:32
This commit adds the board_boot_image() implementation for ESP32, enabling support for MCUboot and nxboot bootloaders. It includes parsing the image header and setting up the necessary MMU mappings.

Signed-off-by: Aviral Garg <aviral.garg.2023@gmail.com>
Fix coding style issues in esp32_boot_image.c reported by checkpatch.sh and nxstyle, including brace alignment and blank line usage.

Signed-off-by: Aviral Garg <aviral.garg.2023@gmail.com>
Fix coding style issues in partition.h reported by nxstyle, specifically correcting brace placement in enum definitions.

Signed-off-by: Aviral Garg <aviral.garg.2023@gmail.com>
@aviralgarg05 aviralgarg05 force-pushed the esp32-boot-image-impl branch from 7fbaea9 to 50c7053 Compare February 9, 2026 13:36
acassis
acassis previously approved these changes Feb 9, 2026
@acassis
Copy link
Contributor

acassis commented Feb 9, 2026

Hi @almir-okato could you please take a look?

Copy link
Contributor

@linguini1 linguini1 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Waiting until someone can fulfill the test request.

@almir-okato
Copy link
Contributor

Hi @almir-okato could you please take a look?

I'll take a look, thanks!

This commit fixes the style issues in the ESP32 boot image implementation:

- Reset esp32_partition.c to upstream master to fix extensive formatting issues
- Add minimal OTA_IMG_GET_OFFSET ioctl case with correct NuttX style
- esp32_boot_image.c and partition.h already fixed in previous commits

Signed-off-by: Aviral Garg <aviral.garg.2023@gmail.com>
Signed-off-by: aviralgarg05 <gargaviral99@gmail.com>
Read the MCUboot-Espressif load header at hdr_size and use it to populate entry point plus IROM/DROM mapping parameters.

This replaces the incorrect esp_image_header_t parsing path in board_boot_image() for this flow and aligns with the image layout used by MCUboot/nxboot on ESP32.

Signed-off-by: aviralgarg05 <gargaviral99@gmail.com>
@tmedicci
Copy link
Contributor

Hi @aviralgarg05 ,

Thanks for submitting it. Can you try running it on QEMU, please?

https://nuttx.apache.org/docs/latest/platforms/xtensa/esp32/index.html#using-qemu

@aviralgarg05
Copy link
Contributor Author

@tmedicci

Ran the requested ESP32 QEMU validation locally and confirmed boot to NSH.

QEMU command used

~/.local/espressif-qemu-xtensa/qemu/bin/qemu-system-xtensa \
  -nographic \
  -machine esp32 \
  -drive file=nuttx.merged.bin,if=mtd,format=raw \
  -nic user,model=open_eth

Build/config check

grep -n "CONFIG_ESP32_IGNORE_CHIP_REVISION_CHECK\|CONFIG_ARCH_CHIP_ESP32\|CONFIG_BOARD_ESP32_DEVKITC" .config

Output:

137:CONFIG_ARCH_CHIP_ESP32=y
192:CONFIG_ESP32_IGNORE_CHIP_REVISION_CHECK=y

Boot logs (key excerpts)

Adding SPI flash device
ets Jul 29 2019 12:21:46
...
[esp32] [INF] *** Booting MCUboot build v2.2.0-151-g8a07053d ***
[esp32] [INF] [boot] chip revision: v0.0
...
[esp32] [INF] Loading image 0 - slot 0 from flash, area id: 1
...
WARNING: NuttX supports ESP32 chip revision >= v3.0 (chip is v0.0).
Ignoring this error and continuing because `ESP32_IGNORE_CHIP_REVISION_CHECK` is set...
THIS MAY NOT WORK! DON'T USE THIS CHIP IN PRODUCTION!

NuttShell (NSH) NuttX-12.12.0
nsh>

Runtime sanity check in NSH

uname -a

Output:

NuttX  12.12.0 637c3300a5 Feb 10 2026 19:09:42 xtensa esp32-devkitc

Result

  • QEMU boot is successful and reaches NSH on esp32-devkitc:qemu_openeth.

@tmedicci
Copy link
Contributor

@tmedicci

Ran the requested ESP32 QEMU validation locally and confirmed boot to NSH.

QEMU command used

~/.local/espressif-qemu-xtensa/qemu/bin/qemu-system-xtensa \
  -nographic \
  -machine esp32 \
  -drive file=nuttx.merged.bin,if=mtd,format=raw \
  -nic user,model=open_eth

Build/config check

grep -n "CONFIG_ESP32_IGNORE_CHIP_REVISION_CHECK\|CONFIG_ARCH_CHIP_ESP32\|CONFIG_BOARD_ESP32_DEVKITC" .config

Output:

137:CONFIG_ARCH_CHIP_ESP32=y
192:CONFIG_ESP32_IGNORE_CHIP_REVISION_CHECK=y

Boot logs (key excerpts)

Adding SPI flash device
ets Jul 29 2019 12:21:46
...
[esp32] [INF] *** Booting MCUboot build v2.2.0-151-g8a07053d ***
[esp32] [INF] [boot] chip revision: v0.0
...
[esp32] [INF] Loading image 0 - slot 0 from flash, area id: 1
...
WARNING: NuttX supports ESP32 chip revision >= v3.0 (chip is v0.0).
Ignoring this error and continuing because `ESP32_IGNORE_CHIP_REVISION_CHECK` is set...
THIS MAY NOT WORK! DON'T USE THIS CHIP IN PRODUCTION!

NuttShell (NSH) NuttX-12.12.0
nsh>

Runtime sanity check in NSH

uname -a

Output:

NuttX  12.12.0 637c3300a5 Feb 10 2026 19:09:42 xtensa esp32-devkitc

Result

* QEMU boot is successful and reaches NSH on `esp32-devkitc:qemu_openeth`.

Thanks, @aviralgarg05.

It wasn't clear if you ran the tests you mentioned in the PR description using QEMU or not.

I suggest you update the Testing section and include those tests (either with mcuboot or with nxboot, preferably both) and the commands you used to run the firmware and the test.

You can use esp32s3-devkit:nsh as the base defconfig. The following commands append the necessary changes to build the firmware for QEMU:

kconfig-tweak -e ESP32_QEMU_IMAGE && kconfig-tweak -e ESP32_MERGE_BIN

I suggest you set other Kconfig options using kconfig-tweak as it is easier to replicate. Don't forget to run make olddefconfig before building the firmware.

@aviralgarg05
Copy link
Contributor Author

@aviralgarg05 have you tried building nxboot/mcuboot/miniboot from nuttx-apps using the provided board_boot_image? miniboot might be the simplest for the first tests.

Validated with nuttx-apps boot paths and board_boot_image enabled:

  • BOOT_MINIBOOT: build pass
  • BOOT_NXBOOT + NXBOOT_BOOTLOADER: build pass
  • BOOT_MCUBOOT + MCUBOOT_BOOTLOADER: build pass

I first hit local host-tool gaps (imgtool, esptool) and reran successfully after installing them in the build environment.

board_boot_image chain-boots directly to the selected slot and does not go
through ROM bootloader section loading. Load IRAM/DRAM/LP RTC sections
from the image load header before jumping, and keep ROM remap in the
IRAM stub for non-primary-slot boot.

Also include the required loader/cache headers so BOARDCTL_BOOT_IMAGE
builds cleanly when this file is enabled.

Signed-off-by: aviralgarg05 <gargaviral99@gmail.com>
@Laczen
Copy link
Contributor

Laczen commented Feb 11, 2026

@aviralgarg05 have you tried building nxboot/mcuboot/miniboot from nuttx-apps using the provided board_boot_image? miniboot might be the simplest for the first tests.

Validated with nuttx-apps boot paths and board_boot_image enabled:

  • BOOT_MINIBOOT: build pass
  • BOOT_NXBOOT + NXBOOT_BOOTLOADER: build pass
  • BOOT_MCUBOOT + MCUBOOT_BOOTLOADER: build pass

I first hit local host-tool gaps (imgtool, esptool) and reran successfully after installing them in the build environment.

That the build pass is good, however it is not a test of board_boot_image, to test this it is needed to build a mcuboot format image, write it to OTA.... Build a miniboot image in simpleboot format (this is bootable by the rom bootloader) and write the miniboot image to 0x1000 (for esp32). Then restart and the mcuboot format image should start.

Regarding image formats:

  • mcuboot and simpleboot images are location fixed images (meaning that they have to be placed at a specific location to be bootable). This is done to avoid having to map rom segments, or more correctly "delegate" the rom mapping to the image itself. This is achieved by "including" the image offset inside a part of the image that is loaded from flash to iram/dram. So in this case there is no need to map rom segments.
  • legacy idf images are not location fixed. They require the ram to be loaded, but also the rom mappings to be done by the bootloader.

For location-fixed MCUboot images, ROM mapping is handled by image\nstartup. board_boot_image only needs to load RAM sections and jump to\nentry.\n\nDrop the extra ROM remap/cache handling in the IRAM stub and remove\npartition-offset handling that is no longer used in this flow.

Signed-off-by: aviralgarg05 <gargaviral99@gmail.com>
Drop the temporary OTA_IMG_GET_OFFSET extension from the partition API.

After removing ROM remap from board_boot_image(), this ioctl is no longer
used by the boot path, so keeping it only increases PR scope without benefit.

Signed-off-by: aviralgarg05 <gargaviral99@gmail.com>
The previous Build workflow failed in Linux (arm64-01) while downloading
argtable3 with:

  gzip: stdin: not in gzip format

This appears to be a transient upstream/network artifact fetch issue, so this
commit retriggers CI without source changes.

Signed-off-by: aviralgarg05 <gargaviral99@gmail.com>
acassis
acassis previously approved these changes Feb 11, 2026
jerpelea
jerpelea previously approved these changes Feb 12, 2026
@jerpelea jerpelea changed the title xtensa/esp32: Implement board_boot_image for mcuboot/nxboot support arch/xtensa/esp32: Implement board_boot_image for mcuboot/nxboot support Feb 12, 2026
@Laczen
Copy link
Contributor

Laczen commented Feb 12, 2026

@jerpelea @acassis please do not merge this yet. I don't think it is going to work.

The problem is that the routine that is going to start the new image is in IRAM and might have been overwritten by the copy to IRAM part of the new image. The copying to IRAM (and DRAM) of the new image should be postponed to the last action in the routine that is going to start the new image. The start address of the new image should also be stored in a register to avoid it being overwritten when it is in DRAM.

It is required that this routine is at least evaluated using a bootloader or a NuttX image that is created to execute the board_boot_image(). @aviralgarg05 can you confirm that such a test has been executed?

@aviralgarg05
Copy link
Contributor Author

@jerpelea @acassis please do not merge this yet. I don't think it is going to work.

The problem is that the routine that is going to start the new image is in IRAM and might have been overwritten by the copy to IRAM part of the new image. The copying to IRAM (and DRAM) of the new image should be postponed to the last action in the routine that is going to start the new image. The start address of the new image should also be stored in a register to avoid it being overwritten when it is in DRAM.

It is required that this routine is at least evaluated using a bootloader or a NuttX image that is created to execute the board_boot_image(). @aviralgarg05 can you confirm that such a test has been executed?

Thanks for the feedback, your concern is valid, in the current flow, IRAM/DRAM loading happens before the final handoff path, so the loader stub/data can be overwritten.

I will rework this so the last-stage loader runs safely from IRAM, postpones RAM section copy to the final step, and keeps the entry address in a register-based handoff path.

I have not completed the dedicated runtime validation yet. I will run the boot test flow that exercises board_boot_image() and post the exact test steps/logs before requesting merge.

@acassis
Copy link
Contributor

acassis commented Feb 14, 2026

@Laczen could you please test before we merge it?

@acassis
Copy link
Contributor

acassis commented Feb 14, 2026

@aviralgarg05 I think it should be a nice idea to have some reference to this board_boot_image at https://nuttx.apache.org/docs/latest/platforms/xtensa/esp32/boards/esp32-devkitc/index.html#mcuboot-nsh or similar

@aviralgarg05 aviralgarg05 dismissed stale reviews from jerpelea and acassis via 02e120e February 14, 2026 12:24
@aviralgarg05 aviralgarg05 requested a review from yamt as a code owner February 14, 2026 12:24
@github-actions github-actions bot added Area: Documentation Improvements or additions to documentation Size: L The size of the change in this PR is large labels Feb 14, 2026
Preload RAM sections to temporary buffers and perform the final copy from
RTC fast memory before jumping to the new image entry point. Use a dedicated
handoff stack and overlap checks to avoid self-overwrite during chain boot.

Signed-off-by: aviralgarg05 <gargaviral99@gmail.com>
Document that the ESP32 DevKitC mcuboot_nsh flow invokes
board_boot_image() through BOARDIOC_BOOT_IMAGE and add a reference to
the MCUboot application documentation for generic boot flow details.

Signed-off-by: aviralgarg05 <gargaviral99@gmail.com>
Build workflow run 22017418819 failed in Linux (sim-02) while downloading the minmea archive:\n\n  End-of-central-directory signature not found\n\nThis retries CI without source changes because the failure is external and non-deterministic.

Signed-off-by: aviralgarg05 <gargaviral99@gmail.com>
@Laczen
Copy link
Contributor

Laczen commented Feb 14, 2026

@aviralgarg05 could you mark the PR as draft until it has been verified.

@aviralgarg05 aviralgarg05 marked this pull request as draft February 14, 2026 16:22
@Laczen
Copy link
Contributor

Laczen commented Feb 16, 2026

Hi @aviralgarg05 thanks for the rework. Although the proposed solution might work it might be limited by the available memory.

I think the simplest and best way to boot a different image is to use ioctl(fd, BIOC_PARTINFO, &partinfo) to retrieve the partition offset. Once the partition offset is known the rom based bootloader_mmap()/munmap() routines can be used in the "startup" routine to verify correct header info. Also in the "startup" routine the bootloader_mmap()/munmap() routines can be used together with memcpy() to load the rtc, dram, iram. The approach can be similar to the method used in the esp-idf port of mcuboot (https://github.com/mcu-tools/mcuboot/blob/main/boot/espressif/port/esp_loader.c).

Use BIOC_PARTINFO to resolve the selected slot flash offset and load\nsegments via bootloader_mmap()/bootloader_munmap() rather than\npreloading all segments into heap buffers.\n\nKeep a minimal RTC handoff stage for the final DRAM copy and entry\njump to avoid chain-boot self-overwrite while reducing memory usage.

Signed-off-by: aviralgarg05 <gargaviral99@gmail.com>
@aviralgarg05
Copy link
Contributor Author

Hi @aviralgarg05 thanks for the rework. Although the proposed solution might work it might be limited by the available memory.

I think the simplest and best way to boot a different image is to use ioctl(fd, BIOC_PARTINFO, &partinfo) to retrieve the partition offset. Once the partition offset is known the rom based bootloader_mmap()/munmap() routines can be used in the "startup" routine to verify correct header info. Also in the "startup" routine the bootloader_mmap()/munmap() routines can be used together with memcpy() to load the rtc, dram, iram. The approach can be similar to the method used in the esp-idf port of mcuboot (https://github.com/mcu-tools/mcuboot/blob/main/boot/espressif/port/esp_loader.c).

Thanks for the feedback, fixed it accordingly

@Laczen
Copy link
Contributor

Laczen commented Feb 16, 2026

@aviralgarg05 I will need some time to review this. I am unsure about the bootloader stack requirement, as the dram copy is the last thing to do it might not be required to have a stack while doing this copy.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Arch: xtensa Issues related to the Xtensa architecture Area: Documentation Improvements or additions to documentation Board: xtensa Size: L The size of the change in this PR is large Size: M The size of the change in this PR is medium Size: XL The size of the change in this PR is very large. Consider breaking down the PR into smaller pieces.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[FEATURE] Allow building nxboot, miniboot or NuttX mcuboot on esp32

8 participants