diff --git a/components/esp_driver_uart/test_apps/.build-test-rules.yml b/components/esp_driver_uart/test_apps/.build-test-rules.yml index 6675de71ca..9f4e19d6f5 100644 --- a/components/esp_driver_uart/test_apps/.build-test-rules.yml +++ b/components/esp_driver_uart/test_apps/.build-test-rules.yml @@ -32,5 +32,6 @@ components/esp_driver_uart/test_apps/uart_vfs: components/esp_driver_uart/test_apps/uhci: disable: - if: SOC_UHCI_SUPPORTED != 1 + - if: CONFIG_NAME == "psram" and SOC_SPIRAM_SUPPORTED != 1 depends_components: - esp_driver_uart diff --git a/components/esp_driver_uart/test_apps/uhci/CMakeLists.txt b/components/esp_driver_uart/test_apps/uhci/CMakeLists.txt new file mode 100644 index 0000000000..1f8e0a7419 --- /dev/null +++ b/components/esp_driver_uart/test_apps/uhci/CMakeLists.txt @@ -0,0 +1,28 @@ +# This is the project CMakeLists.txt file for the test subproject +cmake_minimum_required(VERSION 3.16) + +# "Trim" the build. Include the minimal set of components, main, and anything it depends on. +set(COMPONENTS main) + +set(EXTRA_COMPONENT_DIRS "$ENV{IDF_PATH}/tools/unit-test-app/components") + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +project(uhci_test) + +if(CONFIG_COMPILER_DUMP_RTL_FILES) + add_custom_target(check_test_app_sections ALL + COMMAND ${PYTHON} $ENV{IDF_PATH}/tools/ci/check_callgraph.py + --rtl-dirs ${CMAKE_BINARY_DIR}/esp-idf/esp_driver_uart/,${CMAKE_BINARY_DIR}/esp-idf/hal/ + --elf-file ${CMAKE_BINARY_DIR}/uhci_test.elf + find-refs + --from-sections=.iram0.text + --to-sections=.flash.text,.flash.rodata + --exit-code + DEPENDS ${elf} + ) +endif() + +message(STATUS "Checking uhci registers are not read-write by half-word") +include($ENV{IDF_PATH}/tools/ci/check_register_rw_half_word.cmake) +check_register_rw_half_word(SOC_MODULES "uhci" "pcr" "hp_sys_clkrst" "lpperi" "lp_clkrst" + HAL_MODULES "uhci") diff --git a/components/esp_driver_uart/test_apps/uhci/README.md b/components/esp_driver_uart/test_apps/uhci/README.md new file mode 100644 index 0000000000..cb475b8112 --- /dev/null +++ b/components/esp_driver_uart/test_apps/uhci/README.md @@ -0,0 +1,2 @@ +| Supported Targets | ESP32-C3 | ESP32-C6 | ESP32-P4 | ESP32-S3 | +| ----------------- | -------- | -------- | -------- | -------- | diff --git a/components/esp_driver_uart/test_apps/uhci/main/CMakeLists.txt b/components/esp_driver_uart/test_apps/uhci/main/CMakeLists.txt new file mode 100644 index 0000000000..fb1bd9306a --- /dev/null +++ b/components/esp_driver_uart/test_apps/uhci/main/CMakeLists.txt @@ -0,0 +1,8 @@ +# In order for the cases defined by `TEST_CASE` to be linked into the final elf, +# the component can be registered as WHOLE_ARCHIVE +idf_component_register( + SRCS "test_app_main.c" + "test_uhci.c" + REQUIRES esp_driver_uart unity test_utils esp_psram + WHOLE_ARCHIVE +) diff --git a/components/esp_driver_uart/test_apps/uhci/main/test_app_main.c b/components/esp_driver_uart/test_apps/uhci/main/test_app_main.c new file mode 100644 index 0000000000..f3adb8b938 --- /dev/null +++ b/components/esp_driver_uart/test_apps/uhci/main/test_app_main.c @@ -0,0 +1,36 @@ +/* + * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "unity.h" +#include "unity_test_runner.h" +#include "unity_test_utils.h" +#include "esp_heap_caps.h" +#include "esp_newlib.h" + +#define TEST_MEMORY_LEAK_THRESHOLD (500) + +void setUp(void) +{ + unity_utils_record_free_mem(); +} + +void tearDown(void) +{ + esp_reent_cleanup(); //clean up some of the newlib's lazy allocations + unity_utils_evaluate_leaks_direct(TEST_MEMORY_LEAK_THRESHOLD); +} + +void app_main(void) +{ + printf(" __ __ __ __ ______ __ \n"); + printf("| | | | | | | | / || |\n"); + printf("| | | | | |__| | | ,----'| |\n"); + printf("| | | | | __ | | | | |\n"); + printf("| `--' | | | | | | `----.| |\n"); + printf(" \\______/ |__| |__| \\______||__|\n"); + + unity_run_menu(); +} diff --git a/components/esp_driver_uart/test_apps/uhci/main/test_uhci.c b/components/esp_driver_uart/test_apps/uhci/main/test_uhci.c new file mode 100644 index 0000000000..c35edaf5b0 --- /dev/null +++ b/components/esp_driver_uart/test_apps/uhci/main/test_uhci.c @@ -0,0 +1,294 @@ +/* + * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include "unity.h" +#include "test_utils.h" +#include "driver/uart.h" +#include "driver/uhci.h" + +#define DATA_LENGTH 1024 +#define EX_UART_NUM 1 +#define UART_TX_IO 2 +#define UART_RX_IO 3 + +TEST_CASE("UHCI driver memory leaking check", "[uhci]") +{ + uart_config_t uart_config = { + .baud_rate = 1 * 1000 * 1000, + .data_bits = UART_DATA_8_BITS, + .parity = UART_PARITY_DISABLE, + .stop_bits = UART_STOP_BITS_1, + .flow_ctrl = UART_HW_FLOWCTRL_DISABLE, + .source_clk = UART_SCLK_DEFAULT, + }; + + //UART parameter config + TEST_ESP_OK(uart_param_config(EX_UART_NUM, &uart_config)); + TEST_ESP_OK(uart_set_pin(EX_UART_NUM, UART_TX_IO, UART_RX_IO, -1, -1)); + + uhci_controller_config_t uhci_cfg = { + .uart_port = EX_UART_NUM, + .tx_trans_queue_depth = 30, + .max_receive_internal_mem = 10 * 1024, + .max_transmit_size = 10 * 1024, + .dma_burst_size = 32, + .rx_eof_flags.idle_eof = 1, + }; + + uhci_controller_handle_t uhci_ctrl; + + int size = esp_get_free_heap_size(); + for (int i = 0; i < 5; i++) { + TEST_ESP_OK(uhci_new_controller(&uhci_cfg, &uhci_ctrl)); + vTaskDelay(10 / portTICK_PERIOD_MS); + TEST_ESP_OK(uhci_del_controller(uhci_ctrl)); + } + + TEST_ASSERT_INT_WITHIN(300, size, esp_get_free_heap_size()); +} + +TEST_CASE("UHCI controller install-uninstall test", "[i2c]") +{ + uhci_controller_config_t uhci_cfg = { + .uart_port = EX_UART_NUM, + .tx_trans_queue_depth = 30, + .max_receive_internal_mem = 10 * 1024, + .max_transmit_size = 10 * 1024, + .dma_burst_size = 32, + .rx_eof_flags.idle_eof = 1, + }; + + uhci_controller_handle_t uhci_ctrl; + uhci_controller_handle_t uhci_ctrl2; + + TEST_ESP_OK(uhci_new_controller(&uhci_cfg, &uhci_ctrl)); + + TEST_ESP_ERR(ESP_ERR_NOT_FOUND, uhci_new_controller(&uhci_cfg, &uhci_ctrl2)); + + TEST_ESP_OK(uhci_del_controller(uhci_ctrl)); +} + +typedef enum { + UHCI_EVT_PARTIAL_DATA, + UHCI_EVT_EOF, +} uhci_event_t; + +typedef struct { + QueueHandle_t uhci_queue; + size_t receive_size; + uint8_t *p_receive_data; +} uhci_context_t; + +static void disp_buf(uint8_t *buf, int len) +{ + int i; + for (i = 0; i < len; i++) { + printf("%02x ", buf[i]); + if ((i + 1) % 16 == 0) { + printf("\n"); + } + } + printf("\n"); +} + +IRAM_ATTR static bool s_uhci_rx_event_cbs(uhci_controller_handle_t uhci_ctrl, const uhci_rx_event_data_t *edata, void *user_ctx) +{ + uhci_context_t *ctx = (uhci_context_t *)user_ctx; + BaseType_t xTaskWoken = 0; + uhci_event_t evt = 0; + if (edata->flags.totally_received) { + evt = UHCI_EVT_EOF; + ctx->receive_size += edata->recv_size; + memcpy(ctx->p_receive_data, edata->data, edata->recv_size); + } else { + evt = UHCI_EVT_PARTIAL_DATA; + ctx->receive_size += edata->recv_size; + memcpy(ctx->p_receive_data, edata->data, edata->recv_size); + ctx->p_receive_data += edata->recv_size; + } + + xQueueSendFromISR(ctx->uhci_queue, &evt, &xTaskWoken); + return xTaskWoken; +} + +static void uhci_receive_test(void *arg) +{ + uhci_controller_handle_t uhci_ctrl = ((uhci_controller_handle_t *)arg)[0]; + SemaphoreHandle_t exit_sema = ((SemaphoreHandle_t *)arg)[1]; + + uhci_context_t *ctx = calloc(1, sizeof(uhci_context_t)); + assert(ctx); + ctx->uhci_queue = xQueueCreate(15, sizeof(uhci_event_t)); + assert(ctx->uhci_queue); + + uint8_t *receive_data = heap_caps_calloc(1, DATA_LENGTH, MALLOC_CAP_DMA | MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT); + assert(receive_data); + uint8_t *pdata = heap_caps_calloc(1, DATA_LENGTH / 4, MALLOC_CAP_DMA | MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT); + assert(pdata); + ctx->p_receive_data = receive_data; + + uhci_event_callbacks_t uhci_cbs = { + .on_rx_trans_event = s_uhci_rx_event_cbs, + }; + TEST_ESP_OK(uhci_register_event_callbacks(uhci_ctrl, &uhci_cbs, ctx)); + TEST_ESP_OK(uhci_receive(uhci_ctrl, pdata, DATA_LENGTH / 4)); + + uhci_event_t evt; + while (1) { + if (xQueueReceive(ctx->uhci_queue, &evt, portMAX_DELAY) == pdTRUE) { + if (evt == UHCI_EVT_EOF) { + disp_buf(receive_data, ctx->receive_size); + for (int i = 0; i < DATA_LENGTH; i++) { + TEST_ASSERT(receive_data[i] == (uint8_t)i); + } + printf("Received size: %d\n", ctx->receive_size); + break; + } + } + } + + vQueueDelete(ctx->uhci_queue); + free(pdata); + free(receive_data); + free(ctx); + xSemaphoreGive(exit_sema); + vTaskDelete(NULL); +} + +TEST_CASE("UHCI write and receive", "[uhci]") +{ + uart_config_t uart_config = { + .baud_rate = 5 * 1000 * 1000, + .data_bits = UART_DATA_8_BITS, + .parity = UART_PARITY_DISABLE, + .stop_bits = UART_STOP_BITS_1, + .flow_ctrl = UART_HW_FLOWCTRL_DISABLE, + .source_clk = UART_SCLK_XTAL, + }; + TEST_ESP_OK(uart_param_config(EX_UART_NUM, &uart_config)); + // Connect TX and RX together for testing self send-receive + TEST_ESP_OK(uart_set_pin(EX_UART_NUM, UART_TX_IO, UART_TX_IO, -1, -1)); + + uhci_controller_config_t uhci_cfg = { + .uart_port = EX_UART_NUM, + .tx_trans_queue_depth = 30, + .max_receive_internal_mem = 10 * 1024, + .max_transmit_size = 10 * 1024, + .dma_burst_size = 32, + .rx_eof_flags.idle_eof = 1, + }; + + uhci_controller_handle_t uhci_ctrl; + SemaphoreHandle_t exit_sema = xSemaphoreCreateBinary(); + TEST_ESP_OK(uhci_new_controller(&uhci_cfg, &uhci_ctrl)); + + void *args[] = { uhci_ctrl, exit_sema }; + xTaskCreate(uhci_receive_test, "uhci_receive_test", 4096 * 2, args, 5, NULL); + + uint8_t data_wr[DATA_LENGTH]; + for (int i = 0; i < DATA_LENGTH; i++) { + data_wr[i] = i; + } + TEST_ESP_OK(uhci_transmit(uhci_ctrl, data_wr, DATA_LENGTH)); + uhci_wait_all_tx_transaction_done(uhci_ctrl, portMAX_DELAY); + xSemaphoreTake(exit_sema, portMAX_DELAY); + vTaskDelay(2); + TEST_ESP_OK(uhci_del_controller(uhci_ctrl)); + vSemaphoreDelete(exit_sema); +} + +#if CONFIG_SPIRAM +#if CONFIG_IDF_TARGET_ESP32S3 +static void uhci_receive_test_in_psram(void *arg) +{ + uhci_controller_handle_t uhci_ctrl = ((uhci_controller_handle_t *)arg)[0]; + SemaphoreHandle_t exit_sema = ((SemaphoreHandle_t *)arg)[1]; + + uhci_context_t *ctx = calloc(1, sizeof(uhci_context_t)); + assert(ctx); + ctx->uhci_queue = xQueueCreate(15, sizeof(uhci_event_t)); + assert(ctx->uhci_queue); + + uint8_t *receive_data = heap_caps_calloc(1, DATA_LENGTH, MALLOC_CAP_DMA | MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT); + assert(receive_data); + uint8_t *pdata = heap_caps_calloc(1, DATA_LENGTH / 4, MALLOC_CAP_DMA | MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT); + assert(pdata); + ctx->p_receive_data = receive_data; + + uhci_event_callbacks_t uhci_cbs = { + .on_rx_trans_event = s_uhci_rx_event_cbs, + }; + TEST_ESP_OK(uhci_register_event_callbacks(uhci_ctrl, &uhci_cbs, ctx)); + TEST_ESP_OK(uhci_receive(uhci_ctrl, pdata, DATA_LENGTH / 4)); + + uhci_event_t evt; + while (1) { + if (xQueueReceive(ctx->uhci_queue, &evt, portMAX_DELAY) == pdTRUE) { + if (evt == UHCI_EVT_EOF) { + disp_buf(receive_data, ctx->receive_size); + for (int i = 0; i < DATA_LENGTH; i++) { + TEST_ASSERT(receive_data[i] == (uint8_t)i); + } + printf("Received size: %d\n", ctx->receive_size); + break; + } + } + } + + vQueueDelete(ctx->uhci_queue); + free(pdata); + free(receive_data); + free(ctx); + xSemaphoreGive(exit_sema); + vTaskDelete(NULL); +} + +TEST_CASE("UHCI write and receive in psram", "[uhci]") +{ + uart_config_t uart_config = { + .baud_rate = 5 * 1000 * 1000, + .data_bits = UART_DATA_8_BITS, + .parity = UART_PARITY_DISABLE, + .stop_bits = UART_STOP_BITS_1, + .flow_ctrl = UART_HW_FLOWCTRL_DISABLE, + .source_clk = UART_SCLK_XTAL, + }; + TEST_ESP_OK(uart_param_config(EX_UART_NUM, &uart_config)); + // Connect TX and RX together for testing self send-receive + TEST_ESP_OK(uart_set_pin(EX_UART_NUM, UART_TX_IO, UART_TX_IO, -1, -1)); + + uhci_controller_config_t uhci_cfg = { + .uart_port = EX_UART_NUM, + .tx_trans_queue_depth = 30, + .max_receive_internal_mem = 10 * 1024, + .max_transmit_size = 10 * 1024, + .dma_burst_size = 32, + .rx_eof_flags.idle_eof = 1, + }; + + uhci_controller_handle_t uhci_ctrl; + SemaphoreHandle_t exit_sema = xSemaphoreCreateBinary(); + TEST_ESP_OK(uhci_new_controller(&uhci_cfg, &uhci_ctrl)); + + void *args[] = { uhci_ctrl, exit_sema }; + xTaskCreate(uhci_receive_test_in_psram, "uhci_receive_test_in_psram", 4096 * 2, args, 5, NULL); + + uint8_t *data_wr = heap_caps_calloc(1, DATA_LENGTH, MALLOC_CAP_DMA | MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT); + for (int i = 0; i < DATA_LENGTH; i++) { + data_wr[i] = i; + } + TEST_ESP_OK(uhci_transmit(uhci_ctrl, data_wr, DATA_LENGTH)); + uhci_wait_all_tx_transaction_done(uhci_ctrl, portMAX_DELAY); + xSemaphoreTake(exit_sema, portMAX_DELAY); + vTaskDelay(2); + free(data_wr); + TEST_ESP_OK(uhci_del_controller(uhci_ctrl)); + vSemaphoreDelete(exit_sema); +} +#endif +#endif // CONFIG_SPIRAM diff --git a/components/esp_driver_uart/test_apps/uhci/pytest_uhci.py b/components/esp_driver_uart/test_apps/uhci/pytest_uhci.py new file mode 100644 index 0000000000..70d0b963ce --- /dev/null +++ b/components/esp_driver_uart/test_apps/uhci/pytest_uhci.py @@ -0,0 +1,20 @@ +# SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD +# SPDX-License-Identifier: CC0-1.0 +import pytest +from pytest_embedded import Dut +from pytest_embedded_idf.utils import idf_parametrize +from pytest_embedded_idf.utils import soc_filtered_targets + + +@pytest.mark.generic +@pytest.mark.parametrize( + 'config', + [ + 'release', + 'cache_safe', + ], + indirect=True, +) +@idf_parametrize('target', soc_filtered_targets('SOC_UHCI_SUPPORTED == 1'), indirect=['target']) +def test_uhci(dut: Dut) -> None: + dut.run_all_single_board_cases() diff --git a/components/esp_driver_uart/test_apps/uhci/sdkconfig.ci.cache_safe b/components/esp_driver_uart/test_apps/uhci/sdkconfig.ci.cache_safe new file mode 100644 index 0000000000..0f206ba78c --- /dev/null +++ b/components/esp_driver_uart/test_apps/uhci/sdkconfig.ci.cache_safe @@ -0,0 +1,11 @@ +CONFIG_COMPILER_DUMP_RTL_FILES=y +CONFIG_UHCI_ISR_CACHE_SAFE=y +CONFIG_GPIO_CTRL_FUNC_IN_IRAM=y +CONFIG_COMPILER_OPTIMIZATION_NONE=y +# place non-ISR FreeRTOS functions in Flash +CONFIG_FREERTOS_PLACE_FUNCTIONS_INTO_FLASH=y +# silent the error check, as the error string are stored in rodata, causing RTL check failure +CONFIG_COMPILER_OPTIMIZATION_CHECKS_SILENT=y +CONFIG_COMPILER_OPTIMIZATION_ASSERTIONS_SILENT=y +CONFIG_HAL_ASSERTION_SILENT=y +CONFIG_COMPILER_OPTIMIZATION_ASSERTIONS_DISABLE=y diff --git a/components/esp_driver_uart/test_apps/uhci/sdkconfig.ci.psram b/components/esp_driver_uart/test_apps/uhci/sdkconfig.ci.psram new file mode 100644 index 0000000000..cc641ea603 --- /dev/null +++ b/components/esp_driver_uart/test_apps/uhci/sdkconfig.ci.psram @@ -0,0 +1 @@ +CONFIG_SPIRAM=y diff --git a/components/esp_driver_uart/test_apps/uhci/sdkconfig.ci.release b/components/esp_driver_uart/test_apps/uhci/sdkconfig.ci.release new file mode 100644 index 0000000000..79e92b7fcd --- /dev/null +++ b/components/esp_driver_uart/test_apps/uhci/sdkconfig.ci.release @@ -0,0 +1,6 @@ +CONFIG_PM_ENABLE=y +CONFIG_PM_DFS_INIT_AUTO=y +CONFIG_FREERTOS_USE_TICKLESS_IDLE=y +CONFIG_COMPILER_OPTIMIZATION_SIZE=y +CONFIG_BOOTLOADER_COMPILER_OPTIMIZATION_SIZE=y +CONFIG_COMPILER_OPTIMIZATION_ASSERTIONS_SILENT=y diff --git a/components/esp_driver_uart/test_apps/uhci/sdkconfig.defaults b/components/esp_driver_uart/test_apps/uhci/sdkconfig.defaults new file mode 100644 index 0000000000..fa8ac618b9 --- /dev/null +++ b/components/esp_driver_uart/test_apps/uhci/sdkconfig.defaults @@ -0,0 +1,2 @@ +CONFIG_FREERTOS_HZ=1000 +CONFIG_ESP_TASK_WDT_EN=n