309 lines
9.8 KiB
C
309 lines
9.8 KiB
C
/*
|
|
* 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 <time.h>
|
|
|
|
#include <zephyr/types.h>
|
|
|
|
#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_ */
|