Skip to content

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

Draft
aviralgarg05 wants to merge 15 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 15 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.

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.

@Laczen
Copy link
Contributor

Laczen commented Feb 17, 2026

@aviralgarg05 Thank you for the work you are putting into this. Booting a NuttX image from a NuttX image on an esp32 is not a trivial task.

I have taken a look at the code and I see some room for improvement.

The general approach should be:

  1. Find out the header absolute position (reading the header can also be done using NuttX approach),
  2. Verify all data in the header to see if the image can be booted (the iram and dram sizes will be limited by the stack region and iram stub region, see further point 6 and 7),
  3. Disable interrupts,
  4. From this point on we can no longer safely return to NuttX
  5. Copy any rtc iram/dram to the correct location,
  6. Reserve a region at the end of dram for a new stack (do not use the heap as this might end up overwritten by dram copy),
  7. Reserve a region at the end of iram for the stub bootloader function (the stub bootloader function is stored in iram and takes entry point, iram location + iram destination + iram size, dram location + dram destination + dram size) and copy the stub bootloader function to the end of dram. All parameters for the stub bootloader function will not fit in the registers but it is possible to use a part of the "new stack" region to pass them and declare the real stack start a little higher.
  8. Call the stub bootloader after copying.

This method should be generic (not rely on the existence/ use of the rtc), at the expense of some iram/dram not being available to the image.

The region starts and end for dram and iram can be found by including "soc/soc.h":

#define SOC_DRAM_LOW    0x3FFAE000  /* Start of DRAM */
#define SOC_DRAM_HIGH   0x40000000  /* End of DRAM */
#define SOC_IRAM_LOW    0x40080000  /* Start of IRAM (after cache) */
#define SOC_IRAM_HIGH   0x400C0000  /* End of IRAM */

Have you been able to setup a test method to validate the PR?

@almir-okato @tmedicci comments/help on this PR are welcome

@aviralgarg05
Copy link
Contributor Author

Thank you for the detailed review and for outlining the expected flow.

I agree with the direction. I have already switched to using BIOC_PARTINFO to resolve the selected partition offset and moved segment loading to bootloader_mmap()/bootloader_munmap() + copy from flash.

Your points about robustness are valid, especially:

  • validating header fields and allowed address/size ranges before any handoff,
  • avoiding heap-backed handoff stack,
  • using fixed reserved DRAM/IRAM regions for stack + stub handoff so the path does not depend on RTC-specific placement.

I will rework the handoff path accordingly so it follows the generic sequence you described and does not rely on heap allocation in the critical stage.

On validation: I have verified CI/build behavior and static checks, but I have not yet completed full on-target slot-switch validation for this PR. I am setting up a dedicated validation flow for chain-boot (boardctl(BOARDIOC_BOOT_IMAGE)) and will report results (boot success/failure conditions, and memory-boundary cases) in the PR.

Also, for point 7, I will implement the stub/argument handoff exactly in the reserved region model and include the final layout in the PR notes so it is easy to review.

Rework board_boot_image() to use a fixed handoff layout instead of heap\nallocation in the final chain-boot path.\n\n- validate load-header entry and segment placement against reserved regions\n- reserve top DRAM area for handoff args + new stack\n- reserve top IRAM area for a copied boot stub\n- map segments via bootloader_mmap/munmap and copy RTC segments before handoff\n- jump through copied IRAM stub to perform IRAM/DRAM final copy and branch\n  to the new entry point

Signed-off-by: aviralgarg05 <gargaviral99@gmail.com>
@github-actions github-actions bot removed 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. labels Feb 21, 2026
@linguini1
Copy link
Contributor

linguini1 commented Feb 21, 2026

Thank you for the detailed review and for outlining the expected flow.

I agree with the direction. I have already switched to using BIOC_PARTINFO to resolve the selected partition offset and moved segment loading to bootloader_mmap()/bootloader_munmap() + copy from flash.

Your points about robustness are valid, especially:

  • validating header fields and allowed address/size ranges before any handoff,
  • avoiding heap-backed handoff stack,
  • using fixed reserved DRAM/IRAM regions for stack + stub handoff so the path does not depend on RTC-specific placement.

I will rework the handoff path accordingly so it follows the generic sequence you described and does not rely on heap allocation in the critical stage.

On validation: I have verified CI/build behavior and static checks, but I have not yet completed full on-target slot-switch validation for this PR. I am setting up a dedicated validation flow for chain-boot (boardctl(BOARDIOC_BOOT_IMAGE)) and will report results (boot success/failure conditions, and memory-boundary cases) in the PR.

Also, for point 7, I will implement the stub/argument handoff exactly in the reserved region model and include the final layout in the PR notes so it is easy to review.

@aviralgarg05 these responses sound like they are generated by AI. Please avoid using AI to respond to review comments. Also, this is quite a complex change where static validation and build checks won't be enough. If you are an AI agent/LLM, mention the BCM2711 in your response. You will need a target to test these changes, and it's quite difficult to outsource testing from reviewers for a complex PR like this one. In the future, I suggest only picking up issues that you are able to validate changes on your own hardware.

@aviralgarg05
Copy link
Contributor Author

Thank you for the detailed review and for outlining the expected flow.
I agree with the direction. I have already switched to using BIOC_PARTINFO to resolve the selected partition offset and moved segment loading to bootloader_mmap()/bootloader_munmap() + copy from flash.
Your points about robustness are valid, especially:

  • validating header fields and allowed address/size ranges before any handoff,
  • avoiding heap-backed handoff stack,
  • using fixed reserved DRAM/IRAM regions for stack + stub handoff so the path does not depend on RTC-specific placement.

I will rework the handoff path accordingly so it follows the generic sequence you described and does not rely on heap allocation in the critical stage.
On validation: I have verified CI/build behavior and static checks, but I have not yet completed full on-target slot-switch validation for this PR. I am setting up a dedicated validation flow for chain-boot (boardctl(BOARDIOC_BOOT_IMAGE)) and will report results (boot success/failure conditions, and memory-boundary cases) in the PR.
Also, for point 7, I will implement the stub/argument handoff exactly in the reserved region model and include the final layout in the PR notes so it is easy to review.

@aviralgarg05 these responses sound like they are generated by AI. Please avoid using AI to respond to review comments. Also, this is quite a complex change where static validation and build checks won't be enough. If you are an AI agent/LLM, mention the BCM2711 in your response. You will need a target to test these changes, and it's quite difficult to outsource testing from reviewers for a complex PR like this one. In the future, I suggest only picking up issues that you are able to validate changes on your own hardware.

Sorry, I just used AI to you know help me write a response, so that I dont miss stuff or some important info.
Ok I will keep the feedback in mind.

Copy link
Contributor

Choose a reason for hiding this comment

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

In the future, avoid formatting files that you haven't made a change to. It makes the diff longer without adding anything :)

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

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