fix(oru): Fix timestamp generation in O-RU

Instead of guessing what is the absolute timestamp of each symbol returned
from fronthaul, add absolute hyper frame from GPS Epoch to the O-RU. The
O-RU can calculate the absolute sample index from hyper frame reliably
regardless of the frame/slot/symbol ordering.

Add config file for 1x1 O-DU that matches the edited O-RU config.

Signed-off-by: Bartosz Podrygajlo <bartosz.podrygajlo@openairinterface.org>
This commit is contained in:
Bartosz Podrygajlo
2026-05-27 15:11:56 +02:00
parent 71c692d727
commit df5a9facbe
8 changed files with 145 additions and 26 deletions

View File

@@ -223,12 +223,12 @@ void oru_fh_cleanup(void *handle)
free(fh);
}
int oru_fh_tx_read_symbol(void *handle, uint32_t **txdataF, int nb_tx, int *frame, int *slot, int *symbol)
int oru_fh_tx_read_symbol(void *handle, uint32_t **txdataF, int nb_tx, uint64_t *hyper_frame, int *frame, int *slot, int *symbol)
{
if (!handle)
return -1;
oru_fh_t *fh = (oru_fh_t *)handle;
read_dl_iq(fh->packet_processor, txdataF, nb_tx, frame, slot, symbol);
read_dl_iq(fh->packet_processor, txdataF, nb_tx, hyper_frame, frame, slot, symbol);
return 0;
}
@@ -240,7 +240,7 @@ int oru_fh_get_ready_jobs(void *handle)
return get_ready_job_count(fh->packet_processor);
}
int oru_fh_get_utc_anchor_point(void *handle, uint32_t *frame, uint32_t *slot, struct timespec *ts)
int oru_fh_get_utc_anchor_point(void *handle, uint64_t *hyper_frame, uint32_t *frame, uint32_t *slot, struct timespec *ts)
{
if (!handle || !frame || !slot || !ts)
return -1;
@@ -249,6 +249,7 @@ int oru_fh_get_utc_anchor_point(void *handle, uint32_t *frame, uint32_t *slot, s
absolute_gps_symbol -= absolute_gps_symbol % NR_SYMBOLS_PER_SLOT; // Round down to start of current slot
uint64_t absolute_slot = absolute_gps_symbol / NR_SYMBOLS_PER_SLOT;
uint32_t slots_per_frame = 10 << fh->cfg.numerology;
*hyper_frame = (absolute_slot / slots_per_frame) / 1024;
*frame = (absolute_slot / slots_per_frame) % 1024;
*slot = absolute_slot % slots_per_frame;

View File

@@ -74,23 +74,25 @@ int oru_fh_get_ready_jobs(void *handle);
* @param handle Pointer to the fronthaul handle.
* @param txdataF Array of pointers to buffers to store the received frequency-domain IQ samples (per TX antenna).
* @param nb_tx Number of TX antennas.
* @param hyper_frame Absolute GPS hyper-frame number
* @param frame Pointer to store the frame number of the read symbol.
* @param slot Pointer to store the slot number of the read symbol.
* @param symbol Pointer to store the symbol number.
* @return 0 on success or -1 on failure
*/
int oru_fh_tx_read_symbol(void *handle, uint32_t **txdataF, int nb_tx, int *frame, int *slot, int *symbol);
int oru_fh_tx_read_symbol(void *handle, uint32_t **txdataF, int nb_tx, uint64_t *hyper_frame, int *frame, int *slot, int *symbol);
/**
* @brief Get the UTC anchor point mapping between 5G time and system time.
*
* @param handle Pointer to the fronthaul handle.
* @param frame Pointer to store the reference frame number.
* @param hyper_frame Pointer to store the reference hyperframe number (1024 frames each)
* @param slot Pointer to store the reference slot number.
* @param ts Pointer to a timespec structure to store the corresponding system time.
* @return 0 on success, negative on error.
*/
int oru_fh_get_utc_anchor_point(void *handle, uint32_t* frame, uint32_t* slot, struct timespec *ts);
int oru_fh_get_utc_anchor_point(void *handle, uint64_t *hyper_frame, uint32_t* frame, uint32_t* slot, struct timespec *ts);
/**
* @brief Send PRACH symbol data (U-Plane) over the Fronthaul interface.

View File

@@ -739,7 +739,7 @@ static void unpack_iq(c16_t *txdataF, void *iqdata, int start_prb, int num_prb)
}
}
void read_dl_iq(void *context, uint32_t **txdataF, int nb_tx, int *frame, int *slot, int *symbol)
void read_dl_iq(void *context, uint32_t **txdataF, int nb_tx, uint64_t *hyper_frame, int *frame, int *slot, int *symbol)
{
oru_packet_processor_context_t *ctx = (oru_packet_processor_context_t *)context;
if (ctx == NULL)
@@ -754,6 +754,7 @@ void read_dl_iq(void *context, uint32_t **txdataF, int nb_tx, int *frame, int *s
uint64_t absolute_gps_symbol = job->absolute_symbol;
int numerology = ctx->numerology;
int num_symbols_per_frame = NR_NUMBER_OF_SUBFRAMES_PER_FRAME * (1 << numerology) * NR_SYMBOLS_PER_SLOT;
*hyper_frame = (absolute_gps_symbol / num_symbols_per_frame) / 1024;
*frame = (absolute_gps_symbol / num_symbols_per_frame) % 1024;
*slot = (absolute_gps_symbol % num_symbols_per_frame) / NR_SYMBOLS_PER_SLOT;
*symbol = absolute_gps_symbol % NR_SYMBOLS_PER_SLOT;

View File

@@ -68,7 +68,7 @@ void handle_uplane_packet(void *context, void *pkt);
void handle_cplane_packet(void *context, void *pkt);
void print_packet_processor_stats(void *context);
void get_packet_processor_stats(void *context, oru_packet_processor_stats_t *out_stats);
void read_dl_iq(void *context, uint32_t **txdataF, int nb_tx, int *frame, int *slot, int *symbol);
void read_dl_iq(void *context, uint32_t **txdataF, int nb_tx, uint64_t *hyper_frame, int *frame, int *slot, int *symbol);
int get_ready_job_count(void *context);
#ifdef __cplusplus

View File

@@ -11,6 +11,7 @@
#include <unistd.h>
#include "common/config/config_userapi.h"
#include <rte_eal.h>
#include <assert.h>
// OAI Linkage Satisfiers
void exit_function(const char *file, const char *function, const int line, const char *s, const int assertflag)
@@ -84,6 +85,20 @@ int main(int argc, char **argv)
return 1;
}
// Testing UTC Anchor Point and Hyper-frame
printf("Testing UTC Anchor Point and Hyper-frame...\n");
uint64_t hf;
uint32_t f, s;
struct timespec ts;
if (oru_fh_get_utc_anchor_point(handle, &hf, &f, &s, &ts) < 0) {
fprintf(stderr, "FAIL: oru_fh_get_utc_anchor_point failed\n");
oru_fh_cleanup(handle);
return 1;
}
printf("UTC Anchor Point: hf=%lu, f=%u, s=%u, ts=%ld.%09ld\n", hf, f, s, ts.tv_sec, ts.tv_nsec);
assert(f < 1024);
assert(s < (10 << cfg.numerology));
printf("Running live loop for 2 seconds...\n");
uint32_t *txData[1];
txData[0] = malloc(273 * 12 * sizeof(uint32_t));
@@ -96,8 +111,9 @@ int main(int argc, char **argv)
uint64_t target_cycles = start_cycles + (rte_get_timer_hz() / 1000);
while (rte_get_timer_cycles() < target_cycles) {
int f, s, sym;
uint64_t hf;
while (oru_fh_get_ready_jobs(handle) > 0) {
oru_fh_tx_read_symbol(handle, txData, 1, &f, &s, &sym);
oru_fh_tx_read_symbol(handle, txData, 1, &hf, &f, &s, &sym);
}
}
}

View File

@@ -235,8 +235,9 @@ void test_cplane_uplane_match()
txdataF[0] = output_iq;
int frame, slot, symbol;
uint64_t hyper_frame;
do {
read_dl_iq(ctx, txdataF, 1, &frame, &slot, &symbol);
read_dl_iq(ctx, txdataF, 1, &hyper_frame, &frame, &slot, &symbol);
} while (!(frame == (target_sym / num_symbols_per_frame) % 1024 && symbol == target_sym % 14));
assert(symbol == target_sym % 14);
@@ -343,8 +344,9 @@ void test_frame_wrap_around()
txdataF[0] = output_iq;
int frame, slot, symbol;
uint64_t hyper_frame;
do {
read_dl_iq(ctx, txdataF, 1, &frame, &slot, &symbol);
read_dl_iq(ctx, txdataF, 1, &hyper_frame, &frame, &slot, &symbol);
} while (!(frame == (target_sym / num_symbols_per_frame) % 1024 && symbol == target_sym % 14));
assert(frame == (target_sym / num_symbols_per_frame) % 1024);
@@ -456,9 +458,10 @@ void test_cplane_14_symbols()
for (int i = 0; i < 14; i++) {
int frame, slot, symbol;
uint64_t hyper_frame;
uint64_t sym_i = target_sym + i;
do {
read_dl_iq(ctx, txdataF, 1, &frame, &slot, &symbol);
read_dl_iq(ctx, txdataF, 1, &hyper_frame, &frame, &slot, &symbol);
} while (!(frame == (sym_i / num_symbols_per_frame) % 1024 && symbol == sym_i % 14));
assert(frame == (sym_i / num_symbols_per_frame) % 1024);
@@ -566,8 +569,9 @@ void test_other_bw_4ant_prb_offset()
txdataF[3] = out_iq3;
int frame, slot, symbol;
uint64_t hyper_frame;
do {
read_dl_iq(ctx, txdataF, 4, &frame, &slot, &symbol);
read_dl_iq(ctx, txdataF, 4, &hyper_frame, &frame, &slot, &symbol);
} while (!(frame == frameId && symbol == startSymbolId));
// Verify memory contents for each antenna
@@ -1129,6 +1133,102 @@ void test_prach_generation()
printf("PRACH generation passed!\n");
}
void test_hyper_frame_calculation()
{
printf("Testing hyper-frame calculation...\n");
int mu = 1; // 30kHz
int slots_per_subframe = 1 << mu;
void *ctx = init_packet_processor(mu, 273, 200, 400, 100, 300, 2, 2, 0, 0, 5, test_alloc_mbuf, test_send_mbuf, NULL, 1500, 0);
assert(ctx != NULL);
int num_symbols_per_frame = 10 * slots_per_subframe * 14; // 280
// One hyper-frame has 1024 frames. So 1024 * 280 = 286720 symbols.
// Target absolute symbol index: 3 * 286720 + 5 * 280 + 1 * 14 + 7 = 860160 + 1400 + 14 + 7 = 861581.
uint64_t target_sym = 861581;
uint64_t current_sym = target_sym - 7;
handle_absolute_symbol_tick(ctx, current_sym);
// 1. Send C-plane packet for target_sym
struct rte_mbuf *c_mbuf = rte_pktmbuf_alloc(mp);
struct xran_ecpri_hdr *ecpri = (struct xran_ecpri_hdr *)rte_pktmbuf_append(c_mbuf, sizeof(struct xran_ecpri_hdr));
ecpri->ecpri_xtc_id = xran_compose_cid(&g_eaxcid_config, 0, 0, 0, 0);
struct xran_cp_radioapp_section1_header *apphdr =
(struct xran_cp_radioapp_section1_header *)rte_pktmbuf_append(c_mbuf, sizeof(struct xran_cp_radioapp_section1_header));
memset(apphdr, 0, sizeof(*apphdr));
apphdr->cmnhdr.field.dataDirection = XRAN_DIR_DL;
apphdr->cmnhdr.field.payloadVer = XRAN_PAYLOAD_VER;
apphdr->cmnhdr.field.frameId = (target_sym / num_symbols_per_frame) % 256;
int slot_in_frame = (target_sym % num_symbols_per_frame) / 14;
apphdr->cmnhdr.field.subframeId = slot_in_frame / slots_per_subframe;
apphdr->cmnhdr.field.slotId = slot_in_frame % slots_per_subframe;
apphdr->cmnhdr.field.startSymbolId = target_sym % 14;
apphdr->cmnhdr.sectionType = XRAN_CP_SECTIONTYPE_1;
apphdr->cmnhdr.field.all_bits = rte_cpu_to_be_32(apphdr->cmnhdr.field.all_bits);
struct xran_cp_radioapp_section1 *sec =
(struct xran_cp_radioapp_section1 *)rte_pktmbuf_append(c_mbuf, sizeof(struct xran_cp_radioapp_section1));
memset(sec, 0, sizeof(*sec));
sec->hdr.u.s1.numSymbol = 1;
sec->hdr.u1.common.numPrbc = 1;
*((uint64_t *)sec) = rte_be_to_cpu_64(*((uint64_t *)sec));
handle_cplane_packet(ctx, c_mbuf);
// 2. Send U-plane packet for target_sym
current_sym += 3;
handle_absolute_symbol_tick(ctx, current_sym);
struct rte_mbuf *u_mbuf = rte_pktmbuf_alloc(mp);
struct xran_ecpri_hdr *u_ecpri = (struct xran_ecpri_hdr *)rte_pktmbuf_append(u_mbuf, sizeof(struct xran_ecpri_hdr));
u_ecpri->ecpri_xtc_id = xran_compose_cid(&g_eaxcid_config, 0, 0, 0, 0);
struct radio_app_common_hdr *u_app =
(struct radio_app_common_hdr *)rte_pktmbuf_append(u_mbuf, sizeof(struct radio_app_common_hdr));
u_app->frame_id = (target_sym / num_symbols_per_frame) % 256;
u_app->sf_slot_sym.subframe_id = slot_in_frame / slots_per_subframe;
u_app->sf_slot_sym.slot_id = slot_in_frame % slots_per_subframe;
u_app->sf_slot_sym.symb_id = target_sym % 14;
u_app->sf_slot_sym.value = rte_cpu_to_be_16(u_app->sf_slot_sym.value);
struct data_section_hdr *u_data = (struct data_section_hdr *)rte_pktmbuf_append(u_mbuf, sizeof(struct data_section_hdr));
u_data->fields.num_prbu = 1;
u_data->fields.start_prbu = 0;
u_data->fields.sect_id = 0;
u_data->fields.all_bits = rte_cpu_to_be_32(u_data->fields.all_bits);
// IQ Data
uint16_t *iq = (uint16_t *)rte_pktmbuf_append(u_mbuf, 1 * 12 * 4);
assert(iq != NULL);
iq[0] = 0xAAAA;
handle_uplane_packet(ctx, u_mbuf);
// 3. Advance to trigger window expiry and job completion
current_sym += 10;
handle_absolute_symbol_tick(ctx, current_sym);
uint32_t *txdataF[1] = {0};
uint32_t output_iq[273 * 12] = {0};
txdataF[0] = output_iq;
int frame, slot, symbol;
uint64_t hyper_frame = 0xFFFFFFFF;
do {
read_dl_iq(ctx, txdataF, 1, &hyper_frame, &frame, &slot, &symbol);
} while (!(frame == (target_sym / num_symbols_per_frame) % 1024 && symbol == target_sym % 14));
assert(hyper_frame == 3);
assert(frame == 5);
assert(slot == 1);
assert(symbol == 7);
cleanup_packet_processor(ctx);
printf("Hyper-frame calculation test passed!\n");
}
int main(int argc, char **argv)
{
setup_dpdk(argc, argv);
@@ -1155,6 +1255,8 @@ int main(int argc, char **argv)
usleep(10000);
test_prach_generation();
usleep(10000);
test_hyper_frame_calculation();
usleep(10000);
printf("All tests passed!\n");
return 0;

View File

@@ -205,8 +205,9 @@ int main(int argc, char *argv[])
// Drain ready jobs to prevent ring overflow
int f, sl, sy;
uint64_t hf;
while (get_ready_job_count(ctx) > 0) {
read_dl_iq(ctx, txdataF, MAX_ANTENNAS, &f, &sl, &sy);
read_dl_iq(ctx, txdataF, MAX_ANTENNAS, &hf, &f, &sl, &sy);
}
}
last_tick_sym = current_sym;
@@ -238,8 +239,9 @@ int main(int argc, char *argv[])
// Drain ready jobs to prevent ring overflow during flush
int f, sl, sy;
uint64_t hf;
while (get_ready_job_count(ctx) > 0) {
read_dl_iq(ctx, txdataF, MAX_ANTENNAS, &f, &sl, &sy);
read_dl_iq(ctx, txdataF, MAX_ANTENNAS, &hf, &f, &sl, &sy);
}
}