Skip to content

Commit 4a33bf0

Browse files
tridgeclaude
andcommitted
bl_update: disable interrupts for the flash_bootloader() sequence
The bl_update firmware runs from the app slot in bank 1 and erases + reprograms the bootloader (also in bank 1) over its own AHB path. On dual-bank STM32G4 parts (verified on STM32G491 / G4Axx with DBANK=1, which is the default factory option byte) reads from bank 1 stall during any bank-1 write, including the implicit instruction fetches the cortex-m core does when an interrupt arrives. If an interrupt fires while a flash page is being erased or programmed, the CPU attempts to fetch the handler from a half-erased bank and the fetch returns either undefined data or a stall that the EXCRETURN logic cannot recover from cleanly, taking the CPU into HardFault. The HardFault handler in the partially-written bootloader is now a `b .` infinite loop, so the chip is wedged with a corrupted bootloader and a soft reset can't recover it - SWD reflash is the only way out. Observed on ARK_G431_CAN (G491): the updater erased and programmed the first three 2 KB pages of bank 1 (0x08000000 - 0x08001800) correctly, then HardFaulted during the erase or program of page 3 (0x08001800), leaving the bootloader 1.5 KB written and 2.5 KB erased. `__disable_irq()` before `flash_bootloader()` removes the race - no interrupts can fire during the flash sequence. We don't bother re-enabling because `NVIC_SystemReset()` immediately follows; the reset restores PRIMASK. STM32L431 (vimdrones) didn't observe the failure mode on bench but the same race window exists; the fix is harmless there. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 38a129a commit 4a33bf0

1 file changed

Lines changed: 13 additions & 0 deletions

File tree

bl_update/main.c

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,19 @@ int main(void)
116116
// give 1.5s for debugger to attach
117117
delayMicroseconds(1500000);
118118

119+
/*
120+
disable interrupts for the whole flash sequence. The updater runs from
121+
the same flash bank that we are about to erase + reprogram; on dual-bank
122+
STM32G4 (e.g. STM32G491 / G4Axx in DBANK=1 mode) the bootloader live in
123+
bank 1 just like the updater code, and bank-1 reads stall during any
124+
bank-1 write. If an interrupt fires during a write, the CPU may try to
125+
fetch the handler from the half-erased bank and HardFault, leaving the
126+
chip stuck with a partially-written bootloader. Disabling interrupts
127+
avoids the race; we NVIC_SystemReset below so interrupt state is
128+
naturally restored on the next boot.
129+
*/
130+
__disable_irq();
131+
119132
// do the flash
120133
flash_bootloader();
121134
}

0 commit comments

Comments
 (0)