Skip to main content
Graduate
December 18, 2023
Solved

Jump to application from bootloader not working

  • December 18, 2023
  • 4 replies
  • 15773 views

I have reviewed the many posts from others that have not been able to jump from a bootloader the application code and nothing that I tried worked.

My MCU has its 128kB of flash in two banks, one at 0x08000000 and the other at 0x08040000.  When the MCU is reset, it starts with the code in the first bank, so this is where my bootloader is located.

I have 6kB of flash reserved for storing calibration and configuration information immediately following the bootloader.  I have the application code in the second bank (0x08040000). 

The sole purpose of my bootloader is to allow an application software package to update the application firmware over a Modbus connection (via USB or RS485).  I am able to send the new firmware and successfully program the new application code.  I have verified this by using the STM32CubeProgrammer to compare the file to the updated flash contents.

Without the bootloader, the application code works fine from STM23CubeIDE when debugging.

When the bootloader resets, it verifies the application code CRC, then it will jump to the application code in the second bank.  I have the following code that is a compilation from a few posts, but it is not working.  Please Help...  

 

#define APP_ADDR	0x08040000		// my MCU app code base address
#define	MCU_IRQS	102u				// no. of NVIC IRQ inputs

struct app_vectable_ {
 uint32_t Initial_SP;
 void (*Reset_Handler)(void);
};

#define APPVTAB	((struct app_vectable_ *)APP_ADDR)
void JumpToApploader(void)
{
	/* Disable all interrupts */
	__disable_irq();

	/* Disable Systick timer */
	SysTick->CTRL = 0;

	/* Set the clock to the default state */
	HAL_RCC_DeInit();

	/* Clear Interrupt Enable Register & Interrupt Pending Register */
	for (uint8_t i = 0; i < (MCU_IRQS + 31u) / 32; i++)
	{
		NVIC->ICER[i]=0xFFFFFFFF;
		NVIC->ICPR[i]=0xFFFFFFFF;
	}

	/* Re-enable all interrupts */
	__enable_irq();

	// Set the MSP
	__set_MSP(APPVTAB->Initial_SP);

	// Jump to app firmware
	APPVTAB->Reset_Handler();
}


