diff --git a/components/console/CMakeLists.txt b/components/console/CMakeLists.txt index 09eecbd209..a6c7632830 100644 --- a/components/console/CMakeLists.txt +++ b/components/console/CMakeLists.txt @@ -2,15 +2,17 @@ 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") +set(requires vfs) + if(${target} STREQUAL "linux") list(APPEND srcs "esp_console_repl_linux.c") - set(requires "") else() list(APPEND srcs "esp_console_repl_chip.c") - set(requires vfs esp_vfs_console) + list(APPEND requires esp_vfs_console) endif() set(argtable_srcs argtable3/arg_cmd.c diff --git a/components/console/commands.c b/components/console/commands.c index a07e743e12..b3bb082c6d 100644 --- a/components/console/commands.c +++ b/components/console/commands.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2016-2024 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2016-2025 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -376,7 +376,18 @@ esp_err_t esp_console_register_help_command(void) .argtable = &help_args }; return esp_console_cmd_register(&command); + return ESP_OK; } + +esp_err_t esp_console_deregister_help_command(void) +{ + free(help_args.help_cmd); + free(help_args.verbose_level); + free(help_args.end); + + return esp_console_cmd_deregister("help"); +} + esp_err_t esp_console_set_help_verbose_level(esp_console_help_verbose_level_e verbose_level) { /* legal verbose level sanity check */ diff --git a/components/console/esp_console.h b/components/console/esp_console.h index 2cebe9b9bf..c13234ce51 100644 --- a/components/console/esp_console.h +++ b/components/console/esp_console.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2016-2024 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2016-2025 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -331,6 +331,15 @@ const char *esp_console_get_hint(const char *buf, int *color, int *bold); */ esp_err_t esp_console_register_help_command(void); +/** + * @brief Deregister a 'help' command + * + * @return esp_err_t + * - ESP_OK on success + * - other on failure + */ +esp_err_t esp_console_deregister_help_command(void); + /** * @brief Set the verbose level for 'help' command * @@ -442,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 395aa272f7..ce5c61b4a7 100644 --- a/components/console/esp_console_common.c +++ b/components/console/esp_console_common.c @@ -1,16 +1,23 @@ /* - * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2024-2025 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ #include "sdkconfig.h" #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_IDF_TARGET_LINUX +#include +#endif + static const char *TAG = "console.common"; esp_err_t esp_console_setup_prompt(const char *prompt, esp_console_repl_com_t *repl_com) @@ -59,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; @@ -94,11 +106,38 @@ 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); +#if CONFIG_VFS_SUPPORT_SELECT + ret = esp_console_internal_set_event_fd(repl_com); + if (ret != ESP_OK) { + goto _exit; + } +#endif + return ESP_OK; _exit: return ret; } +__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(); + 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) { esp_err_t ret = ESP_OK; @@ -126,6 +165,11 @@ void esp_console_repl_task(void *args) * function is called. */ ulTaskNotifyTake(pdTRUE, portMAX_DELAY); + 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. */ @@ -187,6 +231,10 @@ void esp_console_repl_task(void *args) /* linenoise allocates line buffer on the heap, so need to free it */ linenoiseFree(line); } + + if (repl_com->state_mux != NULL) { + xSemaphoreGive(repl_com->state_mux); + } ESP_LOGD(TAG, "The End"); vTaskDelete(NULL); } diff --git a/components/console/esp_console_repl_chip.c b/components/console/esp_console_repl_chip.c index f0d2f6e002..912fa90a66 100644 --- a/components/console/esp_console_repl_chip.c +++ b/components/console/esp_console_repl_chip.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2016-2024 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2016-2025 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -12,18 +12,19 @@ #include "esp_err.h" #include "esp_log.h" #include "esp_console.h" -#include "esp_vfs_cdcacm.h" -#include "driver/usb_serial_jtag_vfs.h" #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "driver/uart.h" #include "driver/uart_vfs.h" #include "driver/usb_serial_jtag.h" +#include "driver/usb_serial_jtag_vfs.h" +#include "esp_private/usb_console.h" +#include "esp_vfs_cdcacm.h" #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 @@ -294,7 +295,12 @@ static esp_err_t esp_console_repl_uart_delete(esp_console_repl_t *repl) ret = ESP_ERR_INVALID_STATE; goto _exit; } - repl_com->state = CONSOLE_REPL_STATE_DEINIT; + + ret = esp_console_common_deinit(&uart_repl->repl_com); + if (ret != ESP_OK) { + goto _exit; + } + esp_console_deinit(); uart_vfs_dev_use_nonblocking(uart_repl->uart_channel); uart_driver_delete(uart_repl->uart_channel); @@ -316,7 +322,12 @@ static esp_err_t esp_console_repl_usb_cdc_delete(esp_console_repl_t *repl) ret = ESP_ERR_INVALID_STATE; goto _exit; } - repl_com->state = CONSOLE_REPL_STATE_DEINIT; + + ret = esp_console_common_deinit(&cdc_repl->repl_com); + if (ret != ESP_OK) { + goto _exit; + } + esp_console_deinit(); free(cdc_repl); _exit: @@ -336,7 +347,12 @@ static esp_err_t esp_console_repl_usb_serial_jtag_delete(esp_console_repl_t *rep ret = ESP_ERR_INVALID_STATE; goto _exit; } - repl_com->state = CONSOLE_REPL_STATE_DEINIT; + + ret = esp_console_common_deinit(&usb_serial_jtag_repl->repl_com); + if (ret != ESP_OK) { + goto _exit; + } + 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 b1f16db017..d4e4dc64b5 100644 --- a/components/console/linenoise/linenoise.c +++ b/components/console/linenoise/linenoise.c @@ -116,13 +116,12 @@ #include #include #include -#include #include #include #include #include #include -#include +#include #include #include "linenoise.h" @@ -239,6 +238,23 @@ static void flushWrite(void) { fsync(fileno(stdout)); } +static linenoise_read_bytes_fn read_func = NULL; +void linenoiseSetReadFunction(linenoise_read_bytes_fn read_fn) +{ + read_func = read_fn; +} + +__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 * and return it. On error -1 is returned, on success the position of the * cursor. */ @@ -273,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 (read(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; @@ -413,7 +429,7 @@ static int completeLine(struct linenoiseState *ls) { refreshLine(ls); } - nread = read(in_fd, &c, 1); + nread = read_func(in_fd, &c, 1); if (nread <= 0) { freeCompletions(&lc); return -1; @@ -890,8 +906,6 @@ static int linenoiseEdit(char *buf, size_t buflen, const char *prompt) while(1) { char c; - int nread; - char seq[3]; /** * To determine whether the user is pasting data or typing itself, we @@ -903,8 +917,10 @@ static int linenoiseEdit(char *buf, size_t buflen, const char *prompt) * about 40ms (or even more) */ t1 = getMillis(); - nread = read(in_fd, &c, 1); - if (nread <= 0) return l.len; + int nread = read_func(in_fd, &c, 1); + if (nread <= 0) { + return l.len; + } if ( (getMillis() - t1) < LINENOISE_PASTE_KEY_DELAY && c != ENTER) { /* Pasting data, insert characters without formatting. @@ -946,7 +962,7 @@ static int linenoiseEdit(char *buf, size_t buflen, const char *prompt) errno = EAGAIN; return -1; case BACKSPACE: /* backspace */ - case 8: /* ctrl-h */ + case CTRL_H: /* ctrl-h */ linenoiseEditBackspace(&l); break; case CTRL_D: /* ctrl-d, remove char at right of cursor, or if the @@ -980,18 +996,42 @@ static int linenoiseEdit(char *buf, size_t buflen, const char *prompt) case CTRL_N: /* ctrl-n */ linenoiseEditHistoryNext(&l, LINENOISE_HISTORY_NEXT); break; - case ESC: /* escape sequence */ - /* Read the next two bytes representing the escape sequence. */ - if (read(in_fd, seq, 2) < 2) { - break; - } - + case CTRL_U: /* Ctrl+u, delete the whole line. */ + buf[0] = '\0'; + l.pos = l.len = 0; + refreshLine(&l); + break; + case CTRL_K: /* Ctrl+k, delete from current to end of line. */ + buf[l.pos] = '\0'; + l.len = l.pos; + refreshLine(&l); + break; + case CTRL_A: /* Ctrl+a, go to the start of the line */ + linenoiseEditMoveHome(&l); + break; + case CTRL_E: /* ctrl+e, go to the end of the line */ + linenoiseEditMoveEnd(&l); + break; + case CTRL_L: /* ctrl+l, clear screen */ + linenoiseClearScreen(); + refreshLine(&l); + break; + case CTRL_W: /* ctrl+w, delete previous word */ + linenoiseEditDeletePrevWord(&l); + break; + case ESC: { /* escape sequence */ /* ESC [ sequences. */ + char seq[3]; + 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. */ - if (read(in_fd, seq+2, 1) == -1) { - break; + r = read_func(in_fd, seq + 2, 1); + if (r != 1) { + return -1; } if (seq[2] == '~') { switch(seq[1]) { @@ -1023,7 +1063,6 @@ static int linenoiseEdit(char *buf, size_t buflen, const char *prompt) } } } - /* ESC O sequences. */ else if (seq[0] == 'O') { switch(seq[1]) { @@ -1036,32 +1075,10 @@ static int linenoiseEdit(char *buf, size_t buflen, const char *prompt) } } break; + } default: if (linenoiseEditInsert(&l,c)) return -1; break; - case CTRL_U: /* Ctrl+u, delete the whole line. */ - buf[0] = '\0'; - l.pos = l.len = 0; - refreshLine(&l); - break; - case CTRL_K: /* Ctrl+k, delete from current to end of line. */ - buf[l.pos] = '\0'; - l.len = l.pos; - refreshLine(&l); - break; - case CTRL_A: /* Ctrl+a, go to the start of the line */ - linenoiseEditMoveHome(&l); - break; - case CTRL_E: /* ctrl+e, go to the end of the line */ - linenoiseEditMoveEnd(&l); - break; - case CTRL_L: /* ctrl+l, clear screen */ - linenoiseClearScreen(); - refreshLine(&l); - break; - case CTRL_W: /* ctrl+w, delete previous word */ - linenoiseEditDeletePrevWord(&l); - break; } flushWrite(); } @@ -1073,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; } @@ -1103,12 +1122,13 @@ int linenoiseProbe(void) { } read_bytes += cb; } - /* Restore old mode */ - flags &= ~O_NONBLOCK; - res = fcntl(stdin_fileno, F_SETFL, flags); + + /* 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; } @@ -1133,9 +1153,17 @@ static int linenoiseDumb(char* buf, size_t buflen, const char* prompt) { /* dumb terminal, fall back to fgets */ fputs(prompt, stdout); flushWrite(); + size_t count = 0; + const int in_fd = fileno(stdin); + char c = 'c'; + while (count < buflen) { - int c = fgetc(stdin); + + int nread = read_func(in_fd, &c, 1); + if (nread < 0) { + return nread; + } if (c == '\n') { break; } else if (c == BACKSPACE || c == CTRL_H) { @@ -1220,6 +1248,7 @@ void linenoiseHistoryFree(void) { free(history); } history = NULL; + history_len = 0; } /* This is the API call to add a new entry in the linenoise history. diff --git a/components/console/linenoise/linenoise.h b/components/console/linenoise/linenoise.h index 98876104aa..9cc82184e7 100644 --- a/components/console/linenoise/linenoise.h +++ b/components/console/linenoise/linenoise.h @@ -43,8 +43,11 @@ extern "C" { #endif +#include +#include #include #include +#include typedef struct linenoiseCompletions { size_t len; @@ -75,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 c21144efcb..cbcca4d448 100644 --- a/components/console/private_include/console_private.h +++ b/components/console/private_include/console_private.h @@ -11,6 +11,7 @@ #include "sdkconfig.h" #include "freertos/FreeRTOS.h" #include "freertos/task.h" +#include "freertos/semphr.h" #include "esp_console.h" #define CONSOLE_PROMPT_MAX_LEN (32) @@ -32,6 +33,7 @@ typedef struct { esp_console_repl_t repl_core; // base class char prompt[CONSOLE_PROMPT_MAX_LEN]; // Prompt to be printed before each line repl_state_t state; + SemaphoreHandle_t state_mux; const char *history_save_path; TaskHandle_t task_hdl; // REPL task handle size_t max_cmdline_length; // Maximum length of a command line. If 0, default value will be used. @@ -45,6 +47,8 @@ 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); +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, diff --git a/components/console/test_apps/console/main/test_app_main.c b/components/console/test_apps/console/main/test_app_main.c index 258953bd47..2336f50da5 100644 --- a/components/console/test_apps/console/main/test_app_main.c +++ b/components/console/test_apps/console/main/test_app_main.c @@ -10,7 +10,12 @@ #include // Some resources are lazy allocated (newlib locks) in the console code, the threshold is left for that case -#define TEST_MEMORY_LEAK_THRESHOLD (150) +#define TEST_MEMORY_LEAK_THRESHOLD_DEFAULT (150) +static int leak_threshold = TEST_MEMORY_LEAK_THRESHOLD_DEFAULT; +void set_leak_threshold(int threshold) +{ + leak_threshold = threshold; +} void setUp(void) { @@ -19,7 +24,8 @@ void setUp(void) void tearDown(void) { - unity_utils_evaluate_leaks_direct(TEST_MEMORY_LEAK_THRESHOLD); + unity_utils_evaluate_leaks_direct(leak_threshold); + leak_threshold = TEST_MEMORY_LEAK_THRESHOLD_DEFAULT; } void app_main(void) diff --git a/components/console/test_apps/console/main/test_console.c b/components/console/test_apps/console/main/test_console.c index 1e56db049c..71e96f12ea 100644 --- a/components/console/test_apps/console/main/test_console.c +++ b/components/console/test_apps/console/main/test_console.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2022-2024 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2022-2025 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -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,13 +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"); - 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; } @@ -145,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)); @@ -152,7 +164,51 @@ 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"); +} + +TEST_CASE("esp console repl deinit", "[console][ignore]") +{ + set_leak_threshold(400); + + 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)); + + /* start the repl task */ + TEST_ESP_OK(esp_console_start_repl(s_repl)); + + /* wait to make sure the task reaches linenoiseEdit function + * and gets stuck in the select */ + vTaskDelay(pdMS_TO_TICKS(500)); + + /* call the delete function, this returns only when the repl task terminated */ + const esp_err_t res = esp_console_stop_repl(s_repl); + + vTaskDelay(pdMS_TO_TICKS(500)); + + /* if this point is reached, the repl environment has been deleted successfully */ + TEST_ASSERT(res == ESP_OK); } static const esp_console_cmd_t cmd_a = { diff --git a/components/console/test_apps/console/pytest_console.py b/components/console/test_apps/console/pytest_console.py index cac89e155d..df7901ff36 100644 --- a/components/console/test_apps/console/pytest_console.py +++ b/components/console/test_apps/console/pytest_console.py @@ -12,6 +12,11 @@ def do_test_quit(dut: Dut) -> None: dut.confirm_write('quit', expect_str='ByeBye') +def do_test_repl_deinit(dut: Dut) -> None: + dut.expect_exact('Press ENTER to see the list of tests') + dut.confirm_write('"esp console repl deinit"', expect_str='esp>') + + def do_test_help_generic(dut: Dut, registration_order: str) -> None: dut.expect_exact('Press ENTER to see the list of tests') dut.confirm_write('"esp console help command - {} registration"'.format(registration_order), expect_str='esp>') diff --git a/components/vfs/CMakeLists.txt b/components/vfs/CMakeLists.txt index 0d47c4dc97..51d7a21dd7 100644 --- a/components/vfs/CMakeLists.txt +++ b/components/vfs/CMakeLists.txt @@ -1,7 +1,10 @@ idf_build_get_property(target IDF_TARGET) +# On Linux, we only support a few features, hence this simple component registration if(${target} STREQUAL "linux") - return() # This component is not supported by the POSIX/Linux simulator + idf_component_register(SRCS "vfs_eventfd_linux.c" + INCLUDE_DIRS "include") + return() endif() list(APPEND sources "vfs.c" diff --git a/components/vfs/vfs_eventfd_linux.c b/components/vfs/vfs_eventfd_linux.c new file mode 100644 index 0000000000..de00b10490 --- /dev/null +++ b/components/vfs/vfs_eventfd_linux.c @@ -0,0 +1,19 @@ +/* + * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "esp_vfs_eventfd.h" +#include + +esp_err_t esp_vfs_eventfd_register(const esp_vfs_eventfd_config_t *config) +{ + (void)config; + return ESP_OK; +} + +esp_err_t esp_vfs_eventfd_unregister(void) +{ + return 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));