OVMS3-idf/components/esp32/test/test_dport.c

420 lines
14 KiB
C

#include <stdio.h>
#include <stdlib.h>
#include "xtensa/core-macros.h"
#include "xtensa/hal.h"
#include "esp_types.h"
#include "esp_clk.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"
#include "freertos/xtensa_timer.h"
#include "soc/cpu.h"
#include "unity.h"
#include "test_utils.h"
#include "rom/uart.h"
#include "soc/uart_reg.h"
#include "soc/dport_reg.h"
#include "soc/rtc.h"
#include "esp_intr_alloc.h"
#include "driver/timer.h"
#define MHZ (1000000)
static volatile bool exit_flag;
static bool dport_test_result;
static bool apb_test_result;
uint32_t volatile apb_intr_test_result;
static void accessDPORT(void *pvParameters)
{
xSemaphoreHandle *sema = (xSemaphoreHandle *) pvParameters;
uint32_t dport_date = DPORT_REG_READ(DPORT_DATE_REG);
dport_test_result = true;
// although exit flag is set in another task, checking (exit_flag == false) is safe
while (exit_flag == false) {
if (dport_date != DPORT_REG_READ(DPORT_DATE_REG)) {
dport_test_result = false;
break;
}
}
xSemaphoreGive(*sema);
vTaskDelete(NULL);
}
static void accessAPB(void *pvParameters)
{
xSemaphoreHandle *sema = (xSemaphoreHandle *) pvParameters;
uint32_t uart_date = REG_READ(UART_DATE_REG(0));
apb_test_result = true;
// although exit flag is set in another task, checking (exit_flag == false) is safe
while (exit_flag == false) {
if (uart_date != REG_READ(UART_DATE_REG(0))) {
apb_test_result = false;
break;
}
}
xSemaphoreGive(*sema);
vTaskDelete(NULL);
}
void run_tasks(const char *task1_description, void (* task1_func)(void *), const char *task2_description, void (* task2_func)(void *), uint32_t delay_ms)
{
apb_intr_test_result = 1;
int i;
TaskHandle_t th[2];
xSemaphoreHandle exit_sema[2];
for (i=0; i<2; i++) {
if((task1_func != NULL && i == 0) || (task2_func != NULL && i == 1)){
exit_sema[i] = xSemaphoreCreateMutex();
xSemaphoreTake(exit_sema[i], portMAX_DELAY);
}
}
exit_flag = false;
#ifndef CONFIG_FREERTOS_UNICORE
printf("assign task accessing DPORT to core 0 and task accessing APB to core 1\n");
if(task1_func != NULL) xTaskCreatePinnedToCore(task1_func, task1_description, 2048, &exit_sema[0], UNITY_FREERTOS_PRIORITY - 1, &th[0], 0);
if(task2_func != NULL) xTaskCreatePinnedToCore(task2_func, task2_description, 2048, &exit_sema[1], UNITY_FREERTOS_PRIORITY - 1, &th[1], 1);
#else
printf("assign task accessing DPORT and accessing APB\n");
if(task1_func != NULL) xTaskCreate(task1_func, task1_description, 2048, &exit_sema[0], UNITY_FREERTOS_PRIORITY - 1, &th[0]);
if(task2_func != NULL) xTaskCreate(task2_func, task2_description, 2048, &exit_sema[1], UNITY_FREERTOS_PRIORITY - 1, &th[1]);
#endif
printf("start wait for %d seconds [Test %s and %s]\n", delay_ms/1000, task1_description, task2_description);
vTaskDelay(delay_ms / portTICK_PERIOD_MS);
// set exit flag to let thread exit
exit_flag = true;
for (i=0; i<2; i++) {
if ((task1_func != NULL && i == 0) || (task2_func != NULL && i == 1)) {
xSemaphoreTake(exit_sema[i], portMAX_DELAY);
vSemaphoreDelete(exit_sema[i]);
}
}
TEST_ASSERT(dport_test_result == true && apb_test_result == true && apb_intr_test_result == 1);
}
TEST_CASE("access DPORT and APB at same time", "[esp32]")
{
dport_test_result = false;
apb_test_result = false;
printf("CPU_FREQ = %d MHz\n", esp_clk_cpu_freq());
run_tasks("accessDPORT", accessDPORT, "accessAPB", accessAPB, 10000);
}
void run_tasks_with_change_freq_cpu(int cpu_freq_mhz)
{
const int uart_num = CONFIG_CONSOLE_UART_NUM;
const int uart_baud = CONFIG_CONSOLE_UART_BAUDRATE;
dport_test_result = false;
apb_test_result = false;
rtc_cpu_freq_config_t old_config;
rtc_clk_cpu_freq_get_config(&old_config);
printf("CPU_FREQ = %d MHz\n", old_config.freq_mhz);
if (cpu_freq_mhz != old_config.freq_mhz) {
rtc_cpu_freq_config_t new_config;
bool res = rtc_clk_cpu_freq_mhz_to_config(cpu_freq_mhz, &new_config);
assert(res && "invalid frequency value");
uart_tx_wait_idle(uart_num);
rtc_clk_cpu_freq_set_config(&new_config);
uart_div_modify(uart_num, (rtc_clk_apb_freq_get() << 4) / uart_baud);
/* adjust RTOS ticks */
_xt_tick_divisor = cpu_freq_mhz * 1000000 / XT_TICK_PER_SEC;
vTaskDelay(2);
printf("CPU_FREQ switched to %d MHz\n", cpu_freq_mhz);
}
run_tasks("accessDPORT", accessDPORT, "accessAPB", accessAPB, 10000);
// return old freq.
uart_tx_wait_idle(uart_num);
rtc_clk_cpu_freq_set_config(&old_config);
uart_div_modify(uart_num, (rtc_clk_apb_freq_get() << 4) / uart_baud);
_xt_tick_divisor = old_config.freq_mhz * 1000000 / XT_TICK_PER_SEC;
}
TEST_CASE("access DPORT and APB at same time (Freq CPU and APB = 80 MHz)", "[esp32] [ignore]")
{
run_tasks_with_change_freq_cpu(80);
}
TEST_CASE("access DPORT and APB at same time (Freq CPU and APB = 40 MHz (XTAL))", "[esp32]")
{
run_tasks_with_change_freq_cpu((int) rtc_clk_xtal_freq_get());
}
static uint32_t stall_other_cpu_counter;
static uint32_t pre_reading_apb_counter;
static uint32_t apb_counter;
static void accessDPORT_stall_other_cpu(void *pvParameters)
{
xSemaphoreHandle *sema = (xSemaphoreHandle *) pvParameters;
uint32_t dport_date = DPORT_REG_READ(DPORT_DATE_REG);
uint32_t dport_date_cur;
dport_test_result = true;
stall_other_cpu_counter = 0;
// although exit flag is set in another task, checking (exit_flag == false) is safe
while (exit_flag == false) {
++stall_other_cpu_counter;
DPORT_STALL_OTHER_CPU_START();
dport_date_cur = _DPORT_REG_READ(DPORT_DATE_REG);
DPORT_STALL_OTHER_CPU_END();
if (dport_date != dport_date_cur) {
apb_test_result = false;
break;
}
}
xSemaphoreGive(*sema);
vTaskDelete(NULL);
}
static void accessAPB_measure_performance(void *pvParameters)
{
xSemaphoreHandle *sema = (xSemaphoreHandle *) pvParameters;
uint32_t uart_date = REG_READ(UART_DATE_REG(0));
apb_test_result = true;
apb_counter = 0;
// although exit flag is set in another task, checking (exit_flag == false) is safe
while (exit_flag == false) {
++apb_counter;
if (uart_date != REG_READ(UART_DATE_REG(0))) {
apb_test_result = false;
break;
}
}
xSemaphoreGive(*sema);
vTaskDelete(NULL);
}
static void accessDPORT_pre_reading_apb(void *pvParameters)
{
xSemaphoreHandle *sema = (xSemaphoreHandle *) pvParameters;
uint32_t dport_date = DPORT_REG_READ(DPORT_DATE_REG);
uint32_t dport_date_cur;
dport_test_result = true;
pre_reading_apb_counter = 0;
// although exit flag is set in another task, checking (exit_flag == false) is safe
while (exit_flag == false) {
++pre_reading_apb_counter;
dport_date_cur = DPORT_REG_READ(DPORT_DATE_REG);
if (dport_date != dport_date_cur) {
apb_test_result = false;
break;
}
}
xSemaphoreGive(*sema);
vTaskDelete(NULL);
}
TEST_CASE("test for DPORT access performance", "[esp32]")
{
dport_test_result = true;
apb_test_result = true;
typedef struct {
uint32_t dport;
uint32_t apb;
uint32_t summ;
} test_performance_t;
test_performance_t t[5] = {0};
uint32_t delay_ms = 5000;
run_tasks("-", NULL, "accessAPB", accessAPB_measure_performance, delay_ms);
t[0].apb = apb_counter;
t[0].dport = 0;
t[0].summ = t[0].apb + t[0].dport;
run_tasks("accessDPORT_stall_other_cpu", accessDPORT_stall_other_cpu, "-", NULL, delay_ms);
t[1].apb = 0;
t[1].dport = stall_other_cpu_counter;
t[1].summ = t[1].apb + t[1].dport;
run_tasks("accessDPORT_pre_reading_apb", accessDPORT_pre_reading_apb, "-", NULL, delay_ms);
t[2].apb = 0;
t[2].dport = pre_reading_apb_counter;
t[2].summ = t[2].apb + t[2].dport;
run_tasks("accessDPORT_stall_other_cpu", accessDPORT_stall_other_cpu, "accessAPB", accessAPB_measure_performance, delay_ms);
t[3].apb = apb_counter;
t[3].dport = stall_other_cpu_counter;
t[3].summ = t[3].apb + t[3].dport;
run_tasks("accessDPORT_pre_reading_apb", accessDPORT_pre_reading_apb, "accessAPB", accessAPB_measure_performance, delay_ms);
t[4].apb = apb_counter;
t[4].dport = pre_reading_apb_counter;
t[4].summ = t[4].apb + t[4].dport;
printf("\nPerformance table: \n"
"The number of simultaneous read operations of the APB and DPORT registers\n"
"by different methods for %d seconds.\n", delay_ms/1000);
printf("+-----------------------+----------+----------+----------+\n");
printf("| Method read DPORT | DPORT | APB | SUMM |\n");
printf("+-----------------------+----------+----------+----------+\n");
printf("|1.Only accessAPB |%10d|%10d|%10d|\n", t[0].dport, t[0].apb, t[0].summ);
printf("|2.Only STALL_OTHER_CPU |%10d|%10d|%10d|\n", t[1].dport, t[1].apb, t[1].summ);
printf("|3.Only PRE_READ_APB_REG|%10d|%10d|%10d|\n", t[2].dport, t[2].apb, t[2].summ);
printf("+-----------------------+----------+----------+----------+\n");
printf("|4.STALL_OTHER_CPU |%10d|%10d|%10d|\n", t[3].dport, t[3].apb, t[3].summ);
printf("|5.PRE_READ_APB_REG |%10d|%10d|%10d|\n", t[4].dport, t[4].apb, t[4].summ);
printf("+-----------------------+----------+----------+----------+\n");
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);
printf("+-----------------------+----------+----------+----------+\n");
}
#define REPEAT_OPS 10000
static uint32_t start, end;
#define BENCHMARK_START() do { \
RSR(CCOUNT, start); \
} while(0)
#define BENCHMARK_END(OPERATION) do { \
RSR(CCOUNT, end); \
printf("%s took %d cycles/op (%d cycles for %d ops)\n", \
OPERATION, (end - start)/REPEAT_OPS, \
(end - start), REPEAT_OPS); \
} while(0)
TEST_CASE("BENCHMARK for DPORT access performance", "[freertos]")
{
BENCHMARK_START();
for (int i = 0; i < REPEAT_OPS; i++) {
DPORT_STALL_OTHER_CPU_START();
_DPORT_REG_READ(DPORT_DATE_REG);
DPORT_STALL_OTHER_CPU_END();
}
BENCHMARK_END("[old]DPORT access STALL OTHER CPU");
BENCHMARK_START();
for (int i = 0; i < REPEAT_OPS; i++) {
DPORT_REG_READ(DPORT_DATE_REG);
}
BENCHMARK_END("[new]DPORT access PRE-READ APB REG");
BENCHMARK_START();
for (int i = 0; i < REPEAT_OPS; i++) {
DPORT_SEQUENCE_REG_READ(DPORT_DATE_REG);
}
BENCHMARK_END("[seq]DPORT access PRE-READ APB REG");
BENCHMARK_START();
for (int i = 0; i < REPEAT_OPS; i++) {
REG_READ(UART_DATE_REG(0));
}
BENCHMARK_END("REG_READ");
BENCHMARK_START();
for (int i = 0; i < REPEAT_OPS; i++) {
_DPORT_REG_READ(DPORT_DATE_REG);
}
BENCHMARK_END("_DPORT_REG_READ");
}
uint32_t xt_highint5_read_apb;
#ifndef CONFIG_FREERTOS_UNICORE
timer_isr_handle_t inth;
xSemaphoreHandle sync_sema;
static void init_hi_interrupt(void *arg)
{
printf("init hi_interrupt on CPU%d \n", xPortGetCoreID());
TEST_ESP_OK(esp_intr_alloc(ETS_INTERNAL_TIMER2_INTR_SOURCE, ESP_INTR_FLAG_LEVEL5 | ESP_INTR_FLAG_IRAM, NULL, NULL, &inth));
while (exit_flag == false);
esp_intr_free(inth);
printf("disable hi_interrupt on CPU%d \n", xPortGetCoreID());
vTaskDelete(NULL);
}
static void accessDPORT2_stall_other_cpu(void *pvParameters)
{
xSemaphoreHandle *sema = (xSemaphoreHandle *) pvParameters;
dport_test_result = true;
while (exit_flag == false) {
DPORT_STALL_OTHER_CPU_START();
XTHAL_SET_CCOMPARE(2, XTHAL_GET_CCOUNT());
xt_highint5_read_apb = 1;
for (int i = 0; i < 200; ++i) {
if (_DPORT_REG_READ(DPORT_DATE_REG) != _DPORT_REG_READ(DPORT_DATE_REG)) {
apb_test_result = false;
break;
}
}
xt_highint5_read_apb = 0;
DPORT_STALL_OTHER_CPU_END();
}
printf("accessDPORT2_stall_other_cpu finish\n");
xSemaphoreGive(*sema);
vTaskDelete(NULL);
}
TEST_CASE("Check stall workaround DPORT and Hi-interrupt", "[esp32]")
{
xt_highint5_read_apb = 0;
dport_test_result = false;
apb_test_result = true;
TEST_ASSERT(xTaskCreatePinnedToCore(&init_hi_interrupt, "init_hi_intr", 2048, NULL, 6, NULL, 1) == pdTRUE);
// Access DPORT(stall other cpu method) - CPU0
// STALL - CPU1
// Hi-interrupt - CPU1
run_tasks("accessDPORT2_stall_other_cpu", accessDPORT2_stall_other_cpu, " - ", NULL, 10000);
}
static void accessDPORT2(void *pvParameters)
{
xSemaphoreHandle *sema = (xSemaphoreHandle *) pvParameters;
dport_test_result = true;
TEST_ESP_OK(esp_intr_alloc(ETS_INTERNAL_TIMER2_INTR_SOURCE, ESP_INTR_FLAG_LEVEL5 | ESP_INTR_FLAG_IRAM, NULL, NULL, &inth));
XTHAL_SET_CCOMPARE(2, XTHAL_GET_CCOUNT() + 21);
int sync = 0;
while (exit_flag == false) {
ets_delay_us(++sync % 10);
for (int i = 0; i < 200; ++i) {
if (DPORT_REG_READ(DPORT_DATE_REG) != DPORT_REG_READ(DPORT_DATE_REG)) {
dport_test_result = false;
break;
}
}
}
esp_intr_free(inth);
printf("accessDPORT2 finish\n");
xSemaphoreGive(*sema);
vTaskDelete(NULL);
}
TEST_CASE("Check pre-read workaround DPORT and Hi-interrupt", "[esp32]")
{
xt_highint5_read_apb = 0;
dport_test_result = false;
apb_test_result = true;
// Access DPORT(pre-read method) - CPU1
// Hi-interrupt - CPU1
run_tasks("accessAPB", accessAPB, "accessDPORT2", accessDPORT2, 10000);
}
#endif // CONFIG_FREERTOS_UNICORE