30179df8e6
Remove the fixed pseudorange start offset, preserve per-PRN navigation assistance across RF loss and power cycles, and seed coherent navigation measurement phase on reacquisition. Add full LNAV almanac visibility scheduling, power-safe QSPI journaling, RF-domain reset isolation, and PPS Gray CDC hardening with Linux, sanitizer, RTL, and end-to-end coverage.
197 lines
6.5 KiB
C
197 lines
6.5 KiB
C
#include <assert.h>
|
|
#include <stdbool.h>
|
|
#include <stdio.h>
|
|
|
|
#include "gps/gps_acq_manager.h"
|
|
|
|
static gps_acq_manager_t make_manager(
|
|
uint64_t retry_delay,
|
|
uint64_t hot_retry_delay,
|
|
uint64_t hot_window,
|
|
unsigned int max_hot_streak)
|
|
{
|
|
gps_acq_manager_t manager;
|
|
const gps_acq_manager_config_t config = {
|
|
.retry_delay_ticks = retry_delay,
|
|
.hot_retry_delay_ticks = hot_retry_delay,
|
|
.hot_window_ticks = hot_window,
|
|
.unknown_probe_interval_ticks = 2u,
|
|
.visibility_probe_interval_ticks = 60u,
|
|
.max_hot_streak = max_hot_streak
|
|
};
|
|
gps_acq_manager_init(&manager, &config);
|
|
return manager;
|
|
}
|
|
|
|
static unsigned int start_next(
|
|
gps_acq_manager_t* manager,
|
|
uint64_t now,
|
|
bool expected_hot)
|
|
{
|
|
unsigned int prn = GPS_ACQ_MANAGER_INVALID_PRN;
|
|
bool hot = !expected_hot;
|
|
assert(gps_acq_manager_start_next(manager, now, &prn, &hot));
|
|
assert(hot == expected_hot);
|
|
return prn;
|
|
}
|
|
|
|
static void test_cold_round_robin_and_retry_from_tick_zero(void)
|
|
{
|
|
gps_acq_manager_t manager =
|
|
make_manager(5u, 5u, 100u, 4u);
|
|
assert(gps_acq_manager_enable_cold(&manager, 0u));
|
|
assert(gps_acq_manager_enable_cold(&manager, 1u));
|
|
assert(gps_acq_manager_enable_cold(&manager, 2u));
|
|
|
|
assert(start_next(&manager, 0u, false) == 0u);
|
|
/* A tick-zero attempt is real and must still observe retry delay. */
|
|
gps_acq_manager_mark_failed(&manager, 0u, 0u);
|
|
assert(start_next(&manager, 0u, false) == 1u);
|
|
gps_acq_manager_mark_failed(&manager, 1u, 0u);
|
|
assert(start_next(&manager, 0u, false) == 2u);
|
|
gps_acq_manager_mark_failed(&manager, 2u, 0u);
|
|
|
|
unsigned int ignored;
|
|
assert(!gps_acq_manager_start_next(
|
|
&manager, 4u, &ignored, NULL));
|
|
assert(start_next(&manager, 5u, false) == 0u);
|
|
}
|
|
|
|
static void test_hot_priority_round_robin_and_cold_fairness(void)
|
|
{
|
|
gps_acq_manager_t manager =
|
|
make_manager(1u, 1u, 100u, 2u);
|
|
assert(gps_acq_manager_enable_cold(&manager, 0u));
|
|
assert(gps_acq_manager_enable_cold(&manager, 1u));
|
|
gps_acq_manager_mark_lost(&manager, 7u, 10u);
|
|
gps_acq_manager_mark_lost(&manager, 8u, 10u);
|
|
|
|
assert(start_next(&manager, 10u, true) == 7u);
|
|
gps_acq_manager_mark_failed(&manager, 7u, 11u);
|
|
assert(start_next(&manager, 11u, true) == 8u);
|
|
gps_acq_manager_mark_failed(&manager, 8u, 12u);
|
|
|
|
/* Two hot attempts force one cold discovery attempt. */
|
|
assert(start_next(&manager, 12u, false) == 0u);
|
|
gps_acq_manager_mark_failed(&manager, 0u, 13u);
|
|
assert(start_next(&manager, 13u, true) == 7u);
|
|
}
|
|
|
|
static void test_expired_hot_becomes_cold_and_tracking_is_excluded(void)
|
|
{
|
|
gps_acq_manager_t manager =
|
|
make_manager(1u, 1u, 10u, 4u);
|
|
assert(gps_acq_manager_enable_cold(&manager, 2u));
|
|
gps_acq_manager_mark_lost(&manager, 9u, 20u);
|
|
|
|
unsigned int prn = start_next(&manager, 31u, false);
|
|
assert(prn == 2u);
|
|
gps_acq_manager_mark_tracking(&manager, prn);
|
|
|
|
prn = start_next(&manager, 31u, false);
|
|
assert(prn == 9u);
|
|
gps_acq_manager_mark_acquired(&manager, prn);
|
|
unsigned int ignored;
|
|
assert(!gps_acq_manager_start_next(
|
|
&manager, 100u, &ignored, NULL));
|
|
|
|
gps_acq_manager_mark_tracking(&manager, 9u);
|
|
gps_acq_manager_mark_stale(&manager, 9u);
|
|
assert(gps_acq_manager_state(&manager, 9u) ==
|
|
GPS_ACQ_PRN_STALE);
|
|
gps_acq_manager_mark_lost(&manager, 9u, 101u);
|
|
assert(gps_acq_manager_state(&manager, 9u) ==
|
|
GPS_ACQ_PRN_HOT);
|
|
}
|
|
|
|
static void test_invalid_inputs_are_safe(void)
|
|
{
|
|
gps_acq_manager_t manager =
|
|
make_manager(1u, 1u, 10u, 0u);
|
|
assert(!gps_acq_manager_enable_cold(
|
|
&manager, GPS_ACQ_MANAGER_PRN_COUNT));
|
|
assert(gps_acq_manager_state(
|
|
&manager, GPS_ACQ_MANAGER_PRN_COUNT) ==
|
|
GPS_ACQ_PRN_DISABLED);
|
|
assert(!gps_acq_manager_start_next(NULL, 0u, NULL, NULL));
|
|
assert(gps_acq_manager_count_searchable(NULL) == 0u);
|
|
}
|
|
|
|
static void test_failed_hot_search_is_backed_off(void)
|
|
{
|
|
gps_acq_manager_t manager =
|
|
make_manager(1u, 30u, 100u, 4u);
|
|
assert(gps_acq_manager_enable_cold(&manager, 0u));
|
|
gps_acq_manager_mark_lost(&manager, 20u, 0u);
|
|
|
|
assert(start_next(&manager, 0u, true) == 20u);
|
|
gps_acq_manager_mark_failed(&manager, 20u, 5u);
|
|
assert(start_next(&manager, 5u, false) == 0u);
|
|
gps_acq_manager_mark_failed(&manager, 0u, 6u);
|
|
assert(start_next(&manager, 7u, false) == 0u);
|
|
gps_acq_manager_mark_failed(&manager, 0u, 8u);
|
|
|
|
assert(start_next(&manager, 35u, true) == 20u);
|
|
}
|
|
|
|
static void test_visibility_priority_discovery_and_safety_probe(void)
|
|
{
|
|
gps_acq_manager_t manager =
|
|
make_manager(1u, 1u, 100u, 4u);
|
|
for(unsigned int prn = 1u ; prn <= 4u ; prn++)
|
|
{
|
|
assert(gps_acq_manager_enable_cold(&manager, prn));
|
|
}
|
|
assert(gps_acq_manager_set_visibility(
|
|
&manager, 1u, true, true, 400));
|
|
assert(gps_acq_manager_set_visibility(
|
|
&manager, 2u, true, false, -100));
|
|
assert(gps_acq_manager_set_visibility(
|
|
&manager, 3u, true, true, 200));
|
|
assert(gps_acq_manager_set_visibility(
|
|
&manager, 4u, false, false, 0));
|
|
gps_acq_manager_set_visibility_mode(&manager, true, 10u);
|
|
|
|
/* Highest predicted elevation wins, independent of numeric PRN. */
|
|
assert(start_next(&manager, 10u, false) == 1u);
|
|
/* Keep the highest predicted-visible satellite absent and retryable. This
|
|
* used to starve every unknown PRN forever. */
|
|
gps_acq_manager_mark_failed(&manager, 1u, 10u);
|
|
assert(start_next(&manager, 11u, false) == 1u);
|
|
gps_acq_manager_mark_failed(&manager, 1u, 11u);
|
|
/* The independent discovery deadline overrides that failing visible
|
|
* candidate and advances through the unknown-orbit queue. */
|
|
assert(start_next(&manager, 12u, false) == 4u);
|
|
gps_acq_manager_mark_tracking(&manager, 4u);
|
|
assert(start_next(&manager, 12u, false) == 1u);
|
|
gps_acq_manager_mark_tracking(&manager, 1u);
|
|
assert(start_next(&manager, 12u, false) == 3u);
|
|
gps_acq_manager_mark_tracking(&manager, 3u);
|
|
|
|
unsigned int ignored;
|
|
assert(!gps_acq_manager_start_next(
|
|
&manager, 69u, &ignored, NULL));
|
|
/* Known below-mask PRNs are not scanned continuously. Exactly one is
|
|
* admitted when the slower stale-model safety probe becomes due. */
|
|
assert(start_next(&manager, 70u, false) == 2u);
|
|
gps_acq_manager_mark_failed(&manager, 2u, 70u);
|
|
assert(!gps_acq_manager_start_next(
|
|
&manager, 71u, &ignored, NULL));
|
|
|
|
/* Removing the position/time model restores full cold-search behavior. */
|
|
gps_acq_manager_set_visibility_mode(&manager, false, 72u);
|
|
assert(start_next(&manager, 72u, false) == 2u);
|
|
}
|
|
|
|
int main(void)
|
|
{
|
|
test_cold_round_robin_and_retry_from_tick_zero();
|
|
test_hot_priority_round_robin_and_cold_fairness();
|
|
test_expired_hot_becomes_cold_and_tracking_is_excluded();
|
|
test_invalid_inputs_are_safe();
|
|
test_failed_hot_search_is_backed_off();
|
|
test_visibility_priority_discovery_and_safety_probe();
|
|
puts("gps acquisition manager tests passed");
|
|
return 0;
|
|
}
|