From 183b3e38d595f6651c1b0a8d6f808c2d1bbb3189 Mon Sep 17 00:00:00 2001 From: Jacob Dahl <37091262+dakejahl@users.noreply.github.com> Date: Fri, 12 Jun 2026 19:21:01 -0600 Subject: [PATCH] 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 --------- Signed-off-by: Jacob Dahl --- Tools/flash_fault_injection/.gitignore | 3 + Tools/flash_fault_injection/README.md | 51 ++++++ Tools/flash_fault_injection/brick_ecc.c | 87 ++++++++++ Tools/flash_fault_injection/brick_ecc.sh | 72 ++++++++ .../src/bootloader/stm/stm32_common/main.c | 16 +- .../src/bootloader/stm/stm32h7/CMakeLists.txt | 10 ++ .../src/bootloader/stm/stm32h7/ecc_scrub.c | 155 ++++++++++++++++++ .../src/bootloader/stm/stm32h7/ecc_scrub.h | 13 ++ 8 files changed, 406 insertions(+), 1 deletion(-) create mode 100644 Tools/flash_fault_injection/.gitignore create mode 100644 Tools/flash_fault_injection/README.md create mode 100644 Tools/flash_fault_injection/brick_ecc.c create mode 100755 Tools/flash_fault_injection/brick_ecc.sh create mode 100644 platforms/nuttx/src/bootloader/stm/stm32h7/ecc_scrub.c create mode 100644 platforms/nuttx/src/bootloader/stm/stm32h7/ecc_scrub.h diff --git a/Tools/flash_fault_injection/.gitignore b/Tools/flash_fault_injection/.gitignore new file mode 100644 index 00000000000..53719a3a52a --- /dev/null +++ b/Tools/flash_fault_injection/.gitignore @@ -0,0 +1,3 @@ +# Build artifacts from brick_ecc.sh +*.elf +*.bin diff --git a/Tools/flash_fault_injection/README.md b/Tools/flash_fault_injection/README.md new file mode 100644 index 00000000000..3954fd05188 --- /dev/null +++ b/Tools/flash_fault_injection/README.md @@ -0,0 +1,51 @@ +# 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///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. diff --git a/Tools/flash_fault_injection/brick_ecc.c b/Tools/flash_fault_injection/brick_ecc.c new file mode 100644 index 00000000000..7f2228a793f --- /dev/null +++ b/Tools/flash_fault_injection/brick_ecc.c @@ -0,0 +1,87 @@ +/* + * brick_ecc.c - plant an uncorrectable flash ECC error (STM32H7) + * + * Plants an uncorrectable ECC error in the parameter flash word at ADDR, + * reproducing the torn-write brick: the firmware's find_entry() scan reads the + * word, takes an uncorrectable (double-bit) ECC error -> precise bus fault -> + * hard fault on every boot, recoverable only by a mass erase or the + * bootloader's ECC scrub. See README.md. + * + * Recipe (verified on H743, BFAR landed here in the sweep): program the flash + * 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 it -> uncorrectable. (A + * complementary pair like 0x55/0xAA collapses to data 0 with valid ECC and + * does NOT fault - that is why the sweep was needed.) + * + * Runs from RAM with interrupts masked, no OS, driving the bank-2 flash + * controller directly. Defaults target the ARK_FPV parameter sector + * (STM32H743, bank 2, sector 7 @ 0x081E0000). + */ + +#define REG(a) (*(volatile unsigned int *)(a)) + +#define FLASH_KEYR2 0x52002104u +#define FLASH_CR2 0x5200210Cu +#define FLASH_SR2 0x52002110u +#define FLASH_CCR2 0x52002114u + +#define KEY1 0x45670123u +#define KEY2 0xCDEF89ABu + +#define CR_LOCK (1u << 0) +#define CR_PG (1u << 1) +#define CR_SER (1u << 2) +#define CR_START (1u << 7) +#define CR_SNB(n) (((n) & 0x7u) << 8) + +#define SR_BUSY ((1u << 0) | (1u << 2)) /* BSY | QW */ +#define CCR_CLEAR_ALL 0x0FEF0000u + +#define ADDR 0x081E0000u /* set to 0x081FD100 to match the original BFAR */ +#define SNB 7u +#define P1 0xFFFFFFFEu /* clear bit 0 */ +#define P2 0xFFFFFFFDu /* then clear bit 1 -> data 0xFFFFFFFC, bad ECC */ + +static void wait_idle(void) +{ + while (REG(FLASH_SR2) & SR_BUSY) { } +} + +/* program the 256-bit flash word at ADDR with value v (8 x 32-bit) */ +static void program_word(unsigned v) +{ + volatile unsigned int *w = (volatile unsigned int *)ADDR; + REG(FLASH_CR2) = CR_PG; + + for (unsigned i = 0; i < 8; i++) { w[i] = v; } + + wait_idle(); + REG(FLASH_CR2) = 0; +} + +void _start(void) +{ + __asm volatile ("cpsid i"); + + /* unlock bank 2 and clear stale error flags */ + REG(FLASH_KEYR2) = KEY1; + REG(FLASH_KEYR2) = KEY2; + REG(FLASH_CCR2) = CCR_CLEAR_ALL; + + /* erase sector 7 for a known starting state */ + REG(FLASH_CR2) = CR_SER | CR_SNB(SNB); + REG(FLASH_CR2) = CR_SER | CR_SNB(SNB) | CR_START; + wait_idle(); + REG(FLASH_CR2) = 0; + + /* double-program the word with two different single-bit clears */ + program_word(P1); + program_word(P2); + + /* lock and stop for the debugger */ + REG(FLASH_CR2) = CR_LOCK; + __asm volatile ("bkpt #0"); + + for (;;) { } +} diff --git a/Tools/flash_fault_injection/brick_ecc.sh b/Tools/flash_fault_injection/brick_ecc.sh new file mode 100755 index 00000000000..5e2f6211317 --- /dev/null +++ b/Tools/flash_fault_injection/brick_ecc.sh @@ -0,0 +1,72 @@ +#!/usr/bin/env bash +# +# brick_ecc.sh - plant an uncorrectable flash ECC error (STM32H7) +# +# Builds brick_ecc.c, loads it into RAM over ST-Link, and runs it to plant an +# uncorrectable ECC word in the parameter sector. Requires the board on SWD +# and st-tools + an ARM gdb. See README.md for usage and adaptation. +# +set -euo pipefail +HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + +GCC=arm-none-eabi-gcc +OBJCOPY=arm-none-eabi-objcopy +# st-link/st-util cannot write the Cortex-M7 TCMs (ITCM/DTCM) over SWD, so the +# stub lives in unused AXI SRAM (the firmware uses only the low ~54 KiB of the +# 512 KiB region). We reset the board afterwards, so clobbering RAM is fine. +LOAD_ADDR=0x24040000 # AXI SRAM, well past the firmware's RAM use +STACK=0x24048000 # 32 KiB above the stub + +# Pick an ARM-capable gdb that actually runs. The 2020-q4 arm-none-eabi-gdb is +# linked against libncurses.so.5 which modern distros do not ship, so prefer +# gdb-multiarch (it auto-detects the Cortex-M target from st-util). +GDB="" +for cand in gdb-multiarch arm-none-eabi-gdb gdb; do + if command -v "$cand" >/dev/null 2>&1 && "$cand" --version >/dev/null 2>&1; then + GDB="$cand"; break + fi +done +if [ -z "$GDB" ]; then + echo "No working ARM gdb found. Install one: sudo apt-get install gdb-multiarch" >&2 + exit 1 +fi +echo "using gdb: $GDB" + +"$GCC" -mcpu=cortex-m7 -mthumb -nostdlib -nostartfiles -ffreestanding -Os \ + -Wl,-Ttext=$LOAD_ADDR -Wl,-e_start -o "$HERE/brick_ecc.elf" "$HERE/brick_ecc.c" +"$OBJCOPY" -O binary "$HERE/brick_ecc.elf" "$HERE/brick_ecc.bin" + +# entry = address of _start in the loaded image (should equal LOAD_ADDR) +ENTRY=0x$(arm-none-eabi-nm "$HERE/brick_ecc.elf" | awk '/ _start$/{print $1}') +echo "stub _start = $ENTRY (load $LOAD_ADDR)" + +st-util >/tmp/brick_stutil.log 2>&1 & +STUTIL_PID=$! +trap 'kill "$STUTIL_PID" 2>/dev/null || true' EXIT +sleep 1 + +# st-util has no "monitor reset" - it halts on attach, so we run the stub from +# the halted state (it masks interrupts itself) and reset via SCB AIRCR after +# the stub's bkpt to reboot into the firmware. +"$GDB" -nx -q -batch \ + -ex "target extended-remote :4242" \ + -ex "monitor halt" \ + -ex "restore $HERE/brick_ecc.bin binary $LOAD_ADDR" \ + -ex "set \$control = 0" \ + -ex "set \$sp = $STACK" \ + -ex "set \$xpsr = 0x01000000" \ + -ex "set \$pc = $ENTRY" \ + -ex "continue" \ + -ex "printf \"\\n=== stub stopped at pc=0x%08x (want inside $LOAD_ADDR..) ===\\n\", \$pc" \ + -ex "printf \"reading planted word at 0x081E0000 (a bad-ECC word should FAIL):\\n\"" \ + -ex "x/1xw 0x081E0000" \ + -ex "printf \"resetting board into the firmware...\\n\"" \ + -ex "set *(unsigned int *)0xE000ED0C = 0x05FA0004" \ + -ex "quit" || true + +echo +echo "Done. If the read above said 'Cannot access memory at 0x081e0000', the" +echo "bad-ECC word is planted. The board was reset; if it didn't reboot on its" +echo "own, power-cycle it and watch the console:" +echo " non-hardened bootloader: hard faults every boot (BFAR=0x081e0000, find_entry)." +echo " with ECC scrub: boots clean, parameter sector erased, params re-seed." diff --git a/platforms/nuttx/src/bootloader/stm/stm32_common/main.c b/platforms/nuttx/src/bootloader/stm/stm32_common/main.c index 4713660703d..d309da6bbfe 100644 --- a/platforms/nuttx/src/bootloader/stm/stm32_common/main.c +++ b/platforms/nuttx/src/bootloader/stm/stm32_common/main.c @@ -19,6 +19,10 @@ #include "bl.h" #include "uart.h" +#if defined(CONFIG_ARCH_CHIP_STM32H7) +#include "ecc_scrub.h" +#endif + #define MK_GPIO_INPUT(def) (((def) & (GPIO_PORT_MASK | GPIO_PIN_MASK)) | (GPIO_INPUT)) @@ -455,11 +459,16 @@ inline void arch_setvtor(const uint32_t *address) putreg32((uint32_t)address, NVIC_VECTAB); } +/* the chip code may provide its own sector size (see stm32h7/ecc_scrub.h) */ +#if !defined(FLASH_SECTOR_SIZE) +#define FLASH_SECTOR_SIZE (128u * 1024u) +#endif + uint32_t flash_func_sector_size(unsigned sector) { if (sector <= BOARD_FLASH_SECTORS) { - return 128 * 1024; + return FLASH_SECTOR_SIZE; } return 0; @@ -664,6 +673,11 @@ bootloader_main(void) /* configure the clock for bootloader activity */ clock_init(); +#if defined(CONFIG_ARCH_CHIP_STM32H7) + /* scrub any uncorrectable flash ECC errors before we try to run the app */ + check_ecc_errors(); +#endif + /* * Check the force-bootloader register; if we find the signature there, don't * try booting. diff --git a/platforms/nuttx/src/bootloader/stm/stm32h7/CMakeLists.txt b/platforms/nuttx/src/bootloader/stm/stm32h7/CMakeLists.txt index 4e7642951b9..5290d95cd85 100644 --- a/platforms/nuttx/src/bootloader/stm/stm32h7/CMakeLists.txt +++ b/platforms/nuttx/src/bootloader/stm/stm32h7/CMakeLists.txt @@ -32,3 +32,13 @@ ############################################################################ add_subdirectory(../stm32_common arch_bootloader) + +target_sources(arch_bootloader + PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR}/ecc_scrub.c +) + +target_include_directories(arch_bootloader + PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR} +) diff --git a/platforms/nuttx/src/bootloader/stm/stm32h7/ecc_scrub.c b/platforms/nuttx/src/bootloader/stm/stm32h7/ecc_scrub.c new file mode 100644 index 00000000000..d795718bcd1 --- /dev/null +++ b/platforms/nuttx/src/bootloader/stm/stm32h7/ecc_scrub.c @@ -0,0 +1,155 @@ +/* + * STM32H7 flash ECC scrub. + * + * 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 it can print anything, and only a mass erase recovers it - + * reflashing does not, because neither the app nor the bootloader image + * touches the parameter sector. + * + * Before booting we scan the application and parameter flash with DMA: a DMA + * read of a corrupt row latches FLASH_SR DBECCERR instead of bus-faulting the + * CPU. Any sector holding an uncorrectable error is erased, so a corrupt + * parameter sector re-seeds to defaults on the next app boot, and corrupt app + * flash fails the image check and stays in the bootloader for reflash. + * + */ + +#include + +#include "hw_config.h" + +#include +#include +#include + +#include "ecc_scrub.h" + +/* Registers defined locally to avoid pulling in arch-internal headers. */ +#define ECC_RCC_AHB1ENR (*(volatile uint32_t *)0x580244d8u) +#define ECC_RCC_DMA1EN (1u << 0) + +#define ECC_DMA1 0x40020000u +#define ECC_DMA_LISR (*(volatile uint32_t *)(ECC_DMA1 + 0x00u)) +#define ECC_DMA_LIFCR (*(volatile uint32_t *)(ECC_DMA1 + 0x08u)) +#define ECC_DMA_S0CR (*(volatile uint32_t *)(ECC_DMA1 + 0x10u)) +#define ECC_DMA_S0NDTR (*(volatile uint32_t *)(ECC_DMA1 + 0x14u)) +#define ECC_DMA_S0PAR (*(volatile uint32_t *)(ECC_DMA1 + 0x18u)) +#define ECC_DMA_S0M0AR (*(volatile uint32_t *)(ECC_DMA1 + 0x1cu)) +#define ECC_DMA_S0FCR (*(volatile uint32_t *)(ECC_DMA1 + 0x24u)) +#define ECC_DMA_S0CR_EN (1u << 0) +#define ECC_DMA_S0_FLAGS (0x3du) /* stream-0 FE/DME/TE/HT/TC flags in LISR/LIFCR */ +#define ECC_DMA_S0_TCIF (1u << 5) +/* DIR=mem-to-mem, PINC, MINC, PSIZE=32, MSIZE=32 */ +#define ECC_DMA_S0CR_CFG ((2u << 6) | (1u << 9) | (1u << 10) | (2u << 11) | (2u << 13)) +#define ECC_DMA_S0FCR_CFG ((1u << 2) | (3u << 0)) /* direct mode off (FIFO required for M2M), threshold full */ + +#define ECC_FLASH 0x52002000u +#define ECC_FLASH_SR1 (*(volatile uint32_t *)(ECC_FLASH + 0x010u)) +#define ECC_FLASH_CCR1 (*(volatile uint32_t *)(ECC_FLASH + 0x014u)) +#define ECC_FLASH_SR2 (*(volatile uint32_t *)(ECC_FLASH + 0x110u)) +#define ECC_FLASH_CCR2 (*(volatile uint32_t *)(ECC_FLASH + 0x114u)) +#define ECC_FLASH_DBECCERR (1u << 26) +#define ECC_FLASH_CLR_ECC ((1u << 26) | (1u << 25)) /* clear DBECCERR + SNECCERR */ + +#define ECC_CHUNK_WORDS 256u /* 1 KiB per DMA transfer */ + +/* Optional: define to an output pinset (e.g. a spare FMU channel pad) to + * measure the scan time on a scope - driven high for the duration. */ +// #define ECC_SCRUB_TIMING_GPIO /* PI0 = FMU_CH1 */ (GPIO_OUTPUT|GPIO_PUSHPULL|GPIO_SPEED_50MHz|GPIO_OUTPUT_CLEAR|GPIO_PORTI|GPIO_PIN0) + +/* DMA scratch: .bss lands in AXI SRAM (0x24000000), which DMA1 can reach. */ +static uint32_t ecc_scan_buf[ECC_CHUNK_WORDS]; + +static void ecc_flags_clear(void) +{ + ECC_FLASH_CCR1 = ECC_FLASH_CLR_ECC; + ECC_FLASH_CCR2 = ECC_FLASH_CLR_ECC; +} + +/* bounded so a misbehaving DMA can never hang the bootloader (a 1 KiB + * transfer completes in a few thousand cycles; this is orders more) */ +#define ECC_DMA_TIMEOUT 2000000u + +static bool ecc_wait_stream_idle(void) +{ + uint32_t timeout = ECC_DMA_TIMEOUT; + + while ((ECC_DMA_S0CR & ECC_DMA_S0CR_EN) && --timeout) { } + + return timeout != 0; +} + +static bool flash_region_has_ecc_error(uintptr_t base, uint32_t size) +{ + for (uint32_t ofs = 0; ofs < size; ofs += ECC_CHUNK_WORDS * sizeof(uint32_t)) { + ecc_flags_clear(); + + ECC_DMA_S0CR = 0; + + if (!ecc_wait_stream_idle()) { + ECC_DMA_S0CR = 0; + return false; /* DMA stuck: skip the scan, fall back to a plain boot */ + } + + ECC_DMA_LIFCR = ECC_DMA_S0_FLAGS; + + ECC_DMA_S0PAR = base + ofs; /* source: flash */ + ECC_DMA_S0M0AR = (uint32_t)ecc_scan_buf; /* destination: SRAM */ + ECC_DMA_S0NDTR = ECC_CHUNK_WORDS; + ECC_DMA_S0FCR = ECC_DMA_S0FCR_CFG; + ECC_DMA_S0CR = ECC_DMA_S0CR_CFG; + ECC_DMA_S0CR |= ECC_DMA_S0CR_EN; + + /* stream self-disables on completion or on a read error */ + if (!ecc_wait_stream_idle()) { + ECC_DMA_S0CR = 0; + return false; + } + + if ((ECC_FLASH_SR1 & ECC_FLASH_DBECCERR) || (ECC_FLASH_SR2 & ECC_FLASH_DBECCERR)) { + return true; + } + + /* a clean verdict needs transfer-complete: the stream also stops on + * non-ECC bus errors, which must skip the scan rather than pass it */ + if (!(ECC_DMA_LISR & ECC_DMA_S0_TCIF)) { + return false; + } + } + + return false; +} + +void check_ecc_errors(void) +{ + ECC_RCC_AHB1ENR |= ECC_RCC_DMA1EN; + (void)ECC_RCC_AHB1ENR; + +#if defined(ECC_SCRUB_TIMING_GPIO) + px4_arch_configgpio(ECC_SCRUB_TIMING_GPIO); + px4_arch_gpiowrite(ECC_SCRUB_TIMING_GPIO, true); +#endif + + /* scan to the last physical sector: parameter sectors sit past + * BOARD_FLASH_SECTORS, and boards may have more than one */ + const unsigned last_sector = up_progmem_neraseblocks() - 1; + + for (unsigned sector = BOARD_FIRST_FLASH_SECTOR_TO_ERASE; sector <= last_sector; sector++) { + uintptr_t base = APP_LOAD_ADDRESS + (sector - BOARD_FIRST_FLASH_SECTOR_TO_ERASE) * FLASH_SECTOR_SIZE; + + if (flash_region_has_ecc_error(base, FLASH_SECTOR_SIZE)) { + ecc_flags_clear(); + up_progmem_eraseblock(sector); + } + } + + /* leave the stream idle and the ECC flags clear for the rest of boot */ + ECC_DMA_S0CR = 0; + ecc_flags_clear(); + +#if defined(ECC_SCRUB_TIMING_GPIO) + px4_arch_gpiowrite(ECC_SCRUB_TIMING_GPIO, false); +#endif +} diff --git a/platforms/nuttx/src/bootloader/stm/stm32h7/ecc_scrub.h b/platforms/nuttx/src/bootloader/stm/stm32h7/ecc_scrub.h new file mode 100644 index 00000000000..60be7c9a87b --- /dev/null +++ b/platforms/nuttx/src/bootloader/stm/stm32h7/ecc_scrub.h @@ -0,0 +1,13 @@ +/* + * STM32H7 flash ECC scrub for the bootloader. + * + */ + +#pragma once + +/* All supported H7 parts have uniform 128 KiB sectors. */ +#if !defined(FLASH_SECTOR_SIZE) +#define FLASH_SECTOR_SIZE (128u * 1024u) +#endif + +void check_ecc_errors(void);