From b4dd32772ec9bf86969abea24d6a8dc8a03a28a0 Mon Sep 17 00:00:00 2001 From: "Ching-Hsin,Lee" Date: Mon, 1 Jun 2026 13:32:27 +0800 Subject: [PATCH] Add CMock+Unity unit tests for FreeRTOS-Kernel PR #1418 Add a new SMP test suite scheduler_core_mask covering the vTaskSetSchedulerCoreMask() and uxTaskGetSchedulerCoreMask() APIs introduced by https://github.com/FreeRTOS/FreeRTOS-Kernel/pull/1418. Also add taskTASK_SCHEDULED_TO_YIELD alias to global_vars.h so that test files can reference the same macro name used in tasks.c. --- FreeRTOS/Test/CMock/smp/Makefile | 3 + FreeRTOS/Test/CMock/smp/global_vars.h | 1 + .../smp/scheduler_core_mask/FreeRTOSConfig.h | 159 ++++++++ .../CMock/smp/scheduler_core_mask/Makefile | 46 +++ .../covg_scheduler_core_mask_utest.c | 386 ++++++++++++++++++ .../scheduler_core_mask_utest.c | 383 +++++++++++++++++ 6 files changed, 978 insertions(+) create mode 100644 FreeRTOS/Test/CMock/smp/scheduler_core_mask/FreeRTOSConfig.h create mode 100644 FreeRTOS/Test/CMock/smp/scheduler_core_mask/Makefile create mode 100644 FreeRTOS/Test/CMock/smp/scheduler_core_mask/covg_scheduler_core_mask_utest.c create mode 100644 FreeRTOS/Test/CMock/smp/scheduler_core_mask/scheduler_core_mask_utest.c diff --git a/FreeRTOS/Test/CMock/smp/Makefile b/FreeRTOS/Test/CMock/smp/Makefile index 948942e367a..3750d12e8ff 100644 --- a/FreeRTOS/Test/CMock/smp/Makefile +++ b/FreeRTOS/Test/CMock/smp/Makefile @@ -16,6 +16,9 @@ SUITES += multiple_priorities_no_timeslice_mock # SUITS for configASSERT SUITES += config_assert +# SUITE for configUSE_SCHEDULER_CORE_MASK +SUITES += scheduler_core_mask + # PROJECT and SUITE variables are determined based on path like so: # $(UT_ROOT_DIR)/$(PROJECT)/$(SUITE) PROJECT := $(lastword $(subst /, ,$(dir $(abspath $(MAKEFILE_ABSPATH))))) diff --git a/FreeRTOS/Test/CMock/smp/global_vars.h b/FreeRTOS/Test/CMock/smp/global_vars.h index 377cc274f9e..fe3f1cb9d43 100644 --- a/FreeRTOS/Test/CMock/smp/global_vars.h +++ b/FreeRTOS/Test/CMock/smp/global_vars.h @@ -52,6 +52,7 @@ /* Indicates that the task is actively running but scheduled to yield. */ #define taskTASK_YIELDING ( TaskRunning_t ) ( -2 ) +#define taskTASK_SCHEDULED_TO_YIELD taskTASK_YIELDING #if ( configUSE_16_BIT_TICKS == 1 ) #define taskEVENT_LIST_ITEM_VALUE_IN_USE 0x8000U diff --git a/FreeRTOS/Test/CMock/smp/scheduler_core_mask/FreeRTOSConfig.h b/FreeRTOS/Test/CMock/smp/scheduler_core_mask/FreeRTOSConfig.h new file mode 100644 index 00000000000..ff11d409b09 --- /dev/null +++ b/FreeRTOS/Test/CMock/smp/scheduler_core_mask/FreeRTOSConfig.h @@ -0,0 +1,159 @@ +/* + * FreeRTOS V202212.00 + * Copyright (C) 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * https://www.FreeRTOS.org + * https://github.com/FreeRTOS + * + */ + + +#ifndef FREERTOS_CONFIG_H +#define FREERTOS_CONFIG_H + +#include "fake_assert.h" + +/*----------------------------------------------------------- +* Application specific definitions. +* +* These definitions should be adjusted for your particular hardware and +* application requirements. +* +* THESE PARAMETERS ARE DESCRIBED WITHIN THE 'CONFIGURATION' SECTION OF THE +* FreeRTOS API DOCUMENTATION AVAILABLE ON THE FreeRTOS.org WEB SITE. See +* https://www.FreeRTOS.org/a00110.html +*----------------------------------------------------------*/ + +/* SMP test specific configuration */ +#define configRUN_MULTIPLE_PRIORITIES 1 +#define configNUMBER_OF_CORES 2 +#define configUSE_CORE_AFFINITY 1 +#define configUSE_TIME_SLICING 0 +#define configUSE_TASK_PREEMPTION_DISABLE 0 +#define configUSE_SCHEDULER_CORE_MASK 1 +#define configTICK_CORE 0 + +/* OS Configuration */ +#define configUSE_PREEMPTION 1 +#define configUSE_PORT_OPTIMISED_TASK_SELECTION 0 +#define configUSE_IDLE_HOOK 0 +#define configUSE_TICK_HOOK 0 +#define configUSE_DAEMON_TASK_STARTUP_HOOK 1 +#define configTICK_RATE_HZ ( 1000 ) +#define configMINIMAL_STACK_SIZE ( ( unsigned short ) 70 ) +#define configTOTAL_HEAP_SIZE ( ( size_t ) ( 52 * 1024 ) ) +#define configMAX_TASK_NAME_LEN ( 12 ) +#define configUSE_TRACE_FACILITY 1 +#define configUSE_16_BIT_TICKS 0 +#define configIDLE_SHOULD_YIELD 1 +#define configUSE_MUTEXES 1 +#define configCHECK_FOR_STACK_OVERFLOW 0 +#define configUSE_RECURSIVE_MUTEXES 1 +#define configQUEUE_REGISTRY_SIZE 20 +#define configUSE_MALLOC_FAILED_HOOK 1 +#define configUSE_APPLICATION_TASK_TAG 1 +#define configUSE_COUNTING_SEMAPHORES 1 +#define configUSE_ALTERNATIVE_API 0 +#define configUSE_QUEUE_SETS 1 +#define configUSE_TASK_NOTIFICATIONS 1 +#define configTASK_NOTIFICATION_ARRAY_ENTRIES 5 +#define configSUPPORT_STATIC_ALLOCATION 1 +#define configSUPPORT_DYNAMIC_ALLOCATION 1 +#define configINITIAL_TICK_COUNT ( ( TickType_t ) 0 ) +#define configSTREAM_BUFFER_TRIGGER_LEVEL_TEST_MARGIN 1 +#define portREMOVE_STATIC_QUALIFIER 1 +#define portCRITICAL_NESTING_IN_TCB 1 +#define portSTACK_GROWTH ( 1 ) +#define configUSE_MINIMAL_IDLE_HOOK 0 /* Keep this for backward compatibility. */ +#define configUSE_PASSIVE_IDLE_HOOK 0 + +#define configNUM_THREAD_LOCAL_STORAGE_POINTERS 4 + +/* Software timer related configuration options. */ +#define configUSE_TIMERS 1 +#define configTIMER_TASK_PRIORITY ( configMAX_PRIORITIES - 1 ) +#define configTIMER_QUEUE_LENGTH 20 +#define configTIMER_TASK_STACK_DEPTH ( configMINIMAL_STACK_SIZE * 2 ) + +#define configMAX_PRIORITIES ( 7 ) + +/* Run time stats gathering configuration options. */ +unsigned long ulGetRunTimeCounterValue( void ); /* Prototype of function that returns run time counter. */ +void vConfigureTimerForRunTimeStats( void ); /* Prototype of function that initialises the run time counter. */ +#define configGENERATE_RUN_TIME_STATS 0 +#define portGET_RUN_TIME_COUNTER_VALUE() ulGetRunTimeCounterValue() +#define portUSING_MPU_WRAPPERS 0 +#define portHAS_STACK_OVERFLOW_CHECKING 0 +#define configENABLE_MPU 0 + +/* Co-routine related configuration options. */ +#define configUSE_CO_ROUTINES 0 +#define configMAX_CO_ROUTINE_PRIORITIES ( 2 ) + +/* This demo makes use of one or more example stats formatting functions. These + * format the raw data provided by the uxTaskGetSystemState() function in to human + * readable ASCII form. See the notes in the implementation of vTaskList() within + * FreeRTOS/Source/tasks.c for limitations. */ +#define configUSE_STATS_FORMATTING_FUNCTIONS 1 + +/* Set the following definitions to 1 to include the API function, or zero + * to exclude the API function. In most cases the linker will remove unused + * functions anyway. */ +#define INCLUDE_vTaskPrioritySet 1 +#define INCLUDE_uxTaskPriorityGet 1 +#define INCLUDE_vTaskDelete 1 +#define INCLUDE_vTaskCleanUpResources 0 +#define INCLUDE_vTaskSuspend 1 +#define INCLUDE_vTaskDelayUntil 1 +#define INCLUDE_vTaskDelay 1 +#define INCLUDE_uxTaskGetStackHighWaterMark 1 +#define INCLUDE_xTaskGetSchedulerState 1 +#define INCLUDE_xTimerGetTimerDaemonTaskHandle 1 +#define INCLUDE_xTaskGetIdleTaskHandle 1 +#define INCLUDE_xTaskGetCurrentTaskHandle 1 +#define INCLUDE_xTaskGetHandle 1 +#define INCLUDE_eTaskGetState 1 +#define INCLUDE_xSemaphoreGetMutexHolder 1 +#define INCLUDE_xTimerPendFunctionCall 1 +#define INCLUDE_xTaskAbortDelay 1 +#define INCLUDE_xTaskGetCurrentTaskHandle 1 + +/* It is a good idea to define configASSERT() while developing. configASSERT() + * uses the same semantics as the standard C assert() macro. */ +#define configASSERT( x ) \ + do \ + { \ + if( x ) \ + { \ + vFakeAssert( true, __FILE__, __LINE__ ); \ + } \ + else \ + { \ + vFakeAssert( false, __FILE__, __LINE__ ); \ + } \ + } while( 0 ) + +#define configINCLUDE_MESSAGE_BUFFER_AMP_DEMO 0 +#if ( configINCLUDE_MESSAGE_BUFFER_AMP_DEMO == 1 ) + extern void vGenerateCoreBInterrupt( void * xUpdatedMessageBuffer ); + #define sbSEND_COMPLETED( pxStreamBuffer ) vGenerateCoreBInterrupt( pxStreamBuffer ) +#endif /* configINCLUDE_MESSAGE_BUFFER_AMP_DEMO */ + +#endif /* FREERTOS_CONFIG_H */ diff --git a/FreeRTOS/Test/CMock/smp/scheduler_core_mask/Makefile b/FreeRTOS/Test/CMock/smp/scheduler_core_mask/Makefile new file mode 100644 index 00000000000..820f21486d2 --- /dev/null +++ b/FreeRTOS/Test/CMock/smp/scheduler_core_mask/Makefile @@ -0,0 +1,46 @@ +# indent with spaces +.RECIPEPREFIX := $(.RECIPEPREFIX) $(.RECIPEPREFIX) + +# Do not move this line below the include +MAKEFILE_ABSPATH := $(abspath $(lastword $(MAKEFILE_LIST))) +include ../../makefile.in + +# PROJECT_SRC lists the .c files under test +PROJECT_SRC := tasks.c + +# PROJECT_DEPS_SRC lists the .c files that are dependencies of PROJECT_SRC files +# Files in PROJECT_DEPS_SRC are excluded from coverage measurements +PROJECT_DEPS_SRC := list.c queue.c + +# PROJECT_HEADER_DEPS: headers that should be excluded from coverage measurements. +PROJECT_HEADER_DEPS := FreeRTOS.h + +# SUITE_UT_SRC: .c files that contain test cases (must end in _utest.c) +# Functional file listed first, coverage file second. +SUITE_UT_SRC := scheduler_core_mask_utest.c covg_scheduler_core_mask_utest.c + +# SUITE_SUPPORT_SRC: .c files used for testing that do not contain test cases. +# Paths are relative to PROJECT_DIR +SUITE_SUPPORT_SRC := smp_utest_common.c + +# List the headers used by PROJECT_SRC that you would like to mock +MOCK_FILES_FP += $(KERNEL_DIR)/include/timers.h +MOCK_FILES_FP += $(UT_ROOT_DIR)/config/fake_assert.h +MOCK_FILES_FP += $(UT_ROOT_DIR)/config/fake_port.h + +# List any additional flags needed by the preprocessor +CPPFLAGS += + +# List any additional flags needed by the compiler +CFLAGS += + +# Try not to edit beyond this line unless necessary. + +# Project is determined based on path: $(UT_ROOT_DIR)/$(PROJECT) +PROJECT := $(lastword $(subst /, ,$(dir $(abspath $(MAKEFILE_ABSPATH)/../)))) +SUITE := $(lastword $(subst /, ,$(dir $(MAKEFILE_ABSPATH)))) + +# Make variables available to included makefile +export + +include ../../testdir.mk diff --git a/FreeRTOS/Test/CMock/smp/scheduler_core_mask/covg_scheduler_core_mask_utest.c b/FreeRTOS/Test/CMock/smp/scheduler_core_mask/covg_scheduler_core_mask_utest.c new file mode 100644 index 00000000000..1f37318623a --- /dev/null +++ b/FreeRTOS/Test/CMock/smp/scheduler_core_mask/covg_scheduler_core_mask_utest.c @@ -0,0 +1,386 @@ +/* + * FreeRTOS V202212.00 + * Copyright (C) 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * https://www.FreeRTOS.org + * https://github.com/FreeRTOS + * + */ +/*! @file covg_scheduler_core_mask_utest.c */ + +/* C runtime includes. */ +#include +#include +#include +#include + +/* FreeRTOS includes. */ +#include "FreeRTOS.h" +#include "FreeRTOSConfig.h" +#include "task.h" + +/* Test includes. */ +#include "unity.h" +#include "unity_memory.h" +#include "../global_vars.h" +#include "../smp_utest_common.h" + +/* Mock includes. */ +#include "mock_timers.h" +#include "mock_fake_assert.h" +#include "mock_fake_port.h" + +/* =========================== EXTERN VARIABLES =========================== */ +/* Verified with: grep -n "" $KERNEL_DIR/tasks.c */ +extern volatile UBaseType_t uxSchedulerCoreMask; +extern volatile TCB_t * pxCurrentTCBs[ configNUMBER_OF_CORES ]; +extern volatile BaseType_t xSchedulerRunning; +extern List_t pxReadyTasksLists[ configMAX_PRIORITIES ]; +extern volatile BaseType_t xYieldPendings[ configNUMBER_OF_CORES ]; +extern TaskHandle_t xIdleTaskHandles[ configNUMBER_OF_CORES ]; + +/* =========================== EXTERN FUNCTIONS =========================== */ +extern void prvSelectHighestPriorityTask( BaseType_t xCoreID ); + +/* ============================ Global VARIABLES ========================== */ +static StaticTask_t xIdleTaskTCBs[ configNUMBER_OF_CORES ]; +static StackType_t uxIdleTaskStacks[ configNUMBER_OF_CORES ][ configMINIMAL_STACK_SIZE ]; + +/* =========================== Core-count macros =========================== */ +/* All valid core bits set: e.g. 0x3 for 2 cores, 0xFFFF for 16 cores. */ +#define ALL_CORES_MASK ( ( UBaseType_t ) ( ( 1U << configNUMBER_OF_CORES ) - 1U ) ) +/* Highest valid core index. */ +#define LAST_CORE ( ( BaseType_t ) ( configNUMBER_OF_CORES - 1 ) ) +/* Bit for the last core. */ +#define LAST_CORE_BIT ( ( UBaseType_t ) 1U << ( UBaseType_t ) LAST_CORE ) +/* All cores enabled except the last one. */ +#define ALL_BUT_LAST_CORE ( ALL_CORES_MASK ^ LAST_CORE_BIT ) +/* First bit beyond the valid range — always triggers configASSERT. */ +#define INVALID_CORE_BIT ( ( UBaseType_t ) 1U << ( UBaseType_t ) configNUMBER_OF_CORES ) + +/* ============================ Unity Fixtures ============================ */ + +/*! called before each testcase */ +void setUp( void ) +{ + UBaseType_t uxPriority; + + commonSetUp(); + + for( uxPriority = ( UBaseType_t ) 0U; uxPriority < ( UBaseType_t ) configMAX_PRIORITIES; uxPriority++ ) + { + vListInitialise( &( pxReadyTasksLists[ uxPriority ] ) ); + } + + uxSchedulerCoreMask = ALL_CORES_MASK; + + UnityMalloc_StartTest(); +} + +/*! called after each testcase */ +void tearDown( void ) +{ + commonTearDown(); + UnityMalloc_EndTest(); +} + +/*! called at the beginning of the whole suite */ +void suiteSetUp() +{ +} + +/*! called at the end of the whole suite */ +int suiteTearDown( int numFailures ) +{ + return numFailures; +} + +/* ============================ FreeRTOS static allocate functions ======== */ + +void vApplicationGetIdleTaskMemory( StaticTask_t ** ppxIdleTaskTCBBuffer, + StackType_t ** ppxIdleTaskStackBuffer, + uint32_t * pulIdleTaskStackSize ) +{ + *ppxIdleTaskTCBBuffer = &( xIdleTaskTCBs[ 0 ] ); + *ppxIdleTaskStackBuffer = &( uxIdleTaskStacks[ 0 ][ 0 ] ); + *pulIdleTaskStackSize = configMINIMAL_STACK_SIZE; +} + +void vApplicationGetPassiveIdleTaskMemory( StaticTask_t ** ppxIdleTaskTCBBuffer, + StackType_t ** ppxIdleTaskStackBuffer, + uint32_t * pulIdleTaskStackSize, + BaseType_t xPassiveIdleTaskIndex ) +{ + *ppxIdleTaskTCBBuffer = &( xIdleTaskTCBs[ xPassiveIdleTaskIndex + 1 ] ); + *ppxIdleTaskStackBuffer = &( uxIdleTaskStacks[ xPassiveIdleTaskIndex + 1 ][ 0 ] ); + *pulIdleTaskStackSize = configMINIMAL_STACK_SIZE; +} + +/* ======================== Setup helper =================================== */ + +/* Fill pxCurrentTCBs[0..N-1] with idle-task TCBs and clear xYieldPendings. + * Tests that only need background state call this then override specific cores. */ +static void prvSetAllCoresIdle( TCB_t * pxTCBArray ) +{ + BaseType_t i; + + for( i = 0; i < ( BaseType_t ) configNUMBER_OF_CORES; i++ ) + { + memset( &pxTCBArray[ i ], 0, sizeof( TCB_t ) ); + pxTCBArray[ i ].uxTaskAttributes = taskATTRIBUTE_IS_IDLE; + pxTCBArray[ i ].xTaskRunState = ( TaskRunning_t ) i; + pxCurrentTCBs[ i ] = &pxTCBArray[ i ]; + xYieldPendings[ i ] = pdFALSE; + } +} + +/* ============================== Test Cases ============================== */ + +/** + * @brief vTaskSetSchedulerCoreMask — invalid mask fires configASSERT (tasks.c:3150 branch 1). + * + * covers tasks.c:3150 branch 1 — configASSERT false-branch when uxCoreMask has + * bits set beyond configNUMBER_OF_CORES. + * + * INVALID_CORE_BIT is always the first invalid bit regardless of core count. + * vFakeAssert_Ignore() (set by commonSetUp) absorbs the assert; execution + * continues and the mask is clamped to 0 by the next line. + */ +void test_coverage_vTaskSetSchedulerCoreMask_invalid_mask( void ) +{ + /* covers tasks.c:3150 branch 1 — configASSERT false-branch for out-of-range mask */ + TCB_t xTasks[ configNUMBER_OF_CORES ]; + + prvSetAllCoresIdle( xTasks ); + + /* INVALID_CORE_BIT == (1U << configNUMBER_OF_CORES): always beyond valid range. + * The assert fires (absorbed); the mask is clamped: INVALID_CORE_BIT & ALL_CORES_MASK == 0. */ + vTaskSetSchedulerCoreMask( INVALID_CORE_BIT ); + + TEST_ASSERT_EQUAL( ( UBaseType_t ) 0U, uxSchedulerCoreMask ); +} + +/** + * @brief vTaskSetSchedulerCoreMask — disabled core already running idle task (tasks.c:3181 branch 1). + * + * covers tasks.c:3181 branch 1 — the IS_IDLE check is false (i.e. the task IS idle), + * so prvYieldCore is NOT called for the disabled core. + * + * The last core is disabled. All cores run idle tasks so the inner IS_IDLE + * condition is false for the last core → no yield issued for it. + */ +void test_coverage_vTaskSetSchedulerCoreMask_disable_core_idle_task( void ) +{ + /* covers tasks.c:3181 branch 1 — disabled core running idle task → prvYieldCore NOT called */ + TCB_t xTasks[ configNUMBER_OF_CORES ]; + + prvSetAllCoresIdle( xTasks ); + + /* Prime uxSchedulerCoreMask via the API while scheduler is not running. */ + vTaskSetSchedulerCoreMask( ALL_CORES_MASK ); + + xSchedulerRunning = pdTRUE; + + /* Disable the last core; it runs an idle task so prvYieldCore must NOT fire. */ + vTaskSetSchedulerCoreMask( ALL_BUT_LAST_CORE ); + + TEST_ASSERT_EQUAL( ( UBaseType_t ) ALL_BUT_LAST_CORE, uxSchedulerCoreMask ); + TEST_ASSERT_EQUAL( pdFALSE, xYieldPendings[ LAST_CORE ] ); +} + +/** + * @brief vTaskSetSchedulerCoreMask — disable the current core (tasks.c:3183 branch 0). + * + * covers tasks.c:3183 branch 0 — prvYieldCore is called for the core that IS the + * current core (xCoreID == portGET_CORE_ID()), so the macro sets xYieldPendings + * instead of calling portYIELD_CORE. + * + * Functional tests always disable a remote core from core 0, taking the + * portYIELD_CORE path. This test disables core 0 while running on core 0. + */ +void test_coverage_vTaskSetSchedulerCoreMask_disable_current_core( void ) +{ + /* covers tasks.c:3183 branch 0 — same-core disable → xYieldPendings set */ + TCB_t xTasks[ configNUMBER_OF_CORES ]; + + /* All cores start as idle; override core 0 to non-idle so it triggers the + * yield path when disabled. */ + prvSetAllCoresIdle( xTasks ); + xTasks[ 0 ].uxTaskAttributes = 0; /* NOT idle */ + + vSetCurrentCore( 0 ); + + /* Prime the mask while scheduler is not running. */ + vTaskSetSchedulerCoreMask( ALL_CORES_MASK ); + + xSchedulerRunning = pdTRUE; + + /* Disable core 0 (the current core): prvYieldCore(0) fires. + * Because xCoreID == portGET_CORE_ID() == 0, the macro writes + * xYieldPendings[0] instead of calling portYIELD_CORE. */ + vTaskSetSchedulerCoreMask( ALL_CORES_MASK ^ ( UBaseType_t ) 0x1U ); + + TEST_ASSERT_EQUAL( ( UBaseType_t ) ( ALL_CORES_MASK ^ ( UBaseType_t ) 0x1U ), uxSchedulerCoreMask ); + TEST_ASSERT_EQUAL( pdTRUE, xYieldPendings[ 0 ] ); +} + +/** + * @brief vTaskSetSchedulerCoreMask — re-enable the current core (tasks.c:3188 branch 0). + * + * covers tasks.c:3188 branch 0 — prvYieldCore is called for a newly enabled core + * that IS the current core, so the macro sets xYieldPendings instead of calling + * portYIELD_CORE. + * + * Functional tests always re-enable a remote core from core 0. This test + * re-enables core 0 while running on core 0. + */ +void test_coverage_vTaskSetSchedulerCoreMask_enable_current_core( void ) +{ + /* covers tasks.c:3188 branch 0 — same-core enable → xYieldPendings set */ + TCB_t xTasks[ configNUMBER_OF_CORES ]; + + prvSetAllCoresIdle( xTasks ); + + vSetCurrentCore( 0 ); + + /* Start with core 0 disabled. */ + vTaskSetSchedulerCoreMask( ALL_CORES_MASK ^ ( UBaseType_t ) 0x1U ); + + xSchedulerRunning = pdTRUE; + + /* Re-enable core 0 (the current core): prvYieldCore(0) fires. + * Because xCoreID == portGET_CORE_ID() == 0, xYieldPendings[0] is set. */ + vTaskSetSchedulerCoreMask( ALL_CORES_MASK ); + + TEST_ASSERT_EQUAL( ALL_CORES_MASK, uxSchedulerCoreMask ); + TEST_ASSERT_EQUAL( pdTRUE, xYieldPendings[ 0 ] ); +} + +/** + * @brief prvSelectHighestPriorityTask — non-idle task skipped as preemption target + * for disabled core (tasks.c:960–961). + * + * covers tasks.c:960–961 — the IS_IDLE/mask guard in the preemption-candidate + * search loop. A non-idle task must not be considered as a preemption target + * for a scheduler-disabled core. + * + * The last core is disabled and runs a non-idle task. A high-priority task + * in the ready list must not be assigned to the last (disabled) core. + */ +void test_coverage_prvSelectHighestPriorityTask_preemption_disabled_core( void ) +{ + /* covers tasks.c:960–961 — non-idle task excluded from preemption on disabled core */ + TCB_t xTasks[ configNUMBER_OF_CORES ]; + TCB_t xHighPriorityTask = { 0 }; + + /* All cores start as idle. */ + prvSetAllCoresIdle( xTasks ); + + /* Override last core: non-idle task at priority 1 (potential preemption target). */ + xTasks[ LAST_CORE ].uxTaskAttributes = 0; /* NOT idle */ + xTasks[ LAST_CORE ].uxPriority = 1; + xTasks[ LAST_CORE ].xStateListItem.pvOwner = &xTasks[ LAST_CORE ]; + + /* High-priority task waiting to run, not yet assigned to any core. */ + xHighPriorityTask.uxTaskAttributes = 0; + xHighPriorityTask.uxPriority = 2; + xHighPriorityTask.xTaskRunState = taskTASK_NOT_RUNNING; + xHighPriorityTask.xStateListItem.pvOwner = &xHighPriorityTask; + vListInsertEnd( &( pxReadyTasksLists[ 2 ] ), &( xHighPriorityTask.xStateListItem ) ); + + /* Disable only the last core. */ + uxSchedulerCoreMask = ALL_BUT_LAST_CORE; + xSchedulerRunning = pdTRUE; + vSetCurrentCore( 0 ); + + /* Context switch on core 0: the preemption search must NOT choose the last + * (disabled) core as the target even though it runs a lower-priority task. */ + vTaskSwitchContext( 0 ); + + TEST_ASSERT_NOT_EQUAL( ( TaskRunning_t ) LAST_CORE, xHighPriorityTask.xTaskRunState ); +} + +/** + * @brief prvSelectHighestPriorityTask — NOT_RUNNING non-idle task skipped for + * disabled core (tasks.c:1117 branch 1). + * + * covers tasks.c:1117 branch 1 — IS_IDLE/mask guard in the task-assignment loop + * when the candidate task is NOT_RUNNING and the target core is disabled. + * The assignment must not execute. + */ +void test_coverage_prvSelectHighestPriorityTask_disabled_core_not_running( void ) +{ + /* covers tasks.c:1117 branch 1 — NOT_RUNNING non-idle task not assigned to disabled core */ + TCB_t xTasks[ configNUMBER_OF_CORES ]; + TCB_t xNonIdleTask = { 0 }; + + prvSetAllCoresIdle( xTasks ); + + /* A non-idle task waiting (NOT_RUNNING) in the ready list. */ + xNonIdleTask.uxTaskAttributes = 0; + xNonIdleTask.xTaskRunState = taskTASK_NOT_RUNNING; + xNonIdleTask.uxPriority = 1; + xNonIdleTask.xStateListItem.pvOwner = &xNonIdleTask; + vListInsertEnd( &( pxReadyTasksLists[ 1 ] ), &( xNonIdleTask.xStateListItem ) ); + + /* Disable only the last core. */ + uxSchedulerCoreMask = ALL_BUT_LAST_CORE; + xSchedulerRunning = pdTRUE; + vSetCurrentCore( LAST_CORE ); + + /* Schedule for the disabled last core: the non-idle NOT_RUNNING task must + * not be assigned because the core is disabled. */ + prvSelectHighestPriorityTask( LAST_CORE ); + + TEST_ASSERT_EQUAL( taskTASK_NOT_RUNNING, xNonIdleTask.xTaskRunState ); +} + +/** + * @brief prvSelectHighestPriorityTask — already-running non-idle task not continued + * on disabled core (tasks.c:1141 branch 1). + * + * covers tasks.c:1141 branch 1 — IS_IDLE/mask guard in the task-assignment loop + * for the pxTCB == pxCurrentTCBs[xCoreID] path when the core is disabled. + * The task must not be re-scheduled on the disabled core. + */ +void test_coverage_prvSelectHighestPriorityTask_disabled_core_already_running( void ) +{ + /* covers tasks.c:1141 branch 1 — non-idle current task not continued on disabled core */ + TCB_t xTasks[ configNUMBER_OF_CORES ]; + TCB_t xNonIdleTask = { 0 }; + + prvSetAllCoresIdle( xTasks ); + + /* A non-idle task waiting (NOT_RUNNING) in the ready list. */ + xNonIdleTask.uxTaskAttributes = 0; + xNonIdleTask.xTaskRunState = taskTASK_NOT_RUNNING; + xNonIdleTask.uxPriority = 1; + xNonIdleTask.xStateListItem.pvOwner = &xNonIdleTask; + vListInsertEnd( &( pxReadyTasksLists[ 1 ] ), &( xNonIdleTask.xStateListItem ) ); + + /* Disable only the last core. */ + uxSchedulerCoreMask = ALL_BUT_LAST_CORE; + xSchedulerRunning = pdTRUE; + vSetCurrentCore( LAST_CORE ); + + prvSelectHighestPriorityTask( LAST_CORE ); + + TEST_ASSERT_EQUAL( taskTASK_NOT_RUNNING, xNonIdleTask.xTaskRunState ); +} diff --git a/FreeRTOS/Test/CMock/smp/scheduler_core_mask/scheduler_core_mask_utest.c b/FreeRTOS/Test/CMock/smp/scheduler_core_mask/scheduler_core_mask_utest.c new file mode 100644 index 00000000000..021a7fec674 --- /dev/null +++ b/FreeRTOS/Test/CMock/smp/scheduler_core_mask/scheduler_core_mask_utest.c @@ -0,0 +1,383 @@ +/* + * FreeRTOS V202212.00 + * Copyright (C) 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * https://www.FreeRTOS.org + * https://github.com/FreeRTOS + * + */ +/*! @file scheduler_core_mask_utest.c */ + +/* C runtime includes. */ +#include +#include +#include +#include + +/* FreeRTOS includes. */ +#include "FreeRTOS.h" +#include "FreeRTOSConfig.h" +#include "task.h" + +/* Test includes. */ +#include "unity.h" +#include "unity_memory.h" +#include "../global_vars.h" +#include "../smp_utest_common.h" + +/* Mock includes. */ +#include "mock_timers.h" +#include "mock_fake_assert.h" +#include "mock_fake_port.h" + +/* =========================== EXTERN VARIABLES =========================== */ +/* Verified with: grep -n "uxSchedulerCoreMask" $KERNEL_DIR/tasks.c */ +extern volatile UBaseType_t uxSchedulerCoreMask; + +/* ============================ Unity Fixtures ============================ */ + +/*! called before each testcase */ +void setUp( void ) +{ + commonSetUp(); + + /* Reset the scheduler core mask to the all-enabled default (all + * configNUMBER_OF_CORES bits set) before every test so that mask state + * from a previous test cannot bleed into the next one. + * tasks.c initialises uxSchedulerCoreMask to this same value at boot. */ + uxSchedulerCoreMask = ( UBaseType_t ) ( ( 1U << configNUMBER_OF_CORES ) - 1U ); +} + +/*! called after each testcase */ +void tearDown( void ) +{ + commonTearDown(); +} + +/*! called at the beginning of the whole suite */ +void suiteSetUp() +{ +} + +/*! called at the end of the whole suite */ +int suiteTearDown( int numFailures ) +{ + return numFailures; +} + +/* ============================ FreeRTOS static allocate functions ======== */ + +void vApplicationGetIdleTaskMemory( StaticTask_t ** ppxIdleTaskTCBBuffer, + StackType_t ** ppxIdleTaskStackBuffer, + uint32_t * pulIdleTaskStackSize ) +{ + static StaticTask_t xIdleTaskTCB; + static StackType_t uxIdleTaskStack[ configMINIMAL_STACK_SIZE ]; + + *ppxIdleTaskTCBBuffer = &( xIdleTaskTCB ); + *ppxIdleTaskStackBuffer = &( uxIdleTaskStack[ 0 ] ); + *pulIdleTaskStackSize = configMINIMAL_STACK_SIZE; +} + +void vApplicationGetPassiveIdleTaskMemory( StaticTask_t ** ppxIdleTaskTCBBuffer, + StackType_t ** ppxIdleTaskStackBuffer, + uint32_t * pulIdleTaskStackSize, + BaseType_t xPassiveIdleTaskIndex ) +{ + static StaticTask_t xIdleTaskTCBs[ configNUMBER_OF_CORES - 1 ]; + static StackType_t uxIdleTaskStacks[ configNUMBER_OF_CORES - 1 ][ configMINIMAL_STACK_SIZE ]; + + *ppxIdleTaskTCBBuffer = &( xIdleTaskTCBs[ xPassiveIdleTaskIndex ] ); + *ppxIdleTaskStackBuffer = &( uxIdleTaskStacks[ xPassiveIdleTaskIndex ][ 0 ] ); + *pulIdleTaskStackSize = configMINIMAL_STACK_SIZE; +} + +/* ============================== Test Cases ============================== */ + +/** + * @brief vTaskSetSchedulerCoreMask — called before the scheduler starts. + * + * Verifies that when xSchedulerRunning is pdFALSE the function still writes + * uxSchedulerCoreMask but does not issue any yield requests. + * + * Configuration: + * #define configNUMBER_OF_CORES 2 + * #define configUSE_SCHEDULER_CORE_MASK 1 + * + * Setup: + * Scheduler is NOT started. No tasks are created. + * + * Action: + * vTaskSetSchedulerCoreMask( 0x1U ) — enable core 0 only. + * + * Expected: + * uxTaskGetSchedulerCoreMask() returns 0x1U. + * No xYieldPendings entries are set (scheduler loop is skipped entirely). + */ +void test_vTaskSetSchedulerCoreMask_before_scheduler_starts( void ) +{ + UBaseType_t uxMask; + + /* xSchedulerRunning is pdFALSE after commonSetUp — do not start scheduler. */ + + /* Call the API under test. */ + vTaskSetSchedulerCoreMask( 0x1U ); + + /* Verify the mask was stored. */ + uxMask = uxTaskGetSchedulerCoreMask(); + TEST_ASSERT_EQUAL( ( UBaseType_t ) 0x1U, uxMask ); +} + +/** + * @brief uxTaskGetSchedulerCoreMask — returns the value written by the last Set call. + * + * Verifies the round-trip: Set then Get must produce the identical value. + * + * Configuration: + * #define configNUMBER_OF_CORES 2 + * #define configUSE_SCHEDULER_CORE_MASK 1 + * + * Setup: + * Scheduler is NOT started. + * + * Action: + * Write 0x1U via vTaskSetSchedulerCoreMask, then read via + * uxTaskGetSchedulerCoreMask. + * + * Expected: + * Returned mask equals 0x1U exactly. + */ +void test_uxTaskGetSchedulerCoreMask_returns_current_mask( void ) +{ + UBaseType_t uxReturned; + + vTaskSetSchedulerCoreMask( 0x1U ); + + uxReturned = uxTaskGetSchedulerCoreMask(); + + TEST_ASSERT_EQUAL( ( UBaseType_t ) 0x1U, uxReturned ); +} + +/** + * @brief uxTaskGetSchedulerCoreMask — called while the scheduler is running. + * + * Exercises the portBASE_TYPE_ENTER_CRITICAL / portBASE_TYPE_EXIT_CRITICAL + * path inside uxTaskGetSchedulerCoreMask (tasks.c:3212–3216) with + * xSchedulerRunning == pdTRUE. + * + * Configuration: + * #define configNUMBER_OF_CORES 2 + * #define configUSE_SCHEDULER_CORE_MASK 1 + * + * Setup: + * Create configNUMBER_OF_CORES tasks and call vTaskStartScheduler() so that + * xSchedulerRunning becomes pdTRUE. + * + * Action: + * uxTaskGetSchedulerCoreMask() + * + * Expected: + * Returned mask equals (1U << configNUMBER_OF_CORES) - 1U (the + * all-cores-enabled default reset by setUp). + * No crash or lock inversion through the critical-section wrapper. + */ +void test_uxTaskGetSchedulerCoreMask_while_scheduler_running( void ) +{ + TaskHandle_t xTaskHandles[ configNUMBER_OF_CORES ] = { NULL }; + UBaseType_t uxMask; + uint32_t i; + + /* Create one task per core so the scheduler has real tasks to run. */ + for( i = 0; i < configNUMBER_OF_CORES; i++ ) + { + xTaskCreate( vSmpTestTask, "SMP Task", configMINIMAL_STACK_SIZE, NULL, 1, + &xTaskHandles[ i ] ); + } + + /* Start the scheduler — sets xSchedulerRunning = pdTRUE. */ + vTaskStartScheduler(); + + /* Call the getter while the scheduler is running: exercises the + * portBASE_TYPE_ENTER_CRITICAL / portBASE_TYPE_EXIT_CRITICAL path + * at tasks.c:3212-3216. */ + uxMask = uxTaskGetSchedulerCoreMask(); + + TEST_ASSERT_EQUAL( ( UBaseType_t ) ( ( 1U << configNUMBER_OF_CORES ) - 1U ), uxMask ); +} + +/** + * @brief vTaskSetSchedulerCoreMask — all cores enabled (no-op relative to default). + * + * With a 2-core build the initial mask is 0x3U. Setting the same mask while + * the scheduler is running must not yield any core (unchanged-core path). + * + * Configuration: + * #define configNUMBER_OF_CORES 2 + * #define configUSE_SCHEDULER_CORE_MASK 1 + * + * Setup: + * Create one user task per core then call vTaskStartScheduler. + * + * Action: + * vTaskSetSchedulerCoreMask( 0x3U ) — same as current value. + * + * Expected: + * Both tasks remain eRunning. + * uxTaskGetSchedulerCoreMask() == 0x3U. + */ +void test_vTaskSetSchedulerCoreMask_all_cores_enabled( void ) +{ + TaskHandle_t xTaskHandles[ configNUMBER_OF_CORES ] = { NULL }; + uint32_t i; + + /* Create one task per core. */ + for( i = 0; i < configNUMBER_OF_CORES; i++ ) + { + xTaskCreate( vSmpTestTask, "SMP Task", configMINIMAL_STACK_SIZE, NULL, 1, + &xTaskHandles[ i ] ); + } + + vTaskStartScheduler(); + + /* All tasks should be running. */ + for( i = 0; i < configNUMBER_OF_CORES; i++ ) + { + verifySmpTask( &xTaskHandles[ i ], eRunning, ( TaskRunning_t ) i ); + } + + /* Re-applying the same all-enabled mask must not disturb the running state. */ + vTaskSetSchedulerCoreMask( ( UBaseType_t ) ( ( 1U << configNUMBER_OF_CORES ) - 1U ) ); + + TEST_ASSERT_EQUAL( ( UBaseType_t ) ( ( 1U << configNUMBER_OF_CORES ) - 1U ), + uxTaskGetSchedulerCoreMask() ); + + for( i = 0; i < configNUMBER_OF_CORES; i++ ) + { + verifySmpTask( &xTaskHandles[ i ], eRunning, ( TaskRunning_t ) i ); + } +} + +/** + * @brief vTaskSetSchedulerCoreMask — disable core 1 while a non-idle task runs on it. + * + * When the scheduler is running and a non-idle task occupies core 1, clearing + * core 1's bit must trigger a yield so the scheduler reassigns the idle task + * to that core. + * + * Configuration: + * #define configNUMBER_OF_CORES 2 + * #define configUSE_SCHEDULER_CORE_MASK 1 + * + * Setup: + * Create configNUMBER_OF_CORES tasks; start the scheduler so each core runs + * one task. + * + * Action: + * vTaskSetSchedulerCoreMask( 0x1U ) — disable core 1. + * + * Expected: + * uxTaskGetSchedulerCoreMask() == 0x1U. + * The task previously on core 1 is no longer eRunning on core 1; core 1 is + * now running its idle task. + */ +void test_vTaskSetSchedulerCoreMask_disable_core_running_nonidle( void ) +{ + TaskHandle_t xTaskHandles[ configNUMBER_OF_CORES ] = { NULL }; + uint32_t i; + + for( i = 0; i < configNUMBER_OF_CORES; i++ ) + { + xTaskCreate( vSmpTestTask, "SMP Task", configMINIMAL_STACK_SIZE, NULL, 1, + &xTaskHandles[ i ] ); + } + + vTaskStartScheduler(); + + /* Verify initial state: tasks running on both cores. */ + for( i = 0; i < configNUMBER_OF_CORES; i++ ) + { + verifySmpTask( &xTaskHandles[ i ], eRunning, ( TaskRunning_t ) i ); + } + + /* Disable core 1 — the non-idle task there should be yielded. + * With the default sync setup the yield fires inside taskEXIT_CRITICAL() + * (vFakePortReleaseTaskLockCallback → vYieldCores) so the context switch + * has already completed when vTaskSetSchedulerCoreMask returns. */ + vTaskSetSchedulerCoreMask( 0x1U ); + + /* Mask must be updated. */ + TEST_ASSERT_EQUAL( ( UBaseType_t ) 0x1U, uxTaskGetSchedulerCoreMask() ); + + /* Core 1 must now run its idle task, not the user task. + * The scheduler picks the first available idle task (xIdleTaskHandles[0]) + * since idle tasks have tskNO_AFFINITY and [0] is first in the ready list. */ + verifyIdleTask( 0, 1 ); +} + +/** + * @brief vTaskSetSchedulerCoreMask — re-enable a previously disabled core. + * + * After masking core 1, re-enabling it should trigger a yield on core 1 so + * the scheduler immediately dispatches a ready non-idle task there. + * + * Configuration: + * #define configNUMBER_OF_CORES 2 + * #define configUSE_SCHEDULER_CORE_MASK 1 + * + * Setup: + * Start scheduler; call vTaskSetSchedulerCoreMask(0x1U) to disable core 1. + * + * Action: + * vTaskSetSchedulerCoreMask( 0x3U ) — re-enable core 1. + * + * Expected: + * uxTaskGetSchedulerCoreMask() == 0x3U. + * xTaskHandles[1] is dispatched back to core 1 (eRunning, xTaskRunState == 1). + * xTaskHandles[0] continues running on core 0 undisturbed. + */ +void test_vTaskSetSchedulerCoreMask_enable_previously_disabled_core( void ) +{ + TaskHandle_t xTaskHandles[ configNUMBER_OF_CORES ] = { NULL }; + uint32_t i; + + for( i = 0; i < configNUMBER_OF_CORES; i++ ) + { + xTaskCreate( vSmpTestTask, "SMP Task", configMINIMAL_STACK_SIZE, NULL, 1, + &xTaskHandles[ i ] ); + } + + vTaskStartScheduler(); + + /* Disable core 1. */ + vTaskSetSchedulerCoreMask( 0x1U ); + TEST_ASSERT_EQUAL( ( UBaseType_t ) 0x1U, uxTaskGetSchedulerCoreMask() ); + + /* Re-enable core 1. The implementation calls prvYieldCore(1) which triggers + * vTaskSwitchContext(1); prvSelectHighestPriorityTask must dispatch a + * non-idle task back to the now-enabled core. */ + vTaskSetSchedulerCoreMask( 0x3U ); + TEST_ASSERT_EQUAL( ( UBaseType_t ) 0x3U, uxTaskGetSchedulerCoreMask() ); + + /* Core 1 must be running a user task again, not an idle task. */ + verifySmpTask( &xTaskHandles[ 1 ], eRunning, ( TaskRunning_t ) 1 ); + + /* Core 0 must be undisturbed. */ + verifySmpTask( &xTaskHandles[ 0 ], eRunning, ( TaskRunning_t ) 0 ); +}