The LITEON FlexFi Indoor (model FF-RFI078I4, firmware 02.00.10) is a 4-transmit / 4-receive
(4T4R) O-RAN Radio Unit (O-RU) with no management plane (M-Plane) at all. It runs from
settings stored in the radio, which you set over its command-line interface (CLI). On COSMOS it
carries a 100 MHz band-n78 carrier centred on 3750 MHz. Both open-source O-RAN Distributed Unit
(O-DU) stacks drive it end to end with a commercial Quectel modem: OCUDU (srsRAN) and
Duranta (OAI). Both stacks need the same radio state, so this is the simpler of the two
commercial O-RUs to share between stacks.
This page is the single place where the current best configuration for this radio lives. The
O-RU hardware page covers the whole pool, power control, the shared RF
space and the golden-state rule. The tutorials walk through a full run:
OCUDU over a commercial O-RU and
Duranta over a commercial O-RU.
Other O-RUs: fleet overview · Benetel RAN650 · LiteOn FlexFi · Foxconn RPQN-7801 · Software O-RUs
| Item | Value |
|---|---|
| Vendor / model | LITEON FlexFi Indoor, FF-RFI078I4, product name FlexFi-RU |
| Band / range | n78 (3300–3800 MHz) on COSMOS. The fleet table also lists n79 (4400–5000 MHz) for the FlexFi family; n79 is not used on COSMOS |
| Bandwidth / PRB / SCS | 100 MHz, 273 physical resource blocks (PRB), 30 kHz subcarrier spacing (SCS). The RU stores Band Width=100000000, subcarrier spacing=1 |
| Duplex / TDD | Time-division duplex (TDD). The RU stores no TDD pattern; it follows the DU's control plane. Duranta uses DDDSU (2.5 ms); OCUDU uses srsRAN's built-in default (see Carrier) |
| Antennas | 4T4R (show dl-ul-layer reports 4T4R) |
| Max TX power | not recorded in COSMOS sources |
| Firmware | 02.00.10. OAI's supported-RU table lists "LiteON RU FR1 — 02.00.10"; srsRAN's LiteOn guide needs ≥ 02.00.09 |
| Fronthaul split | O-RAN 7.2x, eCPRI over Ethernet |
| Fronthaul VLAN | C-plane and U-plane on VLAN 4 (Control and User Plane vlan=4); M Plane vlan=0 |
| MTU | jumbo frames: RU jumboframe=0x00000001 (volatile, resets on every boot); Duranta mtu = 9216 |
| Compression | block floating point (BFP), 9 bits (Compression Bit=9), for DL, UL and PRACH. Static compression headers |
| M-Plane | none. TCP port 830 accepts a connection but resets during the SSH key exchange: it is a stub, not a NETCONF server |
| Timing | PTP, ITU-T G.8275.1 telecom profile, domain 24 (sync source=PTP); the fabric switch is the grandmaster |
| DU timing windows | Duranta: T1a_cp_dl = (285, 429), T1a_up = (96, 196), Ta4 = (110, 180). OCUDU: t1a_*_cp_* 200–350, t1a_*_up 0–300, ta4 0–1960 (µs) |
| Management address | 10.3.14.1 (CLI over SSH, liteon-oru-mgmt). 10.3.14.2 is the fronthaul IP and never answers ping |
| Fronthaul MAC | e8:c7:4f:25:89:39 (eth1, the SFP). eth0 (…7f:75) is management only |
| DU MAC stored in the RU | 00:11:22:33:44:66 (DU MAC Address=001122334466) |
| Power control | liteon-oru.instrument.orbit-lab.org, from your fronthaul console (the console your O-RU reservation targets) |
| Physical cell ID (PCI) on COSMOS | 1 (cell identity 1). Each O-RU has its own PCI and cell identity: LiteOn 1, Benetel 2, Foxconn 3 |
Danger: The LiteOn and the Benetel RAN650 share one RF space. Since 2026-10-16 every O-RU reservation carries its band, centre frequency and bandwidth, and the shared scheduler refuses overlapping bookings, so both may be powered only on non-overlapping channels;
cosmos-oru resetstops if a co-channel O-RU is being driven, or if a DU is already streaming at yours.
Confirm power by ping, not by theomf tellreply.
Standalone by design. The LiteOn has no O-RAN M-Plane. You log in to its vendor CLI over SSH
at 10.3.14.1 (user user, then enable), and set the carrier, compression, DU MAC, PRACH eAxC IDs
and VLAN in configure terminal. The RU stores these values and applies them at boot. After that
the DU only sends control-plane (C-plane) and user-plane (U-plane) traffic. No NETCONF session runs
at any point. The same fact makes the radio a natural fit for OCUDU, which has no NETCONF client,
and for Duranta built against the static (non-M-Plane) fronthaul library.
The CLI accepts one command per SSH session reliably; when several commands are pipelined it can
drop a response. The COSMOS tools open a fresh session per command for that reason.
Persistent and volatile settings. The RU's settings fall into two classes, and the tooling
treats them differently:
| Class | Keys | Takes effect | Survives an RU power cycle |
|---|---|---|---|
| Persistent | center-frequency, compression-bit, du-mac-address, slot-id, eAXC_id, c/u-plane-vlan, phasecomp-mode |
only after an RU power cycle | yes |
| Volatile | jumboframe |
live, at once | no: resets to 0 on every boot |
jumboframe must be re-applied after every RU boot. The RU refuses the write until its own
radio bring-up has finished, and it still reports success. So the tools first wait for
Sync State : SYNCHRONIZED (exact match: NOT_SYNCHRONIZED contains the same word), then write,
then read back. Without jumbo frames the DU sends about 6 million packets per second at
MTU 1500. That load starves the RU's PTP client, and the RU drops out of SYNCHRONIZED, taking RF,
digital pre-distortion (DPD) and PRACH down with it.
Tools. Two tools set the radio. Both are on the Duranta image in /root/ and in the
duranta-cosmos repository:
liteon-ru-config.py [IP] [--check] applies and verifies the full parameter set (below). ItREBOOT-REQUIRED when a persistent value changed. With--check it only verifies./root/oru-setup/oru_golden.py diffs and restores the radio against a golden captureoru_capture_liteon.py captures the full show running-config in a diffable form).The golden-state rule. A rig counts as working only once a full capture of the O-RU's state
is committed as its golden. Every bring-up restores that golden and refuses to start the DU on a
mismatch. For the LiteOn, the Duranta golden (osc-liteon-duranta.txt) and the OCUDU golden
(osc-liteon-ocudu.txt) are identical in every configuration key, so the radio does not change
state when it changes stacks. The restore works in two stages:
COLD-CYCLE-REQUIRED). Power the RU off and on, then run the restore again.SYNCHRONIZED, then write jumboframe, read back, andliteon-du-bringup.sh (Duranta) runs the gate at step [6b]. The OCUDU bring-ups run
liteon-ru-config.py before the DU starts.
# on the DU host, before the DU starts
python3 /root/oru-setup/oru_golden.py diff /root/oru-setup/golden/osc-liteon-duranta.txt
python3 /root/oru-setup/oru_golden.py restore /root/oru-setup/golden/osc-liteon-duranta.txt
Power cycling. Change a persistent value only with the DU stopped. Then do a real power
cycle from your fronthaul console:
omf tell -a offh -t liteon-oru.instrument.orbit-lab.org
omf tell -a on -t liteon-oru.instrument.orbit-lab.org
A real cycle loses ping for about 15 s and brings it back about 30 s after on. If the RU still
pings 20 s after offh, it never powered down: repeat. The RU then needs time to reach
SYNCHRONIZED. It has taken about 70 s on a fresh power-on and 4–5 minutes in other runs, so poll
for it rather than sleeping. A LiteOn that has sat powered but idle does not resynchronise on its
own: power-cycle it. Wait for SYNCHRONIZED before starting the DU. A DU started against an
unsynchronised RU sees rx_on_time 332 of 4800 instead of 4800 of 4800.
The RU's stored centre frequency is 3750.000 MHz, so both stacks build a 100 MHz carrier around
it. They express it differently:
| Parameter | Duranta (OAI) | OCUDU (srsRAN) |
|---|---|---|
| Centre frequency | 3750.00 MHz (from PointA + 273 PRB) | dl_arfcn: 650000 = 3750.00 MHz |
| SSB | absoluteFrequencySSB = 650016 (3750.24 MHz) |
derived by srsRAN |
| PointA | dl_absoluteFrequencyPointA = 646724 |
derived by srsRAN |
| Bandwidth | dl_carrierBandwidth = 273 |
channel_bandwidth_MHz: 100 |
| CORESET#0 / search space 0 | 11 / 0 |
derived |
| TDD pattern | DDDSU, 2.5 ms (dl_UL_TransmissionPeriodicity = 5) |
no tdd_ul_dl_cfg block: srsRAN's default 5 ms pattern, DDDDDDDSUU |
| Special slot | 6 DL symbols / 4 guard / 4 UL symbols | default |
| PRACH | config index 159, root 1, ZCZ 0, prach_msg1_FrequencyStart = 22 |
config index 159, root 1, ZCZ 0, prach_frequency_start: 2 |
| PCI / cell identity | physCellId = 1, nr_cellid = 1L |
pci: 1 |
| DU-side gain | att_tx = 0, att_rx = 0, tx_amp_backoff_dB = 12 |
iq_scaling: 5.0 |
| RU-side attenuation | TX attenuation=17, RX attenuation=14 (both stacks) |
same |
The RU follows whatever TDD pattern the DU's C-plane describes, so the two stacks can run
different patterns against the same stored RU state.
Warning: Build each O-RU's configuration from its own reference, never by editing another
radio's file. A LiteOn configuration seeded from the Benetel one carriedatt_tx = 35, which
attenuated the LiteOn downlink by 35 dB: the UE saw RSRP −121 dBm / SINR −20 dB and could not
attach. Upstream OAI's LiteOn configuration usesatt_tx = 0.
An extended Antenna-Carrier identifier (eAxC ID) labels each fronthaul stream. The LiteOn stores
its PRACH eAxC IDs and uses fixed IDs for the data antennas. The RU is the authority. Both DUs
mirror its map. Neither DU rewrites the radio.
| Stream | RU eAxC IDs | Duranta | OCUDU |
|---|---|---|---|
| DL (PDSCH, 4 antennas) | 0, 1, 2, 3 | implicit, 4 TX | dl_port_id: [0, 1, 2, 3] |
| UL (PUSCH, 4 antennas) | 0, 1, 2, 3 | implicit, 4 RX | ul_port_id: [0, 1, 2, 3] |
| PRACH | 4, 5, 6, 7 (eAXC_id 4 5 6 7) |
eAxC_offset unset: defaults to max(N_tx, N_rx) = 4 |
prach_port_id: [4, 5, 6, 7] |
| Item | Value |
|---|---|
| Compression | BFP, 9 bits, DL / UL / PRACH; static compression headers (enable_*_static_compr_hdr: true on OCUDU; the Benetel uses dynamic) |
PRACH guard kbar |
the LiteOn hard-codes the PRACH guard in the RU, so Duranta sets kbar = 0 and skips it; OCUDU needs no equivalent |
| eCPRI sequence ID | the LiteOn does not maintain it: OCUDU sets ignore_ecpri_seq_id: true |
| VLAN | 4 for C-plane and U-plane. The LiteOn sends priority code point (PCP) 7; srsRAN sends PCP 0; the RU accepts either |
| DU MAC | 00:11:22:33:44:66. Duranta programs it onto the E810 virtual function (VF) it transmits from; OCUDU writes it into the frames it sends from a raw socket on the physical function, which must be promiscuous |
| RU MAC | e8:c7:4f:25:89:39 |
slot-id |
1 on the RU. It ships as 2; with 2 the RU counts nearly all DL as late |
| Phase compensation | done in the RU (phase compensation mode=Enable); Duranta sets phase_compensation = 0 because OAI compensates DL only |
Warning: Keep
ul_port_idat[0, 1, 2, 3]. A packet capture taken with no UE connected
shows RU→DU traffic only on eAxC 4–7, because at that moment only PRACH is flowing. Changing
ul_port_idto[4, 5, 6, 7]to match it breaks registration.
OCUDU runs the RU standalone. It has no NETCONF client and does not need one. The srsRAN Open
Fronthaul (ru_ofh) driver opens a raw socket on the E810 physical function (DATA1a), with
no DPDK and no SR-IOV VFs, so the interface must be promiscuous (the bring-up sets it). The
configuration is derived from srsRAN's LiteOn reference design (gnb_ru_liteon_tdd_n78_100mhz.yml)
with these COSMOS changes:
ta4_max: 1960 and max_proc_delay: 10.Two rigs use it, with the same RU golden:
| Rig | Core | DU configuration | Bring-up |
|---|---|---|---|
| Aether SD-Core (golden captured with it) | Aether SD-Core on a separate server | /root/ocudu/gnb-ocudu-liteon-sdcore.yml (repo configs/ocudu-sdcore-72/gnb-ocudu-liteon-sdcore-20260921.yml); the E2 variant adds the near-RT RIC agent |
/root/ocudu-liteon-sdcore-bringup.sh |
| Open5GS, host-portable | Open5GS on the DU host | gnb-ocudu-liteon.yml (repo configs/ocudu-liteon-72/) |
FH=DATA1a ocudu-liteon-bringup.sh |
ru_ofh (identical in both files apart from comments; shown from gnb-ocudu-liteon.yml):
ru_ofh:
# --- reference timing (LiteOn); note these are WIDER on t1a_min_up and TIGHTER on t1a_max
# --- than the Benetel's, which is why per-RU parameter sets are not interchangeable.
t1a_max_cp_dl: 350
t1a_min_cp_dl: 200
t1a_max_cp_ul: 350
t1a_min_cp_ul: 200
t1a_max_up: 300
t1a_min_up: 0
ta4_max: 1960 # 2026-09-14: at 500 every PRACH message from this RU arrived just after the window
# closed ("missed incoming ... PRACH" then "no uplink PRACH context"); 1960 = 0 misses,
# rx_late 0. The same value the Benetel needed on this fabric.
ta4_min: 0
is_prach_cp_enabled: true
ignore_ecpri_payload_size: false
ignore_ecpri_seq_id: true # reference: the LiteOn does not maintain the eCPRI sequence id
compr_method_ul: bfp
compr_bitwidth_ul: 9
compr_method_dl: bfp
compr_bitwidth_dl: 9
compr_method_prach: bfp
compr_bitwidth_prach: 9
enable_ul_static_compr_hdr: true # reference: LiteOn uses STATIC headers (the Benetel uses dynamic)
enable_dl_static_compr_hdr: true
iq_scaling: 5.0 # reference value, and already in the low range the short
# RU-UE distance on this rig requires
cells:
- network_interface: DATA1a # COSMOS: srsRAN OFH uses a raw socket on the PF, not a PCI VF
ru_mac_addr: e8:c7:4f:25:89:39 # COSMOS: this RU's eth1 (fronthaul) MAC
du_mac_addr: 00:11:22:33:44:66 # COSMOS: srv1-lg1 DATA1a, and what the RU has as du-mac-address
vlan_tag_cp: 4 # COSMOS: the fabric VLAN; the RU has c/u-plane-vlan 4
vlan_tag_up: 4 # (the reference omits VLAN because its lab RU used 564)
prach_port_id: [4, 5, 6, 7] # RU eAxC map (0x0004-0x0007). FOUR ports because srsRAN
# refuses to start otherwise: "RU number of PRACH ports=1 must
# be equal or greater than the number of reception antennas=4".
dl_port_id: [0, 1, 2, 3] # 4T4R -- see the ⚠️ note at the top of this file
ul_port_id: [0, 1, 2, 3]
Cell (cell_cfg, from the SD-Core file):
cell_cfg:
# The RU's own center-frequency is 3750000000 (see liteon-ru-config.py), so the carrier must be
# ARFCN 650000 = 3750.00 MHz -- NOT the reference's 649980 (3749.7 MHz), which was its lab RU.
dl_arfcn: 650000
band: 78
channel_bandwidth_MHz: 100
common_scs: 30
plmn: "00101"
tac: 1
# STANDING CONVENTION: unique physCellId AND cell identity per O-RU (LiteOn 1, Benetel 2,
# Foxconn 3), so a UE moving between radios registers fresh instead of trying to re-establish.
pci: 1
sector_id: 1
nof_antennas_dl: 4 # reference 2T1R
nof_antennas_ul: 4
prach:
prach_config_index: 159
prach_root_sequence_index: 1
zero_correlation_zone: 0
prach_frequency_start: 2
# 2026-09-20 MEASURED: the DU's reported TA is a deterministic function of the
# preamble INDEX (450 strong detections), and only index 0 gives a correct TA.
# Every other index lands msg3 outside the 2.3 us CP -> crc=KO at -11 dB SINR.
# Pinning the contention-based set to ONE preamble forces index 0.
# WORKAROUND, single-UE only: all UEs would now collide on the same preamble.
nof_cb_preambles_per_ssb: 4
Two inline comments in this block are loose. # reference 2T1R describes srsRAN's reference, not
this radio, which is 4T4R. The nof_cb_preambles_per_ssb comment says "ONE preamble", but the
value is 4. srsRAN rejects 1: with one SSB per RACH occasion, the information element encodes only
4, 8, … 64, so 4 is the minimum. The UE then draws its preamble from {0, 1, 2, 3}, not from all 64.
The host-portable Open5GS file does not set nof_cb_preambles_per_ssb (it uses tac: 7 for
Open5GS). Aether SD-Core serves only TAC 1 and 2, which is why the SD-Core file uses tac: 1.
PHY timing (expert_phy):
expert_phy:
max_proc_delay: 10 # 2026-09-14: at 15 the MAC ran so far ahead that this RU's PRACH reached the
# scheduler at slot x.17 -- after the last DL slot of the RAR window (DDDDDDDSUU:
# x.10-16 D) -> "Could not transmit RAR within the window", UE retries forever.
# 10 -> RAR sent, Msg3 crc=OK, REGISTERED; no new late DL_TTI on srv18.
CPU affinities are per host, and both bring-ups render them from the host's isolated cores:
the RU thread gets the first two isolated cores, the OFH TX/RX threads the next four, and the main
pool the rest. The main pool must not include the hyperthread siblings of the RU/OFH cores; no
counter reports that mistake. The SD-Core file on the 2× Xeon Gold 6126 host uses
main_pool_cpus: 12,…,23,36,…,47 with ru_cpus: 3,4 and txrx_cpus: ["5,6,7,8"].
RU-side golden for OCUDU: /root/oru-setup/golden/osc-liteon-ocudu.txt. Its configuration
section is identical to the Duranta golden shown above.
Launch (Open5GS variant). Copy ocudu-liteon-bringup.sh, gnb-ocudu-liteon.yml,
liteon-ru-config.py and the bundle's ocudu-72_bringup.sh to the O-DU host, then:
FH=DATA1a /root/gap-liteon/ocudu-liteon-bringup.sh # stop: systemctl stop ocudu-72 ocudu-iperf
It renders /root/ocudu/gnb-ocudu-liteon.<host>.yml, applies and checks the RU parameter set, then
runs the bundle's bring-up: promiscuous NIC, PTP, Open5GS, and the gNB as the systemd unit
ocudu-72. The gNB log is /tmp/ocudu-liteon.log. ether_rx rx_bytes > 0 in the metrics proves
the DU's raw socket is receiving fronthaul frames.
Duranta drives the LiteOn with the static fronthaul library (liboran_fhlib_5g.so, no
M-Plane). An M-Plane build would take the PRACH eAxC offset from the M-Plane and ignore the
configuration. The O-DU runs nr-softmodem with the fhi_72/xran transport on a DPDK-bound Intel
E810 VF. The configuration is on the image at /root/oru/gnb-liteon.conf. At launch the bring-up
renders a host-specific copy, /root/oru/gnb-liteon.<host>.conf, with this host's VF PCI address
and xran/PHY cores, so the core numbers below are the file's defaults.
Physical parameters (as shipped in the image, duranta-20260927 onward):
////////// Physical parameters (4x4, 2 layers — matches Benetel RAN650):
pdsch_AntennaPorts_XP = 2;
pdsch_AntennaPorts_N1 = 2;
pusch_AntennaPorts = 4;
maxMIMO_layers = 4;
do_CSIRS = 1; # CSI-RS on: without it the UE reports no rank and OAI sends ONE layer (2026-09-25)
min_rxtxtime = 6;
Note: The host copy the bring-up renders may differ. On a server that gives the DU fewer than 20
CPUs (a 2× Xeon Gold 6126 gives it 10) the host tool writesmaxMIMO_layers = 2and
do_CSIRS = 0, the proven single-layer settings, because multi-layer downlink overloads the
real-time L1 there. See Tuning and open items.
Cell (servingCellConfigCommon):
servingCellConfigCommon = (
{
physCellId = 1;
# SSB @ 3750.24 MHz (ARFCN 650016), band n78, 273 PRB (100 MHz), PointA 646724
absoluteFrequencySSB = 650016;
dl_frequencyBand = 78;
dl_absoluteFrequencyPointA = 646724;
dl_offstToCarrier = 0;
dl_subcarrierSpacing = 1;
dl_carrierBandwidth = 273;
initialDLBWPlocationAndBandwidth = 1099;
initialDLBWPsubcarrierSpacing = 1;
initialDLBWPcontrolResourceSetZero = 11;
initialDLBWPsearchSpaceZero = 0;
ul_frequencyBand = 78;
ul_offstToCarrier = 0;
ul_subcarrierSpacing = 1;
ul_carrierBandwidth = 273;
pMax = 23;
initialULBWPlocationAndBandwidth = 1099;
initialULBWPsubcarrierSpacing = 1;
prach_ConfigurationIndex = 159;
prach_msg1_FDM = 0;
prach_msg1_FrequencyStart = 22;
zeroCorrelationZoneConfig = 0;
preambleReceivedTargetPower = -100;
preambleTransMax = 7;
powerRampingStep = 2;
ssb_perRACH_OccasionAndCB_PreamblesPerSSB_PR = 4;
ssb_perRACH_OccasionAndCB_PreamblesPerSSB = 15;
ra_ContentionResolutionTimer = 7;
rsrp_ThresholdSSB = 19;
prach_RootSequenceIndex_PR = 2;
prach_RootSequenceIndex = 1;
msg1_SubcarrierSpacing = 1,
restrictedSetConfig = 0,
msg3_DeltaPreamble = 2;
p0_NominalWithGrant = -100;
pucchGroupHopping = 0;
hoppingId = 0;
p0_nominal = -96;
ssb_PositionsInBurst_Bitmap = 0x1;
ssb_periodicityServingCell = 2;
dmrs_TypeA_Position = 0;
subcarrierSpacing = 1;
referenceSubcarrierSpacing = 1;
dl_UL_TransmissionPeriodicity = 5;
nrofDownlinkSlots = 3;
nrofDownlinkSymbols = 6;
nrofUplinkSlots = 1;
nrofUplinkSymbols = 4;
ssPBCH_BlockPower = 0;
}
);
MAC, L1 and RU sections:
MACRLCs = (
{
tr_s_preference = "local_L1";
tr_n_preference = "local_RRC";
pusch_TargetSNRx10 = 200;
pucch_TargetSNRx10 = 200;
pusch_FailureThres = 100;
ul_max_mcs = 28;
pusch_RSSI_Threshold = -100; # 0.1 dBFS: stop TPC-up above -10 dBFS at the RU (healthy full-band PUSCH reads -15); guards against the power runaway seen 2026-09-04
}
);
L1s = (
{
num_cc = 1;
tr_n_preference = "local_mac";
prach_dtx_threshold = 100;
pucch0_dtx_threshold = 80;
pusch_dtx_threshold = 10;
tx_amp_backoff_dB = 12; # needs to match O-RU configuration
L1_rx_thread_core = 8; # isolated physical core NOT used by xran (xran: system 3, io 5, workers 7,9)
L1_tx_thread_core = 10; # idem
phase_compensation = 0; # RU does it (LiteOn `phasecomp-mode true`, the srsRAN recipe). OAI compensates DL ONLY, so the RU must own it for the UL too.
}
);
RUs = (
{
local_rf = "no";
nb_tx = 4;
nb_rx = 4;
att_tx = 0; # upstream LiteOn conf uses 0; 35 was inherited from the Benetel config
att_rx = 0;
bands = [78];
max_pdschReferenceSignalPower = -27;
max_rxgain = 75;
sf_extension = 0;
eNB_instances = [0];
ru_thread_core = 6; # isolated physical core NOT used by xran
sl_ahead = 10;
tr_preference = "raw_if4p5"; # important: activate FHI7.2
do_precoding = 0; # needs to match O-RU configuration
}
);
Fronthaul (fhi_72):
fhi_72 = {
dpdk_iova_mode = "VA";
dpdk_mem_size = 16384;
dpdk_devices = ("0000:12:01.0"); # srv19 DATA1a (E810) VF0, bound to vfio-pci
system_core = 3;
io_core = 5;
worker_cores = (7, 9); # inside isolcpus 3-11,27-35
du_addr = ("00:11:22:33:44:66");
ru_addr = ("e8:c7:4f:25:89:39"); # LiteOn FlexFi eth1 (SFP fronthaul)
vlan_tag = (4);
# NO du_key_pair / ru_username / ru_ip_addr: the LiteOn has no O-RAN M-Plane, so this
# must run against liboran_fhlib_5g.so (static). An M-Plane build would take
# prachEaxcOffset from the M-Plane and ignore the config entirely.
mtu = 9216; # jumbo re-enabled on the RU post-boot; mtu 1500 gave ~6M pps and starved the RU's PTP
file_prefix = "fhi_72";
fh_config = ({
T1a_cp_dl = (285, 429); # upstream LiteOn; retested with STABLE sync + jumbo
T1a_cp_ul = (285, 429);
T1a_up = (96, 196);
Ta4 = (110, 180);
ru_config = {
iq_width = 9;
iq_width_prach = 9;
};
prach_config = {
kbar = 0; # LiteOn hard-codes the PRACH guard in the RU; OAI must skip it
# eAxC_offset deliberately UNSET -> defaults to max(Ntx,Nrx)=4, matching the RU's
# `eAXC_id 4 5 6 7`. Do NOT copy the FR2 file's eAxC_offset = 0.
};
});
};
The timing windows, iq_width and kbar come from OAI's own LiteOn sample,
targets/PROJECTS/GENERIC-NR-5GC/CONF/gnb.sa.band78.273prb.fhi72.4x4-liteon.conf (also on the image
under /opt/duranta).
RU-side golden for Duranta (/root/oru-setup/golden/osc-liteon-duranta.txt, configuration
section; the OCUDU golden is identical):
### RU_CONFIG.CFG (full, sorted: the LiteOn `show running-config`)
Band Width=100000000
Center Frequency=3750000000
Compression Bit=9
Control and User Plane vlan=4
DU MAC Address=001122334466
M Plane vlan=0
RX attenuation=14
SFP_non_vlan_static_ip=192.168.1.100
SFP_vlan_ip=10.101.131.62
TX attenuation=17
default gateway=10.101.131.254
dpd mode=Enable
jumboframe=0x00000001
phase compensation mode=Enable
prach eAxC-id port 0, 1, 2, 3=0x0004, 0x0005, 0x0006, 0x0007
rj45_vlan_ip=10.101.131.61
slotid=0x00000001
subcarrier spacing=1
sync source=PTP
### REGISTERS (none: no register interface on this radio)
### VOLATILE (per-boot measurements, not configuration)
Sync State : SYNCHRONIZED
RF State : Ready
DPD : Ready
DuConnected : Ready
Launch. On the O-DU host, with the OAI CN5G core on the same host:
/root/liteon-du-bringup.sh
The script derives the host-specific values (VF PCI, NUMA node, xran/PHY cores) from sysfs with
/root/oai72-host.py, so it runs on any 7.2 server (Intel Xeon or AMD EPYC). Its steps are:
| Step | What it does |
|---|---|
[0] |
stop any running DU first, then set the performance CPU governor |
[1] |
select the static fronthaul library |
[2], [2b] |
create the SR-IOV VFs and set the VF MAC; move the NIC's IRQs off the isolated cores |
[3] |
hugepages |
[4] |
PTP: step once, then slew |
[5], [6] |
apply the RU parameter set (re-applies jumboframe); wait until the RU is radio-ready |
[6b] |
golden gate |
[7], [7b] |
core; patch the AMF address from the live container; render the host's DU configuration |
[8], [9] |
launch nr-softmodem -O <host conf> under taskset; verify |
The DU log is /tmp/du_liteon.log. A healthy start shows NGSetupResponse, tx_gain offset 0
(this is att_tx; it must be 0 for the LiteOn), all six fronthaul error counters at 0
(drop / ecpri / cp / up / pusch / prach), prach_I0 around 21 dB, and on the RU
Sync State : SYNCHRONIZED and DuConnected : Ready.
PHY patch in the image. The Duranta image carries a local patch to
phy_procedures_nr_gNB.c. On a 273-PRB cell, the PUSCH delay estimator can lock onto the
DMRS comb-2 alias (half an FFT away). OAI turns that estimate into timing advance (TA) command 63
(8.3 µs, far outside the 2.3 µs cyclic prefix). The UE then loses uplink alignment for good, and the
power-control loop drives the RU towards full scale. The patch folds the alias back and ignores
estimates beyond 2 µs; the log line outside sane TA window, ignored shows it firing.
pusch_RSSI_Threshold = -100 in MACRLCs is the matching guard: it stops transmit power control
(TPC) from raising UE power once the RU's received level is above −10 dBFS.
The RU state is the same for both stacks; everything that differs is on the DU side.
| Setting | Duranta (OAI) | OCUDU (srsRAN) | Why |
|---|---|---|---|
| RU stored state | golden osc-liteon-duranta.txt |
golden osc-liteon-ocudu.txt (identical) |
the LiteOn has no mode to switch |
| Fronthaul transport | DPDK on an E810 VF (vfio-pci), static xran library |
raw socket on the PF, promiscuous | each stack's own OFH implementation |
| DL/UL/PRACH timing windows (µs) | T1a_cp_dl (285, 429), T1a_up (96, 196), Ta4 (110, 180) |
t1a_*_cp_* 200–350, t1a_*_up 0–300, ta4 0–1960 |
OAI's LiteOn sample vs srsRAN's reference; OCUDU's ta4_max is widened because the LiteOn's PRACH arrives just after a 500 µs window closes |
| Processing look-ahead | sl_ahead = 10 (OAI's LiteOn sample uses 5) |
max_proc_delay: 10 |
at 15, OCUDU's PRACH reaches the scheduler after the last DL slot of the RAR window |
| PRACH eAxC | eAxC_offset unset → 4 |
prach_port_id: [4, 5, 6, 7] |
both mirror the RU's eAXC_id 4 5 6 7; srsRAN requires as many PRACH ports as RX antennas |
| PRACH guard | kbar = 0 |
— | OAI-only knob for the LiteOn's hard-coded guard |
| PRACH frequency start | 22 | 2 | each stack's LiteOn reference |
| Contention preambles | 64 (ssb_perRACH_OccasionAndCB_PreamblesPerSSB = 15) |
4 (SD-Core file) | OCUDU single-UE workaround for the preamble-index TA fault (below) |
| TDD | DDDSU, 2.5 ms, special slot 6/4/4 |
srsRAN default DDDDDDDSUU, 5 ms |
the RU follows the DU's C-plane |
| Carrier | SSB 650016, PointA 646724 | dl_arfcn: 650000 |
same 3750 MHz centre, different parameterisation |
| DL layers | maxMIMO_layers = 2, no CSI-RS → 1 layer |
4 antennas; rank 4 reported | Duranta's shipped configuration lacks do_CSIRS = 1 |
| DL power scaling | att_tx = 0, tx_amp_backoff_dB = 12 |
iq_scaling: 5.0 |
each stack's scaling; the RU-UE distance on COSMOS is short |
| Phase compensation | phase_compensation = 0 (RU does it) |
RU does it (srsRAN reference) | OAI compensates DL only |
| eCPRI sequence ID | not configured | ignore_ecpri_seq_id: true |
the LiteOn does not maintain it |
| Uplink timing protection | TA-alias PHY patch + pusch_RSSI_Threshold = -100 |
— | OAI's delay estimator aliases on 273-PRB comb-2 DMRS |
| Core / TAC | OAI CN5G on the DU host, TAC 1 | Open5GS (TAC 7) or Aether SD-Core (TAC 1) | SD-Core serves only TAC 1 and 2 |
iperf3 throughput, receiver side, with a Quectel RM520N-GL modem. DL / UL in Mbit/s. All Duranta
rows ran one downlink layer because the configuration lacks do_CSIRS = 1.
Info: The UE node on the
oscrigs has its modem on a USB 2.0 port, which caps the UE's
application throughput at about 300 Mbit/s. That does not bind any row below, but it will bind a
multi-layer Duranta run: measure that from a UE host with USB 3.
| Stack (core) | DU CPU platform | Logical CPUs | DL / UL (Mbit/s) | Layers / notes | Date | Image |
|---|---|---|---|---|---|---|
| Duranta (OAI CN5G) | 2× Intel Xeon Gold 6126 | 48 | TCP 173 (1 stream) / 123 (4) / 100 (8); UDP UL 48.3 | 1 layer (host tool: 10 DU CPUs); SimCom on USB 3; stable at 300 Mbit/s UDP offered, the DU fails at 1.2 Gbit/s offered | 2026-09-27 | duranta-20260927 |
| OCUDU (Aether SD-Core) | 2× Intel Xeon Gold 6126, 24 cores | 48 | 117–121 / 76 | golden restored; main pool off the HT siblings | 2026-09-22 | ocudu-20260916 |
| OCUDU (Aether SD-Core) + E2 agent | 2× Intel Xeon Gold 6126, 24 cores | 48 | 123 / 87.7 | E2/KPM to the near-RT RIC; KPM per-UE DL 136–148 | 2026-09-22 | ocudu-20260916 |
| OCUDU (Open5GS) | 2× Intel Xeon Gold 6126, 24 cores | 48 | 154 / 48.6 | CQI 12, rank 4; DL BLER high | 2026-09-14 | ocudu-20260909b |
| OCUDU (Open5GS) | 2× Intel Xeon Gold 6226, 24 cores | 24 | 170–195 / 136–140 | ocudu-liteon-bringup.sh, 3 runs; DL BLER ~1 % |
2026-09-14 | ocudu-20260909b |
| OCUDU (Open5GS) | 1× AMD EPYC 9355P, 32 cores | 32 | 140–155 / 131–145 | ocudu-liteon-bringup.sh, 3 runs |
2026-09-14 | ocudu-20260909b |
| Duranta (OAI CN5G) | 2× Intel Xeon Gold 6126, 24 cores | 48 | 157–163 / 38–39 | 1 layer; golden restored | 2026-09-22 | not recorded |
| Duranta (OAI CN5G) | 2× Intel Xeon Gold 6126, 24 cores | 48 | 181 / 35.3 | 1 layer | 2026-09-16 | duranta-20260916 |
| Duranta (OAI CN5G) | 2× Intel Xeon Gold 6226, 24 cores | 24 | 185–202 / 43.7–46.9 | 1 layer; ping 32 ms, 0 % loss | 2026-09-14 | not recorded |
| Duranta (OAI CN5G) | 1× AMD EPYC 9355P, 32 cores | 32 | 172–200 / 44.1–48.3 | 1 layer; ping 32 ms, 0 % loss | 2026-09-14 | not recorded |
| Duranta (OAI CN5G) | 2× Intel Xeon Gold 6126, 24 cores | 48 | 165 / 28.8 | 1 layer; ping 31 ms, 0 % loss | 2026-09-07 | not recorded |
Duranta layers are chosen per host. The image (duranta-20260927 onward) ships maxMIMO_layers = 4 and do_CSIRS = 1; the bring-up host tool keeps them on servers that give the DU at least 20 CPUs and renders the single-layer settings (maxMIMO_layers = 2, do_CSIRS = 0) on smaller ones — on a 2× Xeon Gold 6126 the DU gets 10 CPUs and multi-layer downlink overloads its real-time L1. Four layers on the LiteOn have not been measured yet; they need a larger server (see the Benetel RAN650 page). Also on these hosts: do not offer far more UDP than the cell carries (a 1.2 Gbit/s flood took the one-layer DU down; 300 Mbit/s did not).
Duranta: tx_amp_backoff_dB. The COSMOS configuration uses 12; OAI's LiteOn sample uses 20.
The effect of the difference on this radio has not been measured.
OCUDU: RACH on this radio is single-UE. The PRACH detector reports a timing advance that
depends on the preamble index, not on distance. It is repeatable per index, and only index 0
gives a correct value; the others give 2–11 µs, well outside the 2.3 µs cyclic prefix. Msg3 then
fails CRC. nof_cb_preambles_per_ssb: 4 narrows the choice to {0, 1, 2, 3}. In the validation run
only indices 0 and 2 appeared, both with correct TA, and first-try registration went from 1 in 4
to 3 in 4. It is a workaround, not a fix: with four contention preambles, several UEs would
collide. The detector's index-to-TA mapping is still wrong.
OCUDU: throughput spread between rigs. The SD-Core rig (117–121 DL) is below the Open5GS runs
on the same CPU family (154) and on the other platforms (140–195). The difference is not explained.
OCUDU: ocudu-liteon-bringup.sh renders the main pool over the remaining isolated cores
(iso[6:]), which can include the hyperthread siblings of the RU/OFH cores. The SD-Core
configuration avoids that. Check the rendered main_pool_cpus against
/sys/devices/system/cpu/cpu*/topology/thread_siblings_list before trusting a throughput figure
from the Open5GS variant.
| Symptom | Cause | Fix |
|---|---|---|
RU drops out of SYNCHRONIZED under load; RF, DPD, PRACH go down |
jumboframe reset to 0 at the last RU boot; at MTU 1500 the DU's packet rate starves the RU's PTP |
re-apply jumboframe 1 after SYNCHRONIZED and read it back (liteon-ru-config.py, or the golden restore) |
jumboframe write "succeeds" but reads back 0 |
the RU refuses it until its radio bring-up finishes | wait for SYNCHRONIZED (exact match), then write and read back |
| Nearly all DL counted late at the RU | slot-id is 2 (factory value) |
slot-id 1 (persistent: power-cycle the RU) |
| UE sees RSRP around −121 dBm / SINR −20 dB, cannot attach | att_tx = 35 inherited from the Benetel configuration |
att_tx = 0; the bring-up prints tx_gain offset, which must be 0 |
| No answer from the RU | wrong address: 10.3.14.2 is the fronthaul IP |
manage it at 10.3.14.1 |
| Fronthaul silently broken | the DU targets eth0's MAC |
the fronthaul MAC is eth1, e8:c7:4f:25:89:39 |
RU never reaches SYNCHRONIZED |
it sat powered but idle | power-cycle it; confirm ping is lost after offh |
DU started too early: rx_on_time 332/4800, or ether_rx 0 |
the RU was not synchronised (a plain grep SYNCHRONIZED also matches NOT_SYNCHRONIZED) |
wait for an exact Sync State : SYNCHRONIZED |
RU RX_ON_TIME 0 with a 50/50 early/late split, or TX_TOTAL 0, or DuConnected : notReady under OCUDU |
none of these is a health signal on this firmware; TX_TOTAL counts uplink frames to the DU; OCUDU registers UEs with DuConnected : notReady |
judge by the DU counters (rx_on_time, PRACH detections, ether_rx) and a UE scan |
| Two UEs or no UE camp; interference | both the LiteOn and the Benetel are powered | power one off (shared RF space) |
OCUDU: missed incoming User-Plane PRACH messages, then no uplink PRACH context |
the LiteOn's PRACH arrives just after a 500 µs uplink window closes | ta4_max: 1960 |
OCUDU: preambles detected, then Could not transmit RAR within the window |
the MAC runs so far ahead that PRACH reaches the scheduler after the RAR window's last DL slot | max_proc_delay: 10 |
OCUDU: PRACH detected, Msg3 crc=KO at SINR around −11 dB, intermittently, while DL is good |
preamble-index-dependent TA (see Tuning) | retry AT+CFUN=1,1 first; nof_cb_preambles_per_ssb: 4. Do not raise gains: the level is fine, the timing is wrong |
OCUDU: ether_rx rx_bytes = 0 although tcpdump shows frames |
the NIC is not promiscuous; tcpdump taps before protocol demultiplexing |
set the interface promiscuous (the bundle bring-up does) |
| OCUDU: RU receives DL but cannot assemble symbols | the DU sends 2 DL streams to a 4T4R radio | nof_antennas_dl: 4, dl_port_id: [0, 1, 2, 3] |
| OCUDU with SD-Core: gNB connects, UEs are not served | TAC not served by SD-Core | tac: 1 (SD-Core serves TAC 1 and 2) |
Registration breaks after "matching" ul_port_id to a capture |
the capture had no UE, so only PRACH (eAxC 4–7) was flowing | ul_port_id: [0, 1, 2, 3] |
| Duranta: UL works about 10 s, then every UL block fails and does not recover until re-attach; RU UL level rises towards −4 dBFS | PUSCH delay-estimator alias becomes TA command 63 | the image's TA-alias patch (outside sane TA window in the log) and pusch_RSSI_Threshold = -100 |
Duranta: RRC completes, UE released before NAS; No AMF is associated to the gNB |
stale amf_ip_address: the OAI CN5G AMF gets a new address on each compose up |
the bring-up patches it from docker inspect oai-amf; by hand, /root/patch-amf.sh <conf> |
| Duranta: the PRACH eAxC setting is ignored | an M-Plane build of the fronthaul library takes the offset from the M-Plane | use the static library (bring-up step [1]) |
Duranta host: echo 0 > sriov_numvfs hangs; afterwards the PF is stuck DOWN and writes return Device or resource busy |
VFs destroyed while nr-softmodem still held one |
stop the DU before any VF work (step [0]); recovery is a host power cycle |
| After a host reboot the cell is unusable for about 25 minutes | CLOCK_REALTIME is ~144 ms off and phc2sys -S 1 will not step it |
step once with a threshold below the offset (-S 0.01), then run with -S 0.0 to slew |
Vendor and upstream
compression-bit 9, eAXC_id 4 5 6 7, jumboframe 1) and the supportedtargets/PROJECTS/GENERIC-NR-5GC/CONF/gnb.sa.band78.273prb.fhi72.4x4-liteon.confgnb_ru_liteon_tdd_n78_100mhz.yml —ssb-perRACH-OccasionAndCB-PreamblesPerSSB, the IE behindnof_cb_preambles_per_ssb.COSMOS
duranta-cosmos: configs/liteon-flexfi/, configs/ocudu-liteon-72/,configs/ocudu-sdcore-72/, configs/oru-profiles/, tutorials/oru-setup/.Author(s): COSMOS team · Last verified: 2026-09-22 (both stacks end to end, golden captured) · text written 2026-09-25 · Tested image/release: ocudu-20260916; Duranta image not recorded for the 2026-09-22 run; LiteOn FlexFi firmware 02.00.10 · Tags: oran, o-ru, 7.2, fronthaul, liteon, flexfi, ocudu, srsran, duranta, oai, hardware