fix(gps): harden navigation epoch recovery
This commit is contained in:
@@ -3,7 +3,8 @@ CFLAGS ?= -O2 -std=c11 -Wall -Wextra -Werror
|
||||
|
||||
ROOT := ..
|
||||
TARGETS := test_imu_calibration test_gps_nav_epoch \
|
||||
test_gps_nav_epoch_coalescing test_plic_warm_reset
|
||||
test_gps_nav_epoch_coalescing test_gps_time_anchor \
|
||||
test_plic_warm_reset
|
||||
|
||||
.PHONY: all run clean
|
||||
|
||||
@@ -18,6 +19,9 @@ test_gps_nav_epoch: test_gps_nav_epoch.c $(ROOT)/src/gps/gps_nav_epoch.c
|
||||
test_gps_nav_epoch_coalescing: test_gps_nav_epoch_coalescing.c $(ROOT)/src/gps/gps_nav_epoch.c
|
||||
$(CC) $(CFLAGS) -I$(ROOT)/header $^ -lm -o $@
|
||||
|
||||
test_gps_time_anchor: test_gps_time_anchor.c $(ROOT)/src/gps/gps_time_anchor.c
|
||||
$(CC) $(CFLAGS) -I$(ROOT)/header $^ -lm -o $@
|
||||
|
||||
test_plic_warm_reset: test_plic_warm_reset.c $(ROOT)/../bsp/drivers/plic.c
|
||||
$(CC) $(CFLAGS) -Ifake_bsp -I$(ROOT)/../bsp/include $^ -o $@
|
||||
|
||||
@@ -25,6 +29,7 @@ run: $(TARGETS)
|
||||
./test_imu_calibration
|
||||
./test_gps_nav_epoch
|
||||
./test_gps_nav_epoch_coalescing
|
||||
./test_gps_time_anchor
|
||||
./test_plic_warm_reset
|
||||
|
||||
clean:
|
||||
|
||||
@@ -5,8 +5,11 @@
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#define SAMPLE_WINDOW 409200u
|
||||
#define TOW_TOLERANCE 0.001
|
||||
#define SAMPLE_WINDOW 409200u
|
||||
#define TOW_TOLERANCE 0.001
|
||||
#define CAPTURE_PERIOD_TICKS 25000000ULL
|
||||
#define CAPTURE_WINDOW_TICKS 3125000ULL
|
||||
#define HOLDOFF_LOCAL_TICKS 15000ULL
|
||||
|
||||
static int failures;
|
||||
|
||||
@@ -29,167 +32,17 @@ static gps_nav_epoch_selection_t select_epoch(
|
||||
return selection;
|
||||
}
|
||||
|
||||
static unsigned int mask_count(uint32_t mask)
|
||||
static gps_nav_epoch_event_result_t push_capture(
|
||||
gps_nav_epoch_assembly_t* assembly,
|
||||
uint64_t capture_counter,
|
||||
uint64_t local_ticks,
|
||||
unsigned int channel,
|
||||
uint32_t ready_mask)
|
||||
{
|
||||
unsigned int count = 0u;
|
||||
while(mask != 0u)
|
||||
{
|
||||
count += mask & 1u;
|
||||
mask >>= 1u;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
static void epoch_id_rounding_and_week_wrap(void)
|
||||
{
|
||||
uint64_t before_wrap;
|
||||
uint64_t after_wrap;
|
||||
uint64_t same_epoch_a;
|
||||
uint64_t same_epoch_b;
|
||||
CHECK(gps_nav_epoch_id(2300, 604799.9999, 200u, &before_wrap),
|
||||
"epoch id rejected a valid pre-wrap TOW");
|
||||
CHECK(gps_nav_epoch_id(2301, 0.0001, 200u, &after_wrap),
|
||||
"epoch id rejected a valid post-wrap TOW");
|
||||
CHECK(before_wrap == after_wrap,
|
||||
"week rollover split one rounded navigation epoch");
|
||||
CHECK(gps_nav_epoch_id(2301, 123.3997, 200u, &same_epoch_a) &&
|
||||
gps_nav_epoch_id(2301, 123.4003, 200u, &same_epoch_b) &&
|
||||
(same_epoch_a == same_epoch_b),
|
||||
"sub-millisecond TOW skew split one navigation epoch");
|
||||
CHECK(!gps_nav_epoch_id(2301, NAN, 200u, &same_epoch_a),
|
||||
"non-finite TOW produced an epoch id");
|
||||
CHECK(!gps_nav_epoch_id(2301, 1.0, 333u, &same_epoch_a),
|
||||
"non-integral epochs per GPS week were accepted");
|
||||
}
|
||||
|
||||
static void assembly_waits_for_every_ready_channel(void)
|
||||
{
|
||||
gps_nav_epoch_assembly_t assembly;
|
||||
gps_nav_epoch_assembly_reset(&assembly);
|
||||
const uint32_t ready_mask = 0xafu;
|
||||
const unsigned int order[] = {7u, 0u, 5u, 3u, 2u, 1u};
|
||||
for(unsigned int i = 0u ; i < sizeof(order) / sizeof(order[0]) ; i++)
|
||||
{
|
||||
gps_nav_epoch_event_result_t result =
|
||||
gps_nav_epoch_assembly_push(
|
||||
&assembly, 1000u, order[i], ready_mask);
|
||||
bool last = i + 1u == sizeof(order) / sizeof(order[0]);
|
||||
CHECK((result == GPS_NAV_EPOCH_EVENT_COMPLETE) == last,
|
||||
"assembly completed before every ready channel arrived");
|
||||
}
|
||||
CHECK(assembly.expected_mask == ready_mask,
|
||||
"assembly changed its frozen ready mask");
|
||||
CHECK(assembly.received_mask == ready_mask,
|
||||
"assembly lost a ready-channel interrupt");
|
||||
}
|
||||
|
||||
static void busy_unlocked_channel_does_not_block_epoch(void)
|
||||
{
|
||||
gps_nav_epoch_assembly_t assembly;
|
||||
gps_nav_epoch_assembly_reset(&assembly);
|
||||
|
||||
/* Model the board failure: channels 0..8 are allocated and busy, but
|
||||
* channel 4 has lost lock and therefore cannot produce this NAV epoch. */
|
||||
const uint32_t allocated_busy_mask = 0x1ffu;
|
||||
const uint32_t locked_mask = allocated_busy_mask & ~(1u << 4);
|
||||
const uint32_t ready_mask = allocated_busy_mask & locked_mask;
|
||||
|
||||
for(unsigned int channel = 0u ; channel < 9u ; channel++)
|
||||
{
|
||||
if((ready_mask & (1u << channel)) == 0u)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
gps_nav_epoch_event_result_t result =
|
||||
gps_nav_epoch_assembly_push(
|
||||
&assembly, 1500u, channel, ready_mask);
|
||||
bool last = channel == 8u;
|
||||
CHECK((result == GPS_NAV_EPOCH_EVENT_COMPLETE) == last,
|
||||
"busy unlocked channel blocked a complete locked-channel epoch");
|
||||
}
|
||||
CHECK(assembly.expected_mask == 0x1efu,
|
||||
"busy unlocked channel entered the ready mask");
|
||||
CHECK(assembly.received_mask == assembly.expected_mask,
|
||||
"locked channels did not complete the epoch");
|
||||
}
|
||||
|
||||
static void assembly_handles_duplicates_and_epoch_order(void)
|
||||
{
|
||||
gps_nav_epoch_assembly_t assembly;
|
||||
gps_nav_epoch_assembly_reset(&assembly);
|
||||
CHECK(gps_nav_epoch_assembly_push(&assembly, 2000u, 0u, 0x0fu) ==
|
||||
GPS_NAV_EPOCH_EVENT_ACCEPTED,
|
||||
"first channel did not start assembly");
|
||||
CHECK(gps_nav_epoch_assembly_push(&assembly, 2000u, 0u, 0x0fu) ==
|
||||
GPS_NAV_EPOCH_EVENT_DUPLICATE,
|
||||
"duplicate channel interrupt was not idempotent");
|
||||
CHECK(gps_nav_epoch_assembly_push(&assembly, 1999u, 1u, 0x0fu) ==
|
||||
GPS_NAV_EPOCH_EVENT_STALE,
|
||||
"stale epoch interrupt was not rejected");
|
||||
CHECK(gps_nav_epoch_assembly_push(&assembly, 2001u, 1u, 0x0fu) ==
|
||||
GPS_NAV_EPOCH_EVENT_RESTARTED,
|
||||
"newer epoch did not replace the incomplete epoch");
|
||||
CHECK((assembly.epoch_id == 2001u) &&
|
||||
(assembly.expected_mask == 0x0fu) &&
|
||||
(assembly.received_mask == 0x02u),
|
||||
"newer epoch restart retained measurements from the old epoch");
|
||||
}
|
||||
|
||||
static void assembly_disconnect_and_reconnect(void)
|
||||
{
|
||||
gps_nav_epoch_assembly_t assembly;
|
||||
gps_nav_epoch_assembly_reset(&assembly);
|
||||
CHECK(gps_nav_epoch_assembly_push(&assembly, 3000u, 0u, 0x1fu) ==
|
||||
GPS_NAV_EPOCH_EVENT_ACCEPTED,
|
||||
"disconnect scenario did not start");
|
||||
CHECK(gps_nav_epoch_assembly_push(&assembly, 3000u, 1u, 0x1fu) ==
|
||||
GPS_NAV_EPOCH_EVENT_ACCEPTED,
|
||||
"second interrupt unexpectedly completed");
|
||||
CHECK(!gps_nav_epoch_assembly_remove_channel(&assembly, 4u),
|
||||
"pending-channel disconnect completed too early");
|
||||
CHECK(gps_nav_epoch_assembly_push(&assembly, 3000u, 2u, 0x0fu) ==
|
||||
GPS_NAV_EPOCH_EVENT_ACCEPTED,
|
||||
"post-disconnect interrupt was rejected");
|
||||
CHECK(gps_nav_epoch_assembly_push(&assembly, 3000u, 3u, 0x0fu) ==
|
||||
GPS_NAV_EPOCH_EVENT_COMPLETE,
|
||||
"unlock did not release the removed channel from the epoch");
|
||||
|
||||
gps_nav_epoch_assembly_reset(&assembly);
|
||||
CHECK(gps_nav_epoch_assembly_push(&assembly, 3001u, 0u, 0x1fu) ==
|
||||
GPS_NAV_EPOCH_EVENT_ACCEPTED,
|
||||
"reconnect epoch did not start");
|
||||
for(unsigned int channel = 1u ; channel < 5u ; channel++)
|
||||
{
|
||||
gps_nav_epoch_event_result_t result =
|
||||
gps_nav_epoch_assembly_push(
|
||||
&assembly, 3001u, channel, 0x1fu);
|
||||
CHECK((result == GPS_NAV_EPOCH_EVENT_COMPLETE) ==
|
||||
(channel == 4u),
|
||||
"reconnected channel was not required by the next epoch");
|
||||
}
|
||||
}
|
||||
|
||||
static void channel_join_does_not_expand_active_epoch(void)
|
||||
{
|
||||
gps_nav_epoch_assembly_t assembly;
|
||||
gps_nav_epoch_assembly_reset(&assembly);
|
||||
CHECK(gps_nav_epoch_assembly_push(&assembly, 4000u, 0u, 0x0fu) ==
|
||||
GPS_NAV_EPOCH_EVENT_ACCEPTED,
|
||||
"join scenario did not start");
|
||||
CHECK(gps_nav_epoch_assembly_push(&assembly, 4000u, 4u, 0x1fu) ==
|
||||
GPS_NAV_EPOCH_EVENT_INVALID,
|
||||
"joining channel changed the active epoch");
|
||||
CHECK(assembly.expected_mask == 0x0fu,
|
||||
"active expected mask expanded after a channel joined");
|
||||
for(unsigned int channel = 1u ; channel < 4u ; channel++)
|
||||
{
|
||||
gps_nav_epoch_event_result_t result =
|
||||
gps_nav_epoch_assembly_push(
|
||||
&assembly, 4000u, channel, 0x1fu);
|
||||
CHECK((result == GPS_NAV_EPOCH_EVENT_COMPLETE) ==
|
||||
(channel == 3u),
|
||||
"original ready set did not complete after a join");
|
||||
}
|
||||
return gps_nav_epoch_assembly_push(
|
||||
assembly, capture_counter, local_ticks, channel, ready_mask,
|
||||
CAPTURE_PERIOD_TICKS, CAPTURE_WINDOW_TICKS,
|
||||
HOLDOFF_LOCAL_TICKS);
|
||||
}
|
||||
|
||||
static uint32_t deterministic_random(uint32_t* state)
|
||||
@@ -202,122 +55,144 @@ static uint32_t deterministic_random(uint32_t* state)
|
||||
return value;
|
||||
}
|
||||
|
||||
static void randomized_interrupt_and_unlock_model(void)
|
||||
static void local_capture_assembly_completes(void)
|
||||
{
|
||||
uint32_t random_state = 0x6a09e667u;
|
||||
for(unsigned int trial = 0u ; trial < 10000u ; trial++)
|
||||
gps_nav_epoch_assembly_t assembly;
|
||||
gps_nav_epoch_assembly_reset(&assembly);
|
||||
const uint32_t ready_mask = 0xafu;
|
||||
const unsigned int order[] = {7u, 0u, 5u, 3u, 2u, 1u};
|
||||
const uint64_t base = 810102900000ULL;
|
||||
|
||||
for(unsigned int i = 0u ; i < sizeof(order) / sizeof(order[0]) ; i++)
|
||||
{
|
||||
unsigned int channel_count =
|
||||
4u + (deterministic_random(&random_state) % 6u);
|
||||
uint32_t ready_mask = (1u << channel_count) - 1u;
|
||||
unsigned int order[GPS_NAV_EPOCH_MAX_CANDIDATES];
|
||||
for(unsigned int i = 0u ; i < channel_count ; i++)
|
||||
{
|
||||
order[i] = i;
|
||||
}
|
||||
for(unsigned int i = channel_count ; i > 1u ; i--)
|
||||
{
|
||||
unsigned int swap =
|
||||
deterministic_random(&random_state) % i;
|
||||
unsigned int temporary = order[i - 1u];
|
||||
order[i - 1u] = order[swap];
|
||||
order[swap] = temporary;
|
||||
}
|
||||
gps_nav_epoch_event_result_t result =
|
||||
push_capture(&assembly, base + (uint64_t)i * 250000u,
|
||||
100000u + i, order[i], ready_mask);
|
||||
bool last = i + 1u == sizeof(order) / sizeof(order[0]);
|
||||
CHECK((result == GPS_NAV_EPOCH_EVENT_COMPLETE) == last,
|
||||
"coherent local capture group completed at the wrong event");
|
||||
}
|
||||
CHECK(assembly.expected_mask == ready_mask,
|
||||
"assembly changed its frozen ready mask");
|
||||
CHECK(assembly.received_mask == ready_mask,
|
||||
"assembly lost a ready-channel capture");
|
||||
CHECK((assembly.maximum_capture_counter -
|
||||
assembly.minimum_capture_counter) <= CAPTURE_WINDOW_TICKS,
|
||||
"coherent group exceeded its capture span");
|
||||
}
|
||||
|
||||
bool disconnect =
|
||||
(deterministic_random(&random_state) & 3u) == 0u;
|
||||
unsigned int dropped_channel = disconnect ?
|
||||
(deterministic_random(&random_state) % channel_count) :
|
||||
GPS_NAV_EPOCH_MAX_CANDIDATES;
|
||||
uint32_t final_mask = ready_mask;
|
||||
if(disconnect)
|
||||
{
|
||||
final_mask &= ~(1u << dropped_channel);
|
||||
}
|
||||
static void missing_channel_expires_as_usable_subset(void)
|
||||
{
|
||||
gps_nav_epoch_assembly_t assembly;
|
||||
gps_nav_epoch_assembly_reset(&assembly);
|
||||
const uint32_t ready_mask = 0x3fu;
|
||||
const uint64_t base = 900000000ULL;
|
||||
|
||||
gps_nav_epoch_assembly_t assembly;
|
||||
gps_nav_epoch_assembly_reset(&assembly);
|
||||
bool complete = false;
|
||||
for(unsigned int i = 0u ; i < channel_count ; i++)
|
||||
{
|
||||
unsigned int channel = order[i];
|
||||
if(channel == dropped_channel)
|
||||
{
|
||||
complete = gps_nav_epoch_assembly_remove_channel(
|
||||
&assembly, channel);
|
||||
continue;
|
||||
}
|
||||
gps_nav_epoch_event_result_t result =
|
||||
gps_nav_epoch_assembly_push(
|
||||
&assembly, 5000u + trial, channel, ready_mask);
|
||||
if((deterministic_random(&random_state) & 7u) == 0u)
|
||||
{
|
||||
CHECK(gps_nav_epoch_assembly_push(
|
||||
&assembly, 5000u + trial, channel,
|
||||
ready_mask) ==
|
||||
GPS_NAV_EPOCH_EVENT_DUPLICATE,
|
||||
"random duplicate interrupt was not idempotent");
|
||||
}
|
||||
complete = result == GPS_NAV_EPOCH_EVENT_COMPLETE;
|
||||
}
|
||||
if(disconnect &&
|
||||
((assembly.expected_mask & (1u << dropped_channel)) != 0u))
|
||||
{
|
||||
complete = gps_nav_epoch_assembly_remove_channel(
|
||||
&assembly, dropped_channel);
|
||||
}
|
||||
CHECK(mask_count(final_mask) < 4u ||
|
||||
(complete && gps_nav_epoch_assembly_complete(&assembly)),
|
||||
"random interrupt/unlock sequence did not complete");
|
||||
CHECK(assembly.expected_mask == final_mask,
|
||||
"random unlock changed the wrong expected channel");
|
||||
CHECK(assembly.received_mask == final_mask,
|
||||
"random model lost or invented an interrupt");
|
||||
for(unsigned int channel = 0u ; channel < 5u ; channel++)
|
||||
{
|
||||
CHECK(push_capture(&assembly,
|
||||
base + (uint64_t)channel * 100000u,
|
||||
200000u + channel,
|
||||
channel, ready_mask) ==
|
||||
GPS_NAV_EPOCH_EVENT_ACCEPTED,
|
||||
"partial epoch rejected a coherent channel");
|
||||
}
|
||||
CHECK(!gps_nav_epoch_assembly_complete(&assembly),
|
||||
"missing ready channel completed before holdoff");
|
||||
CHECK(gps_nav_epoch_assembly_usable(&assembly, 4u),
|
||||
"five-channel partial epoch was not usable");
|
||||
CHECK(!gps_nav_epoch_assembly_expired(
|
||||
&assembly, assembly.deadline_local_ticks - 1u),
|
||||
"epoch expired before its deadline");
|
||||
CHECK(gps_nav_epoch_assembly_expired(
|
||||
&assembly, assembly.deadline_local_ticks),
|
||||
"epoch did not expire at its deadline");
|
||||
}
|
||||
|
||||
static void local_outlier_cannot_poison_epoch(void)
|
||||
{
|
||||
gps_nav_epoch_assembly_t assembly;
|
||||
gps_nav_epoch_assembly_reset(&assembly);
|
||||
const uint32_t ready_mask = 0x1fu;
|
||||
const uint64_t base = 1000000000ULL;
|
||||
|
||||
CHECK(push_capture(&assembly, base, 300000u, 0u, ready_mask) ==
|
||||
GPS_NAV_EPOCH_EVENT_ACCEPTED,
|
||||
"outlier scenario did not start");
|
||||
CHECK(push_capture(&assembly, base + 6000000u, 300001u,
|
||||
1u, ready_mask) ==
|
||||
GPS_NAV_EPOCH_EVENT_OUTLIER,
|
||||
"ambiguous local timestamp entered the active group");
|
||||
CHECK((assembly.received_mask == 0x01u) &&
|
||||
(assembly.minimum_capture_counter == base) &&
|
||||
(assembly.maximum_capture_counter == base),
|
||||
"local outlier mutated the active group");
|
||||
|
||||
CHECK(push_capture(&assembly, base - CAPTURE_PERIOD_TICKS,
|
||||
300002u, 2u, ready_mask) ==
|
||||
GPS_NAV_EPOCH_EVENT_STALE,
|
||||
"delayed previous epoch was not rejected");
|
||||
CHECK(push_capture(&assembly, base + CAPTURE_PERIOD_TICKS,
|
||||
300003u, 1u, ready_mask) ==
|
||||
GPS_NAV_EPOCH_EVENT_RESTARTED,
|
||||
"new local epoch did not replace the incomplete group");
|
||||
CHECK((assembly.first_capture_counter ==
|
||||
base + CAPTURE_PERIOD_TICKS) &&
|
||||
(assembly.received_mask == 0x02u),
|
||||
"new epoch retained state from the old group");
|
||||
}
|
||||
|
||||
static void duplicate_join_and_disconnect_are_bounded(void)
|
||||
{
|
||||
gps_nav_epoch_assembly_t assembly;
|
||||
gps_nav_epoch_assembly_reset(&assembly);
|
||||
const uint64_t base = 1100000000ULL;
|
||||
|
||||
CHECK(push_capture(&assembly, base, 400000u, 0u, 0x1fu) ==
|
||||
GPS_NAV_EPOCH_EVENT_ACCEPTED,
|
||||
"membership scenario did not start");
|
||||
CHECK(push_capture(&assembly, base, 400001u, 0u, 0x1fu) ==
|
||||
GPS_NAV_EPOCH_EVENT_DUPLICATE,
|
||||
"duplicate capture was not idempotent");
|
||||
CHECK(push_capture(&assembly, base + 100000u, 400002u,
|
||||
5u, 0x3fu) ==
|
||||
GPS_NAV_EPOCH_EVENT_INVALID,
|
||||
"joining channel expanded the active epoch");
|
||||
CHECK(!gps_nav_epoch_assembly_remove_channel(&assembly, 4u),
|
||||
"disconnect completed a one-channel group");
|
||||
|
||||
for(unsigned int channel = 1u ; channel < 4u ; channel++)
|
||||
{
|
||||
gps_nav_epoch_event_result_t result =
|
||||
push_capture(&assembly,
|
||||
base + (uint64_t)channel * 100000u,
|
||||
400010u + channel, channel, 0x0fu);
|
||||
CHECK((result == GPS_NAV_EPOCH_EVENT_COMPLETE) ==
|
||||
(channel == 3u),
|
||||
"remaining frozen membership completed incorrectly");
|
||||
}
|
||||
}
|
||||
|
||||
static void multi_epoch_hardware_interrupt_model(void)
|
||||
static void randomized_capture_liveness(void)
|
||||
{
|
||||
gps_nav_epoch_assembly_t assembly;
|
||||
uint32_t random_state = 0xbb67ae85u;
|
||||
uint32_t ready_mask = 0x7fu;
|
||||
unsigned int completed_epochs = 0u;
|
||||
unsigned int dropped_epochs = 0u;
|
||||
unsigned int joined_epochs = 0u;
|
||||
|
||||
for(unsigned int epoch = 0u ; epoch < 2000u ; epoch++)
|
||||
uint32_t random_state = 0x6a09e667u;
|
||||
uint64_t base = 2000000000ULL;
|
||||
for(unsigned int trial = 0u ; trial < 100000u ; trial++)
|
||||
{
|
||||
bool drop_before_interrupt = (epoch == 250u);
|
||||
bool drop_after_interrupt = (epoch == 900u);
|
||||
bool join_during_epoch = (epoch == 500u) || (epoch == 1200u);
|
||||
unsigned int changing_channel =
|
||||
(epoch == 1200u) ? 6u : 5u;
|
||||
|
||||
if(epoch == 400u)
|
||||
{
|
||||
ready_mask |= 1u << 2;
|
||||
}
|
||||
if(epoch == 1050u)
|
||||
{
|
||||
ready_mask |= 1u << 4;
|
||||
}
|
||||
if(join_during_epoch)
|
||||
{
|
||||
ready_mask &= ~(1u << changing_channel);
|
||||
}
|
||||
|
||||
uint32_t epoch_ready_mask = ready_mask;
|
||||
gps_nav_epoch_assembly_t assembly;
|
||||
gps_nav_epoch_assembly_reset(&assembly);
|
||||
unsigned int ready_count =
|
||||
4u + (deterministic_random(&random_state) % 6u);
|
||||
uint32_t ready_mask = (1u << ready_count) - 1u;
|
||||
unsigned int delivered_count =
|
||||
4u + (deterministic_random(&random_state) %
|
||||
(ready_count - 3u));
|
||||
unsigned int order[GPS_NAV_EPOCH_MAX_CANDIDATES];
|
||||
unsigned int order_count = 0u;
|
||||
for(unsigned int channel = 0u ;
|
||||
channel < GPS_NAV_EPOCH_MAX_CANDIDATES ; channel++)
|
||||
for(unsigned int i = 0u ; i < ready_count ; i++)
|
||||
{
|
||||
if((epoch_ready_mask & (1u << channel)) != 0u)
|
||||
{
|
||||
order[order_count++] = channel;
|
||||
}
|
||||
order[i] = i;
|
||||
}
|
||||
for(unsigned int i = order_count ; i > 1u ; i--)
|
||||
for(unsigned int i = ready_count ; i > 1u ; i--)
|
||||
{
|
||||
unsigned int swap =
|
||||
deterministic_random(&random_state) % i;
|
||||
@@ -326,76 +201,28 @@ static void multi_epoch_hardware_interrupt_model(void)
|
||||
order[swap] = temporary;
|
||||
}
|
||||
|
||||
gps_nav_epoch_assembly_reset(&assembly);
|
||||
bool completed = false;
|
||||
for(unsigned int event_index = 0u ;
|
||||
event_index < order_count ; event_index++)
|
||||
for(unsigned int i = 0u ; i < delivered_count ; i++)
|
||||
{
|
||||
unsigned int channel = order[event_index];
|
||||
if(drop_before_interrupt && (channel == 2u))
|
||||
{
|
||||
ready_mask &= ~(1u << channel);
|
||||
(void)gps_nav_epoch_assembly_remove_channel(
|
||||
&assembly, channel);
|
||||
dropped_epochs++;
|
||||
continue;
|
||||
}
|
||||
|
||||
uint64_t offset =
|
||||
deterministic_random(&random_state) %
|
||||
(CAPTURE_WINDOW_TICKS + 1u);
|
||||
gps_nav_epoch_event_result_t result =
|
||||
gps_nav_epoch_assembly_push(
|
||||
&assembly, 100000u + epoch, channel,
|
||||
epoch_ready_mask);
|
||||
if(result == GPS_NAV_EPOCH_EVENT_COMPLETE)
|
||||
{
|
||||
completed = true;
|
||||
}
|
||||
|
||||
if(drop_after_interrupt && (channel == 4u))
|
||||
{
|
||||
ready_mask &= ~(1u << channel);
|
||||
if(gps_nav_epoch_assembly_remove_channel(
|
||||
&assembly, channel))
|
||||
{
|
||||
completed = true;
|
||||
}
|
||||
dropped_epochs++;
|
||||
}
|
||||
|
||||
if(join_during_epoch && (event_index == 0u))
|
||||
{
|
||||
ready_mask |= 1u << changing_channel;
|
||||
CHECK(gps_nav_epoch_assembly_push(
|
||||
&assembly, 100000u + epoch,
|
||||
changing_channel, ready_mask) ==
|
||||
GPS_NAV_EPOCH_EVENT_INVALID,
|
||||
"mid-epoch connection expanded the frozen mask");
|
||||
joined_epochs++;
|
||||
}
|
||||
|
||||
if((event_index == 0u) && (epoch != 0u))
|
||||
{
|
||||
CHECK(gps_nav_epoch_assembly_push(
|
||||
&assembly, 99999u + epoch,
|
||||
channel, epoch_ready_mask) ==
|
||||
GPS_NAV_EPOCH_EVENT_STALE,
|
||||
"delayed previous-epoch interrupt was accepted");
|
||||
}
|
||||
push_capture(&assembly, base + offset,
|
||||
500000u + i, order[i], ready_mask);
|
||||
CHECK((result == GPS_NAV_EPOCH_EVENT_ACCEPTED) ||
|
||||
(result == GPS_NAV_EPOCH_EVENT_COMPLETE),
|
||||
"random coherent capture was rejected");
|
||||
}
|
||||
|
||||
CHECK(completed &&
|
||||
gps_nav_epoch_assembly_complete(&assembly),
|
||||
"hardware stream did not complete a live epoch");
|
||||
CHECK(assembly.received_mask == assembly.expected_mask,
|
||||
"hardware stream completed with a missing interrupt");
|
||||
completed_epochs++;
|
||||
CHECK(gps_nav_epoch_assembly_usable(&assembly, 4u),
|
||||
"random stream lost four-channel liveness");
|
||||
if(delivered_count != ready_count)
|
||||
{
|
||||
CHECK(gps_nav_epoch_assembly_expired(
|
||||
&assembly, assembly.deadline_local_ticks),
|
||||
"partial random epoch did not expire");
|
||||
}
|
||||
base += CAPTURE_PERIOD_TICKS;
|
||||
}
|
||||
|
||||
CHECK(completed_epochs == 2000u,
|
||||
"hardware stream produced more or fewer than one completion per epoch");
|
||||
CHECK(dropped_epochs == 2u,
|
||||
"hardware stream did not exercise both unlock positions");
|
||||
CHECK(joined_epochs == 2u,
|
||||
"hardware stream did not exercise both reconnect positions");
|
||||
}
|
||||
|
||||
static void all_coherent_across_sample_wrap(void)
|
||||
@@ -416,19 +243,17 @@ static void all_coherent_across_sample_wrap(void)
|
||||
static void tow_outlier_does_not_block_good_cluster(void)
|
||||
{
|
||||
gps_nav_epoch_key_t candidates[GPS_NAV_EPOCH_MAX_CANDIDATES] = {
|
||||
{2300, 100.0000, 1000000u},
|
||||
{2300, 100.0001, 1010000u},
|
||||
{2300, 99.9999, 1020000u},
|
||||
{2300, 100.0002, 1030000u},
|
||||
{2300, 100.0000, 1040000u},
|
||||
{2300, 100.0001, 1050000u},
|
||||
{2300, 100.2000, 1060000u}
|
||||
{380, 469367.4000, 2982422271u},
|
||||
{380, 469367.4001, 2982428475u},
|
||||
{381, 28831.6000, 2982490274u},
|
||||
{380, 469367.3999, 2982550441u},
|
||||
{380, 469367.4002, 2982661405u}
|
||||
};
|
||||
gps_nav_epoch_selection_t selection = select_epoch(candidates, 7u);
|
||||
CHECK(selection.selected_count == 6u,
|
||||
"single TOW outlier blocked the six-satellite cluster");
|
||||
CHECK(selection.selected_mask == 0x3fu,
|
||||
"TOW outlier was not excluded");
|
||||
gps_nav_epoch_selection_t selection = select_epoch(candidates, 5u);
|
||||
CHECK(selection.selected_count == 4u,
|
||||
"live 45.6-hour TOW outlier blocked the good cluster");
|
||||
CHECK(selection.selected_mask == 0x1bu,
|
||||
"live TOW outlier was not excluded");
|
||||
}
|
||||
|
||||
static void newest_sample_outlier_does_not_define_the_cluster(void)
|
||||
@@ -486,14 +311,11 @@ static void nonfinite_candidate_is_excluded(void)
|
||||
|
||||
int main(void)
|
||||
{
|
||||
epoch_id_rounding_and_week_wrap();
|
||||
assembly_waits_for_every_ready_channel();
|
||||
busy_unlocked_channel_does_not_block_epoch();
|
||||
assembly_handles_duplicates_and_epoch_order();
|
||||
assembly_disconnect_and_reconnect();
|
||||
channel_join_does_not_expand_active_epoch();
|
||||
randomized_interrupt_and_unlock_model();
|
||||
multi_epoch_hardware_interrupt_model();
|
||||
local_capture_assembly_completes();
|
||||
missing_channel_expires_as_usable_subset();
|
||||
local_outlier_cannot_poison_epoch();
|
||||
duplicate_join_and_disconnect_are_bounded();
|
||||
randomized_capture_liveness();
|
||||
all_coherent_across_sample_wrap();
|
||||
tow_outlier_does_not_block_good_cluster();
|
||||
newest_sample_outlier_does_not_define_the_cluster();
|
||||
|
||||
@@ -4,15 +4,9 @@
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#define CHANNEL_COUNT 6u
|
||||
#define READY_MASK 0x3fu
|
||||
#define MISSING_CH 4u
|
||||
|
||||
typedef struct {
|
||||
bool pending;
|
||||
uint32_t capture_sequence;
|
||||
uint64_t mailbox_epoch;
|
||||
} synthetic_tracking_irq_t;
|
||||
#define PERIOD_TICKS 25000000ULL
|
||||
#define WINDOW_TICKS 3125000ULL
|
||||
#define HOLDOFF_TICKS 15000ULL
|
||||
|
||||
static int failures;
|
||||
|
||||
@@ -23,152 +17,115 @@ static int failures;
|
||||
} \
|
||||
} while(0)
|
||||
|
||||
/*
|
||||
* The tracking interrupt source is a sticky pending bit, while the associated
|
||||
* measurement registers are a one-entry mailbox. A second pulse therefore
|
||||
* advances the PPS capture sequence and overwrites the mailbox even when the
|
||||
* first pulse has not yet been serviced.
|
||||
*/
|
||||
static void hardware_nav_pulse(
|
||||
synthetic_tracking_irq_t channels[CHANNEL_COUNT],
|
||||
uint64_t epoch)
|
||||
{
|
||||
for(unsigned int channel = 0u ; channel < CHANNEL_COUNT ; channel++)
|
||||
{
|
||||
channels[channel].capture_sequence++;
|
||||
channels[channel].mailbox_epoch = epoch;
|
||||
channels[channel].pending = true;
|
||||
}
|
||||
}
|
||||
|
||||
static gps_nav_epoch_event_result_t service_channel(
|
||||
synthetic_tracking_irq_t channels[CHANNEL_COUNT],
|
||||
uint32_t serviced_sequence[CHANNEL_COUNT],
|
||||
static gps_nav_epoch_event_result_t push(
|
||||
gps_nav_epoch_assembly_t* assembly,
|
||||
uint64_t counter,
|
||||
uint64_t local_ticks,
|
||||
unsigned int channel,
|
||||
unsigned int* coalesced_pulses)
|
||||
uint32_t ready_mask)
|
||||
{
|
||||
if((channel >= CHANNEL_COUNT) || !channels[channel].pending)
|
||||
{
|
||||
return GPS_NAV_EPOCH_EVENT_INVALID;
|
||||
}
|
||||
|
||||
uint32_t sequence_delta =
|
||||
channels[channel].capture_sequence - serviced_sequence[channel];
|
||||
if((serviced_sequence[channel] != 0u) && (sequence_delta > 1u))
|
||||
{
|
||||
*coalesced_pulses += sequence_delta - 1u;
|
||||
}
|
||||
serviced_sequence[channel] = channels[channel].capture_sequence;
|
||||
channels[channel].pending = false;
|
||||
|
||||
return gps_nav_epoch_assembly_push(
|
||||
assembly, channels[channel].mailbox_epoch, channel, READY_MASK);
|
||||
assembly, counter, local_ticks, channel, ready_mask,
|
||||
PERIOD_TICKS, WINDOW_TICKS, HOLDOFF_TICKS);
|
||||
}
|
||||
|
||||
static void service_complete_epoch(
|
||||
synthetic_tracking_irq_t channels[CHANNEL_COUNT],
|
||||
uint32_t serviced_sequence[CHANNEL_COUNT],
|
||||
gps_nav_epoch_assembly_t* assembly,
|
||||
unsigned int* coalesced_pulses)
|
||||
static void live_fault_recovery(void)
|
||||
{
|
||||
for(unsigned int channel = 0u ; channel < CHANNEL_COUNT ; channel++)
|
||||
/* Exact topology from the captured board state: channel 6 is locked but
|
||||
* 11.6 seconds stale; channel 3 carries a transient GPS time 45.6 hours
|
||||
* ahead. Local capture time still places five measurements in one epoch. */
|
||||
const uint32_t ready_mask =
|
||||
(1u << 0) | (1u << 2) | (1u << 3) |
|
||||
(1u << 4) | (1u << 6) | (1u << 7);
|
||||
const unsigned int channels[] = {3u, 0u, 2u, 4u, 7u};
|
||||
const uint64_t captures[] = {
|
||||
810103886055ULL,
|
||||
810102907239ULL,
|
||||
810102954617ULL,
|
||||
810104733473ULL,
|
||||
810103426569ULL
|
||||
};
|
||||
gps_nav_epoch_assembly_t assembly;
|
||||
gps_nav_epoch_assembly_reset(&assembly);
|
||||
|
||||
for(unsigned int i = 0u ; i < 5u ; i++)
|
||||
{
|
||||
CHECK(push(&assembly, captures[i], 100000u + i,
|
||||
channels[i], ready_mask) ==
|
||||
GPS_NAV_EPOCH_EVENT_ACCEPTED,
|
||||
"live coherent local capture was rejected");
|
||||
}
|
||||
CHECK(assembly.received_mask ==
|
||||
((1u << 0) | (1u << 2) | (1u << 3) |
|
||||
(1u << 4) | (1u << 7)),
|
||||
"live capture mask differs from the expected five channels");
|
||||
CHECK(gps_nav_epoch_assembly_usable(&assembly, 4u),
|
||||
"live five-channel group was not usable");
|
||||
CHECK(gps_nav_epoch_assembly_expired(
|
||||
&assembly, assembly.deadline_local_ticks),
|
||||
"missing channel did not release the live group at holdoff");
|
||||
|
||||
/* A physically later local group advances normally regardless of whatever
|
||||
* week/TOW any channel decoded. */
|
||||
uint32_t old_mask = assembly.received_mask;
|
||||
CHECK(push(&assembly, captures[0] + PERIOD_TICKS,
|
||||
200000u, 3u, ready_mask) ==
|
||||
GPS_NAV_EPOCH_EVENT_RESTARTED,
|
||||
"next physical epoch did not advance after the live fault");
|
||||
CHECK(old_mask != 0u && assembly.received_mask == (1u << 3),
|
||||
"next epoch mixed measurements from the previous group");
|
||||
}
|
||||
|
||||
static void overwritten_mailbox_remains_bounded(void)
|
||||
{
|
||||
const uint32_t ready_mask = 0x3fu;
|
||||
gps_nav_epoch_assembly_t assembly;
|
||||
gps_nav_epoch_assembly_reset(&assembly);
|
||||
uint64_t first_epoch = 900000000000ULL;
|
||||
|
||||
/* Channel 4's first mailbox value is overwritten. The other five still
|
||||
* form a usable bounded snapshot instead of waiting indefinitely. */
|
||||
for(unsigned int channel = 0u ; channel < 6u ; channel++)
|
||||
{
|
||||
if(channel == 4u)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
CHECK(push(&assembly, first_epoch + channel * 100000u,
|
||||
300000u + channel, channel, ready_mask) ==
|
||||
GPS_NAV_EPOCH_EVENT_ACCEPTED,
|
||||
"partial mailbox epoch rejected");
|
||||
}
|
||||
CHECK(gps_nav_epoch_assembly_usable(&assembly, 4u),
|
||||
"mailbox overwrite blocked the usable subset");
|
||||
|
||||
uint32_t previous_mask = assembly.received_mask;
|
||||
CHECK(push(&assembly, first_epoch + PERIOD_TICKS,
|
||||
400000u, 0u, ready_mask) ==
|
||||
GPS_NAV_EPOCH_EVENT_RESTARTED,
|
||||
"new mailbox epoch did not advance");
|
||||
CHECK(previous_mask == 0x2fu,
|
||||
"partial mailbox mask was not preserved for publication");
|
||||
|
||||
for(unsigned int channel = 1u ; channel < 6u ; channel++)
|
||||
{
|
||||
gps_nav_epoch_event_result_t result =
|
||||
service_channel(channels, serviced_sequence, assembly,
|
||||
channel, coalesced_pulses);
|
||||
push(&assembly,
|
||||
first_epoch + PERIOD_TICKS + channel * 100000u,
|
||||
400000u + channel, channel, ready_mask);
|
||||
CHECK((result == GPS_NAV_EPOCH_EVENT_COMPLETE) ==
|
||||
(channel == CHANNEL_COUNT - 1u),
|
||||
"healthy epoch did not complete on its final channel");
|
||||
(channel == 5u),
|
||||
"healthy mailbox epoch completed incorrectly");
|
||||
}
|
||||
}
|
||||
|
||||
int main(void)
|
||||
{
|
||||
synthetic_tracking_irq_t channels[CHANNEL_COUNT] = {0};
|
||||
uint32_t serviced_sequence[CHANNEL_COUNT] = {0};
|
||||
gps_nav_epoch_assembly_t assembly;
|
||||
unsigned int coalesced_pulses = 0u;
|
||||
unsigned int completed_epochs = 0u;
|
||||
const uint64_t baseline_epoch = 1151141640u;
|
||||
const uint64_t blocked_epoch = 1151141641u;
|
||||
|
||||
/* Establish the same non-zero PPS sequence baseline as the running board. */
|
||||
gps_nav_epoch_assembly_reset(&assembly);
|
||||
hardware_nav_pulse(channels, baseline_epoch);
|
||||
service_complete_epoch(channels, serviced_sequence, &assembly,
|
||||
&coalesced_pulses);
|
||||
completed_epochs++;
|
||||
gps_nav_epoch_assembly_reset(&assembly);
|
||||
|
||||
/* All six pulse, but the CPU services every source except tracking 4. */
|
||||
hardware_nav_pulse(channels, blocked_epoch);
|
||||
for(unsigned int channel = 0u ; channel < CHANNEL_COUNT ; channel++)
|
||||
{
|
||||
if(channel == MISSING_CH)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
CHECK(service_channel(channels, serviced_sequence, &assembly,
|
||||
channel, &coalesced_pulses) ==
|
||||
GPS_NAV_EPOCH_EVENT_ACCEPTED,
|
||||
"partial blocked epoch was not accepted");
|
||||
}
|
||||
CHECK(assembly.expected_mask == 0x3fu,
|
||||
"blocked epoch expected mask differs from the board");
|
||||
CHECK(assembly.received_mask == 0x2fu,
|
||||
"blocked epoch received mask differs from the board");
|
||||
|
||||
/*
|
||||
* Before channel 4 is serviced, the next hardware pulse overwrites its
|
||||
* one-entry mailbox. Its sequence delta is two, but only the newer epoch
|
||||
* can be delivered to the assembler.
|
||||
*/
|
||||
hardware_nav_pulse(channels, blocked_epoch + 1u);
|
||||
for(unsigned int channel = 0u ; channel < CHANNEL_COUNT ; channel++)
|
||||
{
|
||||
gps_nav_epoch_event_result_t result =
|
||||
service_channel(channels, serviced_sequence, &assembly,
|
||||
channel, &coalesced_pulses);
|
||||
gps_nav_epoch_event_result_t expected =
|
||||
(channel == 0u) ? GPS_NAV_EPOCH_EVENT_RESTARTED :
|
||||
(channel == CHANNEL_COUNT - 1u) ?
|
||||
GPS_NAV_EPOCH_EVENT_COMPLETE :
|
||||
GPS_NAV_EPOCH_EVENT_ACCEPTED;
|
||||
CHECK(result == expected,
|
||||
"newer hardware epoch did not restart and complete cleanly");
|
||||
}
|
||||
CHECK(coalesced_pulses == 1u,
|
||||
"PPS sequence did not expose the overwritten channel-4 pulse");
|
||||
CHECK((assembly.epoch_id == blocked_epoch + 1u) &&
|
||||
(assembly.expected_mask == READY_MASK) &&
|
||||
(assembly.received_mask == READY_MASK) &&
|
||||
gps_nav_epoch_assembly_complete(&assembly),
|
||||
"assembler did not recover on the first complete newer epoch");
|
||||
completed_epochs++;
|
||||
|
||||
/* Every later healthy epoch must continue to complete normally. */
|
||||
for(uint64_t offset = 2u ; offset < 102u ; offset++)
|
||||
{
|
||||
gps_nav_epoch_assembly_reset(&assembly);
|
||||
hardware_nav_pulse(channels, blocked_epoch + offset);
|
||||
service_complete_epoch(channels, serviced_sequence, &assembly,
|
||||
&coalesced_pulses);
|
||||
completed_epochs++;
|
||||
}
|
||||
|
||||
CHECK(completed_epochs == 102u,
|
||||
"recovery stream lost or duplicated a completed epoch");
|
||||
|
||||
live_fault_recovery();
|
||||
overwritten_mailbox_remains_bounded();
|
||||
if(failures == 0)
|
||||
{
|
||||
printf("RECOVERED: active=%llu expected=0x%x received=0x%x "
|
||||
"coalesced=%u completed_epochs=%u\n",
|
||||
(unsigned long long)assembly.epoch_id,
|
||||
assembly.expected_mask,
|
||||
assembly.received_mask,
|
||||
coalesced_pulses,
|
||||
completed_epochs);
|
||||
puts("gps_nav_epoch coalescing/fault tests passed");
|
||||
}
|
||||
return failures == 0 ? 0 : 1;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,114 @@
|
||||
#include "gps/gps_time_anchor.h"
|
||||
|
||||
#include <math.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#define SAMPLE_RATE_HZ 16368000.0
|
||||
|
||||
static int failures;
|
||||
|
||||
#define CHECK(condition, message) do { \
|
||||
if(!(condition)) { \
|
||||
fprintf(stderr, "FAIL: %s\n", message); \
|
||||
failures++; \
|
||||
} \
|
||||
} while(0)
|
||||
|
||||
static void normal_and_skipped_subframes_are_consistent(void)
|
||||
{
|
||||
uint32_t first = 1000000000u;
|
||||
uint32_t six_seconds = (uint32_t)(SAMPLE_RATE_HZ * 6.0);
|
||||
CHECK(gps_time_anchors_consistent(
|
||||
380, 469362.0, first, (double)first,
|
||||
380, 469368.0, first + six_seconds,
|
||||
(double)(first + six_seconds),
|
||||
SAMPLE_RATE_HZ, 120.0, 0.050),
|
||||
"normal six-second HOW progression was rejected");
|
||||
CHECK(gps_time_anchors_consistent(
|
||||
380, 469362.0, first, (double)first,
|
||||
380, 469380.0, first + 3u * six_seconds,
|
||||
(double)(first + 3u * six_seconds),
|
||||
SAMPLE_RATE_HZ, 120.0, 0.050),
|
||||
"skipped subframes broke HOW continuity");
|
||||
}
|
||||
|
||||
static void week_rollover_is_consistent(void)
|
||||
{
|
||||
uint32_t first = 2000000000u;
|
||||
uint32_t six_seconds = (uint32_t)(SAMPLE_RATE_HZ * 6.0);
|
||||
CHECK(gps_time_anchors_consistent(
|
||||
380, 604794.0, first, (double)first,
|
||||
381, 0.0, first + six_seconds,
|
||||
(double)(first + six_seconds),
|
||||
SAMPLE_RATE_HZ, 120.0, 0.050),
|
||||
"valid GPS week rollover was rejected");
|
||||
CHECK(gps_time_extend_lnav_week(true, 1023, 0u) == 1024,
|
||||
"LNAV week rollover was not extended monotonically");
|
||||
CHECK(gps_time_extend_lnav_week(true, 1024, 1023u) == 1023,
|
||||
"previous modulo week was not extended backward");
|
||||
CHECK(gps_time_extend_lnav_week(false, 0, 380u) == 380,
|
||||
"initial LNAV week was altered");
|
||||
}
|
||||
|
||||
static void temporary_week_can_upgrade_on_first_subframe_one(void)
|
||||
{
|
||||
uint32_t first = 2500000000u;
|
||||
uint32_t six_seconds = (uint32_t)(SAMPLE_RATE_HZ * 6.0);
|
||||
CHECK(gps_time_anchors_consistent(
|
||||
0, 470940.0, first, (double)first,
|
||||
0, 470946.0, first + six_seconds,
|
||||
(double)(first + six_seconds),
|
||||
SAMPLE_RATE_HZ, 120.0, 0.050),
|
||||
"temporary-week TOW continuity was rejected");
|
||||
CHECK(gps_time_extend_lnav_week(true, 0, 380u) == 380,
|
||||
"first SF1 could not upgrade the temporary week");
|
||||
}
|
||||
|
||||
static void captured_future_jump_is_rejected_and_recovers(void)
|
||||
{
|
||||
uint32_t first = 3000000000u;
|
||||
uint32_t six_seconds = (uint32_t)(SAMPLE_RATE_HZ * 6.0);
|
||||
CHECK(!gps_time_anchors_consistent(
|
||||
380, 469362.0, first, (double)first,
|
||||
381, 28830.0, first + six_seconds,
|
||||
(double)(first + six_seconds),
|
||||
SAMPLE_RATE_HZ, 120.0, 0.050),
|
||||
"captured 45.6-hour HOW jump was accepted");
|
||||
|
||||
/* Two consecutive candidates on the replacement timeline agree, allowing
|
||||
* the channel-level pending-anchor logic to recover without a reset. */
|
||||
CHECK(gps_time_anchors_consistent(
|
||||
380, 469368.0, first + six_seconds,
|
||||
(double)(first + six_seconds),
|
||||
380, 469374.0, first + 2u * six_seconds,
|
||||
(double)(first + 2u * six_seconds),
|
||||
SAMPLE_RATE_HZ, 120.0, 0.050),
|
||||
"two good HOW candidates could not establish recovery");
|
||||
}
|
||||
|
||||
static void invalid_inputs_are_rejected(void)
|
||||
{
|
||||
CHECK(!gps_time_anchors_consistent(
|
||||
380, NAN, 1u, 1.0, 380, 6.0, 2u, 2.0,
|
||||
SAMPLE_RATE_HZ, 120.0, 0.050),
|
||||
"non-finite HOW was accepted");
|
||||
CHECK(!gps_time_anchors_consistent(
|
||||
380, 0.0, 1u, 1.0, 380, 604800.0, 2u, 2.0,
|
||||
SAMPLE_RATE_HZ, 120.0, 0.050),
|
||||
"out-of-range HOW was accepted");
|
||||
}
|
||||
|
||||
int main(void)
|
||||
{
|
||||
normal_and_skipped_subframes_are_consistent();
|
||||
week_rollover_is_consistent();
|
||||
temporary_week_can_upgrade_on_first_subframe_one();
|
||||
captured_future_jump_is_rejected_and_recovers();
|
||||
invalid_inputs_are_rejected();
|
||||
if(failures == 0)
|
||||
{
|
||||
puts("gps_time_anchor tests passed");
|
||||
}
|
||||
return failures == 0 ? 0 : 1;
|
||||
}
|
||||
Reference in New Issue
Block a user