0022. Field extraction: read external flash with an SRAM loader, not a recovery firmware
Date: 2026-09-27
Chosen when field recovery was first designed, and built as
embedded/loaders (first loader AT25XE_PresTagv3, 2026-09-30; see
0016). Moved from
Field Data Extraction,
which keeps the parts still proposed.
Context
A tag comes back from a field site months after it was flashed. The recorded data must be extracted whatever state the tag is in, and enough recorded alongside it to understand what went wrong. Where the bulk of recorded data is on external flash, something has to drive that flash over SWD.
Where the bulk of recorded data is on external flash, the extraction path can
reuse STM32CubeProgrammer's external loader mechanism: a small binary (.stldr)
that the programmer downloads into the tag's SRAM and calls, exporting a
StorageInfo descriptor plus Init, Read, Write, SectorErase and
MassErase. Reads are function calls, so the address space is fictional and the
mechanism is not restricted to memory-mapped QSPI/OSPI -- it works for plain SPI
NOR and NAND on an ordinary SPI peripheral, which is what the tags use.
Decision
Why this rather than a recovery firmware
The obvious alternative is to flash a dedicated dumper image that reads external
flash and sends it out over the monitor. That overwrites internal flash, and
internal flash is where the t_StateMarker marker log, the persistent
configuration and the NAND map live. It would destroy the evidence for goal 2 in
the course of serving goal 1, and it would do so silently.
An external loader runs entirely from SRAM and leaves internal flash untouched. For a tag returned from the field that is the deciding property.
Capture order is therefore fixed
The loader occupies SRAM, destroying whatever the application left there. So:
tag_capture_state.pyfirst -- SRAM, writable internal flash, RTC backup registers.- The external loader second.
Reversing these loses SRAM state and looks like it worked. This belongs in the recovery procedure as a rule, not as a note.
Read-only by default
STM32_Programmer_CLI will happily erase external memory through a loader. A
loader used for field recovery should implement Read and StorageInfo and
stub Write, SectorErase and MassErase to fail, so the forensic tool is
incapable of destroying the thing it was brought in to recover.
Implemented as two images from one source: <PART>_<Board>.stldr is read-only
and is built without the erase and program code at all, and
<PART>_<Board>-RW.stldr erases and programs, each verified by read-back, for
rescue and bench testing. External erase is a rescue operation, always paired
with an internal erase, and the order matters; see
Rescue erase.
Raw reads for NAND
For the GD5F SPI-NAND parts, prefer reading raw pages including the spare area and ECC bytes, and do bad-block skipping and ECC correction on the host. A loader that corrects and skips internally can discard information irrecoverably -- and when the failure under investigation is itself in the bad-block map or the ECC path, the loader would be hiding exactly the evidence that matters.
Revised by Offline Log Reconstruction: the GD5F's on-die ECC algorithm is not in the source, so the host cannot correct a raw page itself. A capture should read each used page raw and through on-die ECC, recording the ECC status. The firmware drops a page whose ECC fails, and only the on-die read shows which pages those are.
One loader per board; sharing is at the source level
There is no way to have fewer loaders than boards. A .stldr is fully linked
to run from RAM -- the programmer downloads the image into SRAM and calls its
entry points -- so the artifact is bound to one board's pin assignment, memory
part, clock setup, power-enable GPIO, and to a link address and size that fit
that target's SRAM map. Nothing about that is shareable between a STM32L432
board and a U375 one.
What is shared is source, though less of it than this document first expected.
The loader runtime (clock, delay, SPI, entry points) is shared across loaders in
embedded/loaders/common/. Between a loader and the firmware, the shared layer
is the part's command set -- opcodes, register bits and measured timing budgets
-- in a dependency-free <part>_commands.h beside the firmware driver
(at25xe_commands.h is the first). The drivers themselves are not shared; the
next section says why.
So the build is a matrix over boards, like the firmware itself, and the per-board work is a short configuration rather than a driver: pins, board bring-up, part selection, descriptor.
Alternatives considered
- A dedicated dumper firmware that reads external flash and sends it out over the monitor. Rejected: it overwrites internal flash, as above.
- ST's External Memory Manager to generate the loader. Rejected when the first loader was built; see 0016.
Consequences
The loaders are in embedded/loaders, with
their runtime rules in
Loader Runtime Design. The
host library that drives them in place of STM32CubeProgrammer, and enforces the
capture-first order, is
SWD Capture and Recovery Library.
tag-capture reads SPI NAND both raw and through on-die ECC, as the revision
above requires.