diff --git a/components/esp_driver_uart/src/uart_vfs.c b/components/esp_driver_uart/src/uart_vfs.c index fa9221f2a58..69fef254ed5 100644 --- a/components/esp_driver_uart/src/uart_vfs.c +++ b/components/esp_driver_uart/src/uart_vfs.c @@ -588,7 +588,6 @@ static esp_err_t uart_end_select(void *end_select_args) if (s_uart_select_count[i] == 0) { uart_set_select_notif_callback(i, NULL); } - break; } } portEXIT_CRITICAL(uart_get_selectlock()); diff --git a/components/esp_libc/src/picolibc/picolibc_init.c b/components/esp_libc/src/picolibc/picolibc_init.c index 0756627ef42..2e6731b5bf3 100644 --- a/components/esp_libc/src/picolibc/picolibc_init.c +++ b/components/esp_libc/src/picolibc/picolibc_init.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2024-2025 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2024-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -138,32 +138,38 @@ void esp_reent_cleanup(void) * Initialize stdin, stdout, and stderr using static memory allocation. * Creating them with fopen() would call malloc() internally. */ -static char write_buf[BUFSIZ]; -static char read_buf[BUFSIZ]; +static char in_buf[BUFSIZ]; +static char out_buf[BUFSIZ]; +static char err_buf[BUFSIZ]; -static struct __file_bufio __stdin = FDEV_SETUP_BUFIO(0, read_buf, BUFSIZ, read, write, lseek, close, __SRD, 0); -static struct __file_bufio __stdout = FDEV_SETUP_BUFIO(1, write_buf, BUFSIZ, read, write, lseek, close, __SWR, __BLBF); +static struct __file_bufio __stdin = FDEV_SETUP_BUFIO(0, in_buf, BUFSIZ, read, write, lseek, close, __SRD, 0); +static struct __file_bufio __stdout = FDEV_SETUP_BUFIO(1, out_buf, BUFSIZ, read, write, lseek, close, __SWR, __BLBF); +static struct __file_bufio __stderr = FDEV_SETUP_BUFIO(2, err_buf, BUFSIZ, read, write, lseek, close, __SWR, __BLBF); FILE *stdin = &__stdin.xfile.cfile.file; FILE *stdout = &__stdout.xfile.cfile.file; -FILE *stderr = &__stdout.xfile.cfile.file; +FILE *stderr = &__stderr.xfile.cfile.file; #if CONFIG_LIBC_PICOLIBC_NEWLIB_COMPATIBILITY __thread FILE* tls_stdin = &__stdin.xfile.cfile.file; __thread FILE* tls_stdout = &__stdout.xfile.cfile.file; -__thread FILE* tls_stderr = &__stdout.xfile.cfile.file; +__thread FILE* tls_stderr = &__stderr.xfile.cfile.file; #endif #if CONFIG_VFS_SUPPORT_IO void esp_libc_init_global_stdio(const char *stdio_dev) { int stdin_fd = open(stdio_dev, O_RDONLY); - assert(stdin_fd > 0); + assert(stdin_fd >= 0); // Should be 0 __stdin.ptr = (void *)(intptr_t)(stdin_fd); int stdout_fd = open(stdio_dev, O_WRONLY); - assert(stdout_fd > 0); + assert(stdout_fd >= 0); // Should be 1 __stdout.ptr = (void *)(intptr_t)(stdout_fd); + + int stderr_fd = open(stdio_dev, O_WRONLY); + assert(stderr_fd >= 0); // Should be 2 + __stderr.ptr = (void *)(intptr_t)(stderr_fd); } #else /* CONFIG_VFS_SUPPORT_IO */ void esp_libc_init_global_stdio(void) diff --git a/components/esp_stdio/CMakeLists.txt b/components/esp_stdio/CMakeLists.txt index f4253624659..e3d15f06693 100644 --- a/components/esp_stdio/CMakeLists.txt +++ b/components/esp_stdio/CMakeLists.txt @@ -7,7 +7,6 @@ if(non_os_build) idf_component_register() return() endif() - set(srcs) set(includes) @@ -18,10 +17,13 @@ if(${target} STREQUAL "linux") "linux/include") else() list(APPEND srcs "stdio_port.c" - "stdio_vfs.c" "stdio_simple.c" "stdio_syscalls_simple.c") list(APPEND includes "include") + + if(CONFIG_VFS_SUPPORT_IO) + list(APPEND srcs "stdio_vfs.c") + endif() endif() idf_component_register(SRCS ${srcs} diff --git a/components/esp_stdio/Kconfig b/components/esp_stdio/Kconfig index a103ba0c526..3083d0f740d 100644 --- a/components/esp_stdio/Kconfig +++ b/components/esp_stdio/Kconfig @@ -185,4 +185,12 @@ menu "ESP-STDIO" If enabled, esp_rom_printf and ESP_EARLY_LOG output will also be sent over USB CDC. Disabling this option saves about 1kB or RAM. + config ESP_STDIO_MAX_VFS_ENTRIES + int "Maximum number of registerable VFS sinks" + depends on VFS_SUPPORT_IO + range 1 10 + default 2 + help + Maximum number of filesystems that can be registered as sinks for console output. + endmenu diff --git a/components/esp_stdio/stdio_vfs.c b/components/esp_stdio/stdio_vfs.c index 28fd6487582..fb0ce9efb56 100644 --- a/components/esp_stdio/stdio_vfs.c +++ b/components/esp_stdio/stdio_vfs.c @@ -5,10 +5,15 @@ */ #include "sdkconfig.h" +#include +#include #include +#include #include "esp_err.h" +#include "esp_log.h" #include "esp_rom_sys.h" #include "esp_stdio.h" +#include "esp_vfs.h" #include #if CONFIG_VFS_SUPPORT_IO @@ -30,6 +35,8 @@ #include "esp_private/startup_internal.h" #include "esp_private/nullfs.h" +#include +#endif #define STRINGIFY(s) STRINGIFY2(s) #define STRINGIFY2(s) #s @@ -42,135 +49,233 @@ * while the secondary is only used for output. */ +#if CONFIG_VFS_SUPPORT_IO + typedef struct { - int fd_primary; - int fd_secondary; -} vfs_console_context_t; + const esp_vfs_fs_ops_t *ops; /* NULL = slot is free */ + void *vfs_ctx; + const char *path; + int fd; /* -1 if not opened (used by auxiliaries) */ +} mux_entry_t; -// Secondary register part. -#if CONFIG_ESP_CONSOLE_SECONDARY_USB_SERIAL_JTAG -const static esp_vfs_fs_ops_t *secondary_vfs = NULL; -#endif // Secondary part +/* Primary backend (all ops forwarded except open + write + fsync) */ +static mux_entry_t s_primary = { .fd = -1 }; -const static esp_vfs_fs_ops_t *primary_vfs = NULL; +/* Auxiliary sinks (write-only fan-out) */ +#define STDIO_MAX_AUXILIARY (CONFIG_ESP_STDIO_MAX_VFS_ENTRIES - 1) +static mux_entry_t s_auxiliary[STDIO_MAX_AUXILIARY] = { [0 ... STDIO_MAX_AUXILIARY - 1] = { .fd = -1 } }; +static int s_open_count = 0; +static _lock_t s_lock; -static vfs_console_context_t vfs_console = {0}; +static const char *TAG = "esp_stdio"; -static size_t s_open_count = 0; +#ifdef CONFIG_VFS_SUPPORT_TERMIOS +static const mux_entry_t *get_primary_termios_entry(int fd, const esp_vfs_termios_ops_t **out_ops) +{ + const mux_entry_t *entry = &s_primary; + assert(entry); + + const esp_vfs_termios_ops_t *termios = entry->ops->termios; + if (!termios) { + errno = ENOSYS; + return NULL; + } + + if (out_ops) { + *out_ops = termios; + } + + return entry; +} +#endif // CONFIG_VFS_SUPPORT_TERMIOS + +static esp_err_t __attribute__((unused)) register_auxiliary(const esp_vfs_fs_ops_t *ops, void *ctx, const char *path) +{ + if (!ops || !path || !ops->write_p) { + return ESP_ERR_INVALID_ARG; + } + _lock_acquire(&s_lock); + for (size_t i = 0; i < STDIO_MAX_AUXILIARY; i++) { + if (s_auxiliary[i].ops == NULL) { + s_auxiliary[i] = (mux_entry_t) { + .ops = ops, + .vfs_ctx = ctx, + .path = path, + .fd = -1, + }; + _lock_release(&s_lock); + return ESP_OK; + } + } + _lock_release(&s_lock); + ESP_EARLY_LOGE(TAG, "Too many auxiliary sinks"); + return ESP_ERR_NO_MEM; +} int console_open(__attribute__((unused)) void *ctx, const char * path, int flags, int mode) { - if (s_open_count > 0) { - // Underlying fd is already open, so just increment the open count - // and return the same fd - - s_open_count++; - return 0; + (void)path; + const mux_entry_t *entry = &s_primary; + if (!entry->ops || !entry->ops->open_p) { + errno = ENOSYS; + return -1; } -// Primary port open -#if CONFIG_ESP_CONSOLE_UART - vfs_console.fd_primary = open("/dev/uart/"STRINGIFY(CONFIG_ESP_CONSOLE_UART_NUM), flags, mode); -#elif CONFIG_ESP_CONSOLE_USB_SERIAL_JTAG - vfs_console.fd_primary = open("/dev/usbserjtag", flags, mode); -#elif CONFIG_ESP_CONSOLE_USB_CDC - vfs_console.fd_primary = open("/dev/cdcacm", flags, mode); -#else - vfs_console.fd_primary = open("/dev/null", flags, mode); -#endif - -// Secondary port open -#if CONFIG_ESP_CONSOLE_SECONDARY_USB_SERIAL_JTAG - vfs_console.fd_secondary = open("/dev/secondary", flags, mode); -#endif + int local_fd = entry->ops->open_p(entry->vfs_ctx, entry->path, flags, mode); + if (local_fd < 0) { + return -1; + } + /* Lazily open auxiliaries on first console open */ + _lock_acquire(&s_lock); + if (s_open_count == 0) { + for (size_t i = 0; i < STDIO_MAX_AUXILIARY; i++) { + mux_entry_t *aux = &s_auxiliary[i]; + if (aux->ops && aux->ops->open_p && aux->fd < 0) { + aux->fd = aux->ops->open_p(aux->vfs_ctx, aux->path, O_WRONLY, 0); + } + } + } s_open_count++; + _lock_release(&s_lock); + + return local_fd; +} + +int console_close(__attribute__((unused)) void *ctx, int fd) +{ + const mux_entry_t *entry = &s_primary; + if (!entry->ops || !entry->ops->close_p) { + errno = ENOSYS; + return -1; + } + + int ret = entry->ops->close_p(entry->vfs_ctx, fd); + if (ret != 0) { + return ret; + } + + /* Close auxiliaries when last console fd is closed */ + _lock_acquire(&s_lock); + if (s_open_count > 0) { + s_open_count--; + } + if (s_open_count == 0) { + for (size_t i = 0; i < STDIO_MAX_AUXILIARY; i++) { + mux_entry_t *aux = &s_auxiliary[i]; + if (aux->ops && aux->ops->close_p && aux->fd >= 0) { + aux->ops->close_p(aux->vfs_ctx, aux->fd); + aux->fd = -1; + } + } + } + _lock_release(&s_lock); + return 0; } ssize_t console_write(__attribute__((unused)) void *ctx, int fd, const void *data, size_t size) { - write(vfs_console.fd_primary, data, size); -#if CONFIG_ESP_CONSOLE_SECONDARY_USB_SERIAL_JTAG - write(vfs_console.fd_secondary, data, size); -#endif - return size; + const mux_entry_t *primary = &s_primary; + ssize_t ret_val = primary->ops->write_p(primary->vfs_ctx, fd, data, size); + + _lock_acquire(&s_lock); + for (size_t i = 0; i < STDIO_MAX_AUXILIARY; i++) { + const mux_entry_t *entry = &s_auxiliary[i]; + if (entry->ops && entry->ops->write_p && entry->fd >= 0) { + (void) entry->ops->write_p(entry->vfs_ctx, entry->fd, data, size); + } + } + _lock_release(&s_lock); + + return ret_val; } int console_fstat(__attribute__((unused)) void *ctx, int fd, struct stat * st) { - return fstat(vfs_console.fd_primary, st); -} - -int console_close(__attribute__((unused)) void *ctx, int fd) -{ - if (s_open_count == 0) { - errno = EBADF; + const mux_entry_t *entry = &s_primary; + if (!entry->ops->fstat_p) { + errno = ENOSYS; return -1; } - - s_open_count--; - - // We don't actually close the underlying fd until the open count reaches 0 - if (s_open_count > 0) { - return 0; - } - - // All function calls are to primary, except from write and close, which will be forwarded to both primary and secondary. - close(vfs_console.fd_primary); -#if CONFIG_ESP_CONSOLE_SECONDARY_USB_SERIAL_JTAG - close(vfs_console.fd_secondary); -#endif - return 0; + return entry->ops->fstat_p(entry->vfs_ctx, fd, st); } ssize_t console_read(__attribute__((unused)) void *ctx, int fd, void * dst, size_t size) { - return read(vfs_console.fd_primary, dst, size); + const mux_entry_t *entry = &s_primary; + if (!entry->ops->read_p) { + errno = ENOSYS; + return -1; + } + return entry->ops->read_p(entry->vfs_ctx, fd, dst, size); } int console_fcntl(__attribute__((unused)) void *ctx, int fd, int cmd, int arg) { - return fcntl(vfs_console.fd_primary, cmd, arg); + const mux_entry_t *entry = &s_primary; + if (!entry->ops->fcntl_p) { + errno = ENOSYS; + return -1; + } + return entry->ops->fcntl_p(entry->vfs_ctx, fd, cmd, arg); } int console_fsync(__attribute__((unused)) void *ctx, int fd) { - const int ret_val = fsync(vfs_console.fd_primary); -#if CONFIG_ESP_CONSOLE_SECONDARY_USB_SERIAL_JTAG - (void)fsync(vfs_console.fd_secondary); -#endif + const mux_entry_t *primary = &s_primary; + if (!primary->ops->fsync_p) { + errno = ENOSYS; + return -1; + } + + int ret_val = primary->ops->fsync_p(primary->vfs_ctx, fd); + + _lock_acquire(&s_lock); + for (size_t i = 0; i < STDIO_MAX_AUXILIARY; i++) { + const mux_entry_t *entry = &s_auxiliary[i]; + if (entry->ops && entry->ops->fsync_p && entry->fd >= 0) { + (void) entry->ops->fsync_p(entry->vfs_ctx, entry->fd); + } + } + _lock_release(&s_lock); + return ret_val; } #ifdef CONFIG_VFS_SUPPORT_DIR int console_access(__attribute__((unused)) void *ctx, const char *path, int amode) { - // currently only UART support DIR. - return access("/dev/uart/"STRINGIFY(CONFIG_ESP_CONSOLE_UART_NUM), amode); + const mux_entry_t *entry = &s_primary; + assert(entry); + if (!entry->ops->dir || !entry->ops->dir->access_p) { + errno = ENOSYS; + return -1; + } + (void) path; + return entry->ops->dir->access_p(entry->vfs_ctx, entry->path, amode); } #endif // CONFIG_VFS_SUPPORT_DIR #ifdef CONFIG_VFS_SUPPORT_SELECT + static esp_err_t console_start_select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, esp_vfs_select_sem_t select_sem, void **end_select_args) { - // start_select is not guaranteed be implemented even though CONFIG_VFS_SUPPORT_SELECT is enabled in sdkconfig - if (primary_vfs->select->start_select) { - return primary_vfs->select->start_select(nfds, readfds, writefds, exceptfds, select_sem, end_select_args); + const mux_entry_t *entry = &s_primary; + if (!entry->ops || !entry->ops->select || !entry->ops->select->start_select) { + return ESP_ERR_NOT_SUPPORTED; } - - return ESP_ERR_NOT_SUPPORTED; + return entry->ops->select->start_select(nfds, readfds, writefds, exceptfds, select_sem, end_select_args); } -esp_err_t console_end_select(void *end_select_args) +static esp_err_t console_end_select(void *end_select_args) { - // end_select is not guaranteed be implemented even though CONFIG_VFS_SUPPORT_SELECT is enabled in sdkconfig - if (primary_vfs->select->end_select) { - return primary_vfs->select->end_select(end_select_args); + const mux_entry_t *entry = &s_primary; + if (!entry->ops || !entry->ops->select || !entry->ops->select->end_select) { + return ESP_ERR_NOT_SUPPORTED; } - - return ESP_ERR_NOT_SUPPORTED; + return entry->ops->select->end_select(end_select_args); } #endif // CONFIG_VFS_SUPPORT_SELECT @@ -179,22 +284,58 @@ esp_err_t console_end_select(void *end_select_args) int console_tcsetattr(__attribute__((unused)) void *ctx, int fd, int optional_actions, const struct termios *p) { - return tcsetattr(vfs_console.fd_primary, optional_actions, p); + const esp_vfs_termios_ops_t *termios = NULL; + const mux_entry_t *entry = get_primary_termios_entry(fd, &termios); + if (!entry) { + return -1; + } + if (!termios->tcsetattr_p) { + errno = ENOSYS; + return -1; + } + return entry->ops->termios->tcsetattr_p(entry->vfs_ctx, fd, optional_actions, p); } int console_tcgetattr(__attribute__((unused)) void *ctx, int fd, struct termios *p) { - return tcgetattr(vfs_console.fd_primary, p); + const esp_vfs_termios_ops_t *termios = NULL; + const mux_entry_t *entry = get_primary_termios_entry(fd, &termios); + if (!entry) { + return -1; + } + if (!termios->tcgetattr_p) { + errno = ENOSYS; + return -1; + } + return entry->ops->termios->tcgetattr_p(entry->vfs_ctx, fd, p); } int console_tcdrain(__attribute__((unused)) void *ctx, int fd) { - return tcdrain(vfs_console.fd_primary); + const esp_vfs_termios_ops_t *termios = NULL; + const mux_entry_t *entry = get_primary_termios_entry(fd, &termios); + if (!entry) { + return -1; + } + if (!termios->tcdrain_p) { + errno = ENOSYS; + return -1; + } + return entry->ops->termios->tcdrain_p(entry->vfs_ctx, fd); } int console_tcflush(__attribute__((unused)) void *ctx, int fd, int select) { - return tcflush(vfs_console.fd_primary, select); + const esp_vfs_termios_ops_t *termios = NULL; + const mux_entry_t *entry = get_primary_termios_entry(fd, &termios); + if (!entry) { + return -1; + } + if (!termios->tcflush_p) { + errno = ENOSYS; + return -1; + } + return entry->ops->termios->tcflush_p(entry->vfs_ctx, fd, select); } #endif // CONFIG_VFS_SUPPORT_TERMIOS @@ -242,31 +383,55 @@ static const esp_vfs_fs_ops_t s_vfs_console = { #endif // CONFIG_VFS_SUPPORT_TERMIOS }; -static esp_err_t esp_vfs_dev_console_register(void) -{ - return esp_vfs_register_fs(ESP_VFS_DEV_CONSOLE, &s_vfs_console, ESP_VFS_FLAG_STATIC | ESP_VFS_FLAG_CONTEXT_PTR, NULL); -} - esp_err_t esp_stdio_register(void) { - esp_err_t err = ESP_OK; + _lock_init(&s_lock); + // Primary vfs part. #if CONFIG_ESP_CONSOLE_UART - primary_vfs = esp_vfs_uart_get_vfs(); + s_primary = (mux_entry_t) { + .ops = esp_vfs_uart_get_vfs(), + .vfs_ctx = NULL, + .path = "/" STRINGIFY(CONFIG_ESP_CONSOLE_UART_NUM), + .fd = -1, + }; #elif CONFIG_ESP_CONSOLE_USB_SERIAL_JTAG - primary_vfs = esp_vfs_usb_serial_jtag_get_vfs(); + s_primary = (mux_entry_t) { + .ops = esp_vfs_usb_serial_jtag_get_vfs(), + .vfs_ctx = NULL, + .path = "/", + .fd = -1, + }; #elif CONFIG_ESP_CONSOLE_USB_CDC - primary_vfs = esp_vfs_cdcacm_get_vfs(); + s_primary = (mux_entry_t) { + .ops = esp_vfs_cdcacm_get_vfs(), + .vfs_ctx = NULL, + .path = "/", + .fd = -1, + }; #else - primary_vfs = esp_vfs_null_get_vfs(); + s_primary = (mux_entry_t) { + .ops = esp_vfs_null_get_vfs(), + .vfs_ctx = NULL, + .path = "/", + .fd = -1, + }; #endif -// Secondary vfs part. + if (!s_primary.ops) { + ESP_EARLY_LOGE(TAG, "No primary console backend available"); + return ESP_FAIL; + } + +// Auxiliary sinks (write-only fan-out). #if CONFIG_ESP_CONSOLE_SECONDARY_USB_SERIAL_JTAG - secondary_vfs = esp_vfs_usb_serial_jtag_get_vfs(); + esp_err_t err = register_auxiliary(esp_vfs_usb_serial_jtag_get_vfs(), NULL, "/"); + if (err != ESP_OK) { + return err; + } #endif - err = esp_vfs_dev_console_register(); - return err; + + return esp_vfs_register_fs(ESP_VFS_DEV_CONSOLE, &s_vfs_console, ESP_VFS_FLAG_STATIC | ESP_VFS_FLAG_CONTEXT_PTR, NULL); } ESP_SYSTEM_INIT_FN(init_vfs_console, CORE, BIT(0), 119) diff --git a/components/esp_stdio/test_apps/stdio/main/test_app_main.c b/components/esp_stdio/test_apps/stdio/main/test_app_main.c index 8a8663867b6..b860be7f343 100644 --- a/components/esp_stdio/test_apps/stdio/main/test_app_main.c +++ b/components/esp_stdio/test_apps/stdio/main/test_app_main.c @@ -1,11 +1,12 @@ /* - * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2024-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ #include #include #include +#include #include "sdkconfig.h" #include "esp_rom_serial_output.h" @@ -18,6 +19,7 @@ #include #include +#include #if CONFIG_ESP_CONSOLE_NONE /* Set up UART on UART_0 (console) to be able to @@ -50,6 +52,9 @@ static void console_none_print(void) #endif #if CONFIG_VFS_SUPPORT_IO +#define ESP_STDIO_RUN_OPEN_CLOSE_CHECK 1 + +#if ESP_STDIO_RUN_OPEN_CLOSE_CHECK static void console_open_close_check(void) { printf("Opening /dev/console\n"); @@ -65,6 +70,79 @@ static void console_open_close_check(void) printf("This should be printed to stdout\n"); } +#endif + +static void stdio_fd_mode_behavior_check(void) +{ + printf("STDIO_TEST:MODE=FORWARDING\n"); + + int fd0 = open("/dev/console", O_RDWR); + assert(fd0 >= 0); + + int fd1 = open("/dev/console", O_RDWR); + assert(fd1 >= 0); + assert(fd0 != fd1); + printf("STDIO_TEST:UNIQUE_FDS_OK\n"); + + const char *msg = "STDIO_TEST:WRITE_OK\n"; + ssize_t wr = write(fd0, msg, strlen(msg)); + assert(wr == (ssize_t) strlen(msg)); + + assert(fsync(fd0) == 0); + printf("STDIO_TEST:FSYNC_OK\n"); + + assert(close(fd0) == 0); + assert(close(fd1) == 0); + printf("STDIO_TEST:CLOSE_OK\n"); +} + +static void stdio_fd_init_check(void) +{ + int in_fd = fileno(stdin); + int out_fd = fileno(stdout); + int err_fd = fileno(stderr); + + assert(in_fd == 0); + assert(out_fd == 1); + assert(err_fd == 2); + + printf("STDIO_TEST:FD_INIT:IN=%d,OUT=%d,ERR=%d\n", in_fd, out_fd, err_fd); + fprintf(stdout, "STDIO_TEST:STREAM:STDOUT\n"); + fflush(stdout); + fprintf(stderr, "STDIO_TEST:STREAM:STDERR\n"); + fflush(stderr); +} + +#if CONFIG_VFS_SUPPORT_SELECT && CONFIG_ESP_CONSOLE_UART +static void stdio_select_fd_mapping_check(void) +{ + bool driver_installed_here = false; + esp_err_t err = uart_driver_install(CONFIG_ESP_CONSOLE_UART_NUM, 256, 0, 0, NULL, 0); + if (err == ESP_OK) { + driver_installed_here = true; + } else { + assert(err == ESP_ERR_INVALID_STATE); + } + + fd_set writefds; + FD_ZERO(&writefds); + FD_SET(fileno(stdout), &writefds); + FD_SET(fileno(stderr), &writefds); + + struct timeval tv = { + .tv_sec = 0, + .tv_usec = 0, + }; + + errno = 0; + int ret = select(fileno(stderr) + 1, NULL, &writefds, NULL, &tv); + assert(ret >= 0); + if (driver_installed_here) { + assert(uart_driver_delete(CONFIG_ESP_CONSOLE_UART_NUM) == ESP_OK); + } + printf("STDIO_TEST:SELECT:LOGICAL_FD_MAP_OK\n"); +} +#endif #endif // CONFIG_VFS_SUPPORT_IO void app_main(void) @@ -80,6 +158,11 @@ void app_main(void) assert(fd > 2 && "Incorrect file descriptor returned, stdin, stdout, stderr were not correctly assigned"); close(fd); + + stdio_fd_init_check(); +#if CONFIG_VFS_SUPPORT_SELECT && CONFIG_ESP_CONSOLE_UART + stdio_select_fd_mapping_check(); +#endif #endif // CONFIG_VFS_SUPPORT_IO #if CONFIG_ESP_CONSOLE_NONE @@ -87,7 +170,12 @@ void app_main(void) #endif // CONFIG_ESP_CONSOLE_NONE #if CONFIG_VFS_SUPPORT_IO +#if ESP_STDIO_RUN_OPEN_CLOSE_CHECK console_open_close_check(); +#else + printf("STDIO_TEST:SKIP:OPEN_CLOSE_CHECK\n"); +#endif + stdio_fd_mode_behavior_check(); #endif // CONFIG_VFS_SUPPORT_IO } diff --git a/components/esp_stdio/test_apps/stdio/pytest_esp_stdio_tests.py b/components/esp_stdio/test_apps/stdio/pytest_esp_stdio_tests.py index 953deadccbd..29692865aa2 100644 --- a/components/esp_stdio/test_apps/stdio/pytest_esp_stdio_tests.py +++ b/components/esp_stdio/test_apps/stdio/pytest_esp_stdio_tests.py @@ -1,5 +1,9 @@ # SPDX-FileCopyrightText: 2024-2026 Espressif Systems (Shanghai) CO LTD # SPDX-License-Identifier: CC0-1.0 +# +# HW runs: use --embedded-services esp,idf (not idf alone) so serial is attached. +# See README.md in this directory for QEMU vs hardware commands and common failures. +# import pexpect import pytest from pytest_embedded import Dut @@ -7,6 +11,11 @@ from pytest_embedded_idf.utils import idf_parametrize from pytest_embedded_idf.utils import soc_filtered_targets +def _expect_app_main_banner(dut: Dut) -> None: + """Sync to the latest app boot (avoids matching a stale 'Hello World' left in the serial buffer).""" + dut.expect('Calling app_main()') + + def not_expect(dut: Dut, output_regex: str) -> None: try: dut.expect(output_regex, timeout=5) @@ -34,7 +43,11 @@ def test_esp_system_stdio_no_output_uart(dut: Dut) -> None: @pytest.mark.temp_skip_ci(targets=['esp32h4'], reason='lack of runner # TODO: IDFCI-10703') def test_esp_system_stdio_only_serial_jtag(dut: Dut) -> None: dut.expect('2nd stage bootloader') + _expect_app_main_banner(dut) dut.expect('Hello World') + dut.expect('STDIO_TEST:FD_INIT:IN=0,OUT=1,ERR=2') + dut.expect('STDIO_TEST:STREAM:STDOUT') + dut.expect('STDIO_TEST:STREAM:STDERR') dut.expect('Opening /dev/console') dut.expect('This should be printed to stdout') dut.expect('Closing /dev/console') @@ -50,6 +63,7 @@ def test_esp_system_stdio_only_serial_jtag(dut: Dut) -> None: @pytest.mark.temp_skip_ci(targets=['esp32h4'], reason='lack of runner # TODO: IDFCI-10703') def test_esp_system_stdio_only_serial_jtag_no_vfs(dut: Dut) -> None: dut.expect('2nd stage bootloader') + _expect_app_main_banner(dut) dut.expect('Hello World') @@ -62,6 +76,7 @@ def test_esp_system_stdio_only_serial_jtag_no_vfs(dut: Dut) -> None: @pytest.mark.temp_skip_ci(targets=['esp32h4'], reason='lack of runner # TODO: IDFCI-10703') def test_esp_system_stdio_secondary_serial_jtag(dut: Dut) -> None: dut.expect('2nd stage bootloader') + _expect_app_main_banner(dut) dut.expect('Hello World') @@ -71,8 +86,37 @@ def test_esp_system_stdio_secondary_serial_jtag(dut: Dut) -> None: @pytest.mark.temp_skip_ci(targets=['esp32p4'], reason='p4 rev3 migration') def test_esp_system_stdio_correct_open_and_close(dut: Dut) -> None: dut.expect('2nd stage bootloader') + _expect_app_main_banner(dut) dut.expect('Hello World') + # Order matches test_app_main.c: stdio_fd_init_check() runs before console_open_close_check() + dut.expect('STDIO_TEST:FD_INIT:IN=0,OUT=1,ERR=2') + dut.expect('STDIO_TEST:STREAM:STDOUT') + dut.expect('STDIO_TEST:STREAM:STDERR') + dut.expect('STDIO_TEST:SELECT:LOGICAL_FD_MAP_OK') dut.expect('Opening /dev/console') dut.expect('This should be printed to stdout') dut.expect('Closing /dev/console') dut.expect('This should be printed to stdout') + dut.expect('STDIO_TEST:MODE=FORWARDING') + dut.expect('STDIO_TEST:UNIQUE_FDS_OK') + dut.expect('STDIO_TEST:WRITE_OK') + dut.expect('STDIO_TEST:FSYNC_OK') + dut.expect('STDIO_TEST:CLOSE_OK') + + +@pytest.mark.host_test +@pytest.mark.qemu +@pytest.mark.parametrize( + 'config, embedded_services', + [('simple', 'idf,qemu')], +) +@idf_parametrize('target', ['esp32'], indirect=['target']) +def test_esp_stdio_non_basic_default_qemu(dut: Dut) -> None: + dut.expect('Hello World') + dut.expect('STDIO_TEST:FD_INIT:IN=0,OUT=1,ERR=2') + dut.expect('STDIO_TEST:SELECT:LOGICAL_FD_MAP_OK') + dut.expect('STDIO_TEST:MODE=FORWARDING') + dut.expect('STDIO_TEST:UNIQUE_FDS_OK') + dut.expect('STDIO_TEST:WRITE_OK') + dut.expect('STDIO_TEST:FSYNC_OK') + dut.expect('STDIO_TEST:CLOSE_OK') diff --git a/components/spi_flash/test_apps/mspi_test/main/test_app_main.c b/components/spi_flash/test_apps/mspi_test/main/test_app_main.c index abb1c1cd7dc..eaa93bad03f 100644 --- a/components/spi_flash/test_apps/mspi_test/main/test_app_main.c +++ b/components/spi_flash/test_apps/mspi_test/main/test_app_main.c @@ -9,7 +9,7 @@ #include "esp_heap_caps.h" // Some resources are lazy allocated in mspi bus test, the threshold is left for that case -#define TEST_MEMORY_LEAK_THRESHOLD (-600) +#define TEST_MEMORY_LEAK_THRESHOLD (-650) static size_t before_free_8bit; static size_t before_free_32bit;