feat(fronthaul): Add oru fronthaul library

Add an O-RU centered fronthaul library that includes components
from the fronthaul directory.

Signed-off-by: Bartosz Podrygajlo <bartosz.podrygajlo@openairinterface.org> and assisted-by Gemini
This commit is contained in:
Bartosz Podrygajlo
2026-05-23 06:09:09 +02:00
parent 16850a3794
commit acf7e6d91d
6 changed files with 670 additions and 0 deletions

106
fronthaul/README.md Normal file
View File

@@ -0,0 +1,106 @@
# Fronthaul Library
Fronthaul stack for 7.2x split using DPDK. Currently supports O-RU only; O-DU support is not implemented.
## Architecture overview
### Software component diagram
```mermaid
graph TD
O-RU
subgraph integration ["Fronthaul API"]
oru_fh["oru_fh"]
end
subgraph internals ["O-RU fronthaul internals"]
oru_io["oru_io"]
oru_pkt["oru_packet_processor"]
end
subgraph fronthaul_core ["Fronthaul Core (core/)"]
direction LR
fh_rx["fh_recv (RX)"]
fh_tx["fh_send (TX)"]
fh_timer["fh_timer (Clock)"]
fh_rx --> fh_tx
fh_tx --> fh_timer
end
subgraph protocol ["Protocol Layer (xran_pkt/)"]
xran_api["xran_pkt_api"]
end
dpdk[("DPDK / NIC")]
O-RU --> oru_fh
oru_fh --> oru_pkt
oru_fh --> oru_io
oru_io --> oru_pkt
oru_io --> fh_rx
oru_io --> fh_tx
oru_pkt --> xran_api
fh_rx --> xran_api
fh_tx --> xran_api
fh_rx --> dpdk
fh_tx --> dpdk
fh_timer --> dpdk
```
### Threading
The library uses a simplified threading model designed for low-latency synchronization:
- **Single Worker Thread**: A single worker thread (pinned to `rx_core`) handles both packet reception and timing.
The `fh_timer` is driven by a `tick()` function called within the RX poll loop. This ensures that timing events are
processed with minimal context-switching overhead.
- **Immediate Transmission**: Packet transmission happens immediately when the application calls the send function.
#### Sequence Diagram
```mermaid
sequenceDiagram
participant HW as Hardware / NIC
participant FW as Fronthaul Worker (lcore)
participant PP as Packet Processor
participant DL as DL App Threads (Multi)
participant UL as UL App Threads (Multi)
Note over FW: Continuous Loop (fh_recv_run)
FW->>HW: Poll for Packets
HW-->>FW: eCPRI Packets (C/U-Plane)
FW->>PP: handle_packet()
alt [All IQ Samples Received]
PP-->>DL: Enqueue Job (Multi-Consumer Ring)
end
FW->>FW: fh_timer_tick()
FW->>PP: handle_symbol_tick()
alt [Window Expired (Handling Missing Packets)]
PP-->>DL: Enqueue Job (Multi-Consumer Ring)
end
Note over DL: Parallel consumption of DL IQ data
DL->>PP: read_dl_iq()
PP-->>DL: IQ Data (Blocking MC Dequeue)
Note over UL: Parallel production of UL packets
UL->>PP: write_ul_iq()
PP->>HW: send_mbuf() (Direct Parallel TX)
Note over HW,FW: rte_spinlock ensures thread-safe TX burst
```
## Core API (`oru_fh.h`)
- `oru_fh_init`: Initializes DPDK EAL, configures ports, and sets up memory pools based on PRB requirements.
- `oru_fh_start`: Launches the worker thread and starts the fronthaul processing loop.
- `oru_fh_stop`: Gracefully stops the worker thread and releases resources.
- `oru_fh_tx_read_symbol`: Intended for reading UPlane data for a specific slot/symbol in a synchronized manner.
- `oru_fh_rx_send_pusch`/`prach`: Sends IQ via UPlane to DU
## Configuration
See `oru_fh_config_t` for details
## Tools
### Packet Dissector - `xran_pcap_dump`
A tool used to verify that packets are correctly formed. It reads PCAP files and dissects them using the `xran_pkt`
library to ensure compatibility with O-RU/O-DU implementations.

