1 /*
2 * Copyright 2020 Peter Bigot Consulting
3 *
4 * SPDX-License-Identifier: Apache-2.0
5 */
6
7 /* Tests for the time_sync data structures */
8
9 #include <string.h>
10 #include <zephyr/ztest.h>
11 #include "timeutil_test.h"
12
13 static const struct timeutil_sync_config cfg1 = {
14 .ref_Hz = USEC_PER_SEC,
15 .local_Hz = 32768,
16 };
17
18 static const struct timeutil_sync_config cfg2 = {
19 .ref_Hz = NSEC_PER_SEC,
20 .local_Hz = 100,
21 };
22
scale_ref(uint32_t factor,const struct timeutil_sync_config * cfg)23 static inline uint64_t scale_ref(uint32_t factor,
24 const struct timeutil_sync_config *cfg)
25 {
26 return (uint64_t)factor * (uint64_t)cfg->ref_Hz;
27 }
28
scale_local(uint32_t factor,const struct timeutil_sync_config * cfg)29 static inline uint64_t scale_local(uint32_t factor,
30 const struct timeutil_sync_config *cfg)
31 {
32 return (uint64_t)factor * (uint64_t)cfg->local_Hz;
33 }
34
scale_local_signed(int32_t factor,const struct timeutil_sync_config * cfg)35 static inline int64_t scale_local_signed(int32_t factor,
36 const struct timeutil_sync_config *cfg)
37 {
38 return (int64_t)factor * (int64_t)cfg->local_Hz;
39 }
40
41
test_state_update(void)42 static void test_state_update(void)
43 {
44 struct timeutil_sync_instant si = { 0 };
45 struct timeutil_sync_state ss = { 0 };
46 int rv = timeutil_sync_state_update(&ss, &si);
47
48 zassert_equal(rv, -EINVAL,
49 "invalid init got: %d", rv);
50 zassert_equal(ss.base.ref, 0,
51 "unexpected base ref");
52 zassert_equal(ss.skew, 0,
53 "unexpected skew");
54
55 si.ref = 1;
56 rv = timeutil_sync_state_update(&ss, &si);
57 zassert_equal(rv, 0,
58 "valid first init got: %d", rv);
59 zassert_equal(ss.base.ref, 1,
60 "base not updated");
61 zassert_equal(ss.latest.ref, 0,
62 "unexpected latest ref");
63 zassert_equal(ss.skew, 1.0,
64 "unexpected skew");
65
66 rv = timeutil_sync_state_update(&ss, &si);
67 zassert_equal(rv, -EINVAL,
68 "non-increasing ref got: %d", rv);
69 zassert_equal(ss.base.ref, 1,
70 "unexpected base ref");
71 zassert_equal(ss.base.local, 0,
72 "unexpected base local");
73 zassert_equal(ss.latest.ref, 0,
74 "unexpected latest ref");
75
76 si.ref += 1;
77 rv = timeutil_sync_state_update(&ss, &si);
78 zassert_equal(rv, -EINVAL,
79 "non-increasing local got: %d", rv);
80 zassert_equal(ss.latest.ref, 0,
81 "unexpected latest ref");
82
83 si.local += 20;
84 rv = timeutil_sync_state_update(&ss, &si);
85 zassert_equal(rv, 1,
86 "increasing got: %d", rv);
87 zassert_equal(ss.base.ref, 1,
88 "unexpected base ref");
89 zassert_equal(ss.base.local, 0,
90 "unexpected base local");
91 zassert_equal(ss.latest.ref, si.ref,
92 "unexpected latest ref");
93 zassert_equal(ss.latest.local, si.local,
94 "unexpected latest local");
95 }
96
test_state_set_skew(void)97 static void test_state_set_skew(void)
98 {
99 struct timeutil_sync_instant si = {
100 .ref = 1,
101 };
102 struct timeutil_sync_state ss = {
103 .cfg = &cfg1,
104 };
105 float skew = 0.99;
106 int rv = timeutil_sync_state_update(&ss, &si);
107
108 zassert_equal(rv, 0,
109 "valid first init got: %d", rv);
110 zassert_equal(ss.skew, 1.0,
111 "unexpected skew");
112
113 rv = timeutil_sync_state_set_skew(&ss, -1.0, NULL);
114 zassert_equal(rv, -EINVAL,
115 "negative skew set got: %d", rv);
116 zassert_equal(ss.skew, 1.0,
117 "unexpected skew");
118
119 rv = timeutil_sync_state_set_skew(&ss, 0.0, NULL);
120 zassert_equal(rv, -EINVAL,
121 "zero skew set got: %d", rv);
122 zassert_equal(ss.skew, 1.0,
123 "unexpected skew");
124
125 rv = timeutil_sync_state_set_skew(&ss, skew, NULL);
126 zassert_equal(rv, 0,
127 "valid skew set got: %d", rv);
128 zassert_equal(ss.skew, skew,
129 "unexpected skew");
130 zassert_equal(ss.base.ref, si.ref,
131 "unexpected base ref");
132 zassert_equal(ss.base.local, si.local,
133 "unexpected base ref");
134
135 skew = 1.01;
136 si.ref += 5;
137 si.local += 3;
138
139 rv = timeutil_sync_state_set_skew(&ss, skew, &si);
140 zassert_equal(rv, 0,
141 "valid skew set got: %d", rv);
142 zassert_equal(ss.skew, skew,
143 "unexpected skew");
144 zassert_equal(ss.base.ref, si.ref,
145 "unexpected base ref");
146 zassert_equal(ss.base.local, si.local,
147 "unexpected base ref");
148 zassert_equal(ss.latest.ref, 0,
149 "uncleared latest ref");
150 zassert_equal(ss.latest.local, 0,
151 "uncleared latest local");
152 }
153
test_estimate_skew(void)154 static void test_estimate_skew(void)
155 {
156 struct timeutil_sync_state ss = {
157 .cfg = &cfg1,
158 };
159 struct timeutil_sync_instant si0 = {
160 .ref = cfg1.ref_Hz,
161 };
162 struct timeutil_sync_instant si1 = {
163 .ref = si0.ref + cfg1.ref_Hz,
164 .local = si0.local + cfg1.local_Hz,
165 };
166 float skew = 0.0;
167
168 skew = timeutil_sync_estimate_skew(&ss);
169 zassert_equal(skew, 0,
170 "unexpected uninit skew: %f", skew);
171
172 int rv = timeutil_sync_state_update(&ss, &si0);
173
174 zassert_equal(rv, 0,
175 "valid init got: %d", rv);
176
177 skew = timeutil_sync_estimate_skew(&ss);
178 zassert_equal(skew, 0,
179 "unexpected base-only skew: %f", skew);
180
181 rv = timeutil_sync_state_update(&ss, &si1);
182 zassert_equal(rv, 1,
183 "valid update got: %d", rv);
184
185 zassert_equal(ss.base.ref, si0.ref,
186 "unexpected base ref");
187 zassert_equal(ss.base.local, si0.local,
188 "unexpected base local");
189 zassert_equal(ss.latest.ref, si1.ref,
190 "unexpected latest ref");
191 zassert_equal(ss.latest.local, si1.local,
192 "unexpected latest local");
193
194 skew = timeutil_sync_estimate_skew(&ss);
195 zassert_equal(skew, 1.0,
196 "unexpected linear skew: %f", skew);
197
198 /* Local advanced half as far as it should: scale by 2 to
199 * correct.
200 */
201 ss.latest.local = scale_local(1, ss.cfg) / 2;
202 skew = timeutil_sync_estimate_skew(&ss);
203 zassert_equal(skew, 2.0,
204 "unexpected half skew: %f", skew);
205
206 /* Local advanced twice as far as it should: scale by 1/2 to
207 * correct.
208 */
209 ss.latest.local = scale_local(2, ss.cfg);
210 skew = timeutil_sync_estimate_skew(&ss);
211 zassert_equal(skew, 0.5,
212 "unexpected double skew: %f", skew);
213 }
214
tref_from_local(const char * tag,const struct timeutil_sync_config * cfg)215 static void tref_from_local(const char *tag,
216 const struct timeutil_sync_config *cfg)
217 {
218 struct timeutil_sync_state ss = {
219 .cfg = cfg,
220 };
221 struct timeutil_sync_instant si0 = {
222 /* Absolute local 0 is 5 s ref */
223 .ref = scale_ref(10, cfg),
224 .local = scale_local(5, cfg),
225 };
226 uint64_t ref = 0;
227 int rv = timeutil_sync_ref_from_local(&ss, 0, &ref);
228 int skew_factor;
229
230 zassert_equal(rv, -EINVAL,
231 "%s: unexpected uninit convert: %d", tag, rv);
232
233 rv = timeutil_sync_state_update(&ss, &si0);
234 zassert_equal(rv, 0,
235 "%s: unexpected init: %d", tag, rv);
236 zassert_equal(ss.skew, 1.0,
237 "%s: unexpected skew", tag);
238
239 rv = timeutil_sync_ref_from_local(&ss, ss.base.local, NULL);
240 zassert_equal(rv, -EINVAL,
241 "%s: unexpected missing dest: %d", tag, rv);
242
243 rv = timeutil_sync_ref_from_local(&ss, ss.base.local, &ref);
244 zassert_equal(rv, 0,
245 "%s: unexpected fail %d", tag, rv);
246 zassert_equal(ref, ss.base.ref,
247 "%s: unexpected base convert", tag);
248
249 rv = timeutil_sync_ref_from_local(&ss, 0, &ref);
250 zassert_equal(rv, 0,
251 "%s: unexpected local=0 fail %d", tag, rv);
252 zassert_equal(ref, scale_ref(5, cfg),
253 "%s: unexpected local=0 ref", tag);
254
255 rv = timeutil_sync_ref_from_local(&ss, ss.base.local, &ref);
256 zassert_equal(rv, 0,
257 "%s: unexpected local=base fail, %d", tag, rv);
258 zassert_equal(ref, ss.base.ref,
259 "%s: unexpected local=base ref", tag);
260
261 rv = timeutil_sync_ref_from_local(&ss, ss.base.local
262 + scale_local(2, cfg), &ref);
263 zassert_equal(rv, 0,
264 "%s: unexpected local=base+2s fail %d", tag, rv);
265 zassert_equal(ref, ss.base.ref + scale_ref(2, cfg),
266 "%s: unexpected local=base+2s ref", tag);
267
268 rv = timeutil_sync_ref_from_local(&ss, (int64_t)ss.base.local
269 - scale_local(12, cfg), &ref);
270 zassert_equal(rv, -ERANGE,
271 "%s: unexpected local=base-12s res %u", tag, rv);
272
273 /* Skew of 0.5 means local runs at double speed */
274 rv = timeutil_sync_state_set_skew(&ss, 0.5, NULL);
275 zassert_equal(rv, 0,
276 "%s: failed set skew", tag);
277
278 /* Whether the conversion takes skew into account is controlled
279 * by the Kconfig option.
280 */
281 skew_factor = IS_ENABLED(CONFIG_TIMEUTIL_APPLY_SKEW) ? 2 : 1;
282
283 /* Local at double speed corresponds to half advance in ref */
284 rv = timeutil_sync_ref_from_local(&ss, ss.base.local
285 + scale_local(skew_factor, cfg),
286 &ref);
287 zassert_equal(rv, 1,
288 "%s: unexpected skew adj fail", tag);
289 zassert_equal(ref, ss.base.ref + cfg->ref_Hz,
290 "%s: unexpected skew adj convert", tag);
291 }
292
test_ref_from_local(void)293 static void test_ref_from_local(void)
294 {
295 tref_from_local("std", &cfg1);
296 tref_from_local("ext", &cfg2);
297 }
298
tlocal_from_ref(const char * tag,const struct timeutil_sync_config * cfg)299 static void tlocal_from_ref(const char *tag,
300 const struct timeutil_sync_config *cfg)
301 {
302 struct timeutil_sync_state ss = {
303 .cfg = cfg,
304 };
305 struct timeutil_sync_instant si0 = {
306 /* Absolute local 0 is 5 s ref */
307 .ref = scale_ref(10, cfg),
308 .local = scale_local(5, cfg),
309 };
310 int64_t local = 0;
311 int rv = timeutil_sync_local_from_ref(&ss, 0, &local);
312 int skew_factor;
313
314 zassert_equal(rv, -EINVAL,
315 "%s: unexpected uninit convert: %d", tag, rv);
316
317 rv = timeutil_sync_state_update(&ss, &si0);
318 zassert_equal(rv, 0,
319 "%s: unexpected init: %d", tag, rv);
320 zassert_equal(ss.skew, 1.0,
321 "%s: unexpected skew", tag);
322
323 rv = timeutil_sync_local_from_ref(&ss, ss.base.ref, NULL);
324 zassert_equal(rv, -EINVAL,
325 "%s: unexpected missing dest %d", tag, rv);
326
327 rv = timeutil_sync_local_from_ref(&ss, ss.base.ref, &local);
328 zassert_equal(rv, 0,
329 "%s: unexpected fail %d", tag, rv);
330 zassert_equal(local, ss.base.local,
331 "%s: unexpected base convert", tag);
332
333 rv = timeutil_sync_local_from_ref(&ss, ss.base.ref
334 + scale_ref(2, cfg), &local);
335 zassert_equal(rv, 0,
336 "%s: unexpected base+2s fail", tag);
337 zassert_equal(local, ss.base.local + scale_local(2, cfg),
338 "%s: unexpected base+2s convert", tag);
339
340 rv = timeutil_sync_local_from_ref(&ss, ss.base.ref
341 - scale_ref(7, cfg), &local);
342 zassert_equal(rv, 0,
343 "%s: unexpected base-7s fail", tag);
344 zassert_equal(local, scale_local_signed(-2, cfg),
345 "%s: unexpected base-7s convert", tag);
346
347 /* Skew of 0.5 means local runs at double speed */
348 rv = timeutil_sync_state_set_skew(&ss, 0.5, NULL);
349 zassert_equal(rv, 0,
350 "%s: failed set skew", tag);
351
352 /* Whether the conversion takes skew into account is controlled
353 * by the Kconfig option.
354 */
355 skew_factor = IS_ENABLED(CONFIG_TIMEUTIL_APPLY_SKEW) ? 2 : 1;
356
357 /* Local at double speed corresponds to half advance in ref */
358 rv = timeutil_sync_local_from_ref(&ss, ss.base.ref
359 + scale_ref(1, cfg) / skew_factor,
360 &local);
361 zassert_equal(rv, 1,
362 "%s: unexpected skew adj fail", tag);
363 zassert_equal(local, ss.base.local + scale_local(1, cfg),
364 "%s: unexpected skew adj convert", tag);
365 }
366
test_local_from_ref(void)367 static void test_local_from_ref(void)
368 {
369 tlocal_from_ref("std", &cfg1);
370 tlocal_from_ref("ext", &cfg2);
371 }
372
test_large_linearity(void)373 static void test_large_linearity(void)
374 {
375 uint64_t inputs[] = {
376 1000ULL,
377 3999999999ULL,
378 4000000000ULL,
379 4000000001ULL,
380 UINT64_MAX / 10000000
381 };
382 uint64_t ref_out;
383 int64_t loc_out;
384 int rv;
385
386 const struct timeutil_sync_config unity = {
387 .ref_Hz = 1000,
388 .local_Hz = 1000,
389 };
390 struct timeutil_sync_instant inst = {
391 .ref = 200,
392 .local = 100
393 };
394 struct timeutil_sync_state ss = {
395 .cfg = &unity,
396 };
397 uint64_t offset = inst.ref - inst.local;
398
399 timeutil_sync_state_set_skew(&ss, 1.0f, &inst);
400
401 for (int i = 0; i < ARRAY_SIZE(inputs); i++) {
402 rv = timeutil_sync_ref_from_local(&ss, inputs[i], &ref_out);
403 zassert_equal(rv, 0, "Unexpected conversion fail");
404 zassert_equal(ref_out, inputs[i] + offset,
405 "Large unity local->ref conversion fail");
406
407 rv = timeutil_sync_local_from_ref(&ss, inputs[i], &loc_out);
408 zassert_equal(rv, 0, "Unexpected conversion fail");
409 zassert_equal(loc_out, inputs[i] - offset,
410 "Large unity ref->local conversion fail");
411 }
412 }
413
test_skew_to_ppb(void)414 static void test_skew_to_ppb(void)
415 {
416 float skew = 1.0;
417 int32_t ppb = timeutil_sync_skew_to_ppb(skew);
418
419 zassert_equal(ppb, 0,
420 "unexpected perfect: %d", ppb);
421
422 skew = 0.999976;
423 ppb = timeutil_sync_skew_to_ppb(skew);
424 zassert_equal(ppb, 24020,
425 "unexpected fast: %d", ppb);
426
427 skew = 1.000022;
428 ppb = timeutil_sync_skew_to_ppb(skew);
429 zassert_equal(ppb, -22053,
430 "unexpected slow: %d", ppb);
431
432 skew = 3.147483587;
433 ppb = timeutil_sync_skew_to_ppb(skew);
434 zassert_equal(ppb, -2147483587,
435 "unexpected near limit: %.10g %d", skew, ppb);
436 skew = 3.147483826;
437 ppb = timeutil_sync_skew_to_ppb(skew);
438 zassert_equal(ppb, INT32_MIN,
439 "unexpected above limit: %.10g %d", skew, ppb);
440 }
441
ZTEST(timeutil_api,test_sync)442 ZTEST(timeutil_api, test_sync)
443 {
444 test_state_update();
445 test_state_set_skew();
446 test_estimate_skew();
447 test_ref_from_local();
448 test_local_from_ref();
449 test_large_linearity();
450 test_skew_to_ppb();
451 }
452