From bfa0f41efe0255214e5b9e39f602651484f4cbeb Mon Sep 17 00:00:00 2001 From: Djuri Baars Date: Sat, 2 May 2026 12:06:18 +0200 Subject: [PATCH] fix: pass detected flash size to SPI_SET_PARAMS so writes past 4 MB succeed attachFlash hardcoded 4 MB in spiSetParams regardless of FlashSize / detected chip size. The stub uses this value as a write bound, so any write past 4 MB on a larger chip is rejected with RESPONSE_FAILED_SPI_OP (status 0xC4), even though detectFlashSize correctly identifies the chip as 8 MB / 16 MB. Reproducible on ESP32-S3 with an 8 MB flash by writing at offset 0x6F0000: the first compressed block fails with `command 0x11 failed: status=0xC4`. esptool and esptool-js both pass the actual size to SPI_SET_PARAMS for this exact reason ("the flash chip should be configured with the real size"). This change: - adds flashSizeStringToBytes helper for the existing 1MB..128MB names - splits attachFlash so the size step is a reusable configureSPIForSize - re-runs configureSPIForSize after detectFlashSize in FlashImages so the stub gets the real chip size before the first write Falls back to 4 MB when FlashSize is empty / "keep" / unrecognised, matching the previous behaviour for the unconfigured case. Fixes #41 --- pkg/espflasher/flasher.go | 61 +++++++++++++++++++++++++++++++++------ 1 file changed, 52 insertions(+), 9 deletions(-) diff --git a/pkg/espflasher/flasher.go b/pkg/espflasher/flasher.go index a633f0e..c84fb8f 100644 --- a/pkg/espflasher/flasher.go +++ b/pkg/espflasher/flasher.go @@ -541,7 +541,10 @@ func (f *Flasher) FlashImages(images []ImagePart, progress ProgressFunc) error { return fmt.Errorf("attach flash: %w", err) } - // Auto-detect flash size when not explicitly set. + // Auto-detect flash size when not explicitly set, then re-push the SPI + // params so the stub knows the real chip size. Without the re-push, the + // stub keeps its default 4 MB bound from the initial attachFlash() and + // rejects any write past 4 MB with ESP_FAILED_SPI_OP (status 0xC4). if f.opts.FlashSize == "" || f.opts.FlashSize == "keep" { if detected := f.detectFlashSize(); detected != "" { f.logf("Configuring flash size...") @@ -549,6 +552,11 @@ func (f *Flasher) FlashImages(images []ImagePart, progress ProgressFunc) error { f.opts.FlashSize = detected } } + if f.chip == nil || f.chip.ChipType != ChipESP8266 { + if err := f.configureSPIForSize(); err != nil { + return err + } + } // ESP8266 only supports DOUT (and DIO on some boards). Force DOUT // when no explicit flash mode was requested to ensure the image boots. @@ -903,22 +911,57 @@ func (f *Flasher) attachFlash() error { return err } - // Configure flash parameters for common 4MB flash - // These defaults work for most development boards + return f.configureSPIForSize() +} + +// configureSPIForSize tells the stub the flash chip's actual size derived +// from f.opts.FlashSize. Without this, the stub keeps the default 4 MB +// bound and rejects any write past 4 MB with ESP_FAILED_SPI_OP (status +// 0xC4). Falls back to 4 MB when the size string is empty / "keep" / +// unrecognised, matching the previous behaviour for the unconfigured case. +func (f *Flasher) configureSPIForSize() error { + sizeBytes := uint32(4 * 1024 * 1024) + if b, ok := flashSizeStringToBytes(f.opts.FlashSize); ok { + sizeBytes = b + } err := f.conn.spiSetParams( - 4*1024*1024, // 4MB total - 64*1024, // 64KB block - 4*1024, // 4KB sector - 256, // 256B page + sizeBytes, + 64*1024, // 64KB block + 4*1024, // 4KB sector + 256, // 256B page ) if err != nil { - // Don't fail - some ROM versions don't support this + // Don't fail - some ROM versions don't support this. f.logf("Warning: SPI params config failed (may be OK): %v", err) } - return nil } +// flashSizeStringToBytes converts a flash size string (e.g. "8MB") to the +// chip capacity in bytes. Returns (0, false) for empty / "keep" / unknown +// inputs so callers can apply a default. +func flashSizeStringToBytes(s string) (uint32, bool) { + switch s { + case "1MB": + return 1 * 1024 * 1024, true + case "2MB": + return 2 * 1024 * 1024, true + case "4MB": + return 4 * 1024 * 1024, true + case "8MB": + return 8 * 1024 * 1024, true + case "16MB": + return 16 * 1024 * 1024, true + case "32MB": + return 32 * 1024 * 1024, true + case "64MB": + return 64 * 1024 * 1024, true + case "128MB": + return 128 * 1024 * 1024, true + } + return 0, false +} + // changeBaud switches to a higher baud rate for faster data transfer. func (f *Flasher) changeBaud(newBaud int) error { f.logf("Switching to %d baud...", newBaud)