[BUG] SMP FreeRTOS scheduler start critical section race condition
Description
- Core 0 has already started the scheduler (via
vTaskStartScheduler()) - Core 1 has not called
xPortStartScheduler()yet - Core 0 triggers a yield on core 1 (e.g., by creating a new task with core 1 affinity)
- If core 1 attempts to enter a critical section (e.g., via calling
xTaskGetSchedulerState()), core 1 will assert in the following line withinprvCheckForRunStateChange():
configASSERT( pxThisTCB->xTaskRunState != taskTASK_YIELDING );
Cause
- Core 0 calling
vTaskStartScheduler()should assign one of the idle tasks as core 1's current task - Core 0 triggering a yield will cause core 1's current task's state to be
taskTASK_YIELDING - When core 1 enters a critical section, it will call
prvCheckForRunStateChange()because the scheduler has already been started by core 0 - In
prvCheckForRunStateChange(), core 1 callsportENABLE_INTERRUPTS()expecting a context switch to occur, but core 1 cannot switch contexts because it has not calledxPortStartScheduler()yet. - No context switch occurs, thus core 1's current task's state is still
taskTASK_YIELDING.
Possible Fixes
Restrict usage of FreeRTOS API before the core has started the scheduler
One way to avoid this race condition is restrict a core from calling any FreeRTOS API before the core has called xPortStartScheduler(). However this may be inconvenient if the port has particular routines that are used both before and after the scheduler has started, and that those routines call taskENTER_CRITICAL().
Each core has their own xSchedulerRunning
Each core could individually track whether they have started their scheduler as such:
PRIVILEGED_DATA static volatile BaseType_t xSchedulerRunnings[ configNUM_CORES ] = { pdFALSE };
#define xSchedulerRunning xSchedulerRunnings[ portGET_CORE_ID() ]
However, since vTaskStartScheduler() is only called from one core (e.g., core 0), all other cores will need a separate function to set their own xSchedulerRunnings[x] variable to pdTRUE when starting the scheduler, such as a vTaskStartSchedulerOtherCores() function that is called by all other cores before they call xPortStartScheduler().