Merge remote-tracking branch 'bpodrygajlo/fix-gps-offset' into integration_2026_w25

fronthaul library fixes (#150)

three fixes:
- add missing spdx license identifier for some files
- fix utc-to-gps offset
- make fh_timer robust in case of PTP Sync

Reviewed-by: Teodora Vladić <teodora.vladic@openairinterface.org>
Reviewed-By: Merkebu Girmay <merkebu.girmay@openairinterface.org>
This commit is contained in:
Jaroslava Fiedlerova
2026-06-16 19:33:56 +02:00
11 changed files with 217 additions and 8 deletions

View File

@@ -18,7 +18,7 @@ static uint64_t get_gps_ns(void)
{
struct timespec ts;
clock_gettime(CLOCK_REALTIME, &ts);
return ((uint64_t)ts.tv_sec - GPS_EPOCH_OFFSET_UNIX) * NS_PER_SEC + ts.tv_nsec;
return ((uint64_t)ts.tv_sec - GPS_EPOCH_OFFSET_UNIX + GPS_LEAP_SECONDS) * NS_PER_SEC + ts.tv_nsec;
}
void fh_timer_tick(fh_timer_t *timer)
@@ -31,8 +31,17 @@ void fh_timer_tick(fh_timer_t *timer)
typedef __int128_t int128;
uint64_t total_syms_per_ms = 14 << timer->numerology;
// Boundary(ies) crossed
while (now >= timer->target_gps_ns && timer->running) {
// 1. Calculate how many symbols *should* have elapsed up to 'now'
// This catches up if the clock jumped forward, or processes at least 1 symbol.
uint64_t expected_s_abs = (uint64_t)((int128)now * total_syms_per_ms / NS_PER_MS);
// PTP sync backwards adjustment - don't run callbacks until we catch up to the previously expected symbol index
if (expected_s_abs < timer->next_s_abs) {
return;
}
// 2. Drive the loop using the absolute symbol counter, not the volatile 'now' time
while (timer->next_s_abs <= expected_s_abs && timer->running) {
uint64_t s_abs = timer->next_s_abs;
rte_atomic64_set(&timer->s_abs, s_abs);
@@ -43,8 +52,10 @@ void fh_timer_tick(fh_timer_t *timer)
// Prepare for next symbol
timer->next_s_abs++;
timer->target_gps_ns = (uint64_t)((int128)timer->next_s_abs * NS_PER_MS / total_syms_per_ms);
}
// 3. Recalculate the next target timestamp based purely on the new symbol index
timer->target_gps_ns = (uint64_t)((int128)timer->next_s_abs * NS_PER_MS / total_syms_per_ms);
}
}

View File

