Great post François. I used to dread writing bootloaders until I made the effort to really understand the ARM early boot process, and now they’re pretty straight-forward.
If you’re using CMSIS you (technically) don’t need assembler to branch to application code. Here’s a snippet of some code showing how my bootloaders launch application code, modified to use your linker variables.
// Reset stack and vector table
uint32_t *stack_ptr = (uint32_t *)__approm_start__;
__disable_irq(); // Good idea when messing with MSP/VTOR
SCB->VTOR = __approm_start__;
// Call the reset handler (the construct below is 'pointer to a pointer to a function that takes no arguments and returns void')
void (**reset_handler)(void) = (void(**)(void))(__approm_start__ + 4);
(*reset_handler)(); // Call it as if it were a C function
Although your ASM version may be smaller, this is an alternative that boils down to pretty much the same instructions.
Some advice for others from working with bootloaders in the field: always have a last resort way to ‘break into’ the bootloader by, for example, holding a button down, or checking for a voltage on a pin. My bootloaders typically checksum the application and if it validates, immediately execute the code. If you don’t have a way to bypass this, and force the bootloader to remain active for new new code to be loaded, you may need to attach a debugger to recover from buggy firmware.
As well as a manual override to remain in the bootloader for loading new code, you also want the bootloader to automatically detect that it should not launch any existing code but instead wait for new code to be loaded. For a while I used a technique similar to your ‘Message passing to catch reboot loops’ code, putting magic numbers into RAM that is not cleared during a restart. These days most ARM vendors provide a ‘reset control’ register that reports the reason for the last reset. One of the MCUs I use, for example, reports whether the system was reset because of low voltage, watchdog, lockup, reset pin (debugger attached), or software (request via the NVIC). If the bootloader detects a software reset, it will not launch any existing app and instead wait for new code. This means no more messing around with application linker scripts to reserve memory for communicating with the bootloader. It could also perform some specific actions based on continued watchdog resets, as suggested in your article.
Lastly, my favourite user interface to a bootloader has to be an emulated USB mass-storage device. When the bootloader is waiting for new code, it brings up a USB stack and emulates a hard drive that you simply drag and drop the new application image onto, and the bootloader validates the image and programs it into flash. This is a serious undertaking! USB stacks are usually pretty large and MSD emulation is complex, so only recommended for fearless developers targeting chips with lots of flash. But when it works, it is pure magic!