From a30546cd2418eb4bb3fe5f550a545944125013d8 Mon Sep 17 00:00:00 2001 From: Jakob Hasse Date: Fri, 17 Nov 2023 18:44:49 +0800 Subject: [PATCH] feat(console): Refactored code to support Linux target --- components/console/CMakeLists.txt | 26 ++- components/console/commands.c | 4 + components/console/esp_console_common.c | 194 ++++++++++++++++ ...console_repl.c => esp_console_repl_chip.c} | 216 +----------------- components/console/esp_console_repl_linux.c | 177 ++++++++++++++ components/console/linenoise/linenoise.c | 7 +- .../console/private_include/console_private.h | 51 +++++ .../console/test_apps/.build-test-rules.yml | 6 + .../console/test_apps/console/README.md | 4 +- .../test_apps/console/main/test_app_main.c | 26 +-- .../test_apps/console/main/test_console.c | 13 +- .../test_apps/console/pytest_console.py | 12 +- .../console/sdkconfig.defaults.linux | 1 + 13 files changed, 492 insertions(+), 245 deletions(-) create mode 100644 components/console/esp_console_common.c rename components/console/{esp_console_repl.c => esp_console_repl_chip.c} (62%) create mode 100644 components/console/esp_console_repl_linux.c create mode 100644 components/console/private_include/console_private.h create mode 100644 components/console/test_apps/.build-test-rules.yml create mode 100644 components/console/test_apps/console/sdkconfig.defaults.linux diff --git a/components/console/CMakeLists.txt b/components/console/CMakeLists.txt index ab3f99cf26..9f8aaa5397 100644 --- a/components/console/CMakeLists.txt +++ b/components/console/CMakeLists.txt @@ -1,8 +1,14 @@ idf_build_get_property(target IDF_TARGET) +set(srcs "commands.c" + "esp_console_common.c" + "split_argv.c" + "linenoise/linenoise.c") + if(${target} STREQUAL "linux") - return() # This component is currently not supported by the POSIX/Linux simulator, but we may support it in the - # future (TODO: IDF-8103) + list(APPEND srcs "esp_console_repl_linux.c") +else() + list(APPEND srcs "esp_console_repl_chip.c") endif() set(argtable_srcs argtable3/arg_cmd.c @@ -21,13 +27,21 @@ set(argtable_srcs argtable3/arg_cmd.c argtable3/argtable3.c) -idf_component_register(SRCS "commands.c" - "esp_console_repl.c" - "split_argv.c" - "linenoise/linenoise.c" +idf_component_register(SRCS ${srcs} ${argtable_srcs} INCLUDE_DIRS ${CMAKE_CURRENT_SOURCE_DIR} + PRIV_INCLUDE_DIRS private_include REQUIRES vfs PRIV_REQUIRES esp_driver_uart esp_driver_usb_serial_jtag ) + +if(${target} STREQUAL "linux") + # link bsd library for strlcpy + find_library(LIB_BSD bsd) + if(LIB_BSD) + target_link_libraries(${COMPONENT_LIB} PRIVATE ${LIB_BSD}) + elseif(NOT CMAKE_HOST_SYSTEM_NAME STREQUAL "Darwin") + message(WARNING "Missing LIBBSD library. Install libbsd-dev package and/or check linker directories.") + endif() +endif() diff --git a/components/console/commands.c b/components/console/commands.c index a91e4fc337..7f5cd39c07 100644 --- a/components/console/commands.c +++ b/components/console/commands.c @@ -6,6 +6,10 @@ #include #include +#if __has_include() +// for strlcpy +#include +#endif #include #include #include "esp_heap_caps.h" diff --git a/components/console/esp_console_common.c b/components/console/esp_console_common.c new file mode 100644 index 0000000000..5c1a95ef04 --- /dev/null +++ b/components/console/esp_console_common.c @@ -0,0 +1,194 @@ +/* + * SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "sdkconfig.h" +#include "esp_console.h" +#include "console_private.h" +#include "esp_log.h" +#include "linenoise/linenoise.h" +#if CONFIG_IDF_TARGET_LINUX +#include "esp_linux_helper.h" // __containerof +#endif + +static const char *TAG = "console.common"; + +esp_err_t esp_console_setup_prompt(const char *prompt, esp_console_repl_com_t *repl_com) +{ + /* set command line prompt */ + const char *prompt_temp = "esp>"; + if (prompt) { + prompt_temp = prompt; + } + snprintf(repl_com->prompt, CONSOLE_PROMPT_MAX_LEN - 1, LOG_COLOR_I "%s " LOG_RESET_COLOR, prompt_temp); + + /* Figure out if the terminal supports escape sequences */ + int probe_status = linenoiseProbe(); + if (probe_status) { + /* zero indicates success */ + linenoiseSetDumbMode(1); +#if CONFIG_LOG_COLORS + /* Since the terminal doesn't support escape sequences, + * don't use color codes in the s_prompt. + */ + snprintf(repl_com->prompt, CONSOLE_PROMPT_MAX_LEN - 1, "%s ", prompt_temp); +#endif //CONFIG_LOG_COLORS + } + + return ESP_OK; +} + +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 ret = ESP_OK; + + repl_com->history_save_path = history_path; + if (history_path) { + /* Load command history from filesystem */ + linenoiseHistoryLoad(history_path); + } + + /* Set command history size */ + if (linenoiseHistorySetMaxLen(max_history_len) != 1) { + ESP_LOGE(TAG, "set max history length to %"PRIu32" failed", max_history_len); + ret = ESP_FAIL; + goto _exit; + } + return ESP_OK; +_exit: + return ret; +} + +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; + /* Initialize the console */ + esp_console_config_t console_config = ESP_CONSOLE_CONFIG_DEFAULT(); + repl_com->max_cmdline_length = console_config.max_cmdline_length; + /* Replace the default command line length if passed as a parameter */ + if (max_cmdline_length != 0) { + console_config.max_cmdline_length = max_cmdline_length; + repl_com->max_cmdline_length = max_cmdline_length; + } + +#if CONFIG_LOG_COLORS + console_config.hint_color = atoi(LOG_COLOR_CYAN); +#else + console_config.hint_color = -1; +#endif + ret = esp_console_init(&console_config); + if (ret != ESP_OK) { + goto _exit; + } + + ret = esp_console_register_help_command(); + if (ret != ESP_OK) { + goto _exit; + } + + /* Configure linenoise line completion library */ + /* Enable multiline editing. If not set, long commands will scroll within single line */ + linenoiseSetMultiLine(1); + + /* Tell linenoise where to get command completions and hints */ + linenoiseSetCompletionCallback(&esp_console_get_completion); + linenoiseSetHintsCallback((linenoiseHintsCallback *)&esp_console_get_hint); + + return ESP_OK; +_exit: + return ret; +} + +esp_err_t esp_console_start_repl(esp_console_repl_t *repl) +{ + esp_err_t ret = ESP_OK; + esp_console_repl_com_t *repl_com = __containerof(repl, esp_console_repl_com_t, repl_core); + // check if already initialized + if (repl_com->state != CONSOLE_REPL_STATE_INIT) { + ret = ESP_ERR_INVALID_STATE; + goto _exit; + } + + repl_com->state = CONSOLE_REPL_STATE_START; + xTaskNotifyGive(repl_com->task_hdl); + return ESP_OK; +_exit: + return ret; +} + +void esp_console_repl_task(void *args) +{ + esp_console_repl_universal_t *repl_conf = (esp_console_repl_universal_t *) args; + esp_console_repl_com_t *repl_com = &repl_conf->repl_com; + const int uart_channel = repl_conf->uart_channel; + + /* Waiting for task notify. This happens when `esp_console_start_repl()` + * function is called. */ + ulTaskNotifyTake(pdTRUE, portMAX_DELAY); + + /* 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. + */ + if (uart_channel != CONFIG_ESP_CONSOLE_UART_NUM) { + char path[CONSOLE_PATH_MAX_LEN] = { 0 }; + snprintf(path, CONSOLE_PATH_MAX_LEN, "/dev/uart/%d", uart_channel); + + stdin = fopen(path, "r"); + stdout = fopen(path, "w"); + stderr = stdout; + } + + /* Disable buffering on stdin of the current task. + * If the console is ran on a different UART than the default one, + * buffering shall only be disabled for the current one. */ + setvbuf(stdin, NULL, _IONBF, 0); + + /* This message shall be printed here and not earlier as the stdout + * has just been set above. */ + printf("\r\n" + "Type 'help' to get the list of commands.\r\n" + "Use UP/DOWN arrows to navigate through command history.\r\n" + "Press TAB when typing command name to auto-complete.\r\n"); + + if (linenoiseIsDumbMode()) { + printf("\r\n" + "Your terminal application does not support escape sequences.\n\n" + "Line editing and history features are disabled.\n\n" + "On Windows, try using Putty instead.\r\n"); + } + + linenoiseSetMaxLineLen(repl_com->max_cmdline_length); + while (repl_com->state == CONSOLE_REPL_STATE_START) { + char *line = linenoise(repl_com->prompt); + if (line == NULL) { + ESP_LOGD(TAG, "empty line"); + /* Ignore empty lines */ + continue; + } + /* Add the command to the history */ + linenoiseHistoryAdd(line); + /* Save command history to filesystem */ + if (repl_com->history_save_path) { + linenoiseHistorySave(repl_com->history_save_path); + } + + /* Try to run the command */ + int ret; + esp_err_t err = esp_console_run(line, &ret); + if (err == ESP_ERR_NOT_FOUND) { + printf("Unrecognized command\n"); + } else if (err == ESP_ERR_INVALID_ARG) { + // command was empty + } else if (err == ESP_OK && ret != ESP_OK) { + printf("Command returned non-zero error code: 0x%x (%s)\n", ret, esp_err_to_name(ret)); + } else if (err != ESP_OK) { + printf("Internal error: %s\n", esp_err_to_name(err)); + } + /* linenoise allocates line buffer on the heap, so need to free it */ + linenoiseFree(line); + } + ESP_LOGD(TAG, "The End"); + vTaskDelete(NULL); +} diff --git a/components/console/esp_console_repl.c b/components/console/esp_console_repl_chip.c similarity index 62% rename from components/console/esp_console_repl.c rename to components/console/esp_console_repl_chip.c index 07bb6569b8..199669ae7c 100644 --- a/components/console/esp_console_repl.c +++ b/components/console/esp_console_repl_chip.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2016-2023 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2016-2024 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -19,34 +19,11 @@ #include "driver/uart.h" #include "driver/uart_vfs.h" #include "driver/usb_serial_jtag.h" -#include "linenoise/linenoise.h" + +#include "console_private.h" static const char *TAG = "console.repl"; -#define CONSOLE_PROMPT_MAX_LEN (32) -#define CONSOLE_PATH_MAX_LEN (ESP_VFS_PATH_MAX) - -typedef enum { - CONSOLE_REPL_STATE_DEINIT, - CONSOLE_REPL_STATE_INIT, - CONSOLE_REPL_STATE_START, -} repl_state_t; - -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; - 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. -} esp_console_repl_com_t; - -typedef struct { - esp_console_repl_com_t repl_com; // base class - int uart_channel; // uart channel number -} esp_console_repl_universal_t; - -static void esp_console_repl_task(void *args); #if CONFIG_ESP_CONSOLE_UART_DEFAULT || CONFIG_ESP_CONSOLE_UART_CUSTOM static esp_err_t esp_console_repl_uart_delete(esp_console_repl_t *repl); #endif // CONFIG_ESP_CONSOLE_UART_DEFAULT || CONFIG_ESP_CONSOLE_UART_CUSTOM @@ -56,16 +33,13 @@ static esp_err_t esp_console_repl_usb_cdc_delete(esp_console_repl_t *repl); #if CONFIG_ESP_CONSOLE_USB_SERIAL_JTAG static esp_err_t esp_console_repl_usb_serial_jtag_delete(esp_console_repl_t *repl); #endif //CONFIG_ESP_CONSOLE_USB_SERIAL_JTAG -static esp_err_t esp_console_common_init(size_t max_cmdline_length, esp_console_repl_com_t *repl_com); -static esp_err_t esp_console_setup_prompt(const char *prompt, esp_console_repl_com_t *repl_com); -static esp_err_t esp_console_setup_history(const char *history_path, uint32_t max_history_len, esp_console_repl_com_t *repl_com); #if CONFIG_ESP_CONSOLE_USB_CDC esp_err_t esp_console_new_repl_usb_cdc(const esp_console_dev_usb_cdc_config_t *dev_config, const esp_console_repl_config_t *repl_config, esp_console_repl_t **ret_repl) { esp_err_t ret = ESP_OK; esp_console_repl_universal_t *cdc_repl = NULL; - if (!repl_config | !dev_config | !ret_repl) { + if (!repl_config || !dev_config || !ret_repl) { ret = ESP_ERR_INVALID_ARG; goto _exit; } @@ -130,7 +104,7 @@ _exit: esp_err_t esp_console_new_repl_usb_serial_jtag(const esp_console_dev_usb_serial_jtag_config_t *dev_config, const esp_console_repl_config_t *repl_config, esp_console_repl_t **ret_repl) { esp_console_repl_universal_t *usb_serial_jtag_repl = NULL; - if (!repl_config | !dev_config | !ret_repl) { + if (!repl_config || !dev_config || !ret_repl) { return ESP_ERR_INVALID_ARG; } @@ -208,7 +182,7 @@ esp_err_t esp_console_new_repl_uart(const esp_console_dev_uart_config_t *dev_con { esp_err_t ret = ESP_OK; esp_console_repl_universal_t *uart_repl = NULL; - if (!repl_config | !dev_config | !ret_repl) { + if (!repl_config || !dev_config || !ret_repl) { ret = ESP_ERR_INVALID_ARG; goto _exit; } @@ -306,109 +280,6 @@ _exit: } #endif // CONFIG_ESP_CONSOLE_UART_DEFAULT || CONFIG_ESP_CONSOLE_UART_CUSTOM -esp_err_t esp_console_start_repl(esp_console_repl_t *repl) -{ - esp_err_t ret = ESP_OK; - esp_console_repl_com_t *repl_com = __containerof(repl, esp_console_repl_com_t, repl_core); - // check if already initialized - if (repl_com->state != CONSOLE_REPL_STATE_INIT) { - ret = ESP_ERR_INVALID_STATE; - goto _exit; - } - - repl_com->state = CONSOLE_REPL_STATE_START; - xTaskNotifyGive(repl_com->task_hdl); - return ESP_OK; -_exit: - return ret; -} - -static esp_err_t esp_console_setup_prompt(const char *prompt, esp_console_repl_com_t *repl_com) -{ - /* set command line prompt */ - const char *prompt_temp = "esp>"; - if (prompt) { - prompt_temp = prompt; - } - snprintf(repl_com->prompt, CONSOLE_PROMPT_MAX_LEN - 1, LOG_COLOR_I "%s " LOG_RESET_COLOR, prompt_temp); - - /* Figure out if the terminal supports escape sequences */ - int probe_status = linenoiseProbe(); - if (probe_status) { - /* zero indicates success */ - linenoiseSetDumbMode(1); -#if CONFIG_LOG_COLORS - /* Since the terminal doesn't support escape sequences, - * don't use color codes in the s_prompt. - */ - snprintf(repl_com->prompt, CONSOLE_PROMPT_MAX_LEN - 1, "%s ", prompt_temp); -#endif //CONFIG_LOG_COLORS - } - - return ESP_OK; -} - -static 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 ret = ESP_OK; - - repl_com->history_save_path = history_path; - if (history_path) { - /* Load command history from filesystem */ - linenoiseHistoryLoad(history_path); - } - - /* Set command history size */ - if (linenoiseHistorySetMaxLen(max_history_len) != 1) { - ESP_LOGE(TAG, "set max history length to %"PRIu32" failed", max_history_len); - ret = ESP_FAIL; - goto _exit; - } - return ESP_OK; -_exit: - return ret; -} - -static 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; - /* Initialize the console */ - esp_console_config_t console_config = ESP_CONSOLE_CONFIG_DEFAULT(); - repl_com->max_cmdline_length = console_config.max_cmdline_length; - /* Replace the default command line length if passed as a parameter */ - if (max_cmdline_length != 0) { - console_config.max_cmdline_length = max_cmdline_length; - repl_com->max_cmdline_length = max_cmdline_length; - } - -#if CONFIG_LOG_COLORS - console_config.hint_color = atoi(LOG_COLOR_CYAN); -#else - console_config.hint_color = -1; -#endif - ret = esp_console_init(&console_config); - if (ret != ESP_OK) { - goto _exit; - } - - ret = esp_console_register_help_command(); - if (ret != ESP_OK) { - goto _exit; - } - - /* Configure linenoise line completion library */ - /* Enable multiline editing. If not set, long commands will scroll within single line */ - linenoiseSetMultiLine(1); - - /* Tell linenoise where to get command completions and hints */ - linenoiseSetCompletionCallback(&esp_console_get_completion); - linenoiseSetHintsCallback((linenoiseHintsCallback *)&esp_console_get_hint); - - return ESP_OK; -_exit: - return ret; -} - #if CONFIG_ESP_CONSOLE_UART_DEFAULT || CONFIG_ESP_CONSOLE_UART_CUSTOM static esp_err_t esp_console_repl_uart_delete(esp_console_repl_t *repl) { @@ -472,78 +343,3 @@ _exit: return ret; } #endif // CONFIG_ESP_CONSOLE_USB_SERIAL_JTAG - -static void esp_console_repl_task(void *args) -{ - esp_console_repl_universal_t *repl_conf = (esp_console_repl_universal_t *) args; - esp_console_repl_com_t *repl_com = &repl_conf->repl_com; - const int uart_channel = repl_conf->uart_channel; - - /* Waiting for task notify. This happens when `esp_console_start_repl()` - * function is called. */ - ulTaskNotifyTake(pdTRUE, portMAX_DELAY); - - /* 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. - */ - if (uart_channel != CONFIG_ESP_CONSOLE_UART_NUM) { - char path[CONSOLE_PATH_MAX_LEN] = { 0 }; - snprintf(path, CONSOLE_PATH_MAX_LEN, "/dev/uart/%d", uart_channel); - - stdin = fopen(path, "r"); - stdout = fopen(path, "w"); - stderr = stdout; - } - - /* Disable buffering on stdin of the current task. - * If the console is ran on a different UART than the default one, - * buffering shall only be disabled for the current one. */ - setvbuf(stdin, NULL, _IONBF, 0); - - /* This message shall be printed here and not earlier as the stdout - * has just been set above. */ - printf("\r\n" - "Type 'help' to get the list of commands.\r\n" - "Use UP/DOWN arrows to navigate through command history.\r\n" - "Press TAB when typing command name to auto-complete.\r\n"); - - if (linenoiseIsDumbMode()) { - printf("\r\n" - "Your terminal application does not support escape sequences.\n\n" - "Line editing and history features are disabled.\n\n" - "On Windows, try using Putty instead.\r\n"); - } - - linenoiseSetMaxLineLen(repl_com->max_cmdline_length); - while (repl_com->state == CONSOLE_REPL_STATE_START) { - char *line = linenoise(repl_com->prompt); - if (line == NULL) { - ESP_LOGD(TAG, "empty line"); - /* Ignore empty lines */ - continue; - } - /* Add the command to the history */ - linenoiseHistoryAdd(line); - /* Save command history to filesystem */ - if (repl_com->history_save_path) { - linenoiseHistorySave(repl_com->history_save_path); - } - - /* Try to run the command */ - int ret; - esp_err_t err = esp_console_run(line, &ret); - if (err == ESP_ERR_NOT_FOUND) { - printf("Unrecognized command\n"); - } else if (err == ESP_ERR_INVALID_ARG) { - // command was empty - } else if (err == ESP_OK && ret != ESP_OK) { - printf("Command returned non-zero error code: 0x%x (%s)\n", ret, esp_err_to_name(ret)); - } else if (err != ESP_OK) { - printf("Internal error: %s\n", esp_err_to_name(err)); - } - /* linenoise allocates line buffer on the heap, so need to free it */ - linenoiseFree(line); - } - ESP_LOGD(TAG, "The End"); - vTaskDelete(NULL); -} diff --git a/components/console/esp_console_repl_linux.c b/components/console/esp_console_repl_linux.c new file mode 100644 index 0000000000..7d616e1129 --- /dev/null +++ b/components/console/esp_console_repl_linux.c @@ -0,0 +1,177 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "sdkconfig.h" +#include "esp_log.h" +#include "esp_linux_helper.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" + +#include "linenoise/linenoise.h" +#include "esp_console.h" +#include "console_private.h" + +static const char *TAG = "console.repl"; + +static struct termios s_orig_termios; + +/** + * This function restores the original terminal settings. + */ +static void disable_raw_mode(void) +{ + assert(tcsetattr(STDIN_FILENO, TCSAFLUSH, &s_orig_termios) == 0); +} + +/** + * Depending on if the input is a terminal or a file or pipe, we need to apply different + * settings to avoid additional processing or buffering getting into our way. + */ +static void prepare_input_stream(void) +{ + // Set stdin to unbuffered + setvbuf(stdin, NULL, _IONBF, 0); + + const int stdin_fileno = fileno(stdin); + + if (isatty(stdin_fileno)) { + // Use Termios driver to activate CR-NL translation and deactivate echo and canonical mode + assert(tcgetattr(stdin_fileno, &s_orig_termios) == 0); + struct termios raw = s_orig_termios; + raw.c_iflag |= ICRNL; // we translate to NL because linenoise expects NL + raw.c_lflag &= ~(ECHO | ICANON); // turn off echo and cononical mode + assert(tcsetattr(stdin_fileno, TCSAFLUSH, &raw) == 0); + + // Make sure user does not end up with a broken terminal + assert(atexit(disable_raw_mode) == 0); + } else { + // Flush input + assert(fflush(stdin) == 0); + } +} + +static esp_err_t esp_console_repl_linux_delete(esp_console_repl_t *repl) +{ + esp_err_t ret = ESP_OK; + esp_console_repl_com_t *repl_com = __containerof(repl, esp_console_repl_com_t, repl_core); + esp_console_repl_universal_t *linux_repl = __containerof(repl_com, esp_console_repl_universal_t, repl_com); + // check if already de-initialized + if (repl_com->state == CONSOLE_REPL_STATE_DEINIT) { + ESP_LOGE(TAG, "already de-initialized"); + ret = ESP_ERR_INVALID_STATE; + goto _exit; + } + repl_com->state = CONSOLE_REPL_STATE_DEINIT; + esp_console_deinit(); + + free(linux_repl); +_exit: + return ret; +} + +static esp_err_t esp_console_new_repl_linux(const esp_console_repl_config_t *repl_config, esp_console_repl_t **ret_repl) +{ + esp_console_repl_universal_t *linux_repl = NULL; + if (!repl_config || !ret_repl) { + return ESP_ERR_INVALID_ARG; + } + + esp_err_t ret = ESP_OK; + // allocate memory for console REPL context + linux_repl = calloc(1, sizeof(esp_console_repl_universal_t)); + if (!linux_repl) { + ret = ESP_ERR_NO_MEM; + goto _exit; + } + + /* Enable blocking mode on stdin and stdout */ + fcntl(fileno(stdout), F_SETFL, 0); + fcntl(fileno(stdin), F_SETFL, 0); + + // initialize console , common part + ret = esp_console_common_init(repl_config->max_cmdline_length, &linux_repl->repl_com); + if (ret != ESP_OK) { + goto _exit; + } + + // setup history + ret = esp_console_setup_history(repl_config->history_save_path, repl_config->max_history_len, &linux_repl->repl_com); + if (ret != ESP_OK) { + goto _exit; + } + + // Make sure the setup works on Linux without buffering or additional processing + prepare_input_stream(); + + // setup prompt + esp_console_setup_prompt(repl_config->prompt, &linux_repl->repl_com); + + /* Fill the structure here as it will be used directly by the created task. */ + linux_repl->uart_channel = CONFIG_ESP_CONSOLE_UART_NUM; + linux_repl->repl_com.state = CONSOLE_REPL_STATE_INIT; + linux_repl->repl_com.repl_core.del = esp_console_repl_linux_delete; + + /* spawn a single thread to run REPL */ + if (xTaskCreate(esp_console_repl_task, "console_repl", repl_config->task_stack_size, + linux_repl, repl_config->task_priority, &linux_repl->repl_com.task_hdl) != pdTRUE) { + ret = ESP_FAIL; + goto _exit; + } + + *ret_repl = &linux_repl->repl_com.repl_core; + return ESP_OK; +_exit: + if (linux_repl) { + esp_console_deinit(); + free(linux_repl); + } + if (ret_repl) { + *ret_repl = NULL; + } + return ret; +} + +#if CONFIG_ESP_CONSOLE_UART_DEFAULT || CONFIG_ESP_CONSOLE_UART_CUSTOM +esp_err_t esp_console_new_repl_uart(const esp_console_dev_uart_config_t *dev_config, const esp_console_repl_config_t *repl_config, esp_console_repl_t **ret_repl) +{ + if (!dev_config) { + return ESP_ERR_INVALID_ARG; + } + + return esp_console_new_repl_linux(repl_config, ret_repl); +} +#endif // CONFIG_ESP_CONSOLE_UART_DEFAULT || CONFIG_ESP_CONSOLE_UART_CUSTOM + +#if CONFIG_ESP_CONSOLE_USB_CDC +esp_err_t esp_console_new_repl_usb_cdc(const esp_console_dev_usb_cdc_config_t *dev_config, const esp_console_repl_config_t *repl_config, esp_console_repl_t **ret_repl) +{ + if (!dev_config) { + return ESP_ERR_INVALID_ARG; + } + + return esp_console_new_repl_linux(repl_config, ret_repl); +} +#endif // CONFIG_ESP_CONSOLE_USB_CDC + +#if CONFIG_ESP_CONSOLE_USB_SERIAL_JTAG +esp_err_t esp_console_new_repl_usb_serial_jtag(const esp_console_dev_usb_serial_jtag_config_t *dev_config, const esp_console_repl_config_t *repl_config, esp_console_repl_t **ret_repl) +{ + if (!dev_config) { + return ESP_ERR_INVALID_ARG; + } + + return esp_console_new_repl_linux(repl_config, ret_repl); +} +#endif // CONFIG_ESP_CONSOLE_USB_SERIAL_JTAG diff --git a/components/console/linenoise/linenoise.c b/components/console/linenoise/linenoise.c index 221c86c20e..92fcab393d 100644 --- a/components/console/linenoise/linenoise.c +++ b/components/console/linenoise/linenoise.c @@ -105,6 +105,7 @@ #include #include +#include #include #include #include @@ -114,6 +115,7 @@ #include #include #include +#include #include #include #include "linenoise.h" @@ -237,7 +239,10 @@ static int getCursorPosition(void) { /* Send the command to the TTY on the other end of the UART. * Let's use unistd's write function. Thus, data sent through it are raw * reducing the overhead compared to using fputs, fprintf, etc... */ - write(out_fd, get_cursor_cmd, sizeof(get_cursor_cmd)); + int num_written = write(out_fd, get_cursor_cmd, sizeof(get_cursor_cmd)); + if (num_written != sizeof(get_cursor_cmd)) { + return -1; + } /* For USB CDC, it is required to flush the output. */ flushWrite(); diff --git a/components/console/private_include/console_private.h b/components/console/private_include/console_private.h new file mode 100644 index 0000000000..6f9f8361af --- /dev/null +++ b/components/console/private_include/console_private.h @@ -0,0 +1,51 @@ +/* + * SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include +#include +#include "sdkconfig.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "esp_console.h" + +#define CONSOLE_PROMPT_MAX_LEN (32) + +#if CONFIG_IDF_TARGET_LINUX +#define CONSOLE_PATH_MAX_LEN (128) +#else +#include "esp_vfs_dev.h" +#define CONSOLE_PATH_MAX_LEN (ESP_VFS_PATH_MAX) +#endif + +typedef enum { + CONSOLE_REPL_STATE_DEINIT, + CONSOLE_REPL_STATE_INIT, + CONSOLE_REPL_STATE_START, +} repl_state_t; + +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; + 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. +} esp_console_repl_com_t; + +typedef struct { + esp_console_repl_com_t repl_com; // base class + int uart_channel; // uart channel number +} esp_console_repl_universal_t; + +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_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); diff --git a/components/console/test_apps/.build-test-rules.yml b/components/console/test_apps/.build-test-rules.yml new file mode 100644 index 0000000000..2878ea71c2 --- /dev/null +++ b/components/console/test_apps/.build-test-rules.yml @@ -0,0 +1,6 @@ +# Documentation: .gitlab/ci/README.md#manifest-file-to-control-the-buildtest-apps + +components/console/test_apps/console: + enable: + - if: INCLUDE_DEFAULT == 1 or IDF_TARGET == "linux" + reason: Tested on all chips before, now also testing on Linux diff --git a/components/console/test_apps/console/README.md b/components/console/test_apps/console/README.md index 1e72354679..bd34510f87 100644 --- a/components/console/test_apps/console/README.md +++ b/components/console/test_apps/console/README.md @@ -1,5 +1,5 @@ -| Supported Targets | ESP32 | ESP32-C2 | ESP32-C3 | ESP32-C6 | ESP32-H2 | ESP32-P4 | ESP32-S2 | ESP32-S3 | -| ----------------- | ----- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | +| Supported Targets | ESP32 | ESP32-C2 | ESP32-C3 | ESP32-C6 | ESP32-H2 | ESP32-P4 | ESP32-S2 | ESP32-S3 | Linux | +| ----------------- | ----- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | ----- | Note: Most of the test cases shouldn't be run manually, but [pytest](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/contribute/esp-idf-tests-with-pytest.html) should be used instead. E.g., to run all test cases on ESP32 using pytest, use: 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 8dffdfd32f..258953bd47 100644 --- a/components/console/test_apps/console/main/test_app_main.c +++ b/components/console/test_apps/console/main/test_app_main.c @@ -1,41 +1,25 @@ /* - * SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2022-2024 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ #include "unity.h" #include "unity_test_runner.h" -#include "esp_heap_caps.h" +#include "unity_test_utils_memory.h" #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) - -static size_t before_free_8bit; -static size_t before_free_32bit; - -static void check_leak(size_t before_free, size_t after_free, const char *type) -{ - ssize_t delta = after_free - before_free; - printf("MALLOC_CAP_%s: Before %u bytes free, After %u bytes free (delta %d)\n", type, before_free, after_free, delta); - TEST_ASSERT_MESSAGE(delta >= TEST_MEMORY_LEAK_THRESHOLD, "memory leak"); -} +#define TEST_MEMORY_LEAK_THRESHOLD (150) void setUp(void) { - before_free_8bit = heap_caps_get_free_size(MALLOC_CAP_8BIT); - before_free_32bit = heap_caps_get_free_size(MALLOC_CAP_32BIT); + unity_utils_record_free_mem(); } - - void tearDown(void) { - size_t after_free_8bit = heap_caps_get_free_size(MALLOC_CAP_8BIT); - size_t after_free_32bit = heap_caps_get_free_size(MALLOC_CAP_32BIT); - check_leak(before_free_8bit, after_free_8bit, "8BIT"); - check_leak(before_free_32bit, after_free_32bit, "32BIT"); + unity_utils_evaluate_leaks_direct(TEST_MEMORY_LEAK_THRESHOLD); } 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 78cda6c453..d2364cd940 100644 --- a/components/console/test_apps/console/main/test_console.c +++ b/components/console/test_apps/console/main/test_console.c @@ -29,6 +29,8 @@ typedef struct { const char *out; } cmd_context_t; +static esp_console_repl_t *s_repl = NULL; + static int do_hello_cmd_with_context(void *context, int argc, char **argv) { cmd_context_t *cmd_context = (cmd_context_t *)context; @@ -78,6 +80,15 @@ TEST_CASE("esp console register with normal and context aware function set to NU TEST_ESP_OK(esp_console_deinit()); } +TEST_CASE("esp console init function NULL param fails", "[console]") +{ + 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_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, esp_console_new_repl_uart(NULL, &repl_config, &s_repl)); + TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, esp_console_new_repl_uart(&uart_config, NULL, &s_repl)); + TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, esp_console_new_repl_uart(&uart_config, &repl_config, NULL)); +} + TEST_CASE("esp console init/deinit test", "[console]") { esp_console_config_t console_config = ESP_CONSOLE_CONFIG_DEFAULT(); @@ -112,8 +123,6 @@ TEST_CASE("esp console init/deinit with context test", "[console]") TEST_ESP_OK(esp_console_deinit()); } -static esp_console_repl_t *s_repl = NULL; - /* handle 'quit' command */ static int do_cmd_quit(int argc, char **argv) { diff --git a/components/console/test_apps/console/pytest_console.py b/components/console/test_apps/console/pytest_console.py index 1cbaffea2a..d92ed11037 100644 --- a/components/console/test_apps/console/pytest_console.py +++ b/components/console/test_apps/console/pytest_console.py @@ -1,6 +1,5 @@ -# SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD +# SPDX-FileCopyrightText: 2022-2024 Espressif Systems (Shanghai) CO LTD # SPDX-License-Identifier: CC0-1.0 - import pytest from pytest_embedded import Dut @@ -52,16 +51,20 @@ def do_test_help_quit(dut: Dut) -> None: @pytest.mark.parametrize( 'test_on', [ + pytest.param('host', marks=[pytest.mark.linux, pytest.mark.host_test]), pytest.param('target', marks=[pytest.mark.supported_targets, pytest.mark.generic]), pytest.param('qemu', marks=[pytest.mark.esp32, pytest.mark.host_test, pytest.mark.qemu]), ] ) def test_console(dut: Dut, test_on: str) -> None: - dut.run_all_single_board_cases() + dut.expect_exact('Press ENTER to see the list of tests.') + dut.write('![ignore]') + dut.expect(r'\d{1} Tests 0 Failures 0 Ignored', timeout=120) @pytest.mark.parametrize( 'test_on', [ + pytest.param('host', marks=[pytest.mark.linux, pytest.mark.host_test]), pytest.param('target', marks=[pytest.mark.supported_targets, pytest.mark.generic]), pytest.param('qemu', marks=[pytest.mark.esp32, pytest.mark.host_test, pytest.mark.qemu]), ] @@ -72,6 +75,7 @@ def test_console_repl(dut: Dut, test_on: str) -> None: @pytest.mark.parametrize( 'test_on', [ + pytest.param('host', marks=[pytest.mark.linux, pytest.mark.host_test]), pytest.param('target', marks=[pytest.mark.supported_targets, pytest.mark.generic]), pytest.param('qemu', marks=[pytest.mark.esp32, pytest.mark.host_test, pytest.mark.qemu]), ] @@ -82,6 +86,7 @@ def test_console_help_sorted_registration(dut: Dut, test_on: str) -> None: @pytest.mark.parametrize( 'test_on', [ + pytest.param('host', marks=[pytest.mark.linux, pytest.mark.host_test]), pytest.param('target', marks=[pytest.mark.supported_targets, pytest.mark.generic]), pytest.param('qemu', marks=[pytest.mark.esp32, pytest.mark.host_test, pytest.mark.qemu]), ] @@ -92,6 +97,7 @@ def test_console_help_reverse_registration(dut: Dut, test_on: str) -> None: @pytest.mark.parametrize( 'test_on', [ + pytest.param('host', marks=[pytest.mark.linux, pytest.mark.host_test]), pytest.param('target', marks=[pytest.mark.supported_targets, pytest.mark.generic]), pytest.param('qemu', marks=[pytest.mark.esp32, pytest.mark.host_test, pytest.mark.qemu]), ] diff --git a/components/console/test_apps/console/sdkconfig.defaults.linux b/components/console/test_apps/console/sdkconfig.defaults.linux new file mode 100644 index 0000000000..e1ed9340db --- /dev/null +++ b/components/console/test_apps/console/sdkconfig.defaults.linux @@ -0,0 +1 @@ +CONFIG_FREERTOS_WATCHPOINT_END_OF_STACK=n