From 30f8b59ed03764141ca6759a1da1c9be8a531ad5 Mon Sep 17 00:00:00 2001 From: Guillaume Souchere Date: Tue, 11 Mar 2025 12:18:22 +0100 Subject: [PATCH] feat(console): Make console deinit optional --- components/console/CMakeLists.txt | 1 + components/console/esp_console.h | 12 +- components/console/esp_console_common.c | 74 +++----- components/console/esp_console_repl_chip.c | 68 ++----- .../console/esp_console_repl_internal.c | 166 ++++++++++++++++++ components/console/linenoise/linenoise.c | 79 ++++----- components/console/linenoise/linenoise.h | 7 +- .../console/private_include/console_private.h | 4 +- .../test_apps/console/main/test_console.c | 50 ++++-- docs/en/api-reference/system/console.rst | 8 + .../usb/device/tusb_msc/main/tusb_msc_main.c | 2 +- .../icmp_echo/main/echo_example_main.c | 20 --- 12 files changed, 304 insertions(+), 187 deletions(-) create mode 100644 components/console/esp_console_repl_internal.c diff --git a/components/console/CMakeLists.txt b/components/console/CMakeLists.txt index ed20f246bb..a6c7632830 100644 --- a/components/console/CMakeLists.txt +++ b/components/console/CMakeLists.txt @@ -2,6 +2,7 @@ idf_build_get_property(target IDF_TARGET) set(srcs "commands.c" "esp_console_common.c" + "esp_console_repl_internal.c" "split_argv.c" "linenoise/linenoise.c") diff --git a/components/console/esp_console.h b/components/console/esp_console.h index 7e7a18ddb1..c13234ce51 100644 --- a/components/console/esp_console.h +++ b/components/console/esp_console.h @@ -451,13 +451,23 @@ esp_err_t esp_console_new_repl_usb_serial_jtag(const esp_console_dev_usb_serial_ /** * @brief Start REPL environment * @param[in] repl REPL handle returned from esp_console_new_repl_xxx - * @note Once the REPL gets started, it won't be stopped until the user calls repl->del(repl) to destroy the REPL environment. + * @note Once the REPL gets started, it won't be stopped until the user calls esp_console_stop_repl to destroy the REPL environment. * @return * - ESP_OK on success * - ESP_ERR_INVALID_STATE, if repl has started already */ esp_err_t esp_console_start_repl(esp_console_repl_t *repl); +/** + * @brief Stop REPL environment + * + * @param[in] repl REPL handle returned from esp_console_new_repl_xxx + * @return + * - ESP_OK on success + * - others on failure + */ +esp_err_t esp_console_stop_repl(esp_console_repl_t *repl); + #ifdef __cplusplus } #endif diff --git a/components/console/esp_console_common.c b/components/console/esp_console_common.c index dac7729d45..ce5c61b4a7 100644 --- a/components/console/esp_console_common.c +++ b/components/console/esp_console_common.c @@ -6,6 +6,8 @@ #include "sdkconfig.h" #include // __containerof +#include +#include #include "esp_console.h" #include "console_private.h" #include "esp_log.h" @@ -18,10 +20,6 @@ static const char *TAG = "console.common"; -static bool s_init = false; -static int s_interrupt_reading_fd = -1; -static uint64_t s_interrupt_reading_signal = 1; - esp_err_t esp_console_setup_prompt(const char *prompt, esp_console_repl_com_t *repl_com) { /* set command line prompt */ @@ -68,6 +66,11 @@ _exit: return ret; } +__attribute__((weak)) esp_err_t esp_console_internal_set_event_fd(esp_console_repl_com_t *repl_com) +{ + return ESP_OK; +} + esp_err_t esp_console_common_init(size_t max_cmdline_length, esp_console_repl_com_t *repl_com) { esp_err_t ret = ESP_OK; @@ -103,50 +106,36 @@ esp_err_t esp_console_common_init(size_t max_cmdline_length, esp_console_repl_co linenoiseSetCompletionCallback(&esp_console_get_completion); linenoiseSetHintsCallback((linenoiseHintsCallback *)&esp_console_get_hint); - /* Tell linenoise what file descriptor to add to the read file descriptor set, - * that will be used to signal a read termination */ - esp_vfs_eventfd_config_t config = ESP_VFS_EVENTD_CONFIG_DEFAULT(); - ret = esp_vfs_eventfd_register(&config); - if (ret == ESP_OK) { - /* first time calling the eventfd register function */ - s_interrupt_reading_fd = eventfd(0, 0); - linenoiseSetInterruptReadingData(s_interrupt_reading_fd, s_interrupt_reading_signal); - } else if (ret == ESP_ERR_INVALID_STATE) { - /* the evenfd has already been registered*/ - } else { - /* issue with arg, this should not happen */ +#if CONFIG_VFS_SUPPORT_SELECT + ret = esp_console_internal_set_event_fd(repl_com); + if (ret != ESP_OK) { goto _exit; } +#endif - repl_com->state_mux = xSemaphoreCreateMutex(); - if (repl_com->state_mux == NULL) { - ESP_LOGE(TAG, "state_mux create error"); - ret = ESP_ERR_NO_MEM; - goto _exit; - } - xSemaphoreGive(repl_com->state_mux); - - s_init = true; return ESP_OK; _exit: - s_init = false; return ret; } -void esp_console_common_deinit(esp_console_repl_com_t *repl_com) +__attribute__((weak)) esp_err_t esp_console_common_deinit(esp_console_repl_com_t *repl_com) { + // set the state to deinit to force the while loop in + // esp_console_repl_task to break + repl_com->state = CONSOLE_REPL_STATE_DEINIT; + /* Unregister the heap function to avoid memory leak, since it is created * every time a console init is called. */ esp_err_t ret = esp_console_deregister_help_command(); - assert(ret == ESP_OK); - - /* unregister eventfd to avoid memory leaks, since it is created every time a - * console init is called */ - (void)esp_vfs_eventfd_unregister(); + if (ret != ESP_OK) { + return ret; + } /* free the history to avoid memory leak, since it is created * every time a console init is called. */ linenoiseHistoryFree(); + + return ESP_OK; } esp_err_t esp_console_start_repl(esp_console_repl_t *repl) @@ -176,9 +165,10 @@ void esp_console_repl_task(void *args) * function is called. */ ulTaskNotifyTake(pdTRUE, portMAX_DELAY); - assert(repl_com->state_mux != NULL); - BaseType_t ret_val = xSemaphoreTake(repl_com->state_mux, portMAX_DELAY); - assert(ret_val == pdTRUE); + if (repl_com->state_mux != NULL) { + BaseType_t ret_val = xSemaphoreTake(repl_com->state_mux, portMAX_DELAY); + assert(ret_val == pdTRUE); + } /* Change standard input and output of the task if the requested UART is * NOT the default one. This block will replace stdin, stdout and stderr. @@ -242,17 +232,9 @@ void esp_console_repl_task(void *args) linenoiseFree(line); } - xSemaphoreGive(repl_com->state_mux); + if (repl_com->state_mux != NULL) { + xSemaphoreGive(repl_com->state_mux); + } ESP_LOGD(TAG, "The End"); vTaskDelete(NULL); } - -esp_err_t esp_console_interrupt_reading(void) -{ - if (!s_init) { - return ESP_FAIL; - } - ssize_t ret = write(s_interrupt_reading_fd, &s_interrupt_reading_signal, sizeof(s_interrupt_reading_signal)); - assert(ret == sizeof(s_interrupt_reading_signal)); - return ESP_OK; -} diff --git a/components/console/esp_console_repl_chip.c b/components/console/esp_console_repl_chip.c index 3ec5dbd3de..912fa90a66 100644 --- a/components/console/esp_console_repl_chip.c +++ b/components/console/esp_console_repl_chip.c @@ -24,7 +24,7 @@ #include "console_private.h" #if !CONFIG_ESP_CONSOLE_NONE -static const char *TAG = "console.repl"; +static const char *TAG = "console.repl.chip"; #endif // !CONFIG_ESP_CONSOLE_NONE #if CONFIG_ESP_CONSOLE_UART_DEFAULT || CONFIG_ESP_CONSOLE_UART_CUSTOM @@ -296,27 +296,11 @@ static esp_err_t esp_console_repl_uart_delete(esp_console_repl_t *repl) goto _exit; } - // set the state to deinit to force the while loop in - // esp_console_repl_task to break - repl_com->state = CONSOLE_REPL_STATE_DEINIT; - - const esp_err_t read_interrupted = esp_console_interrupt_reading(); - if (read_interrupted != ESP_OK) { - return ESP_FAIL; + ret = esp_console_common_deinit(&uart_repl->repl_com); + if (ret != ESP_OK) { + goto _exit; } - // wait for the task to notify that - // esp_console_repl_task returned - assert(repl_com->state_mux != NULL); - BaseType_t ret_val = xSemaphoreTake(repl_com->state_mux, portMAX_DELAY); - assert(ret_val == pdTRUE); - - // delete the semaphore for the repl state - vSemaphoreDelete(repl_com->state_mux); - repl_com->state_mux = NULL; - - esp_console_common_deinit(&uart_repl->repl_com); - esp_console_deinit(); uart_vfs_dev_use_nonblocking(uart_repl->uart_channel); uart_driver_delete(uart_repl->uart_channel); @@ -339,27 +323,11 @@ static esp_err_t esp_console_repl_usb_cdc_delete(esp_console_repl_t *repl) goto _exit; } - // set the state to deinit to force the while loop in - // esp_console_repl_task to break - repl_com->state = CONSOLE_REPL_STATE_DEINIT; - - const esp_err_t read_interrupted = esp_console_interrupt_reading(); - if (read_interrupted != ESP_OK) { - return ESP_FAIL; + ret = esp_console_common_deinit(&cdc_repl->repl_com); + if (ret != ESP_OK) { + goto _exit; } - // wait for the task to notify that - // esp_console_repl_task returned - assert(repl_com->state_mux != NULL); - BaseType_t ret_val = xSemaphoreTake(repl_com->state_mux, portMAX_DELAY); - assert(ret_val == pdTRUE); - - // delete the semaphore for the repl state - vSemaphoreDelete(repl_com->state_mux); - repl_com->state_mux = NULL; - - esp_console_common_deinit(&cdc_repl->repl_com); - esp_console_deinit(); free(cdc_repl); _exit: @@ -380,27 +348,11 @@ static esp_err_t esp_console_repl_usb_serial_jtag_delete(esp_console_repl_t *rep goto _exit; } - // set the state to deinit to force the while loop in - // esp_console_repl_task to break - repl_com->state = CONSOLE_REPL_STATE_DEINIT; - - const esp_err_t read_interrupted = esp_console_interrupt_reading(); - if (read_interrupted != ESP_OK) { - return ESP_FAIL; + ret = esp_console_common_deinit(&usb_serial_jtag_repl->repl_com); + if (ret != ESP_OK) { + goto _exit; } - // wait for the task to notify that - // esp_console_repl_task returned - assert(repl_com->state_mux != NULL); - BaseType_t ret_val = xSemaphoreTake(repl_com->state_mux, portMAX_DELAY); - assert(ret_val == pdTRUE); - - // delete the semaphore for the repl state - vSemaphoreDelete(repl_com->state_mux); - repl_com->state_mux = NULL; - - esp_console_common_deinit(&usb_serial_jtag_repl->repl_com); - esp_console_deinit(); usb_serial_jtag_vfs_use_nonblocking(); usb_serial_jtag_driver_uninstall(); diff --git a/components/console/esp_console_repl_internal.c b/components/console/esp_console_repl_internal.c new file mode 100644 index 0000000000..10bdf6b96c --- /dev/null +++ b/components/console/esp_console_repl_internal.c @@ -0,0 +1,166 @@ +/* + * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "sdkconfig.h" +#include +#include // __containerof +#include +#include +#include "esp_console.h" +#include "console_private.h" +#include "esp_log.h" +#include "linenoise/linenoise.h" + +#include "esp_vfs_eventfd.h" + +#if CONFIG_VFS_SUPPORT_SELECT + +static const char *TAG = "console.repl.internal"; + +static int s_interrupt_reading_fd = -1; +static uint64_t s_interrupt_reading_signal = 1; + +static ssize_t read_bytes(int fd, void *buf, size_t max_bytes) +{ + fd_set read_fds; + FD_ZERO(&read_fds); + FD_SET(fd, &read_fds); + if (s_interrupt_reading_fd != -1) { + FD_SET(s_interrupt_reading_fd, &read_fds); + } + int maxFd = MAX(fd, s_interrupt_reading_fd); + /* call select to wait for either a read ready or an except to happen */ + int nread = select(maxFd + 1, &read_fds, NULL, NULL, NULL); + if (nread < 0) { + return -1; + } + + if (FD_ISSET(s_interrupt_reading_fd, &read_fds)) { + /* read termination request happened, return */ + int buf[sizeof(s_interrupt_reading_signal)]; + nread = read(s_interrupt_reading_fd, buf, sizeof(s_interrupt_reading_signal)); + if ((nread == sizeof(s_interrupt_reading_signal)) && (buf[0] == s_interrupt_reading_signal)) { + return -1; + } + } else if (FD_ISSET(fd, &read_fds)) { + /* a read ready triggered the select to return. call the + * read function with the number of bytes max_bytes */ + nread = read(fd, buf, max_bytes); + } + return nread; +} + +/* Strong definition of the weak definition of linenoiseSetReadCharacteristics in + * linenoise.c. This function set the read to be non blocking and set the read + * function used by linenoise to read_bytes. This function is compiled only if + * esp_console_stop_repl is used. */ +void linenoiseSetReadCharacteristics(void) +{ + /* Make sure we are using non blocking reads with + * the select */ + int stdin_fileno = fileno(stdin); + int flags = fcntl(stdin_fileno, F_GETFL); + flags |= O_NONBLOCK; + (void)fcntl(stdin_fileno, F_SETFL, flags); + + linenoiseSetReadFunction(read_bytes); +} + +/* Strong definition of the weak definition of esp_console_internal_set_event_fd + * in esp_console_common to allow the use of select with non blocking + * read. This function is compiled only if esp_console_stop_repl + * is used. */ +esp_err_t esp_console_internal_set_event_fd(esp_console_repl_com_t *repl_com) +{ + /* Tell linenoise what file descriptor to add to the read file descriptor set, + * that will be used to signal a read termination */ + esp_vfs_eventfd_config_t config = ESP_VFS_EVENTD_CONFIG_DEFAULT(); + esp_err_t ret = esp_vfs_eventfd_register(&config); + if (ret == ESP_OK) { + /* first time calling the eventfd register function */ + s_interrupt_reading_fd = eventfd(0, 0); + } else if (ret == ESP_ERR_INVALID_STATE) { + /* the evenfd has already been registered*/ + } else { + /* issue with arg, this should not happen */ + return ESP_FAIL; + } + + repl_com->state_mux = xSemaphoreCreateMutex(); + if (repl_com->state_mux == NULL) { + ESP_LOGE(TAG, "state_mux create error"); + return ESP_ERR_NO_MEM; + } + xSemaphoreGive(repl_com->state_mux); + + return ESP_OK; +} + +/* Strong definition of the weak definition of esp_console_common_deinit + * in esp_console_common to allow the use of select with non blocking + * read. This function is compiled only if esp_console_stop_repl + * is used. */ +esp_err_t esp_console_common_deinit(esp_console_repl_com_t *repl_com) +{ + // set the state to deinit to force the while loop in + // esp_console_repl_task to break + repl_com->state = CONSOLE_REPL_STATE_DEINIT; + + if (s_interrupt_reading_fd == -1) { + return ESP_FAIL; + } + + int nwrite = write(s_interrupt_reading_fd, &s_interrupt_reading_signal, sizeof(s_interrupt_reading_signal)); + if (nwrite != sizeof(s_interrupt_reading_signal)) { + return ESP_FAIL; + } + + // wait for the task to notify that + // esp_console_repl_task returned + assert(repl_com->state_mux != NULL); + BaseType_t ret_val = xSemaphoreTake(repl_com->state_mux, portMAX_DELAY); + assert(ret_val == pdTRUE); + + // delete the semaphore for the repl state + vSemaphoreDelete(repl_com->state_mux); + repl_com->state_mux = NULL; + + /* Unregister the heap function to avoid memory leak, since it is created + * every time a console init is called. */ + esp_err_t ret = esp_console_deregister_help_command(); + if (ret != ESP_OK) { + return ret; + } + + /* unregister eventfd to avoid memory leaks, since it is created every time a + * console init is called */ + ret = esp_vfs_eventfd_unregister(); + if (ret != ESP_OK) { + return ret; + } + + /* free the history to avoid memory leak, since it is created + * every time a console init is called. */ + linenoiseHistoryFree(); + + return ESP_OK; +} +#endif // CONFIG_VFS_SUPPORT_SELECT + +/* DO NOT move this function out of this file. All other definitions in this + * file are strong definition of weak functions. + * + * Those function are used to provide a clean way to exit linenoise + * and properly deinitialize the console by using select with non blocking + * read instead of blocking read as the default way to read character implemented + * in linenoise. + * + * If the user never calls this function, then the default read process is used and + * those functions will be ignored by the linker. */ +esp_err_t esp_console_stop_repl(esp_console_repl_t *repl) +{ + return repl->del(repl); +} diff --git a/components/console/linenoise/linenoise.c b/components/console/linenoise/linenoise.c index 7b8d378ec2..d4e4dc64b5 100644 --- a/components/console/linenoise/linenoise.c +++ b/components/console/linenoise/linenoise.c @@ -116,14 +116,12 @@ #include #include #include -#include #include #include #include #include #include #include -#include #include #include "linenoise.h" @@ -145,9 +143,6 @@ static int history_len = 0; static char **history = NULL; static bool allow_empty = true; -static int intr_reading_fd = -1; -static uint64_t intr_reading_signal = -1; - /* The linenoiseState structure represents the state during line editing. * We pass this state to functions implementing specific editing * functionalities. */ @@ -243,35 +238,21 @@ static void flushWrite(void) { fsync(fileno(stdout)); } -static int linenoiseReadBytes(int fd, void *buf, size_t max_bytes) +static linenoise_read_bytes_fn read_func = NULL; +void linenoiseSetReadFunction(linenoise_read_bytes_fn read_fn) { - fd_set read_fds; - FD_ZERO(&read_fds); - FD_SET(fd, &read_fds); - if (intr_reading_fd !=-1) { - FD_SET(intr_reading_fd, &read_fds); - } - int maxFd = MAX(fd, intr_reading_fd); - /* call select to wait for either a read ready or an except to happen */ - int nread = select(maxFd + 1, &read_fds, NULL, NULL, NULL); - if (nread < 0) { - return -1; - } + read_func = read_fn; +} - if(FD_ISSET(intr_reading_fd, &read_fds)) { - /* read termination request happened, return */ - int buf[sizeof(intr_reading_signal)]; - nread = read(intr_reading_fd, buf, sizeof(intr_reading_signal)); - if ((nread == sizeof(intr_reading_signal)) && (buf[0] == intr_reading_signal)) { - return -1; - } - } - else if(FD_ISSET(fd, &read_fds)) { - /* a read ready triggered the select to return. call the - * read function with the number of bytes max_bytes */ - nread = read(fd, buf, max_bytes); - } - return nread; +__attribute__((weak)) void linenoiseSetReadCharacteristics(void) +{ + /* By default linenoise uses blocking reads */ + int stdin_fileno = fileno(stdin); + int flags = fcntl(stdin_fileno, F_GETFL); + flags &= ~O_NONBLOCK; + (void)fcntl(stdin_fileno, F_SETFL, flags); + + linenoiseSetReadFunction(read); } /* Use the ESC [6n escape sequence to query the horizontal cursor position @@ -308,7 +289,7 @@ static int getCursorPosition(void) { /* Keep using unistd's functions. Here, using `read` instead of `fgets` * or `fgets` guarantees us that we we can read a byte regardless on * whether the sender sent end of line character(s) (CR, CRLF, LF). */ - if (linenoiseReadBytes(in_fd, buf + i, 1) != 1 || buf[i] == 'R') { + if (read_func(in_fd, buf + i, 1) != 1 || buf[i] == 'R') { /* If we couldn't read a byte from STDIN or if 'R' was received, * the transmission is finished. */ break; @@ -448,7 +429,7 @@ static int completeLine(struct linenoiseState *ls) { refreshLine(ls); } - nread = linenoiseReadBytes(in_fd, &c, 1); + nread = read_func(in_fd, &c, 1); if (nread <= 0) { freeCompletions(&lc); return -1; @@ -491,12 +472,6 @@ void linenoiseSetHintsCallback(linenoiseHintsCallback *fn) { hintsCallback = fn; } -void linenoiseSetInterruptReadingData(int fd, uint64_t data) -{ - intr_reading_fd = fd; - intr_reading_signal = data; -} - /* Register a function to free the hints returned by the hints callback * registered with linenoiseSetHintsCallback(). */ void linenoiseSetFreeHintsCallback(linenoiseFreeHintsCallback *fn) { @@ -942,7 +917,7 @@ static int linenoiseEdit(char *buf, size_t buflen, const char *prompt) * about 40ms (or even more) */ t1 = getMillis(); - int nread = linenoiseReadBytes(in_fd, &c, 1); + int nread = read_func(in_fd, &c, 1); if (nread <= 0) { return l.len; } @@ -1047,14 +1022,14 @@ static int linenoiseEdit(char *buf, size_t buflen, const char *prompt) case ESC: { /* escape sequence */ /* ESC [ sequences. */ char seq[3]; - int r = linenoiseReadBytes(in_fd, seq, 2); + int r = read_func(in_fd, seq, 2); if (r != 2) { return -1; } if (seq[0] == '[') { if (seq[1] >= '0' && seq[1] <= '9') { /* Extended escape, read additional byte. */ - r = linenoiseReadBytes(in_fd, seq + 2, 1); + r = read_func(in_fd, seq + 2, 1); if (r != 1) { return -1; } @@ -1115,11 +1090,13 @@ void linenoiseAllowEmpty(bool val) { } int linenoiseProbe(void) { - /* Switch to non-blocking mode */ + linenoiseSetReadCharacteristics(); + + /* Make sure we are in non blocking mode */ int stdin_fileno = fileno(stdin); - int flags = fcntl(stdin_fileno, F_GETFL); - flags |= O_NONBLOCK; - int res = fcntl(stdin_fileno, F_SETFL, flags); + int old_flags = fcntl(stdin_fileno, F_GETFL); + int new_flags = old_flags | O_NONBLOCK; + int res = fcntl(stdin_fileno, F_SETFL, new_flags); if (res != 0) { return -1; } @@ -1146,6 +1123,12 @@ int linenoiseProbe(void) { read_bytes += cb; } + /* Switch back to whatever mode we had before the function call */ + res = fcntl(stdin_fileno, F_SETFL, old_flags); + if (res != 0) { + return -1; + } + if (read_bytes < 4) { return -2; } @@ -1177,7 +1160,7 @@ static int linenoiseDumb(char* buf, size_t buflen, const char* prompt) { while (count < buflen) { - int nread = linenoiseReadBytes(in_fd, &c, 1); + int nread = read_func(in_fd, &c, 1); if (nread < 0) { return nread; } diff --git a/components/console/linenoise/linenoise.h b/components/console/linenoise/linenoise.h index 37cbf4c978..9cc82184e7 100644 --- a/components/console/linenoise/linenoise.h +++ b/components/console/linenoise/linenoise.h @@ -43,6 +43,8 @@ extern "C" { #endif +#include +#include #include #include #include @@ -58,7 +60,6 @@ typedef void(linenoiseFreeHintsCallback)(void *); void linenoiseSetCompletionCallback(linenoiseCompletionCallback *); void linenoiseSetHintsCallback(linenoiseHintsCallback *); void linenoiseSetFreeHintsCallback(linenoiseFreeHintsCallback *); -void linenoiseSetInterruptReadingData(int, uint64_t); void linenoiseAddCompletion(linenoiseCompletions *, const char *); int linenoiseProbe(void); @@ -77,6 +78,10 @@ void linenoisePrintKeyCodes(void); void linenoiseAllowEmpty(bool); int linenoiseSetMaxLineLen(size_t len); +typedef ssize_t (*linenoise_read_bytes_fn)(int, void*, size_t); +void linenoiseSetReadFunction(linenoise_read_bytes_fn read_fn); +void linenoiseSetReadCharacteristics(void); + #ifdef __cplusplus } #endif diff --git a/components/console/private_include/console_private.h b/components/console/private_include/console_private.h index 606c0c81d7..cbcca4d448 100644 --- a/components/console/private_include/console_private.h +++ b/components/console/private_include/console_private.h @@ -47,9 +47,9 @@ typedef struct { void esp_console_repl_task(void *args); esp_err_t esp_console_common_init(size_t max_cmdline_length, esp_console_repl_com_t *repl_com); -void esp_console_common_deinit(esp_console_repl_com_t *repl_com); +esp_err_t esp_console_common_deinit(esp_console_repl_com_t *repl_com); +esp_err_t esp_console_internal_set_event_fd(esp_console_repl_com_t *repl_com); esp_err_t esp_console_setup_prompt(const char *prompt, esp_console_repl_com_t *repl_com); esp_err_t esp_console_setup_history(const char *history_path, uint32_t max_history_len, esp_console_repl_com_t *repl_com); -esp_err_t esp_console_interrupt_reading(void); diff --git a/components/console/test_apps/console/main/test_console.c b/components/console/test_apps/console/main/test_console.c index 3111fc9760..71e96f12ea 100644 --- a/components/console/test_apps/console/main/test_console.c +++ b/components/console/test_apps/console/main/test_console.c @@ -12,6 +12,7 @@ #include "linenoise/linenoise.h" #include "freertos/FreeRTOS.h" #include "freertos/task.h" +#include "freertos/semphr.h" /* * NOTE: Most of these unit tests DO NOT work standalone. They require pytest to control @@ -24,6 +25,8 @@ * https://docs.espressif.com/projects/esp-idf/en/latest/esp32/contribute/esp-idf-tests-with-pytest.html. */ +extern void set_leak_threshold(int threshold); + typedef struct { const char *in; const char *out; @@ -123,12 +126,17 @@ TEST_CASE("esp console init/deinit with context test", "[console]") TEST_ESP_OK(esp_console_deinit()); } +static bool can_terminate = false; +static SemaphoreHandle_t s_test_console_mutex = NULL; +static StaticSemaphore_t s_test_console_mutex_buf; + /* handle 'quit' command */ static int do_cmd_quit(int argc, char **argv) { - printf("ByeBye\r\n"); - TEST_ESP_OK(s_repl->del(s_repl)); - linenoiseHistoryFree(); // Free up memory + TEST_ASSERT_NOT_NULL(s_test_console_mutex); + xSemaphoreTake(s_test_console_mutex, portMAX_DELAY); + can_terminate = true; + xSemaphoreGive(s_test_console_mutex); return 0; } @@ -144,6 +152,11 @@ static esp_console_cmd_t s_quit_cmd = { ran separately in test_console_repl */ TEST_CASE("esp console repl test", "[console][ignore]") { + set_leak_threshold(400); + + s_test_console_mutex = xSemaphoreCreateMutexStatic(&s_test_console_mutex_buf); + TEST_ASSERT_NOT_NULL(s_test_console_mutex); + esp_console_repl_config_t repl_config = ESP_CONSOLE_REPL_CONFIG_DEFAULT(); esp_console_dev_uart_config_t uart_config = ESP_CONSOLE_DEV_UART_CONFIG_DEFAULT(); TEST_ESP_OK(esp_console_new_repl_uart(&uart_config, &repl_config, &s_repl)); @@ -151,10 +164,29 @@ TEST_CASE("esp console repl test", "[console][ignore]") TEST_ESP_OK(esp_console_cmd_register(&s_quit_cmd)); TEST_ESP_OK(esp_console_start_repl(s_repl)); - vTaskDelay(pdMS_TO_TICKS(2000)); + + bool temp_can_terminate = false; + do { + xSemaphoreTake(s_test_console_mutex, portMAX_DELAY); + temp_can_terminate = can_terminate; + xSemaphoreGive(s_test_console_mutex); + } while (temp_can_terminate != true); + + /* Wait to make sure the command has finished its execution */ + vTaskDelay(pdMS_TO_TICKS(500)); + + esp_err_t ret = esp_console_stop_repl(s_repl); + TEST_ESP_OK(ret); + + xSemaphoreTake(s_test_console_mutex, portMAX_DELAY); + can_terminate = false; + xSemaphoreGive(s_test_console_mutex); + + vTaskDelay(pdMS_TO_TICKS(500)); + + printf("ByeBye\r\n"); } -extern void set_leak_threshold(int threshold); TEST_CASE("esp console repl deinit", "[console][ignore]") { set_leak_threshold(400); @@ -168,14 +200,12 @@ TEST_CASE("esp console repl deinit", "[console][ignore]") /* wait to make sure the task reaches linenoiseEdit function * and gets stuck in the select */ - vTaskDelay(pdMS_TO_TICKS(10)); + vTaskDelay(pdMS_TO_TICKS(500)); /* call the delete function, this returns only when the repl task terminated */ - const esp_err_t res = s_repl->del(s_repl); + const esp_err_t res = esp_console_stop_repl(s_repl); - /* wait to make sure the task reaches linenoiseEdit function - * and gets stuck in the select */ - vTaskDelay(pdMS_TO_TICKS(10)); + vTaskDelay(pdMS_TO_TICKS(500)); /* if this point is reached, the repl environment has been deleted successfully */ TEST_ASSERT(res == ESP_OK); diff --git a/docs/en/api-reference/system/console.rst b/docs/en/api-reference/system/console.rst index d12f42e106..dae47f4cdf 100644 --- a/docs/en/api-reference/system/console.rst +++ b/docs/en/api-reference/system/console.rst @@ -51,6 +51,14 @@ Linenoise library does not need explicit initialization. However, some configura Set maximum length of the line for linenoise library. Default length is 4096 bytes. The default value can be updated to optimize RAM memory usage. +- :cpp:func:`linenoiseSetReadFunction` + + Set the read function to be used by linenoise. + +- :cpp:func:`linenoiseSetReadCharacteristics` + + Set the characteristics of the read file descriptor (e.g., blocking or non blocking mode). The function has a weak definition in linenoise.c that can be overridden + by providing a strong definition of the function. Main Loop ^^^^^^^^^ diff --git a/examples/peripherals/usb/device/tusb_msc/main/tusb_msc_main.c b/examples/peripherals/usb/device/tusb_msc/main/tusb_msc_main.c index c40b04e1f1..e31aec7726 100644 --- a/examples/peripherals/usb/device/tusb_msc/main/tusb_msc_main.c +++ b/examples/peripherals/usb/device/tusb_msc/main/tusb_msc_main.c @@ -274,7 +274,7 @@ static int console_exit(int argc, char **argv) tinyusb_msc_storage_deinit(); tinyusb_driver_uninstall(); printf("Application Exit\n"); - repl->del(repl); + esp_console_stop_repl(repl); return 0; } diff --git a/examples/protocols/icmp_echo/main/echo_example_main.c b/examples/protocols/icmp_echo/main/echo_example_main.c index 4e54bde864..4dc036caf7 100644 --- a/examples/protocols/icmp_echo/main/echo_example_main.c +++ b/examples/protocols/icmp_echo/main/echo_example_main.c @@ -201,24 +201,6 @@ static void register_ping(void) static esp_console_repl_t *s_repl = NULL; -/* handle 'quit' command */ -static int do_cmd_quit(int argc, char **argv) -{ - printf("ByeBye\r\n"); - s_repl->del(s_repl); - return 0; -} - -static esp_err_t register_quit(void) -{ - esp_console_cmd_t command = { - .command = "quit", - .help = "Quit REPL environment", - .func = &do_cmd_quit - }; - return esp_console_cmd_register(&command); -} - void app_main(void) { ESP_ERROR_CHECK(nvs_flash_init()); @@ -256,8 +238,6 @@ void app_main(void) /* register command `ping` */ register_ping(); - /* register command `quit` */ - register_quit(); // start console REPL ESP_ERROR_CHECK(esp_console_start_repl(s_repl));