DIY bootloader for nRF5340

I'm writing my own lightweight bootloader for nRF5340 (which may or may not turn out to be a good idea).  I have something similar that I'm using successfully on nRF52832 based on a minimal Zephyr application.

The bootloader doesn't use TF-M so it has a single flash partition, however the application has both secure and non-secure code.  By inspection the vector table is in the non-secure partition.

This is what I do in the bootloader to start up the application:

__STATIC_INLINE void jump_to_addr(uint32_t new_msp, uint32_t new_lr, uint32_t addr) 
{
	__ASM volatile("MSR MSP, %[arg]" : : [arg] "r" (new_msp));
	__ASM volatile("MOV LR,  %[arg]" : : [arg] "r" (new_lr) : "lr");
	__ASM volatile("BX       %[arg]" : : [arg] "r" (addr));
}

__STATIC_INLINE void app_start(uint32_t vector_table_addr)
{
    struct arm_vector_table *vt = (void *)vector_table_addr;
	printk("Inspect vector table at %08x\n", vector_table_addr);

	//const uint32_t current_isr_num = (__get_IPSR() & IPSR_ISR_Msk);
	const uint32_t new_msp         = vt->msp;     // The app's Stack Pointer is found as the first word of the vector table.
	const uint32_t reset_handler   = vt->reset;   // The app's Reset Handler is found as the second word of the vector table.
	const uint32_t new_lr          = 0xFFFFFFFF;

	printk("Stack pointer %08x\n", new_msp);
	printk("Reset vector %08x\n", reset_handler);

    irq_lock();

	__set_CONTROL(0x00000000);     // Set CONTROL to its reset value 0.
	__set_PRIMASK(0x00000000);     // Set PRIMASK to its reset value 0.
	__set_BASEPRI(0x00000000);     // Set BASEPRI to its reset value 0.
	__set_FAULTMASK(0x00000000);   // Set FAULTMASK to its reset value 0.

	jump_to_addr(new_msp, new_lr, reset_handler);   // Jump directly to the App's Reset Handler.
}

I'm investigating this by programming the bootloader code first and then running a debug session with the application code.  I can see from the RTT output that the bootloader starts up and attempts to run the application, but it doesn't hit a breakpoint in the application's Reset_Handler(); instead it seems to jump immediately into k_sys_fatal_error_handler with reason 25.

Debugging the bootloader code I see that jump_to_addr appears to have the correct parameters, and switching to disassembly mode I see that the application's Reset_Handler() is invoked and at least the first few instructions are executed.

Can you see what I'm going wrong?

  • Hi,

    I am not able to pinpoint the issue in your implementation. However, I wonder if you could just use the implementation from the nRF Immutable bootlaoder, particularily the bl_boot() implementation from bl_boot.c?

  • I believe I've resolved this - it seems that there are two vector tables, one in the secure area of the application and one in the non-secure area.  The solution is to jump to the reset vector in the secure partition.  I copied a few lines from bl_boot.c, resulting in this

    __STATIC_INLINE void app_start(uint32_t vector_table_addr)
    {
        struct arm_vector_table *vt = (void *)vector_table_addr;
    	printk("Inspect vector table at %08x\n", vector_table_addr);
    	printk("Stack pointer %08x\n", vt->msp);
    	printk("Reset vector %08x\n", (uint32_t)vt->reset);
    
    	// Shut down hardware as needed
    	//nrfx_qspi_uninit();
    	nrf_clock_int_disable(NRF_CLOCK, 0xFFFFFFFF);
    
        irq_lock();
    
    	/* Allow any pending interrupts to be recognized */
    	__ISB();
    	__disable_irq();
    
    	NVIC_Type *nvic = NVIC;
    
    	/* Disable NVIC interrupts */
    	for (uint8_t i = 0; i < ARRAY_SIZE(nvic->ICER); i++) 
    	{
    		nvic->ICER[i] = 0xFFFFFFFF;
    	}
    
    	/* Clear pending NVIC interrupts */
    	for (uint8_t i = 0; i < ARRAY_SIZE(nvic->ICPR); i++) 
    	{
    		nvic->ICPR[i] = 0xFFFFFFFF;
    	}
    
    	SysTick->CTRL = 0;
    
    	/* Disable fault handlers used by the bootloader */
    	SCB->ICSR |= SCB_ICSR_PENDSTCLR_Msk;
    
    	/* Activate the MSP if the core is currently running with the PSP */
    	if (CONTROL_SPSEL_Msk & __get_CONTROL()) 
    	{
    		__set_CONTROL(__get_CONTROL() & ~CONTROL_SPSEL_Msk);
    	}
    
    	__DSB(); /* Force Memory Write before continuing */
    	__ISB(); /* Flush and refill pipeline with updated permissions */
    
    	__set_PSPLIM(0);
    	__set_MSPLIM(0);
    
    	__set_MSP(vt->msp);
    	__set_PSP(0);
    
    	vt->reset();
    }

    Although at the time of writing I'm still having an issue that appears to be to do with timers, k_sleep seems to return almost immediately and I think that's causing spurious timeouts elsewhere in the application code.  I'll investigate further and may raise that as a new ticket if appropriate.

Related