Interrupt

Zero to main(): How to Write a Bootloader from Scratch | Interrupt

This is the third post in our zero to main() series, where we boostrap a working firmware from zero code on a cortex-M series microcontroller.


This is a companion discussion topic for the original entry at https://interrupt.memfault.com/blog/how-to-write-a-bootloader-from-scratch
1 Like

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
__set_CONTROL(0);
__set_MSP(*stack_ptr);
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!

Thanks for the comment, @simonhaines. It adds a ton to the conversation.

You make a great point that the bootloader may want to do something different based on the reset type. Some implementations also provide some RTC scratch registers (e.g. STM32) which can be used to communicate between the app & the bootloader without the linker complexity.

On the topic of setting the MSP, Keil makes this even nicer using named register variables. I’ll reproduce their example below:

register unsigned int _control __asm("control");
register unsigned int _msp     __asm("msp");
register unsigned int _psp     __asm("psp");

void init(void)
{
  _msp = 0x30000000;        // set up Main Stack Pointer
  _control = _control | 3;  // switch to User Mode with Process Stack
  _psp = 0x40000000;        // set up Process Stack Pointer
}

source: http://www.keil.com/support/man/docs/armcc/armcc_chr1359125006491.htm

Unfortunately this is not supported by GCC.

On the USB mass storage device: the nRF52 development kit does just this, and you are right to say it is a magical experience :slight_smile:

Thank you francois for your very good job ! I am starting in programation and stm32 and i thank it will very help me to build my own bootloader. Yet, may your help me to build a sucure bootloader for stm32f103x8 from this repository linked (i have many error) ? Thank you : https://github.com/dmitrystu/sboot_stm32

Hi @sorokolo,

Thanks for getting in touch on Interrupt.

I am happy to answer any specific questions you have. Reading through your code is a bit more than I’d like to take on.

Hello ! Have you done something about stm32 secure bootloader or stm X-CUBE-SBSFU ? thank you