mirror of
https://github.com/PX4/PX4-Autopilot.git
synced 2026-08-02 04:58:15 +08:00
* 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>
73 lines
3.0 KiB
Bash
Executable File
73 lines
3.0 KiB
Bash
Executable File
#!/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."
|