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