Compare commits

..

58 Commits

Author SHA1 Message Date
Raymond Knopp
8e42e5ac1e feat: configurable VTCM for Hexagon LDPC skel via LDPC_HEX_VTCM cmake option
Add cmake option LDPC_HEX_VTCM (default OFF) that gates all CRM API code
behind #ifdef LDPC_HEX_VTCM. When OFF, the skel's dynsym is unchanged from
the baseline — only the four safe weak symbols (HAP_debug*, __cxa_finalize)
are present, so unsigned-PD on SA9000P continues to work.

When ON (-DLDPC_HEX_VTCM=ON), the 539 KB LDPC working set is allocated as a
single VTCM page via HAP_compute_res_acquire; malloc is the fallback if VTCM
is unavailable. Both paths verified to compile; default path verified against
device dynsym (no new weak symbols). VTCM ON path requires signed-PD.

Assisted-by: Claude Sonnet 4.6
Signed-off-by: Raymond Knopp <raymond.knopp@eurecom.fr>
2026-06-10 07:04:05 +00:00
Raymond Knopp
e744491de2 revert: remove VTCM allocation — CRM API unavailable in unsigned-PD
Reverts commit 16de8c0ce2.

The CRM API (HAP_compute_res_acquire / compute_resource_*) is not
exported by the SA9000P DSP runtime in unsigned-PD mode.  The SA9000P
RTLD (unlike a POSIX-compliant loader) fails to load the skel with
AEE_EUNABLETOLOAD (0x80000406) when any weak unresolvable symbol
appears in the dynsym — even though ELF semantics require resolving
truly-unresolved weak symbols to NULL.

Neither the include-at-compile-time approach (HAP_compute_res.h adds
weak compute_resource_* references) nor the dlopen/dlsym approach
(dlsym itself is not in the DSP runtime) can work in this mode.

VTCM requires one of:
  1. Signed-PD — full HAP API surface, including CRM and HAP_power_set
  2. QCOM support confirming which VTCM API is available in unsigned-PD
  3. A DSP firmware update that exports compute_resource_acquire

Performance path without VTCM: the 538 KB working set is in DRAM.
Permutations (cn2bn/bn2cn at 81% of simulator cycles) are the
bottleneck.  Layered decoding would reduce working set to fit L1
(~7 KB per row) and halve iterations — but requires full restructuring.

Assisted-by: Claude Sonnet 4.6
Signed-off-by: Raymond Knopp <raymond.knopp@eurecom.fr>
2026-06-10 07:04:05 +00:00
Raymond Knopp
e7cfeb6554 perf: VTCM allocation for LDPC working set via CRM API
Use HAP_compute_res_acquire() to place the 539 KB LDPC working set
(cnProcBuf, cnProcBufRes, bnProcBuf, bnProcBufRes, llrRes, llrProcBuf)
in the 8 MB VTCM scratchpad at session open.  VTCM provides near-
register latency for HVX vector loads/stores, eliminating DRAM misses
that currently dominate decode time (permutations at 81% of cycles).

At TURBO (1.459 GHz) + VTCM: estimated 344K cycles / 1.459 GHz = 242 µs
per iteration vs 3.3 ms today, targeting ~1 ms for 4 iterations.

Falls back transparently to malloc if VTCM is unavailable (unsigned-PD
or resource contention); no behaviour change in that case.  The CRM
API functions are __attribute__((weak)) — no extra library linkage needed.

Assisted-by: Claude Sonnet 4.6
Signed-off-by: Raymond Knopp <raymond.knopp@eurecom.fr>
2026-06-10 07:04:05 +00:00
Raymond Knopp
1d6334f6ea fix: Hexagon LDPC early termination — correct parity check algorithm and HVX alignment
Two root causes of always-running-4-iterations and subsequent HVX crash:

1. Wrong parity algorithm: scalar_cnProcPc checked sign(cnProcBuf[edge])
   which is sign(BN→CN extrinsic) rather than sign(llrRes[b]) (posterior LLR).
   OAI uses sat8(cnProcBuf + cnProcBufRes): the two messages at each edge cancel
   to give llrRes[b], whose sign is the hard decision for parity.
   Fix: add cnProcBufRes parameter; XOR sat8(p+q) across k-layers per b-chunk.

2. HVX alignment exception: memcpy(uint64_t[16], &HVX_Vector, 128) was inlined by
   the Hexagon compiler as vmem (aligned) → vmemu (unaligned) store sequence. When
   the destination was only 8-byte aligned, the vmem store faulted, crashing the
   DSP and causing the FastRPC caller to receive error 0x8000040D.
   Fix: declare the spill buffer with __attribute__((aligned(128))) so the
   compiler emits vmem to a properly aligned stack slot.

Result at SNR=10dB, BG1 Z=384: BER=0, mean iterations=1.0 (was 4.0),
decode time ~3.3ms (includes ~400µs FastRPC overhead + permutation bottleneck).

Assisted-by: Claude Sonnet 4.6
Signed-off-by: Raymond Knopp <raymond.knopp@eurecom.fr>
2026-06-10 07:04:05 +00:00
Raymond Knopp
f00119e272 fix: Hexagon LDPC decoder correctness — two root causes of BER≈0.5
Bug 1 — uninitialized llrProcBuf (malloc without memset)
  nrLDPC_llr2llrProcBuf writes only 42+22 of the 68 BG1 BNs (24576 of
  26112 bytes).  The reference decoder uses a zero-initialised stack
  buffer; the hexagon skel used calloc'd ctx buffers but forgot to
  zero llrProcBuf before each codeword.  The 4 higher-degree parity BNs
  at the end of llrProcBuf picked up malloc garbage, corrupting every
  BP iteration from the first.  Fix: memset(llrProcBuf, 0, ...) in
  ldpc_scalar_core before nrLDPC_llr2llrProcBuf.

Bug 2 — HVX byte deinterleave in hvx_sat8_add / hvx_sat8_sub / hvx_bnProcPc
  Q6_Wh_vsxt_Vb(v) DEINTERLEAVES: even-indexed bytes of v go to lo_W,
  odd-indexed bytes go to hi_W.  After accumulating in int16 and clamping,
  Q6_Vb_vpacke_VhVh(hi, lo) therefore writes [even_results | odd_results]
  in the output buffer instead of the correct linear order — a byte
  transposition that scrambles every LLR and extrinsic message.

  Fix: apply Q6_Vb_vshuff_Vb() after every vpacke call.  vshuff is the
  inverse of the implicit vdeal performed by vsxt: it re-interleaves the
  two 64-byte halves back to the correct [v0,v1,v2,v3,...] order.
  Three call sites: hvx_sat8_add, hvx_sat8_sub, and the multi-term
  accumulation loop in hvx_bnProcPc.

Both bugs diagnosed with the Hexagon simulator (hexagon-sim -mv73),
which faithfully emulates HVX and supports printf from the ELF harness.
Verified on the DragonWing IQ-9075 EVK:
  Before: BER≈0.5 at all SNRs (random output)
  After:  BER=0 at SNR=3dB, clean waterfall dropping from SNR ~1.0–3.0 dB

Assisted-by: Claude Sonnet 4.6
Signed-off-by: Raymond Knopp <raymond.knopp@eurecom.fr>
2026-06-10 07:04:05 +00:00
Raymond Knopp
47e4785e93 CMake: enable +crypto for Cortex-A72 to get hardware PMULL for CRC
Without +crypto, GCC does not define __ARM_FEATURE_AES, so SIMDe's
simde_mm_clmulepi64_si128 falls back to a scalar carry-less multiply
(16 integer multiplies + many AND/XOR ops per call) instead of emitting
vmull_p64.  The NXP LS2160A A72 has full hardware crypto (aes, pmull,
sha1, sha2) but -march=armv8-a+simd does not enable it.

Split the A72 / A53 march cases:
  A72 (0xd08): -march=armv8-a+simd+crypto  (hardware crypto confirmed)
  A53 (0xd03): -march=armv8-a+simd         (crypto optional on A53)

Result on A72 @ 2 GHz (nr_dlsim -n100 -e25 -s25 -R273 -b273 -P -x2 -y4 -z4 -p55):
  DLSCH Outer CRC:    ~250 us -> 22.9 us  (~11x)
  DLSCH segmentation: ~250 us -> 37.5 us  (~6.7x)
  Total DLSCH encoding: ~1500 us -> ~1057 us

Assisted by Claude Sonnet 4.6
2026-06-08 10:20:38 +02:00
Raymond Knopp
3b6e481724 Finishing off the "alignr" additions for all BG1 sizes. Some additional
timing information in gNB TX (overall CRC)

Signed-off-by: Raymond Knopp <raymond.knopp@openairinterface.org>
2026-06-08 10:20:38 +02:00
Raymond Knopp
d534623077 added missing BG1 LDPC encoder for 128-bit with "alignr"
Signed-off-by: Raymond Knopp <raymond.knopp@eurecom.fr>
2026-06-08 10:20:37 +02:00
Raymond Knopp
8f8dcbeff7 cosmetic changes
Signed-off-by: Raymond Knopp <raymond.knopp@eurecom.fr>
2026-06-07 08:26:21 +00:00
Raymond Knopp
1fcd02b1f0 remove ldpc_generators target from Dockerfile.build.ubuntu.cross-arm64
Signed-off-by: Raymond Knopp <raymond.knopp@eurecom.fr>
2026-06-07 08:26:21 +00:00
Raymond Knopp
115987c53c remove ldpc_gen_HEADERS from CMakeLists.txt to avoid warnings during build
Signed-off-by: Raymond Knopp <raymond.knopp@eurecom.fr>
2026-06-07 08:26:21 +00:00
Raymond Knopp
dbbecb4103 perf: replace generated bnProc/bnProcPc with generic three-width kernels
Replace 1360-line bnProc.h (BG1-only, 256-bit only, 13 copy-pasted degree
groups) and all 36 generated bnProc/bnProcPc #includes with two generic
loops over the 30 BN-degree groups, dispatching at compile time to:

  - AVX512BW: 512-bit accumulation in bnProcPc (mm512_cvtepi8_epi16 +
    mm512_cvtsepi16_epi8), 512-bit subs in bnProc
  - AVX2:     existing 256-bit paired-128 widen/accumulate/pack pattern
  - 128-bit:  hi/lo split via mm_srli_si128, always compiled

Both functions now cover all BG variants (BG1/BG2, all rates) without
rate-specific dispatch in the decoder.  bnProc includes degree-1 BNs
(previously special-cased) via the generic loop.  nrLDPC_decoder.c
dispatch blocks for UNROLL_BN_PROC / UNROLL_BN_PROC_PC are removed;
UNROLL_CN_PROC remains commented-in for benchmarking.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Raymond Knopp <raymond.knopp@eurecom.fr>
2026-06-07 08:26:21 +00:00
Raymond Knopp
781e2a0147 perf: make two-pass cnProc the default, keep unrolled as opt-in
Remove TWOPASS_CN_PROC guard now that the two-pass path is the only
non-unrolled option:

  - Comment out #define UNROLL_CN_PROC 1 so the two-pass path is
    active by default on all targets (AVX2, AVX512, aarch64/NEON).
  - Collapse the four dispatch sites from
      #ifndef UNROLL_CN_PROC
        #ifdef TWOPASS_CN_PROC ... #else ... #endif
      #else  <unrolled switch>
    to the simpler
      #ifndef UNROLL_CN_PROC
        nrLDPC_cnProc_BG{1,2}_2pass(...)
      #else  <unrolled switch>
  - The unrolled path (UNROLL_CN_PROC) is retained for benchmarking
    and will be removed in a follow-up once the two-pass path is
    confirmed as the permanent default.

Tested on: AMD Ryzen (AVX2), Intel (AVX512), Rockchip A76 (NEON),
           NVIDIA Neoverse (DGX Spark, GH200).

Signed-off-by: Raymond Knopp <raymond.knopp@eurecom.fr>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-07 08:26:21 +00:00
Raymond Knopp
274a3d42d9 doc: update r8127 section to reference patched fork and summarise all fixes
Remove the manual patch instructions (hrtimer API + spinlock) and
replace them with a pointer to the maintained fork and a five-row
summary table of every fix it contains relative to openwrt/rtl8127
v11.015.00:

- hrtimer_setup() API (kernel >= 6.15)
- rtl8127_set_local_time() moved outside spinlock (PHC init deadlock)
- 0xA640 arm sequence: PTP_CTL must be non-zero at write time
- hwtstamp_enable disable: write PTP_CTL=0x0000 not just clear BIT_0
- rtl8127_ptp_link_up_init() to restore PTP_CTL after cable pull

Fork URL is a placeholder pending publication; marked with TODO comment.

Assisted-by: Claude Sonnet 4.6
Signed-off-by: Raymond Knopp <raymond.knopp@eurecom.fr>
2026-06-07 08:23:03 +00:00
Raymond Knopp
d15ea06da3 doc: fix r8127_ptp.c Patch 2 description — spinlock deadlock
The previous description said to simply uncomment rtl8127_set_local_time()
inside rtl8127_hwtstamp_enable(). That causes an RCU stall because
_rtl8127_phc_settime() tries to re-acquire phy_lock, which is already held
by the caller. The correct fix is to move the call to after the spinlock is
released. Update the patch description accordingly.

Assisted-by: Claude Sonnet 4.6
Signed-off-by: Raymond Knopp <raymond.knopp@eurecom.fr>
2026-06-07 08:23:03 +00:00
Raymond Knopp
5257d49203 Testing modifications to 8127 driver for PTP.
Signed-off-by: Raymond Knopp <raymond.knopp@eurecom.fr>
2026-06-07 08:23:03 +00:00
Raymond Knopp
d3b3b1f83b doc: add FHI 7.2 peripheral build procedures for DragonWing
Extends dragonwing-build.md with three new cross-compilation sections:

- linuxptp 3.1.1 (ptp4l/phc2sys): correct KBUILD_OUTPUT and CROSS_COMPILE
  usage; document EXTRA_CFLAGS vs CFLAGS pitfall that drops $(incdefs)
  and causes HWTSTAMP_TX_ONESTEP_SYNC redeclaration errors.

- RTL8127 kernel modules (r8169 + realtek PHY, then Realtek out-of-tree
  r8127): mainline 6.18.12 cross-compile against vendor kernel config;
  vermagic matching via LOCALVERSION; KBUILD_MODPOST_WARN=1 workaround;
  r8127 with ENABLE_PTP_SUPPORT=y for hardware timestamping; hrtimer_init
  -> hrtimer_setup patch for kernel 6.18 API change.

Assisted-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Raymond Knopp <raymond.knopp@eurecom.fr>
2026-06-07 08:23:03 +00:00
Raymond Knopp
bc6a8625aa perf: add 512-bit two-pass cnProc kernel for AVX512BW
Add nrLDPC_cnProc_group_2pass_512() — a native 512-bit variant of the
two-pass min-sum CN processor — and update the BG1/BG2 wrappers to
dispatch to the widest available register set.

Kernel design:
- Uses AVX512BW mask-register comparisons (simde__mmask64) and
  predicated blend (simde_mm512_mask_blend_epi8) for single-cycle
  select instead of the vector-mask XOR trick used in the 256-bit path.
- Pass 1: simde_mm512_abs/xor/min_epu8/max_epu8 — same structure as
  256-bit kernel, 64 CNs per iteration.
- Pass 2: eq_mask = cmpeq_epi8_mask(|vk|, vmin1);
          out_mag = mask_blend_epi8(eq_mask, vmin1, vmin2);
          neg_mask = cmpgt_epi8_mask(zeros, other_xor);
          result = mask_blend_epi8(neg_mask, out_mag, -out_mag)

Wrapper dispatch (compile-time):
  __AVX512BW__: 512-bit, M = ceil(numCN*Z/64), off >>= 6
  __AVX2__:     256-bit, M = ceil(numCN*Z/32), off >>= 5
  else:         128-bit, M = ceil(numCN*Z/16), off >>= 4

All buffer strides (lut_numCnInCnGroups_{BG1_R13,BG2_R15}[grp] *
NR_LDPC_ZMAX) are divisible by 64, so the 512-bit stride is exact.

Compile-tested: -mavx512bw   → exit 0 (512-bit path)
  -mavx2       → exit 0 (256-bit path)
  -mno-avx2    → exit 0 (128-bit path, pre-existing Wpsabi warning)
Signed-off-by: Raymond Knopp <raymond.knopp@eurecom.fr>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-07 08:23:03 +00:00
Raymond Knopp
de4da3b1d5 perf: add 128-bit two-pass cnProc for aarch64/NEON targets
Add nrLDPC_cnProc_group_2pass_128() — a native 128-bit variant of the
two-pass min1/min2 CN processor — and restructure the file so the
two-pass section compiles on every target.

Changes:
- Close the #if !AVX512BW guard immediately after the existing generic
  BG1/BG2 256-bit functions (line ~857); the two-pass section is now
  outside that guard so it is visible on all platforms.
- Add #if defined(__AVX2__) || defined(__AVX512BW__) guard around the
  256-bit nrLDPC_cnProc_group_2pass kernel; the 128-bit kernel below
  it is always compiled.
- nrLDPC_cnProc_BG1_2pass / _BG2_2pass wrappers now dispatch at
  compile time:
    AVX2 / AVX512: 256-bit path, M = ceil(numCN*Z/32), off >>= 5
    aarch64 / SSE2: 128-bit path, M = ceil(numCN*Z/16), off >>= 4
  Each simde__m128i op maps to a single NEON instruction on aarch64,
  matching the code-generation strategy used by the existing
  cnProc128/ generated functions.

Compile-tested:
  -mavx2       → exit 0 (256-bit path taken)
  -mno-avx2    → exit 0 (128-bit path taken, one pre-existing warning)
  -mavx512bw   → pre-existing errors in nrLDPC_cnProc_avx512.h
                 (ones512_epi8 undeclared); not caused by this change.
Signed-off-by: Raymond Knopp <raymond.knopp@eurecom.fr>
2026-06-07 08:23:03 +00:00
Raymond Knopp
35a0b83a16 fix: two-pass cnProc sign corruption when input LLR == 0
sign_epi8(vsgn, vk) returns 0 when vk == 0, then stays 0 for all
subsequent accumulation steps, zeroing the sign product of every
output in that lane. Quantised LLRs hit exactly 0 regularly (e.g.
channel LLR +5 cancelled by a -5 CN message), so this caused
widespread incorrect CN-to-VN messages and decoder divergence.

Fix: accumulate sign parity via XOR of raw input bytes.
XOR with 0 is identity, so zero-valued inputs do not corrupt the
accumulator. In Pass 2, other_xor = vsgn_xor XOR vk removes self
via XOR self-inverse property (also zero-safe). Sign is then applied
using the (x XOR mask) - mask trick which correctly handles
out_mag == 0 and avoids the sign_epi8() zero-output problem.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Raymond Knopp <raymond.knopp@eurecom.fr>
2026-06-07 08:23:03 +00:00
Raymond Knopp
12cb9e16ff perf: add TWOPASS_CN_PROC dispatch in nrLDPC_decoder
Add //#define TWOPASS_CN_PROC alongside UNROLL_CN_PROC=1.
When UNROLL_CN_PROC is commented out and TWOPASS_CN_PROC is
defined, all four cnProc call sites (first-iter BG1/BG2,
loop BG1/BG2) dispatch to nrLDPC_cnProc_BG1_2pass /
nrLDPC_cnProc_BG2_2pass instead of the LUT-based variants.

To benchmark: comment UNROLL_CN_PROC and uncomment TWOPASS_CN_PROC.
To revert to LUT generic: comment both.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Raymond Knopp <raymond.knopp@eurecom.fr>
2026-06-07 08:23:03 +00:00
Raymond Knopp
00d84877c1 perf: add two-pass min1/min2 cnProc for BG1 and BG2 (CPU)
nrLDPC_cnProc_BG1_2pass and nrLDPC_cnProc_BG2_2pass replace the
LUT-exclude-self approach with a two-pass algorithm:

  Pass 1: single sweep over all numBN inputs to collect min1, min2
          (two smallest |vk|) and the full sign product via sign_epi8.
  Pass 2: re-read each vk, select min2 where |vk|==min1 (tie approx),
          remove self sign with sign_epi8(vsgn_all, vk), and store.

Memory reads per CN group: 2*numBN vs numBN*(numBN-1) for LUT approach:
  numBN= 4:  8 vs  12  (1.5x  fewer)
  numBN= 7: 14 vs  42  (3.0x  fewer)
  numBN=10: 20 vs  90  (4.5x  fewer)
  numBN=19: 38 vs 342  (9.0x  fewer)

A generic nrLDPC_cnProc_group_2pass(buf, res, numBN, M, off) helper
does the work; the BG1/BG2 wrappers loop over their degree groups and
derive the BN stride from lut_numCnInCnGroups_BG1/2_R13/15 exactly as
the existing functions do.  No new LUT tables are required.

Placed inside the #else (non-AVX512) block alongside the existing
BG1/BG2 functions; AVX512 path is unchanged.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Raymond Knopp <raymond.knopp@eurecom.fr>
2026-06-07 08:23:03 +00:00
Raymond Knopp
f046045c57 diag: expose DSP clock and improve power vote sequence
DSP-side:
- Add HAP_POWER_COMPUTE_CLIENT_CLASS app-type vote before DCVS_v3
- Add explicit HAP_power_set_HVX(power_up=TRUE)
- Add sleep_disable=HAP_DCVS_LPM_LEVEL1 to prevent DSP sleep modes
- Add HAP_power_request_abs(1000MHz) as fallback after DCVS_v3
- Change power-vote error log from FARF(HIGH,...) to FARF(ALWAYS,...)
  so it surfaces in logcat (HIGH is suppressed in unsigned-PD by default)
- Read actual DSP clock via HAP_power_get after voting; return in
  meta bytes [4..7] so ARM side can print it

ARM-side:
- Expand pre-allocated meta buffer from 4 to 8 bytes
- Print DSP clock from meta[4..7] on first decode call

Diagnosis: HAP_power_get returns 0 Hz — both the HAP power get/set and
hardware cycle counters are blocked in unsigned-PD on this device.
clk_summary confirms the CDSP remoteproc is running at 'xo' (19.2 MHz
crystal oscillator), not a PLL-derived turbo corner.  DCVS_v3 vote
appears silently accepted but has no effect on the actual DSP clock.

At 19.2 MHz: 38.3 ms/iter × 19.2 MHz = 735K cycles/iter vs simulator
344K — 2.1× overhead, consistent with ~2-cycle cache-miss penalty at
19.2 MHz (100 ns DRAM latency × 19.2 MHz ≈ 2 cycles).

At TURBO (1.2 GHz) with same 538 KB working set: estimated 1200-cycle
miss penalty per L1 miss would still yield ~110–150 ms — barely better
than 19.2 MHz.  The real fix requires eliminating the working set
problem (VTCM scratchpad, layered decoding, or cache-line locking).
Need to ask QCOM: (1) how to raise DSP clock in unsigned-PD on SA9000P,
(2) whether HAP_vtcm_request works in unsigned-PD.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Raymond Knopp <raymond.knopp@eurecom.fr>
2026-06-07 08:23:03 +00:00
Raymond Knopp
55678979f6 perf: add dcfetch/l2fetch software prefetch to bnProc phases
Hardware timing unchanged (still ~153 ms, Z=384): working set 538 KB
exceeds both L1 (32 KB) and L2 caches so miss latency dominates
regardless of prefetch depth.  Prefetch is cheap (no correctness risk)
and may help for smaller lifting factors (Z ≤ 128) where BN groups fit
in L2.

Changes:
- Add hvx_l2fetch_2d() helper: 2D l2fetch with descriptor
  [stride:16|width:16|height:16] for strided rectangular prefetch.
- hvx_bnProcPc groups 1+: issue l2fetch 2D at each 128-byte b-chunk
  for the next chunk across all cnidx+1 k-layers (stride=cnOff,
  width=128, height=cnidx+1 ≤ 30 → 3840 B ≤ 4096 limit).
- hvx_bnProc: issue 4 × dcfetch at start of each k-layer iteration
  to prefetch the first 128 bytes of the next k-layer before the
  inner b-loop consumes the current one.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Raymond Knopp <raymond.knopp@eurecom.fr>
2026-06-07 08:23:03 +00:00
Raymond Knopp
3fc07f2445 perf: HVX vectorise bnProc and bnProcPc — 5.7× per-iteration speedup
Replace scalar sat8 loops in bnProc and bnProcPc with HVX:
  widen int8→int16 (Q6_Wh_vsxt_Vb), add/sub in 16-bit, clamp to
  [-128,127] with vmin/vmax, pack low bytes (Q6_Vb_vpacke_VhVh).
  Both functions have a scalar tail for Z not a multiple of 128
  (for Z=384=3×128 the tail is never taken).

Simulator cycle counts (BG1, Z=384, 8-iter avg):
  bnProcPc: 668 616 → 19 551 cycles  (34× faster)
  bnProc:   949 458 → 15 705 cycles  (60× faster)
  per-iteration total: 1 920 877 → 338 029 cycles (5.7× overall)

Remaining bottleneck: cn2bn + bn2cn permutations now 82% of cycles.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Raymond Knopp <raymond.knopp@eurecom.fr>
2026-06-07 08:23:03 +00:00
Raymond Knopp
acefc9294b fix: exclude sim binary and stats file from git
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Raymond Knopp <raymond.knopp@eurecom.fr>
2026-06-07 08:23:03 +00:00
Raymond Knopp
5fc94e0e58 perf: add Hexagon simulator profiling harness for LDPC phase breakdown
Adds a standalone test (sim/ldpc_sim_main.c + sim/Makefile) that builds
as a Hexagon executable and runs under hexagon-sim.  Uses
hexagon_sim_read_pcycles() to time each BP phase independently —
hardware cycle counters are inaccessible in unsigned-PD on the device
but work correctly in the simulator.

Results (BG1, Z=384, 8-iter average, v73 simulator):
  cnProc  (HVX min-sum)   :  24 951 cycles   ( 1%)
  cn2bn   (permutation)   : 146 121 cycles   ( 8%)
  bnProcPc (BN+posterior) : 668 616 cycles   (35%)
  bnProc  (BN extrinsic)  : 949 458 cycles   (49%)
  bn2cn   (permutation)   : 131 607 cycles   ( 7%)
  per-iteration total     : 1 920 877 cycles

cnProc was the wrong optimisation target.  bnProc+bnProcPc account for
84% of cycles and have sequential memory access — prime targets for HVX.

Guards LDPC_SIM_STANDALONE in ldpc_hexagon_imp.c exclude the FastRPC
session lifecycle and qaic-generated dispatch header from the sim build.

Also adds cnProc_algorithm_notes.md: analysis of two-pass min1/min2 vs
OAI LUT exclude-self memory traffic tradeoffs.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Raymond Knopp <raymond.knopp@eurecom.fr>
2026-06-07 08:23:03 +00:00
Raymond Knopp
727fd68bc4 perf: HVX cnProc vectorisation + ARM-side phase profiling
cnProc_group() in ldpc_hexagon_imp.c now uses 128B HVX intrinsics
(Q6_Vb_vabs_Vb, Q6_Vub_vmin_VubVub, Q6_Q_vcmp_gt_VubVub, Q6_V_vmux_QVV,
Q6_Vb_vsub_VbVb) to process all Z lifting symbols in parallel.  For
Z = 384 = 3×128 the code is pure HVX with no scalar tail.

CMakeLists.txt adds -mhvx -mhvx-length=128B for the DSP skel target.
BER = 0 verified on device at 2.8 dB (100 frames).

No wall-clock improvement observed (~153 ms unchanged).
ARM-side phase profiling (diag=0/1/2 modes in ldpc_hexagon_arm.c) shows:
  RPC overhead:  ~220 µs  (0.14%)
  scatter+gather: ~220 µs (0.14%)
  BP compute:    ~152.8 ms (99.7%)

The cDSP hardware cycle counters (C15:14 pcycles, C31:30 qtimer) and
QURT pcycle API are inaccessible from unsigned-PD user mode on this
device.  ARM wall-clock is the only available timing source.

The bottleneck is cache-miss latency in the BP loop, not compute
throughput.  The cnProcBuf working set (121 KB, stride bitOff ≈ 8–12 KB)
exceeds the DSP L1 cache (32 KB), causing a cache miss on nearly every
HVX vector load.  HVX increases throughput per load but cannot hide
miss latency — the loop is latency-bound, not compute-bound.

Next steps to improve BP throughput:
  - Add software prefetch (dcfetch) to overlap miss latency with HVX
  - Vectorise bnProc and bn2cn/cn2bn permutation phases
  - Consider buffer layout change to reduce stride

Also updates hexagon_offload_template to reflect validated patterns
from the LDPC offload (unsigned-PD setup, pre-allocation, rpcmem.a
exclusion, IDL naming, rpcmem init ordering).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Raymond Knopp <raymond.knopp@eurecom.fr>
2026-06-07 08:22:56 +00:00
Raymond Knopp
252719a8d1 perf: pre-allocate DSP working buffers and vote turbo DCVS
Pre-allocate the six BP working buffers (~540 KB total) in the FastRPC
session context at ldpc_hexagon_open time rather than calloc/free-ing
them on every decode call.  At high call rates (e.g. ldpctest loops)
per-call heap allocation exhausts the DSP heap; pre-allocation also
eliminates the allocation latency from the critical path.

Request turbo core and bus clock corners via HAP_power_set DCVS_v3 at
session open with sleep latency = 1 µs to prevent the cDSP from
clock-gating between frames.

Note: at the current scalar baseline (~150 M ops / decode call) the
dominant cost is the BP computation itself; these changes do not yet
produce a measurable latency reduction.  They are correct foundation
work before the HVX vectorisation of cnProc_group.

Signed-off-by: Raymond Knopp <raymond.knopp@openairinterface.org>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Raymond Knopp <raymond.knopp@eurecom.fr>
2026-06-07 08:22:56 +00:00
Raymond Knopp
6e36071df7 doc: update README to reflect current implementation state
- params table: offset 3 is `diag`, not reserved
- LDPCdecoder flow: rpcmem buffers are pre-allocated in LDPCinit, not per-call
- remove stale limitation about per-call rpcmem_alloc (already fixed)

Signed-off-by: Raymond Knopp <raymond.knopp@openairinterface.org>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Raymond Knopp <raymond.knopp@eurecom.fr>
2026-06-07 08:22:55 +00:00
Raymond Knopp
31a17813ae fix: correct bit-packing and harden Hexagon LDPC FastRPC offload
arm_llr2bitPacked was packing bits LSB-first (1u<<i) while OAI's
nrLDPC_llr2bitPacked and the LDPC encoder both use MSB-first
(bit 7 = sign of first LLR in group).  The mismatch caused BER≈50%
at all SNRs.  Fix: change to (1u << (7-i)).

Additional robustness changes validated on DragonWing SA9000P:
- Enable unsigned-PD mode via DSPRPC_CONTROL_UNSIGNED_MODULE before
  opening the FastRPC session; required for custom skels on Linux HLOS
  with fastrpc ≥ 1.0.4.
- Use "&_dom=cdsp" URI suffix (string literal instead of integer domain
  index) to identify the cDSP on Linux HLOS.
- Pre-allocate ION-backed rpcmem buffers once in LDPCinit rather than
  per-codeword; per-call alloc/free exhausted the rpcmem pool within
  a few hundred codewords.
- Order rpcmem_init() before ldpc_hexagon_open() so the DMA heap is
  available when the FastRPC shell is loaded.
- Add diag mode dispatch in ldpc_hexagon_decode (DSP side) for
  memcpy/scatter-gather passthrough modes used during bringup.
- CMakeLists.txt: integrated OAI cross-compile path (HEXAGON_LDPC=ON),
  T_IDs.h host-side generation, rpcmem.a exclusion rationale.

Verified: ldpctest -v _hexagon gives BER=0 at 2.7 dB (BG1 R13 Z=384).

Signed-off-by: Raymond Knopp <raymond.knopp@openairinterface.org>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Raymond Knopp <raymond.knopp@eurecom.fr>
2026-06-07 08:22:55 +00:00
Raymond Knopp
025206e410 cmake: add -DHEXAGON_LDPC=ON option for top-level ARM stub build
Adds option(HEXAGON_LDPC ...) to CODING/CMakeLists.txt so that
libldpc_hexagon.so can be built from the normal DragonWing top-level
cmake invocation without needing OS_TYPE set manually:

  cmake ... -DHEXAGON_LDPC=ON -DHEXAGON_SDK_ROOT=/opt/Hexagon_SDK/6.4.0.2

The DSP skel (libldpc_hexagon_skel.so) is always built separately with
the hexagon-clang toolchain as before.  ldpctest gains a dependency on
ldpc_hexagon so it can be used to test the offload path.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Raymond Knopp <raymond.knopp@eurecom.fr>
2026-06-07 08:22:55 +00:00
Raymond Knopp
c124997d3c build: include LDPC encoder in libldpc_hexagon.so
Add ldpc_encoder_optim8segmulti.c to the ARM (HLOS) build target so that
libldpc_hexagon.so exports all four symbols expected by ldpctest and the
OAI segmentation layer: LDPCinit, LDPCshutdown, LDPCdecoder, LDPCencoder.

The encoder runs on the ARM side unchanged. Offloading the encoder to the
Hexagon DSP can be evaluated separately.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Raymond Knopp <raymond.knopp@eurecom.fr>
2026-06-07 08:22:55 +00:00
Raymond Knopp
7a74c9f377 doc: add README for nrLDPC_decoder_hexagon
Covers interface, wire format, algorithm (min-sum BP, buffer layout),
build instructions for ARM stub and DSP skel, deploy steps, and the
HVX upgrade path for cnProc_group.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Raymond Knopp <raymond.knopp@eurecom.fr>
2026-06-07 08:22:55 +00:00
Raymond Knopp
af244b8be9 feat: add scalar Hexagon LDPC decoder offload via FastRPC
Adds openair1/PHY/CODING/nrLDPC_decoder/nrLDPC_decoder_hexagon/:

  inc/ldpc_hexagon.idl  — FastRPC interface (qaic → stub/skel)
  src/ldpc_hexagon_arm.c — ARM stub: exports LDPCinit/LDPCshutdown/LDPCdecoder,
      marshals params+LLRs into rpcmem, calls cDSP, post-processes output.
  src/ldpc_hexagon_imp.c — DSP scalar decoder:
      · scalar_cnProc: min-sum with first/second-minimum tracking
      · scalar_bnProcPc: saturating sum (posterior LLR computation)
      · scalar_bnProc: extrinsic message (posterior minus self CN message)
      · scalar_cnProcPc: XOR-of-signs parity check
      · heap-allocated working buffers (~540 KB total, avoids DSP stack limits)
      · reuses nrLDPC_mPass.h (circular memcpy) and nrLDPC_init.h (LUTs)
      · HVX placeholder: replace cnProc_group() loops with Q6_V_vmux_QVV etc.
  CMakeLists.txt — standalone dual-mode build (OS_TYPE=HLOS for ARM stub,
      default for DSP skel); OAI CODING/CMakeLists.txt adds the subdirectory
      when QUALCOMM_DRAGONWING and HEXAGON_SDK_ROOT are set.

The IDL interface passes LLRs in and returns posterior LLRs out; ARM side
handles outMode bit-packing so the DSP stays format-agnostic.
check_crc (function pointer) is not crossed over FastRPC; convergence is
determined by min-sum parity check on the DSP.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Raymond Knopp <raymond.knopp@eurecom.fr>
2026-06-07 08:22:55 +00:00
Raymond Knopp
3bcfd7beaa perf: eliminate mm_sign_epi8 in LDPC cnProc generators for aarch64
Under AVOID_SIGN (cross-compile for aarch64 or cmake -DAVOID_SIGN=ON):

BG1: the output-store `mm_sign_epi8(min, sgn)` — previously left unguarded
and translated by SIMDe into 5-6 NEON instructions — is now replaced with a
3-instruction SSE2-clean conditional negate:
  msk = vcltq_s8(sgn, 0)          (1 instr)
  xor = veorq_u8(min, msk)        (1 instr)
  out = vsubq_s8(xor, msk)        (1 instr)
The init step `xor_si128(ones, ymm0)` is simplified to `sgn = ymm0` (the
XOR with ones only flips low bits that mm_sign_epi8 never reads), and the
`ones` variable is dropped from the generated function signature.

BG2: add AVOID_SIGN support from scratch — init, accumulate, and output-store
in all six CN groups (G3, G4, G5, G6, G8, G10) now follow the same
SSE2-clean pattern as BG1.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Raymond Knopp <raymond.knopp@eurecom.fr>
2026-06-07 08:22:55 +00:00
Raymond Knopp
c8c6380c4e cmake: add AVOID_SIGN option for aarch64 cross-compile LDPC generators
When Step 1 (native host tools) is building generators that will produce
code for an aarch64 target, __aarch64__ is not defined on the x86 host,
so cnProc_gen_BG1_128.c never sets AVOID_SIGN and emits mm_sign_epi8.
SIMDe's translation of mm_sign_epi8 is incorrect for the MSB-only sign
extraction pattern in the CN processing kernel; mm_xor_si128 is
equivalent and translates correctly.

Add -DAVOID_SIGN=ON as an explicit cmake option for Step 1 of the
DragonWing cross-compile workflow.  Has no effect in Step 2 (generators
are not rebuilt when CROSS_COMPILE is set).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Raymond Knopp <raymond.knopp@eurecom.fr>
2026-06-07 08:22:55 +00:00
Raymond Knopp
57cd2c9441 small change in README.md
Signed-off-by: Raymond Knopp <raymond.knopp@eurecom.fr>
2026-06-07 08:22:55 +00:00
Raymond Knopp
41b9fb584c feat: add Hexagon DSP offload template for DragonWing
Provides a self-contained starting point for offloading computation from
OAI ARM cores to the Hexagon cDSP via Qualcomm FastRPC:

- inc/hexdsp_offload.idl  — QIDL interface definition (qaic input)
- src/hexdsp_offload_arm.c — ARM glue: rpcmem bounce buffers + OAI
                             dlopen-compatible init/shutdown/process exports
- src/hexdsp_offload_imp.c — DSP skel stub with HVX placeholder
- CMakeLists.txt           — single file builds ARM stub (OS_TYPE=HLOS)
                             or DSP skel (hexagon_toolchain.cmake)
- README.md                — explains FastRPC, ION/rpcmem memory model,
                             build/deploy steps, OAI integration pattern,
                             HVX enablement, debugging

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Raymond Knopp <raymond.knopp@eurecom.fr>
2026-06-07 08:22:55 +00:00
Raymond Knopp
f3661bbb1d doc: add linuxptp 3.1.1 cross-build section for ptp4l/phc2sys
The DragonWing does not ship with ptp4l or phc2sys; both are required
for S-plane synchronization in the FHI 7.2 setup (version 3.1.1 per
ORAN_FHI7.2_Tutorial.md).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Raymond Knopp <raymond.knopp@eurecom.fr>
2026-06-07 08:22:55 +00:00
Raymond Knopp
9f41e3876f cmake: fix -march=native in oran_fhlib_5g for cross-compilation
-march=native is meaningless for a cross-compiler and causes build
failure. Use -mcpu=cortex-a78+crypto for DragonWing (crypto extension
needed for armral BFP compression), empty string for other cross-compile
targets, and -march=native only for native builds.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Raymond Knopp <raymond.knopp@eurecom.fr>
2026-06-07 08:22:55 +00:00
Raymond Knopp
9910cd32db doc: fix armral cross-build: +crypto suffix and correct mcpu flag
GCC 11.3 does not forward the crypto extension to the assembler when
using -mcpu=cortex-a78 alone; pmull/pmull2 instructions then fail with
"selected processor does not support". Using -mcpu=cortex-a78+crypto
makes the extension explicit and passes it through to the assembler.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Raymond Knopp <raymond.knopp@eurecom.fr>
2026-06-07 08:22:55 +00:00
Raymond Knopp
26793f76de doc: fix libxran cross-build: use make args, CPATH, and correct RTE_SDK
- Pass CC/CPP/AR/AS/LD as make command-line args (not env vars) so they
  override the armv8 branch hardcoding CC := gcc in the xran Makefile
- Use CPATH to inject Ubuntu arm64 system headers without touching the
  Makefile's CC_FLAGS variable
- Point RTE_SDK at the installed DPDK prefix ($DW_SYSROOT), not the
  source tree, so pkg-config resolves the correct arm64 include paths
- Add libnuma-dev:arm64 install step

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Raymond Knopp <raymond.knopp@eurecom.fr>
2026-06-07 08:22:55 +00:00
Raymond Knopp
306308f42a doc: push dpdk-devbind.py to device in FHI 7.2 deploy section
Needed to bind VFs to vfio-pci before starting the gNB, matching the
/usr/local/bin/dpdk-devbind.py path used in ORAN_FHI7.2_Tutorial.md.
Python 3 and ethtool are already present on the DragonWing device.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Raymond Knopp <raymond.knopp@eurecom.fr>
2026-06-07 08:22:55 +00:00
Raymond Knopp
c57f33c034 doc: wipe build-dragonwing before meson setup to avoid stale libelf cache
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Raymond Knopp <raymond.knopp@eurecom.fr>
2026-06-07 08:22:55 +00:00
Raymond Knopp
7c06beb3cb doc: set PKG_CONFIG_LIBDIR and disable bpf lib for DPDK cross-build
Without PKG_CONFIG_LIBDIR restricted to arm64 packages, meson picks up
x86 libelf and the link fails with "file in wrong format".  Disable the
bpf library (-Ddisable_libs=bpf) to drop the libelf dependency entirely
since it is not needed for the fronthaul use case.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Raymond Knopp <raymond.knopp@eurecom.fr>
2026-06-07 08:22:55 +00:00
Raymond Knopp
bc91687122 doc: add [properties] platform=generic to meson cross-file for DPDK
When cross-compiling, DPDK's arm/meson.build reads the platform via
meson.get_external_property(), not from the -Dplatform= command-line
option.  The [properties] section must coexist with [built-in options]
since they serve different purposes.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Raymond Knopp <raymond.knopp@eurecom.fr>
2026-06-07 08:22:55 +00:00
Raymond Knopp
4c47fc66a5 doc: fix meson cross-file cpu field for DPDK aarch64 build
DPDK passes the cpu field directly as -march=<cpu>, so cortex-a78
(a valid -mcpu value) fails.  Use armv8.2-a instead, which is the
correct GCC -march flag for the Cortex-A78 architecture level.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Raymond Knopp <raymond.knopp@eurecom.fr>
2026-06-07 08:22:55 +00:00
Raymond Knopp
0f9da769e1 doc: hardcode SDK paths in meson cross-file, use \$HOME for prefix
Using <<'EOF' prevents variable expansion so compiler paths are always
literal.  Replace \$DW_SYSROOT with \$HOME/dw-sysroot in the meson
--prefix to avoid empty-variable errors if the env var is not set.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Raymond Knopp <raymond.knopp@eurecom.fr>
2026-06-07 08:22:55 +00:00
Raymond Knopp
ac3e9022e5 doc: fix meson cross-file format and add env var warning
Update [properties] to [built-in options] and pkgconfig to pkg-config
for meson >= 1.3 compatibility.  Add a note that DW_TC/DW_SYSROOT must
be exported before running any build commands in this section.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Raymond Knopp <raymond.knopp@eurecom.fr>
2026-06-07 08:22:55 +00:00
Raymond Knopp
04b803f9f4 doc: move basic adb deploy to section 2.3, fix stale paths
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Raymond Knopp <raymond.knopp@eurecom.fr>
2026-06-07 08:22:55 +00:00
Raymond Knopp
92c7e8ee40 doc: add DPDK/libxran/armral cross-compilation for DragonWing FHI 7.2
Documents the full dependency chain for building the O-RAN 7.2 fronthaul
interface on the DragonWing IQ-9/X:
- DPDK 24.11.4 (K) / 20.11.9 (F) via meson with an aarch64 cross-file
- libxran 11.1.1 (K) / 6.1.9 (F) via its Makefile with CC/TARGET=armv8
- armral 25.01 via cmake with the DragonWing toolchain file
- OAI gNB cmake invocation with -DOAI_FHI72=ON and staging sysroot
- adb deployment of FHI 7.2 binaries and shared libraries

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Raymond Knopp <raymond.knopp@eurecom.fr>
2026-06-07 08:22:55 +00:00
Raymond Knopp
f93ebdc494 build: fix DragonWing toolchain sysroot header conflict
The Hexagon SDK GCC's built-in sysroot has an incomplete glibc
(L_tmpnam undeclared in stdio.h).  Fix by prepending Ubuntu system
headers via -I flags, which take priority over the compiler's sysroot
paths.  Also restore the linker -L/usr/lib/aarch64-linux-gnu flags and
drop the non-existent /usr/aarch64-linux-gnu CMAKE_SYSROOT and
CMAKE_FIND_ROOT_PATH settings.  Confirmed producing aarch64 ELF binaries.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Raymond Knopp <raymond.knopp@eurecom.fr>
2026-06-07 08:22:55 +00:00
Raymond Knopp
b60b1437f5 doc: fix dragonwing build directory layout and cmake toolchain warning
Correct the two-step build paths to use build/ and dragonwing_build/
directly under the repo root.  Add an explicit warning that
CMAKE_TOOLCHAIN_FILE is silently ignored if CMakeCache.txt already
exists in the build directory, and how to avoid it.  Drop -GNinja
in favour of plain make.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Raymond Knopp <raymond.knopp@eurecom.fr>
2026-06-07 08:22:55 +00:00
Raymond Knopp
25b555e6ac build: add cross-compilation support for Qualcomm DragonWing IQ-9/X (ARM)
Introduces cmake_targets/cross-arm-dragonwing.cmake using the GCC
cross-compiler bundled with the Hexagon SDK 6.4.x (aarch64-none-linux-gnu).
Adds QUALCOMM_DRAGONWING flag handling in CMakeLists.txt to select
-mcpu=cortex-a78 (Kryo 780 Gold) instead of the generic -march=armv8.2-a.
Includes doc/dragonwing-build.md covering prerequisites, the two-step
build procedure, adb deployment, and troubleshooting.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Raymond Knopp <raymond.knopp@eurecom.fr>
2026-06-07 08:22:55 +00:00
Raymond Knopp
f668d2e527 nrLDPC: AArch64 ns=8 input interleaving fast path with asm-volatile OR-tree
Replace the generic j-loop (7 serial ORRs, 28-cycle critical path, j-loop
overhead) with a fully-unrolled ns=8 specialization for Cortex-A72:

 - Pre-extract 8 segment base pointers into registers so GCC holds them
   across the 528-iteration inner loop instead of reloading from input[].

 - CONTRIB macro: vcombine(vld1_dup(p+bi), vld1_dup(p+bi+1)) + VTST +
   VAND to produce each segment's bit-mask contribution in one expression;
   avoids ANDNOT+CMPEQ+AND (saves one NEON op per segment per iteration).

 - asm volatile barrier 1: "+w" on all 8 contributions forces GCC to
   materialize r0..r7 as physical NEON registers before any ORR is issued.
   Without it GCC left-folds each ORR greedily as soon as one input is
   ready (28-cycle serial chain).  With it the four level-1 ORRs are
   data-independent and A72's 2-wide NEON pipe issues two per cycle.

 - asm volatile barrier 2: "+w" on r01/r23/r45/r67 forces level-1 results
   before level-2.  GCC's instruction scheduler then fills the 4-cycle
   level-2 ORR latency gap with the loop's scalar work (add i_byte, cmp),
   placing those instructions between level-2 and level-3/4 ORRs.  This
   lets A72's out-of-order engine start the next iteration's address chain
   (lsr bi = i_byte>>3) ~4 cycles earlier, hiding load latency for free.

 - ns != 8 fallback: original j-loop preserved for 1..7 segments.

Measured on NXP LS2160A (Cortex-A72, 2.0 GHz), BG1 Zc=384 (-n100 -S8 -s3.0):
  Before: ~13 µs  →  After: ~9-10 µs  (~25% reduction)

Assisted by Claude Sonnet 4.6
2026-06-07 08:28:07 +02:00
Raymond Knopp
3ff24df579 LDPC encoder AArch64 optimizations (BG1 Zc=384).
Input interleaving (ldpc_encoder_optim8segmulti.c): replace the SIMDE
shuffle_epi8 path (-> NEON TBL, 5-cycle latency) with direct NEON
vcombine(vld1_dup_u8, vld1_dup_u8) to broadcast each input byte, and
use VTST+AND instead of ANDNOT+CMPEQ+AND (one fewer op per segment per
iteration).  On NXP LS2160A (Cortex-A72, 2 GHz) this reduces the input
interleaving phase from 20.2 us to ~2.0 us (10x speedup), and total
encoder latency from ~126 us to ~107 us for 8 segments.

Parity-check VLA fix (ldpc_encode_parity_check.c): under USE_ALIGNR
(aarch64, BG1, Zc=384) the simd_size memcpy copies are skipped, so
allocate only one copy (vla_simd=1) instead of simd_size=32, reducing
the stack VLA from 528 KB to 16,896 bytes.

CMakeLists.txt: add Cortex-A72 (CPUPART 0xd08) detection with
-mcpu=cortex-a72 -march=armv8-a+simd (ARMv8.0-A only, no ARMv8.2-A).

ldpctest.c: wire per-phase timers (tinput, tparity, toutput,
tinput_memcpy) into the encoder_implemparams so each phase can be
measured independently.

ldpc_encoder.c / ldpc384_byte_128.c: add code generator and generated
output for a 4-wide 128-bit parity-check variant (ldpc_BG1_Zc384_byte_128_x4)
that unrolls 4 i2 iterations per outer loop; not yet wired into the
call path.

Assisted by Claude Sonnet 4.6
2026-06-06 23:27:28 +02:00
Raymond Knopp
66253f2289 TX optimizations for gNB.
- phase compensation in symbol processing to allow for thread-pool parallelization
- some aarch64 optimizations for precoding and layer mapping
2026-06-06 07:39:19 +02:00
353 changed files with 14482 additions and 18532 deletions

View File

@@ -5,4 +5,3 @@ common/utils/T/T_IDs.h
common/utils/T/T_messages.txt.h
common/utils/T/genids
common/utils/T/genids.o
build/

View File

@@ -1,15 +1,14 @@
name: Duranta Jenkins Dispatch
on:
pull_request_target:
pull_request:
types: [opened, synchronize, reopened, labeled]
branches: [develop]
push:
branches:
- develop
concurrency:
group: ${{ github.event_name }}-${{ github.event.action }}-${{ github.event.pull_request.number || github.ref }}
group: ${{ github.event_name }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
permissions:
@@ -17,72 +16,12 @@ permissions:
pull-requests: write
jobs:
verify-signed-commits:
runs-on: ubuntu-24.04
if: github.event_name == 'pull_request_target'
steps:
- name: Check all commits are signed
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
UNSIGNED=$(gh api repos/${{ github.repository }}/pulls/${{ github.event.pull_request.number }}/commits \
--paginate --jq '[.[] | select(.commit.verification.verified == false) | .sha[:7]] | join(", ")')
if [ -n "$UNSIGNED" ]; then
echo -e "$UNSIGNED commits are missing signature"
gh pr comment ${{ github.event.pull_request.number }} --repo ${{ github.repository }} --body "$(cat <<EOF
**Validation:**
The following commit(s) are missing signature:
$UNSIGNED
Please use 'git commit -S' to sign your commits.
For detailed instructions, refer to the [CONTRIBUTING.md](https://github.com/duranta-project/openairinterface5g/blob/develop/CONTRIBUTING.md) file at the root of this repository or [GitHub Docs](https://docs.github.com/en/authentication/managing-commit-signature-verification/about-commit-signature-verification)
EOF
)"
exit 1
fi
check-labels:
needs: verify-signed-commits
runs-on: ubuntu-24.04
if: github.event_name == 'pull_request_target'
steps:
- name: Check for mandatory CI label
run: |
PR_LABELS=$(echo '${{ toJson(github.event.pull_request.labels) }}' | jq -r '.[].name')
for label in \
"${{ vars.BUILD_ONLY_LABEL }}" \
"${{ vars.DOC_LABEL }}" \
"${{ vars.NR_LABEL }}" \
"${{ vars.NRUE_LABEL }}" \
"${{ vars.LTE_LABEL }}" \
"${{ vars.CI_LABEL }}" \
"${{ vars.RETRIGGER_CI_LABEL }}"; do
if echo "$PR_LABELS" | grep -qxF "$label"; then
exit 0
fi
done
exit 1
detect-changes:
needs: check-labels
runs-on: ubuntu-24.04
if: |
always() && (
github.event_name == 'push' ||
needs.check-labels.result == 'success'
) && (
github.event.action != 'labeled' ||
github.event.label.name == vars.RETRIGGER_CI_LABEL
)
github.event.action != 'labeled' ||
github.event.label.name == vars.RETRIGGER_CI_LABEL
outputs:
protected_files_changed: ${{ steps.filter.outputs.protected }}
@@ -100,18 +39,12 @@ jobs:
- 'ci-scripts/**'
- '.github/workflows/**'
- '.github/actions/**'
- 'docker/**'
- 'charts/**'
- 'openshift/**'
- 'cmake_targets/build_oai'
- 'cmake_targets/tools/build_helper'
require-maintainer-approval:
needs: detect-changes
if: |
always() &&
needs.detect-changes.outputs.protected_files_changed == 'true'
if: needs.detect-changes.outputs.protected_files_changed == 'true'
runs-on: ubuntu-24.04
environment:
@@ -122,7 +55,6 @@ jobs:
trigger-jenkins:
needs:
- check-labels
- detect-changes
- require-maintainer-approval
@@ -137,8 +69,6 @@ jobs:
steps:
- uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.sha || github.ref }}
- name: Trigger Jenkins
run: |
@@ -150,41 +80,54 @@ jobs:
# Filter and send
jq -c 'del(.pull_request.body)' /tmp/payload.json > /tmp/filtered_payload.json
# Remap pull_request_target -> pull_request for Jenkins compatibility
EVENT_NAME="${{ github.event_name }}"
if [ "$EVENT_NAME" = "pull_request_target" ]; then
EVENT_NAME="pull_request"
fi
curl -X POST "https://${{ secrets.J_USER }}:${{ secrets.J_PASS }}@${{ secrets.J_URL }}" \
-H "Accept: */*" \
-H "Content-Type: application/json" \
-H "User-Agent: GitHub-Hookshot/${{ secrets.H_AGENT }}" \
-H "X-Github-Delivery: ${{ secrets.GITHUB_TOKEN }}" \
-H "X-Github-Event: $EVENT_NAME" \
-H "X-Github-Event: ${{ github.event_name }}" \
-H "X-Github-Hook-Id: ${{ secrets.H_ID }}" \
-H "X-Github-Hook-Installation-Target-Id: ${{ secrets.H_TARGET }}" \
-H "X-Github-Hook-Installation-Target-Type: repository" \
--data @/tmp/filtered_payload.json
cleanup:
needs:
- verify-signed-commits
- check-labels
- detect-changes
- require-maintainer-approval
- trigger-jenkins
if: always() && github.event_name == 'pull_request_target'
runs-on: ubuntu-24.04
steps:
- name: Remove retrigger-ci label
run: |
PR_LABELS=$(echo '${{ toJson(github.event.pull_request.labels) }}' | jq -r '.[].name')
if echo "$PR_LABELS" | grep -qxF "${{ vars.RETRIGGER_CI_LABEL }}"; then
gh pr edit ${{ github.event.pull_request.number }} --remove-label "${{ vars.RETRIGGER_CI_LABEL }}" --repo ${{ github.repository }}
fi
if: always() && github.event.action == 'labeled' && github.event.label.name == vars.RETRIGGER_CI_LABEL
run: gh pr edit ${{ github.event.pull_request.number }} --remove-label "${{ vars.RETRIGGER_CI_LABEL }}"
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
welcome-first-time-contributor:
runs-on: ubuntu-24.04
if: github.event_name == 'pull_request' && github.event.action == 'opened'
steps:
- name: Post welcome message
uses: actions/first-interaction@v1
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
pr-message: |
Welcome @${{ github.event.pull_request.user.login }}! Thanks for opening your first PR in this project!
Here are a few things to help you get started:
- Read the [Contributing Guide](https://github.com/${{ github.repository }}/blob/develop/CONTRIBUTING.md) to understand our development process.
- Check the [issues page](https://github.com/duranta-project/openairinterface5g/issues) to see if your PR is related to an existing issue.
- Add a [label](https://github.com/duranta-project/openairinterface5g/labels/) to categorize your PR so reviewers can triage it faster.
pr-retrigger-ci-instructions:
runs-on: ubuntu-24.04
if: github.event_name == 'pull_request' && github.event.action == 'opened'
steps:
- name: Post CI retrigger instructions
run: |
gh pr comment ${{ github.event.pull_request.number }} --repo ${{ github.repository }} --body "$(cat <<'EOF'
The CI pipeline runs automatically when this pull request is opened or updated.
To manually retrigger the CI pipeline without making any changes to the pull request, add the https://github.com/duranta-project/openairinterface5g/labels/retrigger-ci label to this PR.
The label will be removed automatically after the pipeline is triggered.
EOF
)"
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View File

@@ -153,6 +153,15 @@ endif()
eval_boolean(AUTODETECT_AVX512 DEFINED CPUFLAGS AND CPUFLAGS MATCHES "avx512")
add_boolean_option(AVX512 ${AUTODETECT_AVX512} "Whether AVX512 intrinsics is available on the host processor" ON)
# When the native-tools build (Step 1) is preparing generators for a
# cross-compile targeting aarch64, pass -DAVOID_SIGN=ON so that the LDPC
# code generators emit mm_xor_si128 instead of mm_sign_epi8. SIMDe's
# translation of mm_sign_epi8 is incorrect for the sign-extraction pattern
# used in cnProc; mm_xor_si128 is equivalent for the MSB-only use case and
# translates correctly. Has no effect when CROSS_COMPILE is set (Step 2
# does not build the generators).
add_boolean_option(AVOID_SIGN OFF "Use mm_xor_si128 instead of mm_sign_epi8 in LDPC generators (required when cross-compiling for aarch64)" OFF)
eval_boolean(AUTODETECT_AVX2 DEFINED CPUFLAGS AND CPUFLAGS MATCHES "avx2")
add_boolean_option(AVX2 ${AUTODETECT_AVX2} "Whether AVX2 intrinsics is available on the host processor" ON)
@@ -161,7 +170,11 @@ add_boolean_option(GFNI ${AUTODETECT_GFNI} "Whether GFNI intrinsics is available
message(STATUS "CPU architecture is ${CMAKE_SYSTEM_PROCESSOR}")
if (CROSS_COMPILE)
message(STATUS "setting as aarch64")
if (QUALCOMM_DRAGONWING)
message(STATUS "Target: Qualcomm DragonWing IQ-9/X (Cortex-A78 / ARMv8.2-A)")
else()
message(STATUS "setting as aarch64")
endif()
set(C_FLAGS_PROCESSOR "${C_FLAGS_PROCESSOR} -gdwarf-2 -lgcc -lrt")
elseif(CMAKE_SYSTEM_PROCESSOR STREQUAL "x86_64")
# The following intrinsics are assumed to be available on any x86 system
@@ -202,6 +215,8 @@ elseif (CMAKE_SYSTEM_PROCESSOR STREQUAL "aarch64")
set(C_FLAGS_PROCESSOR "${C_FLAGS_PROCESSOR} -gdwarf-2 -mcpu=neoverse-n1 -lgcc -lrt")
elseif (CPUPART MATCHES "0xd49") # Neoverse-N2
set(C_FLAGS_PROCESSOR "${C_FLAGS_PROCESSOR} -gdwarf-2 -mcpu=neoverse-n2 -ftree-vectorize -lgcc -lrt")
elseif (CPUPART MATCHES "0xd08") # Cortex-A72 (e.g. NXP LS2160A)
set(C_FLAGS_PROCESSOR "${C_FLAGS_PROCESSOR} -gdwarf-2 -mcpu=cortex-a72 -lgcc -lrt")
elseif (CPUPART MATCHES "0xd03") # Cortex-A53
set(C_FLAGS_PROCESSOR "${C_FLAGS_PROCESSOR} -gdwarf-2 -mcpu=cortex-a53 -lgcc -lrt")
else ()
@@ -212,14 +227,23 @@ else()
endif()
if (NOT CROSS_COMPILE)
if (CPUPART MATCHES "0xd03")
# Cortex-A53 does not support ARMv8.2-A extensions
if (CPUPART MATCHES "0xd08")
# Cortex-A72 is ARMv8.0-A only (no ARMv8.2-A); hardware includes crypto (aes, pmull, sha1, sha2)
set(C_FLAGS_PROCESSOR "${C_FLAGS_PROCESSOR} -march=armv8-a+simd+crypto")
elseif (CPUPART MATCHES "0xd03")
# Cortex-A53 is ARMv8.0-A only; crypto extension is optional and not assumed present
set(C_FLAGS_PROCESSOR "${C_FLAGS_PROCESSOR} -march=armv8-a+simd")
else()
set(C_FLAGS_PROCESSOR "${C_FLAGS_PROCESSOR} -march=native")
endif()
else ()
set(C_FLAGS_PROCESSOR "${C_FLAGS_PROCESSOR} -march=armv8.2-a")
if (QUALCOMM_DRAGONWING)
# DragonWing IQ-9/X: Kryo 780 Gold = Cortex-A78 (ARMv8.2-A + fp16 + dotprod)
set(C_FLAGS_PROCESSOR "${C_FLAGS_PROCESSOR} -mcpu=cortex-a78")
#set(C_FLAGS_PROCESSOR "${C_FLAGS_PROCESSOR} -march=armv8.2-a")
else()
set(C_FLAGS_PROCESSOR "${C_FLAGS_PROCESSOR} -march=armv8.2-a")
endif()
endif()
# we need -rdynamic to incorporate all symbols in shared objects, see man page
@@ -383,12 +407,6 @@ endif()
add_boolean_option(ENABLE_IMSCOPE OFF "Enable phy scope based on imgui" OFF)
add_boolean_option(ENABLE_IMSCOPE_RECORD OFF "Enable recording IQ data for imscope" OFF)
#########################
##### E3 AGENT
#########################
set(E3_AGENT "OFF" CACHE STRING "O-RAN nGRG E3 Agent for dApps")
set_property(CACHE E3_AGENT PROPERTY STRINGS "ON" "OFF")
##################################################
# ASN.1 grammar C code generation & dependencies #
##################################################
@@ -861,6 +879,7 @@ set(NR_PHY_SRC_RU
${OPENAIR1_DIR}/PHY/MODULATION/nr_beamforming.c
${OPENAIR1_DIR}/PHY/MODULATION/slot_fep_nr.c
${OPENAIR1_DIR}/PHY/INIT/nr_init_ru.c
${OPENAIR1_DIR}/PHY/if4_tools.c
)
set(PHY_SRC_UE
@@ -929,6 +948,7 @@ set(PHY_SRC_UE
${OPENAIR1_DIR}/PHY/NR_TRANSPORT/nr_ulsch.c
${OPENAIR1_DIR}/PHY/NR_TRANSPORT/nr_sch_dmrs.c
${OPENAIR1_DIR}/PHY/NR_TRANSPORT/nr_prach.c
${OPENAIR1_DIR}/PHY/NR_TRANSPORT/nr_ulsch_llr_computation.c
${OPENAIR1_DIR}/PHY/NR_TRANSPORT/nr_ulsch_demodulation.c
${OPENAIR1_DIR}/PHY/NR_REFSIG/ul_ref_seq_nr.c
${OPENAIR1_DIR}/PHY/NR_REFSIG/nr_dmrs_rx.c
@@ -1211,7 +1231,6 @@ set (MAC_NR_SRC
${NR_GNB_MAC_DIR}/gNB_scheduler_uci.c
${NR_GNB_MAC_DIR}/gNB_scheduler_srs.c
${NR_GNB_MAC_DIR}/gNB_scheduler_RA.c
${NR_GNB_MAC_DIR}/gNB_scheduler_pcch.c
${NR_GNB_MAC_DIR}/mac_rrc_dl_handler.c
${NR_GNB_MAC_DIR}/mac_rrc_ul_direct.c
${NR_GNB_MAC_DIR}/mac_rrc_ul_f1ap.c
@@ -1774,25 +1793,23 @@ target_link_libraries(lte-uesoftmodem PRIVATE asn1_nr_rrc_hdrs asn1_lte_rrc_hdrs
# force the generation of ASN.1 so that we don't need to wait during the build
target_link_libraries(lte-uesoftmodem PRIVATE
asn1_lte_rrc asn1_s1ap asn1_m2ap asn1_m3ap asn1_x2ap)
# nr RRU
add_executable(nr-oru
${OPENAIR_DIR}/executables/nr-ru.c
${OPENAIR_DIR}/openair1/PHY/INIT/nr_parms.c
${OPENAIR_DIR}/openair1/SCHED_NR/phy_frame_config_nr.c
${OPENAIR_DIR}/openair1/SCHED_NR/nr_prach_procedures.c
${OPENAIR_DIR}/openair1/SCHED_NR/nr_ru_procedures.c
${OPENAIR_DIR}/openair1/SCHED/phy_procedures_lte_common.c
${OPENAIR_DIR}/executables/main_nr_ru.c
)
target_link_libraries(nr-oru PRIVATE
UTIL NR_PHY_RU PHY_NR shlib_loader dl
radio_common softmodem_common nfapi_pnf_lib)
target_link_libraries(nr-oru PRIVATE pthread m CONFIG_LIB rt ${T_LIB} utils
barrier actor nfapi_user_lib)
target_link_libraries(nr-oru PRIVATE asn1_nr_rrc_hdrs asn1_lte_rrc_hdrs nr_phy_common time_management)
if (OAI_RU_FRONTHAUL)
add_executable(nr-oru
${OPENAIR_DIR}/executables/nr-ru.c
${OPENAIR_DIR}/openair1/PHY/INIT/nr_parms.c
${OPENAIR_DIR}/openair1/SCHED_NR/phy_frame_config_nr.c
${OPENAIR_DIR}/openair1/SCHED_NR/nr_prach_procedures.c
${OPENAIR_DIR}/openair1/SCHED_NR/nr_ru_procedures.c
${OPENAIR_DIR}/openair1/SCHED/phy_procedures_lte_common.c
${OPENAIR_DIR}/executables/main_nr_ru.c
${OPENAIR_DIR}/executables/nr-oru.c
)
target_link_libraries(nr-oru PRIVATE
UTIL NR_PHY_RU PHY_NR shlib_loader dl oru_fh MAC_NR_COMMON
radio_common softmodem_common nfapi_pnf_lib)
target_link_libraries(nr-oru PRIVATE pthread m CONFIG_LIB rt ${T_LIB} utils
barrier actor nfapi_user_lib)
target_link_libraries(nr-oru PRIVATE asn1_nr_rrc_hdrs asn1_lte_rrc_hdrs nr_phy_common time_management)
endif()
# nr-softmodem
###################################################
@@ -1837,11 +1854,6 @@ if(E2_AGENT)
target_compile_definitions(nr-softmodem PRIVATE ${E2AP_VERSION} ${KPM_VERSION} E2_AGENT)
endif()
if (E3_AGENT)
target_link_libraries(nr-softmodem PRIVATE e3ap)
target_compile_definitions(nr-softmodem PRIVATE E3_AGENT)
endif()
# force the generation of ASN.1 so that we don't need to wait during the build
target_link_libraries(nr-softmodem PRIVATE

View File

@@ -1,6 +1,6 @@
<!-- SPDX-License-Identifier: CC-BY-4.0 -->
# Contributing to Duranta
# Contributing to OpenAirInterface
We want to make contributing to this project as easy and transparent as possible.
@@ -18,30 +18,41 @@ We want to make contributing to this project as easy and transparent as possible
## Commit Guidelines
Every pull request must pass two CI checks before it can be merged:
1. **[Developer Certificate of Origin (DCO)](https://en.wikipedia.org/wiki/Developer_Certificate_of_Origin)**:
Each commit must include a `Signed-off-by:` trailer in the commit message.
Use `git commit -s` (or `--signoff`).
2. **[Verified commits](https://docs.github.com/en/authentication/managing-commit-signature-verification/about-commit-signature-verification)**:
Each commit must be cryptographically signed using SSH or GPG keys to confirm
its origin.
### Signing Commits
GitHub supports commit signing using either SSH keys or GPG keys. For the
step-by-step setup (key generation, Git configuration, registering the key on
GitHub, and verifying signatures locally), see the
[commit signing section of the Git guide](doc/git-guide.md#setting-up-commit-signing).
To sign commits:
You can also get the verified label on your commits via using [SSH keys or GPG
keys](https://docs.github.com/en/authentication/managing-commit-signature-verification/signing-commits)
```
# Edit .git/config in the git repository you are working on
# Add the user section
[user]
name = YOUR NAME
email = YOUR EMAIL ADDRESS
# If you use a signing key, use the below configuration instead
[user]
name = YOUR NAME
email = YOUR EMAIL ADDRESS
signingkey = LOCATION OF SSH KEYS or GPG KEY
[gpg]
format = ssh
[commit]
gpgsign = true
```
> **NOTE:** If your commits are not signed the CI framework will not accept the PR.
> **NOTE:** If your commits are not signed, the CI framework will not accept the PR.
For more information regarding contribution guidelines
please check [this document](doc/code-style-contrib.md)
## License
By contributing to Duranta, you agree that your contributions will be
By contributing to OpenAirInterface, you agree that your contributions will be
licensed under
1. [CSSL v1.0 license](LICENSES/preferred/CSSL-v1.0.txt): for RAN and UE

View File

@@ -1,7 +1,7 @@
<!-- SPDX-License-Identifier: CC-BY-4.0 -->
<h1 align="center">
<a href="https://lfnetworking.org/projects/duranta/"><img src="https://raw.githubusercontent.com/duranta-project/governance/main/logos/Duranta-Logo-Color.png" alt="Duranta OAI" width="550"></a>
<a href="https://lfnetworking.org/projects/duranta/"><img src="https://raw.githubusercontent.com/duranta-project/governance/main/logos/Duranta-OAI-Combined.png" alt="Duranta OAI" width="550"></a>
</h1>
<p align="center">
@@ -14,8 +14,8 @@
</p>
<p align="center">
<a href="https://jenkins-oai.eurecom.fr/job/RAN-Ubuntu-Image-Builder/"><img src="https://img.shields.io/jenkins/build?jobUrl=https%3A%2F%2Fjenkins-oai.eurecom.fr%2Fjob%2FRAN-Ubuntu-Image-Builder%2F&label=build-Ubuntu-x86%20Images"></a>
<a href="https://jenkins-oai.eurecom.fr/job/RAN-RHEL-Cluster-Image-Builder/"><img src="https://img.shields.io/jenkins/build?jobUrl=https%3A%2F%2Fjenkins-oai.eurecom.fr%2Fjob%2FRAN-RHEL-Cluster-Image-Builder%2F&label=build-RHEL-Cluster%20Images"></a>
<a href="https://jenkins-oai.eurecom.fr/job/RAN-Ubuntu18-Image-Builder/"><img src="https://img.shields.io/jenkins/build?jobUrl=https%3A%2F%2Fjenkins-oai.eurecom.fr%2Fjob%2FRAN-Ubuntu18-Image-Builder%2F&label=build-Ubuntu-x86%20Images"></a>
<a href="https://jenkins-oai.eurecom.fr/job/RAN-RHEL8-Cluster-Image-Builder/"><img src="https://img.shields.io/jenkins/build?jobUrl=https%3A%2F%2Fjenkins-oai.eurecom.fr%2Fjob%2FRAN-RHEL8-Cluster-Image-Builder%2F&label=build-UBI-x86%20Images"></a>
<a href="https://jenkins-oai.eurecom.fr/job/RAN-Ubuntu-ARM-Image-Builder/"><img src="https://img.shields.io/jenkins/build?jobUrl=https%3A%2F%2Fjenkins-oai.eurecom.fr%2Fjob%2FRAN-Ubuntu-ARM-Image-Builder%2F&label=build-Ubuntu-ARM%20Images"></a>
</p>

View File

@@ -1,61 +0,0 @@
# Duranta-OpenAirInterface Roadmap
## RAN Roadmap
### Q2 2026
- GPU LDPC Accelerator
- NR-DC Support
- CN Paging procedures
- Support for 64 UEs with E2E throughput validation
- Basic Cat-A OAI O-RU (simulated radio)
### Q3 2026
- QoS-enforcing scheduler
- Xn Handover Support
- UL MU-MIMO
- Aperiodic UL channels (SRS/CSI reporting)
- NR user plane protocol
- Multi-cell support in L2
- Support for 128 UEs with E2E throughput validation
### Q4 2026
- Cat-B Support for FHI 7.2 at RU/DU
- RAN Paging
- Support of 5G RAN Slicing
- Multi-cell performance evaluation (with at-least 3 cells)
### Q1 2027
- Digital Beamforming Support
- DL MU-MIMO at L1
---
## UE Roadmap
### Q2 2026
- Support for Handover Procedures
- Basic Sidelink Procedures (PSSCH, PSCCH)
- PUCCH formats 1&3
### Q3 2026
- Support for 2 UL layers
- Support for 2 DL layers
- RU sharing
### Q4 2026
- Reduce feedback time
- AT command interface
- DL KPI Improvements
- UL KPI Improvements
### Q1 2027
- Scan carrier
- Power control procedures for outdoor operation

View File

@@ -14,7 +14,7 @@ description: A Helm chart for physical simulators network function
# pipeline. Library charts do not define any templates and therefore cannot be deployed.
type: application
icon: https://raw.githubusercontent.com/duranta-project/governance/main/logos/Duranta-Logo-Color.png
icon: http://www.openairinterface.org/wp-content/uploads/2015/06/cropped-oai_final_logo.png
# This is the chart version. This version number should be incremented each time you make changes
# to the chart and its templates, including the app version.
@@ -35,4 +35,4 @@ sources:
maintainers:
- name: OPENAIRINTERFACE
email: oaicicdteam@openairinterface.org
email: contact@openairinterface.org

View File

@@ -14,7 +14,7 @@ description: A Helm subchart for 4G physims network function
# pipeline. Library charts do not define any templates and therefore cannot be deployed.
type: application
icon: https://raw.githubusercontent.com/duranta-project/governance/main/logos/Duranta-Logo-Color.png
icon: http://www.openairinterface.org/wp-content/uploads/2015/06/cropped-oai_final_logo.png
# This is the chart version. This version number should be incremented each time you make changes
# to the chart and its templates, including the app version.
@@ -34,4 +34,4 @@ sources:
maintainers:
- name: OPENAIRINTERFACE
email: oaicicdteam@openairinterface.org
email: contact@openairinterface.org

View File

@@ -14,7 +14,7 @@ description: A Helm chart for physical simulators network function
# pipeline. Library charts do not define any templates and therefore cannot be deployed.
type: application
icon: https://raw.githubusercontent.com/duranta-project/governance/main/logos/Duranta-Logo-Color.png
icon: http://www.openairinterface.org/wp-content/uploads/2015/06/cropped-oai_final_logo.png
# This is the chart version. This version number should be incremented each time you make changes
# to the chart and its templates, including the app version.
@@ -35,4 +35,4 @@ sources:
maintainers:
- name: OPENAIRINTERFACE
email: oaicicdteam@openairinterface.org
email: contact@openairinterface.org

View File

@@ -14,7 +14,7 @@ description: A Helm subchart for 5G physims network function
# pipeline. Library charts do not define any templates and therefore cannot be deployed.
type: application
icon: https://raw.githubusercontent.com/duranta-project/governance/main/logos/Duranta-Logo-Color.png
icon: http://www.openairinterface.org/wp-content/uploads/2015/06/cropped-oai_final_logo.png
# This is the chart version. This version number should be incremented each time you make changes
# to the chart and its templates, including the app version.
@@ -34,4 +34,4 @@ sources:
maintainers:
- name: OPENAIRINTERFACE
email: oaicicdteam@openairinterface.org
email: contact@openairinterface.org

View File

@@ -48,6 +48,7 @@ def nsaTDDB200Status = ''
def saTDDB200Status = ''
def phytestLDPCoffloadStatus = ''
def saAW2SStatus = ''
def cn5gCOTSUESanityCheck = ''
def saAERIALStatus = ''
def saMultiAntennaStatus = ''
def saFHI72Status = ''
@@ -711,6 +712,29 @@ pipeline {
}
}
}
stage ("Sanity-Check OAI-CN5G") {
when { expression {do5Gtest} }
steps {
script {
triggerCN5GDownstreamJob ('OAI-CN5G-COTS-UE-Test')
}
}
post {
always {
script {
// Using a unique variable name for each test stage to avoid overwriting on a global variable
// due to parallel-time concurrency
cn5gCOTSUESanityCheck = finalizeDownstreamJob('OAI-CN5G-COTS-UE-Test')
}
}
failure {
script {
currentBuild.result = 'FAILURE'
failingStages += cn5gCOTSUESanityCheck
}
}
}
}
stage ("SA-AERIAL-CN5G") {
when { expression {do5Gtest} }
steps {

View File

@@ -7,7 +7,7 @@
#define N_ANTENNA_UL 1
#define TDD 1
log_options: "all.level=info,all.max_size=0,nas.level=debug,nas.max_size=1,rrc.level=debug,rrc.max_size=1",
log_options: "all.level=debug,all.max_size=0,nas.level=debug,nas.max_size=1,rrc.level=debug,rrc.max_size=1",
log_filename: "/tmp/ue.log",
/* Enable remote API and Web interface */

View File

@@ -7,7 +7,7 @@
#define N_ANTENNA_UL 1
#define TDD 1
log_options: "all.level=info,all.max_size=0,nas.level=debug,nas.max_size=1,rrc.level=debug,rrc.max_size=1",
log_options: "all.level=debug,all.max_size=0,nas.level=debug,nas.max_size=1,rrc.level=debug,rrc.max_size=1",
log_filename: "/tmp/ue.log",
/* Enable remote API and Web interface */

View File

@@ -217,19 +217,11 @@ amarisoft_00105_40MHz:
InitScript: /root/lteue-linux-2025-03-15/lteue /root/oaicicd/ran_sa_fhi72_mplane_40MHz/multi-00105-40.cfg &
TermScript: /root/lteue-linux-2025-03-15/ws.js -t 10 127.0.0.1:9002 '{"message":"quit"}' || killall -KILL lteue-avx2
NetworkScript: ip netns exec ue1 ip a show dev pdn0
Tracing:
Start: '' # nothing to be done
Stop: '' # nothing to be done
Collect: 'cp /tmp/ue.log %%log_dir%%'
amarisoft_00105_100MHz:
Host: amariue
InitScript: /root/lteue-linux-2025-03-15/lteue /root/oaicicd/ran_sa_fhi72_mplane_100MHz/multi-00105-100.cfg &
TermScript: /root/lteue-linux-2025-03-15/ws.js -t 10 127.0.0.1:9002 '{"message":"quit"}' || killall -KILL lteue-avx2
NetworkScript: ip netns exec ue1 ip a show dev pdn0
Tracing:
Start: '' # nothing to be done
Stop: '' # nothing to be done
Collect: 'cp /tmp/ue.log %%log_dir%%'
amarisoft_ue_1:
Host: amariue
AttachScript: /root/lteue-linux-2025-03-15/ws.js 127.0.0.1:9002 '{"message":"power_on","ue_id":1}'

View File

@@ -95,13 +95,13 @@ class Analysis():
test_summary['Nbfail'] = nb_failed
return nb_failed == 0, test_summary, test_result
def analyze_rt_stats(thresholds, stat_files):
def analyze_rt_stats(thresholds, L1_stats, MAC_stats):
with open(thresholds, 'r') as f:
datalog_rt_stats = yaml.load(f, Loader=yaml.FullLoader)
rt_keys = datalog_rt_stats['Ref']
real_time_stats = {}
for sf in stat_files:
for sf in [L1_stats, MAC_stats]:
with open(sf, 'r') as f:
for line in f.readlines():
for k in rt_keys:

View File

@@ -401,7 +401,7 @@ class Containerize():
# I would like to run it with --rm and mount the ctest result directory to avoid 'docker cp'
# below, but then permissions are messed up and we can't remove the directory without sudo
# making the next pipeline fail
ret = cmd.run(f'docker run -a STDOUT {runtime_opt} --shm-size=2g --workdir /oai-ran/build/ --env LD_LIBRARY_PATH=/oai-ran/build/ --name ran-unittests ran-unittests:{baseTag} ctest --no-label-summary -j$(nproc) {ctest_opt}')
ret = cmd.run(f'docker run -a STDOUT {runtime_opt} --workdir /oai-ran/build/ --env LD_LIBRARY_PATH=/oai-ran/build/ --name ran-unittests ran-unittests:{baseTag} ctest --no-label-summary -j$(nproc) {ctest_opt}')
cmd.run('docker cp ran-unittests:/oai-ran/build/Testing/Temporary/LastTest.log .')
archiveArtifact(cmd, ctx, f'{lSourcePath}/LastTest.log')
cmd.run('docker cp ran-unittests:/oai-ran/build/Testing/Temporary/LastTestsFailed.log .')
@@ -644,7 +644,7 @@ class Containerize():
logging.error('\u001B[1m Undeploying objects Failed\u001B[0m')
return success
def AnalyzeRTStatsObject(self, HTML, node, ctx, thresholds, service=None, stats_files=None):
def AnalyzeRTStatsObject(self, HTML, node, ctx, thresholds, service=None):
logging.info(f'Analyzing realtime stats from server: {node}')
yaml = self.yamlPath.strip('/')
wd = f'{self.workspace}/{yaml}'
@@ -660,20 +660,12 @@ class Containerize():
raise RuntimeError(f"Requested service {s} not found among services: {deployed_services}")
logging.info(f"Analyzing deployed service '{s}'")
# similar to BuildRunTests(), use docker cp to avoid problems with permissions
local_files = []
for sf in stats_files:
basename = os.path.basename(sf)
ret = cmd.run(f'docker compose -f {wd_yaml} cp {s}:{sf} {wd}/')
if ret.returncode != 0:
logging.error(f"Cannot retrieve {s}:{sf}")
return False
file = archiveArtifact(cmd, ctx, f"{wd}/{basename}")
if not file:
logging.error(f"Cannot retrieve file {basename}")
return False
local_files.append(file)
cmd.run(f'docker compose -f {wd_yaml} cp {s}:/opt/oai-gnb/nrL1_stats.log {wd}/')
l1_file = archiveArtifact(cmd, ctx, f"{wd}/nrL1_stats.log")
cmd.run(f'docker compose -f {wd_yaml} cp {s}:/opt/oai-gnb/nrMAC_stats.log {wd}/')
mac_file = archiveArtifact(cmd, ctx, f"{wd}/nrMAC_stats.log")
logging.info(f"check against thresholds from {thresholds}")
success, datalog_rt_stats = cls_analysis.Analysis.analyze_rt_stats(thresholds, local_files)
success, datalog_rt_stats = cls_analysis.Analysis.analyze_rt_stats(thresholds, l1_file, mac_file)
HTML.CreateHtmlDataLogTable(datalog_rt_stats)
return success

View File

@@ -75,12 +75,12 @@ class HTMLManagement():
self.htmlFile.write(' <tr style="border-collapse: collapse; border: none;">\n')
self.htmlFile.write(' <td style="border-collapse: collapse; border: none;">\n')
self.htmlFile.write(' <a href="http://www.openairinterface.org/">\n')
self.htmlFile.write(' <img src="https://raw.githubusercontent.com/duranta-project/governance/main/logos/Duranta-Logo-Color.png" alt="" border="none" style="margin-right: 2rem;" width=150>\n')
self.htmlFile.write(' <img src="http://www.openairinterface.org/wp-content/uploads/2016/03/cropped-oai_final_logo2.png" alt="" border="none" height=50 width=150>\n')
self.htmlFile.write(' </img>\n')
self.htmlFile.write(' </a>\n')
self.htmlFile.write(' </td>\n')
self.htmlFile.write(' <td style="border-collapse: collapse; border: none; vertical-align: center;">\n')
self.htmlFile.write(' <b><font size = "6">TEMPLATE_JOB_NAME -- Build-ID: TEMPLATE_BUILD_ID</font></b>\n')
self.htmlFile.write(' <b><font size = "6">Job Summary -- Job: TEMPLATE_JOB_NAME -- Build-ID: TEMPLATE_BUILD_ID</font></b>\n')
self.htmlFile.write(' </td>\n')
self.htmlFile.write(' </tr>\n')
self.htmlFile.write(' </table>\n')

View File

@@ -184,8 +184,7 @@ L1s = (
{
num_cc = 1;
tr_n_preference = "local_mac";
prach_dtx_threshold = 200;
}
}
);
RUs = (

View File

@@ -184,8 +184,7 @@ L1s = (
{
num_cc = 1;
tr_n_preference = "local_mac";
prach_dtx_threshold = 200;
}
}
);
RUs = (

View File

@@ -216,7 +216,6 @@ L1s =
{
num_cc = 1;
tr_n_preference = "local_mac";
prach_dtx_threshold = 200;
}
);

View File

@@ -216,7 +216,6 @@ L1s =
{
num_cc = 1;
tr_n_preference = "local_mac";
prach_dtx_threshold = 200;
}
);

View File

@@ -216,7 +216,6 @@ L1s =
{
num_cc = 1;
tr_n_preference = "local_mac";
prach_dtx_threshold = 200;
}
);

View File

@@ -216,7 +216,6 @@ L1s =
{
num_cc = 1;
tr_n_preference = "local_mac";
prach_dtx_threshold = 200;
}
);

View File

@@ -1,222 +0,0 @@
# SPDX-License-Identifier: LicenseRef-CSSL-1.0
Active_gNBs = ( "gNB-OAI");
# Asn1_verbosity, choice in: none, info, annoying
Asn1_verbosity = "none";
gNBs =
(
{
////////// Identification parameters:
gNB_ID = 0xe00;
gNB_name = "gNB-OAI";
// Tracking area code, 0x0000 and 0xfffe are reserved values
tracking_area_code = 1;
plmn_list = ({mcc = 208; mnc = 99; mnc_length = 2;});
tr_s_preference = "local_mac"
////////// Physical parameters:
min_rxtxtime = 6;
servingCellConfigCommon = (
{
#spCellConfigCommon
physCellId = 0;
# downlinkConfigCommon
#frequencyInfoDL
# this is 4412.64 MHz
absoluteFrequencySSB = 694176;
dl_frequencyBand = 79;
# this is 4400.94 MHz
dl_absoluteFrequencyPointA = 693396;
#scs-SpecificCarrierList
dl_offstToCarrier = 0;
# subcarrierSpacing
# 0=kHz15, 1=kHz30, 2=kHz60, 3=kHz120
dl_subcarrierSpacing = 1;
dl_carrierBandwidth = 106;
#initialDownlinkBWP
#genericParameters
# this is RBstart=41,L=24 (275*(L-1))+RBstart
initialDLBWPlocationAndBandwidth = 6368;
# subcarrierSpacing
# 0=kHz15, 1=kHz30, 2=kHz60, 3=kHz120
initialDLBWPsubcarrierSpacing = 1;
#pdcch-ConfigCommon
initialDLBWPcontrolResourceSetZero = 12;
initialDLBWPsearchSpaceZero = 0;
#uplinkConfigCommon
#frequencyInfoUL
ul_frequencyBand = 79;
#scs-SpecificCarrierList
ul_offstToCarrier = 0;
# subcarrierSpacing
# 0=kHz15, 1=kHz30, 2=kHz60, 3=kHz120
ul_subcarrierSpacing = 1;
ul_carrierBandwidth = 106;
pMax = 20;
#initialUplinkBWP
#genericParameters
initialULBWPlocationAndBandwidth = 6368;
# subcarrierSpacing
# 0=kHz15, 1=kHz30, 2=kHz60, 3=kHz120
initialULBWPsubcarrierSpacing = 1;
#rach-ConfigCommon
#rach-ConfigGeneric
prach_ConfigurationIndex = 98;
#prach_msg1_FDM
#0 = one, 1=two, 2=four, 3=eight
prach_msg1_FDM = 0;
prach_msg1_FrequencyStart = 0;
zeroCorrelationZoneConfig = 13;
preambleReceivedTargetPower = -118;
#preamblTransMax (0...10) = (3,4,5,6,7,8,10,20,50,100,200)
preambleTransMax = 6;
#powerRampingStep
# 0=dB0,1=dB2,2=dB4,3=dB6
powerRampingStep = 1;
#ssb_perRACH_OccasionAndCB_PreamblesPerSSB_PR
#1=oneeighth,2=onefourth,3=half,4=one,5=two,6=four,7=eight,8=sixteen
ssb_perRACH_OccasionAndCB_PreamblesPerSSB_PR = 4;
#one (0..15) 4,8,12,16,...60,64
ssb_perRACH_OccasionAndCB_PreamblesPerSSB = 15;
#ra_ContentionResolutionTimer
#(0..7) 8,16,24,32,40,48,56,64
ra_ContentionResolutionTimer = 7;
rsrp_ThresholdSSB = 19;
#prach-RootSequenceIndex_PR
#1 = 839, 2 = 139
prach_RootSequenceIndex_PR = 2;
prach_RootSequenceIndex = 1;
# SCS for msg1, can only be 15 for 30 kHz < 6 GHz, takes precendence over the one derived from prach-ConfigIndex
#
msg1_SubcarrierSpacing = 1,
# restrictedSetConfig
# 0=unrestricted, 1=restricted type A, 2=restricted type B
restrictedSetConfig = 0,
msg3_DeltaPreamble = 1;
p0_NominalWithGrant =-90;
# pucch-ConfigCommon setup :
# pucchGroupHopping
# 0 = neither, 1= group hopping, 2=sequence hopping
pucchGroupHopping = 0;
hoppingId = 40;
p0_nominal = -90;
ssb_PositionsInBurst_Bitmap = 1;
# ssb_periodicityServingCell
# 0 = ms5, 1=ms10, 2=ms20, 3=ms40, 4=ms80, 5=ms160, 6=spare2, 7=spare1
ssb_periodicityServingCell = 2;
# dmrs_TypeA_position
# 0 = pos2, 1 = pos3
dmrs_TypeA_Position = 0;
# subcarrierSpacing
# 0=kHz15, 1=kHz30, 2=kHz60, 3=kHz120
subcarrierSpacing = 1;
#tdd-UL-DL-ConfigurationCommon
# subcarrierSpacing
# 0=kHz15, 1=kHz30, 2=kHz60, 3=kHz120
referenceSubcarrierSpacing = 1;
# pattern1
# dl_UL_TransmissionPeriodicity
# 0=ms0p5, 1=ms0p625, 2=ms1, 3=ms1p25, 4=ms2, 5=ms2p5, 6=ms5, 7=ms10
dl_UL_TransmissionPeriodicity = 6;
nrofDownlinkSlots = 7;
nrofDownlinkSymbols = 6;
nrofUplinkSlots = 2;
nrofUplinkSymbols = 4;
ssPBCH_BlockPower = -25;
}
);
# ------- SCTP definitions
SCTP :
{
# Number of streams to use in input/output
SCTP_INSTREAMS = 2;
SCTP_OUTSTREAMS = 2;
};
////////// MME parameters:
mme_ip_address = ({ ipv4 = "192.168.12.26"; port = 36412; });
NETWORK_INTERFACES :
{
GNB_IPV4_ADDRESS_FOR_S1_MME = "192.168.12.111/24";
GNB_IPV4_ADDRESS_FOR_S1U = "192.168.12.111/24";
GNB_PORT_FOR_S1U = 2152; # Spec 2152
};
}
);
MACRLCs = (
{
tr_s_preference = "local_L1";
tr_n_preference = "local_RRC";
});
L1s = (
{
tr_n_preference = "local_mac";
});
RUs = (
{
local_rf = "yes"
nb_tx = 1
nb_rx = 1
att_tx = 0
att_rx = 0;
bands = [7];
max_pdschReferenceSignalPower = -27;
max_rxgain = 50;
eNB_instances = [0];
});
security = {
# preferred ciphering algorithms
# the first one of the list that an UE supports in chosen
# valid values: nea0, nea1, nea2, nea3
ciphering_algorithms = ( "nea0" );
# preferred integrity algorithms
# the first one of the list that an UE supports in chosen
# valid values: nia0, nia1, nia2, nia3
integrity_algorithms = ( "nia2", "nia0" );
# setting 'drb_ciphering' to "no" disables ciphering for DRBs, no matter
# what 'ciphering_algorithms' configures; same thing for 'drb_integrity'
drb_ciphering = "yes";
drb_integrity = "no";
};
log_config :
{
global_log_level ="info";
hw_log_level ="info";
phy_log_level ="info";
mac_log_level ="info";
rlc_log_level ="info";
pdcp_log_level ="info";
rrc_log_level ="info";
};

View File

@@ -1,32 +0,0 @@
# SPDX-License-Identifier: LicenseRef-CSSL-1.0
#this is a configuration file
#used to build real time processing statistics
#for 5G NR phy test (gNB terminate)
Title : gNB Processing Time (us) from datalog_rt_stats.default.yaml
ColNames :
- Metric
- Average; Max; Count
- Average vs Reference Deviation (Reference Value; Acceptability Deviation Threshold)
Ref :
L1 Tx processing : 190.0
DLSCH encoding : 120.0
L1 Rx processing : 235.0
UL segments decoding : 120.0
PUSCH inner-receiver : 150.0
UL Indication : 2.0
Slot Indication : 10.0
feprx : 40.0
feptx_ofdm (per port, half_slot) : 30
feptx_total : 52
DeviationThreshold :
L1 Tx processing : 0.25
DLSCH encoding : 0.25
L1 Rx processing : 0.25
UL segments decoding : 0.25
PUSCH inner-receiver : 0.25
UL Indication : 1.00
Slot Indication : 0.50
feprx : 0.25
feptx_ofdm (per port, half_slot) : 0.25
feptx_total : 0.25

View File

@@ -1,40 +0,0 @@
# SPDX-License-Identifier: LicenseRef-CSSL-1.0
#this is a configuration file
#used to build real time processing statistics
#for 5G NR phy test (gNB terminate)
Title : UE Processing Time (us) from datalog_rt_stats.ue.40.vrtsim.yaml
ColNames :
- Metric
- Average; Max; Count
- Average vs Reference Deviation (Reference Value; Acceptability Deviation Threshold)
Ref :
RX_PDSCH_STATS : 62.0
DLSCH_RX_PDCCH_STATS : 30.5
RX_DFT_STATS : 5.5
DLSCH_CHANNEL_ESTIMATION_STATS : 39.0
DLSCH_DECODING_STATS : 178.0
DLSCH_LDPC_DECODING_STATS : 78.0
DLSCH_LLR_STATS : 25.0
DLSCH_LAYER_DEMAPPING : 8.5
DLSCH_PROCEDURES_STATS : 188.0
PHY_PROC_TX : 158.0
PUSCH_PROC_STATS : 167.0
ULSCH_LDPC_ENCODING_STATS : 43.0
ULSCH_ENCODING_STATS : 121.0
OFDM_MOD_STATS : 44.5
DeviationThreshold :
RX_PDSCH_STATS : 0.25
DLSCH_RX_PDCCH_STATS : 0.25
RX_DFT_STATS : 0.25
DLSCH_CHANNEL_ESTIMATION_STATS : 0.25
DLSCH_DECODING_STATS : 0.25
DLSCH_LDPC_DECODING_STATS : 0.25
DLSCH_LLR_STATS : 0.25
DLSCH_LAYER_DEMAPPING : 0.5
DLSCH_PROCEDURES_STATS : 0.25
PHY_PROC_TX : 0.25
PUSCH_PROC_STATS : 0.25
ULSCH_LDPC_ENCODING_STATS : 0.25
ULSCH_ENCODING_STATS : 0.25
OFDM_MOD_STATS : 0.25

View File

@@ -10,7 +10,8 @@
FROM ran-base:develop AS ran-tests
RUN apt-get update && \
DEBIAN_FRONTEND=noninteractive apt-get install --yes --no-install-recommends \
DEBIAN_FRONTEND=noninteractive apt-get upgrade --yes && \
DEBIAN_FRONTEND=noninteractive apt-get install --yes \
libgtest-dev \
libyaml-cpp-dev \
libzmq3-dev \
@@ -25,9 +26,7 @@ WORKDIR /oai-ran
COPY . .
WORKDIR /oai-ran/build
RUN --mount=type=cache,target=/root/.cache/ccache/ \
--mount=type=cache,target=/root/.cache/cpm/ \
cmake .. -GNinja \
RUN cmake .. -GNinja \
-DENABLE_TESTS=ON -DOAI_ZMQ=ON -DCMAKE_BUILD_TYPE=Debug \
-DSANITIZE_ADDRESS=True -DOAI_VRTSIM_TAPS_CLIENT=ON -DOAI_RU_FRONTHAUL=ON \
&& \

View File

@@ -245,8 +245,7 @@ def ExecuteActionWithParam(action, ctx, node):
elif action == 'AnalyzeRTStats_Object':
yaml = test.findtext('stats_cfg')
service = test.findtext('service')
stats_files = (test.findtext('stats_file') or '').split()
success = CONTAINERS.AnalyzeRTStatsObject(HTML, node, ctx, yaml, service, stats_files)
success = CONTAINERS.AnalyzeRTStatsObject(HTML, node, ctx, yaml, service)
else:
logging.warning(f"unknown action {action}, skip step")

View File

@@ -55,17 +55,8 @@ done
# ----------------------------
# Merged commits
# ----------------------------
if [[ "$SOURCE_BRANCH" =~ ^[0-9a-f]{40}$ ]]; then
# note: if no branch could be found, it will result in "" and git rev-list
# will use the commit ID. Exclude "HEAD detached at", then use first branch
# name.
BRANCH_NAME=$(git branch -a --points-at $SOURCE_BRANCH --format='%(refname:short)' | grep -v detached | head -n1)
echo "SHA recognized in $SOURCE_BRANCH, using \"$BRANCH_NAME\" as branch name"
else
BRANCH_NAME="$SOURCE_BRANCH"
fi
if [[ ! "$BRANCH_NAME" =~ ^(origin/)?integration_[0-9]{4}_w[0-9]{2}$ ]]; then
mergeCommits=$(git rev-list --merges --abbrev-commit "$TARGET_BRANCH".."$SOURCE_BRANCH")
mergeCommits=$(git rev-list --merges --abbrev-commit "$TARGET_BRANCH".."$SOURCE_BRANCH")
if [[ ! "$SOURCE_BRANCH" =~ ^(origin/)?integration_[0-9]{4}_w[0-9]{2}$ ]]; then
if [[ -n "$mergeCommits" ]]; then
message="Error: Following merge commits are found in the source branch history. Please rebase your branch.\n\n"
message+="$(echo "$mergeCommits" | paste -sd ',' | sed 's/,/, /g')\n"

View File

@@ -145,6 +145,6 @@ class RANManagement():
mac_file = archiveArtifact(cmd, ctx, f"{logdir}/nrMAC_stats.log")
logging.info(f"check against thresholds from {thresholds}")
success, datalog_rt_stats = cls_analysis.Analysis.analyze_rt_stats(thresholds, [l1_file, mac_file])
success, datalog_rt_stats = cls_analysis.Analysis.analyze_rt_stats(thresholds, l1_file, mac_file)
HTML.CreateHtmlDataLogTable(datalog_rt_stats)
return success

View File

@@ -26,7 +26,7 @@ class TestAnalysis(unittest.TestCase):
rtsf = "datalog_rt_stats.100.2x2.yaml"
l1f = "tests/analysis/gnb_phytest.success.nrL1.log"
macf = "tests/analysis/gnb_phytest.success.nrMAC.log"
status, s = cls_analysis.Analysis.analyze_rt_stats(rtsf, [l1f, macf])
status, s = cls_analysis.Analysis.analyze_rt_stats(rtsf, l1f, macf)
self.assertTrue(status)
self.assertEqual(s['Title'], "Processing Time (us) from datalog_rt_stats.100.2x2.yaml")
self.assertEqual(s['ColNames'], ["Metric", "Average; Max; Count", "Average vs Reference Deviation (Reference Value; Acceptability Deviation Threshold)"])

View File

@@ -36,7 +36,6 @@
<desc>Analyze Real-Time Stats</desc>
<stats_cfg>datalog_rt_stats.100.4x4.fhi72.yaml</stats_cfg>
<service>oai-gnb</service>
<stats_file>/opt/oai-gnb/nrL1_stats.log /opt/oai-gnb/nrMAC_stats.log</stats_file>
<node>bulbul</node>
</testCase>

View File

@@ -44,7 +44,6 @@
<desc>Analyze Real-Time Stats</desc>
<stats_cfg>datalog_rt_stats.100.2x2.fhi72.cacofonix.yaml</stats_cfg>
<service>oai-gnb</service>
<stats_file>/opt/oai-gnb/nrL1_stats.log /opt/oai-gnb/nrMAC_stats.log</stats_file>
<node>cacofonix</node>
</testCase>

View File

@@ -1,82 +0,0 @@
<!-- SPDX-License-Identifier: LicenseRef-CSSL-1.0 -->
<testCaseList>
<htmlTabRef>PHY-Test-40-vrtsim</htmlTabRef>
<htmlTabName>Timing phytest 40 MHz with vrtsim</htmlTabName>
<htmlTabIcon>tasks</htmlTabIcon>
<testCase>
<class>Pull_Cluster_Image</class>
<desc>Pull Images from Cluster</desc>
<images>oai-gnb oai-nr-ue</images>
<node>caracal</node>
</testCase>
<testCase>
<class>Create_Workspace</class>
<desc>Create new Workspace</desc>
<node>caracal</node>
</testCase>
<testCase>
<class>Deploy_Object</class>
<desc>Initialize gNB</desc>
<node>caracal</node>
<yaml_path>ci-scripts/yaml_files/phytest_vrtsim_40MHz</yaml_path>
<services>oai-gnb</services>
</testCase>
<testCase>
<class>Deploy_Object</class>
<desc>Initialize UE</desc>
<node>caracal</node>
<yaml_path>ci-scripts/yaml_files/phytest_vrtsim_40MHz</yaml_path>
<services>oai-nr-ue</services>
</testCase>
<testCase>
<class>IdleSleep</class>
<desc>Sleep</desc>
<idle_sleep_time_in_sec>60</idle_sleep_time_in_sec>
</testCase>
<testCase>
<class>AnalyzeRTStats_Object</class>
<desc>Analyze Real-Time Stats on gNB</desc>
<always_exec>true</always_exec>
<stats_cfg>datalog_rt_stats.gnb.40.vrtsim.yaml</stats_cfg>
<service>oai-gnb</service>
<stats_file>/opt/oai-gnb/nrL1_stats.log /opt/oai-gnb/nrMAC_stats.log</stats_file>
<node>caracal</node>
</testCase>
<testCase>
<class>AnalyzeRTStats_Object</class>
<desc>Analyze Real-Time Stats on UE</desc>
<always_exec>true</always_exec>
<stats_cfg>datalog_rt_stats.ue.40.vrtsim.yaml</stats_cfg>
<service>oai-nr-ue</service>
<stats_file>/opt/oai-nr-ue/nrL1_UE_stats-0.log</stats_file>
<node>caracal</node>
</testCase>
<testCase>
<class>Undeploy_Object</class>
<node>caracal</node>
<always_exec>true</always_exec>
<desc>Terminate gNB</desc>
<yaml_path>ci-scripts/yaml_files/phytest_vrtsim_40MHz</yaml_path>
<analysis>
<services>oai-gnb=EndsWithBye oai-nr-ue=EndsWithBye</services>
</analysis>
</testCase>
<testCase>
<class>Clean_Test_Server_Images</class>
<always_exec>true</always_exec>
<desc>Clean Test Images on Test Server</desc>
<node>caracal</node>
<images>oai-nr-ue oai-gnb</images>
</testCase>
</testCaseList>

View File

@@ -1,6 +1,20 @@
<!-- SPDX-License-Identifier: CC-BY-4.0 -->
OAI Full Stack 4G-LTE RF simulation with containers
<table style="border-collapse: collapse; border: none;">
<tr style="border-collapse: collapse; border: none;">
<td style="border-collapse: collapse; border: none;">
<a href="http://www.openairinterface.org/">
<img src="../../../doc/images/oai_final_logo.png" alt="" border=3 height=50 width=150>
</img>
</a>
</td>
<td style="border-collapse: collapse; border: none; vertical-align: center;">
<b><font size = "5">OAI Full Stack 4G-LTE RF simulation with containers</font></b>
</td>
</tr>
</table>
This page is only valid for an `Ubuntu 22` host.
**Table of Contents**

View File

@@ -1,6 +1,22 @@
<!-- SPDX-License-Identifier: CC-BY-4.0 -->
OAI Full Stack 5G-NR RF simulation with containers
<table style="border-collapse: collapse; border: none;">
<tr style="border-collapse: collapse; border: none;">
<td style="border-collapse: collapse; border: none;">
<a href="http://www.openairinterface.org/">
<img src="../../../doc/images/oai_final_logo.png" alt="" border=3 height=50 width=150>
</img>
</a>
</td>
<td style="border-collapse: collapse; border: none; vertical-align: center;">
<b><font size = "5">OAI Full Stack 5G-NR RF simulation with containers</font></b>
</td>
</tr>
</table>
This page is only valid for an `Ubuntu 22` host.
**NOTE: this version (2023-01-27) has been updated for the `v1.5.0` version of the `OAI 5G CN`.**
**Table of Contents**

View File

@@ -20,8 +20,6 @@ ru_sdr:
srate: 23.04
tx_gain: 25
rx_gain: 25
amplitude_control:
tx_gain_backoff: 22
cell_cfg:
dl_arfcn: 632628
@@ -44,8 +42,8 @@ cell_cfg:
nof_cell_csi_res: 0
log:
filename: stdout
all_level: info
filename: gnb.log
all_level: debug
pcap:
mac_enable: false

View File

@@ -20,8 +20,6 @@ ru_sdr:
srate: 61.44
tx_gain: 25
rx_gain: 25
amplitude_control:
tx_gain_backoff: 22
cell_cfg:
dl_arfcn: 632628
@@ -46,8 +44,8 @@ cell_cfg:
nof_cell_csi_res: 0
log:
filename: stdout
all_level: info
filename: gnb.log
all_level: debug
pcap:
mac_enable: false

View File

@@ -1,52 +0,0 @@
# SPDX-License-Identifier: MIT
services:
oai-gnb:
image: ${REGISTRY-oaisoftwarealliance/}${GNB_IMG:-oai-gnb}:${TAG:-develop}
container_name: rfsim5g-oai-gnb
cap_drop:
- ALL
cap_add:
- SYS_NICE
environment:
USE_ADDITIONAL_OPTIONS: --phy-test --device.name vrtsim --vrtsim.role server -q -U 768 -T 106 -t 23 -D 127 -m 28 -M 106 --log_config.global_log_options level,nocolor,time
ASAN_OPTIONS: detect_leaks=0
ipc: host
volumes:
- ../../conf_files/gnb.band79.106prb.vrtsim.phytest-dora.conf:/opt/oai-gnb/etc/gnb.conf
- rrc.config:/opt/oai-gnb/
- tmp_data:/tmp/
healthcheck:
test: /bin/bash -c "pgrep nr-softmodem"
interval: 10s
timeout: 5s
retries: 5
oai-nr-ue:
image: ${REGISTRY-oaisoftwarealliance/}${NRUE_IMG:-oai-nr-ue}:${TAG:-develop}
container_name: rfsim5g-oai-nr-ue
cap_drop:
- ALL
cap_add:
- SYS_NICE
environment:
USE_ADDITIONAL_OPTIONS: --phy-test --reconfig-file etc/rrc/reconfig.raw --rbconfig-file etc/rrc/rbconfig.raw --device.name vrtsim --vrtsim.role client -q --log_config.global_log_options level,nocolor,time
ASAN_OPTIONS: detect_leaks=0
devices:
- /dev/net/tun:/dev/net/tun
ipc: host
volumes:
- ../../conf_files/nrue.uicc.conf:/opt/oai-nr-ue/etc/nr-ue.conf
- rrc.config:/opt/oai-nr-ue/etc/rrc/
- tmp_data:/tmp/
depends_on:
- oai-gnb
healthcheck:
test: /bin/bash -c "pgrep nr-uesoftmodem"
interval: 10s
timeout: 5s
retries: 5
volumes:
rrc.config:
tmp_data:

View File

@@ -1,6 +1,18 @@
<!-- SPDX-License-Identifier: CC-BY-4.0 -->
OAI O-RAN 7.2 Front-haul Docker Compose
<table style="border-collapse: collapse; border: none;">
<tr style="border-collapse: collapse; border: none;">
<td style="border-collapse: collapse; border: none;">
<a href="http://www.openairinterface.org/">
<img src="../../../doc/images/oai_final_logo.png" alt="" border=3 height=50 width=150>
</img>
</a>
</td>
<td style="border-collapse: collapse; border: none; vertical-align: center;">
<b><font size = "5">OAI O-RAN 7.2 Front-haul Docker Compose</font></b>
</td>
</tr>
</table>
![Docker deploy 7.2](../../../doc/images/docker-deploy-oai-7-2.png)

View File

@@ -43,8 +43,6 @@ Options:
Sets build directory (will be <oai-root>/cmake_targets/<build-dir>/build)
--build-e2
Enable the the E2 Agent
--build-e3
Enable the the E3 Agent
-I | --install-external-packages
Installs required packages such as LibXML, asn1.1 compiler, ...
This option will require root password
@@ -145,10 +143,6 @@ function main() {
CMAKE_CMD="$CMAKE_CMD -DE2_AGENT=ON"
shift
;;
--build-e3 )
CMAKE_CMD="$CMAKE_CMD -DE3_AGENT=ON"
shift
;;
-I | --install-external-packages)
INSTALL_EXTERNAL=1
echo_info "Will install external packages"
@@ -207,7 +201,6 @@ function main() {
shift;;
--nrRU)
NRRU=1
CMAKE_CMD="$CMAKE_CMD -DOAI_RU_FRONTHAUL=ON"
TARGET_LIST="$TARGET_LIST nr-oru"
echo_info "Will compile NR O-RU"
shift;;

View File

@@ -0,0 +1,77 @@
# SPDX-License-Identifier: LicenseRef-CSSL-1.0
# CMake toolchain for Qualcomm DragonWing IQ-9/X (ARM component, no Hexagon DSP).
# Uses the GCC aarch64 cross-compiler bundled with the Hexagon SDK 6.4.x.
#
# Note: the SDK's clang (hexagon-clang) targets only the Hexagon DSP; the ARM
# cross-compiler is the Arm GNU Toolchain GCC (aarch64-none-linux-gnu-gcc).
#
# Prerequisites same as the generic cross-arm.cmake, install arm64 target libs:
# sudo dpkg --add-architecture arm64
# sudo apt-get install -y libgnutls28-dev:arm64 libconfig-dev:arm64 \
# libsctp-dev:arm64 libssl-dev:arm64 zlib1g-dev:arm64 libreadline-dev:arm64
#
# Two-step build (see doc/cross-compile.md for details):
#
# Step 1 native host tools (ldpc generators, T-tracer id generator):
# mkdir -p ran_build/build ran_build/build-dragonwing
# cd ran_build/build
# cmake ../../..
# make -j$(nproc) ldpc_generators generate_T
#
# Step 2 cross-compiled ARM binaries:
# cd ../build-dragonwing
# cmake ../../.. -GNinja \
# -DCMAKE_TOOLCHAIN_FILE=../../../cmake_targets/cross-arm-dragonwing.cmake \
# -DNATIVE_DIR=../build
# ninja nr-softmodem nr-cuup nr-uesoftmodem params_libconfig coding rfsimulator
#
# Deploy via adb:
# adb push ran_build/build-dragonwing/nr-softmodem /data/oai/
#
# Optional overrides:
# -DHEXAGON_SDK_ROOT=/opt/Hexagon_SDK/6.4.0.2 (if SDK is in a different location)
set(CMAKE_SYSTEM_NAME Linux)
set(CMAKE_SYSTEM_PROCESSOR aarch64)
if(NOT DEFINED HEXAGON_SDK_ROOT)
set(HEXAGON_SDK_ROOT "/opt/Hexagon_SDK/6.4.0.2")
endif()
set(_DW_TC "${HEXAGON_SDK_ROOT}/tools/gcc_tools_64/bin/aarch64-none-linux-gnu")
set(CMAKE_C_COMPILER "${_DW_TC}-gcc")
set(CMAKE_CXX_COMPILER "${_DW_TC}-g++")
set(CMAKE_AR "${_DW_TC}-ar")
# The SDK GCC's built-in sysroot has an incomplete glibc (missing L_tmpnam and
# similar symbols in stdio.h). Override with the Ubuntu system headers by
# prepending them via -I, which takes priority over the compiler's sysroot
# search paths. /usr/include is the host glibc (architecture-neutral API);
# /usr/include/aarch64-linux-gnu has the arm64-specific bits/ and sys/ headers.
set(CMAKE_C_FLAGS_INIT "-I/usr/include/aarch64-linux-gnu -I/usr/include")
set(CMAKE_CXX_FLAGS_INIT "-I/usr/include/aarch64-linux-gnu -I/usr/include")
# Linker: search the arm64 multiarch directory so arm64 .so files are found
# before any host x86 libraries.
set(CMAKE_EXE_LINKER_FLAGS_INIT "-L/usr/lib/aarch64-linux-gnu")
set(CMAKE_SHARED_LINKER_FLAGS_INIT "-L/usr/lib/aarch64-linux-gnu")
set(CMAKE_MODULE_LINKER_FLAGS_INIT "-L/usr/lib/aarch64-linux-gnu")
# Direct pkg-config at the arm64 multiarch .pc files
set(ENV{PKG_CONFIG_LIBDIR} "/usr/lib/aarch64-linux-gnu/pkgconfig")
# Tell cmake's find_library() where Ubuntu multiarch arm64 libs live
set(CMAKE_LIBRARY_PATH "/usr/lib/aarch64-linux-gnu")
set(CROSS_COMPILE 1)
set(QUALCOMM_DRAGONWING 1)
# Paths to host-compiled code-generation tools (built in Step 1 above)
set(bnProc_gen_128_DIR ${CMAKE_CURRENT_BINARY_DIR}/${NATIVE_DIR})
set(bnProc_gen_avx2_DIR ${CMAKE_CURRENT_BINARY_DIR}/${NATIVE_DIR})
set(bnProc_gen_avx512_DIR ${CMAKE_CURRENT_BINARY_DIR}/${NATIVE_DIR})
set(cnProc_gen_128_DIR ${CMAKE_CURRENT_BINARY_DIR}/${NATIVE_DIR})
set(cnProc_gen_avx2_DIR ${CMAKE_CURRENT_BINARY_DIR}/${NATIVE_DIR})
set(cnProc_gen_avx512_DIR ${CMAKE_CURRENT_BINARY_DIR}/${NATIVE_DIR})
set(genids_DIR ${CMAKE_CURRENT_BINARY_DIR}/${NATIVE_DIR})
set(_check_vcd_DIR ${CMAKE_CURRENT_BINARY_DIR}/${NATIVE_DIR})

View File

@@ -6,7 +6,7 @@
# Finds the xran library. Note that the library number is as follows:
# - oran_e_maintenance_release_v1.5 -> 5.1.6
# - oran_f_release_v1.3 -> 6.1.4
# - oran_k_release_v1.3 -> 11.1.3
# - oran_k_release_v1.1 -> 11.1.1
#
# Required options
# ^^^^^^^^^^^^^^^^

View File

@@ -494,8 +494,8 @@ install_simde_from_source(){
if [[ -v SIMDE_VERSION ]]; then
git checkout -f $SIMDE_VERSION
else
# At time of writing, last working commit for OAI: 1c68d9a
git checkout 1c68d9ad60bf63f3fb527c4ee3b2319d828ffcc6
# At time of writing, last working commit for OAI: c7f26b7
git checkout c7f26b73ba8e874b95c2cec2b497826ad2188f68
fi
# Showing which version is used
git log -n1

View File

@@ -39,9 +39,8 @@ typedef enum { NON_DYNAMIC, DYNAMIC } fiveQI_t;
/* 5QI (5G QoS Identifier) - 3GPP TS 23.501 §5.7.2.1
* Range: 0..255
* - Standardized 5QI values: have one-to-one mapping to standardized 5G QoS characteristics (Table 5.7.4-1)
* - Pre-configured 5QI values: pre-configured in the AN (not in Table 5.7.4-1)
* - Dynamically assigned 5QI values: require signaling of QoS characteristics as part of QoS profile
* OAI implements standardized non-dynamic 5QI only. */
* - Pre-configured 5QI values: pre-configured in the AN
* - Dynamically assigned 5QI values: require signaling of QoS characteristics as part of QoS profile */
#define MIN_FIVEQI 0
#define MAX_STANDARDIZED_FIVEQI 90
#define MAX_FIVEQI 255

View File

@@ -45,20 +45,10 @@
#define NR_MAX_NB_PDU_SESSIONS (256)
#define NR_MAX_NB_ALLOWED_SNSSAI (8) /* Maximum number of allowed S-NSSAI in TS 38.413 */
#define MAX_DRBS_PER_UE (32) /* Maximum number of Data Radio Bearers per UE
* defined for NGAP in TS 38.413 - maxnoofDRBs */
#define MAX_PDUS_PER_UE (8) /* Maximum number of PDU Sessions per UE */
/** Maximum value of nrofPDCCH-MonitoringOccasionPerSSB-InPO-r16 (TS 38.331 PCCH-Config) */
#define NR_PCCH_MAX_MO_PER_SSB_IN_PO 4
/** Maximum number of Paging Occasions per Paging Frame (TS 38.331 PCCH-Config) */
#define NR_PCCH_MAX_PO 4
#define NR_PHYS_CELL_ID_MAX 1007 /* Maximum Physical Cell ID (0..1007) */
#define NB_RB_MBMS_MAX (29 * 16) /* 29 = LTE_maxSessionPerPMCH + 16 = LTE_maxServiceCount from LTE_asn_constant.h */
#define NB_RAB_MAX 11 /* from LTE_maxDRB in LTE_asn_constant.h */
@@ -81,9 +71,6 @@
// SDAP
#define MAX_QOS_FLOWS 64
/** Maximum number of PagingRecords in one PCCH Paging message (TS 38.331) */
#define NR_PCCH_MAX_PAGING_RECORDS 32
// SDAP/5G NAS NOS1
#define DEFAULT_NOS1_PDU_ID 10

View File

@@ -11,7 +11,7 @@ LOG_D(<component>,<format>,<argument>,...)
LOG_T(<component>,<format>,<argument>,...)
)
```
these macros are used in place of the printf C function. The additional ***component*** parameter identifies the functional module which generates the message. At run time, the message will only be printed if the configured log level for the component is greater or equal than the macro level used in the code.
these macros are used in place of the printf C function. The additionnal ***component*** parameter identifies the functionnal module which generates the message. At run time, the message will only be printed if the configured log level for the component is greater or equal than the macro level used in the code.
| macro | level letter | level value | level name |
|:---------|:---------------|:---------------|----------------:|

View File

@@ -155,7 +155,6 @@ static const char *const flag_name[] = {FOREACH_FLAG(FLAG_TEXT) ""};
COMP_DEF(NFAPI_PNF, log) \
COMP_DEF(ITTI, log) \
COMP_DEF(UTIL, log) \
COMP_DEF(E3AP, log) \
COMP_DEF(MAX_LOG_PREDEF_COMPONENTS, )
#define COMP_ENUM(comp, file_extension) comp,

View File

@@ -434,27 +434,6 @@ ID = LEGACY_MAC_TRACE
GROUP = ALL:LEGACY_MAC:LEGACY_GROUP_TRACE:LEGACY
FORMAT = string,log
ID = LEGACY_E3AP_INFO
DESC = E3AP legacy logs - info level
GROUP = ALL:LEGACY_E3AP:LEGACY_GROUP_INFO:LEGACY
FORMAT = string,log
ID = LEGACY_E3AP_ERROR
DESC = E3AP legacy logs - error level
GROUP = ALL:LEGACY_E3AP:LEGACY_GROUP_ERROR:LEGACY
FORMAT = string,log
ID = LEGACY_E3AP_WARNING
DESC = E3AP legacy logs - warning level
GROUP = ALL:LEGACY_E3AP:LEGACY_GROUP_WARNING:LEGACY
FORMAT = string,log
ID = LEGACY_E3AP_DEBUG
DESC = E3AP legacy logs - debug level
GROUP = ALL:LEGACY_E3AP:LEGACY_GROUP_DEBUG:LEGACY
FORMAT = string,log
ID = LEGACY_E3AP_TRACE
DESC = E3AP legacy logs - trace level
GROUP = ALL:LEGACY_E3AP:LEGACY_GROUP_TRACE:LEGACY
FORMAT = string,log
ID = LEGACY_NR_MAC_INFO
DESC = NR_MAC legacy logs - info level
GROUP = ALL:LEGACY_NR_MAC:LEGACY_GROUP_INFO:LEGACY

View File

@@ -70,8 +70,7 @@ static __attribute__((always_inline)) inline int count_bits64(uint64_t v)
static __attribute__((always_inline)) inline int count_bits64_with_mask(uint64_t v, int start, int num)
{
AssertFatal(start >= 0 && num > 0 && start + num <= 64, "Invalid range: start=%d num=%d for 64 bits mask\n", start, num);
uint64_t mask = (num == 64 ? ~0ULL : (1ULL << num) - 1) << start;
uint64_t mask = ((1LL << num) - 1) << start;
return count_bits64(v & mask);
}

View File

@@ -0,0 +1,135 @@
# SPDX-License-Identifier: LicenseRef-CSSL-1.0
#
# CMakeLists.txt — Hexagon DSP offload template.
#
# Builds two targets from a single file depending on OS_TYPE:
#
# OS_TYPE=HLOS → libhexdsp_offload.so (ARM stub, loaded by OAI)
# (default) → libhexdsp_offload_skel.so (DSP skeleton, pushed to device)
#
# ── ARM (HLOS) build ────────────────────────────────────────────────────────
#
# cmake -B build-arm \
# -DHEXAGON_SDK_ROOT=/opt/Hexagon_SDK/6.4.0.2 \
# -DCMAKE_TOOLCHAIN_FILE=../../cmake_targets/cross-arm-dragonwing.cmake \
# -DOS_TYPE=HLOS \
# common/utils/hexagon_offload_template
# cmake --build build-arm
#
# ── DSP (Hexagon) build ──────────────────────────────────────────────────────
#
# cmake -B build-dsp \
# -DHEXAGON_SDK_ROOT=/opt/Hexagon_SDK/6.4.0.2 \
# -DCMAKE_TOOLCHAIN_FILE=/opt/Hexagon_SDK/6.4.0.2/build/cmake/hexagon_toolchain.cmake \
# -DDSP_VERSION=v73 \
# common/utils/hexagon_offload_template
# cmake --build build-dsp
#
# ── Deploy ──────────────────────────────────────────────────────────────────
#
# # ARM stub: push alongside the other OAI shared libs
# adb push build-arm/libhexdsp_offload.so /data/oai/lib/
#
# # DSP skel: FastRPC loads it from the adsp library path
# adb push build-dsp/libhexdsp_offload_skel.so /vendor/lib/rfsa/adsp/
#
# ── OAI integration ──────────────────────────────────────────────────────────
#
# OAI loads the ARM stub via load_module_shlib (common/utils/load_module_shlib.c):
#
# loader_shlibfunc_t fdesc[] = {
# {.fname = "hexdsp_offload_init"},
# {.fname = "hexdsp_offload_shutdown"},
# {.fname = "hexdsp_offload_process"},
# };
# load_module_version_shlib("hexdsp_offload", "", fdesc, 3, NULL);
cmake_minimum_required(VERSION 3.22)
project(hexdsp_offload C)
# ---------------------------------------------------------------------------
# Hexagon SDK root — defaults to the standard install path.
# Override with -DHEXAGON_SDK_ROOT=<path> if needed.
# ---------------------------------------------------------------------------
if(NOT HEXAGON_SDK_ROOT)
set(HEXAGON_SDK_ROOT "/opt/Hexagon_SDK/6.4.0.2")
endif()
include(${HEXAGON_SDK_ROOT}/build/cmake/hexagon_fun.cmake)
# ---------------------------------------------------------------------------
# Include paths shared by both ARM stub and DSP skel.
# ---------------------------------------------------------------------------
set(common_incs
${CMAKE_CURRENT_SOURCE_DIR}/inc
${CMAKE_CURRENT_BINARY_DIR} # qaic writes generated .h here
${HEXAGON_SDK_ROOT}/incs
${HEXAGON_SDK_ROOT}/incs/stddef
${HEXAGON_SDK_ROOT}/ipc/fastrpc/rpcmem/inc
)
# ---------------------------------------------------------------------------
# ARM (HLOS) side — stub shared library loaded by OAI via dlopen.
# ---------------------------------------------------------------------------
if(${OS_TYPE} MATCHES "HLOS")
add_library(hexdsp_offload SHARED
# qaic-generated stub (file created at configure time by build_idl)
${CMAKE_CURRENT_BINARY_DIR}/hexdsp_offload_stub
# ARM glue: rpcmem bounce buffers + OAI-compatible function exports
${CMAKE_CURRENT_SOURCE_DIR}/src/hexdsp_offload_arm
)
# Run qaic on the IDL file; adds hexdsp_offload_stub.c and hexdsp_offload.h
# to the build as side-effects.
build_idl(inc/hexdsp_offload.idl hexdsp_offload)
include_directories(${common_incs})
# Pick the right cdsprpc library for the target OS (Android vs Ubuntu ARM).
choose_dsprpc(cdsp dsprpc)
link_custom_library(hexdsp_offload ${dsprpc})
# Do NOT link rpcmem.a here.
# The SDK's rpcmem.a contains only deprecated no-op stubs for
# rpcmem_init / rpcmem_deinit. If linked, they shadow the real
# implementations in the device's libcdsprpc.so, leaving the DMA heap
# uninitialised so rpcmem_alloc() silently fails (error -104 on open).
# All rpcmem symbols are satisfied at runtime by libcdsprpc.so.
# Applies SDK-standard compile flags (PIC, warnings, …).
set_common_compile_and_link_options(hexdsp_offload)
install(TARGETS hexdsp_offload LIBRARY DESTINATION lib)
# ---------------------------------------------------------------------------
# DSP (Hexagon) side — skel shared library pushed to /vendor/lib/rfsa/adsp/.
# ---------------------------------------------------------------------------
else()
# DSP_VERSION selects the Hexagon ISA revision; must match the hardware.
# Known values: v73 (SM8550 era), v75, v79, v81.
# For the DragonWing SA9000P confirm the exact version with:
# adb shell cat /sys/devices/soc0/soc_id
# and cross-reference with Qualcomm product documentation.
if(NOT DSP_VERSION)
set(DSP_VERSION "v73")
message(STATUS "DSP_VERSION not set; defaulting to ${DSP_VERSION}. "
"Override with -DDSP_VERSION=v75 etc.")
endif()
add_library(hexdsp_offload_skel SHARED
# qaic-generated skel dispatch layer
${CMAKE_CURRENT_BINARY_DIR}/hexdsp_offload_skel
# DSP-side compute implementation (student fills this in)
${CMAKE_CURRENT_SOURCE_DIR}/src/hexdsp_offload_imp
)
build_idl(inc/hexdsp_offload.idl hexdsp_offload_skel)
include_directories(${common_incs})
# copy_binaries puts the .so into the SDK ship/ staging directory.
copy_binaries(hexdsp_offload_skel)
endif()

View File

@@ -0,0 +1,427 @@
<!-- SPDX-License-Identifier: CC-BY-4.0 -->
# Hexagon DSP Offload Template
This directory is a starting point for offloading computation from the OAI ARM
cores to the Qualcomm Hexagon DSP on the DragonWing IQ-9075.
The pattern is the same as `libldpc.so` or `libdfts.so`: OAI loads a shared
library at runtime via `dlopen`, calls functions through typed function
pointers, and is unaware of how the work is performed inside. Here the .so
is a thin glue layer on the ARM side; the actual computation runs on the
Compute DSP (cDSP) via Qualcomm's **FastRPC** mechanism.
[[_TOC_]]
---
## 1 How FastRPC works
```
┌─────────────────────────────────────────────────────────────────────┐
│ ARM cores (aarch64, Linux/HLOS) │
│ │
│ OAI process │
│ ├─ dlopen("libhexdsp_offload.so") │
│ └─ hexdsp_offload_process(in, out) │
│ │ │
│ │ 1. (ION buffers pre-allocated in hexdsp_offload_init) │
│ │ 2. memcpy(in → g_rpc_in) │
│ │ 3. hexdsp_offload_run() ────── FastRPC syscall ──► │
│ │ │ │
│ libhexdsp_offload.so │ │
│ ├─ hexdsp_offload_stub.c (qaic-generated marshalling) │ │
│ └─ hexdsp_offload_arm.c (rpcmem + OAI glue) │ │
└────────────────────────────────────────────────────────────────│────┘
kernel: ion_map / smmu_map │
┌─────────────────────────────────────────────────────────────────────┐
│ Hexagon cDSP (QuRT RTOS, protected domain) │
│ │
│ FastRPC dispatcher │
│ └─ unmarshal args → call hexdsp_offload_process() │
│ │
│ libhexdsp_offload_skel.so (loaded from /vendor/lib/rfsa/adsp/) │
│ ├─ hexdsp_offload_skel.c (qaic-generated dispatch) │
│ └─ hexdsp_offload_imp.c (your compute kernel — fill this in) │
│ │
│ 4. compute on in_buf[], write out_buf[] │
│ 5. return 0 │
└─────────────────────────────────────────────────────────────────────┘
kernel: flush cache / unmap │
ARM side resumes:
│ 6. memcpy(g_rpc_out → out)
│ (ION buffers remain allocated for the next call)
```
### 1.1 The IDL and qaic
The interface between ARM and DSP is declared in a single `.idl` file
(`inc/hexdsp_offload.idl`). The SDK tool `qaic` compiles it into three
files that are never hand-edited:
| Generated file | Used by | Purpose |
|---------------------------|----------|----------------------------------------------|
| `hexdsp_offload_stub.c` | ARM .so | Marshals arguments into a FastRPC message |
| `hexdsp_offload_skel.c` | DSP .so | Unmarshals the message, calls `_imp.c` |
| `hexdsp_offload.h` | both | C function prototypes visible on both sides |
The IDL method is named `run` (generating `hexdsp_offload_run()`) to avoid a
name collision with the public ARM API function `hexdsp_offload_process()`
that OAI resolves via `dlsym`. Both live in the same `.so`; identical names
would cause the ARM glue to call itself recursively.
IDL types map to C as follows:
| IDL type | C type (ARM stub) | C type (DSP skel) |
|---------------------|---------------------------------|--------------------------|
| `in sequence<uint8>`| `const uint8_t *, int len` | `const uint8_t *, int` |
| `rout sequence<uint8>`| `uint8_t *, int len` | `uint8_t *, int` |
| `long` | `int` | `int` |
| `remote_handle64` | opaque handle (open/close) | `remote_handle64` |
`in` means ARM → DSP (read-only on DSP). `rout` means DSP → ARM
(written by DSP, read by ARM after the call returns).
### 1.2 Memory: ION and rpcmem
The DSP has its own MMU (SMMU). Ordinary `malloc` buffers on the ARM side
are **not visible** to the DSP. Data must be in an ION buffer — a
physically-contiguous allocation that both the ARM MMU and the DSP SMMU can
map.
`rpcmem_alloc()` (from the Hexagon SDK) handles this transparently:
```c
uint8_t *buf = rpcmem_alloc(RPCMEM_HEAP_ID_SYSTEM, RPCMEM_DEFAULT_FLAGS, size);
// use buf normally from ARM ...
// FastRPC maps it into DSP address space automatically when passed as
// an in/rout sequence argument.
rpcmem_free(buf);
```
If you pass a plain `malloc` buffer to a FastRPC call the kernel will make
an extra copy; for large blocks (e.g. a full LDPC codeblock) this kills
performance. The ARM glue in `hexdsp_offload_arm.c` therefore allocates
rpcmem bounce buffers internally and copies once on each side, keeping the
OAI caller agnostic of this requirement.
For zero-copy operation the caller can allocate rpcmem directly and skip
the bounce; this requires exposing `rpcmem_alloc` to the OAI layer.
### 1.3 FastRPC domains
The SA9000P has several DSP subsystems. For compute offload the relevant
one is the **cDSP** (Compute DSP). The domain is encoded in the URI passed
to `hexdsp_offload_open()`:
```c
const char *uri = hexdsp_offload_URI "&_dom=cdsp";
```
`hexdsp_offload_URI` is a string constant generated by `qaic` from the
interface name. Use the string form `"&_dom=cdsp"` rather than `"&_dom=3"`;
the named form is more readable and robust across SDK versions.
### 1.4 Init ordering and unsigned-PD mode
Two ordering constraints apply on Linux HLOS with fastrpc ≥ 1.0.4:
1. **Unsigned-PD mode** must be enabled *before* `hexdsp_offload_open()`.
By default, fastrpc uses the secure cDSP domain which requires a signed
skel library. Custom skels (not signed by Qualcomm) need unsigned-PD mode:
```c
struct remote_rpc_control_unsigned_module um = { .domain = 3, .enable = 1 };
remote_session_control(DSPRPC_CONTROL_UNSIGNED_MODULE, &um, sizeof(um));
// then: hexdsp_offload_open(uri, &handle);
```
Include `remote.h` from `${HEXAGON_SDK_ROOT}/incs/`.
2. **`rpcmem_init()` must be called before `hexdsp_offload_open()`.**
`hexdsp_offload_open()` triggers a DMA transfer of the FastRPC shell
(`fastrpc_shell_unsigned_3`, ~1.26 MB) to the DSP. The DMA heap
(`/dev/dma_heap/system`) must already be open for this to succeed.
### 1.5 rpcmem pre-allocation
`rpcmem_alloc` / `rpcmem_free` on every `hexdsp_offload_process()` call
exhausts the rpcmem pool within a few hundred invocations (e.g. in a
per-codeword test loop). The correct pattern is to pre-allocate ION bounce
buffers once in `hexdsp_offload_init()` and reuse them across all calls.
Do **not** link `rpcmem.a` from the Hexagon SDK. It contains only no-op
stubs for `rpcmem_init` / `rpcmem_deinit` that shadow the real
implementations in `libcdsprpc.so`, leaving the DMA heap uninitialised so
`rpcmem_alloc()` fails silently. All rpcmem symbols are provided by the
device's `libcdsprpc.so` at runtime.
### 1.4 The skel library on the device
The DSP side (`libhexdsp_offload_skel.so`) is a Hexagon shared library
loaded by the FastRPC runtime from a fixed search path on the device:
```
/vendor/lib/rfsa/adsp/
```
The ARM stub looks for it there automatically; no path configuration is
needed. The library runs inside a **protected domain** (PD) on the cDSP —
a sandboxed QuRT process. A crash in the skel library does not crash the
ARM process; FastRPC returns an error code.
---
## 2 Directory structure
```
hexagon_offload_template/
├── CMakeLists.txt ← single file, builds ARM stub or DSP skel
├── inc/
│ └── hexdsp_offload.idl ← interface definition (qaic input)
└── src/
├── hexdsp_offload_arm.c ← ARM glue: rpcmem bounce + OAI dlopen exports
└── hexdsp_offload_imp.c ← DSP compute kernel (fill this in)
```
### 2.1 `inc/hexdsp_offload.idl`
Declares the `process()` method. Edit this to add new methods or change
the argument types. Run `qaic` (or let cmake do it via `build_idl()`) after
any change.
### 2.2 `src/hexdsp_offload_arm.c`
Compiled into **`libhexdsp_offload.so`** together with the qaic-generated
`hexdsp_offload_stub.c`. Exports three symbols that OAI resolves via
`dlsym`:
| Symbol | Called by OAI when |
|--------------------------|-----------------------------|
| `hexdsp_offload_init` | module is first loaded |
| `hexdsp_offload_shutdown`| OAI exits or unloads module |
| `hexdsp_offload_process` | each compute invocation |
`hexdsp_offload_init` is idempotent: if a test harness calls it before every
work unit, the first call opens the session and pre-allocates ION buffers;
subsequent calls return 0 immediately (guarded by `dsp_handle != -1`).
### 2.3 `src/hexdsp_offload_imp.c`
Compiled into **`libhexdsp_offload_skel.so`** with hexagon-clang. This is
the file a student replaces with the actual Hexagon kernel. It has access
to:
- **HAP_farf** — DSP-side logging (`FARF(RUNTIME_HIGH, "...")`), visible
via `adb logcat -s adsprpc`
- **HVX intrinsics** — 128-byte SIMD vectors; enable with
`-mhvx -mhvx-length=128B` and `#include <hexagon_types.h>`
- **HAP_mem** — DSP heap if local scratch is needed
- **QuRT** — threading, mutexes, timers (advanced use)
---
## 3 Build
Both builds are invoked from the OAI repository root. They are independent
and can run in any order.
### 3.1 Prerequisites
```shell
# Hexagon SDK (already installed for DragonWing ARM build)
source /opt/Hexagon_SDK/6.4.0.2/setup_sdk_env.source
```
### 3.2 ARM stub (libhexdsp_offload.so)
Cross-compiled with the DragonWing ARM toolchain.
```shell
cmake -B build-hexdsp-arm \
-DHEXAGON_SDK_ROOT=/opt/Hexagon_SDK/6.4.0.2 \
-DCMAKE_TOOLCHAIN_FILE=cmake_targets/cross-arm-dragonwing.cmake \
-DOS_TYPE=HLOS \
common/utils/hexagon_offload_template
cmake --build build-hexdsp-arm
```
Output: `build-hexdsp-arm/libhexdsp_offload.so`
### 3.3 DSP skel (libhexdsp_offload_skel.so)
Compiled with hexagon-clang targeting the cDSP instruction set.
```shell
cmake -B build-hexdsp-dsp \
-DHEXAGON_SDK_ROOT=/opt/Hexagon_SDK/6.4.0.2 \
-DCMAKE_TOOLCHAIN_FILE=/opt/Hexagon_SDK/6.4.0.2/build/cmake/hexagon_toolchain.cmake \
-DDSP_VERSION=v73 \
common/utils/hexagon_offload_template
cmake --build build-hexdsp-dsp
```
Output: `build-hexdsp-dsp/libhexdsp_offload_skel.so`
> **DSP_VERSION:** must match the Hexagon ISA version of the cDSP on the
> device. To find it:
> ```shell
> adb shell cat /sys/devices/soc0/soc_id
> ```
> Cross-reference the SoC ID with the Qualcomm product documentation.
> Known values: `v73` (SM8550 era), `v75`, `v79`, `v81`.
### 3.4 Enabling HVX in the DSP build
Add the following to the DSP cmake invocation:
```shell
-DCMAKE_C_FLAGS="-mhvx -mhvx-length=128B"
```
Then in `hexdsp_offload_imp.c`:
```c
#include <hexagon_types.h>
// HVX_Vector is 128 bytes wide. Pointers must be 128-byte aligned.
HVX_Vector *vin = (HVX_Vector *)in_buf;
HVX_Vector *vout = (HVX_Vector *)out_buf;
int nvec = in_bufLen / sizeof(HVX_Vector);
for (int i = 0; i < nvec; i++)
vout[i] = Q6_Vub_vsat_VhVh(vin[i], vin[i]); // example: saturate
```
---
## 4 Deploy
```shell
# ARM stub: alongside the other OAI shared libs
adb push build-hexdsp-arm/libhexdsp_offload.so /data/oai/lib/
# DSP skel: FastRPC searches this path automatically
adb shell mkdir -p /vendor/lib/rfsa/adsp
adb push build-hexdsp-dsp/libhexdsp_offload_skel.so /vendor/lib/rfsa/adsp/
```
---
## 5 OAI integration
OAI's `load_module_shlib` infrastructure (`common/utils/load_module_shlib.c`)
loads offload libraries at runtime using `dlopen` + `dlsym`. To wire in
the hexdsp offload, add a loader similar to `nrLDPC_load.c`:
```c
#include "load_module_shlib.h"
// Function pointer types matching hexdsp_offload_arm.c exports
typedef int (*hexdsp_init_f)(void);
typedef int (*hexdsp_shutdown_f)(void);
typedef int (*hexdsp_process_f)(const uint8_t *, uint32_t, uint8_t *, uint32_t);
typedef struct {
hexdsp_init_f init;
hexdsp_shutdown_f shutdown;
hexdsp_process_f process;
} hexdsp_interface_t;
int load_hexdsp(hexdsp_interface_t *itf)
{
loader_shlibfunc_t fdesc[] = {
{.fname = "hexdsp_offload_init"},
{.fname = "hexdsp_offload_shutdown"},
{.fname = "hexdsp_offload_process"},
};
int ret = load_module_version_shlib("hexdsp_offload", "", fdesc,
sizeofArray(fdesc), NULL);
if (ret) return ret;
itf->init = (hexdsp_init_f) fdesc[0].fptr;
itf->shutdown = (hexdsp_shutdown_f)fdesc[1].fptr;
itf->process = (hexdsp_process_f) fdesc[2].fptr;
return itf->init();
}
```
The loader searches for `libhexdsp_offload.so` on `LD_LIBRARY_PATH` or the
path configured under `loader.hexdsp_offload.shlibpath` in the OAI config
file.
---
## 6 Adapting the template
To implement a specific kernel (e.g. LDPC decoding):
1. **Rename** — replace `hexdsp_offload` with your module name throughout
(IDL, source files, CMakeLists.txt).
2. **Extend the IDL** — add methods matching the ARM function signatures OAI
expects. Each `in sequence<T>` argument becomes a `(const T*, int)` pair
in C. Each `rout sequence<T>` becomes a `(T*, int)` pair.
3. **ARM glue** — update `hexdsp_offload_arm.c`:
- Set `HEXDSP_MAX_IN_LEN` / `HEXDSP_MAX_OUT_LEN` to the maximum buffer
sizes your workload needs.
- Call `remote_session_control(DSPRPC_CONTROL_UNSIGNED_MODULE)` before
`hexdsp_offload_open()`.
- Call `rpcmem_init()` before `hexdsp_offload_open()`.
- Do not link `rpcmem.a`; all rpcmem symbols come from `libcdsprpc.so`.
- The IDL method and the public API function must have different names to
avoid a recursive call (the IDL uses `run`, the public API uses `process`).
4. **DSP kernel** — implement the compute in `hexdsp_offload_imp.c`. Start
scalar, verify correctness, then vectorise with HVX.
5. **OAI loader** — write a `load_<module>.c` following the pattern in §5
above, using the same function-pointer types OAI already uses for LDPC or
DFT offload.
---
## 7 Debugging
### 7.1 DSP logs
`FARF()` messages from the skel library appear in the Android/Linux log:
```shell
adb logcat -s adsprpc
```
Log levels: `RUNTIME_HIGH` (always on), `HIGH`, `MED`, `LOW`.
### 7.2 FastRPC error codes
Negative return values from `hexdsp_offload_open()` or `_process()` are
FastRPC error codes. Common ones:
| Code | Meaning |
|--------|--------------------------------------------------------------|
| `-1` | Generic failure (check logcat for detail) |
| `3` | Skel library not found in `/vendor/lib/rfsa/adsp/` |
| `6` | DSP subsystem not available or not powered |
| `-14` | Bad address — buffer not allocated with `rpcmem_alloc` |
### 7.3 Simulator
The DSP skel can be tested on the build host without a device using the
Hexagon simulator:
```shell
# Build with the simulator target (no device needed)
cmake -B build-hexdsp-sim \
-DHEXAGON_SDK_ROOT=/opt/Hexagon_SDK/6.4.0.2 \
-DCMAKE_TOOLCHAIN_FILE=/opt/Hexagon_SDK/6.4.0.2/build/cmake/hexagon_toolchain.cmake \
-DDSP_VERSION=v73 \
-DNO_QURT_INC=1 \
common/utils/hexagon_offload_template
cmake --build build-hexdsp-sim
# The SDK's hexagon_fun.cmake adds a runHexagonSim() target automatically.
cmake --build build-hexdsp-sim --target run
```

View File

@@ -0,0 +1,36 @@
// SPDX-License-Identifier: LicenseRef-CSSL-1.0
//
// hexdsp_offload.idl — FastRPC interface between ARM host and Hexagon DSP.
//
// qaic compiles this into:
// hexdsp_offload_stub.c (linked into the ARM .so)
// hexdsp_offload_skel.c (linked into the DSP .so)
// hexdsp_offload.h (shared header, included by both sides)
//
// Naming rule: every function below becomes hexdsp_offload_<name>() in C.
//
// The method is named "run" (not "process") to avoid a name collision with
// the public ARM API function hexdsp_offload_process() that OAI resolves via
// dlsym. Both live in the same .so; identical names would cause the ARM glue
// to call itself recursively instead of dispatching to the DSP.
#include "AEEStdDef.idl"
#include "remote.idl"
// IDL_VERSION is checked at runtime when stub and skel versions are mismatched.
const string IDL_VERSION = "1.0.0";
interface hexdsp_offload : remote_handle64 {
// Run one block of computation on the DSP.
//
// in_buf — input data, must be allocated with rpcmem_alloc() on ARM.
// out_buf — output data, must be allocated with rpcmem_alloc() on ARM.
//
// The DSP receives physically-contiguous ION buffers via DMA mapping;
// they arrive as ordinary pointers in hexdsp_offload_imp.c.
//
// Returns 0 on success, negative errno on failure.
long run(in sequence<uint8> in_buf,
rout sequence<uint8> out_buf);
};

View File

@@ -0,0 +1,150 @@
// SPDX-License-Identifier: LicenseRef-CSSL-1.0
//
// hexdsp_offload_arm.c — ARM-side glue library.
//
// This .c file is compiled into libhexdsp_offload.so (with the qaic-generated
// hexdsp_offload_stub.c). OAI loads it at runtime via load_module_shlib /
// dlopen, resolving the three symbols below:
//
// hexdsp_offload_init() — open FastRPC session, initialise rpcmem
// hexdsp_offload_shutdown() — close session, release rpcmem
// hexdsp_offload_process() — copy to ION buffers, call DSP, copy result back
//
// The caller owns the in/out memory; this glue handles the rpcmem bounce
// buffers internally so the DSP always sees physically-contiguous ION memory
// regardless of how OAI allocated the working buffers.
//
// Validated on DragonWing SA9000P / IQ-9075 with fastrpc 1.0.4 (Linux HLOS).
//
// FastRPC docs: Hexagon SDK → ipc/fastrpc/
// RPCMEM docs: Hexagon SDK → ipc/fastrpc/rpcmem/
#include "hexdsp_offload.h" // generated by qaic from hexdsp_offload.idl
#include "remote.h" // DSPRPC_CONTROL_UNSIGNED_MODULE, remote_session_control
#include "rpcmem.h"
#include <stdint.h>
#include <string.h>
#include <stdio.h>
// Maximum buffer sizes for the pre-allocated ION bounce buffers.
// Set these to the largest buffers your workload will ever pass in a single
// hexdsp_offload_process() call. Pre-allocating at init time avoids
// per-call rpcmem_alloc/free cycles that exhaust the pool on high-call-rate
// workloads (e.g. per-codeword LDPC decode loops).
#define HEXDSP_MAX_IN_LEN (64 * 1024) // TODO: adjust for your workload
#define HEXDSP_MAX_OUT_LEN (64 * 1024) // TODO: adjust for your workload
static remote_handle64 dsp_handle = (remote_handle64)-1;
static int rpcmem_ready = 0;
static uint8_t *g_rpc_in = NULL;
static uint8_t *g_rpc_out = NULL;
// ---------------------------------------------------------------------------
// hexdsp_offload_init — open FastRPC session and pre-allocate ION buffers.
// ---------------------------------------------------------------------------
int hexdsp_offload_init(void)
{
// Guard: test harnesses often call init() before every work unit to reset
// state. Re-opening the session and re-allocating ION buffers on every
// call exhausts the rpcmem pool within a few hundred invocations.
if (dsp_handle != (remote_handle64)-1)
return 0;
// On Linux HLOS with fastrpc >= 1.0.4 the secure cDSP device is used by
// default, which requires signed skels. Enable unsigned-PD mode so that
// custom skels can be loaded without a hardware signature.
// MUST be called before the first remote_handle64_open() call.
struct remote_rpc_control_unsigned_module um = { .domain = 3, .enable = 1 };
int rc = remote_session_control(DSPRPC_CONTROL_UNSIGNED_MODULE, &um, sizeof(um));
if (rc)
fprintf(stderr, "hexdsp_offload: remote_session_control(UNSIGNED_MODULE) returned %d (continuing)\n", rc);
// rpcmem_init() opens /dev/dma_heap/system so libcdsprpc.so can DMA-transfer
// the FastRPC shell to the DSP during remote_handle64_open() below.
// MUST be called before hexdsp_offload_open().
// NOTE: rpcmem.a must NOT be linked (see CMakeLists.txt); rpcmem symbols
// come from the device's libcdsprpc.so.
rpcmem_init();
rpcmem_ready = 1;
// "&_dom=cdsp" selects the Compute DSP on Linux HLOS (fastrpc >= 1.0.4).
// Use the string form rather than "&_dom=3" — more readable and robust
// across SDK versions.
const char *uri = hexdsp_offload_URI "&_dom=cdsp";
int err = hexdsp_offload_open(uri, &dsp_handle);
if (err) {
fprintf(stderr, "hexdsp_offload_open failed: %d\n", err);
rpcmem_deinit();
rpcmem_ready = 0;
return err;
}
// Pre-allocate ION-backed bounce buffers once; reuse across all process() calls.
g_rpc_in = rpcmem_alloc(RPCMEM_HEAP_ID_SYSTEM, RPCMEM_DEFAULT_FLAGS, HEXDSP_MAX_IN_LEN);
g_rpc_out = rpcmem_alloc(RPCMEM_HEAP_ID_SYSTEM, RPCMEM_DEFAULT_FLAGS, HEXDSP_MAX_OUT_LEN);
if (!g_rpc_in || !g_rpc_out) {
fprintf(stderr, "hexdsp_offload: rpcmem_alloc for pre-allocated buffers failed\n");
if (g_rpc_in) { rpcmem_free(g_rpc_in); g_rpc_in = NULL; }
if (g_rpc_out) { rpcmem_free(g_rpc_out); g_rpc_out = NULL; }
hexdsp_offload_close(dsp_handle);
dsp_handle = (remote_handle64)-1;
rpcmem_deinit();
rpcmem_ready = 0;
return -1;
}
return 0;
}
// ---------------------------------------------------------------------------
// hexdsp_offload_shutdown — tear down.
// ---------------------------------------------------------------------------
int hexdsp_offload_shutdown(void)
{
if (dsp_handle != (remote_handle64)-1) {
hexdsp_offload_close(dsp_handle);
dsp_handle = (remote_handle64)-1;
}
if (g_rpc_in) { rpcmem_free(g_rpc_in); g_rpc_in = NULL; }
if (g_rpc_out) { rpcmem_free(g_rpc_out); g_rpc_out = NULL; }
if (rpcmem_ready) {
rpcmem_deinit();
rpcmem_ready = 0;
}
return 0;
}
// ---------------------------------------------------------------------------
// hexdsp_offload_process — main offload entry point.
//
// in/out are OAI-allocated buffers (e.g. stack or malloc). We copy through
// pre-allocated ION bounce buffers so FastRPC can DMA-map them to the DSP
// without an extra kernel copy. The OAI caller sees ordinary pointers.
// ---------------------------------------------------------------------------
int hexdsp_offload_process(const uint8_t *in, uint32_t in_len,
uint8_t *out, uint32_t out_len)
{
if (!g_rpc_in || !g_rpc_out) {
fprintf(stderr, "hexdsp_offload: buffers not ready — call hexdsp_offload_init() first\n");
return -1;
}
if ((int)in_len > HEXDSP_MAX_IN_LEN || (int)out_len > HEXDSP_MAX_OUT_LEN) {
fprintf(stderr, "hexdsp_offload: request too large (in=%u max=%d, out=%u max=%d)\n",
in_len, HEXDSP_MAX_IN_LEN, out_len, HEXDSP_MAX_OUT_LEN);
return -1;
}
memcpy(g_rpc_in, in, in_len);
// FastRPC call: DMA-maps g_rpc_in/g_rpc_out to the DSP address space.
int err = hexdsp_offload_run(dsp_handle,
g_rpc_in, (int)in_len,
g_rpc_out, (int)out_len);
if (err) {
fprintf(stderr, "hexdsp_offload_run RPC failed: %d\n", err);
return err;
}
memcpy(out, g_rpc_out, out_len);
return 0;
}

View File

@@ -0,0 +1,79 @@
// SPDX-License-Identifier: LicenseRef-CSSL-1.0
//
// hexdsp_offload_imp.c — DSP-side skeleton implementation.
//
// This file is compiled with hexagon-clang and loaded by FastRPC into the
// Compute DSP (cDSP) protected domain at runtime. The ARM stub calls
// hexdsp_offload_process(); FastRPC unmarshalls the call and invokes
// hexdsp_offload_run() here on the DSP.
//
// Useful DSP headers:
// HAP_farf.h — logging (FARF macro, visible via: adb logcat -s adsprpc)
// HAP_mem.h — DSP-side heap allocation
// hexagon_types.h / hexagon_protos.h — HVX intrinsics
//
// To enable HVX vector processing, compile with:
// -mhvx -mhvx-length=128B
// and #include <hexagon_types.h>.
#include "hexdsp_offload.h" // generated by qaic
#include "HAP_farf.h"
#include <stdlib.h>
#include <string.h>
// ---------------------------------------------------------------------------
// Session lifecycle — called by FastRPC on remote_handle64 open/close.
// ---------------------------------------------------------------------------
int hexdsp_offload_open(const char *uri, remote_handle64 *handle)
{
// Allocate a small context token; the value is opaque to FastRPC.
void *ctx = malloc(sizeof(int));
if (!ctx)
return -1;
*handle = (remote_handle64)(uintptr_t)ctx;
FARF(RUNTIME_HIGH, "hexdsp_offload: session opened (handle=%p)", ctx);
return 0;
}
int hexdsp_offload_close(remote_handle64 handle)
{
free((void *)(uintptr_t)handle);
FARF(RUNTIME_HIGH, "hexdsp_offload: session closed");
return 0;
}
// ---------------------------------------------------------------------------
// Main compute entry point.
//
// in_buf / out_buf are DMA-mapped by FastRPC from the ARM rpcmem buffers;
// they arrive as ordinary pointers here — no extra mapping needed.
// ---------------------------------------------------------------------------
int hexdsp_offload_run(remote_handle64 handle,
const uint8_t *in_buf, int in_bufLen,
uint8_t *out_buf, int out_bufLen)
{
FARF(RUNTIME_HIGH,
"hexdsp_offload_run: in=%d out=%d bytes", in_bufLen, out_bufLen);
// -----------------------------------------------------------------------
// TODO: replace this passthrough with the actual Hexagon kernel.
//
// Scalar example:
// for (int i = 0; i < out_bufLen && i < in_bufLen; i++)
// out_buf[i] = in_buf[i] ^ 0xff;
//
// HVX vector example (requires -mhvx -mhvx-length=128B):
// #include <hexagon_types.h>
// HVX_Vector *vin = (HVX_Vector *)in_buf;
// HVX_Vector *vout = (HVX_Vector *)out_buf;
// int nvec = in_bufLen / VLEN; // VLEN = 128 for 128B HVX
// for (int i = 0; i < nvec; i++)
// vout[i] = Q6_V_vnot_V(vin[i]); // bitwise NOT as placeholder
// -----------------------------------------------------------------------
int len = (out_bufLen < in_bufLen) ? out_bufLen : in_bufLen;
memcpy(out_buf, in_buf, len);
return 0;
}

View File

@@ -257,9 +257,11 @@ uint32_t nr_timer_remaining_time(const NR_timer_t *timer);
int set_default_nta_offset(frequency_range_t freq_range, uint32_t samples_per_subframe);
static inline int get_num_dmrs(uint16_t dmrs_mask)
static inline int get_num_dmrs(uint16_t dmrs_mask )
{
return __builtin_popcount(dmrs_mask);
int num_dmrs=0;
for (int i=0;i<16;i++) num_dmrs+=((dmrs_mask>>i)&1);
return(num_dmrs);
}
void warn_higher_threequarter_fs(const int n_rb, const int mu);

View File

@@ -40,9 +40,6 @@ typedef struct ShmTDIQChannel_s {
ShmTDIQChannel *shm_td_iq_channel_create(const char *name, int num_tx_ant, int num_rx_ant)
{
// Unlink any stale shared memory segment first to ensure we start fresh and reclaim space
shm_unlink(name);
// Create shared memory segment
int fd = shm_open(name, O_CREAT | O_RDWR, S_IRUSR | S_IWUSR);
AssertFatal(fd != -1, "shm_open failed: %s\n", strerror(errno));
@@ -99,48 +96,29 @@ ShmTDIQChannel *shm_td_iq_channel_connect(const char *name, int timeout_in_secon
{
// Create shared memory segment
int fd = -1;
double timeout_in_uS = timeout_in_seconds * 1000000.0;
while (timeout_in_uS > 0 && fd == -1) {
while (timeout_in_seconds > 0 && fd == -1) {
for (int i = 0; i < 1000; i++) {
fd = shm_open(name, O_RDWR, S_IRUSR | S_IWUSR);
if (fd != -1) {
break;
}
usleep(1000);
timeout_in_uS -= 1000;
}
timeout_in_seconds--;
if (fd == -1) {
printf("Waiting for server to create shared memory segment\n");
}
}
AssertFatal(fd != -1, "shm_open() failed: errno %d, %s", errno, strerror(errno));
// Map just the header first to wait for initialization
ShmTDIQChannelData *shm_ptr = mmap(NULL, sizeof(ShmTDIQChannelData), PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (shm_ptr == MAP_FAILED) {
perror("mmap header");
exit(1);
}
// Wait until server initializes the segment
while (timeout_in_uS > 0 && shm_ptr->magic != SHM_MAGIC_NUMBER) {
usleep(1000);
timeout_in_uS -= 1000;
}
AssertFatal(shm_ptr->magic == SHM_MAGIC_NUMBER, "Timeout waiting for server to initialize shared memory segment\n");
// Now that it's initialized, ftruncate has finished, so we can get the actual size
struct stat buf;
fstat(fd, &buf);
size_t total_size = buf.st_size;
// Unmap the temporary header mapping
munmap(shm_ptr, sizeof(ShmTDIQChannelData));
// Map the entire shared memory segment
shm_ptr = mmap(NULL, total_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
// Map shared memory segment to address space
ShmTDIQChannelData *shm_ptr = mmap(NULL, total_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (shm_ptr == MAP_FAILED) {
perror("mmap full");
perror("mmap");
exit(1);
}
@@ -156,6 +134,10 @@ ShmTDIQChannel *shm_td_iq_channel_connect(const char *name, int timeout_in_secon
channel->nb_rx_ant = channel->data->num_antennas_tx;
printf("\033[38;5;208mnb_tx_ant, nb_rx_ant: %d, %d\n\033[0m", channel->nb_tx_ant, channel->nb_rx_ant);
channel->type = IQ_CHANNEL_TYPE_CLIENT;
while (shm_ptr->magic != SHM_MAGIC_NUMBER) {
printf("Waiting for server to initialize shared memory\n");
sleep(1);
}
close(fd);
return channel;
}
@@ -256,7 +238,7 @@ int shm_td_iq_channel_wait(ShmTDIQChannel *channel, uint64_t timestamp, uint64_t
fprintf(stderr, "Error: clock_gettime failed: %s\n", strerror(errno));
return 1;
}
ts.tv_sec += timeout_uS / 1000000; // Convert microseconds to seconds
ts.tv_nsec += (timeout_uS % 1000000) * 1000; // Convert remaining microseconds to nanoseconds

View File

@@ -31,6 +31,7 @@
#include <arpa/inet.h>
#include <common/utils/assertions.h>
#include <common/utils/LOG/log.h>
#include "common_lib.h"
#ifdef T
#undef T

View File

@@ -11,8 +11,6 @@
#include <stdarg.h>
#include "openair2/RRC/NR/rrc_gNB_UE_context.h"
#include "openair2/RRC/NR/rrc_gNB_NGAP.h"
#include "openair3/NGAP/ngap_gNB_ue_context.h"
#define TELNETSERVERCODE
#include "telnetsrv.h"
@@ -108,50 +106,9 @@ int rrc_gNB_trigger_release_all(char *buf, int debug, telnet_printfunc_t prnt)
return 0;
}
static int rrc_gNB_trigger_ue_context_release_req(char *buf, int debug, telnet_printfunc_t prnt)
{
UNUSED(debug);
int ue_id = -1;
if (!buf) {
ue_id = get_single_ue_id();
if (ue_id < 1) {
prnt("No UE found!\n");
ERROR_MSG_RET("No UE found!\n");
}
} else {
char *end = NULL;
errno = 0;
long parsed_id = strtol(buf, &end, 10);
if (end == buf || *end != '\0' || errno != 0 || parsed_id < 1 || parsed_id >= 0xfffffe) {
ERROR_MSG_RET("UE ID needs to be [1,0xfffffe]\n");
}
ue_id = parsed_id;
}
gNB_RRC_INST *rrc = RC.nrrrc[0];
rrc_gNB_ue_context_t *ue_context_p = rrc_gNB_get_ue_context(rrc, ue_id);
if (!ue_context_p) {
ERROR_MSG_RET("No RRC UE context for ue_id %d\n", ue_id);
}
if (!ngap_get_ue_context(ue_id)) {
ERROR_MSG_RET("No NGAP UE context for ue_id %d\n", ue_id);
}
ngap_cause_t cause = {
.type = NGAP_CAUSE_RADIO_NETWORK,
.value = NGAP_CAUSE_RADIO_NETWORK_USER_INACTIVITY,
};
rrc_gNB_send_NGAP_UE_CONTEXT_RELEASE_REQ(0, ue_context_p, cause);
prnt("Sent NGAP UE Context Release Request (user-inactivity) for ue_id %d\n", ue_id);
return 0;
}
static telnetshell_cmddef_t rrc_cmds[] = {
{"release_rrc", "[rrc_ue_id(int,opt)]", rrc_gNB_trigger_release},
{"release_rrc_all", "", rrc_gNB_trigger_release_all},
{"ctx_rel_req", "[rrc_ue_id(int,opt)]", rrc_gNB_trigger_ue_context_release_req},
{"", "", NULL},
};

View File

@@ -9,7 +9,3 @@ add_executable(test_fsn test_fsn.cpp)
add_dependencies(tests test_fsn)
target_link_libraries(test_fsn PRIVATE utils GTest::gtest)
add_test(NAME test_fsn COMMAND ./test_fsn)
add_executable(test_bits test_bits.c)
add_dependencies(tests test_bits)
add_test(NAME test_bits COMMAND ./test_bits)

View File

@@ -1,224 +0,0 @@
/*
* SPDX-License-Identifier: LicenseRef-CSSL-1.0
*/
#include <bits.h>
#include <stdlib.h>
#include <time.h>
#include <stdio.h>
#include <string.h>
/* ── test harness ── */
static int g_failures = 0;
#define ARRSIZE 9
#define BITS_PER_WORD 32
#define TOTAL_BITS (ARRSIZE * BITS_PER_WORD)
void exit_function(const char *file, const char *function, const int line, const char *s, const int assert)
{
abort();
}
static inline void set_bit_in_array(uint32_t *arr, int bit_pos)
{
arr[bit_pos / BITS_PER_WORD] |= 1u << (bit_pos % BITS_PER_WORD);
}
static void run_test(const char *name, int result, int expected)
{
if (result != expected) {
fprintf(stderr, "FAIL [%s]: got %d, want %d\n", name, result, expected);
g_failures++;
} else {
printf("PASS [%s]\n", name);
}
}
static void test_get_first_bit_index_mask(void)
{
/* --- no bits set --- */
uint32_t zeros[ARRSIZE] = {0};
run_test("firstbit_all_zeros", get_first_bit_index_mask(zeros, ARRSIZE, 0, TOTAL_BITS), -1);
uint32_t arr[ARRSIZE];
/* --- random single hit at a random position --- */
int num_trials = 5;
for (int t = 0; t < num_trials; t++) {
int bit_pos = rand() % (TOTAL_BITS); /* random bit in [0, 128) */
char test_name[64];
snprintf(test_name, sizeof(test_name), "firstbit_single_hit_bit%d", bit_pos);
memset(arr, 0, sizeof(arr));
set_bit_in_array(arr, bit_pos);
run_test(test_name, get_first_bit_index_mask(arr, ARRSIZE, 0, TOTAL_BITS), bit_pos);
}
/* --- random multi-hit with random window --- */
int multi_trials = 5;
for (int t = 0; t < multi_trials; t++) {
memset(arr, 0, sizeof(arr));
int from_bit = rand() % (TOTAL_BITS);
int num_bits = 1 + rand() % (TOTAL_BITS - from_bit); /* at least 1 bit wide */
int window_end = from_bit + num_bits;
int min_pos = -1;
int num_bits_set = 5 + rand() % 20;
for (int b = 0; b < num_bits_set; b++) {
int bit_pos = from_bit + rand() % num_bits; /* confined to the window */
set_bit_in_array(arr, bit_pos);
if (min_pos == -1 || bit_pos < min_pos)
min_pos = bit_pos;
}
/* put some bits outside the window to test they are ignored in some trial */
if (from_bit > 0 && t % 2) {
int bit_pos = rand() % from_bit; /* before the window */
set_bit_in_array(arr, bit_pos);
}
if (window_end < TOTAL_BITS && t % 2) {
int bit_pos = window_end + rand() % (TOTAL_BITS - window_end); /* after */
set_bit_in_array(arr, bit_pos);
}
char test_name[64];
snprintf(test_name, sizeof(test_name), "firstbit_multi_hit_trial%d_from%d_num%d_min%d", t, from_bit, num_bits, min_pos);
run_test(test_name, get_first_bit_index_mask(arr, ARRSIZE, from_bit, num_bits), min_pos);
}
}
static void test_get_last_bit_index(void)
{
/* --- no bits set --- */
uint32_t zeros[ARRSIZE] = {0};
run_test("lastbit_all_zeros", get_last_bit_index(zeros, ARRSIZE), -1);
uint32_t arr[ARRSIZE];
/* --- random single hit at a random position --- */
int num_trials = 5;
for (int t = 0; t < num_trials; t++) {
int bit_pos = rand() % (TOTAL_BITS);
char test_name[64];
snprintf(test_name, sizeof(test_name), "lastbit_single_hit_bit%d", bit_pos);
memset(arr, 0, sizeof(arr));
set_bit_in_array(arr, bit_pos);
run_test(test_name, get_last_bit_index(arr, ARRSIZE), bit_pos);
}
/* --- random multi-hit: scattered bits, must return the maximum --- */
int multi_trials = 5;
for (int t = 0; t < multi_trials; t++) {
memset(arr, 0, sizeof(arr));
int max_pos = -1;
int num_bits_set = 5 + rand() % 20;
for (int b = 0; b < num_bits_set; b++) {
int bit_pos = rand() % (TOTAL_BITS);
set_bit_in_array(arr, bit_pos);
if (bit_pos > max_pos)
max_pos = bit_pos;
}
char test_name[64];
snprintf(test_name, sizeof(test_name), "lastbit_multi_hit_trial%d_max%d", t, max_pos);
run_test(test_name, get_last_bit_index(arr, ARRSIZE), max_pos);
}
}
static void fisher_yates_shuffle(int *positions, int total, int n)
{
/* partial shuffle: pick n distinct random positions from [0, total) */
/* see https://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle#The_modern_algorithm */
for (int i = 0; i < n; i++) {
int j = i + rand() % (total - i);
int tmp = positions[i];
positions[i] = positions[j];
positions[j] = tmp;
}
}
static void test_count_bits(void)
{
uint32_t arr[ARRSIZE];
/* --- no bits set --- */
uint32_t zeros[ARRSIZE] = {0};
run_test("countbits_all_zeros", count_bits(zeros, ARRSIZE), 0);
/* --- all bits set --- */
uint32_t all_set[ARRSIZE];
memset(all_set, 0xFF, sizeof(all_set));
run_test("countbits_all_set", count_bits(all_set, ARRSIZE), TOTAL_BITS);
/* --- random trials: set exactly N random bits, expect count N --- */
int positions[TOTAL_BITS];
for (int i = 0; i < TOTAL_BITS; i++)
positions[i] = i;
int num_trials = 5;
for (int t = 0; t < num_trials; t++) {
memset(arr, 0, sizeof(arr));
int n = 1 + rand() % TOTAL_BITS;
fisher_yates_shuffle(positions, TOTAL_BITS, n);
for (int i = 0; i < n; i++)
set_bit_in_array(arr, positions[i]);
char test_name[64];
snprintf(test_name, sizeof(test_name), "countbits_trial%d_n%d", t, n);
run_test(test_name, count_bits(arr, ARRSIZE), n);
}
}
void test_count_bits64_with_mask(void)
{
/* --- zero value --- */
run_test("countbits64_zero_value", count_bits64_with_mask(0ULL, 0, 64), 0);
/* --- all bits set, full window --- */
run_test("countbits64_all_set_full_window", count_bits64_with_mask(0xFFFFFFFFFFFFFFFFULL, 0, 64), 64);
/* --- all bits set, window filters correctly --- */
run_test("countbits64_all_set_window_16_from_8", count_bits64_with_mask(0xFFFFFFFFFFFFFFFFULL, 8, 16), 16);
/* --- random trials: set exactly N bits within window, expect count N --- */
int num_trials = 5;
for (int t = 0; t < num_trials; t++) {
int start = rand() % 64;
int num = 1 + rand() % (64 - start);
int window_end = start + num;
/* initialise positions to the window [start, start+num) */
int positions[64];
for (int i = 0; i < num; i++)
positions[i] = start + i;
int n = 1 + rand() % num;
fisher_yates_shuffle(positions, num, n);
uint64_t v = 0;
for (int i = 0; i < n; i++)
v |= 1ULL << positions[i];
/* put bits outside the window on odd trials */
if (t % 2) {
if (start > 0)
v |= 1ULL << (rand() % start);
if (window_end < 64)
v |= 1ULL << (window_end + rand() % (64 - window_end));
}
char test_name[64];
snprintf(test_name, sizeof(test_name), "countbits64_trial%d_start%d_num%d_n%d", t, start, num, n);
run_test(test_name, count_bits64_with_mask(v, start, num), n);
}
}
int main(void)
{
srand((unsigned)time(NULL));
test_get_first_bit_index_mask();
test_get_last_bit_index();
test_count_bits();
test_count_bits64_with_mask();
if (g_failures > 0) {
fprintf(stderr, "\n%d test(s) FAILED.\n", g_failures);
return 1;
}
printf("\nAll tests passed.\n");
return 0;
}

View File

@@ -184,18 +184,6 @@ static bool set_if_flags(int sock_fd, const char *ifn, short flags)
return true;
}
short tuntap_set_up(const char *ifname, int sock_fd)
{
short flags = 0;
if (!get_if_flags(sock_fd, ifname, &flags))
return -1;
flags |= IFF_UP;
if (!set_if_flags(sock_fd, ifname, flags))
return -1;
return flags;
}
bool tun_config(const char* ifname, const char *ipv4, const char *ipv6)
{
@@ -227,10 +215,10 @@ bool tun_config(const char* ifname, const char *ipv4, const char *ipv6)
close(sock_fd);
}
if (success) {
const short flags = tuntap_set_up(ifname, sock_fd);
success = flags >= 0 && set_if_flags(sock_fd, ifname, (flags | IFF_NOARP | IFF_POINTOPOINT) & ~IFF_MULTICAST);
}
// if successfully set IP addresses: set iterface up, disable ARP, no
// multicast, point-to-point
if (success)
success = set_if_flags(sock_fd, ifname, (IFF_UP | IFF_NOARP | IFF_POINTOPOINT) & ~IFF_MULTICAST);
if (success)
LOG_A(OIP, "TUN Interface %s successfully configured, IPv4 %s, IPv6 %s\n", ifname, ipv4, ipv6);
@@ -245,11 +233,11 @@ bool tap_config(const char* ifname)
{
int sock_fd = socket(AF_INET, SOCK_DGRAM, 0);
if (sock_fd < 0) {
LOG_E(UTIL, "tap_config: failed creating socket %d, %s\n", errno, strerror(errno));
LOG_E(UTIL, "Failed creating socket for interface management: %d, %s\n", errno, strerror(errno));
return false;
}
const bool success = tuntap_set_up(ifname, sock_fd) >= 0;
bool success = set_if_flags(sock_fd, ifname, IFF_UP);
if (success)
LOG_A(OIP, "TAP interface %s successfully configured\n", ifname);
@@ -294,7 +282,6 @@ int tuntap_generate_ue_ifname(char *ifname, int flag, int instance_id, int pdu_s
char pdu_session_string[10];
snprintf(pdu_session_string, sizeof(pdu_session_string), "p%d", pdu_session_id);
const char *basename = flag == IFF_TUN ? "oaitun_ue" : "oaitap_ue";
// ifname: oaitun_ue<ue_id+1>[p<pdu_session_id>] when not default
return snprintf(ifname, IFNAMSIZ, "%s%d%s", basename, instance_id + 1, pdu_session_id == -1 ? "" : pdu_session_string);
}
@@ -308,7 +295,7 @@ void tuntap_destroy(const char *dev)
}
// interface not up => down
short flags = 0;
short flags;
bool success = get_if_flags(fd, dev, &flags);
success = success && set_if_flags(fd, dev, flags & ~IFF_UP);
if (success) {

View File

@@ -90,12 +90,4 @@ int tuntap_alloc(int flag, const char *dev);
*/
void tuntap_destroy(const char *dev);
/*!
* \brief Bring a TUN/TAP interface administratively up (IOCTL SIOCSIFFLAGS, set IFF_UP).
* \param[in] ifname name of the interface
* \param[in] sock_fd IPv4 SOCK_DGRAM fd for ioctl (opened by the caller)
* \return interface flags with IFF_UP set, or -1 on failure
*/
short tuntap_set_up(const char *ifname, int sock_fd);
#endif /*TUN_IF_H_*/

View File

@@ -17,26 +17,17 @@ Key 5GS NAS messages:
## OAI Implementation Status
The following table lists NAS messages with dedicated `encode_*` / `decode_*` codecs under
`openair3/NAS/NR_UE/5GS/` (`5GMM/MSG`, `5GSM/MSG`). Unit test entries refer to
[`nas_lib_test.c`](../openair3/NAS/NR_UE/5GS/tests/nas_lib_test.c), most are encode/decode round-trips.
The following tables lists implemented NAS messages and whether there is an encoder or decoder function, and if a corresponding unit test exists.
| Type | Message | Encoding | Decoding | Unit test |
|-------|-------------------------------------------|----------|----------|------------|
| 5GMM | Service Request | yes | yes | yes |
| 5GMM | Service Accept | yes | yes | yes |
| 5GMM | Service Reject | yes | yes | yes |
| 5GMM | Authentication Failure | yes | yes | yes |
| 5GMM | Authentication Reject | yes | yes | yes |
| 5GMM | Security Mode Reject | yes | yes | yes |
| 5GMM | Identity Request | no | yes | no |
| 5GMM | Authentication Response | yes | no | no |
| 5GMM | Identity Response | yes | no | no |
| 5GMM | Security Mode Complete | yes | no | no |
| 5GMM | Uplink NAS Transport | yes | no | no |
| 5GMM | Authentication Failure | yes | yes | yes |
| 5GMM | Authentication Reject | yes | yes | yes |
| 5GMM | Security Mode Reject | yes | yes | yes |
| 5GMM | Registration Request | yes | yes | no |
| 5GMM | Registration Accept | yes | yes | yes |
| 5GMM | Registration Complete | yes | yes | no |
@@ -44,23 +35,6 @@ The following table lists NAS messages with dedicated `encode_*` / `decode_*` co
| 5GSM | PDU Session Establishment Request | yes | no | no |
| 5GSM | PDU Session Establishment Accept | no | yes | no |
### Runtime-handled messages
These network-originated messages are handled in [`nr_nas_msg.c`](../openair3/NAS/NR_UE/nr_nas_msg.c):
* Authentication Request
* Security Mode Command
* Downlink NAS Transport
* Deregistration Accept (UE originating)
* Registration Reject
* PDU Session Establishment Reject
## Integration testing
End-to-end attach can be tested with the [NR UE NAS simulator](../tests/nr-ue-nas-simulator/README.md),
which runs the OAI UE NAS stack and gNB NGAP against the AMF, forwarding NAS PDUs without PHY/MAC/RLC.
Use `nas_lib_test` for isolated codec round-trips.
### Code Structure
[openair3/NAS/NR_UE/nr_nas_msg.c](../openair3/NAS/NR_UE/nr_nas_msg.c):
@@ -71,14 +45,10 @@ Use `nas_lib_test` for isolated codec round-trips.
[openair3/NAS/NR_UE/5GS/fgs_nas_lib.c](../openair3/NAS/NR_UE/5GS/fgs_nas_lib.c):
* top-level encode/decode dispatch for NAS 5GMM/5GSM payloads
* delegates message-specific encoding/decoding to `5GMM/MSG` and `5GSM/MSG`
[openair3/NAS/NR_UE/5GS/NR_NAS_defs.h](../openair3/NAS/NR_UE/5GS/NR_NAS_defs.h):
* 5GS NAS message types and security header definitions
* shared NAS structures used by UE NAS handlers and encoders
* prototypes for 5GMM header and security-header encode/decode, implementations in `fgs_nas_lib.c`
* encoding and decoding functions for 5G NAS message headers and payloads
* relies on 5GMM/5GSM messages libs for payload encoding
[openair3/NAS/NR_UE/5GS/fgs_nas_utils.h](../openair3/NAS/NR_UE/5GS/fgs_nas_utils.h):

View File

@@ -254,7 +254,7 @@ Both `GNB_IPV4_ADDRESS_FOR_NG_AMF` and `GNB_IPV4_ADDRESS_FOR_NGU` need to be
set to the IP address of the NIC referenced previously.
**Note**: If the Core Network is running on the same server, 3 cores should be
allocated to it. 2 for the UPF and 1 core shared between the remaining services as shown
allocated to it. 2 for the UPF and 1 for the all the remaining services as shown
below.
```patch

View File

@@ -51,7 +51,7 @@ PROJECT_BRIEF = "Full experimental OpenSource LTE and NR implementation
# pixels and the maximum width should not exceed 200 pixels. Doxygen will copy
# the logo to the output directory.
PROJECT_LOGO =
PROJECT_LOGO = @CMAKE_SOURCE_DIR@/doc/images/oai_logo.png
# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) path
# into which the generated documentation will be written. If a relative path is

View File

@@ -145,11 +145,6 @@ These modes of operation are supported:
- evalution of RSRP report
- evaluation of CQI report
- MAC scheduling of SR reception
- Paging (PCCH/P-RNTI)
- CN paging records queued
- PF/PO dequeue from SIB1 PCCH-Config
- PCCH encoded at the UE's PO
- Type2 common search-space based P-RNTI PDCCH + PDSCH scheduling for PCCH
- Intra-frequency handover
- Inter-frequency handover
- Measurement gaps are automatically computed at the DU if the CU has neighbor information and the configured
@@ -235,12 +230,9 @@ These modes of operation are supported:
- NGAP Initial UE message
- NGAP Initial context setup request/response
- NGAP Downlink/Uplink NAS transfer
- NGAP Paging
- NGAP UE context release request/command/complete
- NGAP UE context release request/complete
- NGAP UE radio capability info indication
- NGAP PDU session resource setup request/response
- NGAP PDU session resource modify request/response
- NGAP PDU session resource release command/response
- NGAP Mobility Management Procedures:
* NGAP Handover Required
* NGAP Handover Request
@@ -267,7 +259,6 @@ These modes of operation are supported:
* F1 UE Context modification request/response
* F1 UE Context modification required
* F1 UE Context release req/cmd/complete
- F1 Paging
- F1 gNB CU configuration update
- F1 gNB DU configuration update
- F1 Reset (handled at DU only, full reset only)
@@ -288,14 +279,10 @@ These modes of operation are supported:
- E1 Bearer Context Setup (gNB-CU-CP initiated)
- E1 Bearer Context Setup Request
- E1 Bearer Context Setup Response
- E1 Bearer Context Setup Failure
- Bearer Context Modification (gNB-CU-CP initiated)
- E1 Bearer Context Modification Request
- E1 Bearer Context Modification Response
- E1 Bearer Context Modification Failure
- Bearer Context Release (gNB-CU-CP initiated)
- E1 Bearer Context Release Command
- E1 Bearer Context Release Complete
- E1 Reset
- Interface with RRC and PDCP/SDAP
- One CU-CP can handle multiple CU-UPs
@@ -369,9 +356,9 @@ These modes of operation are supported:
* NR-PRACH
- Formats 0,1,2,3, A1-A3, B1-B3
* NTN
- TA adjustment based on ntn-Config-r17 information
- Different TA adjustment algorithms between SIB19 receptions:
- Autonomous TA adjustment based on DL time tracking
- TA adjustemt based on ntn-Config-r17 information
- Different TA adjustemt algorithms between SIB19 receptions:
- Autonomous TA adjustemt based on DL time tracking
- Standard compliant epoch time based TA adjustment including orbital propagation
- DL Doppler compensation based on ntn-Config-r17 information
- UL Doppler pre-compensation based on ntn-Config-r17 information and residual DL FO estimation
@@ -408,12 +395,9 @@ These modes of operation are supported:
- Fallback not supported
* DCI processing
- format 10 (RA-RNTI, C-RNTI, SI-RNTI, TC-RNTI)
- format 10 with P-RNTI
- format 00 (C-RNTI, TC-RNTI)
- format 11 (C-RNTI)
- format 01 (C-RNTI)
* Paging monitoring and reception
- PF/PO-based paging PDCCH monitoring in IDLE/non-connected states
* UCI processing
- ACK/NACK processing
- Scheduling request procedures
@@ -478,7 +462,6 @@ These modes of operation are supported:
- Support for master cell group configuration
- Reception of UECapabilityEnquiry, encoding and transmission of UECapability
- Support for measurement report of Event A2/A3
- Paging: PCCH reception
* NTN according to 38.331 Rel.17
- Reception of ntn-Config-r17 from SIB19 or reconfigurationWithSync
- Handling of ntn-UlSyncValidityDuration-r17 in SIB19
@@ -489,8 +472,7 @@ These modes of operation are supported:
* Transfer of NAS messages between the AMF and the UE supporting the UE registration with the core network and the PDU session establishment according to 24.501 Rel.16
* 5GMM (5G Mobility Management) messages:
- Service Request/Accept/Reject (Network-triggered Service Request TS 23.502 §4.2.3.3,
UE-Triggered Service Request after paging, TS 23.502 §4.2.3.2)
- Service Request/Accept/Reject (enc/dec library only)
- Identity Request/Response
- Authentication Request/Response
- Security Mode Command/Complete

View File

@@ -13,7 +13,7 @@ sudo apt-get update
sudo apt-get install git
```
## Clone the Git repository (for OAI Users without login to github server)
## Clone the Git repository (for OAI Users without login to gitlab server)
The [openairinterface5g repository](https://github.com/duranta-project/openairinterface5g.git)
holds the source code for the RAN (4G and 5G).

View File

@@ -44,8 +44,7 @@ PTP enabled switches and Grandmaster clock we have tested with:
|Fibrolan Falcon-RX/812/G|
|Qulsar Qg2 (Grandmaster)|
**S-Plane synchronization is mandatory.** S-plane support is done via
`ptp4l` and `phc2sys`. Make sure your version matches.
**S-Plane synchronization is mandatory.** S-plane support is done via `ptp4l` and `phc2sys`. Make sure your version matches.
| Software | Software Version|
|-----------|-----------------|
@@ -229,19 +228,19 @@ Once installed you can use this configuration file for ptp4l (`/etc/ptp4l.conf`)
```
[global]
domainNumber 24
clientOnly 1
slaveOnly 1
time_stamping hardware
tx_timestamp_timeout 50
tx_timestamp_timeout 1
logging_level 6
summary_interval 0
#priority1 127
[PTP_ENABLED_NIC_INTERFACE]
[your_PTP_ENABLED_NIC]
network_transport L2
hybrid_e2e 0
```
You need to increase `tx_timestamp_timeout` to 100 if needed. You will see that in the logs of ptp.
You need to increase `tx_timestamp_timeout` to 50 or 100 for Intel E-810. You will see that in the logs of ptp.
Create the configuration file for ptp4l (`/etc/sysconfig/ptp4l`)
@@ -252,7 +251,7 @@ OPTIONS="-f /etc/ptp4l.conf"
Create the configuration file for phc2sys (`/etc/sysconfig/phc2sys`)
```
OPTIONS="-s PTP_ENABLED_NIC_INTERFACE -w -n 24 -r -r -m -R 8"
OPTIONS="-a -r -r -n 24"
```
The service of ptp4l (`/usr/lib/systemd/system/ptp4l.service`) should be configured as below:
@@ -296,7 +295,8 @@ Beware that PTP issues may show up only when running OAI and XRAN. If you are us
1. Make sure that you have `skew_tick=1` in `/proc/cmdline`
2. For Intel E-810 cards set `tx_timestamp_timeout` to 50 or 100 if there are errors in ptp4l logs
3. Other time sources than PTP, such as NTP or chrony timesources, should be disabled. Make sure they are enabled as further below.
4. If `rms` or `delay` in `ptp4l` or `offset` in `phc2sys` logs remain high then you can try pinning the `ptp4l` and `phc2sys` processes to an isolated CPU.
4. Make sure you set `kthread_cpus=<cpu_list>` in `/proc/cmdline`.
5. If `rms` or `delay` in `ptp4l` or `offset` in `phc2sys` logs remain high then you can try pinning the `ptp4l` and `phc2sys` processes to an isolated CPU.
```bash
#to check there is NTP enabled or not
@@ -421,14 +421,14 @@ git apply ~/openairinterface5g/cmake_targets/tools/oran_fhi_integration_patches/
```bash
git clone https://github.com/openairinterface/o-du-phy.git ~/phy
cd ~/phy
git checkout 11.1.3 # the tag points to the `main` branch which has all patches applied that are relevant for OAI integration; the tag matches the value of cmake variable `K_VERSION`
git checkout <desired-tag> # shall match a variable `K_VERSION`
```
or use `xran_DOWNLOAD` option when compiling OAI gNB.
Compile the fronthaul interface library by calling `make` and the option
`XRAN_LIB_SO=1` to have it build a shared object. Note that we provide two
environment variables `RTE_SDK` for the path to the source tree of DPDK, and
`XRAN_DIR` to set the path to the fronthaul library.
`XRAN_DIR` to set the path to the fronthaul library. For building for a Arm
target, set as well the environment variable `TARGET=armv8`.
**Note**: you need at least gcc-11 and g++-11.
@@ -1258,7 +1258,7 @@ The OAI configuration file [`gnb.sa.band78.106prb.fhi72.1x1-proto-ru.conf`](../t
First, compile the RU as outlined in the [building ProtO-RU tutorial](https://github.com/NUS-CIR/ProtO-RU/tree/proto-ru?tab=readme-ov-file#building-proto-ru).
Then, ensure that both your DU and ProtO-RU host are PTP synchronized.
Next, use the RU config, [protoru-OAI-B210-TDD-n78-40MHz-1x1-30kHz.yml](https://github.com/NUS-CIR/ProtO-RU/blob/proto-ru/proto-ru/conf-files/protoru-OAI-B210-TDD-n78-40MHz-1x1-30kHz.yml), which corresponds to the above mentioned DU config file. Please note that the RU delay profile might need to be adjusted according to the setup. The E2E test with xran K release required `T2a_max_cp_ul: 2985`.
Next, use the RU config, [protoru-OAI-B210-TDD-n78-40MHz-1x1-30kHz.yml](https://github.com/NUS-CIR/ProtO-RU/blob/proto-ru/proto-ru/conf-files/protoru-OAI-B210-TDD-n78-40MHz-1x1-30kHz.yml), which corresponds to the above mentioned DU config file.
In addition, please adapt the DU MAC address and VLAN tag to your needs.
ProtO-RU was successfully tested with USRP B210.
@@ -1556,10 +1556,7 @@ Edit the sample OAI gNB configuration file and check following parameters:
* `RUs` section
* Set an isolated core for RU thread `ru_thread_core`, in our environment we are using CPU 6
* If testing with a numerology different than 1 (e.g., FDD with numerology 0),
set `nr_scs_for_raster` to the used numerology, and adapt `sl_ahead`: it must be
strictly less than the number of slots in a frame (e.g., 5 for numerology 0).
* `fhi_72` (FrontHaul Interface) section: this config follows the structure
that is employed by the xRAN library (`xran_fh_init` and `xran_fh_config`
structs in the code):

View File

@@ -133,9 +133,6 @@ The other SDRs (AW2S, LimeSDR, ...) have no READMEs.
## Developer tools
- [code-style-contrib.md](./code-style-contrib.md): overall working practices, code style, and review process
- [git-guide.md](./git-guide.md): Git how-tos — commit signing setup, branch
management, submodules, recovering from mistakes, reusing conflict
resolutions (rerere)
- [cross-compile.md](./cross-compile.md): how to cross-compile OAI for ARM
- [clang-format.md](./clang-format.md): how to format the code. See also the
next entry for an error detection tool.

View File

@@ -626,116 +626,6 @@ sequenceDiagram
Note over ue,tdu: UE active on target DU
```
### Paging and Network Triggered Service Request (CM-IDLE to CM-CONNECTED)
The following flow documents the current OAI stack path for paging-triggered
service resumption in SA: context release to idle, NGAP/F1AP/PCCH paging, then
RRC setup with NAS Service Request.
End-to-end flow is split across these 3GPP procedures:
| Spec clause | Procedure name | Actor | Spec role in this flow |
|-------------|----------------|-------|------------------------|
| TS 23.502 §4.2.3.3 | Network Triggered Service Request | AMF / 5GC | Network must deliver MT signalling/data to a CM-IDLE UE. "The Paging Request triggers the UE Triggered Service Request procedure in the UE". Step 4b = Paging Request to RAN, step 6 = UE initiates §4.2.3.2 |
| TS 23.502 §4.2.3.2 | UE Triggered Service Request | UE | CM-IDLE UE may initiate SR "as a response to a network paging request" |
| TS 24.501 §5.6.2.2 | Paging for 5GS services | UE NAS | On paging indication from lower layers: initiate service request (§5.6.1.2) when in `5GMM-REGISTERED` and `5GMM-IDLE` |
| TS 24.501 §8.2.16 | SERVICE REQUEST | UE NAS | NAS PDU sent by UE |
#### OAI implementation
In summary:
- UE transitions to `RRC_IDLE` and AMF transitions UE to `CM-IDLE`
- CN-triggered paging is performed with `5G-S-TMSI` (NGAP Paging -> RRC Paging)
- One radio page per cell per NGAP PAGING at UE PO
- UE resumes via NAS Service Request and `RRCSetup`/`RRCSetupComplete`
- AMF continues §4.2.3.3 after Initial UE Message / context setup
```mermaid
sequenceDiagram
participant dn as DN
participant cn as 5GC
participant cu as gNB-CU
participant du as gNB-DU
participant ue as UE
cu->>cn: NGAP UE Context Release Request
Note over cu: example trigger: telnet `rrc ctx_rel_req`
cu->>cu: rrc_gNB_trigger_ue_context_release_req()
cu->>cu: rrc_gNB_send_NGAP_UE_CONTEXT_RELEASE_REQ()
cn->>cu: NGAP UE Context Release Command
cu->>cu: rrc_gNB_process_NGAP_UE_CONTEXT_RELEASE_COMMAND()
cu->>cu: rrc_gNB_generate_RRCRelease()
cu->>du: F1AP DL RRC Message Transfer (RRCRelease)
du->>ue: RRCRelease
ue->>ue: handle_RRCRelease()
ue->>ue: nr_rrc_going_to_IDLE()
Note over ue: RRC_IDLE, NAS remains REGISTERED and can request service
du->>cu: F1AP UE Context Release Complete
cu->>cn: NGAP UE Context Release Complete
Note over dn,ue: Trigger Paging
dn->>cn: DL user data
Note over cn: AMF paging decision [23.502 §4.2.3.3]
cn->>cu: NGAP Paging (5G-S-TMSI)
cu->>cu: ngap_gNB_handle_paging()
cu->>cu: decode_ng_paging()
cu->>cu: rrc_gNB_process_PAGING_IND()
cu->>cu: rrc_send_paging_to_dus()
cu->>du: F1AP Paging
du->>du: DU_handle_Paging()
du->>du: f1_paging()
du->>du: nr_mac_pcch_enqueue()
Note over du: at UE PO: schedule_nr_pcch()
du->>du: do_NR_Paging()
du->>ue: RRC Paging (ng-5G-S-TMSI, P-RNTI)
ue->>ue: nr_rrc_ue_decode_pcch()
ue->>ue: NAS_PAGING_IND
ue->>ue: generateServiceRequest()
ue->>ue: send_nas_initial_ul_transfer_req()
Note over ue: paging match triggers Service Request [24.501 §5.6.2]
Note over ue,cn: Resume service
ue->>ue: NAS_INITIAL_UL_TRANSFER_REQ
ue->>ue: nr_rrc_ue_prepare_RRCSetupRequest()
ue->>ue: nr_rrc_trigger_mac_ra(NR_MAC_RA_START_SETUP)
ue->>du: RRCSetupRequest
du->>cu: F1AP Initial UL RRC Message Transfer (RRCSetupRequest)
cu->>cu: rrc_handle_RRCSetupRequest()
cu->>du: F1AP DL RRC Message Transfer (RRCSetup)
du->>ue: RRCSetup
ue->>ue: do_RRCSetupComplete()
Note over ue: uses dedicatedNAS when no SRB exists
ue->>du: RRCSetupComplete + NAS SERVICE REQUEST
du->>cu: F1AP UL RRC Message Transfer (RRCSetupComplete + NAS)
cu->>cu: rrc_handle_RRCSetupComplete()
cu->>cn: NGAP Initial UE Message
cn->>cu: NGAP Initial Context Setup Request
cu->>cu: rrc_gNB_process_NGAP_INITIAL_CONTEXT_SETUP_REQ()
cu->>du: F1AP DL RRC Message Transfer (RRCReconfiguration)
du->>ue: RRCReconfiguration
ue->>du: RRCReconfigurationComplete
du->>cu: F1AP UL RRC Message Transfer (RRCReconfigurationComplete)
cu->>cu: handle_rrcReconfigurationComplete()
cu->>cn: NGAP Initial Context Setup Response
cn->>ue: DL user data
```
In OAI RFsim lab runs, a practical trigger sequence is:
1. `rrc ctx_rel_req <ue-id>` at gNB telnet
2. wait until AMF reports UE in IDLE state
3. send host-side traffic to UE IP
Relevant specs:
- TS 23.502: §4.2.3.3 Network Triggered Service Request (AMF pages, step 4b),
UE Triggered Service Request §4.2.3.2 (by network paging)
- TS 24.501 §5.6.2.2: Paging for 5G services
- TS 24.501 §5.6.1: Service Request procedure
- TS 38.331 §5.3.2: Paging
- TS 38.331 §5.3.11: UE actions upon going to RRC_IDLE
- TS 38.413 §8.5: Paging procedures (NGAP Paging)
- TS 38.473 §8.7: Paging procedures (F1AP CU-to-DU paging)
## Structures
### DUs and Cells

View File

@@ -90,9 +90,9 @@ the [MAC configuration](../MAC/mac-usage.md) as well for SIB configuration.
`0xffffff` is a reserved value and means "no SD"
Note that: SST=1, no SD is "eMBB"; SST=2, no SD is "URLLC"; SST=3, no SD
is "mMTC"
- `enable_sdap` (default: true): set `sdap-HeaderUL` and `sdap-HeaderDL` to
present in the RRC `SDAP-Config` IE for SA PDU sessions. If false, both
headers are absent (per DRB). SDAP entities are still created, SDAP layer always enabled.
- `enable_sdap` (default: true): enable the use of the SDAP layer. If
deactivated, a transparent SDAP header is prepended to packets, but no
further processing is being done.
- `cu_sibs` (default: `[]`) list of SIBs to give to the DU for transmission.
Currently supported:
- SIB2: serving-cell reselection parameters (configured in `sib2_config`)

View File

@@ -118,12 +118,43 @@ e.g., `v3.0`. We target to make releases bi-yearly.
### How to manage your own branch
Branch off the latest `develop` branch before starting to work, keep your
branch synchronized with `origin/develop` through regular rebases, and push
with `--force-with-lease` after rebasing. The step-by-step commands — including
how to rebase over multiple develop tags in intermediate steps and how to avoid
resolving the same conflicts repeatedly with `git rerere` — are in the
[branch management section of the Git guide](./git-guide.md#managing-your-own-branch).
Before starting to work, please make sure to branch off the latest `develop`
branch. Make commits as appropriate.
```bash
$ git fetch origin
$ git checkout develop
$ git checkout -b my-new-feature # name as appropriate
$ git add -p # add changes for change set 1, use `-p` to review what to include
$ git commit # in the editor, describe your changes
$ git add -p # add changes for change set 2
$ git commit # in the editor, describe your changes
```
Again, commit message should take multiple lines; after the initial title, a
blank line should follow. Read the `DISCUSSION` section in `man git commit` for
more information.
If your development takes longer, make sure to synchronize regularly with
`origin/develop` using `git rebase`:
```bash
$ git fetch origin
$ git rebase -i origin/develop
```
If you do logical changes, you should not have to resolve the same conflicts
over and over again. Note that if you jumped over multiple develop tags, you
can also rebase in intermediate steps, in case you fear the differences might
be too big.
```
$ git rebase -i 2023.w38
$ git rebase -i 2023.w41
$ git rebase -i develop
```
Once you rebased, push the changes to the remote
```
$ git push origin my-new-feature --force-with-lease # force with lease let's you only overwrite what you also have locally in origin/my-new-feature
```
### Use of git commit trailers

View File

@@ -61,7 +61,7 @@ Correct (Markdown):
**Bold text**
*Italic text*
![some image](./path/to/image.png)
![OAI Logo](./images/oai_final_logo.png) instead of <img src="./images/oai_final_logo.png" alt="OAI Logo">
| Feature | Description |
|---------------|---------------------------------|
@@ -75,7 +75,7 @@ Incorrect (HTML):
<b>Bold text</b>
<i>Italic text</i>
<img src="./path/to/image.png" alt="some image">
<img src="./images/oai_final_logo.png" alt="OAI Logo">
<table style="border-collapse: collapse; border: none;">
<tr>
@@ -114,7 +114,7 @@ Incorrect (HTML):
is](https://www.kernel.org/doc/html/latest/process/submitting-patches.html#separate-your-changes).
- See [OAI CN5G configuration files](#22-oai-cn5g-configuration-files) for
details.
- ![some image](./path/to/image.png)
- ![OAI Logo](./images/oai_final_logo.png)
```
## Inline code for technical elements

718
doc/dragonwing-build.md Normal file
View File

@@ -0,0 +1,718 @@
<!-- SPDX-License-Identifier: CC-BY-4.0 -->
# Building OAI for Qualcomm DragonWing IQ-9/X (ARM)
This document covers cross-compiling OAI for the ARM cores of the Qualcomm
DragonWing IQ-9/X platform (SA9000P SoC, Kryo 780 / Cortex-A78).
The Hexagon DSP component is not covered here.
The toolchain file is `cmake_targets/cross-arm-dragonwing.cmake`, which uses the
GCC cross-compiler bundled with the Hexagon SDK rather than the Ubuntu package.
The build procedure otherwise follows the same two-step pattern as the generic
ARM64 cross-compile described in `cross-compile.md`.
> **Note on the compiler:** The Hexagon SDK includes two compilers.
> `hexagon-clang` (LLVM 19) targets the **Hexagon DSP only** and cannot produce
> ARM binaries. The ARM cross-compiler is `aarch64-none-linux-gnu-gcc`
> (Arm GNU Toolchain 11.3.1) located under
> `tools/gcc_tools_64/bin/` in the SDK tree.
[[_TOC_]]
---
## 1 Prerequisites on the build host
### 1.1 Hexagon SDK
The SDK must be installed, defaulting to `/opt/Hexagon_SDK/6.4.0.2`.
Run the SDK's environment script once per shell session (or add to your profile):
```shell
source /opt/Hexagon_SDK/6.4.0.2/setup_sdk_env.source
```
If the SDK is installed elsewhere, pass `-DHEXAGON_SDK_ROOT=<path>` to every
`cmake` invocation in this guide.
### 1.2 OAI native build dependencies
The host machine needs OAI's standard build tools. If not installed yet:
```shell
cmake_targets/build_oai -I
```
### 1.3 ARM64 target libraries
OAI links against several libraries that must be available for the `aarch64`
architecture at link time. The standard way is Ubuntu multiarch.
> **Caveat:** The `dpkg --add-architecture` approach requires that your
> apt sources serve `aarch64` packages. Ubuntu 22.04 / 24.04 on a standard
> x86 host works out of the box via `ports.ubuntu.com`. If you are on a
> non-standard host, inside a corporate mirror, or inside a container without
> network access, you may need to adapt the sources list below — or provide
> the libraries from the DragonWing device filesystem instead (see
> [Section 4](#4-alternative-sysroot-from-device)).
#### 1.3.1 Enable the arm64 architecture in apt
**Ubuntu 24.04 (Noble)** — the sources are in `ubuntu.sources` format:
```shell
sudo dpkg --add-architecture arm64
# Restrict the existing ubuntu.sources to amd64 only
sudo sed -i '/^Components:/a Architectures: amd64' \
/etc/apt/sources.list.d/ubuntu.sources
# Add a separate arm64 source pointing to ports.ubuntu.com
sudo tee /etc/apt/sources.list.d/arm-cross-compile-sources.list <<'EOF'
deb [arch=arm64] http://ports.ubuntu.com/ noble main restricted
deb [arch=arm64] http://ports.ubuntu.com/ noble-updates main restricted
deb [arch=arm64] http://ports.ubuntu.com/ noble universe
deb [arch=arm64] http://ports.ubuntu.com/ noble-updates universe
deb [arch=arm64] http://ports.ubuntu.com/ noble multiverse
deb [arch=arm64] http://ports.ubuntu.com/ noble-updates multiverse
deb [arch=arm64] http://ports.ubuntu.com/ noble-backports main restricted universe multiverse
EOF
```
**Ubuntu 22.04 (Jammy)** — sources are still in the old `sources.list` format:
```shell
sudo dpkg --add-architecture arm64
sudo cp /etc/apt/sources.list "/etc/apt/sources.list.$(date).backup"
sudo sed -i -E "s/(deb)\ (http:.+)/\1\ [arch=amd64]\ \2/" \
/etc/apt/sources.list
sudo tee /etc/apt/sources.list.d/arm-cross-compile-sources.list <<'EOF'
deb [arch=arm64] http://ports.ubuntu.com/ jammy main restricted
deb [arch=arm64] http://ports.ubuntu.com/ jammy-updates main restricted
deb [arch=arm64] http://ports.ubuntu.com/ jammy universe
deb [arch=arm64] http://ports.ubuntu.com/ jammy-updates universe
deb [arch=arm64] http://ports.ubuntu.com/ jammy multiverse
deb [arch=arm64] http://ports.ubuntu.com/ jammy-updates multiverse
deb [arch=arm64] http://ports.ubuntu.com/ jammy-backports main restricted universe multiverse
EOF
```
#### 1.3.2 Install the arm64 packages
```shell
sudo apt-get update
sudo apt-get install --yes \
libc6-dev-i386 \
libreadline-dev:arm64 \
libgnutls28-dev:arm64 \
libconfig-dev:arm64 \
libsctp-dev:arm64 \
libssl-dev:arm64 \
libtool:arm64 \
zlib1g-dev:arm64 \
libyaml-cpp-dev:arm64
```
> **Note:** `libc6-dev-i386` is for the host (code-generation tools), not the
> target. All other packages with `:arm64` suffix are for the cross-linked
> ARM binaries.
> The list above is believed to be complete but may grow as new OAI features
> are enabled. If cmake reports a missing package, install `<package>:arm64`
> and re-run cmake.
---
## 2 Build
Both build directories live directly under the OAI repository root.
**Each directory must be created fresh**`CMAKE_TOOLCHAIN_FILE` is only
honoured on the very first cmake run in a directory. If a `CMakeCache.txt`
already exists, cmake ignores the toolchain file and silently produces a
native x86 build.
### 2.1 Step 1 — native host tools
These are x86 binaries that cmake runs during the cross-compile step to
generate LDPC processing code and the T-tracer event IDs.
```shell
cd <oai-root>
mkdir build # must not contain a prior CMakeCache.txt
cd build
cmake .. -DAVOID_SIGN=ON
make -j$(nproc) ldpc_generators generate_T
cd ..
```
> **Note:** `-DAVOID_SIGN=ON` tells the LDPC code generator to emit
> `simde_mm_xor_si128` instead of `simde_mm_sign_epi8` for sign extraction.
> SIMDe's translation of `mm_sign_epi8` is incorrect for the MSB-only pattern
> used in the CN processing kernel; `mm_xor_si128` is equivalent and
> translates correctly on aarch64. This flag has no effect on Step 2.
### 2.2 Step 2 — cross-compile for DragonWing
```shell
# Remove any previous attempt first — a stale CMakeCache.txt will cause
# CMAKE_TOOLCHAIN_FILE to be ignored with a warning but no error.
rm -rf dragonwing_build
mkdir dragonwing_build
cd dragonwing_build
cmake .. \
-DCMAKE_TOOLCHAIN_FILE=../cmake_targets/cross-arm-dragonwing.cmake \
-DNATIVE_DIR=../build
# Example targets — add or remove as needed
make -j$(nproc) nr-softmodem nr-cuup nr-uesoftmodem \
params_libconfig coding rfsimulator
```
If the Hexagon SDK is not at the default path, add:
```
-DHEXAGON_SDK_ROOT=/your/path/to/Hexagon_SDK/6.4.0.2
```
The `QUALCOMM_DRAGONWING=1` flag is set automatically by the toolchain file and
causes CMake to use `-mcpu=cortex-a78` instead of the generic `-march=armv8.2-a`.
### 2.3 Deploy via adb
```shell
# Verify the device is reachable
adb devices
# Create a destination directory on the device
adb shell mkdir -p /data/oai
# Push the binaries
adb push dragonwing_build/nr-softmodem /data/oai/
adb push dragonwing_build/nr-cuup /data/oai/
adb push dragonwing_build/nr-uesoftmodem /data/oai/
# Push shared libraries that OAI loads at runtime
adb push dragonwing_build/libparams_libconfig.so /data/oai/
adb push dragonwing_build/libcoding.so /data/oai/
adb push dragonwing_build/librfsimulator.so /data/oai/
# On the device, set LD_LIBRARY_PATH before running
adb shell "export LD_LIBRARY_PATH=/data/oai && /data/oai/nr-softmodem --help"
```
If the standard system libraries (libgnutls, libssl, libconfig, …) are not
present on the device, they must be pushed alongside the OAI binaries:
```shell
# Example: find and push the arm64 shared libs from the build host
for lib in libgnutls libssl libcrypto libconfig libsctp; do
find /usr/lib/aarch64-linux-gnu -name "${lib}.so*" -exec \
adb push {} /data/oai/ \;
done
```
---
## 3 Building for O-RAN FHI 7.2 (DPDK + libxran + armral)
This section covers cross-compiling the three additional libraries needed for
the O-RAN 7.2 fronthaul interface (`-DOAI_FHI72=ON`). They must be built in
order: DPDK → libxran → armral → OAI.
All libraries are installed into a common staging directory (`~/dw-sysroot`)
so they stay isolated from the host system and can be pushed to the device
together with the OAI binaries.
```shell
export DW_TC=/opt/Hexagon_SDK/6.4.0.2/tools/gcc_tools_64/bin/aarch64-none-linux-gnu
export DW_SYSROOT=$HOME/dw-sysroot
mkdir -p $DW_SYSROOT
```
### 3.1 DPDK
OAI supports two xran release tracks:
| xran release | xran version | DPDK version |
|---|---|---|
| F | 6.1.9 | 20.11.9 |
| K | 11.1.1 | 24.11.4 (minimum 22) |
The K release is recommended for new deployments. The commands below show
the K release; swap version strings for F where noted.
#### 3.1.1 Build tools
```shell
sudo apt-get install -y meson ninja-build python3-pyelftools
```
#### 3.1.2 Create a meson cross-file
```shell
cat > ~/aarch64-dragonwing.ini <<'EOF'
[binaries]
c = '/opt/Hexagon_SDK/6.4.0.2/tools/gcc_tools_64/bin/aarch64-none-linux-gnu-gcc'
cpp = '/opt/Hexagon_SDK/6.4.0.2/tools/gcc_tools_64/bin/aarch64-none-linux-gnu-g++'
ar = '/opt/Hexagon_SDK/6.4.0.2/tools/gcc_tools_64/bin/aarch64-none-linux-gnu-ar'
strip = '/opt/Hexagon_SDK/6.4.0.2/tools/gcc_tools_64/bin/aarch64-none-linux-gnu-strip'
pkg-config = 'pkg-config'
[host_machine]
system = 'linux'
cpu_family = 'aarch64'
cpu = 'armv8.2-a'
endian = 'little'
[properties]
# DPDK reads this via meson.get_external_property() when cross-compiling;
# -Dplatform=generic on the command line is ignored in that path.
platform = 'generic'
[built-in options]
c_args = ['-I/usr/include/aarch64-linux-gnu', '-I/usr/include']
cpp_args = ['-I/usr/include/aarch64-linux-gnu', '-I/usr/include']
c_link_args = ['-L/usr/lib/aarch64-linux-gnu']
cpp_link_args = ['-L/usr/lib/aarch64-linux-gnu']
EOF
```
#### 3.1.3 Download, build and install
```shell
cd ~
wget http://fast.dpdk.org/rel/dpdk-24.11.4.tar.xz # K release
# wget http://fast.dpdk.org/rel/dpdk-20.11.9.tar.xz # F release
tar xf dpdk-24.11.4.tar.xz
cd dpdk-stable-24.11.4
# Always wipe the build directory — meson caches pkg-config results and will
# silently reuse x86 libelf paths if the previous setup run was bad.
rm -rf build-dragonwing
PKG_CONFIG_LIBDIR=/usr/lib/aarch64-linux-gnu/pkgconfig \
meson setup build-dragonwing \
--cross-file ~/aarch64-dragonwing.ini \
--prefix=$HOME/dw-sysroot \
-Dplatform=generic \
-Ddisable_libs=bpf \
-Ddisable_drivers=net/ice,net/i40e,net/iavf,net/ixgbe
ninja -C build-dragonwing
ninja -C build-dragonwing install
```
> **Note:** `PKG_CONFIG_LIBDIR` replaces (not appends) the pkg-config search
> path, ensuring meson finds arm64 `.pc` files rather than x86 ones.
> `-Ddisable_libs=bpf` drops the libelf dependency (only needed for eBPF
> packet filtering, not for fronthaul use), avoiding the x86/arm64 libelf
> mismatch. The `-Ddisable_drivers` list trims x86-only NIC drivers;
> add DragonWing-specific PMDs with `-Denable_drivers=...` if needed.
Verify pkg-config can see it:
```shell
PKG_CONFIG_PATH=$DW_SYSROOT/lib/pkgconfig \
pkg-config --modversion libdpdk
```
### 3.2 libxran
#### 3.2.1 Clone and patch
```shell
git clone https://github.com/openairinterface/o-du-phy.git ~/phy
cd ~/phy
# K release — tag must match K_VERSION in radio/fhi_72/CMakeLists.txt
git checkout oran_k_release_v1.1
# F release alternative:
# git checkout oran_f_release_v1.9
# git apply ~/openairinterface5g/cmake_targets/tools/oran_fhi_integration_patches/F/oaioran_F.patch
```
#### 3.2.2 Cross-compile
```shell
sudo apt-get install -y libnuma-dev:arm64
cd ~/phy/fhi_lib/lib
make clean
CPATH=/usr/include/aarch64-linux-gnu:/usr/include \
PKG_CONFIG_PATH=$DW_SYSROOT/lib/pkgconfig \
make -j$(nproc) XRAN_LIB_SO=1 \
CC=${DW_TC}-gcc \
CPP=${DW_TC}-g++ \
AR=${DW_TC}-ar \
AS=${DW_TC}-as \
LD=${DW_TC}-gcc \
WIRELESS_SDK_TOOLCHAIN=gcc \
TARGET=armv8 \
RTE_SDK=$DW_SYSROOT \
XRAN_DIR=~/phy/fhi_lib
```
The output is `~/phy/fhi_lib/lib/build/libxran.so`.
> **Note:** The xran Makefile for `TARGET=armv8` hardcodes `CC := gcc`,
> `CPP := g++` etc., so the cross-compiler must be passed as **make
> command-line arguments** (after `make`) — not as shell environment variables
> — so they override the Makefile assignments. `PKG_CONFIG_PATH` and `CPATH`
> must remain shell environment variables: the Makefile invokes pkg-config via
> `$(shell ...)` which inherits the shell environment, and GCC reads `CPATH`
> independently to prepend extra include directories (bridging the gap between
> the SDK GCC's minimal built-in sysroot and the Ubuntu arm64 system headers).
> `RTE_SDK` must point at the **installed** DPDK prefix so pkg-config finds
> `libdpdk.pc` and resolves the correct arm64 include paths.
### 3.3 armral (Arm RAN Acceleration Library)
armral is required by OAI on aarch64 for BFP compression.
```shell
git clone https://git.gitlab.arm.com/networking/ral.git ~/ral
cd ~/ral
git checkout armral-25.01
mkdir build && cd build
cmake .. -GNinja \
-DCMAKE_TOOLCHAIN_FILE=~/openairinterface5g/cmake_targets/cross-arm-dragonwing.cmake \
-DBUILD_SHARED_LIBS=ON \
-DCMAKE_INSTALL_PREFIX=$DW_SYSROOT \
-DCMAKE_C_FLAGS="-mcpu=cortex-a78+crypto" \
-DCMAKE_CXX_FLAGS="-mcpu=cortex-a78+crypto"
ninja
ninja install
```
### 3.4 OAI gNB with FHI 7.2
```shell
cd ~/openairinterface5g
# Step 1 — native host tools (skip if already done)
mkdir -p build && cd build && cmake .. -DAVOID_SIGN=ON && make -j$(nproc) ldpc_generators generate_T && cd ..
# Step 2 — DragonWing cross-compile with FHI 7.2
rm -rf dragonwing_build && mkdir dragonwing_build && cd dragonwing_build
PKG_CONFIG_PATH=$DW_SYSROOT/lib/pkgconfig \
cmake .. \
-DCMAKE_TOOLCHAIN_FILE=../cmake_targets/cross-arm-dragonwing.cmake \
-DNATIVE_DIR=../build \
-DOAI_FHI72=ON \
-Dxran_LOCATION=$HOME/phy/fhi_lib/lib \
-Darmral_LOCATION=$DW_SYSROOT
make -j$(nproc) nr-softmodem nr-cuup params_libconfig coding oran_fhlib_5g
```
> **Note:** `PKG_CONFIG_PATH` (not `PKG_CONFIG_LIBDIR`) is used here so that
> the ARM DPDK `.pc` file is found *in addition to* the existing system arm64
> packages, rather than replacing them.
### 3.5 linuxptp (ptp4l / phc2sys)
S-plane synchronization requires `ptp4l` and `phc2sys` from linuxptp 3.1.1.
They are not present on the DragonWing by default and must be cross-compiled.
```shell
git clone https://git.code.sf.net/p/linuxptp/code ~/linuxptp
cd ~/linuxptp
git checkout v3.1.1
KBUILD_OUTPUT=/opt/Hexagon_SDK/6.4.0.2/tools/gcc_tools_64/aarch64-none-linux-gnu/libc \
CROSS_COMPILE=${DW_TC}- \
make -j$(nproc) EXTRA_CFLAGS="-mcpu=cortex-a78"
```
> **Note — two common pitfalls when cross-compiling linuxptp:**
>
> 1. Use `EXTRA_CFLAGS`, **not** `CFLAGS`, to add compiler flags.
> The linuxptp `makefile` defines `CFLAGS = -Wall $(VER) $(incdefs) $(EXTRA_CFLAGS)`.
> Passing `CFLAGS=...` on the make command line replaces that entire definition,
> discarding `$(incdefs)` — which contains `-DHAVE_ONESTEP_SYNC` and `-D_GNU_SOURCE`.
> Without `-DHAVE_ONESTEP_SYNC`, `missing.h` redeclares the enum that the system
> `net_tstamp.h` already defines, causing a compile error.
>
> 2. Set `KBUILD_OUTPUT` to the cross-compiler sysroot so `incdefs.sh` picks up the
> ARM `linux/net_tstamp.h` instead of the host x86 one. `CROSS_COMPILE` must be
> an environment variable (not a `make CC=` argument) because `incdefs.sh` calls
> `${CROSS_COMPILE}cpp` in a subshell to discover include paths.
Deploy:
```shell
adb push ~/linuxptp/ptp4l /usr/sbin/ptp4l
adb push ~/linuxptp/phc2sys /usr/sbin/phc2sys
adb shell chmod +x /usr/sbin/ptp4l /usr/sbin/phc2sys
```
Follow the ptp4l / phc2sys configuration in `doc/ORAN_FHI7.2_Tutorial.md`
(§ "PTP configuration") once the RTL 10G NIC is available.
### 3.6 RTL8127 kernel module
The IQ-9075 EVK uses a Realtek RTL8127A (10 GbE) NIC for the fronthaul connection.
The vendor kernel (`6.18.12-g06f8ab99cf04-dirty`) does not include any RTL8127 driver.
Two options are available, depending on whether hardware timestamping is needed:
| Option | Module | Hardware timestamping | Notes |
|--------|--------|-----------------------|-------|
| A (§ 3.6.13.6.5) | mainline `r8169` | **No** — software only | Simpler build |
| B (§ 3.6.6) | Realtek out-of-tree `r8127` | **Yes** — PHC registered | Required for ptp4l hardware mode |
For S-plane synchronisation with `ptp4l`, **use option B**. Option A is adequate if
only basic connectivity is needed (e.g. rfsimulator, debug access).
Because the vendor kernel has `CONFIG_MODVERSIONS=n` and `CONFIG_MODULE_SIG=n`, the only
requirement for any module to load is that its **version magic string matches exactly**:
`6.18.12-g06f8ab99cf04-dirty SMP preempt mod_unload aarch64`
> **Note:** If QCOM provides a kernel SDK or `kernel-devsrc` package for the IQ-9075
> build, use that instead — it will produce a better-matched module. The procedure
> below is a best-effort workaround using the mainline source.
#### 3.6.1 Download and configure the kernel source
```shell
cd ~
# Download mainline stable 6.18.12
wget https://cdn.kernel.org/pub/linux/kernel/v6.x/linux-6.18.12.tar.xz
tar xf linux-6.18.12.tar.xz
cd linux-6.18.12
# Seed config from the running device kernel
adb pull /proc/config.gz /tmp/kernel-config.gz
gunzip -f /tmp/kernel-config.gz
cp /tmp/kernel-config .config
# Enable r8169 and its Realtek PHY driver as loadable modules
scripts/config --module CONFIG_R8169
scripts/config --module CONFIG_REALTEK_PHY
# Set LOCALVERSION so vermagic matches the device exactly
scripts/config --set-str CONFIG_LOCALVERSION "-g06f8ab99cf04-dirty"
scripts/config --disable CONFIG_LOCALVERSION_AUTO
# Resolve any new Kconfig options introduced since the device config was made.
# CROSS_COMPILE must be set here so compiler/assembler capability checks (MTE,
# shadow call stack, etc.) are evaluated against the ARM toolchain, not the host.
# Omitting CROSS_COMPILE causes interactive questions during modules_prepare.
make ARCH=arm64 CROSS_COMPILE=${DW_TC}- olddefconfig
```
> **Note:** `olddefconfig` may set `CONFIG_HAVE_RUST=y` if the build host has `rustc`
> installed. This does not affect the r8169 module build, but if `modules_prepare`
> fails with a Rust-related error, disable it and re-run olddefconfig:
> ```shell
> scripts/config --disable CONFIG_HAVE_RUST
> make ARCH=arm64 CROSS_COMPILE=${DW_TC}- olddefconfig
> ```
#### 3.6.2 Prepare the kernel build tree
```shell
# Builds host scripts (modpost etc.) and generates arch headers.
# Does NOT compile the full kernel.
make ARCH=arm64 CROSS_COMPILE=${DW_TC}- modules_prepare
```
#### 3.6.3 Build r8169.ko and realtek.ko
Two modules are needed: the MAC driver (`r8169`) and the Realtek PHY driver (`realtek`).
The r8169 probe fails with "no dedicated PHY driver found for PHY ID 0x001cc890" if
`realtek.ko` is not loaded first.
```shell
make ARCH=arm64 CROSS_COMPILE=${DW_TC}- M=drivers/net/ethernet/realtek KBUILD_MODPOST_WARN=1 modules
make ARCH=arm64 CROSS_COMPILE=${DW_TC}- M=drivers/net/phy/realtek KBUILD_MODPOST_WARN=1 modules
```
Outputs: `drivers/net/ethernet/realtek/r8169.ko` and `drivers/net/phy/realtek/realtek.ko`.
> **Note:** `KBUILD_MODPOST_WARN=1` is required because `Module.symvers` does not exist
> (only generated by a full kernel build). Without it, `modpost` errors on unresolved
> symbols such as `napi_alloc_skb`, `free_irq`, etc. These are all standard kernel
> exports that are present in the running kernel; since `CONFIG_MODVERSIONS=n` on the
> device there is no CRC check at load time, so the warnings are safe to ignore.
#### 3.6.4 Prepare the firmware
The RTL8127A requires firmware `rtl_nic/rtl8127a-1.fw`. The build host has it in
compressed form (`linux-firmware` package); decompress before pushing because the vendor
kernel does not support `CONFIG_FW_LOADER_COMPRESS`.
```shell
zstd -d /lib/firmware/rtl_nic/rtl8127a-1.fw.zst -o ~/rtl8127a-1.fw
```
#### 3.6.5 Deploy and load
The modules depend on `libphy.ko` and `mdio-bus.ko`, which are already present in the
device's module tree. Load order matters: `libphy``realtek``r8169`.
```shell
# Push modules and firmware
adb push ~/linux-6.18.12/drivers/net/ethernet/realtek/r8169.ko /data/local/tmp/
adb push ~/linux-6.18.12/drivers/net/phy/realtek/realtek.ko /data/local/tmp/
adb shell mkdir -p /lib/firmware/rtl_nic
adb push ~/rtl8127a-1.fw /lib/firmware/rtl_nic/
# Load in dependency order
adb shell "modprobe libphy && insmod /data/local/tmp/realtek.ko && insmod /data/local/tmp/r8169.ko"
# Verify
adb shell "dmesg | grep -i r8169"
adb shell "ip link show"
```
If the NIC is detected, assign an IP and verify link before proceeding to the DPDK /
ptp4l configuration. To make the modules persistent across reboots, copy them into the
device module tree:
```shell
adb shell mkdir -p /lib/modules/6.18.12-g06f8ab99cf04-dirty/kernel/drivers/net/ethernet/realtek
adb shell mkdir -p /lib/modules/6.18.12-g06f8ab99cf04-dirty/kernel/drivers/net/phy/realtek
adb push ~/linux-6.18.12/drivers/net/ethernet/realtek/r8169.ko \
/lib/modules/6.18.12-g06f8ab99cf04-dirty/kernel/drivers/net/ethernet/realtek/
adb push ~/linux-6.18.12/drivers/net/phy/realtek/realtek.ko \
/lib/modules/6.18.12-g06f8ab99cf04-dirty/kernel/drivers/net/phy/realtek/
adb shell depmod -a
```
#### 3.6.6 Option B — Realtek out-of-tree r8127 driver (hardware timestamping)
Realtek publishes a standalone driver with full PTP/PHC support
(`ptp_clock_info`, hardware timestamp registers). It replaces both `r8169.ko` and
`realtek.ko` from option A with a single `r8127.ko`.
The upstream Realtek source (`openwrt/rtl8127`) requires several fixes before the
driver works correctly on the DragonWing. Use the patched fork below instead of
the upstream repo; it already contains all fixes described in the repo's `README.md`:
```shell
cd ~
# TODO: update URL once the fork is published
git clone https://github.com/rtknopp/rtl8127.git
cd rtl8127
make KERNELDIR=~/linux-6.18.12 \
ARCH=arm64 \
CROSS_COMPILE=${DW_TC}- \
KBUILD_MODPOST_WARN=1 \
ENABLE_PTP_SUPPORT=y \
modules
```
The fork contains the following fixes relative to `openwrt/rtl8127` v11.015.00
(see `README.md` in the fork for full technical details):
| Fix | Symptom without it |
|-----|--------------------|
| **Kernel 6.15+ API**`hrtimer_init()` + manual `.function` assignment replaced by `hrtimer_setup()` | Build fails on kernels ≥ 6.15 with "implicit declaration" error |
| **PHC clock initialisation outside spinlock**`rtl8127_set_local_time()` moved to after `r8127_spin_unlock()` in `rtl8127_hwtstamp_enable()` | `_rtl8127_phc_settime()` re-acquires `phy_lock` while it is already held, causing an RCU stall / spinlock deadlock; PHC stays at 0 while the PTP master uses Unix time, producing ~1.8 s rms offset |
| **Capture engine arm sequence**`rtl8127_ptp_enable_config()` (PTP_CTL = 0x103F) called *before* writing `0xA640 BIT_15` at probe time; `0xA640` removed from the `hwtstamp_enable()` path entirely | The PHY timestamp pipeline activates only when `0xA640 BIT_15` is written while `PTP_CTL != 0`; writing it with `PTP_CTL = 0` (the upstream sequence) is silently accepted but `PTP_INSR` never signals `TX_TX_INTR` or `RX_TS_INTR` — all timestamps read as zero. Writing `0xA640` from the `hwtstamp_enable()` path (called on every `SIOCSHWTSTAMP`) causes an infinite link-drop loop: the autoneg restart drops the link, which ptp4l answers with another `SIOCSHWTSTAMP`, which drops the link again |
| **Disable path writes `PTP_CTL = 0x0000`** instead of clearing only `BIT_0` | Clearing `BIT_0` alone leaves the PHY in a partial PTP mode that causes the link partner to time out and drop the link on disable |
| **`rtl8127_ptp_link_up_init()`** — new function called from `rtl8127_link_on_patch` to re-write `PTP_CTL` and `PTP_INER` after any link recovery while hwtstamp is enabled | After a cable pull / reinsert, `PTP_CTL` was not restored; upstream only called `rtl8127_set_local_time()` on link-up, which does not restore PTP capture configuration |
If option A modules are already loaded, unload them first:
```shell
adb shell "rmmod r8169; rmmod realtek"
```
Deploy and load:
```shell
adb push ~/rtl8127/r8127.ko /data/local/tmp/
adb shell "modprobe libphy && insmod /data/local/tmp/r8127.ko"
# Verify hardware timestamping is now available
adb shell "ethtool -T enP1p1s0"
```
Expected output should include `PTP Hardware Clock: 0` and hardware transmit/receive
timestamp modes. To make persistent across reboots:
```shell
adb shell mkdir -p /lib/modules/6.18.12-g06f8ab99cf04-dirty/kernel/drivers/net/ethernet/realtek
adb push ~/rtl8127/r8127.ko \
/lib/modules/6.18.12-g06f8ab99cf04-dirty/kernel/drivers/net/ethernet/realtek/
adb shell depmod -a
```
#### 3.7 Deploy FHI 7.2 binaries via adb
```shell
adb shell mkdir -p /data/oai/lib
# OAI executables
adb push dragonwing_build/nr-softmodem /data/oai/
adb push dragonwing_build/liboran_fhlib_5g.so /data/oai/lib/
adb push dragonwing_build/libparams_libconfig.so /data/oai/lib/
adb push dragonwing_build/libcoding.so /data/oai/lib/
# DPDK shared libraries
find $DW_SYSROOT/lib -name "librte_*.so*" -exec adb push {} /data/oai/lib/ \;
# dpdk-devbind.py — needed to bind VFs to vfio-pci before starting the gNB.
# Install to /usr/local/bin to match the path used in ORAN_FHI7.2_Tutorial.md.
# Python 3 is already present on the device.
adb push $DW_SYSROOT/bin/dpdk-devbind.py /data/oai/
adb shell "cp /data/oai/dpdk-devbind.py /usr/local/bin/dpdk-devbind.py && chmod +x /usr/local/bin/dpdk-devbind.py"
# xran
adb push ~/phy/fhi_lib/lib/build/libxran.so /data/oai/lib/
# armral
adb push $DW_SYSROOT/lib/libarmral.so /data/oai/lib/
# Run with combined LD_LIBRARY_PATH
adb shell "export LD_LIBRARY_PATH=/data/oai/lib && /data/oai/nr-softmodem --help"
```
---
## 4 Alternative: sysroot from device
If the Ubuntu multiarch packages are not available on the build host, you can
extract the device's root filesystem and use it as a sysroot instead.
```shell
# Pull relevant library directories from the device
adb pull /usr/lib dragonwing-sysroot/usr/lib
adb pull /usr/include dragonwing-sysroot/usr/include
adb pull /lib dragonwing-sysroot/lib
# Then configure cmake with an explicit sysroot (from the repo root)
rm -rf dragonwing_build && mkdir dragonwing_build && cd dragonwing_build
cmake .. \
-DCMAKE_TOOLCHAIN_FILE=../cmake_targets/cross-arm-dragonwing.cmake \
-DNATIVE_DIR=../build \
-DCMAKE_SYSROOT=$(pwd)/../dragonwing-sysroot \
-DCMAKE_FIND_ROOT_PATH=$(pwd)/../dragonwing-sysroot
```
You will also need to regenerate the pkg-config search path:
```shell
export PKG_CONFIG_LIBDIR=$(pwd)/dragonwing-sysroot/usr/lib/pkgconfig:$(pwd)/dragonwing-sysroot/usr/lib/aarch64-linux-gnu/pkgconfig
```
---
## 5 Troubleshooting
| Symptom | Likely cause | Fix |
|---------|--------------|-----|
| `Could NOT find GnuTLS` | arm64 packages not installed or wrong `PKG_CONFIG_LIBDIR` | Check Section 1.3; verify `pkg-config --list-all` finds arm64 packages |
| `cannot find -lgnutls` at link | Linker not searching `/usr/lib/aarch64-linux-gnu` | Verify that toolchain file `CMAKE_EXE_LINKER_FLAGS_INIT` is being applied |
| `Unsupported architecture` from apt | Host apt sources not configured for arm64 | Redo Section 1.3.1 |
| Binary crashes with `SIGILL` on device | Wrong `-mcpu` / `-march` flag | The toolchain defaults to `cortex-a78`; if running on a Silver (A55) core, add `-DCMAKE_C_FLAGS=-mcpu=cortex-a55` |
| `adb: error: failed to copy` | `/data/oai` directory doesn't exist or no permission | `adb shell mkdir -p /data/oai` or use a writable path like `/data/local/tmp/oai` |

View File

@@ -1,390 +0,0 @@
<!-- SPDX-License-Identifier: CC-BY-4.0 -->
# Git guide
This guide collects the practical Git knowledge needed to contribute to OAI in
one place: how to set up commit signing, how to manage and synchronize a
feature branch, how to handle submodules, how to recover from common mistakes,
and how to avoid resolving the same merge conflicts over and over. It is a
how-to companion to the contribution *requirements*, which are
defined in [CONTRIBUTING.md](../CONTRIBUTING.md) (CLA, DCO, verified commits)
and [code-style-contrib.md](./code-style-contrib.md) (workflow, commit, and
review policy).
[[_TOC_]]
## Setting up commit signing
Every commit in a pull request must pass two independent CI checks, described
in [CONTRIBUTING.md](../CONTRIBUTING.md#commit-guidelines):
1. **[Developer Certificate of Origin (DCO)](https://en.wikipedia.org/wiki/Developer_Certificate_of_Origin)**:
the commit message carries a `Signed-off-by:` trailer.
2. **[Verified commits](https://docs.github.com/en/authentication/managing-commit-signature-verification/about-commit-signature-verification)**:
the commit is cryptographically signed with an SSH or GPG key.
These are two different mechanisms: the sign-off is a line of text you add with
`git commit -s`, the signature is created automatically by Git once signing is
configured. You need both.
### Quick setup (SSH signing)
```bash
# 1. Generate a key pair (skip if you already have one)
ssh-keygen -t ed25519 -C "<your email>"
# 2. Configure Git to sign every commit with it
git config --global user.name "<Your Name>"
git config --global user.email "<your email>"
git config --global gpg.format ssh
git config --global user.signingkey ~/.ssh/id_ed25519.pub
git config --global commit.gpgsign true
```
> **NOTE:**
> `--global` writes to `~/.gitconfig` and applies to every repository on the
> machine. When working on a shared server (or with different identities in
> different clones), drop `--global` to store the same settings in the current
> repository's `.git/config` only.
Then print the public key with `cat ~/.ssh/id_ed25519.pub` and paste it into
your GitHub account under *Settings → SSH and GPG keys → New SSH key*, choosing
the key type **Signing Key**.
> **NOTE:**
> Adding an SSH key for repository access does not automatically enable commit
> signing. The key must also be added under GitHub's Signing Keys settings.
For commits to show as *Verified* on GitHub:
- your `git config user.email` must match an email of your GitHub account,
- that email must be [verified in your GitHub account](https://docs.github.com/en/account-and-profile/how-tos/email-preferences/verifying-your-email-address),
- and it must be the email address used for the CLA (see
[CONTRIBUTING.md](../CONTRIBUTING.md)).
If you prefer GPG over SSH, set `gpg.format` to `openpgp` and `user.signingkey`
to your GPG key ID instead; see the [GitHub documentation on signing
commits](https://docs.github.com/en/authentication/managing-commit-signature-verification/signing-commits)
for the full walkthrough of both methods.
### Signing off your commits (DCO)
The `Signed-off-by:` trailer is added with the `-s`/`--signoff` flag:
```bash
git commit -s # new commit
git commit --amend -s --no-edit # add the trailer to the last commit
```
It must read `Signed-off-by: Full Name <email-for-cla>`. See the
[commit trailers section](./code-style-contrib.md#use-of-git-commit-trailers)
of the contribution guidelines for this and other trailers.
### Verifying signed commits
You can verify that commits are properly signed locally using:
```bash
git log --show-signature
```
GitHub should also display a *Verified* badge next to signed commits once the
signing key has been correctly configured in your account.
For SSH commit signing, local Git verification may require an
`allowed_signers` file. This is only used for local verification in Git and is
not required by GitHub. If you see errors such as:
```text
No principal matched
Can't check signature
error: gpg.ssh.allowedSignersFile needs to be configured
```
create the file, add your signing identity, and enable it in your Git config:
```bash
mkdir -p ~/.config/git
echo "user@example.com ssh-ed25519 AAAACexamplekeystringhere" > ~/.config/git/allowed_signers
git config gpg.ssh.allowedSignersFile ~/.config/git/allowed_signers
```
> **NOTE:**
> This is only for local Git signature verification and does not affect GitHub,
> or remote repository behavior.
## Managing your own branch
The general development branch, and the target of every contribution, is
`develop`; see [GET_SOURCES.md](./GET_SOURCES.md) for the branch and tag model
(weekly `YYYY.wXX` tags, `vX.Y` releases). The rules for what a branch should
look like — linear history, small self-contained logical commits, commit
messages that explain *why* — are policy and live in
[code-style-contrib.md](./code-style-contrib.md#workflow).
Before starting to work, please make sure to branch off the latest `develop`
branch. Make commits as appropriate.
```bash
git fetch origin
git checkout develop
git checkout -b my-new-feature # name as appropriate
git add -p # add changes for change set 1, use `-p` to review what to include
git commit -s # in the editor, describe your changes
git add -p # add changes for change set 2
git commit -s # in the editor, describe your changes
```
Recent Git versions also offer `git switch` as a clearer alternative to
`git checkout` for branch operations: `git switch develop` changes branch,
`git switch -c my-new-feature` creates one.
Commit messages should take multiple lines; after the initial title, a blank
line should follow. Read the `DISCUSSION` section in `man git commit` for more
information. For documentation-only commits, prefix the title with `docs:`
(see [doc_best_practices.md](./doc_best_practices.md)).
Code must be formatted with clang-format; an optional pre-commit hook can
check this automatically at every commit — see
[clang-format.md](./clang-format.md) for its installation and how to combine
it with `git add -p`/`git stash -p`.
If your development takes longer, make sure to synchronize regularly with
`origin/develop` using `git rebase`:
```bash
git fetch origin
git rebase -i origin/develop
```
If you do logical changes, you should not have to resolve the same conflicts
over and over again. If the same conflicts do keep reappearing, e.g., when
maintaining a long-lived fork, consider enabling
[`git rerere`](#reusing-conflict-resolutions-with-git-rerere). Note that if
you jumped over multiple develop tags, you can also rebase in intermediate
steps, in case you fear the differences might be too big.
```bash
git rebase -i 2023.w38
git rebase -i 2023.w41
git rebase -i develop
```
Once you rebased, push the changes to the remote:
```bash
git push origin my-new-feature --force-with-lease # force with lease lets you only overwrite what you also have locally in origin/my-new-feature
```
### Fixing up earlier commits
The [workflow policy](./code-style-contrib.md#workflow) asks for a history
without "clean up" commits: when review or testing reveals a problem in an
earlier commit of your branch, fold the fix into that commit instead of
appending a `Fix bug` commit on top. Git automates this with fixup commits and
`--autosquash`:
```bash
git add -p # stage the fix
git commit --fixup=<commit> # creates a commit titled "fixup! <original title>"
git rebase -i --autosquash origin/develop # moves it after <commit> and squashes the two
```
During the `--autosquash` rebase, Git pre-arranges the todo list so each
`fixup!` commit is squashed into the commit it references; you normally just
accept it. The result is the same clean history as if the fix had been part of
the original commit.
A handy variant is `git commit --fixup=amend:<commit>`, which folds in the fix
and also rewrites the commit message: during the `--autosquash` rebase the
editor opens pre-filled with the original message, ready to be edited into the
new one.
## Working with submodules
Parts of the tree are Git submodules. After cloning, and after every branch
switch or pull, make sure they match the superproject:
```bash
git submodule update --init --recursive
```
A recurring review problem is the *unintended submodule pointer update*: a
submodule whose checked-out commit differs from what the superproject records
shows up in `git status` as `modified: <path> (new commits)`, and a broad
`git add .`, `git add -A`, or `git commit -a` silently records the new pointer
in your commit. To avoid it:
- review `git status` before committing and stage files explicitly (e.g. with
`git add -p`) rather than adding everything;
- if a pointer change was staged by accident, unstage it with
`git restore --staged <path>` and realign the submodule with
`git submodule update --init <path>`.
Only commit a submodule pointer change when updating that submodule is the
purpose of the commit, and say so in the commit message.
## Recovering from mistakes
To unstage a file that was added by accident (the changes stay in your working
tree), or to throw away local changes to a file:
```bash
git restore --staged <file> # unstage; keeps the modifications
git restore <file> # discard unstaged modifications - cannot be undone
```
`git reset` moves the current branch to another commit and differs in what it
does to your files:
```bash
git reset --soft HEAD~1 # undo the last commit, keep its changes staged (e.g. to re-split it)
git reset --hard <commit> # make branch, index and working tree identical to <commit>
```
> **Warning:** `git reset --hard` discards all uncommitted changes; there is no
> way to recover them.
Committed work is much harder to lose than it seems: `git reflog` records every
position of `HEAD` (commits, rebases, resets, checkouts) for a retention period
of at least 30 days, even for commits no branch points to anymore. If a rebase
or reset went wrong, find the last good state and reset back to it:
```bash
git reflog # e.g.: e75076172 HEAD@{5}: commit: doc: add git rerere guide
git reset --hard 'HEAD@{5}' # return the branch to that state
```
## Reusing conflict resolutions with git rerere
The `develop` branch is updated roughly once a week. Feature branches that live
for more than a few days therefore have to be re-synced with `develop`
repeatedly, and the same merge conflicts tend to reappear at every sync - often
in the same scheduler, PHY, or RRC files that several contributors touch at
once. Resolving the identical conflict by hand every week is error-prone and
wastes time.
Git ships a built-in feature for exactly this situation: `rerere`, short for
**reuse recorded resolution**. Once enabled, Git remembers how you resolved a
given conflict and replays that resolution automatically the next time the same
conflict appears.
This section explains how to enable and use it. It is a local developer
convenience: nothing about it changes the repository, the history you push, or
the contribution workflow.
### What it does
When a conflict occurs, `rerere` records the conflicted hunk (the *preimage*).
After you resolve it, `rerere` records your resolution (the *postimage*), keyed
by a hash of the preimage. The next time a conflict with the same preimage shows
up - in a later rebase, a later merge, or even another branch - Git reapplies
your recorded resolution instead of presenting the conflict again.
The data lives in `.git/rr-cache/` inside your local clone. It is never part of
any commit and is never pushed.
### Enabling it
Enable it once, globally, so it applies to every repository on your machine:
```bash
git config --global rerere.enabled true
git config --global rerere.autoupdate true
```
`rerere.autoupdate` stages a replayed resolution automatically. Without it, the
resolution is still written into your working tree, but you have to `git add`
the file yourself.
### Typical flow
The first time you hit a conflict after enabling `rerere`, resolve it exactly as
you always have:
```bash
# during a rebase or a merge that conflicts
git status # rerere reports which paths it is recording
# edit the conflicted files, remove the markers
git add <resolved-files>
git rebase --continue # or: git commit, for a merge
```
That resolution is now recorded. The next time the same conflict appears, Git
resolves it for you. With `autoupdate` on, the file is already staged and you can
go straight to:
```bash
git rebase --continue # or git commit
```
Always review the replayed result before continuing - see *Caveats* below.
### Inspecting and undoing recorded resolutions
```bash
git rerere status # paths with a recorded preimage in the current operation
git rerere diff # the resolution rerere is applying
git rerere forget <path> # discard a recorded resolution (e.g. a wrong one)
```
`git rerere forget` is the escape hatch when you recorded a bad resolution: it
drops the cached entry for that path so the next conflict is presented fresh.
### Seeding from existing history
If your branch already contains **merge commits** whose conflicts you resolved
before enabling `rerere`, you can backfill the cache so those resolutions are
available immediately. Git ships a helper for this in `contrib/`:
```bash
sh /path/to/git/contrib/rerere-train.sh origin/develop..HEAD
```
It replays the merge commits in the given range, reconstructs each conflict, and
records the resolution found in the merge commit.
> **Note:** this only works for resolutions captured in merge commits. A purely
> linear (rebased) history has no merge commits to learn from, so there is
> nothing to backfill - `rerere` will simply start recording from your next
> conflict onward.
### Sharing the cache (optional)
The cache is local. If you work across several machines, or want a team to share
resolutions for the same recurring conflicts, copy the directory:
```bash
rsync -a ~/work/oai-A/.git/rr-cache/ ~/work/oai-B/.git/rr-cache/
```
There is no built-in push/pull for the cache; treat it as an ordinary directory
to sync.
### Caveats and good practice
- `rerere` matches on the **exact** conflicting text. If `develop` changed the
lines surrounding your change, the preimage differs and the conflict is
presented as new. This is expected - the resolution is still recorded for the
next identical occurrence.
- A replayed resolution is only as correct as the original. When the code around
a conflict has evolved, an old resolution can apply cleanly yet be wrong.
**Review every replayed resolution and build/test before continuing.**
- `rerere` reduces repeated manual work; it does not change which branch
strategy you use. It helps both when rebasing onto `develop` and when merging
`develop` into a feature branch. Remember that branches intended for
contribution must have a linear history without merge commits (see the
[workflow policy](./code-style-contrib.md#workflow)); a fork can of course
carry merge commits if that is convenient for its development.
## See also
- [CONTRIBUTING.md](../CONTRIBUTING.md) - CLA, DCO, and licensing requirements.
- [code-style-contrib.md](./code-style-contrib.md) - workflow, commit, and
review policy, including commit trailers.
- [GET_SOURCES.md](./GET_SOURCES.md) - branches, tags, and how to obtain the
sources.
- [clang-format.md](./clang-format.md) - code formatting and its Git
integration (pre-commit hook).
- The [Git Book](https://git-scm.com/book/en/v2) and the
[`git rerere` manual](https://git-scm.com/docs/git-rerere)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 161 KiB

After

Width:  |  Height:  |  Size: 271 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 209 KiB

After

Width:  |  Height:  |  Size: 361 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

BIN
doc/images/oai_logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.4 KiB

View File

@@ -9,7 +9,7 @@
FROM registry.redhat.io/ubi9/ubi:latest AS ran-base
LABEL MAINTAINER OpenAirInterface <oaicicdteam@openairinterface.org>
LABEL MAINTAINER OpenAirInterface <contact@openairinterface.org>
ARG NEEDED_GIT_PROXY
ARG TARGETARCH
ENV TZ=Europe/Paris

View File

@@ -9,7 +9,7 @@
FROM ran-base:latest AS ran-base
ARG xran_VERSION=11.1.3
ARG xran_VERSION=11.1.1
ARG E2AP_VERSION=E2AP_V3
ARG KPM_VERSION=KPM_V3_00
ENV DEBIAN_FRONTEND=noninteractive
@@ -27,6 +27,7 @@ RUN apt-get update && \
RUN rm -Rf /oai-ran
WORKDIR /oai-ran
COPY . .
## Download and build DPDK
RUN wget --no-verbose http://fast.dpdk.org/rel/dpdk-24.11.4.tar.xz && \
@@ -41,7 +42,7 @@ RUN git clone https://github.com/openairinterface/o-du-phy.git /opt/phy && \
cd /opt/phy && \
git checkout $xran_VERSION &&\
cd /opt/phy/fhi_lib/lib && \
WIRELESS_SDK_TOOLCHAIN=gcc RTE_SDK=/oai-ran/dpdk-stable-24.11.4/ XRAN_DIR=/opt/phy/fhi_lib make XRAN_LIB_SO=1
TARGET=armv8 WIRELESS_SDK_TOOLCHAIN=gcc RTE_SDK=/oai-ran/dpdk-stable-24.11.4/ XRAN_DIR=/opt/phy/fhi_lib make XRAN_LIB_SO=1
## Build Arm RAN Acceleration Library
RUN git clone https://git.gitlab.arm.com/networking/ral.git /opt/ral && \
@@ -53,8 +54,6 @@ RUN git clone https://git.gitlab.arm.com/networking/ral.git /opt/ral && \
ninja && \
ninja install
COPY . .
FROM ran-base AS ran-build-fhi72
## Build and install OAI
#run build_oai to build the target image

View File

@@ -12,8 +12,9 @@ ENV TZ=Europe
RUN rm -Rf /oai-ran
WORKDIR /oai-ran
COPY . .
ARG xran_VERSION=11.1.3
ARG xran_VERSION=11.1.1
## Download and build DPDK
RUN wget --no-verbose http://fast.dpdk.org/rel/dpdk-24.11.4.tar.xz && \
@@ -30,8 +31,6 @@ RUN git clone https://github.com/openairinterface/o-du-phy.git /opt/phy && \
cd /opt/phy/fhi_lib/lib && \
WIRELESS_SDK_TOOLCHAIN=gcc RTE_SDK=/oai-ran/dpdk-stable-24.11.4/ XRAN_DIR=/opt/phy/fhi_lib make XRAN_LIB_SO=1
COPY . .
FROM ran-base AS ran-build-fhi72
ARG E2AP_VERSION=E2AP_V3
ARG KPM_VERSION=KPM_V3_00

View File

@@ -9,7 +9,7 @@
FROM ran-base:latest AS ran-base
ARG xran_VERSION=11.1.3
ARG xran_VERSION=11.1.1
ARG DPDK_VERSION=22.11
ARG T2_PATCH=AMD-T2-SDFEC_25-03-1.patch
ENV DEBIAN_FRONTEND=noninteractive
@@ -27,7 +27,7 @@ RUN apt-get update && \
RUN rm -Rf /oai-ran
WORKDIR /oai-ran
COPY . .
## Copy T2 patch
COPY ./$T2_PATCH /opt/.
## Download and build DPDK
@@ -46,8 +46,6 @@ RUN git clone https://github.com/openairinterface/o-du-phy.git /opt/phy && \
cd /opt/phy/fhi_lib/lib && \
WIRELESS_SDK_TOOLCHAIN=gcc RTE_SDK=/oai-ran/dpdk-$DPDK_VERSION/ XRAN_DIR=/opt/phy/fhi_lib make XRAN_LIB_SO=1
COPY . .
FROM ran-base AS ran-build-fhi72-t2
## Build and install OAI
## Run build_oai to build the target image, if T2 patch present, build with T2 support

View File

@@ -9,7 +9,7 @@
FROM ran-base:latest AS ran-base
ARG xran_VERSION=11.1.3
ARG xran_VERSION=11.1.1
ARG E2AP_VERSION=E2AP_V3
ARG KPM_VERSION=KPM_V3_00
ENV DEBIAN_FRONTEND=noninteractive
@@ -32,6 +32,7 @@ RUN apt-get update && \
RUN rm -Rf /oai-ran
WORKDIR /oai-ran
COPY . .
RUN git clone https://github.com/CESNET/libyang.git && \
cd libyang && \
@@ -80,8 +81,6 @@ RUN git clone https://github.com/openairinterface/o-du-phy.git /opt/phy && \
cd /opt/phy/fhi_lib/lib && \
WIRELESS_SDK_TOOLCHAIN=gcc RTE_SDK=/oai-ran/dpdk-stable-24.11.4/ XRAN_DIR=/opt/phy/fhi_lib make XRAN_LIB_SO=1
COPY . .
FROM ran-base AS ran-build-fhi72
## Build and install OAI
#run build_oai to build the target image

View File

@@ -19,7 +19,7 @@ RUN /bin/sh oaienv && \
mkdir -p log ran_build/build ran_build/build-cross && \
cd ran_build/build && \
cmake ../../.. -GNinja && \
ninja ldpc_generators generate_T && \
ninja generate_T && \
cd ../build-cross/ && \
# install missing libyaml-cpp-dev for arm64
apt install -y libyaml-cpp-dev:arm64 && \

View File

@@ -1,6 +1,20 @@
<!-- SPDX-License-Identifier: CC-BY-4.0 -->
OAI Docker/Podman Build and Usage Procedures
<table style="border-collapse: collapse; border: none;">
<tr style="border-collapse: collapse; border: none;">
<td style="border-collapse: collapse; border: none;">
<a href="http://www.openairinterface.org/">
<img src="../doc/images/oai_final_logo.png" alt="" border=3 height=50 width=150>
</img>
</a>
</td>
<td style="border-collapse: collapse; border: none; vertical-align: center;">
<b><font size = "5">OAI Docker/Podman Build and Usage Procedures</font></b>
</td>
</tr>
</table>
---
**Table of Contents**

View File

@@ -18,6 +18,7 @@
#include "SCHED/sched_eNB.h"
#include "PHY/LTE_TRANSPORT/transport_proto.h"
#include "nfapi/oai_integration/vendor_ext.h"
#include "radio/COMMON/common_lib.h"
#include "PHY/if4_tools.h"
#include "PHY/LTE_ESTIMATION/lte_estimation.h"

View File

@@ -33,6 +33,7 @@
#include "SCHED/sched_common.h"
#include "common/utils/LOG/log.h"
#include "common/utils/LOG/vcd_signal_dumper.h"
#include "radio/COMMON/common_lib.h"
#include "radio/ETHERNET/ethernet_lib.h"
/* these variables have to be defined before including ENB_APP/enb_paramdef.h */

View File

@@ -18,6 +18,7 @@
#include "common/ran_context.h"
#include "common/config/config_userapi.h"
#include "common/utils/load_module_shlib.h"
#include "radio/COMMON/common_lib.h"
#include "radio/ETHERNET/if_defs.h"
#include <openair1/PHY/phy_extern_ue.h>

View File

@@ -23,6 +23,7 @@
#include <sys/sysinfo.h>
#include <sys/types.h>
#include <unistd.h>
#include "radio/COMMON/common_lib.h"
#include "assertions.h"
#include "PHY/types.h"
#include "PHY/defs_eNB.h"

View File

@@ -19,6 +19,7 @@
#include "common/ran_context.h"
#include "common/config/config_userapi.h"
#include "common/utils/load_module_shlib.h"
#include "radio/COMMON/common_lib.h"
#include "radio/ETHERNET/if_defs.h"
#include "PHY/phy_vars_ue.h"

View File

@@ -10,6 +10,7 @@
#include "common/config/config_userapi.h"
#include "common/utils/load_module_shlib.h"
#include "common/ran_context.h"
#include "radio/COMMON/common_lib.h"
#include "radio/ETHERNET/if_defs.h"
#include "PHY/phy_vars.h"
#include "PHY/phy_extern.h"
@@ -21,9 +22,6 @@
#include <executables/softmodem-common.h>
#include <executables/thread-common.h>
#include "executables/nr-softmodem.h"
#include "nr-oru.h"
#include "openair1/PHY/INIT/nr_phy_init.h"
#include "openair1/SCHED_NR/sched_nr.h"
pthread_cond_t sync_cond;
pthread_mutex_t sync_mutex;
@@ -35,9 +33,6 @@ int sf_ahead = 4;
int emulate_rf = 0;
RAN_CONTEXT_t RC;
int64_t uplink_frequency_offset[MAX_NUM_CCs][4];
void nfapi_setmode(nfapi_mode_t nfapi_mode)
@@ -111,15 +106,23 @@ struct timespec timespec_sub(struct timespec, struct timespec)
struct timespec t = {0};
return t;
};
void beam_index_allocation(uint16_t fapi_beam_index,
int ant,
int num_ports,
int symbols_per_slot,
int slot,
uint16_t bitmap_symbols,
int num_ant_max,
uint16_t **ant_beam_id_list)
void perform_symbol_rotation(NR_DL_FRAME_PARMS *fp, double f0, c16_t *symbol_rotation)
{
return;
}
void init_timeshift_rotation(NR_DL_FRAME_PARMS *fp)
{
return;
};
int beam_index_allocation(bool das,
int fapi_beam_index,
NR_gNB_COMMON *common_vars,
int slot,
int symbols_per_slot,
int bitmap_symbols)
{
return 0;
}
uint16_t get_first_ant_idx(bool das, uint16_t num_ports_beams, uint16_t beam_id, uint16_t fapi_start_port)
{
@@ -129,12 +132,6 @@ void nr_fill_du(uint16_t N_ZC, const uint16_t *prach_root_sequence_map, uint16_t
{
return;
};
static void sig_handler(int sig_num)
{
oai_exit = 1;
}
uint16_t nr_du[838];
uint64_t downlink_frequency[MAX_NUM_CCs][4];
@@ -171,7 +168,6 @@ int main(int argc, char **argv)
printf("About to Init RU threads\n");
lock_memory_to_ram();
load_dftslib();
RC.nb_RU = 1;
RC.ru = malloc(sizeof(RC.ru));
@@ -179,77 +175,24 @@ int main(int argc, char **argv)
init_NR_RU(config_get_if(), NULL);
RU_t *ru = RC.ru[0];
ORU_t oru = {0};
oru.ru = ru;
int ret = get_oru_options(&oru);
AssertFatal(ret == 0, "Cannot configure oru, check your config file/cmdline");
ru->numerology = oru.numerology;
oru_init_frame_parms(&oru);
NR_DL_FRAME_PARMS *fp = ru->nr_frame_parms;
nr_dump_frame_parms(fp);
init_symbol_rotation(fp);
init_timeshift_rotation(fp->ofdm_symbol_size, fp->nb_prefix_samples, fp->ofdm_offset_divisor, fp->timeshift_symbol_rotation);
ru->if_south = LOCAL_RF;
nr_phy_init_RU(oru.ru);
fill_rf_config(ru, ru->rf_config_file);
ru->N_TA_offset = set_default_nta_offset(fp->freq_range, fp->samples_per_subframe);
/* set PRACH configuration */
nfapi_nr_prach_config_t *prach_config = &ru->config.prach_config;
prach_config->prach_ConfigurationIndex.value = oru.prach_config_index;
prach_config->num_prach_fd_occasions_list[0].k1.value = oru.prach_msg1_freq;
prach_config->prach_sequence_length.value = 1;
prach_config->prach_sub_c_spacing.value = 1;
prach_config->num_prach_fd_occasions.value = 1;
reset_meas(&oru.rx_prach);
oru.prach_info = get_nr_prach_occasion_info_from_index(oru.prach_config_index, FR1, fp->frame_type);
LOG_A(PHY, "PRACH configuration index %d\n", oru.prach_config_index);
LOG_A(PHY,
"PRACH format %d start_symbol %d duration %d\n",
oru.prach_info.format,
oru.prach_info.start_symbol,
oru.prach_info.N_dur);
prepare_prach_item(&oru);
oru.fronthaul = oru_fh_init(&oru.fh_config);
AssertFatal(oru.fronthaul != NULL, "Cannot configure oru fronthaul, check your config file/cmdline");
LOG_I(PHY, "starting rfdevice\n");
ret = openair0_device_load(&ru->rfdevice, &ru->openair0_cfg);
AssertFatal(ret == 0, "RU %u: openair0_device_load() ret %d: cannot initialize rfdevice\n", ru->idx, ret);
ret = ru->rfdevice.trx_start_func(&ru->rfdevice);
AssertFatal(ret == 0, "RU %u: trx_start_func() ret %d: cannot start rfdevice\n", ru->idx, ret);
threadCreate(&oru.north_read_thread, oru_north_read_thread, (void *)&oru, "north_read_thread", -1, OAI_PRIORITY_RT_MAX);
threadCreate(&oru.south_read_thread, oru_south_read_thread, (void *)&oru, "south_read_thread", -1, OAI_PRIORITY_RT_MAX);
usleep(1000);
oru_fh_start(oru.fronthaul);
// Signal handler
signal(SIGINT, sig_handler);
while (oai_exit == 0) {
oru_fh_print_stats(oru.fronthaul);
while (oai_exit == 0)
sleep(1);
}
// stop threads
pthread_join(oru.north_read_thread, NULL);
pthread_join(oru.south_read_thread, NULL);
kill_NR_RU_proc(0);
oru_fh_stop(oru.fronthaul);
if (ru->rfdevice.trx_stop_func) {
ru->rfdevice.trx_stop_func(&ru->rfdevice);
ru->rfdevice.trx_stop_func = NULL;
}
end_configmodule(uniqCfg);
if (ru->rfdevice.trx_end_func) {
ru->rfdevice.trx_end_func(&ru->rfdevice);
ru->rfdevice.trx_end_func = NULL;
}
end_configmodule(uniqCfg);
if (ru->ifdevice.trx_end_func) {
ru->ifdevice.trx_end_func(&ru->ifdevice);
ru->ifdevice.trx_end_func = NULL;
}
logClean();
printf("Bye.\n");

View File

@@ -22,6 +22,7 @@
#include "common/utils/load_module_shlib.h"
#include "radio/COMMON/common_lib.h"
#include "radio/ETHERNET/if_defs.h"
#include "PHY/phy_vars.h"

View File

@@ -172,17 +172,13 @@ static void rx_func(processingData_L1_t *info)
if (gNB->phase_comp) {
//apply the rx signal rotation here
int soffset = (slot_rx % RU_RX_SLOT_DEPTH) * gNB->frame_parms.symbols_per_slot * gNB->frame_parms.ofdm_symbol_size;
const NR_DL_FRAME_PARMS *fp = &gNB->frame_parms;
for (int aa = 0; aa < fp->nb_antennas_rx; aa++) {
const uint max_symb = fp->Ncp == NR_EXTENDED ? 12 : 14;
for (int aa = 0; aa < gNB->frame_parms.nb_antennas_tx; aa++) {
const uint max_symb = (gNB->frame_parms.Ncp == NR_EXTENDED) ? 12 : 14;
for (int sym = 0; sym < max_symb; sym++)
apply_nr_rotation_symbol_RX(fp->symbols_per_slot,
fp->slots_per_subframe,
fp->timeshift_symbol_rotation,
fp->first_carrier_offset,
gNB->common_vars.rxdataF[aa] + soffset + sym * fp->ofdm_symbol_size,
fp->symbol_rotation[1],
fp->N_RB_UL,
apply_nr_rotation_symbol_RX(&gNB->frame_parms,
gNB->common_vars.rxdataF[aa] + soffset + sym * gNB->frame_parms.ofdm_symbol_size,
gNB->frame_parms.symbol_rotation[1],
gNB->frame_parms.N_RB_UL,
slot_rx,
sym);
}

View File

@@ -1,760 +0,0 @@
/*
* SPDX-License-Identifier: LicenseRef-CSSL-1.0
*/
#include "common/config/config_userapi.h"
#include "common/utils/system.h"
#include "nr-oru.h"
#include "openair1/PHY/defs_nr_common.h"
#include "PHY/NR_TRANSPORT/nr_transport_proto.h"
#include "oru_packet_processor.h"
#include <time.h>
#include "openair1/PHY/MODULATION/nr_modulation.h"
#include "openair1/SCHED_NR/sched_nr.h"
#include "openair1/PHY/MODULATION/modulation_common.h"
#include "openair2/LAYER2/NR_MAC_COMMON/nr_mac_common.h"
#define CONFIG_SECTION_ORU "ORUs.[0]"
#define CONFIG_STRING_ORU_TX_BW_LIST "tx_bw"
#define CONFIG_STRING_ORU_RX_BW_LIST "rx_bw"
#define CONFIG_STRING_ORU_CARRIER_TX_LIST "carrier_tx"
#define CONFIG_STRING_ORU_CARRIER_RX_LIST "carrier_rx"
#define CONFIG_STRING_ORU_FRAME_TYPE "frame_type"
#define CONFIG_STRING_ORU_PRACH_CONFIGID "prach_config_index"
#define CONFIG_STRING_ORU_PRACH_MSG1FREQ "prach_msg1_start"
#define CONFIG_STRING_ORU_NUMEROLOGY "mu"
#define CONFIG_STRING_ORU_TDD_PERIOD "tdd_period"
#define CONFIG_STRING_ORU_NUM_DL_SLOTS "num_dl_slots"
#define CONFIG_STRING_ORU_NUM_UL_SLOTS "num_ul_slots"
#define CONFIG_STRING_ORU_NUM_DL_SYMBOLS "num_dl_symbols"
#define CONFIG_STRING_ORU_NUM_UL_SYMBOLS "num_ul_symbols"
#define HLP_ORU_TX_BW "set the TX bandwidth list per component carrier"
#define HLP_ORU_RX_BW "set the RX bandwidth list per component carrier"
#define HLP_ORU_CARRIER_TX "set the TX carrier frequencies per component carrier"
#define HLP_ORU_CARRIER_RX "set the RX carrier frequencies per component carrier"
#define HLP_ORU_FRAMETYPE "set the Frame type TDD/FDD of all component carriers"
#define HLP_ORU_PRACH_CONFIGID "set the PRACH configuration id of all component carriers"
#define HLP_ORU_PRACH_MSG1FREQ "set the PRACH MSG1 frequency of all component carriers"
#define HLP_ORU_NUMEROLOGY "set the numerology of the RU"
#define HLP_ORU_TDD_PERIOD "set the 3GPP TDD periodificty 0-9"
#define HLP_ORU_NUM_DL_SLOTS "set the number of DL Slots in TDD"
#define HLP_ORU_NUM_UL_SLOTS "set the number of UL Slots in TDD"
#define HLP_ORU_NUM_DL_SYMBOLS "set the number of DL symbols in the mixed slot"
#define HLP_ORU_NUM_UL_SYMBOLS "set the number of UL symbols in the mixed slot"
// clang-format off
#define CMDLINE_PARAMS_DESC_ORU \
{ \
{CONFIG_STRING_ORU_TX_BW_LIST, HLP_ORU_TX_BW, 0, .iptr=NULL, .defintarrayval=DEFBW, TYPE_INTARRAY, 0}, \
{CONFIG_STRING_ORU_RX_BW_LIST, HLP_ORU_RX_BW, 0, .iptr=NULL, .defintarrayval=DEFBW, TYPE_INTARRAY, 0}, \
{CONFIG_STRING_ORU_CARRIER_TX_LIST, HLP_ORU_CARRIER_TX, 0, .iptr=NULL, .defintarrayval=DEFCARRIER, TYPE_INTARRAY, 0}, \
{CONFIG_STRING_ORU_CARRIER_RX_LIST, HLP_ORU_CARRIER_RX, 0, .iptr=NULL, .defintarrayval=DEFCARRIER, TYPE_INTARRAY, 0}, \
{CONFIG_STRING_ORU_FRAME_TYPE, HLP_ORU_FRAMETYPE, 0, .uptr=NULL, .defintval=1, TYPE_UINT, 0}, \
{CONFIG_STRING_ORU_PRACH_CONFIGID, HLP_ORU_PRACH_CONFIGID, 0, .uptr=NULL, .defintval=152, TYPE_UINT, 0}, \
{CONFIG_STRING_ORU_PRACH_MSG1FREQ, HLP_ORU_PRACH_MSG1FREQ, 0, .uptr=NULL, .defintval=0, TYPE_UINT, 0}, \
{CONFIG_STRING_ORU_NUMEROLOGY, HLP_ORU_NUMEROLOGY, 0, .uptr=NULL, .defintval=1, TYPE_UINT, 0}, \
{CONFIG_STRING_ORU_TDD_PERIOD, HLP_ORU_TDD_PERIOD, 0, .uptr=NULL, .defintval=5, TYPE_UINT, 0}, \
{CONFIG_STRING_ORU_NUM_DL_SLOTS, HLP_ORU_NUM_DL_SLOTS, 0, .uptr=NULL, .defintval=3, TYPE_UINT, 0}, \
{CONFIG_STRING_ORU_NUM_UL_SLOTS, HLP_ORU_NUM_UL_SLOTS, 0, .uptr=NULL, .defintval=1, TYPE_UINT, 0}, \
{CONFIG_STRING_ORU_NUM_DL_SYMBOLS, HLP_ORU_NUM_DL_SYMBOLS, 0, .uptr=NULL, .defintval=7, TYPE_UINT, 0}, \
{CONFIG_STRING_ORU_NUM_UL_SYMBOLS, HLP_ORU_NUM_UL_SYMBOLS, 0, .uptr=NULL, .defintval=3, TYPE_UINT, 0}, \
}
// clang-format on
#define CONFIG_SECTION_ORU_FH "ORUs.[0].fronthaul"
#define CONFIG_STRING_ORU_DPDK_DEVICES "dpdk_devices"
#define CONFIG_STRING_RX_CORE "rx_core"
#define CONFIG_STRING_EXTRA_EAL_ARGS "extra_eal_args"
#define CONFIG_STRING_DU_MAC_ADDRESSES "du_mac_addr"
#define CONFIG_STRING_MTU "mtu"
#define CONFIG_STRING_T2A_UP "T2a_up"
#define CONFIG_STRING_T2A_CP "T2a_cp"
#define CONFIG_STRING_PRACH_EAXC_OFFSET "prach_eaxc_offset"
#define HLP_DPDK_DEVICES "DPDK devices to use for the O-RU."
#define HLP_RX_CORE "The CPU core to be used to deploy dpdk RX worker for O-RU."
#define HLP_EXTRA_EAL_ARGS "Extra arguments passed to RTE_EAL_INIT."
#define HLP_DU_MAC_ADDRESSES "DU MAC addreses, used to prepare Ethernet headers."
#define HLP_MTU "MTU for RX and TX."
#define HLP_PRACH_EAXC_OFFSET "PRACH eAxC offset."
// clang-format off
#define CMDLINE_PARAMS_DESC_ORU_FH \
{ \
{CONFIG_STRING_ORU_DPDK_DEVICES, HLP_DPDK_DEVICES, PARAMFLAG_MANDATORY, .strptr=NULL, .defstrval=NULL, TYPE_STRINGLIST, 0}, \
{CONFIG_STRING_RX_CORE, HLP_RX_CORE, PARAMFLAG_MANDATORY, .iptr=NULL, .defintval=-1, TYPE_INT, 0}, \
{CONFIG_STRING_EXTRA_EAL_ARGS, HLP_EXTRA_EAL_ARGS, 0, .strptr=NULL, .defstrval=NULL, TYPE_STRINGLIST, 0}, \
{CONFIG_STRING_DU_MAC_ADDRESSES, HLP_DU_MAC_ADDRESSES, PARAMFLAG_MANDATORY, .strptr=NULL, .defstrval=NULL, TYPE_STRINGLIST, 0}, \
{CONFIG_STRING_MTU, HLP_MTU, 0, .iptr=NULL, .defintval=9600, TYPE_INT, 0}, \
{CONFIG_STRING_T2A_UP, "", 0, .iptr=NULL, .defintarrayval=NULL, TYPE_INTARRAY, 0}, \
{CONFIG_STRING_T2A_CP, "", 0, .iptr=NULL, .defintarrayval=NULL, TYPE_INTARRAY, 0}, \
{CONFIG_STRING_PRACH_EAXC_OFFSET, HLP_PRACH_EAXC_OFFSET, 0, .iptr=NULL, .defintval=0, TYPE_INT, 0} \
}
// clang-format on
extern void set_scs_parameters(NR_DL_FRAME_PARMS *fp, int mu, int N_RB_DL, int ssb_case);
void tx_rf_symbols(RU_t *ru, int frame, int slot, uint64_t timestamp, int start_symbol, int num_symbols);
void prepare_prach_item(ORU_t *oru)
{
AssertFatal(oru->ru != NULL, "ORU not configured\n");
AssertFatal(oru->ru->nr_frame_parms != NULL, "ORU not configured\n");
NR_DL_FRAME_PARMS *fp = oru->ru->nr_frame_parms;
RU_t *ru = oru->ru;
prach_item_t *prach_item = &oru->prach_item;
prach_item->num_slots = oru->prach_info.format < 4 ? get_long_prach_dur(oru->prach_info.format, fp->numerology_index) : 1;
prach_item->msg1_frequencystart = oru->prach_msg1_freq;
prach_item->mu = fp->numerology_index;
nfapi_nr_config_request_scf_t *cfg = &ru->config;
prach_item->prach_sequence_length = cfg->prach_config.prach_sequence_length.value;
prach_item->restricted_set = 0;
prach_item->numerology_index = fp->numerology_index;
prach_item->nb_rx = ru->nb_rx;
prach_item->rx_prach = &oru->rx_prach;
// Fill PRACH PDU
nfapi_nr_prach_pdu_t *prach_pdu = &prach_item->pdu;
prach_pdu->prach_start_symbol = oru->prach_info.start_symbol;
prach_pdu->num_prach_ocas = 1; // TODO: Hardcoded.
uint16_t format0 = oru->prach_info.format & 0xff;
uint16_t format1 = (oru->prach_info.format >> 8) & 0xff;
if (format1 != 0xff) {
switch (format0) {
case 0xa1:
prach_pdu->prach_format = 11;
break;
case 0xa2:
prach_pdu->prach_format = 12;
break;
case 0xa3:
prach_pdu->prach_format = 13;
break;
default:
AssertFatal(1 == 0, "Only formats A1/B1 A2/B2 A3/B3 are valid for dual format");
}
} else {
switch (format0) {
case 0:
prach_pdu->prach_format = 0;
break;
case 1:
prach_pdu->prach_format = 1;
break;
case 2:
prach_pdu->prach_format = 2;
break;
case 3:
prach_pdu->prach_format = 3;
break;
case 0xa1:
prach_pdu->prach_format = 4;
break;
case 0xa2:
prach_pdu->prach_format = 5;
break;
case 0xa3:
prach_pdu->prach_format = 6;
break;
case 0xb1:
prach_pdu->prach_format = 7;
break;
case 0xb4:
prach_pdu->prach_format = 8;
break;
case 0xc0:
prach_pdu->prach_format = 9;
break;
case 0xc2:
prach_pdu->prach_format = 10;
break;
default:
AssertFatal(1 == 0, "Invalid PRACH format");
}
}
}
int get_oru_options(ORU_t *oru)
{
int DEFBW[] = {273};
int DEFCARRIER[] = {3430560};
paramdef_t param[] = CMDLINE_PARAMS_DESC_ORU;
int nump = sizeofArray(param);
int ret = config_get(config_get_if(), param, nump, CONFIG_SECTION_ORU);
if (ret <= 0) {
LOG_E(NR_PHY, "problem reading section \"%s\"\n", CONFIG_SECTION_ORU);
return -1;
}
for (int i = 0; i < oru->ru->num_bands; i++) {
oru->bw_tx[i] = gpd(param, nump, CONFIG_STRING_ORU_TX_BW_LIST)->iptr[i];
oru->bw_rx[i] = gpd(param, nump, CONFIG_STRING_ORU_RX_BW_LIST)->iptr[i];
oru->carrier_freq_tx[i] = gpd(param, nump, CONFIG_STRING_ORU_CARRIER_TX_LIST)->iptr[i];
oru->carrier_freq_rx[i] = gpd(param, nump, CONFIG_STRING_ORU_CARRIER_RX_LIST)->iptr[i];
}
oru->frame_type = *gpd(param, nump, CONFIG_STRING_ORU_FRAME_TYPE)->iptr;
oru->prach_config_index = *gpd(param, nump, CONFIG_STRING_ORU_PRACH_CONFIGID)->iptr;
oru->prach_msg1_freq = *gpd(param, nump, CONFIG_STRING_ORU_PRACH_MSG1FREQ)->iptr;
oru->numerology = *gpd(param, nump, CONFIG_STRING_ORU_NUMEROLOGY)->iptr;
oru->tdd_period = *gpd(param, nump, CONFIG_STRING_ORU_TDD_PERIOD)->iptr;
oru->num_DL_slots = *gpd(param, nump, CONFIG_STRING_ORU_NUM_DL_SLOTS)->iptr;
oru->num_UL_slots = *gpd(param, nump, CONFIG_STRING_ORU_NUM_UL_SLOTS)->iptr;
oru->num_DL_symbols = *gpd(param, nump, CONFIG_STRING_ORU_NUM_DL_SYMBOLS)->iptr;
oru->num_UL_symbols = *gpd(param, nump, CONFIG_STRING_ORU_NUM_UL_SYMBOLS)->iptr;
paramdef_t fh_param[] = CMDLINE_PARAMS_DESC_ORU_FH;
nump = sizeofArray(fh_param);
oru_fh_config_t *fh_cfg = &oru->fh_config;
ret = config_get(config_get_if(), fh_param, nump, CONFIG_SECTION_ORU_FH);
if (ret <= 0) {
printf("problem reading section \"%s\"\n", CONFIG_SECTION_ORU_FH);
return -1;
}
oru_fh_dpdk_config_t *dpdk_conf = &fh_cfg->dpdk_conf;
int num_dpdk_devices = gpd(fh_param, nump, CONFIG_STRING_ORU_DPDK_DEVICES)->numelt;
dpdk_conf->num_dpdk_devices = num_dpdk_devices;
AssertFatal(num_dpdk_devices > 0 && num_dpdk_devices <= 2,
"Invalid number of DPDK devices (%d). Configure 1 or 2 devices\n",
num_dpdk_devices);
for (int i = 0; i < num_dpdk_devices; i++) {
dpdk_conf->dpdk_devices[i] = gpd(fh_param, nump, CONFIG_STRING_ORU_DPDK_DEVICES)->strlistptr[i];
}
dpdk_conf->extra_eal_args = gpd(fh_param, nump, CONFIG_STRING_EXTRA_EAL_ARGS)->strlistptr;
dpdk_conf->num_extra_eal_args = gpd(fh_param, nump, CONFIG_STRING_EXTRA_EAL_ARGS)->numelt;
fh_cfg->num_du_mac_addrs = gpd(fh_param, nump, CONFIG_STRING_DU_MAC_ADDRESSES)->numelt;
for (int i = 0; i < fh_cfg->num_du_mac_addrs; i++) {
fh_cfg->du_mac_addrs[i] = gpd(fh_param, nump, CONFIG_STRING_DU_MAC_ADDRESSES)->strlistptr[i];
AssertFatal(strlen(fh_cfg->du_mac_addrs[i]) == 17, "Invalid MAC address\n");
}
fh_cfg->enable_compression = false;
fh_cfg->rx_core = *gpd(fh_param, nump, CONFIG_STRING_RX_CORE)->iptr;
fh_cfg->mtu = *gpd(fh_param, nump, CONFIG_STRING_MTU)->iptr;
fh_cfg->num_prbs = oru->bw_tx[0];
fh_cfg->numerology = oru->numerology;
fh_cfg->prach_eaxc_offset = *gpd(fh_param, nump, CONFIG_STRING_PRACH_EAXC_OFFSET)->iptr;
AssertFatal(gpd(fh_param, nump, CONFIG_STRING_T2A_UP)->numelt == 2, "Two parameters required for %s\n", CONFIG_STRING_T2A_UP);
fh_cfg->T2a_up_min_uS = gpd(fh_param, nump, CONFIG_STRING_T2A_UP)->iptr[0];
fh_cfg->T2a_up_max_uS = gpd(fh_param, nump, CONFIG_STRING_T2A_UP)->iptr[1];
AssertFatal(fh_cfg->T2a_up_min_uS <= fh_cfg->T2a_up_max_uS,
"T2a max (%d) has to be greater than T2a min (%d)\n",
fh_cfg->T2a_up_max_uS,
fh_cfg->T2a_up_min_uS);
AssertFatal(gpd(fh_param, nump, CONFIG_STRING_T2A_CP)->numelt == 2, "Two parameters required for %s\n", CONFIG_STRING_T2A_CP);
fh_cfg->T2a_cp_min_uS = gpd(fh_param, nump, CONFIG_STRING_T2A_CP)->iptr[0];
fh_cfg->T2a_cp_max_uS = gpd(fh_param, nump, CONFIG_STRING_T2A_CP)->iptr[1];
AssertFatal(fh_cfg->T2a_cp_min_uS <= fh_cfg->T2a_cp_max_uS,
"T2a max (%d) has to be greater than T2a min (%d)\n",
fh_cfg->T2a_cp_max_uS,
fh_cfg->T2a_cp_min_uS);
oru_fh_tdd_pattern_t *tdd_pattern = &fh_cfg->tdd_pattern;
tdd_pattern->num_dl_slots = oru->num_DL_slots;
tdd_pattern->num_ul_slots = oru->num_UL_slots;
tdd_pattern->num_dl_symbols = oru->num_DL_symbols;
tdd_pattern->num_ul_symbols = oru->num_UL_symbols;
int num_slots_frame = (1 << oru->numerology) * NR_NUMBER_OF_SUBFRAMES_PER_FRAME;
int num_period_frame = get_nb_periods_per_frame(oru->tdd_period);
int num_slots_period = num_slots_frame / num_period_frame;
tdd_pattern->tdd_pattern_length_slots = num_slots_period;
return 0;
}
void oru_init_frame_parms(ORU_t *oru)
{
RU_t *ru = oru->ru;
NR_DL_FRAME_PARMS *fp = ru->nr_frame_parms;
fp->frame_type = oru->frame_type;
ru->config.cell_config.frame_duplex_type.value = oru->frame_type;
ru->config.cell_config.frame_duplex_type.tl.tag = 0x100D;
fp->N_RB_DL = oru->bw_tx[0];
ru->config.ssb_config.scs_common.value = ru->numerology;
ru->config.carrier_config.dl_grid_size[ru->config.ssb_config.scs_common.value].value = oru->bw_tx[0];
fp->N_RB_UL = oru->bw_rx[0];
ru->config.carrier_config.ul_grid_size[ru->config.ssb_config.scs_common.value].value = oru->bw_rx[0];
fp->numerology_index = ru->numerology;
LOG_I(NR_PHY,
"Set RU frame type to %s, N_RB_DL %d, N_RB_UL %d, mu %d\n",
oru->frame_type == TDD ? "TDD" : "FDD",
oru->bw_tx[0],
oru->bw_rx[0],
ru->numerology);
set_scs_parameters(fp, fp->numerology_index, oru->bw_tx[0], 0);
fp->slots_per_frame = 10 * fp->slots_per_subframe;
fp->nb_antennas_rx = ru->nb_rx;
fp->nb_antennas_tx = ru->nb_tx;
fp->symbols_per_slot = 14;
fp->samples_per_subframe_wCP = fp->ofdm_symbol_size * fp->symbols_per_slot * fp->slots_per_subframe;
fp->samples_per_frame_wCP = 10 * fp->samples_per_subframe_wCP;
fp->samples_per_slot_wCP = fp->symbols_per_slot * fp->ofdm_symbol_size;
fp->samples_per_slotN0 = (fp->nb_prefix_samples + fp->ofdm_symbol_size) * fp->symbols_per_slot;
fp->samples_per_slot0 =
fp->nb_prefix_samples0 + ((fp->symbols_per_slot - 1) * fp->nb_prefix_samples) + (fp->symbols_per_slot * fp->ofdm_symbol_size);
fp->samples_per_subframe = (fp->nb_prefix_samples0 + fp->ofdm_symbol_size) * 2
+ (fp->nb_prefix_samples + fp->ofdm_symbol_size) * (fp->symbols_per_slot * fp->slots_per_subframe - 2);
fp->samples_per_frame = 10 * fp->samples_per_subframe;
fp->freq_range = (oru->carrier_freq_tx[0] < 6e6) ? FR1 : FR2;
fp->dl_CarrierFreq = (double)oru->carrier_freq_tx[0] * 1000;
fp->ul_CarrierFreq = (double)oru->carrier_freq_rx[0] * 1000;
fp->Ncp = NORMAL;
fp->ofdm_offset_divisor = 8;
// Split 7.2 parameters
ru->config.prach_config.num_prach_fd_occasions.value = 1;
ru->config.prach_config.prach_ConfigurationIndex.value = oru->prach_config_index;
ru->config.prach_config.prach_ConfigurationIndex.tl.tag = 0x1029;
ru->config.prach_config.num_prach_fd_occasions_list = malloc(sizeof(*ru->config.prach_config.num_prach_fd_occasions_list));
ru->config.prach_config.num_prach_fd_occasions_list[0].k1.value = oru->prach_msg1_freq;
if (ru->config.cell_config.frame_duplex_type.value == 1 /* TDD */) {
ru->config.tdd_table.tdd_period.value = oru->tdd_period;
ru->config.tdd_table.tdd_period.tl.tag = 0x1026;
int numb_slots_frame = (1 << ru->numerology) * NR_NUMBER_OF_SUBFRAMES_PER_FRAME;
int numb_period_frame = get_nb_periods_per_frame(oru->tdd_period);
int numb_slots_period = numb_slots_frame / numb_period_frame;
ru->config.tdd_table.max_tdd_periodicity_list =
malloc(sizeof(*ru->config.tdd_table.max_tdd_periodicity_list) * (numb_slots_frame));
for (int n = 0; n < numb_slots_frame; n++) {
int s = 0;
int p = n % numb_slots_period;
nfapi_nr_max_tdd_periodicity_t *periodicity = &ru->config.tdd_table.max_tdd_periodicity_list[n];
periodicity->max_num_of_symbol_per_slot_list =
malloc(sizeof(*periodicity->max_num_of_symbol_per_slot_list) * NR_SYMBOLS_PER_SLOT);
nfapi_nr_max_num_of_symbol_per_slot_t *symbol_list = periodicity->max_num_of_symbol_per_slot_list;
if (p < oru->num_DL_slots) {
for (s = 0; s < 14; s++)
symbol_list[s].slot_config.value = 0;
} else if (p == oru->num_DL_slots) {
for (s = 0; s < oru->num_DL_symbols; s++)
symbol_list[s].slot_config.value = 0;
for (; s < NR_SYMBOLS_PER_SLOT - oru->num_UL_symbols; s++)
symbol_list[s].slot_config.value = 2;
for (; s < NR_SYMBOLS_PER_SLOT; s++)
symbol_list[s].slot_config.value = 1;
} else {
for (s = 0; s < NR_SYMBOLS_PER_SLOT; s++)
symbol_list[s].slot_config.value = 1;
}
}
}
}
void fft_and_cp_insertion(NR_DL_FRAME_PARMS *fp, c16_t *txdataF, c16_t *txdata, int slot, int symbol)
{
int nb_prefix_samples = fp->nb_prefix_samples;
if (fp->Ncp != 1) {
if (fp->numerology_index != 0) {
if (!(slot % (fp->slots_per_subframe / 2)) && (symbol == 0)) {
nb_prefix_samples = fp->nb_prefix_samples0;
}
} else {
if (symbol % 0x7 == 0) {
nb_prefix_samples = fp->nb_prefix_samples0;
}
}
}
PHY_ofdm_mod((int *)txdataF, (int *)txdata, fp->ofdm_symbol_size, 1, nb_prefix_samples, CYCLIC_PREFIX);
}
static void dl_symbol_process(RU_t *ru, int frame, int slot, int symbol, c16_t **txDataF, int64_t timestamp)
{
NR_DL_FRAME_PARMS *fp = ru->nr_frame_parms;
uint32_t slot_offset = get_samples_slot_timestamp(fp, slot);
uint32_t symbol_offset = get_samples_symbol_timestamp(fp, slot, symbol);
__attribute__((aligned(64))) c16_t txdataF_shifted[fp->ofdm_symbol_size];
memset(txdataF_shifted, 0, sizeof(txdataF_shifted));
c16_t *rotation = fp->symbol_rotation[0] + (slot % fp->slots_per_subframe) * fp->symbols_per_slot + symbol;
for (int aatx = 0; aatx < ru->nb_tx; aatx++) {
// Phase compensation
rotate_cpx_vector(txDataF[aatx], *rotation, txDataF[aatx], fp->N_RB_DL * NR_NB_SC_PER_RB, 15);
// FFT Shift
const int num_samp_half = fp->N_RB_DL * NR_NB_SC_PER_RB / 2;
const int first_carrier_offset = fp->ofdm_symbol_size - num_samp_half;
memcpy(txdataF_shifted + first_carrier_offset, txDataF[aatx], num_samp_half * sizeof(c16_t));
memcpy(txdataF_shifted, txDataF[aatx] + num_samp_half, num_samp_half * sizeof(c16_t));
fft_and_cp_insertion(ru->nr_frame_parms,
txdataF_shifted,
(c16_t *)&ru->common.txdata[aatx][slot_offset + symbol_offset],
slot,
symbol);
}
tx_rf_symbols(ru, frame, slot, timestamp, symbol, 1);
}
void *oru_north_read_thread(void *arg)
{
ORU_t *oru = (ORU_t *)arg;
RU_t *ru = (RU_t *)oru->ru;
NR_DL_FRAME_PARMS *fp = ru->nr_frame_parms;
__attribute__((aligned(64))) c16_t txDataF[ru->nb_tx][fp->N_RB_DL * NR_NB_SC_PER_RB];
memset(txDataF, 0, sizeof(txDataF));
c16_t *txDataF_ptr[ru->nb_tx];
for (int aatx = 0; aatx < ru->nb_tx; aatx++) {
txDataF_ptr[aatx] = txDataF[aatx];
}
uint32_t start_frame, start_slot;
uint64_t start_hyper_frame;
struct timespec utc_anchor_point;
oru_fh_get_utc_anchor_point(oru->fronthaul, &start_hyper_frame, &start_frame, &start_slot, &utc_anchor_point);
AssertFatal(ru->rfdevice.get_timestamp != NULL, "rfdevice has no capability to translate UTC timestamp to sample index\n");
int64_t start_timestamp = ru->rfdevice.get_timestamp(&ru->rfdevice, &utc_anchor_point);
// subtract the start_frame and start_slot from the timestamp simplify calculation below.
start_timestamp -= (start_frame * fp->samples_per_frame + get_samples_slot_timestamp(fp, start_slot));
// Now start_timestamp points to the start sample of the frame 0 slot 0 symbol 0 of hyperframe 0
LOG_A(PHY, "DL thread started: start_timestamp %ld, start_frame %d, start_slot %d\n", start_timestamp, start_frame, start_slot);
while (!oai_exit) {
int frame = -1, slot = -1, symbol = -1;
uint64_t hyper_frame;
int ret = oru_fh_tx_read_symbol(oru->fronthaul, (uint32_t **)txDataF_ptr, ru->nb_tx, &hyper_frame, &frame, &slot, &symbol);
if (ret != 0) {
LOG_E(PHY, "[RU_thread] read data error: frame %d, slot %d, symbol %d\n", frame, slot, symbol);
continue;
}
if (start_hyper_frame > hyper_frame) {
continue;
}
uint64_t num_frames = (hyper_frame - start_hyper_frame) * 1024 + frame;
int64_t timestamp = start_timestamp + num_frames * fp->samples_per_frame + get_samples_slot_timestamp(fp, slot)
+ get_samples_symbol_timestamp(fp, slot, symbol);
if (timestamp < 0) {
continue;
}
dl_symbol_process(ru, frame, slot, symbol, txDataF_ptr, timestamp);
if (frame % 256 == 0 && slot == 0 && symbol == 0) {
LOG_I(PHY, "[RU_thread] read data: frame %d, slot %d, symbol %d\n", frame, slot, symbol);
}
}
return NULL;
}
// Returns PRACH symbol that was received in current frame, slot and symbol.
// If no PRACH symbol was received, returns -1
int get_prach_symbol(ORU_t *oru, int frame, int slot, int symbol, int numerology)
{
uint16_t RA_sfn_index;
AssertFatal(oru->ru->nr_frame_parms->frame_type == TDD, "Only supports TDD\n");
if (get_nr_prach_sched_from_info(oru->prach_info, oru->prach_config_index, frame, slot, numerology, FR1, &RA_sfn_index, true)) {
int format = oru->prach_item.pdu.prach_format;
int start_symbol = oru->prach_item.pdu.prach_start_symbol;
symbol -= start_symbol;
// TODO: Support more PRACH formats
AssertFatal(format == 8, "only support format B4\n");
// TODO: This is not exactly the case but it is correct
if (symbol >= 0 && symbol < 12) {
return symbol;
}
}
return -1;
}
void receive_prach(ORU_t *oru, int frame, int slot, int symbol, int prach_symbol)
{
RU_t *ru = oru->ru;
NR_DL_FRAME_PARMS *fp = ru->nr_frame_parms;
oru->prach_item.frame = frame;
oru->prach_item.slot = slot;
c16_t rxdataF[ru->nb_rx][NR_PRACH_SEQ_LEN_L];
memset(rxdataF, 0, sizeof(rxdataF));
rx_nr_prach_ru_rep(&oru->prach_item,
ru->common.rxdata,
fp,
ru->N_TA_offset,
prach_symbol,
0, // prachOccasion
rxdataF);
c16_t *rxdataF_ptr[ru->nb_rx];
for (int aarx = 0; aarx < ru->nb_rx; aarx++) {
rxdataF_ptr[aarx] = rxdataF[aarx];
}
oru_fh_rx_send_prach(oru->fronthaul, (uint32_t **)rxdataF_ptr, ru->nb_rx, frame, slot, symbol);
}
#define MAX_PENDING_UL_JOBS 64
typedef struct {
ul_job_t job;
bool active;
int symbols_sent;
} ul_pending_t;
static void receive_pusch(ORU_t *oru, int frame, int slot, int symbol, ul_job_t *job)
{
RU_t *ru = oru->ru;
NR_DL_FRAME_PARMS *fp = ru->nr_frame_parms;
int aarx = job->antenna_id;
if (aarx < 0 || aarx >= fp->nb_antennas_rx) {
LOG_W(PHY, "[ORU south] receive_pusch: invalid antenna_id %d\n", aarx);
return;
}
// CP removal + FFT → full ofdm_symbol_size frequency-domain output
c16_t rxdataF_fft[fp->ofdm_symbol_size] __attribute__((aligned(32)));
nr_symbol_fep_ul(fp, (c16_t *)ru->common.rxdata[aarx], rxdataF_fft, symbol, slot, ru->N_TA_offset);
// Phase decompensation (conjugate rotation for UL)
apply_nr_rotation_symbol_RX(fp->symbols_per_slot,
fp->slots_per_subframe,
fp->timeshift_symbol_rotation,
fp->first_carrier_offset,
rxdataF_fft,
fp->symbol_rotation[link_type_ul],
fp->N_RB_UL,
slot,
symbol);
// Inverse FFT shift: split format → contiguous PRB format sent to DU.
// DL TX shift: contiguous[0..N/2-1] → FFT_input[first_carrier_offset..] (negative freqs)
// contiguous[N/2..N-1] → FFT_input[0..N/2-1] (positive freqs)
// UL RX inverse: FFT_out[first_carrier_offset..] → contiguous[0..N/2-1]
// FFT_out[0..N/2-1] → contiguous[N/2..N-1]
const int num_samp_half = fp->N_RB_UL * NR_NB_SC_PER_RB / 2;
const int first_carrier_offset = fp->ofdm_symbol_size - num_samp_half;
c16_t rxdataF[fp->N_RB_UL * NR_NB_SC_PER_RB];
memcpy(rxdataF, rxdataF_fft + first_carrier_offset, num_samp_half * sizeof(c16_t));
memcpy(rxdataF + num_samp_half, rxdataF_fft, num_samp_half * sizeof(c16_t));
oru_fh_rx_send_pusch(oru->fronthaul, (uint32_t *)rxdataF, symbol, job);
}
#define UL_WORK_QUEUE_DEPTH 128
typedef struct {
ORU_t *oru;
int frame;
int slot;
int symbol;
ul_job_t job;
} ul_work_item_t;
typedef struct {
ul_work_item_t ring[UL_WORK_QUEUE_DEPTH];
int head;
int tail;
int count;
pthread_mutex_t lock;
pthread_cond_t work_available;
pthread_cond_t space_available;
bool running;
} ul_work_queue_t;
static void *ul_worker_thread(void *arg)
{
ul_work_queue_t *q = arg;
while (1) {
pthread_mutex_lock(&q->lock);
while (q->count == 0 && q->running)
pthread_cond_wait(&q->work_available, &q->lock);
if (!q->running && q->count == 0) {
pthread_mutex_unlock(&q->lock);
break;
}
ul_work_item_t item = q->ring[q->head];
q->head = (q->head + 1) % UL_WORK_QUEUE_DEPTH;
q->count--;
pthread_cond_signal(&q->space_available);
pthread_mutex_unlock(&q->lock);
receive_pusch(item.oru, item.frame, item.slot, item.symbol, &item.job);
}
return NULL;
}
static void dispatch_ul_work(ul_work_queue_t *q, ORU_t *oru, int frame, int slot, int symbol, const ul_job_t *job)
{
pthread_mutex_lock(&q->lock);
while (q->count == UL_WORK_QUEUE_DEPTH)
pthread_cond_wait(&q->space_available, &q->lock);
q->ring[q->tail] = (ul_work_item_t){.oru = oru, .frame = frame, .slot = slot, .symbol = symbol, .job = *job};
q->tail = (q->tail + 1) % UL_WORK_QUEUE_DEPTH;
q->count++;
pthread_cond_signal(&q->work_available);
pthread_mutex_unlock(&q->lock);
}
void *oru_south_read_thread(void *arg)
{
ORU_t *oru = arg;
RU_t *ru = oru->ru;
NR_DL_FRAME_PARMS *fp = ru->nr_frame_parms;
struct timespec utc_anchor_point;
AssertFatal(ru->rfdevice.get_timestamp != NULL, "rfdevice has no capability to translate UTC timestamp to sample index\n");
uint32_t start_frame, start_slot;
uint64_t hyper_frame;
oru_fh_get_utc_anchor_point(oru->fronthaul, &hyper_frame, &start_frame, &start_slot, &utc_anchor_point);
int64_t start_timestamp = ru->rfdevice.get_timestamp(&ru->rfdevice, &utc_anchor_point);
const int num_samples = 3000;
c16_t throwaway_samples[ru->nb_rx][num_samples];
void *rxp[ru->nb_rx];
for (int i = 0; i < ru->nb_rx; i++)
rxp[i] = throwaway_samples[i];
openair0_timestamp_t timestamp;
int num_samples_read = ru->rfdevice.trx_read_func(&ru->rfdevice, &timestamp, rxp, num_samples, ru->nb_rx);
AssertFatal(num_samples_read == num_samples, "Unexpected number of samples received\n");
openair0_timestamp_t next_timestamp = timestamp + num_samples_read;
while (next_timestamp > start_timestamp) {
start_timestamp += get_samples_slot_duration(fp, start_slot, 1);
start_slot++;
if (start_slot == fp->slots_per_frame) {
start_slot = 0;
start_frame++;
if (start_frame == 1024) {
start_frame = 0;
}
}
}
while (next_timestamp < start_timestamp) {
int num_samples_to_read = min(num_samples, (int)(start_timestamp - next_timestamp));
int num_samples_read = ru->rfdevice.trx_read_func(&ru->rfdevice, &timestamp, rxp, num_samples_to_read, ru->nb_rx);
AssertFatal(num_samples_read == num_samples_to_read, "Unexpected number of samples received\n");
next_timestamp += num_samples_read;
}
AssertFatal(next_timestamp == start_timestamp, "O-RU South thread could not sync to UTC anchor point\n");
int slot = start_slot;
int frame = start_frame;
// Worker pool: one thread per RX antenna so all antennas in a symbol process in parallel.
const int num_workers = ru->nb_rx;
pthread_t workers[num_workers];
ul_work_queue_t work_queue = {
.lock = PTHREAD_MUTEX_INITIALIZER,
.work_available = PTHREAD_COND_INITIALIZER,
.space_available = PTHREAD_COND_INITIALIZER,
.running = true,
};
for (int i = 0; i < num_workers; i++) {
char name[32];
snprintf(name, sizeof(name), "ul_worker_%d", i);
threadCreate(&workers[i], ul_worker_thread, &work_queue, name, -1, OAI_PRIORITY_RT_MAX);
}
ul_pending_t pending_ul[MAX_PENDING_UL_JOBS] = {0};
while (!oai_exit) {
int rx_slot_type = nr_slot_select(&ru->config, frame, slot);
for (int symbol = 0; symbol < 14; symbol++) {
int samples_to_read = get_samples_symbol_duration(fp, slot, symbol, 1);
size_t offset = get_samples_slot_timestamp(fp, slot) + get_samples_symbol_timestamp(fp, slot, symbol);
c16_t *rxp[fp->nb_antennas_rx];
for (int aarx = 0; aarx < fp->nb_antennas_rx; aarx++) {
rxp[aarx] = (c16_t *)&ru->common.rxdata[aarx][offset];
}
openair0_timestamp_t timestamp;
int num_samples_read = ru->rfdevice.trx_read_func(&ru->rfdevice, &timestamp, (void **)rxp, samples_to_read, ru->nb_rx);
AssertFatal(num_samples_read == samples_to_read, "Unexpected number of samples received\n");
LOG_D(PHY,
"[ORU south] read data: frame %d, slot %d, symbol %d, timestamp %ld num_symbols %d, samples %d\n",
frame,
slot,
symbol,
timestamp,
1,
num_samples_read);
// Drain the UL job ring
ul_job_t new_job;
while (oru_fh_poll_ul_job(oru->fronthaul, &new_job) == 0) {
bool added = false;
for (int i = 0; i < MAX_PENDING_UL_JOBS; i++) {
if (!pending_ul[i].active) {
pending_ul[i] = (ul_pending_t){.job = new_job, .active = true, .symbols_sent = 0};
added = true;
break;
}
}
if (!added)
LOG_W(PHY, "[ORU south] UL pending queue full, dropping job frame=%d slot=%d sym=%d\n",
new_job.frame, new_job.slot_in_frame, new_job.symbol);
}
if (rx_slot_type == NR_UPLINK_SLOT || rx_slot_type == NR_MIXED_SLOT) {
// Process pending jobs whose next symbol matches the current one
for (int i = 0; i < MAX_PENDING_UL_JOBS; i++) {
if (!pending_ul[i].active)
continue;
ul_job_t *j = &pending_ul[i].job;
// Skip jobs scheduled for a future slot or frame, and drop jobs in the past
int slots_per_frame = fp->slots_per_frame;
int total_slots = 1024 * slots_per_frame;
int diff_slots = (j->frame * slots_per_frame + j->slot_in_frame) - (frame * slots_per_frame + slot);
if (diff_slots < -total_slots / 2) {
diff_slots += total_slots;
} else if (diff_slots > total_slots / 2) {
diff_slots -= total_slots;
}
if (diff_slots > 0) {
// Future slot: skip
continue;
}
if (diff_slots < 0) {
// Past slot: drop
LOG_W(PHY, "[ORU south] missed UL slot %d.%d (now %d.%d), dropping job ant=%d\n",
j->frame, j->slot_in_frame, frame, slot, j->antenna_id);
pending_ul[i].active = false;
continue;
}
// Same slot: check symbol
int expected_symbol = j->symbol + pending_ul[i].symbols_sent;
if (expected_symbol < symbol) {
LOG_W(PHY, "[ORU south] missed UL symbol %d (now %d), dropping job ant=%d\n",
expected_symbol, symbol, j->antenna_id);
pending_ul[i].active = false;
} else if (expected_symbol == symbol) {
dispatch_ul_work(&work_queue, oru, frame, slot, symbol, j);
if (++pending_ul[i].symbols_sent == j->num_symbols)
pending_ul[i].active = false;
}
// expected_symbol > symbol: job spans multiple symbols, revisit next iteration
}
}
int prach_symbol = get_prach_symbol(oru, frame, slot, symbol, ru->numerology);
if (prach_symbol != -1)
receive_prach(oru, frame, slot, symbol, prach_symbol);
}
slot++;
if (slot == fp->slots_per_frame) {
slot = 0;
frame++;
if (frame == 1024) {
frame = 0;
}
}
}
pthread_mutex_lock(&work_queue.lock);
work_queue.running = false;
pthread_cond_broadcast(&work_queue.work_available);
pthread_mutex_unlock(&work_queue.lock);
for (int i = 0; i < num_workers; i++)
pthread_join(workers[i], NULL);
pthread_cond_destroy(&work_queue.work_available);
pthread_cond_destroy(&work_queue.space_available);
pthread_mutex_destroy(&work_queue.lock);
return NULL;
}

View File

@@ -1,59 +0,0 @@
/*
* SPDX-License-Identifier: LicenseRef-CSSL-1.0
*/
#ifndef __NR_ORU_H__
#define __NR_ORU_H__
#include "openair1/PHY/defs_RU.h"
#include <pthread.h>
#include "oru_fh.h"
#include "openair2/LAYER2/NR_MAC_COMMON/nr_prach_config.h"
#include "openair1/PHY/defs_gNB.h"
typedef struct {
RU_t *ru;
/// tx carrier
uint64_t carrier_freq_tx[MAX_BANDS_PER_RRU];
/// rx carrier
uint64_t carrier_freq_rx[MAX_BANDS_PER_RRU];
/// tx BW in PRBs
int bw_tx[MAX_BANDS_PER_RRU];
/// rx BW in PRBs
int bw_rx[MAX_BANDS_PER_RRU];
/// 3GPP FRAME Type FDD/TDD
int frame_type;
/// 3GPP PRACH configuration index
int prach_config_index;
/// 3GPP MSG1 Start frequency
int prach_msg1_freq;
/// 3GPP TDD periodicity (0.5 ms, 1 0.625ms, 2 1ms, 3 1.25ms, 4 2ms,5 2.5ms, 6 5ms, 7 10ms, 8 3ms, 9 4ms
int tdd_period;
/// number of DL slots
int num_DL_slots;
/// number of UL slots
int num_UL_slots;
/// number of DL symbols
int num_DL_symbols;
/// number of UL symbols
int num_UL_symbols;
int numerology;
pthread_t north_read_thread;
pthread_t south_read_thread;
oru_fh_config_t fh_config;
void *fronthaul;
// PRACH related
nr_prach_info_t prach_info;
time_stats_t rx_prach;
time_stats_t rx;
prach_item_t prach_item;
} ORU_t;
int get_oru_options(ORU_t *oru);
void oru_init_frame_parms(ORU_t *oru);
void *oru_north_read_thread(void *arg);
void *oru_south_read_thread(void *arg);
void prepare_prach_item(ORU_t *oru);
#endif

View File

@@ -18,8 +18,11 @@
#include "common/utils/fsn.h"
#include "common/ran_context.h"
#include "radio/COMMON/common_lib.h"
#include "radio/ETHERNET/ethernet_lib.h"
#include "PHY/if4_tools.h"
#include "PHY/defs_nr_common.h"
#include "PHY/phy_extern.h"
#include "PHY/NR_TRANSPORT/nr_transport_proto.h"
@@ -73,6 +76,15 @@ void fh_if5_south_out(RU_t *ru, int frame, int slot, uint64_t timestamp)
ru->ifdevice.trx_write_func2(&ru->ifdevice, timestamp, buffs, 0, get_samples_per_slot(slot, ru->nr_frame_parms), 0, ru->nb_tx);
}
// southbound IF4p5 fronthaul
void fh_if4p5_south_out(RU_t *ru, int frame, int slot, uint64_t timestamp)
{
LOG_D(PHY,"Sending IF4p5 for frame %d subframe %d\n",ru->proc.frame_tx,ru->proc.tti_tx);
if ((nr_slot_select(&ru->config, ru->proc.frame_tx, ru->proc.tti_tx) & NR_DOWNLINK_SLOT) > 0)
send_IF4p5(ru,frame, slot, IF4p5_PDLFFT);
}
/*************************************************************/
/* Input Fronthaul from south RCC/RAU */
@@ -136,6 +148,196 @@ void fh_if5_south_in(RU_t *ru, int *frame, int *tti)
rxmeas.tv_nsec);
}
// Synchronous if4p5 from south
void fh_if4p5_south_in(RU_t *ru,
int *frame,
int *slot) {
NR_DL_FRAME_PARMS *fp = ru->nr_frame_parms;
RU_proc_t *proc = &ru->proc;
int f,sl;
uint16_t packet_type;
uint32_t symbol_number=0;
uint32_t symbol_mask_full=0;
do { // Blocking, we need a timeout on this !!!!!!!!!!!!!!!!!!!!!!!
recv_IF4p5(ru, &f, &sl, &packet_type, &symbol_number);
if (packet_type == IF4p5_PULFFT) proc->symbol_mask[sl] = proc->symbol_mask[sl] | (1<<symbol_number);
else if (packet_type == IF4p5_PULTICK) {
if ((proc->first_rx == 0) && (f != *frame))
LOG_E(PHY, "rx_fh_if4p5: PULTICK received frame %d != expected %d\n", f, *frame);
if ((proc->first_rx == 0) && (sl != *slot))
LOG_E(PHY, "rx_fh_if4p5: PULTICK received subframe %d != expected %d (first_rx %d)\n", sl, *slot, proc->first_rx);
break;
} else if (packet_type == IF4p5_PRACH) {
// nothing in RU for RAU
}
LOG_D(PHY,"rx_fh_if4p5: subframe %d symbol mask %x\n",*slot,proc->symbol_mask[sl]);
} while(proc->symbol_mask[sl] != symbol_mask_full);
//caculate timestamp_rx, timestamp_tx based on frame and subframe
proc->tti_rx = sl;
proc->frame_rx = f;
proc->timestamp_rx = (proc->frame_rx * fp->samples_per_subframe * 10) + get_samples_slot_timestamp(fp, proc->tti_rx);
// proc->timestamp_tx = proc->timestamp_rx + (4*fp->samples_per_subframe);
proc->tti_tx = (sl+ru->sl_ahead)%fp->slots_per_frame;
proc->frame_tx = (sl > (fp->slots_per_frame - 1 - (ru->sl_ahead))) ? (f + 1) & 1023 : f;
if (proc->first_rx == 0) {
if (proc->tti_rx != *slot) {
LOG_E(PHY,"Received Timestamp (IF4p5) doesn't correspond to the time we think it is (proc->tti_rx %d, subframe %d)\n",proc->tti_rx,*slot);
exit_fun("Exiting");
}
if (proc->frame_rx != *frame) {
LOG_E(PHY,"Received Timestamp (IF4p5) doesn't correspond to the time we think it is (proc->frame_rx %d frame %d)\n",proc->frame_rx,*frame);
exit_fun("Exiting");
}
} else {
proc->first_rx = 0;
*frame = proc->frame_rx;
*slot = proc->tti_rx;
}
proc->symbol_mask[proc->tti_rx] = 0;
LOG_D(PHY,"RU %d: fh_if4p5_south_in sleeping ...\n",ru->idx);
}
// asynchronous inbound if4p5 fronthaul from south
void fh_if4p5_south_asynch_in(RU_t *ru,int *frame,int *slot) {
NR_DL_FRAME_PARMS *fp = ru->nr_frame_parms;
RU_proc_t *proc = &ru->proc;
uint16_t packet_type;
uint32_t symbol_number = 0;
uint32_t symbol_mask = (1 << fp->symbols_per_slot) - 1;
uint32_t prach_rx = 0;
do { // Blocking, we need a timeout on this !!!!!!!!!!!!!!!!!!!!!!!
recv_IF4p5(ru, &proc->frame_rx, &proc->tti_rx, &packet_type, &symbol_number);
if (proc->first_rx != 0) {
*frame = proc->frame_rx;
*slot = proc->tti_rx;
proc->first_rx = 0;
} else {
if (proc->frame_rx != *frame) {
LOG_E(PHY,"frame_rx %d is not what we expect %d\n",proc->frame_rx,*frame);
exit_fun("Exiting");
}
if (proc->tti_rx != *slot) {
LOG_E(PHY,"tti_rx %d is not what we expect %d\n",proc->tti_rx,*slot);
exit_fun("Exiting");
}
}
if (packet_type == IF4p5_PULFFT)
symbol_mask &= ~(1 << symbol_number);
else if (packet_type == IF4p5_PRACH)
prach_rx &= ~0x1;
} while (symbol_mask > 0 || prach_rx > 0); // haven't received all PUSCH symbols and PRACH information
}
/*************************************************************/
/* Input Fronthaul from North RRU */
// RRU IF4p5 TX fronthaul receiver. Assumes an if_device on input and if or rf device on output
// receives one subframe's worth of IF4p5 OFDM symbols and OFDM modulates
void fh_if4p5_north_in(RU_t *ru,int *frame,int *slot)
{
uint32_t symbol_number=0;
uint32_t symbol_mask, symbol_mask_full;
uint16_t packet_type;
/// **** incoming IF4p5 from remote RCC/RAU **** ///
symbol_number = 0;
symbol_mask = 0;
symbol_mask_full = (1<<(ru->nr_frame_parms->symbols_per_slot))-1;
do {
recv_IF4p5(ru, frame, slot, &packet_type, &symbol_number);
symbol_mask = symbol_mask | (1<<symbol_number);
} while (symbol_mask != symbol_mask_full);
}
void fh_if5_north_asynch_in(RU_t *ru, int *frame, int *slot)
{
AssertFatal(1 == 0, "Shouldn't get here\n");
}
void fh_if4p5_north_asynch_in(RU_t *ru,int *frame,int *slot) {
NR_DL_FRAME_PARMS *fp = ru->nr_frame_parms;
nfapi_nr_config_request_scf_t *cfg = &ru->config;
RU_proc_t *proc = &ru->proc;
uint16_t packet_type;
uint32_t symbol_mask_full = 0;
int slot_tx,frame_tx;
LOG_D(PHY, "%s(ru:%p frame, subframe)\n", __FUNCTION__, ru);
uint32_t symbol_number = 0;
uint32_t symbol_mask = 0;
// symbol_mask_full = ((subframe_select(fp,*slot) == SF_S) ? (1<<fp->dl_symbols_in_S_subframe) : (1<<fp->symbols_per_slot))-1;
do {
recv_IF4p5(ru, &frame_tx, &slot_tx, &packet_type, &symbol_number);
if (((nr_slot_select(cfg, frame_tx, slot_tx) & NR_DOWNLINK_SLOT) > 0) && (symbol_number == 0))
start_meas(&ru->rx_fhaul);
LOG_D(PHY,"slot %d (%d): frame %d, slot %d, symbol %d\n",
*slot,nr_slot_select(cfg,frame_tx,*slot),frame_tx,slot_tx,symbol_number);
if (proc->first_tx != 0) {
*frame = frame_tx;
*slot = slot_tx;
proc->first_tx = 0;
} else {
AssertFatal(frame_tx == *frame,
"frame_tx %d is not what we expect %d\n",frame_tx,*frame);
AssertFatal(slot_tx == *slot,
"slot_tx %d is not what we expect %d\n",slot_tx,*slot);
}
if (packet_type == IF4p5_PDLFFT) {
symbol_mask = symbol_mask | (1<<symbol_number);
} else
AssertFatal(false, "Illegal IF4p5 packet type (should only be IF4p5_PDLFFT%d\n", packet_type);
} while (symbol_mask != symbol_mask_full);
if ((nr_slot_select(cfg, frame_tx, slot_tx) & NR_DOWNLINK_SLOT) > 0)
stop_meas(&ru->rx_fhaul);
proc->tti_tx = slot_tx;
proc->frame_tx = frame_tx;
if (frame_tx == 0 && slot_tx == 0)
proc->frame_tx_unwrap += 1024;
proc->timestamp_tx =
((uint64_t)frame_tx + proc->frame_tx_unwrap) * fp->samples_per_subframe * 10 + get_samples_slot_timestamp(fp, slot_tx);
LOG_D(PHY, "RU %d/%d TST %lu, frame %d, subframe %d\n", ru->idx, 0, proc->timestamp_tx, frame_tx, slot_tx);
if (ru->feptx_ofdm)
ru->feptx_ofdm(ru, frame_tx, slot_tx);
if (ru->fh_south_out)
ru->fh_south_out(ru, frame_tx, slot_tx, proc->timestamp_tx);
}
void fh_if5_north_out(RU_t *ru)
{
/// **** send_IF5 of rxdata to BBU **** ///
AssertFatal(1 == 0, "Shouldn't get here\n");
}
// RRU IF4p5 northbound interface (RX)
void fh_if4p5_north_out(RU_t *ru)
{
RU_proc_t *proc=&ru->proc;
start_meas(&ru->tx_fhaul);
send_IF4p5(ru, proc->frame_rx, proc->tti_rx, IF4p5_PULFFT);
stop_meas(&ru->tx_fhaul);
}
static void rx_rf(RU_t *ru, int *frame, int *slot)
{
RU_proc_t *proc = &ru->proc;
@@ -292,7 +494,7 @@ static radio_tx_gpio_flag_t get_gpio_flags(RU_t *ru, int slot)
return flags_gpio;
}
void tx_rf_symbols(RU_t *ru, int frame, int slot, uint64_t timestamp, int start_symbol, int num_symbols)
void tx_rf(RU_t *ru, int frame,int slot, uint64_t timestamp)
{
RU_proc_t *proc = &ru->proc;
NR_DL_FRAME_PARMS *fp = ru->nr_frame_parms;
@@ -319,23 +521,22 @@ void tx_rf_symbols(RU_t *ru, int frame, int slot, uint64_t timestamp, int start_
txsymb++;
}
AssertFatal(txsymb > 0, "illegal txsymb %d\n", txsymb);
AssertFatal(txsymb>0,"illegal txsymb %d\n",txsymb);
if (txsymb < start_symbol) {
// No DL symbols in this transmission
return;
}
int end_symbol = start_symbol + num_symbols - 1;
if (end_symbol >= txsymb) {
flags_burst = TX_BURST_END;
if (fp->slots_per_subframe == 1) {
if (txsymb <= 7)
siglen = (fp->ofdm_symbol_size + fp->nb_prefix_samples0) + (txsymb - 1) * (fp->ofdm_symbol_size + fp->nb_prefix_samples);
else
siglen = 2 * (fp->ofdm_symbol_size + fp->nb_prefix_samples0) + (txsymb - 2) * (fp->ofdm_symbol_size + fp->nb_prefix_samples);
} else {
flags_burst = TX_BURST_MIDDLE;
if(slot%(fp->slots_per_subframe/2))
siglen = txsymb * (fp->ofdm_symbol_size + fp->nb_prefix_samples);
else
siglen = (fp->ofdm_symbol_size + fp->nb_prefix_samples0) + (txsymb - 1) * (fp->ofdm_symbol_size + fp->nb_prefix_samples);
}
int num_symbols_this_transmission = min(txsymb, end_symbol) - start_symbol + 1;
siglen = get_samples_symbol_duration(fp, slot, start_symbol, num_symbols_this_transmission);
//+ ru->end_of_burst_delay;
flags_burst = TX_BURST_END;
} else if (slot_type == NR_DOWNLINK_SLOT) {
int prevslot_type = nr_slot_select(cfg,frame,(slot+(fp->slots_per_frame-1))%fp->slots_per_frame);
int nextslot_type = nr_slot_select(cfg,frame,(slot+1)%fp->slots_per_frame);
@@ -347,11 +548,9 @@ void tx_rf_symbols(RU_t *ru, int frame, int slot, uint64_t timestamp, int start_
} else {
flags_burst = proc->first_tx == 1 ? TX_BURST_START : TX_BURST_MIDDLE;
}
siglen = get_samples_symbol_duration(fp, slot, start_symbol, num_symbols);
}
} else { // FDD
flags_burst = proc->first_tx == 1 ? TX_BURST_START : TX_BURST_MIDDLE;
siglen = get_samples_symbol_duration(fp, slot, start_symbol, num_symbols);
}
if (ru->openair0_cfg.gpio_controller != RU_GPIO_CONTROL_NONE)
@@ -362,9 +561,8 @@ void tx_rf_symbols(RU_t *ru, int frame, int slot, uint64_t timestamp, int start_
int nt = ru->nb_tx;
void *txp[nt];
uint32_t time_offset = get_samples_slot_timestamp(fp, slot) + get_samples_symbol_timestamp(fp, slot, start_symbol);
for (int i = 0; i < nt; i++)
txp[i] = (void *)&ru->common.txdata[i][time_offset] - sf_extension * sizeof(int32_t);
txp[i] = (void *)&ru->common.txdata[i][get_samples_slot_timestamp(fp, slot)] - sf_extension * sizeof(int32_t);
// prepare tx buffer pointers
uint32_t txs = ru->rfdevice.trx_write_func(&ru->rfdevice,
@@ -388,12 +586,7 @@ void tx_rf_symbols(RU_t *ru, int frame, int slot, uint64_t timestamp, int start_
10 * log10((double)signal_energy(txp[0], siglen + sf_extension)));
}
void tx_rf(RU_t *ru, int frame, int slot, uint64_t timestamp)
{
tx_rf_symbols(ru, frame, slot, timestamp, 0, 14);
}
void fill_rf_config(RU_t *ru, char *rf_config_file)
static void fill_rf_config(RU_t *ru, char *rf_config_file)
{
NR_DL_FRAME_PARMS *fp = ru->nr_frame_parms;
nfapi_nr_config_request_scf_t *config = &ru->config; //tmp index
@@ -456,7 +649,7 @@ void fill_rf_config(RU_t *ru, char *rf_config_file)
}
}
void fill_split7_2_config(split7_config_t *split7, const nfapi_nr_config_request_scf_t *config, const NR_DL_FRAME_PARMS *fp)
static void fill_split7_2_config(split7_config_t *split7, const nfapi_nr_config_request_scf_t *config, const NR_DL_FRAME_PARMS *fp)
{
const nfapi_nr_prach_config_t *prach_config = &config->prach_config;
const nfapi_nr_tdd_table_t *tdd_table = &config->tdd_table;
@@ -640,7 +833,7 @@ void *ru_thread(void *param)
ret = openair0_transport_load(&ru->ifdevice, &ru->openair0_cfg, &ru->eth_params);
AssertFatal(ret == 0, "RU %u: openair0_transport_init() ret %d: cannot initialize transport protocol\n", ru->idx, ret);
if (ru->ifdevice.get_internal_parameter) {
if (ru->ifdevice.get_internal_parameter != NULL) {
/* it seems the device can "overwrite" (request?) to set the callbacks
* for fh_south_in()/fh_south_out() differently */
void *t = ru->ifdevice.get_internal_parameter("fh_if4p5_south_in");
@@ -649,6 +842,8 @@ void *ru_thread(void *param)
t = ru->ifdevice.get_internal_parameter("fh_if4p5_south_out");
if (t != NULL)
ru->fh_south_out = t;
} else {
malloc_IF4p5_buffer(ru);
}
int cpu = sched_getcpu();
@@ -893,59 +1088,94 @@ void set_function_spec_param(RU_t *ru)
switch (ru->if_south) {
case LOCAL_RF: // this is an RU with integrated RF (RRU, gNB)
reset_meas(&ru->rx_fhaul);
AssertFatal(ru->function == gNodeB_3GPP, "ru->function %d not supported for LOCAL_RF\n", ru->function);
ru->do_prach = 0; // no prach processing in RU
ru->feprx = nr_fep_tp; // this is frequency-shift + DFTs
ru->feptx_ofdm = nr_feptx_tp; // this is fep with idft and precoding
ru->feptx_prec = NULL;
ru->fh_north_in = NULL; // no incoming fronthaul from north
ru->fh_north_out = NULL; // no outgoing fronthaul to north
ru->nr_start_if = NULL; // no if interface
ru->rfdevice.host_type = RAU_HOST;
ru->fh_south_in = rx_rf; // local synchronous RF RX
ru->fh_south_out = tx_rf; // local synchronous RF TX
ru->start_rf = start_rf; // need to start the local RF interface
ru->stop_rf = stop_rf;
ru->start_write_thread = start_write_thread; // starting RF TX in different thread
if (ru->function == NGFI_RRU_IF5) { // IF5 RRU
ru->do_prach = 0; // no prach processing in RU
ru->fh_north_in = NULL; // no shynchronous incoming fronthaul from north
ru->fh_north_out = fh_if5_north_out; // need only to do send_IF5 reception
ru->fh_south_out = tx_rf; // send output to RF
ru->fh_north_asynch_in = fh_if5_north_asynch_in; // TX packets come asynchronously
ru->feprx = NULL; // nothing (this is a time-domain signal)
ru->feptx_ofdm = NULL; // nothing (this is a time-domain signal)
ru->feptx_prec = NULL; // nothing (this is a time-domain signal)
ru->nr_start_if = nr_start_if; // need to start the if interface for if5
ru->ifdevice.host_type = RRU_HOST;
ru->rfdevice.host_type = RRU_HOST;
ru->ifdevice.eth_params = &ru->eth_params;
reset_meas(&ru->rx_fhaul);
reset_meas(&ru->tx_fhaul);
reset_meas(&ru->compression);
reset_meas(&ru->transport);
} else if (ru->function == NGFI_RRU_IF4p5) {
ru->do_prach = 1; // do part of prach processing in RU
ru->fh_north_in = NULL; // no synchronous incoming fronthaul from north
ru->fh_north_out = fh_if4p5_north_out; // send_IF4p5 on reception
ru->fh_south_out = tx_rf; // send output to RF
ru->fh_north_asynch_in = fh_if4p5_north_asynch_in; // TX packets come asynchronously
ru->feprx = nr_fep_tp; // this is frequency-shift + DFTs
ru->feptx_ofdm = nr_feptx_tp; // this is fep with idft only (no precoding in RRU)
ru->feptx_prec = NULL;
ru->nr_start_if = nr_start_if; // need to start the if interface for if4p5
ru->ifdevice.host_type = RRU_HOST;
ru->rfdevice.host_type = RRU_HOST;
ru->ifdevice.eth_params = &ru->eth_params;
reset_meas(&ru->tx_fhaul);
reset_meas(&ru->compression);
reset_meas(&ru->transport);
} else if (ru->function == gNodeB_3GPP) {
ru->do_prach = 0; // no prach processing in RU
ru->feprx = nr_fep_tp; // this is frequency-shift + DFTs
ru->feptx_ofdm = nr_feptx_tp; // this is fep with idft and precoding
ru->feptx_prec = NULL;
ru->fh_north_in = NULL; // no incoming fronthaul from north
ru->fh_north_out = NULL; // no outgoing fronthaul to north
ru->nr_start_if = NULL; // no if interface
ru->rfdevice.host_type = RAU_HOST;
ru->fh_south_in = rx_rf; // local synchronous RF RX
ru->fh_south_out = tx_rf; // local synchronous RF TX
ru->start_rf = start_rf; // need to start the local RF interface
ru->stop_rf = stop_rf;
ru->start_write_thread = start_write_thread; // starting RF TX in different thread
}
break;
case REMOTE_IF5: // the remote unit is IF5 RRU
ru->do_prach = 0;
ru->txfh_in_fep = 0;
ru->feprx = nr_fep_tp; // this is frequency-shift + DFTs
ru->feptx_prec = NULL; // need to do transmit Precoding + IDFTs
ru->feptx_ofdm = nr_feptx_tp; // need to do transmit Precoding + IDFTs
ru->fh_south_in = fh_if5_south_in; // synchronous IF5 reception
ru->fh_south_out = (ru->txfh_in_fep > 0) ? NULL : fh_if5_south_out; // synchronous IF5 transmission
ru->fh_south_asynch_in = NULL; // no asynchronous UL
ru->start_rf = ru->eth_params.transp_preference == ETH_UDP_IF5_ECPRI_MODE ? start_streaming : NULL;
ru->stop_rf = NULL;
ru->start_write_thread = NULL;
ru->nr_start_if = nr_start_if; // need to start if interface for IF5
ru->ifdevice.host_type = RAU_HOST;
ru->ifdevice.eth_params = &ru->eth_params;
ru->do_prach = 0;
ru->txfh_in_fep = 0;
ru->feprx = nr_fep_tp; // this is frequency-shift + DFTs
ru->feptx_prec = NULL; // need to do transmit Precoding + IDFTs
ru->feptx_ofdm = nr_feptx_tp; // need to do transmit Precoding + IDFTs
ru->fh_south_in = fh_if5_south_in; // synchronous IF5 reception
ru->fh_south_out = (ru->txfh_in_fep>0) ? NULL : fh_if5_south_out; // synchronous IF5 transmission
ru->fh_south_asynch_in = NULL; // no asynchronous UL
ru->start_rf = ru->eth_params.transp_preference == ETH_UDP_IF5_ECPRI_MODE ? start_streaming : NULL;
ru->stop_rf = NULL;
ru->start_write_thread = NULL;
ru->nr_start_if = nr_start_if; // need to start if interface for IF5
ru->ifdevice.host_type = RAU_HOST;
ru->ifdevice.eth_params = &ru->eth_params;
break;
case REMOTE_IF4p5:
ru->do_prach = 0;
ru->feprx = NULL; // DFTs
ru->feptx_prec = nr_feptx_prec; // Precoding operation
ru->feptx_ofdm = NULL; // no OFDM mod
ru->fh_south_in = NULL;
ru->fh_south_out = NULL;
ru->fh_south_asynch_in = NULL;
ru->fh_north_out = NULL;
ru->fh_north_asynch_in = NULL;
ru->start_rf = NULL; // no local RF
ru->stop_rf = NULL;
ru->start_write_thread = NULL;
ru->nr_start_if = nr_start_if; // need to start if interface for IF4p5
ru->ifdevice.host_type = RAU_HOST;
ru->ifdevice.eth_params = &ru->eth_params;
ru->do_prach = 0;
ru->feprx = NULL; // DFTs
ru->feptx_prec = nr_feptx_prec; // Precoding operation
ru->feptx_ofdm = NULL; // no OFDM mod
ru->fh_south_in = fh_if4p5_south_in; // synchronous IF4p5 reception
ru->fh_south_out = fh_if4p5_south_out; // synchronous IF4p5 transmission
ru->fh_south_asynch_in = (ru->if_timing == synch_to_other) ? fh_if4p5_south_in : NULL; // asynchronous UL if synch_to_other
ru->fh_north_out = NULL;
ru->fh_north_asynch_in = NULL;
ru->start_rf = NULL; // no local RF
ru->stop_rf = NULL;
ru->start_write_thread = NULL;
ru->nr_start_if = nr_start_if; // need to start if interface for IF4p5
ru->ifdevice.host_type = RAU_HOST;
ru->ifdevice.eth_params = &ru->eth_params;
break;
default:
LOG_E(PHY, "RU with invalid or unknown southbound interface type %d\n", ru->if_south);
LOG_E(PHY,"RU with invalid or unknown southbound interface type %d\n",ru->if_south);
break;
} // switch on interface type
}
@@ -1139,14 +1369,47 @@ static void NRRCconfig_RU(configmodule_interface_t *cfg)
ru->openair0_cfg.tune_offset = get_softmodem_params()->tune_offset;
if (strcmp(*param[RU_LOCAL_RF_IDX].strptr, "yes") == 0) {
AssertFatal(!config_isparamset(param, RU_LOCAL_IF_NAME_IDX), "RU_TRANSPORT_PREFERENCE not supported for local RF\n");
ru->if_south = LOCAL_RF;
ru->function = gNodeB_3GPP;
LOG_D(PHY, "Setting function for RU %d to gNodeB_3GPP\n", j);
if (!config_isparamset(param, RU_LOCAL_IF_NAME_IDX)) {
ru->if_south = LOCAL_RF;
ru->function = gNodeB_3GPP;
LOG_D(PHY, "Setting function for RU %d to gNodeB_3GPP\n", j);
} else {
ru->eth_params.local_if_name = strdup(*param[RU_LOCAL_IF_NAME_IDX].strptr);
ru->eth_params.my_addr = strdup(*param[RU_LOCAL_ADDRESS_IDX].strptr);
ru->eth_params.remote_addr = strdup(*param[RU_REMOTE_ADDRESS_IDX].strptr);
ru->eth_params.my_portc = *param[RU_LOCAL_PORTC_IDX].uptr;
ru->eth_params.remote_portc = *param[RU_REMOTE_PORTC_IDX].uptr;
ru->eth_params.my_portd = *param[RU_LOCAL_PORTD_IDX].uptr;
ru->eth_params.remote_portd = *param[RU_REMOTE_PORTD_IDX].uptr;
char *str = *param[RU_TRANSPORT_PREFERENCE_IDX].strptr;
if (strcmp(str, "udp") == 0) {
ru->if_south = LOCAL_RF;
ru->function = NGFI_RRU_IF5;
ru->eth_params.transp_preference = ETH_UDP_MODE;
LOG_D(PHY, "Setting function for RU %d to NGFI_RRU_IF5 (udp)\n", j);
} else if (strcmp(str, "raw") == 0) {
ru->if_south = LOCAL_RF;
ru->function = NGFI_RRU_IF5;
ru->eth_params.transp_preference = ETH_RAW_MODE;
LOG_D(PHY, "Setting function for RU %d to NGFI_RRU_IF5 (raw)\n", j);
} else if (strcmp(str, "udp_if4p5") == 0) {
ru->if_south = LOCAL_RF;
ru->function = NGFI_RRU_IF4p5;
ru->eth_params.transp_preference = ETH_UDP_IF4p5_MODE;
LOG_D(PHY, "Setting function for RU %d to NGFI_RRU_IF4p5 (udp)\n", j);
} else if (strcmp(str, "raw_if4p5") == 0) {
ru->if_south = LOCAL_RF;
ru->function = NGFI_RRU_IF4p5;
ru->eth_params.transp_preference = ETH_RAW_IF4p5_MODE;
LOG_D(PHY, "Setting function for RU %d to NGFI_RRU_IF4p5 (raw)\n", j);
}
}
ru->max_pdschReferenceSignalPower = *param[RU_MAX_RS_EPRE_IDX].uptr;
ru->max_rxgain = *param[RU_MAX_RXGAIN_IDX].uptr;
ru->sf_extension = *param[RU_SF_EXTENSION_IDX].uptr;
} else { // strcmp(local_rf, "yes") == 0
} // strcmp(local_rf, "yes") == 0
else {
char *str = *param[RU_TRANSPORT_PREFERENCE_IDX].strptr;
LOG_D(PHY, "RU %d: Transport %s\n", j, str);
ru->eth_params.local_if_name = strdup(*param[RU_LOCAL_IF_NAME_IDX].strptr);
@@ -1169,6 +1432,10 @@ static void NRRCconfig_RU(configmodule_interface_t *cfg)
ru->if_south = REMOTE_IF5;
ru->function = NGFI_RAU_IF5;
ru->eth_params.transp_preference = ETH_RAW_MODE;
} else if (strcmp(str, "udp_if4p5") == 0) {
ru->if_south = REMOTE_IF4p5;
ru->function = NGFI_RAU_IF4p5;
ru->eth_params.transp_preference = ETH_UDP_IF4p5_MODE;
} else if (strcmp(str, "raw_if4p5") == 0) {
ru->if_south = REMOTE_IF4p5;
ru->function = NGFI_RAU_IF4p5;

View File

@@ -28,6 +28,7 @@
#include <unistd.h>
#include <sys/sysinfo.h>
#include "radio/COMMON/common_lib.h"
#include "assertions.h"
/* help strings definition for command line options, used in CMDLINE_XXX_DESC macros and printed when -h option is used */

View File

@@ -19,11 +19,6 @@ unsigned short config_frames[4] = {2,9,11,13};
#include "openair2/E2AP/flexric/src/agent/e2_agent_api.h"
#include "openair2/E2AP/RAN_FUNCTION/init_ran_func.h"
#endif
#ifdef E3_AGENT
#include "openair2/E3AP/e3_agent.h"
#endif
#include "nr-softmodem.h"
#include <common/utils/assertions.h>
#include <openair2/GNB_APP/gnb_app.h>
@@ -65,6 +60,7 @@ unsigned short config_frames[4] = {2,9,11,13};
#include "nr-softmodem-common.h"
#include "openair2/E1AP/e1ap_common.h"
#include "pdcp.h"
#include "radio/COMMON/common_lib.h"
#include "s1ap_eNB.h"
#include "sctp_eNB_task.h"
#include "system.h"
@@ -525,13 +521,6 @@ int main( int argc, char **argv ) {
softmodem_verify_mode(get_softmodem_params());
//////////////////////////////////
//// Init the E3 Agent
#ifdef E3_AGENT
printf("Init E3 Agent\n");
e3_init();
#endif // E3_AGENT
#if T_TRACER
T_Config_Init();
#endif
@@ -738,11 +727,6 @@ int main( int argc, char **argv ) {
time_manager_finish();
#ifdef E3_AGENT
printf("Destroy E3 Agent\n");
e3_destroy();
#endif // E3_AGENT
free(pckg);
logClean();
printf("Bye.\n");

Some files were not shown because too many files have changed in this diff Show More