diff --git a/components/esp_stdio/Kconfig b/components/esp_stdio/Kconfig index 6810878ad16..595f9668ee5 100644 --- a/components/esp_stdio/Kconfig +++ b/components/esp_stdio/Kconfig @@ -185,28 +185,6 @@ 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_BASIC_MODE - bool "Use basic /dev/console descriptor handling" - depends on VFS_SUPPORT_IO - default n - help - Enable the low-overhead legacy /dev/console descriptor handling. - - In basic mode, all open() calls share a single internal descriptor. This keeps RAM usage - lower, but disables checks for per-fd behaviour, such as read-only versus write-only access. - - config ESP_STDIO_MAX_FDS - int "Max logical stdio file descriptors" - depends on VFS_SUPPORT_IO && !ESP_STDIO_BASIC_MODE - range 3 64 - default 16 - help - Maximum number of logical /dev/console descriptors tracked by esp_stdio VFS. - - stdin, stdout, and stderr each occupy one slot. The default leaves room for additional - opens of /dev/console (e.g. dup2, reopen). If set to exactly 3, only the three standard - streams fit and further opens fail with ENFILE unless one is closed first. - config ESP_STDIO_MAX_VFS_ENTRIES int "Maximum number of registerable VFS sinks" depends on VFS_SUPPORT_IO diff --git a/components/esp_stdio/stdio_vfs.c b/components/esp_stdio/stdio_vfs.c index dc5c6d9aad0..fb0ce9efb56 100644 --- a/components/esp_stdio/stdio_vfs.c +++ b/components/esp_stdio/stdio_vfs.c @@ -35,7 +35,6 @@ #include "esp_private/startup_internal.h" #include "esp_private/nullfs.h" -#include "esp_heap_caps.h" #include #endif @@ -52,96 +51,28 @@ #if CONFIG_VFS_SUPPORT_IO -#if !CONFIG_ESP_STDIO_BASIC_MODE -_Static_assert(CONFIG_ESP_STDIO_MAX_FDS >= 3, "Invalid value for CONFIG_ESP_STDIO_MAX_FDS"); -#endif - -#define ESP_STDIO_IS_BASIC (CONFIG_ESP_STDIO_BASIC_MODE) - typedef struct { - const esp_vfs_fs_ops_t *ops; - int vfs_flags; + const esp_vfs_fs_ops_t *ops; /* NULL = slot is free */ void *vfs_ctx; const char *path; - int fd; + int fd; /* -1 if not opened (used by auxiliaries) */ } mux_entry_t; -typedef struct { - bool in_use; - int flags; -} fd_entry_t; +/* Primary backend (all ops forwarded except open + write + fsync) */ +static mux_entry_t s_primary = { .fd = -1 }; -typedef struct { - mux_entry_t entries[CONFIG_ESP_STDIO_MAX_VFS_ENTRIES]; - size_t used; - -#if !ESP_STDIO_IS_BASIC - fd_entry_t fds[CONFIG_ESP_STDIO_MAX_FDS]; -#endif - - size_t fd_count; -} context_t; - -static context_t s_ctx = {0}; -static _lock_t s_ctx_lock; +/* 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 const char *TAG = "esp_stdio"; -static inline bool is_fd_valid_nolock(int fd) -{ -#if ESP_STDIO_IS_BASIC - return (fd >= 0) && (s_ctx.fd_count > 0); -#else - return (fd >= 0) && (fd < CONFIG_ESP_STDIO_MAX_FDS) && s_ctx.fds[fd].in_use; -#endif -} - -static inline bool is_fd_valid(int fd) -{ - bool valid; - - _lock_acquire(&s_ctx_lock); - valid = is_fd_valid_nolock(fd); - _lock_release(&s_ctx_lock); - - return valid; -} - -#if !ESP_STDIO_IS_BASIC -static inline int get_fd_flags_nolock(int fd) -{ - assert((fd >= 0) && (fd < CONFIG_ESP_STDIO_MAX_FDS)); - return s_ctx.fds[fd].flags; -} - -static inline bool fd_allows_read_nolock(int fd) -{ - const int flags = get_fd_flags_nolock(fd); - return (flags & O_ACCMODE) != O_WRONLY; -} - -static inline bool fd_allows_write_nolock(int fd) -{ - const int flags = get_fd_flags_nolock(fd); - return (flags & O_ACCMODE) != O_RDONLY; -} -#endif - -static inline mux_entry_t *get_primary_entry(void) -{ - assert(s_ctx.used > 0); - return &s_ctx.entries[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) { - if (!is_fd_valid(fd)) { - errno = EBADF; - return NULL; - } - - const mux_entry_t *entry = get_primary_entry(); + const mux_entry_t *entry = &s_primary; assert(entry); const esp_vfs_termios_ops_t *termios = entry->ops->termios; @@ -158,238 +89,156 @@ static const mux_entry_t *get_primary_termios_entry(int fd, const esp_vfs_termio } #endif // CONFIG_VFS_SUPPORT_TERMIOS -static esp_err_t esp_stdio_register_sink(const esp_vfs_fs_ops_t *ops, int vfs_flags, void *vfs_ctx, const char *path, int fd) +static esp_err_t __attribute__((unused)) register_auxiliary(const esp_vfs_fs_ops_t *ops, void *ctx, const char *path) { - if (!ops || !path) { + if (!ops || !path || !ops->write_p) { return ESP_ERR_INVALID_ARG; } - - if (s_ctx.used >= CONFIG_ESP_STDIO_MAX_VFS_ENTRIES) { - ESP_EARLY_LOGE(TAG, "Too many stdio sinks registered"); - return ESP_ERR_NO_MEM; + _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; + } } - - int mask = ESP_VFS_FLAG_CONTEXT_PTR | ESP_VFS_FLAG_STATIC; - - if ((vfs_flags & mask) != mask) { - ESP_EARLY_LOGE(TAG, "The sink FS needs to use both context pointer api and be static allocated"); - return ESP_ERR_INVALID_ARG; - } - - if (!ops->write_p) { - ESP_EARLY_LOGE(TAG, "Stdio sink missing mandatory write handler"); - return ESP_ERR_INVALID_ARG; - } - - bool is_primary = (s_ctx.used == 0); - if (is_primary && (!ops->read_p || !ops->fstat_p || !ops->fcntl_p || !ops->fsync_p)) { - ESP_EARLY_LOGE(TAG, "Primary stdio sink missing mandatory read/fstat/fcntl/fsync handlers"); - return ESP_ERR_INVALID_ARG; - } - - size_t idx = s_ctx.used++; - - s_ctx.entries[idx] = (mux_entry_t) { - .ops = ops, - .vfs_flags = vfs_flags, - .vfs_ctx = vfs_ctx, - .path = path, - .fd = fd, - }; - - 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 ESP_STDIO_IS_BASIC - (void) path; - (void) flags; - (void) mode; - - _lock_acquire(&s_ctx_lock); - s_ctx.fd_count++; - _lock_release(&s_ctx_lock); - - return 0; -#else - (void) mode; - - if (!path || strcmp(path, "/") != 0) { - errno = ENOENT; + (void)path; + const mux_entry_t *entry = &s_primary; + if (!entry->ops || !entry->ops->open_p) { + errno = ENOSYS; return -1; } - _lock_acquire(&s_ctx_lock); - - if (s_ctx.fd_count >= CONFIG_ESP_STDIO_MAX_FDS) { - _lock_release(&s_ctx_lock); - errno = ENFILE; + int local_fd = entry->ops->open_p(entry->vfs_ctx, entry->path, flags, mode); + if (local_fd < 0) { return -1; } - int fd = -1; - for (size_t i = 0; i < CONFIG_ESP_STDIO_MAX_FDS; ++i) { - if (!s_ctx.fds[i].in_use) { - fd = (int)i; - break; + /* 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); - if (fd < 0) { - _lock_release(&s_ctx_lock); - errno = ENFILE; - return -1; - } - - s_ctx.fd_count++; - s_ctx.fds[fd] = (fd_entry_t) { - .in_use = true, - .flags = flags, - }; - - _lock_release(&s_ctx_lock); - return fd; -#endif + return local_fd; } int console_close(__attribute__((unused)) void *ctx, int fd) { - _lock_acquire(&s_ctx_lock); - - if (!is_fd_valid_nolock(fd)) { - _lock_release(&s_ctx_lock); - errno = EBADF; + const mux_entry_t *entry = &s_primary; + if (!entry->ops || !entry->ops->close_p) { + errno = ENOSYS; return -1; } -#if ESP_STDIO_IS_BASIC - s_ctx.fd_count--; -#else - s_ctx.fds[fd].in_use = false; - s_ctx.fd_count--; -#endif + 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); - _lock_release(&s_ctx_lock); return 0; } ssize_t console_write(__attribute__((unused)) void *ctx, int fd, const void *data, size_t size) { -#if ESP_STDIO_IS_BASIC - if (!is_fd_valid(fd)) { - errno = EBADF; - return -1; - } -#else - _lock_acquire(&s_ctx_lock); - if (!is_fd_valid_nolock(fd)) { - _lock_release(&s_ctx_lock); - errno = EBADF; - return -1; - } - if (!fd_allows_write_nolock(fd)) { - _lock_release(&s_ctx_lock); - errno = EBADF; - return -1; - } - _lock_release(&s_ctx_lock); -#endif + const mux_entry_t *primary = &s_primary; + ssize_t ret_val = primary->ops->write_p(primary->vfs_ctx, fd, data, size); - const mux_entry_t *primary = get_primary_entry(); - ssize_t ret_val = primary->ops->write_p(primary->vfs_ctx, primary->fd, data, size); - - for (size_t i = 1; i < s_ctx.used; i++) { - const mux_entry_t *entry = s_ctx.entries + i; - if (entry->ops->write_p && entry->fd >= 0) { + _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) { - if (!is_fd_valid(fd)) { - errno = EBADF; - return -1; - } - - const mux_entry_t *entry = get_primary_entry(); + const mux_entry_t *entry = &s_primary; if (!entry->ops->fstat_p) { errno = ENOSYS; return -1; } - return entry->ops->fstat_p(entry->vfs_ctx, entry->fd, st); + 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) { -#if ESP_STDIO_IS_BASIC - if (!is_fd_valid(fd)) { - errno = EBADF; - return -1; - } -#else - _lock_acquire(&s_ctx_lock); - if (!is_fd_valid_nolock(fd)) { - _lock_release(&s_ctx_lock); - errno = EBADF; - return -1; - } - if (!fd_allows_read_nolock(fd)) { - _lock_release(&s_ctx_lock); - errno = EBADF; - return -1; - } - _lock_release(&s_ctx_lock); -#endif - - const mux_entry_t *entry = get_primary_entry(); + const mux_entry_t *entry = &s_primary; if (!entry->ops->read_p) { errno = ENOSYS; return -1; } - return entry->ops->read_p(entry->vfs_ctx, entry->fd, dst, size); + return entry->ops->read_p(entry->vfs_ctx, fd, dst, size); } int console_fcntl(__attribute__((unused)) void *ctx, int fd, int cmd, int arg) { - if (!is_fd_valid(fd)) { - errno = EBADF; - return -1; - } - - const mux_entry_t *entry = get_primary_entry(); + const mux_entry_t *entry = &s_primary; if (!entry->ops->fcntl_p) { errno = ENOSYS; return -1; } - return entry->ops->fcntl_p(entry->vfs_ctx, entry->fd, cmd, arg); + return entry->ops->fcntl_p(entry->vfs_ctx, fd, cmd, arg); } int console_fsync(__attribute__((unused)) void *ctx, int fd) { - if (!is_fd_valid(fd)) { - errno = EBADF; - return -1; - } - - const mux_entry_t *primary = get_primary_entry(); + 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, primary->fd); + int ret_val = primary->ops->fsync_p(primary->vfs_ctx, fd); - for (size_t i = 1; i < s_ctx.used; i++) { - const mux_entry_t *entry = s_ctx.entries + i; - if (entry->ops->fsync_p && entry->fd >= 0) { + _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; } @@ -397,7 +246,7 @@ int console_fsync(__attribute__((unused)) void *ctx, int fd) #ifdef CONFIG_VFS_SUPPORT_DIR int console_access(__attribute__((unused)) void *ctx, const char *path, int amode) { - const mux_entry_t *entry = get_primary_entry(); + const mux_entry_t *entry = &s_primary; assert(entry); if (!entry->ops->dir || !entry->ops->dir->access_p) { errno = ENOSYS; @@ -410,165 +259,23 @@ int console_access(__attribute__((unused)) void *ctx, const char *path, int amod #ifdef CONFIG_VFS_SUPPORT_SELECT -typedef struct { - void *sink_select_args; - fd_set *readfds; - fd_set *writefds; - fd_set *exceptfds; - uint64_t interested_read; - uint64_t interested_write; - uint64_t interested_except; -} console_select_ctx_t; - -/* Kconfig allows up to 64 logical fds; uint64_t masks and (1ULL << i) require i < 64. */ -enum { CONSOLE_SELECT_MAX_SCAN = 64 }; - 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) { - const mux_entry_t *entry = get_primary_entry(); - assert(entry); - *end_select_args = NULL; - - if (entry->fd < 0 || entry->ops->select == NULL || entry->ops->select->start_select == NULL) { + const mux_entry_t *entry = &s_primary; + if (!entry->ops || !entry->ops->select || !entry->ops->select->start_select) { return ESP_ERR_NOT_SUPPORTED; } + return entry->ops->select->start_select(nfds, readfds, writefds, exceptfds, select_sem, end_select_args); +} - uint64_t interested_read = 0; - uint64_t interested_write = 0; - uint64_t interested_except = 0; - - const int scan = nfds < CONSOLE_SELECT_MAX_SCAN ? nfds : CONSOLE_SELECT_MAX_SCAN; - for (int i = 0; i < scan; ++i) { - if (!is_fd_valid(i)) { - continue; - } - if (readfds && FD_ISSET(i, readfds)) { - interested_read |= (1ULL << i); - } - if (writefds && FD_ISSET(i, writefds)) { - interested_write |= (1ULL << i); - } - if (exceptfds && FD_ISSET(i, exceptfds)) { - interested_except |= (1ULL << i); - } - } - - if (interested_read == 0 && interested_write == 0 && interested_except == 0) { +static esp_err_t console_end_select(void *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; } - - const int hw = entry->fd; - - if (readfds) { - FD_ZERO(readfds); - if (interested_read) { - FD_SET(hw, readfds); - } - } - if (writefds) { - FD_ZERO(writefds); - if (interested_write) { - FD_SET(hw, writefds); - } - } - if (exceptfds) { - FD_ZERO(exceptfds); - if (interested_except) { - FD_SET(hw, exceptfds); - } - } - - const int forward_nfds = nfds > hw + 1 ? nfds : hw + 1; - - console_select_ctx_t *ctx = heap_caps_malloc(sizeof(console_select_ctx_t), MALLOC_CAP_INTERNAL); - if (ctx == NULL) { - return ESP_ERR_NO_MEM; - } - - ctx->sink_select_args = NULL; - ctx->readfds = readfds; - ctx->writefds = writefds; - ctx->exceptfds = exceptfds; - ctx->interested_read = interested_read; - ctx->interested_write = interested_write; - ctx->interested_except = interested_except; - - esp_err_t err = entry->ops->select->start_select(forward_nfds, readfds, writefds, exceptfds, select_sem, - &ctx->sink_select_args); - if (err != ESP_OK) { - if (readfds) { - FD_ZERO(readfds); - for (int i = 0; i < scan; ++i) { - if (interested_read & (1ULL << i)) { - FD_SET(i, readfds); - } - } - } - if (writefds) { - FD_ZERO(writefds); - for (int i = 0; i < scan; ++i) { - if (interested_write & (1ULL << i)) { - FD_SET(i, writefds); - } - } - } - if (exceptfds) { - FD_ZERO(exceptfds); - for (int i = 0; i < scan; ++i) { - if (interested_except & (1ULL << i)) { - FD_SET(i, exceptfds); - } - } - } - heap_caps_free(ctx); - return err; - } - - *end_select_args = ctx; - return ESP_OK; -} - -static void console_expand_fdset(fd_set *fds, uint64_t interested, int hw) -{ - if (fds == NULL) { - return; - } - const bool ready = FD_ISSET(hw, fds); - FD_ZERO(fds); - if (!ready || interested == 0) { - return; - } - for (int i = 0; i < CONSOLE_SELECT_MAX_SCAN; ++i) { - if (interested & (1ULL << i)) { - FD_SET(i, fds); - } - } -} - -esp_err_t console_end_select(void *end_select_args) -{ - if (end_select_args == NULL) { - return ESP_ERR_INVALID_ARG; - } - console_select_ctx_t *ctx = (console_select_ctx_t *)end_select_args; - const mux_entry_t *entry = get_primary_entry(); - assert(entry); - - esp_err_t ret = ESP_OK; - if (entry->fd >= 0 && entry->ops->select && entry->ops->select->end_select) { - ret = entry->ops->select->end_select(ctx->sink_select_args); - } - - const int hw = entry->fd; - if (hw >= 0) { - console_expand_fdset(ctx->readfds, ctx->interested_read, hw); - console_expand_fdset(ctx->writefds, ctx->interested_write, hw); - console_expand_fdset(ctx->exceptfds, ctx->interested_except, hw); - } - - heap_caps_free(ctx); - return ret; + return entry->ops->select->end_select(end_select_args); } #endif // CONFIG_VFS_SUPPORT_SELECT @@ -586,7 +293,7 @@ int console_tcsetattr(__attribute__((unused)) void *ctx, int fd, int optional_ac errno = ENOSYS; return -1; } - return entry->ops->termios->tcsetattr_p(entry->vfs_ctx, entry->fd, optional_actions, p); + 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) @@ -600,7 +307,7 @@ int console_tcgetattr(__attribute__((unused)) void *ctx, int fd, struct termios errno = ENOSYS; return -1; } - return entry->ops->termios->tcgetattr_p(entry->vfs_ctx, entry->fd, p); + return entry->ops->termios->tcgetattr_p(entry->vfs_ctx, fd, p); } int console_tcdrain(__attribute__((unused)) void *ctx, int fd) @@ -614,7 +321,7 @@ int console_tcdrain(__attribute__((unused)) void *ctx, int fd) errno = ENOSYS; return -1; } - return entry->ops->termios->tcdrain_p(entry->vfs_ctx, entry->fd); + return entry->ops->termios->tcdrain_p(entry->vfs_ctx, fd); } int console_tcflush(__attribute__((unused)) void *ctx, int fd, int select) @@ -628,7 +335,7 @@ int console_tcflush(__attribute__((unused)) void *ctx, int fd, int select) errno = ENOSYS; return -1; } - return entry->ops->termios->tcflush_p(entry->vfs_ctx, entry->fd, select); + return entry->ops->termios->tcflush_p(entry->vfs_ctx, fd, select); } #endif // CONFIG_VFS_SUPPORT_TERMIOS @@ -678,80 +385,52 @@ static const esp_vfs_fs_ops_t s_vfs_console = { esp_err_t esp_stdio_register(void) { - esp_err_t err = ESP_OK; - _lock_init(&s_ctx_lock); + _lock_init(&s_lock); + // Primary vfs part. #if CONFIG_ESP_CONSOLE_UART - const esp_vfs_fs_ops_t *ops = esp_vfs_uart_get_vfs(); - int fd = ops->open_p(NULL, "/" STRINGIFY(CONFIG_ESP_CONSOLE_UART_NUM), O_RDWR, 0); - if (fd < 0) { - ESP_EARLY_LOGE(TAG, "Failed to open primary UART sink, errno=%d", errno); - return ESP_FAIL; - } - err = esp_stdio_register_sink(ops, - ESP_VFS_FLAG_CONTEXT_PTR | ESP_VFS_FLAG_STATIC, - NULL, - "/" STRINGIFY(CONFIG_ESP_CONSOLE_UART_NUM), - fd); + 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 - const esp_vfs_fs_ops_t *ops = esp_vfs_usb_serial_jtag_get_vfs(); - int fd = ops->open_p(NULL, "/", O_RDWR, 0); - if (fd < 0) { - ESP_EARLY_LOGE(TAG, "Failed to open primary USB JTAG sink, errno=%d", errno); - return ESP_FAIL; - } - err = esp_stdio_register_sink(ops, - ESP_VFS_FLAG_CONTEXT_PTR | ESP_VFS_FLAG_STATIC, - NULL, - "/", - fd); + 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 - const esp_vfs_fs_ops_t *ops = esp_vfs_cdcacm_get_vfs(); - int fd = ops->open_p(NULL, "/", O_RDWR, 0); - if (fd < 0) { - ESP_EARLY_LOGE(TAG, "Failed to open primary USB CDC sink, errno=%d", errno); - return ESP_FAIL; - } - err = esp_stdio_register_sink(ops, - ESP_VFS_FLAG_CONTEXT_PTR | ESP_VFS_FLAG_STATIC, - NULL, - "/", - fd); + s_primary = (mux_entry_t) { + .ops = esp_vfs_cdcacm_get_vfs(), + .vfs_ctx = NULL, + .path = "/", + .fd = -1, + }; #else - const esp_vfs_fs_ops_t *ops = esp_vfs_null_get_vfs(); - int fd = ops->open_p(NULL, "/", O_RDWR, 0); - if (fd < 0) { - ESP_EARLY_LOGE(TAG, "Failed to open primary null sink, errno=%d", errno); - return ESP_FAIL; - } - err = esp_stdio_register_sink(ops, - ESP_VFS_FLAG_CONTEXT_PTR | ESP_VFS_FLAG_STATIC, - NULL, - "/", - fd); + s_primary = (mux_entry_t) { + .ops = esp_vfs_null_get_vfs(), + .vfs_ctx = NULL, + .path = "/", + .fd = -1, + }; #endif - if (err != ESP_OK) { - return err; + if (!s_primary.ops) { + ESP_EARLY_LOGE(TAG, "No primary console backend available"); + return ESP_FAIL; } -// Secondary vfs part. +// Auxiliary sinks (write-only fan-out). #if CONFIG_ESP_CONSOLE_SECONDARY_USB_SERIAL_JTAG - const esp_vfs_fs_ops_t *ops_secondary = esp_vfs_usb_serial_jtag_get_vfs(); - int fd_secondary = ops_secondary->open_p(NULL, "/", O_RDWR, 0); - if (fd_secondary < 0) { - ESP_EARLY_LOGE(TAG, "Failed to open secondary USB JTAG sink, errno=%d", errno); - return ESP_FAIL; - } - err = esp_stdio_register_sink(ops_secondary, - ESP_VFS_FLAG_CONTEXT_PTR | ESP_VFS_FLAG_STATIC, - NULL, - "/", - fd_secondary); + esp_err_t err = register_auxiliary(esp_vfs_usb_serial_jtag_get_vfs(), NULL, "/"); if (err != ESP_OK) { return err; } #endif + return esp_vfs_register_fs(ESP_VFS_DEV_CONSOLE, &s_vfs_console, ESP_VFS_FLAG_STATIC | ESP_VFS_FLAG_CONTEXT_PTR, NULL); } 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 c6f5c8dffd3..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 @@ -52,13 +52,7 @@ static void console_none_print(void) #endif #if CONFIG_VFS_SUPPORT_IO -#if CONFIG_ESP_STDIO_BASIC_MODE #define ESP_STDIO_RUN_OPEN_CLOSE_CHECK 1 -#elif CONFIG_ESP_STDIO_MAX_FDS > 3 -#define ESP_STDIO_RUN_OPEN_CLOSE_CHECK 1 -#else -#define ESP_STDIO_RUN_OPEN_CLOSE_CHECK 0 -#endif #if ESP_STDIO_RUN_OPEN_CLOSE_CHECK static void console_open_close_check(void) @@ -80,122 +74,26 @@ static void console_open_close_check(void) static void stdio_fd_mode_behavior_check(void) { -#if CONFIG_ESP_STDIO_BASIC_MODE - printf("STDIO_TEST:MODE=BASIC\n"); + printf("STDIO_TEST:MODE=FORWARDING\n"); int fd0 = open("/dev/console", O_RDWR); - int fd1 = open("/dev/console", O_RDWR); - int fd2 = open("/dev/console", O_RDWR); assert(fd0 >= 0); - assert(fd1 >= 0); - assert(fd2 >= 0); - printf("STDIO_TEST:BASIC:OPEN_OK\n"); - const char *msg = "STDIO_TEST:BASIC:WRITE_OK\n"; + 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)); - /* Verify write return value matches actual bytes (primary sink propagation) */ - const char *short_msg = "ab"; - wr = write(fd1, short_msg, 2); - assert(wr == 2); + assert(fsync(fd0) == 0); + printf("STDIO_TEST:FSYNC_OK\n"); assert(close(fd0) == 0); assert(close(fd1) == 0); - assert(close(fd2) == 0); - printf("STDIO_TEST:BASIC:CLOSE_OK\n"); - - errno = 0; - assert(close(-1) < 0); - assert(errno == EBADF); - - errno = 0; - assert(write(fd0, msg, strlen(msg)) < 0); - assert(errno == EBADF); - printf("STDIO_TEST:BASIC:EBADF_OK\n"); -#else - printf("STDIO_TEST:MODE=NON_BASIC\n"); - - errno = 0; - int fd0 = open("/dev/console", O_RDWR); - if (fd0 < 0) { - /* Small fd limits can be fully consumed by stdin/stdout/stderr. */ - assert(errno == ENFILE); - printf("STDIO_TEST:NON_BASIC:SATURATED_OK\n"); - return; - } - - int fd1 = open("/dev/console", O_RDWR); - int fd2 = open("/dev/console", O_RDWR); - assert(fd0 >= 0); - assert(fd1 >= 0); - assert(fd2 >= 0); - assert(fd0 != fd1 && fd1 != fd2 && fd0 != fd2); - printf("STDIO_TEST:NON_BASIC:UNIQUE_OK\n"); - - assert(close(fd1) == 0); - int fd_reused = open("/dev/console", O_RDWR); - assert(fd_reused == fd1); - printf("STDIO_TEST:NON_BASIC:REUSE_OK\n"); - assert(close(fd0) == 0); - assert(close(fd2) == 0); - assert(close(fd_reused) == 0); - - int fd_ro = open("/dev/console", O_RDONLY); - assert(fd_ro >= 0); - errno = 0; - assert(write(fd_ro, "x", 1) < 0); - assert(errno == EBADF); - assert(close(fd_ro) == 0); - - int fd_wo = open("/dev/console", O_WRONLY); - assert(fd_wo >= 0); - errno = 0; - char tmp = 0; - assert(read(fd_wo, &tmp, 1) < 0); - assert(errno == EBADF); - assert(close(fd_wo) == 0); - - int fd_rw = open("/dev/console", O_RDWR); - assert(fd_rw >= 0); - const char *wmsg = "z"; - ssize_t wret = write(fd_rw, wmsg, 1); - assert(wret == 1); - assert(close(fd_rw) == 0); - printf("STDIO_TEST:NON_BASIC:FLAGS_OK\n"); - - int fd_fsync = open("/dev/console", O_RDWR); - assert(fd_fsync >= 0); - assert(fsync(fd_fsync) == 0); - assert(close(fd_fsync) == 0); - printf("STDIO_TEST:NON_BASIC:FSYNC_OK\n"); - - int fds[CONFIG_ESP_STDIO_MAX_FDS + 2]; - int opened = 0; - while (opened < (int)(sizeof(fds) / sizeof(fds[0]))) { - int fd = open("/dev/console", O_RDWR); - if (fd < 0) { - break; - } - fds[opened++] = fd; - } - assert(opened >= 0); - assert(opened <= CONFIG_ESP_STDIO_MAX_FDS); - errno = 0; - assert(open("/dev/console", O_RDWR) < 0); - assert(errno == ENFILE); - printf("STDIO_TEST:NON_BASIC:LIMIT_OK\n"); - - for (int i = 0; i < opened; ++i) { - assert(close(fds[i]) == 0); - } - - const char *msg = "x"; - errno = 0; - assert(write(-1, msg, 1) < 0); - assert(errno == EBADF); - printf("STDIO_TEST:NON_BASIC:EBADF_OK\n"); -#endif + printf("STDIO_TEST:CLOSE_OK\n"); } static void stdio_fd_init_check(void) 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 61a7549a180..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 @@ -97,13 +97,11 @@ def test_esp_system_stdio_correct_open_and_close(dut: Dut) -> None: 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=NON_BASIC') - dut.expect('STDIO_TEST:NON_BASIC:UNIQUE_OK') - dut.expect('STDIO_TEST:NON_BASIC:REUSE_OK') - dut.expect('STDIO_TEST:NON_BASIC:FLAGS_OK') - dut.expect('STDIO_TEST:NON_BASIC:FSYNC_OK') - dut.expect('STDIO_TEST:NON_BASIC:LIMIT_OK') - dut.expect('STDIO_TEST:NON_BASIC:EBADF_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') @pytest.mark.host_test @@ -117,74 +115,8 @@ 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=NON_BASIC') - dut.expect('STDIO_TEST:NON_BASIC:UNIQUE_OK') - dut.expect('STDIO_TEST:NON_BASIC:REUSE_OK') - dut.expect('STDIO_TEST:NON_BASIC:FLAGS_OK') - dut.expect('STDIO_TEST:NON_BASIC:FSYNC_OK') - dut.expect('STDIO_TEST:NON_BASIC:LIMIT_OK') - dut.expect('STDIO_TEST:NON_BASIC:EBADF_OK') - - -@pytest.mark.host_test -@pytest.mark.qemu -@pytest.mark.parametrize( - 'config, embedded_services', - [('stdio_non_basic_small_fd', 'idf,qemu')], -) -@idf_parametrize('target', ['esp32'], indirect=['target']) -def test_esp_stdio_non_basic_small_fd_mode_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=NON_BASIC') - dut.expect('STDIO_TEST:NON_BASIC:SATURATED_OK') - - -@pytest.mark.host_test -@pytest.mark.qemu -@pytest.mark.parametrize( - 'config, embedded_services', - [('stdio_basic_mode', 'idf,qemu')], -) -@idf_parametrize('target', ['esp32'], indirect=['target']) -def test_esp_stdio_basic_mode_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=BASIC') - dut.expect('STDIO_TEST:BASIC:OPEN_OK') - dut.expect('STDIO_TEST:BASIC:WRITE_OK') - dut.expect('STDIO_TEST:BASIC:CLOSE_OK') - dut.expect('STDIO_TEST:BASIC:EBADF_OK') - - -@pytest.mark.generic -@idf_parametrize('config', ['stdio_non_basic_small_fd'], indirect=['config']) -@idf_parametrize('target', ['supported_targets'], indirect=['target']) -@pytest.mark.temp_skip_ci(targets=['esp32p4'], reason='p4 rev3 migration') -def test_esp_stdio_non_basic_small_fd_mode(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:SELECT:LOGICAL_FD_MAP_OK') - dut.expect('STDIO_TEST:MODE=NON_BASIC') - dut.expect('STDIO_TEST:NON_BASIC:SATURATED_OK') - - -@pytest.mark.generic -@idf_parametrize('config', ['stdio_basic_mode'], indirect=['config']) -@idf_parametrize('target', ['supported_targets'], indirect=['target']) -@pytest.mark.temp_skip_ci(targets=['esp32p4'], reason='p4 rev3 migration') -def test_esp_stdio_basic_mode(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:SELECT:LOGICAL_FD_MAP_OK') - dut.expect('STDIO_TEST:MODE=BASIC') - dut.expect('STDIO_TEST:BASIC:OPEN_OK') - dut.expect('STDIO_TEST:BASIC:WRITE_OK') - dut.expect('STDIO_TEST:BASIC:CLOSE_OK') - dut.expect('STDIO_TEST:BASIC:EBADF_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/esp_stdio/test_apps/stdio/sdkconfig.ci.stdio_basic_mode b/components/esp_stdio/test_apps/stdio/sdkconfig.ci.stdio_basic_mode deleted file mode 100644 index 25d7a763ddc..00000000000 --- a/components/esp_stdio/test_apps/stdio/sdkconfig.ci.stdio_basic_mode +++ /dev/null @@ -1 +0,0 @@ -CONFIG_ESP_STDIO_BASIC_MODE=y diff --git a/components/esp_stdio/test_apps/stdio/sdkconfig.ci.stdio_non_basic_small_fd b/components/esp_stdio/test_apps/stdio/sdkconfig.ci.stdio_non_basic_small_fd deleted file mode 100644 index 3703a0c45a6..00000000000 --- a/components/esp_stdio/test_apps/stdio/sdkconfig.ci.stdio_non_basic_small_fd +++ /dev/null @@ -1,2 +0,0 @@ -CONFIG_ESP_STDIO_BASIC_MODE=n -CONFIG_ESP_STDIO_MAX_FDS=3