///////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////
void dbaseGoToApp(void)
{
	// start executing the application code if it appears to be valid
	if (dbaseIsAppOk())
	{
		JumpToApploader();
	}
}

 

 

    This topic has been closed for replies.
    Best answer by Piranha

    It seems that you saw the initial version of my post, which was a bit incorrect. A bit later I updated it, so just check my previous post as the updated version shows the correct code. Yes, the barriers should be before enabling the interrupts, because the VTOR and SP changes must be complete until then, so that the interrupts use the updated values.

    If the checksum/hash does not come with the firmware, you cannot trust whether the file was not corrupted during the transfer, by software on PC or any other way. Generally the best way of storing the metadata like the device ID, firmware version, anti-rollback counter, addresses and sizes of the FLASH blocks, checksum/hash, signature, shared secret for asymmetric cryptography etc. is to add a header in front of the firmware. The header is also flashed to he device and that is where the bootloader looks first. Another simpler option is to insert some data in the free locations of the vector table. On Cortex-M even the first 16 words have at least 5 reserved words, which gives 20 bytes that can definitely store the device ID, firmware version, CRC-32 and some more data. And in addition one can use all the other words in vector table, about which one can be sure that those will never be necessary for any future firmware version. Anyway, the device ID must be validated and the update refused at the bootloader, not at a PC application or anywhere else. By the way, with "device ID" I mean the "model number", not the "serial number". And, of course, the device has to have those values stored at production time at a different FLASH/OTP location, which is never modified.

    g_xBootRAM = 0xB00720AD;
    NVIC_SystemReset();

    Even, if wearing out FLASH is not an issue, there is still no point in implementing a relatively complex large code (and to a lesser extent that is also true for RTC registers), when there is a much simpler and better way like this.

    4 replies

    Graduate
    December 18, 2023

    The above code was mostly from a post from gbm:

    How to jump to system bootloader from application ... - STMicroelectronics Community

     

    I was really hoping it would work for me, but I haven't gotten working yet.

    Graduate II
    December 18, 2023

    The gbm's version is sane and he makes a valid points. Though, none of the code there re-configures the VTOR register and, if the application is not doing it either or sets it to an incorrect address, interrupts will jump to the addresses in bootloader's vector table.

    Graduate
    December 18, 2023

    I have written a few bootloaders for other processors, and it was generally an issue jumping to the application code.  However, there seems to be so many suggestions for the STM32 and not much success in the posts.  I am sure that there are many who have that working in their applications.

    My main concern is that the purpose of the bootloader is to allow for firmware updates.  Of course, I have to allow for the unexpected, such as aborting the update before it was completed.  If I had the flash space (which I don't), I would hold the new firmware in a reserved area that I could transfer to its final destination once it has been sent.

    I have to write the firmware to the flash as it is being downloaded.  Once the firmware has been completely received and programmed, I generate a CRC that I save in a 6kB reserved flash area.  So it takes some time to decide if the application code is ok and some resources.

    In addition to the CRC, I check to make sure that the vector table is not erased before jumping to the application code.  It's easy going from the application code to the bootloader, the application simply does a software reset after setting a flag in the 6kB reserved flash area.  

    I have a rotary switch that can be set so that at the next reset, the bootloader will erase the application vector table so that I can recover from a bricked condition (maybe they downloaded the wrong firmware).

    I just need help with developing the code to jump to the application firmware when the bootloader wants to.  I'll take a more detailed look at the assembly version.

     

     

    Graduate II
    December 18, 2023

    There is your salvation and generally both the most robust and the simplest way of doing it:

    https://community.st.com/t5/stm32-mcus-embedded-software/using-nvic-systemreset-in-bootloader-lt-gt-application-jumps/m-p/398390/highlight/true#M29676

    Just have to put a variable in a non-initialized RAM, add those assembler instructions and implement a decision making function. You seem to be capable of implementing at least a proof of concept code for that in few hours.

    Graduate
    December 18, 2023

    A good place for storing the boot status/commans is the backup RAM/registers in RTC module.

    A bootloader should set the VTOR register to APP_ADDRT just before invoking the application code.

    Graduate
    December 18, 2023

    Good idea!  Does that require that a backup battery be used?

    PiranhaAnswer
    Graduate II
    December 20, 2023

    It seems that you saw the initial version of my post, which was a bit incorrect. A bit later I updated it, so just check my previous post as the updated version shows the correct code. Yes, the barriers should be before enabling the interrupts, because the VTOR and SP changes must be complete until then, so that the interrupts use the updated values.

    If the checksum/hash does not come with the firmware, you cannot trust whether the file was not corrupted during the transfer, by software on PC or any other way. Generally the best way of storing the metadata like the device ID, firmware version, anti-rollback counter, addresses and sizes of the FLASH blocks, checksum/hash, signature, shared secret for asymmetric cryptography etc. is to add a header in front of the firmware. The header is also flashed to he device and that is where the bootloader looks first. Another simpler option is to insert some data in the free locations of the vector table. On Cortex-M even the first 16 words have at least 5 reserved words, which gives 20 bytes that can definitely store the device ID, firmware version, CRC-32 and some more data. And in addition one can use all the other words in vector table, about which one can be sure that those will never be necessary for any future firmware version. Anyway, the device ID must be validated and the update refused at the bootloader, not at a PC application or anywhere else. By the way, with "device ID" I mean the "model number", not the "serial number". And, of course, the device has to have those values stored at production time at a different FLASH/OTP location, which is never modified.

    g_xBootRAM = 0xB00720AD;
    NVIC_SystemReset();

    Even, if wearing out FLASH is not an issue, there is still no point in implementing a relatively complex large code (and to a lesser extent that is also true for RTC registers), when there is a much simpler and better way like this.

    Graduate
    December 20, 2023

    Yes, I noticed your update and that is why I asked.  I have updated my bootloader to properly position the barriers before enabling the interrupts. 

    When updating the firmware, the new firmware is being sent to the bootloader via Modbus which verifies that the data was sent and programmed correctly.  The process aborts if any problem was detected before the entire firmware update has been sent.

    When the last 2kB page is sent, the bootloader calculates the firmware CRC across the two non-continuous flash regions and saves all of the required information needed by the bootloader to verify that the application code is valid.

    If I use the filename of the update firmware to include the device ID, the PC update software could inform the user that the firmware file that they selected is not valid for the attached device(s) before writing any new firmware to the flash. I prefer this to the alternative.

    I like your suggestion to use an uninitialized variable to signal that the bootloader should run and not jump to the application code.  I don't have time to implement that at the moment.  But will likely do that for my next project using this MCU (coming in a couple of weeks).

    Thank you for your detailed responses.  They have been very helpful to me.