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