1 /* Tests for FreeRTOS scheduler suspend & resume all tasks */
2 #include <stdio.h>
3
4 #include "freertos/FreeRTOS.h"
5 #include "freertos/task.h"
6 #include "freertos/semphr.h"
7 #include "freertos/queue.h"
8 #include "unity.h"
9 #include "soc/cpu.h"
10 #include "test_utils.h"
11
12 #include "driver/timer.h"
13 #include "sdkconfig.h"
14
15 #include "esp_rom_sys.h"
16
17 #ifdef CONFIG_IDF_TARGET_ESP32S2
18 #define int_clr_timers int_clr
19 #define update update.update
20 #define int_st_timers int_st
21 #endif
22
23 static SemaphoreHandle_t isr_semaphore;
24 static volatile unsigned isr_count;
25
26 /* Timer ISR increments an ISR counter, and signals a
27 mutex semaphore to wake up another counter task */
timer_group0_isr(void * vp_arg)28 static void timer_group0_isr(void *vp_arg)
29 {
30 timer_group_clr_intr_status_in_isr(TIMER_GROUP_0, TIMER_0);
31 timer_group_enable_alarm_in_isr(TIMER_GROUP_0, TIMER_0);
32 portBASE_TYPE higher_awoken = pdFALSE;
33 isr_count++;
34 xSemaphoreGiveFromISR(isr_semaphore, &higher_awoken);
35 if (higher_awoken == pdTRUE) {
36 portYIELD_FROM_ISR();
37 }
38 }
39
40 typedef struct {
41 SemaphoreHandle_t trigger_sem;
42 volatile unsigned counter;
43 } counter_config_t;
44
counter_task_fn(void * vp_config)45 static void counter_task_fn(void *vp_config)
46 {
47 counter_config_t *config = (counter_config_t *)vp_config;
48 printf("counter_task running...\n");
49 while(1) {
50 xSemaphoreTake(config->trigger_sem, portMAX_DELAY);
51 config->counter++;
52 }
53 }
54
55
56 /* This test verifies that an interrupt can wake up a task while the scheduler is disabled.
57
58 In the FreeRTOS implementation, this exercises the xPendingReadyList for that core.
59 */
60 TEST_CASE("Scheduler disabled can handle a pending context switch on resume", "[freertos]")
61 {
62 isr_count = 0;
63 isr_semaphore = xSemaphoreCreateBinary();
64 TaskHandle_t counter_task;
65 intr_handle_t isr_handle = NULL;
66
67 counter_config_t count_config = {
68 .trigger_sem = isr_semaphore,
69 .counter = 0,
70 };
71 xTaskCreatePinnedToCore(counter_task_fn, "counter", 2048,
72 &count_config, UNITY_FREERTOS_PRIORITY + 1,
73 &counter_task, UNITY_FREERTOS_CPU);
74
75 /* Configure timer ISR */
76 const timer_config_t timer_config = {
77 .alarm_en = 1,
78 .auto_reload = 1,
79 .counter_dir = TIMER_COUNT_UP,
80 .divider = 2, //Range is 2 to 65536
81 .intr_type = TIMER_INTR_LEVEL,
82 .counter_en = TIMER_PAUSE,
83 };
84 /* Configure timer */
85 timer_init(TIMER_GROUP_0, TIMER_0, &timer_config);
86 timer_pause(TIMER_GROUP_0, TIMER_0);
87 timer_set_counter_value(TIMER_GROUP_0, TIMER_0, 0);
88 timer_set_alarm_value(TIMER_GROUP_0, TIMER_0, 1000);
89 timer_enable_intr(TIMER_GROUP_0, TIMER_0);
90 timer_isr_register(TIMER_GROUP_0, TIMER_0, timer_group0_isr, NULL, 0, &isr_handle);
91 timer_start(TIMER_GROUP_0, TIMER_0);
92
93 vTaskDelay(5);
94
95 // Check some counts have been triggered via the ISR
96 TEST_ASSERT(count_config.counter > 10);
97 TEST_ASSERT(isr_count > 10);
98
99 for (int i = 0; i < 20; i++) {
100 vTaskSuspendAll();
101 esp_intr_noniram_disable();
102
103 unsigned no_sched_task = count_config.counter;
104
105 // scheduler off on this CPU...
106 esp_rom_delay_us(20 * 1000);
107
108 //TEST_ASSERT_NOT_EQUAL(no_sched_isr, isr_count);
109 TEST_ASSERT_EQUAL(count_config.counter, no_sched_task);
110
111 // disable timer interrupts
112 timer_disable_intr(TIMER_GROUP_0, TIMER_0);
113
114 // When we resume scheduler, we expect the counter task
115 // will preempt and count at least one more item
116 esp_intr_noniram_enable();
117 timer_enable_intr(TIMER_GROUP_0, TIMER_0);
118 xTaskResumeAll();
119
120 TEST_ASSERT_NOT_EQUAL(count_config.counter, no_sched_task);
121 }
122
123 esp_intr_free(isr_handle);
124 timer_disable_intr(TIMER_GROUP_0, TIMER_0);
125
126 vTaskDelete(counter_task);
127 vSemaphoreDelete(isr_semaphore);
128 }
129
130 /* Multiple tasks on different cores can be added to the pending ready list
131 while scheduler is suspended, and should be started once the scheduler
132 resumes.
133 */
134 TEST_CASE("Scheduler disabled can wake multiple tasks on resume", "[freertos]")
135 {
136 #define TASKS_PER_PROC 4
137 TaskHandle_t tasks[portNUM_PROCESSORS][TASKS_PER_PROC] = { 0 };
138 counter_config_t counters[portNUM_PROCESSORS][TASKS_PER_PROC] = { 0 };
139
140 /* Start all the tasks, they will block on isr_semaphore */
141 for (int p = 0; p < portNUM_PROCESSORS; p++) {
142 for (int t = 0; t < TASKS_PER_PROC; t++) {
143 counters[p][t].trigger_sem = xSemaphoreCreateMutex();
144 TEST_ASSERT_NOT_NULL( counters[p][t].trigger_sem );
145 TEST_ASSERT( xSemaphoreTake(counters[p][t].trigger_sem, 0) );
146 xTaskCreatePinnedToCore(counter_task_fn, "counter", 2048,
147 &counters[p][t], UNITY_FREERTOS_PRIORITY + 1,
148 &tasks[p][t], p);
149 TEST_ASSERT_NOT_NULL( tasks[p][t] );
150 }
151 }
152
153 /* takes a while to initialize tasks on both cores, sometimes... */
154 vTaskDelay(TASKS_PER_PROC * portNUM_PROCESSORS * 3);
155
156 /* Check nothing is counting, each counter should be blocked on its trigger_sem */
157 for (int p = 0; p < portNUM_PROCESSORS; p++) {
158 for (int t = 0; t < TASKS_PER_PROC; t++) {
159 TEST_ASSERT_EQUAL(0, counters[p][t].counter);
160 }
161 }
162
163 /* Suspend scheduler on this CPU */
164 vTaskSuspendAll();
165
166 /* Give all the semaphores once. This will wake tasks immediately on the other
167 CPU, but they are deferred here until the scheduler resumes.
168 */
169 for (int p = 0; p < portNUM_PROCESSORS; p++) {
170 for (int t = 0; t < TASKS_PER_PROC; t++) {
171 xSemaphoreGive(counters[p][t].trigger_sem);
172 }
173 }
174
175 esp_rom_delay_us(200); /* Let the other CPU do some things */
176
177 for (int p = 0; p < portNUM_PROCESSORS; p++) {
178 for (int t = 0; t < TASKS_PER_PROC; t++) {
179 int expected = (p == UNITY_FREERTOS_CPU) ? 0 : 1; // Has run if it was on the other CPU
180 esp_rom_printf("Checking CPU %d task %d (expected %d actual %d)\n", p, t, expected, counters[p][t].counter);
181 TEST_ASSERT_EQUAL(expected, counters[p][t].counter);
182 }
183 }
184
185 /* Resume scheduler */
186 xTaskResumeAll();
187
188 /* Now the tasks on both CPUs should have been woken once and counted once. */
189 for (int p = 0; p < portNUM_PROCESSORS; p++) {
190 for (int t = 0; t < TASKS_PER_PROC; t++) {
191 esp_rom_printf("Checking CPU %d task %d (expected 1 actual %d)\n", p, t, counters[p][t].counter);
192 TEST_ASSERT_EQUAL(1, counters[p][t].counter);
193 }
194 }
195
196 /* Clean up */
197 for (int p = 0; p < portNUM_PROCESSORS; p++) {
198 for (int t = 0; t < TASKS_PER_PROC; t++) {
199 vTaskDelete(tasks[p][t]);
200 vSemaphoreDelete(counters[p][t].trigger_sem);
201 }
202 }
203 }
204
205 #ifndef CONFIG_FREERTOS_UNICORE
206 static volatile bool sched_suspended;
suspend_scheduler_5ms_task_fn(void * ignore)207 static void suspend_scheduler_5ms_task_fn(void *ignore)
208 {
209 vTaskSuspendAll();
210 sched_suspended = true;
211 for (int i = 0; i <5; i++) {
212 esp_rom_delay_us(1000);
213 }
214 xTaskResumeAll();
215 sched_suspended = false;
216 vTaskDelete(NULL);
217 }
218
219 /* If the scheduler is disabled on one CPU (A) with a task blocked on something, and a task
220 on B (where scheduler is running) wakes it, then the task on A should be woken on resume.
221 */
222 TEST_CASE("Scheduler disabled on CPU B, tasks on A can wake", "[freertos]")
223 {
224 TaskHandle_t counter_task;
225 SemaphoreHandle_t wake_sem = xSemaphoreCreateMutex();
226 xSemaphoreTake(wake_sem, 0);
227 counter_config_t count_config = {
228 .trigger_sem = wake_sem,
229 .counter = 0,
230 };
231 xTaskCreatePinnedToCore(counter_task_fn, "counter", 2048,
232 &count_config, UNITY_FREERTOS_PRIORITY + 1,
233 &counter_task, !UNITY_FREERTOS_CPU);
234
235 xTaskCreatePinnedToCore(suspend_scheduler_5ms_task_fn, "suspender", 2048,
236 NULL, UNITY_FREERTOS_PRIORITY - 1,
237 NULL, !UNITY_FREERTOS_CPU);
238
239 /* counter task is now blocked on other CPU, waiting for wake_sem, and we expect
240 that this CPU's scheduler will be suspended for 5ms shortly... */
241 while(!sched_suspended) { }
242
243 xSemaphoreGive(wake_sem);
244 esp_rom_delay_us(1000);
245 // Bit of a race here if the other CPU resumes its scheduler, but 5ms is a long time... */
246 TEST_ASSERT(sched_suspended);
247 TEST_ASSERT_EQUAL(0, count_config.counter); // the other task hasn't woken yet, because scheduler is off
248 TEST_ASSERT(sched_suspended);
249
250 /* wait for the rest of the 5ms... */
251 while(sched_suspended) { }
252
253 esp_rom_delay_us(100);
254 TEST_ASSERT_EQUAL(1, count_config.counter); // when scheduler resumes, counter task should immediately count
255
256 vTaskDelete(counter_task);
257 }
258 #endif
259