From 83d00b154a1118e142360138646718c4f8ae7484 Mon Sep 17 00:00:00 2001 From: Guillaume Souchere Date: Fri, 17 May 2024 09:32:45 +0200 Subject: [PATCH] feat(usb_serial_jtag): Add select functionality to the driver Implement the select() function in the USB serial JTAG driver. Add test for the select functionality. closes https://github.com/espressif/esp-idf/issues/9964 --- .../esp_driver_usb_serial_jtag/CMakeLists.txt | 1 + .../include/driver/usb_serial_jtag.h | 7 + .../include/driver/usb_serial_jtag_select.h | 46 +++++ .../esp_driver_usb_serial_jtag/linker.lf | 5 + .../src/usb_serial_jtag.c | 39 ++++ .../src/usb_serial_jtag_vfs.c | 195 +++++++++++++++++- .../test_apps/.build-test-rules.yml | 11 + .../usb_serial_jtag_vfs/CMakeLists.txt | 11 + .../test_apps/usb_serial_jtag_vfs/README.md | 2 + .../usb_serial_jtag_vfs/main/CMakeLists.txt | 9 + .../usb_serial_jtag_vfs/main/test_app_main.c | 28 +++ .../main/test_vfs_usb_serial_jtag.c | 130 ++++++++++++ .../pytest_usb_serial_jtag_vfs.py | 25 +++ .../usb_serial_jtag_vfs/sdkconfig.ci.release | 6 + .../usb_serial_jtag_vfs/sdkconfig.defaults | 8 + 15 files changed, 522 insertions(+), 1 deletion(-) create mode 100644 components/esp_driver_usb_serial_jtag/include/driver/usb_serial_jtag_select.h create mode 100644 components/esp_driver_usb_serial_jtag/linker.lf create mode 100644 components/esp_driver_usb_serial_jtag/test_apps/usb_serial_jtag_vfs/CMakeLists.txt create mode 100644 components/esp_driver_usb_serial_jtag/test_apps/usb_serial_jtag_vfs/README.md create mode 100644 components/esp_driver_usb_serial_jtag/test_apps/usb_serial_jtag_vfs/main/CMakeLists.txt create mode 100644 components/esp_driver_usb_serial_jtag/test_apps/usb_serial_jtag_vfs/main/test_app_main.c create mode 100644 components/esp_driver_usb_serial_jtag/test_apps/usb_serial_jtag_vfs/main/test_vfs_usb_serial_jtag.c create mode 100644 components/esp_driver_usb_serial_jtag/test_apps/usb_serial_jtag_vfs/pytest_usb_serial_jtag_vfs.py create mode 100644 components/esp_driver_usb_serial_jtag/test_apps/usb_serial_jtag_vfs/sdkconfig.ci.release create mode 100644 components/esp_driver_usb_serial_jtag/test_apps/usb_serial_jtag_vfs/sdkconfig.defaults diff --git a/components/esp_driver_usb_serial_jtag/CMakeLists.txt b/components/esp_driver_usb_serial_jtag/CMakeLists.txt index 884353ac00..ca7b6a30ce 100644 --- a/components/esp_driver_usb_serial_jtag/CMakeLists.txt +++ b/components/esp_driver_usb_serial_jtag/CMakeLists.txt @@ -18,6 +18,7 @@ endif() idf_component_register(SRCS ${srcs} INCLUDE_DIRS ${include} PRIV_REQUIRES "${priv_requires}" + LDFRAGMENTS "linker.lf" ) if(CONFIG_VFS_SUPPORT_IO AND CONFIG_ESP_CONSOLE_USB_SERIAL_JTAG_ENABLED) diff --git a/components/esp_driver_usb_serial_jtag/include/driver/usb_serial_jtag.h b/components/esp_driver_usb_serial_jtag/include/driver/usb_serial_jtag.h index 20eca41126..bd516d3306 100644 --- a/components/esp_driver_usb_serial_jtag/include/driver/usb_serial_jtag.h +++ b/components/esp_driver_usb_serial_jtag/include/driver/usb_serial_jtag.h @@ -105,6 +105,13 @@ esp_err_t usb_serial_jtag_driver_uninstall(void); */ bool usb_serial_jtag_is_connected(void); +/** + * @brief Get information whether the USB serial JTAG driver is installed or not + * + * @return True if driver is installed and False if driver not installed + */ +bool usb_serial_jtag_is_driver_installed(void); + #ifdef __cplusplus } #endif diff --git a/components/esp_driver_usb_serial_jtag/include/driver/usb_serial_jtag_select.h b/components/esp_driver_usb_serial_jtag/include/driver/usb_serial_jtag_select.h new file mode 100644 index 0000000000..3fb185ed3d --- /dev/null +++ b/components/esp_driver_usb_serial_jtag/include/driver/usb_serial_jtag_select.h @@ -0,0 +1,46 @@ + +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#include "driver/usb_serial_jtag.h" + +typedef enum { + USJ_SELECT_READ_NOTIF, + USJ_SELECT_WRITE_NOTIF, + USJ_SELECT_ERROR_NOTIF, +} usj_select_notif_t; + +typedef void (*usj_select_notif_callback_t)(usj_select_notif_t usb_serial_jtag_select_notif, BaseType_t *task_woken); + +/** + * @brief Set notification callback function for select() events + * @param usb_serial_jtag_select_notif_callback callback function + */ +void usb_serial_jtag_set_select_notif_callback(usj_select_notif_callback_t usb_serial_jtag_select_notif_callback); + +/** + * @brief Return the readiness status of the driver for read operation + * + * @return true if driver read ready, false if not + */ +bool usb_serial_jtag_read_ready(void); + +/** + * @brief Return the readiness status of the driver for write operation + * + * @return true if driver is write ready, false if not + */ +bool usb_serial_jtag_write_ready(void); + +#ifdef __cplusplus +} +#endif diff --git a/components/esp_driver_usb_serial_jtag/linker.lf b/components/esp_driver_usb_serial_jtag/linker.lf new file mode 100644 index 0000000000..341175df2b --- /dev/null +++ b/components/esp_driver_usb_serial_jtag/linker.lf @@ -0,0 +1,5 @@ +[mapping:usb_serial_jtag_vfs] +archive: libesp_driver_usb_serial_jtag.a +entries: + if VFS_SELECT_IN_RAM = y: + usb_serial_jtag_vfs: select_notif_callback_isr (noflash) diff --git a/components/esp_driver_usb_serial_jtag/src/usb_serial_jtag.c b/components/esp_driver_usb_serial_jtag/src/usb_serial_jtag.c index 9351dcc06f..3d24d9716a 100644 --- a/components/esp_driver_usb_serial_jtag/src/usb_serial_jtag.c +++ b/components/esp_driver_usb_serial_jtag/src/usb_serial_jtag.c @@ -14,6 +14,7 @@ #include "freertos/ringbuf.h" #include "esp_intr_alloc.h" #include "driver/usb_serial_jtag.h" +#include "driver/usb_serial_jtag_select.h" #include "soc/periph_defs.h" #include "soc/soc_caps.h" #include "esp_check.h" @@ -49,6 +50,8 @@ typedef struct { //Synchronization stuff, only used for flush for now SemaphoreHandle_t tx_mux; SemaphoreHandle_t tx_idle_sem; + + usj_select_notif_callback_t usj_select_notif_callback; /*!< Notification about select() events */ } usb_serial_jtag_obj_t; static usb_serial_jtag_obj_t *p_usb_serial_jtag_obj = NULL; @@ -103,6 +106,12 @@ static void usb_serial_jtag_isr_handler_default(void *arg) // Return the buffer if we got it from the ring buffer. if (queued_buf != p_usb_serial_jtag_obj->tx_stash_buf) { vRingbufferReturnItemFromISR(p_usb_serial_jtag_obj->tx_ring_buf, queued_buf, &xTaskWoken); + + // We just moved items out of the TX ring buffer, the driver is considered write ready, since + // the TX ring buffer is assured to be not full. + if (p_usb_serial_jtag_obj->usj_select_notif_callback) { + p_usb_serial_jtag_obj->usj_select_notif_callback(USJ_SELECT_WRITE_NOTIF, &xTaskWoken); + } } } else { // No data to send. @@ -139,6 +148,10 @@ static void usb_serial_jtag_isr_handler_default(void *arg) uint8_t buf[USB_SER_JTAG_RX_MAX_SIZE]; uint32_t rx_fifo_len = usb_serial_jtag_ll_read_rxfifo(buf, USB_SER_JTAG_RX_MAX_SIZE); xRingbufferSendFromISR(p_usb_serial_jtag_obj->rx_ring_buf, buf, rx_fifo_len, &xTaskWoken); + + if (p_usb_serial_jtag_obj->usj_select_notif_callback) { + p_usb_serial_jtag_obj->usj_select_notif_callback(USJ_SELECT_READ_NOTIF, &xTaskWoken); + } } if (xTaskWoken == pdTRUE) { @@ -341,3 +354,29 @@ esp_err_t usb_serial_jtag_driver_uninstall(void) p_usb_serial_jtag_obj = NULL; return ESP_OK; } + +bool usb_serial_jtag_is_driver_installed(void) +{ + return (p_usb_serial_jtag_obj != NULL); +} + +void usb_serial_jtag_set_select_notif_callback(usj_select_notif_callback_t usj_select_notif_callback) +{ + if (usb_serial_jtag_is_driver_installed()) { + p_usb_serial_jtag_obj->usj_select_notif_callback = usj_select_notif_callback; + } +} + +bool usb_serial_jtag_read_ready(void) +{ + // sign the the driver is read ready is that data is waiting in the RX ringbuffer + UBaseType_t items_waiting = 0; + vRingbufferGetInfo(p_usb_serial_jtag_obj->rx_ring_buf, NULL, NULL, NULL, NULL, &items_waiting); + return items_waiting != 0; +} + +bool usb_serial_jtag_write_ready(void) +{ + // sign that the driver is write ready is that the TX ring buffer is not full + return (xRingbufferGetCurFreeSize(p_usb_serial_jtag_obj->tx_ring_buf) > 0); +} diff --git a/components/esp_driver_usb_serial_jtag/src/usb_serial_jtag_vfs.c b/components/esp_driver_usb_serial_jtag/src/usb_serial_jtag_vfs.c index d8ea60c81f..3d74b7ae27 100644 --- a/components/esp_driver_usb_serial_jtag/src/usb_serial_jtag_vfs.c +++ b/components/esp_driver_usb_serial_jtag/src/usb_serial_jtag_vfs.c @@ -23,9 +23,14 @@ #include "sdkconfig.h" #include "soc/soc_caps.h" #include "hal/usb_serial_jtag_ll.h" +#include "driver/usb_serial_jtag_select.h" #include "driver/usb_serial_jtag_vfs.h" #include "driver/usb_serial_jtag.h" #include "esp_private/startup_internal.h" +#include "esp_heap_caps.h" + +// local file descriptor value for the USJ +#define USJ_LOCAL_FD 0 // Token signifying that no character is available #define NONE -1 @@ -46,6 +51,12 @@ # define DEFAULT_RX_MODE ESP_LINE_ENDINGS_LF #endif +#if CONFIG_VFS_SELECT_IN_RAM +#define USJ_VFS_MALLOC_FLAGS (MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT) +#else +#define USJ_VFS_MALLOC_FLAGS MALLOC_CAP_DEFAULT +#endif + // write bytes function type typedef void (*tx_func_t)(int, int); // read bytes function type @@ -108,10 +119,30 @@ static usb_serial_jtag_vfs_context_t s_ctx = { .fsync_func = usb_serial_jtag_wait_tx_done_no_driver }; +#ifdef CONFIG_VFS_SUPPORT_SELECT + +typedef struct { + esp_vfs_select_sem_t select_sem; + fd_set *readfds; + fd_set *writefds; + fd_set *errorfds; + fd_set readfds_orig; + fd_set writefds_orig; + fd_set errorfds_orig; +} usb_serial_jtag_select_args_t; + +static usb_serial_jtag_select_args_t **s_registered_selects = NULL; +static int s_registered_select_num = 0; +static portMUX_TYPE s_registered_select_lock = portMUX_INITIALIZER_UNLOCKED; + +static esp_err_t usb_serial_jtag_end_select(void *end_select_args); + +#endif // CONFIG_VFS_SUPPORT_SELECT + static int usb_serial_jtag_open(const char * path, int flags, int mode) { s_ctx.non_blocking = ((flags & O_NONBLOCK) == O_NONBLOCK); - return 0; + return USJ_LOCAL_FD; } static void usb_serial_jtag_tx_char_no_driver(int fd, int c) @@ -300,6 +331,164 @@ static int usb_serial_jtag_fsync(int fd) } } +#ifdef CONFIG_VFS_SUPPORT_SELECT + +static void select_notif_callback_isr(usj_select_notif_t usj_select_notif, BaseType_t *task_woken) +{ + portENTER_CRITICAL_ISR(&s_registered_select_lock); + for (int i = 0; i < s_registered_select_num; ++i) { + usb_serial_jtag_select_args_t *args = s_registered_selects[i]; + if (args) { + switch (usj_select_notif) { + case USJ_SELECT_READ_NOTIF: + if (FD_ISSET(USJ_LOCAL_FD, &args->readfds_orig)) { + FD_SET(USJ_LOCAL_FD, args->readfds); + esp_vfs_select_triggered_isr(args->select_sem, task_woken); + } + break; + case USJ_SELECT_WRITE_NOTIF: + if (FD_ISSET(USJ_LOCAL_FD, &args->writefds_orig)) { + FD_SET(USJ_LOCAL_FD, args->writefds); + esp_vfs_select_triggered_isr(args->select_sem, task_woken); + } + break; + case USJ_SELECT_ERROR_NOTIF: + if (FD_ISSET(USJ_LOCAL_FD, &args->errorfds_orig)) { + FD_SET(USJ_LOCAL_FD, args->errorfds); + esp_vfs_select_triggered_isr(args->select_sem, task_woken); + } + break; + } + } + } + portEXIT_CRITICAL_ISR(&s_registered_select_lock); +} + +static esp_err_t register_select(usb_serial_jtag_select_args_t *args) +{ + esp_err_t ret = ESP_ERR_INVALID_ARG; + + if (args) { + portENTER_CRITICAL(&s_registered_select_lock); + const int new_size = s_registered_select_num + 1; + usb_serial_jtag_select_args_t **new_selects; + if ((new_selects = heap_caps_realloc(s_registered_selects, new_size * sizeof(usb_serial_jtag_select_args_t *), USJ_VFS_MALLOC_FLAGS)) == NULL) { + ret = ESP_ERR_NO_MEM; + } else { + /* on first select registration register the callback */ + if (s_registered_select_num == 0) { + usb_serial_jtag_set_select_notif_callback(select_notif_callback_isr); + } + + s_registered_selects = new_selects; + s_registered_selects[s_registered_select_num] = args; + s_registered_select_num = new_size; + ret = ESP_OK; + } + portEXIT_CRITICAL(&s_registered_select_lock); + } + + return ret; +} + +static esp_err_t unregister_select(usb_serial_jtag_select_args_t *args) +{ + esp_err_t ret = ESP_OK; + if (args) { + ret = ESP_ERR_INVALID_STATE; + portENTER_CRITICAL(&s_registered_select_lock); + for (int i = 0; i < s_registered_select_num; ++i) { + if (s_registered_selects[i] == args) { + const int new_size = s_registered_select_num - 1; + // The item is removed by overwriting it with the last item. The subsequent rellocation will drop the + // last item. + s_registered_selects[i] = s_registered_selects[new_size]; + s_registered_selects = heap_caps_realloc(s_registered_selects, new_size * sizeof(usb_serial_jtag_select_args_t *), USJ_VFS_MALLOC_FLAGS); + // Shrinking a buffer with realloc is guaranteed to succeed. + s_registered_select_num = new_size; + + /* when the last select is unregistered, also unregister the callback */ + if (s_registered_select_num == 0) { + usb_serial_jtag_set_select_notif_callback(NULL); + } + + ret = ESP_OK; + break; + } + } + portEXIT_CRITICAL(&s_registered_select_lock); + } + return ret; +} + +static esp_err_t usb_serial_jtag_start_select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, + esp_vfs_select_sem_t select_sem, void **end_select_args) +{ + (void)nfds; /* Since there is only 1 usb serial jtag port, this parameter is useless */ + *end_select_args = NULL; + if (!usb_serial_jtag_is_driver_installed()) { + return ESP_ERR_INVALID_STATE; + } + + usb_serial_jtag_select_args_t *args = heap_caps_malloc(sizeof(usb_serial_jtag_select_args_t), USJ_VFS_MALLOC_FLAGS); + + if (args == NULL) { + return ESP_ERR_NO_MEM; + } + args->select_sem = select_sem; + args->readfds = readfds; + args->writefds = writefds; + args->errorfds = exceptfds; + args->readfds_orig = *readfds; // store the original values because they will be set to zero + args->writefds_orig = *writefds; + args->errorfds_orig = *exceptfds; + FD_ZERO(readfds); + FD_ZERO(writefds); + FD_ZERO(exceptfds); + + esp_err_t ret = register_select(args); + if (ret != ESP_OK) { + free(args); + return ret; + } + + bool trigger_select = false; + + // check if the select should return instantly if the bus is read ready + if (FD_ISSET(USJ_LOCAL_FD, &args->readfds_orig) && usb_serial_jtag_read_ready()) { + // signal immediately when data is buffered + FD_SET(USJ_LOCAL_FD, readfds); + trigger_select = true; + } + + // check if the select should return instantly if the bus is write ready + if (FD_ISSET(USJ_LOCAL_FD, &args->writefds_orig) && usb_serial_jtag_write_ready()) { + // signal immediately when data can be written + FD_SET(USJ_LOCAL_FD, writefds); + trigger_select = true; + } + + if (trigger_select) { + esp_vfs_select_triggered(args->select_sem); + } + + *end_select_args = args; + return ESP_OK; +} + +static esp_err_t usb_serial_jtag_end_select(void *end_select_args) +{ + usb_serial_jtag_select_args_t *args = end_select_args; + esp_err_t ret = unregister_select(args); + if (args) { + free(args); + } + + return ret; +} + +#endif // CONFIG_VFS_SUPPORT_SELECT + #ifdef CONFIG_VFS_SUPPORT_TERMIOS static int usb_serial_jtag_tcsetattr(int fd, int optional_actions, const struct termios *p) { @@ -391,6 +580,10 @@ static const esp_vfs_t usj_vfs = { .read = &usb_serial_jtag_read, .fcntl = &usb_serial_jtag_fcntl, .fsync = &usb_serial_jtag_fsync, +#ifdef CONFIG_VFS_SUPPORT_SELECT + .start_select = &usb_serial_jtag_start_select, + .end_select = &usb_serial_jtag_end_select, +#endif // CONFIG_VFS_SUPPORT_SELECT #ifdef CONFIG_VFS_SUPPORT_TERMIOS .tcsetattr = &usb_serial_jtag_tcsetattr, .tcgetattr = &usb_serial_jtag_tcgetattr, diff --git a/components/esp_driver_usb_serial_jtag/test_apps/.build-test-rules.yml b/components/esp_driver_usb_serial_jtag/test_apps/.build-test-rules.yml index cd9c709a2e..88ae136943 100644 --- a/components/esp_driver_usb_serial_jtag/test_apps/.build-test-rules.yml +++ b/components/esp_driver_usb_serial_jtag/test_apps/.build-test-rules.yml @@ -11,3 +11,14 @@ components/esp_driver_usb_serial_jtag/test_apps/usb_serial_jtag: - vfs - esp_driver_gpio - esp_driver_usb_serial_jtag + +components/esp_driver_usb_serial_jtag/test_apps/usb_serial_jtag_vfs: + disable: + - if: SOC_USB_SERIAL_JTAG_SUPPORTED != 1 + disable_test: + - if: IDF_TARGET in ["esp32p4", "esp32c5", "esp32c61"] + temporary: true + reason: No runners. + depends_components: + - vfs + - esp_driver_usb_serial_jtag diff --git a/components/esp_driver_usb_serial_jtag/test_apps/usb_serial_jtag_vfs/CMakeLists.txt b/components/esp_driver_usb_serial_jtag/test_apps/usb_serial_jtag_vfs/CMakeLists.txt new file mode 100644 index 0000000000..7e13de758f --- /dev/null +++ b/components/esp_driver_usb_serial_jtag/test_apps/usb_serial_jtag_vfs/CMakeLists.txt @@ -0,0 +1,11 @@ +# This is the project CMakeLists.txt file for the test subproject +cmake_minimum_required(VERSION 3.5) + +set(EXTRA_COMPONENT_DIRS "$ENV{IDF_PATH}/tools/unit-test-app/components") + +list(PREPEND SDKCONFIG_DEFAULTS "$ENV{IDF_PATH}/tools/test_apps/configs/sdkconfig.debug_helpers" "sdkconfig.defaults") + +set(COMPONENTS main) + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +project(usb_serial_jtag_vfs_test) diff --git a/components/esp_driver_usb_serial_jtag/test_apps/usb_serial_jtag_vfs/README.md b/components/esp_driver_usb_serial_jtag/test_apps/usb_serial_jtag_vfs/README.md new file mode 100644 index 0000000000..c1d7d543d3 --- /dev/null +++ b/components/esp_driver_usb_serial_jtag/test_apps/usb_serial_jtag_vfs/README.md @@ -0,0 +1,2 @@ +| Supported Targets | ESP32-C3 | ESP32-C5 | ESP32-C6 | ESP32-C61 | ESP32-H2 | ESP32-P4 | ESP32-S3 | +| ----------------- | -------- | -------- | -------- | --------- | -------- | -------- | -------- | diff --git a/components/esp_driver_usb_serial_jtag/test_apps/usb_serial_jtag_vfs/main/CMakeLists.txt b/components/esp_driver_usb_serial_jtag/test_apps/usb_serial_jtag_vfs/main/CMakeLists.txt new file mode 100644 index 0000000000..294aac9482 --- /dev/null +++ b/components/esp_driver_usb_serial_jtag/test_apps/usb_serial_jtag_vfs/main/CMakeLists.txt @@ -0,0 +1,9 @@ +set(src "test_app_main.c" + "test_vfs_usb_serial_jtag.c" + ) + +idf_component_register(SRCS ${src} + PRIV_INCLUDE_DIRS . + PRIV_REQUIRES esp_driver_usb_serial_jtag unity test_utils esp_psram + WHOLE_ARCHIVE + ) diff --git a/components/esp_driver_usb_serial_jtag/test_apps/usb_serial_jtag_vfs/main/test_app_main.c b/components/esp_driver_usb_serial_jtag/test_apps/usb_serial_jtag_vfs/main/test_app_main.c new file mode 100644 index 0000000000..7e481a9f4d --- /dev/null +++ b/components/esp_driver_usb_serial_jtag/test_apps/usb_serial_jtag_vfs/main/test_app_main.c @@ -0,0 +1,28 @@ +/* + * SPDX-FileCopyrightText: 2022-2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Unlicense OR CC0-1.0 + */ + +#include "unity.h" +#include "unity_test_utils.h" +#include "esp_heap_caps.h" + +// Some resources are lazy allocated, the threadhold is left for that case +#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) +{ + unity_run_menu(); +} diff --git a/components/esp_driver_usb_serial_jtag/test_apps/usb_serial_jtag_vfs/main/test_vfs_usb_serial_jtag.c b/components/esp_driver_usb_serial_jtag/test_apps/usb_serial_jtag_vfs/main/test_vfs_usb_serial_jtag.c new file mode 100644 index 0000000000..c349aab14c --- /dev/null +++ b/components/esp_driver_usb_serial_jtag/test_apps/usb_serial_jtag_vfs/main/test_vfs_usb_serial_jtag.c @@ -0,0 +1,130 @@ +/* + * SPDX-FileCopyrightText: 2015-2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include "unity.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "freertos/semphr.h" +#include "driver/usb_serial_jtag_vfs.h" +#include "driver/usb_serial_jtag.h" +#include "hal/usb_serial_jtag_ll.h" +#include "esp_clk_tree.h" +#include "test_utils.h" +#include "sdkconfig.h" + +struct task_arg_t { + FILE* stream; + SemaphoreHandle_t done; +}; + +static int read_bytes_with_select(FILE *stream, void *buf, size_t buf_len, struct timeval tv) +{ + + int fd = fileno(stream); + fd_set read_fds; + FD_ZERO(&read_fds); + FD_SET(fd, &read_fds); + + /* call select with to wait for either a read ready or timeout to happen */ + int nread = select(fd + 1, &read_fds, NULL, NULL, &tv); + if (nread < 0) { + return -1; + } else if (FD_ISSET(fd, &read_fds)) { + int read_count = 0; + int total_read = 0; + + do { + read_count = read(fileno(stream), buf + total_read, buf_len - total_read); + if (read_count < 0 && errno != EWOULDBLOCK) { + return -1; + } else if (read_count > 0) { + total_read += read_count; + if (total_read > buf_len) { + fflush(stream); + break; + } + } + } while (read_count > 0); + + return total_read; + } else { + return -2; + } +} + +/* In this test, the select returning on timeout, write and read ready functionalities + * are under test. To achieve that, the testing environment: + * 0) Starts the test + * 1) Waits for the select to timeout (read_bytes_with_select returns timeout error which + * triggers the sending of the timeout message back to the testing environment) + * 2) Sends the test message (read_bytes_with_select returns with the received message that + * is sent back to the testing environment to be checked) + * 3) A parallel tasks calls select on stdout for writing and decrement a variable for every + * write calls. At the end of the test, the variable is check to make sure the select returned + * for each of the write calls. + */ +TEST_CASE("test select read, write and timeout", "[usb_serial_jtag]") +{ + struct timeval tv; + tv.tv_sec = 1; + tv.tv_usec = 0; + char out_buffer[32]; + memset(out_buffer, 0, sizeof(out_buffer)); + size_t out_buffer_len = sizeof(out_buffer); + + // stdin needs to be non blocking to properly call read after select returns + // with read ready on stdin. + int fd = fileno(stdin); + int flags = fcntl(fd, F_GETFL); + flags |= O_NONBLOCK; + int res = fcntl(fd, F_SETFL, flags); + TEST_ASSERT(res == 0); + + // init driver + usb_serial_jtag_driver_config_t usj_config = USB_SERIAL_JTAG_DRIVER_CONFIG_DEFAULT(); + ESP_ERROR_CHECK(usb_serial_jtag_driver_install(&usj_config)); + usb_serial_jtag_vfs_use_driver(); + + // send the message from pytest environment and make sure it can be read + bool message_received = false; + size_t char_read = 0; + while (!message_received && out_buffer_len > char_read) { + int nread = read_bytes_with_select(stdin, out_buffer + char_read, out_buffer_len - char_read, tv); + if (nread > 0) { + char_read += nread; + if (out_buffer[char_read - 1] == '\n') { + message_received = true; + } + } else if (nread == -2) { + // time out occurred, send the expected message back to the testing + // environment to trigger the testing environment into sending the + // test message. don't update this message without updating the pytest + // function since the string is expected as is by the test environment + char timeout_msg[] = "select timed out\n"; + write(fileno(stdout), timeout_msg, sizeof(timeout_msg)); + } else { + break; + } + } + + // write the received message back to the test environment. The test + // environment will check that the message received matches the one sent + write(fileno(stdout), out_buffer, char_read); + + // wait for the string to send + vTaskDelay(10); + + usb_serial_jtag_vfs_use_nonblocking(); + usb_serial_jtag_driver_uninstall(); +} diff --git a/components/esp_driver_usb_serial_jtag/test_apps/usb_serial_jtag_vfs/pytest_usb_serial_jtag_vfs.py b/components/esp_driver_usb_serial_jtag/test_apps/usb_serial_jtag_vfs/pytest_usb_serial_jtag_vfs.py new file mode 100644 index 0000000000..897f3179ee --- /dev/null +++ b/components/esp_driver_usb_serial_jtag/test_apps/usb_serial_jtag_vfs/pytest_usb_serial_jtag_vfs.py @@ -0,0 +1,25 @@ +# SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD +# SPDX-License-Identifier: CC0-1.0 +import pytest +from pytest_embedded import Dut + + +@pytest.mark.esp32s3 +@pytest.mark.esp32c3 +@pytest.mark.esp32c6 +@pytest.mark.esp32h2 +@pytest.mark.usb_serial_jtag +@pytest.mark.parametrize( + 'port, flash_port, config', + [ + pytest.param('/dev/serial_ports/ttyACM-esp32', '/dev/serial_ports/ttyUSB-esp32', 'release'), + ], + indirect=True,) +@pytest.mark.parametrize('test_message', ['test123456789!@#%^&*']) +def test_usj_vfs_release(dut: Dut, test_message: list) -> None: + dut.expect_exact('Press ENTER to see the list of tests') + dut.write('\"test select read, write and timeout\"') + dut.expect_exact('select timed out', timeout=2) + dut.write(test_message) + dut.expect_exact(test_message, timeout=2) + dut.expect(r'\d{1} Tests 0 Failures 0 Ignored', timeout=10) diff --git a/components/esp_driver_usb_serial_jtag/test_apps/usb_serial_jtag_vfs/sdkconfig.ci.release b/components/esp_driver_usb_serial_jtag/test_apps/usb_serial_jtag_vfs/sdkconfig.ci.release new file mode 100644 index 0000000000..673b6f8f74 --- /dev/null +++ b/components/esp_driver_usb_serial_jtag/test_apps/usb_serial_jtag_vfs/sdkconfig.ci.release @@ -0,0 +1,6 @@ +CONFIG_PM_ENABLE=y +CONFIG_FREERTOS_USE_TICKLESS_IDLE=y +CONFIG_COMPILER_OPTIMIZATION_SIZE=y +CONFIG_BOOTLOADER_COMPILER_OPTIMIZATION_SIZE=y +CONFIG_COMPILER_OPTIMIZATION_ASSERTIONS_SILENT=y +CONFIG_COMPILER_OPTIMIZATION_NONE=y diff --git a/components/esp_driver_usb_serial_jtag/test_apps/usb_serial_jtag_vfs/sdkconfig.defaults b/components/esp_driver_usb_serial_jtag/test_apps/usb_serial_jtag_vfs/sdkconfig.defaults new file mode 100644 index 0000000000..0dc25bbc63 --- /dev/null +++ b/components/esp_driver_usb_serial_jtag/test_apps/usb_serial_jtag_vfs/sdkconfig.defaults @@ -0,0 +1,8 @@ +# Enable Unity fixture support +CONFIG_UNITY_ENABLE_FIXTURE=n +CONFIG_UNITY_ENABLE_IDF_TEST_RUNNER=y + +# Custom partition table for this test app +CONFIG_ESP_TASK_WDT_INIT=n + +CONFIG_ESP_CONSOLE_USB_SERIAL_JTAG=y