This commit is contained in:
2026-01-30 23:38:37 +09:00
parent 90290d0d7b
commit 312d75d759
25 changed files with 3076 additions and 776 deletions
+2
View File
@@ -873,6 +873,7 @@ set(PHY_SRC_RU
${OPENAIR1_DIR}/PHY/MODULATION/gen_75KHz.cpp
${OPENAIR1_DIR}/PHY/MODULATION/beamforming.c
${OPENAIR1_DIR}/PHY/MODULATION/compute_bf_weights.c
${OPENAIR1_DIR}/PHY/NR_TRANSPORT/nr_antenna_state.c
${OPENAIR1_DIR}/PHY/INIT/lte_init_ru.c
${OPENAIR1_DIR}/PHY/INIT/nr_init_ru.c
${OPENAIR1_DIR}/PHY/LTE_TRANSPORT/prach.c
@@ -948,6 +949,7 @@ set(PHY_SRC_UE
${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_TRANSPORT/nr_antenna_control.c
${OPENAIR1_DIR}/PHY/NR_REFSIG/ul_ref_seq_nr.c
${OPENAIR1_DIR}/PHY/NR_REFSIG/nr_dmrs_rx.c
${OPENAIR1_DIR}/PHY/NR_REFSIG/nr_gen_mod_table.c
@@ -11,8 +11,7 @@ gNBs =
// Tracking area code, 0x0000 and 0xfffe are reserved values
tracking_area_code = 1;
plmn_list = ({ mcc = 222; mnc = 01; mnc_length = 2; snssaiList = ({ sst = 1, sd = 0xffffff }) });
plmn_list = ({ mcc = 001; mnc = 01; mnc_length = 2; snssaiList = ({ sst = 1; sd = 0xffffff}) });
nr_cellid = 12345678L;
////////// Physical parameters:
@@ -188,8 +187,8 @@ L1s = (
RUs = (
{
local_rf = "yes"
nb_tx = 1
nb_rx = 1
nb_tx = 4;
nb_rx = 4;
att_tx = 14;
att_rx = 14;
bands = [78];
@@ -200,6 +199,14 @@ RUs = (
}
);
rfsimulator :
{
serveraddr = "server";
serverport = "4043";
# options = ("saviq", "chanmod"); #("saviq"); or/and "chanmod"
# modelname = "AWGN";
IQfile = "/tmp/rfsimulator.iqs";
};
security = {
# preferred ciphering algorithms
+262
View File
@@ -0,0 +1,262 @@
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 = 001; mnc = 01; mnc_length = 2; snssaiList = ({ sst = 1; }) });
nr_cellid = 1;
////////// Physical parameters:
pdsch_AntennaPorts_XP = 2;
pdsch_AntennaPorts_N1 = 1;
maxMIMO_layers = 2;
pusch_AntennaPorts = 4;
do_CSIRS = 1;
do_SRS = 0;
# force_UL256qam_off = 1;
servingCellConfigCommon = (
{
#spCellConfigCommon
physCellId = 0;
# n_TimingAdvanceOffset = 0;
# downlinkConfigCommon
#frequencyInfoDL
# center frequency = 3350.01 MHz
# selected SSB frequency = 3349.92 MHz
absoluteFrequencySSB = 623328;
dl_frequencyBand = 78;
# frequency point A = 3300.87 MHz
dl_absoluteFrequencyPointA = 620052;
#scs-SpecificCarrierList
dl_offstToCarrier = 0;
# subcarrierSpacing
# 0=kHz15, 1=kHz30, 2=kHz60, 3=kHz120
dl_subcarrierSpacing = 1;
dl_carrierBandwidth = 273;
#initialDownlinkBWP
#genericParameters
initialDLBWPlocationAndBandwidth = 1099; #38.101-1 Table 5.3.2-1
#
# subcarrierSpacing
# 0=kHz15, 1=kHz30, 2=kHz60, 3=kHz120
initialDLBWPsubcarrierSpacing = 1;
#pdcch-ConfigCommon
initialDLBWPcontrolResourceSetZero = 11;
initialDLBWPsearchSpaceZero = 0;
#uplinkConfigCommon
#frequencyInfoUL
ul_frequencyBand = 78;
#scs-SpecificCarrierList
ul_offstToCarrier = 0;
# subcarrierSpacing
# 0=kHz15, 1=kHz30, 2=kHz60, 3=kHz120
ul_subcarrierSpacing = 1;
ul_carrierBandwidth = 273;
pMax = 23;
#initialUplinkBWP
#genericParameters
initialULBWPlocationAndBandwidth = 1099;
# subcarrierSpacing
# 0=kHz15, 1=kHz30, 2=kHz60, 3=kHz120
initialULBWPsubcarrierSpacing = 1;
#rach-ConfigCommon
#rach-ConfigGeneric
prach_ConfigurationIndex = 152;
#prach_msg1_FDM
#0 = one, 1=two, 2=four, 3=eight
prach_msg1_FDM = 0;
prach_msg1_FrequencyStart = 0;
zeroCorrelationZoneConfig = 0;
preambleReceivedTargetPower = -100;
#preamblTransMax (0...10) = (3,4,5,6,7,8,10,20,50,100,200)
preambleTransMax = 8;
#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,
# this is the offset between the last PRACH preamble power and the Msg3 PUSCH, 2 times the field value in dB
msg3_DeltaPreamble = 2;
p0_NominalWithGrant = -96;
# pucch-ConfigCommon setup :
# pucchGroupHopping
# 0 = neither, 1= group hopping, 2=sequence hopping
pucchGroupHopping = 0;
hoppingId = 0;
p0_nominal = -96;
ssb_PositionsInBurst_Bitmap = 0x1;
# 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 = -15;
}
);
# ------- SCTP definitions
SCTP :
{
# Number of streams to use in input/output
SCTP_INSTREAMS = 2;
SCTP_OUTSTREAMS = 2;
};
////////// AMF parameters:
amf_ip_address = ({ ipv4 = "192.168.71.132"; });
NETWORK_INTERFACES :
{
GNB_IPV4_ADDRESS_FOR_NG_AMF = "192.168.71.140";
GNB_IPV4_ADDRESS_FOR_NGU = "192.168.71.140";
GNB_PORT_FOR_S1U = 2152; # Spec 2152
};
}
);
MACRLCs = (
{
num_cc = 1;
tr_s_preference = "local_L1";
tr_n_preference = "local_RRC";
pusch_TargetSNRx10 = 100;
pucch_TargetSNRx10 = 200;
dl_bler_target_upper = .35;
dl_bler_target_lower = .15;
ul_bler_target_upper = .35;
ul_bler_target_lower = .15;
pusch_FailureThres = 1000;
ul_max_mcs = 28;
}
);
L1s = (
{
num_cc = 1;
tr_n_preference = "local_mac";
prach_dtx_threshold = 90;
pucch0_dtx_threshold = 80;
pusch_dtx_threshold = 10;
max_ldpc_iterations = 15;
tx_amp_backoff_dB = 12; # needs to match O-RU configuration
L1_rx_thread_core = 3;
L1_tx_thread_core = 4;
phase_compensation = 0; # needs to match O-RU configuration
}
);
RUs = (
{
local_rf = "yes";
nb_tx = 4;
nb_rx = 4;
att_tx = 0;
att_rx = 0;
bands = [78];
max_pdschReferenceSignalPower = -27;
max_rxgain = 75;
sf_extension = 0;
eNB_instances = [0];
ru_thread_core = 5;
sl_ahead = 10;
tr_preference = "raw_if4p5"; # important: activate FHI7.2
do_precoding = 0; # needs to match O-RU configuration
}
);
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";
ngap_log_level = "info";
f1ap_log_level = "info";
};
rfsimulator :
{
serveraddr = "server";
serverport = 4043;
options = (); #("saviq"); or/and "chanmod"
modelname = "AWGN";
IQfile = "/tmp/rfsimulator.iqs";
};
+44
View File
@@ -0,0 +1,44 @@
FROM ran-base:latest AS ran-build
ARG BUILD_OPTION
RUN rm -Rf /oai-ran
WORKDIR /oai-ran
COPY . .
# build all targets so final targets can be created afterwards
RUN /bin/sh oaienv && \
cd cmake_targets && \
mkdir -p log && \
./build_oai --ninja \
--gNB --RU --nrUE -w SIMU \
-w USRP -t Ethernet \
--build-e2 --cmake-opt -DKPM_VERSION=KPM_V3_00 --cmake-opt -DE2AP_VERSION=E2AP_V3 \
--cmake-opt -DCMAKE_C_FLAGS="-Werror" --cmake-opt -DCMAKE_CXX_FLAGS="-Werror" $BUILD_OPTION && \
echo "---- ldd on executables ----" && \
ldd ran_build/build/*softmodem* ran_build/build/nr-cuup ran_build/build/oairu && \
echo "---- ldd on shared libraries ----" && \
ldd ran_build/build/*.so
RUN set -eux; \
apt-get update; \
DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
ca-certificates curl wget iproute2 iputils-ping dnsutils jq \
iperf iperf3 ffmpeg gdb\
nghttp2-client || true; \
if ! command -v h2load >/dev/null 2>&1; then \
DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends nghttp2 || true; \
fi; \
wget -O /usr/local/bin/sipp https://github.com/SIPp/sipp/releases/download/v3.7.7/sipp && chmod +x /usr/local/bin/sipp; \
rm -rf /var/lib/apt/lists/*
# FlexRIC libraries (built by --build-e2) - only copy service model libraries (*_sm.so)
RUN mkdir -p /usr/local/etc/flexric /usr/local/lib/flexric && \
find /oai-ran/openair2/E2AP/flexric/build -name "*_sm.so" -exec cp {} /usr/local/lib/flexric/ \; && \
cp /oai-ran/openair2/E2AP/flexric/flexric.conf /usr/local/etc/flexric/ 2>/dev/null || true && \
echo "/usr/local/lib/flexric" > /etc/ld.so.conf.d/flexric.conf && ldconfig && \
ls -la /usr/local/lib/flexric/
# Install netcat for antenna control testing
RUN apt-get update && apt-get install -y netcat-openbsd && rm -rf /var/lib/apt/lists/*
ENTRYPOINT ["/bin/bash"]
+10
View File
@@ -48,6 +48,8 @@
#include "NR_PHY_INTERFACE/NR_IF_Module.h"
#include "PHY/INIT/nr_phy_init.h"
#include "PHY/MODULATION/nr_modulation.h"
#include "openair2/LAYER2/NR_MAC_gNB/nr_mac_gNB.h"
#include "PHY/NR_TRANSPORT/nr_antenna_control.h"
#include "PHY/NR_TRANSPORT/nr_transport_proto.h"
#include "PHY/TOOLS/tools_defs.h"
#include "PHY/defs_RU.h"
@@ -402,6 +404,14 @@ void init_eNB_afterRU(void)
*/
//init_precoding_weights(RC.gNB[inst]);
init_gNB_Tpool(inst);
// antenna control
if (nr_antenna_control_start_test_server(&gNB->antenna_state, 9999))
LOG_I(PHY, "[ANTENNA_CTRL] Test control server started on port 9999\n");
if (inst < RC.nb_nr_macrlc_inst && RC.nrmac && RC.nrmac[inst]) {
RC.nrmac[inst]->antenna_state = &gNB->antenna_state;
LOG_I(PHY, "[ANTENNA_STATE] Connected MAC scheduler to PHY antenna state for dynamic layer adaptation\n");
}
}
}
+3
View File
@@ -158,6 +158,9 @@ void phy_init_nr_gNB(PHY_VARS_gNB *gNB)
init_DLSCH_struct(gNB);
nr_init_antenna_state(&gNB->antenna_state, Ptx, Prx, "tx-array-0", "rx-array-0");
LOG_I(PHY, "[ANTENNA_STATE] Initialized with %d TX, %d RX antennas\n", Ptx, Prx);
gNB->nr_srs_info = (nr_srs_info_t **)malloc16_clear(gNB->max_nb_srs * sizeof(nr_srs_info_t*));
for (int id = 0; id < gNB->max_nb_srs; id++) {
gNB->nr_srs_info[id] = (nr_srs_info_t *)malloc16_clear(sizeof(nr_srs_info_t));
+33
View File
@@ -51,6 +51,7 @@
#include "modulation_eNB.h"
#include "nr_modulation.h"
#include "common/utils/LOG/vcd_signal_dumper.h"
#include "PHY/NR_TRANSPORT/nr_antenna_state.h"
int beam_precoding(int32_t **txdataF,
int32_t **txdataF_BF,
@@ -143,3 +144,35 @@ void nr_beam_precoding(c16_t **txdataF,
15);
}
}
void nr_beam_precoding_with_muting(c16_t **txdataF,
c16_t **txdataF_BF,
NR_DL_FRAME_PARMS *frame_parms,
int32_t ***beam_weights,
int slot,
int symbol,
int aa,
int nb_antenna_ports,
int offset,
const nr_antenna_state_t *ant_state)
{
// Clear output buffer first
memset(&txdataF_BF[aa][symbol * frame_parms->ofdm_symbol_size], 0,
sizeof(c16_t) * frame_parms->ofdm_symbol_size);
// O-RAN energy saving: skip precoding if antenna is muted
// This implements transmission blanking per WG4 spec
if (ant_state != NULL && !nr_is_tx_antenna_active(ant_state, aa)) {
// Antenna is muted - output remains zero (transmission blank)
LOG_D(PHY, "[ANTENNA_MUTE] Antenna %d muted, blanking transmission for symbol %d\n",
aa, symbol);
return;
}
// Normal precoding for active antennas
for (int p = 0; p < nb_antenna_ports; p++) {
multadd_cpx_vector((c16_t *)&txdataF[p][(symbol * frame_parms->ofdm_symbol_size) + offset],
(c16_t *)beam_weights[p][aa],
(c16_t *)&txdataF_BF[aa][symbol * frame_parms->ofdm_symbol_size],
frame_parms->ofdm_symbol_size,
15);
}
}
+29
View File
@@ -103,6 +103,35 @@ void nr_beam_precoding(c16_t **txdataF,
int nb_antenna_ports,
int offset);
/*! \brief NR beam precoding with O-RAN compliant antenna muting
*
* Extends nr_beam_precoding to support antenna muting for energy saving
* per O-RAN WG4 MPLANE-TRX-CONTROL specification (o-ran-uplane-conf.yang).
* When an antenna is muted, transmission is blanked (output zeroed).
*
* @param[in] txdataF Input frequency-domain data per antenna port
* @param[out] txdataF_BF Output beamformed data per physical antenna
* @param[in] frame_parms Frame parameters
* @param[in] beam_weights Beamforming weights
* @param[in] slot Current slot number
* @param[in] symbol Current symbol number
* @param[in] aa Physical antenna index
* @param[in] nb_antenna_ports Number of antenna ports
* @param[in] offset Frequency offset
* @param[in] ant_state Antenna state for muting control (NULL = all active)
*/
void nr_beam_precoding_with_muting(c16_t **txdataF,
c16_t **txdataF_BF,
NR_DL_FRAME_PARMS *frame_parms,
int32_t ***beam_weights,
int slot,
int symbol,
int aa,
int nb_antenna_ports,
int offset,
const nr_antenna_state_t *ant_state);
void apply_nr_rotation_TX(const NR_DL_FRAME_PARMS *fp,
c16_t *txdataF,
const c16_t *symbol_rotation,
@@ -1,18 +0,0 @@
#include "nr_antenna_control.h"
#include "common/utils/LOG/log.h"
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <errno.h>
#include <signal.h>
#ifdef OAI_MPLANE
#include "radio/fhi_72/mplane/antenna-control-mplane.h"
#include "radio/fhi_72/mplane/ru-mplane-api.h"
#endif
@@ -0,0 +1,532 @@
/*
* Licensed to the OpenAirInterface (OAI) Software Alliance under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The OpenAirInterface Software Alliance licenses this file to You under
* the OAI Public License, Version 1.1 (the "License"); you may not use this file
* except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.openairinterface.org/?page_id=698
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*-------------------------------------------------------------------------------
* For more information about the OpenAirInterface (OAI) Software Alliance:
* contact@openairinterface.org
*/
/*! \file PHY/NR_TRANSPORT/nr_antenna_control.c
* \brief Antenna control interface implementation with O-RAN M-Plane support
* \author OpenAirInterface
* \date 2025
* \version 2.0
*/
#include "nr_antenna_control.h"
#include "common/utils/LOG/log.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <errno.h>
#include <signal.h>
// Try to include M-Plane header if available
#ifdef OAI_MPLANE
#include "radio/fhi_72/mplane/antenna-control.h"
#include "radio/fhi_72/mplane/ru-mplane-api.h"
#endif
// Global control state
static struct {
pthread_t thread;
int server_fd;
nr_antenna_state_t *ant_state;
volatile bool running;
int port;
nr_antenna_ctrl_backend_t backend;
#ifdef OAI_MPLANE
ru_session_t *ru_session;
#else
void *ru_session;
#endif
} g_ctrl = {0};
// ============================================================================
// Test Socket Server Implementation (Development/Testing Mode)
// ============================================================================
// Process a single command and generate response
static void process_command(const char *cmd, char *response, size_t resp_size)
{
char cmd_upper[256];
strncpy(cmd_upper, cmd, sizeof(cmd_upper) - 1);
cmd_upper[sizeof(cmd_upper) - 1] = '\0';
// Convert to uppercase for case-insensitive matching
for (char *p = cmd_upper; *p; p++) {
if (*p >= 'a' && *p <= 'z') *p -= 32;
}
// Trim whitespace
char *p = cmd_upper;
while (*p == ' ' || *p == '\t' || *p == '\r' || *p == '\n') p++;
char *end = p + strlen(p) - 1;
while (end > p && (*end == ' ' || *end == '\t' || *end == '\r' || *end == '\n')) *end-- = '\0';
if (strncmp(p, "MUTE TX ", 8) == 0) {
int ant_id = atoi(p + 8);
nr_mute_tx_antenna(g_ctrl.ant_state, ant_id);
snprintf(response, resp_size, "OK: TX antenna %d muted\n", ant_id);
}
else if (strncmp(p, "UNMUTE TX ", 10) == 0) {
int ant_id = atoi(p + 10);
nr_unmute_tx_antenna(g_ctrl.ant_state, ant_id);
snprintf(response, resp_size, "OK: TX antenna %d unmuted\n", ant_id);
}
else if (strncmp(p, "MUTE RX ", 8) == 0) {
int ant_id = atoi(p + 8);
nr_mute_rx_antenna(g_ctrl.ant_state, ant_id);
snprintf(response, resp_size, "OK: RX antenna %d muted\n", ant_id);
}
else if (strncmp(p, "UNMUTE RX ", 10) == 0) {
int ant_id = atoi(p + 10);
nr_unmute_rx_antenna(g_ctrl.ant_state, ant_id);
snprintf(response, resp_size, "OK: RX antenna %d unmuted\n", ant_id);
}
else if (strncmp(p, "SET TX MASK ", 12) == 0) {
unsigned int mask;
if (sscanf(p + 12, "%x", &mask) == 1 || sscanf(p + 12, "0X%x", &mask) == 1) {
uint8_t old = nr_set_tx_antenna_bitmask(g_ctrl.ant_state, (uint8_t)mask);
snprintf(response, resp_size, "OK: TX mask 0x%02X -> 0x%02X\n", old, (uint8_t)mask);
} else {
snprintf(response, resp_size, "ERROR: Invalid mask format. Use hex (e.g., 0F or 0x0F)\n");
}
}
else if (strncmp(p, "SET RX MASK ", 12) == 0) {
unsigned int mask;
if (sscanf(p + 12, "%x", &mask) == 1 || sscanf(p + 12, "0X%x", &mask) == 1) {
uint8_t old = nr_set_rx_antenna_bitmask(g_ctrl.ant_state, (uint8_t)mask);
snprintf(response, resp_size, "OK: RX mask 0x%02X -> 0x%02X\n", old, (uint8_t)mask);
} else {
snprintf(response, resp_size, "ERROR: Invalid mask format. Use hex (e.g., 0F or 0x0F)\n");
}
}
else if (strcmp(p, "STATUS") == 0) {
bool in_recovery = nr_is_in_recovery_mode(g_ctrl.ant_state);
snprintf(response, resp_size,
"=== Antenna Status ===\n"
"TX Array: %s\n"
" Antennas: %d configured, %d active\n"
" Bitmask: 0x%02X\n"
" Antenna 0 protected: %s\n"
"RX Array: %s\n"
" Antennas: %d configured, %d active\n"
" Bitmask: 0x%02X\n"
"Recovery Mode: %s\n"
"Backend: %s\n",
g_ctrl.ant_state->tx_array_name,
g_ctrl.ant_state->num_tx_antennas,
nr_get_active_tx_antenna_count(g_ctrl.ant_state),
nr_get_tx_antenna_bitmask(g_ctrl.ant_state),
g_ctrl.ant_state->config.protect_antenna0 ? "YES" : "NO",
g_ctrl.ant_state->rx_array_name,
g_ctrl.ant_state->num_rx_antennas,
nr_get_active_rx_antenna_count(g_ctrl.ant_state),
nr_get_rx_antenna_bitmask(g_ctrl.ant_state),
in_recovery ? "ACTIVE (use identity matrix)" : "OFF",
g_ctrl.backend == ANTENNA_CTRL_BACKEND_MPLANE ? "O-RAN M-Plane NETCONF" :
g_ctrl.backend == ANTENNA_CTRL_BACKEND_TEST_SOCKET ? "Test Socket (NOT O-RAN)" :
"None");
}
else if (strcmp(p, "STATS") == 0) {
uint64_t tx_events, rx_events, muted_slots;
nr_get_antenna_stats(g_ctrl.ant_state, &tx_events, &rx_events, &muted_slots);
snprintf(response, resp_size,
"=== Energy Saving Statistics ===\n"
"TX mute events: %lu\n"
"RX mute events: %lu\n"
"Total muted slots: %lu\n",
tx_events, rx_events, muted_slots);
}
else if (strcmp(p, "RESET STATS") == 0) {
nr_reset_antenna_stats(g_ctrl.ant_state);
snprintf(response, resp_size, "OK: Statistics reset\n");
}
else if (strcmp(p, "RESET") == 0 || strcmp(p, "UNMUTE ALL") == 0) {
// Reset all antennas to active state with recovery mode
nr_unmute_all_tx_antennas(g_ctrl.ant_state);
nr_unmute_all_rx_antennas(g_ctrl.ant_state);
snprintf(response, resp_size,
"OK: All antennas unmuted (recovery mode: %u ms)\n"
" TX mask: 0x%02X (%d antennas)\n"
" RX mask: 0x%02X (%d antennas)\n",
g_ctrl.ant_state->recovery.recovery_duration_ms,
nr_get_tx_antenna_bitmask(g_ctrl.ant_state), g_ctrl.ant_state->num_tx_antennas,
nr_get_rx_antenna_bitmask(g_ctrl.ant_state), g_ctrl.ant_state->num_rx_antennas);
}
else if (strcmp(p, "PROTECT ANT0 ON") == 0) {
nr_set_antenna0_protection(g_ctrl.ant_state, true);
snprintf(response, resp_size, "OK: Antenna 0 protection enabled (SSB)\n");
}
else if (strcmp(p, "PROTECT ANT0 OFF") == 0) {
nr_set_antenna0_protection(g_ctrl.ant_state, false);
snprintf(response, resp_size, "OK: Antenna 0 protection disabled (WARNING: may cause UE sync loss)\n");
}
else if (strcmp(p, "SYNC") == 0) {
// Synchronize to RU (only works with M-Plane backend)
if (nr_antenna_control_sync_to_ru(g_ctrl.ant_state)) {
snprintf(response, resp_size, "OK: Synchronized to RU\n");
} else {
snprintf(response, resp_size, "ERROR: Sync failed (M-Plane backend required)\n");
}
}
else if (strcmp(p, "HELP") == 0) {
snprintf(response, resp_size,
"=== Antenna Control ===\n"
"MUTE TX/RX <id> - Mute antenna\n"
"UNMUTE TX/RX <id> - Unmute antenna\n"
"SET TX/RX MASK <hex> - Set bitmask\n"
"STATUS - Show state\n"
"STATS - Show statistics\n"
"RESET - Unmute all\n"
"PROTECT ANT0 ON/OFF\n"
"HELP/QUIT\n");
}
else if (strcmp(p, "QUIT") == 0 || strcmp(p, "EXIT") == 0) {
snprintf(response, resp_size, "BYE\n");
}
else if (strlen(p) == 0) {
response[0] = '\0'; // Empty command, no response
}
else {
snprintf(response, resp_size, "ERROR: Unknown command '%s'. Type HELP for usage.\n", cmd);
}
}
// Handle a single client connection
static void handle_client(int client_fd)
{
char buffer[256];
char response[1024];
// Send welcome message with warning about non-compliance
const char *welcome =
"=== OAI Antenna Control Test Server ===\n"
"WARNING: This is a TEST interface, NOT O-RAN compliant!\n"
"For production, use NETCONF via radio/fhi_72/mplane/\n"
"Type HELP for commands.\n\n";
send(client_fd, welcome, strlen(welcome), 0);
while (g_ctrl.running) {
// Send prompt
send(client_fd, "> ", 2, 0);
// Receive command
ssize_t n = recv(client_fd, buffer, sizeof(buffer) - 1, 0);
if (n <= 0) break;
buffer[n] = '\0';
// Process command
process_command(buffer, response, sizeof(response));
// Send response
if (strlen(response) > 0) {
send(client_fd, response, strlen(response), 0);
}
// Check for quit
if (strstr(buffer, "QUIT") || strstr(buffer, "quit") ||
strstr(buffer, "EXIT") || strstr(buffer, "exit")) {
break;
}
}
close(client_fd);
LOG_I(PHY, "[ANTENNA_CTRL] Client disconnected\n");
}
// Server thread function
static void *server_thread(void *arg)
{
(void)arg;
struct sockaddr_in client_addr;
socklen_t addr_len = sizeof(client_addr);
LOG_I(PHY, "[ANTENNA_CTRL] Test server listening on port %d\n", g_ctrl.port);
while (g_ctrl.running) {
// Accept connection with timeout
fd_set read_fds;
FD_ZERO(&read_fds);
FD_SET(g_ctrl.server_fd, &read_fds);
struct timeval tv = {.tv_sec = 1, .tv_usec = 0};
int ret = select(g_ctrl.server_fd + 1, &read_fds, NULL, NULL, &tv);
if (ret <= 0) continue;
int client_fd = accept(g_ctrl.server_fd, (struct sockaddr *)&client_addr, &addr_len);
if (client_fd < 0) continue;
LOG_I(PHY, "[ANTENNA_CTRL] Client connected from %s:%d\n",
inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port));
handle_client(client_fd);
}
return NULL;
}
// ============================================================================
// Public API Implementation
// ============================================================================
bool nr_antenna_control_init_mplane(nr_antenna_state_t *ant_state, void *ru_session)
{
#ifdef OAI_MPLANE
if (g_ctrl.running) {
LOG_W(PHY, "[ANTENNA_CTRL] Control interface already active\n");
return false;
}
if (ant_state == NULL || ru_session == NULL) {
LOG_E(PHY, "[ANTENNA_CTRL] Invalid parameters\n");
return false;
}
g_ctrl.ant_state = ant_state;
g_ctrl.ru_session = (ru_session_t *)ru_session;
g_ctrl.backend = ANTENNA_CTRL_BACKEND_MPLANE;
g_ctrl.running = true;
// Check if RU supports TRX control feature
if (!mplane_supports_trx_control(g_ctrl.ru_session)) {
LOG_W(PHY, "[ANTENNA_CTRL] RU does not support MPLANE-TRX-CONTROL feature\n");
LOG_W(PHY, "[ANTENNA_CTRL] Antenna muting may not work on this RU\n");
}
LOG_I(PHY, "[ANTENNA_CTRL] Initialized with O-RAN M-Plane NETCONF backend\n");
LOG_I(PHY, "[ANTENNA_CTRL] TX array: %s, RX array: %s\n",
ant_state->tx_array_name, ant_state->rx_array_name);
return true;
#else
LOG_E(PHY, "[ANTENNA_CTRL] M-Plane support not compiled in (OAI_MPLANE not defined)\n");
LOG_E(PHY, "[ANTENNA_CTRL] Use nr_antenna_control_start_test_server() for testing\n");
return false;
#endif
}
bool nr_antenna_control_start_test_server(nr_antenna_state_t *ant_state, int port)
{
if (g_ctrl.running) {
LOG_W(PHY, "[ANTENNA_CTRL] Control interface already active\n");
return false;
}
if (ant_state == NULL) {
LOG_E(PHY, "[ANTENNA_CTRL] Antenna state is NULL\n");
return false;
}
LOG_W(PHY, "[ANTENNA_CTRL] ======================================================\n");
LOG_W(PHY, "[ANTENNA_CTRL] Starting TEST socket server - NOT O-RAN COMPLIANT!\n");
LOG_W(PHY, "[ANTENNA_CTRL] For production O-RAN compliance, use M-Plane NETCONF\n");
LOG_W(PHY, "[ANTENNA_CTRL] See: radio/fhi_72/mplane/antenna-control-mplane.h\n");
LOG_W(PHY, "[ANTENNA_CTRL] ======================================================\n");
g_ctrl.ant_state = ant_state;
g_ctrl.port = (port > 0) ? port : NR_ANTENNA_CONTROL_TEST_PORT;
g_ctrl.backend = ANTENNA_CTRL_BACKEND_TEST_SOCKET;
// Create socket
g_ctrl.server_fd = socket(AF_INET, SOCK_STREAM, 0);
if (g_ctrl.server_fd < 0) {
LOG_E(PHY, "[ANTENNA_CTRL] Failed to create socket: %s\n", strerror(errno));
return false;
}
// Allow address reuse
int opt = 1;
setsockopt(g_ctrl.server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
// Bind
struct sockaddr_in addr = {
.sin_family = AF_INET,
.sin_addr.s_addr = INADDR_ANY,
.sin_port = htons(g_ctrl.port)
};
if (bind(g_ctrl.server_fd, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
LOG_E(PHY, "[ANTENNA_CTRL] Failed to bind port %d: %s\n", g_ctrl.port, strerror(errno));
close(g_ctrl.server_fd);
return false;
}
// Listen
if (listen(g_ctrl.server_fd, 1) < 0) {
LOG_E(PHY, "[ANTENNA_CTRL] Failed to listen: %s\n", strerror(errno));
close(g_ctrl.server_fd);
return false;
}
// Start server thread
g_ctrl.running = true;
if (pthread_create(&g_ctrl.thread, NULL, server_thread, NULL) != 0) {
LOG_E(PHY, "[ANTENNA_CTRL] Failed to create server thread\n");
g_ctrl.running = false;
close(g_ctrl.server_fd);
return false;
}
LOG_I(PHY, "[ANTENNA_CTRL] Test server started on port %d\n", g_ctrl.port);
LOG_I(PHY, "[ANTENNA_CTRL] Connect with: nc localhost %d\n", g_ctrl.port);
return true;
}
bool nr_antenna_control_start(nr_antenna_state_t *ant_state, int port)
{
LOG_W(PHY, "[ANTENNA_CTRL] nr_antenna_control_start() is DEPRECATED\n");
LOG_W(PHY, "[ANTENNA_CTRL] Use nr_antenna_control_init_mplane() for O-RAN compliance\n");
LOG_W(PHY, "[ANTENNA_CTRL] or nr_antenna_control_start_test_server() for testing\n");
return nr_antenna_control_start_test_server(ant_state, port);
}
void nr_antenna_control_stop(void)
{
if (!g_ctrl.running) return;
g_ctrl.running = false;
if (g_ctrl.backend == ANTENNA_CTRL_BACKEND_TEST_SOCKET) {
// Close server socket to unblock accept()
if (g_ctrl.server_fd >= 0) {
close(g_ctrl.server_fd);
g_ctrl.server_fd = -1;
}
// Wait for thread
pthread_join(g_ctrl.thread, NULL);
}
g_ctrl.backend = ANTENNA_CTRL_BACKEND_NONE;
g_ctrl.ant_state = NULL;
g_ctrl.ru_session = NULL;
LOG_I(PHY, "[ANTENNA_CTRL] Control interface stopped\n");
}
bool nr_antenna_control_is_running(void)
{
return g_ctrl.running;
}
nr_antenna_ctrl_backend_t nr_antenna_control_get_backend(void)
{
return g_ctrl.backend;
}
bool nr_antenna_control_sync_to_ru(nr_antenna_state_t *ant_state)
{
#ifdef OAI_MPLANE
if (g_ctrl.backend != ANTENNA_CTRL_BACKEND_MPLANE || g_ctrl.ru_session == NULL) {
LOG_W(PHY, "[ANTENNA_CTRL] M-Plane backend not active, cannot sync to RU\n");
return false;
}
if (ant_state == NULL) {
ant_state = g_ctrl.ant_state;
}
if (ant_state == NULL) {
return false;
}
// Send TX antenna mask to RU
antenna_ctrl_result_t result;
result = mplane_set_tx_antenna_mask(g_ctrl.ru_session,
ant_state->tx_array_name,
nr_get_tx_antenna_bitmask(ant_state),
ant_state->num_tx_antennas);
if (result != ANTENNA_CTRL_SUCCESS) {
LOG_W(PHY, "[ANTENNA_CTRL] Failed to sync TX mask: %s\n",
antenna_ctrl_result_str(result));
return false;
}
// Send RX antenna mask to RU
result = mplane_set_rx_antenna_mask(g_ctrl.ru_session,
ant_state->rx_array_name,
nr_get_rx_antenna_bitmask(ant_state),
ant_state->num_rx_antennas);
if (result != ANTENNA_CTRL_SUCCESS) {
LOG_W(PHY, "[ANTENNA_CTRL] Failed to sync RX mask: %s\n",
antenna_ctrl_result_str(result));
return false;
}
LOG_I(PHY, "[ANTENNA_CTRL] Successfully synchronized antenna masks to RU\n");
return true;
#else
LOG_W(PHY, "[ANTENNA_CTRL] M-Plane not compiled in\n");
return false;
#endif
}
bool nr_antenna_control_sync_from_ru(nr_antenna_state_t *ant_state)
{
#ifdef OAI_MPLANE
if (g_ctrl.backend != ANTENNA_CTRL_BACKEND_MPLANE || g_ctrl.ru_session == NULL) {
LOG_W(PHY, "[ANTENNA_CTRL] M-Plane backend not active, cannot sync from RU\n");
return false;
}
if (ant_state == NULL) {
ant_state = g_ctrl.ant_state;
}
if (ant_state == NULL) {
return false;
}
uint8_t tx_mask, rx_mask;
antenna_ctrl_result_t result;
// Get TX antenna mask from RU
result = mplane_get_tx_antenna_mask(g_ctrl.ru_session,
ant_state->tx_array_name,
&tx_mask);
if (result == ANTENNA_CTRL_SUCCESS) {
nr_set_tx_antenna_bitmask(ant_state, tx_mask);
}
// Get RX antenna mask from RU
result = mplane_get_rx_antenna_mask(g_ctrl.ru_session,
ant_state->rx_array_name,
&rx_mask);
if (result == ANTENNA_CTRL_SUCCESS) {
nr_set_rx_antenna_bitmask(ant_state, rx_mask);
}
LOG_I(PHY, "[ANTENNA_CTRL] Synchronized antenna masks from RU: TX=0x%02X, RX=0x%02X\n",
tx_mask, rx_mask);
return true;
#else
LOG_W(PHY, "[ANTENNA_CTRL] M-Plane not compiled in\n");
return false;
#endif
}
@@ -0,0 +1,123 @@
#ifndef NR_ANTENNA_CONTROL_H
#define NR_ANTENNA_CONTROL_H
#include "nr_antenna_state.h"
#include <stdbool.h>
#ifdef __cplusplus
extern "C" {
#endif
/// Default port for the test socket server (development mode only)
#define NR_ANTENNA_CONTROL_TEST_PORT 9999
/**
* @brief Antenna control backend type
*/
typedef enum {
ANTENNA_CTRL_BACKEND_NONE = 0, ///< No external control
ANTENNA_CTRL_BACKEND_MPLANE, ///< O-RAN M-Plane NETCONF (production)
ANTENNA_CTRL_BACKEND_TEST_SOCKET ///< Test socket interface (development)
} nr_antenna_ctrl_backend_t;
/**
* @brief Initialize antenna control with O-RAN M-Plane NETCONF backend
*
* This is the O-RAN compliant production interface. It connects the
* antenna state to a real NETCONF session to an O-RU.
*
* When antenna masks are changed via nr_set_tx_antenna_bitmask() or
* similar functions, the changes are automatically propagated to the
* RU via NETCONF edit-config operations.
*
* @param ant_state Pointer to the antenna state to control
* @param ru_session Active M-Plane NETCONF session (from radio/fhi_72/mplane/)
* @return true if initialization successful
*/
bool nr_antenna_control_init_mplane(nr_antenna_state_t *ant_state,
void *ru_session);
/**
* @brief Start the test socket server (development mode only)
*
* Creates a simple TCP server for testing antenna control without
* NETCONF infrastructure. This is NOT O-RAN compliant.
*
* Commands supported (for backwards compatibility):
* MUTE TX <ant_id> - Mute TX antenna
* UNMUTE TX <ant_id> - Unmute TX antenna
* MUTE RX <ant_id> - Mute RX antenna
* UNMUTE RX <ant_id> - Unmute RX antenna
* SET TX MASK <hex> - Set TX antenna bitmask
* SET RX MASK <hex> - Set RX antenna bitmask
* STATUS - Get current antenna state
* STATS - Get energy saving statistics
* RESET STATS - Reset statistics
* QUIT - Close connection
*
* @param ant_state Pointer to the antenna state to control
* @param port TCP port to listen on (use 0 for default)
* @return true if server started successfully
*
* @note This function logs a warning that it's not O-RAN compliant
*/
bool nr_antenna_control_start_test_server(nr_antenna_state_t *ant_state, int port);
/**
* @brief Legacy function - start antenna control server
*
* @deprecated Use nr_antenna_control_init_mplane() for O-RAN compliance
* or nr_antenna_control_start_test_server() for testing.
*
* This function starts the test socket server for backwards compatibility.
* It logs a deprecation warning.
*/
bool nr_antenna_control_start(nr_antenna_state_t *ant_state, int port);
/**
* @brief Stop the antenna control interface
*
* Stops the test socket server if running.
* For M-Plane backend, this disconnects from NETCONF notifications.
*/
void nr_antenna_control_stop(void);
/**
* @brief Check if antenna control is active
* @return true if control interface is running
*/
bool nr_antenna_control_is_running(void);
/**
* @brief Get current antenna control backend type
* @return Backend type currently in use
*/
nr_antenna_ctrl_backend_t nr_antenna_control_get_backend(void);
/**
* @brief Synchronize local antenna state to RU via M-Plane NETCONF
*
* Sends the current antenna bitmask configuration to the RU.
* Only applicable when using the M-Plane NETCONF backend.
*
* @param ant_state Pointer to the antenna state
* @return true if synchronization successful or not applicable
*/
bool nr_antenna_control_sync_to_ru(nr_antenna_state_t *ant_state);
/**
* @brief Synchronize RU antenna state to local via M-Plane NETCONF
*
* Reads the current antenna bitmask from the RU and updates local state.
* Only applicable when using the M-Plane NETCONF backend.
*
* @param ant_state Pointer to the antenna state
* @return true if synchronization successful or not applicable
*/
bool nr_antenna_control_sync_from_ru(nr_antenna_state_t *ant_state);
#ifdef __cplusplus
}
#endif
#endif /* NR_ANTENNA_CONTROL_H */
@@ -0,0 +1,366 @@
#include "nr_antenna_state.h"
#include "common/utils/LOG/log.h"
#include <string.h>
#include <time.h>
// Helper to count set bits in a byte
static inline int popcount8(uint8_t x) {
int count = 0;
while (x) {
count += x & 1;
x >>= 1;
}
return count;
}
// Get current timestamp in microseconds
static inline uint64_t get_timestamp_us(void) {
struct timespec ts;
clock_gettime(CLOCK_MONOTONIC, &ts);
return (uint64_t)ts.tv_sec * 1000000ULL + (uint64_t)ts.tv_nsec / 1000ULL;
}
void nr_init_antenna_state(nr_antenna_state_t *state,
uint8_t num_tx,
uint8_t num_rx,
const char *tx_array_name,
const char *rx_array_name)
{
if (state == NULL) return;
memset(state, 0, sizeof(nr_antenna_state_t));
state->num_tx_antennas = (num_tx > NR_MAX_ANTENNAS) ? NR_MAX_ANTENNAS : num_tx;
state->num_rx_antennas = (num_rx > NR_MAX_ANTENNAS) ? NR_MAX_ANTENNAS : num_rx;
// Initialize with all configured antennas active
// Create bitmask with num_tx/num_rx lowest bits set
state->tx_antenna_bitmask = (1 << state->num_tx_antennas) - 1;
state->rx_antenna_bitmask = (1 << state->num_rx_antennas) - 1;
// Set O-RAN array names
if (tx_array_name) {
strncpy(state->tx_array_name, tx_array_name, sizeof(state->tx_array_name) - 1);
} else {
strncpy(state->tx_array_name, "tx-array-0", sizeof(state->tx_array_name) - 1);
}
if (rx_array_name) {
strncpy(state->rx_array_name, rx_array_name, sizeof(state->rx_array_name) - 1);
} else {
strncpy(state->rx_array_name, "rx-array-0", sizeof(state->rx_array_name) - 1);
}
pthread_mutex_init(&state->lock, NULL);
state->stats.last_update_timestamp = get_timestamp_us();
// Initialize recovery mode
state->recovery.in_recovery = false;
state->recovery.recovery_start_ts = 0;
state->recovery.recovery_duration_ms = 500; // 500ms default recovery time
// Protect antenna 0 by default (needed for SSB/PSS/SSS)
state->config.protect_antenna0 = true;
LOG_I(PHY, "[ANTENNA_STATE] Initialized: TX antennas=%d (mask=0x%02X, array=%s), "
"RX antennas=%d (mask=0x%02X, array=%s), antenna0_protected=%d\n",
state->num_tx_antennas, state->tx_antenna_bitmask, state->tx_array_name,
state->num_rx_antennas, state->rx_antenna_bitmask, state->rx_array_name,
state->config.protect_antenna0);
}
void nr_free_antenna_state(nr_antenna_state_t *state)
{
if (state == NULL) return;
pthread_mutex_destroy(&state->lock);
}
uint8_t nr_set_tx_antenna_bitmask(nr_antenna_state_t *state, uint8_t bitmask)
{
if (state == NULL) return 0;
pthread_mutex_lock(&state->lock);
uint8_t old_bitmask = state->tx_antenna_bitmask;
// Mask to only valid antenna bits
uint8_t valid_mask = (1 << state->num_tx_antennas) - 1;
state->tx_antenna_bitmask = bitmask & valid_mask;
// Update statistics
if (state->tx_antenna_bitmask != old_bitmask) {
state->stats.tx_mute_events++;
state->stats.last_update_timestamp = get_timestamp_us();
LOG_I(PHY, "[ANTENNA_STATE] TX bitmask changed: 0x%02X -> 0x%02X (array=%s, active=%d/%d)\n",
old_bitmask, state->tx_antenna_bitmask, state->tx_array_name,
popcount8(state->tx_antenna_bitmask), state->num_tx_antennas);
}
pthread_mutex_unlock(&state->lock);
return old_bitmask;
}
uint8_t nr_set_rx_antenna_bitmask(nr_antenna_state_t *state, uint8_t bitmask)
{
if (state == NULL) return 0;
pthread_mutex_lock(&state->lock);
uint8_t old_bitmask = state->rx_antenna_bitmask;
// Mask to only valid antenna bits
uint8_t valid_mask = (1 << state->num_rx_antennas) - 1;
state->rx_antenna_bitmask = bitmask & valid_mask;
// Update statistics
if (state->rx_antenna_bitmask != old_bitmask) {
state->stats.rx_mute_events++;
state->stats.last_update_timestamp = get_timestamp_us();
LOG_I(PHY, "[ANTENNA_STATE] RX bitmask changed: 0x%02X -> 0x%02X (array=%s, active=%d/%d)\n",
old_bitmask, state->rx_antenna_bitmask, state->rx_array_name,
popcount8(state->rx_antenna_bitmask), state->num_rx_antennas);
}
pthread_mutex_unlock(&state->lock);
return old_bitmask;
}
uint8_t nr_get_tx_antenna_bitmask(const nr_antenna_state_t *state)
{
if (state == NULL) return 0xFF;
pthread_mutex_lock((pthread_mutex_t*)&state->lock);
uint8_t m = state->tx_antenna_bitmask;
pthread_mutex_unlock((pthread_mutex_t*)&state->lock);
return m;
}
uint8_t nr_get_rx_antenna_bitmask(const nr_antenna_state_t *state)
{
if (state == NULL) return 0xFF;
pthread_mutex_lock((pthread_mutex_t*)&state->lock);
uint8_t m = state->rx_antenna_bitmask;
pthread_mutex_unlock((pthread_mutex_t*)&state->lock);
return m;
}
bool nr_is_tx_antenna_active(const nr_antenna_state_t *state, uint8_t ant_id)
{
if (state == NULL || ant_id >= state->num_tx_antennas) return false;
return (state->tx_antenna_bitmask & (1 << ant_id)) != 0;
}
bool nr_is_rx_antenna_active(const nr_antenna_state_t *state, uint8_t ant_id)
{
if (state == NULL || ant_id >= state->num_rx_antennas) return false;
return (state->rx_antenna_bitmask & (1 << ant_id)) != 0;
}
int nr_get_active_tx_antenna_count(const nr_antenna_state_t *state)
{
if (state == NULL) return 0;
return popcount8(state->tx_antenna_bitmask);
}
int nr_get_active_rx_antenna_count(const nr_antenna_state_t *state)
{
if (state == NULL) return 0;
return popcount8(state->rx_antenna_bitmask);
}
void nr_mute_tx_antenna(nr_antenna_state_t *state, uint8_t ant_id)
{
if (state == NULL || ant_id >= state->num_tx_antennas) return;
// Protect antenna 0 if configured (needed for SSB/PSS/SSS transmission)
if (ant_id == 0 && state->config.protect_antenna0) {
LOG_W(PHY, "[ANTENNA_STATE] Cannot mute TX antenna 0: protected for SSB transmission\n");
LOG_W(PHY, "[ANTENNA_STATE] Use 'SET TX MASK' to override protection if needed\n");
return;
}
uint8_t new_mask = state->tx_antenna_bitmask & ~(1 << ant_id);
nr_set_tx_antenna_bitmask(state, new_mask);
}
void nr_unmute_tx_antenna(nr_antenna_state_t *state, uint8_t ant_id)
{
if (state == NULL || ant_id >= state->num_tx_antennas) return;
uint8_t new_mask = state->tx_antenna_bitmask | (1 << ant_id);
nr_set_tx_antenna_bitmask(state, new_mask);
}
void nr_mute_rx_antenna(nr_antenna_state_t *state, uint8_t ant_id)
{
if (state == NULL || ant_id >= state->num_rx_antennas) return;
uint8_t new_mask = state->rx_antenna_bitmask & ~(1 << ant_id);
nr_set_rx_antenna_bitmask(state, new_mask);
}
void nr_unmute_rx_antenna(nr_antenna_state_t *state, uint8_t ant_id)
{
if (state == NULL || ant_id >= state->num_rx_antennas) return;
uint8_t new_mask = state->rx_antenna_bitmask | (1 << ant_id);
nr_set_rx_antenna_bitmask(state, new_mask);
}
int nr_get_effective_mimo_layers(const nr_antenna_state_t *state, int requested_layers)
{
if (state == NULL) return requested_layers;
int active_tx = nr_get_active_tx_antenna_count(state);
// MIMO layers cannot exceed active TX antennas
int effective = (requested_layers < active_tx) ? requested_layers : active_tx;
// Must have at least 1 layer if any antenna is active
if (effective < 1 && active_tx > 0) {
effective = 1;
}
return effective;
}
void nr_log_antenna_state(const nr_antenna_state_t *state)
{
if (state == NULL) {
LOG_I(PHY, "[ANTENNA_STATE] State is NULL\n");
return;
}
LOG_I(PHY, "[ANTENNA_STATE] === Antenna State Report ===\n");
LOG_I(PHY, "[ANTENNA_STATE] TX Array: %s\n", state->tx_array_name);
LOG_I(PHY, "[ANTENNA_STATE] Configured: %d antennas\n", state->num_tx_antennas);
LOG_I(PHY, "[ANTENNA_STATE] Bitmask: 0x%02X\n", state->tx_antenna_bitmask);
LOG_I(PHY, "[ANTENNA_STATE] Active: %d antennas\n", nr_get_active_tx_antenna_count(state));
LOG_I(PHY, "[ANTENNA_STATE] Individual: ");
for (int i = 0; i < state->num_tx_antennas; i++) {
LOG_I(PHY, "TX%d=%s ", i, nr_is_tx_antenna_active(state, i) ? "ON" : "OFF");
}
LOG_I(PHY, "\n");
LOG_I(PHY, "[ANTENNA_STATE] RX Array: %s\n", state->rx_array_name);
LOG_I(PHY, "[ANTENNA_STATE] Configured: %d antennas\n", state->num_rx_antennas);
LOG_I(PHY, "[ANTENNA_STATE] Bitmask: 0x%02X\n", state->rx_antenna_bitmask);
LOG_I(PHY, "[ANTENNA_STATE] Active: %d antennas\n", nr_get_active_rx_antenna_count(state));
LOG_I(PHY, "[ANTENNA_STATE] Statistics:\n");
LOG_I(PHY, "[ANTENNA_STATE] TX mute events: %lu\n", state->stats.tx_mute_events);
LOG_I(PHY, "[ANTENNA_STATE] RX mute events: %lu\n", state->stats.rx_mute_events);
LOG_I(PHY, "[ANTENNA_STATE] Total muted slots: %lu\n", state->stats.total_muted_slots);
LOG_I(PHY, "[ANTENNA_STATE] =============================\n");
}
void nr_get_antenna_stats(const nr_antenna_state_t *state,
uint64_t *tx_mute_events,
uint64_t *rx_mute_events,
uint64_t *total_muted_slots)
{
if (state == NULL) return;
if (tx_mute_events) *tx_mute_events = state->stats.tx_mute_events;
if (rx_mute_events) *rx_mute_events = state->stats.rx_mute_events;
if (total_muted_slots) *total_muted_slots = state->stats.total_muted_slots;
}
void nr_reset_antenna_stats(nr_antenna_state_t *state)
{
if (state == NULL) return;
pthread_mutex_lock(&state->lock);
state->stats.tx_mute_events = 0;
state->stats.rx_mute_events = 0;
state->stats.total_muted_slots = 0;
state->stats.energy_saved_estimate = 0;
state->stats.last_update_timestamp = get_timestamp_us();
LOG_I(PHY, "[ANTENNA_STATE] Statistics reset\n");
pthread_mutex_unlock(&state->lock);
}
void nr_unmute_all_tx_antennas(nr_antenna_state_t *state)
{
if (state == NULL) return;
pthread_mutex_lock(&state->lock);
uint8_t old_mask = state->tx_antenna_bitmask;
uint8_t all_on = (1 << state->num_tx_antennas) - 1;
state->tx_antenna_bitmask = all_on;
// Enter recovery mode if we were muted
if (old_mask != all_on) {
state->recovery.in_recovery = true;
state->recovery.recovery_start_ts = get_timestamp_us();
LOG_I(PHY, "[ANTENNA_STATE] Entering recovery mode for %u ms after unmuting TX antennas\n",
state->recovery.recovery_duration_ms);
}
state->stats.tx_mute_events++;
state->stats.last_update_timestamp = get_timestamp_us();
LOG_I(PHY, "[ANTENNA_STATE] All TX antennas unmuted: 0x%02X -> 0x%02X (array=%s)\n",
old_mask, all_on, state->tx_array_name);
pthread_mutex_unlock(&state->lock);
}
void nr_unmute_all_rx_antennas(nr_antenna_state_t *state)
{
if (state == NULL) return;
pthread_mutex_lock(&state->lock);
uint8_t old_mask = state->rx_antenna_bitmask;
uint8_t all_on = (1 << state->num_rx_antennas) - 1;
state->rx_antenna_bitmask = all_on;
state->stats.rx_mute_events++;
state->stats.last_update_timestamp = get_timestamp_us();
LOG_I(PHY, "[ANTENNA_STATE] All RX antennas unmuted: 0x%02X -> 0x%02X (array=%s)\n",
old_mask, all_on, state->rx_array_name);
pthread_mutex_unlock(&state->lock);
}
bool nr_is_in_recovery_mode(const nr_antenna_state_t *state)
{
if (state == NULL) return false;
if (!state->recovery.in_recovery) return false;
// Check if recovery period has elapsed
uint64_t now = get_timestamp_us();
uint64_t elapsed_us = now - state->recovery.recovery_start_ts;
uint64_t duration_us = (uint64_t)state->recovery.recovery_duration_ms * 1000ULL;
if (elapsed_us >= duration_us) {
// Recovery complete - cast away const for state update
nr_antenna_state_t *s = (nr_antenna_state_t *)state;
s->recovery.in_recovery = false;
LOG_I(PHY, "[ANTENNA_STATE] Recovery mode complete after %lu ms\n", elapsed_us / 1000);
return false;
}
return true;
}
void nr_set_antenna0_protection(nr_antenna_state_t *state, bool protect)
{
if (state == NULL) return;
pthread_mutex_lock(&state->lock);
state->config.protect_antenna0 = protect;
LOG_I(PHY, "[ANTENNA_STATE] Antenna 0 protection %s\n", protect ? "enabled" : "disabled");
pthread_mutex_unlock(&state->lock);
}
@@ -0,0 +1,239 @@
#ifndef NR_ANTENNA_STATE_H
#define NR_ANTENNA_STATE_H
#include <stdint.h>
#include <stdbool.h>
#include <pthread.h>
#ifdef __cplusplus
extern "C" {
#endif
/// Maximum number of physical antennas supported
#define NR_MAX_ANTENNAS 8
/// O-RAN compliant antenna state for TX/RX arrays
/// Mirrors o-ran-uplane-conf.yang: tx-array-antenna-mask-config
typedef struct {
/// Antenna bitmask per O-RAN WG4 spec: bit N = 1 means antenna N is active
/// Corresponds to o-ran-uplane-conf:antenna-bitmask
uint8_t tx_antenna_bitmask;
uint8_t rx_antenna_bitmask;
/// Total number of configured antennas
uint8_t num_tx_antennas;
uint8_t num_rx_antennas;
/// Thread safety for runtime updates
pthread_mutex_t lock;
/// Energy saving statistics (for research/monitoring)
struct {
uint64_t tx_mute_events; ///< Number of TX mute operations
uint64_t rx_mute_events; ///< Number of RX mute operations
uint64_t total_muted_slots; ///< Total slots with at least one antenna muted
uint64_t energy_saved_estimate; ///< Estimated energy saved (arbitrary units)
uint64_t last_update_timestamp; ///< Last update time (for O1 notifications)
} stats;
/// O-RAN compliance: array name for NETCONF identification
char tx_array_name[64];
char rx_array_name[64];
/// Recovery mode: after unmuting, allow CSI to update before using full capacity
struct {
bool in_recovery; ///< Currently in recovery mode
uint64_t recovery_start_ts; ///< Timestamp when recovery started (us)
uint32_t recovery_duration_ms; ///< Recovery duration in milliseconds (default 500ms)
} recovery;
/// Configuration flags
struct {
bool protect_antenna0; ///< Never mute antenna 0 (needed for SSB)
} config;
} nr_antenna_state_t;
/**
* @brief Initialize antenna state with all antennas active
* @param state Pointer to antenna state structure
* @param num_tx Number of TX antennas
* @param num_rx Number of RX antennas
* @param tx_array_name O-RAN array name for TX (e.g., "tx-array-0")
* @param rx_array_name O-RAN array name for RX (e.g., "rx-array-0")
*/
void nr_init_antenna_state(nr_antenna_state_t *state,
uint8_t num_tx,
uint8_t num_rx,
const char *tx_array_name,
const char *rx_array_name);
/**
* @brief Free antenna state resources
* @param state Pointer to antenna state structure
*/
void nr_free_antenna_state(nr_antenna_state_t *state);
/**
* @brief Set TX antenna bitmask (O-RAN O1 interface equivalent)
*
* This function implements the O-RAN WG4 MPLANE-TRX-CONTROL feature.
* Corresponds to NETCONF edit-config on tx-array-antenna-mask-config.
*
* @param state Pointer to antenna state structure
* @param bitmask Antenna bitmask (bit N = 1 means antenna N active)
* @return Previous bitmask value
*/
uint8_t nr_set_tx_antenna_bitmask(nr_antenna_state_t *state, uint8_t bitmask);
/**
* @brief Set RX antenna bitmask (O-RAN O1 interface equivalent)
* @param state Pointer to antenna state structure
* @param bitmask Antenna bitmask (bit N = 1 means antenna N active)
* @return Previous bitmask value
*/
uint8_t nr_set_rx_antenna_bitmask(nr_antenna_state_t *state, uint8_t bitmask);
/**
* @brief Get current TX antenna bitmask
* @param state Pointer to antenna state structure
* @return Current TX antenna bitmask
*/
uint8_t nr_get_tx_antenna_bitmask(const nr_antenna_state_t *state);
/**
* @brief Get current RX antenna bitmask
* @param state Pointer to antenna state structure
* @return Current RX antenna bitmask
*/
uint8_t nr_get_rx_antenna_bitmask(const nr_antenna_state_t *state);
/**
* @brief Check if a specific TX antenna is active
* @param state Pointer to antenna state structure
* @param ant_id Antenna index (0-based)
* @return true if antenna is active, false if muted
*/
bool nr_is_tx_antenna_active(const nr_antenna_state_t *state, uint8_t ant_id);
/**
* @brief Check if a specific RX antenna is active
* @param state Pointer to antenna state structure
* @param ant_id Antenna index (0-based)
* @return true if antenna is active, false if muted
*/
bool nr_is_rx_antenna_active(const nr_antenna_state_t *state, uint8_t ant_id);
/**
* @brief Get count of currently active TX antennas
* @param state Pointer to antenna state structure
* @return Number of active TX antennas
*/
int nr_get_active_tx_antenna_count(const nr_antenna_state_t *state);
/**
* @brief Get count of currently active RX antennas
* @param state Pointer to antenna state structure
* @return Number of active RX antennas
*/
int nr_get_active_rx_antenna_count(const nr_antenna_state_t *state);
/**
* @brief Mute a specific TX antenna
* @param state Pointer to antenna state structure
* @param ant_id Antenna index to mute (0-based)
*/
void nr_mute_tx_antenna(nr_antenna_state_t *state, uint8_t ant_id);
/**
* @brief Unmute a specific TX antenna
* @param state Pointer to antenna state structure
* @param ant_id Antenna index to unmute (0-based)
*/
void nr_unmute_tx_antenna(nr_antenna_state_t *state, uint8_t ant_id);
/**
* @brief Mute a specific RX antenna
* @param state Pointer to antenna state structure
* @param ant_id Antenna index to mute (0-based)
*/
void nr_mute_rx_antenna(nr_antenna_state_t *state, uint8_t ant_id);
/**
* @brief Unmute a specific RX antenna
* @param state Pointer to antenna state structure
* @param ant_id Antenna index to unmute (0-based)
*/
void nr_unmute_rx_antenna(nr_antenna_state_t *state, uint8_t ant_id);
/**
* @brief Get effective number of MIMO layers based on active antennas
*
* For MIMO, the number of layers is limited by active antennas.
* This function returns min(requested_layers, active_antennas).
*
* @param state Pointer to antenna state structure
* @param requested_layers Originally requested number of layers
* @return Effective number of layers that can be used
*/
int nr_get_effective_mimo_layers(const nr_antenna_state_t *state, int requested_layers);
/**
* @brief Log antenna state for debugging
* @param state Pointer to antenna state structure
*/
void nr_log_antenna_state(const nr_antenna_state_t *state);
/**
* @brief Get energy saving statistics
* @param state Pointer to antenna state structure
* @param tx_mute_events Output: number of TX mute events
* @param rx_mute_events Output: number of RX mute events
* @param total_muted_slots Output: total slots with muting
*/
void nr_get_antenna_stats(const nr_antenna_state_t *state,
uint64_t *tx_mute_events,
uint64_t *rx_mute_events,
uint64_t *total_muted_slots);
/**
* @brief Reset energy saving statistics
* @param state Pointer to antenna state structure
*/
void nr_reset_antenna_stats(nr_antenna_state_t *state);
/**
* @brief Unmute all TX antennas and enter recovery mode
* @param state Pointer to antenna state structure
*
* This initiates a recovery period during which the scheduler
* uses conservative settings to allow UE CSI reports to update.
*/
void nr_unmute_all_tx_antennas(nr_antenna_state_t *state);
/**
* @brief Unmute all RX antennas
* @param state Pointer to antenna state structure
*/
void nr_unmute_all_rx_antennas(nr_antenna_state_t *state);
/**
* @brief Check if system is in recovery mode after unmuting
* @param state Pointer to antenna state structure
* @return true if in recovery mode
*/
bool nr_is_in_recovery_mode(const nr_antenna_state_t *state);
/**
* @brief Set antenna 0 protection (SSB antenna)
* @param state Pointer to antenna state structure
* @param protect true to prevent muting antenna 0
*/
void nr_set_antenna0_protection(nr_antenna_state_t *state, bool protect);
#ifdef __cplusplus
}
#endif
#endif /* NR_ANTENNA_STATE_H */
+3
View File
@@ -43,6 +43,7 @@
#include "PHY/CODING/nrLDPC_extern.h"
#include "PHY/CODING/nrLDPC_decoder/nrLDPC_types.h"
#include "nfapi_nr_interface_scf.h"
#include "PHY/NR_TRANSPORT/nr_antenna_state.h"
#define MAX_NUM_RU_PER_gNB 8
#define MAX_PUCCH0_NID 8
@@ -445,6 +446,8 @@ typedef struct PHY_VARS_gNB_s {
bool enable_analog_das;
nr_antenna_state_t antenna_state;
time_stats_t l1_tx_proc;
time_stats_t l1_rx_proc;
+33 -1
View File
@@ -46,6 +46,19 @@
#include <time.h>
// Rate-limit TX power logs (every N slots per antenna task, startSymbol==0 only)
#ifndef TXF_PWR_LOG_PERIOD_FRAME
#define TXF_PWR_LOG_PERIOD_FRAME 100
#endif
static inline int count_nnz_c16(const c16_t *v, int n)
{
int nnz = 0;
for (int i = 0; i < n; i++)
nnz += (v[i].r != 0) || (v[i].i != 0);
return nnz;
}
// RU OFDM Modulator gNodeB
// OFDM modulation core routine, generates a first_symbol to first_symbol+num_symbols on a particular slot and TX antenna port
void nr_feptx0(RU_t *ru, int tti_tx, int first_symbol, int num_symbols, int aa)
@@ -246,14 +259,33 @@ void nr_feptx(void *arg)
}
// If there is no digital beamforming we just need to copy the data to RU
if (ru->config.dbt_config.num_dig_beams == 0 || ru->gNB_list[0]->common_vars.analog_bf)
if (ru->config.dbt_config.num_dig_beams == 0 || ru->gNB_list[0]->common_vars.analog_bf) {
memcpy((void*)&ru->common.txdataF_BF[tx_idx][txdataF_BF_offset],
(void*)&ru->gNB_list[0]->common_vars.txdataF[bb][aa][txdataF_offset],
numSamples * sizeof(int32_t));
}
else {
AssertFatal(false, "This needs to be fixed by using appropriate beams from config\n");
}
if (startSymbol == 0 && ((ru->proc.frame_tx % TXF_PWR_LOG_PERIOD_FRAME) == 0)) {
const c16_t *src = (const c16_t *)&ru->gNB_list[0]->common_vars.txdataF[bb][aa][txdataF_offset];
const c16_t *dst = (const c16_t *)&ru->common.txdataF_BF[tx_idx][txdataF_BF_offset];
// signal_energy_nodc() in OAI expects "2*N" for c16_t buffers (see existing usage in nr_feptx_ofdm()).
const int src_pwr_db = dB_fixed(signal_energy_nodc((c16_t *)src, 2 * numSamples));
const int dst_pwr_db = dB_fixed(signal_energy_nodc((c16_t *)dst, 2 * numSamples));
const int src_nnz = count_nnz_c16(src, numSamples);
const int dst_nnz = count_nnz_c16(dst, numSamples);
LOG_I(PHY,
"[TXF_PWR] frame=%d slot=%d beam=%d aa=%d tx_idx=%d sym0 len=%d: "
"SRC pwr=%d dB nnz=%d | DST pwr=%d dB nnz=%d\n",
ru->proc.frame_tx, slot, bb, aa, tx_idx, numSamples,
src_pwr_db, src_nnz, dst_pwr_db, dst_nnz);
}
if (tx_idx == 0)
stop_meas(&ru->precoding_stats);
VCD_SIGNAL_DUMPER_DUMP_FUNCTION_BY_NAME(VCD_SIGNAL_DUMPER_FUNCTIONS_PHY_PROCEDURES_RU_FEPTX_PREC+feptx->aid , 0);
@@ -435,7 +435,7 @@ static bool allocate_dl_retransmission(gNB_MAC_INST *nr_mac,
NR_UE_DL_BWP_t *dl_bwp = &UE->current_DL_BWP;
NR_UE_UL_BWP_t *ul_bwp = &UE->current_UL_BWP;
NR_sched_pdsch_t new_sched = sched_ctrl->harq_processes[current_harq_pid].sched_pdsch;
int layers = get_dl_nrOfLayers(sched_ctrl, dl_bwp->dci_format);
int layers = get_dl_nrOfLayers(nr_mac, sched_ctrl, dl_bwp->dci_format);
int pm_index = get_pm_index(nr_mac, UE, dl_bwp->dci_format, layers, nr_mac->radio_config.pdsch_AntennaPorts.XP);
// If the RI changed between current rtx and a previous transmission
@@ -732,7 +732,7 @@ static void pf_dl(gNB_MAC_INST *mac,
sched_ctrl->dl_bler_stats.mcs = selected_mcs;
} else
selected_mcs = get_mcs_from_bler(bo, stats, &sched_ctrl->dl_bler_stats, max_mcs, frame);
int l = get_dl_nrOfLayers(sched_ctrl, current_BWP->dci_format);
int l = get_dl_nrOfLayers(mac, sched_ctrl, current_BWP->dci_format);
const uint8_t Qm = nr_get_Qm_dl(selected_mcs, current_BWP->mcsTableIdx);
const uint16_t R = nr_get_code_rate_dl(selected_mcs, current_BWP->mcsTableIdx);
uint32_t tbs = nr_compute_tbs(Qm,
@@ -877,7 +877,7 @@ static void pf_dl(gNB_MAC_INST *mac,
sched_ctrl->cce_index = CCEIndex;
fill_pdcch_vrb_map(mac, CC_id, &sched_ctrl->sched_pdcch, CCEIndex, sched_ctrl->aggregation_level, beam.idx);
int l = get_dl_nrOfLayers(sched_ctrl, dl_bwp->dci_format);
int l = get_dl_nrOfLayers(mac, sched_ctrl, dl_bwp->dci_format);
NR_sched_pdsch_t sched_pdsch = {
// rbSize below
.rbStart = rbStart,
@@ -127,14 +127,13 @@ static void determine_aggregation_level_search_order(int agg_level_search_order[
static int nr_mac_interrupt_ue_transmission(gNB_MAC_INST *mac, NR_UE_info_t *UE, int slots, int slots_per_frame);
uint8_t get_dl_nrOfLayers(const NR_UE_sched_ctrl_t *sched_ctrl, const nr_dci_format_t dci_format)
uint8_t get_dl_nrOfLayers(const gNB_MAC_INST *mac, const NR_UE_sched_ctrl_t *sched_ctrl, const nr_dci_format_t dci_format)
{
// TODO check this but it should be enough for now
// if there is not csi report activated RI is 0 from initialization
if(dci_format == NR_DL_DCI_FORMAT_1_0)
return 1;
else
return sched_ctrl->CSI_report.cri_ri_li_pmi_cqi_report.ri + 1;
int layers = (dci_format == NR_DL_DCI_FORMAT_1_0) ? 1
: sched_ctrl->CSI_report.cri_ri_li_pmi_cqi_report.ri + 1;
if (mac && mac->antenna_state)
return nr_get_effective_mimo_layers(mac->antenna_state, layers);
return layers;
}
int get_ul_nrOfLayers(const NR_UE_sched_ctrl_t *sched_ctrl, const nr_dci_format_t dci_format)
@@ -191,63 +190,92 @@ uint16_t get_pm_index(const gNB_MAC_INST *nrmac,
int layers,
int xp_pdsch_antenna_ports)
{
if (dci_format == NR_DL_DCI_FORMAT_1_0 || nrmac->identity_pm || xp_pdsch_antenna_ports == 1)
return 0; //identity matrix (basic 5G configuration handled by PMI report is with XP antennas)
const NR_UE_sched_ctrl_t *sched_ctrl = &UE->UE_sched_ctrl;
const int report_id = sched_ctrl->CSI_report.cri_ri_li_pmi_cqi_report.csi_report_id;
const nr_csi_report_t *csi_report = &UE->csi_report_template[report_id];
const int N1 = csi_report->N1;
const int N2 = csi_report->N2;
const int antenna_ports = (N1 * N2) << 1;
if (antenna_ports < 2)
return 0; // single antenna port
if (dci_format == NR_DL_DCI_FORMAT_1_0 || nrmac->identity_pm || xp_pdsch_antenna_ports == 1)
return 0; //identity matrix (basic 5G configuration handled by PMI report is with XP antennas)
const NR_UE_sched_ctrl_t *sched_ctrl = &UE->UE_sched_ctrl;
int x1 = sched_ctrl->CSI_report.cri_ri_li_pmi_cqi_report.pmi_x1;
const int x2 = sched_ctrl->CSI_report.cri_ri_li_pmi_cqi_report.pmi_x2;
LOG_D(NR_MAC,"PMI report: x1 %d x2 %d layers: %d\n", x1, x2, layers);
int prev_layers_size = 0;
for (int i = 1; i < layers; i++)
prev_layers_size += nrmac->precoding_matrix_size[i - 1];
// need to return PM index to matrix initialized in init_DL_MIMO_codebook
// index 0 is for identity matrix
// order of matrices depends on layers to be transmitted
// elements from 1 to n for 1 layer
// elements from n+1 to m for 2 layers etc.
if (antenna_ports == 2)
return 1 + prev_layers_size + x2; // 0 for identity matrix
else {
int idx = layers - 1;
// the order of i1x in X1 report needs to be verified
// the standard is not very clear (Table 6.3.1.1.2-7 in 38.212)
// it says: PMI wideband information fields X1 , from left to right
int bitlen = csi_report->csi_meas_bitlen.pmi_i13_bitlen[idx];
if (layers == 1 && bitlen != 0) {
LOG_E(NR_MAC, "Invalid i13 bit length %d for single layer! It should be 0\n", bitlen);
return 0;
int reported_ri_layers = sched_ctrl->CSI_report.cri_ri_li_pmi_cqi_report.ri + 1;
if (layers != reported_ri_layers) {
LOG_D(NR_MAC, "PMI mismatch: using %d layers but RI report is for %d layers, using identity matrix\n",
layers, reported_ri_layers);
return 0;
}
const int i13 = x1 & ((1 << bitlen) - 1);
x1 >>= bitlen;
bitlen = csi_report->csi_meas_bitlen.pmi_i12_bitlen[idx];
const int i12 = x1 & ((1 << bitlen) - 1);
x1 >>= bitlen;
bitlen = csi_report->csi_meas_bitlen.pmi_i11_bitlen[idx];
const int i11 = x1 & ((1 << bitlen) - 1);
const int i2 = x2;
int k1, k2;
get_k1_k2_indices(layers, N1, N2, i13, &k1, &k2); // get indices k1 and k2 for PHY matrix (not actual k1 and k2 values)
const int O2 = N2 == 1 ? 1 : 4;
int O1 = 4; //Horizontal beam oversampling = 4 for more than 2 antenna ports
int max_i2 = 0;
int lay_index = 0;
max_i2 = layers == 1 ? 4 : 2;
int K1, K2;
get_K1_K2(N1, N2, &K1, &K2, layers);
// computing precoding matrix index according to rule set in allocation function init_codebook_gNB
lay_index = i2 + (i11 * max_i2) + (i12 * max_i2 * N1 * O1) + (k1 * max_i2 * N1 * O1 * N2 * O2) + (k2 * max_i2 * N1 * O1 * N2 * O2 * K1);
return 1 + prev_layers_size + lay_index;
}
nr_antenna_state_t* ant_state = nrmac->antenna_state;
if (ant_state) {
int active_tx = nr_get_active_tx_antenna_count(ant_state);
int configured_tx = ant_state->num_tx_antennas;
if (nr_is_in_recovery_mode(ant_state)) {
LOG_D(NR_MAC, "[ANTENNA_MUTE] Recovery mode active, using identity matrix\n");
return 0;
}
if (active_tx < configured_tx && layers < configured_tx) {
LOG_D(NR_MAC, "[ANTENNA_MUTE] Using identity matrix: %d/%d TX antennas active, %d layers\n",
active_tx, configured_tx, layers);
return 0;
}
}
const int report_id = sched_ctrl->CSI_report.cri_ri_li_pmi_cqi_report.csi_report_id;
const nr_csi_report_t *csi_report = &UE->csi_report_template[report_id];
const int N1 = csi_report->N1;
const int N2 = csi_report->N2;
const int antenna_ports = (N1 * N2) << 1;
if (antenna_ports < 2) return 0; // single antenna port
int x1 = sched_ctrl->CSI_report.cri_ri_li_pmi_cqi_report.pmi_x1;
const int x2 = sched_ctrl->CSI_report.cri_ri_li_pmi_cqi_report.pmi_x2;
LOG_D(NR_MAC,"PMI report: x1 %d x2 %d layers: %d\n", x1, x2, layers);
int prev_layers_size = 0;
for (int i = 1; i < layers; i++)
prev_layers_size += nrmac->precoding_matrix_size[i - 1];
// need to return PM index to matrix initialized in init_DL_MIMO_codebook
// index 0 is for identity matrix
// order of matrices depends on layers to be transmitted
// elements from 1 to n for 1 layer
// elements from n+1 to m for 2 layers etc.
int pm_index;
if (antenna_ports == 2)
pm_index = 1 + prev_layers_size + x2; // 0 for identity matrix
else {
int idx = layers - 1;
// the order of i1x in X1 report needs to be verified
// the standard is not very clear (Table 6.3.1.1.2-7 in 38.212)
// it says: PMI wideband information fields X1 , from left to right
int bitlen = csi_report->csi_meas_bitlen.pmi_i13_bitlen[idx];
if (layers == 1 && bitlen != 0) {
LOG_E(NR_MAC, "Invalid i13 bit length %d for single layer! It should be 0\n", bitlen);
return 0;
}
const int i13 = x1 & ((1 << bitlen) - 1);
x1 >>= bitlen;
bitlen = csi_report->csi_meas_bitlen.pmi_i12_bitlen[idx];
const int i12 = x1 & ((1 << bitlen) - 1);
x1 >>= bitlen;
bitlen = csi_report->csi_meas_bitlen.pmi_i11_bitlen[idx];
const int i11 = x1 & ((1 << bitlen) - 1);
const int i2 = x2;
int k1, k2;
get_k1_k2_indices(layers, N1, N2, i13, &k1, &k2); // get indices k1 and k2 for PHY matrix (not actual k1 and k2 values)
const int O2 = N2 == 1 ? 1 : 4;
int O1 = 4; //Horizontal beam oversampling = 4 for more than 2 antenna ports
int max_i2 = 0;
int lay_index = 0;
max_i2 = layers == 1 ? 4 : 2;
int K1, K2;
get_K1_K2(N1, N2, &K1, &K2, layers);
// computing precoding matrix index according to rule set in allocation function init_codebook_gNB
lay_index = i2 + (i11 * max_i2) + (i12 * max_i2 * N1 * O1) + (k1 * max_i2 * N1 * O1 * N2 * O2) + (k2 * max_i2 * N1 * O1 * N2 * O2 * K1);
pm_index = 1 + prev_layers_size + lay_index;
}
int max_valid_pm = prev_layers_size + nrmac->precoding_matrix_size[layers - 1];
if (pm_index < 1 || pm_index > max_valid_pm) {
LOG_W(NR_MAC, "PM index %d out of range [1, %d] for %d layers, using identity matrix\n",
pm_index, max_valid_pm, layers);
return 0;
}
return pm_index;
}
// look-up table for AMC. Based on BLER vs SNR curves from the nr_dlsim simulation
@@ -544,7 +544,7 @@ static void evaluate_sinr_report(gNB_MAC_INST *nrmac,
stats->num_sinr_meas++;
const int mcs_table = UE->current_DL_BWP.mcsTableIdx;
const int nrOfLayers = get_dl_nrOfLayers(sched_ctrl, UE->current_DL_BWP.dci_format);
const int nrOfLayers = get_dl_nrOfLayers(nrmac, sched_ctrl, UE->current_DL_BWP.dci_format);
sched_ctrl->dl_max_mcs = get_mcs_from_SINRx10(mcs_table, sinr_report->SINRx10[0], nrOfLayers);
LOG_D(MAC, "Reported SSB-SINR = %d.%d, dl_max_mcs %d\n", sinr_report->SINRx10[0] / 10, sinr_report->SINRx10[0] % 10, sched_ctrl->dl_max_mcs);
+1 -1
View File
@@ -399,7 +399,7 @@ uint16_t get_pm_index(const gNB_MAC_INST *nrmac,
int get_mcs_from_SINRx10(int mcs_table, int SINRx10, int Nl);
uint8_t get_mcs_from_cqi(int mcs_table, int cqi_table, int cqi_idx);
uint8_t get_dl_nrOfLayers(const NR_UE_sched_ctrl_t *sched_ctrl, const nr_dci_format_t dci_format);
uint8_t get_dl_nrOfLayers(const gNB_MAC_INST *mac, const NR_UE_sched_ctrl_t *sched_ctrl, const nr_dci_format_t dci_format);
int get_ul_nrOfLayers(const NR_UE_sched_ctrl_t *sched_ctrl, const nr_dci_format_t dci_format);
void free_sched_pucch_list(NR_UE_sched_ctrl_t *sched_ctrl);
File diff suppressed because it is too large Load Diff
+1
View File
@@ -32,6 +32,7 @@ target_sources(oran_fhlib_5g_mplane PRIVATE
subscribe-mplane.c
config-mplane.c
ru-mplane-api.c
antenna-control.c
xml/get-xml.c
yang/get-yang.c
yang/create-yang-config.c
+368
View File
@@ -0,0 +1,368 @@
#include "antenna-control.h"
#include "ru-mplane-api.h"
#include "xml/get-xml.h"
#include "common/utils/assertions.h"
// Forward declaration - actual function in config-mplane.c
extern bool edit_val_commmit_rpc(ru_session_t *ru_session, const char *content);
// Forward declaration - actual function in get-mplane.c
extern bool get_mplane(ru_session_t *ru_session, char **answer);
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <libyang/libyang.h>
#include <nc_client.h>
const char *antenna_ctrl_result_str(antenna_ctrl_result_t result)
{
switch (result) {
case ANTENNA_CTRL_SUCCESS:
return "Success";
case ANTENNA_CTRL_ERR_NO_SESSION:
return "No active NETCONF session";
case ANTENNA_CTRL_ERR_FEATURE_NOT_SUPPORTED:
return "MPLANE-TRX-CONTROL feature not supported by RU";
case ANTENNA_CTRL_ERR_INVALID_ARRAY_NAME:
return "Invalid array name";
case ANTENNA_CTRL_ERR_INVALID_MASK:
return "Invalid antenna bitmask";
case ANTENNA_CTRL_ERR_NETCONF_FAILED:
return "NETCONF RPC failed";
case ANTENNA_CTRL_ERR_VALIDATION_FAILED:
return "Configuration validation failed";
case ANTENNA_CTRL_ERR_COMMIT_FAILED:
return "Configuration commit failed";
default:
return "Unknown error";
}
}
/**
* @brief Convert antenna bitmask to O-RAN binary format
*
* O-RAN uses binary type in YANG which is base64 encoded in XML.
* For simplicity, we encode the bitmask as hex bytes.
*
* Per O-RAN spec: "The antenna mask contains as many binary values as
* number of array elements associated with respective tx or rx-array.
* Value of '1' indicates antenna element shall be kept active and
* value of '0' indicates antenna element should be turned inactive."
*/
static char *bitmask_to_base64(uint8_t bitmask, uint8_t num_antennas)
{
// For up to 8 antennas, we need 1 byte
// Base64 encoding of 1 byte = 4 characters + null terminator
char *result = malloc(8);
if (result == NULL) return NULL;
// Mask to only include configured antennas
uint8_t valid_mask = (1 << num_antennas) - 1;
uint8_t masked_value = bitmask & valid_mask;
// Simple base64 encoding for single byte
// Input: masked_value (8 bits) -> padded to 6+6+4 bits for base64
static const char b64chars[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
// Encode single byte with padding
result[0] = b64chars[(masked_value >> 2) & 0x3F];
result[1] = b64chars[(masked_value << 4) & 0x30];
result[2] = '=';
result[3] = '=';
result[4] = '\0';
return result;
}
/**
* @brief Decode base64 to antenna bitmask
*/
static uint8_t base64_to_bitmask(const char *b64_str)
{
if (b64_str == NULL || strlen(b64_str) < 2) return 0xFF;
static const char b64chars[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
// Find index of first two base64 characters
const char *p1 = strchr(b64chars, b64_str[0]);
const char *p2 = strchr(b64chars, b64_str[1]);
if (p1 == NULL || p2 == NULL) return 0xFF;
uint8_t val1 = (uint8_t)(p1 - b64chars);
uint8_t val2 = (uint8_t)(p2 - b64chars);
// Reconstruct byte
return (val1 << 2) | (val2 >> 4);
}
bool mplane_create_antenna_mask_xml(ru_session_t *ru_session,
bool is_tx,
const char *array_name,
uint8_t antenna_bitmask,
uint8_t num_antennas,
char **xml_content)
{
if (ru_session == NULL || ru_session->ctx == NULL || array_name == NULL || xml_content == NULL) {
return false;
}
*xml_content = NULL;
LY_ERR ret = LY_SUCCESS;
struct lyd_node *root = NULL;
// Create root node for o-ran-uplane-conf
ret = lyd_new_path(NULL, (struct ly_ctx *)ru_session->ctx,
"/o-ran-uplane-conf:user-plane-configuration", NULL, 0, &root);
if (ret != LY_SUCCESS) {
MP_LOG_W("Failed to create root user-plane-configuration node.\n");
return false;
}
// Create the antenna mask configuration
const char *list_name = is_tx ? "tx-array-antenna-mask-config" : "rx-array-antenna-mask-config";
struct lyd_node *mask_node = NULL;
ret = lyd_new_list(root, NULL, list_name, 0, &mask_node, array_name);
if (ret != LY_SUCCESS) {
MP_LOG_W("Failed to create %s list node for array '%s'.\n", list_name, array_name);
lyd_free_tree(root);
return false;
}
// Convert bitmask to base64 for YANG binary type
char *b64_mask = bitmask_to_base64(antenna_bitmask, num_antennas);
if (b64_mask == NULL) {
MP_LOG_W("Failed to encode antenna bitmask.\n");
lyd_free_tree(root);
return false;
}
// Add antenna-bitmask leaf
ret = lyd_new_term(mask_node, NULL, "antenna-bitmask", b64_mask, 0, NULL);
free(b64_mask);
if (ret != LY_SUCCESS) {
MP_LOG_W("Failed to create antenna-bitmask node.\n");
lyd_free_tree(root);
return false;
}
// Print to XML
// Note: LYD_PRINT_SIBLINGS in libyang v2.1+, LYD_PRINT_WITHSIBLINGS in older v2, LYP_WITHSIBLINGS in v1
#ifdef MPLANE_V1
lyd_print_mem(xml_content, root, LYD_XML, LYP_WITHSIBLINGS);
#elif defined MPLANE_V2
#ifdef LYD_PRINT_SIBLINGS
lyd_print_mem(xml_content, root, LYD_XML, LYD_PRINT_SIBLINGS);
#else
lyd_print_mem(xml_content, root, LYD_XML, LYD_PRINT_WITHSIBLINGS);
#endif
#endif
lyd_free_tree(root);
if (*xml_content == NULL) {
MP_LOG_W("Failed to generate XML content.\n");
return false;
}
MP_LOG_I("Generated %s antenna mask XML for array '%s', mask=0x%02X\n",
is_tx ? "TX" : "RX", array_name, antenna_bitmask);
return true;
}
antenna_ctrl_result_t mplane_set_tx_antenna_mask(ru_session_t *ru_session,
const char *array_name,
uint8_t antenna_bitmask,
uint8_t num_antennas)
{
if (ru_session == NULL || ru_session->session == NULL) {
return ANTENNA_CTRL_ERR_NO_SESSION;
}
if (array_name == NULL || strlen(array_name) == 0) {
return ANTENNA_CTRL_ERR_INVALID_ARRAY_NAME;
}
if (num_antennas == 0 || num_antennas > 8) {
return ANTENNA_CTRL_ERR_INVALID_MASK;
}
// Generate XML for antenna mask configuration
char *xml_content = NULL;
if (!mplane_create_antenna_mask_xml(ru_session, true, array_name,
antenna_bitmask, num_antennas, &xml_content)) {
return ANTENNA_CTRL_ERR_NETCONF_FAILED;
}
MP_LOG_I("Setting TX antenna mask via NETCONF:\n%s\n", xml_content);
// Use existing M-Plane infrastructure to send edit-config, validate, commit
bool success = edit_val_commmit_rpc(ru_session, xml_content);
free(xml_content);
if (!success) {
MP_LOG_W("Failed to configure TX antenna mask for array '%s'\n", array_name);
return ANTENNA_CTRL_ERR_COMMIT_FAILED;
}
MP_LOG_I("Successfully configured TX antenna mask 0x%02X for array '%s'\n",
antenna_bitmask, array_name);
return ANTENNA_CTRL_SUCCESS;
}
antenna_ctrl_result_t mplane_set_rx_antenna_mask(ru_session_t *ru_session,
const char *array_name,
uint8_t antenna_bitmask,
uint8_t num_antennas)
{
if (ru_session == NULL || ru_session->session == NULL) {
return ANTENNA_CTRL_ERR_NO_SESSION;
}
if (array_name == NULL || strlen(array_name) == 0) {
return ANTENNA_CTRL_ERR_INVALID_ARRAY_NAME;
}
if (num_antennas == 0 || num_antennas > 8) {
return ANTENNA_CTRL_ERR_INVALID_MASK;
}
// Generate XML for antenna mask configuration
char *xml_content = NULL;
if (!mplane_create_antenna_mask_xml(ru_session, false, array_name,
antenna_bitmask, num_antennas, &xml_content)) {
return ANTENNA_CTRL_ERR_NETCONF_FAILED;
}
MP_LOG_I("Setting RX antenna mask via NETCONF:\n%s\n", xml_content);
// Use existing M-Plane infrastructure to send edit-config, validate, commit
bool success = edit_val_commmit_rpc(ru_session, xml_content);
free(xml_content);
if (!success) {
MP_LOG_W("Failed to configure RX antenna mask for array '%s'\n", array_name);
return ANTENNA_CTRL_ERR_COMMIT_FAILED;
}
MP_LOG_I("Successfully configured RX antenna mask 0x%02X for array '%s'\n",
antenna_bitmask, array_name);
return ANTENNA_CTRL_SUCCESS;
}
antenna_ctrl_result_t mplane_get_tx_antenna_mask(ru_session_t *ru_session,
const char *array_name,
uint8_t *antenna_bitmask)
{
if (ru_session == NULL || ru_session->session == NULL) {
return ANTENNA_CTRL_ERR_NO_SESSION;
}
if (array_name == NULL || antenna_bitmask == NULL) {
return ANTENNA_CTRL_ERR_INVALID_ARRAY_NAME;
}
// Get operational datastore
char *operational_ds = NULL;
if (!get_mplane(ru_session, &operational_ds)) {
return ANTENNA_CTRL_ERR_NETCONF_FAILED;
}
// Build XPath to find the antenna mask for this array
char xpath[256];
snprintf(xpath, sizeof(xpath),
"tx-array-antenna-mask-config[array-name='%s']/antenna-bitmask",
array_name);
// Extract the value from XML response
char *mask_b64 = get_ru_xml_node(operational_ds, "antenna-bitmask");
free(operational_ds);
if (mask_b64 == NULL) {
// No mask configured, assume all active
*antenna_bitmask = 0xFF;
return ANTENNA_CTRL_SUCCESS;
}
*antenna_bitmask = base64_to_bitmask(mask_b64);
free(mask_b64);
MP_LOG_I("Retrieved TX antenna mask 0x%02X for array '%s'\n",
*antenna_bitmask, array_name);
return ANTENNA_CTRL_SUCCESS;
}
antenna_ctrl_result_t mplane_get_rx_antenna_mask(ru_session_t *ru_session,
const char *array_name,
uint8_t *antenna_bitmask)
{
if (ru_session == NULL || ru_session->session == NULL) {
return ANTENNA_CTRL_ERR_NO_SESSION;
}
if (array_name == NULL || antenna_bitmask == NULL) {
return ANTENNA_CTRL_ERR_INVALID_ARRAY_NAME;
}
// Get operational datastore
char *operational_ds = NULL;
if (!get_mplane(ru_session, &operational_ds)) {
return ANTENNA_CTRL_ERR_NETCONF_FAILED;
}
// Extract the value from XML response (simplified - real impl would use XPath)
char *mask_b64 = get_ru_xml_node(operational_ds, "antenna-bitmask");
free(operational_ds);
if (mask_b64 == NULL) {
// No mask configured, assume all active
*antenna_bitmask = 0xFF;
return ANTENNA_CTRL_SUCCESS;
}
*antenna_bitmask = base64_to_bitmask(mask_b64);
free(mask_b64);
MP_LOG_I("Retrieved RX antenna mask 0x%02X for array '%s'\n",
*antenna_bitmask, array_name);
return ANTENNA_CTRL_SUCCESS;
}
bool mplane_supports_trx_control(ru_session_t *ru_session)
{
if (ru_session == NULL || ru_session->session == NULL) {
return false;
}
// Check if MPLANE-TRX-CONTROL feature is advertised in RU capabilities
// This is done by checking the server capabilities from the hello exchange
// or by querying the YANG library
struct nc_session *session = (struct nc_session *)ru_session->session;
const char * const *caps = nc_session_get_cpblts(session);
if (caps == NULL) {
return false;
}
// Look for o-ran-uplane-conf module with MPLANE-TRX-CONTROL feature
for (int i = 0; caps[i] != NULL; i++) {
if (strstr(caps[i], "o-ran-uplane-conf") != NULL) {
// Module is supported, assume feature is enabled
// A more complete implementation would query module features
MP_LOG_I("RU supports o-ran-uplane-conf module (MPLANE-TRX-CONTROL)\n");
return true;
}
}
MP_LOG_W("RU does not advertise o-ran-uplane-conf module\n");
return false;
}
+135
View File
@@ -0,0 +1,135 @@
#ifndef ANTENNA_CONTROL_H
#define ANTENNA_CONTROL_H
#include "ru-mplane-api.h"
#include <stdint.h>
#include <stdbool.h>
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief O-RAN antenna mask configuration result
*/
typedef enum {
ANTENNA_CTRL_SUCCESS = 0,
ANTENNA_CTRL_ERR_NO_SESSION,
ANTENNA_CTRL_ERR_FEATURE_NOT_SUPPORTED,
ANTENNA_CTRL_ERR_INVALID_ARRAY_NAME,
ANTENNA_CTRL_ERR_INVALID_MASK,
ANTENNA_CTRL_ERR_NETCONF_FAILED,
ANTENNA_CTRL_ERR_VALIDATION_FAILED,
ANTENNA_CTRL_ERR_COMMIT_FAILED
} antenna_ctrl_result_t;
/**
* @brief Convert result code to string description
*/
const char *antenna_ctrl_result_str(antenna_ctrl_result_t result);
/**
* @brief Set TX antenna bitmask via NETCONF O1 interface
*
* Implements O-RAN WG4 MPLANE-TRX-CONTROL feature by sending
* NETCONF edit-config RPC to configure tx-array-antenna-mask-config.
*
* The operation sequence is:
* 1. Create XML configuration for o-ran-uplane-conf:tx-array-antenna-mask-config
* 2. Send NETCONF <edit-config> to candidate datastore
* 3. Send NETCONF <validate>
* 4. Send NETCONF <commit>
*
* @param ru_session Active NETCONF session to the RU
* @param array_name O-RAN TX array name (e.g., from tx-arrays list)
* @param antenna_bitmask Bitmask where bit N = 1 means antenna N is active
* @param num_antennas Number of antennas in the array
* @return Result code indicating success or specific error
*/
antenna_ctrl_result_t mplane_set_tx_antenna_mask(ru_session_t *ru_session,
const char *array_name,
uint8_t antenna_bitmask,
uint8_t num_antennas);
/**
* @brief Set RX antenna bitmask via NETCONF O1 interface
*
* Same as mplane_set_tx_antenna_mask but for RX arrays.
* Configures rx-array-antenna-mask-config in o-ran-uplane-conf.
*
* @param ru_session Active NETCONF session to the RU
* @param array_name O-RAN RX array name (e.g., from rx-arrays list)
* @param antenna_bitmask Bitmask where bit N = 1 means antenna N is active
* @param num_antennas Number of antennas in the array
* @return Result code indicating success or specific error
*/
antenna_ctrl_result_t mplane_set_rx_antenna_mask(ru_session_t *ru_session,
const char *array_name,
uint8_t antenna_bitmask,
uint8_t num_antennas);
/**
* @brief Get current TX antenna mask from RU via NETCONF
*
* Sends NETCONF <get> RPC to retrieve current tx-array-antenna-mask-config.
*
* @param ru_session Active NETCONF session to the RU
* @param array_name O-RAN TX array name
* @param antenna_bitmask Output: current antenna bitmask
* @return Result code indicating success or specific error
*/
antenna_ctrl_result_t mplane_get_tx_antenna_mask(ru_session_t *ru_session,
const char *array_name,
uint8_t *antenna_bitmask);
/**
* @brief Get current RX antenna mask from RU via NETCONF
*
* Sends NETCONF <get> RPC to retrieve current rx-array-antenna-mask-config.
*
* @param ru_session Active NETCONF session to the RU
* @param array_name O-RAN RX array name
* @param antenna_bitmask Output: current antenna bitmask
* @return Result code indicating success or specific error
*/
antenna_ctrl_result_t mplane_get_rx_antenna_mask(ru_session_t *ru_session,
const char *array_name,
uint8_t *antenna_bitmask);
/**
* @brief Check if RU supports MPLANE-TRX-CONTROL feature
*
* Queries the RU capabilities to verify MPLANE-TRX-CONTROL feature
* is advertised (defined in o-ran-wg4-features.yang).
*
* @param ru_session Active NETCONF session to the RU
* @return true if feature is supported, false otherwise
*/
bool mplane_supports_trx_control(ru_session_t *ru_session);
/**
* @brief Create antenna bitmask XML content for NETCONF edit-config
*
* Helper function to generate O-RAN compliant XML for antenna mask configuration.
* Uses libyang to create proper o-ran-uplane-conf structure.
*
* @param ru_session Active NETCONF session (for libyang context)
* @param is_tx true for TX array, false for RX array
* @param array_name O-RAN array name
* @param antenna_bitmask Antenna bitmask value
* @param num_antennas Number of antennas (for binary encoding)
* @param xml_content Output: allocated XML string (caller must free)
* @return true on success, false on error
*/
bool mplane_create_antenna_mask_xml(ru_session_t *ru_session,
bool is_tx,
const char *array_name,
uint8_t antenna_bitmask,
uint8_t num_antennas,
char **xml_content);
#ifdef __cplusplus
}
#endif
#endif /* ANTENNA_CONTROL_H */
+98
View File
@@ -0,0 +1,98 @@
#!/usr/bin/env bash
#!/bin/bash
# Continuous traffic generator for OAI UE containers (no intervals, runs virtually forever).
# Usage examples:
# ./traffic_runner.sh --profile boring
# PROFILE=bulk_download ./traffic_runner.sh
set -euo pipefail
PROFILE="${PROFILE:-}"
# Fixed server coordinates
IPERF_HOST_UDP="192.168.71.129"
IPERF_HOST_TCP="192.168.71.130"
IPERF_PORT_UDP=5201
IPERF_PORT_TCP=5202
OAITUN_IP="$(ip -o -4 addr show dev oaitun_ue1 | awk '{print $4}' | cut -d/ -f1)"
# Very long duration to approximate "forever"
LONG_T="${LONG_T:-31536000}" # 1 year in seconds
# -------------- args --------------
if [[ $# -gt 0 ]]; then
case "$1" in
--profile=*) PROFILE="${1#--profile=}"; shift ;;
--profile) PROFILE="${2-}"; shift 2 ;;
-p=*) PROFILE="${1#-p=}"; shift ;;
-p) PROFILE="${2-}"; shift 2 ;;
-*) ;; # ignore other flags
*) PROFILE="$1"; shift ;;
esac
fi
[[ -n "$PROFILE" ]] || { echo "ERROR: --profile is required" >&2; exit 2; }
iperf_tcp_dl_forever() { ( iperf -c "$IPERF_HOST_TCP" -B "$OAITUN_IP" -p "$IPERF_PORT_TCP" -R -t "$LONG_T" >/dev/null 2>&1 ); }
iperf_tcp_ul_forever() { ( iperf -c "$IPERF_HOST_TCP" -B "$OAITUN_IP" -p "$IPERF_PORT_TCP" -t "$LONG_T" >/dev/null 2>&1 ); }
# iperf_udp_dl_forever() { ( nohup iperf -u -c "$IPERF_HOST_UDP" -B "$OAITUN_IP" -p "$IPERF_PORT_UDP" -R -t "$LONG_T" >/dev/null 2>&1 ); }
iperf_udp_dl_forever() { ( iperf -u -c "$IPERF_HOST_UDP" -B "$OAITUN_IP" -p "$IPERF_PORT_UDP" -R -b $1 -t "$LONG_T" >/dev/null 2>&1 ); }
iperf_udp_ul_forever() { ( iperf -u -c "$IPERF_HOST_UDP" -B "$OAITUN_IP" -p "$IPERF_PORT_UDP" -b $1 -t "$LONG_T" >/dev/null 2>&1 ); }
# ---------------- profiles ----------------
do_boring() {
iperf_udp_dl_forever 15M
wait
# iperf -c "$IPERF_HOST" -p "$IPERF_PORT" -B "$OAITUN_IP" -u -b 25M -t "$LONG_T"
}
do_browsing() {
iperf_tcp_dl_forever
iperf_tcp_ul_forever
wait
}
do_video() {
iperf_udp_dl_forever 6M
wait
}
do_bulk_download() {
iperf -c "$IPERF_HOST" -p "$IPERF_PORT" -B "$OAITUN_IP" -R -P 8 -w 1M -t "$LONG_T"
wait
}
do_voip() {
iperf -c "$IPERF_HOST" -p "$IPERF_PORT" -B "$OAITUN_IP" -R -u -b 80K -t "$LONG_T";
iperf -c "$IPERF_HOST" -p "$IPERF_PORT" -B "$OAITUN_IP" -u -b 80K -t "$LONG_T";
wait
}
do_iot_chatty() {
iperf -c "$IPERF_HOST" -p "$IPERF_PORT" -B "$OAITUN_IP" -R -u -b 40K -t "$LONG_T";
iperf -c "$IPERF_HOST" -p "$IPERF_PORT" -B "$OAITUN_IP" -u -b 20K -t "$LONG_T";
wait
}
do_gaming() {
iperf -c "$IPERF_HOST" -p "$IPERF_PORT" -B "$OAITUN_IP" -R -u -b 120K -t "$LONG_T";
iperf -c "$IPERF_HOST" -p "$IPERF_PORT" -B "$OAITUN_IP" -u -b 10K -t "$LONG_T";
wait
}
do_idle() {
tail -f /dev/null
}
# -------------- dispatch --------------
case "$PROFILE" in
boring) do_boring;;
browsing) do_browsing;;
video) do_video;;
bulk_download) do_bulk_download;;
voip) do_voip;;
iot_chatty) do_iot_chatty;;
gaming) do_gaming;;
idle) do_idle;;
*) echo "Unknown profile: $PROFILE" >&2; exit 1;;
esac