View File

@@ -9,6 +9,11 @@ add_library(oru_packet_processor oru_packet_processor.c)
target_include_directories(oru_packet_processor PUBLIC ./)
target_link_libraries(oru_packet_processor PUBLIC ${dpdk_LIBRARIES} PRIVATE xran_pkt UTIL)
add_library(oru_fh oru_fh.c)
target_include_directories(oru_fh PUBLIC ./)
target_link_libraries(oru_fh PUBLIC fh_core oru_io xran_pkt oru_packet_processor ${dpdk_LIBRARIES})
target_include_directories(oru_fh PRIVATE ${dpdk_INCLUDE_DIRS})
if (ENABLE_TESTS)
add_subdirectory(tests)
endif()

305
fronthaul/oru/oru_fh.c Normal file
View File

@@ -0,0 +1,305 @@
/*
* SPDX-License-Identifier: LicenseRef-CSSL-1.0
*/
#define _GNU_SOURCE
#include "oru_fh.h"
#include "PHY/CODING/nrPolar_tools/nr_polar_defs.h"
#include "assertions.h"
#include "common/utils/utils.h"
#include "fh_timer.h"
#include "nr/nr_common.h"
#include "oru_io.h"
#include "platform_types.h"
#include "xran_pkt_api.h"
#include <rte_eal.h>
#include <rte_ethdev.h>
#include "oru_packet_processor.h"
#include "log.h"
#include <sched.h>
typedef struct {
oru_io_t io;
oru_io_config_t io_config;
oru_fh_config_t cfg;
void *packet_processor;
} oru_fh_t;
static void rx_cb(struct rte_mbuf **pkts, uint16_t n, void *user_data)
{
oru_fh_t *fh = (oru_fh_t *)user_data;
for (int i = 0; i < n; i++) {
struct rte_mbuf *pkt = pkts[i];
struct rte_ether_hdr *eth = rte_pktmbuf_mtod(pkt, struct rte_ether_hdr *);
uint16_t eth_type = rte_be_to_cpu_16(eth->ether_type);
size_t hdr_len = sizeof(struct rte_ether_hdr);
if (eth_type == 0x8100) { // VLAN
struct rte_vlan_hdr *vlan = (struct rte_vlan_hdr *)(eth + 1);
eth_type = rte_be_to_cpu_16(vlan->eth_proto);
hdr_len += sizeof(struct rte_vlan_hdr);
}
if (eth_type == ECPRI_ETHER_TYPE) {
rte_pktmbuf_adj(pkt, hdr_len);
struct xran_ecpri_hdr *ecpri_hdr = rte_pktmbuf_mtod(pkt, struct xran_ecpri_hdr *);
switch (ecpri_hdr->cmnhdr.bits.ecpri_mesg_type) {
case ECPRI_IQ_DATA:
handle_uplane_packet(fh->packet_processor, pkt);
break;
case ECPRI_RT_CONTROL_DATA:
handle_cplane_packet(fh->packet_processor, pkt);
break;
default:
rte_pktmbuf_free(pkt);
break;
}
} else {
rte_pktmbuf_free(pkt);
}
}
}
static void timer_cb(uint64_t s_abs, void *user_data)
{
oru_fh_t *fh = (oru_fh_t *)user_data;
handle_absolute_symbol_tick(fh->packet_processor, s_abs);
}
void *oru_fh_init(oru_fh_config_t *cfg)
{
AssertFatal(cfg->enable_compression == false, "IQ compression not supported\n");
char *argv[64];
int argc = 0;
char vdev_args[MAX_RU_PORTS][64];
int vdev_idx = 0;
argv[argc++] = "oru_fh";
for (int i = 0; i < cfg->dpdk_conf.num_dpdk_devices; i++) {
if (strchr(cfg->dpdk_conf.dpdk_devices[i], ':')) {
argv[argc++] = "-a";
argv[argc++] = cfg->dpdk_conf.dpdk_devices[i];
} else {
snprintf(vdev_args[vdev_idx], sizeof(vdev_args[vdev_idx]), "--vdev=%s", cfg->dpdk_conf.dpdk_devices[i]);
argv[argc++] = vdev_args[vdev_idx++];
}
}
char lcores[32];
cpu_set_t cpuset;
CPU_ZERO(&cpuset);
if (sched_getaffinity(0, sizeof(cpu_set_t), &cpuset) == -1) {
LOG_E(HW, "sched_getaffinity error\n");
return NULL;
}
// Select the system core. This is needed to corrently dispatch DPDK workers
// The core will be returned to the applicaton after rte_eal_init
int sys_core = -1;
long num_cpus = sysconf(_SC_NPROCESSORS_ONLN);
for (int i = 0; i < num_cpus; i++) {
if (CPU_ISSET(i, &cpuset)) {
if (i != cfg->rx_core) {
sys_core = i;
break;
}
}
}
char main_lcore_arg[32];
if (sys_core != -1) {
snprintf(lcores, sizeof(lcores), "%d,%d", sys_core, cfg->rx_core);
argv[argc++] = "-l";
argv[argc++] = lcores;
// DPDK defaults to the lowest lcore ID as the main lcore. Explicitly set it so rx_core is always a worker lcore.
snprintf(main_lcore_arg, sizeof(main_lcore_arg), "--main-lcore=%d", sys_core);
argv[argc++] = main_lcore_arg;
}
if (cfg->dpdk_conf.extra_eal_args) {
for (int i = 0; i < cfg->dpdk_conf.num_extra_eal_args; i++) {
argv[argc++] = cfg->dpdk_conf.extra_eal_args[i];
}
}
LOG_I(HW, "EAL init: %d args\n", argc);
for (int i = 0; i < argc; i++) {
LOG_I(HW, "EAL arg %d: %s\n", i, argv[i]);
}
int ret = rte_eal_init(argc, argv);
if (ret < 0) {
if (rte_errno == EALREADY) {
LOG_I(HW, "DPDK EAL already initialized\n");
} else {
if (sys_core == -1)
LOG_E(HW, "Need to have at least 2 cores to start fronthaul library\n");
else
LOG_E(HW, "DPDK EAL initialization failed: %s\n", rte_strerror(rte_errno));
return NULL;
}
} else if (sys_core == -1) {
LOG_E(HW, "Need to have at least 2 cores to start fronthaul library\n");
return NULL;
}
if (sys_core != -1) {
// Restore affinity to all original cores except rx_core, which DPDK owns
CPU_CLR(cfg->rx_core, &cpuset);
if (pthread_setaffinity_np(pthread_self(), sizeof(cpuset), &cpuset) != 0) {
LOG_E(HW, "pthread_setaffinity_np error\n");
return NULL;
}
}
oru_fh_t *fh = (oru_fh_t *)malloc_or_fail(sizeof(oru_fh_t));
memset(fh, 0, sizeof(oru_fh_t));
fh->cfg = *cfg;
fh->io_config.numerology = cfg->numerology;
fh->io_config.num_ports = cfg->dpdk_conf.num_dpdk_devices;
for (int i = 0; i < cfg->dpdk_conf.num_dpdk_devices; i++) {
if (rte_eth_dev_get_port_by_name(cfg->dpdk_conf.dpdk_devices[i], &fh->io_config.port_ids[i]) < 0) {
fprintf(stderr, "DPDK device %s not found\n", cfg->dpdk_conf.dpdk_devices[i]);
free(fh);
return NULL;
}
}
fh->io_config.num_macs = cfg->num_du_mac_addrs;
for (int i = 0; i < cfg->num_du_mac_addrs; i++) {
if (rte_ether_unformat_addr(cfg->du_mac_addrs[i], &fh->io_config.du_macs[i]) < 0) {
fprintf(stderr, "Invalid MAC address: %s\n", cfg->du_mac_addrs[i]);
free(fh);
return NULL;
}
}
fh->io_config.rx_cb = rx_cb;
fh->io_config.rx_user_data = fh;
fh->io_config.timer_cb = timer_cb;
fh->io_config.timer_user_data = fh;
// DPDK Port and Mbuf Pool configuration
uint16_t header_size = sizeof(struct xran_up_pkt_hdr);
uint16_t payload_size = cfg->num_prbs * 12 * 4; // 16-bit IQ (4 bytes per sample)
uint16_t required_mtu = header_size + payload_size;
printf("ORU_FH: PRBs %u -> Required MTU %u (Limit %u)\n", cfg->num_prbs, required_mtu, cfg->mtu);
fh->io_config.mbuf_data_room = required_mtu + RTE_ETHER_HDR_LEN + RTE_ETHER_CRC_LEN + RTE_PKTMBUF_HEADROOM;
fh->io_config.mbuf_count = 8192; // Default pool size
fh->io_config.mtu = cfg->mtu;
if (oru_io_init(&fh->io, &fh->io_config) < 0) {
free(fh);
return NULL;
}
fh->packet_processor = init_packet_processor(cfg->numerology,
cfg->num_prbs,
cfg->T2a_cp_min_uS,
cfg->T2a_cp_max_uS,
cfg->T2a_up_min_uS,
cfg->T2a_up_max_uS,
cfg->tdd_pattern.num_dl_slots,
cfg->tdd_pattern.num_ul_slots,
cfg->tdd_pattern.num_dl_symbols,
cfg->tdd_pattern.num_ul_symbols,
cfg->tdd_pattern.tdd_pattern_length_slots,
(alloc_func_t)oru_io_get_sendbuf,
(send_func_t)oru_io_send_uplane,
&fh->io,
cfg->mtu,
cfg->prach_eaxc_offset);
return fh;
}
void oru_fh_cleanup(void *handle)
{
if (!handle)
return;
oru_fh_t *fh = (oru_fh_t *)handle;
if (fh->packet_processor) {
cleanup_packet_processor(fh->packet_processor);
}
oru_io_cleanup(&fh->io);
free(fh);
}
int oru_fh_tx_read_symbol(void *handle, uint32_t **txdataF, int nb_tx, 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);
return 0;
}
int oru_fh_get_ready_jobs(void *handle)
{
if (!handle)
return 0;
oru_fh_t *fh = (oru_fh_t *)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)
{
if (!handle || !frame || !slot || !ts)
return -1;
oru_fh_t *fh = (oru_fh_t *)handle;
uint64_t absolute_gps_symbol = fh_timer_get_current_symbol(&fh->io.timer);
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;
*frame = (absolute_slot / slots_per_frame) % 1024;
*slot = absolute_slot % slots_per_frame;
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_nsec = leftover_syms * ns_per_symbol;
return 0;
}
void oru_fh_rx_send_prach(void *handle, uint32_t **prachF, int nb_rx, int frame, int slot, int symbol)
{
oru_fh_t *fh = (oru_fh_t *)handle;
AssertFatal(fh, "Invalid handle\n");
write_prach_iq(fh->packet_processor, prachF, nb_rx, frame, slot, symbol);
}
void oru_fh_rx_send_pusch(void *handle, uint32_t **puschF, int nb_rx, int frame, int slot, int symbol)
{
oru_fh_t *fh = (oru_fh_t *)handle;
AssertFatal(fh, "Invalid handle\n");
write_ul_iq(fh->packet_processor, puschF, nb_rx, frame, slot, symbol);
}
int oru_fh_start(void *handle)
{
if (!handle)
return -1;
oru_fh_t *fh = (oru_fh_t *)handle;
return oru_io_run(&fh->io, fh->cfg.rx_core);
}
void oru_fh_stop(void *handle)
{
if (!handle)
return;
oru_fh_t *fh = (oru_fh_t *)handle;
print_packet_processor_stats(fh->packet_processor);
oru_io_stop(&fh->io);
// Wait for worker thread to finish
if (fh->cfg.rx_core != -1 && fh->cfg.rx_core != RTE_MAX_LCORE)
rte_eal_wait_lcore(fh->cfg.rx_core);
}
void oru_fh_print_stats(void *handle)
{
if (!handle)
return;
oru_fh_t *fh = (oru_fh_t *)handle;
print_packet_processor_stats(fh->packet_processor);
}

