6 years, 5 months ago.

Nordic nRF IRQ Handlers and Bootloader

Hello,

I am trying to develop a product with mbed that requires the ability to upgrade firmware over the air. I am opting not to use the premade DFU service and bootloader provided by mbed/Nordic since it has a bit more functionality than I need.

I am trying to make an unmanaged bootloader application (as defined here: https://docs.mbed.com/docs/mbed-os-handbook/en/latest/advanced/bootloader/) that resides at the very end of flash memory. Using the UICR of the Nordic nRF52832, I point the Master Boot Record/Softdevice to this bootloader which then boots into the appropriate application or updates firmware as needed.

I have modified the original linker script to allow me to use the application configuration parameters mentioned in the link above (target.mbed_app_start and target.mbed_app_size) and the bootloader application is being put into the appropriate chunk of flash.

However

I have encountered an issue where the bootloader application runs as expected when placed in the normal location (0x1C000, right after the soft device in flash) but enters an infinite loop IRQn when placed anywhere else in flash. I looked in the .map file to see what the infinite loop was and I found this:

/media/uploads/aglass0fmilk/irqn.png

There were several interrupt routines (including those suffixed with "_v") that were weak symbols not defined anywhere else all sharing the same address and somehow the device would enter one of these and get stuck in an infinite loop. My workaround looks like this:

IRQn Workaround

extern void GPIOTE_IRQHandler(void);
extern void PWM1_IRQHandler(void);
//extern void NFCT_IRQHandler(void);
//extern void FPU_IRQHandler(void);
extern void POWER_CLOCK_IRQHandler(void);
extern void SPIM0_SPIS0_TWIM0_TWIS0_SPI0_TWI0_IRQHandler(void);
extern void RTC1_IRQHandler(void);
extern void SPIM2_SPIS2_SPI2_IRQHandler(void);
extern void PWM2_IRQHandler(void);
extern void MWU_IRQHandler(void);
//extern void RTC2_IRQHandler(void);
extern void CCM_AAR_IRQHandler(void);
extern void SWI4_EGU4_IRQHandler(void);
extern void SPIM1_SPIS1_TWIM1_TWIS1_SPI1_TWI1_IRQHandler(void);
extern void SAADC_IRQHandler(void);
extern void TIMER1_IRQHandler(void);
//extern void I2S_IRQHandler(void);
//extern void SWI3_EGU3_IRQHandler(void);
extern void SWI5_EGU5_IRQHandler(void);
extern void UARTE0_UART0_IRQHandler(void);
//extern void QDEC_IRQHandler(void);
//extern void TIMER3_IRQHandler(void);
extern void Default_Handler(void);
extern void RNG_IRQHandler(void);
extern void COMP_LPCOMP_IRQHandler(void);
extern void SWI2_EGU2_IRQHandler(void);
//extern void TIMER0_IRQHandler(void);
//extern void SWI0_EGU0_IRQHandler(void);
//extern void WDT_IRQHandler(void);
extern void PDM_IRQHandler(void);
extern void RTC0_IRQHandler(void);
extern void TIMER2_IRQHandler(void);
//extern void TIMER4_IRQHandler(void);
extern void RADIO_IRQHandler(void);
extern void ECB_IRQHandler(void);
//extern void TEMP_IRQHandler(void);
extern void SWI1_EGU1_IRQHandler(void);
extern void PWM0_IRQHandler(void);

void GPIOTE_IRQHandler_v(void)
{
	GPIOTE_IRQHandler();
}

void PWM1_IRQHandler_v(void)
{
	PWM1_IRQHandler();
}

void NFCT_IRQHandler_v(void)
{
	//NFCT_IRQHandler();
}

void FPU_IRQHandler_v(void)
{
	//FPU_IRQHandler();
}

void POWER_CLOCK_IRQHandler_v(void)
{
	POWER_CLOCK_IRQHandler();
}

void SPIM0_SPIS0_TWIM0_TWIS0_SPI0_TWI0_IRQHandler_v(void)
{
	SPIM0_SPIS0_TWIM0_TWIS0_SPI0_TWI0_IRQHandler();
}

void RTC1_IRQHandler_v(void)
{
	RTC1_IRQHandler();
}

void SPIM2_SPIS2_SPI2_IRQHandler_v(void)
{
	SPIM2_SPIS2_SPI2_IRQHandler();
}

void PWM2_IRQHandler_v(void)
{
	PWM2_IRQHandler();
}

void MWU_IRQHandler_v(void)
{
	MWU_IRQHandler();
}
void RTC2_IRQHandler_v(void)
{
	//RTC2_IRQHandler();
}
void CCM_AAR_IRQHandler_v(void)
{
	CCM_AAR_IRQHandler();
}
void SWI4_EGU4_IRQHandler_v(void)
{
	SWI4_EGU4_IRQHandler();
}
void SPIM1_SPIS1_TWIM1_TWIS1_SPI1_TWI1_IRQHandler_v(void)
{
	SPIM1_SPIS1_TWIM1_TWIS1_SPI1_TWI1_IRQHandler();
}
void SAADC_IRQHandler_v(void)
{
	SAADC_IRQHandler();
}
void TIMER1_IRQHandler_v(void)
{
	TIMER1_IRQHandler();
}
void I2S_IRQHandler_v(void)
{
	//I2S_IRQHandler();
}
void SWI3_EGU3_IRQHandler_v(void)
{
	//SWI3_EGU3_IRQHandler();
}
void SWI5_EGU5_IRQHandler_v(void)
{
	SWI5_EGU5_IRQHandler();
}
void UARTE0_UART0_IRQHandler_v(void)
{
	UARTE0_UART0_IRQHandler();
}
void QDEC_IRQHandler_v(void)
{
	//QDEC_IRQHandler();
}
void TIMER3_IRQHandler_v(void)
{
	//TIMER3_IRQHandler();
}
void Default_Handler_v(void)
{
	Default_Handler();
}
void RNG_IRQHandler_v(void)
{
	RNG_IRQHandler();
}
void COMP_LPCOMP_IRQHandler_v(void)
{
	COMP_LPCOMP_IRQHandler();
}
void SWI2_EGU2_IRQHandler_v(void)
{
	SWI2_EGU2_IRQHandler();
}
void TIMER0_IRQHandler_v(void)
{
	//TIMER0_IRQHandler();
}
void SWI0_EGU0_IRQHandler_v(void)
{
	//SWI0_EGU0_IRQHandler();
}
void WDT_IRQHandler_v(void)
{
	//WDT_IRQHandler();
}
void RTC0_IRQHandler_v(void)
{
	RTC0_IRQHandler();
}
void TIMER2_IRQHandler_v(void)
{
	TIMER2_IRQHandler();
}
void TIMER4_IRQHandler_v(void)
{
	//TIMER4_IRQHandler();
}
void RADIO_IRQHandler_v(void)
{
	RADIO_IRQHandler();
}
void ECB_IRQHandler_v(void)
{
	ECB_IRQHandler();
}
void TEMP_IRQHandler_v(void)
{
	//TEMP_IRQHandler();
}
void SWI1_EGU1_IRQHandler_v(void)
{
	SWI1_EGU1_IRQHandler();
}
void PWM0_IRQHandler_v(void)
{
	PWM0_IRQHandler();
}

I just implement every "_v" handler I saw that wasn't implemented and call the actual IRQ handler, if it exists (those that didn't exist were commented out)

