From c926adcf6e0be826e3313fc503a92b53a78f8ef8 Mon Sep 17 00:00:00 2001 From: Harsh Abasaheb Chavan Date: Sun, 15 Mar 2026 04:12:12 +0530 Subject: [PATCH] Fix SMP scheduler evicting task when preemption is disabled In the SMP time-slicing path of xTaskIncrementTick(), xYieldPendings[] was set unconditionally without checking xPreemptionDisable. Although the yield-processing block correctly skipped acting on it for cores with preemption disabled, the stale flag remained and was later misinterpreted by FromISR functions (xTaskGenericNotifyFromISR, xTaskResumeFromISR, etc.) that use xYieldPendings as a proxy for whether prvYieldForTask() decided to yield. This caused *pxHigherPriorityTaskWoken to be erroneously set to pdTRUE, triggering portYIELD_FROM_ISR() and ultimately evicting the preemption-disabled task via prvSelectHighestPriorityTask(). Three-layer fix: 1. Root cause: Guard the time-slicing xYieldPendings[] assignment in xTaskIncrementTick() with a configUSE_TASK_PREEMPTION_DISABLE check so the flag is never set for cores running preemption-disabled tasks. 2. Defense in depth: In the four functions that check xYieldPendings[] after prvYieldForTask() (xTaskResumeFromISR, xTaskRemoveFromEventList, xTaskGenericNotifyFromISR, vTaskGenericNotifyGiveFromISR), save the value before the call and only report a yield if it transitioned from pdFALSE to pdTRUE, eliminating false positives from stale flags. 3. Safety net: Add a preemption-disable check in the SMP vTaskSwitchContext(), analogous to the existing uxSchedulerSuspended check. If a context switch is somehow triggered for a core whose current task has preemption disabled, the switch is deferred by setting xYieldPendings and will execute when vTaskPreemptionEnable() is called. Closes #1376 Made-with: Cursor --- tasks.c | 37 ++++++++++++++++++++++++++++++++----- 1 file changed, 32 insertions(+), 5 deletions(-) diff --git a/tasks.c b/tasks.c index c596c475f8..5e68c7d265 100644 --- a/tasks.c +++ b/tasks.c @@ -3524,9 +3524,12 @@ static void prvInitialiseNewTask( TaskFunction_t pxTaskCode, #if ( ( configNUMBER_OF_CORES > 1 ) && ( configUSE_PREEMPTION == 1 ) ) { + BaseType_t xYieldPendingBefore = xYieldPendings[ portGET_CORE_ID() ]; + prvYieldForTask( pxTCB ); - if( xYieldPendings[ portGET_CORE_ID() ] != pdFALSE ) + if( ( xYieldPendingBefore == pdFALSE ) && + ( xYieldPendings[ portGET_CORE_ID() ] != pdFALSE ) ) { xYieldRequired = pdTRUE; } @@ -4890,7 +4893,12 @@ BaseType_t xTaskIncrementTick( void ) { if( listCURRENT_LIST_LENGTH( &( pxReadyTasksLists[ pxCurrentTCBs[ xCoreID ]->uxPriority ] ) ) > 1U ) { - xYieldPendings[ xCoreID ] = pdTRUE; + #if ( configUSE_TASK_PREEMPTION_DISABLE == 1 ) + if( pxCurrentTCBs[ xCoreID ]->xPreemptionDisable == pdFALSE ) + #endif + { + xYieldPendings[ xCoreID ] = pdTRUE; + } } else { @@ -5222,6 +5230,16 @@ BaseType_t xTaskIncrementTick( void ) * SMP port. */ configASSERT( portGET_CRITICAL_NESTING_COUNT( xCoreID ) == 0 ); + #if ( configUSE_TASK_PREEMPTION_DISABLE == 1 ) + if( pxCurrentTCBs[ xCoreID ]->xPreemptionDisable != pdFALSE ) + { + /* The current task has preemption disabled - do not allow a + * context switch. The yield will be performed when preemption + * is re-enabled. */ + xYieldPendings[ xCoreID ] = pdTRUE; + } + else + #endif /* #if ( configUSE_TASK_PREEMPTION_DISABLE == 1 ) */ if( uxSchedulerSuspended != ( UBaseType_t ) 0U ) { /* The scheduler is currently suspended - do not allow a context @@ -5479,9 +5497,12 @@ BaseType_t xTaskRemoveFromEventList( const List_t * const pxEventList ) #if ( configUSE_PREEMPTION == 1 ) { + BaseType_t xYieldPendingBefore = xYieldPendings[ portGET_CORE_ID() ]; + prvYieldForTask( pxUnblockedTCB ); - if( xYieldPendings[ portGET_CORE_ID() ] != pdFALSE ) + if( ( xYieldPendingBefore == pdFALSE ) && + ( xYieldPendings[ portGET_CORE_ID() ] != pdFALSE ) ) { xReturn = pdTRUE; } @@ -8190,9 +8211,12 @@ TickType_t uxTaskResetEventItemValue( void ) { #if ( configUSE_PREEMPTION == 1 ) { + BaseType_t xYieldPendingBefore = xYieldPendings[ portGET_CORE_ID() ]; + prvYieldForTask( pxTCB ); - if( xYieldPendings[ portGET_CORE_ID() ] == pdTRUE ) + if( ( xYieldPendingBefore == pdFALSE ) && + ( xYieldPendings[ portGET_CORE_ID() ] == pdTRUE ) ) { if( pxHigherPriorityTaskWoken != NULL ) { @@ -8324,9 +8348,12 @@ TickType_t uxTaskResetEventItemValue( void ) { #if ( configUSE_PREEMPTION == 1 ) { + BaseType_t xYieldPendingBefore = xYieldPendings[ portGET_CORE_ID() ]; + prvYieldForTask( pxTCB ); - if( xYieldPendings[ portGET_CORE_ID() ] == pdTRUE ) + if( ( xYieldPendingBefore == pdFALSE ) && + ( xYieldPendings[ portGET_CORE_ID() ] == pdTRUE ) ) { if( pxHigherPriorityTaskWoken != NULL ) {