134
fronthaul/oru/oru_fh.h Normal file
View File

@@ -0,0 +1,134 @@
/*
* SPDX-License-Identifier: LicenseRef-CSSL-1.0
*/
#ifndef __OAIORAN_RU_H__
#define __OAIORAN_RU_H__
#include "oru_io.h"
#include <stdint.h>
typedef struct {
char *dpdk_devices[MAX_RU_PORTS];
int num_dpdk_devices;
char **extra_eal_args;
int num_extra_eal_args;
} oru_fh_dpdk_config_t;
typedef struct {
uint32_t num_ul_slots;
uint32_t num_dl_slots;
uint32_t num_ul_symbols;
uint32_t num_dl_symbols;
uint32_t tdd_pattern_length_slots;
} oru_fh_tdd_pattern_t;
typedef struct {
// Compression configuration
bool enable_compression;
int numerology;
uint16_t num_prbs;
// MTU configuration
uint16_t mtu;
// DU mac addreses, used to prepare Ethernet headers
char *du_mac_addrs[MAX_RU_PORTS];
int num_du_mac_addrs;
// DPDK configuration
oru_fh_dpdk_config_t dpdk_conf;
// The main core for RX processing
int rx_core;
uint32_t T2a_up_min_uS;
uint32_t T2a_up_max_uS;
uint32_t T2a_cp_min_uS;
uint32_t T2a_cp_max_uS;
oru_fh_tdd_pattern_t tdd_pattern;
int prach_eaxc_offset;
} oru_fh_config_t;
/**
* @brief Initialize the O-RU Fronthaul interface.
*
* @param cfg Pointer to the fronthaul configuration structure.
* @return void* Pointer to the initialized fronthaul handle, or NULL on failure.
*/
void *oru_fh_init(oru_fh_config_t *cfg);
/**
* @brief Clean up and release resources used by the O-RU Fronthaul interface.
*
* @param handle Pointer to the fronthaul handle.
*/
void oru_fh_cleanup(void *handle);
/**
* @brief Get the number of DL jobs ready to be processed.
*
* @param handle Pointer to the fronthaul handle.
* @return int Number of ready jobs.
*/
int oru_fh_get_ready_jobs(void *handle);
/**
* @brief Read downlink symbol data (IQ samples) from the Fronthaul interface.
*
* @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 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);
/**
* @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 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);
/**
* @brief Send PRACH symbol data (U-Plane) over the Fronthaul interface.
*
* @param handle Pointer to the fronthaul handle.
* @param prachF Array of pointers to buffers containing the frequency-domain PRACH IQ samples.
* @param nb_rx Number of RX antennas.
* @param frame Target frame number.
* @param slot Target slot number.
* @param symbol Target symbol number.
*/
void oru_fh_rx_send_prach(void *handle, uint32_t **prachF, int nb_rx, int frame, int slot, int symbol);
/**
* @brief Send PUSCH symbol data (U-Plane) over the Fronthaul interface.
*
* @param handle Pointer to the fronthaul handle.
* @param puschF Array of pointers to buffers containing the frequency-domain PUSCH IQ samples.
* @param nb_rx Number of RX antennas.
* @param frame Target frame number.
* @param slot Target slot number.
* @param symbol Target symbol number.
*/
void oru_fh_rx_send_pusch(void *handle, uint32_t **puschF, int nb_rx, int frame, int slot, int symbol);
/**
* @brief Start the O-RU Fronthaul processing threads and loops.
*
* @param handle Pointer to the fronthaul handle.
* @return int 0 on success, negative on error.
*/
int oru_fh_start(void *handle);
/**
* @brief Stop the O-RU Fronthaul processing.
*
* @param handle Pointer to the fronthaul handle.
*/
void oru_fh_stop(void *handle);
void oru_fh_print_stats(void *handle);
#endif

