cancel
Showing results for 
Search instead for 
Did you mean: 

Jump to application from bootloader not working

DavidNaviaux
Senior

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();
	}
}

 

 

1 ACCEPTED SOLUTION

Accepted Solutions
Piranha
Chief II

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.

View solution in original post

16 REPLIES 16
DavidNaviaux
Senior

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.

Piranha
Chief II

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.

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.

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.

 

 

The approach in the link I gave fits perfectly to your task! 😉

For most projects there is absolutely no need to waste half of the flash for reliable updates. The only "downside" is that after a failed update one has to retry and complete a successful update to get the device back to a normally working state. Though that is a completely acceptable requirement for most projects.

Also there is no need to use FLASH for command/status exchange between the bootloader and application. Using FLASH for that is unnecessarily complex and wears it out. The RAM is not modified by a hardware at system reset! Just reserve a space in RAM that is not modified by the system initialization or other code and choose a magic value, which has a low probability of happening accidentally at a power on reset.

The hash or checksum must be calculated at the creation of firmware file and must be put in the file itself. The bootloader then has to calculate it and compare to the original one at every boot. That way it will detect a corrupted application in FLASH even, when it is caused or appears later, when done by buggy application, malicious actions or barely successful update because of glitching power supply and similar issues. Such a test takes milliseconds even for a data over 1 MB, therefore generally is not an issue.

In addition it is recommended for the firmware file to also contain a device identifier and for the bootloader to refuse updating to a firmware, which is for a different device.

gbm
Lead III

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.

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

With a lot of trial and error, I have it working properly when I am debugging the bootloader.  However, it only seems to work while debugging.  I try to vector to the application code before most of the MCU initialization.  Here is what I now have:

 

/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */
#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)


/* USER CODE END 0 */

/**
 * @brief  The application entry point.
 * @retval int
 */
int main(void)
{
	/* USER CODE BEGIN 1 */
	uint8_t ucRotarySwitch;

	/* USER CODE END 1 */

	/* MCU Configuration--------------------------------------------------------*/

	/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
	HAL_Init();

	/* USER CODE BEGIN Init */

	/* USER CODE END Init */

	/* Configure the system clock */
	SystemClock_Config();

	/* USER CODE BEGIN SysInit */

	/////////////////////////////////////////////////////////////////////////////////////////////////
	// Get the rotary switch setting so we can check for special power on duties
	/////////////////////////////////////////////////////////////////////////////////////////////////

	// Temporarily enable the GPIOB and read the rotary switch position to check for bricked MCU
	// recovery request
	{
		// if the rotary switches are set to F7 we will erase the application code
		GPIO_InitTypeDef GPIO_InitStruct = {0};

		// enable GPIOB so that we can read the rotary switches
		__HAL_RCC_GPIOB_CLK_ENABLE();

		// put pull-up resistors on the rotary switch inputs
		GPIO_InitStruct.Pin =
				SW1_1_Pin|SW1_2_Pin|SW1_4_Pin|SW1_8_Pin|SW2_1_Pin|SW2_2_Pin|SW2_4_Pin|SW2_8_Pin;
		GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
		GPIO_InitStruct.Pull = GPIO_PULLUP;
		HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);

		// delay for the switch capacitance to charge with the pull-ups
		HAL_Delay(10);

		// read the switches
		ucRotarySwitch = GetSlaveIdHex();

		// we don't need the GPIOB clock any more at this point
		__HAL_RCC_GPIOB_CLK_DISABLE();
	}

	/////////////////////////////////////////////////////////////////////////////////////////////////
	// check if we are to run the application code
	/////////////////////////////////////////////////////////////////////////////////////////////////

	// initialize the CRC so we can validate the application firmware
	hcrc.Instance = CRC;
	hcrc.Init.DefaultPolynomialUse = DEFAULT_POLYNOMIAL_ENABLE;
	hcrc.Init.DefaultInitValueUse = DEFAULT_INIT_VALUE_ENABLE;
	hcrc.Init.InputDataInversionMode = CRC_INPUTDATA_INVERSION_NONE;
	hcrc.Init.OutputDataInversionMode = CRC_OUTPUTDATA_INVERSION_DISABLE;
	hcrc.InputDataFormat = CRC_INPUTDATA_FORMAT_BYTES;
	if (HAL_CRC_Init(&hcrc) == HAL_OK)
	{
		// read the config structure from flash
		// NOTE: there are two copies of the configuration struct in two separate 2kB flash pages
		//       so that we can recover from one of the copies being corrupted
		dbaseLoadAndFixConfig();

		// erase the application if the rotary switch is set to "F7" which will allow us to recover
		// from a bricked MCU because the application firmware is not working
		if (ucRotarySwitch == 0xf7)
		{
			// erase the vector table of the application
			dbaseEraseApp();

			// save the updated configuration
			dbaseSaveConfig();
		}


		// try to jump to the application code if requested
		if (config.bGoToApp)
		{
			uint32_t ulCrc;

			// make sure that the vector table is not erased
			uint32_t *pu32 = (uint32_t*)(APPLICATION_ADDRESS);

			if (pu32[0]!=0xffffffff && pu32[1]!=0xffffffff)
			{
				// is appears that we have a vector table for the application code

				// now make sure our config struct agrees
				if ( config.nBank2Pages && config.uBotBank2Page==0)
				{
					// calculate the CRC of the application code
					ulCrc = HAL_CRC_Calculate(&hcrc, (uint32_t*)(config.uBotBank2Page*2048+APP_ADDR), config.nBank2Pages*FLASH_PAGE_SIZE);

					// add the crc from the first bank if we have some code there too
					if (config.nBank1Pages)
					{
						// some code was placed in bank1 we need to include it in the CRC
						ulCrc = HAL_CRC_Accumulate(&hcrc, (uint32_t*)(config.uBotBank1Page*2048+BOOTLOADER_ADDRESS), config.nBank1Pages*FLASH_PAGE_SIZE);
					}

					// make sure that the CRC is valid
					if (ulCrc == config.ulAppCrc)
					{
						// at this point, we know:
						// 1. a boot to the application code is requested
						// 2. that the vector table for the application code is not erased
						// 3. that our config struct shows that we have application code
						// 4. that the CRC of the application code is valid

						// undo some of our initialization in preparation to the jump to the application code

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

						/* Disable all interrupts */
						__disable_irq();

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

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

						// set the vector table address to the application vector table
						SCB->VTOR = APP_ADDR;

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

						// Set the stack pointer
						__set_MSP(APPVTAB->Initial_SP);

						// and now jump to the application vector
						APPVTAB->Reset_Handler();
					}
				}
			}
		}
	}

	// fall through to run the bootloader if for any reason we didn't boot to the application


	/* USER CODE END SysInit */

 

Replace the lines 153-163 with this:

 

// set the vector table address to the application vector table
SCB->VTOR = APP_ADDR;

// Set the stack pointer
__set_MSP(APPVTAB->Initial_SP);

__DSB(); // Ensure the VTOR and SP operations are complete
__ISB(); // Flush the pipeline because of SP change

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

// and now jump to the application vector
APPVTAB->Reset_Handler();

 

Moved the SP setting before enabling the interrupts and added the necessary memory barriers as documented in the ARM Application Note 321. Take a note that interrupts use both VTOR and SP!

Also in APPVTAB definition it is safer to cast the address to (const struct app_vectable_ *).

Using RTC registers for boot commands/status requires enabling RTC and PWR clocks, disabling backup domain protection and then reversing all or part of it. In my opinion it is still unnecessarily complex compared to just using a basic variable in RAM. The only thing necessary for it is a minor modification in a linker script.