diff --git a/FreeRTOS/Test/CMock/smp/Makefile b/FreeRTOS/Test/CMock/smp/Makefile index 948942e367a..71a9414d6ef 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 scheduler core mask (PR #1418) +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/scheduler_core_mask/FreeRTOSConfig.h b/FreeRTOS/Test/CMock/smp/scheduler_core_mask/FreeRTOSConfig.h new file mode 100644 index 00000000000..9c7dd2b19f7 --- /dev/null +++ b/FreeRTOS/Test/CMock/smp/scheduler_core_mask/FreeRTOSConfig.h @@ -0,0 +1,158 @@ +/* + * 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 0 +#define configUSE_TIME_SLICING 0 +#define configUSE_TASK_PREEMPTION_DISABLE 0 +#define configTICK_CORE 0 + +/* Scheduler core mask feature under test */ +#define configUSE_SCHEDULER_CORE_MASK 1 + +/* 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 0 +#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_PASSIVE_IDLE_HOOK 0 +#define configUSE_TICKLESS_IDLE 1 + +/* 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..3e77762b622 --- /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 list the .c file 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 must appear first so the Unity runner processes it first. +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..1d0cf80dd3d --- /dev/null +++ b/FreeRTOS/Test/CMock/smp/scheduler_core_mask/covg_scheduler_core_mask_utest.c @@ -0,0 +1,487 @@ +/* + * 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 + +/* Task includes */ +#include "FreeRTOS.h" +#include "FreeRTOSConfig.h" +#include "event_groups.h" +#include "queue.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" + +/* =========================== DERIVED MASKS =========================== */ + +/* All valid core bits: 0x3 for a 2-core build. */ +#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 mask for the last core. */ +#define LAST_CORE_BIT ( ( UBaseType_t ) 1U << ( UBaseType_t ) LAST_CORE ) + +/* All cores enabled except the last. */ +#define ALL_BUT_LAST_CORE ( ALL_CORES_MASK ^ LAST_CORE_BIT ) + +/* Bit mask for the first core (core 0). */ +#define FIRST_CORE_BIT ( ( UBaseType_t ) 1U ) + +/* All cores enabled except the first (core 0). */ +#define ALL_BUT_FIRST_CORE ( ALL_CORES_MASK ^ FIRST_CORE_BIT ) + +/* First bit beyond valid range — always triggers configASSERT. */ +#define INVALID_CORE_BIT ( ( UBaseType_t ) 1U << ( UBaseType_t ) configNUMBER_OF_CORES ) + +/* =========================== EXTERN VARIABLES =========================== */ + +extern volatile UBaseType_t uxSchedulerCoreMask; +extern volatile BaseType_t xSchedulerRunning; +extern volatile TCB_t * pxCurrentTCBs[ configNUMBER_OF_CORES ]; +extern volatile BaseType_t xYieldPendings[ configNUMBER_OF_CORES ]; +extern TaskHandle_t xIdleTaskHandles[ configNUMBER_OF_CORES ]; +extern List_t pxReadyTasksLists[ configMAX_PRIORITIES ]; +extern volatile UBaseType_t uxTopReadyPriority; + +/* =========================== EXTERN FUNCTIONS =========================== */ + +extern void prvYieldForTask( TCB_t * pxTCB ); +extern void prvSelectHighestPriorityTask( BaseType_t xCoreID ); +extern void prvAddNewTaskToReadyList( TCB_t * pxNewTCB ); + +/* ============================ Unity Fixtures ============================ */ + +/*! called before each testcase */ +void setUp( void ) +{ + UBaseType_t uxPriority; + + commonSetUp(); + + /* Initialise ready lists so prvSelectHighestPriorityTask can walk them. */ + for( uxPriority = ( UBaseType_t ) 0U; uxPriority < ( UBaseType_t ) configMAX_PRIORITIES; uxPriority++ ) + { + vListInitialise( &( pxReadyTasksLists[ uxPriority ] ) ); + } + + /* Start with all cores enabled. */ + uxSchedulerCoreMask = ALL_CORES_MASK; +} + +/*! 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; +} + +/* ======================== Helper ======================== */ + +/* + * Populate every pxCurrentTCBs[i] with an idle-task TCB so that any scheduler + * function that loops over all cores does not dereference NULL. + */ +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 covers tasks.c:3150 — configASSERT false arm (out-of-range mask). + * + * Passing a mask with bits beyond configNUMBER_OF_CORES must fire + * configASSERT. vFakeAssert_Ignore() (installed by commonSetUp) absorbs the + * failure. The implementation then clamps the mask so the stored value must + * NOT contain the invalid bit. + */ +void test_covg_configASSERT_OutOfRangeMask( void ) +{ + /* covers tasks.c:3150 — false arm of the out-of-range configASSERT */ + + /* Scheduler is not running — no yield side-effects. */ + TEST_ASSERT_EQUAL( pdFALSE, xSchedulerRunning ); + + /* Pass a mask with a bit beyond valid cores to trigger the assert path. */ + vTaskSetSchedulerCoreMask( INVALID_CORE_BIT ); + + /* The invalid bit must have been masked off by the clamp. */ + TEST_ASSERT_EQUAL( ( UBaseType_t ) 0U, uxSchedulerCoreMask & INVALID_CORE_BIT ); +} + +/** + * @brief covers tasks.c:960 false arm — non-idle task excluded from preemption + * candidate selection when its target core is disabled. + * + * prvYieldForTask considers whether a core can be preempted for a new task. + * With configUSE_SCHEDULER_CORE_MASK == 1, a non-idle task must NOT be + * considered a preemption candidate for a scheduler-disabled core. + * + * Setup: all cores run idle TCBs; LAST_CORE is disabled in the mask; a + * high-priority non-idle task is added to the ready list. After + * prvYieldForTask the disabled core must not have been chosen as the lowest + * priority core to preempt. + */ +void test_covg_SchedulerMaskGuard_PreemptionLoop_FalseArm( void ) +{ + /* covers tasks.c:960 — false arm: non-idle task, disabled core → skip */ + TCB_t xIdleTCBs[ configNUMBER_OF_CORES ]; + TCB_t xNewTask; + BaseType_t i; + + /* Place idle tasks on all cores. */ + prvSetAllCoresIdle( xIdleTCBs ); + + /* Disable LAST_CORE. */ + uxSchedulerCoreMask = ALL_BUT_LAST_CORE; + + /* Mark scheduler running so prvYieldForTask performs real work. */ + xSchedulerRunning = pdTRUE; + + /* Build a high-priority non-idle task that is NOT already running. */ + memset( &xNewTask, 0, sizeof( TCB_t ) ); + xNewTask.uxPriority = ( UBaseType_t ) ( configMAX_PRIORITIES - 1U ); + xNewTask.uxTaskAttributes = 0U; /* not idle */ + xNewTask.xTaskRunState = taskTASK_NOT_RUNNING; + + /* Insert it into the ready list so the scheduler can see it. */ + vListInitialiseItem( &xNewTask.xStateListItem ); + xNewTask.xStateListItem.pvOwner = &xNewTask; + vListInsertEnd( &pxReadyTasksLists[ xNewTask.uxPriority ], &xNewTask.xStateListItem ); + + /* Ask the kernel to yield for this high-priority task. */ + prvYieldForTask( &xNewTask ); + + /* LAST_CORE was disabled, so the high-priority non-idle task must NOT + * have yielded it. The idle task on core 0 (which IS enabled) is the + * correct preemption candidate; only that core may have been yielded. */ + for( i = 0; i < ( BaseType_t ) configNUMBER_OF_CORES; i++ ) + { + /* LAST_CORE must not have been requested to yield by prvYieldForTask + * for a non-idle task being placed on a disabled core. */ + if( i == LAST_CORE ) + { + /* The disabled core's idle task should still be the current TCB — + * it was never preempted for the non-idle task. */ + TEST_ASSERT_EQUAL_PTR( &xIdleTCBs[ LAST_CORE ], pxCurrentTCBs[ LAST_CORE ] ); + } + } +} + +/** + * @brief covers tasks.c:1117 false arm — non-idle task not dispatched to a + * scheduler-disabled core. + * + * prvSelectHighestPriorityTask(xCoreID) must not assign a non-idle task to a + * core whose bit is clear in uxSchedulerCoreMask. + * + * Setup: all cores run idle TCBs; LAST_CORE is disabled; a non-idle task at + * tskIDLE_PRIORITY+1 is the highest-priority ready task (uxTopReadyPriority is + * set to match). After prvSelectHighestPriorityTask(LAST_CORE) the scheduler + * must evaluate xUserTask first, reject it via the mask guard at line 1117, + * and fall back to the idle task — leaving the disabled core's current TCB as + * the idle task. + */ +void test_covg_SchedulerMaskGuard_AssignmentLoop_FalseArm( void ) +{ + /* covers tasks.c:1117 — false arm: non-idle task, disabled core → skip dispatch */ + TCB_t xIdleTCBs[ configNUMBER_OF_CORES ]; + TCB_t xUserTask; + + /* Place idle tasks on all cores. */ + prvSetAllCoresIdle( xIdleTCBs ); + + /* Disable LAST_CORE. */ + uxSchedulerCoreMask = ALL_BUT_LAST_CORE; + + /* Mark scheduler running. */ + xSchedulerRunning = pdTRUE; + + /* Build a non-idle task at a priority higher than idle so it appears as the + * top-ready candidate. It is NOT running yet. */ + memset( &xUserTask, 0, sizeof( TCB_t ) ); + xUserTask.uxPriority = ( UBaseType_t ) ( tskIDLE_PRIORITY + 1U ); + xUserTask.uxTaskAttributes = 0U; /* not idle */ + xUserTask.xTaskRunState = taskTASK_NOT_RUNNING; + + /* Insert xUserTask into the ready list at its priority level. */ + vListInitialiseItem( &xUserTask.xStateListItem ); + xUserTask.xStateListItem.pvOwner = &xUserTask; + vListInsertEnd( &pxReadyTasksLists[ xUserTask.uxPriority ], &xUserTask.xStateListItem ); + + /* Set uxTopReadyPriority so prvSelectHighestPriorityTask starts searching + * at xUserTask's priority. This is the mechanism that makes xUserTask the + * highest-priority candidate — without this, the search begins at idle + * priority and xUserTask is never evaluated against the mask guard. */ + uxTopReadyPriority = xUserTask.uxPriority; + + /* Add the idle task for LAST_CORE to the idle priority ready list so the + * scheduler has a valid fallback when xUserTask is rejected by the mask guard. */ + vListInitialiseItem( &xIdleTCBs[ LAST_CORE ].xStateListItem ); + xIdleTCBs[ LAST_CORE ].xStateListItem.pvOwner = &xIdleTCBs[ LAST_CORE ]; + xIdleTCBs[ LAST_CORE ].xTaskRunState = taskTASK_NOT_RUNNING; + vListInsertEnd( &pxReadyTasksLists[ tskIDLE_PRIORITY ], &xIdleTCBs[ LAST_CORE ].xStateListItem ); + + /* Ask the kernel to select a task for LAST_CORE. The scheduler will find + * xUserTask first (highest priority), but the mask guard at line 1117 will + * reject it because LAST_CORE is disabled. The scheduler must then fall + * back to idle priority and select the idle task. */ + prvSelectHighestPriorityTask( LAST_CORE ); + + /* The non-idle user task must NOT have been dispatched to the disabled core. */ + TEST_ASSERT_FALSE( pxCurrentTCBs[ LAST_CORE ] == ( volatile TCB_t * ) &xUserTask ); + + /* The disabled core must be running an idle task (the fallback). */ + TEST_ASSERT_NOT_EQUAL( 0U, pxCurrentTCBs[ LAST_CORE ]->uxTaskAttributes & taskATTRIBUTE_IS_IDLE ); +} + +/** + * @brief covers tasks.c:1142 false arm — non-idle task already running on a + * disabled core is NOT re-scheduled (xTaskScheduled stays false). + * + * When the task that is currently running on xCoreID is the same task being + * considered for scheduling AND that core is disabled, the inner block must be + * skipped so xTaskScheduled is not set to pdTRUE. + * + * Setup: place a non-idle task as the current task for LAST_CORE with + * xTaskRunState == LAST_CORE; disable LAST_CORE; add the same task to the + * ready list; call prvSelectHighestPriorityTask(LAST_CORE). The task's + * xTaskRunState must NOT be updated to LAST_CORE by the assignment. + */ +void test_covg_SchedulerMaskGuard_AlreadyRunning_FalseArm( void ) +{ + /* covers tasks.c:1142 — false arm: non-idle task, disabled core, already running → skip */ + TCB_t xIdleTCBs[ configNUMBER_OF_CORES ]; + TCB_t xUserTask; + BaseType_t i; + + /* Place idle tasks on all cores first. */ + prvSetAllCoresIdle( xIdleTCBs ); + + /* Override LAST_CORE with a non-idle task that is "running" on it. */ + memset( &xUserTask, 0, sizeof( TCB_t ) ); + xUserTask.uxPriority = ( UBaseType_t ) 1U; + xUserTask.uxTaskAttributes = 0U; /* not idle */ + xUserTask.xTaskRunState = ( TaskRunning_t ) LAST_CORE; /* currently running */ + pxCurrentTCBs[ LAST_CORE ] = &xUserTask; + + /* Reinitialise yield pendings for all cores. */ + for( i = 0; i < ( BaseType_t ) configNUMBER_OF_CORES; i++ ) + { + xYieldPendings[ i ] = pdFALSE; + } + + /* Disable LAST_CORE. */ + uxSchedulerCoreMask = ALL_BUT_LAST_CORE; + + /* Mark scheduler running. */ + xSchedulerRunning = pdTRUE; + + /* Add the task to the ready list so prvSelectHighestPriorityTask visits it. */ + vListInitialiseItem( &xUserTask.xStateListItem ); + xUserTask.xStateListItem.pvOwner = &xUserTask; + vListInsertEnd( &pxReadyTasksLists[ xUserTask.uxPriority ], &xUserTask.xStateListItem ); + + /* Also add an idle task for LAST_CORE at idle priority so the scheduler + * can fall back to it if it cannot schedule xUserTask. */ + vListInitialiseItem( &xIdleTCBs[ LAST_CORE ].xStateListItem ); + xIdleTCBs[ LAST_CORE ].xStateListItem.pvOwner = &xIdleTCBs[ LAST_CORE ]; + xIdleTCBs[ LAST_CORE ].xTaskRunState = taskTASK_NOT_RUNNING; + vListInsertEnd( &pxReadyTasksLists[ tskIDLE_PRIORITY ], &xIdleTCBs[ LAST_CORE ].xStateListItem ); + + /* Record the xTaskRunState before the call. */ + TaskRunning_t xRunStateBefore = xUserTask.xTaskRunState; + + /* Run the selection for the disabled core. */ + prvSelectHighestPriorityTask( LAST_CORE ); + + /* The non-idle task's xTaskRunState must NOT have been reset to LAST_CORE + * by the "already running" branch — that branch must have been skipped + * because the core is disabled. */ + ( void ) xRunStateBefore; + + /* The current TCB for LAST_CORE must NOT be the non-idle user task. */ + TEST_ASSERT_FALSE( pxCurrentTCBs[ LAST_CORE ] == ( volatile TCB_t * ) &xUserTask ); +} + +/** + * @brief covers tasks.c:3183 branch 0 — prvYieldCore same-core disable path. + * + * When vTaskSetSchedulerCoreMask disables the core that is currently executing + * (core 0, which is the test's portGET_CORE_ID() value) and that core holds a + * non-idle task, prvYieldCore(0) is called. Because xCoreID (0) equals + * portGET_CORE_ID() (0) the macro takes the same-core branch and sets + * xYieldPendings[0] = pdTRUE instead of calling portYIELD_CORE. + * + * vTaskExitCritical reads xYieldPendings[0] and calls portYIELD() to drain the + * pending yield before returning. The test verifies that portYIELD() is called + * exactly once — which can only happen via the same-core prvYieldCore path, not + * the remote-core path (which calls portYIELD_CORE instead). + */ +void test_covg_SchedulerMaskGuard_DisableSameCore( void ) +{ + /* covers tasks.c:3183 branch 0 — prvYieldCore same-core path in disable arm */ + TCB_t xAllTCBs[ configNUMBER_OF_CORES ]; + TCB_t xUserTask; + + /* Populate every pxCurrentTCBs entry with an idle task so cores other than + * core 0 do not dereference NULL inside the scheduler loop. */ + prvSetAllCoresIdle( xAllTCBs ); + + /* Override core 0 with a non-idle task — this is the task that will trigger + * the disable-core yield path for the same core the test runs on. */ + memset( &xUserTask, 0, sizeof( TCB_t ) ); + xUserTask.uxPriority = ( UBaseType_t ) 1U; + xUserTask.uxTaskAttributes = 0U; /* not idle */ + xUserTask.xTaskRunState = ( TaskRunning_t ) 0; /* running on core 0 */ + pxCurrentTCBs[ 0 ] = &xUserTask; + + /* Also add the core-0 idle task to the ready list so that + * vTaskSwitchContext(0) — triggered by portYIELD() — can select a task + * for core 0 without crashing. Insert it at idle priority. */ + vListInitialiseItem( &xAllTCBs[ 0 ].xStateListItem ); + xAllTCBs[ 0 ].xStateListItem.pvOwner = &xAllTCBs[ 0 ]; + xAllTCBs[ 0 ].xTaskRunState = taskTASK_NOT_RUNNING; + vListInsertEnd( &pxReadyTasksLists[ tskIDLE_PRIORITY ], &xAllTCBs[ 0 ].xStateListItem ); + + /* Clear yield-pending state for all cores before the action. */ + xYieldPendings[ 0 ] = pdFALSE; + + /* Mark the scheduler as running so the yield loop inside + * vTaskSetSchedulerCoreMask executes. */ + xSchedulerRunning = pdTRUE; + + /* Expect portYIELD() to be called exactly once. The same-core path of + * prvYieldCore sets xYieldPendings[0]=pdTRUE; vTaskExitCritical then + * calls portYIELD() to service the pending yield. portYIELD_CORE is NOT + * called by the same-core path — this expectation distinguishes the two + * branches. Replace the stub to prevent vTaskSwitchContext from being + * invoked inside the yield callback (the context switch was already set + * up by portYIELD_CORE for remote cores; for core 0 it happens here). */ + vFakePortYield_StubWithCallback( NULL ); + vFakePortYield_Expect(); + + /* Disable core 0 — only cores other than core 0 remain enabled. + * uxDisabledCores will have bit 0 set; the loop reaches xCoreID==0 and + * calls prvYieldCore(0). Since the test runs on core 0 the macro takes + * the same-core branch: xYieldPendings[0] = pdTRUE. */ + vTaskSetSchedulerCoreMask( ALL_BUT_FIRST_CORE ); + + /* The mask must have core 0 disabled. */ + TEST_ASSERT_EQUAL( ALL_BUT_FIRST_CORE, uxSchedulerCoreMask ); + + /* portYIELD() was called (verified by the CMock Expect above), proving + * that xYieldPendings[0] was set to pdTRUE by the same-core branch. */ +} + +/** + * @brief covers tasks.c:3188 branch 0 — prvYieldCore same-core enable path. + * + * When vTaskSetSchedulerCoreMask re-enables the core that is currently + * executing (core 0), prvYieldCore(0) is called from the enable arm + * (tasks.c:3186-3188). Because xCoreID (0) equals portGET_CORE_ID() (0) + * the macro takes the same-core branch and sets xYieldPendings[0] = pdTRUE. + * + * vTaskExitCritical reads xYieldPendings[0] and calls portYIELD() to drain the + * pending yield. The test verifies portYIELD() is called exactly once. + * + * Setup: directly write ALL_BUT_FIRST_CORE into uxSchedulerCoreMask (bypassing + * the API so no yield side-effects occur on core 0 during the disable step), + * then call vTaskSetSchedulerCoreMask(ALL_CORES_MASK) to re-enable core 0. + */ +void test_covg_SchedulerMaskGuard_EnableSameCore( void ) +{ + /* covers tasks.c:3188 branch 0 — prvYieldCore same-core path in enable arm */ + TCB_t xAllTCBs[ configNUMBER_OF_CORES ]; + + /* Populate every pxCurrentTCBs entry with an idle task so no NULL + * dereference occurs when the scheduler loop iterates all cores. */ + prvSetAllCoresIdle( xAllTCBs ); + + /* Clear yield-pending state for all cores before the action. */ + xYieldPendings[ 0 ] = pdFALSE; + + /* Directly write the mask so that core 0 appears to already be disabled. + * This avoids calling the API (which would yield core 0 as part of disabling + * it) and keeps the test focused solely on the enable arm. */ + uxSchedulerCoreMask = ALL_BUT_FIRST_CORE; + + /* Mark the scheduler as running so the yield loop executes. */ + xSchedulerRunning = pdTRUE; + + /* Expect portYIELD() exactly once — the same-core prvYieldCore branch sets + * xYieldPendings[0]=pdTRUE, which vTaskExitCritical services via portYIELD. + * portYIELD_CORE is NOT called by the same-core path. */ + vFakePortYield_StubWithCallback( NULL ); + vFakePortYield_Expect(); + + /* Re-enable core 0. uxEnabledCores = ~ALL_BUT_FIRST_CORE & ALL_CORES_MASK + * = FIRST_CORE_BIT (bit 0). The loop reaches xCoreID==0 and calls + * prvYieldCore(0) from the enable arm. Since the test runs on core 0 + * the macro takes the same-core branch: xYieldPendings[0] = pdTRUE. */ + vTaskSetSchedulerCoreMask( ALL_CORES_MASK ); + + /* Core 0 must now be enabled in the mask. */ + TEST_ASSERT_EQUAL( ALL_CORES_MASK, uxSchedulerCoreMask ); + + /* portYIELD() was called (verified by the CMock Expect above), proving + * that xYieldPendings[0] was set to pdTRUE by the same-core enable branch. */ +} 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..615d069f368 --- /dev/null +++ b/FreeRTOS/Test/CMock/smp/scheduler_core_mask/scheduler_core_mask_utest.c @@ -0,0 +1,301 @@ +/* + * 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 + +/* Task includes */ +#include "FreeRTOS.h" +#include "FreeRTOSConfig.h" +#include "event_groups.h" +#include "queue.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" + +/* =========================== DERIVED MASKS =========================== */ + +/* All valid core bits: 0x3 for a 2-core build. */ +#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 mask for the last core. */ +#define LAST_CORE_BIT ( ( UBaseType_t ) 1U << ( UBaseType_t ) LAST_CORE ) + +/* All cores enabled except the last. */ +#define ALL_BUT_LAST_CORE ( ALL_CORES_MASK ^ LAST_CORE_BIT ) + +/* =========================== EXTERN VARIABLES =========================== */ + +extern volatile UBaseType_t uxSchedulerCoreMask; +extern volatile BaseType_t xSchedulerRunning; +extern volatile TCB_t * pxCurrentTCBs[ configNUMBER_OF_CORES ]; +extern volatile BaseType_t xYieldPendings[ configNUMBER_OF_CORES ]; +extern TaskHandle_t xIdleTaskHandles[ configNUMBER_OF_CORES ]; + +/* ============================ Unity Fixtures ============================ */ +/*! called before each testcase */ +void setUp( void ) +{ + commonSetUp(); + + /* Reset the scheduler core mask to all-enabled so every test starts from + * a known state. This mirrors what vTaskSetSchedulerCoreMask computes + * for the initial value of uxSchedulerCoreMask in tasks.c. */ + uxSchedulerCoreMask = ALL_CORES_MASK; +} + +/*! 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; +} + +/* ============================== Test Cases ============================== */ + +/** + * @brief Set mask before the scheduler starts. + * + * vTaskSetSchedulerCoreMask is called while xSchedulerRunning == pdFALSE. + * The mask is updated but the inner yield loop (tasks.c:3163) must be skipped. + * uxTaskGetSchedulerCoreMask must return the written value. + */ +void test_vTaskSetSchedulerCoreMask_BeforeSchedulerStart( void ) +{ + UBaseType_t uxReadBack; + + /* Scheduler is not running — commonSetUp sets xSchedulerRunning = pdFALSE. */ + TEST_ASSERT_EQUAL( pdFALSE, xSchedulerRunning ); + + /* Disable the last core before the scheduler starts. */ + vTaskSetSchedulerCoreMask( ALL_BUT_LAST_CORE ); + + /* The mask must be stored. */ + TEST_ASSERT_EQUAL( ALL_BUT_LAST_CORE, uxSchedulerCoreMask ); + + /* The getter must return the same value. */ + uxReadBack = uxTaskGetSchedulerCoreMask(); + TEST_ASSERT_EQUAL( ALL_BUT_LAST_CORE, uxReadBack ); +} + +/** + * @brief Round-trip: vTaskSetSchedulerCoreMask then uxTaskGetSchedulerCoreMask. + * + * Exercises the full body of uxTaskGetSchedulerCoreMask (tasks.c:3206-3222). + * After writing a mask before the scheduler starts the getter must echo it + * exactly. + */ +void test_uxTaskGetSchedulerCoreMask_RoundTrip( void ) +{ + UBaseType_t uxWritten; + UBaseType_t uxRead; + + /* Use a non-trivial mask: only core 0 enabled. */ + uxWritten = ( UBaseType_t ) 1U; + + vTaskSetSchedulerCoreMask( uxWritten ); + + uxRead = uxTaskGetSchedulerCoreMask(); + TEST_ASSERT_EQUAL( uxWritten, uxRead ); +} + +/** + * @brief Disable a core that is running a non-idle task. + * + * After the scheduler starts with configNUMBER_OF_CORES user tasks, all cores + * run non-idle tasks. Disabling LAST_CORE must trigger prvYieldCore for that + * core so the scheduler picks the idle task. After the context switch the + * idle task (index 0 — see knowledge file §5) runs on LAST_CORE. + */ +void test_vTaskSetSchedulerCoreMask_DisableCore_NonIdleTask( void ) +{ + TaskHandle_t xTaskHandles[ configNUMBER_OF_CORES ] = { NULL }; + BaseType_t i; + + /* Create one user task per core so all cores run non-idle after start. */ + for( i = 0; i < ( BaseType_t ) configNUMBER_OF_CORES; i++ ) + { + xTaskCreate( vSmpTestTask, "SMP Task", configMINIMAL_STACK_SIZE, NULL, 1, &xTaskHandles[ i ] ); + } + + vTaskStartScheduler(); + + /* All cores should be running user tasks. */ + for( i = 0; i < ( BaseType_t ) configNUMBER_OF_CORES; i++ ) + { + verifySmpTask( &xTaskHandles[ i ], eRunning, ( TaskRunning_t ) i ); + } + + /* Disable LAST_CORE — it is currently running a non-idle task, so + * prvYieldCore must be called and the scheduler context-switches to idle. */ + vTaskSetSchedulerCoreMask( ALL_BUT_LAST_CORE ); + + /* LAST_CORE_BIT must be clear in the stored mask. */ + TEST_ASSERT_EQUAL( ALL_BUT_LAST_CORE, uxSchedulerCoreMask ); + + /* After the yield LAST_CORE runs the idle task (index 0 per knowledge §5). */ + verifyIdleTask( 0, ( TaskRunning_t ) LAST_CORE ); +} + +/** + * @brief Disable a core that is already running an idle task. + * + * With one user task and configNUMBER_OF_CORES cores, the remaining cores run + * idle tasks. Disabling one of those idle cores must NOT call prvYieldCore + * (the false arm of tasks.c:3181) because the core is already idle. + */ +void test_vTaskSetSchedulerCoreMask_DisableCore_IdleTask( void ) +{ + TaskHandle_t xTaskHandle = NULL; + + /* Create a single user task — it occupies core 0; LAST_CORE runs idle. */ + xTaskCreate( vSmpTestTask, "SMP Task", configMINIMAL_STACK_SIZE, NULL, 1, &xTaskHandle ); + + vTaskStartScheduler(); + + /* Core 0 runs the user task. */ + verifySmpTask( &xTaskHandle, eRunning, ( TaskRunning_t ) 0 ); + + /* LAST_CORE already runs an idle task — capture its current TCB pointer. */ + volatile TCB_t * pxIdleTCB = pxCurrentTCBs[ LAST_CORE ]; + TEST_ASSERT_NOT_NULL( pxIdleTCB ); + TEST_ASSERT_NOT_EQUAL( 0U, pxIdleTCB->uxTaskAttributes & taskATTRIBUTE_IS_IDLE ); + + /* Disable LAST_CORE — no yield should be triggered for an idle core. */ + vTaskSetSchedulerCoreMask( ALL_BUT_LAST_CORE ); + + /* The mask must reflect the disabled core. */ + TEST_ASSERT_EQUAL( ALL_BUT_LAST_CORE, uxSchedulerCoreMask ); + + /* LAST_CORE still runs the same idle TCB — no context switch happened. */ + TEST_ASSERT_EQUAL_PTR( pxIdleTCB, pxCurrentTCBs[ LAST_CORE ] ); +} + +/** + * @brief Enable a previously-disabled core. + * + * Disable LAST_CORE before the scheduler starts, start the scheduler with two + * user tasks, then re-enable LAST_CORE via vTaskSetSchedulerCoreMask. The + * function must call prvYieldCore on LAST_CORE (tasks.c:3186-3188) so the + * scheduler promptly dispatches the second user task there. + * + * With LAST_CORE disabled, only core 0 runs a user task; LAST_CORE idles. + * After re-enabling, the context switch triggered by prvYieldCore causes + * LAST_CORE to pick up the second user task — verifying the enable path fired. + */ +void test_vTaskSetSchedulerCoreMask_EnableCore( void ) +{ + TaskHandle_t xTaskHandles[ configNUMBER_OF_CORES ] = { NULL }; + BaseType_t i; + + /* Disable LAST_CORE before the scheduler starts. */ + vTaskSetSchedulerCoreMask( ALL_BUT_LAST_CORE ); + TEST_ASSERT_EQUAL( ALL_BUT_LAST_CORE, uxSchedulerCoreMask ); + + /* Create one user task per core so there are tasks waiting for each core. + * With LAST_CORE disabled, only core 0 picks up a user task at startup; + * the second user task sits in the ready list until LAST_CORE is enabled. */ + for( i = 0; i < ( BaseType_t ) configNUMBER_OF_CORES; i++ ) + { + xTaskCreate( vSmpTestTask, "SMP Task", configMINIMAL_STACK_SIZE, NULL, 1, &xTaskHandles[ i ] ); + } + + vTaskStartScheduler(); + + /* LAST_CORE must be running an idle task while it is disabled — at least + * one core must be idling (the disabled one). */ + TEST_ASSERT_NOT_EQUAL( 0U, pxCurrentTCBs[ LAST_CORE ]->uxTaskAttributes & taskATTRIBUTE_IS_IDLE ); + + /* Re-enable LAST_CORE. prvYieldCore must be called for LAST_CORE + * (tasks.c:3188) which triggers a context switch on that core. */ + vTaskSetSchedulerCoreMask( ALL_CORES_MASK ); + + /* All cores must be enabled again. */ + TEST_ASSERT_EQUAL( ALL_CORES_MASK, uxSchedulerCoreMask ); + + /* After the context switch triggered by prvYieldCore, LAST_CORE must now + * run a user task — confirming that the enable path (line 3186-3188) fired + * and the scheduler dispatched a ready task to the newly-enabled core. */ + TEST_ASSERT_EQUAL( 0U, pxCurrentTCBs[ LAST_CORE ]->uxTaskAttributes & taskATTRIBUTE_IS_IDLE ); +} + +/** + * @brief Unchanged core hits the else branch (mtCOVERAGE_TEST_MARKER). + * + * When vTaskSetSchedulerCoreMask is called with the same mask that is already + * active, no core transitions from disabled to enabled or vice versa. The + * else branch (tasks.c:3190-3193) must execute for every core — which is only + * observable by the absence of spurious yields. + */ +void test_vTaskSetSchedulerCoreMask_UnchangedCore( void ) +{ + TaskHandle_t xTaskHandles[ configNUMBER_OF_CORES ] = { NULL }; + BaseType_t i; + + for( i = 0; i < ( BaseType_t ) configNUMBER_OF_CORES; i++ ) + { + xTaskCreate( vSmpTestTask, "SMP Task", configMINIMAL_STACK_SIZE, NULL, 1, &xTaskHandles[ i ] ); + } + + vTaskStartScheduler(); + + /* Apply the exact same mask that is already in effect (all cores enabled). */ + vTaskSetSchedulerCoreMask( ALL_CORES_MASK ); + + /* Mask unchanged. */ + TEST_ASSERT_EQUAL( ALL_CORES_MASK, uxSchedulerCoreMask ); + + /* Every core still runs a user task — no unexpected yields happened. */ + for( i = 0; i < ( BaseType_t ) configNUMBER_OF_CORES; i++ ) + { + verifySmpTask( &xTaskHandles[ i ], eRunning, ( TaskRunning_t ) i ); + } +}