fix(mesh): restore esptool's esp32s3 stub instead of routing around it
--erase-all hit the exact same "ROM does not support function erase_flash"
error as a standalone erase_flash — confirmed live: esptool's --erase-all
is implemented as the same full-chip-erase command, not a per-sector loop,
so it needs the stub just as much. The real fix isn't finding another way
around the ROM bootloader's limitations — it's restoring the stub Debian's
package is missing.
Fetched the exact missing file (stub_flasher_32s3.json) from the matching
upstream esptool release tag and installed it directly on archy-x250-dev;
verified live with a read-only `flash_id` that stub mode now loads
("Uploading stub... Running stub..."). Removed --no-stub from flash.rs now
that normal stub-loader mode works correctly (faster and fully-featured vs.
the ROM-only fallback). scripts/self-update.sh now fetches and installs
this same file automatically whenever it's missing, so this isn't a
one-off manual fix tied to a single node.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -686,17 +686,13 @@ const ESPTOOL_CHIP: &str = "esp32s3";
|
||||
/// first hiccup, retry once at a conservative baud before giving up.
|
||||
const ESPTOOL_FALLBACK_BAUD: &str = "115200";
|
||||
|
||||
/// A standalone `erase_flash` (full-chip erase via a single ROM opcode) does
|
||||
/// NOT work with --no-stub — confirmed live 2026-07-23 against a real
|
||||
/// Heltec V4: "A fatal error occurred: ESP32-S3 ROM does not support
|
||||
/// function erase_flash." The ESP32-S3 ROM bootloader only implements
|
||||
/// per-sector erase (used internally as write_flash goes), not a bulk
|
||||
/// full-chip erase opcode — that's a stub-firmware-only feature, and
|
||||
/// Debian's esptool package has no stub (see esptool_global_args' doc
|
||||
/// comment). `write_flash --erase-all` gets the same "erase everything
|
||||
/// first" outcome our "always erase before write" default requires, but via
|
||||
/// the per-sector mechanism the ROM bootloader actually supports, as one
|
||||
/// esptool invocation instead of two.
|
||||
/// `write_flash --erase-all` erases the whole chip before writing, in one
|
||||
/// esptool invocation. This needs the esp32s3 stub flasher loaded (see
|
||||
/// esptool_global_args' doc comment) — without it, --erase-all hits the
|
||||
/// exact same ROM limitation a standalone `erase_flash` does ("ESP32-S3 ROM
|
||||
/// does not support function erase_flash", confirmed live 2026-07-23), since
|
||||
/// esptool's --erase-all is implemented as the same full-chip-erase command,
|
||||
/// not a per-sector loop.
|
||||
async fn esptool_erase_and_write(path: &str, image: &Path, job: &Arc<FlashJob>) -> Result<()> {
|
||||
job.set_stage(FlashStage::Writing).await;
|
||||
let image_str = image.to_string_lossy().to_string();
|
||||
@@ -711,28 +707,33 @@ async fn esptool_erase_and_write(path: &str, image: &Path, job: &Arc<FlashJob>)
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// esptool's global flags (--chip/--port/--baud/--no-stub) MUST precede the
|
||||
/// subcommand token (erase_flash/write_flash/...) — confirmed live
|
||||
/// 2026-07-23: appending `--baud 115200` after the subcommand on the retry
|
||||
/// path produced "esptool: error: unrecognized arguments: --baud 115200"
|
||||
/// every time, so the fallback-baud retry never actually got a chance to
|
||||
/// run. Building global args separately from subcommand args keeps this
|
||||
/// correct by construction instead of relying on call-site ordering.
|
||||
/// esptool's global flags (--chip/--port/--baud) MUST precede the subcommand
|
||||
/// token (erase_flash/write_flash/...) — confirmed live 2026-07-23:
|
||||
/// appending `--baud 115200` after the subcommand on the retry path
|
||||
/// produced "esptool: error: unrecognized arguments: --baud 115200" every
|
||||
/// time, so the fallback-baud retry never actually got a chance to run.
|
||||
/// Building global args separately from subcommand args keeps this correct
|
||||
/// by construction instead of relying on call-site ordering.
|
||||
///
|
||||
/// --no-stub is not optional here: Debian's `esptool` package
|
||||
/// (4.7.0+dfsg-0.1) ships without the precompiled "stub flasher" blobs
|
||||
/// (stripped for DFSG compliance), so the default stub-loader flash mode
|
||||
/// fails immediately with `FileNotFoundError: ... stub_flasher_32s3.json`
|
||||
/// — also confirmed live. --no-stub talks directly to the ROM bootloader
|
||||
/// instead, which doesn't need those files (slower, but actually works on
|
||||
/// this packaging).
|
||||
/// Normal stub-loader mode (no --no-stub) needs the esp32s3 stub flasher
|
||||
/// blob at /usr/lib/python3/dist-packages/esptool/targets/stub_flasher/
|
||||
/// stub_flasher_32s3.json — Debian's `esptool` package (4.7.0+dfsg-0.1)
|
||||
/// ships without it (stripped for DFSG compliance: the prebuilt blob has no
|
||||
/// buildable-from-source path Debian could verify), so scripts/self-update.sh
|
||||
/// fetches the exact same file from the matching upstream esptool release
|
||||
/// tag and installs it alongside the apt package (see the esptool install
|
||||
/// step there). --no-stub (talk directly to the ROM bootloader, skip the
|
||||
/// stub) was tried first and works for connecting, but the ROM bootloader
|
||||
/// doesn't implement a full-chip-erase opcode at all — only the stub does —
|
||||
/// so --no-stub broke our "always erase before write" default outright
|
||||
/// rather than just being slower. Restoring the real stub file is the
|
||||
/// correct fix, not routing around its absence.
|
||||
fn esptool_global_args<'a>(path: &'a str, baud: Option<&'a str>) -> Vec<&'a str> {
|
||||
let mut args = vec!["--chip", ESPTOOL_CHIP, "--port", path];
|
||||
if let Some(b) = baud {
|
||||
args.push("--baud");
|
||||
args.push(b);
|
||||
}
|
||||
args.push("--no-stub");
|
||||
args
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user