/* * Copyright (c) 2019 Peter Bigot Consulting, LLC * * SPDX-License-Identifier: Apache-2.0 */ /** * @file * @brief Utilities supporting operation on time data structures. * * POSIX defines gmtime() to convert from time_t to struct tm, but all * inverse transformations are non-standard or require access to time * zone information. timeutil_timegm() implements the functionality * of the GNU extension timegm() function, but changes the error value * as @c EOVERFLOW is not a standard C error identifier. * * timeutil_timegm64() is provided to support full precision * conversion on platforms where @c time_t is limited to 32 bits. */ #ifndef ZEPHYR_INCLUDE_SYS_TIMEUTIL_H_ #define ZEPHYR_INCLUDE_SYS_TIMEUTIL_H_ #include #include #ifdef __cplusplus extern "C" { #endif /** * @defgroup timeutil_apis Time Utility APIs * @defgroup timeutil_repr_apis Time Representation APIs * @ingroup timeutil_apis * @{ */ /** * @brief Convert broken-down time to a POSIX epoch offset in seconds. * * @param tm pointer to broken down time. * * @return the corresponding time in the POSIX epoch time scale. * * @see http://man7.org/linux/man-pages/man3/timegm.3.html */ int64_t timeutil_timegm64(const struct tm *tm); /** * @brief Convert broken-down time to a POSIX epoch offset in seconds. * * @param tm pointer to broken down time. * * @return the corresponding time in the POSIX epoch time scale. If * the time cannot be represented then @c (time_t)-1 is returned and * @c errno is set to @c ERANGE`. * * @see http://man7.org/linux/man-pages/man3/timegm.3.html */ time_t timeutil_timegm(const struct tm *tm); /** * @} * @defgroup timeutil_sync_apis Time Synchronization APIs * @ingroup timeutil_apis * @{ */ /** * @brief Immutable state for synchronizing two clocks. * * Values required to convert durations between two time scales. * * @note The accuracy of the translation and calculated skew between sources * depends on the resolution of these frequencies. A reference frequency with * microsecond or nanosecond resolution would produce the most accurate * tracking when the local reference is the Zephyr tick counter. A reference * source like an RTC chip with 1 Hz resolution requires a much larger * interval between sampled instants to detect relative clock drift. */ struct timeutil_sync_config { /** The nominal instance counter rate in Hz. * * This value is assumed to be precise, but may drift depending on * the reference clock source. * * The value must be positive. */ uint32_t ref_Hz; /** The nominal local counter rate in Hz. * * This value is assumed to be inaccurate but reasonably stable. For * a local clock driven by a crystal oscillator an error of 25 ppm is * common; for an RC oscillator larger errors should be expected. The * timeutil_sync infrastructure can calculate the skew between the * local and reference clocks and apply it when converting between * time scales. * * The value must be positive. */ uint32_t local_Hz; }; /** * @brief Representation of an instant in two time scales. * * Capturing the same instant in two time scales provides a * registration point that can be used to convert between those time * scales. */ struct timeutil_sync_instant { /** An instant in the reference time scale. * * This must never be zero in an initialized timeutil_sync_instant * object. */ uint64_t ref; /** The corresponding instance in the local time scale. * * This may be zero in a valid timeutil_sync_instant object. */ uint64_t local; }; /** * @brief State required to convert instants between time scales. * * This state in conjunction with functions that manipulate it capture * the offset information necessary to convert between two timescales * along with information that corrects for skew due to inaccuracies * in clock rates. * * State objects should be zero-initialized before use. */ struct timeutil_sync_state { /** Pointer to reference and local rate information. */ const struct timeutil_sync_config *cfg; /** The base instant in both time scales. */ struct timeutil_sync_instant base; /** The most recent instant in both time scales. * * This is captured here to provide data for skew calculation. */ struct timeutil_sync_instant latest; /** The scale factor used to correct for clock skew. * * The nominal rate for the local counter is assumed to be * inaccurate but stable, i.e. it will generally be some * parts-per-million faster or slower than specified. * * A duration in observed local clock ticks must be multiplied by * this value to produce a duration in ticks of a clock operating at * the nominal local rate. * * A zero value indicates that the skew has not been initialized. * If the value is zero when #base is initialized the skew will be * set to 1. Otherwise the skew is assigned through * timeutil_sync_state_set_skew(). */ float skew; }; /** * @brief Record a new instant in the time synchronization state. * * Note that this updates only the latest persisted instant. The skew * is not adjusted automatically. * * @param tsp pointer to a timeutil_sync_state object. * * @param inst the new instant to be recorded. This becomes the base * instant if there is no base instant, otherwise the value must be * strictly after the base instant in both the reference and local * time scales. * * @retval 0 if installation succeeded in providing a new base * @retval 1 if installation provided a new latest instant * @retval -EINVAL if the new instant is not compatible with the base instant */ int timeutil_sync_state_update(struct timeutil_sync_state *tsp, const struct timeutil_sync_instant *inst); /** * @brief Update the state with a new skew and possibly base value. * * Set the skew from a value retrieved from persistent storage, or * calculated based on recent skew estimations including from * timeutil_sync_estimate_skew(). * * Optionally update the base timestamp. If the base is replaced the * latest instant will be cleared until timeutil_sync_state_update() is * invoked. * * @param tsp pointer to a time synchronization state. * * @param skew the skew to be used. The value must be positive and * shouldn't be too far away from 1. * * @param base optional new base to be set. If provided this becomes * the base timestamp that will be used along with skew to convert * between reference and local timescale instants. Setting the base * clears the captured latest value. * * @return 0 if skew was updated * @return -EINVAL if skew was not valid */ int timeutil_sync_state_set_skew(struct timeutil_sync_state *tsp, float skew, const struct timeutil_sync_instant *base); /** * @brief Estimate the skew based on current state. * * Using the base and latest syncpoints from the state determine the * skew of the local clock relative to the reference clock. See * timeutil_sync_state::skew. * * @param tsp pointer to a time synchronization state. The base and latest * syncpoints must be present and the latest syncpoint must be after * the base point in the local time scale. * * @return the estimated skew, or zero if skew could not be estimated. */ float timeutil_sync_estimate_skew(const struct timeutil_sync_state *tsp); /** * @brief Interpolate a reference timescale instant from a local * instant. * * @param tsp pointer to a time synchronization state. This must have a base * and a skew installed. * * @param local an instant measured in the local timescale. This may * be before or after the base instant. * * @param refp where the corresponding instant in the reference * timescale should be stored. A negative interpolated reference time * produces an error. If interpolation fails the referenced object is * not modified. * * @retval 0 if interpolated using a skew of 1 * @retval 1 if interpolated using a skew not equal to 1 * @retval -EINVAL * * the times synchronization state is not adequately initialized * * @p refp is null * @retval -ERANGE the interpolated reference time would be negative */ int timeutil_sync_ref_from_local(const struct timeutil_sync_state *tsp, uint64_t local, uint64_t *refp); /** * @brief Interpolate a local timescale instant from a reference * instant. * * @param tsp pointer to a time synchronization state. This must have a base * and a skew installed. * * @param ref an instant measured in the reference timescale. This * may be before or after the base instant. * * @param localp where the corresponding instant in the local * timescale should be stored. An interpolated value before local * time 0 is provided without error. If interpolation fails the * referenced object is not modified. * * @retval 0 if successful with a skew of 1 * @retval 1 if successful with a skew not equal to 1 * @retval -EINVAL * * the time synchronization state is not adequately initialized * * @p refp is null */ int timeutil_sync_local_from_ref(const struct timeutil_sync_state *tsp, uint64_t ref, int64_t *localp); /** * @brief Convert from a skew to an error in parts-per-billion. * * A skew of 1.0 has zero error. A skew less than 1 has a positive * error (clock is faster than it should be). A skew greater than one * has a negative error (clock is slower than it should be). * * Note that due to the limited precision of @c float compared with @c * double the smallest error that can be represented is about 120 ppb. * A "precise" time source may have error on the order of 2000 ppb. * * A skew greater than 3.14748 may underflow the 32-bit * representation; this represents a clock running at less than 1/3 * its nominal rate. * * @return skew error represented as parts-per-billion, or INT32_MIN * if the skew cannot be represented in the return type. */ int32_t timeutil_sync_skew_to_ppb(float skew); #ifdef __cplusplus } #endif /** * @} */ #endif /* ZEPHYR_INCLUDE_SYS_TIMEUTIL_H_ */