/* * 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_from_clockid(int clock_id) { switch (clock_id) { #if defined(CLOCK_REALTIME) || defined(_POSIX_C_SOURCE) case (int)CLOCK_REALTIME: return SYS_CLOCK_REALTIME; #endif #if defined(CLOCK_MONOTONIC) || defined(_POSIX_MONOTONIC_CLOCK) case (int)CLOCK_MONOTONIC: return SYS_CLOCK_MONOTONIC; #endif default: return -EINVAL; } } 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 ((sizeof(rqtp->tv_sec) == sizeof(int64_t)) && unlikely(rqtp->tv_sec >= (time_t)(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 */