Fix GPS nav epoch recovery and document debug levels

This commit is contained in:
2026-07-23 20:48:42 +03:00
parent 516016efb9
commit fc242b2883
7 changed files with 252 additions and 38 deletions
+7 -2
View File
@@ -2,7 +2,8 @@ CC ?= gcc
CFLAGS ?= -O2 -std=c11 -Wall -Wextra -Werror
ROOT := ..
TARGETS := test_imu_calibration test_gps_nav_epoch test_plic_warm_reset
TARGETS := test_imu_calibration test_gps_nav_epoch \
test_gps_nav_epoch_coalescing test_plic_warm_reset
.PHONY: all run clean
@@ -14,13 +15,17 @@ test_imu_calibration: test_imu_calibration.c $(ROOT)/src/imu/imu_calibration.c
test_gps_nav_epoch: test_gps_nav_epoch.c $(ROOT)/src/gps/gps_nav_epoch.c
$(CC) $(CFLAGS) -I$(ROOT)/header $^ -lm -o $@
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_plic_warm_reset: test_plic_warm_reset.c $(ROOT)/../bsp/drivers/plic.c
$(CC) $(CFLAGS) -Ifake_bsp -I$(ROOT)/../bsp/include $^ -o $@
run: $(TARGETS)
./test_imu_calibration
./test_gps_nav_epoch
./test_gps_nav_epoch_coalescing
./test_plic_warm_reset
clean:
rm -f $(TARGETS)
rm -f $(TARGETS) test_gps_nav_epoch_coalescing_repro
@@ -97,10 +97,12 @@ static void assembly_handles_duplicates_and_epoch_order(void)
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_FUTURE,
"future epoch was mixed into an incomplete epoch");
CHECK(assembly.received_mask == 0x01u,
"rejected interrupts changed the received mask");
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)
@@ -0,0 +1,174 @@
#include "gps/gps_nav_epoch.h"
#include <stdbool.h>
#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;
static int failures;
#define CHECK(condition, message) do { \
if(!(condition)) { \
fprintf(stderr, "FAIL: %s\n", message); \
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],
gps_nav_epoch_assembly_t* assembly,
unsigned int channel,
unsigned int* coalesced_pulses)
{
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);
}
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)
{
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);
CHECK((result == GPS_NAV_EPOCH_EVENT_COMPLETE) ==
(channel == CHANNEL_COUNT - 1u),
"healthy epoch did not complete on its final channel");
}
}
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");
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);
}
return failures == 0 ? 0 : 1;
}