Files
PX4-Autopilot/Tools/flash_fault_injection/README.md
Jacob Dahl 183b3e38d5 fix(bootloader): scrub uncorrectable flash ECC errors on STM32H7 before boot (#27642)
* fix(bootloader): scrub uncorrectable flash ECC errors on STM32H7 before boot

A torn write to a flash row (power lost mid program/erase) can leave it
with inconsistent ECC. On H7 a CPU read of such a row raises an
uncorrectable double-bit ECC error -> bus fault, so the application hard
faults on every boot before printing anything, and only a mass erase
recovers it: reflashing does not, because neither the app nor the
bootloader image touches the parameter sector.

Scan the application and parameter flash with DMA right after clock_init
(a DMA read latches FLASH_SR DBECCERR instead of bus-faulting the CPU)
and erase any sector that holds an uncorrectable error. A corrupt
parameter sector then re-seeds to defaults on the next app boot; corrupt
app flash fails the image check and stays in the bootloader for reflash.
Mirrors ArduPilot's bootloader ECC scrub. H7-gated; the scan runs before
the interface DMA is brought up, so DMA1 stream 0 is uncontended.

* test(tools): add STM32H7 flash ECC fault injection tool

A bench helper that deterministically plants an uncorrectable (double-bit)
flash ECC error on an STM32H7, reproducing the torn-write parameter-store
brick. Used to validate the bootloader ECC scrub and the parameter re-seed
recovery on hardware: plant the fault, then confirm the board boots clean
instead of hard-faulting every boot.

brick_ecc.c is a freestanding stub that double-programs one flash word with
two different single-bit clears (the recipe that yields an inconsistent ECC);
brick_ecc.sh builds it, loads it into AXI SRAM over ST-Link, runs it, and
resets the board. README documents usage and how to retarget another board.

* fix(bootloader): require DMA transfer-complete for a clean ECC scan chunk

The stream self-disables on both transfer-complete and transfer-error.
A non-ECC bus error (unreachable scan buffer, rejected FIFO config)
previously read as a clean chunk, silently turning the scan into a
no-op. Require TCIF so an unread chunk takes the skip-the-scan
fallback instead of passing.

Signed-off-by: Jacob Dahl <dahl.jakejacob@gmail.com>

---------

Signed-off-by: Jacob Dahl <dahl.jakejacob@gmail.com>
2026-06-12 19:21:01 -06:00

3.7 KiB

Flash ECC fault injection (STM32H7)

A bench tool that deterministically plants an uncorrectable (double-bit) flash ECC error on an STM32H7 board, to test the bootloader's ECC scrub and the parameter-store recovery path.

Why this exists

On STM32H7, a torn write to a flash row (power lost mid program/erase) can leave a 256-bit flash word with inconsistent ECC. A CPU read of such a word raises an uncorrectable ECC error, which becomes a precise bus fault. On a board that stores parameters in flash, this hits find_entry() during parameter_flashfs_init() very early in boot, so the board hard-faults on every boot. Reflashing the app or bootloader does not help (neither image touches the parameter sector); only a mass erase recovers it.

The bootloader ECC scrub (platforms/nuttx/src/bootloader/stm/stm32h7/ecc_scrub.c) DMA-scans the application and parameter flash before booting the app and erases any sector with an uncorrectable error. This tool plants the exact fault that scrub is meant to recover from, so the scrub and the param re-seed can be validated on hardware.

How it works

A complete double-program of a flash word does not always yield an uncorrectable codeword. The recipe that does (verified on H743): program one 256-bit word twice with no erase between, clearing a different single bit each time — 0xFFFFFFFE then 0xFFFFFFFD. The stored data becomes 0xFFFFFFFC but the stored ECC is ecc(P1) & ecc(P2), inconsistent with the data → uncorrectable. (A complementary pair such as 0x55…/0xAA… collapses to data 0 with a valid ECC and does not fault.)

brick_ecc.c is a tiny freestanding stub: it masks interrupts, unlocks flash bank 2, erases the target sector, double-programs the target word, then halts on a breakpoint. It runs from AXI SRAM because st-link cannot write the Cortex-M7 TCMs over SWD; the board is reset afterwards so clobbering RAM is harmless.

Requirements

  • Target board connected over SWD (ST-Link).
  • stlink-tools (provides st-util).
  • gcc-arm-none-eabi (provides the compiler, objcopy, nm).
  • An ARM-capable gdb. gdb-multiarch is preferred — the 2020-q4 arm-none-eabi-gdb links against libncurses.so.5, which modern distros do not ship.
sudo apt-get install stlink-tools gcc-arm-none-eabi gdb-multiarch

Usage

The defaults target the ARK_FPV parameter sector (STM32H743, bank 2, sector 7 @ 0x081E0000).

./brick_ecc.sh

The script builds the stub, loads it into RAM, runs it, reads back the planted word (a bad-ECC word reports Cannot access memory — that is the expected success indicator), and resets the board. Then power-cycle and watch the NSH console:

  • Non-hardened bootloader: hard-faults on every boot (BFAR=0x081e0000, fault in find_entry).
  • Bootloader with the ECC scrub: boots clean — the scrub erased the parameter sector, and the app re-seeds parameters to defaults.

Adapting to another board

The defaults are ARK_FPV-specific. For a different H7 target, edit brick_ecc.c:

  • ADDR — a word inside the sector you want to corrupt (e.g. the parameter sector base for that board, from its sector_map in boards/<vendor>/<board>/src/init.c).
  • SNB — the sector number within the bank for that address.
  • The FLASH_*2 register addresses target bank 2; for a word in bank 1 use the bank-1 registers (FLASH_KEYR1 0x52002004, FLASH_CR1 0x5200200C, FLASH_SR1 0x52002010, FLASH_CCR1 0x52002014).

LOAD_ADDR/STACK in brick_ecc.sh assume free AXI SRAM at 0x24040000; adjust if your target's RAM map differs.

⚠️ This tool intentionally corrupts flash. Use it only on a development board you are willing to mass-erase. It is a bench/test helper and is not built or shipped as part of any firmware image.