Skip to content

xtensa/esp32: Let a protected build boot from simple boot - #19789

Merged
xiaoxiang781216 merged 1 commit into
apache:masterfrom
casaroli:esp32-protected-simpleboot
Aug 11, 2026
Merged

xtensa/esp32: Let a protected build boot from simple boot#19789
xiaoxiang781216 merged 1 commit into
apache:masterfrom
casaroli:esp32-protected-simpleboot

Conversation

@casaroli

Copy link
Copy Markdown
Contributor

Summary

A protected build on the ESP32 could only use the legacy IDF image format. This pull request lets it boot from simple boot instead.

Kconfig already allowed the choice. ESP32_APP_FORMAT_LEGACY is only a default y if BUILD_PROTECTED, not a select, so a user could clear it and get CONFIG_ESPRESSIF_SIMPLE_BOOT=y. The result did not link, and after it linked it did not boot.

Simple boot has no second-stage bootloader. __start() maps the flash itself, so everything it reaches has to be in RAM already. kernel-space.ld pinned none of it, and it did not place esp32_start at all, so the entry point landed in the flash that the code was about to map:

entry 0x400d0ba4
Fatal exception (0): IllegalInstruction
epc1=0x400d0ba6

What changed

One file, boards/xtensa/esp32/common/scripts/kernel-space.ld, and every block is behind CONFIG_ESPRESSIF_SIMPLE_BOOT. A legacy build gets exactly the IRAM it had before.

It pins esp32_start and the bootloader, flash, ROM, clock and log objects that bootloader_init() and map_rom_segments() reach, with the matching read-only data.

It defines the six _image_irom_* and _image_drom_* symbols that __start() needs to link. Under simple boot map_rom_segments() reads the segment table out of the image in flash, so these only have to exist.

It adds #include <nuttx/config.h>. The file held no conditionals until now, so nothing revealed the omission. Without it the new blocks compiled away in silence and the link failed exactly as if the file had never been edited. This cost me a build cycle and is worth knowing about for anyone adding a conditional to this script.

The default does not change. A protected build still uses the legacy format unless the user clears CONFIG_ESP32_APP_FORMAT_LEGACY.

This is the ESP32 counterpart of #19764, which did the same for the ESP32-S3.

Testing

Board: ESP32-DevKitC V4, with an ESP32-D0WD-V3 revision 3.1.

Host: macOS 15 on Apple Silicon, xtensa-esp32-elf-gcc 12.2.0.

configuration image format result
esp32-devkitc:knsh simple boot boots to NSH, maps 7 segments
esp32-devkitc:knsh legacy unchanged, boots to NSH

With simple boot the kernel flashes at 0x1000 and the user image at 0x90000. There is no bootloader and no partition table to flash:

rst:0x1 (POWERON_RESET),boot:0x13 (SPI_FAST_FLASH_BOOT)
entry 0x40091c90
*** Booting NuttX ***
imap: lma 0x00010020 vma 0x400d0020 len 0x15f4c  (89932)
dmap: lma 0x00030020 vma 0x3f400020 len 0x2c64   (11364)
total segments stored 7
ABCESP32 chip revision is v3.1

NuttShell (NSH) NuttX-12.2.1

ostest reaches the same point in both image formats, so userspace behaves the same either way.

Note for reviewers: ostest does not finish on esp32-devkitc:knsh in either format. It stops in the barrier test with barrier_test: ERROR thread 6 create, status=12. That is heap size, not this change — the protected user heap arena is 95,908 B against 320,236 B in the flat build, and eight barrier threads want 8192 B of stack each. I confirmed it is pre-existing by building esp32-devkitc:knsh from master and getting the identical failure on the same board.

tools/checkpatch.sh -c -u -m -g reports no errors.

A protected build on the ESP32 could only use the legacy IDF image format.
Kconfig allowed simple boot to be selected with BUILD_PROTECTED, because the
legacy format is only a default and not a select, but the result did not link
and then did not boot.

Simple boot has no second-stage bootloader.  __start() maps the flash itself,
so everything it reaches must already be in RAM.  kernel-space.ld pinned none
of it, and it did not place esp32_start at all, so the entry point went to the
flash the code was about to map.  The chip loaded the RAM segments, jumped to
0x400d0ba4 and took an IllegalInstruction on the first instruction.

So this pins the bootloader, flash, ROM, clock and log objects that
bootloader_init() and map_rom_segments() reach, along with esp32_start itself,
and defines the six _image_* symbols that __start() needs.  All of it is
behind CONFIG_ESPRESSIF_SIMPLE_BOOT, so a legacy build gets the same IRAM it
had before.

kernel-space.ld also had no `#include <nuttx/config.h>'.  It held no
conditionals until now, so nothing showed the omission:  the new blocks
compiled away silently and the link failed as if the file had not been
changed.

The default is unchanged.  A protected build still selects the legacy format
unless the user clears CONFIG_ESP32_APP_FORMAT_LEGACY.

Verified on an ESP32-DevKitC V4, ESP32-D0WD-V3 revision 3.1, with
esp32-devkitc:knsh and the legacy format turned off.  The kernel flashes at
0x1000 and the user image at 0x90000, with no bootloader and no partition
table.  It maps seven segments, reaches NSH, and runs ostest to the same point
as the legacy build.

Assisted-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Marco Casaroli <marco.casaroli@gmail.com>
@github-actions github-actions Bot added Size: M The size of the change in this PR is medium Board: xtensa labels Aug 11, 2026
@github-actions

Copy link
Copy Markdown

MemBrowse Memory Report

No memory changes detected for:

@xiaoxiang781216
xiaoxiang781216 merged commit 5dafb68 into apache:master Aug 11, 2026
28 checks passed
@tmedicci

Copy link
Copy Markdown
Contributor

Hi @casaroli , thanks for the contribution. If you don't mind, I'd suggest removing the legacy bootloader from any defconfig that still uses it. We don't maintain it anymore as we already have simple (direct) boot and MCUboot (Espressif port) to handle OTA.

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

Labels

Board: xtensa Size: M The size of the change in this PR is medium

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants