1 /*
2 * SPDX-FileCopyrightText: 2021 Espressif Systems (Shanghai) CO LTD
3 *
4 * SPDX-License-Identifier: Apache-2.0
5 */
6 #include "sdkconfig.h"
7
8 #if CONFIG_IDF_TARGET_ESP32
9 #include <stdio.h>
10 #include <stdlib.h>
11 #include "xtensa/core-macros.h"
12 #include "xtensa/hal.h"
13 #include "esp_types.h"
14 #include "esp32/clk.h"
15
16 #include "freertos/FreeRTOS.h"
17 #include "freertos/task.h"
18 #include "freertos/semphr.h"
19 #include "freertos/xtensa_timer.h"
20 #include "soc/cpu.h"
21 #include "unity.h"
22 #include "test_utils.h"
23 #include "esp_rom_uart.h"
24 #include "hal/uart_types.h"
25 #include "hal/uart_ll.h"
26 #include "soc/dport_reg.h"
27 #include "soc/rtc.h"
28 #include "hal/cpu_hal.h"
29 #include "esp_intr_alloc.h"
30 #include "driver/timer.h"
31
32
33 #define MHZ (1000000)
34 static volatile bool exit_flag;
35 static bool dport_test_result;
36 static bool apb_test_result;
37 uint32_t volatile apb_intr_test_result;
38
accessDPORT(void * pvParameters)39 static void accessDPORT(void *pvParameters)
40 {
41 xSemaphoreHandle *sema = (xSemaphoreHandle *) pvParameters;
42 uint32_t dport_date = DPORT_REG_READ(DPORT_DATE_REG);
43
44 dport_test_result = true;
45
46 // although exit flag is set in another task, checking (exit_flag == false) is safe
47 while (exit_flag == false) {
48 if (dport_date != DPORT_REG_READ(DPORT_DATE_REG)) {
49 dport_test_result = false;
50 break;
51 }
52 }
53
54 xSemaphoreGive(*sema);
55 vTaskDelete(NULL);
56 }
57
accessAPB(void * pvParameters)58 static void accessAPB(void *pvParameters)
59 {
60 xSemaphoreHandle *sema = (xSemaphoreHandle *) pvParameters;
61 uint32_t uart_date = REG_READ(UART_DATE_REG(0));
62
63 apb_test_result = true;
64
65 // although exit flag is set in another task, checking (exit_flag == false) is safe
66 while (exit_flag == false) {
67 if (uart_date != REG_READ(UART_DATE_REG(0))) {
68 apb_test_result = false;
69 break;
70 }
71 }
72
73 xSemaphoreGive(*sema);
74 vTaskDelete(NULL);
75 }
76
run_tasks(const char * task1_description,void (* task1_func)(void *),const char * task2_description,void (* task2_func)(void *),uint32_t delay_ms)77 void run_tasks(const char *task1_description, void (* task1_func)(void *), const char *task2_description, void (* task2_func)(void *), uint32_t delay_ms)
78 {
79 apb_intr_test_result = 1;
80 int i;
81 TaskHandle_t th[2];
82 xSemaphoreHandle exit_sema[2];
83
84 for (i=0; i<2; i++) {
85 if((task1_func != NULL && i == 0) || (task2_func != NULL && i == 1)){
86 exit_sema[i] = xSemaphoreCreateBinary();
87 }
88 }
89
90 exit_flag = false;
91
92 #ifndef CONFIG_FREERTOS_UNICORE
93 printf("assign task accessing DPORT to core 0 and task accessing APB to core 1\n");
94 if(task1_func != NULL) xTaskCreatePinnedToCore(task1_func, task1_description, 2048, &exit_sema[0], UNITY_FREERTOS_PRIORITY - 1, &th[0], 0);
95 if(task2_func != NULL) xTaskCreatePinnedToCore(task2_func, task2_description, 2048, &exit_sema[1], UNITY_FREERTOS_PRIORITY - 1, &th[1], 1);
96 #else
97 printf("assign task accessing DPORT and accessing APB\n");
98 if(task1_func != NULL) xTaskCreate(task1_func, task1_description, 2048, &exit_sema[0], UNITY_FREERTOS_PRIORITY - 1, &th[0]);
99 if(task2_func != NULL) xTaskCreate(task2_func, task2_description, 2048, &exit_sema[1], UNITY_FREERTOS_PRIORITY - 1, &th[1]);
100 #endif
101
102 printf("start wait for %d seconds [Test %s and %s]\n", delay_ms/1000, task1_description, task2_description);
103 vTaskDelay(delay_ms / portTICK_PERIOD_MS);
104
105 // set exit flag to let thread exit
106 exit_flag = true;
107
108 for (i=0; i<2; i++) {
109 if ((task1_func != NULL && i == 0) || (task2_func != NULL && i == 1)) {
110 xSemaphoreTake(exit_sema[i], portMAX_DELAY);
111 vSemaphoreDelete(exit_sema[i]);
112 }
113 }
114 TEST_ASSERT(dport_test_result == true && apb_test_result == true && apb_intr_test_result == 1);
115 }
116
117 TEST_CASE("access DPORT and APB at same time", "[esp32]")
118 {
119 dport_test_result = false;
120 apb_test_result = false;
121 printf("CPU_FREQ = %d MHz\n", esp_clk_cpu_freq());
122 run_tasks("accessDPORT", accessDPORT, "accessAPB", accessAPB, 10000);
123 }
124
run_tasks_with_change_freq_cpu(int cpu_freq_mhz)125 void run_tasks_with_change_freq_cpu(int cpu_freq_mhz)
126 {
127 const int uart_num = CONFIG_ESP_CONSOLE_UART_NUM;
128 const int uart_baud = CONFIG_ESP_CONSOLE_UART_BAUDRATE;
129 dport_test_result = false;
130 apb_test_result = false;
131 rtc_cpu_freq_config_t old_config;
132 rtc_clk_cpu_freq_get_config(&old_config);
133
134 printf("CPU_FREQ = %d MHz\n", old_config.freq_mhz);
135
136 if (cpu_freq_mhz != old_config.freq_mhz) {
137 rtc_cpu_freq_config_t new_config;
138 bool res = rtc_clk_cpu_freq_mhz_to_config(cpu_freq_mhz, &new_config);
139 assert(res && "invalid frequency value");
140
141 esp_rom_uart_tx_wait_idle(uart_num);
142 rtc_clk_cpu_freq_set_config(&new_config);
143 uart_ll_set_sclk(UART_LL_GET_HW(uart_num), UART_SCLK_APB);
144 uart_ll_set_baudrate(UART_LL_GET_HW(uart_num), uart_baud);
145 /* adjust RTOS ticks */
146 _xt_tick_divisor = cpu_freq_mhz * 1000000 / XT_TICK_PER_SEC;
147 vTaskDelay(2);
148
149 printf("CPU_FREQ switched to %d MHz\n", cpu_freq_mhz);
150 }
151 run_tasks("accessDPORT", accessDPORT, "accessAPB", accessAPB, 10000);
152
153 // return old freq.
154 esp_rom_uart_tx_wait_idle(uart_num);
155 rtc_clk_cpu_freq_set_config(&old_config);
156 uart_ll_set_sclk(UART_LL_GET_HW(uart_num), UART_SCLK_APB);
157 uart_ll_set_baudrate(UART_LL_GET_HW(uart_num), uart_baud);
158 _xt_tick_divisor = old_config.freq_mhz * 1000000 / XT_TICK_PER_SEC;
159 }
160
161 TEST_CASE("access DPORT and APB at same time (Freq CPU and APB = 80 MHz)", "[esp32] [ignore]")
162 {
163 run_tasks_with_change_freq_cpu(80);
164 }
165
166 TEST_CASE("access DPORT and APB at same time (Freq CPU and APB = 40 MHz (XTAL))", "[esp32]")
167 {
168 run_tasks_with_change_freq_cpu((int) rtc_clk_xtal_freq_get());
169 }
170
171 static uint32_t stall_other_cpu_counter;
172 static uint32_t pre_reading_apb_counter;
173 static uint32_t apb_counter;
174
accessDPORT_stall_other_cpu(void * pvParameters)175 static void accessDPORT_stall_other_cpu(void *pvParameters)
176 {
177 xSemaphoreHandle *sema = (xSemaphoreHandle *) pvParameters;
178 uint32_t dport_date = DPORT_REG_READ(DPORT_DATE_REG);
179 uint32_t dport_date_cur;
180 dport_test_result = true;
181 stall_other_cpu_counter = 0;
182 // although exit flag is set in another task, checking (exit_flag == false) is safe
183 while (exit_flag == false) {
184 ++stall_other_cpu_counter;
185 DPORT_STALL_OTHER_CPU_START();
186 dport_date_cur = _DPORT_REG_READ(DPORT_DATE_REG);
187 DPORT_STALL_OTHER_CPU_END();
188 if (dport_date != dport_date_cur) {
189 apb_test_result = false;
190 break;
191 }
192 }
193
194 xSemaphoreGive(*sema);
195 vTaskDelete(NULL);
196 }
197
accessAPB_measure_performance(void * pvParameters)198 static void accessAPB_measure_performance(void *pvParameters)
199 {
200 xSemaphoreHandle *sema = (xSemaphoreHandle *) pvParameters;
201 uint32_t uart_date = REG_READ(UART_DATE_REG(0));
202
203 apb_test_result = true;
204 apb_counter = 0;
205 // although exit flag is set in another task, checking (exit_flag == false) is safe
206 while (exit_flag == false) {
207 ++apb_counter;
208 if (uart_date != REG_READ(UART_DATE_REG(0))) {
209 apb_test_result = false;
210 break;
211 }
212 }
213
214 xSemaphoreGive(*sema);
215 vTaskDelete(NULL);
216 }
217
accessDPORT_pre_reading_apb(void * pvParameters)218 static void accessDPORT_pre_reading_apb(void *pvParameters)
219 {
220 xSemaphoreHandle *sema = (xSemaphoreHandle *) pvParameters;
221 uint32_t dport_date = DPORT_REG_READ(DPORT_DATE_REG);
222 uint32_t dport_date_cur;
223 dport_test_result = true;
224 pre_reading_apb_counter = 0;
225 // although exit flag is set in another task, checking (exit_flag == false) is safe
226 while (exit_flag == false) {
227 ++pre_reading_apb_counter;
228 dport_date_cur = DPORT_REG_READ(DPORT_DATE_REG);
229 if (dport_date != dport_date_cur) {
230 apb_test_result = false;
231 break;
232 }
233 }
234
235 xSemaphoreGive(*sema);
236 vTaskDelete(NULL);
237 }
238
239 TEST_CASE("test for DPORT access performance", "[esp32]")
240 {
241 dport_test_result = true;
242 apb_test_result = true;
243 typedef struct {
244 uint32_t dport;
245 uint32_t apb;
246 uint32_t summ;
247 } test_performance_t;
248 test_performance_t t[5] = {0};
249 uint32_t delay_ms = 5000;
250
251 run_tasks("-", NULL, "accessAPB", accessAPB_measure_performance, delay_ms);
252 t[0].apb = apb_counter;
253 t[0].dport = 0;
254 t[0].summ = t[0].apb + t[0].dport;
255
256 run_tasks("accessDPORT_stall_other_cpu", accessDPORT_stall_other_cpu, "-", NULL, delay_ms);
257 t[1].apb = 0;
258 t[1].dport = stall_other_cpu_counter;
259 t[1].summ = t[1].apb + t[1].dport;
260
261 run_tasks("accessDPORT_pre_reading_apb", accessDPORT_pre_reading_apb, "-", NULL, delay_ms);
262 t[2].apb = 0;
263 t[2].dport = pre_reading_apb_counter;
264 t[2].summ = t[2].apb + t[2].dport;
265
266 run_tasks("accessDPORT_stall_other_cpu", accessDPORT_stall_other_cpu, "accessAPB", accessAPB_measure_performance, delay_ms);
267 t[3].apb = apb_counter;
268 t[3].dport = stall_other_cpu_counter;
269 t[3].summ = t[3].apb + t[3].dport;
270
271 run_tasks("accessDPORT_pre_reading_apb", accessDPORT_pre_reading_apb, "accessAPB", accessAPB_measure_performance, delay_ms);
272 t[4].apb = apb_counter;
273 t[4].dport = pre_reading_apb_counter;
274 t[4].summ = t[4].apb + t[4].dport;
275
276 printf("\nPerformance table: \n"
277 "The number of simultaneous read operations of the APB and DPORT registers\n"
278 "by different methods for %d seconds.\n", delay_ms/1000);
279 printf("+-----------------------+----------+----------+----------+\n");
280 printf("| Method read DPORT | DPORT | APB | SUMM |\n");
281 printf("+-----------------------+----------+----------+----------+\n");
282 printf("|1.Only accessAPB |%10d|%10d|%10d|\n", t[0].dport, t[0].apb, t[0].summ);
283 printf("|2.Only STALL_OTHER_CPU |%10d|%10d|%10d|\n", t[1].dport, t[1].apb, t[1].summ);
284 printf("|3.Only PRE_READ_APB_REG|%10d|%10d|%10d|\n", t[2].dport, t[2].apb, t[2].summ);
285 printf("+-----------------------+----------+----------+----------+\n");
286 printf("|4.STALL_OTHER_CPU |%10d|%10d|%10d|\n", t[3].dport, t[3].apb, t[3].summ);
287 printf("|5.PRE_READ_APB_REG |%10d|%10d|%10d|\n", t[4].dport, t[4].apb, t[4].summ);
288 printf("+-----------------------+----------+----------+----------+\n");
289 printf("| ratio=PRE_READ/STALL |%10f|%10f|%10f|\n", (float)t[4].dport/t[3].dport, (float)t[4].apb/t[3].apb, (float)t[4].summ/t[3].summ);
290 printf("+-----------------------+----------+----------+----------+\n");
291 }
292
293 #define REPEAT_OPS 10000
294
295 static uint32_t start, end;
296
297 #define BENCHMARK_START() do { \
298 RSR(CCOUNT, start); \
299 } while(0)
300
301 #define BENCHMARK_END(OPERATION) do { \
302 RSR(CCOUNT, end); \
303 printf("%s took %d cycles/op (%d cycles for %d ops)\n", \
304 OPERATION, (end - start)/REPEAT_OPS, \
305 (end - start), REPEAT_OPS); \
306 } while(0)
307
308 TEST_CASE("BENCHMARK for DPORT access performance", "[freertos]")
309 {
310 BENCHMARK_START();
311 for (int i = 0; i < REPEAT_OPS; i++) {
312 DPORT_STALL_OTHER_CPU_START();
313 _DPORT_REG_READ(DPORT_DATE_REG);
314 DPORT_STALL_OTHER_CPU_END();
315 }
316 BENCHMARK_END("[old]DPORT access STALL OTHER CPU");
317
318
319 BENCHMARK_START();
320 for (int i = 0; i < REPEAT_OPS; i++) {
321 DPORT_REG_READ(DPORT_DATE_REG);
322 }
323 BENCHMARK_END("[new]DPORT access PRE-READ APB REG");
324
325
326 BENCHMARK_START();
327 for (int i = 0; i < REPEAT_OPS; i++) {
328 DPORT_SEQUENCE_REG_READ(DPORT_DATE_REG);
329 }
330 BENCHMARK_END("[seq]DPORT access PRE-READ APB REG");
331
332
333 BENCHMARK_START();
334 for (int i = 0; i < REPEAT_OPS; i++) {
335 REG_READ(UART_DATE_REG(0));
336 }
337 BENCHMARK_END("REG_READ");
338
339
340 BENCHMARK_START();
341 for (int i = 0; i < REPEAT_OPS; i++) {
342 _DPORT_REG_READ(DPORT_DATE_REG);
343 }
344 BENCHMARK_END("_DPORT_REG_READ");
345 }
346
347 uint32_t xt_highint5_read_apb;
348
349 #ifndef CONFIG_FREERTOS_UNICORE
350 timer_isr_handle_t inth;
351 xSemaphoreHandle sync_sema;
352
init_hi_interrupt(void * arg)353 static void init_hi_interrupt(void *arg)
354 {
355 printf("init hi_interrupt on CPU%d \n", xPortGetCoreID());
356 TEST_ESP_OK(esp_intr_alloc(ETS_INTERNAL_TIMER2_INTR_SOURCE, ESP_INTR_FLAG_LEVEL5 | ESP_INTR_FLAG_IRAM, NULL, NULL, &inth));
357 while (exit_flag == false);
358 esp_intr_free(inth);
359 printf("disable hi_interrupt on CPU%d \n", xPortGetCoreID());
360 vTaskDelete(NULL);
361 }
362
accessDPORT2_stall_other_cpu(void * pvParameters)363 static void accessDPORT2_stall_other_cpu(void *pvParameters)
364 {
365 xSemaphoreHandle *sema = (xSemaphoreHandle *) pvParameters;
366 dport_test_result = true;
367 while (exit_flag == false) {
368 DPORT_STALL_OTHER_CPU_START();
369 XTHAL_SET_CCOMPARE(2, cpu_hal_get_cycle_count());
370 xt_highint5_read_apb = 1;
371 for (int i = 0; i < 200; ++i) {
372 if (_DPORT_REG_READ(DPORT_DATE_REG) != _DPORT_REG_READ(DPORT_DATE_REG)) {
373 apb_test_result = false;
374 break;
375 }
376 }
377 xt_highint5_read_apb = 0;
378 DPORT_STALL_OTHER_CPU_END();
379 }
380 printf("accessDPORT2_stall_other_cpu finish\n");
381
382 xSemaphoreGive(*sema);
383 vTaskDelete(NULL);
384 }
385
386 TEST_CASE("Check stall workaround DPORT and Hi-interrupt", "[esp32]")
387 {
388 xt_highint5_read_apb = 0;
389 dport_test_result = false;
390 apb_test_result = true;
391 TEST_ASSERT(xTaskCreatePinnedToCore(&init_hi_interrupt, "init_hi_intr", 2048, NULL, 6, NULL, 1) == pdTRUE);
392 // Access DPORT(stall other cpu method) - CPU0
393 // STALL - CPU1
394 // Hi-interrupt - CPU1
395 run_tasks("accessDPORT2_stall_other_cpu", accessDPORT2_stall_other_cpu, " - ", NULL, 10000);
396 }
397
accessDPORT2(void * pvParameters)398 static void accessDPORT2(void *pvParameters)
399 {
400 xSemaphoreHandle *sema = (xSemaphoreHandle *) pvParameters;
401 dport_test_result = true;
402
403 TEST_ESP_OK(esp_intr_alloc(ETS_INTERNAL_TIMER2_INTR_SOURCE, ESP_INTR_FLAG_LEVEL5 | ESP_INTR_FLAG_IRAM, NULL, NULL, &inth));
404
405 while (exit_flag == false) {
406 XTHAL_SET_CCOMPARE(2, cpu_hal_get_cycle_count() + 21);
407 for (int i = 0; i < 200; ++i) {
408 if (DPORT_REG_READ(DPORT_DATE_REG) != DPORT_REG_READ(DPORT_DATE_REG)) {
409 dport_test_result = false;
410 break;
411 }
412 }
413 }
414 esp_intr_free(inth);
415 printf("accessDPORT2 finish\n");
416
417 xSemaphoreGive(*sema);
418 vTaskDelete(NULL);
419 }
420
421 TEST_CASE("Check pre-read workaround DPORT and Hi-interrupt", "[esp32]")
422 {
423 xt_highint5_read_apb = 0;
424 dport_test_result = false;
425 apb_test_result = true;
426 // Access DPORT(pre-read method) - CPU1
427 // Hi-interrupt - CPU1
428 run_tasks("accessAPB", accessAPB, "accessDPORT2", accessDPORT2, 10000);
429 }
430
431 static uint32_t s_shift_counter;
432
433 /*
434 The test_dport_access_reg_read() is similar DPORT_REG_READ() but has differents:
435 - generate an interrupt by SET_CCOMPARE
436 - additional branch command helps get good reproducing an issue with breaking the DPORT pre-read workaround
437 - uncomment (1) and comment (2) it allows seeing the broken pre-read workaround
438 For pre-reading the workaround, it is important that the two reading commands APB and DPORT
439 are executed without interruption. For this reason, it disables interrupts and to do reading inside the safe area.
440 But despite a disabling interrupt it was still possible that these two readings can be interrupted.
441 The reason is linked with work parallel execution commands in the pipeline (it is not a bug).
442 To resolve this issue (1) was moved to (2) position into the disabled interrupt part.
443 When the read command is interrupted after stage E(execute), the result of its execution will be saved in the internal buffer,
444 and after returning from the interrupt, this command takes this value from the buffer without repeating the reading,
445 which is critical for the DPORT pre-read workaround. To fix it we added additional command under safe area ((1)->(2)).
446 */
test_dport_access_reg_read(uint32_t reg)447 static uint32_t IRAM_ATTR test_dport_access_reg_read(uint32_t reg)
448 {
449 #if defined(BOOTLOADER_BUILD) || !defined(CONFIG_ESP32_DPORT_WORKAROUND) || !defined(ESP_PLATFORM)
450 return _DPORT_REG_READ(reg);
451 #else
452 uint32_t apb;
453 unsigned int intLvl;
454 XTHAL_SET_CCOMPARE(2, cpu_hal_get_cycle_count() + s_shift_counter);
455 __asm__ __volatile__ (\
456 /* "movi %[APB], "XTSTR(0x3ff40078)"\n" */ /* (1) uncomment for reproduce issue */ \
457 "bnez %[APB], kl1\n" /* this branch command helps get good reproducing */ \
458 "kl1:\n"\
459 "rsil %[LVL], "XTSTR(CONFIG_ESP32_DPORT_DIS_INTERRUPT_LVL)"\n"\
460 "movi %[APB], "XTSTR(0x3ff40078)"\n" /* (2) comment for reproduce issue */ \
461 "l32i %[APB], %[APB], 0\n"\
462 "l32i %[REG], %[REG], 0\n"\
463 "wsr %[LVL], "XTSTR(PS)"\n"\
464 "rsync\n"\
465 : [APB]"=a"(apb), [REG]"+a"(reg), [LVL]"=a"(intLvl)\
466 : \
467 : "memory" \
468 );
469 return reg;
470 #endif
471 }
472
473 // The accessDPORT3 task is similar accessDPORT2 but uses test_dport_access_reg_read() instead of usual DPORT_REG_READ().
accessDPORT3(void * pvParameters)474 static void accessDPORT3(void *pvParameters)
475 {
476 xSemaphoreHandle *sema = (xSemaphoreHandle *) pvParameters;
477 dport_test_result = true;
478
479 TEST_ESP_OK(esp_intr_alloc(ETS_INTERNAL_TIMER2_INTR_SOURCE, ESP_INTR_FLAG_LEVEL5 | ESP_INTR_FLAG_IRAM, NULL, NULL, &inth));
480 int i = 0;
481 while (exit_flag == false) {
482 if (test_dport_access_reg_read(DPORT_DATE_REG) != test_dport_access_reg_read(DPORT_DATE_REG)) {
483 dport_test_result = false;
484 break;
485 }
486 if ((++i % 100) == 0) {
487 s_shift_counter = (s_shift_counter + 1) % 30;
488 }
489 }
490 esp_intr_free(inth);
491 printf("accessDPORT3 finish\n");
492
493 xSemaphoreGive(*sema);
494 vTaskDelete(NULL);
495 }
496
497 TEST_CASE("Check pre-read workaround DPORT and Hi-interrupt (2)", "[esp32]")
498 {
499 s_shift_counter = 1;
500 xt_highint5_read_apb = 0;
501 dport_test_result = false;
502 apb_test_result = true;
503 // Access DPORT(pre-read method) - CPU1
504 // Hi-interrupt - CPU1
505 run_tasks("accessAPB", accessAPB, "accessDPORT3", accessDPORT3, 10000);
506 }
507 #endif // CONFIG_FREERTOS_UNICORE
508
509 #endif // CONFIG_IDF_TARGET_ESP32
510