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