Skip to content

Use a QIO bootloader for --flash-mode qio and keep its header dual - #1082

Open
damischa1 wants to merge 1 commit into
esp-rs:mainfrom
damischa1:qio-bootloader
Open

damischa1 wants to merge 1 commit into
esp-rs:mainfrom
damischa1:qio-bootloader

Conversation

@damischa1

@damischa1 damischa1 commented Oct 2, 2026 •

Copy link
Copy Markdown

Fixes #657.

--flash-mode qio (or qout) writes the quad mode into the 2nd-stage bootloader's own header as well as the app's. The ROM loads the bootloader before anything has enabled quad mode on the flash chip. With a quad header it fails at ets_loader.c 78 and goes into a TG0WDT reset loop.

ESP-IDF and esptool always flash the bootloader as DIO/DOUT. A bootloader built with CONFIG_ESPTOOLPY_FLASHMODE_QIO then sets the QE bit and switches SPI0 to quad itself (esptool docs).

This PR does the same:

  • Bootloader header. It gets the dual counterpart of the requested mode (qio → dio, qout → dout), and the app header keeps the requested mode. This alone ends the boot loop for every chip, including with a user-supplied --bootloader.
  • Bundled QIO bootloader. For qio/qout, espflash uses a bundled bootloader built for QIO where one exists. For now that is only esp32s3-qio, built by cargo xtask build-bootloaders from the same release/v6.1 with CONFIG_ESPTOOLPY_FLASHMODE_QIO=y.
  • Other chips. Where no QIO bootloader exists, espflash warns that the flash will be read in dual mode and points to --bootloader.

A QIO-built bootloader cannot become the default. The QIO switch is compile-time, and such a bootloader enables quad mode even when its header says DIO, which would break boards whose flash or pins do not support it. Hence a separate binary per chip, selected only on request.

About the binary:

  • esp32s3-qio-bootloader.bin is built from ESP-IDF 14f663f003e (v6.1-beta1-497), the commit the existing bundled bootloaders come from. release/v6.1 has moved on since then.
  • From that commit, the plain esp32s3 entry rebuilds byte-identical to the committed esp32s3-bootloader.bin, so the QIO one comes from the same source and config.
  • On Windows I could not run cargo xtask build-bootloaders as is. The canonicalized \\?\ workspace path ends up in IDF_TOOLS_PATH and in the idf.py arguments, and cmd /C does not handle the escaped quotes. So I ran the same generated project and sdkconfig.defaults by hand. I can send a separate fix for the xtask on Windows if that is useful.

I started with the ESP32-S3 because that is the chip I can test. If you want QIO entries for the other chips as well, I can add them to the manifest. I would rather not ship ones I cannot try on hardware without you saying so.

Testing:

  • New unit tests in image_format::idf, using the S3 and C3 test ELFs:
    • the bootloader header is never quad;
    • with qio on the S3, the QIO bootloader is used, its header says DIO and its SHA-256 is valid, and the app header says QIO;
    • dio keeps the default bootloader;
    • qio on a chip without a QIO bootloader still gets a DIO bootloader header.
  • On hardware, an ESP32-S3 (LILYGO T-2CAN, 16 MB quad flash, octal PSRAM) with an esp-hal app:
    • espflash flash --flash-mode qio --flash-freq 80mhz now boots with SPI Mode: QIO;
    • after esp-hal's init, SPI0 CTRL has fread_qio set;
    • before this change, the same command gave the TG0WDT loop;
    • plain espflash flash still boots in DIO with the default bootloader.
  • The benefit: with QIO, a 32-byte I-cache miss under load went from ~2.4 µs to ~1.4 µs (ESP32-S3: flash-resident code on the second core stalls up to ~140 µs while BLE streams on the first (blocking SPI transfer up to ~260 µs) esp-hal#6422).

This change was developed with the help of an AI assistant (Claude). I reviewed and tested it on hardware.

🤖 Generated with Claude Code

The ROM loads the 2nd stage bootloader before anything has enabled quad
mode on the flash chip, so a bootloader whose header says QIO or QOUT
ends in a watchdog reset loop (esp-rs#657). ESP-IDF and esptool always mark the
bootloader DIO/DOUT; a bootloader built for QIO then switches the flash
to quad mode itself, and only the app header carries the quad mode.

Do the same: the bootloader header gets the dual counterpart of the
requested mode, and for QIO/QOUT the bundled bootloader is one built for
QIO where there is one (ESP32-S3 for now). Without one, warn that the
flash will be read in dual mode.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

QIO flash-mode does not work (at least with esp-s3-wroom-1-N16R8)

1 participant