From 0ca8f25b1b83c45b146ad08e5188da97fa6dd77f Mon Sep 17 00:00:00 2001 From: ssmithx Date: Thu, 23 Jul 2026 12:28:51 +0000 Subject: [PATCH] fix(mesh): standalone erase_flash unsupported in --no-stub ROM mode MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Real esptool output on a live Heltec V4 exposed the next layer of the --no-stub fix: "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 streams data), not a bulk full-chip-erase opcode — that's a stub-firmware-only feature, and Debian's esptool package ships without the stub (hence --no-stub in the first place). write_flash's own --erase-all flag gets the same "erase everything before writing" outcome our "always erase before write" default requires, but via the per-sector mechanism the ROM bootloader actually supports — one esptool invocation instead of a separate erase_flash + write_flash pair. Co-Authored-By: Claude Sonnet 5 --- core/archipelago/src/mesh/flash.rs | 26 ++++++++++++++++++-------- 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/core/archipelago/src/mesh/flash.rs b/core/archipelago/src/mesh/flash.rs index 9b6b66a9..fc2f2931 100644 --- a/core/archipelago/src/mesh/flash.rs +++ b/core/archipelago/src/mesh/flash.rs @@ -686,17 +686,27 @@ 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. async fn esptool_erase_and_write(path: &str, image: &Path, job: &Arc) -> Result<()> { - job.set_stage(FlashStage::Erasing).await; - esptool_with_retry(path, &["erase_flash"], job) - .await - .context("esptool erase_flash failed")?; - job.set_stage(FlashStage::Writing).await; let image_str = image.to_string_lossy().to_string(); - esptool_with_retry(path, &["write_flash", "0x0", &image_str], job) - .await - .context("esptool write_flash failed")?; + esptool_with_retry( + path, + &["write_flash", "--erase-all", "0x0", &image_str], + job, + ) + .await + .context("esptool write_flash failed")?; Ok(()) }