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 <cfriedt@tenstorrent.com>
This commit is contained in:
Chris Friedt 2025-05-17 21:16:12 -04:00 committed by Benjamin Cabé
parent 282b47cf37
commit a3e934f12c
3 changed files with 362 additions and 0 deletions

134
include/zephyr/sys/clock.h Normal file
View File

@ -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 <time.h>
#include <zephyr/toolchain.h>
#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 <zephyr/syscalls/clock.h>
#ifdef __cplusplus
}
#endif
#endif

View File

@ -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

226
lib/os/clock.c Normal file
View File

@ -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 <errno.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <zephyr/kernel.h>
#include <zephyr/internal/syscall_handler.h>
#include <zephyr/sys/clock.h>
#include <zephyr/sys/timeutil.h>
#include <zephyr/toolchain.h>
/*
* `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 <zephyr/syscalls/sys_clock_getrtoffset_mrsh.c>
#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 <zephyr/syscalls/sys_clock_settime_mrsh.c>
#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 <zephyr/syscalls/sys_clock_nanosleep_mrsh.c>
#endif /* CONFIG_USERSPACE */
#ifdef CONFIG_ZTEST
#include <zephyr/ztest.h>
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 */