mirror of
https://gitlab.eurecom.fr/oai/openairinterface5g.git
synced 2026-07-13 04:30:28 +00:00
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:
106
fronthaul/README.md
Normal file
106
fronthaul/README.md
Normal 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.
|
||||||
@@ -9,6 +9,11 @@ add_library(oru_packet_processor oru_packet_processor.c)
|
|||||||
target_include_directories(oru_packet_processor PUBLIC ./)
|
target_include_directories(oru_packet_processor PUBLIC ./)
|
||||||
target_link_libraries(oru_packet_processor PUBLIC ${dpdk_LIBRARIES} PRIVATE xran_pkt UTIL)
|
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)
|
if (ENABLE_TESTS)
|
||||||
add_subdirectory(tests)
|
add_subdirectory(tests)
|
||||||
endif()
|
endif()
|
||||||
|
|||||||
305
fronthaul/oru/oru_fh.c
Normal file
305
fronthaul/oru/oru_fh.c
Normal 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
134
fronthaul/oru/oru_fh.h
Normal 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
|
||||||
@@ -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)
|
set_property(TEST test_oru_pcap_frag PROPERTY LABELS fronthaul)
|
||||||
|
|
||||||
add_dependencies(tests test_oru_pcap)
|
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)
|
||||||
|
|||||||
113
fronthaul/oru/tests/test_oru_fh.c
Normal file
113
fronthaul/oru/tests/test_oru_fh.c
Normal 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;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user