From 657932c563e7b6867d9d4e00d5ed62a22c788676 Mon Sep 17 00:00:00 2001 From: Kuoping Hsu Date: Fri, 15 May 2026 16:04:14 +0800 Subject: [PATCH] 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 7859a5c77..9c1ef935a 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 63e2feb51..b295ef50b 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 2a19fa73f..048d043a1 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 c596c475f..9e210b782 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 )