Skip to content

Commit 6a740a9

Browse files
Merge branch 'fix/freertos_tickless_idle_tick_jump' into 'master'
fix(freertos): Fixed tickless idle tick count accounting Closes IDFGH-14933 See merge request espressif/esp-idf!38068
2 parents 9345570 + 3caddd5 commit 6a740a9

File tree

5 files changed

+149
-35
lines changed

5 files changed

+149
-35
lines changed

components/freertos/FreeRTOS-Kernel/tasks.c

Lines changed: 28 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -3072,19 +3072,7 @@ char * pcTaskGetName( TaskHandle_t xTaskToQuery ) /*lint !e971 Unqualified char
30723072
/* Arrange for xTickCount to reach xNextTaskUnblockTime in
30733073
* xTaskIncrementTick() when the scheduler resumes. This ensures
30743074
* that any delayed tasks are resumed at the correct time. */
3075-
#if ( configNUMBER_OF_CORES > 1 )
3076-
{
3077-
/* In SMP, the entire tickless idle handling block
3078-
* is replaced with a critical section, taking the kernel lock. */
3079-
configASSERT( taskIS_SCHEDULER_SUSPENDED() == pdFALSE );
3080-
}
3081-
#else /* configNUMBER_OF_CORES > 1 */
3082-
{
3083-
/* In single-core, the entire tickless idle handling block
3084-
* is done with scheduler suspended. */
3085-
configASSERT( taskIS_SCHEDULER_SUSPENDED() == pdTRUE );
3086-
}
3087-
#endif /* configNUMBER_OF_CORES > 1 */
3075+
configASSERT( taskIS_SCHEDULER_SUSPENDED() == pdTRUE );
30883076
configASSERT( xTicksToJump != ( TickType_t ) 0 );
30893077

30903078
xPendedTicks++;
@@ -4378,31 +4366,37 @@ static portTASK_FUNCTION( prvIdleTask, pvParameters )
43784366

