Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
136 changes: 99 additions & 37 deletions ports/stm/supervisor/port.c
Original file line number Diff line number Diff line change
Expand Up @@ -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();

Expand All @@ -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();

Expand Down Expand Up @@ -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;");
Expand Down
Loading