diff --git a/doc/services/portability/posix/option_groups/index.rst b/doc/services/portability/posix/option_groups/index.rst index 542ef326303e2..473982e4dbd01 100644 --- a/doc/services/portability/posix/option_groups/index.rst +++ b/doc/services/portability/posix/option_groups/index.rst @@ -678,6 +678,21 @@ Enable this option group with :kconfig:option:`CONFIG_XSI_REALTIME`. When this option group is enabled, the ``_XOPEN_REALTIME`` feature test macro will be defined to a value other than -1. +.. _posix_option_group_xsi_single_process: + +XSI_SINGLE_PROCESS +++++++++++++++++++ + +Enable this option group with :kconfig:option:`CONFIG_XSI_SINGLE_PROCESS`. + +.. csv-table:: XSI_SINGLE_PROCESS + :header: API, Supported + :widths: 50,10 + + gethostid(),yes + gettimeofday(),yes + putenv(),yes + .. _posix_option_group_xsi_system_logging: XSI_SYSTEM_LOGGING diff --git a/include/zephyr/posix/unistd.h b/include/zephyr/posix/unistd.h index b7ac069bdc5e5..10d44bfd4a8d2 100644 --- a/include/zephyr/posix/unistd.h +++ b/include/zephyr/posix/unistd.h @@ -77,6 +77,10 @@ size_t confstr(int name, char *buf, size_t len); long sysconf(int opt); #endif /* CONFIG_POSIX_SYSCONF_IMPL_FULL */ +#if _XOPEN_SOURCE >= 500 +long gethostid(void); +#endif + #ifdef __cplusplus } #endif diff --git a/lib/posix/options/CMakeLists.txt b/lib/posix/options/CMakeLists.txt index 58f44773e31b9..f12904aefb600 100644 --- a/lib/posix/options/CMakeLists.txt +++ b/lib/posix/options/CMakeLists.txt @@ -2,7 +2,9 @@ set(GEN_DIR ${ZEPHYR_BINARY_DIR}/include/generated) +zephyr_syscall_header_ifdef(CONFIG_POSIX_CLOCK_SELECTION posix_clock.h) zephyr_syscall_header_ifdef(CONFIG_POSIX_TIMERS posix_clock.h) +zephyr_syscall_header_ifdef(CONFIG_XSI_SINGLE_PROCESS posix_clock.h) if(CONFIG_POSIX_API) zephyr_include_directories(${ZEPHYR_BASE}/include/zephyr/posix) @@ -44,6 +46,13 @@ if (NOT CONFIG_TC_PROVIDES_POSIX_BARRIERS) zephyr_library_sources_ifdef(CONFIG_POSIX_BARRIERS barrier.c) endif() +if (NOT CONFIG_TC_PROVIDES_POSIX_CLOCK_SELECTION) + zephyr_library_sources_ifdef(CONFIG_POSIX_CLOCK_SELECTION + clock_common.c + clock_selection.c + ) +endif() + if (NOT CONFIG_TC_PROVIDES_POSIX_C_LIB_EXT) zephyr_library_sources_ifdef(CONFIG_POSIX_C_LIB_EXT fnmatch.c @@ -102,6 +111,7 @@ if (NOT CONFIG_TC_PROVIDES_POSIX_SINGLE_PROCESS) zephyr_library_sources_ifdef(CONFIG_POSIX_SINGLE_PROCESS confstr.c env.c + env_common.c sysconf.c uname.c ) @@ -114,6 +124,7 @@ endif() if (NOT CONFIG_TC_PROVIDES_POSIX_TIMERS) zephyr_library_sources_ifdef(CONFIG_POSIX_TIMERS clock.c + clock_common.c timer.c timespec_to_timeout.c ) @@ -153,6 +164,14 @@ endif() zephyr_library_sources_ifdef(CONFIG_XOPEN_STREAMS stropts.c) +if (NOT CONFIG_TC_PROVIDES_XSI_SINGLE_PROCESS) + zephyr_library_sources_ifdef(CONFIG_XSI_SINGLE_PROCESS + clock_common.c + env_common.c + xsi_single_process.c + ) +endif() + if (NOT CONFIG_TC_PROVIDES_XSI_SYSTEM_LOGGING) zephyr_library_sources_ifdef(CONFIG_XSI_SYSTEM_LOGGING syslog.c) endif() diff --git a/lib/posix/options/Kconfig.clock_selection b/lib/posix/options/Kconfig.clock_selection new file mode 100644 index 0000000000000..34b12ff8f238e --- /dev/null +++ b/lib/posix/options/Kconfig.clock_selection @@ -0,0 +1,13 @@ +# Copyright (c) 2025 Tenstorrent AI ULC +# +# SPDX-License-Identifier: Apache-2.0 + +config POSIX_CLOCK_SELECTION + bool "POSIX Clock Selection" + help + Select 'y' here and Zephyr will provide an implementation of the POSIX_CLOCK_SELECTION Option + Group, which includes the functions clock_nanosleep(), pthread_condattr_getclock(), + pthread_condattr_setclock(). + + For more information, please see + https://pubs.opengroup.org/onlinepubs/9699919799/xrat/V4_subprofiles.html diff --git a/lib/posix/options/Kconfig.proc1 b/lib/posix/options/Kconfig.proc1 index 9ab29048ca2bb..30f9c8fe601d6 100644 --- a/lib/posix/options/Kconfig.proc1 +++ b/lib/posix/options/Kconfig.proc1 @@ -49,8 +49,12 @@ config POSIX_UNAME_NODENAME_LEN help Defines the maximum string length of nodename version. +endif # POSIX_SINGLE_PROCESS + +if POSIX_SINGLE_PROCESS || XSI_SINGLE_PROCESS + module = POSIX_ENV module-str = POSIX env logging source "subsys/logging/Kconfig.template.log_config" -endif # POSIX_SINGLE_PROCESS +endif # POSIX_SINGLE_PROCESS || XSI_SINGLE_PROCESS diff --git a/lib/posix/options/Kconfig.profile b/lib/posix/options/Kconfig.profile index 2eb0c9a11ce05..9fdbe71de74e1 100644 --- a/lib/posix/options/Kconfig.profile +++ b/lib/posix/options/Kconfig.profile @@ -12,6 +12,7 @@ config POSIX_API imply EVENTFD # eventfd(), eventfd_read(), eventfd_write() imply POSIX_FD_MGMT # open(), close(), read(), write() imply POSIX_MULTI_PROCESS # sleep(), getpid(), etc + imply XSI_SINGLE_PROCESS # gettimeofday() help This option enables the required POSIX System Interfaces (base definitions), all of PSE51, and some features found in PSE52. diff --git a/lib/posix/options/Kconfig.xsi_single_process b/lib/posix/options/Kconfig.xsi_single_process index ac5a8316455fa..9ef3d3a6dac96 100644 --- a/lib/posix/options/Kconfig.xsi_single_process +++ b/lib/posix/options/Kconfig.xsi_single_process @@ -4,11 +4,14 @@ config XSI_SINGLE_PROCESS bool "X/Open single process" - depends on POSIX_SINGLE_PROCESS - depends on POSIX_TIMERS + select HWINFO help Select 'y' here and Zephyr will provide implementations of gethostid(), gettimeofday(), and putenv(). For more information, please see https://pubs.opengroup.org/onlinepubs/9699919799/xrat/V4_subprofiles.html + +module = XSI_SINGLE_PROCESS +module-str = XSI Single Process +source "subsys/logging/Kconfig.template.log_config" diff --git a/lib/posix/options/clock.c b/lib/posix/options/clock.c index 61ea5f5ac6bc2..1f45334673128 100644 --- a/lib/posix/options/clock.c +++ b/lib/posix/options/clock.c @@ -5,97 +5,21 @@ * SPDX-License-Identifier: Apache-2.0 */ -#include "posix_clock.h" - -#include #include +#include + #include #include #include -#include -#include -/* - * `k_uptime_get` returns a timestamp based on an always increasing - * value from the system start. To support the `CLOCK_REALTIME` - * clock, this `rt_clock_base` records the time that the system was - * started. This can either be set via 'clock_settime', or could be - * set from a real time clock, if such hardware is present. - */ -static struct timespec rt_clock_base; -static struct k_spinlock rt_clock_base_lock; - -/** - * @brief Get clock time specified by clock_id. - * - * See IEEE 1003.1 - */ -int z_impl___posix_clock_get_base(clockid_t clock_id, struct timespec *base) -{ - switch (clock_id) { - case CLOCK_MONOTONIC: - base->tv_sec = 0; - base->tv_nsec = 0; - break; - - case CLOCK_REALTIME: - K_SPINLOCK(&rt_clock_base_lock) { - *base = rt_clock_base; - } - break; - - default: - errno = EINVAL; - return -1; - } - - return 0; -} - -#ifdef CONFIG_USERSPACE -int z_vrfy___posix_clock_get_base(clockid_t clock_id, struct timespec *ts) -{ - K_OOPS(K_SYSCALL_MEMORY_WRITE(ts, sizeof(*ts))); - return z_impl___posix_clock_get_base(clock_id, ts); -} -#include -#endif +extern int z_clock_nanosleep(clockid_t clock_id, int flags, const struct timespec *rqtp, + struct timespec *rmtp); +extern int z_clock_gettime(clockid_t clock_id, struct timespec *ts); +extern int z_clock_settime(clockid_t clock_id, const struct timespec *tp); int clock_gettime(clockid_t clock_id, struct timespec *ts) { - struct timespec base; - - switch (clock_id) { - case CLOCK_MONOTONIC: - base.tv_sec = 0; - base.tv_nsec = 0; - break; - - case CLOCK_REALTIME: - (void)__posix_clock_get_base(clock_id, &base); - break; - - default: - errno = EINVAL; - return -1; - } - - uint64_t ticks = k_uptime_ticks(); - uint64_t elapsed_secs = ticks / CONFIG_SYS_CLOCK_TICKS_PER_SEC; - uint64_t nremainder = ticks - elapsed_secs * CONFIG_SYS_CLOCK_TICKS_PER_SEC; - - ts->tv_sec = (time_t) elapsed_secs; - /* For ns 32 bit conversion can be used since its smaller than 1sec. */ - ts->tv_nsec = (int32_t) k_ticks_to_ns_floor32(nremainder); - - ts->tv_sec += base.tv_sec; - ts->tv_nsec += base.tv_nsec; - if (ts->tv_nsec >= NSEC_PER_SEC) { - ts->tv_sec++; - ts->tv_nsec -= NSEC_PER_SEC; - } - - return 0; + return z_clock_gettime(clock_id, ts); } int clock_getres(clockid_t clock_id, struct timespec *res) @@ -130,31 +54,7 @@ int clock_getres(clockid_t clock_id, struct timespec *res) */ int clock_settime(clockid_t clock_id, const struct timespec *tp) { - struct timespec base; - k_spinlock_key_t key; - - if (clock_id != CLOCK_REALTIME) { - errno = EINVAL; - return -1; - } - - if (tp->tv_nsec < 0 || tp->tv_nsec >= NSEC_PER_SEC) { - errno = EINVAL; - return -1; - } - - uint64_t elapsed_nsecs = k_ticks_to_ns_floor64(k_uptime_ticks()); - int64_t delta = (int64_t)NSEC_PER_SEC * tp->tv_sec + tp->tv_nsec - - elapsed_nsecs; - - base.tv_sec = delta / NSEC_PER_SEC; - base.tv_nsec = delta % NSEC_PER_SEC; - - key = k_spin_lock(&rt_clock_base_lock); - rt_clock_base = base; - k_spin_unlock(&rt_clock_base_lock, key); - - return 0; + return z_clock_settime(clock_id, tp); } /* @@ -186,103 +86,9 @@ int usleep(useconds_t useconds) return 0; } -/** - * @brief Suspend execution for a nanosecond interval, or - * until some absolute time relative to the specified clock. - * - * See IEEE 1003.1 - */ -static int __z_clock_nanosleep(clockid_t clock_id, int flags, const struct timespec *rqtp, - struct timespec *rmtp) -{ - uint64_t ns; - uint64_t us; - uint64_t uptime_ns; - k_spinlock_key_t key; - const bool update_rmtp = rmtp != NULL; - - if (!((clock_id == CLOCK_REALTIME) || (clock_id == CLOCK_MONOTONIC))) { - errno = EINVAL; - return -1; - } - - if (rqtp == NULL) { - errno = EFAULT; - return -1; - } - - if ((rqtp->tv_sec < 0) || (rqtp->tv_nsec < 0) || (rqtp->tv_nsec >= NSEC_PER_SEC)) { - errno = EINVAL; - return -1; - } - - if ((flags & TIMER_ABSTIME) == 0 && unlikely(rqtp->tv_sec >= ULLONG_MAX / NSEC_PER_SEC)) { - ns = rqtp->tv_nsec + NSEC_PER_SEC + - (uint64_t)k_sleep(K_SECONDS(rqtp->tv_sec - 1)) * NSEC_PER_MSEC; - } else { - ns = (uint64_t)rqtp->tv_sec * NSEC_PER_SEC + rqtp->tv_nsec; - } - - uptime_ns = k_ticks_to_ns_ceil64(sys_clock_tick_get()); - - if (flags & TIMER_ABSTIME && clock_id == CLOCK_REALTIME) { - key = k_spin_lock(&rt_clock_base_lock); - ns -= rt_clock_base.tv_sec * NSEC_PER_SEC + rt_clock_base.tv_nsec; - k_spin_unlock(&rt_clock_base_lock, key); - } - - if ((flags & TIMER_ABSTIME) == 0) { - ns += uptime_ns; - } - - if (ns <= uptime_ns) { - goto do_rmtp_update; - } - - us = DIV_ROUND_UP(ns, NSEC_PER_USEC); - do { - us = k_sleep(K_TIMEOUT_ABS_US(us)) * 1000; - } while (us != 0); - -do_rmtp_update: - if (update_rmtp) { - rmtp->tv_sec = 0; - rmtp->tv_nsec = 0; - } - - return 0; -} - int nanosleep(const struct timespec *rqtp, struct timespec *rmtp) { - return __z_clock_nanosleep(CLOCK_MONOTONIC, 0, rqtp, rmtp); -} - -int clock_nanosleep(clockid_t clock_id, int flags, const struct timespec *rqtp, - struct timespec *rmtp) -{ - return __z_clock_nanosleep(clock_id, flags, rqtp, rmtp); -} - -/** - * @brief Get current real time. - * - * See IEEE 1003.1 - */ -int gettimeofday(struct timeval *tv, void *tz) -{ - struct timespec ts; - int res; - - /* As per POSIX, "if tzp is not a null pointer, the behavior - * is unspecified." "tzp" is the "tz" parameter above. */ - ARG_UNUSED(tz); - - res = clock_gettime(CLOCK_REALTIME, &ts); - tv->tv_sec = ts.tv_sec; - tv->tv_usec = ts.tv_nsec / NSEC_PER_USEC; - - return res; + return z_clock_nanosleep(CLOCK_MONOTONIC, 0, rqtp, rmtp); } int clock_getcpuclockid(pid_t pid, clockid_t *clock_id) @@ -296,23 +102,3 @@ int clock_getcpuclockid(pid_t pid, clockid_t *clock_id) return 0; } - -#ifdef CONFIG_ZTEST -#include -static void reset_clock_base(void) -{ - K_SPINLOCK(&rt_clock_base_lock) { - rt_clock_base = (struct timespec){0}; - } -} - -static void clock_base_reset_rule_after(const struct ztest_unit_test *test, void *data) -{ - ARG_UNUSED(test); - ARG_UNUSED(data); - - reset_clock_base(); -} - -ZTEST_RULE(clock_base_reset_rule, NULL, clock_base_reset_rule_after); -#endif /* CONFIG_ZTEST */ diff --git a/lib/posix/options/clock_common.c b/lib/posix/options/clock_common.c new file mode 100644 index 0000000000000..2819f3e3c4343 --- /dev/null +++ b/lib/posix/options/clock_common.c @@ -0,0 +1,202 @@ +/* + * Copyright (c) 2018 Intel Corporation + * Copyright (c) 2018 Friedt Professional Engineering Services, Inc + * Copyright (c) 2025 Tenstorrent AI ULC + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "posix_clock.h" + +#include +#include +#include +#include +#include +#include +#include + +/* + * `k_uptime_get` returns a timestamp based on an always increasing + * value from the system start. To support the `CLOCK_REALTIME` + * clock, this `rt_clock_base` records the time that the system was + * started. This can either be set via 'clock_settime', or could be + * set from a real time clock, if such hardware is present. + */ +static struct timespec rt_clock_base; +static SYS_SEM_DEFINE(rt_clock_base_lock, 1, 1); + +int z_impl___posix_clock_get_base(clockid_t clock_id, struct timespec *base) +{ + switch (clock_id) { + case CLOCK_MONOTONIC: + base->tv_sec = 0; + base->tv_nsec = 0; + break; + + case CLOCK_REALTIME: + SYS_SEM_LOCK(&rt_clock_base_lock) { + *base = rt_clock_base; + } + break; + + default: + errno = EINVAL; + return -1; + } + + return 0; +} + +#ifdef CONFIG_USERSPACE +int z_vrfy___posix_clock_get_base(clockid_t clock_id, struct timespec *ts) +{ + K_OOPS(K_SYSCALL_MEMORY_WRITE(ts, sizeof(*ts))); + return z_impl___posix_clock_get_base(clock_id, ts); +} +#include +#endif + +int z_clock_gettime(clockid_t clock_id, struct timespec *ts) +{ + struct timespec base; + + switch (clock_id) { + case CLOCK_MONOTONIC: + base.tv_sec = 0; + base.tv_nsec = 0; + break; + + case CLOCK_REALTIME: + (void)__posix_clock_get_base(clock_id, &base); + break; + + default: + errno = EINVAL; + return -1; + } + + uint64_t ticks = k_uptime_ticks(); + uint64_t elapsed_secs = ticks / CONFIG_SYS_CLOCK_TICKS_PER_SEC; + uint64_t nremainder = ticks - elapsed_secs * CONFIG_SYS_CLOCK_TICKS_PER_SEC; + + ts->tv_sec = (time_t)elapsed_secs; + /* For ns 32 bit conversion can be used since its smaller than 1sec. */ + ts->tv_nsec = (int32_t)k_ticks_to_ns_floor32(nremainder); + + ts->tv_sec += base.tv_sec; + ts->tv_nsec += base.tv_nsec; + if (ts->tv_nsec >= NSEC_PER_SEC) { + ts->tv_sec++; + ts->tv_nsec -= NSEC_PER_SEC; + } + + return 0; +} + +int z_clock_settime(clockid_t clock_id, const struct timespec *tp) +{ + struct timespec base; + + if (clock_id != CLOCK_REALTIME) { + errno = EINVAL; + return -1; + } + + if (tp->tv_nsec < 0 || tp->tv_nsec >= NSEC_PER_SEC) { + errno = EINVAL; + return -1; + } + + uint64_t elapsed_nsecs = k_ticks_to_ns_floor64(k_uptime_ticks()); + int64_t delta = (int64_t)NSEC_PER_SEC * tp->tv_sec + tp->tv_nsec - elapsed_nsecs; + + base.tv_sec = delta / NSEC_PER_SEC; + base.tv_nsec = delta % NSEC_PER_SEC; + + SYS_SEM_LOCK(&rt_clock_base_lock) { + rt_clock_base = base; + } + + return 0; +} + +int z_clock_nanosleep(clockid_t clock_id, int flags, const struct timespec *rqtp, + struct timespec *rmtp) +{ + uint64_t ns; + uint64_t us; + uint64_t uptime_ns; + const bool update_rmtp = rmtp != NULL; + + if (!((clock_id == CLOCK_REALTIME) || (clock_id == CLOCK_MONOTONIC))) { + errno = EINVAL; + return -1; + } + + if (rqtp == NULL) { + errno = EFAULT; + return -1; + } + + if ((rqtp->tv_sec < 0) || (rqtp->tv_nsec < 0) || (rqtp->tv_nsec >= NSEC_PER_SEC)) { + errno = EINVAL; + return -1; + } + + if ((flags & TIMER_ABSTIME) == 0 && unlikely(rqtp->tv_sec >= ULLONG_MAX / NSEC_PER_SEC)) { + ns = rqtp->tv_nsec + NSEC_PER_SEC + + (uint64_t)k_sleep(K_SECONDS(rqtp->tv_sec - 1)) * NSEC_PER_MSEC; + } else { + ns = (uint64_t)rqtp->tv_sec * NSEC_PER_SEC + rqtp->tv_nsec; + } + + uptime_ns = k_ticks_to_ns_ceil64(sys_clock_tick_get()); + + if (flags & TIMER_ABSTIME && clock_id == CLOCK_REALTIME) { + SYS_SEM_LOCK(&rt_clock_base_lock) { + ns -= rt_clock_base.tv_sec * NSEC_PER_SEC + rt_clock_base.tv_nsec; + } + } + + if ((flags & TIMER_ABSTIME) == 0) { + ns += uptime_ns; + } + + if (ns <= uptime_ns) { + goto do_rmtp_update; + } + + us = DIV_ROUND_UP(ns, NSEC_PER_USEC); + do { + us = k_sleep(K_TIMEOUT_ABS_US(us)) * 1000; + } while (us != 0); + +do_rmtp_update: + if (update_rmtp) { + rmtp->tv_sec = 0; + rmtp->tv_nsec = 0; + } + + return 0; +} + +#ifdef CONFIG_ZTEST +#include +static void reset_clock_base(void) +{ + SYS_SEM_LOCK(&rt_clock_base_lock) { + rt_clock_base = (struct timespec){0}; + } +} + +static void clock_base_reset_rule_after(const struct ztest_unit_test *test, void *data) +{ + ARG_UNUSED(test); + ARG_UNUSED(data); + + reset_clock_base(); +} + +ZTEST_RULE(clock_base_reset_rule, NULL, clock_base_reset_rule_after); +#endif /* CONFIG_ZTEST */ diff --git a/lib/posix/options/clock_selection.c b/lib/posix/options/clock_selection.c new file mode 100644 index 0000000000000..6bddc3212080c --- /dev/null +++ b/lib/posix/options/clock_selection.c @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2023 Meta + * Copyright (c) 2025 Tenstorrent AI ULC + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "posix_clock.h" +#include "posix_internal.h" + +#include +#include +#include + +#include +#include + +extern int z_clock_nanosleep(clockid_t clock_id, int flags, const struct timespec *rqtp, + struct timespec *rmtp); + +extern int clock_nanosleep(clockid_t clock_id, int flags, const struct timespec *rqtp, + struct timespec *rmtp) +{ + return z_clock_nanosleep(clock_id, flags, rqtp, rmtp); +} + +int pthread_condattr_getclock(const pthread_condattr_t *ZRESTRICT att, + clockid_t *ZRESTRICT clock_id) +{ + struct posix_condattr *const attr = (struct posix_condattr *)att; + + if ((attr == NULL) || !attr->initialized) { + return EINVAL; + } + + *clock_id = attr->clock; + + return 0; +} + +int pthread_condattr_setclock(pthread_condattr_t *att, clockid_t clock_id) +{ + struct posix_condattr *const attr = (struct posix_condattr *)att; + + if (clock_id != CLOCK_REALTIME && clock_id != CLOCK_MONOTONIC) { + return -EINVAL; + } + + if ((attr == NULL) || !attr->initialized) { + return EINVAL; + } + + attr->clock = clock_id; + + return 0; +} diff --git a/lib/posix/options/cond.c b/lib/posix/options/cond.c index c93386ab5392a..f2b5af94ad725 100644 --- a/lib/posix/options/cond.c +++ b/lib/posix/options/cond.c @@ -276,36 +276,4 @@ int pthread_condattr_destroy(pthread_condattr_t *att) return 0; } -int pthread_condattr_getclock(const pthread_condattr_t *ZRESTRICT att, - clockid_t *ZRESTRICT clock_id) -{ - struct posix_condattr *const attr = (struct posix_condattr *)att; - - if ((attr == NULL) || !attr->initialized) { - LOG_DBG("%s %s initialized", "attribute", "not"); - return EINVAL; - } - - *clock_id = attr->clock; - - return 0; -} - -int pthread_condattr_setclock(pthread_condattr_t *att, clockid_t clock_id) -{ - struct posix_condattr *const attr = (struct posix_condattr *)att; - - if (clock_id != CLOCK_REALTIME && clock_id != CLOCK_MONOTONIC) { - return -EINVAL; - } - - if ((attr == NULL) || !attr->initialized) { - LOG_DBG("%s %s initialized", "attribute", "not"); - return EINVAL; - } - - attr->clock = clock_id; - - return 0; -} SYS_INIT(pthread_cond_pool_init, PRE_KERNEL_1, 0); diff --git a/lib/posix/options/env.c b/lib/posix/options/env.c index 56d3b4ae5430e..b8819280d7398 100644 --- a/lib/posix/options/env.c +++ b/lib/posix/options/env.c @@ -4,264 +4,29 @@ * SPDX-License-Identifier: Apache-2.0 */ -#include -#include -#include -#include +#include -#include -#include - -#define TRACK_ALLOC (IS_ENABLED(CONFIG_POSIX_ENV_LOG_LEVEL_DBG) || IS_ENABLED(CONFIG_ZTEST)) - -LOG_MODULE_REGISTER(posix_env, CONFIG_POSIX_ENV_LOG_LEVEL); - -static struct k_spinlock environ_lock; -static size_t allocated; -char **environ; - -#ifdef CONFIG_ZTEST -size_t posix_env_get_allocated_space(void) -{ - return allocated; -} -#endif - -static size_t environ_size(void) -{ - size_t ret; - - if (environ == NULL) { - return 0; - } - - for (ret = 0; environ[ret] != NULL; ++ret) { - } - - return ret; -} - -static int findenv(const char *name, size_t namelen) -{ - const char *env; - - if (name == NULL || namelen == 0 || strchr(name, '=') != NULL) { - /* Note: '=' is not a valid name character */ - return -EINVAL; - } - - if (environ == NULL) { - return -ENOENT; - } - - for (char **envp = &environ[0]; *envp != NULL; ++envp) { - env = *envp; - if (strncmp(env, name, namelen) == 0 && env[namelen] == '=') { - return envp - environ; - } - } - - return -ENOENT; -} +extern char *z_getenv(const char *name); +extern int z_getenv_r(const char *name, char *buf, size_t len); +extern int z_setenv(const char *name, const char *val, int overwrite); +extern int z_unsetenv(const char *name); char *getenv(const char *name) { - int ret; - size_t nsize; - char *val = NULL; - - nsize = (name == NULL) ? 0 : strlen(name); - K_SPINLOCK(&environ_lock) - { - ret = findenv(name, nsize); - if (ret < 0) { - K_SPINLOCK_BREAK; - } - - val = environ[ret] + nsize + 1; - } - - return val; + return z_getenv(name); } int getenv_r(const char *name, char *buf, size_t len) { - int ret = 0; - size_t vsize; - size_t nsize; - char *val = NULL; - - nsize = (name == NULL) ? 0 : strlen(name); - K_SPINLOCK(&environ_lock) - { - ret = findenv(name, nsize); - if (ret < 0) { - LOG_DBG("No entry for name '%s'", name); - K_SPINLOCK_BREAK; - } - - val = environ[ret] + nsize + 1; - vsize = strlen(val) + 1; - if (vsize > len) { - ret = -ERANGE; - K_SPINLOCK_BREAK; - } - strcpy(buf, val); - LOG_DBG("Found entry %s", environ[ret]); - } - - if (ret < 0) { - errno = -ret; - ret = -1; - } - - return ret; + return z_getenv_r(name, buf, len); } int setenv(const char *name, const char *val, int overwrite) { - int ret = 0; - char *env; - char **envp; - size_t esize; - const size_t vsize = (val == NULL) ? 0 : strlen(val); - const size_t nsize = (name == NULL) ? 0 : strlen(name); - /* total size of name + '=' + val + '\0' */ - const size_t tsize = nsize + 1 /* '=' */ + vsize + 1 /* '\0' */; - - if (name == NULL || val == NULL) { - LOG_DBG("Invalid name '%s' or value '%s'", name, val); - errno = EINVAL; - return -1; - } - - K_SPINLOCK(&environ_lock) - { - ret = findenv(name, nsize); - if (ret == -EINVAL) { - LOG_DBG("Invalid name '%s'", name); - K_SPINLOCK_BREAK; - } - if (ret >= 0) { - /* name was found in environ */ - esize = strlen(environ[ret]) + 1; - if (overwrite == 0) { - LOG_DBG("Found entry %s", environ[ret]); - ret = 0; - K_SPINLOCK_BREAK; - } - } else { - /* name was not found in environ -> add new entry */ - esize = environ_size(); - envp = realloc(environ, sizeof(char **) * - (esize + 1 /* new entry */ + 1 /* NULL */)); - if (envp == NULL) { - ret = -ENOMEM; - K_SPINLOCK_BREAK; - } - - if (TRACK_ALLOC) { - allocated += sizeof(char **) * (esize + 2); - LOG_DBG("realloc %zu bytes (allocated: %zu)", - sizeof(char **) * (esize + 2), allocated); - } - - environ = envp; - ret = esize; - environ[ret] = NULL; - environ[ret + 1] = NULL; - esize = 0; - } - - if (esize < tsize) { - /* need to malloc or realloc space for new environ entry */ - env = realloc(environ[ret], tsize); - if (env == NULL) { - ret = -ENOMEM; - K_SPINLOCK_BREAK; - } - if (TRACK_ALLOC) { - allocated += tsize - esize; - LOG_DBG("realloc %zu bytes (allocated: %zu)", tsize - esize, - allocated); - } - environ[ret] = env; - } - - strcpy(environ[ret], name); - environ[ret][nsize] = '='; - strncpy(environ[ret] + nsize + 1, val, vsize + 1); - LOG_DBG("Added entry %s", environ[ret]); - - ret = 0; - } - - if (ret < 0) { - errno = -ret; - ret = -1; - } - - return ret; + return z_setenv(name, val, overwrite); } int unsetenv(const char *name) { - int ret = 0; - char **envp; - size_t esize; - size_t nsize; - - nsize = (name == NULL) ? 0 : strlen(name); - K_SPINLOCK(&environ_lock) - { - ret = findenv(name, nsize); - if (ret < 0) { - ret = (ret == -EINVAL) ? -EINVAL : 0; - K_SPINLOCK_BREAK; - } - - esize = environ_size(); - if (TRACK_ALLOC) { - allocated -= strlen(environ[ret]) + 1; - LOG_DBG("free %zu bytes (allocated: %zu)", strlen(environ[ret]) + 1, - allocated); - } - free(environ[ret]); - - /* shuffle remaining environment variable pointers forward */ - for (; ret < esize; ++ret) { - environ[ret] = environ[ret + 1]; - } - /* environ must be terminated with a NULL pointer */ - environ[ret] = NULL; - - /* reduce environ size and update allocation */ - --esize; - if (esize == 0) { - free(environ); - environ = NULL; - } else { - envp = realloc(environ, (esize + 1 /* NULL */) * sizeof(char **)); - if (envp != NULL) { - environ = envp; - } - } - __ASSERT_NO_MSG((esize >= 1 && environ != NULL) || environ == NULL); - - if (TRACK_ALLOC) { - /* recycle nsize here */ - nsize = ((esize == 0) ? 2 : 1) * sizeof(char **); - allocated -= nsize; - LOG_DBG("free %zu bytes (allocated: %zu)", nsize, allocated); - } - - ret = 0; - } - - if (ret < 0) { - errno = -ret; - ret = -1; - } - - return ret; + return z_unsetenv(name); } diff --git a/lib/posix/options/env_common.c b/lib/posix/options/env_common.c new file mode 100644 index 0000000000000..d18049f7ab5ed --- /dev/null +++ b/lib/posix/options/env_common.c @@ -0,0 +1,264 @@ +/* + * Copyright (c) 2023, Meta + * Copyright (c) 2025 Tenstorrent AI ULC + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include + +#include +#include + +#define TRACK_ALLOC (IS_ENABLED(CONFIG_POSIX_ENV_LOG_LEVEL_DBG) || IS_ENABLED(CONFIG_ZTEST)) + +LOG_MODULE_REGISTER(posix_env, CONFIG_POSIX_ENV_LOG_LEVEL); + +static SYS_SEM_DEFINE(environ_lock, 1, 1); +static size_t allocated; +char **environ; + +#ifdef CONFIG_ZTEST +size_t posix_env_get_allocated_space(void) +{ + return allocated; +} +#endif + +static size_t environ_size(void) +{ + size_t ret; + + if (environ == NULL) { + return 0; + } + + for (ret = 0; environ[ret] != NULL; ++ret) { + } + + return ret; +} + +static int findenv(const char *name, size_t namelen) +{ + const char *env; + + if (name == NULL || namelen == 0 || strchr(name, '=') != NULL) { + /* Note: '=' is not a valid name character */ + return -EINVAL; + } + + if (environ == NULL) { + return -ENOENT; + } + + for (char **envp = &environ[0]; *envp != NULL; ++envp) { + env = *envp; + if (strncmp(env, name, namelen) == 0 && env[namelen] == '=') { + return envp - environ; + } + } + + return -ENOENT; +} + +char *z_getenv(const char *name) +{ + int ret; + size_t nsize; + char *val = NULL; + + nsize = (name == NULL) ? 0 : strlen(name); + SYS_SEM_LOCK(&environ_lock) { + ret = findenv(name, nsize); + if (ret < 0) { + SYS_SEM_LOCK_BREAK; + } + + val = environ[ret] + nsize + 1; + } + + return val; +} + +int z_getenv_r(const char *name, char *buf, size_t len) +{ + int ret = 0; + size_t vsize; + size_t nsize; + char *val = NULL; + + nsize = (name == NULL) ? 0 : strlen(name); + SYS_SEM_LOCK(&environ_lock) { + ret = findenv(name, nsize); + if (ret < 0) { + LOG_DBG("No entry for name '%s'", name); + SYS_SEM_LOCK_BREAK; + } + + val = environ[ret] + nsize + 1; + vsize = strlen(val) + 1; + if (vsize > len) { + ret = -ERANGE; + SYS_SEM_LOCK_BREAK; + } + strcpy(buf, val); + LOG_DBG("Found entry %s", environ[ret]); + } + + if (ret < 0) { + errno = -ret; + ret = -1; + } + + return ret; +} + +int z_setenv(const char *name, const char *val, int overwrite) +{ + int ret = 0; + char *env; + char **envp; + size_t esize; + const size_t vsize = (val == NULL) ? 0 : strlen(val); + const size_t nsize = (name == NULL) ? 0 : strlen(name); + /* total size of name + '=' + val + '\0' */ + const size_t tsize = nsize + 1 /* '=' */ + vsize + 1 /* '\0' */; + + if (name == NULL || val == NULL) { + LOG_DBG("Invalid name '%s' or value '%s'", name, val); + errno = EINVAL; + return -1; + } + + SYS_SEM_LOCK(&environ_lock) { + ret = findenv(name, nsize); + if (ret == -EINVAL) { + LOG_DBG("Invalid name '%s'", name); + SYS_SEM_LOCK_BREAK; + } + if (ret >= 0) { + /* name was found in environ */ + esize = strlen(environ[ret]) + 1; + if (overwrite == 0) { + LOG_DBG("Found entry %s", environ[ret]); + ret = 0; + SYS_SEM_LOCK_BREAK; + } + } else { + /* name was not found in environ -> add new entry */ + esize = environ_size(); + envp = realloc(environ, sizeof(char **) * + (esize + 1 /* new entry */ + 1 /* NULL */)); + if (envp == NULL) { + ret = -ENOMEM; + SYS_SEM_LOCK_BREAK; + } + + if (TRACK_ALLOC) { + allocated += sizeof(char **) * (esize + 2); + LOG_DBG("realloc %zu bytes (allocated: %zu)", + sizeof(char **) * (esize + 2), allocated); + } + + environ = envp; + ret = esize; + environ[ret] = NULL; + environ[ret + 1] = NULL; + esize = 0; + } + + if (esize < tsize) { + /* need to malloc or realloc space for new environ entry */ + env = realloc(environ[ret], tsize); + if (env == NULL) { + ret = -ENOMEM; + SYS_SEM_LOCK_BREAK; + } + if (TRACK_ALLOC) { + allocated += tsize - esize; + LOG_DBG("realloc %zu bytes (allocated: %zu)", tsize - esize, + allocated); + } + environ[ret] = env; + } + + strcpy(environ[ret], name); + environ[ret][nsize] = '='; + strncpy(environ[ret] + nsize + 1, val, vsize + 1); + LOG_DBG("Added entry %s", environ[ret]); + + ret = 0; + } + + if (ret < 0) { + errno = -ret; + ret = -1; + } + + return ret; +} + +int z_unsetenv(const char *name) +{ + int ret = 0; + char **envp; + size_t esize; + size_t nsize; + + nsize = (name == NULL) ? 0 : strlen(name); + SYS_SEM_LOCK(&environ_lock) { + ret = findenv(name, nsize); + if (ret < 0) { + ret = (ret == -EINVAL) ? -EINVAL : 0; + SYS_SEM_LOCK_BREAK; + } + + esize = environ_size(); + if (TRACK_ALLOC) { + allocated -= strlen(environ[ret]) + 1; + LOG_DBG("free %zu bytes (allocated: %zu)", strlen(environ[ret]) + 1, + allocated); + } + free(environ[ret]); + + /* shuffle remaining environment variable pointers forward */ + for (; ret < esize; ++ret) { + environ[ret] = environ[ret + 1]; + } + /* environ must be terminated with a NULL pointer */ + environ[ret] = NULL; + + /* reduce environ size and update allocation */ + --esize; + if (esize == 0) { + free(environ); + environ = NULL; + } else { + envp = realloc(environ, (esize + 1 /* NULL */) * sizeof(char **)); + if (envp != NULL) { + environ = envp; + } + } + __ASSERT_NO_MSG((esize >= 1 && environ != NULL) || environ == NULL); + + if (TRACK_ALLOC) { + /* recycle nsize here */ + nsize = ((esize == 0) ? 2 : 1) * sizeof(char **); + allocated -= nsize; + LOG_DBG("free %zu bytes (allocated: %zu)", nsize, allocated); + } + + ret = 0; + } + + if (ret < 0) { + errno = -ret; + ret = -1; + } + + return ret; +} diff --git a/lib/posix/options/xsi_single_process.c b/lib/posix/options/xsi_single_process.c new file mode 100644 index 0000000000000..cc5e82cb99a75 --- /dev/null +++ b/lib/posix/options/xsi_single_process.c @@ -0,0 +1,92 @@ +/* + * Copyright (c) 2018 Intel Corporation + * Copyright (c) 2023 Meta + * Copyright (c) 2025 Tenstorrent AI ULC + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include + +#include +#include +#include +#include + +LOG_MODULE_REGISTER(xsi_single_process, CONFIG_XSI_SINGLE_PROCESS_LOG_LEVEL); + +extern int z_setenv(const char *name, const char *val, int overwrite); +extern int z_clock_gettime(clockid_t clockid, struct timespec *tp); + +long gethostid(void) +{ + int rc; + uint32_t buf = 0; + + rc = hwinfo_get_device_id((uint8_t *)&buf, sizeof(buf)); + if ((rc < 0) || (rc != sizeof(buf)) || (buf == 0)) { + LOG_DBG("%s() failed: %d", "hwinfo_get_device_id", rc); + return (long)rc; + } + + return (long)buf; +} + +int gettimeofday(struct timeval *tv, void *tz) +{ + struct timespec ts; + int res; + + /* + * As per POSIX, "if tzp is not a null pointer, the behavior is unspecified." "tzp" is the + * "tz" parameter above. + */ + ARG_UNUSED(tz); + + res = z_clock_gettime(CLOCK_REALTIME, &ts); + if (res < 0) { + LOG_DBG("%s() failed: %d", "clock_gettime", res); + return res; + } + + tv->tv_sec = ts.tv_sec; + tv->tv_usec = ts.tv_nsec / NSEC_PER_USEC; + + return 0; +} + +int putenv(char *string) +{ + if (string == NULL) { + errno = EINVAL; + return -1; + } + + char *const name = string; + + for (char *val = name; *val != '\0'; ++val) { + if (*val == '=') { + int rc; + + *val = '\0'; + ++val; + rc = z_setenv(name, val, 1); + --val; + *val = '='; + + if (rc < 0) { + LOG_DBG("%s() failed: %d", "setenv", rc); + return rc; + } + + return 0; + } + } + + /* was unable to find '=' in string */ + errno = EINVAL; + return -1; +} diff --git a/tests/lib/time/prj.conf b/tests/lib/time/prj.conf index ce932c79220e4..dd87f5a1338c0 100644 --- a/tests/lib/time/prj.conf +++ b/tests/lib/time/prj.conf @@ -1,3 +1,4 @@ CONFIG_ZTEST=y CONFIG_POSIX_TIMERS=y +CONFIG_XSI_SINGLE_PROCESS=y CONFIG_PICOLIBC=y diff --git a/tests/posix/clock_selection/CMakeLists.txt b/tests/posix/clock_selection/CMakeLists.txt new file mode 100644 index 0000000000000..e66e7e7ebdf79 --- /dev/null +++ b/tests/posix/clock_selection/CMakeLists.txt @@ -0,0 +1,9 @@ +# SPDX-License-Identifier: Apache-2.0 + +cmake_minimum_required(VERSION 3.20.0) +find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) +project(posix_clock_selection) + +target_sources(app PRIVATE src/main.c ../timers/src/nanosleep_common.c) + +target_compile_options(app PRIVATE -U_POSIX_C_SOURCE -D_POSIX_C_SOURCE=200809L) diff --git a/tests/posix/clock_selection/prj.conf b/tests/posix/clock_selection/prj.conf new file mode 100644 index 0000000000000..ef606a6a135d3 --- /dev/null +++ b/tests/posix/clock_selection/prj.conf @@ -0,0 +1,9 @@ +CONFIG_POSIX_API=y +CONFIG_ZTEST=y + +CONFIG_POSIX_AEP_CHOICE_BASE=y +CONFIG_POSIX_CLOCK_SELECTION=y + +CONFIG_DYNAMIC_THREAD=y +CONFIG_DYNAMIC_THREAD_POOL_SIZE=3 +CONFIG_THREAD_STACK_INFO=y diff --git a/tests/posix/clock_selection/src/main.c b/tests/posix/clock_selection/src/main.c new file mode 100644 index 0000000000000..91c6aa9ea3539 --- /dev/null +++ b/tests/posix/clock_selection/src/main.c @@ -0,0 +1,116 @@ +/* + * Copyright (c) 2018 Intel Corporation + * Copyright (c) 2025 Tenstorrent AI ULC + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include + +#include +#include +#include + +#define SELECT_NANOSLEEP 1 +#define SELECT_CLOCK_NANOSLEEP 0 + +void common_lower_bound_check(int selection, clockid_t clock_id, int flags, const uint32_t s, + uint32_t ns); +int select_nanosleep(int selection, clockid_t clock_id, int flags, const struct timespec *rqtp, + struct timespec *rmtp); + +ZTEST(posix_clock_selection, test_clock_nanosleep_execution) +{ + struct timespec ts; + + clock_gettime(CLOCK_MONOTONIC, &ts); + + /* absolute sleeps with the monotonic clock and reference time ts */ + + /* until 1s + 1ns past the reference time */ + common_lower_bound_check(SELECT_CLOCK_NANOSLEEP, CLOCK_MONOTONIC, TIMER_ABSTIME, + ts.tv_sec + 1, 1); + + /* until 1s + 1us past the reference time */ + common_lower_bound_check(SELECT_CLOCK_NANOSLEEP, CLOCK_MONOTONIC, TIMER_ABSTIME, + ts.tv_sec + 1, 1000); + + /* until 1s + 500000000ns past the reference time */ + common_lower_bound_check(SELECT_CLOCK_NANOSLEEP, CLOCK_MONOTONIC, TIMER_ABSTIME, + ts.tv_sec + 1, 500000000); + + /* until 2s past the reference time */ + common_lower_bound_check(SELECT_CLOCK_NANOSLEEP, CLOCK_MONOTONIC, TIMER_ABSTIME, + ts.tv_sec + 2, 0); + + /* until 2s + 1ns past the reference time */ + common_lower_bound_check(SELECT_CLOCK_NANOSLEEP, CLOCK_MONOTONIC, TIMER_ABSTIME, + ts.tv_sec + 2, 1); + + /* until 2s + 1us + 1ns past reference time */ + common_lower_bound_check(SELECT_CLOCK_NANOSLEEP, CLOCK_MONOTONIC, TIMER_ABSTIME, + ts.tv_sec + 2, 1001); + + clock_gettime(CLOCK_REALTIME, &ts); + + /* absolute sleeps with the real time clock and adjusted reference time ts */ + + /* until 1s + 1ns past the reference time */ + common_lower_bound_check(SELECT_CLOCK_NANOSLEEP, CLOCK_REALTIME, TIMER_ABSTIME, + ts.tv_sec + 1, 1); + + /* until 1s + 1us past the reference time */ + common_lower_bound_check(SELECT_CLOCK_NANOSLEEP, CLOCK_REALTIME, TIMER_ABSTIME, + ts.tv_sec + 1, 1000); + + /* until 1s + 500000000ns past the reference time */ + common_lower_bound_check(SELECT_CLOCK_NANOSLEEP, CLOCK_REALTIME, TIMER_ABSTIME, + ts.tv_sec + 1, 500000000); + + /* until 2s past the reference time */ + common_lower_bound_check(SELECT_CLOCK_NANOSLEEP, CLOCK_REALTIME, TIMER_ABSTIME, + ts.tv_sec + 2, 0); + + /* until 2s + 1ns past the reference time */ + common_lower_bound_check(SELECT_CLOCK_NANOSLEEP, CLOCK_REALTIME, TIMER_ABSTIME, + ts.tv_sec + 2, 1); + + /* until 2s + 1us + 1ns past the reference time */ + common_lower_bound_check(SELECT_CLOCK_NANOSLEEP, CLOCK_REALTIME, TIMER_ABSTIME, + ts.tv_sec + 2, 1001); +} + +ZTEST(posix_clock_selection, test_pthread_condattr_getclock) +{ + clockid_t clock_id; + pthread_condattr_t att = {0}; + + zassert_ok(pthread_condattr_init(&att)); + + zassert_ok(pthread_condattr_getclock(&att, &clock_id), "pthread_condattr_getclock failed"); + zassert_equal(clock_id, CLOCK_REALTIME, "clock attribute not set correctly"); + + zassert_ok(pthread_condattr_destroy(&att)); +} + +ZTEST(posix_clock_selection, test_pthread_condattr_setclock) +{ + clockid_t clock_id; + pthread_condattr_t att = {0}; + + zassert_ok(pthread_condattr_init(&att)); + + zassert_ok(pthread_condattr_setclock(&att, CLOCK_MONOTONIC), + "pthread_condattr_setclock failed"); + + zassert_ok(pthread_condattr_getclock(&att, &clock_id), "pthread_condattr_setclock failed"); + zassert_equal(clock_id, CLOCK_MONOTONIC, "clock attribute not set correctly"); + + zassert_equal(pthread_condattr_setclock(&att, 42), -EINVAL, + "pthread_condattr_setclock did not return EINVAL"); + + zassert_ok(pthread_condattr_destroy(&att)); +} + +ZTEST_SUITE(posix_clock_selection, NULL, NULL, NULL, NULL, NULL); diff --git a/tests/posix/clock_selection/testcase.yaml b/tests/posix/clock_selection/testcase.yaml new file mode 100644 index 0000000000000..e58bdfdf42638 --- /dev/null +++ b/tests/posix/clock_selection/testcase.yaml @@ -0,0 +1,28 @@ +common: + filter: not CONFIG_NATIVE_LIBC + tags: + - posix + - clock_selection + # 1 tier0 platform per supported architecture + platform_key: + - arch + - simulation + integration_platforms: + - qemu_riscv64 + min_flash: 64 + min_ram: 32 +tests: + portability.posix.clock_selection: {} + portability.posix.clock_selection.minimal: + extra_configs: + - CONFIG_MINIMAL_LIBC=y + portability.posix.clock_selection.newlib: + filter: TOOLCHAIN_HAS_NEWLIB == 1 + extra_configs: + - CONFIG_NEWLIB_LIBC=y + - CONFIG_NEWLIB_LIBC_MIN_REQUIRED_HEAP_SIZE=8192 + portability.posix.clock_selection.picolibc: + tags: picolibc + filter: CONFIG_PICOLIBC_SUPPORTED + extra_configs: + - CONFIG_PICOLIBC=y diff --git a/tests/posix/common/src/cond.c b/tests/posix/common/src/cond.c index 83d3c84756d89..e269b1f9d117f 100644 --- a/tests/posix/common/src/cond.c +++ b/tests/posix/common/src/cond.c @@ -49,23 +49,10 @@ ZTEST(cond, test_cond_resource_leak) ZTEST(cond, test_pthread_condattr) { - clockid_t clock_id; pthread_condattr_t att = {0}; zassert_ok(pthread_condattr_init(&att)); - zassert_ok(pthread_condattr_getclock(&att, &clock_id), "pthread_condattr_getclock failed"); - zassert_equal(clock_id, CLOCK_REALTIME, "clock attribute not set correctly"); - - zassert_ok(pthread_condattr_setclock(&att, CLOCK_REALTIME), - "pthread_condattr_setclock failed"); - - zassert_ok(pthread_condattr_getclock(&att, &clock_id), "pthread_condattr_setclock failed"); - zassert_equal(clock_id, CLOCK_REALTIME, "clock attribute not set correctly"); - - zassert_equal(pthread_condattr_setclock(&att, 42), -EINVAL, - "pthread_condattr_setclock did not return EINVAL"); - zassert_ok(pthread_condattr_destroy(&att)); } diff --git a/tests/posix/headers/src/sys_time_h.c b/tests/posix/headers/src/sys_time_h.c index aa766943961fe..4de47eaf78590 100644 --- a/tests/posix/headers/src/sys_time_h.c +++ b/tests/posix/headers/src/sys_time_h.c @@ -29,7 +29,7 @@ ZTEST(posix_headers, test_sys_time_h) /* zassert_not_equal(-1, ITIMER_VIRTUAL); */ /* not implemented */ /* zassert_not_equal(-1, ITIMER_PROF); */ /* not implemented */ - if (IS_ENABLED(CONFIG_POSIX_API)) { + if (IS_ENABLED(CONFIG_XSI_SINGLE_PROCESS)) { /* zassert_not_null(getitimer); */ /* not implemented */ zassert_not_null(gettimeofday); /* zassert_not_null(setitimer); */ /* not implemented */ diff --git a/tests/posix/timers/src/nanosleep.c b/tests/posix/timers/src/nanosleep.c index 94e40a62f60eb..a11e5c3b3a2f7 100644 --- a/tests/posix/timers/src/nanosleep.c +++ b/tests/posix/timers/src/nanosleep.c @@ -14,23 +14,10 @@ #define SELECT_NANOSLEEP 1 #define SELECT_CLOCK_NANOSLEEP 0 -static inline int select_nanosleep(int selection, clockid_t clock_id, int flags, - const struct timespec *rqtp, struct timespec *rmtp) -{ - if (selection == SELECT_NANOSLEEP) { - return nanosleep(rqtp, rmtp); - } - return clock_nanosleep(clock_id, flags, rqtp, rmtp); -} - -static inline uint64_t cycle_get_64(void) -{ - if (IS_ENABLED(CONFIG_TIMER_HAS_64BIT_CYCLE_COUNTER)) { - return k_cycle_get_64(); - } else { - return k_cycle_get_32(); - } -} +void common_lower_bound_check(int selection, clockid_t clock_id, int flags, const uint32_t s, + uint32_t ns); +int select_nanosleep(int selection, clockid_t clock_id, int flags, const struct timespec *rqtp, + struct timespec *rmtp); static void common_errors(int selection, clockid_t clock_id, int flags) { @@ -129,77 +116,6 @@ ZTEST(posix_timers, test_clock_nanosleep_errors_errno) zassert_equal(rem.tv_nsec, 0, "actual: %d expected: %d", rem.tv_nsec, 0); } -/** - * @brief Check that a call to nanosleep has yielded execution for some minimum time. - * - * Check that the actual time slept is >= the total time specified by @p s (in seconds) and - * @p ns (in nanoseconds). - * - * @note The time specified by @p s and @p ns is assumed to be absolute (i.e. a time-point) - * when @p selection is set to @ref SELECT_CLOCK_NANOSLEEP. The time is assumed to be relative - * when @p selection is set to @ref SELECT_NANOSLEEP. - * - * @param selection Either @ref SELECT_CLOCK_NANOSLEEP or @ref SELECT_NANOSLEEP - * @param clock_id The clock to test (e.g. @ref CLOCK_MONOTONIC or @ref CLOCK_REALTIME) - * @param flags Flags to pass to @ref clock_nanosleep - * @param s Partial lower bound for yielded time (in seconds) - * @param ns Partial lower bound for yielded time (in nanoseconds) - */ -static void common_lower_bound_check(int selection, clockid_t clock_id, int flags, const uint32_t s, - uint32_t ns) -{ - int r; - uint64_t actual_ns = 0; - uint64_t exp_ns; - uint64_t now; - uint64_t then; - struct timespec rem = {0, 0}; - struct timespec req = {s, ns}; - - errno = 0; - then = cycle_get_64(); - r = select_nanosleep(selection, clock_id, flags, &req, &rem); - now = cycle_get_64(); - - zassert_equal(r, 0, "actual: %d expected: %d", r, 0); - zassert_equal(errno, 0, "actual: %d expected: %d", errno, 0); - zassert_equal(req.tv_sec, s, "actual: %d expected: %d", req.tv_sec, s); - zassert_equal(req.tv_nsec, ns, "actual: %d expected: %d", req.tv_nsec, ns); - zassert_equal(rem.tv_sec, 0, "actual: %d expected: %d", rem.tv_sec, 0); - zassert_equal(rem.tv_nsec, 0, "actual: %d expected: %d", rem.tv_nsec, 0); - - switch (selection) { - case SELECT_NANOSLEEP: - /* exp_ns and actual_ns are relative (i.e. durations) */ - actual_ns = k_cyc_to_ns_ceil64(now + then); - break; - case SELECT_CLOCK_NANOSLEEP: - /* exp_ns and actual_ns are absolute (i.e. time-points) */ - actual_ns = k_cyc_to_ns_ceil64(now); - break; - default: - zassert_unreachable(); - break; - } - - exp_ns = (uint64_t)s * NSEC_PER_SEC + ns; - /* round up to the nearest microsecond for k_busy_wait() */ - exp_ns = DIV_ROUND_UP(exp_ns, NSEC_PER_USEC) * NSEC_PER_USEC; - - /* The comparison may be incorrect if counter wrap happened. In case of ARC HSDK platforms - * we have high counter clock frequency (500MHz or 1GHz) so counter wrap quite likely to - * happen if we wait long enough. As in some test cases we wait more than 1 second, there - * are significant chances to get false-positive assertion. - * TODO: switch test for k_cycle_get_64 usage where available. - */ -#if !defined(CONFIG_SOC_ARC_HSDK) && !defined(CONFIG_SOC_ARC_HSDK4XD) - /* lower bounds check */ - zassert_true(actual_ns >= exp_ns, "actual: %llu expected: %llu", actual_ns, exp_ns); -#endif - - /* TODO: Upper bounds check when hr timers are available */ -} - ZTEST(posix_timers, test_nanosleep_execution) { /* sleep for 1ns */ @@ -220,64 +136,3 @@ ZTEST(posix_timers, test_nanosleep_execution) /* sleep for 1s + 1us + 1ns */ common_lower_bound_check(SELECT_NANOSLEEP, 0, 0, 1, 1001); } - -ZTEST(posix_timers, test_clock_nanosleep_execution) -{ - struct timespec ts; - - clock_gettime(CLOCK_MONOTONIC, &ts); - - /* absolute sleeps with the monotonic clock and reference time ts */ - - /* until 1s + 1ns past the reference time */ - common_lower_bound_check(SELECT_CLOCK_NANOSLEEP, CLOCK_MONOTONIC, TIMER_ABSTIME, - ts.tv_sec + 1, 1); - - /* until 1s + 1us past the reference time */ - common_lower_bound_check(SELECT_CLOCK_NANOSLEEP, CLOCK_MONOTONIC, TIMER_ABSTIME, - ts.tv_sec + 1, 1000); - - /* until 1s + 500000000ns past the reference time */ - common_lower_bound_check(SELECT_CLOCK_NANOSLEEP, CLOCK_MONOTONIC, TIMER_ABSTIME, - ts.tv_sec + 1, 500000000); - - /* until 2s past the reference time */ - common_lower_bound_check(SELECT_CLOCK_NANOSLEEP, CLOCK_MONOTONIC, TIMER_ABSTIME, - ts.tv_sec + 2, 0); - - /* until 2s + 1ns past the reference time */ - common_lower_bound_check(SELECT_CLOCK_NANOSLEEP, CLOCK_MONOTONIC, TIMER_ABSTIME, - ts.tv_sec + 2, 1); - - /* until 2s + 1us + 1ns past reference time */ - common_lower_bound_check(SELECT_CLOCK_NANOSLEEP, CLOCK_MONOTONIC, TIMER_ABSTIME, - ts.tv_sec + 2, 1001); - - clock_gettime(CLOCK_REALTIME, &ts); - - /* absolute sleeps with the real time clock and adjusted reference time ts */ - - /* until 1s + 1ns past the reference time */ - common_lower_bound_check(SELECT_CLOCK_NANOSLEEP, CLOCK_REALTIME, TIMER_ABSTIME, - ts.tv_sec + 1, 1); - - /* until 1s + 1us past the reference time */ - common_lower_bound_check(SELECT_CLOCK_NANOSLEEP, CLOCK_REALTIME, TIMER_ABSTIME, - ts.tv_sec + 1, 1000); - - /* until 1s + 500000000ns past the reference time */ - common_lower_bound_check(SELECT_CLOCK_NANOSLEEP, CLOCK_REALTIME, TIMER_ABSTIME, - ts.tv_sec + 1, 500000000); - - /* until 2s past the reference time */ - common_lower_bound_check(SELECT_CLOCK_NANOSLEEP, CLOCK_REALTIME, TIMER_ABSTIME, - ts.tv_sec + 2, 0); - - /* until 2s + 1ns past the reference time */ - common_lower_bound_check(SELECT_CLOCK_NANOSLEEP, CLOCK_REALTIME, TIMER_ABSTIME, - ts.tv_sec + 2, 1); - - /* until 2s + 1us + 1ns past the reference time */ - common_lower_bound_check(SELECT_CLOCK_NANOSLEEP, CLOCK_REALTIME, TIMER_ABSTIME, - ts.tv_sec + 2, 1001); -} diff --git a/tests/posix/timers/src/nanosleep_common.c b/tests/posix/timers/src/nanosleep_common.c new file mode 100644 index 0000000000000..85dc338f78577 --- /dev/null +++ b/tests/posix/timers/src/nanosleep_common.c @@ -0,0 +1,104 @@ +/* + * Copyright (c) 2018 Friedt Professional Engineering Services, Inc + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include + +#include +#include + +#define SELECT_NANOSLEEP 1 +#define SELECT_CLOCK_NANOSLEEP 0 + +static inline uint64_t cycle_get_64(void) +{ + if (IS_ENABLED(CONFIG_TIMER_HAS_64BIT_CYCLE_COUNTER)) { + return k_cycle_get_64(); + } else { + return k_cycle_get_32(); + } +} + +int select_nanosleep(int selection, clockid_t clock_id, int flags, const struct timespec *rqtp, + struct timespec *rmtp) +{ + if (selection == SELECT_NANOSLEEP) { + return nanosleep(rqtp, rmtp); + } + return clock_nanosleep(clock_id, flags, rqtp, rmtp); +} + +/** + * @brief Check that a call to nanosleep has yielded execution for some minimum time. + * + * Check that the actual time slept is >= the total time specified by @p s (in seconds) and + * @p ns (in nanoseconds). + * + * @note The time specified by @p s and @p ns is assumed to be absolute (i.e. a time-point) + * when @p selection is set to @ref SELECT_CLOCK_NANOSLEEP. The time is assumed to be relative + * when @p selection is set to @ref SELECT_NANOSLEEP. + * + * @param selection Either @ref SELECT_CLOCK_NANOSLEEP or @ref SELECT_NANOSLEEP + * @param clock_id The clock to test (e.g. @ref CLOCK_MONOTONIC or @ref CLOCK_REALTIME) + * @param flags Flags to pass to @ref clock_nanosleep + * @param s Partial lower bound for yielded time (in seconds) + * @param ns Partial lower bound for yielded time (in nanoseconds) + */ +void common_lower_bound_check(int selection, clockid_t clock_id, int flags, const uint32_t s, + uint32_t ns) +{ + int r; + uint64_t actual_ns = 0; + uint64_t exp_ns; + uint64_t now; + uint64_t then; + struct timespec rem = {0, 0}; + struct timespec req = {s, ns}; + + errno = 0; + then = cycle_get_64(); + r = select_nanosleep(selection, clock_id, flags, &req, &rem); + now = cycle_get_64(); + + zassert_equal(r, 0, "actual: %d expected: %d", r, 0); + zassert_equal(errno, 0, "actual: %d expected: %d", errno, 0); + zassert_equal(req.tv_sec, s, "actual: %d expected: %d", req.tv_sec, s); + zassert_equal(req.tv_nsec, ns, "actual: %d expected: %d", req.tv_nsec, ns); + zassert_equal(rem.tv_sec, 0, "actual: %d expected: %d", rem.tv_sec, 0); + zassert_equal(rem.tv_nsec, 0, "actual: %d expected: %d", rem.tv_nsec, 0); + + switch (selection) { + case SELECT_NANOSLEEP: + /* exp_ns and actual_ns are relative (i.e. durations) */ + actual_ns = k_cyc_to_ns_ceil64(now + then); + break; + case SELECT_CLOCK_NANOSLEEP: + /* exp_ns and actual_ns are absolute (i.e. time-points) */ + actual_ns = k_cyc_to_ns_ceil64(now); + break; + default: + zassert_unreachable(); + break; + } + + exp_ns = (uint64_t)s * NSEC_PER_SEC + ns; + /* round up to the nearest microsecond for k_busy_wait() */ + exp_ns = DIV_ROUND_UP(exp_ns, NSEC_PER_USEC) * NSEC_PER_USEC; + +/* The comparison may be incorrect if counter wrap happened. In case of ARC HSDK platforms + * we have high counter clock frequency (500MHz or 1GHz) so counter wrap quite likely to + * happen if we wait long enough. As in some test cases we wait more than 1 second, there + * are significant chances to get false-positive assertion. + * TODO: switch test for k_cycle_get_64 usage where available. + */ +#if !defined(CONFIG_SOC_ARC_HSDK) && !defined(CONFIG_SOC_ARC_HSDK4XD) + /* lower bounds check */ + zassert_true(actual_ns >= exp_ns, "actual: %llu expected: %llu", actual_ns, exp_ns); +#endif + + /* TODO: Upper bounds check when hr timers are available */ +} diff --git a/tests/posix/xsi_single_process/CMakeLists.txt b/tests/posix/xsi_single_process/CMakeLists.txt new file mode 100644 index 0000000000000..105e382e5ea33 --- /dev/null +++ b/tests/posix/xsi_single_process/CMakeLists.txt @@ -0,0 +1,13 @@ +# SPDX-License-Identifier: Apache-2.0 + +cmake_minimum_required(VERSION 3.20.0) +find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) +project(xsi_single_process) + +FILE(GLOB app_sources src/*.c) + +target_sources(app PRIVATE ${app_sources}) + +target_compile_options(app PRIVATE -U_POSIX_C_SOURCE -D_POSIX_C_SOURCE=200809L) +target_compile_options(app PRIVATE -U_XOPEN_SOURCE -D_XOPEN_SOURCE=700) +target_include_directories(app PRIVATE ${ZEPHYR_BASE}/lib/posix/options) diff --git a/tests/posix/xsi_single_process/prj.conf b/tests/posix/xsi_single_process/prj.conf new file mode 100644 index 0000000000000..71998704aaabc --- /dev/null +++ b/tests/posix/xsi_single_process/prj.conf @@ -0,0 +1,4 @@ +CONFIG_ZTEST=y + +CONFIG_POSIX_API=y +CONFIG_XSI_SINGLE_PROCESS=y diff --git a/tests/posix/xsi_single_process/src/gethostid.c b/tests/posix/xsi_single_process/src/gethostid.c new file mode 100644 index 0000000000000..d41544511fda1 --- /dev/null +++ b/tests/posix/xsi_single_process/src/gethostid.c @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2025 Tenstorrent AI ULC + * + * SPDX-License-Identifier: Apache-2.0 + */ +#include + +#include + +ZTEST(xsi_single_process, test_gethostid) +{ + long id = gethostid(); + + if (id == -ENOSYS) { + printk("CONFIG_HWINFO not implemented for %s\n", CONFIG_BOARD); + ztest_test_skip(); + } + + uint32_t id32 = gethostid(); + + zassert_equal((uint32_t)id, id32, "gethostid() returned inconsistent values", (uint32_t)id, + id32); +} diff --git a/tests/posix/xsi_single_process/src/gettimeofday.c b/tests/posix/xsi_single_process/src/gettimeofday.c new file mode 100644 index 0000000000000..2470bc1733426 --- /dev/null +++ b/tests/posix/xsi_single_process/src/gettimeofday.c @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2018 Intel Corporation + * Copyright (c) 2023, Meta + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "posix_clock.h" + +#include +#include +#include + +#include + +ZTEST(xsi_single_process, test_gettimeofday) +{ + struct timeval tv; + struct timespec ts; + struct timespec rts; + + if (false) { + /* undefined behaviour */ + errno = 0; + zassert_equal(gettimeofday(NULL, NULL), -1); + zassert_equal(errno, EINVAL); + } + + /* Validate gettimeofday API */ + zassert_ok(gettimeofday(&tv, NULL)); + zassert_ok(clock_gettime(CLOCK_REALTIME, &rts)); + + /* TESTPOINT: Check if time obtained from + * gettimeofday is same or more than obtained + * from clock_gettime + */ + tv_to_ts(&tv, &ts); + zassert_true(tp_ge(&rts, &ts)); +} diff --git a/tests/posix/xsi_single_process/src/main.c b/tests/posix/xsi_single_process/src/main.c new file mode 100644 index 0000000000000..2b1c54b43990a --- /dev/null +++ b/tests/posix/xsi_single_process/src/main.c @@ -0,0 +1,9 @@ +/* + * Copyright (c) 2025 Tenstorrent AI ULC + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include + +ZTEST_SUITE(xsi_single_process, NULL, NULL, NULL, NULL, NULL); diff --git a/tests/posix/xsi_single_process/src/putenv.c b/tests/posix/xsi_single_process/src/putenv.c new file mode 100644 index 0000000000000..2b05eeca6e0f7 --- /dev/null +++ b/tests/posix/xsi_single_process/src/putenv.c @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2025 Tenstorrent AI ULC + * + * SPDX-License-Identifier: Apache-2.0 + */ +#include +#include + +#include + +ZTEST(xsi_single_process, test_putenv) +{ + char buf[64]; + + { + /* degenerate cases */ + static const char *const cases[] = { + NULL, "", "=", "abc", "42", "=abc", + /* + * Note: + * There are many poorly-formatted environment variable names and values + * that are invalid (from the perspective of a POSIX shell), but still + * accepted by setenv() and subsequently putenv(). + * + * See also tests/posix/single_process/src/env.c + * See also lib/posix/shell/env.c:101 + */ + }; + + ARRAY_FOR_EACH(cases, i) { + char *s; + + if (cases[i] == NULL) { + s = NULL; + } else { + strncpy(buf, cases[i], sizeof(buf)); + buf[sizeof(buf) - 1] = '\0'; + s = buf; + } + + errno = 0; + zexpect_equal(-1, putenv(s), "putenv(%s) unexpectedly succeeded", s); + zexpect_not_equal(0, errno, "putenv(%s) did not set errno", s); + } + } + + { + /* valid cases */ + static const char *const cases[] = { + "FOO=bar", + }; + + ARRAY_FOR_EACH(cases, i) { + strncpy(buf, cases[i], sizeof(buf)); + buf[sizeof(buf) - 1] = '\0'; + + zexpect_ok(putenv(buf), "putenv(%s) failed: %d", buf, errno); + } + } +} diff --git a/tests/posix/xsi_single_process/testcase.yaml b/tests/posix/xsi_single_process/testcase.yaml new file mode 100644 index 0000000000000..7c9b06b13b9cc --- /dev/null +++ b/tests/posix/xsi_single_process/testcase.yaml @@ -0,0 +1,38 @@ +common: + filter: not CONFIG_NATIVE_LIBC + tags: + - posix + - xsi + - single_process + # 1 tier0 platform per supported architecture + platform_key: + - arch + - simulation + integration_platforms: + - qemu_cortex_m0 +tests: + portability.xsi.single_process: + extra_configs: + - CONFIG_COMMON_LIBC_MALLOC_ARENA_SIZE=256 + portability.xsi.single_process.armclang_std_libc: + toolchain_allow: armclang + extra_configs: + - CONFIG_ARMCLANG_STD_LIBC=y + portability.xsi.single_process.arcmwdtlib: + toolchain_allow: arcmwdt + extra_configs: + - CONFIG_ARCMWDT_LIBC=y + portability.xsi.single_process.minimal: + extra_configs: + - CONFIG_MINIMAL_LIBC=y + - CONFIG_COMMON_LIBC_MALLOC_ARENA_SIZE=256 + portability.xsi.single_process.newlib: + filter: TOOLCHAIN_HAS_NEWLIB == 1 + extra_configs: + - CONFIG_NEWLIB_LIBC=y + - CONFIG_NEWLIB_LIBC_MIN_REQUIRED_HEAP_SIZE=256 + portability.xsi.single_process.picolibc: + tags: picolibc + filter: CONFIG_PICOLIBC_SUPPORTED + extra_configs: + - CONFIG_PICOLIBC=y