ARM M0: NoRTOS Bootloader / FreeRTOS application: no context switches

Hello,

i am writing a nortos baremetal bootloader, which should handover to a freertos app for an ARM Cortex M0 (without the VTOR)

the ‘standalone’ (no bootloader involved) freertos application works fine (3 tasks, everyone gets scheduled).

to implement the bootloader the app was moved 32k downwards the flash, and the bootloader lives on the very beginning of the flash.
as the hardware involved is a ARM M0 - without VTOR - i have a variable in reserved boot ram called application_active space determining which IRQ code is to be executed. handover is done this way:

static void __attribute__((naked)) start_app(uint32_t pc, uint32_t sp) {
    __asm("           \n\
          msr msp, r1 /* load r1 into MSP */\n\
		  bx r0       /* branch to the address at r0 */\n\
    ");
}

int main(void) {
...

  // handover to application
  __DISABLE_IRQ();
  PH_HAL_NVIC_DISABLE_ALL_INTERRUPTS();
  phHal_Nvic_ClearAllPendingInterrupt();

  application_active = 1;
  uint32_t *app_code = (uint32_t *)USER_APP_FLASH_START;
  uint32_t app_sp = app_code[0];
  uint32_t app_start = app_code[1];
  start_app(app_start, app_sp);

    while(1) {
        ;;
    }
}

all the VT handlers have a structure like this, with pUserAppVector pointing to the beginning of the app flash section (where SP + handler adreses are)

void SysTick_Handler(void )
{
    if(application_active) {
        (*pUserAppVector->vtTable[14])();
    } else {
   // run 'normal' bootloader irq code
    }
}

after handover i can see the app starting, create the tasks/timers/queues, and then each tasks blocks on its own mqueue. i can trigger a gpio irq, which creates a message and should unblock on of the tasks - but this never happens.

i believe i have done the vector remapping (yes - i have searched for ‘bootloader’ entries in this forum) correctly as the app creates an usb ccid device - which becomes visible on the connected host pc. also, the gpio irq described above seems to be correctly routed from bootloader irq vector to freertos irq handler.

also, i can see the ARM0 ported xPortSysTickHandler() beeing called regularly (trigger a led every 100 times). BUT it seems that xTaskIncrementTick() is never called(verified via another led).

before diving into the internals of the scheduler and the unblocking mechanism - has anyone an idea about this problem?

thx in advance!
hari

That doesn’t seem right. Here is xPortSysTickHandler():

void xPortSysTickHandler( void )
{
    uint32_t ulPreviousMask;

    ulPreviousMask = portSET_INTERRUPT_MASK_FROM_ISR();
    {
        /* Increment the RTOS tick. */
        if( xTaskIncrementTick() != pdFALSE )
        {
            /* Pend a context switch. */
            portNVIC_INT_CTRL_REG = portNVIC_PENDSVSET_BIT;
        }
    }
    portCLEAR_INTERRUPT_MASK_FROM_ISR( ulPreviousMask );
}

It always calls xTaskIncrementTick().

Separately from that issue, for FreeRTOS to work correctly, your re-vectoring code for PendSV should be written as a naked function. It must not change any processor context except R0 through R3 and the PC. That includes the MSP which must remain at its entry value. You’ll want to use a BX (not BLX) to jump to xPortPendSVHandler.

And one last thought:

You did this by editing the linker file right?

1 Like

Which FreeRTOS port are you using? This Cortex-M0 port, has the following definition of xPortSysTickHandler:

void xPortSysTickHandler( void )
{
    uint32_t ulPreviousMask;

    ulPreviousMask = portSET_INTERRUPT_MASK_FROM_ISR();
    {
        /* Increment the RTOS tick. */
        if( xTaskIncrementTick() != pdFALSE )
        {
            /* Pend a context switch. */
            portNVIC_INT_CTRL_REG = portNVIC_PENDSVSET_BIT;
        }
    }
    portCLEAR_INTERRUPT_MASK_FROM_ISR( ulPreviousMask );
}

I do not see how it is possible that xPortSysTickHandler is called and xTaskIncrementTick is not called. Can you step through the code?

Thanks.

thx!

you are of course right, it is called, but it seems it never returns pdTRUE and therefore triggers no context switch.

