From 657932c563e7b6867d9d4e00d5ed62a22c788676 Mon Sep 17 00:00:00 2001 From: Kuoping Hsu Date: Fri, 15 May 2026 16:04:14 +0800 Subject: [PATCH 1/4] Add configUSE_SCHEDULER_CORE_MASK and vTaskSetSchedulerCoreMask API --- .../template_configuration/FreeRTOSConfig.h | 8 ++ include/FreeRTOS.h | 8 ++ include/task.h | 39 ++++++ tasks.c | 113 +++++++++++++++--- 4 files changed, 154 insertions(+), 14 deletions(-) diff --git a/examples/template_configuration/FreeRTOSConfig.h b/examples/template_configuration/FreeRTOSConfig.h index 7859a5c77f..9c1ef935a6 100644 --- a/examples/template_configuration/FreeRTOSConfig.h +++ b/examples/template_configuration/FreeRTOSConfig.h @@ -546,6 +546,14 @@ * vTaskPreemptionEnable APIs. */ #define configUSE_TASK_PREEMPTION_DISABLE 0 +/* When using SMP (i.e. configNUMBER_OF_CORES is greater than one), set + * configUSE_SCHEDULER_CORE_MASK to 1 to enable the scheduler core mask feature. + * When enabled, the vTaskSetSchedulerCoreMask and uxTaskGetSchedulerCoreMask + * APIs can be used to control which cores are allowed to run non-idle tasks + * system-wide at run time. Set to 0 to exclude this feature from the build. + * Defaults to 1 if left undefined. */ +#define configUSE_SCHEDULER_CORE_MASK 1 + /* When using SMP (i.e. configNUMBER_OF_CORES is greater than one), set * configUSE_PASSIVE_IDLE_HOOK to 1 to allow the application writer to use * the passive idle task hook to add background functionality without the diff --git a/include/FreeRTOS.h b/include/FreeRTOS.h index 63e2feb519..b295ef50b0 100644 --- a/include/FreeRTOS.h +++ b/include/FreeRTOS.h @@ -512,6 +512,10 @@ #define configUSE_CORE_AFFINITY 0 #endif /* configUSE_CORE_AFFINITY */ +#ifndef configUSE_SCHEDULER_CORE_MASK + #define configUSE_SCHEDULER_CORE_MASK 1 +#endif /* configUSE_SCHEDULER_CORE_MASK */ + #if ( ( configNUMBER_OF_CORES > 1 ) && ( configUSE_CORE_AFFINITY == 1 ) ) #ifndef configTASK_DEFAULT_CORE_AFFINITY #define configTASK_DEFAULT_CORE_AFFINITY tskNO_AFFINITY @@ -2902,6 +2906,10 @@ #error configUSE_CORE_AFFINITY is not supported in single core FreeRTOS #endif +#if ( ( configNUMBER_OF_CORES == 1 ) && ( configUSE_SCHEDULER_CORE_MASK != 0 ) ) + #error configUSE_SCHEDULER_CORE_MASK is not supported in single core FreeRTOS +#endif + #if ( ( configNUMBER_OF_CORES > 1 ) && ( configUSE_PORT_OPTIMISED_TASK_SELECTION != 0 ) ) #error configUSE_PORT_OPTIMISED_TASK_SELECTION is not supported in SMP FreeRTOS #endif diff --git a/include/task.h b/include/task.h index 2a19fa73fc..048d043a14 100644 --- a/include/task.h +++ b/include/task.h @@ -1420,6 +1420,45 @@ BaseType_t xTaskResumeFromISR( TaskHandle_t xTaskToResume ) PRIVILEGED_FUNCTION; UBaseType_t vTaskCoreAffinityGet( ConstTaskHandle_t xTask ); #endif +#if ( ( configNUMBER_OF_CORES > 1 ) && ( configUSE_SCHEDULER_CORE_MASK == 1 ) ) + +/** + * + * Controls which cores are allowed to run non-idle tasks system-wide. + * Bit N = 1 means core N may run tasks; bit N = 0 means core N will only + * run its idle task. configNUMBER_OF_CORES must be greater than 1 for this + * function to be available. + * + * If a core that is currently running a non-idle task becomes disabled by + * the new mask, it is yielded immediately so the scheduler can replace the + * running task with the idle task. + * + * @param uxCoreMask Bitmask of cores to enable. Cores are numbered 0 to + * configNUMBER_OF_CORES - 1. Pass ( tskNO_AFFINITY ) to re-enable all cores. + * + * Example usage (4-core system, exclude core 2): + * + * // Allow scheduling on cores 0, 1 and 3 only. + * vTaskSetSchedulerCoreMask( ( 1 << 0 ) | ( 1 << 1 ) | ( 1 << 3 ) ); + * + * // Later, restore all four cores. + * vTaskSetSchedulerCoreMask( ( 1 << 0 ) | ( 1 << 1 ) | ( 1 << 2 ) | ( 1 << 3 ) ); + */ + void vTaskSetSchedulerCoreMask( UBaseType_t uxCoreMask ) PRIVILEGED_FUNCTION; + +/** + * @brief Gets the current global scheduler core mask. + * + * @return Bitmask where bit N = 1 means core N is currently allowed to run + * non-idle tasks. + * + * Example usage: + * + * UBaseType_t uxMask = uxTaskGetSchedulerCoreMask(); + */ + UBaseType_t uxTaskGetSchedulerCoreMask( void ) PRIVILEGED_FUNCTION; +#endif + #if ( configUSE_TASK_PREEMPTION_DISABLE == 1 ) /** diff --git a/tasks.c b/tasks.c index c596c475f8..9e210b7824 100644 --- a/tasks.c +++ b/tasks.c @@ -511,6 +511,14 @@ PRIVILEGED_DATA static UBaseType_t uxTaskNumber = ( UBaseType_t ) 0U; PRIVILEGED_DATA static volatile TickType_t xNextTaskUnblockTime = ( TickType_t ) 0U; /* Initialised to portMAX_DELAY before the scheduler starts. */ PRIVILEGED_DATA static TaskHandle_t xIdleTaskHandles[ configNUMBER_OF_CORES ]; /**< Holds the handles of the idle tasks. The idle tasks are created automatically when the scheduler is started. */ +#if ( ( configNUMBER_OF_CORES > 1 ) && ( configUSE_SCHEDULER_CORE_MASK == 1 ) ) + /* Global scheduler core mask. Bit N = 1 means core N is allowed to run + * non-idle tasks. Defaults to all cores enabled. Use + * vTaskSetSchedulerCoreMask() / uxTaskGetSchedulerCoreMask() to change it + * at run time. */ + PRIVILEGED_DATA static volatile UBaseType_t uxSchedulerCoreMask = ( UBaseType_t ) ( ( 1UL << configNUMBER_OF_CORES ) - 1UL ); +#endif /* #if ( ( configNUMBER_OF_CORES > 1 ) && ( configUSE_SCHEDULER_CORE_MASK == 1 ) ) */ + /* Improve support for OpenOCD. The kernel tracks Ready tasks via priority lists. * For tracking the state of remote threads, OpenOCD uses uxTopUsedPriority * to determine the number of priority lists to read back from the remote target. */ @@ -941,12 +949,19 @@ static void prvAddNewTaskToReadyList( TCB_t * pxNewTCB ) PRIVILEGED_FUNCTION; if( ( pxTCB->uxCoreAffinityMask & ( ( UBaseType_t ) 1U << ( UBaseType_t ) xCoreID ) ) != 0U ) #endif { - #if ( configUSE_TASK_PREEMPTION_DISABLE == 1 ) - if( pxCurrentTCBs[ xCoreID ]->xPreemptionDisable == pdFALSE ) + #if ( configUSE_SCHEDULER_CORE_MASK == 1 ) + /* Non-idle tasks may not be scheduled on disabled cores. */ + if( ( ( pxTCB->uxTaskAttributes & taskATTRIBUTE_IS_IDLE ) != 0U ) || + ( ( uxSchedulerCoreMask & ( ( UBaseType_t ) 1U << ( UBaseType_t ) xCoreID ) ) != 0U ) ) #endif { - xLowestPriorityToPreempt = xCurrentCoreTaskPriority; - xLowestPriorityCore = xCoreID; + #if ( configUSE_TASK_PREEMPTION_DISABLE == 1 ) + if( pxCurrentTCBs[ xCoreID ]->xPreemptionDisable == pdFALSE ) + #endif + { + xLowestPriorityToPreempt = xCurrentCoreTaskPriority; + xLowestPriorityCore = xCoreID; + } } } } @@ -1090,14 +1105,21 @@ static void prvAddNewTaskToReadyList( TCB_t * pxNewTCB ) PRIVILEGED_FUNCTION; if( ( pxTCB->uxCoreAffinityMask & ( ( UBaseType_t ) 1U << ( UBaseType_t ) xCoreID ) ) != 0U ) #endif { - /* If the task is not being executed by any core swap it in. */ - pxCurrentTCBs[ xCoreID ]->xTaskRunState = taskTASK_NOT_RUNNING; - #if ( configUSE_CORE_AFFINITY == 1 ) - pxPreviousTCB = pxCurrentTCBs[ xCoreID ]; + #if ( configUSE_SCHEDULER_CORE_MASK == 1 ) + /* Non-idle tasks may not run on scheduler-disabled cores. */ + if( ( ( pxTCB->uxTaskAttributes & taskATTRIBUTE_IS_IDLE ) != 0U ) || + ( ( uxSchedulerCoreMask & ( ( UBaseType_t ) 1U << ( UBaseType_t ) xCoreID ) ) != 0U ) ) #endif - pxTCB->xTaskRunState = xCoreID; - pxCurrentTCBs[ xCoreID ] = pxTCB; - xTaskScheduled = pdTRUE; + { + /* If the task is not being executed by any core swap it in. */ + pxCurrentTCBs[ xCoreID ]->xTaskRunState = taskTASK_NOT_RUNNING; + #if ( configUSE_CORE_AFFINITY == 1 ) + pxPreviousTCB = pxCurrentTCBs[ xCoreID ]; + #endif + pxTCB->xTaskRunState = xCoreID; + pxCurrentTCBs[ xCoreID ] = pxTCB; + xTaskScheduled = pdTRUE; + } } } else if( pxTCB == pxCurrentTCBs[ xCoreID ] ) @@ -1108,9 +1130,16 @@ static void prvAddNewTaskToReadyList( TCB_t * pxNewTCB ) PRIVILEGED_FUNCTION; if( ( pxTCB->uxCoreAffinityMask & ( ( UBaseType_t ) 1U << ( UBaseType_t ) xCoreID ) ) != 0U ) #endif { - /* The task is already running on this core, mark it as scheduled. */ - pxTCB->xTaskRunState = xCoreID; - xTaskScheduled = pdTRUE; + #if ( configUSE_SCHEDULER_CORE_MASK == 1 ) + /* Non-idle tasks may not continue on scheduler-disabled cores. */ + if( ( ( pxTCB->uxTaskAttributes & taskATTRIBUTE_IS_IDLE ) != 0U ) || + ( ( uxSchedulerCoreMask & ( ( UBaseType_t ) 1U << ( UBaseType_t ) xCoreID ) ) != 0U ) ) + #endif + { + /* The task is already running on this core, mark it as scheduled. */ + pxTCB->xTaskRunState = xCoreID; + xTaskScheduled = pdTRUE; + } } } else @@ -3099,6 +3128,62 @@ static void prvInitialiseNewTask( TaskFunction_t pxTaskCode, /*-----------------------------------------------------------*/ +#if ( ( configNUMBER_OF_CORES > 1 ) && ( configUSE_SCHEDULER_CORE_MASK == 1 ) ) + void vTaskSetSchedulerCoreMask( UBaseType_t uxCoreMask ) + { + BaseType_t xCoreID; + UBaseType_t uxOldMask; + UBaseType_t uxDisabledCores; + + taskENTER_CRITICAL(); + { + uxOldMask = uxSchedulerCoreMask; + + /* Clamp to the number of physical cores so stray bits are ignored. */ + uxSchedulerCoreMask = uxCoreMask & ( UBaseType_t ) ( ( 1UL << configNUMBER_OF_CORES ) - 1UL ); + + if( xSchedulerRunning != pdFALSE ) + { + /* For each core that was just disabled (was allowed, now disallowed), + * yield it immediately if it is running a non-idle task so the + * scheduler re-selects the idle task on that core. */ + uxDisabledCores = uxOldMask & ~uxSchedulerCoreMask; + + for( xCoreID = ( BaseType_t ) 0; xCoreID < ( BaseType_t ) configNUMBER_OF_CORES; xCoreID++ ) + { + if( ( uxDisabledCores & ( ( UBaseType_t ) 1U << ( UBaseType_t ) xCoreID ) ) != 0U ) + { + if( ( pxCurrentTCBs[ xCoreID ]->uxTaskAttributes & taskATTRIBUTE_IS_IDLE ) == 0U ) + { + prvYieldCore( xCoreID ); + } + } + } + } + } + taskEXIT_CRITICAL(); + } +#endif /* #if ( ( configNUMBER_OF_CORES > 1 ) && ( configUSE_SCHEDULER_CORE_MASK == 1 ) ) */ + +/*-----------------------------------------------------------*/ + +#if ( ( configNUMBER_OF_CORES > 1 ) && ( configUSE_SCHEDULER_CORE_MASK == 1 ) ) + UBaseType_t uxTaskGetSchedulerCoreMask( void ) + { + UBaseType_t uxCoreMask; + + portBASE_TYPE_ENTER_CRITICAL(); + { + uxCoreMask = uxSchedulerCoreMask; + } + portBASE_TYPE_EXIT_CRITICAL(); + + return uxCoreMask; + } +#endif /* #if ( ( configNUMBER_OF_CORES > 1 ) && ( configUSE_SCHEDULER_CORE_MASK == 1 ) ) */ + +/*-----------------------------------------------------------*/ + #if ( configUSE_TASK_PREEMPTION_DISABLE == 1 ) void vTaskPreemptionDisable( const TaskHandle_t xTask ) From eb5f4ad19782ab74582ab1fc753c1a6721135fe4 Mon Sep 17 00:00:00 2001 From: Kuoping Hsu Date: Fri, 15 May 2026 16:34:33 +0800 Subject: [PATCH 2/4] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- include/FreeRTOS.h | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/include/FreeRTOS.h b/include/FreeRTOS.h index b295ef50b0..729e7c2497 100644 --- a/include/FreeRTOS.h +++ b/include/FreeRTOS.h @@ -513,7 +513,11 @@ #endif /* configUSE_CORE_AFFINITY */ #ifndef configUSE_SCHEDULER_CORE_MASK - #define configUSE_SCHEDULER_CORE_MASK 1 + #if ( configNUMBER_OF_CORES > 1 ) + #define configUSE_SCHEDULER_CORE_MASK 1 + #else + #define configUSE_SCHEDULER_CORE_MASK 0 + #endif #endif /* configUSE_SCHEDULER_CORE_MASK */ #if ( ( configNUMBER_OF_CORES > 1 ) && ( configUSE_CORE_AFFINITY == 1 ) ) From 4f32c359efdff594213c7d1a6404b7d1a13f2742 Mon Sep 17 00:00:00 2001 From: Kuoping Hsu Date: Fri, 15 May 2026 20:27:13 +0800 Subject: [PATCH 3/4] Add tracing macros for core scheduling functions and enhance documentation --- include/FreeRTOS.h | 16 ++++++++++++++++ include/task.h | 15 ++++++++++++--- tasks.c | 8 ++++++++ 3 files changed, 36 insertions(+), 3 deletions(-) diff --git a/include/FreeRTOS.h b/include/FreeRTOS.h index 729e7c2497..ec6ea5940b 100644 --- a/include/FreeRTOS.h +++ b/include/FreeRTOS.h @@ -1854,6 +1854,22 @@ #define traceRETURN_vTaskCoreAffinityGet( uxCoreAffinityMask ) #endif +#ifndef traceENTER_vTaskSetSchedulerCoreMask + #define traceENTER_vTaskSetSchedulerCoreMask( uxCoreMask ) +#endif + +#ifndef traceRETURN_vTaskSetSchedulerCoreMask + #define traceRETURN_vTaskSetSchedulerCoreMask() +#endif + +#ifndef traceENTER_uxTaskGetSchedulerCoreMask + #define traceENTER_uxTaskGetSchedulerCoreMask() +#endif + +#ifndef traceRETURN_uxTaskGetSchedulerCoreMask + #define traceRETURN_uxTaskGetSchedulerCoreMask( uxCoreMask ) +#endif + #ifndef traceENTER_vTaskPreemptionDisable #define traceENTER_vTaskPreemptionDisable( xTask ) #endif diff --git a/include/task.h b/include/task.h index 048d043a14..317a985c4f 100644 --- a/include/task.h +++ b/include/task.h @@ -1425,9 +1425,18 @@ BaseType_t xTaskResumeFromISR( TaskHandle_t xTaskToResume ) PRIVILEGED_FUNCTION; /** * * Controls which cores are allowed to run non-idle tasks system-wide. - * Bit N = 1 means core N may run tasks; bit N = 0 means core N will only - * run its idle task. configNUMBER_OF_CORES must be greater than 1 for this - * function to be available. + * Bit N = 1 means core N may run non-idle tasks; bit N = 0 means core N will + * only run its idle task. configNUMBER_OF_CORES must be greater than 1 for + * this function to be available. + * + * Masking a core (including core 0) does NOT power it off or stop its tick + * ISR and scheduler from executing. All cores remain active; the mask only + * controls whether the scheduler may dispatch a non-idle task onto a core. + * A masked core continues to service its tick interrupt and enters the + * scheduler normally, but will always be assigned the idle task. + * + * Passing 0 as the mask is valid; every core will run only its idle task + * until a new mask is applied. * * If a core that is currently running a non-idle task becomes disabled by * the new mask, it is yielded immediately so the scheduler can replace the diff --git a/tasks.c b/tasks.c index 9e210b7824..d9fb2fc791 100644 --- a/tasks.c +++ b/tasks.c @@ -3135,6 +3135,8 @@ static void prvInitialiseNewTask( TaskFunction_t pxTaskCode, UBaseType_t uxOldMask; UBaseType_t uxDisabledCores; + traceENTER_vTaskSetSchedulerCoreMask( uxCoreMask ); + taskENTER_CRITICAL(); { uxOldMask = uxSchedulerCoreMask; @@ -3162,6 +3164,8 @@ static void prvInitialiseNewTask( TaskFunction_t pxTaskCode, } } taskEXIT_CRITICAL(); + + traceRETURN_vTaskSetSchedulerCoreMask(); } #endif /* #if ( ( configNUMBER_OF_CORES > 1 ) && ( configUSE_SCHEDULER_CORE_MASK == 1 ) ) */ @@ -3172,12 +3176,16 @@ static void prvInitialiseNewTask( TaskFunction_t pxTaskCode, { UBaseType_t uxCoreMask; + traceENTER_uxTaskGetSchedulerCoreMask(); + portBASE_TYPE_ENTER_CRITICAL(); { uxCoreMask = uxSchedulerCoreMask; } portBASE_TYPE_EXIT_CRITICAL(); + traceRETURN_uxTaskGetSchedulerCoreMask( uxCoreMask ); + return uxCoreMask; } #endif /* #if ( ( configNUMBER_OF_CORES > 1 ) && ( configUSE_SCHEDULER_CORE_MASK == 1 ) ) */ From 54c3e971da6dbd6e11e48eef400add6dddbeec22 Mon Sep 17 00:00:00 2001 From: Kuoping Hsu Date: Fri, 15 May 2026 20:37:20 +0800 Subject: [PATCH 4/4] Fix undefined behavior in uxSchedulerCoreMask initialization and clamping logic --- tasks.c | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/tasks.c b/tasks.c index d9fb2fc791..8e5abb8674 100644 --- a/tasks.c +++ b/tasks.c @@ -515,8 +515,14 @@ PRIVILEGED_DATA static TaskHandle_t xIdleTaskHandles[ configNUMBER_OF_CORES ]; /* Global scheduler core mask. Bit N = 1 means core N is allowed to run * non-idle tasks. Defaults to all cores enabled. Use * vTaskSetSchedulerCoreMask() / uxTaskGetSchedulerCoreMask() to change it - * at run time. */ - PRIVILEGED_DATA static volatile UBaseType_t uxSchedulerCoreMask = ( UBaseType_t ) ( ( 1UL << configNUMBER_OF_CORES ) - 1UL ); + * at run time. + * + * The mask is derived by right-shifting ~0 rather than left-shifting 1, + * because left-shifting by a value equal to the type width is undefined + * behaviour in C (C11 ยง6.5.7). Using UBaseType_t throughout also avoids + * the assumption that unsigned long is at least as wide as UBaseType_t. */ + PRIVILEGED_DATA static volatile UBaseType_t uxSchedulerCoreMask = + ( ( UBaseType_t ) ( ~( UBaseType_t ) 0U ) >> ( ( sizeof( UBaseType_t ) * ( size_t ) 8U ) - ( size_t ) configNUMBER_OF_CORES ) ); #endif /* #if ( ( configNUMBER_OF_CORES > 1 ) && ( configUSE_SCHEDULER_CORE_MASK == 1 ) ) */ /* Improve support for OpenOCD. The kernel tracks Ready tasks via priority lists. @@ -3141,8 +3147,12 @@ static void prvInitialiseNewTask( TaskFunction_t pxTaskCode, { uxOldMask = uxSchedulerCoreMask; - /* Clamp to the number of physical cores so stray bits are ignored. */ - uxSchedulerCoreMask = uxCoreMask & ( UBaseType_t ) ( ( 1UL << configNUMBER_OF_CORES ) - 1UL ); + /* Clamp to the number of physical cores so stray bits are ignored. + * The valid-core mask is derived by right-shifting ~0 to avoid + * left-shift UB and unsigned long width assumptions (see the + * uxSchedulerCoreMask initialiser for a full explanation). */ + uxSchedulerCoreMask = uxCoreMask & + ( ( UBaseType_t ) ( ~( UBaseType_t ) 0U ) >> ( ( sizeof( UBaseType_t ) * ( size_t ) 8U ) - ( size_t ) configNUMBER_OF_CORES ) ); if( xSchedulerRunning != pdFALSE ) {