#6948·embassy

cyw43 (main): frequent firmware verify failures with a single-bit corruption on Pico 2 W, and re-running init() after a failure hangs

Author: lazywalkerCreated Sep 4, 2026Updated Sep 4, 2026

Environment

  • crate: cyw43 @ main 73b44d7e (2026-09-03, the xarxa-era rework with the fallible Bus/Chip split; unreleased), cyw43-pio 0.10.0 (DEFAULT_CLOCK_DIVIDER), embassy-rp 0.10, rustc 1.98
  • hardware: Raspberry Pi Pico 2 W (RP2350) on a custom carrier board, USB powered; firmware blobs from this repo unchanged (43439A0.bin sha256 a5007948…, clm e2422bde…, nvram 6b81a482…)
  • observed over ~15 cold boots while porting our firmware to the new API

Symptom 1: verify_download fails with a single-bit error, frequently

After runner.init() downloads the firmware, verify_download's first sample read (fw offset 0, 16 bytes) frequently mismatches by exactly one bit of one byte:

FW expected: [00, 00, 00, 00, 71, 14, 00, 00, 9d, 13, 00, 00, 9d, 13, 00, 00]
FW acutal:   [00, 00, 00, 00, 71, 14, 00, 00, 9d, 13, 00, 00, 9c, 13, 00, 00]

(byte 12: 0x9d -> 0x9c, i.e. LSB cleared; note bytes 8..12 and 12..16 are the same word 9d 13 00 00 — the first copy reads back fine, the second one doesn't.)

Per boot the behavior is bimodal:

  • on ~half the boots, re-reading the sample 1-2 times returns the correct bytes (a read-side transient), verify passes after the re-reads;
  • on the other boots, three consecutive re-reads agree on the wrong byte (we could not yet determine whether that is a worse transient or the byte is actually wrong in chip RAM — a "rewrite the region from the source image, then re-verify" experiment is still pending on our side).

The released cyw43 0.7 with the same blobs, same cyw43-pio, same clock divider and the same board uploads and verifies cleanly (weeks of daily use), so this tracks with the main-branch bus rework. The obvious pacing difference: 0.7 allocated a fresh zeroed ~4 KB stack buffer per bp_read/bp_write call; main reuses one shared aligned buffer and writes the cmd word into its first 4 bytes, which removes the per-chunk delay the old code accidentally provided.

Possibly related: #4791 (Pico 2 W SPI marginality at DEFAULT_CLOCK_DIVIDER -> init loop/assert, fixed by slowing the divider) and #6313 (an independent report of an LSB-stuck bit arriving via cmd_read — the same single-bit signature we see).

Symptom 2: re-running runner.init() after a failure never returns

With a retry loop around init (first failure observed, chip still alive):

rust
loop {
    match runner.init(firmware, nvram, None).await {
        Ok(()) => break,
        Err(_) => Timer::after(Duration::from_millis(500)).await, // then retry
    }
}

the second init() call hangs synchronously — it never completes and never errors; on our board the whole executor freezes behind it (proven by an armed 15 s watchdog resetting the board). Stock cyw43::new() unwrap()s instead, so today this only bites anyone adding a retry — but it also means init is not currently retry-safe if fallibility is meant to be handled rather than unwrapped.

Reproduction

Any main-branch Pico W/W2 WiFi example (e.g. wifi_blinky), flashed repeatedly (power-cycle between boots), with defmt logs on the boot path; the FW expected/acutal error (or the re-read heal) shows within ~4 s of boot. The hang needs the retry snippet above plus one failing boot.

Workaround

Pinning cyw43 = 0.7 (with embassy-net-driver-channel 0.4) restores clean uploads on the same hardware.