OAI L1 + xFAPI + OCUDU L2
This guide covers pairing OCUDU DU-High (L2) with an OAI L1 (PHY) across the FAPI L2-L1 boundary, using xFAPI as the translator-bridge in its OAI_OCUDU mode. OAI L1 speaks nFAPI (open-nFAPI: P5 over SCTP, P7 over UDP); xFAPI translates nFAPI ↔ FAPI and bridges onto xSM shared memory for OCUDU. The deployment is disaggregated: OAI L1 on one host, xFAPI + OCUDU DU-High co-located on another.
For the OCUDU-to-OCUDU split (odu_low instead of OAI), see xFAPI Bridge. This document is the OAI-L1 variant of that flow.
Highlights
- OAI L1 as a drop-in PHY behind the same
odu_high(L2) binary — no L2 code fork; the L1 implementation is a deployment choice. - nFAPI ↔ FAPI ↔ xSM translation in xFAPI (OAI_OCUDU mode): nFAPI PNF/VNF on the OAI side, xSM master/slave on the OCUDU side.
- Disaggregated two-host topology: OAI L1 on Host A, xFAPI + OCUDU DU-High on Host B, joined by an nFAPI (SCTP/UDP) link.
- OAI-tuned cell config (
configs/ocudu_xfapi_oai.yaml) carrying PUCCH/PRACH/power values that match OAI L1's decoders, so a UE attaches end-to-end. - Same xSM transport and FAPI serialization as the OCUDU-to-OCUDU bridge — the only thing that changes is the L1 endpoint and xFAPI's mode.
1. Prerequisites
1.1 Hardware
| Item | Requirement |
|---|---|
| CPU | x86-64 with AVX2 / AVX-512 (both hosts). Isolated cores for the L1 and xSM RX/TX threads. |
| NIC (Host A) | Fronthaul NIC to the O-RU (e.g. Liteon RU), plus a NIC for the nFAPI SCTP/UDP link to Host B. |
| NIC (Host B) | DPDK-capable NIC for the xSM/DPDK domain, plus a NIC for the nFAPI link to Host A. |
| Sync | PTP grandmaster / GNSS on the L1 host for fronthaul timing. |
| Hugepages | Host B requires hugepages for the DPDK-backed xSM memzone. |
1.2 Software
| Component | Minimum | Notes |
|---|---|---|
| DPDK | 22.11 (25.11 validated) | Host B only (xFAPI + OCUDU). Built with ENABLE_DPDK=True. |
| Linux kernel | PREEMPT_RT recommended | isolcpus for L1 and xSM threads; NUMA pinning on Host B. |
| Compiler | gcc/g++ 11+ | C++17. |
| OAI | OAI L1 nr-softmodem w/ nFAPI | Built in PNF mode (NFAPI_MODE=PNF), open-nFAPI split transport. |
| OCUDU | this repo, fapi_split branch | ENABLE_XSM_FAPI_SPLIT=ON (default). |
| xFAPI | OAI_OCUDU mode build | nFAPI VNF (oai_vnf / oai_p7) + xSM bridge. |
1.3 Kernel setup (Host B)
# IOMMU + hugepages on the kernel cmdline (example), then reboot:
# intel_iommu=on iommu=pt default_hugepagesz=1G hugepagesz=1G hugepages=4 isolcpus=...
sudo modprobe vfio-pci
# Bind the DPDK NIC to vfio-pci (use dpdk-devbind.py for your PCI BDF).
2. Architecture Overview
2.1 Where the split sits
OAI owns the PHY and RU; OCUDU owns MAC and above. The FAPI message set is the contract between them; nFAPI is the wire OAI exposes it on.
2.2 Transport layering
2.3 Topology (disaggregated, two-host)
- nFAPI wire (Host A ↔ Host B): P5 over SCTP (OAI client → xFAPI VNF listener), P7 over UDP (bidirectional), big-endian TLV, ~1500 B segmentation.
- xSM wire (inside Host B): xFAPI is xSM master on pair 1's peer side; OCUDU DU-High attaches as xSM master on pair 1, DPDK secondary,
file-prefix = gnb0_l2.
3. Implementation Summary
3.1 xSM proxies and gateways
The L2 FAPI adaptor's outbound interfaces are implemented by thin proxies in apps/du/fapi_xsm_proxy.h:
xsm_p5_requests_gateway— PARAM / CONFIG / START / STOP requests (L2→L1).xsm_p7_requests_gateway— DL_TTI / UL_TTI / UL_DCI / TX_DATA requests (L2→L1).xsm_p7_indications_notifier— RX_DATA / CRC / UCI / SRS / RACH indications (L1→L2).xsm_p7_slot_indication_notifier— SLOT indication; rotates the UL slot ring each slot.xsm_p5_responses_notifier,xsm_error_indication_notifier,xsm_p7_last_request_notifier(0x8Fend-of-slot marker).
Each proxy serializes the message into an xSM buffer and calls xsm_context::put().
3.2 Wire-stable FAPI serialization
Every P5/P7 message has a hand-written serialize() / deserialize() pair under lib/fapi/serialization/ — byte-aligned, length-prefixed for variable arrays, no reflection or RTTI.
Each xSM message is prefixed with a fixed header (fapi_xsm_message_header.h):
| Field | Size | Meaning |
|---|---|---|
msg_type | 1 B | FAPI message type ID (see table below) |
num_messages_in_block | 1 B | messages in this block |
msg_len | 4 B | payload length after the header |
align_offset | 4 B | alignment offset |
time_stamp | 8 B | TX/RX timestamp (ns) |
Header is ≤ 48 B; XSM_BLOCK_SIZE = 128 KiB per buffer.
FAPI message type IDs (the wire contract):
| ID | Message | Dir |
|---|---|---|
| 0x00/0x01 | PARAM.request / response | P5 |
| 0x02/0x03 | CONFIG.request / response | P5 |
| 0x04/0x05/0x06 | START.req / STOP.req / STOP.ind | P5 |
| 0x07 | ERROR.indication | P5 |
| 0x80 | DL_TTI.request | L2→L1 |
| 0x81 | UL_TTI.request | L2→L1 |
| 0x82 | SLOT.indication | L1→L2 |
| 0x83 | UL_DCI.request | L2→L1 |
| 0x84 | TX_DATA.request | L2→L1 |
| 0x85 | RX_DATA.indication | L1→L2 |
| 0x86 | CRC.indication | L1→L2 |
| 0x87 | UCI.indication | L1→L2 |
| 0x88 | SRS.indication | L1→L2 |
| 0x89 | RACH.indication | L1→L2 |
| 0x8F | P7 last-message (end of slot) | L2→L1 |
3.3 Single dispatcher thread per process
fapi_xsm_transport runs one receiver thread:
- Created on
start_receiver(); runsreceive_loop()→dispatch_message()per message. - Pinned via
pthread_setaffinity_nptorx_cpu(when ≥ 0) atSCHED_FIFOpriorityrx_priority. dispatch_message()switches onmsg_typeand routes to the right notifier/gateway.- Peer-liveness is tracked (
is_peer_alive()); sends are skipped when the peer is down, so an L1 restart does not crash L2. - TX path is lock-free:
alloc_buffer()→ serialize →put(); no kernel copy on the hot path.
4. Clone & Build
Pick a common workspace — this guide uses ~/tossi-ran:
4.1 Clone the repositories
mkdir -p ~/tossi-ran && cd ~/tossi-ran
# OAI L1 (PNF)
git clone https://github.com/TOSSI-Foundation/OAI-RAN/
# OCUDU L1+L2 — the fapi_split branch
git clone -b fapi_split https://github.com/TOSSI-Foundation/OCUDU-RAN/
# xFAPI bridge
git clone https://github.com/TOSSI-Foundation/xFAPI/
4.2 Populate the nFAPI sources in xFAPI
xFAPI does not vendor the nFAPI codec; it is synced in from the OAI checkout. sync_nfapi.sh mirrors the OAI nfapi/ tree into src/ipc/nfapi/ plus a small oai_common/ folder gathered from scattered OAI locations.
cd ~/tossi-ran/xFAPI
./sync_nfapi.sh -v ~/tossi-ran/OAI-RAN
-vprints every file copied;-ndoes a dry run.- Destination defaults to
src/ipc/nfapi/(override withNFAPI_DIR). - The summary at the end must report
0 missing in OAI.
Note: Re-runsync_nfapi.shwhenever you update the OAI checkout. Recent upstream OAI split the legacynfapi_p5.c/nfapi_p7.cintonfapi_nr_p5.c/nfapi_nr_p7.c(+nfapi_lte_p7.cfor the generic P7 header helpers);CMakeLists.txtis already wired for this layout.
4.3 Build xFAPI
cd ~/tossi-ran/xFAPI
source ./setup_env.sh
./build_xfapi.sh --mode=oai_ocudu
setup_env.shexports the required environment (incl.DPDK_PATH).- Use
./build_xfapi.sh --cleanfirst for a clean rebuild. - Produces
bin/xfapi_main.
4.4 Build OCUDU
cd ~/tossi-ran/OCUDU-RAN
mkdir -p build && cd build
sudo cmake -DDU_SPLIT_TYPE=SPLIT_7_2 \
-DENABLE_DPDK=True \
-DASSERT_LEVEL=MINIMAL \
-DENABLE_UHD=OFF ../
make -j$(nproc)
Produces the ocu (CU) and odu_high (DU/L2) binaries.
4.5 Build OAI (gNB L1 / PNF)
cd ~/tossi-ran/OAI-RAN/cmake_targets
# One-time: install build dependencies
sudo ./build_oai -I
# Build the gNB with the O-RAN 7.2 fronthaul library
sudo ./build_oai --gNB --ninja -t oran_fhlib_5g \
--cmake-opt -Dxran_LOCATION=$HOME/phy/shi_lib/lib
Adjust -Dxran_LOCATION to wherever your xRAN library is built.
4.6 xSM verification
ldd ~/tossi-ran/OCUDU-RAN/build/apps/du/odu_high | grep xsm # libxsm.so resolved
5. Configuration
5.1 Key knobs
OCUDU L2 side (configs/ocudu_xfapi_oai.yaml):
fapi_split_l2:
rx_cpu: 30 # SCHED_FIFO RX thread affinity (-1 = no pinning)
rx_priority: 85 # [1, 99]
xsm_device_name: xsm_bridge
xsm_pair_index: 1 # OCUDU attaches as master on pair 1
xsm_file_prefix: gnb0_l2 # must match xFAPI-L2's DPDK file-prefix
dpdk_proc_type: secondary # xFAPI owns the primary
5.2 OAI-tuned cell config
These cell_cfg values in ocudu_xfapi_oai.yaml exist specifically so OCUDU's MAC stays on OAI L1's tested decode paths:
| Setting | Value | Why (OAI L1 constraint) |
|---|---|---|
pucch.formats | f0_and_f2 | OAI supports PUCCH format 0/2 only. |
pucch.f2_max_nof_rbs | 4 | Bounds the F2 grant; aligns with mult-of-4 PRB rule. |
prach.preamble_rx_target_pw | -120 | Avoid O-RU RX saturation of nearby UEs. |
pusch.p0_nominal_with_grant | -96 | UL power target for OAI. |
| band / bw / scs | n78 / 100 MHz / 30 kHz | Validated TDD carrier. |
Three source-side fixes back these values (already in the L2 build):
- PUCCH F2 = 1 symbol (
du_high_config_translators.cpp): OAI's F2 receiver only decodes 1-symbol F2. - F2 PRBs aligned to mult-of-4 (
pucch_resource_generator.cpp): OAI'snr_decode_pucch2requires it. - Single-symbol F2 floored at 2 PRBs (
pucch_info.cpp): OAI assertsprb_size * nof_symbols > 1.
5.3 nFAPI socket endpoints (Host A OAI ↔ Host B xFAPI)
| Plane | Transport | Endpoint |
|---|---|---|
| P5 | SCTP | OAI client → xFAPI VNF listener (e.g. :50001) |
| P7 | UDP | bidirectional (e.g. 50010 ↔ 50011) |
The OAI nFAPI config and xFAPI's VNF config must agree on these ports.
6. Run
Start the components in this order, each in its own terminal. xFAPI must own its DPDK/xSM resources before the DU attaches; the L1 must be listening before the DU drives slots.
6.1 xFAPI bridge (Host B, Terminal 1)
cd ~/tossi-ran/xFAPI
./run_xfapi.sh
This creates the xSM memzone (DPDK primary, file-prefix gnb0_l2) and opens the nFAPI P5 SCTP + P7 UDP listeners (VNF).
6.2 OCUDU CU (Host B, Terminal 2)
cd ~/tossi-ran/OCUDU-RAN/build/<cu_bin_dir>
./ocu -c ../../../configs/cu.yml
6.3 OAI L1 / PNF (Host A)
cd ~/tossi-ran/OAI-RAN/cmake_targets/ran_build/build
sudo NFAPI_TRACE_LEVEL=info ./nr-softmodem \
-O ../../../targets/PROJECTS/GENERIC-NR-5GC/CONF/gnb-pnf.band78.liteon.conf \
--nfapi PNF
The nFAPI PNF connects to xFAPI over P5 SCTP; PARAM/CONFIG exchanged; SLOT.indication starts ticking.
6.4 OCUDU DU / L2 (Host B, Terminal 3)
cd ~/tossi-ran/OCUDU-RAN/build/<odu_bin_dir>
./odu_high -c ../../../configs/odu_high_xfapi.yaml
Once all four are up, OAI L1 (PNF) and OCUDU L2 (VNF) are connected through xFAPI: nFAPI P5 (PARAM/CONFIG/START) over SCTP, then P7 (SLOT / DL_TTI / UL_TTI / TX_DATA and the RX/CRC/UCI/RACH indications) over UDP.
6.5 Startup verification
| Process | Expect |
|---|---|
| xFAPI | nFAPI P5 handshake to RUNNING; xSM memzone created; P7 segmenter ready. |
| OCUDU CU | F1AP listener up; waiting for DU F1 Setup. |
| OAI L1 | nFAPI PNF connected; PARAM/CONFIG exchanged; SLOT.indication ticking. |
| OCUDU DU | [OCUDU-CFG] CONFIG.request emitted; cell up; F1AP/RRC to CU established; UE attach completes. |
6.6 Troubleshooting
Cannot find source file ... nfapi_p5.cat CMake configure: the synced tree is from a newer OAI that renamed the codec sources — re-runsync_nfapi.shand rebuild;CMakeLists.txtexpects the NR-split layout.undefined reference to nfapi_p7_message_header_unpackat link: ensurenfapi_lte_p7.cis present in the synced tree (it carries the generic P7 header helpers after the upstream split).- DPDK errors: confirm
DPDK_PATHis set (viasetup_env.sh) and hugepages are configured. - Order matters: if xFAPI or CU are not ready when the DU starts, the F1 Setup or xSM attach will fail — always follow the sequence in §6.1–6.4.
7. Metrics & Observability
7.1 FAPI stats recorder
metrics:
enable_verbose: true
autostart_stdout_metrics: true
An optional in-memory FAPI message recorder captures per-message type/direction/size/IPC-latency and dumps JSON at shutdown — useful to diff OCUDU's emitted PDUs against OAI's expectations.
7.2 Logging
log:
filename: odu_high.log
all_level: debug # bring-up; lower to warning in production
The L2 send path also emits throttled [OCUDU-CFG] / MIB / TB fingerprints for byte-for-byte comparison against xFAPI and OAI dumps during interop debugging.
8. Deployment Checklist
- Host B: IOMMU on, hugepages reserved, DPDK NIC bound to
vfio-pci. - Host A: O-RU fronthaul up, PTP/GNSS locked.
- nFAPI link reachable both ways; P5 SCTP + P7 UDP ports match between OAI and xFAPI.
- xFAPI built in OAI_OCUDU mode;
libxsm.so+ xSM headers placed in the OCUDU tree. - OCUDU built with
ENABLE_XSM_FAPI_SPLIT=ON,ENABLE_DPDK=True. -
ocudu_xfapi_oai.yaml:xsm_file_prefix = gnb0_l2,xsm_pair_index = 1,dpdk_proc_type = secondary. - RX thread
rx_cpu/rx_priorityon an isolated SCHED_FIFO core. - Startup order: xFAPI → OAI L1 → OCUDU L2.
- Verify CONFIG exchange, cell up, F1AP to CU, UE attach.
9. References
- xFAPI — https://github.com/coranlabs/xFAPI
- OCUDU-to-OCUDU bridge — /docs/ran-integration/fapi-split/xfapi-bridge/
- FAPI split topologies —
docs/fapi_split_topologies.md - OpenAirInterface (OAI) — https://gitlab.eurecom.fr/oai/openairinterface5g
- DPDK — https://www.dpdk.org/
- SCF 222.10 (5G FAPI: PHY API), 3GPP TS 38.211/212/213/214 (PUCCH/PRACH).