Merge branch 'fix/console-deinit-deadlock' into 'master'

fix(console): Deadlock on console deletion

Closes IDFGH-9188, IDFGH-8520, and IDF-6906

See merge request espressif/esp-idf!30920
This commit is contained in:
Guillaume Souchere
2025-04-04 13:56:03 +08:00
17 changed files with 471 additions and 92 deletions

View File

@@ -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

View File

@@ -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 */

View File

@@ -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

View File

@@ -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 <sys/cdefs.h> // __containerof
#include <sys/param.h>
#include <sys/fcntl.h>
#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 <unistd.h>
#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);
}

View File

@@ -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();

View File

@@ -0,0 +1,166 @@
/*
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "sdkconfig.h"
#include <unistd.h>
#include <sys/cdefs.h> // __containerof
#include <sys/param.h>
#include <sys/fcntl.h>
#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);
}

View File

@@ -116,13 +116,12 @@
#include <stddef.h>
#include <errno.h>
#include <string.h>
#include <stdlib.h>
#include <ctype.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/fcntl.h>
#include <sys/time.h>
#include <unistd.h>
#include <sys/param.h>
#include <assert.h>
#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.

View File

@@ -43,8 +43,11 @@
extern "C" {
#endif
#include <sys/types.h>
#include <unistd.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
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

View File

@@ -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,

View File

@@ -10,7 +10,12 @@
#include <sys/time.h>
// 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)

View File

@@ -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 = {

View File

@@ -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>')

View File

@@ -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"

View File

@@ -0,0 +1,19 @@
/*
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "esp_vfs_eventfd.h"
#include <sys/eventfd.h>
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;
}

View File

@@ -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
^^^^^^^^^

View File

@@ -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;
}

View File

@@ -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));