View File

@@ -32,3 +32,10 @@ add_test(NAME test_oru_pcap_frag COMMAND ${CMAKE_CURRENT_BINARY_DIR}/run_oru_pca
set_property(TEST test_oru_pcap_frag PROPERTY LABELS fronthaul)
add_dependencies(tests test_oru_pcap)
add_executable(test_oru_fh test_oru_fh.c)
target_link_libraries(test_oru_fh PRIVATE oru_fh xran_pkt ${dpdk_LIBRARIES})
target_include_directories(test_oru_fh PRIVATE ${dpdk_INCLUDE_DIRS})
add_dependencies(tests test_oru_fh)
add_test(NAME test_oru_fh COMMAND test_oru_fh ${DPDK_TEST_ARGS} "--file-prefix=test_oru_fh" "--vdev=net_null0" "--vdev=net_null1")
set_property(TEST test_oru_fh PROPERTY LABELS fronthaul)

View File

@@ -0,0 +1,113 @@
/*
* SPDX-License-Identifier: LicenseRef-CSSL-1.0
*/
#define _GNU_SOURCE
#include <sched.h>
#include <unistd.h>
#include "oru_fh.h"
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include "common/config/config_userapi.h"
#include <rte_eal.h>
// OAI Linkage Satisfiers
void exit_function(const char *file, const char *function, const int line, const char *s, const int assertflag)
{
fprintf(stderr, "Error at %s:%s:%d - %s\n", file, function, line, s ? s : "None");
exit(1);
}
configmodule_interface_t *uniqCfg = NULL;
#include "log.h"
static int get_available_core(void)
{
cpu_set_t cpuset;
CPU_ZERO(&cpuset);
if (sched_getaffinity(0, sizeof(cpu_set_t), &cpuset) == -1) {
return -1;
}
long num_cpus = sysconf(_SC_NPROCESSORS_ONLN);
for (int i = 0; i < num_cpus; i++) {
if (CPU_ISSET(i, &cpuset)) {
return i;
}
}
return -1;
}
int main(int argc, char **argv)
{
printf("--- Running ORU_FH Initialization Test ---\n");
logInit();
g_log->log_component[HW].level = OAILOG_DEBUG;
char *extra_eal_args[] = {"--no-shconf",
"--no-huge",
"--no-pci",
"--iova-mode=va",
"--log-level=lib.eal:7",
"-m",
"1024",
"--file-prefix=test_oru_fh"};
oru_fh_config_t cfg = {.numerology = 1, // 30kHz
.du_mac_addrs = {"00:11:22:33:44:55"},
.num_du_mac_addrs = 1,
.num_prbs = 106,
.mtu = 1500,
.dpdk_conf = {.num_dpdk_devices = 1,
.dpdk_devices = {"net_null0"},
.extra_eal_args = extra_eal_args,
.num_extra_eal_args = 8},
.rx_core = get_available_core(),
.tdd_pattern = {.num_dl_slots = 3,
.num_ul_slots = 1,
.num_dl_symbols = 6,
.num_ul_symbols = 4,
.tdd_pattern_length_slots = 5}};
void *handle = oru_fh_init(&cfg);
if (!handle) {
fprintf(stderr, "FAIL: oru_fh_init failed\n");
return 1;
}
printf("SUCCESS: oru_fh initialized handle: %p\n", handle);
// Testing Lifecycle (Start/Stop)
printf("Starting ORU_FH...\n");
if (oru_fh_start(handle) < 0) {
fprintf(stderr, "FAIL: oru_fh_start failed\n");
oru_fh_cleanup(handle);
return 1;
}
printf("Running live loop for 2 seconds...\n");
uint32_t *txData[1];
txData[0] = malloc(273 * 12 * sizeof(uint32_t));
for (int i = 0; i < 2000; i++) {
if (i % 400 == 0) {
oru_fh_rx_send_pusch(handle, txData, 1, 0, i / 400, 0);
}
uint64_t start_cycles = rte_get_timer_cycles();
uint64_t target_cycles = start_cycles + (rte_get_timer_hz() / 1000);
while (rte_get_timer_cycles() < target_cycles) {
int f, s, sym;
while (oru_fh_get_ready_jobs(handle) > 0) {
oru_fh_tx_read_symbol(handle, txData, 1, &f, &s, &sym);
}
}
}
free(txData[0]);
printf("Stopping ORU_FH...\n");
oru_fh_stop(handle);
printf("Cleaning up handle...\n");
oru_fh_cleanup(handle);
printf("--- ORU_FH Test Complete ---\n");
return 0;
}