439 lines
12 KiB
C
439 lines
12 KiB
C
/*
|
|
* Copyright 2020 Peter Bigot Consulting
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
/* Tests for the time_sync data structures */
|
|
|
|
#include <string.h>
|
|
#include <zephyr/ztest.h>
|
|
#include "timeutil_test.h"
|
|
|
|
static const struct timeutil_sync_config cfg1 = {
|
|
.ref_Hz = USEC_PER_SEC,
|
|
.local_Hz = 32768,
|
|
};
|
|
|
|
static const struct timeutil_sync_config cfg2 = {
|
|
.ref_Hz = NSEC_PER_SEC,
|
|
.local_Hz = 100,
|
|
};
|
|
|
|
static inline uint64_t scale_ref(uint32_t factor,
|
|
const struct timeutil_sync_config *cfg)
|
|
{
|
|
return (uint64_t)factor * (uint64_t)cfg->ref_Hz;
|
|
}
|
|
|
|
static inline uint64_t scale_local(uint32_t factor,
|
|
const struct timeutil_sync_config *cfg)
|
|
{
|
|
return (uint64_t)factor * (uint64_t)cfg->local_Hz;
|
|
}
|
|
|
|
static inline int64_t scale_local_signed(int32_t factor,
|
|
const struct timeutil_sync_config *cfg)
|
|
{
|
|
return (int64_t)factor * (int64_t)cfg->local_Hz;
|
|
}
|
|
|
|
|
|
static void test_state_update(void)
|
|
{
|
|
struct timeutil_sync_instant si = { 0 };
|
|
struct timeutil_sync_state ss = { 0 };
|
|
int rv = timeutil_sync_state_update(&ss, &si);
|
|
|
|
zassert_equal(rv, -EINVAL,
|
|
"invalid init got: %d", rv);
|
|
zassert_equal(ss.base.ref, 0,
|
|
"unexpected base ref");
|
|
zassert_equal(ss.skew, 0,
|
|
"unexpected skew");
|
|
|
|
si.ref = 1;
|
|
rv = timeutil_sync_state_update(&ss, &si);
|
|
zassert_equal(rv, 0,
|
|
"valid first init got: %d", rv);
|
|
zassert_equal(ss.base.ref, 1,
|
|
"base not updated");
|
|
zassert_equal(ss.latest.ref, 0,
|
|
"unexpected latest ref");
|
|
zassert_equal(ss.skew, 1.0,
|
|
"unexpected skew");
|
|
|
|
rv = timeutil_sync_state_update(&ss, &si);
|
|
zassert_equal(rv, -EINVAL,
|
|
"non-increasing ref got: %d", rv);
|
|
zassert_equal(ss.base.ref, 1,
|
|
"unexpected base ref");
|
|
zassert_equal(ss.base.local, 0,
|
|
"unexpected base local");
|
|
zassert_equal(ss.latest.ref, 0,
|
|
"unexpected latest ref");
|
|
|
|
si.ref += 1;
|
|
rv = timeutil_sync_state_update(&ss, &si);
|
|
zassert_equal(rv, -EINVAL,
|
|
"non-increasing local got: %d", rv);
|
|
zassert_equal(ss.latest.ref, 0,
|
|
"unexpected latest ref");
|
|
|
|
si.local += 20;
|
|
rv = timeutil_sync_state_update(&ss, &si);
|
|
zassert_equal(rv, 1,
|
|
"increasing got: %d", rv);
|
|
zassert_equal(ss.base.ref, 1,
|
|
"unexpected base ref");
|
|
zassert_equal(ss.base.local, 0,
|
|
"unexpected base local");
|
|
zassert_equal(ss.latest.ref, si.ref,
|
|
"unexpected latest ref");
|
|
zassert_equal(ss.latest.local, si.local,
|
|
"unexpected latest local");
|
|
}
|
|
|
|
static void test_state_set_skew(void)
|
|
{
|
|
struct timeutil_sync_instant si = {
|
|
.ref = 1,
|
|
};
|
|
struct timeutil_sync_state ss = {
|
|
.cfg = &cfg1,
|
|
};
|
|
float skew = 0.99;
|
|
int rv = timeutil_sync_state_update(&ss, &si);
|
|
|
|
zassert_equal(rv, 0,
|
|
"valid first init got: %d", rv);
|
|
zassert_equal(ss.skew, 1.0,
|
|
"unexpected skew");
|
|
|
|
rv = timeutil_sync_state_set_skew(&ss, -1.0, NULL);
|
|
zassert_equal(rv, -EINVAL,
|
|
"negative skew set got: %d", rv);
|
|
zassert_equal(ss.skew, 1.0,
|
|
"unexpected skew");
|
|
|
|
rv = timeutil_sync_state_set_skew(&ss, 0.0, NULL);
|
|
zassert_equal(rv, -EINVAL,
|
|
"zero skew set got: %d", rv);
|
|
zassert_equal(ss.skew, 1.0,
|
|
"unexpected skew");
|
|
|
|
rv = timeutil_sync_state_set_skew(&ss, skew, NULL);
|
|
zassert_equal(rv, 0,
|
|
"valid skew set got: %d", rv);
|
|
zassert_equal(ss.skew, skew,
|
|
"unexpected skew");
|
|
zassert_equal(ss.base.ref, si.ref,
|
|
"unexpected base ref");
|
|
zassert_equal(ss.base.local, si.local,
|
|
"unexpected base ref");
|
|
|
|
skew = 1.01;
|
|
si.ref += 5;
|
|
si.local += 3;
|
|
|
|
rv = timeutil_sync_state_set_skew(&ss, skew, &si);
|
|
zassert_equal(rv, 0,
|
|
"valid skew set got: %d", rv);
|
|
zassert_equal(ss.skew, skew,
|
|
"unexpected skew");
|
|
zassert_equal(ss.base.ref, si.ref,
|
|
"unexpected base ref");
|
|
zassert_equal(ss.base.local, si.local,
|
|
"unexpected base ref");
|
|
zassert_equal(ss.latest.ref, 0,
|
|
"uncleared latest ref");
|
|
zassert_equal(ss.latest.local, 0,
|
|
"uncleared latest local");
|
|
}
|
|
|
|
static void test_estimate_skew(void)
|
|
{
|
|
struct timeutil_sync_state ss = {
|
|
.cfg = &cfg1,
|
|
};
|
|
struct timeutil_sync_instant si0 = {
|
|
.ref = cfg1.ref_Hz,
|
|
};
|
|
struct timeutil_sync_instant si1 = {
|
|
.ref = si0.ref + cfg1.ref_Hz,
|
|
.local = si0.local + cfg1.local_Hz,
|
|
};
|
|
float skew = 0.0;
|
|
|
|
skew = timeutil_sync_estimate_skew(&ss);
|
|
zassert_equal(skew, 0,
|
|
"unexpected uninit skew: %f", skew);
|
|
|
|
int rv = timeutil_sync_state_update(&ss, &si0);
|
|
|
|
zassert_equal(rv, 0,
|
|
"valid init got: %d", rv);
|
|
|
|
skew = timeutil_sync_estimate_skew(&ss);
|
|
zassert_equal(skew, 0,
|
|
"unexpected base-only skew: %f", skew);
|
|
|
|
rv = timeutil_sync_state_update(&ss, &si1);
|
|
zassert_equal(rv, 1,
|
|
"valid update got: %d", rv);
|
|
|
|
zassert_equal(ss.base.ref, si0.ref,
|
|
"unexpected base ref");
|
|
zassert_equal(ss.base.local, si0.local,
|
|
"unexpected base local");
|
|
zassert_equal(ss.latest.ref, si1.ref,
|
|
"unexpected latest ref");
|
|
zassert_equal(ss.latest.local, si1.local,
|
|
"unexpected latest local");
|
|
|
|
skew = timeutil_sync_estimate_skew(&ss);
|
|
zassert_equal(skew, 1.0,
|
|
"unexpected linear skew: %f", skew);
|
|
|
|
/* Local advanced half as far as it should: scale by 2 to
|
|
* correct.
|
|
*/
|
|
ss.latest.local = scale_local(1, ss.cfg) / 2;
|
|
skew = timeutil_sync_estimate_skew(&ss);
|
|
zassert_equal(skew, 2.0,
|
|
"unexpected half skew: %f", skew);
|
|
|
|
/* Local advanced twice as far as it should: scale by 1/2 to
|
|
* correct.
|
|
*/
|
|
ss.latest.local = scale_local(2, ss.cfg);
|
|
skew = timeutil_sync_estimate_skew(&ss);
|
|
zassert_equal(skew, 0.5,
|
|
"unexpected double skew: %f", skew);
|
|
}
|
|
|
|
static void tref_from_local(const char *tag,
|
|
const struct timeutil_sync_config *cfg)
|
|
{
|
|
struct timeutil_sync_state ss = {
|
|
.cfg = cfg,
|
|
};
|
|
struct timeutil_sync_instant si0 = {
|
|
/* Absolute local 0 is 5 s ref */
|
|
.ref = scale_ref(10, cfg),
|
|
.local = scale_local(5, cfg),
|
|
};
|
|
uint64_t ref = 0;
|
|
int rv = timeutil_sync_ref_from_local(&ss, 0, &ref);
|
|
|
|
zassert_equal(rv, -EINVAL,
|
|
"%s: unexpected uninit convert: %d", tag, rv);
|
|
|
|
rv = timeutil_sync_state_update(&ss, &si0);
|
|
zassert_equal(rv, 0,
|
|
"%s: unexpected init: %d", tag, rv);
|
|
zassert_equal(ss.skew, 1.0,
|
|
"%s: unexpected skew");
|
|
|
|
rv = timeutil_sync_ref_from_local(&ss, ss.base.local, NULL);
|
|
zassert_equal(rv, -EINVAL,
|
|
"%s: unexpected missing dest: %d", tag, rv);
|
|
|
|
rv = timeutil_sync_ref_from_local(&ss, ss.base.local, &ref);
|
|
zassert_equal(rv, 0,
|
|
"%s: unexpected fail", tag, rv);
|
|
zassert_equal(ref, ss.base.ref,
|
|
"%s: unexpected base convert", tag);
|
|
|
|
rv = timeutil_sync_ref_from_local(&ss, 0, &ref);
|
|
zassert_equal(rv, 0,
|
|
"%s: unexpected local=0 fail", tag, rv);
|
|
zassert_equal(ref, scale_ref(5, cfg),
|
|
"%s: unexpected local=0 ref", tag);
|
|
|
|
rv = timeutil_sync_ref_from_local(&ss, ss.base.local, &ref);
|
|
zassert_equal(rv, 0,
|
|
"%s: unexpected local=base fail", tag, rv);
|
|
zassert_equal(ref, ss.base.ref,
|
|
"%s: unexpected local=base ref", tag);
|
|
|
|
rv = timeutil_sync_ref_from_local(&ss, ss.base.local
|
|
+ scale_local(2, cfg), &ref);
|
|
zassert_equal(rv, 0,
|
|
"%s: unexpected local=base+2s fail %d", tag, rv);
|
|
zassert_equal(ref, ss.base.ref + scale_ref(2, cfg),
|
|
"%s: unexpected local=base+2s ref", tag);
|
|
|
|
rv = timeutil_sync_ref_from_local(&ss, (int64_t)ss.base.local
|
|
- scale_local(12, cfg), &ref);
|
|
zassert_equal(rv, -ERANGE,
|
|
"%s: unexpected local=base-12s res %u", tag, rv);
|
|
|
|
/* Skew of 0.5 means local runs at double speed */
|
|
rv = timeutil_sync_state_set_skew(&ss, 0.5, NULL);
|
|
zassert_equal(rv, 0,
|
|
"%s: failed set skew", tag);
|
|
|
|
/* Local at double speed corresponds to half advance in ref */
|
|
rv = timeutil_sync_ref_from_local(&ss, ss.base.local
|
|
+ scale_local(2, cfg), &ref);
|
|
zassert_equal(rv, 1,
|
|
"%s: unexpected skew adj fail", tag);
|
|
zassert_equal(ref, ss.base.ref + cfg->ref_Hz,
|
|
"%s: unexpected skew adj convert", tag);
|
|
}
|
|
|
|
static void test_ref_from_local(void)
|
|
{
|
|
tref_from_local("std", &cfg1);
|
|
tref_from_local("ext", &cfg2);
|
|
}
|
|
|
|
static void tlocal_from_ref(const char *tag,
|
|
const struct timeutil_sync_config *cfg)
|
|
{
|
|
struct timeutil_sync_state ss = {
|
|
.cfg = cfg,
|
|
};
|
|
struct timeutil_sync_instant si0 = {
|
|
/* Absolute local 0 is 5 s ref */
|
|
.ref = scale_ref(10, cfg),
|
|
.local = scale_local(5, cfg),
|
|
};
|
|
int64_t local = 0;
|
|
int rv = timeutil_sync_local_from_ref(&ss, 0, &local);
|
|
|
|
zassert_equal(rv, -EINVAL,
|
|
"%s: unexpected uninit convert: %d", tag, rv);
|
|
|
|
rv = timeutil_sync_state_update(&ss, &si0);
|
|
zassert_equal(rv, 0,
|
|
"%s: unexpected init: %d", tag, rv);
|
|
zassert_equal(ss.skew, 1.0,
|
|
"%s: unexpected skew", tag);
|
|
|
|
rv = timeutil_sync_local_from_ref(&ss, ss.base.ref, NULL);
|
|
zassert_equal(rv, -EINVAL,
|
|
"%s: unexpected missing dest", tag, rv);
|
|
|
|
rv = timeutil_sync_local_from_ref(&ss, ss.base.ref, &local);
|
|
zassert_equal(rv, 0,
|
|
"%s: unexpected fail", tag, rv);
|
|
zassert_equal(local, ss.base.local,
|
|
"%s: unexpected base convert", tag);
|
|
|
|
rv = timeutil_sync_local_from_ref(&ss, ss.base.ref
|
|
+ scale_ref(2, cfg), &local);
|
|
zassert_equal(rv, 0,
|
|
"%s: unexpected base+2s fail", tag);
|
|
zassert_equal(local, ss.base.local + scale_local(2, cfg),
|
|
"%s: unexpected base+2s convert", tag);
|
|
|
|
rv = timeutil_sync_local_from_ref(&ss, ss.base.ref
|
|
- scale_ref(7, cfg), &local);
|
|
zassert_equal(rv, 0,
|
|
"%s: unexpected base-7s fail", tag);
|
|
zassert_equal(local, scale_local_signed(-2, cfg),
|
|
"%s: unexpected base-7s convert", tag);
|
|
|
|
|
|
/* Skew of 0.5 means local runs at double speed */
|
|
rv = timeutil_sync_state_set_skew(&ss, 0.5, NULL);
|
|
zassert_equal(rv, 0,
|
|
"%s: failed set skew", tag);
|
|
|
|
/* Local at double speed corresponds to half advance in ref */
|
|
rv = timeutil_sync_local_from_ref(&ss, ss.base.ref
|
|
+ scale_ref(1, cfg) / 2, &local);
|
|
zassert_equal(rv, 1,
|
|
"%s: unexpected skew adj fail", tag);
|
|
zassert_equal(local, ss.base.local + scale_local(1, cfg),
|
|
"%s: unexpected skew adj convert", tag);
|
|
}
|
|
|
|
static void test_local_from_ref(void)
|
|
{
|
|
tlocal_from_ref("std", &cfg1);
|
|
tlocal_from_ref("ext", &cfg2);
|
|
}
|
|
|
|
static void test_large_linearity(void)
|
|
{
|
|
uint64_t inputs[] = {
|
|
1000ULL,
|
|
3999999999ULL,
|
|
4000000000ULL,
|
|
4000000001ULL,
|
|
UINT64_MAX / 10000000
|
|
};
|
|
uint64_t ref_out;
|
|
int64_t loc_out;
|
|
int rv;
|
|
|
|
const struct timeutil_sync_config unity = {
|
|
.ref_Hz = 1000,
|
|
.local_Hz = 1000,
|
|
};
|
|
struct timeutil_sync_instant inst = {
|
|
.ref = 200,
|
|
.local = 100
|
|
};
|
|
struct timeutil_sync_state ss = {
|
|
.cfg = &unity,
|
|
};
|
|
uint64_t offset = inst.ref - inst.local;
|
|
|
|
timeutil_sync_state_set_skew(&ss, 1.0f, &inst);
|
|
|
|
for (int i = 0; i < ARRAY_SIZE(inputs); i++) {
|
|
rv = timeutil_sync_ref_from_local(&ss, inputs[i], &ref_out);
|
|
zassert_equal(rv, 0, "Unexpected conversion fail");
|
|
zassert_equal(ref_out, inputs[i] + offset,
|
|
"Large unity local->ref conversion fail");
|
|
|
|
rv = timeutil_sync_local_from_ref(&ss, inputs[i], &loc_out);
|
|
zassert_equal(rv, 0, "Unexpected conversion fail");
|
|
zassert_equal(loc_out, inputs[i] - offset,
|
|
"Large unity ref->local conversion fail");
|
|
}
|
|
}
|
|
|
|
static void test_skew_to_ppb(void)
|
|
{
|
|
float skew = 1.0;
|
|
int32_t ppb = timeutil_sync_skew_to_ppb(skew);
|
|
|
|
zassert_equal(ppb, 0,
|
|
"unexpected perfect: %d", ppb);
|
|
|
|
skew = 0.999976;
|
|
ppb = timeutil_sync_skew_to_ppb(skew);
|
|
zassert_equal(ppb, 24020,
|
|
"unexpected fast: %d", ppb);
|
|
|
|
skew = 1.000022;
|
|
ppb = timeutil_sync_skew_to_ppb(skew);
|
|
zassert_equal(ppb, -22053,
|
|
"unexpected slow: %d", ppb);
|
|
|
|
skew = 3.147483587;
|
|
ppb = timeutil_sync_skew_to_ppb(skew);
|
|
zassert_equal(ppb, -2147483587,
|
|
"unexpected near limit: %.10g %d", skew, ppb);
|
|
skew = 3.147483826;
|
|
ppb = timeutil_sync_skew_to_ppb(skew);
|
|
zassert_equal(ppb, INT32_MIN,
|
|
"unexpected above limit: %.10g %d", skew, ppb);
|
|
}
|
|
|
|
ZTEST(timeutil_api, test_sync)
|
|
{
|
|
test_state_update();
|
|
test_state_set_skew();
|
|
test_estimate_skew();
|
|
test_ref_from_local();
|
|
test_local_from_ref();
|
|
test_large_linearity();
|
|
test_skew_to_ppb();
|
|
}
|