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