diff --git a/include/zephyr/posix/unistd.h b/include/zephyr/posix/unistd.h index 10d44bfd4a8d2..3cfbd71c54c84 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 d4153828fbb2d..d351e948f423b 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 ce76e5a2af545..ac495d5ef0d59 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/Kconfig b/lib/libc/common/Kconfig index c4ac88dbab1cd..9c4d406bde113 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. 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 61f946caf780b..90e232920db86 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 4ec1887f1cb54..82d3e24bce808 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 0000000000000..21658c28dacdd --- /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 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..10d0fa800b081 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); @@ -22,6 +23,7 @@ static inline int z_vrfy_zephyr_fputc(int c, FILE *stream) #include #endif +#ifndef CONFIG_ZVFS static int picolibc_put(char a, FILE *f) { zephyr_fputc(a, f); @@ -30,6 +32,7 @@ static int picolibc_put(char a, FILE *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); +#endif #ifdef __strong_reference #define STDIO_ALIAS(x) __strong_reference(stdout, x); @@ -37,18 +40,36 @@ 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; +#ifdef CONFIG_ZVFS + struct __file_bufio *bp = (struct __file_bufio *)stdout; + + bp->ptr = INT_TO_POINTER(1 /* STDOUT_FILENO */); + bp->bflags |= _FDEV_SETUP_WRITE; +#else __stdout.flags |= _FDEV_SETUP_WRITE; +#endif + + _stdout_hook = hook; } void __stdin_hook_install(unsigned char (*hook)(void)) { +#ifdef CONFIG_ZVFS + 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 */); +#else __stdin.get = (int (*)(FILE *)) hook; __stdin.flags |= _FDEV_SETUP_READ; +#endif } diff --git a/lib/libc/picolibc/zvfs_libc.c b/lib/libc/picolibc/zvfs_libc.c new file mode 100644 index 0000000000000..a50c07be11f2d --- /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 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 be53627efca7b..ad060f333befa 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) 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 a9c9518a1d5db..127289e0abd2a 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 0000000000000..49ecbeaf98db2 --- /dev/null +++ b/lib/os/zvfs/zvfs_libc_file.c @@ -0,0 +1,55 @@ +#include + +#include +#include +#include + +/* in case these are defined as macros */ +#undef stdin +#undef stdout +#undef 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); + +FILE *const stdin = (FILE *)(table_data + 0 /* STDIN_FILENO */ * table_stride); +FILE *const stdout = (FILE *)(table_data + 1 /* STDOUT_FILENO */ * table_stride); +FILE *const stderr = (FILE *)(table_data + 2 /* STDERR_FILENO */ * 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; + } + + zvfs_libc_file_alloc_cb(fd, mode, *fp); + + return 0; +} + +void zvfs_libc_file_free(FILE *fp) +{ + if (fp == NULL) { + return; + } + + k_mem_slab_free(&zvfs_libc_file_table, (void *)fp); +} + +FILE *zvfs_libc_file_from_fd(int fd) +{ + if ((fd < 0) || (fd >= CONFIG_ZVFS_OPEN_MAX)) { + return NULL; + } + + return (FILE *)&_k_mem_slab_buf_zvfs_libc_file_table[table_stride * fd]; +} 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/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..1c5f04478117c --- /dev/null +++ b/tests/posix/device_io/prj.conf @@ -0,0 +1,9 @@ +CONFIG_POSIX_API=y +CONFIG_ZTEST=y + +CONFIG_POSIX_AEP_CHOICE_BASE=y +CONFIG_POSIX_DEVICE_IO=y + +CONFIG_NO_OPTIMIZATIONS=y +CONFIG_SYSTEM_WORKQUEUE_STACK_SIZE=4096 +CONFIG_ZTEST_STACK_SIZE=4096 \ No newline at end of file diff --git a/tests/posix/device_io/src/main.c b/tests/posix/device_io/src/main.c new file mode 100644 index 0000000000000..27a6cc389ddd0 --- /dev/null +++ b/tests/posix/device_io/src/main.c @@ -0,0 +1,178 @@ +/* + * 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)); +} + +/* + * FIXME: this corrupts stdin for some reason +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) +{ + /* Zephyr does not yet support writing through a file descriptor */ + 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]; + + /* reading from stdin does not work in Zephyr */ + zassert_equal(read(STDIN_FILENO, buf, sizeof(buf)), 0); +} + +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 0000000000000..0ea0181f5e9df --- /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