1 /*
2  Combined unit tests & benchmarking for spinlock "portMUX" functionality
3 */
4 
5 #include <esp_types.h>
6 #include <stdio.h>
7 
8 #include "freertos/FreeRTOS.h"
9 #include "freertos/task.h"
10 #include "freertos/semphr.h"
11 #include "freertos/queue.h"
12 #include "unity.h"
13 #include "soc/cpu.h"
14 #include "hal/cpu_hal.h"
15 
16 #include "test_utils.h"
17 
18 #define REPEAT_OPS 10000
19 
20 static uint32_t start, end;
21 
22 #define BENCHMARK_START() do {                  \
23         start = cpu_hal_get_cycle_count();                     \
24     } while(0)
25 
26 #define BENCHMARK_END(OPERATION) do {                       \
27         end = cpu_hal_get_cycle_count();                                          \
28         printf("%s took %d cycles/op (%d cycles for %d ops)\n",     \
29                OPERATION, (end - start)/REPEAT_OPS,                 \
30                (end - start), REPEAT_OPS);                          \
31     } while(0)
32 
33 TEST_CASE("portMUX spinlocks (no contention)", "[freertos]")
34 {
35     portMUX_TYPE mux = portMUX_INITIALIZER_UNLOCKED;
36     BENCHMARK_START();
37 
38     for (int i = 0; i < REPEAT_OPS; i++) {
39         portENTER_CRITICAL_ISR(&mux);
40         portEXIT_CRITICAL_ISR(&mux);
41     }
42     BENCHMARK_END("no contention lock");
43 
44 #ifdef CONFIG_FREERTOS_UNICORE
45     TEST_PERFORMANCE_LESS_THAN(FREERTOS_SPINLOCK_CYCLES_PER_OP_UNICORE, "%d cycles/op", ((end - start)/REPEAT_OPS));
46 #else
47 #if CONFIG_SPIRAM
48     TEST_PERFORMANCE_LESS_THAN(FREERTOS_SPINLOCK_CYCLES_PER_OP_PSRAM, "%d cycles/op", ((end - start)/REPEAT_OPS));
49 #else
50     TEST_PERFORMANCE_LESS_THAN(FREERTOS_SPINLOCK_CYCLES_PER_OP, "%d cycles/op", ((end - start)/REPEAT_OPS));
51 #endif
52 #endif
53 }
54 
55 TEST_CASE("portMUX recursive locks (no contention)", "[freertos]")
56 {
57     portMUX_TYPE mux = portMUX_INITIALIZER_UNLOCKED;
58     BENCHMARK_START();
59 
60     const int RECURSE_COUNT = 25;
61 
62     for (int i = 0; i < REPEAT_OPS / RECURSE_COUNT; i++) {
63         for (int j = 0; j < RECURSE_COUNT; j++) {
64             portENTER_CRITICAL(&mux);
65         }
66         for (int j = 0; j < RECURSE_COUNT; j++) {
67             portEXIT_CRITICAL(&mux);
68         }
69     }
70     BENCHMARK_END("no contention recursive");
71 }
72 
73 #if portNUM_PROCESSORS == 2
74 
75 static volatile int shared_value;
76 static portMUX_TYPE shared_mux;
77 static xSemaphoreHandle done_sem;
78 
task_shared_value_increment(void * ignore)79 static void task_shared_value_increment(void *ignore)
80 {
81     for (int i = 0; i < REPEAT_OPS; i++) {
82         portENTER_CRITICAL(&shared_mux);
83         shared_value++;
84         portEXIT_CRITICAL(&shared_mux);
85     }
86     xSemaphoreGive(done_sem);
87     vTaskDelete(NULL);
88 }
89 
90 TEST_CASE("portMUX cross-core locking", "[freertos]")
91 {
92     done_sem = xSemaphoreCreateCounting(2, 0);
93     vPortCPUInitializeMutex(&shared_mux);
94     shared_value = 0;
95 
96     BENCHMARK_START();
97 
98     xTaskCreatePinnedToCore(task_shared_value_increment, "INC0", 2048, NULL, UNITY_FREERTOS_PRIORITY + 1, NULL, UNITY_FREERTOS_CPU ? 0 : 1);
99     xTaskCreatePinnedToCore(task_shared_value_increment, "INC1", 2048, NULL, UNITY_FREERTOS_PRIORITY + 1, NULL, UNITY_FREERTOS_CPU);
100 
101     for(int i = 0; i < 2; i++) {
102         if(!xSemaphoreTake(done_sem, 10000/portTICK_PERIOD_MS)) {
103             TEST_FAIL_MESSAGE("done_sem not released by test task");
104         }
105     }
106 
107     BENCHMARK_END("cross-core incrementing");
108     vSemaphoreDelete(done_sem);
109 
110     TEST_ASSERT_EQUAL_INT(REPEAT_OPS * 2, shared_value);
111 }
112 
113 TEST_CASE("portMUX high contention", "[freertos]")
114 {
115     const int TOTAL_TASKS = 8; /* half on each core */
116     done_sem = xSemaphoreCreateCounting(TOTAL_TASKS, 0);
117     vPortCPUInitializeMutex(&shared_mux);
118     shared_value = 0;
119 
120     BENCHMARK_START();
121 
122     for (int i = 0; i < TOTAL_TASKS / 2; i++) {
123         /* as each task has a higher priority than previous, expect
124            them to preempt the earlier created task, at least on the
125            other core (this core has the unity task, until that
126            blocks)... */
127         xTaskCreatePinnedToCore(task_shared_value_increment, "INC0", 2048, NULL, tskIDLE_PRIORITY + 1 + i, NULL, UNITY_FREERTOS_CPU ? 0 : 1);
128         xTaskCreatePinnedToCore(task_shared_value_increment, "INC1", 2048, NULL, tskIDLE_PRIORITY + 1 + i, NULL, UNITY_FREERTOS_CPU);
129     }
130 
131     for(int i = 0; i < TOTAL_TASKS; i++) {
132         if(!xSemaphoreTake(done_sem, 10000/portTICK_PERIOD_MS)) {
133             TEST_FAIL_MESSAGE("done_sem not released by test task");
134         }
135     }
136 
137     BENCHMARK_END("cross-core high contention");
138     vSemaphoreDelete(done_sem);
139 
140     TEST_ASSERT_EQUAL_INT(REPEAT_OPS * TOTAL_TASKS, shared_value);
141 }
142 
143 #endif // portNUM_PROCESSORS == 2
144