diff --git a/components/freertos/FreeRTOS-Kernel/tasks.c b/components/freertos/FreeRTOS-Kernel/tasks.c index 368a20599f..4d1138a8d7 100644 --- a/components/freertos/FreeRTOS-Kernel/tasks.c +++ b/components/freertos/FreeRTOS-Kernel/tasks.c @@ -3063,19 +3063,7 @@ char * pcTaskGetName( TaskHandle_t xTaskToQuery ) /*lint !e971 Unqualified char /* Arrange for xTickCount to reach xNextTaskUnblockTime in * xTaskIncrementTick() when the scheduler resumes. This ensures * that any delayed tasks are resumed at the correct time. */ - #if ( configNUMBER_OF_CORES > 1 ) - { - /* In SMP, the entire tickless idle handling block - * is replaced with a critical section, taking the kernel lock. */ - configASSERT( taskIS_SCHEDULER_SUSPENDED() == pdFALSE ); - } - #else /* configNUMBER_OF_CORES > 1 */ - { - /* In single-core, the entire tickless idle handling block - * is done with scheduler suspended. */ - configASSERT( taskIS_SCHEDULER_SUSPENDED() == pdTRUE ); - } - #endif /* configNUMBER_OF_CORES > 1 */ + configASSERT( taskIS_SCHEDULER_SUSPENDED() == pdTRUE ); configASSERT( xTicksToJump != ( TickType_t ) 0 ); xPendedTicks++; @@ -4369,31 +4357,37 @@ static portTASK_FUNCTION( prvIdleTask, pvParameters ) if( xExpectedIdleTime >= configEXPECTED_IDLE_TIME_BEFORE_SLEEP ) { - prvENTER_CRITICAL_OR_SUSPEND_ALL( &xKernelLock ); + /* In SMP mode, the entire tickless idle handling block + * must be done with the kernel lock held. */ + prvENTER_CRITICAL_SMP_ONLY( &xKernelLock ); { - /* Now the scheduler is suspended, the expected idle - * time can be sampled again, and this time its value can - * be used. */ - configASSERT( xNextTaskUnblockTime >= xTickCount ); - xExpectedIdleTime = prvGetExpectedIdleTime(); - - /* Define the following macro to set xExpectedIdleTime to 0 - * if the application does not want - * portSUPPRESS_TICKS_AND_SLEEP() to be called. */ - configPRE_SUPPRESS_TICKS_AND_SLEEP_PROCESSING( xExpectedIdleTime ); - - if( xExpectedIdleTime >= configEXPECTED_IDLE_TIME_BEFORE_SLEEP ) + vTaskSuspendAll(); { - traceLOW_POWER_IDLE_BEGIN(); - portSUPPRESS_TICKS_AND_SLEEP( xExpectedIdleTime ); - traceLOW_POWER_IDLE_END(); - } - else - { - mtCOVERAGE_TEST_MARKER(); + /* Now the scheduler is suspended, the expected idle + * time can be sampled again, and this time its value can + * be used. */ + configASSERT( xNextTaskUnblockTime >= xTickCount ); + xExpectedIdleTime = prvGetExpectedIdleTime(); + + /* Define the following macro to set xExpectedIdleTime to 0 + * if the application does not want + * portSUPPRESS_TICKS_AND_SLEEP() to be called. */ + configPRE_SUPPRESS_TICKS_AND_SLEEP_PROCESSING( xExpectedIdleTime ); + + if( xExpectedIdleTime >= configEXPECTED_IDLE_TIME_BEFORE_SLEEP ) + { + traceLOW_POWER_IDLE_BEGIN(); + portSUPPRESS_TICKS_AND_SLEEP( xExpectedIdleTime ); + traceLOW_POWER_IDLE_END(); + } + else + { + mtCOVERAGE_TEST_MARKER(); + } } + ( void ) xTaskResumeAll(); } - ( void ) prvEXIT_CRITICAL_OR_RESUME_ALL( &xKernelLock ); + prvEXIT_CRITICAL_SMP_ONLY( &xKernelLock ); } else { diff --git a/components/freertos/test_apps/freertos/misc/CMakeLists.txt b/components/freertos/test_apps/freertos/misc/CMakeLists.txt index 66163e4806..4611f8e925 100644 --- a/components/freertos/test_apps/freertos/misc/CMakeLists.txt +++ b/components/freertos/test_apps/freertos/misc/CMakeLists.txt @@ -3,5 +3,5 @@ # In order for the cases defined by `TEST_CASE` in "misc" to be linked into # the final elf, the component can be registered as WHOLE_ARCHIVE idf_component_register(SRC_DIRS "." - PRIV_REQUIRES unity test_utils esp_timer + PRIV_REQUIRES unity test_utils esp_timer esp_pm WHOLE_ARCHIVE) diff --git a/components/freertos/test_apps/freertos/misc/test_tickless_idle.c b/components/freertos/test_apps/freertos/misc/test_tickless_idle.c new file mode 100644 index 0000000000..7ed64ddf35 --- /dev/null +++ b/components/freertos/test_apps/freertos/misc/test_tickless_idle.c @@ -0,0 +1,116 @@ +/* + * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#include +#include "unity.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "freertos/semphr.h" +#include "esp_pm.h" +#include "esp_private/esp_clk.h" + +#include "sdkconfig.h" + +#if CONFIG_FREERTOS_USE_TICKLESS_IDLE && CONFIG_PM_ENABLE + +#define MHZ (1000000) + +static SemaphoreHandle_t test_sem; +static volatile bool test_timeout_occurred = false; + +static void producer_task(void *arg) +{ + while (1) { + xSemaphoreGive(test_sem); + vTaskDelay(pdMS_TO_TICKS(5)); // Give semaphore every 5ms + } +} + +static void consumer_task(void *arg) +{ + while (1) { + // Try to take semaphore with 1 second timeout + // Should never timeout as producer is giving the semaphore every 5ms + if (xSemaphoreTake(test_sem, pdMS_TO_TICKS(1000)) == pdFALSE) { + test_timeout_occurred = true; // Mark that timeout occurred + break; + } + } + + vTaskSuspend(NULL); +} + +/** + * Test case to verify timing accuracy during tickless idle mode + * + * This test verifies that FreeRTOS maintains accurate timing when the system + * enters and exits tickless idle mode, particularly when combined with scheduler + * suspension operations. + * + * Test Setup: + * - Configures the system to use tickless idle mode + * - Creates a producer-consumer pair using a semaphore: + * * Producer gives semaphore every 5ms + * * Consumer takes semaphore with 1-second timeout + * - Periodically suspend and resume the scheduler + * + * Expected Behavior: + * - The consumer should never timeout as the producer gives the semaphore + * every 5ms, well within the 1-second timeout window + * - Any timing inaccuracies during tickless idle would manifest as + * unexpected semaphore timeouts + * - The test passes if no timeouts occur. + */ +TEST_CASE("Test semaphore timeout during tickless idle", "[freertos]") +{ + // Configure tickless idle + esp_pm_config_t pm_config = { + .max_freq_mhz = esp_clk_cpu_freq() / MHZ, + .min_freq_mhz = esp_clk_cpu_freq() / MHZ, + .light_sleep_enable = true, + }; + TEST_ESP_OK(esp_pm_configure(&pm_config)); + + // Create test semaphore + test_sem = xSemaphoreCreateBinary(); + TEST_ASSERT_NOT_NULL(test_sem); + + test_timeout_occurred = false; + + // Create producer and consumer tasks + TaskHandle_t producer_handle, consumer_handle; + TEST_ASSERT_EQUAL(pdPASS, xTaskCreate(producer_task, "producer", 2048, NULL, 1, &producer_handle)); + TEST_ASSERT_EQUAL(pdPASS, xTaskCreate(consumer_task, "consumer", 2048, NULL, 2, &consumer_handle)); + + // Run the test for a while to ensure tickless idle mode has + // enough time to enter and exit multiple times + vTaskDelay(pdMS_TO_TICKS(10000)); + + // Suspend scheduler briefly + vTaskSuspendAll(); + + // Resume scheduler. This unrolls pended tick events. + // This should not cause any unexpected timeouts if + // tickless idle mode is working correctly. + xTaskResumeAll(); + + // Verify no timeout occurred + TEST_ASSERT_FALSE(test_timeout_occurred); + + // Disable power management before exit to prevent memory leaks + esp_pm_config_t default_config = { + .max_freq_mhz = CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ, + .min_freq_mhz = CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ, + .light_sleep_enable = false, + }; + esp_pm_configure(&default_config); + + // Cleanup + vTaskDelete(producer_handle); + vTaskDelete(consumer_handle); + vSemaphoreDelete(test_sem); +} + +#endif /* CONFIG_FREERTOS_USE_TICKLESS_IDLE && CONFIG_PM_ENABLE */ diff --git a/components/freertos/test_apps/freertos/pytest_freertos.py b/components/freertos/test_apps/freertos/pytest_freertos.py index d21d9dad70..b3cc933d41 100644 --- a/components/freertos/test_apps/freertos/pytest_freertos.py +++ b/components/freertos/test_apps/freertos/pytest_freertos.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2022-2024 Espressif Systems (Shanghai) CO LTD +# SPDX-FileCopyrightText: 2022-2025 Espressif Systems (Shanghai) CO LTD # SPDX-License-Identifier: Apache-2.0 import pytest from pytest_embedded import Dut @@ -10,6 +10,7 @@ CONFIGS = [ pytest.param('release', marks=[pytest.mark.supported_targets]), pytest.param('single_core', marks=[pytest.mark.esp32, pytest.mark.esp32p4]), pytest.param('smp', marks=[pytest.mark.supported_targets, pytest.mark.temp_skip_ci(targets=['esp32h2', 'esp32p4'], reason='test failed/TBD IDF-8113')]), + pytest.param('tickless_idle', marks=[pytest.mark.supported_targets]), ] diff --git a/components/freertos/test_apps/freertos/sdkconfig.ci.tickless_idle b/components/freertos/test_apps/freertos/sdkconfig.ci.tickless_idle new file mode 100644 index 0000000000..e8945648db --- /dev/null +++ b/components/freertos/test_apps/freertos/sdkconfig.ci.tickless_idle @@ -0,0 +1,2 @@ +CONFIG_PM_ENABLE=y +CONFIG_FREERTOS_USE_TICKLESS_IDLE=y