1 /* Tests for FreeRTOS task suspend & resume */
2 #include <stdio.h>
3 #include <string.h>
4 #include "sdkconfig.h"
5 #include "freertos/FreeRTOS.h"
6 #include "freertos/task.h"
7 #include "freertos/semphr.h"
8 #include "freertos/timers.h"
9 #include "freertos/queue.h"
10 #include "unity.h"
11 #include "soc/cpu.h"
12 #include "test_utils.h"
13
14 #include "driver/timer.h"
15
16 #ifndef CONFIG_FREERTOS_UNICORE
17 #include "esp_ipc.h"
18 #endif
19 #include "esp_freertos_hooks.h"
20
21 #include "esp_rom_sys.h"
22
23 #ifdef CONFIG_IDF_TARGET_ESP32S2
24 #define int_clr_timers int_clr
25 #define update update.update
26 #define int_st_timers int_st
27 #endif
28
29 /* Counter task counts a target variable forever */
task_count(void * vp_counter)30 static void task_count(void *vp_counter)
31 {
32 volatile unsigned *counter = (volatile unsigned *)vp_counter;
33 *counter = 0;
34
35 while (1) {
36 (*counter)++;
37 vTaskDelay(1);
38 }
39 }
40
test_suspend_resume(int target_core)41 static void test_suspend_resume(int target_core)
42 {
43 volatile unsigned counter = 0;
44 TaskHandle_t counter_task;
45
46 xTaskCreatePinnedToCore(task_count, "Count", 2048,
47 (void *)&counter, UNITY_FREERTOS_PRIORITY + 1,
48 &counter_task, target_core);
49
50 vTaskDelay(10);
51 /* check some counting has happened */
52 TEST_ASSERT_NOT_EQUAL(0, counter);
53
54 // Do the next part a few times, just to be sure multiple suspends & resumes
55 // work as expected...
56 const int TEST_ITERATIONS = 5;
57 for (int i = 0; i < TEST_ITERATIONS; i++) {
58 vTaskSuspend(counter_task);
59 unsigned suspend_count = counter;
60 printf("Suspending @ %d\n", suspend_count);
61
62 vTaskDelay(2);
63
64 printf("Still suspended @ %d\n", counter);
65
66 /* check the counter hasn't gone up while the task is suspended */
67 TEST_ASSERT_EQUAL(suspend_count, counter);
68 vTaskResume(counter_task);
69 vTaskDelay(2);
70
71 printf("Resumed @ %d\n", counter);
72 /* check the counter is going up again now the task is resumed */
73 TEST_ASSERT_NOT_EQUAL(suspend_count, counter);
74 }
75
76 vTaskDelete(counter_task);
77 }
78
79
80 TEST_CASE("Suspend/resume task on same core", "[freertos]")
81 {
82 test_suspend_resume(UNITY_FREERTOS_CPU);
83 }
84
85 #ifndef CONFIG_FREERTOS_UNICORE
86 TEST_CASE("Suspend/resume task on other core", "[freertos]")
87 {
88 test_suspend_resume(!UNITY_FREERTOS_CPU);
89 }
90 #endif
91
92 /* Task suspends itself, then sets a flag and deletes itself */
task_suspend_self(void * vp_resumed)93 static void task_suspend_self(void *vp_resumed)
94 {
95 volatile bool *resumed = (volatile bool *)vp_resumed;
96 *resumed = false;
97 vTaskSuspend(NULL);
98 *resumed = true;
99 vTaskDelete(NULL);
100 }
101
102 TEST_CASE("Suspend the current running task", "[freertos]")
103 {
104 volatile bool resumed = false;
105 TaskHandle_t suspend_task;
106
107 xTaskCreatePinnedToCore(task_suspend_self, "suspend_self", 2048,
108 (void *)&resumed, UNITY_FREERTOS_PRIORITY + 1,
109 &suspend_task, UNITY_FREERTOS_CPU);
110
111 vTaskDelay(1);
112 TEST_ASSERT_FALSE(resumed);
113
114 vTaskResume(suspend_task);
115 // Shouldn't need any delay here, as task should resume on this CPU immediately
116 TEST_ASSERT_TRUE(resumed);
117 }
118
119
120 volatile bool timer_isr_fired;
121
122 /* Timer ISR clears interrupt, sets flag, then resumes the task supplied in the
123 * callback argument.
124 */
timer_group0_isr(void * vp_arg)125 void IRAM_ATTR timer_group0_isr(void *vp_arg)
126 {
127 // Clear interrupt
128 timer_group_clr_intr_status_in_isr(TIMER_GROUP_0, TIMER_0);
129
130 timer_isr_fired = true;
131
132 TaskHandle_t handle = vp_arg;
133 BaseType_t higherPriorityTaskWoken = pdFALSE;
134 higherPriorityTaskWoken = xTaskResumeFromISR(handle);
135 if (higherPriorityTaskWoken == pdTRUE) {
136 portYIELD_FROM_ISR();
137 }
138 }
139
140 /* Task suspends itself, then sets parameter value to the current timer group counter and deletes itself */
task_suspend_self_with_timer(void * vp_resumed)141 static void task_suspend_self_with_timer(void *vp_resumed)
142 {
143 volatile uint64_t *resumed_counter = vp_resumed;
144 *resumed_counter = 0;
145 vTaskSuspend(NULL);
146 timer_get_counter_value(TIMER_GROUP_0, TIMER_0, vp_resumed);
147 vTaskDelete(NULL);
148 }
149
150
151 /* Create a task which suspends itself, then resume it from a timer
152 * interrupt. */
test_resume_task_from_isr(int target_core)153 static void test_resume_task_from_isr(int target_core)
154 {
155 volatile uint64_t resumed_counter = 99;
156 TaskHandle_t suspend_task;
157
158 xTaskCreatePinnedToCore(task_suspend_self_with_timer, "suspend_self", 2048,
159 (void *)&resumed_counter, UNITY_FREERTOS_PRIORITY + 1,
160 &suspend_task, target_core);
161
162 vTaskDelay(1);
163 TEST_ASSERT_EQUAL(0, resumed_counter);
164
165 const unsigned APB_CYCLES_PER_TICK = TIMER_BASE_CLK / configTICK_RATE_HZ;
166 const unsigned TEST_TIMER_DIV = 2;
167 const unsigned TEST_TIMER_CYCLES_PER_TICK = APB_CYCLES_PER_TICK / TEST_TIMER_DIV;
168 const unsigned TEST_TIMER_CYCLES_PER_MS = TIMER_BASE_CLK / 1000 / TEST_TIMER_DIV;
169 const unsigned TEST_TIMER_ALARM = TEST_TIMER_CYCLES_PER_TICK / 2; // half an RTOS tick
170
171 /* Configure timer ISR */
172 timer_isr_handle_t isr_handle;
173 const timer_config_t config = {
174 .alarm_en = 1,
175 .auto_reload = 0,
176 .counter_dir = TIMER_COUNT_UP,
177 .divider = TEST_TIMER_DIV,
178 .intr_type = TIMER_INTR_LEVEL,
179 .counter_en = TIMER_PAUSE,
180 };
181 /*Configure timer*/
182 ESP_ERROR_CHECK( timer_init(TIMER_GROUP_0, TIMER_0, &config) );
183 ESP_ERROR_CHECK( timer_pause(TIMER_GROUP_0, TIMER_0) );
184 ESP_ERROR_CHECK( timer_set_counter_value(TIMER_GROUP_0, TIMER_0, 0) );
185 ESP_ERROR_CHECK( timer_set_alarm_value(TIMER_GROUP_0, TIMER_0, TEST_TIMER_ALARM) );
186 ESP_ERROR_CHECK( timer_enable_intr(TIMER_GROUP_0, TIMER_0) );
187 ESP_ERROR_CHECK( timer_isr_register(TIMER_GROUP_0, TIMER_0, timer_group0_isr, (void*)suspend_task, ESP_INTR_FLAG_IRAM, &isr_handle) );
188 timer_isr_fired = false;
189 vTaskDelay(1); // Make sure we're at the start of a new tick
190
191 ESP_ERROR_CHECK( timer_start(TIMER_GROUP_0, TIMER_0) );
192
193 vTaskDelay(1); // We expect timer group will fire half-way through this delay
194
195 TEST_ASSERT_TRUE(timer_isr_fired);
196 TEST_ASSERT_NOT_EQUAL(0, resumed_counter);
197 // The task should have woken within 500us of the timer interrupt event (note: task may be a flash cache miss)
198 printf("alarm value %u task resumed at %u\n", TEST_TIMER_ALARM, (unsigned)resumed_counter);
199 TEST_ASSERT_UINT32_WITHIN(TEST_TIMER_CYCLES_PER_MS/2, TEST_TIMER_ALARM, (unsigned)resumed_counter);
200
201 // clean up
202 timer_deinit(TIMER_GROUP_0, TIMER_0);
203 ESP_ERROR_CHECK( esp_intr_free(isr_handle) );
204 }
205
206 TEST_CASE("Resume task from ISR (same core)", "[freertos]")
207 {
208 test_resume_task_from_isr(UNITY_FREERTOS_CPU);
209 }
210
211 #ifndef CONFIG_FREERTOS_UNICORE
212 TEST_CASE("Resume task from ISR (other core)", "[freertos]")
213 {
214 test_resume_task_from_isr(!UNITY_FREERTOS_CPU);
215 }
216
217 static volatile bool block;
218 static bool suspend_both_cpus;
219
suspend_scheduler_while_block_set(void * arg)220 static void IRAM_ATTR suspend_scheduler_while_block_set(void* arg)
221 {
222 vTaskSuspendAll();
223
224 while (block) { };
225 esp_rom_delay_us(1);
226 xTaskResumeAll();
227 }
228
suspend_scheduler_on_both_cpus(void)229 static void IRAM_ATTR suspend_scheduler_on_both_cpus(void)
230 {
231 block = true;
232 if (suspend_both_cpus) {
233 TEST_ESP_OK(esp_ipc_call((xPortGetCoreID() == 0) ? 1 : 0, &suspend_scheduler_while_block_set, NULL));
234 }
235
236 vTaskSuspendAll();
237 }
238
resume_scheduler_on_both_cpus(void)239 static void IRAM_ATTR resume_scheduler_on_both_cpus(void)
240 {
241 block = false;
242 xTaskResumeAll();
243 }
244
245 static const int waiting_ms = 2000;
246 static const int delta_ms = 100;
247 static int duration_wait_task_ms;
248 static int duration_ctrl_task_ms;
249
waiting_task(void * pvParameters)250 static void waiting_task(void *pvParameters)
251 {
252 int cpu_id = xPortGetCoreID();
253 int64_t start_time = esp_timer_get_time();
254 printf("Start waiting_task cpu=%d\n", cpu_id);
255
256 vTaskDelay(waiting_ms / portTICK_PERIOD_MS);
257
258 duration_wait_task_ms = (esp_timer_get_time() - start_time) / 1000;
259 printf("Finish waiting_task cpu=%d, time=%d ms\n", cpu_id, duration_wait_task_ms);
260 vTaskDelete(NULL);
261 }
262
control_task(void * pvParameters)263 static void control_task(void *pvParameters)
264 {
265 int cpu_id = xPortGetCoreID();
266 esp_rom_delay_us(2000); // let to start the waiting_task first
267 printf("Start control_task cpu=%d\n", cpu_id);
268 int64_t start_time = esp_timer_get_time();
269
270 suspend_scheduler_on_both_cpus();
271 esp_rom_delay_us(waiting_ms * 1000 + delta_ms * 1000);
272 resume_scheduler_on_both_cpus();
273
274 duration_ctrl_task_ms = (esp_timer_get_time() - start_time) / 1000;
275 printf("Finish control_task cpu=%d, time=%d ms\n", cpu_id, duration_ctrl_task_ms);
276 vTaskDelete(NULL);
277 }
278
test_scheduler_suspend1(int cpu)279 static void test_scheduler_suspend1(int cpu)
280 {
281 /* This test tests a case then both CPUs were in suspend state and then resume CPUs back.
282 * A task for which a wait time has been set and this time has elapsed in the suspended state should in any case be ready to start.
283 * (In an old implementation of xTaskIncrementTick function the counting for waiting_task() will be continued
284 * (excluding time in suspended) after control_task() is finished.)
285 */
286 duration_wait_task_ms = 0;
287 duration_ctrl_task_ms = 0;
288
289 printf("Test for CPU%d\n", cpu);
290 int other_cpu = (cpu == 0) ? 1 : 0;
291 xTaskCreatePinnedToCore(&waiting_task, "waiting_task", 8192, NULL, 5, NULL, other_cpu);
292 xTaskCreatePinnedToCore(&control_task, "control_task", 8192, NULL, 5, NULL, cpu);
293
294 vTaskDelay(waiting_ms * 2 / portTICK_PERIOD_MS);
295 TEST_ASSERT_INT_WITHIN(4, waiting_ms + delta_ms + 4, duration_ctrl_task_ms);
296 if (suspend_both_cpus == false && cpu == 1) {
297 // CPU0 continues to increase the TickCount and the wait_task does not depend on Suspended Scheduler on CPU1
298 TEST_ASSERT_INT_WITHIN(2, waiting_ms, duration_wait_task_ms);
299 } else {
300 TEST_ASSERT_INT_WITHIN(4, waiting_ms + delta_ms + 4, duration_wait_task_ms);
301 }
302
303 printf("\n");
304 }
305
306 TEST_CASE("Test the waiting task not missed due to scheduler suspension on both CPUs", "[freertos]")
307 {
308 printf("Suspend both CPUs:\n");
309 suspend_both_cpus = true;
310 test_scheduler_suspend1(0);
311 test_scheduler_suspend1(1);
312 }
313
314 TEST_CASE("Test the waiting task not missed due to scheduler suspension on one CPU", "[freertos]")
315 {
316 printf("Suspend only one CPU:\n");
317 suspend_both_cpus = false;
318 test_scheduler_suspend1(0);
319 test_scheduler_suspend1(1);
320 }
321
322 static uint32_t tick_hook_ms[2];
323
tick_hook(void)324 static void IRAM_ATTR tick_hook(void)
325 {
326 tick_hook_ms[xPortGetCoreID()] += portTICK_PERIOD_MS;
327 }
328
test_scheduler_suspend2(int cpu)329 static void test_scheduler_suspend2(int cpu)
330 {
331 esp_register_freertos_tick_hook_for_cpu(tick_hook, 0);
332 esp_register_freertos_tick_hook_for_cpu(tick_hook, 1);
333
334 memset(tick_hook_ms, 0, sizeof(tick_hook_ms));
335
336 printf("Test for CPU%d\n", cpu);
337 xTaskCreatePinnedToCore(&control_task, "control_task", 8192, NULL, 5, NULL, cpu);
338
339 vTaskDelay(waiting_ms * 2 / portTICK_PERIOD_MS);
340 esp_deregister_freertos_tick_hook(tick_hook);
341
342 printf("tick_hook_ms[cpu0] = %d, tick_hook_ms[cpu1] = %d\n", tick_hook_ms[0], tick_hook_ms[1]);
343
344 TEST_ASSERT_INT_WITHIN(portTICK_PERIOD_MS, waiting_ms * 2, tick_hook_ms[0]);
345 TEST_ASSERT_INT_WITHIN(portTICK_PERIOD_MS, waiting_ms * 2, tick_hook_ms[1]);
346 printf("\n");
347 }
348
349 TEST_CASE("Test suspend-resume CPU. The number of tick_hook should be the same for both CPUs", "[freertos]")
350 {
351 printf("Suspend both CPUs:\n");
352 suspend_both_cpus = true;
353 test_scheduler_suspend2(0);
354 test_scheduler_suspend2(1);
355
356 printf("Suspend only one CPU:\n");
357 suspend_both_cpus = false;
358 test_scheduler_suspend2(0);
359 test_scheduler_suspend2(1);
360 }
361
362 static int duration_timer_ms;
363
timer_callback(void * arg)364 static void timer_callback(void *arg)
365 {
366 duration_timer_ms += portTICK_PERIOD_MS;
367 }
368
test_scheduler_suspend3(int cpu)369 static void test_scheduler_suspend3(int cpu)
370 {
371 duration_timer_ms = 0;
372 duration_ctrl_task_ms = 0;
373
374 printf("Test for CPU%d\n", cpu);
375 TimerHandle_t count_time = xTimerCreate("count_time", 1, pdTRUE, NULL, timer_callback);
376 xTimerStart( count_time, portMAX_DELAY);
377 xTaskCreatePinnedToCore(&control_task, "control_task", 8192, NULL, 5, NULL, cpu);
378
379 vTaskDelay(waiting_ms * 2 / portTICK_PERIOD_MS);
380 xTimerDelete(count_time, portMAX_DELAY);
381 printf("Finish duration_timer_ms=%d ms\n", duration_timer_ms);
382
383 TEST_ASSERT_INT_WITHIN(2, waiting_ms * 2, duration_timer_ms);
384 TEST_ASSERT_INT_WITHIN(5, waiting_ms + delta_ms, duration_ctrl_task_ms);
385
386 printf("\n");
387 }
388
389 TEST_CASE("Test suspend-resume CPU works with xTimer", "[freertos]")
390 {
391 printf("Suspend both CPUs:\n");
392 suspend_both_cpus = true;
393 test_scheduler_suspend3(0);
394 test_scheduler_suspend3(1);
395
396 printf("Suspend only one CPU:\n");
397 suspend_both_cpus = false;
398 test_scheduler_suspend3(0);
399 test_scheduler_suspend3(1);
400 }
401 #endif // CONFIG_FREERTOS_UNICORE
402