diff --git a/MAINTAINERS.yml b/MAINTAINERS.yml index f0b82f77e8ca..b67e2930c835 100644 --- a/MAINTAINERS.yml +++ b/MAINTAINERS.yml @@ -2556,7 +2556,6 @@ Utilities: - tests/unit/list/ - tests/unit/intmath/ - tests/unit/pot/ - - tests/lib/time/ - tests/lib/onoff/ - tests/lib/sys_util/ - tests/lib/sprintf/ diff --git a/doc/kernel/timeutil.rst b/doc/kernel/timeutil.rst index 203d52ea9afd..f6312b495391 100644 --- a/doc/kernel/timeutil.rst +++ b/doc/kernel/timeutil.rst @@ -28,6 +28,7 @@ The time utilities API supports: * :ref:`converting between time representations ` * :ref:`synchronizing and aligning time scales ` +* :ref:`comparing, adding, and subtracting representations ` For terminology and concepts that support these functions see :ref:`timeutil_concepts`. @@ -65,6 +66,20 @@ The inverse transformation is not standardized: APIs like ``mktime()`` expect information about time zones. Zephyr provides this transformation with :c:func:`timeutil_timegm` and :c:func:`timeutil_timegm64`. +To convert between ``struct timespec`` and ``k_timeout_t`` durations, +use :c:func:`timespec_to_timeout` and :c:func:`timespec_from_timeout`. + +.. code-block:: c + + k_timeout_t to; + struct timespec ts; + + timespec_from_timeout(K_FOREVER, &ts); + to = timespec_to_timeout(&ts); /* to == K_FOREVER */ + + timespec_from_timeout(K_MSEC(100), &ts); + to = timespec_to_timeout(&ts); /* to == K_MSEC(100) */ + .. doxygengroup:: timeutil_repr_apis .. _timeutil_sync: @@ -106,6 +121,90 @@ process: .. doxygengroup:: timeutil_sync_apis +.. _timeutil_manip: + +``timespec`` Manipulation +========================= + +Checking the validity of a ``timespec`` can be done with :c:func:`timespec_is_valid`. + +.. code-block:: c + + struct timespec ts = { + .tv_sec = 0, + .tv_nsec = -1, /* out of range! */ + }; + + if (!timespec_is_valid(&ts)) { + /* error-handing code */ + } + +In some cases, invalid ``timespec`` objects may be re-normalized using +:c:func:`timespec_normalize`. + +.. code-block:: c + + if (!timespec_normalize(&ts)) { + /* error-handling code */ + } + + /* ts should be normalized */ + __ASSERT(timespec_is_valid(&ts) == true, "expected normalized timespec"); + +It is possible to compare two ``timespec`` objects for equality using :c:func:`timespec_equal`. + +.. code-block:: c + + if (timespec_equal(then, now)) { + /* time is up! */ + } + +It is possible to compare and fully order (valid) ``timespec`` objects using +:c:func:`timespec_compare`. + +.. code-block:: c + + int cmp = timespec_compare(a, b); + + switch (cmp) { + case 0: + /* a == b */ + break; + case -1: + /* a < b */ + break; + case +1: + /* a > b */ + break; + } + +It is possible to add, subtract, and negate ``timespec`` objects using +:c:func:`timespec_add`, :c:func:`timespec_sub`, and :c:func:`timespec_negate`, +respectively. Like :c:func:`timespec_normalize`, these functions will output +a normalized ``timespec`` when doing so would not result in overflow. +On success, these functions return ``true``. If overflow would occur, the +functions return ``false``. + +.. code-block:: c + + /* a += b */ + if (!timespec_add(&a, &b)) { + /* overflow */ + } + + /* a -= b */ + if (!timespec_sub(&a, &b)) { + /* overflow */ + } + + /* a = -a */ + if (!timespec_negate(&a)) { + /* overflow */ + } + +.. doxygengroup:: timeutil_timespec_apis + + .. _timeutil_concepts: Concepts Underlying Time Support in Zephyr @@ -236,3 +335,29 @@ The mechanism used to populate synchronization points is not relevant: it may involve reading from a local high-precision RTC peripheral, exchanging packets over a network using a protocol like NTP or PTP, or processing NMEA messages received a GPS with or without a 1pps signal. + +``timespec`` Concepts +===================== + +Originally from POSIX, ``struct timespec`` has been a part of the C standard +since C11. The definition of ``struct timespec`` is as shown below. + +.. code-block:: c + + struct timespec { + time_t tv_sec; /* seconds */ + long tv_nsec; /* nanoseconds */ + }; + +.. _note: + + The C standard does not define the size of ``time_t``. However, Zephyr + uses 64-bits for ``time_t``. The ``long`` type is required to be at least + 32-bits, but usually matches the word size of the architecture. Both + elements of ``struct timespec`` are signed integers. ``time_t`` is defined + to be 64-bits both for historical reasons and to be robust enough to + represent times in the future. + +The ``tv_nsec`` field is only valid with values in the range ``[0, 999999999]``. The +``tv_sec`` field is the number of seconds since the epoch. If ``struct timespec`` is +used to express a difference, the ``tv_sec`` field may fall into a negative range. diff --git a/include/zephyr/kernel.h b/include/zephyr/kernel.h index 410d7bf90c90..d4a9907202f4 100644 --- a/include/zephyr/kernel.h +++ b/include/zephyr/kernel.h @@ -69,6 +69,8 @@ BUILD_ASSERT(sizeof(intptr_t) == sizeof(long)); #define Z_DECL_POLL_EVENT #endif +struct timespec; + struct k_thread; struct k_mutex; struct k_sem; @@ -6486,6 +6488,91 @@ void k_sys_runtime_stats_enable(void); */ void k_sys_runtime_stats_disable(void); +/** + * @defgroup kernel_clock_apis Kernel Clock APIs + * @ingroup kernel_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 K_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 K_CLOCK_MONOTONIC 4 + +/** + * @brief The flag used for specifying absolute timeouts + * + * This flag may be passed to @ref k_clock_nanosleep to indicate the requested timeout is an + * absolute time with respect to the specified clock. + */ +#define K_TIMER_ABSTIME 4 + +/** + * @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. + */ +__syscall int k_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. + * @retval -EINVAL when @a tp contains nanoseconds outside of the range `[0, 999999999]`. + */ +__syscall int k_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 K_TIMER_ABSTIME), or + * - until the relative time specified by @a rqtp (if @a flags does not include + * @ref K_TIMER_ABSTIME). + * + * The accepted values for @a clock_id include + * - @ref K_CLOCK_REALTIME + * - @ref K_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 k_clock_nanosleep(int clock_id, int flags, const struct timespec *rqtp, + struct timespec *rmtp); +/** + * @} + */ + #ifdef __cplusplus } #endif diff --git a/include/zephyr/posix/time.h b/include/zephyr/posix/time.h index 7f944b7db97a..323f475091e9 100644 --- a/include/zephyr/posix/time.h +++ b/include/zephyr/posix/time.h @@ -66,7 +66,7 @@ extern "C" { #endif #ifndef CLOCK_REALTIME -#define CLOCK_REALTIME 1 +#define CLOCK_REALTIME K_CLOCK_REALTIME #endif #ifndef CLOCK_PROCESS_CPUTIME_ID @@ -78,7 +78,7 @@ extern "C" { #endif #ifndef CLOCK_MONOTONIC -#define CLOCK_MONOTONIC 4 +#define CLOCK_MONOTONIC K_CLOCK_MONOTONIC #endif #ifndef TIMER_ABSTIME diff --git a/include/zephyr/sys/timeutil.h b/include/zephyr/sys/timeutil.h index 333ff76a2efb..758882c1fb3c 100644 --- a/include/zephyr/sys/timeutil.h +++ b/include/zephyr/sys/timeutil.h @@ -21,8 +21,17 @@ #ifndef ZEPHYR_INCLUDE_SYS_TIMEUTIL_H_ #define ZEPHYR_INCLUDE_SYS_TIMEUTIL_H_ +#include +#include +#include +#include #include +#include +#include +#include +#include +#include #include #ifdef __cplusplus @@ -301,12 +310,396 @@ int timeutil_sync_local_from_ref(const struct timeutil_sync_state *tsp, */ int32_t timeutil_sync_skew_to_ppb(float skew); -#ifdef __cplusplus +/** + * @} + */ + +/** + * @defgroup timeutil_timespec_apis Timespec Utility APIs + * @ingroup timeutil_apis + * @{ + */ + +/** + * @brief Check if a timespec is valid. + * + * Check if a timespec is valid (i.e. normalized) by ensuring that the `tv_nsec` field is in the + * range `[0, NSEC_PER_SEC-1]`. + * + * @note @p ts must not be `NULL`. + * + * @param ts the timespec to check + * + * @return `true` if the timespec is valid, otherwise `false`. + */ +static inline bool timespec_is_valid(const struct timespec *ts) +{ + __ASSERT_NO_MSG(ts != NULL); + + return (ts->tv_nsec >= 0) && (ts->tv_nsec < NSEC_PER_SEC); +} + +#if (defined(CONFIG_SPEED_OPTIMIZATIONS) && HAS_BUILTIN(__builtin_add_overflow)) || \ + defined(__DOXYGEN__) +/** + * @brief Normalize a timespec so that the `tv_nsec` field is in valid range. + * + * Normalize a timespec by adjusting the `tv_sec` and `tv_nsec` fields so that the `tv_nsec` field + * is in the range `[0, NSEC_PER_SEC-1]`. This is achieved by converting nanoseconds to seconds and + * accumulating seconds in either the positive direction when `tv_nsec` > `NSEC_PER_SEC`, or in the + * negative direction when `tv_nsec` < 0. + * + * In pseudocode, normalization can be done as follows: + * ```python + * if ts.tv_nsec >= NSEC_PER_SEC: + * sec = ts.tv_nsec / NSEC_PER_SEC + * ts.tv_sec += sec + * ts.tv_nsec -= sec * NSEC_PER_SEC + * elif ts.tv_nsec < 0: + * # div_round_up(abs(ts->tv_nsec), NSEC_PER_SEC) + * sec = (NSEC_PER_SEC - ts.tv_nsec - 1) / NSEC_PER_SEC + * ts.tv_sec -= sec; + * ts.tv_nsec += sec * NSEC_PER_SEC; + * ``` + * + * @note There are two cases where the normalization can result in integer overflow. These can + * be extrapolated to not simply overflowing the `tv_sec` field by one second, but also by any + * realizable multiple of `NSEC_PER_SEC`. + * + * 1. When `tv_nsec` is negative and `tv_sec` is already most negative. + * 2. When `tv_nsec` is greater-or-equal to `NSEC_PER_SEC` and `tv_sec` is already most positive. + * + * If the operation would result in integer overflow, return value is `false`. + * + * @note @p ts must be non-`NULL`. + * + * @param ts the timespec to be normalized + * + * @return `true` if the operation completes successfully, otherwise `false`. + */ +static inline bool timespec_normalize(struct timespec *ts) +{ + __ASSERT_NO_MSG(ts != NULL); + + int sign = (ts->tv_nsec >= 0) - (ts->tv_nsec < 0); + int64_t sec = (ts->tv_nsec >= (long)NSEC_PER_SEC) * (ts->tv_nsec / (long)NSEC_PER_SEC) + + ((ts->tv_nsec < 0) && (ts->tv_nsec != LONG_MIN)) * + DIV_ROUND_UP((unsigned long)-ts->tv_nsec, (long)NSEC_PER_SEC) + + (ts->tv_nsec == LONG_MIN) * ((LONG_MAX / NSEC_PER_SEC) + 1); + bool overflow = __builtin_add_overflow(ts->tv_sec, sign * sec, &ts->tv_sec); + + ts->tv_nsec -= sign * (long)NSEC_PER_SEC * sec; + + if (!overflow) { + __ASSERT_NO_MSG(timespec_is_valid(ts)); + } + + return !overflow; +} + +/** + * @brief Add one timespec to another + * + * This function sums the two timespecs pointed to by @p a and @p b and stores the result in the + * timespce pointed to by @p a. + * + * If the operation would result in integer overflow, return value is `false`. + * + * @note @p a and @p b must be non-`NULL` and normalized. + * + * @param a the timespec which is added to + * @param b the timespec to be added + * + * @return `true` if the operation was successful, otherwise `false`. + */ +static inline bool timespec_add(struct timespec *a, const struct timespec *b) +{ + __ASSERT_NO_MSG((a != NULL) && timespec_is_valid(a)); + __ASSERT_NO_MSG((b != NULL) && timespec_is_valid(b)); + + return !__builtin_add_overflow(a->tv_sec, b->tv_sec, &a->tv_sec) && + !__builtin_add_overflow(a->tv_nsec, b->tv_nsec, &a->tv_nsec) && + timespec_normalize(a); +} + +/** + * @brief Negate a timespec object + * + * Negate the timespec object pointed to by @p ts and store the result in the same + * memory location. + * + * If the operation would result in integer overflow, return value is `false`. + * + * @param ts The timespec object to negate. + * + * @return `true` of the operation is successful, otherwise `false`. + */ +static inline bool timespec_negate(struct timespec *ts) +{ + __ASSERT_NO_MSG((ts != NULL) && timespec_is_valid(ts)); + + return !__builtin_sub_overflow(0LL, ts->tv_sec, &ts->tv_sec) && + !__builtin_sub_overflow(0L, ts->tv_nsec, &ts->tv_nsec) && timespec_normalize(ts); +} +#else + +static inline bool timespec_normalize(struct timespec *ts) +{ + __ASSERT_NO_MSG(ts != NULL); + + long sec; + + if (ts->tv_nsec >= (long)NSEC_PER_SEC) { + sec = ts->tv_nsec / (long)NSEC_PER_SEC; + } else if (ts->tv_nsec < 0) { + if ((sizeof(ts->tv_nsec) == sizeof(uint32_t)) && (ts->tv_nsec == LONG_MIN)) { + sec = DIV_ROUND_UP(LONG_MAX / NSEC_PER_USEC, USEC_PER_SEC); + } else { + sec = DIV_ROUND_UP((unsigned long)-ts->tv_nsec, NSEC_PER_SEC); + } + } else { + sec = 0; + } + + if ((ts->tv_nsec < 0) && (ts->tv_sec < 0) && (ts->tv_sec - INT64_MIN < sec)) { + /* + * When `tv_nsec` is negative and `tv_sec` is already most negative, + * further subtraction would cause integer overflow. + */ + return false; + } + + if ((ts->tv_nsec >= (long)NSEC_PER_SEC) && (ts->tv_sec > 0) && + (INT64_MAX - ts->tv_sec < sec)) { + /* + * When `tv_nsec` is >= `NSEC_PER_SEC` and `tv_sec` is already most + * positive, further addition would cause integer overflow. + */ + return false; + } + + if (ts->tv_nsec >= (long)NSEC_PER_SEC) { + ts->tv_sec += sec; + ts->tv_nsec -= sec * (long)NSEC_PER_SEC; + } else if (ts->tv_nsec < 0) { + ts->tv_sec -= sec; + ts->tv_nsec += sec * (long)NSEC_PER_SEC; + } else { + /* no change: SonarQube was complaining */ + } + + __ASSERT_NO_MSG(timespec_is_valid(ts)); + + return true; +} + +static inline bool timespec_add(struct timespec *a, const struct timespec *b) +{ + __ASSERT_NO_MSG((a != NULL) && timespec_is_valid(a)); + __ASSERT_NO_MSG((b != NULL) && timespec_is_valid(b)); + + if ((a->tv_sec < 0) && (b->tv_sec < 0) && (INT64_MIN - a->tv_sec > b->tv_sec)) { + /* negative integer overflow would occur */ + return false; + } + + if ((a->tv_sec > 0) && (b->tv_sec > 0) && (INT64_MAX - a->tv_sec < b->tv_sec)) { + /* positive integer overflow would occur */ + return false; + } + + a->tv_sec += b->tv_sec; + a->tv_nsec += b->tv_nsec; + + return timespec_normalize(a); +} + +static inline bool timespec_negate(struct timespec *ts) +{ + __ASSERT_NO_MSG((ts != NULL) && timespec_is_valid(ts)); + + if (ts->tv_sec == INT64_MIN) { + /* -INT64_MIN > INT64_MAX, so +ve integer overflow would occur */ + return false; + } + + ts->tv_sec = -ts->tv_sec; + ts->tv_nsec = -ts->tv_nsec; + + return timespec_normalize(ts); } #endif +/** + * @brief Subtract one timespec from another + * + * This function subtracts the timespec pointed to by @p b from the timespec pointed to by @p a and + * stores the result in the timespce pointed to by @p a. + * + * If the operation would result in integer overflow, return value is `false`. + * + * @note @p a and @p b must be non-`NULL`. + * + * @param a the timespec which is subtracted from + * @param b the timespec to be subtracted + * + * @return `true` if the operation is successful, otherwise `false`. + */ +static inline bool timespec_sub(struct timespec *a, const struct timespec *b) +{ + __ASSERT_NO_MSG(a != NULL); + __ASSERT_NO_MSG(b != NULL); + + struct timespec neg = *b; + + return timespec_negate(&neg) && timespec_add(a, &neg); +} + +/** + * @brief Compare two timespec objects + * + * This function compares two timespec objects pointed to by @p a and @p b. + * + * @note @p a and @p b must be non-`NULL` and normalized. + * + * @param a the first timespec to compare + * @param b the second timespec to compare + * + * @return -1, 0, or +1 if @a a is less than, equal to, or greater than @a b, respectively. + */ +static inline int timespec_compare(const struct timespec *a, const struct timespec *b) +{ + __ASSERT_NO_MSG((a != NULL) && timespec_is_valid(a)); + __ASSERT_NO_MSG((b != NULL) && timespec_is_valid(b)); + + return (((a->tv_sec == b->tv_sec) && (a->tv_nsec < b->tv_nsec)) * -1) + + (((a->tv_sec == b->tv_sec) && (a->tv_nsec > b->tv_nsec)) * 1) + + ((a->tv_sec < b->tv_sec) * -1) + ((a->tv_sec > b->tv_sec)); +} + +/** + * @brief Check if two timespec objects are equal + * + * This function checks if the two timespec objects pointed to by @p a and @p b are equal. + * + * @note @p a and @p b must be non-`NULL` are not required to be normalized. + * + * @param a the first timespec to compare + * @param b the second timespec to compare + * + * @return true if the two timespec objects are equal, otherwise false. + */ +static inline bool timespec_equal(const struct timespec *a, const struct timespec *b) +{ + __ASSERT_NO_MSG(a != NULL); + __ASSERT_NO_MSG(b != NULL); + + return (a->tv_sec == b->tv_sec) && (a->tv_nsec == b->tv_nsec); +} + +/** + * @} + */ + +/** + * @ingroup timeutil_repr_apis + * @{ + */ + +/** + * @brief Convert a kernel timeout to a timespec + * + * @param timeout the kernel timeout to convert + * @param ts the timespec to store the result + */ +static inline void timespec_from_timeout(k_timeout_t timeout, struct timespec *ts) +{ + __ASSERT_NO_MSG(ts != NULL); + + if (timeout.ticks == 0) { + /* This is equivalent to K_NO_WAIT, but without including */ + ts->tv_sec = 0; + ts->tv_nsec = 0; + } else if (timeout.ticks == K_TICKS_FOREVER) { + /* This is roughly equivalent to K_FOREVER, but not including */ + ts->tv_sec = INT64_MAX; + ts->tv_nsec = NSEC_PER_SEC - 1; + } else { + uint64_t ns = k_ticks_to_ns_ceil64(timeout.ticks); + + ts->tv_sec = ns / NSEC_PER_SEC; + ts->tv_nsec = ns - ts->tv_sec * NSEC_PER_SEC; + } + + __ASSERT_NO_MSG(timespec_is_valid(ts)); +} + +/** + * @brief Convert a timespec to a kernel timeout + * + * This function converts durations expressed as a `struct timespec` to Zephyr @ref k_timeout_t + * objects. + * + * Given that the range of a `struct timespec` is much larger than the range of @ref k_timeout_t, + * and also given that the functions are only intended to be used to convert time durations + * (which are always positive), the function will saturate to @ref K_NO_WAIT if the `tv_sec` field + * of @a ts is negative. + * + * Similarly, if the duration is too large to fit in @ref k_timeout_t, the function will + * saturate to @ref K_FOREVER. + * + * @param ts the timespec to convert + * @return the kernel timeout + */ +static inline k_timeout_t timespec_to_timeout(const struct timespec *ts) +{ + k_timeout_t timeout; + + __ASSERT_NO_MSG((ts != NULL) && timespec_is_valid(ts)); + + if ((ts->tv_sec < 0) || (ts->tv_sec == 0 && ts->tv_nsec == 0)) { + /* This is equivalent to K_NO_WAIT, but without including */ + timeout.ticks = 0; + } else if (ts->tv_sec == INT64_MAX && ts->tv_nsec == NSEC_PER_SEC - 1) { + /* This is equivalent to K_FOREVER, but not including */ + timeout.ticks = K_TICKS_FOREVER; + } else { + uint64_t ticks_s; + uint64_t ticks_ns; + uint64_t tick_rate_hz = sys_clock_hw_cycles_per_sec(); + + if (u64_mul_overflow(ts->tv_sec, tick_rate_hz, &ticks_s) || + (IS_ENABLED(CONFIG_TIMEOUT_64BIT) && (ticks_s > INT64_MAX)) || + (!IS_ENABLED(CONFIG_TIMEOUT_64BIT) && (ticks_s > UINT32_MAX))) { + /* tv_sec would saturate k_tick_t */ + timeout.ticks = K_TICKS_FOREVER; + return timeout; + } + + /* probably safe until we have 90+ something GHz machines */ + ticks_ns = ts->tv_nsec * tick_rate_hz / NSEC_PER_SEC; + + if (u64_add_overflow(ticks_s, ticks_ns, &timeout.ticks) || + (IS_ENABLED(CONFIG_TIMEOUT_64BIT) && (INT64_MAX - ticks_s < ticks_ns)) || + (!IS_ENABLED(CONFIG_TIMEOUT_64BIT) && (UINT32_MAX - ticks_s < ticks_ns))) { + /* tv_sec + tv_nsec would saturate k_tick_t */ + timeout.ticks = K_TICKS_FOREVER; + return timeout; + } + + /* unsaturated! */ + timeout.ticks = ticks_s + ticks_ns; + } + + return timeout; +} + /** * @} */ +#ifdef __cplusplus +} +#endif + #endif /* ZEPHYR_INCLUDE_SYS_TIMEUTIL_H_ */ diff --git a/kernel/CMakeLists.txt b/kernel/CMakeLists.txt index 075e091cbb62..0655d417b379 100644 --- a/kernel/CMakeLists.txt +++ b/kernel/CMakeLists.txt @@ -51,6 +51,7 @@ list(APPEND kernel_files main_weak.c banner.c busy_wait.c + clock.c device.c errno.c fatal.c diff --git a/kernel/clock.c b/kernel/clock.c new file mode 100644 index 000000000000..e0c3a4c3446b --- /dev/null +++ b/kernel/clock.c @@ -0,0 +1,199 @@ +/* + * 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 + +/* + * `k_uptime_get` returns a timestamp based on an always increasing + * value from the system start. To support the `K_CLOCK_REALTIME` + * clock, this `rt_clock_base` records the time that the system was + * started. This can either be set via 'k_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; + +static inline bool is_valid_clock_id(int clock_id) +{ + switch (clock_id) { + case K_CLOCK_MONOTONIC: + case K_CLOCK_REALTIME: + return true; + default: + return false; + } +} + +static inline 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 - 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); +} + +int z_impl_k_clock_gettime(int clock_id, struct timespec *ts) +{ + struct timespec base; + + if (!is_valid_clock_id(clock_id)) { + return -EINVAL; + } + + if (clock_id == K_CLOCK_REALTIME) { + K_SPINLOCK(&rt_clock_base_lock) { + base = rt_clock_base; + } + } else { + base = (struct timespec){0}; + } + + timespec_from_ticks(k_uptime_ticks(), ts); + if (!timespec_add(ts, &base)) { + return -EOVERFLOW; + } + + return 0; +} + +#ifdef CONFIG_USERSPACE +int z_vrfy_k_clock_gettime(int clock_id, struct timespec *ts) +{ + K_OOPS(K_SYSCALL_MEMORY_WRITE(ts, sizeof(*ts))); + return z_impl_k_clock_gettime(clock_id, ts); +} +#include +#endif /* CONFIG_USERSPACE */ + +int z_impl_k_clock_settime(int clock_id, const struct timespec *tp) +{ + struct timespec base; + + if (clock_id != K_CLOCK_REALTIME) { + return -EINVAL; + } + + if (!timespec_is_valid(tp)) { + return -EINVAL; + } + + 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; + + K_SPINLOCK(&rt_clock_base_lock) { + rt_clock_base = base; + } + + return 0; +} + +#ifdef CONFIG_USERSPACE +int z_vrfy_k_clock_settime(int clock_id, const struct timespec *ts) +{ + K_OOPS(K_SYSCALL_MEMORY_READ(ts, sizeof(*ts))); + return z_impl_k_clock_settime(clock_id, ts); +} +#include +#endif /* CONFIG_USERSPACE */ + +int z_impl_k_clock_nanosleep(int clock_id, int flags, const struct timespec *rqtp, + struct timespec *rmtp) +{ + uint64_t ns; + uint64_t uptime_ns; + const bool update_rmtp = rmtp != NULL; + const bool abstime = (flags & K_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 && 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 (abstime && (clock_id == K_CLOCK_REALTIME)) { + K_SPINLOCK(&rt_clock_base_lock) { + ns -= rt_clock_base.tv_sec * NSEC_PER_SEC + rt_clock_base.tv_nsec; + } + } + + if (!abstime) { + ns += uptime_ns; + } + + if (ns <= uptime_ns) { + goto do_rmtp_update; + } + + do { + ns = k_sleep(K_TIMEOUT_ABS_NS(ns)) * NSEC_PER_MSEC; + } while (ns != 0); + +do_rmtp_update: + if (update_rmtp) { + rmtp->tv_sec = 0; + rmtp->tv_nsec = 0; + } + + return 0; +} + +#ifdef CONFIG_USERSPACE +int z_vrfy_k_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_k_clock_nanosleep(clock_id, flags, rqtp, rmtp); +} +#include +#endif /* CONFIG_USERSPACE */ + +#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/libc/Kconfig b/lib/libc/Kconfig index 5d981e4719b0..3157fbd99c33 100644 --- a/lib/libc/Kconfig +++ b/lib/libc/Kconfig @@ -84,6 +84,7 @@ config MINIMAL_LIBC imply COMMON_LIBC_MALLOC imply COMMON_LIBC_CALLOC imply COMMON_LIBC_REALLOCARRAY + imply COMMON_LIBC_TIME help Build with minimal C library. @@ -96,6 +97,7 @@ config PICOLIBC select TC_PROVIDES_POSIX_C_LANG_SUPPORT_R imply COMMON_LIBC_MALLOC imply COMMON_LIBC_ABORT + imply COMMON_LIBC_TIME depends on PICOLIBC_SUPPORTED help Build with picolibc library. The picolibc library is built as @@ -116,6 +118,7 @@ config NEWLIB_LIBC imply POSIX_FILE_SYSTEM_ALIAS_FSTAT imply POSIX_MULTI_PROCESS_ALIAS_GETPID imply POSIX_SIGNALS_ALIAS_KILL + imply COMMON_LIBC_TIME help Build with newlib library. The newlib library is expected to be part of the SDK in this case. @@ -137,7 +140,7 @@ config IAR_LIBC depends on IAR_LIBC_SUPPORTED depends on "$(ZEPHYR_TOOLCHAIN_VARIANT)" = "iar" select COMMON_LIBC_STRNLEN - select COMMON_LIBC_TIME if POSIX_TIMERS + select COMMON_LIBC_TIME help Use the full IAR Compiler runtime libraries. A reduced Zephyr minimal libc will be used for library functionality diff --git a/lib/libc/common/source/thrd/thrd.c b/lib/libc/common/source/thrd/thrd.c index d811fe31ce6b..f930a1ebae01 100644 --- a/lib/libc/common/source/thrd/thrd.c +++ b/lib/libc/common/source/thrd/thrd.c @@ -44,7 +44,11 @@ thrd_t thrd_current(void) int thrd_sleep(const struct timespec *duration, struct timespec *remaining) { - return nanosleep(duration, remaining); + if (k_clock_nanosleep(K_CLOCK_MONOTONIC, 0, duration, remaining) != 0) { + return thrd_error; + } + + return thrd_success; } void thrd_yield(void) diff --git a/lib/libc/common/source/time/time.c b/lib/libc/common/source/time/time.c index 85840fa8cd94..42caca5df0bc 100644 --- a/lib/libc/common/source/time/time.c +++ b/lib/libc/common/source/time/time.c @@ -1,22 +1,22 @@ /* * Copyright (c) 2021 Golioth, Inc. + * Copyright (c) 2025 Tenstorrent AI ULC * * SPDX-License-Identifier: Apache-2.0 */ #include -/* clock_gettime() prototype */ -#include +#include time_t time(time_t *tloc) { struct timespec ts; int ret; - ret = clock_gettime(CLOCK_REALTIME, &ts); + ret = k_clock_gettime(K_CLOCK_REALTIME, &ts); if (ret < 0) { - /* errno is already set by clock_gettime */ + errno = -ret; return (time_t) -1; } diff --git a/lib/posix/options/CMakeLists.txt b/lib/posix/options/CMakeLists.txt index f12904aefb60..3f3d31d61c50 100644 --- a/lib/posix/options/CMakeLists.txt +++ b/lib/posix/options/CMakeLists.txt @@ -2,10 +2,6 @@ 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) endif() @@ -47,10 +43,7 @@ if (NOT CONFIG_TC_PROVIDES_POSIX_BARRIERS) endif() if (NOT CONFIG_TC_PROVIDES_POSIX_CLOCK_SELECTION) - zephyr_library_sources_ifdef(CONFIG_POSIX_CLOCK_SELECTION - clock_common.c - clock_selection.c - ) + zephyr_library_sources_ifdef(CONFIG_POSIX_CLOCK_SELECTION clock_selection.c) endif() if (NOT CONFIG_TC_PROVIDES_POSIX_C_LIB_EXT) @@ -124,7 +117,6 @@ 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 ) @@ -166,7 +158,6 @@ 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 ) diff --git a/lib/posix/options/clock.c b/lib/posix/options/clock.c index 1f4533467312..80ca0617dc59 100644 --- a/lib/posix/options/clock.c +++ b/lib/posix/options/clock.c @@ -8,18 +8,22 @@ #include #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 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) { - return z_clock_gettime(clock_id, ts); + int ret; + + ret = k_clock_gettime(clock_id, ts); + if (ret < 0) { + errno = -ret; + return -1; + } + + return 0; } int clock_getres(clockid_t clock_id, struct timespec *res) @@ -54,7 +58,15 @@ int clock_getres(clockid_t clock_id, struct timespec *res) */ int clock_settime(clockid_t clock_id, const struct timespec *tp) { - return z_clock_settime(clock_id, tp); + int ret; + + ret = k_clock_settime(clock_id, tp); + if (ret < 0) { + errno = -ret; + return -1; + } + + return 0; } /* @@ -88,7 +100,20 @@ int usleep(useconds_t useconds) int nanosleep(const struct timespec *rqtp, struct timespec *rmtp) { - return z_clock_nanosleep(CLOCK_MONOTONIC, 0, rqtp, rmtp); + int ret; + + if (rqtp == NULL) { + errno = EFAULT; + return -1; + } + + ret = k_clock_nanosleep(K_CLOCK_MONOTONIC, 0, rqtp, rmtp); + if (ret < 0) { + errno = -ret; + return -1; + } + + return 0; } int clock_getcpuclockid(pid_t pid, clockid_t *clock_id) diff --git a/lib/posix/options/clock_common.c b/lib/posix/options/clock_common.c deleted file mode 100644 index daacd3c7b801..000000000000 --- a/lib/posix/options/clock_common.c +++ /dev/null @@ -1,200 +0,0 @@ -/* - * 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 = {.tv_sec = 0, .tv_nsec = 0}; - - switch (clock_id) { - case CLOCK_MONOTONIC: - 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 index 6bddc3212080..ee30069e9236 100644 --- a/lib/posix/options/clock_selection.c +++ b/lib/posix/options/clock_selection.c @@ -15,13 +15,23 @@ #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) +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 ret; + + if (rqtp == NULL) { + errno = EFAULT; + return -1; + } + + ret = k_clock_nanosleep((int)clock_id, flags, rqtp, rmtp); + if (ret < 0) { + errno = -ret; + return -1; + } + + return 0; } int pthread_condattr_getclock(const pthread_condattr_t *ZRESTRICT att, diff --git a/lib/posix/options/posix_clock.h b/lib/posix/options/posix_clock.h index 31fd829c1478..c4c03a572a45 100644 --- a/lib/posix/options/posix_clock.h +++ b/lib/posix/options/posix_clock.h @@ -16,6 +16,7 @@ #include #include #include +#include #ifdef __cplusplus extern "C" { @@ -23,12 +24,6 @@ extern "C" { /** @cond INTERNAL_HIDDEN */ -static inline bool timespec_is_valid(const struct timespec *ts) -{ - __ASSERT_NO_MSG(ts != NULL); - return (ts->tv_nsec >= 0) && (ts->tv_nsec < NSEC_PER_SEC); -} - static inline int64_t ts_to_ns(const struct timespec *ts) { return ts->tv_sec * NSEC_PER_SEC + ts->tv_nsec; @@ -47,7 +42,7 @@ static inline void tv_to_ts(const struct timeval *tv, struct timespec *ts) static inline bool tp_ge(const struct timespec *a, const struct timespec *b) { - return ts_to_ns(a) >= ts_to_ns(b); + return timespec_compare(a, b) >= 0; } static inline int64_t tp_diff(const struct timespec *a, const struct timespec *b) @@ -66,12 +61,8 @@ static inline bool tp_diff_in_range_ns(const struct timespec *a, const struct ti uint32_t timespec_to_timeoutms(clockid_t clock_id, const struct timespec *abstime); -__syscall int __posix_clock_get_base(clockid_t clock_id, struct timespec *ts); - /** INTERNAL_HIDDEN @endcond */ -#include - #ifdef __cplusplus } #endif diff --git a/lib/posix/options/xsi_single_process.c b/lib/posix/options/xsi_single_process.c index cc5e82cb99a7..6a171b4f3203 100644 --- a/lib/posix/options/xsi_single_process.c +++ b/lib/posix/options/xsi_single_process.c @@ -12,6 +12,7 @@ #include #include +#include #include #include #include @@ -19,7 +20,6 @@ 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) { @@ -46,10 +46,11 @@ int gettimeofday(struct timeval *tv, void *tz) */ ARG_UNUSED(tz); - res = z_clock_gettime(CLOCK_REALTIME, &ts); + res = k_clock_gettime(K_CLOCK_REALTIME, &ts); if (res < 0) { - LOG_DBG("%s() failed: %d", "clock_gettime", res); - return res; + LOG_DBG("%s() failed: %d", "k_clock_gettime", res); + errno = -res; + return -1; } tv->tv_sec = ts.tv_sec; diff --git a/samples/net/cloud/aws_iot_mqtt/prj.conf b/samples/net/cloud/aws_iot_mqtt/prj.conf index ef5f6574ef75..825bd8514b95 100644 --- a/samples/net/cloud/aws_iot_mqtt/prj.conf +++ b/samples/net/cloud/aws_iot_mqtt/prj.conf @@ -39,7 +39,6 @@ CONFIG_NET_DHCPV4=y # SNTP CONFIG_SNTP=y -CONFIG_POSIX_TIMERS=y CONFIG_NET_CONFIG_CLOCK_SNTP_INIT=y CONFIG_NET_CONFIG_SNTP_INIT_SERVER="0.pool.ntp.org" diff --git a/samples/net/lwm2m_client/overlay-data-cache.conf b/samples/net/lwm2m_client/overlay-data-cache.conf index 523b23519add..f8356b12d95a 100644 --- a/samples/net/lwm2m_client/overlay-data-cache.conf +++ b/samples/net/lwm2m_client/overlay-data-cache.conf @@ -1,5 +1,4 @@ CONFIG_ZCBOR=y CONFIG_ZCBOR_CANONICAL=y CONFIG_LWM2M_RW_SENML_CBOR_SUPPORT=y -CONFIG_XSI_SINGLE_PROCESS=y CONFIG_LWM2M_RESOURCE_DATA_CACHE_SUPPORT=y diff --git a/subsys/net/lib/config/Kconfig b/subsys/net/lib/config/Kconfig index d0a98d207ed6..82b534888d43 100644 --- a/subsys/net/lib/config/Kconfig +++ b/subsys/net/lib/config/Kconfig @@ -206,7 +206,7 @@ endif # NET_DHCPV6 config NET_CONFIG_CLOCK_SNTP_INIT bool "Initialize system clock using SNTP on application startup" - depends on SNTP && POSIX_TIMERS + depends on SNTP help Perform an SNTP request over networking to get and absolute wall clock time, and initialize system time from it, so diff --git a/subsys/net/lib/config/init_clock_sntp.c b/subsys/net/lib/config/init_clock_sntp.c index dd63564f9c02..140abc1f3e07 100644 --- a/subsys/net/lib/config/init_clock_sntp.c +++ b/subsys/net/lib/config/init_clock_sntp.c @@ -12,7 +12,6 @@ LOG_MODULE_DECLARE(net_config, CONFIG_NET_CONFIG_LOG_LEVEL); #include #include #include -#include #ifdef CONFIG_NET_CONFIG_SNTP_INIT_RESYNC static void sntp_resync_handler(struct k_work *work); @@ -60,7 +59,7 @@ int net_init_clock_via_sntp(void) tspec.tv_sec = ts.seconds; tspec.tv_nsec = ((uint64_t)ts.fraction * (1000 * 1000 * 1000)) >> 32; - res = clock_settime(CLOCK_REALTIME, &tspec); + res = k_clock_settime(K_CLOCK_REALTIME, &tspec); end: #ifdef CONFIG_NET_CONFIG_SNTP_INIT_RESYNC diff --git a/subsys/net/lib/lwm2m/Kconfig b/subsys/net/lib/lwm2m/Kconfig index 9872e2acd915..8bd0cdef300f 100644 --- a/subsys/net/lib/lwm2m/Kconfig +++ b/subsys/net/lib/lwm2m/Kconfig @@ -227,7 +227,7 @@ config LWM2M_RD_CLIENT_MAX_RETRIES config LWM2M_RESOURCE_DATA_CACHE_SUPPORT bool "Resource Time series data cache support" depends on (LWM2M_RW_SENML_JSON_SUPPORT || LWM2M_RW_SENML_CBOR_SUPPORT) - depends on (XSI_SINGLE_PROCESS && RING_BUFFER) + depends on RING_BUFFER help Enable time series data storage. Requires time() to provide current Unix timestamp. diff --git a/tests/lib/c_lib/thrd/src/thrd.c b/tests/lib/c_lib/thrd/src/thrd.c index 70dca94b4a9a..1d54b22dbd01 100644 --- a/tests/lib/c_lib/thrd/src/thrd.c +++ b/tests/lib/c_lib/thrd/src/thrd.c @@ -10,6 +10,7 @@ #include #include +#include #include static thrd_t thr; @@ -28,7 +29,7 @@ ZTEST(libc_thrd, test_thrd_sleep) zassert_ok(thrd_sleep(&duration, &duration)); for (int i = 0; i < ARRAY_SIZE(delay_ms); ++i) { - duration = (struct timespec){.tv_nsec = delay_ms[i] * NSEC_PER_MSEC}; + timespec_from_timeout(K_MSEC(delay_ms[i]), &duration); remaining = (struct timespec){.tv_sec = 4242, .tv_nsec = 4242}; printk("sleeping %d ms\n", delay_ms[i]); diff --git a/tests/lib/time/CMakeLists.txt b/tests/lib/c_lib/time/CMakeLists.txt similarity index 100% rename from tests/lib/time/CMakeLists.txt rename to tests/lib/c_lib/time/CMakeLists.txt diff --git a/tests/lib/c_lib/time/prj.conf b/tests/lib/c_lib/time/prj.conf new file mode 100644 index 000000000000..9467c2926896 --- /dev/null +++ b/tests/lib/c_lib/time/prj.conf @@ -0,0 +1 @@ +CONFIG_ZTEST=y diff --git a/tests/lib/time/src/main.c b/tests/lib/c_lib/time/src/main.c similarity index 100% rename from tests/lib/time/src/main.c rename to tests/lib/c_lib/time/src/main.c diff --git a/tests/lib/time/testcase.yaml b/tests/lib/c_lib/time/testcase.yaml similarity index 100% rename from tests/lib/time/testcase.yaml rename to tests/lib/c_lib/time/testcase.yaml diff --git a/tests/lib/time/prj.conf b/tests/lib/time/prj.conf deleted file mode 100644 index dd87f5a1338c..000000000000 --- a/tests/lib/time/prj.conf +++ /dev/null @@ -1,4 +0,0 @@ -CONFIG_ZTEST=y -CONFIG_POSIX_TIMERS=y -CONFIG_XSI_SINGLE_PROCESS=y -CONFIG_PICOLIBC=y diff --git a/tests/lib/timespec_util/CMakeLists.txt b/tests/lib/timespec_util/CMakeLists.txt new file mode 100644 index 000000000000..e436a632ba04 --- /dev/null +++ b/tests/lib/timespec_util/CMakeLists.txt @@ -0,0 +1,8 @@ +# SPDX-License-Identifier: Apache-2.0 + +cmake_minimum_required(VERSION 3.20.0) +find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) +project(timespec_util) + +FILE(GLOB app_sources src/main.c) +target_sources(app PRIVATE ${app_sources}) diff --git a/tests/lib/timespec_util/prj.conf b/tests/lib/timespec_util/prj.conf new file mode 100644 index 000000000000..9467c2926896 --- /dev/null +++ b/tests/lib/timespec_util/prj.conf @@ -0,0 +1 @@ +CONFIG_ZTEST=y diff --git a/tests/lib/timespec_util/src/main.c b/tests/lib/timespec_util/src/main.c new file mode 100644 index 000000000000..11e4ccf05976 --- /dev/null +++ b/tests/lib/timespec_util/src/main.c @@ -0,0 +1,349 @@ +/* + * Copyright 2025 Tenstorrent AI ULC + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include + +#include +#include +#include +#include + +BUILD_ASSERT(sizeof(time_t) == sizeof(int64_t), "time_t must be 64-bit"); +BUILD_ASSERT(sizeof(((struct timespec *)0)->tv_sec) == sizeof(int64_t), "tv_sec must be 64-bit"); + +/* need NSEC_PER_SEC to be signed for the purposes of this testsuite */ +#undef NSEC_PER_SEC +#define NSEC_PER_SEC 1000000000L + +#undef CORRECTABLE +#define CORRECTABLE true + +#undef UNCORRECTABLE +#define UNCORRECTABLE false + +/* + * test spec for simple timespec validation + * + * If a timespec is expected to be valid, then invalid_ts and valid_ts are equal. + * + * If a timespec is expected to be invalid, then invalid_ts and valid_ts are not equal. + */ +struct ts_test_spec { + struct timespec invalid_ts; + struct timespec valid_ts; + bool expect_valid; + bool correctable; +}; + +#define DECL_VALID_TS_TEST(sec, nsec) \ + { \ + .invalid_ts = {(sec), (nsec)}, \ + .valid_ts = {(sec), (nsec)}, \ + .expect_valid = true, \ + .correctable = false, \ + } + +/* + * Invalid timespecs can usually be converted to valid ones by adding or subtracting + * multiples of `NSEC_PER_SEC` from tv_nsec, and incrementing or decrementing tv_sec + * proportionately, unless doing so would result in signed integer overflow. + * + * There are two particular corner cases: + * 1. making tv_sec more negative would overflow the tv_sec field in the negative direction + * 1. making tv_sec more positive would overflow the tv_sec field in the positive direction + */ +#define DECL_INVALID_TS_TEST(invalid_sec, invalid_nsec, valid_sec, valid_nsec, is_correctable) \ + { \ + .valid_ts = {(valid_sec), (valid_nsec)}, \ + .invalid_ts = {(invalid_sec), (invalid_nsec)}, \ + .expect_valid = false, \ + .correctable = (is_correctable), \ + } + +/* + * Easily verifiable tests for both valid and invalid timespecs. + */ +static const struct ts_test_spec ts_tests[] = { + /* Valid cases */ + DECL_VALID_TS_TEST(0, 0), + DECL_VALID_TS_TEST(0, 1), + DECL_VALID_TS_TEST(1, 0), + DECL_VALID_TS_TEST(1, 1), + DECL_VALID_TS_TEST(1, NSEC_PER_SEC - 1), + DECL_VALID_TS_TEST(-1, 0), + DECL_VALID_TS_TEST(-1, 1), + DECL_VALID_TS_TEST(-1, 0), + DECL_VALID_TS_TEST(-1, 1), + DECL_VALID_TS_TEST(-1, NSEC_PER_SEC - 1), + DECL_VALID_TS_TEST(INT64_MIN, 0), + DECL_VALID_TS_TEST(INT64_MIN, NSEC_PER_SEC - 1), + DECL_VALID_TS_TEST(INT64_MAX, 0), + DECL_VALID_TS_TEST(INT64_MAX, NSEC_PER_SEC - 1), + + /* Correctable, invalid cases */ + DECL_INVALID_TS_TEST(0, -2 * NSEC_PER_SEC + 1, -2, 1, CORRECTABLE), + DECL_INVALID_TS_TEST(0, -2 * NSEC_PER_SEC - 1, -3, NSEC_PER_SEC - 1, CORRECTABLE), + DECL_INVALID_TS_TEST(0, -NSEC_PER_SEC - 1, -2, NSEC_PER_SEC - 1, CORRECTABLE), + DECL_INVALID_TS_TEST(0, -1, -1, NSEC_PER_SEC - 1, CORRECTABLE), + DECL_INVALID_TS_TEST(0, NSEC_PER_SEC, 1, 0, CORRECTABLE), + DECL_INVALID_TS_TEST(0, NSEC_PER_SEC + 1, 1, 0, CORRECTABLE), + DECL_INVALID_TS_TEST(1, -1, 0, NSEC_PER_SEC - 1, CORRECTABLE), + DECL_INVALID_TS_TEST(1, NSEC_PER_SEC, 2, 0, CORRECTABLE), + DECL_INVALID_TS_TEST(-1, -1, -2, NSEC_PER_SEC - 1, CORRECTABLE), + DECL_INVALID_TS_TEST(0, NSEC_PER_SEC, 1, 0, CORRECTABLE), + DECL_INVALID_TS_TEST(1, -1, 0, NSEC_PER_SEC - 1, CORRECTABLE), + DECL_INVALID_TS_TEST(1, NSEC_PER_SEC, 2, 0, CORRECTABLE), + DECL_INVALID_TS_TEST(INT64_MIN, NSEC_PER_SEC, INT64_MIN + 1, 0, CORRECTABLE), + DECL_INVALID_TS_TEST(INT64_MAX, -1, INT64_MAX - 1, NSEC_PER_SEC - 1, CORRECTABLE), + DECL_INVALID_TS_TEST(0, LONG_MIN, LONG_MAX / NSEC_PER_SEC, 145224192, CORRECTABLE), + DECL_INVALID_TS_TEST(0, LONG_MAX, LONG_MAX / NSEC_PER_SEC, LONG_MAX % NSEC_PER_SEC, + CORRECTABLE), + + /* Uncorrectable, invalid cases */ + DECL_INVALID_TS_TEST(INT64_MIN + 2, -2 * NSEC_PER_SEC - 1, 0, 0, UNCORRECTABLE), + DECL_INVALID_TS_TEST(INT64_MIN + 1, -NSEC_PER_SEC - 1, 0, 0, UNCORRECTABLE), + DECL_INVALID_TS_TEST(INT64_MIN + 1, -NSEC_PER_SEC - 1, 0, 0, UNCORRECTABLE), + DECL_INVALID_TS_TEST(INT64_MIN, -1, 0, 0, UNCORRECTABLE), + DECL_INVALID_TS_TEST(INT64_MAX, NSEC_PER_SEC, 0, 0, UNCORRECTABLE), + DECL_INVALID_TS_TEST(INT64_MAX - 1, 2 * NSEC_PER_SEC, 0, 0, UNCORRECTABLE), +}; + +ZTEST(timeutil_api, test_timespec_is_valid) +{ + ARRAY_FOR_EACH(ts_tests, i) { + const struct ts_test_spec *const tspec = &ts_tests[i]; + bool valid = timespec_is_valid(&tspec->invalid_ts); + + zexpect_equal(valid, tspec->expect_valid, + "%d: timespec_is_valid({%ld, %ld}) = %s, expected true", i, + tspec->valid_ts.tv_sec, tspec->valid_ts.tv_nsec, + tspec->expect_valid ? "false" : "true"); + } +} + +ZTEST(timeutil_api, test_timespec_normalize) +{ + ARRAY_FOR_EACH(ts_tests, i) { + bool different; + bool overflow; + const struct ts_test_spec *const tspec = &ts_tests[i]; + struct timespec norm = tspec->invalid_ts; + + overflow = !timespec_normalize(&norm); + zexpect_not_equal(tspec->expect_valid || tspec->correctable, overflow, + "%d: timespec_normalize({%ld, %ld}) %s, unexpectedly", i, + (long)tspec->invalid_ts.tv_sec, (long)tspec->invalid_ts.tv_nsec, + tspec->correctable ? "failed" : "succeeded"); + + if (!tspec->expect_valid && tspec->correctable) { + different = !timespec_equal(&tspec->invalid_ts, &norm); + zexpect_true(different, "%d: {%ld, %ld} and {%ld, %ld} are unexpectedly %s", + i, tspec->invalid_ts.tv_sec, tspec->invalid_ts.tv_nsec, + norm.tv_sec, tspec->valid_ts.tv_sec, + (tspec->expect_valid || tspec->correctable) ? "different" + : "equal"); + } + } +} + +ZTEST(timeutil_api, test_timespec_add) +{ + bool overflow; + struct timespec actual; + const struct atspec { + struct timespec a; + struct timespec b; + struct timespec result; + bool expect; + } tspecs[] = { + /* non-overflow cases */ + {.a = {0, 0}, .b = {0, 0}, .result = {0, 0}, .expect = false}, + {.a = {1, 1}, .b = {1, 1}, .result = {2, 2}, .expect = false}, + {.a = {-1, 1}, .b = {-1, 1}, .result = {-2, 2}, .expect = false}, + {.a = {-1, NSEC_PER_SEC - 1}, .b = {0, 1}, .result = {0, 0}, .expect = false}, + /* overflow cases */ + {.a = {INT64_MAX, 0}, .b = {1, 0}, .result = {0}, .expect = true}, + {.a = {INT64_MIN, 0}, .b = {-1, 0}, .result = {0}, .expect = true}, + {.a = {INT64_MAX, NSEC_PER_SEC - 1}, .b = {1, 1}, .result = {0}, .expect = true}, + {.a = {INT64_MIN, NSEC_PER_SEC - 1}, .b = {-1, 0}, .result = {0}, .expect = true}, + }; + + ARRAY_FOR_EACH(tspecs, i) { + const struct atspec *const tspec = &tspecs[i]; + + actual = tspec->a; + overflow = !timespec_add(&actual, &tspec->b); + + zexpect_equal(overflow, tspec->expect, + "%d: timespec_add({%ld, %ld}, {%ld, %ld}) %s, unexpectedly", i, + tspec->a.tv_sec, tspec->a.tv_nsec, tspec->b.tv_sec, tspec->b.tv_nsec, + tspec->expect ? "succeeded" : "failed"); + + if (!tspec->expect) { + zexpect_equal(timespec_equal(&actual, &tspec->result), true, + "%d: {%ld, %ld} and {%ld, %ld} are unexpectedly different", i, + actual.tv_sec, actual.tv_nsec, tspec->result.tv_sec, + tspec->result.tv_nsec); + } + } +} + +ZTEST(timeutil_api, test_timespec_negate) +{ + struct ntspec { + struct timespec ts; + struct timespec result; + bool expect_failure; + } tspecs[] = { + /* non-overflow cases */ + {.ts = {0, 0}, .result = {0, 0}, .expect_failure = false}, + {.ts = {1, 1}, .result = {-2, NSEC_PER_SEC - 1}, .expect_failure = false}, + {.ts = {-1, 1}, .result = {0, NSEC_PER_SEC - 1}, .expect_failure = false}, + {.ts = {INT64_MAX, 0}, .result = {INT64_MIN + 1, 0}, .expect_failure = false}, + /* overflow cases */ + {.ts = {INT64_MIN, 0}, .result = {0}, .expect_failure = true}, + }; + + ARRAY_FOR_EACH(tspecs, i) { + bool overflow; + const struct ntspec *const tspec = &tspecs[i]; + struct timespec actual = tspec->ts; + + overflow = !timespec_negate(&actual); + zexpect_equal(overflow, tspec->expect_failure, + "%d: timespec_negate({%ld, %ld}) %s, unexpectedly", i, + tspec->ts.tv_sec, tspec->ts.tv_nsec, + tspec->expect_failure ? "did not overflow" : "overflowed"); + + if (!tspec->expect_failure) { + zexpect_true(timespec_equal(&actual, &tspec->result), + "%d: {%ld, %ld} and {%ld, %ld} are unexpectedly different", i, + actual.tv_sec, actual.tv_nsec, tspec->result.tv_sec, + tspec->result.tv_nsec); + } + } +} + +ZTEST(timeutil_api, test_timespec_sub) +{ + struct timespec a = {0}; + struct timespec b = {0}; + + zexpect_true(timespec_sub(&a, &b)); +} + +ZTEST(timeutil_api, test_timespec_compare) +{ + struct timespec a; + struct timespec b; + + a = (struct timespec){0}; + b = (struct timespec){0}; + zexpect_equal(timespec_compare(&a, &b), 0); + + a = (struct timespec){-1}; + b = (struct timespec){0}; + zexpect_equal(timespec_compare(&a, &b), -1); + + a = (struct timespec){1}; + b = (struct timespec){0}; + zexpect_equal(timespec_compare(&a, &b), 1); +} + +ZTEST(timeutil_api, test_timespec_equal) +{ + struct timespec a; + struct timespec b; + + a = (struct timespec){0}; + b = (struct timespec){0}; + zexpect_true(timespec_equal(&a, &b)); + + a = (struct timespec){-1}; + b = (struct timespec){0}; + zexpect_false(timespec_equal(&a, &b)); +} + +static const struct tospec { + k_timeout_t timeout; + struct timespec tspec; + int saturation; +} tospecs[] = { + {K_NO_WAIT, {INT64_MIN, 0}, -1}, + {K_NO_WAIT, {-1, 0}, -1}, + {K_NO_WAIT, {-1, NSEC_PER_SEC - 1}, -1}, + {K_NO_WAIT, {0, 0}, 0}, + {K_NSEC(0), {0, 0}, 0}, + {K_NSEC(2000000000), {2, 0}, 0}, + {K_USEC(0), {0, 0}, 0}, + {K_USEC(2000000), {2, 0}, 0}, + {K_MSEC(100), {0, 100000000}, 0}, + {K_MSEC(2000), {2, 0}, 0}, + {K_SECONDS(0), {0, 0}, 0}, + {K_SECONDS(1), {1, 0}, 0}, + {K_SECONDS(100), {100, 0}, 0}, + {K_FOREVER, {INT64_MAX, NSEC_PER_SEC - 1}, 0}, +}; + +ZTEST(timeutil_api, test_timespec_from_timeout) +{ + ARRAY_FOR_EACH(tospecs, i) { + const struct tospec *const tspec = &tospecs[i]; + struct timespec actual; + + if (tspec->saturation != 0) { + /* saturation cases are only checked in test_timespec_to_timeout */ + continue; + } + + timespec_from_timeout(tspec->timeout, &actual); + zexpect_true(timespec_equal(&actual, &tspec->tspec), + "%d: {%ld, %ld} and {%ld, %ld} are unexpectedly different", i, + actual.tv_sec, actual.tv_nsec, tspec->tspec.tv_sec, + tspec->tspec.tv_nsec); + } +} + +ZTEST(timeutil_api, test_timespec_to_timeout) +{ + ARRAY_FOR_EACH(tospecs, i) { + const struct tospec *const tspec = &tospecs[i]; + k_timeout_t actual; + + if (tspec->saturation == 0) { + /* no saturation / exact match */ + actual = timespec_to_timeout(&tspec->timeout); + zexpect_equal(actual.ticks, tspec->timeout.ticks, + "%d: {%" PRId64 "} and {%" PRId64 + "} are unexpectedly different", + i, (int64_t)actual.ticks, (int64_t)tspec->timeout.ticks); + continue; + } + + if (tspec->saturation < 0) { + /* K_NO_WAIT saturation */ + actual = timespec_to_timeout(&tspec->tspec); + zexpect_equal(actual.ticks, K_NO_WAIT.ticks, + "%d: {%" PRId64 "} and {%" PRId64 + "} are unexpectedly different", + i, (int64_t)actual.ticks, (int64_t)K_NO_WAIT.ticks); + continue; + } + + if (tspec->saturation > 0) { + /* K_FOREVER saturation */ + actual = timespec_to_timeout(&tspec->tspec); + zexpect_equal(actual.ticks, K_TICKS_FOREVER, + "%d: {%" PRId64 "} and {%" PRId64 + "} are unexpectedly different", + i, (int64_t)actual.ticks, (int64_t)K_TICKS_FOREVER); + continue; + } + } +} + +ZTEST_SUITE(timeutil_api, NULL, NULL, NULL, NULL, NULL); diff --git a/tests/lib/timespec_util/testcase.yaml b/tests/lib/timespec_util/testcase.yaml new file mode 100644 index 000000000000..d69c5bb755fb --- /dev/null +++ b/tests/lib/timespec_util/testcase.yaml @@ -0,0 +1,36 @@ +# FIXME: this should be under tests/unit/timeutil but will not work due to #90029 +common: + filter: not CONFIG_NATIVE_LIBC + tags: + - timeutils + # 1 tier0 platform per supported architecture + platform_key: + - arch + - simulation + integration_platforms: + - native_sim/native/64 +tests: + libraries.timespec_utils: {} + libraries.timespec_utils.speed: + extra_configs: + - CONFIG_SPEED_OPTIMIZATIONS=y + libraries.timespec_utils.armclang_std_libc: + toolchain_allow: armclang + extra_configs: + - CONFIG_ARMCLANG_STD_LIBC=y + libraries.timespec_utils.arcmwdtlib: + toolchain_allow: arcmwdt + extra_configs: + - CONFIG_ARCMWDT_LIBC=y + libraries.timespec_utils.minimal: + extra_configs: + - CONFIG_MINIMAL_LIBC=y + libraries.timespec_utils.newlib: + filter: TOOLCHAIN_HAS_NEWLIB == 1 + extra_configs: + - CONFIG_NEWLIB_LIBC=y + libraries.timespec_utils.picolibc: + tags: picolibc + filter: CONFIG_PICOLIBC_SUPPORTED + extra_configs: + - CONFIG_PICOLIBC=y