This post is older than 2 years and might not be relevant anymore
More Info: Consider searching for newer posts

Basic Bootloader Interrupt Forwarding

Dear all,

I am using SES, I have an application that relies on a softdevice. For it I developed a custom bootloader that DOES NOT rely on the softdevice and only transfers data from and to an external SPI flash. The main looks like this:

#include <stdint.h>
#include <stdio.h>

extern "C"
{
	#include "boards.h"
	#include "nrf_bootloader_info.h"
	#include "nrf_bootloader_app_start.h"
	#include "nrf_nvmc.h"
    #include "app_scheduler.h"
	#include "app_timer.h"
}

void app_error_handler_bare(uint32_t error_code)
{
    (void)error_code;
    //NRF_LOG_ERROR("received an error: 0x%08x!\r\n", error_code);
    NVIC_SystemReset();
}


int main()
{
	nrf_drv_ppi_init();
	nrf_drv_gpiote_init();

    // necessary SPI transfers here

	nrf_bootloader_app_start();

	return 0;
}

Now, the problem is that if I build said bootloader with the NO_VTOR_CONFIG flag, any event (for instance from SPI) will hang the system. If I remove all SPI functionality and only leave the nrf_bootloader_app_start() instruction the application starts fine, as no event is ever generated inside the bootloader.

If I compile without the NO_VTOR_CONFIG flag, all SPI functionality in the bootloader works well but then the application hangs after nrf_bootloader_app_start(), which makes me think that I am doing something wrong or missing something.

I would like to keep my bootloader fairly independent from the presence (or lack) of a softdevice. More specifically I would like to have as little dependencies as possible (includes, libraries and so on). What would be the simplest, most generic way to fix this issue?