This makes my bootloader function as expected... but I'm uncomfortable not knowing why this is necessary in the first place. What static dependence does mbed have on the application's location in flash?

As you can expect, this has been a maddening issue to track down. It makes working with mbed in all but the simplest of scenarios pretty difficult. Any light shed on this issue would be helpful.

2 Answers

6 years, 2 months ago.

You guys are further along than I am. I'm not getting the bootloader to hand over control back to the main application. Our main application is using mBed OS 5.7.3 (#158) with softdevice but the bootloader is the a slightly modified sample "blinky" code from the nRF52 SDK 14 (without softdevice). I put the "bootloader" at 0x78000 and flash my mBed application as per usual (0x0000 - 0x52000). The main app works fine after I flash the "bootloader". When I update BOOTLOADERADDR UICR to point to 0x78000 (using <<code>>nrfjprog -f nrf52 --memwr 0x10001014 --val 0x78000<</code>>) and do a reset, it starts the "blinky" app correctly, but after a few blinks it is supposed to start the main app, but it just resets back to the "blinky" app. Also, when I write 0x1c000 to the BOOTLOADERADDR UICR and do a reset, I expected to get my mBed app running again, but that is not happening. Any insights?

For anyone visiting this thread's reference, here's how I accomplished an mbed based bootloader that forwards execution to a mbed main application on the nRF52832:

