mirror of
https://github.com/espressif/esp-idf.git
synced 2026-10-02 11:10:54 +03:00
Merge branch 'feat/refactor_stdio_vfs_v6.1' into 'release/v6.1'
feat(storage/stdio): refactor stdio vfs (v6.1) See merge request espressif/esp-idf!48957
This commit is contained in:
@@ -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());
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -5,10 +5,15 @@
|
||||
*/
|
||||
|
||||
#include "sdkconfig.h"
|
||||
#include <assert.h>
|
||||
#include <stdbool.h>
|
||||
#include <fcntl.h>
|
||||
#include <string.h>
|
||||
#include "esp_err.h"
|
||||
#include "esp_log.h"
|
||||
#include "esp_rom_sys.h"
|
||||
#include "esp_stdio.h"
|
||||
#include "esp_vfs.h"
|
||||
#include <sys/errno.h>
|
||||
|
||||
#if CONFIG_VFS_SUPPORT_IO
|
||||
@@ -30,6 +35,8 @@
|
||||
|
||||
#include "esp_private/startup_internal.h"
|
||||
#include "esp_private/nullfs.h"
|
||||
#include <sys/lock.h>
|
||||
#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)
|
||||
|
||||
@@ -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 <stdio.h>
|
||||
#include <assert.h>
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
#include "sdkconfig.h"
|
||||
|
||||
#include "esp_rom_serial_output.h"
|
||||
@@ -18,6 +19,7 @@
|
||||
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/select.h>
|
||||
|
||||
#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
|
||||
|
||||
}
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user