1 /*
2 * SPDX-FileCopyrightText: 2021 Espressif Systems (Shanghai) CO LTD
3 *
4 * SPDX-License-Identifier: Apache-2.0
5 */
6
7 #include <stdbool.h>
8 #include "freertos/FreeRTOS.h"
9 #include "freertos/task.h"
10 #include "freertos/queue.h"
11 #include "esp_rom_sys.h"
12 #include "hal/interrupt_controller_hal.h"
13 #include "unity.h"
14 #include "test_utils.h"
15
16 /*
17 Test Best Effort Round Robin Scheduling:
18
19 The following test case tests the "Best Effort Round Robin Scheduling" that fixes the skipping behavior found in older
20 versions of the ESP-IDF SMP FreeRTOS (see docs for more details about Best Effort Round Robin Scheduling).
21
22 This test...
23 - Only runs under dual core configuration
24 - Will disable the tick interrupts of both cores
25
26 Test flow as follows:
27
28 1. Stop preemption on core 0 by raising the priority of the unity task
29 2. Stop preemption on core 0 by creating a blocker task
30 3. Disable tick interrupts on both cores
31 4. Create N spin tasks on each core, each with a sequential task_code
32 5. Unblock those spin tasks in a sequential order
33 6. Lower priority of unity task and stop the blocker task so that spin tasks are run
34 7. Each time a spin task is run (i.e., an iteration) it will send its task code to a queue
35 8. Spin tasks will clean themselves up
36 9. The queue should contain the task codes of the spin tasks in the order they were started in, thus showing that round
37 robin schedules the tasks in a sequential order.
38 */
39
40 #if !defined(CONFIG_FREERTOS_UNICORE) && (defined(CONFIG_FREERTOS_CORETIMER_0) || defined(CONFIG_FREERTOS_CORETIMER_1))
41
42 #define SPIN_TASK_PRIO (CONFIG_UNITY_FREERTOS_PRIORITY + 1)
43 #define SPIN_TASK_NUM_ITER 3
44 #define TASK_STACK_SIZE 1024
45 #define NUM_PINNED_SPIN_TASK_PER_CORE 3
46 #if defined(CONFIG_FREERTOS_CORETIMER_0)
47 #define TICK_INTR_IDX 6
48 #else //defined(CONFIG_FREERTOS_CORETIMER_1)
49 #define TICK_INTR_IDX 15
50 #endif
51
52 static QueueHandle_t core0_run_order_queue;
53 static QueueHandle_t core1_run_order_queue;
54 static uint32_t total_iter_count[configNUM_CORES] = {0};
55
spin_task(void * arg)56 static void spin_task(void *arg)
57 {
58 uint32_t task_code = (uint32_t)arg;
59 QueueHandle_t run_order_queue = ((task_code >> 4) == 0) ? core0_run_order_queue : core1_run_order_queue;
60
61 //Wait to be started
62 ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
63
64 for (int i = 0; i < SPIN_TASK_NUM_ITER; i++) {
65 xQueueSend(run_order_queue, &task_code, 0);
66 //No need for critical sections as tick interrupt is disabled
67 total_iter_count[xPortGetCoreID()]++;
68 taskYIELD();
69 }
70
71 //Last iteration of the last spin task on this core. Reenable this core's tick interrupt
72 if (total_iter_count[xPortGetCoreID()] == (NUM_PINNED_SPIN_TASK_PER_CORE * SPIN_TASK_NUM_ITER)) {
73 interrupt_controller_hal_enable_interrupts(1 <<TICK_INTR_IDX);
74 }
75 vTaskDelete(NULL);
76 }
77
blocker_task(void * arg)78 static void blocker_task(void *arg)
79 {
80 volatile bool *exit_loop = (volatile bool *)arg;
81
82 //Disable tick interrupts on core 1 the duration of the test
83 taskDISABLE_INTERRUPTS();
84 interrupt_controller_hal_disable_interrupts(1 << TICK_INTR_IDX);
85 taskENABLE_INTERRUPTS();
86
87 while (!*exit_loop) {
88 ;
89 }
90
91 //Wait to be resumed
92 vTaskSuspend(NULL);
93
94 //Reenable tick interrupt on core 1
95 taskDISABLE_INTERRUPTS();
96 interrupt_controller_hal_enable_interrupts(1 << TICK_INTR_IDX);
97 taskENABLE_INTERRUPTS();
98
99 vTaskDelete(NULL);
100 }
101
102 TEST_CASE("Test FreeRTOS Scheduling Round Robin", "[freertos]")
103 {
104 core0_run_order_queue = xQueueCreate(SPIN_TASK_NUM_ITER * NUM_PINNED_SPIN_TASK_PER_CORE, sizeof(uint32_t));
105 core1_run_order_queue = xQueueCreate(SPIN_TASK_NUM_ITER * NUM_PINNED_SPIN_TASK_PER_CORE, sizeof(uint32_t));
106
107 /* Increase priority of unity task so that the spin tasks don't preempt us
108 during task creation. */
109 vTaskPrioritySet(NULL, SPIN_TASK_PRIO + 1);
110 /* Create a task on core 1 of the same priority to block core 1 */
111 volatile bool suspend_blocker = false;
112 TaskHandle_t blocker_task_hdl;
113 xTaskCreatePinnedToCore(blocker_task, "blk", TASK_STACK_SIZE, (void *)&suspend_blocker, SPIN_TASK_PRIO + 1, &blocker_task_hdl, 1);
114
115 //Disable tick interrupts on core 0 the duration of the test
116 taskDISABLE_INTERRUPTS();
117 interrupt_controller_hal_disable_interrupts(1 << TICK_INTR_IDX);
118 taskENABLE_INTERRUPTS();
119
120 TaskHandle_t core0_task_hdls[NUM_PINNED_SPIN_TASK_PER_CORE];
121 TaskHandle_t core1_task_hdls[NUM_PINNED_SPIN_TASK_PER_CORE];
122
123 for (int i = 0; i < NUM_PINNED_SPIN_TASK_PER_CORE; i++) {
124 //Create a spin task pinned to core 0
125 xTaskCreatePinnedToCore(spin_task, "spin", TASK_STACK_SIZE, (void *)(0x00 + i), SPIN_TASK_PRIO, &core0_task_hdls[i], 0);
126 //Create a spin task pinned to core 1
127 xTaskCreatePinnedToCore(spin_task, "spin", TASK_STACK_SIZE, (void *)(0x10 + i), SPIN_TASK_PRIO, &core1_task_hdls[i], 1);
128 }
129
130 /* Start the tasks in a particular order. This order should be reflected in
131 in the round robin scheduling on each core */
132 for (int i = 0; i < NUM_PINNED_SPIN_TASK_PER_CORE; i++) {
133 //Start a spin task on core 0
134 xTaskNotifyGive(core0_task_hdls[i]);
135 //Start a spin task on core 1
136 xTaskNotifyGive(core1_task_hdls[i]);
137 }
138
139 //Lower priority of this task and stop blocker task to allow the spin tasks to be scheduled
140 suspend_blocker = true;
141 vTaskPrioritySet(NULL, UNITY_FREERTOS_PRIORITY);
142
143 //Give a enough delay to allow all iterations of the round robin to occur
144 esp_rom_delay_us(10000);
145
146 for (int i = 0; i < SPIN_TASK_NUM_ITER; i++) {
147 for (int j = 0; j < NUM_PINNED_SPIN_TASK_PER_CORE; j++) {
148 uint32_t core0_entry;
149 uint32_t core1_entry;
150 TEST_ASSERT_EQUAL(pdTRUE, xQueueReceive(core0_run_order_queue, &core0_entry, 0));
151 TEST_ASSERT_EQUAL(pdTRUE, xQueueReceive(core1_run_order_queue, &core1_entry, 0));
152 TEST_ASSERT_EQUAL(0x00 + j, core0_entry);
153 TEST_ASSERT_EQUAL(0x10 + j, core1_entry);
154 }
155 }
156
157 //Resume the blocker task for cleanup
158 vTaskResume(blocker_task_hdl);
159 //Reenable tick interrupt on core 0
160 taskDISABLE_INTERRUPTS();
161 interrupt_controller_hal_enable_interrupts(1 << TICK_INTR_IDX);
162 taskENABLE_INTERRUPTS();
163 vTaskDelay(10); //Wait for blocker task to clean up
164 //Clean up queues
165 vQueueDelete(core0_run_order_queue);
166 vQueueDelete(core1_run_order_queue);
167 }
168
169 #endif //!defined(CONFIG_FREERTOS_UNICORE) && (efined(CONFIG_FREERTOS_CORETIMER_0) || defined(CONFIG_FREERTOS_CORETIMER_1))
170