From a3e934f12c2a5a9b72878649ceefc4fbfeaa1e1a Mon Sep 17 00:00:00 2001 From: Chris Friedt Date: Sat, 17 May 2025 21:16:12 -0400 Subject: [PATCH] sys: clock: additional sys_clock api calls Additional entries for the sys_clock API, comprised of: * sys_clock_gettime() * sys_clock_settime() * sys_clock_nanosleep() along with the constants * SYS_CLOCK_REALTIME * SYS_CLOCK_MONOTONIC * SYS_TIMER_ABSTIME The primary motivation for this API is so that libc and other libraries have a familiar-enough API to reach to when POSIX is not available, since POSIX is optional in Zephyr. By adding this API to lib/os, we also eliminate dependency cycles between libc and posix, as lib/os is a mutual dependency. Signed-off-by: Chris Friedt --- include/zephyr/sys/clock.h | 134 ++++++++++++++++++++++ lib/os/CMakeLists.txt | 2 + lib/os/clock.c | 226 +++++++++++++++++++++++++++++++++++++ 3 files changed, 362 insertions(+) create mode 100644 include/zephyr/sys/clock.h create mode 100644 lib/os/clock.c diff --git a/include/zephyr/sys/clock.h b/include/zephyr/sys/clock.h new file mode 100644 index 00000000000..0a1c37e554e --- /dev/null +++ b/include/zephyr/sys/clock.h @@ -0,0 +1,134 @@ +/* + * Copyright (c) 2025 Tenstorrent AI ULC + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @file + * @brief System clock APIs + * + * APIs for getting, setting, and sleeping with respect to system clocks. + */ + +#ifndef ZEPHYR_INCLUDE_SYSCLOCK_H_ +#define ZEPHYR_INCLUDE_SYSCLOCK_H_ + +#include + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @addtogroup clock_apis + * @{ + */ + +/** + * @brief The real-time clock (i.e. "wall clock") + * + * This clock is used to measure time since the epoch (1970-01-01 00:00:00 UTC). + * + * It is not a steady clock; i.e. it may be adjusted for a number of reasons from initialization + * of a hardware real-time-clock, to network-time synchronization, to manual adjustment from the + * application. + */ +#define SYS_CLOCK_REALTIME 1 + +/** + * @brief The monotonic clock + * + * This steady clock is used to measure time since the system booted. Time from this clock is + * always monotonically increasing. + */ +#define SYS_CLOCK_MONOTONIC 4 + +/** + * @brief The flag used for specifying absolute timeouts + * + * This flag may be passed to @ref sys_clock_nanosleep to indicate the requested timeout is an + * absolute time with respect to the specified clock. + */ +#define SYS_TIMER_ABSTIME 4 + +/** + * @brief Get the offset @ref SYS_CLOCK_REALTIME with respect to @ref SYS_CLOCK_MONOTONIC + * + * The "wall clock" (i.e. @ref SYS_CLOCK_REALTIME) depends on a base time that is set by the + * system. The base time may be updated for a number of reasons, such as initialization of a + * hardware real-time-clock (RTC), network time protocol (NTP) synchronization, or manual + * adjustment by the application. + * + * This function retrieves the current time offset, as a `timespec` object, for + * @ref SYS_CLOCK_REALTIME, with respect to @ref SYS_CLOCK_MONOTONIC, and writes it to the + * provided memory location pointed-to by @a tp. + * + * @note This function may assert if @a tp is NULL. + * + * @param tp Pointer to memory where time will be written. + */ +__syscall void sys_clock_getrtoffset(struct timespec *tp); + +/** + * @brief Get the current time from the specified clock + * + * @param clock_id The clock from which to query time. + * @param tp Pointer to memory where time will be written. + * @retval 0 on success. + * @retval -EINVAL when an invalid @a clock_id is specified. + */ +int sys_clock_gettime(int clock_id, struct timespec *tp); + +/** + * @brief Set the current time for the specified clock + * + * @param clock_id The clock for which the time should be set. + * @param tp Pointer to memory specifying the desired time. + * @retval 0 on success. + * @retval -EINVAL when an invalid @a clock_id is specified or when @a tp contains nanoseconds + * outside of the range `[0, 999999999]`. + */ +__syscall int sys_clock_settime(int clock_id, const struct timespec *tp); + +/** + * @brief Sleep for the specified amount of time with respect to the specified clock. + * + * This function will cause the calling thread to sleep either + * - until the absolute time specified by @a rqtp (if @a flags includes @ref SYS_TIMER_ABSTIME), or + * - until the relative time specified by @a rqtp (if @a flags does not include + * @ref SYS_TIMER_ABSTIME). + * + * The accepted values for @a clock_id include + * - @ref SYS_CLOCK_REALTIME + * - @ref SYS_CLOCK_MONOTONIC + * + * If @a rmtp is not NULL, and the thread is awoken prior to the time specified by @a rqtp, then + * any remaining time will be written to @a rmtp. If the thread has slept for at least the time + * specified by @a rqtp, then @a rmtp will be set to zero. + * + * @param clock_id The clock to by which to sleep. + * @param flags Flags to modify the behavior of the sleep operation. + * @param rqtp Pointer to the requested time to sleep. + * @param rmtp Pointer to memory into which to copy the remaining time, if any. + * + * @retval 0 on success. + * @retval -EINVAL when an invalid @a clock_id, when @a rqtp contains nanoseconds outside of the + * range `[0, 999999999]`, or when @a rqtp contains a negative value. + */ +__syscall int sys_clock_nanosleep(int clock_id, int flags, const struct timespec *rqtp, + struct timespec *rmtp); + +/** + * @} + */ + +#include + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/lib/os/CMakeLists.txt b/lib/os/CMakeLists.txt index 80f5f16f5d7..c8c56d7e072 100644 --- a/lib/os/CMakeLists.txt +++ b/lib/os/CMakeLists.txt @@ -1,11 +1,13 @@ # SPDX-License-Identifier: Apache-2.0 zephyr_syscall_header( + ${ZEPHYR_BASE}/include/zephyr/sys/clock.h ${ZEPHYR_BASE}/include/zephyr/sys/mutex.h ) zephyr_sources( cbprintf_packaged.c + clock.c printk.c sem.c thread_entry.c diff --git a/lib/os/clock.c b/lib/os/clock.c new file mode 100644 index 00000000000..494c00c541f --- /dev/null +++ b/lib/os/clock.c @@ -0,0 +1,226 @@ +/* + * 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 +#include +#include +#include + +#include +#include +#include +#include +#include + +/* + * `k_uptime_get` returns a timestamp offset on an always increasing + * value from the system start. To support the `SYS_CLOCK_REALTIME` + * clock, this `rt_clock_offset` records the time that the system was + * started. This can either be set via 'sys_clock_settime', or could be + * set from a real time clock, if such hardware is present. + */ +static struct timespec rt_clock_offset; +static struct k_spinlock rt_clock_offset_lock; + +static bool is_valid_clock_id(int clock_id) +{ + switch (clock_id) { + case SYS_CLOCK_MONOTONIC: + case SYS_CLOCK_REALTIME: + return true; + default: + return false; + } +} + +static void timespec_from_ticks(uint64_t ticks, struct timespec *ts) +{ + uint64_t elapsed_secs = ticks / CONFIG_SYS_CLOCK_TICKS_PER_SEC; + uint64_t nremainder = ticks % CONFIG_SYS_CLOCK_TICKS_PER_SEC; + + *ts = (struct timespec){ + .tv_sec = (time_t)elapsed_secs, + /* For ns 32 bit conversion can be used since its smaller than 1sec. */ + .tv_nsec = (int32_t)k_ticks_to_ns_floor32(nremainder), + }; +} + +int sys_clock_gettime(int clock_id, struct timespec *ts) +{ + if (!is_valid_clock_id(clock_id)) { + return -EINVAL; + } + + switch (clock_id) { + case SYS_CLOCK_REALTIME: { + struct timespec offset; + + timespec_from_ticks(k_uptime_ticks(), ts); + sys_clock_getrtoffset(&offset); + if (unlikely(!timespec_add(ts, &offset))) { + /* Saturate rather than reporting an overflow in 292 billion years */ + *ts = (struct timespec){ + .tv_sec = (time_t)INT64_MAX, + .tv_nsec = NSEC_PER_SEC - 1, + }; + } + } break; + + case SYS_CLOCK_MONOTONIC: + timespec_from_ticks(k_uptime_ticks(), ts); + break; + + default: + CODE_UNREACHABLE; + return -EINVAL; /* Should never reach here */ + } + + __ASSERT_NO_MSG(timespec_is_valid(ts)); + + return 0; +} + +void z_impl_sys_clock_getrtoffset(struct timespec *tp) +{ + __ASSERT_NO_MSG(tp != NULL); + + K_SPINLOCK(&rt_clock_offset_lock) { + *tp = rt_clock_offset; + } + + __ASSERT_NO_MSG(timespec_is_valid(tp)); +} + +#ifdef CONFIG_USERSPACE +void z_vrfy_sys_clock_getrtoffset(struct timespec *tp) +{ + K_OOPS(K_SYSCALL_MEMORY_WRITE(tp, sizeof(*tp))); + return z_impl_sys_clock_getrtoffset(tp); +} +#include +#endif /* CONFIG_USERSPACE */ + +int z_impl_sys_clock_settime(int clock_id, const struct timespec *tp) +{ + struct timespec offset; + + if (clock_id != SYS_CLOCK_REALTIME) { + return -EINVAL; + } + + if (!timespec_is_valid(tp)) { + return -EINVAL; + } + + timespec_from_ticks(k_uptime_ticks(), &offset); + (void)timespec_negate(&offset); + (void)timespec_add(&offset, tp); + + K_SPINLOCK(&rt_clock_offset_lock) { + rt_clock_offset = offset; + } + + return 0; +} + +#ifdef CONFIG_USERSPACE +int z_vrfy_sys_clock_settime(int clock_id, const struct timespec *ts) +{ + K_OOPS(K_SYSCALL_MEMORY_READ(ts, sizeof(*ts))); + return z_impl_sys_clock_settime(clock_id, ts); +} +#include +#endif /* CONFIG_USERSPACE */ + +int z_impl_sys_clock_nanosleep(int clock_id, int flags, const struct timespec *rqtp, + struct timespec *rmtp) +{ + k_timepoint_t end; + k_timeout_t timeout; + struct timespec duration; + const bool update_rmtp = rmtp != NULL; + const bool abstime = (flags & SYS_TIMER_ABSTIME) != 0; + + if (!is_valid_clock_id(clock_id)) { + return -EINVAL; + } + + if ((rqtp->tv_sec < 0) || !timespec_is_valid(rqtp)) { + return -EINVAL; + } + + if (abstime) { + /* convert absolute time to relative time duration */ + (void)sys_clock_gettime(clock_id, &duration); + (void)timespec_negate(&duration); + (void)timespec_add(&duration, rqtp); + } else { + duration = *rqtp; + } + + /* sleep for relative time duration */ + if (unlikely(rqtp->tv_sec >= UINT64_MAX / NSEC_PER_SEC)) { + uint64_t ns = (uint64_t)k_sleep(K_SECONDS(duration.tv_sec - 1)) * NSEC_PER_MSEC; + struct timespec rem = { + .tv_sec = (time_t)(ns / NSEC_PER_SEC), + .tv_nsec = ns % NSEC_PER_MSEC, + }; + + duration.tv_sec = 1; + (void)timespec_add(&duration, &rem); + } + + timeout = timespec_to_timeout(&duration); + end = sys_timepoint_calc(timeout); + do { + (void)k_sleep(timeout); + timeout = sys_timepoint_timeout(end); + } while (!K_TIMEOUT_EQ(timeout, K_NO_WAIT)); + + if (update_rmtp) { + *rmtp = (struct timespec){ + .tv_sec = 0, + .tv_nsec = 0, + }; + } + + return 0; +} + +#ifdef CONFIG_USERSPACE +int z_vrfy_sys_clock_nanosleep(int clock_id, int flags, const struct timespec *rqtp, + struct timespec *rmtp) +{ + K_OOPS(K_SYSCALL_MEMORY_READ(rqtp, sizeof(*rqtp))); + if (rmtp != NULL) { + K_OOPS(K_SYSCALL_MEMORY_WRITE(rmtp, sizeof(*rmtp))); + } + return z_impl_sys_clock_nanosleep(clock_id, flags, rqtp, rmtp); +} +#include +#endif /* CONFIG_USERSPACE */ + +#ifdef CONFIG_ZTEST +#include +static void reset_clock_offset(void) +{ + K_SPINLOCK(&rt_clock_offset_lock) { + rt_clock_offset = (struct timespec){0}; + } +} + +static void clock_offset_reset_rule_after(const struct ztest_unit_test *test, void *data) +{ + ARG_UNUSED(test); + ARG_UNUSED(data); + + reset_clock_offset(); +} + +ZTEST_RULE(clock_offset_reset_rule, NULL, clock_offset_reset_rule_after); +#endif /* CONFIG_ZTEST */