@@ -23,6 +23,7 @@ typedef void (*fh_timer_cb)(uint64_t s_abs, void *user_data);
#define MAX_FH_TIMER_CBS 8
#define GPS_EPOCH_OFFSET_UNIX 315964800ULL
#define GPS_LEAP_SECONDS 18
typedef struct {
fh_timer_cb fn;

View File

@@ -1,3 +1,5 @@
# SPDX-License-Identifier: LicenseRef-CSSL-1.0
set(DPDK_TEST_ARGS "--no-huge" "--iova-mode=va" "--no-pci")
add_executable(test_fh_timer test_fh_timer.c)
@@ -7,6 +9,13 @@ add_dependencies(tests test_fh_timer)
add_test(NAME test_fh_timer COMMAND test_fh_timer ${DPDK_TEST_ARGS} "--file-prefix=test_fh_timer")
set_property(TEST test_fh_timer PROPERTY LABELS fronthaul)
add_executable(test_fh_timer_jumps test_fh_timer_jumps.c)
target_link_libraries(test_fh_timer_jumps PRIVATE fh_timer ${dpdk_LIBRARIES} "-Wl,--wrap=clock_gettime")
target_include_directories(test_fh_timer_jumps PRIVATE ${dpdk_INCLUDE_DIRS})
add_dependencies(tests test_fh_timer_jumps)
add_test(NAME test_fh_timer_jumps COMMAND test_fh_timer_jumps ${DPDK_TEST_ARGS} "--file-prefix=test_fh_timer_jumps")
set_property(TEST test_fh_timer_jumps PROPERTY LABELS fronthaul)
add_executable(test_fh_timer_drift test_fh_timer_drift.c)
target_link_libraries(test_fh_timer_drift PRIVATE fh_timer ${dpdk_LIBRARIES} m)
target_include_directories(test_fh_timer_drift PRIVATE ${dpdk_INCLUDE_DIRS})

View File

@@ -25,7 +25,7 @@ static uint64_t get_gps_ns(void)
{
struct timespec ts;
clock_gettime(CLOCK_REALTIME, &ts);
return ((uint64_t)ts.tv_sec - GPS_EPOCH_OFFSET_UNIX) * NS_PER_SEC + ts.tv_nsec;
return ((uint64_t)ts.tv_sec - GPS_EPOCH_OFFSET_UNIX + GPS_LEAP_SECONDS) * NS_PER_SEC + ts.tv_nsec;
}
typedef struct {

View File

@@ -19,7 +19,7 @@ static uint64_t get_gps_ns(void)
{
struct timespec ts;
clock_gettime(CLOCK_REALTIME, &ts);
return ((uint64_t)ts.tv_sec - GPS_EPOCH_OFFSET_UNIX) * NS_PER_SEC + ts.tv_nsec;
return ((uint64_t)ts.tv_sec - GPS_EPOCH_OFFSET_UNIX + GPS_LEAP_SECONDS) * NS_PER_SEC + ts.tv_nsec;
}
typedef struct {

View File

@@ -20,7 +20,7 @@ static uint64_t get_gps_ns(void)
{
struct timespec ts;
clock_gettime(CLOCK_REALTIME, &ts);
return ((uint64_t)ts.tv_sec - GPS_EPOCH_OFFSET_UNIX) * NS_PER_SEC + ts.tv_nsec;
return ((uint64_t)ts.tv_sec - GPS_EPOCH_OFFSET_UNIX + GPS_LEAP_SECONDS) * NS_PER_SEC + ts.tv_nsec;
}
typedef struct {

View File

@@ -0,0 +1,179 @@
/*
* SPDX-License-Identifier: LicenseRef-CSSL-1.0
*/
#include "fh_timer.h"
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <unistd.h>
#include <assert.h>
#define GPS_EPOCH_OFFSET_UNIX 315964800ULL
#define NS_PER_SEC 1000000000ULL
// Mock clock state
static struct timespec mock_time = {0, 0};
// Mock implementation of clock_gettime
int __wrap_clock_gettime(clockid_t clk_id, struct timespec *tp)
{
if (clk_id == CLOCK_REALTIME) {
*tp = mock_time;
return 0;
}
extern int __real_clock_gettime(clockid_t clk_id, struct timespec * tp);
return __real_clock_gettime(clk_id, tp);
}
static struct timespec gps_ns_to_timespec(uint64_t gps_ns)
{
struct timespec ts;
uint64_t unix_ns = gps_ns + (GPS_EPOCH_OFFSET_UNIX - GPS_LEAP_SECONDS) * NS_PER_SEC;
ts.tv_sec = unix_ns / NS_PER_SEC;
ts.tv_nsec = unix_ns % NS_PER_SEC;
return ts;
}
#define MAX_CALLBACKS 100
typedef struct {
uint64_t symbols[MAX_CALLBACKS];
int count;
} cb_tracker_t;
static void test_cb(uint64_t s_abs, void *user_data)
{
cb_tracker_t *tracker = (cb_tracker_t *)user_data;
if (tracker->count < MAX_CALLBACKS) {
tracker->symbols[tracker->count++] = s_abs;
}
}
int main(int argc, char **argv)
{
// 1. Initialize timer under mock clock
// Set mock time to UNIX 1718000000 (June 2024)
mock_time.tv_sec = 1718000000;
mock_time.tv_nsec = 0;
fh_timer_t timer;
int numerology = 1; // 30kHz
if (fh_timer_init(&timer, numerology) < 0) {
fprintf(stderr, "Timer init failed\n");
return 1;
}
cb_tracker_t tracker = {0};
if (fh_timer_register_cb(&timer, test_cb, &tracker) < 0) {
fprintf(stderr, "Callback registration failed\n");
return 1;
}
// Verify initial state
// Expected target is 35714 ns past the initial time (Unix 1718000000s)
printf("Initial symbol abs: %lu, expected next_s_abs: %lu\n", rte_atomic64_read(&timer.s_abs), timer.next_s_abs);
assert(timer.next_s_abs == rte_atomic64_read(&timer.s_abs) + 1);
// --- Test Case 1: No tick if target not reached ---
fh_timer_tick(&timer);
if (tracker.count != 0) {
fprintf(stderr, "FAIL: callback triggered before target time\n");
return 1;
}
printf("Test Case 1 passed: No premature callback.\n");
// --- Test Case 2: Normal Tick when target is reached ---
// Advance mock time by 36000 ns (slightly more than 35714 ns symbol duration)
mock_time.tv_nsec = 36000;
fh_timer_tick(&timer);
if (tracker.count != 1) {
fprintf(stderr, "FAIL: callback not triggered when target reached (count=%d)\n", tracker.count);
return 1;
}
if (tracker.symbols[0] != rte_atomic64_read(&timer.s_abs)) {
fprintf(stderr,
"FAIL: callback symbol %lu does not match current timer symbol %lu\n",
tracker.symbols[0],
rte_atomic64_read(&timer.s_abs));
return 1;
}
printf("Test Case 2 passed: Normal tick called successfully.\n");
// --- Test Case 3: Clock Jump Forward (Catching Up) ---
// Reset tracker
tracker.count = 0;
// Jump clock forward by 250,000 ns (roughly 7 symbol durations)
mock_time.tv_nsec = 260000;
fh_timer_tick(&timer);
// We expected it to catch up by running callbacks for all missed symbols
// Let's verify the number of symbols it executed.
printf("After forward jump, processed %d callbacks.\n", tracker.count);
if (tracker.count < 5) {
fprintf(stderr, "FAIL: forward jump did not trigger catch up callbacks (count=%d)\n", tracker.count);
return 1;
}
// Let's verify the sequence of symbols is contiguous
for (int i = 0; i < tracker.count; i++) {
printf("Callback %d: Symbol %lu\n", i, tracker.symbols[i]);
if (i > 0 && tracker.symbols[i] != tracker.symbols[i - 1] + 1) {
fprintf(stderr, "FAIL: non-contiguous symbol sequence in catch up\n");
return 1;
}
}
printf("Test Case 3 passed: Forward jump catch up works.\n");
// --- Test Case 4: Clock Jump Backward (PTP Sync Backward) ---
// Reset tracker
tracker.count = 0;
// Get current state
uint64_t pre_jump_next = timer.next_s_abs;
uint64_t pre_jump_target = timer.target_gps_ns;
printf("Before backward jump: next_s_abs=%lu, target_gps_ns=%lu\n", pre_jump_next, pre_jump_target);
// Jump mock time backward to tv_nsec = 100000
mock_time.tv_nsec = 100000;
fh_timer_tick(&timer);
if (tracker.count != 0) {
fprintf(stderr, "FAIL: callbacks executed during backward clock jump (count=%d)\n", tracker.count);
return 1;
}
// Move time forward a bit but still below target
mock_time.tv_nsec = 200000;
fh_timer_tick(&timer);
if (tracker.count != 0) {
fprintf(stderr, "FAIL: callbacks executed before catching up to previous state (count=%d)\n", tracker.count);
return 1;
}
// Now move time past the old target
mock_time = gps_ns_to_timespec(pre_jump_target + 1000);
printf("Setting mock time past old target: tv_sec=%ld, tv_nsec=%ld\n", mock_time.tv_sec, mock_time.tv_nsec);
fh_timer_tick(&timer);
if (tracker.count == 0) {
fprintf(stderr, "FAIL: timer did not resume after clock caught up to previous target\n");
return 1;
}
printf("Resumed successfully with symbol %lu\n", tracker.symbols[0]);
if (tracker.symbols[0] != pre_jump_next) {
fprintf(stderr, "FAIL: resumed symbol %lu does not match expected %lu\n", tracker.symbols[0], pre_jump_next);
return 1;
}
printf("Test Case 4 passed: Backward jump (PTP sync backward) handling works\n");
// --- Test Case 5: fh_timer_get_current_symbol ---
mock_time.tv_sec = 1718000000;
mock_time.tv_nsec = 500000; // 0.5 ms
uint64_t cur_sym = fh_timer_get_current_symbol(&timer);
typedef __int128_t int128;
uint64_t expected_sym =
(uint64_t)((((int128)1718000000 - GPS_EPOCH_OFFSET_UNIX + GPS_LEAP_SECONDS) * NS_PER_SEC + 500000) * 28 / 1000000);
if (cur_sym != expected_sym) {
fprintf(stderr, "FAIL: fh_timer_get_current_symbol returned %lu, expected %lu\n", cur_sym, expected_sym);
return 1;
}
printf("Test Case 5 passed: fh_timer_get_current_symbol works.\n");
printf("ALL TESTS PASSED SUCCESSFULLY!\n");
return 0;
}

View File

@@ -255,7 +255,7 @@ int oru_fh_get_utc_anchor_point(void *handle, uint32_t *frame, uint32_t *slot, s
uint64_t total_syms_per_sec = (NR_SYMBOLS_PER_SLOT * 1000) << fh->cfg.numerology;
uint64_t ns_per_symbol = 1000000000 / total_syms_per_sec;
uint64_t leftover_syms = absolute_gps_symbol % total_syms_per_sec;
ts->tv_sec = (absolute_gps_symbol / total_syms_per_sec) + GPS_EPOCH_OFFSET_UNIX;
ts->tv_sec = (absolute_gps_symbol / total_syms_per_sec) + GPS_EPOCH_OFFSET_UNIX - GPS_LEAP_SECONDS;
ts->tv_nsec = leftover_syms * ns_per_symbol;
return 0;
}

View File

@@ -1,3 +1,7 @@
/*
* SPDX-License-Identifier: LicenseRef-CSSL-1.0
*/
#include "common/platform_types.h"
#include "xran_pkt_api.h"
#include "oru_packet_processor.h"

View File

@@ -1,3 +1,7 @@
/*
* SPDX-License-Identifier: LicenseRef-CSSL-1.0
*/
#include <stdint.h>
#include <stddef.h>

View File

@@ -1,4 +1,5 @@
#!/bin/bash
# SPDX-License-Identifier: MIT
set -e
# This script downloads a PCAP file, unarchives it, and then runs a test executable.