Home > Net >  setting stack pointer before jumping to app from bootloader
setting stack pointer before jumping to app from bootloader

Time:12-22

I am coding a bootloader for Nucleo-F429ZI. I have two different STM32 projects, one for the bootloader itself and an application to jump from the bootloader.

Linker script for bootloader

MEMORY
{
  CCMRAM    (xrw)    :  ORIGIN = 0x10000000,   LENGTH = 64K
  RAM    (xrw)    :     ORIGIN = 0x20000000,   LENGTH = 32K
  FLASH    (rx)    :    ORIGIN = 0x8000000,   LENGTH = 32K
}   

Linker script for app

_estack = ORIGIN(RAM)   LENGTH(RAM);
MEMORY
{
  CCMRAM    (xrw)    :  ORIGIN = 0x10000000,   LENGTH = 64K
  RAM    (xrw)    :     ORIGIN = 0x20000000,   LENGTH = 192K
  FLASH    (rx)    :    ORIGIN = 0x8008000,   LENGTH = 64K
}   

I did not forget to set the flash offset of the app.

system_stm32f4xx.c (in the app project)

#define VECT_TAB_BASE_ADDRESS   FLASH_BASE   // 0x8000000
#define VECT_TAB_OFFSET         0x00008000U 

The tutorial of STMicroelectronics about bootloaders has the following code to jump

main.c (in bootloader project)

#define FLASH_APP_ADDR 0x8008000
typedef void (*pFunction)(void);
uint32_t JumpAddress;
pFunction Jump_To_Application;
void go2APP(void)
{
  JumpAddress = *(uint32_t*)(FLASH_APP_ADDR   4);
  Jump_To_Application = (pFunction) JumpAddress;
  __set_MSP(*(uint32_t*)FLASH_APP_ADDR); // in cmsis_gcc.h 
  Jump_To_Application();
}

cmsis_gcc.h (in bootloader project)

__STATIC_FORCEINLINE void __set_MSP(uint32_t topOfMainStack)
{
  __ASM volatile ("MSR msp, %0" : : "r" (topOfMainStack) : );
}

As you can see, __set_MSP function sets the main stack pointer before jumping to FLASH_APP_ADDR 4. I found the memory location of the target place by debugging. FLASH_APP_ADDR 4 caused to run Reset_Handler function of app project. Lets see what will be executed.

startup_stm32f429zitx.c (in the app project)

    .section  .text.Reset_Handler
  .weak  Reset_Handler
  .type  Reset_Handler, %function
Reset_Handler: 
  ldr   sp, =_estack       /* set stack pointer */
 
/* Copy the data segment initializers from flash to SRAM */  
  ldr r0, =_sdata
  ldr r1, =_edata
  ldr r2, =_sidata
  movs r3, #0
  b LoopCopyDataInit

First thing of what Reset_Handler does is setting the stack pointer. _estack was defined in linker script.

If Reset_Handler is setting stack pointer, why did we call the __set_MSP function? I remove the function __set_MSP and bootloding process is still working. However I examined some other bootloader codes and found the exact same logic.

I tried what i have said and could not find an explanation.

CodePudding user response:

Cortex-M core the loads SP register with initial value from address FLASH_BASE 0 during boot sequence. Then jumps to the code entry point (Reset vector) from address FLASH_BASE 4. Any bootloader code mimics core behaviour. Note, that FLASH_BASE here is not necessarily actual flash base, but an abstract value, that depends on the used processor, and it's settings.

Provided Reset_Handler code loads the sp register with __estack (Main stack top) value, but it doesn't have to! Bootloader can not expect the main program to do it, but has perform the same boot sequence as the core after reset. This way the main code doesn't have to rely on knowing, who started it - core, bootloader, jtag, or something else.

I've seen startup code, that doesn't load SP, but disables interrupts with the first instruction. Or startup code, written in C, which could use stack with the first instruction.

The real question here could be: Why this startup code loads SP if it is already loaded? But perhaps it should be forwarded to the original code author.

CodePudding user response:

Let's see what's happening line by line.

JumpAddress = *(uint32_t*)(FLASH_APP_ADDR   4);

Okay, so we take FLASH_APP_ADDR, add 1 word to it, call it a pointer to a word, dereference it. So it's the content of 0x8008004 (which is the one word after start of the vector table - list of interrupt handler pointers). You can find it in the vector table in reference manual. Here is reference manual for your MCU. Page 375

Next,

Jump_To_Application = (pFunction) JumpAddress;

Okay, so we treat reset handler address as a void function(void).

Eventually, you get to the stack

__set_MSP(*(uint32_t*)FLASH_APP_ADDR);

This function, as we see from its source code, simply sets main stack pointer to its argument. The argument is take vector table address, treat it as a pointer to a word, dereference it. So it's the first word of that vector table. And the first word of the vector table is the main stack pointer auto-loaded after power on. By definition of the vector table. You reset the stack to cold boot value, same value as the first word of your Flash. Your bootloader has used some stack until this point, but it won't be needed anymore, and the bootloader function will never return and free that stack, so you just reset stack to its initial value for your program. It will reuse all stack used by the bootloader.

So right now you've reset the stack pointer and you assigned reset handler to the function you call. And then you, well, call it.

Your vector table and the program that the bootloader starts are two different entities in memory. If you don't need to remap the interrupt handlers at runtime, don't move the vector table. It will stay at the beginning of the flash and will lead to the default interrupt handlers. Just make sure the address you execute from contains executable code and you run it from the start (well, if you don't, you will hardfault).

  • Related