Thank you

  • Hi Vittori, 

    if the SoftDevice is present in flash, then NO_VTOR_CONFIG must be added to the preprocessor definitions. 

    The USB bootloader in our nRF5 SDK is not dependent on the SoftDevice and it does add NO_VTOR_CONFIG to the preprocessor definitions. See Secure DFU Bootloader over Serial Link (UART/USB).

    Now, the problem is that if I build said bootloader with the NO_VTOR_CONFIG flag, any event (for instance from SPI) will hang the system. If I remove all SPI functionality and only leave the nrf_bootloader_app_start() instruction the application starts fine, as no event is ever generated inside the bootloader.

     Which interrupt priority is the SPI using? Do you have any information on the PC and the CPU registers when the nRF device hangs?

    Best regards

    Bjørn

  • dear Bjorn,

    The softdevice may or may not be present in flash, as a matter of fact the bootloader is in charge of flashing it, so for sure while the bootloader is working there is a time when the softdevice code is being overwritten and therefore is, in fact, corrupted.

    The SPI is using priority 6, the same as my main application. Unfortunately I am not able to get any insightful error log for the error. The debugger log just hangs on a "blank line" just a few instants after the first SPI event is generated. if I stop debugging there I see that the CPU is hanging on the following instruction:

    0xA60    4B01  ldr r3, [pc, #4]

    Any Idea how can I get more log?

    Could it be that I am doing something wrong with the flash placement? My xml file looks like this:

    <!DOCTYPE Linker_Placement_File>
    <Root name="Flash Section Placement">
      <MemorySegment name="FLASH" start="$(FLASH_PH_START)" size="$(FLASH_PH_SIZE)">
        <ProgramSection load="no" name=".reserved_flash" start="$(FLASH_PH_START)" size="$(FLASH_START)-$(FLASH_PH_START)" />
        <ProgramSection alignment="0x100" load="Yes" name=".vectors" start="$(FLASH_START)" />
        <ProgramSection alignment="4" load="Yes" name=".init" />
        <ProgramSection alignment="4" load="Yes" name=".init_rodata" />
        <ProgramSection alignment="4" load="Yes" name=".text" />
        <ProgramSection alignment="4" keep="Yes" load="Yes" name=".crypto_data" inputsections="*(SORT(.crypto_data*))" address_symbol="__start_crypto_data" end_symbol="__stop_crypto_data" />
        <ProgramSection alignment="4" keep="Yes" load="Yes" name=".nrf_queue" inputsections="*(.nrf_queue*)" address_symbol="__start_nrf_queue" end_symbol="__stop_nrf_queue" />
        <ProgramSection alignment="4" keep="Yes" load="Yes" name=".log_backends" inputsections="*(SORT(.log_backends*))" address_symbol="__start_log_backends" end_symbol="__stop_log_backends" />
        <ProgramSection alignment="4" keep="Yes" load="Yes" name=".log_const_data" inputsections="*(SORT(.log_const_data*))" address_symbol="__start_log_const_data" end_symbol="__stop_log_const_data" />
        <ProgramSection alignment="4" keep="Yes" load="Yes" name=".nrf_balloc" inputsections="*(.nrf_balloc*)" address_symbol="__start_nrf_balloc" end_symbol="__stop_nrf_balloc" />
        <ProgramSection alignment="4" keep="Yes" load="Yes" name=".sdh_soc_observers" inputsections="*(SORT(.sdh_soc_observers*))" address_symbol="__start_sdh_soc_observers" end_symbol="__stop_sdh_soc_observers" />
        <ProgramSection alignment="4" keep="Yes" load="Yes" name=".sdh_req_observers" inputsections="*(SORT(.sdh_req_observers*))" address_symbol="__start_sdh_req_observers" end_symbol="__stop_sdh_req_observers" />
        <ProgramSection alignment="4" keep="Yes" load="Yes" name=".sdh_state_observers" inputsections="*(SORT(.sdh_state_observers*))" address_symbol="__start_sdh_state_observers" end_symbol="__stop_sdh_state_observers" />
        <ProgramSection alignment="4" keep="Yes" load="Yes" name=".sdh_stack_observers" inputsections="*(SORT(.sdh_stack_observers*))" address_symbol="__start_sdh_stack_observers" end_symbol="__stop_sdh_stack_observers" />
        <ProgramSection alignment="4" keep="Yes" load="No" name=".nrf_sections" address_symbol="__start_nrf_sections" />
        <ProgramSection alignment="4" keep="Yes" load="Yes" name=".log_dynamic_data"  inputsections="*(SORT(.log_dynamic_data*))" runin=".log_dynamic_data_run"/>
        <ProgramSection alignment="4" keep="Yes" load="Yes" name=".log_filter_data"  inputsections="*(SORT(.log_filter_data*))" runin=".log_filter_data_run"/>
        <ProgramSection alignment="4" keep="Yes" load="Yes" name=".fs_data"  inputsections="*(.fs_data*)" runin=".fs_data_run"/>
        <ProgramSection alignment="4" load="Yes" name=".dtors" />
        <ProgramSection alignment="4" load="Yes" name=".ctors" />
        <ProgramSection alignment="4" load="Yes" name=".rodata" />
        <ProgramSection alignment="4" load="Yes" name=".ARM.exidx" address_symbol="__exidx_start" end_symbol="__exidx_end" />
        <ProgramSection alignment="4" load="Yes" runin=".fast_run" name=".fast" />
        <ProgramSection alignment="4" load="Yes" runin=".data_run" name=".data" />
        <ProgramSection alignment="4" load="Yes" runin=".tdata_run" name=".tdata" />
      </MemorySegment>
      <MemorySegment name="RAM" start="$(RAM_PH_START)" size="$(RAM_PH_SIZE)">
        <ProgramSection load="no" name=".reserved_ram" start="$(RAM_PH_START)" size="$(RAM_START)-$(RAM_PH_START)" />
        <ProgramSection alignment="0x100" load="No" name=".vectors_ram" start="$(RAM_START)" address_symbol="__app_ram_start__"/>
        <ProgramSection alignment="4" keep="Yes" load="No" name=".nrf_sections_run" address_symbol="__start_nrf_sections_run" />
        <ProgramSection alignment="4" keep="Yes" load="No" name=".log_dynamic_data_run" address_symbol="__start_log_dynamic_data" end_symbol="__stop_log_dynamic_data" />
        <ProgramSection alignment="4" keep="Yes" load="No" name=".log_filter_data_run" address_symbol="__start_log_filter_data" end_symbol="__stop_log_filter_data" />
        <ProgramSection alignment="4" keep="Yes" load="No" name=".fs_data_run" address_symbol="__start_fs_data" end_symbol="__stop_fs_data" />
        <ProgramSection alignment="4" keep="Yes" load="No" name=".nrf_sections_run_end" address_symbol="__end_nrf_sections_run" />
        <ProgramSection alignment="4" load="No" name=".fast_run" />
        <ProgramSection alignment="4" load="No" name=".data_run" />
        <ProgramSection alignment="4" load="No" name=".tdata_run" />
        <ProgramSection alignment="4" load="No" name=".bss" />
        <ProgramSection alignment="4" load="No" name=".tbss" />
        <ProgramSection alignment="4" load="No" name=".non_init" />
        <ProgramSection alignment="4" size="__HEAPSIZE__" load="No" name=".heap" />
        <ProgramSection alignment="8" size="__STACKSIZE__" load="No" place_from_segment_end="Yes" name=".stack"  address_symbol="__StackLimit" end_symbol="__StackTop"/>
        <ProgramSection alignment="8" size="__STACKSIZE_PROCESS__" load="No" name=".stack_process" />
      </MemorySegment>
      <MemorySegment name="mbr_params_page" start="0x0007E000" size="0x1000">
        <ProgramSection alignment="4" keep="Yes" load="No" name=".mbr_params_page" address_symbol="__start_mbr_params_page" end_symbol="__stop_mbr_params_page" start = "0x0007E000" size="0x1000" />
      </MemorySegment>
      <MemorySegment name="bootloader_settings_page" start="0x0007F000" size="0x1000">
        <ProgramSection alignment="4" keep="Yes" load="No" name=".bootloader_settings_page" address_symbol="__start_bootloader_settings_page" end_symbol="__stop_bootloader_settings_page" start = "0x0007F000" size="0x1000" />
      </MemorySegment>
      <MemorySegment name="uicr_bootloader_start_address" start="0x10001014" size="0x4">
        <ProgramSection alignment="4" keep="Yes" load="Yes" name=".uicr_bootloader_start_address" address_symbol="__start_uicr_bootloader_start_address" end_symbol="__stop_uicr_bootloader_start_address" start = "0x10001014" size="0x4" />
      </MemorySegment>
      <MemorySegment name="uicr_mbr_params_page" start="0x10001018" size="0x4">
        <ProgramSection alignment="4" keep="Yes" load="Yes" name=".uicr_mbr_params_page" address_symbol="__start_uicr_mbr_params_page" end_symbol="__stop_uicr_mbr_params_page" start = "0x10001018" size="0x4" />
      </MemorySegment>
    </Root>

    The memory segments in project options are like this:

    FLASH RX 0x0 0x80000
    RAM RWX 0x20000000 0x10000
    uicr_bootloader_start_address RX 0x10001014 0x4
    uicr_mbr_params_page RX 0x10001018 0x4
    mbr_params_page RX 0x0007E000 0x1000
    bootloader_settings_page RX 0x0007F000 0x1000

    The linker section placement macros are like this:

    FLASH_PH_START=0x0
    FLASH_PH_SIZE=0x80000
    RAM_PH_START=0x20000000
    RAM_PH_SIZE=0x10000
    FLASH_START=0x78000
    FLASH_SIZE=0x6000
    RAM_START=0x20000000
    RAM_SIZE=0x10000

    And preprocessor macros are as follows:

    BOARD_PCA10040
    CONFIG_GPIO_AS_PINRESET
    FLOAT_ABI_HARD
    INITIALIZE_USER_SECTIONS
    MBR_PRESENT
    NRF52
    NRF52832_XXAA
    NRF52_PAN_74
    NRF_DFU_SETTINGS_VERSION=2
    SWI_DISABLE0
    NO_VTOR_CONFIG

  • vittopascu said:
    The softdevice may or may not be present in flash, as a matter of fact the bootloader is in charge of flashing it, so for sure while the bootloader is working there is a time when the softdevice code is being overwritten and therefore is, in fact, corrupted.

     That is OK as long as you do not alter the MBR, which is the first page 0x0000 to 0x1000. This ensures that executon is passed to the bootloader on boot. This should never be touched by the bootloader or the application. 

     

    vittopascu said:

    The SPI is using priority 6, the same as my main application. Unfortunately I am not able to get any insightful error log for the error. The debugger log just hangs on a "blank line" just a few instants after the first SPI event is generated. if I stop debugging there I see that the CPU is hanging on the following instruction:

    0xA60    4B01  ldr r3, [pc, #4]

    Any Idea how can I get more log?

     The PC indicates that its executing code in the MBR, which is a bit weird. Are you entering any of the application error handlers prior to this, i.e. app_error_handler_bare or app_error_save_and_stop or app_error_fault_handler?

    Best regards

    Bjørn

  • At this very moment I am overwriting the MBR too because it is included in the softdevices in the SDK. A binary compare reveals that the first 0x1000 bytes of softdevice s132 are the same as the standalone MBR. Should I skip those bytes when I flash the new softdevice?

    It seems that the program does not enter any of the error handlers that you mentioned.

    Anyhow, the problem presents itself even when I don't actually flash anything. I have the MBR and softdevice correctly in place and everything works until I try any kind of SPI transaction (event driven) like for instance just opening the SPI Flash.

    I am no expert about these things but I have the feeling that when the NO_VTOR_CONFIG option is selected the bootloader execution jumps to the wrong vector table when an event occurs. If I remove NO_VTOR_CONFIG the bootloader works fine but the application hangs, maybe beacuse now the application/softdevice tries to use the vector table of the bootloader...

    I actually found a way where it seems to work: I build the bootloader without the NO_VTOR_CONFIG option and then I manually set the VTOR_REG = 0 before starting the main application. Funny enough, I thought I would have to set it to MBR_SIZE to point at the softdevice vector table, but that didn't work, I would really appreciate if someone could explain to me what is going on because I must say I am a bit lost here.

    So the main looks like this:

    #include <stdint.h>
    #include <stdio.h>
    
    extern "C"
    {
    	#include "boards.h"
    	#include "nrf_log.h"
    	#include "nrf_bootloader_info.h"
    	#include "nrf_bootloader_app_start.h"
    	#include "nrf_nvmc.h"
        #include "nrf_sdm.h"
    	#include "crc32.h"
    }
    
    
    void app_error_handler_bare(uint32_t error_code)
    {
        (void)error_code;
        NRF_LOG_ERROR("received an error: 0x%08x!\r\n", error_code);
        NVIC_SystemReset();
    }
    
    
    int main()
    {
    	nrf_drv_ppi_init();
    	nrf_drv_gpiote_init();
    
        // all my SPI interrupt-driven activity here
    
    	// forwarding interrupts to main application
    	uint32_t *vtor_reg = (uint32_t *)0xE000ED08;
    	*vtor_reg = 0;
    
    	// Branch to the reset handler for the application
    	nrf_bootloader_app_start();
    
    	return 0;
    }

    However I suppose there might be a more elegant and, most importantly, less misterious solution for this. I could verify that the bootloader is regularly executed even after multiple power cycles and that it always passes the control back to the main application correctly.

  • vittopascu said:
    At this very moment I am overwriting the MBR too because it is included in the softdevices in the SDK. A binary compare reveals that the first 0x1000 bytes of softdevice s132 are the same as the standalone MBR. Should I skip those bytes when I flash the new softdevice?

     Yes, if the MBR is already present in flash, then the bootloader should jsut write the SoftDevice to flash, not the MBR and the SoftDevice, i.e. start at 0x1000. 

     

    vittopascu said:
    Anyhow, the problem presents itself even when I don't actually flash anything. I have the MBR and softdevice correctly in place and everything works until I try any kind of SPI transaction (event driven) like for instance just opening the SPI Flash.

    If the MBR is present and the bootloader start address is written to UICR, then all interrupts/events should be forwarded to the bootloader.  this is one of the main purposes for the MBR. When the bootloader starts the app it change the address to where the MBR should forward the interrupts/events to the SoftDevices vector table, which is at 0x1000, see snippet below.

    void nrf_bootloader_app_start(void)
    {
        uint32_t start_addr = MBR_SIZE; // Always boot from end of MBR. If a SoftDevice is present, it will boot the app.
        NRF_LOG_DEBUG("Running nrf_bootloader_app_start with address: 0x%08x", start_addr);
        uint32_t err_code;
    
        // Disable and clear interrupts
        // Notice that this disables only 'external' interrupts (positive IRQn).
        NRF_LOG_DEBUG("Disabling interrupts. NVIC->ICER[0]: 0x%x", NVIC->ICER[0]);
    
        NVIC->ICER[0]=0xFFFFFFFF;
        NVIC->ICPR[0]=0xFFFFFFFF;
    #if defined(__NRF_NVIC_ISER_COUNT) && __NRF_NVIC_ISER_COUNT == 2
        NVIC->ICER[1]=0xFFFFFFFF;
        NVIC->ICPR[1]=0xFFFFFFFF;
    #endif
    
        err_code = nrf_dfu_mbr_irq_forward_address_set();
        if (err_code != NRF_SUCCESS)
        {
            NRF_LOG_ERROR("Failed running nrf_dfu_mbr_irq_forward_address_set()");
        }
    
        NRF_LOG_FLUSH();
        nrf_bootloader_app_start_final(start_addr);
    }
    
    uint32_t nrf_dfu_mbr_irq_forward_address_set(void)
    {
        uint32_t ret_val = NRF_ERROR_INVALID_PARAM;
        uint32_t address = MBR_SIZE;
    
    #if !defined(BLE_STACK_SUPPORT_REQD) && !defined(ANT_STACK_SUPPORT_REQD)
        sd_mbr_command_t command =
        {
            .command = SD_MBR_COMMAND_IRQ_FORWARD_ADDRESS_SET,
            .params.irq_forward_address_set.address = address,
        };
    
        ret_val = sd_mbr_command(&command);
    #endif
    
        if (ret_val == NRF_ERROR_INVALID_PARAM)
        {
            // Manually set the forward address if this MBR doesn't have the command.
            *(uint32_t *)(MBR_IRQ_FORWARD_ADDRESS_ADDRESS) = address;
    
            ret_val = NRF_SUCCESS;
        }
    
        return ret_val;
    }

    vittopascu said:
    I actually found a way where it seems to work: I build the bootloader without the NO_VTOR_CONFIG option and then I manually set the VTOR_REG = 0 before starting the main application. Funny enough, I thought I would have to set it to MBR_SIZE to point at the softdevice vector table, but that didn't work, I would really appreciate if someone could explain to me what is going on because I must say I am a bit lost here.

     In this case are you trying to jump to the application when only the MBR is present or when both the MBR and SD is present? It could sound like its the first.

Related