i will look into the problem regarding the MSP.

and yes, the 2 flash sections are created with 2 different builds using 2 different linker files, which are flashed one after the other.

again, thank you for your input!
Hari

hi and thank you,

you are of course right: xTaskIncrementTick() is called of course, but never returns true, and therefore never sets portNVIC_INT_CTRL_REG, and thus never triggers a context switch.

regards,
hari

pdTRUE will be returned if incrementing the time resulted in a task moving out of the Blocked state because its timeout expired and if the task that exited the blocked state has a priority higher then the currently executing task. Do you have a high priority task that never blocks, which would cause this? Can you show the code for the task that is running and let us know what its priority it relative to other tasks.

Would suggest starting with something very simple, say two tasks that do nothing but increment a variable (each task incrementing a different variable). Create the tasks at the same priority, run the scheduler for a while, stop on the debugger and check both variables are incrementing.

thx richard,

as written above the application works correctly if use stand-alone, i.e. no bootloader involved (and the app flashed to 0x00 s.t. no interrupt ‘remapping’ is necessary).

regarding the structure of the tasks: i am building upon the NFC PN7x62 library, providing example applications for various contact/contactless smartcard standards:

  • system task, listening for usb ccid esc. cmds
  • 7816 / contact card task (which should get woken up by the gpio interrupt)
  • 14443 / nfc polling loop: this task is triggered by a timer cb

hari

jefftenney, thx for pointing to MSP / PendSV_Handler() - looks like that was exactly the problem!

this is the pendsv - handler installed by the bootloader, with the if-stmt deciding which irq code to run:

// bootloader PendSV IRQ handler: not valid in bootloader context, only used for app
void PendSV_Handler(void);
void PendSV_Handler(void){
    if(application_active) {
        (*pUserAppVector->vtTable[13])();
    } else { // Block infinitely, we should have never reached here.
      __phUser_EnterCriticalSection();
      while(1){ 
          __WFI();
    }
}

as jefftenney pointed out, this function interferes with the MSP used by freertos.

for now, i just replaced the bootloader - pendsv_handler() address in the bootloader - flash with the FreeRtos PendSV handler address (as in my nortos bootloader pendsv shouldnt be called anyway, see code snippet above) - and the application works, the tasks are scheduled / executing again!

thx again
hari

1 Like

Hi Hari,

I am also facing with the same problem. My application is also based on PN7462 + second level bootloader + user app with FreeRTOS. However, I could not fix the problem yet. Can you guide me on how to modify the PendSV and maybe the two other handlers (SVC and systick) in the bootloader ?

Thanks a lot,
With Best Regards,
Aydin

Hi Aydin,

As noted above, your re-vectoring code (in the bootloader) must be written as a naked function or in an assembly language file (.s). The function must not modify any processor state except R0 through R3 and the PC. That includes the MSP. Finally jump to the application ISR via BX (not BLX). That means your re-vectoring code will not get control back when the application ISR finishes, so you don’t need anything else after the BX instruction.

Hi Jeff,
Thanks for your prompt reply.
I did some weird workaround for this but it worked :slight_smile:

First, I reserved some space in SRAM:
volatile static uint32_t bootloaderMode attribute((section(".bootloader")));
volatile static uint32_t userAppInfo attribute((section(".bootloader")));
volatile static uint32_t PendSVHandlerAddr attribute((section(".bootloader")));
volatile static uint32_t SysTickHandlerAddr attribute((section(".bootloader")));

Then, I grab the addresses of the handlers from flash (my user app reside in 0x20b000):
PendSVHandlerAddr = *((volatile uint32_t *)(0x20b038));
SysTickHandlerAddr = *((volatile uint32_t *)(0x20b03c));

The last, my handlers are as follows:

void SecPendSV_Handler(void)
{
asm volatile(“ldr r0, =PendSVHandlerAddr”);
asm volatile(“ldr r0, [r0]”);
asm volatile(“mov pc, r0”);
}

void SecSysTick_Handler(void)
{
if (0x02BADE03 == bootloaderMode ) {
SysTick_Handler();
} else {
asm volatile(“ldr r0, =SysTickHandlerAddr”);
asm volatile(“ldr r0, [r0]”);
asm volatile(“mov pc, r0”);
}
}