AnsweredAssumed Answered

How could/would flash-resident bootloader, direct boot option, and application calls to run_bootloader() work?

Question asked by Don Willey on Mar 26, 2018
Latest reply on Apr 4, 2018 by Kerry Zhou

I want my flash resident boot loader to be in flash starting at 0x00000000 where it runs each time the system is restarted.  When there is an application at 0x00008000, I want that to be run directly using the direct boot option of the boot loader.  The boot loader reference describes this option, and when configured in the BCA, I pass through the boot loader's initialization code into my application.  Both images were loaded separately with a debugger into flash.

 

Now I want the application to call the flash-resident boot loader, and based on documentation in section 2.6, I expect a pointer to the boot loader API tree at 0x0000001C (a location in the boot loader vector table) that will tell me how to find runBootloader() and other goodies available from the boot loader.  I don't see the pointer at 0x0000001C when I browse memory (it is zero as expected from the source in my startup.S file).  Not a big deal, I can add it.

 

.section .isr_vector, "a"
.align 2
.globl __isr_vector
__isr_vector:
.long __StackTop /* Top of Stack */
.long Reset_Handler /* Reset Handler */
.long NMI_Handler /* NMI Handler*/
.long HardFault_Handler /* Hard Fault Handler*/
.long 0 /* Reserved*/
.long 0 /* Reserved*/
.long 0 /* Reserved*/
.long g_bootloaderTree /* Reserved ( for g_bootloaderTree, I hope. location = 0x0000001C )*/
.long 0 /* Reserved*/

 

When I browse memory with the debugger, I find it does point at 

 

//! @brief Static API tree.
const bootloader_tree_t g_bootloaderTree = {.runBootloader = bootloader_user_entry,
.version = {.name = kBootloader_Version_Name,
.major = kBootloader_Version_Major,
.minor = kBootloader_Version_Minor,
.bugfix = kBootloader_Version_Bugfix },
.copyright = bootloaderCopyright,
.runtimeContext = &g_bootloaderContext,
.flashDriver = &g_flashDriverInterface,
.aesDriver = &g_aesInterface };

//! @}

 

And that first entry points at the expected function as verified with the Disassembly window:

 

bootloader_user_entry:
00000d2d: push {r7, lr}
00000d2f: sub sp, #8
00000d31: add r7, sp, #0
00000d33: str r0, [r7, #4]
00000d35: bl 0x5cc <Reset_Handler>

 

However, here is the kicker.  As shown below, I am heading right back where I started when I reset the system.  The MCU will reset, jump through its vector, run its inits, and direct boot back into my application.

 

void bootloader_user_entry(void *arg)
{
#if BL_TARGET_ROM || BL_TARGET_FPGA

// For ROM targets, we set the RCM_FM bits to force the ROM to run out of
// reset, then perform a reset. This provides a guaranteed clean system.
RCM_BWR_FM_FORCEROM(RCM, 0x3);
NVIC_SystemReset();

#elif BL_TARGET_FLASH

// For flash-resident configurations, just call the reset handler as the entry point.
Reset_Handler();

#elif !BL_TARGET_RAM
#error "Undefined target type"
#endif // BL_TARGET_ROM || BL_TARGET_FPGA
}


If I understand the way all of this works, we do want to reset, to reboot into the flash loader, to wipe out memory that was previously used by my application, but how does the freshly booted boot loader know that this time things are different and we want to skip the direct boot and perform a flash update? 

The hardware in my system does not have a signal it can put into a GPIO to indicate it wants to program which seems like a potential approach.  Without the hardware input, is this a mode that is dependent on receiving port input before a delay has timed out?  Are there other good options?

Outcomes