diff --git a/include/zephyr/posix/unistd.h b/include/zephyr/posix/unistd.h index 2f07aa636e181..ddf65b97d7720 100644 --- a/include/zephyr/posix/unistd.h +++ b/include/zephyr/posix/unistd.h @@ -25,7 +25,9 @@ extern "C" { /* File related operations */ int close(int file); ssize_t write(int file, const void *buffer, size_t count); +ssize_t pwrite(int file, const void *buffer, size_t count, off_t offset); ssize_t read(int file, void *buffer, size_t count); +ssize_t pread(int fd, void *buf, size_t count, off_t offset); off_t lseek(int file, off_t offset, int whence); int fsync(int fd); int ftruncate(int fd, off_t length); diff --git a/include/zephyr/sys/fdtable.h b/include/zephyr/sys/fdtable.h index d4153828fbb2d..50ca50b50b37f 100644 --- a/include/zephyr/sys/fdtable.h +++ b/include/zephyr/sys/fdtable.h @@ -14,6 +14,20 @@ #include #include +/* FIXME: use k_off_t and k_ssize_t to avoid the POSIX->Zephyr->POSIX dependency cycle */ +#ifdef CONFIG_NEWLIB_LIBC +#ifndef _OFF_T_DECLARED +typedef _off_t off_t; +#define _OFF_T_DECLARED +#endif +#ifndef _SSIZE_T_DECLARED +typedef _ssize_t ssize_t; +#define _SSIZE_T_DECLARED +#endif +#endif + +#include + #ifdef CONFIG_PICOLIBC #define ZVFS_O_APPEND 0x0400 #define ZVFS_O_CREAT 0x0040 @@ -60,18 +74,6 @@ extern "C" { #endif -/* FIXME: use k_off_t and k_ssize_t to avoid the POSIX->Zephyr->POSIX dependency cycle */ -#ifdef CONFIG_NEWLIB_LIBC -#ifndef _OFF_T_DECLARED -typedef __off_t off_t; -#define _OFF_T_DECLARED -#endif -#ifndef _SSIZE_T_DECLARED -typedef _ssize_t ssize_t; -#define _SSIZE_T_DECLARED -#endif -#endif - /** * File descriptor virtual method table. * Currently all operations beyond read/write/close go thru ioctl method. @@ -261,6 +263,12 @@ __syscall int zvfs_select(int nfds, struct zvfs_fd_set *ZRESTRICT readfds, struct zvfs_fd_set *ZRESTRICT errorfds, const struct timespec *ZRESTRICT timeout, const void *ZRESTRICT sigmask); +void zvfs_libc_file_alloc_cb(int fd, const char *mode, FILE *fp); +int zvfs_libc_file_alloc(int fd, const char *mode, FILE **fp, k_timeout_t timeout); +void zvfs_libc_file_free(FILE *fp); +int zvfs_libc_file_get_fd(FILE *fp); +FILE *zvfs_libc_file_from_fd(int fd); + /** * Request codes for fd_op_vtable.ioctl(). * diff --git a/lib/libc/common/CMakeLists.txt b/lib/libc/common/CMakeLists.txt index ed3d28fe5a622..5357c0903da43 100644 --- a/lib/libc/common/CMakeLists.txt +++ b/lib/libc/common/CMakeLists.txt @@ -20,6 +20,7 @@ zephyr_library_sources_ifdef(CONFIG_COMMON_LIBC_THRD source/thrd/tss.c ) zephyr_library_sources_ifdef(CONFIG_COMMON_LIBC_REMOVE source/stdio/remove.c) +zephyr_library_sources_ifdef(CONFIG_COMMON_LIBC_FCLOSE source/stdio/fclose.c) # Prevent compiler from optimizing calloc into an infinite recursive call zephyr_library_compile_options($) diff --git a/lib/libc/common/Kconfig b/lib/libc/common/Kconfig index c4ac88dbab1cd..b98faaef6ac1f 100644 --- a/lib/libc/common/Kconfig +++ b/lib/libc/common/Kconfig @@ -109,7 +109,7 @@ config COMMON_LIBC_THRD depends on DYNAMIC_THREAD # Note: the POSIX_API dependency is only necessary until common elements # of C11 threads and POSIX API can be abstracted out to a common library. - depends on POSIX_API + depends on POSIX_THREADS default y help Common implementation of C11 API. @@ -120,3 +120,9 @@ config COMMON_LIBC_REMOVE default y if FILE_SYSTEM help Common implementation of remove(). + +config COMMON_LIBC_FCLOSE + bool "Common C library fclose" + default y if ZVFS && !MINIMAL_LIBC + help + Enable the common C library implementation of fclose() diff --git a/lib/libc/common/source/stdio/fclose.c b/lib/libc/common/source/stdio/fclose.c new file mode 100644 index 0000000000000..11c8e704f0129 --- /dev/null +++ b/lib/libc/common/source/stdio/fclose.c @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2025 Antmicro + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include + +int fclose(FILE *stream) +{ + int fd; + + fflush(stream); + fd = zvfs_libc_file_get_fd(stream); + + if (!fd) { + return EOF; + } + if (close(fd)) { + return EOF; + } + + return 0; +} diff --git a/lib/libc/minimal/source/stdout/stdout_console.c b/lib/libc/minimal/source/stdout/stdout_console.c index 9cef4afc873d0..dac63a5e80543 100644 --- a/lib/libc/minimal/source/stdout/stdout_console.c +++ b/lib/libc/minimal/source/stdout/stdout_console.c @@ -107,6 +107,28 @@ static inline size_t z_vrfy_zephyr_fwrite(const void *ZRESTRICT ptr, #include #endif +int z_impl_zephyr_write_stdout(const void *buffer, int nbytes) +{ + const char *buf = buffer; + + for (int i = 0; i < nbytes; i++) { + if (*(buf + i) == '\n') { + _stdout_hook('\r'); + } + _stdout_hook(*(buf + i)); + } + return nbytes; +} + +#ifdef CONFIG_USERSPACE +static inline int z_vrfy_zephyr_write_stdout(const void *buf, int nbytes) +{ + K_OOPS(K_SYSCALL_MEMORY_READ(buf, nbytes)); + return z_impl_zephyr_write_stdout((const void *)buf, nbytes); +} +#include +#endif + size_t fwrite(const void *ZRESTRICT ptr, size_t size, size_t nitems, FILE *ZRESTRICT stream) { diff --git a/lib/libc/newlib/CMakeLists.txt b/lib/libc/newlib/CMakeLists.txt index d3dc448eccafc..97af247e6d77f 100644 --- a/lib/libc/newlib/CMakeLists.txt +++ b/lib/libc/newlib/CMakeLists.txt @@ -3,6 +3,8 @@ zephyr_library() zephyr_library_sources(libc-hooks.c) +zephyr_library_sources_ifdef(CONFIG_ZVFS zvfs_libc.c) + # Do not allow LTO when compiling libc-hooks.c file set_source_files_properties(libc-hooks.c PROPERTIES COMPILE_OPTIONS $) diff --git a/lib/libc/newlib/Kconfig b/lib/libc/newlib/Kconfig index 24c114f35f9fa..6359d71496b01 100644 --- a/lib/libc/newlib/Kconfig +++ b/lib/libc/newlib/Kconfig @@ -80,4 +80,12 @@ config NEWLIB_LIBC_USE_POSIX_LIMITS_H Disable this option if your toolchain's newlib already includes all mandatory POSIX limits. +if ZVFS + +config ZVFS_LIBC_FILE_SIZE + default 184 if 64BIT + default 104 + +endif # ZVFS + endif # NEWLIB_LIBC diff --git a/lib/libc/newlib/libc-hooks.c b/lib/libc/newlib/libc-hooks.c index 4ec1887f1cb54..e690006db096d 100644 --- a/lib/libc/newlib/libc-hooks.c +++ b/lib/libc/newlib/libc-hooks.c @@ -169,6 +169,9 @@ static int (*_stdout_hook)(int) = _stdout_hook_default; void __stdout_hook_install(int (*hook)(int)) { _stdout_hook = hook; +#ifdef CONFIG_ZVFS + stdout->_file = 1; /* STDOUT_FILENO */ +#endif } static unsigned char _stdin_hook_default(void) @@ -181,6 +184,9 @@ static unsigned char (*_stdin_hook)(void) = _stdin_hook_default; void __stdin_hook_install(unsigned char (*hook)(void)) { _stdin_hook = hook; +#ifdef CONFIG_ZVFS + stdin->_file = 0; /* STDIN_FILENO */ +#endif } int z_impl_zephyr_read_stdin(char *buf, int nbytes) diff --git a/lib/libc/newlib/zvfs_libc.c b/lib/libc/newlib/zvfs_libc.c new file mode 100644 index 0000000000000..b8f697e4842cd --- /dev/null +++ b/lib/libc/newlib/zvfs_libc.c @@ -0,0 +1,82 @@ +/* + * Copyright (c) 2024 Tenstorrent AI ULC + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include + +#include +#include + +BUILD_ASSERT(CONFIG_ZVFS_LIBC_FILE_SIZE >= sizeof(__FILE), + "CONFIG_ZVFS_LIBC_FILE_SIZE must at least match size of FILE object"); +BUILD_ASSERT(CONFIG_ZVFS_LIBC_FILE_ALIGN == __alignof(__FILE), + "CONFIG_ZVFS_LIBC_FILE_SIZE must match alignment of FILE object"); + +static int z_libc_sflags(const char *mode) +{ + int ret = 0; + + switch (mode[0]) { + case 'r': + ret = ZVFS_O_RDONLY; + break; + + case 'w': + ret = ZVFS_O_WRONLY | ZVFS_O_CREAT | ZVFS_O_TRUNC; + break; + + case 'a': + ret = ZVFS_O_WRONLY | ZVFS_O_CREAT | ZVFS_O_APPEND; + break; + default: + return 0; + } + + while (*++mode) { + switch (*mode) { + case '+': + ret |= ZVFS_O_RDWR; + break; + case 'x': + ret |= ZVFS_O_EXCL; + break; + default: + break; + } + } + + return ret; +} + +void zvfs_libc_file_alloc_cb(int fd, const char *mode, FILE *fp) +{ + /* + * These symbols have conflicting declarations in upstream headers. + * Use uintptr_t and a cast to assign cleanly. + */ + extern uintptr_t _close_r; + extern uintptr_t _lseek_r; + extern uintptr_t _read_r; + extern uintptr_t _write_r; + + __ASSERT_NO_MSG(mode != NULL); + __ASSERT_NO_MSG(fp != NULL); + + fp->_flags = z_libc_sflags(mode); + fp->_file = fd; + fp->_cookie = (void *)fp; + + *(uintptr_t *)&fp->_read = (uintptr_t)&_read_r; + *(uintptr_t *)&fp->_write = (uintptr_t)&_write_r; + *(uintptr_t *)&fp->_seek = (uintptr_t)&_lseek_r; + *(uintptr_t *)&fp->_close = (uintptr_t)&_close_r; +} + +int zvfs_libc_file_get_fd(FILE *fp) +{ + return fp->_file; +} diff --git a/lib/libc/picolibc/CMakeLists.txt b/lib/libc/picolibc/CMakeLists.txt index 752379dd76d44..92bf2852bbfc2 100644 --- a/lib/libc/picolibc/CMakeLists.txt +++ b/lib/libc/picolibc/CMakeLists.txt @@ -11,6 +11,8 @@ zephyr_library_sources( stdio.c ) +zephyr_library_sources_ifdef(CONFIG_ZVFS zvfs_libc.c) + zephyr_library_compile_options($) # define __LINUX_ERRNO_EXTENSIONS__ so we get errno defines like -ESHUTDOWN diff --git a/lib/libc/picolibc/Kconfig b/lib/libc/picolibc/Kconfig index be30e6a8f3a60..e9703f48d1de2 100644 --- a/lib/libc/picolibc/Kconfig +++ b/lib/libc/picolibc/Kconfig @@ -200,4 +200,12 @@ config PICOLIBC_GLOBAL_ERRNO endif # PICOLIBC_USE_MODULE +if ZVFS + +config ZVFS_LIBC_FILE_SIZE + default 144 if 64BIT + default 80 + +endif # ZVFS + endif # PICOLIBC diff --git a/lib/libc/picolibc/stdio.c b/lib/libc/picolibc/stdio.c index 342eedc877101..74608ded862af 100644 --- a/lib/libc/picolibc/stdio.c +++ b/lib/libc/picolibc/stdio.c @@ -5,13 +5,20 @@ */ #include "picolibc-hooks.h" +#include "stdio-bufio.h" -static LIBC_DATA int (*_stdout_hook)(int); +static int _stdout_hook_default(int c) +{ + (void)(c); /* Prevent warning about unused argument */ + + return EOF; +} + +static LIBC_DATA int (*_stdout_hook)(int) = _stdout_hook_default; int z_impl_zephyr_fputc(int a, FILE *out) { - (*_stdout_hook)(a); - return 0; + return ((out == stdout) || (out == stderr)) ? _stdout_hook(a) : EOF; } #ifdef CONFIG_USERSPACE @@ -24,12 +31,13 @@ static inline int z_vrfy_zephyr_fputc(int c, FILE *stream) static int picolibc_put(char a, FILE *f) { - zephyr_fputc(a, f); - return 0; + return zephyr_fputc(a, f); } -static LIBC_DATA FILE __stdout = FDEV_SETUP_STREAM(picolibc_put, NULL, NULL, 0); -static LIBC_DATA FILE __stdin = FDEV_SETUP_STREAM(NULL, NULL, NULL, 0); +#ifndef CONFIG_ZVFS +static LIBC_DATA FILE __stdout = FDEV_SETUP_STREAM(picolibc_put, NULL, NULL, _FDEV_SETUP_WRITE); +static LIBC_DATA FILE __stdin = FDEV_SETUP_STREAM(NULL, NULL, NULL, _FDEV_SETUP_READ); +#endif #ifdef __strong_reference #define STDIO_ALIAS(x) __strong_reference(stdout, x); @@ -37,18 +45,56 @@ static LIBC_DATA FILE __stdin = FDEV_SETUP_STREAM(NULL, NULL, NULL, 0); #define STDIO_ALIAS(x) FILE *const x = &__stdout; #endif +#ifndef CONFIG_ZVFS FILE *const stdin = &__stdin; FILE *const stdout = &__stdout; STDIO_ALIAS(stderr); +#endif void __stdout_hook_install(int (*hook)(int)) { _stdout_hook = hook; - __stdout.flags |= _FDEV_SETUP_WRITE; +#ifdef CONFIG_ZVFS + stdout->put = picolibc_put; + stdout->flags |= _FDEV_SETUP_WRITE; + + struct __file_bufio *bp = (struct __file_bufio *)stdout; + + bp->ptr = INT_TO_POINTER(1 /* STDOUT_FILENO */); + bp->bflags |= _FDEV_SETUP_WRITE; +#endif } void __stdin_hook_install(unsigned char (*hook)(void)) { - __stdin.get = (int (*)(FILE *)) hook; - __stdin.flags |= _FDEV_SETUP_READ; + stdin->get = (int (*)(FILE *))hook; +#ifdef CONFIG_ZVFS + stdin->flags |= _FDEV_SETUP_READ; + struct __file_bufio *bp = (struct __file_bufio *)stdin; + + bp->bflags |= _FDEV_SETUP_READ; + bp->ptr = INT_TO_POINTER(0 /* STDIN_FILENO */); +#endif +} + +int z_impl_zephyr_write_stdout(const void *buffer, int nbytes) +{ + const char *buf = buffer; + + for (int i = 0; i < nbytes; i++) { + if (*(buf + i) == '\n') { + _stdout_hook('\r'); + } + _stdout_hook(*(buf + i)); + } + return nbytes; +} + +#ifdef CONFIG_USERSPACE +static inline int z_vrfy_zephyr_write_stdout(const void *buf, int nbytes) +{ + K_OOPS(K_SYSCALL_MEMORY_READ(buf, nbytes)); + return z_impl_zephyr_write_stdout((const void *)buf, nbytes); } +#include +#endif diff --git a/lib/libc/picolibc/zvfs_libc.c b/lib/libc/picolibc/zvfs_libc.c new file mode 100644 index 0000000000000..470aad20c2337 --- /dev/null +++ b/lib/libc/picolibc/zvfs_libc.c @@ -0,0 +1,85 @@ +/* + * Copyright (c) 2024 Tenstorrent AI ULC + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "stdio-bufio.h" + +#include +#include +#include + +#include +#include + +BUILD_ASSERT(CONFIG_ZVFS_LIBC_FILE_SIZE >= sizeof(struct __file_bufio), + "CONFIG_ZVFS_LIBC_FILE_SIZE must at least match size of FILE object"); +BUILD_ASSERT(CONFIG_ZVFS_LIBC_FILE_ALIGN == __alignof(struct __file_bufio), + "CONFIG_ZVFS_LIBC_FILE_SIZE must match alignment of FILE object"); + +#define FDEV_SETUP_ZVFS(fd, buf, size, rwflags, bflags) \ + FDEV_SETUP_BUFIO(fd, buf, size, zvfs_read_wrap, zvfs_write_wrap, zvfs_lseek, zvfs_close, \ + rwflags, bflags) + +/* FIXME: do not use ssize_t or off_t */ +ssize_t zvfs_read(int fd, void *buf, size_t sz, const size_t *from_offset); +ssize_t zvfs_write(int fd, const void *buf, size_t sz, const size_t *from_offset); +off_t zvfs_lseek(int fd, off_t offset, int whence); +int zvfs_close(int fd); + +static ssize_t zvfs_read_wrap(int fd, void *buf, size_t sz) +{ + return zvfs_read(fd, buf, sz, NULL); +} + +static ssize_t zvfs_write_wrap(int fd, const void *buf, size_t sz) +{ + return zvfs_write(fd, buf, sz, NULL); +} + +static int sflags(const char *mode) +{ + int flags = 0; + + if (mode == NULL) { + return 0; + } + + switch (mode[0]) { + case 'r': + flags |= __SRD; + break; + case 'w': + flags |= __SWR; + break; + case 'a': + flags |= __SWR; + break; + default: + /* ignore */ + break; + } + if (mode[1] == '+') { + flags |= __SRD | __SWR; + } + + return flags; +} + +void zvfs_libc_file_alloc_cb(int fd, const char *mode, FILE *fp) +{ + struct __file_bufio *const bf = (struct __file_bufio *)fp; + + *bf = (struct __file_bufio)FDEV_SETUP_ZVFS(fd, (char *)(bf + 1), BUFSIZ, sflags(mode), + __BFALL); + + __bufio_lock_init(&(bf->xfile.cfile.file)); +} + +int zvfs_libc_file_get_fd(FILE *fp) +{ + const struct __file_bufio *const bf = (const struct __file_bufio *)fp; + + return POINTER_TO_INT(bf->ptr); +} diff --git a/lib/os/fdtable.c b/lib/os/fdtable.c index be53627efca7b..e7c3fc431ff90 100644 --- a/lib/os/fdtable.c +++ b/lib/os/fdtable.c @@ -23,6 +23,10 @@ #include #include #include +#include +#include +#include +#include struct stat; @@ -42,6 +46,12 @@ static const struct fd_op_vtable stdinout_fd_op_vtable; BUILD_ASSERT(CONFIG_ZVFS_OPEN_MAX >= 3, "CONFIG_ZVFS_OPEN_MAX >= 3 for CONFIG_POSIX_DEVICE_IO"); #endif /* defined(CONFIG_POSIX_DEVICE_IO) */ +#if defined(CONFIG_POSIX_DEVICE_IO_STDIO_CONSOLE) +static struct tty_serial tty; +static uint8_t tty_rxbuf[CONFIG_POSIX_DEVICE_IO_STDIN_BUFSIZE]; +static uint8_t tty_txbuf[CONFIG_POSIX_DEVICE_IO_STDOUT_BUFSIZE]; +#endif + static struct fd_entry fdtable[CONFIG_ZVFS_OPEN_MAX] = { #if defined(CONFIG_POSIX_DEVICE_IO) /* @@ -49,6 +59,9 @@ static struct fd_entry fdtable[CONFIG_ZVFS_OPEN_MAX] = { */ { /* STDIN */ +#if defined(CONFIG_POSIX_DEVICE_IO_STDIO_CONSOLE) + .obj = &tty, +#endif .vtable = &stdinout_fd_op_vtable, .refcount = ATOMIC_INIT(1), .lock = Z_MUTEX_INITIALIZER(fdtable[0].lock), @@ -56,6 +69,9 @@ static struct fd_entry fdtable[CONFIG_ZVFS_OPEN_MAX] = { }, { /* STDOUT */ +#if defined(CONFIG_POSIX_DEVICE_IO_STDIO_CONSOLE) + .obj = &tty, +#endif .vtable = &stdinout_fd_op_vtable, .refcount = ATOMIC_INIT(1), .lock = Z_MUTEX_INITIALIZER(fdtable[1].lock), @@ -63,6 +79,9 @@ static struct fd_entry fdtable[CONFIG_ZVFS_OPEN_MAX] = { }, { /* STDERR */ +#if defined(CONFIG_POSIX_DEVICE_IO_STDIO_CONSOLE) + .obj = &tty, +#endif .vtable = &stdinout_fd_op_vtable, .refcount = ATOMIC_INIT(1), .lock = Z_MUTEX_INITIALIZER(fdtable[2].lock), @@ -75,6 +94,47 @@ static struct fd_entry fdtable[CONFIG_ZVFS_OPEN_MAX] = { static K_MUTEX_DEFINE(fdtable_lock); +#ifdef CONFIG_MINIMAL_LIBC +void zvfs_libc_file_alloc_cb(int fd, const char *mode, FILE *fp) +{ + ARG_UNUSED(fd); + ARG_UNUSED(mode); + ARG_UNUSED(fp); +} + +int zvfs_libc_file_alloc(int fd, const char *mode, FILE **fp, k_timeout_t timeout) +{ + ARG_UNUSED(mode); + ARG_UNUSED(timeout); + + __ASSERT_NO_MSG(mode != NULL); + __ASSERT_NO_MSG(fp != NULL); + + *fp = (FILE *)&fdtable[fd]; + + return 0; +} + +void zvfs_libc_file_free(FILE *fp) +{ + ARG_UNUSED(fp); +} + +int zvfs_libc_file_get_fd(FILE *fp) +{ + return (const struct fd_entry *)fp - fdtable; +} + +FILE *zvfs_libc_file_from_fd(int fd) +{ + if ((fd < 0) || (fd >= ARRAY_SIZE(fdtable))) { + return NULL; + } + + return (FILE *)&fdtable[fd]; +} +#endif + static int z_fd_ref(int fd) { return atomic_inc(&fdtable[fd].refcount) + 1; @@ -337,7 +397,7 @@ static ssize_t zvfs_rw(int fd, void *buf, size_t sz, bool is_write, const size_t * Seekable file types should support pread() / pwrite() and per-fd offset passing. * Otherwise, it's a bug. */ - errno = ENOTSUP; + errno = ESPIPE; res = -1; goto unlock; } @@ -406,6 +466,7 @@ int zvfs_close(int fd) } k_mutex_unlock(&fdtable[fd].lock); + zvfs_libc_file_free(zvfs_libc_file_from_fd(fd)); zvfs_free_fd(fd); return res; @@ -413,23 +474,33 @@ int zvfs_close(int fd) FILE *zvfs_fdopen(int fd, const char *mode) { - ARG_UNUSED(mode); + int ret; + FILE *fp = NULL; if (_check_fd(fd) < 0) { return NULL; } - return (FILE *)&fdtable[fd]; + ret = zvfs_libc_file_alloc(fd, mode, &fp, K_NO_WAIT); + if (ret < 0) { + errno = ENOMEM; + } else { + __ASSERT(fp != NULL, "zvfs_libc_file_alloc() returned an invalid file pointer"); + } + + return fp; } int zvfs_fileno(FILE *file) { - if (!IS_ARRAY_ELEMENT(fdtable, file)) { + int fd = zvfs_libc_file_get_fd(file); + + if (fd < 0 || fd >= ARRAY_SIZE(fdtable)) { errno = EBADF; return -1; } - return (struct fd_entry *)file - fdtable; + return fd; } int zvfs_fstat(int fd, struct stat *buf) @@ -541,16 +612,66 @@ int zvfs_ioctl(int fd, unsigned long request, va_list args) * fd operations for stdio/stdout/stderr */ +int z_impl_zephyr_read_stdin(char *buf, int nbytes); int z_impl_zephyr_write_stdout(const char *buf, int nbytes); +#if defined(CONFIG_POSIX_DEVICE_IO_STDIO_CONSOLE) +static int initialize_tty(struct tty_serial *obj) +{ + static bool initialized; + static int ret; + + if (initialized) { + return ret; + } + ret = tty_init(obj, DEVICE_DT_GET(DT_CHOSEN(zephyr_console))); + initialized = true; + if (ret) { + errno = -ret; + ret = -1; + return ret; + } + tty_set_tx_buf(obj, tty_txbuf, sizeof(tty_txbuf)); + tty_set_rx_buf(obj, tty_rxbuf, sizeof(tty_rxbuf)); + return ret; +} +#endif + static ssize_t stdinout_read_vmeth(void *obj, void *buffer, size_t count) { +#if defined(CONFIG_POSIX_DEVICE_IO_STDIO_CONSOLE) + int ret = initialize_tty(obj); + + if (ret) { + return ret; + } + ret = tty_read(obj, buffer, count); + if (ret < -1) { /* return no less than -1 as per POSIX; errno is already set */ + return -1; + } + return ret; +#elif defined(CONFIG_NEWLIB_LIBC) + return z_impl_zephyr_read_stdin(buffer, count); +#else return 0; +#endif } static ssize_t stdinout_write_vmeth(void *obj, const void *buffer, size_t count) { -#if defined(CONFIG_NEWLIB_LIBC) || defined(CONFIG_ARCMWDT_LIBC) +#if defined(CONFIG_POSIX_DEVICE_IO_STDIO_CONSOLE) + int ret = initialize_tty(obj); + + if (ret) { + return ret; + } + ret = tty_write(obj, buffer, count); + if (ret < -1) { /* return no less than -1 as per POSIX; errno is already set */ + return -1; + } + return ret; +#elif defined(CONFIG_ARCMWDT_LIBC) || defined(CONFIG_MINIMAL_LIBC) || \ + defined(CONFIG_NEWLIB_LIBC) || defined(CONFIG_PICOLIBC) return z_impl_zephyr_write_stdout(buffer, count); #else return 0; @@ -559,10 +680,69 @@ static ssize_t stdinout_write_vmeth(void *obj, const void *buffer, size_t count) static int stdinout_ioctl_vmeth(void *obj, unsigned int request, va_list args) { - errno = EINVAL; - return -1; -} +#if defined(CONFIG_POSIX_DEVICE_IO_STDIO_CONSOLE) + struct tty_serial *cons = obj; + if (initialize_tty(cons)) { + return -1; + } +#else + request = 0xffff; +#endif + switch (request) { + case ZFD_IOCTL_POLL_PREPARE: { + __maybe_unused struct zvfs_pollfd *pfd = va_arg(args, struct zvfs_pollfd *); + struct k_poll_event **pev = va_arg(args, struct k_poll_event **); + struct k_poll_event *pev_end = va_arg(args, struct k_poll_event *); + + if (*pev == pev_end) { + return -ENOMEM; + } +#if CONFIG_POSIX_DEVICE_IO_STDIN_BUFSIZE + CONFIG_POSIX_DEVICE_IO_STDOUT_BUFSIZE > 0 + if ((pfd->fd == 0) && (pfd->events & ZVFS_POLLIN)) { + (*pev)->type = K_POLL_TYPE_SEM_AVAILABLE; + (*pev)->state = K_POLL_STATE_NOT_READY; + (*pev)->sem = &(cons->rx_sem); + } + if (((pfd->fd == 1) || (pfd->fd == 2)) && (pfd->events & ZVFS_POLLOUT)) { + (*pev)->type = K_POLL_TYPE_SEM_AVAILABLE; + (*pev)->state = K_POLL_STATE_NOT_READY; + (*pev)->sem = &(cons->tx_sem); + } +#else + (*pev)->type = K_POLL_TYPE_IGNORE; + (*pev)->state = K_POLL_STATE_NOT_READY; + (*pev)->obj = NULL; +#endif + (*pev)++; + return 0; + } + case ZFD_IOCTL_POLL_UPDATE: { + struct zvfs_pollfd *pfd = va_arg(args, struct zvfs_pollfd *); + struct k_poll_event **pev = va_arg(args, struct k_poll_event **); + +#if CONFIG_POSIX_DEVICE_IO_STDIN_BUFSIZE + CONFIG_POSIX_DEVICE_IO_STDOUT_BUFSIZE > 0 + if ((*pev)->state & K_POLL_STATE_SEM_AVAILABLE) { + if ((pfd->fd == 0) && (pfd->events & ZVFS_POLLIN)) { + pfd->revents |= ZVFS_POLLIN; + } + if (((pfd->fd == 1) || (pfd->fd == 2)) && (pfd->events & ZVFS_POLLOUT)) { + pfd->revents |= ZVFS_POLLOUT; + } + } +#else + if (pfd->events & (ZVFS_POLLIN | ZVFS_POLLPRI | ZVFS_POLLOUT)) { + pfd->revents |= ZVFS_POLLNVAL; + } +#endif + (*pev)++; + return 0; + } + default: + errno = EINVAL; + return -1; + } +} static const struct fd_op_vtable stdinout_fd_op_vtable = { .read = stdinout_read_vmeth, diff --git a/lib/os/zvfs/CMakeLists.txt b/lib/os/zvfs/CMakeLists.txt index d855d1005efc6..c9120e697a864 100644 --- a/lib/os/zvfs/CMakeLists.txt +++ b/lib/os/zvfs/CMakeLists.txt @@ -1,6 +1,7 @@ # SPDX-License-Identifier: Apache-2.0 zephyr_library() +zephyr_library_sources_ifndef(CONFIG_MINIMAL_LIBC zvfs_libc_file.c) zephyr_library_sources_ifdef(CONFIG_ZVFS_EVENTFD zvfs_eventfd.c) zephyr_library_sources_ifdef(CONFIG_ZVFS_POLL zvfs_poll.c) zephyr_library_sources_ifdef(CONFIG_ZVFS_SELECT zvfs_select.c) diff --git a/lib/os/zvfs/Kconfig b/lib/os/zvfs/Kconfig index 794c15142db26..ec96025f98a21 100644 --- a/lib/os/zvfs/Kconfig +++ b/lib/os/zvfs/Kconfig @@ -7,6 +7,7 @@ menuconfig ZVFS bool "Zephyr virtual filesystem (ZVFS) support [EXPERIMENTAL]" select FDTABLE select EXPERIMENTAL + select NATIVE_LIBC_INCOMPATIBLE help ZVFS is a central, Zephyr-native library that provides a common interoperable API for all types of file descriptors such as those from the non-virtual FS, sockets, eventfds, FILE *'s @@ -14,6 +15,25 @@ menuconfig ZVFS if ZVFS +config ZVFS_LIBC_FILE_SIZE + int + default -1 + help + Size of C library FILE objects, in bytes. All C libraries must specify this value via + Kconfig. + + Beyond that, all C libraries must also provide the following two functions: + + - void zvfs_libc_file_alloc_cb(int fd, const char *mode, FILE *fp); + - void zvfs_libc_file_get_fd(int fd, const char *mode, FILE *fp); + +config ZVFS_LIBC_FILE_ALIGN + int + default 8 if 64BIT + default 4 + help + Alignment of C library FILE objects, in bytes. + config ZVFS_EVENTFD bool "ZVFS event file descriptor support" imply ZVFS_POLL diff --git a/lib/os/zvfs/zvfs_libc_file.c b/lib/os/zvfs/zvfs_libc_file.c new file mode 100644 index 0000000000000..d0117fe67dbf6 --- /dev/null +++ b/lib/os/zvfs/zvfs_libc_file.c @@ -0,0 +1,85 @@ +/* + * Copyright (c) 2024 Zephyr Contributors + * Copyright (c) 2025 Antmicro + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include + +#include +#include +#include + +/* in case these are defined as macros */ +#undef stdin +#undef stdout +#undef stderr + +static char __aligned(WB_UP(CONFIG_ZVFS_LIBC_FILE_ALIGN)) _stdin[CONFIG_ZVFS_LIBC_FILE_SIZE]; +static char __aligned(WB_UP(CONFIG_ZVFS_LIBC_FILE_ALIGN)) _stdout[CONFIG_ZVFS_LIBC_FILE_SIZE]; +static char __aligned(WB_UP(CONFIG_ZVFS_LIBC_FILE_ALIGN)) _stderr[CONFIG_ZVFS_LIBC_FILE_SIZE]; +FILE *const stdin = (FILE *)(&_stdin); +FILE *const stdout = (FILE *)(&_stdout); +FILE *const stderr = (FILE *)(&_stderr); + +#define table_stride ROUND_UP(CONFIG_ZVFS_LIBC_FILE_SIZE, CONFIG_ZVFS_LIBC_FILE_ALIGN) +#define table_data ((uint8_t *)_k_mem_slab_buf_zvfs_libc_file_table) + +K_MEM_SLAB_DEFINE_STATIC(zvfs_libc_file_table, CONFIG_ZVFS_LIBC_FILE_SIZE, CONFIG_ZVFS_OPEN_MAX, + CONFIG_ZVFS_LIBC_FILE_ALIGN); + +static int file_to_fd[CONFIG_ZVFS_OPEN_MAX]; +#define ptr_to_idx(fp) (POINTER_TO_INT((uint8_t *)fp - table_data) / table_stride) + +int zvfs_libc_file_alloc(int fd, const char *mode, FILE **fp, k_timeout_t timeout) +{ + int ret; + + __ASSERT_NO_MSG(mode != NULL); + __ASSERT_NO_MSG(fp != NULL); + + ret = k_mem_slab_alloc(&zvfs_libc_file_table, (void **)fp, timeout); + if (ret < 0) { + return ret; + } + file_to_fd[ptr_to_idx(*fp)] = fd; + zvfs_libc_file_alloc_cb(fd, mode, *fp); + + return 0; +} + +void zvfs_libc_file_free(FILE *fp) +{ + if ((fp == NULL) || (fp == stdin) || (fp == stdout) || (fp == stderr)) { + return; + } + + k_mem_slab_free(&zvfs_libc_file_table, (void *)fp); + file_to_fd[ptr_to_idx(fp)] = -1; +} + +FILE *zvfs_libc_file_from_fd(int fd) +{ + if ((fd < 0) || (fd >= ARRAY_SIZE(file_to_fd))) { + return NULL; + } + switch (fd) { + case 0: + return stdin; + case 1: + return stdout; + case 2: + return stderr; + default: + /* fd points at an actual file */ + break; + } + + for (int i = 0; i < ARRAY_SIZE(file_to_fd); i++) { + if (file_to_fd[i] == fd) { + return (FILE *)&_k_mem_slab_buf_zvfs_libc_file_table[table_stride * i]; + } + } + return NULL; +} diff --git a/lib/posix/options/Kconfig.device_io b/lib/posix/options/Kconfig.device_io index 416ccf74a6218..ffa7b552b0a15 100644 --- a/lib/posix/options/Kconfig.device_io +++ b/lib/posix/options/Kconfig.device_io @@ -20,6 +20,35 @@ config POSIX_DEVICE_IO if POSIX_DEVICE_IO +config POSIX_DEVICE_IO_STDIO_CONSOLE + bool "read() and write() to/from stdio use console subsystem" + default y + depends on CONSOLE_SUBSYS + depends on !(CONSOLE_GETCHAR || CONSOLE_GETLINE) + help + Enable to use UART console whenever read() or write() is called + on stdin, stdout or stderr. + +if POSIX_DEVICE_IO_STDIO_CONSOLE + +config POSIX_DEVICE_IO_STDIN_BUFSIZE + int "read() buffer size for stdin" + default 16 + help + Size of internal buffer for read()-ing from stdin. Setting both this and + POSIX_DEVICE_IO_STDOUT_BUFSIZE to 0 will replace interrupt-driven operation + with busy-polling and stop poll(), select() and others from working with stdio. + +config POSIX_DEVICE_IO_STDOUT_BUFSIZE + int "write() buffer size for stdout" + default 16 + help + Size of internal buffer for write()-ing to stdout. Setting both this and + POSIX_DEVICE_IO_STDIN_BUFSIZE to 0 will replace interrupt-driven operation + with busy-polling and stop poll(), select() and others from working with stdio. + +endif # POSIX_DEVICE_IO_STDIO_CONSOLE + # These options are intended to be used for compatibility with external POSIX # implementations such as those in Newlib or Picolibc. diff --git a/lib/posix/options/device_io.c b/lib/posix/options/device_io.c index 6fea22d01545d..83c180fa9d208 100644 --- a/lib/posix/options/device_io.c +++ b/lib/posix/options/device_io.c @@ -99,7 +99,7 @@ int pselect(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, return zvfs_select(nfds, readfds, writefds, exceptfds, timeout, sigmask); } -ssize_t pwrite(int fd, void *buf, size_t count, off_t offset) +ssize_t pwrite(int fd, const void *buf, size_t count, off_t offset) { size_t off = (size_t)offset; diff --git a/lib/posix/options/posix_clock.h b/lib/posix/options/posix_clock.h index b955d81f914b3..bad12eb99ea5b 100644 --- a/lib/posix/options/posix_clock.h +++ b/lib/posix/options/posix_clock.h @@ -22,6 +22,18 @@ extern "C" { /** @cond INTERNAL_HIDDEN */ +#if !defined(_CLOCK_T_DECLARED) && !defined(__clock_t_defined) +typedef unsigned long clock_t; +#define _CLOCK_T_DECLARED +#define __clock_t_defined +#endif + +#if !defined(_CLOCKID_T_DECLARED) && !defined(__clockid_t_defined) +typedef unsigned long clockid_t; +#define _CLOCKID_T_DECLARED +#define __clockid_t_defined +#endif + static inline int64_t ts_to_ns(const struct timespec *ts) { return ts->tv_sec * NSEC_PER_SEC + ts->tv_nsec; diff --git a/subsys/console/CMakeLists.txt b/subsys/console/CMakeLists.txt index 37ff969710b77..8c03572443052 100644 --- a/subsys/console/CMakeLists.txt +++ b/subsys/console/CMakeLists.txt @@ -1,4 +1,5 @@ # SPDX-License-Identifier: Apache-2.0 -zephyr_sources_ifdef(CONFIG_CONSOLE_GETCHAR tty.c getchar.c) +zephyr_sources_ifdef(CONFIG_CONSOLE_SUBSYS tty.c) +zephyr_sources_ifdef(CONFIG_CONSOLE_GETCHAR getchar.c) zephyr_sources_ifdef(CONFIG_CONSOLE_GETLINE getline.c) diff --git a/tests/lib/c_lib/thrd/prj.conf b/tests/lib/c_lib/thrd/prj.conf index 2ee6109079b6f..21a8d36f94fae 100644 --- a/tests/lib/c_lib/thrd/prj.conf +++ b/tests/lib/c_lib/thrd/prj.conf @@ -2,7 +2,7 @@ CONFIG_ZTEST=y CONFIG_TEST_USERSPACE=y CONFIG_ZTEST_FATAL_HOOK=y -CONFIG_POSIX_API=y +CONFIG_POSIX_AEP_CHOICE_BASE=y CONFIG_THREAD_STACK_INFO=y CONFIG_DYNAMIC_THREAD=y CONFIG_DYNAMIC_THREAD_POOL_SIZE=2 diff --git a/tests/lib/c_lib/thrd/testcase.yaml b/tests/lib/c_lib/thrd/testcase.yaml index e11360efd2eaf..a126353d6b112 100644 --- a/tests/lib/c_lib/thrd/testcase.yaml +++ b/tests/lib/c_lib/thrd/testcase.yaml @@ -12,7 +12,7 @@ common: tests: libraries.libc.c11_threads.minimal: tags: minimal_libc - filter: CONFIG_MINIMAL_LIBC_SUPPORTED + filter: CONFIG_MINIMAL_LIBC_SUPPORTED and not CONFIG_REQUIRES_FULL_LIBC extra_configs: - CONFIG_MINIMAL_LIBC=y - CONFIG_MINIMAL_LIBC_NON_REENTRANT_FUNCTIONS=y diff --git a/tests/posix/device_io/CMakeLists.txt b/tests/posix/device_io/CMakeLists.txt new file mode 100644 index 0000000000000..24c7cfd219486 --- /dev/null +++ b/tests/posix/device_io/CMakeLists.txt @@ -0,0 +1,12 @@ +# SPDX-License-Identifier: Apache-2.0 + +cmake_minimum_required(VERSION 3.20.0) +find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) +project(posix_c_lib_ext) + +FILE(GLOB app_sources src/*.c) + +target_sources(app PRIVATE ${app_sources}) + +target_include_directories(app PRIVATE ${ZEPHYR_BASE}/lib/posix/options/getopt) +target_compile_options(app PRIVATE -U_POSIX_C_SOURCE -D_POSIX_C_SOURCE=200809L) diff --git a/tests/posix/device_io/prj.conf b/tests/posix/device_io/prj.conf new file mode 100644 index 0000000000000..de1f0919083a9 --- /dev/null +++ b/tests/posix/device_io/prj.conf @@ -0,0 +1,8 @@ +CONFIG_POSIX_API=y +CONFIG_ZTEST=y + +CONFIG_POSIX_AEP_CHOICE_BASE=y +CONFIG_POSIX_DEVICE_IO=y + +CONFIG_SYSTEM_WORKQUEUE_STACK_SIZE=2048 +CONFIG_ZTEST_STACK_SIZE=2048 diff --git a/tests/posix/device_io/src/main.c b/tests/posix/device_io/src/main.c new file mode 100644 index 0000000000000..b922b17a77af3 --- /dev/null +++ b/tests/posix/device_io/src/main.c @@ -0,0 +1,213 @@ +/* + * Copyright (c) 2025 Tenstorrent AI ULC + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include + +#include + +#include +#include +#include +#include + +#ifndef STDIN_FILENO +#define STDIN_FILENO 0 +#endif + +#ifndef STDOUT_FILENO +#define STDOUT_FILENO 1 +#endif + +#ifndef STDERR_FILENO +#define STDERR_FILENO 2 +#endif + +ZTEST(posix_device_io, test_FD_CLR) +{ + fd_set fds; + + FD_CLR(0, &fds); +} + +ZTEST(posix_device_io, test_FD_SET) +{ + fd_set fds; + + FD_SET(0, &fds); + zassert_true(FD_ISSET(0, &fds)); +} + +ZTEST(posix_device_io, test_FD_ZERO) +{ + fd_set fds; + + FD_ZERO(&fds); + zassert_false(FD_ISSET(0, &fds)); +} + +ZTEST(posix_device_io, test_close) +{ + zassert_not_ok(close(-1)); +} + +ZTEST(posix_device_io, test_fdopen) +{ + zassert_not_null(fdopen(1, "r")); +} + +ZTEST(posix_device_io, test_fileno) +{ +#define DECL_TDATA(_stream, _fd) \ + { \ + .stream_name = #_stream, \ + .stream = _stream, \ + .fd_name = #_fd, \ + .fd = _fd, \ + } + + const struct fileno_test_data { + const char *stream_name; + FILE *stream; + const char *fd_name; + int fd; + } test_data[] = { + DECL_TDATA(stdin, STDIN_FILENO), + DECL_TDATA(stdout, STDOUT_FILENO), + DECL_TDATA(stderr, STDERR_FILENO), + }; + + ARRAY_FOR_EACH(test_data, i) { + FILE *stream = test_data[i].stream; + int expect_fd = test_data[i].fd; + int actual_fd = fileno(stream); + + if ((i == STDERR_FILENO) && + (IS_ENABLED(CONFIG_PICOLIBC) || IS_ENABLED(CONFIG_NEWLIB_LIBC))) { + TC_PRINT("Note: stderr not enabled\n"); + continue; + } + + zexpect_equal(actual_fd, expect_fd, "fileno(%s) (%d) != %s (%d)", + test_data[i].stream_name, actual_fd, test_data[i].fd_name, expect_fd); + } +} + +ZTEST(posix_device_io, test_open) +{ + /* + * Note: open() is already exercised extensively in tests/posix/fs, but we should test it + * here on device nodes as well. + */ + zexpect_equal(open("/dev/null", O_RDONLY), -1); + zexpect_equal(errno, ENOENT); +} + +#if (defined(CONFIG_POSIX_DEVICE_IO_STDIO_CONSOLE) && \ + (CONFIG_POSIX_DEVICE_IO_STDIN_BUFSIZE + CONFIG_POSIX_DEVICE_IO_STDOUT_BUFSIZE > 0)) +#define STDIO_POLL +#endif + +ZTEST(posix_device_io, test_poll) +{ + struct pollfd fds[] = { + {.fd = STDIN_FILENO, .events = POLLIN}, + {.fd = STDOUT_FILENO, .events = POLLOUT}, + {.fd = STDERR_FILENO, .events = POLLOUT}, + }; + + /* + * Note: poll() is already exercised extensively in tests/posix/eventfd, but we should test + * it here on device nodes as well. + */ +#ifdef STDIO_POLL + zexpect_equal(poll(fds, ARRAY_SIZE(fds), 0), 2); + /* stdin shouldn't have any bytes ready to read */ + zexpect_equal(fds[0].revents, 0); + zexpect_equal(fds[1].revents, POLLOUT); + zexpect_equal(fds[2].revents, POLLOUT); +#else + zexpect_equal(poll(fds, ARRAY_SIZE(fds), 0), 3); + zexpect_equal(fds[0].revents, POLLNVAL); + zexpect_equal(fds[1].revents, POLLNVAL); + zexpect_equal(fds[2].revents, POLLNVAL); +#endif +} + +ZTEST(posix_device_io, test_pread) +{ + uint8_t buf[8]; + + /* stdio is non-seekable */ + zexpect_equal(pread(STDIN_FILENO, buf, sizeof(buf), 0), -1); + zexpect_equal(errno, ESPIPE); +} + +ZTEST(posix_device_io, test_pselect) +{ + fd_set readfds; + fd_set writefds; + struct timespec timeout = {.tv_sec = 0, .tv_nsec = 0}; + + FD_ZERO(&readfds); + FD_SET(STDIN_FILENO, &readfds); + FD_ZERO(&writefds); + FD_SET(STDOUT_FILENO, &writefds); + FD_SET(STDERR_FILENO, &writefds); + +#ifdef STDIO_POLL + zexpect_equal(pselect(STDERR_FILENO + 1, &readfds, &writefds, NULL, &timeout, NULL), 2); + zassert_false(FD_ISSET(STDIN_FILENO, &readfds)); + zassert_true(FD_ISSET(STDOUT_FILENO, &writefds)); + zassert_true(FD_ISSET(STDERR_FILENO, &writefds)); +#else + zexpect_equal(pselect(STDERR_FILENO + 1, &readfds, &writefds, NULL, &timeout, NULL), -1); + zassert_equal(errno, EBADF); +#endif +} + +ZTEST(posix_device_io, test_pwrite) +{ + /* stdio is non-seekable */ + zexpect_equal(pwrite(STDOUT_FILENO, "x", 1, 0), -1); + zexpect_equal(errno, ESPIPE, "%d", errno); +} + +ZTEST(posix_device_io, test_select) +{ + fd_set readfds; + fd_set writefds; + struct timeval timeout = {.tv_sec = 0, .tv_usec = 0}; + + FD_ZERO(&readfds); + FD_SET(STDIN_FILENO, &readfds); + FD_ZERO(&writefds); + FD_SET(STDOUT_FILENO, &writefds); + FD_SET(STDERR_FILENO, &writefds); + +#ifdef STDIO_POLL + zassert_equal(select(STDERR_FILENO + 1, &readfds, &writefds, NULL, &timeout), 2); + zassert_false(FD_ISSET(STDIN_FILENO, &readfds)); + zassert_true(FD_ISSET(STDOUT_FILENO, &writefds)); + zassert_true(FD_ISSET(STDERR_FILENO, &writefds)); +#else + zassert_equal(select(STDERR_FILENO + 1, &readfds, &writefds, NULL, &timeout), -1); + zassert_equal(errno, EBADF); +#endif +} + +ZTEST(posix_device_io, test_write) +{ + static const char msg[] = "Hello world!\n"; + +#if defined(CONFIG_POSIX_DEVICE_IO_STDIO_CONSOLE) || defined(CONFIG_ARCMWDT_LIBC) || \ + defined(CONFIG_MINIMAL_LIBC) || defined(CONFIG_NEWLIB_LIBC) || defined(CONFIG_PICOLIBC) + zexpect_equal(write(STDOUT_FILENO, msg, ARRAY_SIZE(msg)), ARRAY_SIZE(msg)); +#else + zexpect_equal(write(STDOUT_FILENO, msg, ARRAY_SIZE(msg)), 0); +#endif +} + +ZTEST_SUITE(posix_device_io, NULL, NULL, NULL, NULL, NULL); diff --git a/tests/posix/device_io/testcase.yaml b/tests/posix/device_io/testcase.yaml new file mode 100644 index 0000000000000..4601ba8038421 --- /dev/null +++ b/tests/posix/device_io/testcase.yaml @@ -0,0 +1,39 @@ +common: + filter: not CONFIG_NATIVE_LIBC + tags: + - posix + - device_io + # 1 tier0 platform per supported architecture + platform_key: + - arch + - simulation + min_ram: 32 +tests: + portability.posix.device_io: + extra_configs: + - CONFIG_COMMON_LIBC_MALLOC_ARENA_SIZE=256 + portability.posix.device_io.tty_stdio: + filter: CONFIG_UART_CONSOLE + extra_configs: + - CONFIG_CONSOLE_SUBSYS=y + portability.posix.device_io.armclang_std_libc: + toolchain_allow: armclang + extra_configs: + - CONFIG_ARMCLANG_STD_LIBC=y + portability.posix.device_io.arcmwdtlib: + toolchain_allow: arcmwdt + extra_configs: + - CONFIG_ARCMWDT_LIBC=y + portability.posix.device_io.minimal: + extra_configs: + - CONFIG_MINIMAL_LIBC=y + - CONFIG_COMMON_LIBC_MALLOC_ARENA_SIZE=256 + portability.posix.device_io.newlib: + filter: TOOLCHAIN_HAS_NEWLIB == 1 + extra_configs: + - CONFIG_NEWLIB_LIBC=y + portability.posix.device_io.picolibc: + tags: picolibc + filter: CONFIG_PICOLIBC_SUPPORTED + extra_configs: + - CONFIG_PICOLIBC=y diff --git a/tests/posix/fs/src/test_fs_file.c b/tests/posix/fs/src/test_fs_file.c index 23496c92b8c22..be52a3df625bf 100644 --- a/tests/posix/fs/src/test_fs_file.c +++ b/tests/posix/fs/src/test_fs_file.c @@ -28,6 +28,18 @@ static int test_file_open(void) return TC_PASS; } +static int test_file_fdopen(void) +{ + FILE *fp = fdopen(file, "r"); + + if (fp == NULL) { + TC_ERROR("Failed associating file descriptor %d with FILE, errno=%d\n", file, + errno); + return TC_FAIL; + } + return TC_PASS; +} + int test_file_write(void) { ssize_t brw; @@ -197,6 +209,18 @@ ZTEST(posix_fs_file_test, test_fs_open) zassert_true(test_file_open() == TC_PASS); } +/** + * @brief Test for POSIX fdopen API + * + * @details Test converts file descriptor to FILE * through POSIX fdopen API. + */ +ZTEST(posix_fs_file_test, test_fs_fdopen) +{ + /* FIXME: restructure tests as per #46897 */ + zassert_true(test_file_open() == TC_PASS); + zassert_true(test_file_fdopen() == TC_PASS); +} + /** * @brief Test for POSIX write API *