From 77179266a99573dd83632e69d8587df5cb46d0e8 Mon Sep 17 00:00:00 2001 From: Mikey Sklar Date: Fri, 28 Aug 2026 19:47:20 -0700 Subject: [PATCH] ports/stm: retry ST system bootloader entry on STM32F4 The ROM clocks USB from the HSE but does not know which crystal is fitted, so it measures one against the HSI. On a miss it resets the part instead of starting DFU: AN2606 Figure 32/33, "HSE detected" -> no -> "Generate System reset". On a Feather STM32F405 Express and its 12 MHz crystal a single jump reached DFU 5 times in 12. Record the request in a backup register and take a real reset, then jump from the top of port_init() and retry when the ROM bounces us. That reaches DFU 12 times in 12. Also drops the HAL_RCC_DeInit()/HAL_DeInit()/NVIC teardown, which a system reset supersedes. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01TehTMf9ApHxxU5UNHosXKj --- ports/stm/supervisor/port.c | 136 ++++++++++++++++++++++++++---------- 1 file changed, 99 insertions(+), 37 deletions(-) diff --git a/ports/stm/supervisor/port.c b/ports/stm/supervisor/port.c index 1da862cd886..4c1fc77f51a 100644 --- a/ports/stm/supervisor/port.c +++ b/ports/stm/supervisor/port.c @@ -235,7 +235,96 @@ void *port_realloc(void *ptr, size_t size, bool dma_capable) { } #endif +// Pending request for the ST system bootloader, kept where the other ports keep +// theirs: _bootloader_dbl_tap on atmel-samd, NRF_POWER->GPREGRET on nordic, +// SNVS->LPGPR[3] on mimxrt10xx. BKP0R is already STM_ALARM_FLAG. The low half is +// the attempt count. +#define STM_BOOTLOADER_FLAG (RTC->BKP1R) +#define BOOTLOADER_MAGIC 0xf05a0000 +#define BOOTLOADER_MAGIC_MASK 0xffff0000 +#define BOOTLOADER_SECONDS_SHIFT 8 +#define BOOTLOADER_SECONDS_MASK 0xff + +// A retry only counts if it follows the previous attempt closely. The ROM fails +// and resets within a second, so anything slower is the part being reset by +// something else while the bootloader was running, and the request is stale. +// Without this, exiting DFU with a software reset lands straight back in DFU. +#define BOOTLOADER_RETRY_WINDOW_S 5 + +// The ROM clocks USB from the HSE but does not know which crystal is fitted, so +// it measures one against the HSI and resets the part when that misses: AN2606 +// Figure 32/33, "HSE detected" -> no -> "Generate System reset". On the 12 MHz +// Feather STM32F405 Express one attempt reached DFU 5 times in 12. +#define BOOTLOADER_MAX_ATTEMPTS 16 + +#if CPY_STM32F4 +// Seconds field of the RTC clock, read straight from the register because the +// HAL handle is not initialised this early. rtc_init() leaves shadow bypass on +// and that setting survives a reset, so the register reads live. +static uint32_t bootloader_rtc_seconds(void) { + uint32_t tr = RTC->TR; + return ((tr >> 4) & 0x7) * 10 + (tr & 0xF); +} + +// Naked so nothing touches the stack between setting MSP and the branch. +MP_NORETURN static __attribute__((naked)) void branch_to_bootloader(uint32_t bl_addr) { + __asm volatile ( + "ldr r2, [r0, #0]\n" + "msr msp, r2\n" + "ldr r2, [r0, #4]\n" + "bx r2\n" + ); +} + +// Runs before HAL_Init() starts SysTick, so the ROM gets the part close to reset +// state, and ahead of the __HAL_RCC_BACKUPRESET_FORCE() further down port_init() +// which clears STM_BOOTLOADER_FLAG. The ROM sets its own VTOR and runs from +// 0x1FFF0000, so no SYSCFG remap is needed here. +static void check_enter_bootloader(void) { + __HAL_RCC_PWR_CLK_ENABLE(); + HAL_PWR_EnableBkUpAccess(); + + uint32_t request = STM_BOOTLOADER_FLAG; + if ((request & BOOTLOADER_MAGIC_MASK) != BOOTLOADER_MAGIC) { + return; + } + + // The ROM signals its HSE-detect failure with a software reset, so SFTRSTF is + // what tells a retry apart from a cold boot, and a power cycle always leaves + // the bootloader. + if (!(RCC->CSR & RCC_CSR_SFTRSTF)) { + STM_BOOTLOADER_FLAG = 0; + return; + } + + uint32_t now = bootloader_rtc_seconds(); + uint32_t then = (request >> BOOTLOADER_SECONDS_SHIFT) & BOOTLOADER_SECONDS_MASK; + if ((now + 60 - then) % 60 > BOOTLOADER_RETRY_WINDOW_S) { + STM_BOOTLOADER_FLAG = 0; + return; + } + + uint32_t attempts = request & BOOTLOADER_SECONDS_MASK; + if (attempts >= BOOTLOADER_MAX_ATTEMPTS) { + STM_BOOTLOADER_FLAG = 0; + return; + } + STM_BOOTLOADER_FLAG = BOOTLOADER_MAGIC | + (now << BOOTLOADER_SECONDS_SHIFT) | (attempts + 1); + // Clearing the flags cancels the request once the ROM succeeds: leaving DFU + // is a jump, not a reset, so SFTRSTF stays clear and the branch above zeroes + // the flag on the way back into the application. + RCC->CSR |= RCC_CSR_RMVF; + + branch_to_bootloader(0x1FFF0000); +} +#endif + safe_mode_t port_init(void) { + #if CPY_STM32F4 + check_enter_bootloader(); + #endif + HAL_Init(); // Turns on SysTick __HAL_RCC_SYSCFG_CLK_ENABLE(); @@ -256,6 +345,8 @@ safe_mode_t port_init(void) { } #endif + // This clears STM_BOOTLOADER_FLAG too, so check_enter_bootloader() above has + // to run before it. __HAL_RCC_BACKUPRESET_FORCE(); __HAL_RCC_BACKUPRESET_RELEASE(); @@ -324,44 +415,15 @@ void reset_port(void) { } void reset_to_bootloader(void) { - -/* -From STM AN2606: -Before jumping to bootloader user must: -• Disable all peripheral clocks -• Disable used PLL -• Disable interrupts -• Clear pending interrupts -System memory boot mode can be exited by getting out from bootloader activation -condition and generating hardware reset or using Go command to execute user code -*/ - HAL_RCC_DeInit(); - HAL_DeInit(); - - // Disable all pending interrupts using NVIC - for (uint8_t i = 0; i < MP_ARRAY_SIZE(NVIC->ICER); ++i) { - NVIC->ICER[i] = 0xFFFFFFFF; - } - - // if it is necessary to ensure an interrupt will not be triggered after disabling it in the NVIC, - // add a DSB instruction and then an ISB instruction. (ARM Cortex™-M Programming Guide to - // Memory Barrier Instructions, 4.6 Disabling Interrupts using NVIC) - __DSB(); - __ISB(); - - // Clear all pending interrupts using NVIC - for (uint8_t i = 0; i < MP_ARRAY_SIZE(NVIC->ICPR); ++i) { - NVIC->ICPR[i] = 0xFFFFFFFF; - } - - // information about jump addresses has been taken from STM AN2606. - #if defined(STM32F4) - __set_MSP(*((uint32_t *)0x1FFF0000)); - ((void (*)(void)) * ((uint32_t *)0x1FFF0004))(); - #else - // DFU mode for STM32 variant note implemented. - NVIC_SystemReset(); + #if CPY_STM32F4 + // Record the request and reset, rather than jumping from a running + // application. check_enter_bootloader() jumps on the way back up. + __HAL_RCC_PWR_CLK_ENABLE(); + HAL_PWR_EnableBkUpAccess(); + STM_BOOTLOADER_FLAG = BOOTLOADER_MAGIC | + (bootloader_rtc_seconds() << BOOTLOADER_SECONDS_SHIFT); #endif + NVIC_SystemReset(); while (true) { asm ("nop;");