mirror of
https://github.com/espressif/esp-idf.git
synced 2026-10-02 03:00:34 +03:00
refactor(stdio): remove fd table, forward opens to primary backend
Remove the internal fd table (fd_entry_t, is_fd_valid, fd_allows_read/write,
ESP_STDIO_BASIC_MODE tracking) from the console VFS.
Instead, console_open forwards directly to the primary backend's open_p,
so each open("/dev/console") creates a real fd in the underlying driver.
Read/write/fstat/fcntl/fsync all operate on the per-open fd from the
primary, not a stored init-time fd.
Structure:
- s_primary: separate variable for the primary backend entry
- s_auxiliary[]: slot-based array for auxiliary sinks (NULL = free)
- Scans all slots (no count variable) with NULL guards
- Auxiliaries opened lazily on first console open
- Closed when last console fd is closed (refcounted via s_open_count)
- Write and fsync fan-out protected by s_lock
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
CONFIG_ESP_STDIO_BASIC_MODE=y
|
||||
@@ -1,2 +0,0 @@
|
||||
CONFIG_ESP_STDIO_BASIC_MODE=n
|
||||
CONFIG_ESP_STDIO_MAX_FDS=3
|
||||
Reference in New Issue
Block a user