43794367
if( xExpectedIdleTime >= configEXPECTED_IDLE_TIME_BEFORE_SLEEP )
43804368
{
4381-
prvENTER_CRITICAL_OR_SUSPEND_ALL( &xKernelLock );
4369+
/* In SMP mode, the entire tickless idle handling block
4370+
* must be done with the kernel lock held. */
4371+
prvENTER_CRITICAL_SMP_ONLY( &xKernelLock );
43824372
{
4383-
/* Now the scheduler is suspended, the expected idle
4384-
* time can be sampled again, and this time its value can
4385-
* be used. */
4386-
configASSERT( xNextTaskUnblockTime >= xTickCount );
4387-
xExpectedIdleTime = prvGetExpectedIdleTime();
4388-
4389-
/* Define the following macro to set xExpectedIdleTime to 0
4390-
* if the application does not want
4391-
* portSUPPRESS_TICKS_AND_SLEEP() to be called. */
4392-
configPRE_SUPPRESS_TICKS_AND_SLEEP_PROCESSING( xExpectedIdleTime );
4393-
4394-
if( xExpectedIdleTime >= configEXPECTED_IDLE_TIME_BEFORE_SLEEP )
4395-
{
4396-
traceLOW_POWER_IDLE_BEGIN();
4397-
portSUPPRESS_TICKS_AND_SLEEP( xExpectedIdleTime );
4398-
traceLOW_POWER_IDLE_END();
4399-
}
4400-
else
4373+
vTaskSuspendAll();
44014374
{
4402-
mtCOVERAGE_TEST_MARKER();
4375+
/* Now the scheduler is suspended, the expected idle
4376+
* time can be sampled again, and this time its value can
4377+
* be used. */
4378+
configASSERT( xNextTaskUnblockTime >= xTickCount );
4379+
xExpectedIdleTime = prvGetExpectedIdleTime();
4380+
4381+
/* Define the following macro to set xExpectedIdleTime to 0
4382+
* if the application does not want
4383+
* portSUPPRESS_TICKS_AND_SLEEP() to be called. */
4384+
configPRE_SUPPRESS_TICKS_AND_SLEEP_PROCESSING( xExpectedIdleTime );
4385+
4386+
if( xExpectedIdleTime >= configEXPECTED_IDLE_TIME_BEFORE_SLEEP )
4387+
{
4388+
traceLOW_POWER_IDLE_BEGIN();
4389+
portSUPPRESS_TICKS_AND_SLEEP( xExpectedIdleTime );
4390+
traceLOW_POWER_IDLE_END();
4391+
}
4392+
else
4393+
{
4394+
mtCOVERAGE_TEST_MARKER();
4395+
}
44034396
}
4397+
( void ) xTaskResumeAll();
44044398
}
4405-
( void ) prvEXIT_CRITICAL_OR_RESUME_ALL( &xKernelLock );
4399+
prvEXIT_CRITICAL_SMP_ONLY( &xKernelLock );
44064400
}
44074401
else
44084402
{

components/freertos/test_apps/freertos/misc/CMakeLists.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,5 @@
33
# In order for the cases defined by `TEST_CASE` in "misc" to be linked into
44
# the final elf, the component can be registered as WHOLE_ARCHIVE
55
idf_component_register(SRC_DIRS "."
6-
PRIV_REQUIRES unity test_utils esp_timer
6+
PRIV_REQUIRES unity test_utils esp_timer esp_pm
77
WHOLE_ARCHIVE)
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
/*
2+
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
3+
*
4+
* SPDX-License-Identifier: Apache-2.0
5+
*/
6+
#include <stdio.h>
7+
#include "unity.h"
8+
#include "freertos/FreeRTOS.h"
9+
#include "freertos/task.h"
10+
#include "freertos/semphr.h"
11+
#include "esp_pm.h"
12+
#include "esp_private/esp_clk.h"
13+
14+
#include "sdkconfig.h"
15+
16+
#if CONFIG_FREERTOS_USE_TICKLESS_IDLE && CONFIG_PM_ENABLE
17+
18+
#define MHZ (1000000)
19+
20+
static SemaphoreHandle_t test_sem;
21+
static volatile bool test_timeout_occurred = false;
22+
23+
static void producer_task(void *arg)
24+
{
25+
while (1) {
26+
xSemaphoreGive(test_sem);
27+
vTaskDelay(pdMS_TO_TICKS(5)); // Give semaphore every 5ms
28+
}
29+
}
30+
31+
static void consumer_task(void *arg)
32+
{
33+
while (1) {
34+
// Try to take semaphore with 1 second timeout
35+
// Should never timeout as producer is giving the semaphore every 5ms
36+
if (xSemaphoreTake(test_sem, pdMS_TO_TICKS(1000)) == pdFALSE) {
37+
test_timeout_occurred = true; // Mark that timeout occurred
38+
break;
39+
}
40+
}
41+
42+
vTaskSuspend(NULL);
43+
}
44+
45+
/**
46+
* Test case to verify timing accuracy during tickless idle mode
47+
*
48+
* This test verifies that FreeRTOS maintains accurate timing when the system
49+
* enters and exits tickless idle mode, particularly when combined with scheduler
50+
* suspension operations.
51+
*
52+
* Test Setup:
53+
* - Configures the system to use tickless idle mode
54+
* - Creates a producer-consumer pair using a semaphore:
55+
* * Producer gives semaphore every 5ms
56+
* * Consumer takes semaphore with 1-second timeout
57+
* - Periodically suspend and resume the scheduler
58+
*
59+
* Expected Behavior:
60+
* - The consumer should never timeout as the producer gives the semaphore
61+
* every 5ms, well within the 1-second timeout window
62+
* - Any timing inaccuracies during tickless idle would manifest as
63+
* unexpected semaphore timeouts
64+
* - The test passes if no timeouts occur.
65+
*/
66+
TEST_CASE("Test semaphore timeout during tickless idle", "[freertos]")
67+
{
68+
// Configure tickless idle
69+
esp_pm_config_t pm_config = {
70+
.max_freq_mhz = esp_clk_cpu_freq() / MHZ,
71+
.min_freq_mhz = esp_clk_cpu_freq() / MHZ,
72+
.light_sleep_enable = true,
73+
};
74+
TEST_ESP_OK(esp_pm_configure(&pm_config));
75+
76+
// Create test semaphore
77+
test_sem = xSemaphoreCreateBinary();
78+
TEST_ASSERT_NOT_NULL(test_sem);
79+
80+
test_timeout_occurred = false;
81+
82+
// Create producer and consumer tasks
83+
TaskHandle_t producer_handle, consumer_handle;
84+
TEST_ASSERT_EQUAL(pdPASS, xTaskCreate(producer_task, "producer", 2048, NULL, 1, &producer_handle));
85+
TEST_ASSERT_EQUAL(pdPASS, xTaskCreate(consumer_task, "consumer", 2048, NULL, 2, &consumer_handle));
86+
87+
// Run the test for a while to ensure tickless idle mode has
88+
// enough time to enter and exit multiple times
89+
vTaskDelay(pdMS_TO_TICKS(10000));
90+
91+
// Suspend scheduler briefly
92+
vTaskSuspendAll();
93+
94+
// Resume scheduler. This unrolls pended tick events.
95+
// This should not cause any unexpected timeouts if
96+
// tickless idle mode is working correctly.
97+
xTaskResumeAll();
98+
99+
// Verify no timeout occurred
100+
TEST_ASSERT_FALSE(test_timeout_occurred);
101+
102+
// Disable power management before exit to prevent memory leaks
103+
esp_pm_config_t default_config = {
104+
.max_freq_mhz = CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ,
105+
.min_freq_mhz = CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ,
106+
.light_sleep_enable = false,
107+
};
108+
esp_pm_configure(&default_config);
109+
110+
// Cleanup
111+
vTaskDelete(producer_handle);
112+
vTaskDelete(consumer_handle);
113+
vSemaphoreDelete(test_sem);
114+
}
115+
116+
#endif /* CONFIG_FREERTOS_USE_TICKLESS_IDLE && CONFIG_PM_ENABLE */

components/freertos/test_apps/freertos/pytest_freertos.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
),
2626
],
2727
),
28+
pytest.param('tickless_idle', marks=[pytest.mark.supported_targets]),
2829
]
2930

3031

@@ -34,6 +35,7 @@
3435
[
3536
('default', 'supported_targets'),
3637
('freertos_options', 'supported_targets'),
38+
('tickless_idle', 'supported_targets'),
3739
('psram', 'esp32'),
3840
('psram', 'esp32c5'),
3941
('psram', 'esp32p4'),
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
CONFIG_PM_ENABLE=y
2+
CONFIG_FREERTOS_USE_TICKLESS_IDLE=y

0 commit comments

Comments
 (0)