As mentioned in Russ's post, there's a line in mbed-os/platform/mbed_application.c called during "mbed_start_application" that relocates the VTOR pointer. This breaks compatibility with the Nordic MBR and softdevice (which should always be forwarded interrupts when in use).

You need to conditionalize this line based on your target or comment it out. I have custom targets based on the NRF52_DK and put compiler directives around it as shown below:

mbed-os/platform/mbed_application.c, starting at line 61 or so

static void powerdown_scb(uint32_t vtor)
{
    int i;

    // SCB->CPUID   - Read only CPU ID register
    SCB->ICSR = SCB_ICSR_PENDSVCLR_Msk | SCB_ICSR_PENDSTCLR_Msk;

    /***** MODIFICATION ******/
    // Prevent the vector table from being relocated
   // This line needs to be commented out/not compiled for nRF52 targets
    #if !defined(TARGET_CUSTOM)
    SCB->VTOR = vtor;
    #endif
    SCB->AIRCR = 0x05FA | 0x0000;
    SCB->SCR = 0x00000000;
// SCB->CCR     - Implementation defined value
........

Once this is done, recompile your mbed-os libraries.

As far as the sample bootloader application goes, here's a stripped down version of mine that does exactly what you're doing:

Sample Bootloader - note: untested

/** Library includes */
#include "mbed.h"

extern "C" 
{
	#include "nrf_mbr.h"
	#include "nrf_sdm.h"
}


/** Application level includes */
#include "mbed_config.h"

// Branches execution to the main application in device flash
// Should not return
int loadMainApp(void)
{
	// Forward interrupts to the soft device
	sd_mbr_command_t l_Command = { .command = SD_MBR_COMMAND_INIT_SD};
	sd_mbr_command(&l_Command);

	// Disable SD (main application may re-enable SD)
	sd_softdevice_disable();

	uint32_t l_AppStartAddress = 0x1C000; // Your main application start address goes here

	// Configure SD to forward interrupts to application
	// TODO - Use configurable APP_CODE_BASE
	sd_softdevice_vector_table_base_set(l_AppStartAddress);

	// Branch to main application
	mbed_start_application(l_AppStartAddress);

	return 0; // Should never be reached
}

int main(void)
{
	loadMainApp();
}

Remember to set the UICR->BOOTLOADERADDR to point to your bootloader's start (10001014 -> 0x78000)

posted by George Beckstein 05 Feb 2018
6 years, 5 months ago.

Hi George,

Most of the vectors table entries are written into ram at runtime with NVIC_SetVector. Only a few vectors are present in the flash vector table, so seeing most of these using the same default handler is reasonable.

Bootloader builds for the NRF52 aren't supported yet in mbed-os, so its likely you'll run into problems like this. I believe that the NRF52 leaves the vector table offset register (VTOR) pointing to the softdevice's vector table, which then forwards interrupts to the application. From your description it sounds like the vector table is being relocated to the start of your application. This is problematic as it will prevent softdevice calls from working (if you are using it) and prevent NVIC_SetVector from installing vectors at runtime.

Me and a co-worker were looking into something similar on the NRF52. In our case we wanted to keep the vector table pointing to the softdevice so we locally removed the line of code which sets up the VTOR in the bootloader.

If this is the same problem you are having and you don't want to use the softdevice you should be able fix this problem by setting the VTOR to point to the ram copy in nrf_reloc_vector_table(). Note - the vector table used by the core needs to be aligned, so make sure you add MBED_ALIGN(256) to the array nrf_dispatch_vector.

I hope this helps.

Thanks, Russ

Thanks for your response Russ.

I wrote this question before I had actually started to implement a main application at all. This issue was experienced with just the bootloader trying to run when executed by the softdevice/mbr. Your answer was relevant when I actually was implementing the main application though.

Since my workaround seemed to work (I'm still not 100% comfortable with it) I went ahead and tried to get the application to run after the bootloader ran. This is where I experienced a problem with the mbed_application_start() function relocating the VTOR. I removed the line you mentioned above and the bootloader is successfully able to branch to the main application.

I'm still not sure why I have to do the strange interrupt routine workaround I posted in the original question... My original problem was happening immediately when the MBR tried to execute the bootloader, not when I was trying to branch to the main application. So I would imagine the VTOR would not be relocated by mbed until I called mbed_application_start() in the bootloader.

This interrupt issue has popped up before when I tried using Nordic's SAADC driver (without using mbed's AnalogIn api since my application required a bit more complexity). After initializing the Nordic SAADC driver my program would always lockup in an infinite loop - the same situation described above. Implementing a handler like this:

SAADC IRQn Workaround

extern void SAADC_IRQHandler(void);

void SAADC_IRQHandler_v(void)
{
    SAADC_IRQHandler();
}

Allowed my program to execute as expected... In this case it was easy to guess which unimplemented IRQ handler was causing the lockup...

Any insight on this issue?

posted by George Beckstein 30 Nov 2017

Hi George,

Even in the bootloader you'll need to relocate the vector table to RAM, otherwise you'll experience bugs with interrupts not working as expected. Normally this is done for you in nrf_reloc_vector_table() as part of the boot sequence. Is that line of code still getting called in the bootloader or did you remove it? If it is still present, is there a softdevice present to perform the vector table update?

posted by Russ Butler 30 Nov 2017

The problem is that I have experienced this issue in a regular mbed application as well (no bootloader).

From my understanding, nrf_reloc_vector_table() is called in the startup_NRF528328.S assembly file and is part of the reset handler. I have not modified this file so it should be getting called still.

My application startup is as follows:

Power on/Reset -> MBR (0x0) -> Reads UICR->NRFFW[0] (BOOTLOADERADDR) -> Executes Bootloader from BOOTLOADERADDR -> Bootloader Program executes -> Branch to main application (at address 0x1C000) using modified mbed_start_application() -> Main application executes (softdevice/ble stack functions properly!)

I will try to make a really basic application that can reproduce this issue with the current release of mbed.

posted by George Beckstein 30 Nov 2017

You guys are further along than I am. I'm not getting the bootloader to hand over control back to the main application. Our main application is using mBed OS 5.7.3 (#158) with softdevice but the bootloader is the a slightly modified sample "blinky" code from the nRF52 SDK 14 (without softdevice). I put the "bootloader" at 0x78000 and flash my mBed application as per usual (0x0000 - 0x52000). The main app works fine after I flash the "bootloader". When I update BOOTLOADERADDR UICR to point to 0x78000 (using <<code>>nrfjprog -f nrf52 --memwr 0x10001014 --val 0x78000<</code>>) and do a reset, it starts the "blinky" app correctly, but after a few blinks it is supposed to start the main app, but it just resets back to the "blinky" app. Also, when I write 0x1c000 to the BOOTLOADERADDR UICR and do a reset, I expected to get my mBed app running again, but that is not happening. Any insights?

posted by Stephan Edelman 30 Jan 2018