diff --git a/include/zephyr/posix/unistd.h b/include/zephyr/posix/unistd.h index 10d44bfd4a8d..3cfbd71c54c8 100644 --- a/include/zephyr/posix/unistd.h +++ b/include/zephyr/posix/unistd.h @@ -30,7 +30,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 d4153828fbb2..d351e948f423 100644 --- a/include/zephyr/sys/fdtable.h +++ b/include/zephyr/sys/fdtable.h @@ -7,6 +7,7 @@ #define ZEPHYR_INCLUDE_SYS_FDTABLE_H_ #include +#include #include /* FIXME: For native_posix ssize_t, off_t. */ @@ -261,6 +262,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/kernel/mutex.c b/kernel/mutex.c index ce76e5a2af54..ac495d5ef0d5 100644 --- a/kernel/mutex.c +++ b/kernel/mutex.c @@ -114,18 +114,20 @@ int z_impl_k_mutex_lock(struct k_mutex *mutex, k_timeout_t timeout) key = k_spin_lock(&lock); - if (likely((mutex->lock_count == 0U) || (mutex->owner == _current))) { + if (likely((mutex->lock_count == 0U) || (mutex->owner == _current) || + (mutex->owner == NULL))) { + if (mutex->owner == NULL) { + mutex->owner = _current; + } - mutex->owner_orig_prio = (mutex->lock_count == 0U) ? - _current->base.prio : - mutex->owner_orig_prio; + mutex->owner_orig_prio = + (mutex->lock_count == 0U) ? _current->base.prio : mutex->owner_orig_prio; mutex->lock_count++; mutex->owner = _current; - LOG_DBG("%p took mutex %p, count: %d, orig prio: %d", - _current, mutex, mutex->lock_count, - mutex->owner_orig_prio); + LOG_DBG("%p took mutex %p, count: %d, orig prio: %d", _current, mutex, + mutex->lock_count, mutex->owner_orig_prio); k_spin_unlock(&lock, key); diff --git a/lib/libc/common/CMakeLists.txt b/lib/libc/common/CMakeLists.txt index ed3d28fe5a62..5357c0903da4 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 c4ac88dbab1c..b98faaef6ac1 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 000000000000..11c8e704f012 --- /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/newlib/CMakeLists.txt b/lib/libc/newlib/CMakeLists.txt index d3dc448eccaf..97af247e6d77 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 61f946caf780..90e232920db8 100644 --- a/lib/libc/newlib/Kconfig +++ b/lib/libc/newlib/Kconfig @@ -70,4 +70,12 @@ config NEWLIB_LIBC_CUSTOM_SBRK Allow user to define custom version of the _sbrk function. Some application may need to use the remaining RAM for also other purposes than heap. +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 4ec1887f1cb5..82d3e24bce80 100644 --- a/lib/libc/newlib/libc-hooks.c +++ b/lib/libc/newlib/libc-hooks.c @@ -169,6 +169,7 @@ static int (*_stdout_hook)(int) = _stdout_hook_default; void __stdout_hook_install(int (*hook)(int)) { _stdout_hook = hook; + stdout->_file = 1; /* STDOUT_FILENO */ } static unsigned char _stdin_hook_default(void) @@ -181,6 +182,7 @@ static unsigned char (*_stdin_hook)(void) = _stdin_hook_default; void __stdin_hook_install(unsigned char (*hook)(void)) { _stdin_hook = hook; + stdin->_file = 0; /* STDIN_FILENO */ } 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 000000000000..21658c28dacd --- /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 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 752379dd76d4..92bf2852bbfc 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 be30e6a8f3a6..e9703f48d1de 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 342eedc87710..dfaf5aa7add0 100644 --- a/lib/libc/picolibc/stdio.c +++ b/lib/libc/picolibc/stdio.c @@ -5,6 +5,7 @@ */ #include "picolibc-hooks.h" +#include "stdio-bufio.h" static LIBC_DATA int (*_stdout_hook)(int); @@ -28,8 +29,10 @@ static int picolibc_put(char a, FILE *f) return 0; } -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 +40,35 @@ 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->get = (int (*)(FILE *))hook; */ + bp->ptr = INT_TO_POINTER(0 /* STDIN_FILENO */); +#endif } diff --git a/lib/libc/picolibc/zvfs_libc.c b/lib/libc/picolibc/zvfs_libc.c new file mode 100644 index 000000000000..9c3ea3afc2b4 --- /dev/null +++ b/lib/libc/picolibc/zvfs_libc.c @@ -0,0 +1,82 @@ +/* + * 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; + } + 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 be53627efca7..2a37d580c16c 100644 --- a/lib/os/fdtable.c +++ b/lib/os/fdtable.c @@ -75,6 +75,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; @@ -406,6 +447,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 +455,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,11 +593,26 @@ 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); static ssize_t stdinout_read_vmeth(void *obj, void *buffer, size_t count) { - return 0; +#if defined(CONFIG_NEWLIB_LIBC) || defined(CONFIG_ARCMWDT_LIBC) + return z_impl_zephyr_read_stdin(buffer, count); +#else + int r = 0; + + while (r < count) { + int c = getchar(); + + if (c == '\n' || c == '\r') { + break; + } + ((char *)buffer)[r++] = c; + } + return r; +#endif } static ssize_t stdinout_write_vmeth(void *obj, const void *buffer, size_t count) @@ -553,7 +620,7 @@ static ssize_t stdinout_write_vmeth(void *obj, const void *buffer, size_t count) #if defined(CONFIG_NEWLIB_LIBC) || defined(CONFIG_ARCMWDT_LIBC) return z_impl_zephyr_write_stdout(buffer, count); #else - return 0; + return printf("%.*s", (int)count, (const char *)buffer); #endif } diff --git a/lib/os/zvfs/CMakeLists.txt b/lib/os/zvfs/CMakeLists.txt index d855d1005efc..c9120e697a86 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 a9c9518a1d5d..127289e0abd2 100644 --- a/lib/os/zvfs/Kconfig +++ b/lib/os/zvfs/Kconfig @@ -14,6 +14,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 000000000000..11cfa0de57f2 --- /dev/null +++ b/lib/os/zvfs/zvfs_libc_file.c @@ -0,0 +1,81 @@ +/* + * 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 _stdin[CONFIG_ZVFS_LIBC_FILE_SIZE]; +static char _stdout[CONFIG_ZVFS_LIBC_FILE_SIZE]; +static char _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_LIBC_FILE_SIZE]; +#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 *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; + } + + 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/device_io.c b/lib/posix/options/device_io.c index 6fea22d01545..83c180fa9d20 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 b955d81f914b..bad12eb99ea5 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/tests/lib/c_lib/thrd/prj.conf b/tests/lib/c_lib/thrd/prj.conf index 2ee6109079b6..21a8d36f94fa 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 e11360efd2ea..a126353d6b11 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 000000000000..24c7cfd21948 --- /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 000000000000..01cab1d7ed56 --- /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=4096 +CONFIG_ZTEST_STACK_SIZE=4096 diff --git a/tests/posix/device_io/src/main.c b/tests/posix/device_io/src/main.c new file mode 100644 index 000000000000..03728057a613 --- /dev/null +++ b/tests/posix/device_io/src/main.c @@ -0,0 +1,173 @@ +/* + * 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_ISSET) +{ + fd_set fds; + + zassert_false(FD_ISSET(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); +} + +ZTEST(posix_device_io, test_poll) +{ + struct pollfd fds[1] = {{.fd = STDIN_FILENO, .events = POLLIN}}; + + /* + * Note: poll() is already exercised extensively in tests/posix/eventfd, but we should test + * it here on device nodes as well. + */ + zexpect_equal(poll(fds, ARRAY_SIZE(fds), 0), 1); +} + +ZTEST(posix_device_io, test_pread) +{ + uint8_t buf[8]; + + zexpect_equal(pread(STDIN_FILENO, buf, sizeof(buf), 0), -1); + zexpect_equal(errno, ENOTSUP); +} + +ZTEST(posix_device_io, test_pselect) +{ + fd_set fds; + struct timespec timeout = {.tv_sec = 0, .tv_nsec = 0}; + + FD_ZERO(&fds); + FD_SET(STDIN_FILENO, &fds); + + /* Zephyr does not yet support select or poll on stdin */ + zexpect_equal(pselect(STDIN_FILENO + 1, &fds, NULL, NULL, &timeout, NULL), -1); + zexpect_equal(errno, EBADF); +} + +ZTEST(posix_device_io, test_pwrite) +{ + /* stdout is not seekable, thus it does not support pwrite */ + zexpect_equal(pwrite(STDOUT_FILENO, "x", 1, 0), -1); + zexpect_equal(errno, ENOTSUP, "%d", errno); +} + +ZTEST(posix_device_io, test_read) +{ + uint8_t buf[8]; + + zassert_equal(read(STDIN_FILENO, buf, sizeof(buf)), 8); +} + +ZTEST(posix_device_io, test_select) +{ + fd_set fds; + struct timeval timeout = {.tv_sec = 0, .tv_usec = 0}; + + FD_ZERO(&fds); + FD_SET(STDIN_FILENO, &fds); + + /* Zephyr does not yet support select or poll on stdin */ + zassert_equal(select(STDIN_FILENO + 1, &fds, NULL, NULL, &timeout), -1); + zexpect_equal(errno, EBADF, "%d", errno); +} + +ZTEST(posix_device_io, test_write) +{ + zexpect_equal(write(STDOUT_FILENO, "x", 1), 1); +} + +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 000000000000..0ea0181f5e9d --- /dev/null +++ b/tests/posix/device_io/testcase.yaml @@ -0,0 +1,36 @@ +common: + filter: not CONFIG_NATIVE_LIBC + tags: + - posix + - device_io + # 1 tier0 platform per supported architecture + platform_key: + - arch + - simulation + integration_platforms: + - qemu_cortex_m0 +tests: + portability.posix.device_io: + extra_configs: + - CONFIG_COMMON_LIBC_MALLOC_ARENA_SIZE=256 + 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 23496c92b8c2..be52a3df625b 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 *