Menu

#184 portYIELD_FROM_ISR cannot be called from ISR (on LPC2368, at least)

v1.0 (example)
closed-invalid
nobody
None
5
2018-12-10
2018-12-10
No

Disclaimer section:

I observed this bug with the LPC2368 CPU running in co-operative mode. Results may differ with other ports and/or with pre-emption enabled.

I haven't actually tested this since 9.0.0, but the LPC2368 code (which is responsible for a lot of the issues described here) hasn't changed in years.

Extract from https://www.freertos.org/vTaskNotifyGiveFromISR.html:

/* The transmit end interrupt. */
void vTransmitEndISR( void )
{
BaseType_t xHigherPriorityTaskWoken = pdFALSE;

    /* At this point xTaskToNotify should not be NULL as a transmission was
    in progress. */
    configASSERT( xTaskToNotify != NULL );

    /* Notify the task that the transmission is complete. */
    vTaskNotifyGiveFromISR( xTaskToNotify, &xHigherPriorityTaskWoken );

    /* There are no transmissions in progress, so no tasks to notify. */
    xTaskToNotify = NULL;

    /* If xHigherPriorityTaskWoken is now set to pdTRUE then a context switch
    should be performed to ensure the interrupt returns directly to the highest
    priority task.  The macro used for this purpose is dependent on the port in
    use and may be called portEND_SWITCHING_ISR(). */
    portYIELD_FROM_ISR( xHigherPriorityTaskWoken );
}

Here is a timeline of what happens on the LPC2368 port if you follow this advice:

  • User code running in some task. Let's call it Task A.
  • Interrupt happens.
  • ISR notifies Task B, which is both currently waiting on this notification and is higher priority than Task A.
  • ISR calls portYIELD_FROM_ISR(xHigherPriorityTaskWoken).
  • In GCC/ARM7_LPC23xx/portmacro.h, portYIELD_FROM_ISR() (no arguments) is defined simply as vTaskSwitchContext(). (Technically I think this is also a bug, but it doesn't matter since it also doesn't work even in the case where a higher priority task is woken.)
  • vTaskSwitchContext does various unimportant things followed by invoking the taskSELECT_HIGHEST_PRIORITY_TASK macro.
  • This macro changes the value of pxCurrentTCB from Task A to Task B.
  • It does nothing else. Control returns to the ISR, which in turn returns it to the currently running task, which is still task A because nobody thought to overwrite the link register (not that that would be a good idea in co-operative mode anyway).
  • Eventually, Task A yields control for whatever reason. At this point Task B has still not run, but pxCurrentTCB suggests it is currently running.
  • Control ends up with vPortYieldProcessor, which calls portSAVE_CONTEXT().
  • portSAVE_CONTEXT saves all of the current registers onto Task A's stack, then tries to save Task A's current top of stack into the pxTopOfStack member of Task A's TCB.
  • Except... it uses pxCurrentTCB to do so, overwriting the stack location of Task B.
  • vTaskSwitchContext is called again, to no effect since Task B is still the highest priority ready task.
  • Finally, portRESTORE_CONTEXT is used to initialise the registers and program counter from the stack of (what is supposed to be) the new task.
  • But this stack pointer has been overwritten with the stack of Task A, so all the registers and program counter for that are loaded instead.
  • Control returns back to Task A. Since no record now exists anywhere of Task B's stack pointer, it has effectively been removed from the task list.

I'm not even sure this is a coding issue so much as a documentation one. I can't think of any sensible way to implement the behaviour as advertised, at least in co-operative mode.

Discussion

  • Richard Barry

    Richard Barry - 2018-12-10
    • status: open --> closed-invalid
     
  • Richard Barry

    Richard Barry - 2018-12-10

    Interrupts that cause context switches need to be wrapped in the portSAVE/RESTORE_CONTEXT macros. The easiest way to do that is to create a naked function (one that does not have any compiler generated prologue or epilogue assembly code), call the macros, then branch (in assembler) to a handler written in C. Here is an example, albeit one that does not use the macro correctly: https://sourceforge.net/p/freertos/code/HEAD/tree/trunk/FreeRTOS/Demo/ARM7_LPC2138_Rowley/mainISR.c

    You can also see the "interrupt service routines" section on this page, although it is best to call the handler from asm code, as per the link above, than just as a C function: https://www.freertos.org/portlpc2106.html

    If you are using co-operative mode, then just don't call the yield function - this is exactly as per in a task where a yield is only performed when a task blocks or yield is called - if you call yield you are asking for a context switch.

     

Log in to post a comment.

MongoDB Logo MongoDB