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