1 /*
2  * Copyright (c) 2019 Peter Bigot Consulting, LLC
3  *
4  * SPDX-License-Identifier: Apache-2.0
5  */
6 
7 /**
8  * @file
9  * @brief Utilities supporting operation on time data structures.
10  *
11  * POSIX defines gmtime() to convert from time_t to struct tm, but all
12  * inverse transformations are non-standard or require access to time
13  * zone information.  timeutil_timegm() implements the functionality
14  * of the GNU extension timegm() function, but changes the error value
15  * as @c EOVERFLOW is not a standard C error identifier.
16  *
17  * timeutil_timegm64() is provided to support full precision
18  * conversion on platforms where @c time_t is limited to 32 bits.
19  */
20 
21 #ifndef ZEPHYR_INCLUDE_SYS_TIMEUTIL_H_
22 #define ZEPHYR_INCLUDE_SYS_TIMEUTIL_H_
23 
24 #include <time.h>
25 
26 #include <zephyr/types.h>
27 
28 #ifdef __cplusplus
29 extern "C" {
30 #endif
31 
32 /**
33  * @defgroup timeutil_apis Time Utility APIs
34  * @ingroup utilities
35  * @defgroup timeutil_repr_apis Time Representation APIs
36  * @ingroup timeutil_apis
37  * @{
38  */
39 
40 /* Base Year value use in calculations in "timeutil_timegm64" API */
41 #define TIME_UTILS_BASE_YEAR 1900
42 
43 /**
44  * @brief Convert broken-down time to a POSIX epoch offset in seconds.
45  *
46  * @param tm pointer to broken down time.
47  *
48  * @return the corresponding time in the POSIX epoch time scale.
49  *
50  * @see http://man7.org/linux/man-pages/man3/timegm.3.html
51  */
52 int64_t timeutil_timegm64(const struct tm *tm);
53 
54 /**
55  * @brief Convert broken-down time to a POSIX epoch offset in seconds.
56  *
57  * @param tm pointer to broken down time.
58  *
59  * @return the corresponding time in the POSIX epoch time scale.  If
60  * the time cannot be represented then @c (time_t)-1 is returned and
61  * @c errno is set to @c ERANGE`.
62  *
63  * @see http://man7.org/linux/man-pages/man3/timegm.3.html
64  */
65 time_t timeutil_timegm(const struct tm *tm);
66 
67 /**
68  * @}
69  * @defgroup timeutil_sync_apis Time Synchronization APIs
70  * @ingroup timeutil_apis
71  * @{
72  */
73 
74 /**
75  * @brief Immutable state for synchronizing two clocks.
76  *
77  * Values required to convert durations between two time scales.
78  *
79  * @note The accuracy of the translation and calculated skew between sources
80  * depends on the resolution of these frequencies.  A reference frequency with
81  * microsecond or nanosecond resolution would produce the most accurate
82  * tracking when the local reference is the Zephyr tick counter.  A reference
83  * source like an RTC chip with 1 Hz resolution requires a much larger
84  * interval between sampled instants to detect relative clock drift.
85  */
86 struct timeutil_sync_config {
87 	/** The nominal instance counter rate in Hz.
88 	 *
89 	 * This value is assumed to be precise, but may drift depending on
90 	 * the reference clock source.
91 	 *
92 	 * The value must be positive.
93 	 */
94 	uint32_t ref_Hz;
95 
96 	/** The nominal local counter rate in Hz.
97 	 *
98 	 * This value is assumed to be inaccurate but reasonably stable.  For
99 	 * a local clock driven by a crystal oscillator an error of 25 ppm is
100 	 * common; for an RC oscillator larger errors should be expected.  The
101 	 * timeutil_sync infrastructure can calculate the skew between the
102 	 * local and reference clocks and apply it when converting between
103 	 * time scales.
104 	 *
105 	 * The value must be positive.
106 	 */
107 	uint32_t local_Hz;
108 };
109 
110 /**
111  * @brief Representation of an instant in two time scales.
112  *
113  * Capturing the same instant in two time scales provides a
114  * registration point that can be used to convert between those time
115  * scales.
116  */
117 struct timeutil_sync_instant {
118 	/** An instant in the reference time scale.
119 	 *
120 	 * This must never be zero in an initialized timeutil_sync_instant
121 	 * object.
122 	 */
123 	uint64_t ref;
124 
125 	/** The corresponding instance in the local time scale.
126 	 *
127 	 * This may be zero in a valid timeutil_sync_instant object.
128 	 */
129 	uint64_t local;
130 };
131 
132 /**
133  * @brief State required to convert instants between time scales.
134  *
135  * This state in conjunction with functions that manipulate it capture
136  * the offset information necessary to convert between two timescales
137  * along with information that corrects for skew due to inaccuracies
138  * in clock rates.
139  *
140  * State objects should be zero-initialized before use.
141  */
142 struct timeutil_sync_state {
143 	/** Pointer to reference and local rate information. */
144 	const struct timeutil_sync_config *cfg;
145 
146 	/** The base instant in both time scales. */
147 	struct timeutil_sync_instant base;
148 
149 	/** The most recent instant in both time scales.
150 	 *
151 	 * This is captured here to provide data for skew calculation.
152 	 */
153 	struct timeutil_sync_instant latest;
154 
155 	/** The scale factor used to correct for clock skew.
156 	 *
157 	 * The nominal rate for the local counter is assumed to be
158 	 * inaccurate but stable, i.e. it will generally be some
159 	 * parts-per-million faster or slower than specified.
160 	 *
161 	 * A duration in observed local clock ticks must be multiplied by
162 	 * this value to produce a duration in ticks of a clock operating at
163 	 * the nominal local rate.
164 	 *
165 	 * A zero value indicates that the skew has not been initialized.
166 	 * If the value is zero when #base is initialized the skew will be
167 	 * set to 1.  Otherwise the skew is assigned through
168 	 * timeutil_sync_state_set_skew().
169 	 */
170 	float skew;
171 };
172 
173 /**
174  * @brief Record a new instant in the time synchronization state.
175  *
176  * Note that this updates only the latest persisted instant.  The skew
177  * is not adjusted automatically.
178  *
179  * @param tsp pointer to a timeutil_sync_state object.
180  *
181  * @param inst the new instant to be recorded.  This becomes the base
182  * instant if there is no base instant, otherwise the value must be
183  * strictly after the base instant in both the reference and local
184  * time scales.
185  *
186  * @retval 0 if installation succeeded in providing a new base
187  * @retval 1 if installation provided a new latest instant
188  * @retval -EINVAL if the new instant is not compatible with the base instant
189  */
190 int timeutil_sync_state_update(struct timeutil_sync_state *tsp,
191 			       const struct timeutil_sync_instant *inst);
192 
193 /**
194  * @brief Update the state with a new skew and possibly base value.
195  *
196  * Set the skew from a value retrieved from persistent storage, or
197  * calculated based on recent skew estimations including from
198  * timeutil_sync_estimate_skew().
199  *
200  * Optionally update the base timestamp.  If the base is replaced the
201  * latest instant will be cleared until timeutil_sync_state_update() is
202  * invoked.
203  *
204  * @param tsp pointer to a time synchronization state.
205  *
206  * @param skew the skew to be used.  The value must be positive and
207  * shouldn't be too far away from 1.
208  *
209  * @param base optional new base to be set.  If provided this becomes
210  * the base timestamp that will be used along with skew to convert
211  * between reference and local timescale instants.  Setting the base
212  * clears the captured latest value.
213  *
214  * @return 0 if skew was updated
215  * @return -EINVAL if skew was not valid
216  */
217 int timeutil_sync_state_set_skew(struct timeutil_sync_state *tsp, float skew,
218 				 const struct timeutil_sync_instant *base);
219 
220 /**
221  * @brief Estimate the skew based on current state.
222  *
223  * Using the base and latest syncpoints from the state determine the
224  * skew of the local clock relative to the reference clock.  See
225  * timeutil_sync_state::skew.
226  *
227  * @param tsp pointer to a time synchronization state.  The base and latest
228  * syncpoints must be present and the latest syncpoint must be after
229  * the base point in the local time scale.
230  *
231  * @return the estimated skew, or zero if skew could not be estimated.
232  */
233 float timeutil_sync_estimate_skew(const struct timeutil_sync_state *tsp);
234 
235 /**
236  * @brief Interpolate a reference timescale instant from a local
237  * instant.
238  *
239  * @param tsp pointer to a time synchronization state.  This must have a base
240  * and a skew installed.
241  *
242  * @param local an instant measured in the local timescale.  This may
243  * be before or after the base instant.
244  *
245  * @param refp where the corresponding instant in the reference
246  * timescale should be stored.  A negative interpolated reference time
247  * produces an error.  If interpolation fails the referenced object is
248  * not modified.
249  *
250  * @retval 0 if interpolated using a skew of 1
251  * @retval 1 if interpolated using a skew not equal to 1
252  * @retval -EINVAL
253  *   * the times synchronization state is not adequately initialized
254  *   * @p refp is null
255  * @retval -ERANGE the interpolated reference time would be negative
256  */
257 int timeutil_sync_ref_from_local(const struct timeutil_sync_state *tsp,
258 				 uint64_t local, uint64_t *refp);
259 
260 /**
261  * @brief Interpolate a local timescale instant from a reference
262  * instant.
263  *
264  * @param tsp pointer to a time synchronization state.  This must have a base
265  * and a skew installed.
266  *
267  * @param ref an instant measured in the reference timescale.  This
268  * may be before or after the base instant.
269  *
270  * @param localp where the corresponding instant in the local
271  * timescale should be stored.  An interpolated value before local
272  * time 0 is provided without error.  If interpolation fails the
273  * referenced object is not modified.
274  *
275  * @retval 0 if successful with a skew of 1
276  * @retval 1 if successful with a skew not equal to 1
277  * @retval -EINVAL
278  *   * the time synchronization state is not adequately initialized
279  *   * @p refp is null
280  */
281 int timeutil_sync_local_from_ref(const struct timeutil_sync_state *tsp,
282 				 uint64_t ref, int64_t *localp);
283 
284 /**
285  * @brief Convert from a skew to an error in parts-per-billion.
286  *
287  * A skew of 1.0 has zero error.  A skew less than 1 has a positive
288  * error (clock is faster than it should be).  A skew greater than one
289  * has a negative error (clock is slower than it should be).
290  *
291  * Note that due to the limited precision of @c float compared with @c
292  * double the smallest error that can be represented is about 120 ppb.
293  * A "precise" time source may have error on the order of 2000 ppb.
294  *
295  * A skew greater than 3.14748 may underflow the 32-bit
296  * representation; this represents a clock running at less than 1/3
297  * its nominal rate.
298  *
299  * @return skew error represented as parts-per-billion, or INT32_MIN
300  * if the skew cannot be represented in the return type.
301  */
302 int32_t timeutil_sync_skew_to_ppb(float skew);
303 
304 #ifdef __cplusplus
305 }
306 #endif
307 
308 /**
309  * @}
310  */
311 
312 #endif /* ZEPHYR_INCLUDE_SYS_TIMEUTIL_H_ */
313