28 Commits
Author SHA1 Message Date
maki 0fada7ec83 Merge branch 'develop' of https://gitlab.eurecom.fr/oai/openairinterface5g into maki 2026-01-21 01:05:14 +09:00
maki 274166f18c space 2026-01-17 21:34:26 +09:00
maki e90b943242 一旦2 2026-01-17 15:40:51 +09:00
maki c28054a285 update 2026-01-14 04:27:37 +00:00
maki cf4ca86169 一旦 2026-01-08 09:17:26 +00:00
maki 85fef10860 Fix MIMO antenna muting: copy SSB to active antennas when TX0 muted
When antenna 0 is muted but other antennas are active, SSB data
(generated only on port 0) is now copied to remaining active antennas.
This ensures UE can still detect cell with partial antenna muting.

Behavior:
- TX mask 0x03: Full 2x2 MIMO
- TX mask 0x02: TX1 only, SSB copied from TX0
- TX mask 0x01: TX0 only, normal SSB
- TX mask 0x00: All muted, UE loses sync (correct)
2026-01-05 16:50:37 +00:00
maki 16fa6e766d Fix: Add nr_antenna_state.c to PHY_RU library for LTE/NR compatibility 2026-01-05 10:45:06 +00:00
maki f893e8f072 Revert "Remove -Werror to fix build warnings"
This reverts commit 7d7a25e158.
2026-01-05 10:42:49 +00:00
maki 7d7a25e158 Remove -Werror to fix build warnings 2026-01-05 10:42:09 +00:00
maki f309868f58 Fix Dockerfile.nakaolab.test for flexric paths and add netcat 2026-01-05 10:14:54 +00:00
maki dbfbbf0017 Add O-RAN compliant antenna muting for energy saving
Implements MPLANE-TRX-CONTROL feature from O-RAN WG4:
- nr_antenna_state: TX/RX antenna bitmask management
- nr_antenna_control: TCP control interface on port 9999
- Integrated into nr_feptx (TX) and nr_fep (RX) paths
- Blanks transmission/reception for muted antennas

Commands: MUTE TX/RX <id>, UNMUTE TX/RX <id>, STATUS, STATS
2026-01-05 10:14:21 +00:00
maki 25de29a0ab update 2026-01-01 17:40:45 +09:00
maki 3e09a4e4e3 update 2025-12-18 22:24:37 +09:00
maki 69dd18b09f or else build fails 2025-12-17 00:59:27 +09:00
maki 4c0ae6c43b Merge pull request 'laptop' (#1) from laptop into develop
Reviewed-on: #1
2025-12-13 05:52:08 +00:00
maki 02dc8dadd4 update 2025-12-13 14:51:30 +09:00
maki 63d2238429 laptop init 2025-12-07 13:47:47 +09:00
maki 524e26ede5 Merge branch 'develop' of https://git.aubsebian.net/maki/openairinterface5g into develop 2025-12-06 18:16:12 +09:00
maki 01db9857c4 dec3 2025-12-03 13:51:53 +09:00
maki aa353fdcff dec3 2025-12-03 13:48:14 +09:00
maki bdfe516aa7 update 2025-12-01 19:12:08 +09:00
maki f8f10665e7 update 2025-12-01 14:49:02 +09:00
maki ee3e027041 update 2025-11-20 21:00:28 +09:00
maki 7aca2a5fdf useful 2025-11-18 19:09:53 +09:00
maki 636c124596 update 2025-11-18 18:59:13 +09:00
maki 7a18e84f1b update 2025-11-18 18:36:26 +09:00
maki b30f738b01 Merge tag '2025.w42' into HEAD
Integration 2025 week 42

* !3623 Optimization of PDSCH precoding operation
* !3691 \[CI\] Ensure multi-arch DockerHub Push after successful image build
* !3696 asn1c doesn't follow c99 variables alias rules, it needs to be compiled this...
* !3685 Assign antenna ports correctly for 1 and 2 layers for aerial.
* !3495 Type0 PDSCH frequency allocation at L1 UE
* !3686 Remove NR L2 simulator code from UE
* !3693 Remove B200-specific clock-rate setting
* !3701 OAI UE: add shared RSRP Table 10.1.6.1-1 mapping function to helpers and adopt it RRC UE
* !3662 RRC UE file cleanup
* update doc/README.md
* !3608 use common scale channel functions for DLSCH and ULSCH
* !3694 fix non initialized DMRS port value in case of DCI00 at UE
* !3702 Extend F1 rfsim CI test
* !3704 remove unused global queues, dead code
* !3673 CI: Aerial: Add an UL-heavy pipeline test
* !3651 CI: Upgrade HO setup to use an attenuator
* !3695 CI: Upgrade Aerial setup to 25-2.1
2025-10-20 16:11:42 +09:00
maki ac4dbff76e nakaolab oct19 2025-10-19 20:03:22 +09:00
67 changed files with 12463 additions and 216 deletions
+1
View File
@@ -5,3 +5,4 @@ common/utils/T/T_IDs.h
common/utils/T/T_messages.txt.h
common/utils/T/genids
common/utils/T/genids.o
artifacts
+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
+15
View File
@@ -0,0 +1,15 @@
#!/bin/bash
if [[ $(/usr/bin/id -u) -ne 0 ]]; then
echo "Not running as root"
exit
fi
cd openair2/E2AP/flexric/
rm -rf build && mkdir build && \
cd build && cmake -DE2AP_VERSION=E2AP_V2 -DKPM_VERSION=KPM_V2_03 .. && make -j16 && sudo make install
cd ~/OAI/openairinterface5g/
cp -r /usr/local/etc/flexric/ flexric/etc/
cp -r /usr/local/lib/flexric/ flexric/lib/
@@ -0,0 +1,114 @@
#/* Channel model configuration for antenna muting capacity simulation */
#/* This config adds noise to simulate capacity reduction when antennas are muted */
#/* The noise level is set to cause MCS reduction and throughput limiting */
channelmod = {
max_chan = 10;
modellist = "modellist_antenna_muting";
# Channel model with moderate noise to show MIMO capacity benefits
# Higher noise_power_dB = more noise = lower SNR = lower MCS = lower throughput
modellist_antenna_muting = (
{
# Downlink channel (gNB -> UE) - applied on UE side
model_name = "rfsimu_channel_enB0"
type = "AWGN";
ploss_dB = 0;
noise_power_dB = -50;
forgetfact = 0;
offset = 0;
ds_tdl = 0;
},
{
# Uplink channel (UE -> gNB) - applied on gNB side
model_name = "rfsimu_channel_ue0"
type = "AWGN";
ploss_dB = 0;
noise_power_dB = -50;
forgetfact = 0;
offset = 0;
ds_tdl = 0;
},
{
model_name = "rfsimu_channel_ue1"
type = "AWGN";
ploss_dB = 0;
noise_power_dB = -50;
forgetfact = 0;
offset = 0;
ds_tdl = 0;
},
{
model_name = "rfsimu_channel_ue2"
type = "AWGN";
ploss_dB = 0;
noise_power_dB = -50;
forgetfact = 0;
offset = 0;
ds_tdl = 0;
},
{
model_name = "rfsimu_channel_ue3"
type = "AWGN";
ploss_dB = 0;
noise_power_dB = -50;
forgetfact = 0;
offset = 0;
ds_tdl = 0;
},
{
model_name = "rfsimu_channel_ue4"
type = "AWGN";
ploss_dB = 0;
noise_power_dB = -50;
forgetfact = 0;
offset = 0;
ds_tdl = 0;
},
{
model_name = "rfsimu_channel_ue5"
type = "AWGN";
ploss_dB = 0;
noise_power_dB = -50;
forgetfact = 0;
offset = 0;
ds_tdl = 0;
},
{
model_name = "rfsimu_channel_ue6"
type = "AWGN";
ploss_dB = 0;
noise_power_dB = -50;
forgetfact = 0;
offset = 0;
ds_tdl = 0;
},
{
model_name = "rfsimu_channel_ue7"
type = "AWGN";
ploss_dB = 0;
noise_power_dB = -50;
forgetfact = 0;
offset = 0;
ds_tdl = 0;
},
{
model_name = "rfsimu_channel_ue8"
type = "AWGN";
ploss_dB = 0;
noise_power_dB = -50;
forgetfact = 0;
offset = 0;
ds_tdl = 0;
},
{
model_name = "rfsimu_channel_ue9"
type = "AWGN";
ploss_dB = 0;
noise_power_dB = -50;
forgetfact = 0;
offset = 0;
ds_tdl = 0;
}
);
};
+18 -9
View File
@@ -9,19 +9,28 @@ channelmod = {
model_name = "rfsimu_channel_enB0"
type = "AWGN";
ploss_dB = 0;
noise_power_dB = -10;
forgetfact = 0;
offset = 0;
ds_tdl = 0;
noise_power_dB = -60;
# forgetfact = 0;
# offset = 0;
# ds_tdl = 0;
},
{
model_name = "rfsimu_channel_ue0"
type = "AWGN";
ploss_dB = 0;
noise_power_dB = 0;
forgetfact = 0;
offset = 0;
ds_tdl = 0;
# ploss_dB = 0;
# noise_power_dB = -30;
# forgetfact = 0;
# offset = 0;
# ds_tdl = 0;
},
{
model_name = "rfsimu_channel_ue2"
type = "AWGN";
# ploss_dB = 0;
# noise_power_dB = 0;
# forgetfact = 0;
# offset = 0;
# ds_tdl = 0;
}
);
modellist_rfsimu_2 = (
@@ -12,17 +12,17 @@ gNBs =
gNB_name = "cu-rfsim";
// Tracking area code, 0x0000 and 0xfffe are reserved values
tracking_area_code = 1;
plmn_list = ({ mcc = 208; mnc = 99; mnc_length = 2; snssaiList = ({ sst = 1, sd = 0xffffff }) });
@include "neighbour-config.conf"
# @include "neighbour-config.conf"
nr_cellid = 12345678L;
tr_s_preference = "f1";
local_s_address = "192.168.71.150";
remote_s_address = "192.168.71.171";
local_s_address = "192.168.71.140";
remote_s_address = "192.168.71.141";
local_s_portd = 2153;
remote_s_portd = 2153;
@@ -40,9 +40,8 @@ gNBs =
NETWORK_INTERFACES :
{
GNB_IPV4_ADDRESS_FOR_NG_AMF = "192.168.71.150";
GNB_IPV4_ADDRESS_FOR_NGU = "192.168.71.150";
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
};
}
@@ -0,0 +1,336 @@
Active_gNBs = ("gnb-rfsim");
# Asn1_verbosity, choice in: none, info, annoying
Asn1_verbosity = "none";
e2_agent = {
enabled = "yes";
near_ric_ip_addr = "192.168.71.21"; # FlexRIC Near-RT RIC
near_ric_port = 36422;
sm_dir = "/usr/local/lib/flexric/";
}
gNBs =
(
{
////////// Identification parameters:
gNB_ID = 0xe00;
gNB_name = "gnb-rfsim";
// 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; sd = 0x1; }) });
plmn_list = ({ mcc = 001; mnc = 01; mnc_length = 2; snssaiList = ({ sst = 1; sd = 0xffffff; }) });
nr_cellid = 12345678L;
////////// Physical parameters:
pdsch_AntennaPorts_XP = 2;
pusch_AntennaPorts = 2;
do_CSIRS = 1;
do_SRS = 1;
min_rxtxtime = 6;
pdcch_ConfigSIB1 = (
{
controlResourceSetZero = 12;
searchSpaceZero = 0;
}
);
servingCellConfigCommon = (
{
#spCellConfigCommon
physCellId = 0;
# downlinkConfigCommon
#frequencyInfoDL
# this is 3300.60 MHz + 53*12*30e-3 MHz = 3319.68
absoluteFrequencySSB = 621312; # 641280
dl_frequencyBand = 78;
# this is 3300.60 MHz
dl_absoluteFrequencyPointA = 620040; # 640008
#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=27,L=48 (275*(L-1))+RBstart
initialDLBWPlocationAndBandwidth = 12956; # 6366 12925 12956 28875 12952
# subcarrierSpacing
# 0=kHz15, 1=kHz30, 2=kHz60, 3=kHz120
initialDLBWPsubcarrierSpacing = 1;
#pdcch-ConfigCommon
initialDLBWPcontrolResourceSetZero = 12;
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 = 106;
pMax = 20;
#initialUplinkBWP
#genericParameters
initialULBWPlocationAndBandwidth = 12956;
# 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 = 12;
preambleReceivedTargetPower = -96;
#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;
#ra_ReponseWindow
#1,2,4,8,10,20,40,80
# ra_ResponseWindow = 4;
#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 = 3;
#oneHalf (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 = -70;
# ssb_PositionsInBurs_BitmapPR
# 1=short, 2=medium, 3=long
ssb_PositionsInBurst_PR = 2;
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;
}
);
# Dedicated Serving Cell Configuration
servingCellConfigDedicated = ({
# BWP-Downlink
# BWP 1 Configuration
dl_bwp-Id_1 = 1;
dl_bwp1_locationAndBandwidth = 28875; // RBstart=0, L=106 (40 MHz BW)
# subcarrierSpacing
# 0=kHz15, 1=kHz30, 2=kHz60, 3=kHz120
dl_bwp1_subcarrierSpacing = 1;
# BWP 2 Configuration
dl_bwp-Id_2 = 2;
dl_bwp2_locationAndBandwidth = 13750; // RBstart=0, L=51 (20 MHz BW)
# subcarrierSpacing
# 0=kHz15, 1=kHz30, 2=kHz60, 3=kHz120
dl_bwp2_subcarrierSpacing = 1;
# BWP 3 Configuration
dl_bwp-Id_3 = 3;
dl_bwp3_locationAndBandwidth = 6325; // RBstart=0, L=24 (10 MHz BW)
# subcarrierSpacing
# 0=kHz15, 1=kHz30, 2=kHz60, 3=kHz120
dl_bwp3_subcarrierSpacing = 1;
firstActiveDownlinkBWP-Id = 1; #BWP-Id
defaultDownlinkBWP-Id = 1; #BWP-Id
# bwp-InactivityTimer ENUMERATED {ms2, ms3, ms4, ms5, ms6, ms8, ms10, ms20, ms30,
# ms40,ms50, ms60, ms80,ms100, ms200,ms300, ms500,
# ms750, ms1280, ms1920, ms2560, spare10, spare9, spare8,
# spare7, spare6, spare5, spare4, spare3, spare2, spare1 }
# UplinkConfig
# BWP-Uplink
# BWP 1 Configuration
ul_bwp-Id_1 = 1;
ul_bwp1_locationAndBandwidth = 28875; // RBstart=0, L=106 (40 MHz BW)
# subcarrierSpacing
# 0=kHz15, 1=kHz30, 2=kHz60, 3=kHz120
ul_bwp1_subcarrierSpacing = 1;
# BWP 2 Configuration
ul_bwp-Id_2 = 2;
ul_bwp2_locationAndBandwidth = 13750; // RBstart=0, L=51 (20 MHz BW)
# subcarrierSpacing
# 0=kHz15, 1=kHz30, 2=kHz60, 3=kHz120
ul_bwp2_subcarrierSpacing = 1;
# BWP 3 Configuration
ul_bwp-Id_3 = 3;
ul_bwp3_locationAndBandwidth = 6325; // RBstart=0, L=24 (10 MHz BW)
# subcarrierSpacing
# 0=kHz15, 1=kHz30, 2=kHz60, 3=kHz120
ul_bwp3_subcarrierSpacing = 1;
firstActiveUplinkBWP-Id = 1; #BWP-Id
}
);
# ------- 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.70.132";}); # Full OAI CN AMF (duc-oai-amf)
NETWORK_INTERFACES :
{
GNB_IPV4_ADDRESS_FOR_NG_AMF = "192.168.70.140"; # On full CN network
GNB_IPV4_ADDRESS_FOR_NGU = "192.168.70.140"; # On full CN network
GNB_PORT_FOR_S1U = 2152; # Spec 2152
};
}
);
MACRLCs = (
{
num_cc = 1;
tr_s_preference = "local_L1";
tr_n_preference = "local_RRC";
#pusch_TargetSNRx10 = 150;
#pucch_TargetSNRx10 = 200;
ulsch_max_frame_inactivity = 0;
dl_max_mcs = 16; # there are retransmissions if more
min_grant_prb = 10;
}
);
L1s = (
{
num_cc = 1;
tr_n_preference = "local_mac";
prach_dtx_threshold = 120;
#pucch0_dtx_threshold = 120;
}
);
RUs = (
{
local_rf = "yes"
nb_tx = 2
nb_rx = 2
att_tx = 0
att_rx = 0;
bands = [78];
max_pdschReferenceSignalPower = -27;
max_rxgain = 75;
eNB_instances = [0];
##beamforming 1x2 matrix: 1 layer x 2 antennas
bf_weights = [0x00007fff, 0x0000];
#clock_src = "internal";
# sdr_addrs = "mgmt_addr=192.168.10.2,addr=192.168.10.2,clock_source=internal,time_source=internal"
sdr_addrs = "serial=XXXXXXX";
}
);
THREAD_STRUCT = (
{
#three config for level of parallelism "PARALLEL_SINGLE_THREAD", "PARALLEL_RU_L1_SPLIT", or "PARALLEL_RU_L1_TRX_SPLIT"
parallel_config = "PARALLEL_SINGLE_THREAD";
#two option for worker "WORKER_DISABLE" or "WORKER_ENABLE"
worker_config = "WORKER_ENABLE";
}
);
rfsimulator :
{
serveraddr = "server";
};
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 ="debug";
f1ap_log_level ="debug";
};
# Channel model for antenna muting capacity simulation
@include "/opt/oai-gnb/etc/channelmod_antenna_muting.conf"
@@ -0,0 +1,386 @@
Active_gNBs = ("gnb-rfsim");
# Asn1_verbosity, choice in: none, info, annoying
Asn1_verbosity = "none";
e2_agent = {
enabled = "yes";
near_ric_ip_addr = "192.168.71.21"; # FlexRIC Near-RT RIC
near_ric_port = 36422;
sm_dir = "/usr/local/lib/flexric/";
}
gNBs =
(
{
////////// Identification parameters:
gNB_ID = 0xe00;
gNB_name = "gnb-rfsim";
// 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; sd = 0x1; }) });
plmn_list = ({ mcc = 001; mnc = 01; mnc_length = 2; snssaiList = ({ sst = 1; sd = 0xffffff; }) });
nr_cellid = 12345678L;
////////// Physical parameters:
pdsch_AntennaPorts_XP = 2;
pusch_AntennaPorts = 4;
pdsch_AntennaPorts_N1 = 2;
do_CSIRS = 1; # used to be 1
do_SRS = 0;
min_rxtxtime = 6;
pdcch_ConfigSIB1 = (
{
controlResourceSetZero = 12;
searchSpaceZero = 0;
}
);
servingCellConfigCommon = (
{
#spCellConfigCommon
physCellId = 0;
# downlinkConfigCommon
#frequencyInfoDL
# this is 3300.60 MHz + 53*12*30e-3 MHz = 3319.68
absoluteFrequencySSB = 621312; # 641280
dl_frequencyBand = 78;
# this is 3300.60 MHz
dl_absoluteFrequencyPointA = 620040; # 640008
#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=27,L=48 (275*(L-1))+RBstart
initialDLBWPlocationAndBandwidth = 12956; # 6366 12925 12956 28875 12952
# subcarrierSpacing
# 0=kHz15, 1=kHz30, 2=kHz60, 3=kHz120
initialDLBWPsubcarrierSpacing = 1;
#pdcch-ConfigCommon
initialDLBWPcontrolResourceSetZero = 12;
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 = 106;
pMax = 20;
#initialUplinkBWP
#genericParameters
initialULBWPlocationAndBandwidth = 12956;
# 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 = 12;
preambleReceivedTargetPower = -96;
#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;
#ra_ReponseWindow
#1,2,4,8,10,20,40,80
# ra_ResponseWindow = 4;
#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 = 3;
#oneHalf (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 = -70;
# ssb_PositionsInBurs_BitmapPR
# 1=short, 2=medium, 3=long
ssb_PositionsInBurst_PR = 2;
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;
}
);
# Dedicated Serving Cell Configuration
servingCellConfigDedicated = ({
# BWP-Downlink
# BWP 1 Configuration
dl_bwp-Id_1 = 1;
dl_bwp1_locationAndBandwidth = 28875; // RBstart=0, L=106 (40 MHz BW)
# subcarrierSpacing
# 0=kHz15, 1=kHz30, 2=kHz60, 3=kHz120
dl_bwp1_subcarrierSpacing = 1;
# BWP 2 Configuration
dl_bwp-Id_2 = 2;
dl_bwp2_locationAndBandwidth = 13750; // RBstart=0, L=51 (20 MHz BW)
# subcarrierSpacing
# 0=kHz15, 1=kHz30, 2=kHz60, 3=kHz120
dl_bwp2_subcarrierSpacing = 1;
# BWP 3 Configuration
dl_bwp-Id_3 = 3;
dl_bwp3_locationAndBandwidth = 6325; // RBstart=0, L=24 (10 MHz BW)
# subcarrierSpacing
# 0=kHz15, 1=kHz30, 2=kHz60, 3=kHz120
dl_bwp3_subcarrierSpacing = 1;
firstActiveDownlinkBWP-Id = 1; #BWP-Id
defaultDownlinkBWP-Id = 1; #BWP-Id
# bwp-InactivityTimer ENUMERATED {ms2, ms3, ms4, ms5, ms6, ms8, ms10, ms20, ms30,
# ms40,ms50, ms60, ms80,ms100, ms200,ms300, ms500,
# ms750, ms1280, ms1920, ms2560, spare10, spare9, spare8,
# spare7, spare6, spare5, spare4, spare3, spare2, spare1 }
# UplinkConfig
# BWP-Uplink
# BWP 1 Configuration
ul_bwp-Id_1 = 1;
ul_bwp1_locationAndBandwidth = 28875; // RBstart=0, L=106 (40 MHz BW)
# subcarrierSpacing
# 0=kHz15, 1=kHz30, 2=kHz60, 3=kHz120
ul_bwp1_subcarrierSpacing = 1;
# BWP 2 Configuration
ul_bwp-Id_2 = 2;
ul_bwp2_locationAndBandwidth = 13750; // RBstart=0, L=51 (20 MHz BW)
# subcarrierSpacing
# 0=kHz15, 1=kHz30, 2=kHz60, 3=kHz120
ul_bwp2_subcarrierSpacing = 1;
# BWP 3 Configuration
ul_bwp-Id_3 = 3;
ul_bwp3_locationAndBandwidth = 6325; // RBstart=0, L=24 (10 MHz BW)
# subcarrierSpacing
# 0=kHz15, 1=kHz30, 2=kHz60, 3=kHz120
ul_bwp3_subcarrierSpacing = 1;
firstActiveUplinkBWP-Id = 1; #BWP-Id
}
);
# ------- 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";}); # Full OAI CN AMF (duc-oai-amf)
NETWORK_INTERFACES :
{
GNB_IPV4_ADDRESS_FOR_NG_AMF = "192.168.71.140"; # On full CN network
GNB_IPV4_ADDRESS_FOR_NGU = "192.168.71.140"; # On full CN network
GNB_PORT_FOR_S1U = 2152; # Spec 2152
};
}
);
MACRLCs = (
{
num_cc = 1;
tr_s_preference = "local_L1";
tr_n_preference = "local_RRC";
#pusch_TargetSNRx10 = 150;
#pucch_TargetSNRx10 = 200;
ulsch_max_frame_inactivity = 0;
dl_max_mcs = 30;
}
);
L1s = (
{
num_cc = 1;
tr_n_preference = "local_mac";
pusch_proc_threads = 8;
prach_dtx_threshold = 120;
pucch0_dtx_threshold = 120;
}
);
RUs = (
{
local_rf = "yes"
nb_tx = 4
nb_rx = 4
att_tx = 0
att_rx = 0;
bands = [78];
max_pdschReferenceSignalPower = -27;
max_rxgain = 75;
eNB_instances = [0];
##beamforming 1x4 matrix: 1 layer x 4 antennas
# bf_weights = [0x00007fff, 0x0000, 0x0000, 0x0000];
#clock_src = "internal";
# sdr_addrs = "mgmt_addr=192.168.10.2,addr=192.168.10.2,clock_source=internal,time_source=internal"
bf_weights = [0x00007fff, 0x0000, 0x0000, 0x0000, 0x00000000, 0x00007fff, 0x0000, 0x0000, 0x0000, 0x0000, 0x00007fff, 0x0000, 0x0000, 0x0000, 0x0000, 0x00007fff];
sdr_addrs = "serial=XXXXXXX";
clock_src = "internal";
}
);
THREAD_STRUCT = (
{
#three config for level of parallelism "PARALLEL_SINGLE_THREAD", "PARALLEL_RU_L1_SPLIT", or "PARALLEL_RU_L1_TRX_SPLIT"
parallel_config = "PARALLEL_RU_L1_TRX_SPLIT";
#two option for worker "WORKER_DISABLE" or "WORKER_ENABLE"
worker_config = "WORKER_ENABLE";
}
);
rfsimulator :
{
serveraddr = "server";
modelname = "AWGN";
IQfile = "/tmp/rfsimulator.iqs";
};
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";
};
# channelmod = {
# // 2 descriptors per UE (DL+UL). 10 UEs => 20. Keep margin.
# max_chan = 32;
# modellist = "modellist_antenna_muting";
# modellist_antenna_muting = (
# // UE0
# { model_name="rfsimu_dl_ue0"; type="AWGN"; ploss_dB=20; noise_power_dB=-10; forgetfact=0; offset=0; ds_tdl=0; },
# { model_name="rfsimu_ul_ue0"; type="AWGN"; ploss_dB=20; noise_power_dB=-10; forgetfact=0; offset=0; ds_tdl=0; },
# // UE1
# { model_name="rfsimu_dl_ue1"; type="AWGN"; ploss_dB=20; noise_power_dB=-10; forgetfact=0; offset=0; ds_tdl=0; },
# { model_name="rfsimu_ul_ue1"; type="AWGN"; ploss_dB=20; noise_power_dB=-10; forgetfact=0; offset=0; ds_tdl=0; },
# // UE2
# { model_name="rfsimu_dl_ue2"; type="AWGN"; ploss_dB=20; noise_power_dB=-10; forgetfact=0; offset=0; ds_tdl=0; },
# { model_name="rfsimu_ul_ue2"; type="AWGN"; ploss_dB=20; noise_power_dB=-10; forgetfact=0; offset=0; ds_tdl=0; },
# // UE3
# { model_name="rfsimu_dl_ue3"; type="AWGN"; ploss_dB=20; noise_power_dB=-10; forgetfact=0; offset=0; ds_tdl=0; },
# { model_name="rfsimu_ul_ue3"; type="AWGN"; ploss_dB=20; noise_power_dB=-10; forgetfact=0; offset=0; ds_tdl=0; },
# // UE4
# { model_name="rfsimu_dl_ue4"; type="AWGN"; ploss_dB=20; noise_power_dB=-10; forgetfact=0; offset=0; ds_tdl=0; },
# { model_name="rfsimu_ul_ue4"; type="AWGN"; ploss_dB=20; noise_power_dB=-10; forgetfact=0; offset=0; ds_tdl=0; },
# // UE5
# { model_name="rfsimu_dl_ue5"; type="AWGN"; ploss_dB=20; noise_power_dB=-10; forgetfact=0; offset=0; ds_tdl=0; },
# { model_name="rfsimu_ul_ue5"; type="AWGN"; ploss_dB=20; noise_power_dB=-10; forgetfact=0; offset=0; ds_tdl=0; },
# // UE6
# { model_name="rfsimu_dl_ue6"; type="AWGN"; ploss_dB=20; noise_power_dB=-10; forgetfact=0; offset=0; ds_tdl=0; },
# { model_name="rfsimu_ul_ue6"; type="AWGN"; ploss_dB=20; noise_power_dB=-10; forgetfact=0; offset=0; ds_tdl=0; },
# // UE7
# { model_name="rfsimu_dl_ue7"; type="AWGN"; ploss_dB=20; noise_power_dB=-10; forgetfact=0; offset=0; ds_tdl=0; },
# { model_name="rfsimu_ul_ue7"; type="AWGN"; ploss_dB=20; noise_power_dB=-10; forgetfact=0; offset=0; ds_tdl=0; },
# // UE8
# { model_name="rfsimu_dl_ue8"; type="AWGN"; ploss_dB=20; noise_power_dB=-10; forgetfact=0; offset=0; ds_tdl=0; },
# { model_name="rfsimu_ul_ue8"; type="AWGN"; ploss_dB=20; noise_power_dB=-10; forgetfact=0; offset=0; ds_tdl=0; },
# // UE9
# { model_name="rfsimu_dl_ue9"; type="AWGN"; ploss_dB=20; noise_power_dB=-10; forgetfact=0; offset=0; ds_tdl=0; },
# { model_name="rfsimu_ul_ue9"; type="AWGN"; ploss_dB=20; noise_power_dB=-10; forgetfact=0; offset=0; ds_tdl=0; }
# );
# };
@@ -150,12 +150,12 @@ gNBs =
};
////////// AMF parameters:
amf_ip_address = ({ ipv4 = "192.168.70.132"; });
amf_ip_address = ({ ipv4 = "192.168.71.132"; });
NETWORK_INTERFACES :
{
GNB_IPV4_ADDRESS_FOR_NG_AMF = "192.168.70.151";
GNB_IPV4_ADDRESS_FOR_NGU = "192.168.70.151";
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
};
@@ -169,6 +169,7 @@ MACRLCs = (
tr_n_preference = "local_RRC";
pusch_TargetSNRx10 = 200;
pucch_TargetSNRx10 = 200;
min_grant_prb = 1;
}
);
@@ -230,7 +231,7 @@ log_config : {
};
e2_agent = {
near_ric_ip_addr = "192.168.70.150";
near_ric_ip_addr = "192.168.71.21";
#sm_dir = "/path/where/the/SMs/are/located/"
sm_dir = "/usr/local/lib/flexric/"
};
@@ -1,5 +1,12 @@
Active_gNBs:
- gnb-rfsim
e2_agent:
enabled: "yes"
near_ric_ip_addr: 192.168.71.21
e2ap_server_port: 36422
sm_dir: "/usr/local/lib/flexric/"
# Asn1_verbosity, choice in: none, info, annoying
Asn1_verbosity: none
gNBs:
@@ -139,13 +146,13 @@ gNBs:
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: 200
pucch_TargetSNRx10: 200
min_grant_prb: 5
stats_max_ue: 17
L1s:
@@ -193,6 +200,6 @@ log_config:
mac_log_level: info
rlc_log_level: info
pdcp_log_level: info
rrc_log_level: info
rrc_log_level: debug
ngap_log_level: debug
f1ap_log_level: debug
@@ -1,7 +1,14 @@
Active_gNBs = ( "gNB-OAI");
Active_gNBs = ("gNB-OAI");
# Asn1_verbosity, choice in: none, info, annoying
Asn1_verbosity = "none";
e2_agent = {
enabled = "yes";
near_ric_ip_addr = "192.168.71.21";
near_ric_port = 36421;
sm_dir = "/usr/local/lib/flexric/";
}
gNBs =
(
{
@@ -198,7 +205,7 @@ RUs = (
max_rxgain = 75;
eNB_instances = [0];
#clock_src = "internal";
sdr_addrs = "mgmt_addr=192.168.10.2,addr=192.168.10.2,clock_source=internal,time_source=internal"
# sdr_addrs = "mgmt_addr=192.168.10.2,addr=192.168.10.2,clock_source=internal,time_source=internal"
}
);
@@ -206,7 +213,7 @@ RUs = (
rfsimulator :
{
serveraddr = "server";
serverport = 4043;
# serverport = 4043;
options = (); #("saviq"); or/and "chanmod"
modelname = "AWGN";
IQfile = "/tmp/rfsimulator.iqs";
@@ -0,0 +1,279 @@
Active_gNBs = ("gNB-Eurecom-5GNRBox");
# Asn1_verbosity, choice in: none, info, annoying
Asn1_verbosity = "none";
e2_agent = {
enabled = "yes";
near_ric_ip_addr = "192.168.71.21";
sm_dir = "/usr/local/lib/flexric/"
e2ap_server_port: 36422
};
gNBs =
(
{
////////// Identification parameters:
gNB_ID = 0xe00;
gNB_name = "gNB-Eurecom-5GNRBox";
// Tracking area code, 0x0000 and 0xfffe are reserved values
tracking_area_code = 1;
plmn_list = ({ mcc = 208; mnc = 99; mnc_length = 2; snssaiList = ({ sst = 1 }) });
nr_cellid = 12345678L;
////////// Physical parameters:
pdsch_AntennaPorts_XP = 2;
pdsch_AntennaPorts_N1 = 2;
pusch_AntennaPorts = 4;
do_CSIRS = 1;
do_SRS = 1;
servingCellConfigCommon = (
{
#spCellConfigCommon
physCellId = 0;
# downlinkConfigCommon
#frequencyInfoDL
# this is 3600 MHz + 43 PRBs@30kHz SCS (same as initial BWP)
absoluteFrequencySSB = 641280;
dl_frequencyBand = 78;
# this is 3600 MHz
dl_absoluteFrequencyPointA = 640008;
#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=27,L=48 (275*(L-1))+RBstart
initialDLBWPlocationAndBandwidth = 28875; # 6366 12925 12956 28875 12952
# subcarrierSpacing
# 0=kHz15, 1=kHz30, 2=kHz60, 3=kHz120
initialDLBWPsubcarrierSpacing = 1;
#pdcch-ConfigCommon
initialDLBWPcontrolResourceSetZero = 12;
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 = 106;
pMax = 20;
#initialUplinkBWP
#genericParameters
initialULBWPlocationAndBandwidth = 28875;
# 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 = -96;
#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;
#ra_ReponseWindow
#1,2,4,8,10,20,40,80
ra_ResponseWindow = 4;
#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;
#oneHalf (0..15) 4,8,12,16,...60,64
ssb_perRACH_OccasionAndCB_PreamblesPerSSB = 14;
#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_PositionsInBurs_BitmapPR
# 1=short, 2=medium, 3=long
ssb_PositionsInBurst_PR = 2;
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;
};
////////// AMF parameters:
amf_ip_address = ({ ipv4 = "192.168.70.132";});
NETWORK_INTERFACES :
{
GNB_IPV4_ADDRESS_FOR_NG_AMF = "192.168.70.140/24";
GNB_IPV4_ADDRESS_FOR_NGU = "192.168.70.140/24";
GNB_PORT_FOR_S1U = 2152; # Spec 2152
};
}
);
MACRLCs = (
{
num_cc = 1;
tr_s_preference = "local_L1";
tr_n_preference = "local_RRC";
pusch_TargetSNRx10 = 150;
pucch_TargetSNRx10 = 200;
}
);
L1s = (
{
num_cc = 1;
tr_n_preference = "local_mac";
pusch_proc_threads = 8;
prach_dtx_threshold = 120;
pucch0_dtx_threshold = 150;
ofdm_offset_divisor = 8; #set this to UINT_MAX for offset 0
}
);
RUs = (
{
local_rf = "yes"
nb_tx = 4
nb_rx = 4
att_tx = 0
att_rx = 0;
bands = [78];
max_pdschReferenceSignalPower = -27;
max_rxgain = 114;
eNB_instances = [0];
#beamforming 1x4 matrix:
bf_weights = [0x00007fff, 0x0000, 0x0000, 0x0000, 0x00000000, 0x00007fff, 0x0000, 0x0000, 0x0000, 0x0000, 0x00007fff, 0x0000, 0x0000, 0x0000, 0x0000, 0x00007fff];
clock_src = "internal";
}
);
THREAD_STRUCT = (
{
#three config for level of parallelism "PARALLEL_SINGLE_THREAD", "PARALLEL_RU_L1_SPLIT", or "PARALLEL_RU_L1_TRX_SPLIT"
parallel_config = "PARALLEL_SINGLE_THREAD";
#two option for worker "WORKER_DISABLE" or "WORKER_ENABLE"
worker_config = "WORKER_ENABLE";
}
);
rfsimulator :
{
serveraddr = "server";
serverport = "4043";
options = (); #("saviq"); or/and "chanmod"
modelname = "AWGN";
IQfile = "/tmp/rfsimulator.iqs";
};
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";
global_log_verbosity ="medium";
hw_log_level ="info";
hw_log_verbosity ="medium";
phy_log_level ="info";
phy_log_verbosity ="medium";
mac_log_level ="info";
mac_log_verbosity ="high";
rlc_log_level ="info";
rlc_log_verbosity ="medium";
pdcp_log_level ="info";
pdcp_log_verbosity ="medium";
rrc_log_level ="info";
rrc_log_verbosity ="medium";
ngap_log_level ="info";
ngap_log_verbosity ="medium";
};
@include "channelmod_rfsimu.conf"
@@ -0,0 +1,279 @@
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 = 12345678L;
////////// Physical parameters:
pdsch_AntennaPorts_XP = 2;
pdsch_AntennaPorts_N1 = 1;
pusch_AntennaPorts = 2;
do_CSIRS = 1;
do_SRS = 1;
servingCellConfigCommon = (
{
#spCellConfigCommon
physCellId = 0;
# downlinkConfigCommon
#frequencyInfoDL
# this is 3600 MHz + 43 PRBs@30kHz SCS (same as initial BWP)
absoluteFrequencySSB = 641280;
dl_frequencyBand = 78;
# this is 3600 MHz
dl_absoluteFrequencyPointA = 640008;
#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=27,L=48 (275*(L-1))+RBstart
initialDLBWPlocationAndBandwidth = 28875; # 6366 12925 12956 28875 12952
# subcarrierSpacing
# 0=kHz15, 1=kHz30, 2=kHz60, 3=kHz120
initialDLBWPsubcarrierSpacing = 1;
#pdcch-ConfigCommon
initialDLBWPcontrolResourceSetZero = 12;
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 = 106;
pMax = 20;
#initialUplinkBWP
#genericParameters
initialULBWPlocationAndBandwidth = 28875;
# 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 = -96;
#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;
#ra_ReponseWindow
#1,2,4,8,10,20,40,80
ra_ResponseWindow = 4;
#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;
#oneHalf (0..15) 4,8,12,16,...60,64
ssb_perRACH_OccasionAndCB_PreamblesPerSSB = 14;
#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_PositionsInBurs_BitmapPR
# 1=short, 2=medium, 3=long
ssb_PositionsInBurst_PR = 2;
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;
};
////////// AMF parameters:
amf_ip_address = ( { ipv4 = "192.168.70.132";
ipv6 = "192:168:30::17";
active = "yes";
preference = "ipv4";
}
);
NETWORK_INTERFACES :
{
GNB_INTERFACE_NAME_FOR_NG_AMF = "demo-oai";
GNB_IPV4_ADDRESS_FOR_NG_AMF = "192.168.70.129/24";
GNB_INTERFACE_NAME_FOR_NGU = "demo-oai";
GNB_IPV4_ADDRESS_FOR_NGU = "192.168.70.129/24";
GNB_PORT_FOR_S1U = 2152; # Spec 2152
};
}
);
MACRLCs = (
{
num_cc = 1;
tr_s_preference = "local_L1";
tr_n_preference = "local_RRC";
pusch_TargetSNRx10 = 150;
pucch_TargetSNRx10 = 200;
ulsch_max_frame_inactivity = 0;
ul_max_mcs = 28;
}
);
L1s = (
{
num_cc = 1;
tr_n_preference = "local_mac";
prach_dtx_threshold = 120;
pucch0_dtx_threshold = 100;
ofdm_offset_divisor = 8; #set this to UINT_MAX for offset 0
}
);
RUs = (
{
local_rf = "yes"
nb_tx = 2
nb_rx = 2
att_tx = 12;
att_rx = 12;
bands = [78];
max_pdschReferenceSignalPower = -27;
max_rxgain = 114;
eNB_instances = [0];
#beamforming 1x4 matrix:
# bf_weights = [0x00007fff, 0x0000, 0x0000, 0x00007fff];
clock_src = "internal";
}
);
THREAD_STRUCT = (
{
#three config for level of parallelism "PARALLEL_SINGLE_THREAD", "PARALLEL_RU_L1_SPLIT", or "PARALLEL_RU_L1_TRX_SPLIT"
parallel_config = "PARALLEL_SINGLE_THREAD";
#two option for worker "WORKER_DISABLE" or "WORKER_ENABLE"
worker_config = "WORKER_ENABLE";
}
);
rfsimulator :
{
serveraddr = "server";
serverport = "4043";
options = (); #("saviq"); or/and "chanmod"
modelname = "AWGN";
IQfile = "/tmp/rfsimulator.iqs";
};
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 ="debug";
f1ap_log_level ="debug";
};
e2_agent = {
near_ric_ip_addr = "127.0.0.1";
#sm_dir = "/path/where/the/SMs/are/located/"
sm_dir = "/usr/local/lib/flexric/"
};
@include "channelmod_rfsimu.conf"
@@ -0,0 +1,277 @@
Active_gNBs = ( "gNB-OAI");
# Asn1_verbosity, choice in: none, info, annoying
Asn1_verbosity = "none";
e2_agent = {
enabled = "yes";
near_ric_ip_addr = "192.168.71.21";
e2ap_server_port = 36422;
sm_dir = "/usr/local/lib/flexric/"
};
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 = 12345678L;
////////// Physical parameters:
pdsch_AntennaPorts_XP = 2;
pdsch_AntennaPorts_N1 = 2;
pusch_AntennaPorts = 2;
do_CSIRS = 1;
do_SRS = 1;
servingCellConfigCommon = (
{
#spCellConfigCommon
physCellId = 0;
# downlinkConfigCommon
#frequencyInfoDL
# this is 3600 MHz + 43 PRBs@30kHz SCS (same as initial BWP)
absoluteFrequencySSB = 641280;
dl_frequencyBand = 78;
# this is 3600 MHz
dl_absoluteFrequencyPointA = 640008;
#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=27,L=48 (275*(L-1))+RBstart
initialDLBWPlocationAndBandwidth = 28875; # 6366 12925 12956 28875 12952
# subcarrierSpacing
# 0=kHz15, 1=kHz30, 2=kHz60, 3=kHz120
initialDLBWPsubcarrierSpacing = 1;
#pdcch-ConfigCommon
initialDLBWPcontrolResourceSetZero = 12;
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 = 106;
pMax = 20;
#initialUplinkBWP
#genericParameters
initialULBWPlocationAndBandwidth = 28875;
# 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 = -96;
#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;
#ra_ReponseWindow
#1,2,4,8,10,20,40,80
# ra_ResponseWindow = 8;
#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;
#oneHalf (0..15) 4,8,12,16,...60,64
ssb_perRACH_OccasionAndCB_PreamblesPerSSB = 14;
#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_PositionsInBurs_BitmapPR
# 1=short, 2=medium, 3=long
ssb_PositionsInBurst_PR = 2;
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;
};
////////// AMF parameters:
amf_ip_address = ( { ipv4 = "192.168.71.132";
active = "yes";
preference = "ipv4";
}
);
NETWORK_INTERFACES :
{
GNB_IPV4_ADDRESS_FOR_NG_AMF = "192.168.71.140/24";
GNB_IPV4_ADDRESS_FOR_NGU = "192.168.71.140/24";
GNB_PORT_FOR_S1U = 2152; # Spec 2152
};
}
);
MACRLCs = (
{
num_cc = 1;
tr_s_preference = "local_L1";
tr_n_preference = "local_RRC";
pusch_TargetSNRx10 = 150;
pucch_TargetSNRx10 = 200;
ulsch_max_frame_inactivity = 0;
ul_max_mcs = 28;
}
);
L1s = (
{
num_cc = 1;
tr_n_preference = "local_mac";
prach_dtx_threshold = 120;
pucch0_dtx_threshold = 100;
ofdm_offset_divisor = 8; #set this to UINT_MAX for offset 0
}
);
RUs = (
{
local_rf = "yes"
nb_tx = 4
nb_rx = 2
att_tx = 12;
att_rx = 12;
bands = [78];
max_pdschReferenceSignalPower = -27;
max_rxgain = 114;
eNB_instances = [0];
#beamforming 1x4 matrix:
# bf_weights = [0x00010000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00010000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00010000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00010000];
# bf_weights = [0x00007fff, 0x0000, 0x0000, 0x0000, 0x00000000, 0x00007fff, 0x0000, 0x0000, 0x0000, 0x0000, 0x00007fff, 0x0000, 0x0000, 0x0000, 0x0000, 0x00007fff];
clock_src = "internal";
}
);
THREAD_STRUCT = (
{
#three config for level of parallelism "PARALLEL_SINGLE_THREAD", "PARALLEL_RU_L1_SPLIT", or "PARALLEL_RU_L1_TRX_SPLIT"
parallel_config = "PARALLEL_SINGLE_THREAD";
#two option for worker "WORKER_DISABLE" or "WORKER_ENABLE"
worker_config = "WORKER_ENABLE";
}
);
rfsimulator :
{
serveraddr = "server";
serverport = "4043";
options = (); #("saviq"); or/and "chanmod"
modelname = "AWGN";
IQfile = "/tmp/rfsimulator.iqs";
};
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 ="debug";
f1ap_log_level ="debug";
};
@include "channelmod_rfsimu.conf"
@@ -0,0 +1,52 @@
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";
};
uicc0 = {
imsi = "001010000000002";
key = "fec86ba6eb707ed08905757b1bb44b8f";
opc= "C42449363BBAD02B66D16BC975D77CC1";
dnn= "oai";
nssai_sst=1;
nssai_sd=16777215;
}
MACRLCs = (
{
num_cc = 1;
tr_n_preference = "nfapi";
local_n_if_name = "eno1";
remote_n_address = "127.0.0.1"; //Proxy IP
local_n_address = "127.0.0.1";
local_n_portc = 50600;
remote_n_portc = 50601;
local_n_portd = 50610;
remote_n_portd = 50611;
}
);
RUs = (
{
local_rf = "yes"
nb_tx = 1
nb_rx = 1
att_tx = 5;
att_rx = 5;
bands = [78];
# max_pdschReferenceSignalPower = -27;
# max_rxgain = 114;
# eNB_instances = [0];
#beamforming 1x4 matrix:
# bf_weights = [0x00010000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00010000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00010000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00010000];
# bf_weights = [0x00007fff, 0x0000, 0x0000, 0x0000, 0x00000000, 0x00007fff, 0x0000, 0x0000, 0x0000, 0x0000, 0x00007fff, 0x0000, 0x0000, 0x0000, 0x0000, 0x00007fff];
clock_src = "internal";
}
);
@include "channelmod_rfsimu.conf"
@@ -0,0 +1,34 @@
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";
};
uicc0 = {
imsi = "001010000000001";
key = "fec86ba6eb707ed08905757b1bb44b8f";
opc= "C42449363BBAD02B66D16BC975D77CC1";
dnn= "oai";
nssai_sst=1;
nssai_sd=16777215;
}
MACRLCs = (
{
num_cc = 1;
tr_n_preference = "nfapi";
local_n_if_name = "eno1";
remote_n_address = "127.0.0.1"; //Proxy IP
local_n_address = "127.0.0.1";
local_n_portc = 50600;
remote_n_portc = 50601;
local_n_portd = 50610;
remote_n_portd = 50611;
}
);
@include "channelmod_rfsimu.conf"
+1 -2
View File
@@ -1,5 +1,4 @@
uicc0 = {
imsi = "208990100001100";
uicc0 = { # imsi is omitted
key = "fec86ba6eb707ed08905757b1bb44b8f";
opc= "C42449363BBAD02B66D16BC975D77CC1";
dnn= "oai";
+34 -5
View File
@@ -4,27 +4,56 @@ uicc0:
opc: C42449363BBAD02B66D16BC975D77CC1
dnn: oai
nssai_sst: 1
pdu_type: IPV4
thread-pool: "-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1"
#/* configuration for channel modelisation */
#/* To be included in main config file when */
#/* channel modelisation is used (rfsimulator with chanmod options enabled) */
#/* Realistic channel model for antenna muting tests */
#/* With realistic noise/path loss, antenna muting will show throughput reduction */
channelmod:
max_chan: 10
modellist: modellist_rfsimu_1
modellist_rfsimu_1:
- model_name: rfsimu_channel_enB0
type: AWGN
ploss_dB: 20
noise_power_dB: -4
ploss_dB: 20 # Realistic path loss
noise_power_dB: -10 # Moderate noise to show antenna muting impact
forgetfact: 0
offset: 0
ds_tdl: 0
- model_name: rfsimu_channel_ue0
type: AWGN
ploss_dB: 20 # Realistic path loss
noise_power_dB: -10 # Moderate noise
forgetfact: 0
offset: 0
ds_tdl: 0
- model_name: rfsimu_channel_ue1
type: AWGN
ploss_dB: 20
nose_power_dB: -2
noise_power_dB: -10
forgetfact: 0
offset: 0
ds_tdl: 0
- model_name: rfsimu_channel_ue2
type: AWGN
ploss_dB: 20
noise_power_dB: -10
forgetfact: 0
offset: 0
ds_tdl: 0
- model_name: rfsimu_channel_ue3
type: AWGN
ploss_dB: 20
noise_power_dB: -10
forgetfact: 0
offset: 0
ds_tdl: 0
- model_name: rfsimu_channel_ue4
type: AWGN
ploss_dB: 20
noise_power_dB: -10
forgetfact: 0
offset: 0
ds_tdl: 0
@@ -0,0 +1,9 @@
[NEAR-RIC]
NEAR_RIC_IP = 192.168.71.21
NEAR_RIC_PORT = 36422
[XAPP]
DB_DIR = /tmp/
[LOG]
LEVEL = debug
@@ -1,36 +1,3 @@
################################################################################
# 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
################################################################################
# OAI CN Configuration File
### This file can be used by all OAI NFs
### Some fields are specific to an NF and will be ignored by other NFs
## NOTE ON YAML ANCHORS ##
# We use YAML anchors to ease the configuration and to avoid duplicating parts of the configuration.
# This is especially true for the SNSSAIs, as we have to define them for multiple NFs.
# Please note that the use of anchors is not mandatory, and you can define the SNSSAI in each NF yourself.
# You can read more about anchors here: https://yaml.org/spec/1.2.2/#anchors-and-aliases
############# Common configuration
# Log level for all the NFs
log_level:
general: debug
@@ -103,8 +70,8 @@ amf:
# This really depends on if we want to keep the "mini" version or not
support_features_options:
enable_simple_scenario: yes # "no" by default with the normal deployment scenarios with AMF/SMF/UPF/AUSF/UDM/UDR/NRF.
# set it to "yes" to use with the minimalist deployment scenario (including only AMF/SMF/UPF) by using the internal AUSF/UDM implemented inside AMF.
# There's no NRF in this scenario, SMF info is taken from "nfs" section.
# set it to "yes" to use with the minimalist deployment scenario (including only AMF/SMF/UPF) by using the internal AUSF/UDM implemented inside AMF.
# There's no NRF in this scenario, SMF info is taken from "nfs" section.
enable_nssf: no
enable_smf_selection: no
relative_capacity: 30
File diff suppressed because one or more lines are too long
@@ -0,0 +1,3 @@
[mysqld]
wait_timeout = 2
interactive_timeout = 2
+650
View File
@@ -0,0 +1,650 @@
#!/bin/bash
set -euo pipefail
# --- UE profile map (index 0..10). 0-index kept for convenience ---
# Initialize with "0" to denote no profile chosen.
declare -a UE_PROFILE_MAP
for i in {0..10}; do UE_PROFILE_MAP[$i]=0; done
# ---------------------------[ Random / diurnal Mode ]----------------------------
# UE scaler mode: 'random' (rapid random targets) or 'diurnal' (legacy cosine)
UE_SCALER_MODE="${UE_SCALER_MODE:-random}"
# Interval (sec) for scaler ticks
UE_SCALER_INTERVAL_SEC="${UE_SCALER_INTERVAL_SEC:-30}"
# -------------------------------[ Config ]------------------------------------
ARTIFACT_DIR="/media/nakaolab-go/2nd/artifacts"
mkdir -p "$ARTIFACT_DIR"
POWER_LOG_FILE="${POWER_LOG_FILE:-$ARTIFACT_DIR/power_usage.csv}"
CPU_PROXY_LOG_FILE="${CPU_PROXY_LOG_FILE:-$ARTIFACT_DIR/cpu_proxy.csv}"
RRC_SINCE_STATE="${RRC_SINCE_STATE:-$ARTIFACT_DIR/.rrc_since}"
# xApp (mac stats) container + path inside container
STATS_CONTAINER="${STATS_CONTAINER:-xapp-chatgpt}"
MAC_STATS_FILE_IN_CONTAINER="${MAC_STATS_FILE_IN_CONTAINER:-/tmp/mac_stats.log}"
PDCP_STATS_FILE_IN_CONTAINER="${PDCP_STATS_FILE_IN_CONTAINER:-/tmp/pdcp_stats.log}"
# Intervals (seconds)
RRC_LOG_INTERVAL_SEC="${RRC_LOG_INTERVAL_SEC:-10}"
POWER_LOG_INTERVAL_SEC="${POWER_LOG_INTERVAL_SEC:-5}"
CPU_PROXY_INTERVAL_SEC="${CPU_PROXY_INTERVAL_SEC:-5}"
# Docker compose helper
DC_BIN=(docker compose)
dc() { command "${DC_BIN[@]}" "$@"; }
dc down -v --remove-orphans
# Service names (override if your compose uses different ones)
AMF_SERVICE="${AMF_SERVICE:-oai-amf}"
UPF_SERVICE="${UPF_SERVICE:-oai-upf}"
GNB_SERVICE="${GNB_SERVICE:-oai-gnb}"
XAPP_SERVICE="${XAPP_SERVICE:-xapp-chatgpt}"
# UE service name pattern (for discovery)
UE_NAME_REGEX="${UE_NAME_REGEX:-^oai-nr-ue}"
# Test controls
TEST_MODE="${TEST_MODE:-0}" # 1=manual profiles allowed (TEST_* honored); 0=auto
TEST_UES="${TEST_UES:-}"
TEST_PROFILES="${TEST_PROFILES:-}" # profile name
TEST_DURATION_SEC="${TEST_DURATION_SEC:-0}" # 0 = run indefinitely
# Diurnal scaling controls (normal mode only; ignored if TEST_UE is set)
UE_MIN="${UE_MIN:-1}" # Minimum number of UEs to keep connected
UE_MAX="${UE_MAX:-10}"
UE_PEAK_HOUR="${UE_PEAK_HOUR:-17}" # Local hour with peak load (0..23)
UE_NOISE_FRAC="${UE_NOISE_FRAC:-0.15}" # ±fractional noise applied to target
# -------------------------[ Power Model (mW) ]--------------------------------
declare -A PWR_MW_PROFILE=(
[idle]=60 # screen on, modem camped, no transfers
[iot_chatty]=150 # tiny bursts, long sleeps
[browsing]=380 # short transfers with think-time
[voip]=520 # steady uplink/downlink small packets
[video]=850 # continuous DL; 720–1080p
[gaming]=950 # bursty DL + UL keepalives
[boring]=500 # for single PRB testing
[bulk_download]=1100 # sustained UL/DL at MCS ceiling
)
# -------------------------------[ Utils ]-------------------------------------
ts_now() { date +%s; }
ts_iso() { date -Is; }
ts_tag() { date +%Y%m%d_%H%M%S; }
log() { echo "[$(ts_iso)] $*"; }
exec_safe() {
local svc="$1"; shift
dc exec -T "$svc" bash -lc "$*"
}
# -----------------------------[ Discovery ]-----------------------------------
list_all_services() { dc config --services; }
get_all_ue_services() { list_all_services | grep -E "${UE_NAME_REGEX}" || true; }
get_core_services() {
# everything except UEs, gNB, xApp
list_all_services | grep -Ev "(${UE_NAME_REGEX}|^${GNB_SERVICE}$|^${XAPP_SERVICE}$)" || true
}
get_running_ues() {
dc ps --services --filter "status=running" \
| grep -E "${UE_NAME_REGEX}" || true
}
# -------------------------[ Readiness helpers ]-------------------------------
svc_cid() { dc ps -q "$1"; } # container id for a service
wait_healthy() {
local svc="$1" timeout="${2:-60}"
local cid; cid="$(svc_cid "$svc")"
if [[ -z "$cid" ]]; then
log "wait_healthy: service '$svc' not running."
return 1
fi
local health
health="$(docker inspect -f '{{if .State.Health}}{{.State.Health.Status}}{{end}}' "$cid" 2>/dev/null || true)"
if [[ -z "$health" ]]; then
log "wait_healthy: no healthcheck for '$svc'; skipping."
return 0
fi
local start; start="$(ts_now)"
while :; do
health="$(docker inspect -f '{{.State.Health.Status}}' "$cid" 2>/dev/null || echo "")"
if [[ "$health" == "healthy" ]]; then
log "$svc is healthy."
return 0
fi
if (( $(ts_now) - start > timeout )); then
log "wait_healthy timeout for '$svc' (last='$health')."
return 1
fi
sleep 1
done
}
wait_log() {
local svc="$1" pat="$2" timeout="${3:-60}"
local cid; cid="$(svc_cid "$svc")"
if [[ -z "$cid" ]]; then
log "wait_log: service '$svc' not running."
return 1
fi
local start; start="$(ts_now)"
while :; do
if dc logs "$svc" --since=2m 2>&1 | grep -Eiq -- "$pat"; then
return 0
fi
if (( $(ts_now) - start > timeout )); then
log "wait_log timeout for '$svc' waiting '$pat'."
return 1
fi
sleep 1
done
}
get_container_ip() {
local cid; cid="$(svc_cid "$1")"
[[ -z "$cid" ]] && return 1
docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' "$cid"
}
# --------------------------[ Staged bring-up ]--------------------------------
start_services_list() {
[[ $# -eq 0 ]] && return 0
dc up -d "$@" >/dev/null 2>&1
}
start_ue_list() {
[[ $# -eq 0 ]] && return 0
for svc in "$@"; do
log "Starting: $svc"
dc up -d "$svc" >/dev/null 2>&1
done
}
start_in_stages() {
# 1) CORE
mapfile -t core_svcs < <(get_core_services)
if [[ ${#core_svcs[@]} -gt 0 ]]; then
start_services_list "${core_svcs[@]}"
else
log "No CORE services to start."
exit 1
fi
# Ensure AMF is ready
wait_healthy "${AMF_SERVICE}" 15
wait_healthy "${UPF_SERVICE}" 15
# exec_safe "${UPF_SERVICE}" 'bash -lc "
# set -e
# sysctl -w net.ipv4.ip_forward=1
# sysctl -w net.ipv4.conf.all.rp_filter=2
# sysctl -w net.ipv4.conf.eth0.rp_filter=2
# sysctl -w net.ipv4.conf.eth1.rp_filter=2
# sysctl -w net.ipv4.conf.tun0.rp_filter=2
# ip rule add pref 100 iif tun0 to 192.168.71.0/24 lookup 201 || true
# ip route replace 192.168.71.0/24 dev eth0 table 201
# ip rule add pref 200 iif tun0 lookup 200 || true
# ip route replace 12.1.1.0/24 dev tun0 table 200
# ip route replace default via 192.168.72.129 dev eth1 table 200
# # UE -> DN (192.168.72.128/26) via eth1
# iptables -t nat -C POSTROUTING -s 12.1.1.0/24 -d 192.168.72.128/26 -o eth1 -j MASQUERADE 2>/dev/null || \
# iptables -t nat -A POSTROUTING -s 12.1.1.0/24 -d 192.168.72.128/26 -o eth1 -j MASQUERADE
# # UE -> 192.168.71.0/24 via eth0
# iptables -t nat -C POSTROUTING -s 12.1.1.0/24 -d 192.168.71.0/24 -o eth0 -j MASQUERADE 2>/dev/null || \
# iptables -t nat -A POSTROUTING -s 12.1.1.0/24 -d 192.168.71.0/24 -o eth0 -j MASQUERADE
# "'
wait_healthy oai-flexric 15
# 2) gNB
start_services_list "${GNB_SERVICE}"
wait_log "${GNB_SERVICE}" "Received NGSetupResponse from AMF" 20
wait_log "${GNB_SERVICE}" "Transaction ID E2 SETUP-REQUEST 0 E2 SETUP-RESPONSE 0" 15
# 3) xApp
start_services_list "${XAPP_SERVICE}"
wait_healthy "${XAPP_SERVICE}" 15
# 4) UE(s)
# if [[ -n "${TEST_UE:-}" ]]; then
# # Allow multiple UEs in TEST_UE (comma and/or space separated)
# IFS=', ' read -r -a _ue_list <<< "${TEST_UE}"
# start_services_list "${_ue_list[@]}"
# for ue in "${_ue_list[@]}"; do
# [[ -z "$ue" ]] && continue
# if wait_oai_tun "$ue" 20; then
# exec_safe "$ue" 'ip route replace default dev oaitun_ue1' || true
# run_traffic_profile "$ue" "random" "0" || true
# else
# exit 1
# fi
# done
# else
# # Initial diurnal target, then enforce UE count
# local target; if [[ "${UE_SCALER_MODE}" == "diurnal" ]]; then target="$(calc_target_ue_count)"; else target="$(random_target_ue_count)"; fi
# log "Initial diurnal target UE count: ${target}"
# ensure_ue_count "$target"
# fi
}
# -------------------------[ Diurnal UE scaling ]------------------------------
calc_target_ue_count() {
local total_ues="${UE_MAX:-}"
if [[ -z "$total_ues" ]]; then
total_ues="$(get_all_ue_services | wc -l | tr -d ' ')"
fi
local min="${UE_MIN:-1}"
if (( total_ues < min )); then total_ues="$min"; fi
local H M
H="$(date +%H)"; M="$(date +%M)"
# Compute target via cosine wave (peak at UE_PEAK_HOUR), then add noise and clamp
awk -v min="$min" -v max="$total_ues" -v peak="${UE_PEAK_HOUR:-14}" \
-v noise="${UE_NOISE_FRAC:-0.15}" -v H="$H" -v M="$M" 'BEGIN{
# time-of-day in days [0,1)
t = (H*60.0 + M)/1440.0
ph = peak/24.0
norm = (cos(2*3.141592653589793*(t - ph)) + 1)/2 # 0..1
base = min + norm*(max-min)
srand(); u = (rand()*2.0 - 1.0) * noise # [-noise, +noise]
val = base * (1.0 + u)
if (val < min) val = min
if (val > max) val = max
# round to nearest integer
printf("%.0f\n", val)
}'
}
ensure_ue_count() {
local target="$1"
# Build arrays
mapfile -t all_ues < <(get_all_ue_services)
mapfile -t running < <(get_running_ues)
# Current and desired counts
local cur="${#running[@]}"
if (( cur == target )); then
log "UE scaler: already at target (${target})."
return 0
fi
# Set helpers for membership tests
declare -A IS_RUNNING=()
for u in "${running[@]}"; do IS_RUNNING["$u"]=1; done
# Determine non-running UEs
declare -a not_running=()
for u in "${all_ues[@]}"; do
[[ -z "${IS_RUNNING[$u]:-}" ]] && not_running+=("$u")
done
# Start more UEs if needed
if (( cur < target )); then
local need=$(( target - cur ))
log "UE scaler: need +${need} UEs (cur=${cur}, target=${target})."
if (( ${#not_running[@]} == 0 )); then
log "UE scaler: no available UEs to start."
return 0
fi
local to_start=()
if command -v shuf >/dev/null 2>&1; then
mapfile -t to_start < <(printf '%s\n' "${not_running[@]}" | shuf -n "$need" || true)
else
to_start=("${not_running[@]:0:$need}")
fi
if (( ${#to_start[@]} > 0 )); then
start_ue_list "${to_start[@]}"
for ue in "${to_start[@]}"; do
if wait_log "$ue" "oaitun_ue1 successfully configured" 5 &&
exec_safe "$ue" 'ip link show oaitun_ue1 >/dev/null 2>&1'; then
exec_safe "$ue" 'ip route replace default dev oaitun_ue1'
# run_traffic_profile "$ue" "$(choose_random_profile)" "0"
run_traffic_profile "$ue" "boring"
else
dc down -v "$ue" >/dev/null 2>&1 &
fi
done
fi
return 0
fi
# Otherwise stop some UEs to shrink
local drop=$(( cur - target ))
log "UE scaler: need -${drop} UEs (cur=${cur}, target=${target})."
local to_stop=()
if command -v shuf >/dev/null 2>&1; then
mapfile -t to_stop < <(printf '%s\n' "${running[@]}" | shuf -n "$drop" || true)
else
to_stop=("${running[@]:0:$drop}")
fi
for ue in "${to_stop[@]}"; do
dc down -v "$ue" >/dev/null 2>&1 &
done
wait
}
# --------------------------[ UE traffic helpers ]-----------------------------
PROFILES=(boring browsing video bulk_download voip iot_chatty gaming)
choose_random_profile() {
if command -v shuf >/dev/null 2>&1; then
shuf -e "${PROFILES[@]}" -n1
else
idx=$(( RANDOM % ${#PROFILES[@]} ))
echo "${PROFILES[$idx]}"
fi
}
run_traffic_profile() {
local ue="$1" profile="$2"
# Record selected profile for this UE into UE_PROFILE_MAP
if [[ "$ue" =~ ([0-9]+)$ ]]; then
__idx="${BASH_REMATCH[1]}"
UE_PROFILE_MAP[$__idx]="$profile"
fi
dc cp /home/nakaolab-go/OAI/openairinterface5g/tools/scripts/traffic_runner.sh "$ue":/tmp/traffic_runner.sh >/dev/null 2>&1
exec_safe "$ue" "bash -lc '
prof=\$1;
/tmp/traffic_runner.sh --profile \$prof &
' -- $profile"
}
# --------------------------[ RRC / MAC scraping ]-----------------------------
rotate_stats_capture() {
local tag="${1:-}"
[[ -z "$tag" ]] && tag="$(ts_tag)"
if dc ps --services --filter "status=running" | grep -q "^${STATS_CONTAINER}$"; then
log "Rotating stats from ${STATS_CONTAINER}"
dc cp "${STATS_CONTAINER}:${MAC_STATS_FILE_IN_CONTAINER}" \
"${ARTIFACT_DIR}/mac_stats_${tag}.log" || true
dc cp "${STATS_CONTAINER}:${PDCP_STATS_FILE_IN_CONTAINER}" \
"${ARTIFACT_DIR}/pdcp_stats_${tag}.log" || true
# exec_safe "$STATS_CONTAINER" "> '${MAC_STATS_FILE_IN_CONTAINER}'"
# exec_safe "$STATS_CONTAINER" "> '${PDCP_STATS_FILE_IN_CONTAINER}'"
else
log "stats container ${STATS_CONTAINER} not running; skipping rotate"
fi
}
_since_arg_from_file() {
local f="$1"
local val
val="$(tr -d '[]\r\n' < "$f" 2>/dev/null || true)"
if [[ -n "$val" ]]; then
printf -- '--since=%s' "$val"
else
printf -- '--since=2m'
fi
}
dump_gnb_logs() {
local tag="$1"
dc logs "${GNB_SERVICE}" 2>&1 | grep -E "NR_RRC" > "${ARTIFACT_DIR}/gnb_rrc_${tag}.log"
dc logs "${GNB_SERVICE}" 2>&1 | grep -E "\[NR_MAC\] I Frame\.Slot|MAC:" > "${ARTIFACT_DIR}/gnb_${tag}.log"
}
# -----------------------------[ Power / CPU ]---------------------------------
init_power_logs() {
if [[ ! -s "$POWER_LOG_FILE" ]]; then
echo "epoch,ue,profile,power_mW,dt_s,energy_mJ" > "$POWER_LOG_FILE"
fi
if [[ ! -s "$CPU_PROXY_LOG_FILE" ]]; then
echo "epoch,container,cpu_percent,mem_usage" > "$CPU_PROXY_LOG_FILE"
fi
}
profile_total_mw() {
local profile="$1"
echo "${PWR_MW_PROFILE[$profile]:-0}"
}
sample_power_once() {
local epoch="$(ts_now)"
local dt_s="${POWER_LOG_INTERVAL_SEC}"
while IFS= read -r ue; do
[[ -z "$ue" ]] && continue
# Determine profile without container lock by consulting UE_PROFILE_MAP
local picked_profile picked_duration
if [[ "$ue" =~ ([0-9]+)$ ]]; then
__idx="${BASH_REMATCH[1]}"
picked_profile="${UE_PROFILE_MAP[$__idx]}"
fi
picked_duration=""
if [[ -z "$picked_profile" ]]; then
if [[ "${TEST_MODE}" -eq 1 ]]; then picked_profile="${TEST_PROFILE}"; else picked_profile="idle"; fi
fi
while [[ "$picked_profile" == -* ]]; do
picked_profile="${picked_profile#-}"
done
picked_profile="${picked_profile,,}"
local p_mw
p_mw="$(profile_total_mw "$picked_profile")"
: "${p_mw:=0}"
local energy_mJ=$(( p_mw * dt_s ))
echo "${epoch},${ue},${picked_profile},${p_mw},${dt_s},${energy_mJ}" >> "$POWER_LOG_FILE"
done < <(get_running_ues)
}
# --- CPU proxy: query docker stats by container ID and map back to service name ---
cpu_proxy_once() {
local epoch="$(ts_now)"
# Build list of UE service names (same as container names)
declare -a SVC_LIST=()
while IFS= read -r svc; do
[[ -z "$svc" ]] && continue
SVC_LIST+=("$svc")
done < <(get_running_ues)
# Query docker stats by NAME and key 'seen' by NAME
local fmt='{{.Name}},{{.CPUPerc}},{{.MemUsage}}'
declare -A SEEN=()
while IFS=',' read -r name cpu mem; do
[[ -z "$name" ]] && continue
name="${name#/}"
SEEN["$name"]=1
cpu="${cpu%%%}" # drop trailing '%'
echo "${epoch},${name},${cpu},${mem}" >> "$CPU_PROXY_LOG_FILE"
done < <(docker stats --no-stream --format "$fmt" "${SVC_LIST[@]}" 2>/dev/null || true)
# Any container absent from docker stats output → record zeros once
local svc
for svc in "${SVC_LIST[@]}"; do
if [[ -z "${SEEN[$svc]:-}" ]]; then
echo "${epoch},${svc},0,0" >> "$CPU_PROXY_LOG_FILE"
fi
done
}
rotate_csv_if_any() {
local src="$1" prefix="$2" tag="${3:-}"
[[ -z "$tag" ]] && tag="$(ts_tag)"
if [[ -s "$src" ]]; then
mv "$src" "${ARTIFACT_DIR}/${prefix}_${tag}.csv" || true
case "$prefix" in
power_usage) echo "epoch,ue,profile,power_mW,dt_s,energy_mJ" > "$src" ;;
cpu_proxy) echo "epoch,container,cpu_percent,mem_usage" > "$src" ;;
esac
fi
}
finalize_and_rotate_artifacts() {
local tag; tag="$(ts_tag)"
rotate_stats_capture "$tag" || true
dump_gnb_logs "$tag" || true
rotate_csv_if_any "$POWER_LOG_FILE" "power_usage" "$tag"
rotate_csv_if_any "$CPU_PROXY_LOG_FILE" "cpu_proxy" "$tag"
}
# -------------------------------[ Signals ]-----------------------------------
__SHUTTING_DOWN=0
on_interrupt() {
if [[ "${__SHUTTING_DOWN}" -eq 1 ]]; then return; fi
__SHUTTING_DOWN=1
log "Signal received (INT/TERM); taking final samples and rotating artifacts…"
# One last sample tick
sample_power_once || true
cpu_proxy_once || true
# Rotate artifacts and clean up
finalize_and_rotate_artifacts
cleanup
# 130 is the conventional exit code for SIGINT
exit 130
}
# --------------------------------[ Cleanup ]----------------------------------
cleanup() {
# Default: stop only UEs; keep infra up for inspection
log "Cleanup: stopping UE containers only"
while IFS= read -r ue; do
[[ -z "$ue" ]] && continue
dc down -v "$ue" >/dev/null 2>&1 &
done < <(dc ps --services | grep -E "${UE_NAME_REGEX}" || true)
}
# ---------------------------------[ Main ]------------------------------------
random_target_ue_count() {
local cur="$(get_running_ues | wc -l | tr -d ' ')"
bias=$((RANDOM % 100)) # 0–99
if (( cur <= UE_MIN )); then
if (( bias < 70 )); then
delta=$(( (RANDOM % 3) + 1 )) # +1..+3
else
delta=$(( RANDOM % 7 - 3 )) # -3..+3
fi
elif (( cur >= UE_MAX )); then
if (( bias < 70 )); then
delta=$(( -((RANDOM % 3) + 1) )) # -1..-3
else
delta=$(( RANDOM % 7 - 3 )) # -3..+3
fi
else # normal
delta=$(( RANDOM % 7 - 3 )) # -3..+3
fi
local target=$((cur + delta))
if (( target < UE_MIN )); then
target="${UE_MIN}"
elif (( target > UE_MAX )); then
target="${UE_MAX}"
fi
echo "$target"
}
main() {
trap on_interrupt INT TERM
init_power_logs
# Staged bring-up with readiness checks for both normal and TEST modes
start_in_stages
# Kick off traffic workload(s)
if [[ "${TEST_MODE}" -eq 1 ]]; then
log "TEST UE(S) ENABLED"
# Support multiple UEs (comma/space/newline separated)
read -r -a _ue_list <<< "$(tr ',\n' ' ' <<< "${TEST_UES}")"
read -r -a _ue_profiles <<< "$(tr ',\n' ' ' <<< "${TEST_PROFILES}")"
start_ue_list "${_ue_list[@]}"
for i in "${!_ue_list[@]}"; do
ue="${_ue_list[$i]}"
prof="${_ue_profiles[$i]}"
[[ -z "$ue" ]] && continue
if wait_log "$ue" "oaitun_ue1 successfully configured" 5 &&
exec_safe "$ue" 'ip link show oaitun_ue1 >/dev/null 2>&1'; then
exec_safe "$ue" "ip route replace default dev oaitun_ue1"
run_traffic_profile "$ue" "$prof"
else
exit 1
fi
done
fi
# Optional finite duration for TEST mode
if [[ "${TEST_MODE}" -eq 1 && -n "${TEST_UE:-}" && "${TEST_DURATION_SEC:-0}" -gt 0 ]]; then
RUN_STOP_TS=$(( $(ts_now) + TEST_DURATION_SEC ))
fi
# Main sampling & scaling loop
local last_rrc="$(( $(ts_now) - RRC_LOG_INTERVAL_SEC ))"
local last_power="$(( $(ts_now) - POWER_LOG_INTERVAL_SEC ))"
local last_cpu="$(( $(ts_now) - CPU_PROXY_INTERVAL_SEC ))"
local last_scale="$(( $(ts_now) - UE_SCALER_INTERVAL_SEC ))"
log "Entering main loop (sampling + diurnal scaler)…"
while :; do
local now; now="$(ts_now)"
if (( now - last_power >= POWER_LOG_INTERVAL_SEC )); then
sample_power_once || true
last_power="$now"
fi
if (( now - last_cpu >= CPU_PROXY_INTERVAL_SEC )); then
cpu_proxy_once || true
last_cpu="$now"
fi
# Re-evaluate diurnal target (normal mode only)
if [[ "${TEST_MODE}" -eq 0 ]] && (( now - last_scale >= UE_SCALER_INTERVAL_SEC )); then
local target;
if [[ "${UE_SCALER_MODE}" == "diurnal" ]]; then
target="$(calc_target_ue_count)";
else
target="$(random_target_ue_count)";
fi
log "Scaler tick: target = ${target}"
ensure_ue_count "$target"
last_scale="$now"
fi
# TEST duration stop gate
if [[ -n "${RUN_STOP_TS:-}" && "$now" -ge "${RUN_STOP_TS}" ]]; then
log "TEST run reached duration (${TEST_DURATION_SEC}s); finalizing artifacts…"
finalize_and_rotate_artifacts
cleanup
exit 0
fi
sleep 1
done
}
main "$@"
@@ -1,6 +0,0 @@
services:
oai-nr-ue:
privileged: true
stdin_open: true
tty: true
command: gdb --args /opt/oai-nr-ue/bin/nr-uesoftmodem -O /opt/oai-nr-ue/etc/nr-ue.conf -E --rfsim -r 106 --numerology 1 --uicc0.imsi 208990100001100 -C 3619200000 --rfsimulator.serveraddr 192.168.71.140 --log_config.global_log_options level,nocolor,time
@@ -1,27 +0,0 @@
include:
- ../local_common_overrides/build_images.yaml
- ../local_common_overrides/rebuild_nr_softmodems.yaml
services:
oai-gnb:
privileged: true
depends_on:
rebuild-nr-softmodems:
condition: service_completed_successfully
volumes:
- ../../../cmake_targets/ran_build/build/nr-softmodem:/opt/oai-gnb/bin/nr-softmodem
- ../../../cmake_targets/ran_build/build/librfsimulator.so:/usr/local/lib/librfsimulator.so
oai-nr-ue:
privileged: true
image: ue-local
depends_on:
build-ran-build:
condition: service_completed_successfully
restart: true
oai-gnb:
condition: service_started
rebuild-nr-softmodems:
condition: service_completed_successfully
volumes:
- ../../../cmake_targets/ran_build/build/nr-uesoftmodem:/opt/oai-nr-ue/bin/nr-uesoftmodem
- ../../../cmake_targets/ran_build/build/librfsimulator.so:/usr/local/lib/librfsimulator.so
- ../../../crashdumps/:/cores/
@@ -0,0 +1,44 @@
docker build -f docker/Dockerfile.gnb_e2 -t nakaolab:sep1 .
O-RU and O-DU are connected via the Fronthaul (FH) interface.
FH traffic is logically diviced into 4 planes:
1. Control Plane which transports real-time slot configuration control messages
2. User Plane which transports IQ samples data (amplitude and phase)
3. Synchronization Plane transports timing information messages
4. Management Plane carries non-real-time management operation messages
C/U Plane messages are exchanged using the eCPRI protocol.
The energy consumption of RU can be modeled as
P = P_0 + P_RF + P_BB + P_PAM
where
P_0 : baseline components in the RU (fabric, ethernet, PCIe, etc.)
P_RF : a-to-d/d-to-a converters (ADC/DAC) and the freq up/down converters
P_BB : core digital processor (eFPGA) and the other low-PHY processing units
(e.g. beamforming) for each antenna chain
P_PAM : power amplifier module (PAM) at each antenna chain
The paper claims that components other than PAM are rather static (?)
-> I guess the PAM is controlled by RF. Thus P_RF is the minimum amount of power
required to run RF.
P_PAM = P_(PAM, 0) * P_(PAM, c)(r) * E_PAM * n_ant
where
P_(PAM, 0) : baseline power consumption that depends on the n of antennas,
ganging elements, and the maximum EIRP per antenna
* EIRP (Effective Isotropic Radiated Power) is the total power radiated
by an antenna in the direction of its maximum gain, referenced to an ideal isotropic radiator.
P_(PAM, c)(r) : a factor that depends on _the amount of subcarriers to be amplified_,
which in turn depends on the n of RBs (r) carried therein
E_PAM : captures the efficiency of the amplifier to convert DC power into signal power gain
n_ant : n of radio chains (with ONE amplifier per radio chain)
> This observation motivates the principle behind the solution we propose in this paper:
consolidate data into as few symbol periods as possible, using more radio resources per period instead.
Since keeping PAM awake consumes more energy, it is better to send data in short bursts, where each burst
utilizes more spectrum resources
MIMO Muting
Binary file not shown.

Before

Width:  |  Height:  |  Size: 55 KiB

@@ -0,0 +1,144 @@
#!/usr/bin/env python3
"""
UE Count Plotter
----------------
Parses log lines like:
[2025-09-08 21:33:40.789676] UE RNTI=44512 ...
Groups entries into 1-second slots (by wall-clock time) and plots the number of
unique UEs (by RNTI) per slot for the entire file.
Usage:
python ue_count_plot.py /path/to/log.txt [--save output.png] [--no-show]
Notes:
- Uses matplotlib for a single-line plot.
- Does not set any colors or styles.
- For very large logs, this streams line-by-line with O(N) memory in the number
of distinct seconds encountered (each holds a set of RNTIs).
"""
import argparse
import datetime as dt
import re
import sys
from collections import defaultdict, OrderedDict
from typing import Dict, Set, Iterable, Tuple
import matplotlib.pyplot as plt
import matplotlib.dates as mdates
TIMESTAMP_RE = re.compile(r"\[(?P<ts>\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}(?:\.\d{1,6})?)\]")
RNTI_RE = re.compile(r"\bRNTI=(?P<rnti>\d+)\b")
def parse_lines(lines: Iterable[str]) -> Dict[dt.datetime, Set[int]]:
"""
Parse lines and bucket unique RNTIs per second.
Returns:
dict mapping second (datetime truncated to seconds) -> set of RNTIs
"""
buckets: Dict[dt.datetime, Set[int]] = defaultdict(set)
for line in lines:
ts_m = TIMESTAMP_RE.search(line)
rnti_m = RNTI_RE.search(line)
if not ts_m or not rnti_m:
# Skip lines that don't fit the schema
continue
ts_str = ts_m.group("ts")
# Parse timestamp with or without fractional seconds
# Accept both "%Y-%m-%d %H:%M:%S" and with "%f"
if "." in ts_str:
ts = dt.datetime.strptime(ts_str, "%Y-%m-%d %H:%M:%S.%f")
else:
ts = dt.datetime.strptime(ts_str, "%Y-%m-%d %H:%M:%S")
# Truncate to whole-second wall time
ts_minute = ts.replace(second=0, microsecond=0)
rnti = int(rnti_m.group("rnti"))
buckets[ts_minute].add(rnti)
return buckets
def sort_and_fill(buckets: Dict[dt.datetime, Set[int]]) -> Tuple[list, list]:
"""
Sorts buckets by time and fills missing seconds with zero counts.
Returns:
times (list[datetime]), counts (list[int])
"""
if not buckets:
return [], []
# Sort keys
sorted_times = sorted(buckets.keys())
start = sorted_times[0]
end = sorted_times[-1]
all_times = []
counts = []
cur = start
one_sec = dt.timedelta(minutes=1)
while cur <= end:
all_times.append(cur)
counts.append(len(buckets.get(cur, set())))
cur += one_sec
return all_times, counts
def plot_counts(times: list, counts: list, title: str = "Unique UEs per minute"):
"""
Render a simple matplotlib line plot (no specific colors/styles set).
"""
fig, ax = plt.subplots(figsize=(10, 4))
ax.plot(times, counts, linestyle="-")
ax.set_title(title)
ax.set_xlabel("Time (s)")
ax.set_ylabel("Unique UEs")
ax.xaxis.set_major_formatter(mdates.DateFormatter("%H:%M:%S"))
fig.autofmt_xdate()
plt.tight_layout()
return fig, ax
def main():
parser = argparse.ArgumentParser(description="Plot unique UEs (by RNTI) per minute from a log file.")
parser.add_argument("--save", help="Optional path to save the plot image (e.g., output.png)")
parser.add_argument("--no-show", action="store_true", help="Do not display the plot window")
args = parser.parse_args()
# Read lines
try:
lines = open("artifacts/mac_stats_20250909_231855.log", "r", encoding="utf-8")
except OSError as e:
print(f"Error opening {args.logfile}: {e}", file=sys.stderr)
sys.exit(1)
buckets = parse_lines(lines)
if lines is not sys.stdin:
lines.close()
times, counts = sort_and_fill(buckets)
if not times:
print("No valid entries found to plot.", file=sys.stderr)
sys.exit(2)
fig, _ = plot_counts(times, counts)
if args.save:
try:
fig.savefig(args.save, dpi=150, bbox_inches="tight")
print(f"Saved plot to: {args.save}")
except Exception as e:
print(f"Failed to save figure: {e}", file=sys.stderr)
if not args.no_show:
plt.show()
if __name__ == "__main__":
main()
@@ -0,0 +1,772 @@
from __future__ import annotations
"""
Memory-safe, drop-in refined version of prediction.py
- Streams very large log files (no giant raw DataFrames)
- Computes observed flags without large Python sets
- Zero-copy feature audit (mask features on the fly)
You can run this directly. Key differences from the original are marked with
"STREAMING" and "ZERO-COPY" comments.
"""
import glob, os, re
from collections import defaultdict
from dataclasses import dataclass
from datetime import datetime
from pathlib import Path
from typing import List, Tuple, Dict, Optional
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import torch
import torch.nn as nn
from torch.utils.data import Dataset, DataLoader
# ----------------------------
# Utils
# ----------------------------
def set_seed(seed=42):
import random
random.seed(seed)
np.random.seed(seed)
torch.manual_seed(seed)
torch.cuda.manual_seed_all(seed)
set_seed(42)
def ensure_dir(p: Path):
p.mkdir(parents=True, exist_ok=True)
return p
def _run_lengths(mask: np.ndarray):
"""Return number of runs and their lengths (consecutive True segments)."""
if mask.sum() == 0:
return 0, np.array([], dtype=int)
m = mask.astype(bool)
dm = np.diff(np.concatenate(([False], m, [False])).astype(np.int8))
starts = np.where(dm == 1)[0]
ends = np.where(dm == -1)[0]
lens = ends - starts
return len(lens), lens
def report_underpredictions(
timestamps: pd.Series,
actual: np.ndarray,
pred: np.ndarray,
out_csv_path
):
"""
Save a per-minute CSV of under-predictions (pred < actual) and
return a dict of summary metrics.
"""
actual = actual.astype("float64")
pred = pred.astype("float64")
finite = np.isfinite(actual) & np.isfinite(pred)
if finite.sum() == 0:
return {
"n_pred_points": 0, "n_under": 0, "under_rate": np.nan,
"mae_under": np.nan, "sum_under": np.nan, "max_under": np.nan,
"p95_under": np.nan, "n_runs": 0, "avg_run": np.nan, "max_run": 0
}
under = finite & (pred < actual)
gap = (actual - pred) # positive when under
rel_gap = np.divide(gap, np.maximum(actual, 1e-9))
# summary metrics
n_pred = int(finite.sum())
n_under = int(under.sum())
under_rate = n_under / n_pred
if n_under > 0:
mae_under = float(gap[under].mean())
sum_under = float(gap[under].sum())
max_under = float(gap[under].max())
p95_under = float(np.percentile(gap[under], 95))
runs, lens = _run_lengths(under)
avg_run = float(lens.mean()) if runs > 0 else np.nan
max_run = int(lens.max()) if runs > 0 else 0
else:
mae_under = sum_under = max_under = p95_under = np.nan
runs = 0; avg_run = np.nan; max_run = 0
# write detailed CSV (only the under-predicted rows)
df = pd.DataFrame({
"timestamp": np.asarray(timestamps)[finite],
"actual": actual[finite],
"pred": pred[finite],
"under": (pred[finite] < actual[finite]),
"gap": gap[finite],
"rel_gap": rel_gap[finite],
})
df_under = df[df["under"]].drop(columns=["under"])
df_under.to_csv(out_csv_path, index=False)
return {
"n_pred_points": n_pred,
"n_under": n_under,
"under_rate": under_rate,
"mae_under": mae_under,
"sum_under": sum_under,
"max_under": max_under,
"p95_under": p95_under,
"n_runs": runs,
"avg_run": avg_run,
"max_run": max_run,
}
# ----------------------------
# Config
# ----------------------------
# NOTE: fixed missing comma in DATE_STRS
DATE_STRS = ["20250828", "20250829"] # train/eval days handled independently
LOG_DIR = "artifacts"
RESULT_DIR = Path("results")
SEQ_LEN = 45 # history length (minutes)
PRED_LEN = 10 # forecast horizon (minutes)
BATCH = 256
EPOCHS_FIRST = 4 # per-target epochs on the first day (you can raise)
EPOCHS_NEXT = 2 # per-target epochs on subsequent days
LR = 1e-3
ADD_TIME_OF_DAY = True # add sin/cos time-of-day features
# === Feature selection ===
ENABLE_FEATURE_SELECTION = True
AUDIT_DAY = DATE_STRS[0]
FS_EPOCHS = max(2, min(3, EPOCHS_FIRST)) # quick audit training
FS_IMPROVE_THRESH = 0.01 # drop if masking improves val loss by >=1%
FS_NOISE_BAND = 0.002 # ignore micro-changes (<0.2%)
DEVICE = torch.device("cuda" if torch.cuda.is_available() else "cpu")
# Model targets
TARGETS = ["num_ues", "traffic_load"]
# Columns we aim to have per minute (some may be zero if not present)
FOCUS_COLS = [
"num_ues","traffic_load","dl_prbs","ul_prbs","dl_sched_rbs","ul_sched_rbs",
"wb_cqi","dl_mcs1","ul_mcs1","pusch_snr","pucch_snr",
"dl_bler","ul_bler","bsr","phr",
"rrc_total","rrc_unique_rnti","rrc_setup_cpl","rrc_release",
"rrc_recfg","rrc_recfg_cpl","rrc_resume","rrc_paging"
]
# ----------------------------
# Regex / Parsing
# ----------------------------
LOG_PATTERN_FULL = re.compile(
r"""\[
(?P<ts>.*?)\]
\s+UE\s+RNTI=(?P<rnti>\d+)
\s+DL_TBS=(?P<dl_tbs>\d+)
\s+UL_TBS=(?P<ul_tbs>\d+)
\s+DL_SDUs=(?P<dl_sdus>\d+)
\s+UL_SDUs=(?P<ul_sdus>\d+)
\s+DL_sched_RBs=(?P<dl_sched_rbs>\d+)
\s+UL_sched_RBs=(?P<ul_sched_rbs>\d+)
\s+DL_PRBs=(?P<dl_prbs>\d+)
\s+UL_PRBs=(?P<ul_prbs>\d+)
\s+DL_BLER=(?P<dl_bler>[\d.]+)
\s+UL_BLER=(?P<ul_bler>[\d.]+)
\s+PUSCH_SNR=(?P<pusch_snr>[\d.]+)
\s+PUCCH_SNR=(?P<pucch_snr>[\d.]+)
\s+WB_CQI=(?P<wb_cqi>\d+)
\s+DL_MCS1=(?P<dl_mcs1>\d+)
\s+UL_MCS1=(?P<ul_mcs1>\d+)
\s+DL_HARQ=(?P<dl_harq>\d+)
\s+UL_HARQ=(?P<ul_harq>\d+)
\s+BSR=(?P<bsr>\d+)
\s+PHR=(?P<phr>\d+)
""", re.VERBOSE,
)
RRC_PATTERNS: List[re.Pattern] = [
re.compile(r"\[(?P<ts>[^]]+)\].*RNTI=(?P<rnti>[0-9a-fA-F]+).*RRC[_ ]?MSG=(?P<msg>[A-Za-z_]+)", re.IGNORECASE),
re.compile(r"\[(?P<ts>[^]]+)\].*rrc[=:](?P<msg>[A-Za-z_]+).*rnti[=:](?P<rnti>[0-9a-fA-F]+)", re.IGNORECASE),
re.compile(r"\[(?P<ts>[^]]+)\].*(?P<msg>RRC[A-Za-z]+).*RNTI=(?P<rnti>[0-9a-fA-F]+)", re.IGNORECASE),
re.compile(r"\|\s*(?P<ts>\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?Z?).*?RNTI\s*(?P<rnti>[0-9a-fA-F]+)", re.IGNORECASE),
]
# ----------------------------
# STREAMING: Per-minute aggregators without raw-frame concatenation
# ----------------------------
C_RNTI_MIN, C_RNTI_MAX = 0x003D, 0xFFFC
@dataclass
class _MACAgg:
dl_sdus_sum: float = 0.0; ul_sdus_sum: float = 0.0
dl_sched_rbs_sum: float = 0.0; ul_sched_rbs_sum: float = 0.0
dl_prbs_sum: float = 0.0; ul_prbs_sum: float = 0.0
dl_bler_sum: float = 0.0; ul_bler_sum: float = 0.0
pusch_snr_sum: float = 0.0; pucch_snr_sum: float = 0.0
wb_cqi_sum: float = 0.0; dl_mcs1_sum: float = 0.0; ul_mcs1_sum: float = 0.0
dl_harq_sum: float = 0.0; ul_harq_sum: float = 0.0
bsr_sum: float = 0.0; phr_sum: float = 0.0
count: int = 0
dl_bytes: int = 0; ul_bytes: int = 0
ues: Optional[set] = None
def _floor_min(ts: datetime) -> pd.Timestamp:
return pd.Timestamp(ts).floor("min")
def stream_mac_minutes(files: List[str]) -> pd.DataFrame:
if not files: return pd.DataFrame()
acc: Dict[pd.Timestamp, _MACAgg] = {}
for path in files:
with open(path, "r", errors="ignore") as f:
for line in f:
m = LOG_PATTERN_FULL.search(line)
if not m: continue
d = m.groupdict()
try:
ts = datetime.fromisoformat(d["ts"]) # may raise
except Exception:
continue
t = _floor_min(ts)
A = acc.get(t)
if A is None:
A = _MACAgg(ues=set())
acc[t] = A
# unique UE when real RNTI with any traffic
try:
rnti_val = int(d["rnti"]) # MAC logs use decimal here
except Exception:
rnti_val = -1
dl_tbs = int(d["dl_tbs"]); ul_tbs = int(d["ul_tbs"])
if (C_RNTI_MIN <= rnti_val <= C_RNTI_MAX) and (dl_tbs > 0 or ul_tbs > 0):
A.ues.add(rnti_val)
# sums & bytes
A.dl_bytes += dl_tbs; A.ul_bytes += ul_tbs
A.dl_sdus_sum += int(d["dl_sdus"]); A.ul_sdus_sum += int(d["ul_sdus"])
A.dl_sched_rbs_sum += int(d["dl_sched_rbs"]); A.ul_sched_rbs_sum += int(d["ul_sched_rbs"])
A.dl_prbs_sum += int(d["dl_prbs"]); A.ul_prbs_sum += int(d["ul_prbs"])
A.dl_bler_sum += float(d["dl_bler"]); A.ul_bler_sum += float(d["ul_bler"])
A.pusch_snr_sum += float(d["pusch_snr"]); A.pucch_snr_sum += float(d["pucch_snr"])
A.wb_cqi_sum += int(d["wb_cqi"]); A.dl_mcs1_sum += int(d["dl_mcs1"]); A.ul_mcs1_sum += int(d["ul_mcs1"])
A.dl_harq_sum += int(d["dl_harq"]); A.ul_harq_sum += int(d["ul_harq"])
A.bsr_sum += int(d["bsr"]); A.phr_sum += int(d["phr"])
A.count += 1
if not acc: return pd.DataFrame()
rows = []
for t in sorted(acc.keys()):
A = acc[t]
c = max(1, A.count)
row = {
"timestamp": t,
"num_ues": len(A.ues) if A.ues is not None else 0,
"dl_bytes": A.dl_bytes,
"ul_bytes": A.ul_bytes,
"dl_sdus": A.dl_sdus_sum / c,
"ul_sdus": A.ul_sdus_sum / c,
"dl_sched_rbs": A.dl_sched_rbs_sum / c,
"ul_sched_rbs": A.ul_sched_rbs_sum / c,
"dl_prbs": A.dl_prbs_sum / c,
"ul_prbs": A.ul_prbs_sum / c,
"dl_bler": A.dl_bler_sum / c,
"ul_bler": A.ul_bler_sum / c,
"pusch_snr": A.pusch_snr_sum / c,
"pucch_snr": A.pucch_snr_sum / c,
"wb_cqi": A.wb_cqi_sum / c,
"dl_mcs1": A.dl_mcs1_sum / c,
"ul_mcs1": A.ul_mcs1_sum / c,
"dl_harq": A.dl_harq_sum / c,
"ul_harq": A.ul_harq_sum / c,
"bsr": A.bsr_sum / c,
"phr": A.phr_sum / c,
"traffic_load": A.dl_bytes + A.ul_bytes,
"mac_present": True,
}
rows.append(row)
out = pd.DataFrame(rows)
out.sort_values("timestamp", inplace=True)
return out
RRC_FAM_COLS = ["rrc_conn_req","rrc_setup_cpl","rrc_recfg","rrc_recfg_cpl","rrc_reestab",
"rrc_release","rrc_resume","rrc_suspend","rrc_paging","rrc_other"]
def canonical_rrc_family(msg: str | None) -> str:
if not msg: return "rrc_other"
m = msg.lower()
if "connectionrequest" in m or "connrequest" in m or "setupreq" in m: return "rrc_conn_req"
if "connectionsetupcomplete" in m or "setupcomplete" in m: return "rrc_setup_cpl"
if "reconfigurationcomplete" in m or "reconfigcomplete" in m: return "rrc_recfg_cpl"
if "reconfiguration" in m or "reconfig" in m: return "rrc_recfg"
if "reestablish" in m: return "rrc_reestab"
if "release" in m: return "rrc_release"
if "pag" in m: return "rrc_paging"
if "resume" in m: return "rrc_resume"
if "suspend" in m: return "rrc_suspend"
return "rrc_other"
def stream_rrc_minutes(files: List[str]) -> pd.DataFrame:
if not files: return pd.DataFrame()
total = defaultdict(int)
unique = defaultdict(set)
fam = {c: defaultdict(int) for c in RRC_FAM_COLS}
for path in files:
with open(path, "r", errors="ignore") as f:
for line in f:
ts = rnti = msg = None
for pat in RRC_PATTERNS:
m = pat.search(line)
if m:
gd = m.groupdict(); ts = gd.get("ts"); rnti = gd.get("rnti"); msg = gd.get("msg"); break
if not ts: continue
ts = ts.rstrip("Z")
try:
t = _floor_min(datetime.fromisoformat(ts))
except ValueError:
continue
total[t] += 1
fam_key = canonical_rrc_family(msg)
fam[fam_key][t] += 1
# unique per minute if we can parse rnti
rid = None
if rnti is not None:
try:
rid = int(rnti, 16) if any(c.isalpha() for c in rnti) else int(rnti, 10)
except ValueError:
pass
if rid is not None:
unique[t].add(rid)
if not total: return pd.DataFrame()
minutes = sorted(total.keys())
data = {
"timestamp": minutes,
"rrc_total": [total[t] for t in minutes],
"rrc_unique_rnti": [len(unique[t]) if t in unique else 0 for t in minutes],
"rrc_present": [True] * len(minutes),
}
for c in RRC_FAM_COLS:
data[c] = [fam[c][t] if t in fam[c] else 0 for t in minutes]
out = pd.DataFrame(data)
return out
# ----------------------------
# Per-minute features (compat helpers kept for reference)
# ----------------------------
def full_minute_index(start, end):
return pd.date_range(start.floor("min"), end.ceil("min"), freq="1min")
def add_time_of_day_feats(df: pd.DataFrame) -> pd.DataFrame:
if not ADD_TIME_OF_DAY: return df
out = df.copy()
m = out["timestamp"].dt.hour * 60 + out["timestamp"].dt.minute
out["tod_sin"] = np.sin(2*np.pi*m/1440.0).astype("float32")
out["tod_cos"] = np.cos(2*np.pi*m/1440.0).astype("float32")
return out
# ----------------------------
# STREAMING: Memory-safe build_day_features
# ----------------------------
def build_day_features(day: str) -> pd.DataFrame:
mac_files = glob.glob(os.path.join(LOG_DIR, f"mac_stats_{day}.log")) \
+ glob.glob(os.path.join(LOG_DIR, f"mac_stats_{day}_*.log"))
rrc_files = glob.glob(os.path.join(LOG_DIR, f"rrc_messages_{day}.log")) \
+ glob.glob(os.path.join(LOG_DIR, f"rrc_messages_{day}_*.log"))
mac_feat = stream_mac_minutes(mac_files)
rrc_feat = stream_rrc_minutes(rrc_files)
if mac_feat.empty and rrc_feat.empty:
raise SystemExit(f"[{day}] no parsable lines found")
# build the full per-minute index from whichever side exists
frames = [df for df in (mac_feat, rrc_feat) if not df.empty]
t_start = min(df["timestamp"].min() for df in frames)
t_end = max(df["timestamp"].max() for df in frames)
idx = pd.date_range(t_start.floor("min"), t_end.ceil("min"), freq="1min")
# --- MAC side: reindex or fabricate skeleton if empty
if not mac_feat.empty:
mac_feat = (mac_feat.set_index("timestamp")
.reindex(idx).fillna(0.0)
.rename_axis("timestamp").reset_index())
mac_feat["mac_present"] = True
else:
mac_feat = pd.DataFrame({"timestamp": idx})
mac_feat["mac_present"] = False # no MAC rows this minute
# --- RRC side: reindex or fabricate skeleton if empty
if not rrc_feat.empty:
rrc_feat = (rrc_feat.set_index("timestamp")
.reindex(idx).fillna(0.0)
.rename_axis("timestamp").reset_index())
rrc_feat["rrc_present"] = True
else:
rrc_feat = pd.DataFrame({"timestamp": idx})
# minimal set the rest of the code expects:
rrc_feat["rrc_total"] = 0.0
rrc_feat["rrc_unique_rnti"] = 0.0
for c in RRC_FAM_COLS:
rrc_feat[c] = 0.0
rrc_feat["rrc_present"] = False
# merge & flags
df = (mac_feat.merge(rrc_feat, on="timestamp", how="left")
.fillna(0.0).reset_index(drop=True))
present_cols = []
if "mac_present" in df: present_cols.append(df["mac_present"].astype(bool))
if "rrc_present" in df: present_cols.append(df["rrc_present"].astype(bool))
df["observed"] = False if not present_cols else np.logical_or.reduce(present_cols)
# time-of-day & compact dtypes
if ADD_TIME_OF_DAY:
m = df["timestamp"].dt.hour * 60 + df["timestamp"].dt.minute
df["tod_sin"] = np.sin(2*np.pi*m/1440.0).astype("float32")
df["tod_cos"] = np.cos(2*np.pi*m/1440.0).astype("float32")
float64_cols = df.select_dtypes(include=["float64"]).columns
if len(float64_cols): df[float64_cols] = df[float64_cols].astype("float32")
return df
# ----------------------------
# Sequence dataset (streaming)
# ----------------------------
def valid_t_indices(observed: np.ndarray, seq_len: int, pred_len: int) -> np.ndarray:
"""Return t indices where [t-seq_len, t+pred_len-1] are all observed=True."""
obs = observed.astype(np.uint8)
w = seq_len + pred_len
s = pd.Series(obs).rolling(w, min_periods=w).sum().to_numpy()
idx = []
for i in range(w - 1, len(obs)):
if s[i] == w:
t = i - pred_len + 1
if t >= seq_len:
idx.append(t)
return np.array(idx, dtype=np.int64)
class TSWindowDataset(Dataset):
def __init__(self, values_norm: np.ndarray, t_indices: np.ndarray, seq_len: int, pred_len: int, tgt_idx: int):
self.V = values_norm
self.t_idx = t_indices
self.seq = seq_len
self.h = pred_len
self.tgt = tgt_idx
def __len__(self): return len(self.t_idx)
def __getitem__(self, i):
t = self.t_idx[i]
x = self.V[t - self.seq : t, :]
y = self.V[t : t + self.h, self.tgt]
return torch.from_numpy(x), torch.from_numpy(y)
# ----------------------------
# LSTM model
# ----------------------------
class Seq2PointLSTM(nn.Module):
def __init__(self, input_size, hidden=128, layers=1, pred_len=PRED_LEN, dropout=0.0):
super().__init__()
self.lstm = nn.LSTM(input_size, hidden, layers, batch_first=True, dropout=dropout if layers>1 else 0.0)
self.fc = nn.Linear(hidden, pred_len)
def forward(self, x):
enc, _ = self.lstm(x)
return self.fc(enc[:, -1, :])
# ----------------------------
# Train / Forecast per day
# ----------------------------
def prepare_day_matrix(day_df: pd.DataFrame, feat_cols_override: List[str] | None = None) -> Tuple[np.ndarray, np.ndarray, np.ndarray, np.ndarray, list]:
"""Return values_norm, mu, sigma, observed, feature_names in a fixed order."""
base_cols = FOCUS_COLS + (["tod_sin","tod_cos"] if ADD_TIME_OF_DAY else [])
feat_cols = feat_cols_override if feat_cols_override is not None else base_cols
# ensure presence; if missing, create zeros
df = day_df.copy()
for c in feat_cols:
if c not in df.columns:
df[c] = 0.0
Xdf = df[feat_cols].astype("float32").copy()
observed = df["observed"].to_numpy()
obs_mask = observed.astype(bool)
mu = Xdf[obs_mask].mean(0).to_numpy(dtype=np.float32)
sigma = Xdf[obs_mask].std(0).to_numpy(dtype=np.float32) + 1e-8
values_norm = ((Xdf.to_numpy(dtype=np.float32) - mu) / sigma).astype(np.float32)
return values_norm, mu, sigma, observed.astype(np.bool_), feat_cols
def build_dataloaders(values_norm: np.ndarray, observed: np.ndarray, feat_cols: list, target: str):
tgt_idx = feat_cols.index(target)
t_idx_all = valid_t_indices(observed, SEQ_LEN, PRED_LEN)
if len(t_idx_all) < 100:
raise RuntimeError(f"Not enough valid windows for {target} on this day.")
split = int(0.9 * len(t_idx_all))
t_tr, t_va = t_idx_all[:split], t_idx_all[split:]
ds_tr = TSWindowDataset(values_norm, t_tr, SEQ_LEN, PRED_LEN, tgt_idx)
ds_va = TSWindowDataset(values_norm, t_va, SEQ_LEN, PRED_LEN, tgt_idx)
dl_tr = DataLoader(ds_tr, batch_size=BATCH, shuffle=True, num_workers=0, pin_memory=True)
dl_va = DataLoader(ds_va, batch_size=BATCH, shuffle=False, num_workers=0, pin_memory=True)
return dl_tr, dl_va, tgt_idx
def eval_val_loss(model: nn.Module, dl_va: DataLoader) -> float:
crit = nn.MSELoss()
was_training = model.training # remember mode
model.eval()
with torch.no_grad():
tot = 0.0; n = 0
for xb, yb in dl_va:
xb = xb.to(DEVICE); yb = yb.to(DEVICE)
pred = model(xb)
tot += crit(pred, yb).item() * xb.size(0); n += xb.size(0)
if was_training:
model.train(True) # restore mode
return tot / max(1, n)
def train_one_day(model: nn.Module, dl_tr: DataLoader, dl_va: DataLoader, epochs: int) -> float:
opt = torch.optim.Adam(model.parameters(), lr=LR)
crit = nn.MSELoss()
best_va = float("inf")
for _ in range(epochs):
model.train(True)
for xb, yb in dl_tr:
xb = xb.to(DEVICE); yb = yb.to(DEVICE)
opt.zero_grad()
pred = model(xb)
loss = crit(pred, yb)
loss.backward()
opt.step()
va = eval_val_loss(model, dl_va)
best_va = min(best_va, va)
return best_va
def rolling_forecast_day(model: nn.Module, values_norm: np.ndarray, observed: np.ndarray,
feat_cols: list, target: str, mu: np.ndarray, sigma: np.ndarray) -> np.ndarray:
tgt_idx = feat_cols.index(target)
N = values_norm.shape[0]
merged = np.full(N, np.nan, dtype=np.float32)
counts = np.zeros(N, dtype=np.float32)
t_idx_all = valid_t_indices(observed, SEQ_LEN, PRED_LEN)
model.eval()
with torch.no_grad():
for t in t_idx_all:
x = torch.from_numpy(values_norm[t-SEQ_LEN:t, :]).unsqueeze(0).to(DEVICE)
yhat = model(x).squeeze(0).cpu().numpy() # normalized
yhat_dn = yhat * sigma[tgt_idx] + mu[tgt_idx]
for j, v in enumerate(yhat_dn):
i = t + j
if i < N:
if np.isnan(merged[i]): merged[i] = 0.0
merged[i] += v; counts[i] += 1.0
with np.errstate(invalid="ignore", divide="ignore"):
merged = np.divide(merged, counts, out=merged, where=counts > 0)
return merged
# ----------------------------
# ZERO-COPY: Feature selection (mask-on-the-fly)
# ----------------------------
def eval_val_loss_masked(model: nn.Module, dl_va: DataLoader, col_to_zero: Optional[int]) -> float:
crit = nn.MSELoss()
was_training = model.training
model.eval()
tot = 0.0; n = 0
with torch.no_grad():
for xb, yb in dl_va:
if col_to_zero is not None:
xb = xb.clone()
xb[:, :, col_to_zero] = 0.0
xb = xb.to(DEVICE); yb = yb.to(DEVICE)
pred = model(xb)
tot += crit(pred, yb).item() * xb.size(0); n += xb.size(0)
if was_training:
model.train(True)
return tot / max(1, n)
def feature_audit_and_select(day_df: pd.DataFrame, target: str, base_cols: List[str]) -> Tuple[List[str], Dict[str, float]]:
"""
Memory-safe audit:
- Train once on AUDIT_DAY (fast)
- For each feature, evaluate validation while zeroing that column on-the-fly
(no full matrix copies)
Returns (kept_features, deltas) where deltas[c] = (masked - base)/base.
"""
print(f"[{AUDIT_DAY}] auditing features for target={target}...")
values_norm, mu, sigma, observed, feat_cols = prepare_day_matrix(day_df, feat_cols_override=base_cols)
dl_tr, dl_va, _ = build_dataloaders(values_norm, observed, feat_cols, target)
# Baseline model
model = Seq2PointLSTM(input_size=len(feat_cols), hidden=128, layers=1, pred_len=PRED_LEN).to(DEVICE)
base_va = train_one_day(model, dl_tr, dl_va, epochs=FS_EPOCHS)
print(f"[{AUDIT_DAY}] baseline val MSE (audit) ~ {base_va:.6f}")
deltas: Dict[str, float] = {}
for i, col in enumerate(feat_cols):
if col == target:
deltas[col] = 0.0
continue
va_mask = eval_val_loss_masked(model, dl_va, i)
delta = (va_mask - base_va) / max(1e-12, base_va)
deltas[col] = float(delta)
harmful = [c for c, d in deltas.items() if d <= -FS_IMPROVE_THRESH and c != target]
ignored = [c for c, d in deltas.items() if abs(d) < FS_NOISE_BAND and c != target]
if harmful:
print(f"[{AUDIT_DAY}] dropping harmful features (improve ≥{FS_IMPROVE_THRESH*100:.1f}%): {harmful}")
if ignored:
print(f"[{AUDIT_DAY}] (near-zero impact features): {ignored}")
kept = [c for c in feat_cols if (c == target or c not in harmful)]
print(f"[{AUDIT_DAY}] kept {len(kept)}/{len(feat_cols)} features")
top_help = sorted([(c, d) for c, d in deltas.items() if d < 0], key=lambda x: x[1])[:8]
top_hurt = sorted([(c, d) for c, d in deltas.items() if d > 0], key=lambda x: x[1], reverse=True)[:8]
def fmt(entries): return ", ".join([f"{c}:{d*100:+.1f}%" for c,d in entries]) if entries else "-"
print(f"[{AUDIT_DAY}] best removals (↓val): {fmt(top_help)}")
print(f"[{AUDIT_DAY}] worst removals (↑val): {fmt(top_hurt)}")
return kept, deltas
# ----------------------------
# Orchestration
# ----------------------------
def run_pipeline_for_target(target: str, feat_cols: List[str], run_tag: str, day_tables: Dict[str, pd.DataFrame]) -> Dict[str, float]:
"""
Train across days (inheriting weights). Save plots & underpred CSVs.
Return dict of per-day best validation losses.
"""
print(f"\n=== [{run_tag}] Target: {target} with {len(feat_cols)} features ===")
run_dir = ensure_dir(RESULT_DIR / run_tag)
val_losses: Dict[str, float] = {}
model = None
for k, day in enumerate(DATE_STRS):
print(f"[{day}] preparing data...")
df_day = day_tables[day]
values_norm, mu, sigma, observed, used_cols = prepare_day_matrix(df_day, feat_cols_override=feat_cols)
input_size = values_norm.shape[1]
if model is None:
model = Seq2PointLSTM(input_size=input_size, hidden=128, layers=1, pred_len=PRED_LEN).to(DEVICE)
dl_tr, dl_va, _ = build_dataloaders(values_norm, observed, used_cols, target)
epochs = EPOCHS_FIRST if k == 0 else EPOCHS_NEXT
print(f"[{day}] training ({epochs} epochs)...")
va = train_one_day(model, dl_tr, dl_va, epochs)
val_losses[day] = va
print(f"[{day}] best val MSE ~ {va:.6f}")
print(f"[{day}] forecasting...")
pred = rolling_forecast_day(model, values_norm, observed, used_cols, target, mu, sigma)
actual = df_day[target].astype("float32").to_numpy()
# Under-prediction report
csv_path = run_dir / f"{target}_underpred_{day}.csv"
metrics = report_underpredictions(
timestamps=df_day["timestamp"],
actual=actual,
pred=pred,
out_csv_path=csv_path
)
print(
f"[{day}] {target} under-predictions: "
f"{metrics['n_under']}/{metrics['n_pred_points']} "
f"({metrics['under_rate']*100:.1f}%) | "
f"MAE_under={metrics['mae_under']:.3f}, "
f"sum_under={metrics['sum_under']:.3f}, "
f"max_under={metrics['max_under']:.3f}, "
f"p95_under={metrics['p95_under']:.3f}, "
f"runs={metrics['n_runs']}, avg_run={metrics['avg_run']:.2f} min, "
f"max_run={metrics['max_run']} min"
)
print("saved under-prediction CSV →", csv_path)
# Plot with under-prediction highlighting
plt.figure(figsize=(12,4))
plt.plot(actual, label=f"actual {target}", lw=1)
if np.isfinite(pred).any():
plt.plot(pred, label=f"forecast {target}", lw=2)
finite = np.isfinite(actual) & np.isfinite(pred)
under = finite & (pred < actual)
if under.any():
x = np.arange(len(actual))
pred_f = np.where(np.isfinite(pred), pred, actual)
plt.fill_between(x, pred_f, actual, where=under, alpha=0.15, color="red", label="under-predicted")
plt.title(f"{target} – {PRED_LEN}-min horizon (LSTM, day {day}) [{run_tag}]")
plt.xlabel("minutes (index within day)")
plt.ylabel(target); plt.grid(True); plt.legend()
outp = run_dir / f"{target}_forecast_{day}_lstm.png"
plt.tight_layout(); plt.savefig(outp); plt.close()
print("saved", outp)
return val_losses
def summarize_comparison(target: str, losses_base: Dict[str, float], losses_pruned: Dict[str, float]):
days = sorted(losses_base.keys())
print(f"\n=== Comparison (target={target}) ===")
for d in days:
b, p = losses_base[d], losses_pruned[d]
delta = (p - b) / max(1e-12, b)
print(f"{d}: base={b:.6f} | pruned={p:.6f} | Δ={delta*100:+.2f}%")
b_mean = np.mean([losses_base[d] for d in days])
p_mean = np.mean([losses_pruned[d] for d in days])
d_mean = (p_mean - b_mean) / max(1e-12, b_mean)
print(f"Avg: base={b_mean:.6f} | pruned={p_mean:.6f} | Δ={d_mean*100:+.2f}%")
def main():
ensure_dir(RESULT_DIR)
# Build per-day feature tables once (independently per day)
day_tables: Dict[str, pd.DataFrame] = {d: build_day_features(d) for d in DATE_STRS}
base_cols = FOCUS_COLS + (["tod_sin","tod_cos"] if ADD_TIME_OF_DAY else [])
for target in TARGETS:
# 1) Baseline full-feature run
losses_base = run_pipeline_for_target(target, feat_cols=base_cols, run_tag="baseline", day_tables=day_tables)
# 2) Audit and select (on AUDIT_DAY) then re-run with pruned set
if ENABLE_FEATURE_SELECTION:
kept_cols = base_cols
kept_map = {}
try:
kept_cols, deltas = feature_audit_and_select(day_tables[AUDIT_DAY], target, base_cols)
kept_map = deltas
except Exception as e:
print(f"[WARN] feature audit failed ({e}); proceeding without pruning.")
losses_pruned = run_pipeline_for_target(target, feat_cols=kept_cols, run_tag="pruned", day_tables=day_tables)
# 3) Summarize before/after
summarize_comparison(target, losses_base, losses_pruned)
# Save audit table
audit_path = ensure_dir(RESULT_DIR / "pruned") / f"{target}_feature_audit_{AUDIT_DAY}.csv"
if kept_map:
pd.DataFrame(
[{"feature": c, "delta_val_pct": 100.0 * kept_map[c], "dropped": (c not in kept_cols)} for c in kept_map]
).sort_values("delta_val_pct").to_csv(audit_path, index=False)
print("saved feature audit →", audit_path)
else:
print("[INFO] Feature selection disabled; only baseline run executed.")
if __name__ == "__main__":
main()
+1 -1
View File
@@ -278,7 +278,7 @@ check_install_usrp_uhd_driver(){
x=$((x + 1))
done
$SUDO apt-get update
$SUDO apt-get -y install python-tk $boost_libs_ubuntu libusb-1.0-0-dev
$SUDO apt-get -y install python3-tk $boost_libs_ubuntu libusb-1.0-0-dev
case "$(get_distribution_release)" in
"ubuntu20.04" | "ubuntu22.04")
$SUDO apt-get -y install libuhd-dev libuhd4.5.0 uhd-host
+2 -2
View File
@@ -83,8 +83,8 @@
#define MAX_NUM_SLICES 1024
// RLC Entity
#define RLC_TX_MAXSIZE 10000000
#define RLC_RX_MAXSIZE 10000000
#define RLC_TX_MAXSIZE 100000000
#define RLC_RX_MAXSIZE 100000000
#define SEND_MRW_ON 240
#define MAX_ANT 8
// CBA constant
+363
View File
@@ -0,0 +1,363 @@
# O-RAN Compliant Antenna Muting / Energy Saving Module
## Overview
This module implements O-RAN WG4 compliant MIMO antenna muting for energy saving.
It follows the `MPLANE-TRX-CONTROL` feature defined in `o-ran-uplane-conf.yang`.
## Architecture
```
┌─────────────────────────────────────────────────────────────────────────────┐
│ Control Interfaces │
│ │
│ ┌───────────────────────────┐ ┌─────────────────┐ ┌──────────────────┐ │
│ │ O-RAN M-Plane NETCONF │ │ Test Socket │ │ Future: rApp/A1 │ │
│ │ (Production/O-RAN) │ │ (Development) │ │ (Policy-based) │ │
│ │ │ │ │ │ │ │
│ │ Uses real NETCONF RPC: │ │ Simple TCP │ │ │ │
│ │ • edit-config │ │ socket on │ │ │ │
│ │ • validate │ │ port 9999 │ │ │ │
│ │ • commit │ │ │ │ │ │
│ └───────────┬───────────────┘ └────────┬────────┘ └────────┬─────────┘ │
│ │ │ │ │
│ └───────────────────────────┼────────────────────┘ │
│ ▼ │
│ ┌────────────────────────────────────────────────────────────────────────┐ │
│ │ nr_antenna_state_t (Antenna State Manager) │ │
│ │ - tx_antenna_bitmask: O-RAN compliant antenna mask │ │
│ │ - rx_antenna_bitmask: O-RAN compliant antenna mask │ │
│ │ - Statistics for energy saving monitoring │ │
│ └────────────────────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌────────────────────────────────────────────────────────────────────────┐ │
│ │ PHY Layer Integration │ │
│ │ - nr_beam_precoding_with_muting(): Blanks muted antennas │ │
│ │ - Beamforming skipped for muted antennas │ │
│ │ - Output zeroed = transmission blank (O-RAN compliant) │ │
│ └────────────────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────────────┘
```
## O-RAN Compliance
This implementation follows O-RAN Alliance specifications:
| O-RAN Element | Implementation |
|---------------|----------------|
| WG4 `o-ran-uplane-conf.yang` | `tx-array-antenna-mask-config`, `rx-array-antenna-mask-config` |
| `antenna-bitmask` parameter | `nr_antenna_state_t.tx_antenna_bitmask` |
| `MPLANE-TRX-CONTROL` feature | `mplane_set_tx_antenna_mask()`, `mplane_set_rx_antenna_mask()` |
| NETCONF edit-config/validate/commit | `radio/fhi_72/mplane/antenna-control-mplane.c` |
| Transmission blanking | Zeroing `txdataF_BF` for muted antennas |
### YANG Model Paths Used
```yang
/o-ran-uplane-conf:user-plane-configuration/tx-array-antenna-mask-config
└── array-name # Reference to tx-arrays list
└── antenna-bitmask # Binary bitmask (bit N=1 means antenna N active)
/o-ran-uplane-conf:user-plane-configuration/rx-array-antenna-mask-config
└── array-name # Reference to rx-arrays list
└── antenna-bitmask # Binary bitmask
```
## Files
### Core State Management
- `openair1/PHY/NR_TRANSPORT/nr_antenna_state.h` - Antenna state API
- `openair1/PHY/NR_TRANSPORT/nr_antenna_state.c` - Implementation
### O-RAN M-Plane NETCONF (Production)
- `radio/fhi_72/mplane/antenna-control-mplane.h` - O-RAN compliant NETCONF API
- `radio/fhi_72/mplane/antenna-control-mplane.c` - NETCONF implementation using libnetconf2
- Uses existing M-Plane infrastructure: `config-mplane.c`, `rpc-send-recv.c`
### Control Interface
- `openair1/PHY/NR_TRANSPORT/nr_antenna_control.h` - Unified control interface
- `openair1/PHY/NR_TRANSPORT/nr_antenna_control.c` - Backend-agnostic implementation
### PHY Integration
- `openair1/PHY/MODULATION/beamforming.c` - Modified with `nr_beam_precoding_with_muting()`
## Usage
### Production Mode: O-RAN M-Plane NETCONF
For O-RAN compliant deployments, use the NETCONF interface:
```c
#include "radio/fhi_72/mplane/antenna-control-mplane.h"
#include "radio/fhi_72/mplane/ru-mplane-api.h"
// After establishing M-Plane connection to RU...
extern ru_session_t *ru_session; // From M-Plane init
// Check if RU supports antenna control
if (mplane_supports_trx_control(ru_session)) {
// Set TX antenna mask (0x0F = all 4 antennas active)
antenna_ctrl_result_t result = mplane_set_tx_antenna_mask(
ru_session,
"tx-array-0", // Array name from RU capabilities
0x0F, // Bitmask: bit N=1 means antenna N active
4 // Number of antennas
);
if (result != ANTENNA_CTRL_SUCCESS) {
LOG_E(PHY, "Failed to set TX mask: %s\n", antenna_ctrl_result_str(result));
}
}
```
#### Integrated with PHY Layer
```c
#include "PHY/NR_TRANSPORT/nr_antenna_state.h"
#include "PHY/NR_TRANSPORT/nr_antenna_control.h"
// Initialize antenna state
nr_init_antenna_state(&gNB->antenna_state, 4, 4, "tx-array-0", "rx-array-0");
// Connect to M-Plane backend
nr_antenna_control_init_mplane(&gNB->antenna_state, ru_session);
// Changes via nr_set_tx_antenna_bitmask() will be synchronized to RU
nr_set_tx_antenna_bitmask(&gNB->antenna_state, 0x03); // Enable only antennas 0,1
nr_antenna_control_sync_to_ru(&gNB->antenna_state); // Push to RU via NETCONF
```
### Development Mode: Test Socket Interface
For testing without NETCONF infrastructure:
```c
#include "PHY/NR_TRANSPORT/nr_antenna_state.h"
#include "PHY/NR_TRANSPORT/nr_antenna_control.h"
// Initialize with 4 TX and 4 RX antennas
nr_init_antenna_state(&gNB->antenna_state, 4, 4, "tx-array-0", "rx-array-0");
// Start test socket server (NOT O-RAN compliant - logs warning)
nr_antenna_control_start_test_server(&gNB->antenna_state, 9999);
```
**Warning**: The test socket interface is for development only and is NOT O-RAN compliant.
### Control Antennas via Test Interface
Connect with netcat:
```bash
nc localhost 9999
```
Commands:
```
> HELP # Show all commands
> STATUS # Show current antenna state
> MUTE TX 2 # Mute TX antenna 2
> MUTE TX 3 # Mute TX antenna 3
> SET TX MASK 03 # Set TX mask to 0x03 (antennas 0,1 active)
> SYNC # Sync to RU (M-Plane backend only)
> STATS # Show energy saving statistics
```
### Use in Beamforming
```c
// Original (all antennas active):
nr_beam_precoding(txdataF, txdataF_BF, fp, beam_weights, slot, symbol, aa, nb_ap, offset);
// With antenna muting support:
nr_beam_precoding_with_muting(txdataF, txdataF_BF, fp, beam_weights,
slot, symbol, aa, nb_ap, offset,
&gNB->antenna_state);
```
## Building with M-Plane Support
To build with O-RAN M-Plane NETCONF support:
```bash
cd openairinterface5g/cmake_targets
./build_oai --ninja --gNB --nrUE -w ORAN_FH -t MPLANE
# Or manually:
cmake .. -DENABLE_ORAN_FH=ON -DORAN_FH_VARIANT=MPLANE
make -j$(nproc)
```
Dependencies for M-Plane:
- libnetconf2 >= 2.1.25
- libyang >= 2.1.4
- libssh
## Testing with rfsimulator
### Prerequisites
1. Build OAI:
```bash
cd openairinterface5g/cmake_targets
./build_oai --ninja --gNB --nrUE -w SIMU
```
2. Install Open5GS:
```bash
sudo add-apt-repository ppa:open5gs/latest
sudo apt update
sudo apt install open5gs
```
3. Add subscriber to Open5GS (using mongosh):
```bash
mongosh open5gs --eval 'db.subscribers.insertOne({
imsi: "999700000000001",
security: { k: "fec86ba6eb707ed08905757b1bb44b8f", amf: "8000", opc: "C42449363BBAD02B66D16BC975D77CC1" },
slice: [{ sst: 1, default_indicator: true, session: [{ name: "internet", type: 3 }] }]
})'
```
### Quick Test (Antenna Control Only)
The easiest way to verify the implementation is to start the gNB and test the control interface:
```bash
# Terminal 1: Start gNB
cd cmake_targets/ran_build/build
sudo ./nr-softmodem -O ../../../targets/PROJECTS/GENERIC-NR-5GC/CONF/gnb.sa.band78.fr1.106PRB.pci0.rfsim.conf --rfsim
# Terminal 2: Test antenna control
echo "STATUS" | nc -w 2 localhost 9999
echo "MUTE TX 0" | nc -w 2 localhost 9999
echo "STATUS" | nc -w 2 localhost 9999
echo "STATS" | nc -w 2 localhost 9999
```
### Full Test with Open5GS
Terminal 1 - Core Network (should already be running):
```bash
# Verify Open5GS is running
systemctl status open5gs-amfd open5gs-upfd open5gs-smfd
```
Terminal 2 - gNB (with Open5GS-compatible config):
```bash
cd cmake_targets/ran_build/build
sudo ./nr-softmodem -O ../../../targets/PROJECTS/GENERIC-NR-5GC/CONF/gnb.sa.band78.2x2.rfsim.open5gs.conf --rfsim
```
Terminal 3 - UE:
```bash
cd cmake_targets/ran_build/build
sudo ./nr-uesoftmodem -r 106 --numerology 1 --band 78 -C 3319680000 \
--rfsim -O ../../../targets/PROJECTS/GENERIC-NR-5GC/CONF/ue.open5gs.conf
```
Terminal 4 - Antenna Control:
```bash
nc localhost 9999
> STATUS
> MUTE TX 1 # Mute antenna 1
> STATUS
> STATS
```
Terminal 5 - iperf (after UE attaches):
```bash
# Server on the UPF
iperf3 -s -B 10.45.0.1
# Client from UE (or use tun interface)
ping 10.45.0.1
iperf3 -c 10.45.0.1 -t 30
```
### Using the Test Script
A convenience script is provided:
```bash
./targets/PROJECTS/GENERIC-NR-5GC/CONF/run_antenna_muting_test.sh gnb # Start gNB only
./targets/PROJECTS/GENERIC-NR-5GC/CONF/run_antenna_muting_test.sh mute # Test muting
./targets/PROJECTS/GENERIC-NR-5GC/CONF/run_antenna_muting_test.sh status # Check status
./targets/PROJECTS/GENERIC-NR-5GC/CONF/run_antenna_muting_test.sh --help # Show all options
```
### Observing MIMO Layer Reduction
With 4x4 MIMO:
- All antennas active: Up to 4 layers possible
- 2 antennas muted: Max 2 layers
- 3 antennas muted: Max 1 layer
Throughput should decrease proportionally as antennas are muted.
## API Reference
### O-RAN M-Plane NETCONF API
```c
// Set TX antenna mask via NETCONF
antenna_ctrl_result_t mplane_set_tx_antenna_mask(
ru_session_t *ru_session, // Active NETCONF session
const char *array_name, // O-RAN TX array name
uint8_t antenna_bitmask, // Bitmask (bit N=1 -> antenna N active)
uint8_t num_antennas // Number of antennas in array
);
// Set RX antenna mask via NETCONF
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
);
// Check if RU supports MPLANE-TRX-CONTROL feature
bool mplane_supports_trx_control(ru_session_t *ru_session);
// Result codes
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;
```
### Unified Control Interface
```c
// Initialize with O-RAN M-Plane NETCONF backend (production)
bool nr_antenna_control_init_mplane(nr_antenna_state_t *ant_state, void *ru_session);
// Start test socket server (development - NOT O-RAN compliant)
bool nr_antenna_control_start_test_server(nr_antenna_state_t *ant_state, int port);
// Sync local state to RU (M-Plane backend only)
bool nr_antenna_control_sync_to_ru(nr_antenna_state_t *ant_state);
// Get current backend type
nr_antenna_ctrl_backend_t nr_antenna_control_get_backend(void);
```
## Future Work
1. ~~**O1 NETCONF Integration**: Integrate with `radio/fhi_72/mplane/` for production O-RAN compliance~~ ✅ **DONE**
2. **NETCONF Notifications**: Subscribe to `mplane-trx-control-ant-mask-update` notifications
3. **rApp Development**: Create Non-RT RIC rApp for policy-based antenna control
4. **A1 Policy Interface**: Enable policy-based control from SMO
5. **Power Measurement**: Add actual power consumption metrics (requires hardware)
## References
- O-RAN WG4 M-Plane Specification (O-RAN.WG4.MP.0-v10.00)
- `o-ran-uplane-conf.yang` - YANG model for U-plane configuration
- `o-ran-wg4-features.yang` - Feature definitions including `MPLANE-TRX-CONTROL`
- `radio/fhi_72/mplane/yang/models/` - O-RAN YANG models in this repository
+6 -42
View File
@@ -1,40 +1,11 @@
#/*
# * 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
# */
#---------------------------------------------------------------------
#
# Dockerfile for the Open-Air-Interface BUILD service
# Valid for Ubuntu 24.04
#
#---------------------------------------------------------------------
FROM ubuntu:noble AS ran-base
ARG NEEDED_GIT_PROXY
ARG TARGETARCH
ENV DEBIAN_FRONTEND=noninteractive
ENV TZ=Europe/Paris
ENV TZ=Asia/Tokyo
ENV BUILD_UHD_FROM_SOURCE=True
ENV UHD_VERSION=4.8.0.0
ENV UHD_VERSION=4.4.0.0
#install developers pkg/repo
RUN apt-get update && \
apt-get upgrade --yes && \
apt-get install --yes \
@@ -46,28 +17,20 @@ RUN apt-get update && \
xxd \
libpcre2-dev \
python3-dev \
python3-pip \
bison \
flex \
m4 \
# python3-pip for conf template generation
python3-pip \
wget && \
# --break-system-packages allows externally managed environment in pip
pip3 install --ignore-installed pyyaml --break-system-packages
# Add "Tini - A tiny but valid init for containers", https://github.com/krallin/tini
# it will be copied into target containers, to print exit numbers and handle signals properly
ENV TINI_VERSION=v0.19.0
RUN wget https://github.com/krallin/tini/releases/download/${TINI_VERSION}/tini-${TARGETARCH} -O /tini && chmod +x /tini
RUN wget https://github.com/krallin/tini/releases/download/${TINI_VERSION}/tini-${TARGETARCH} -O /tini
RUN chmod +x /tini
ENV YQ_VERSION=v4.2.0
RUN wget https://github.com/mikefarah/yq/releases/download/${YQ_VERSION}/yq_linux_${TARGETARCH} -O /usr/local/bin/yq &&\
chmod +x /usr/local/bin/yq
# In some network environments, GIT proxy is required
RUN /bin/bash -c "if [[ -v NEEDED_GIT_PROXY ]]; then git config --global http.proxy $NEEDED_GIT_PROXY; fi"
# Copying only the needed files to create ran-base
WORKDIR /oai-ran/cmake_targets/tools
COPY cmake_targets/tools/build_helper \
cmake_targets/tools/uhd-4.x-tdd-patch.diff \
@@ -84,3 +47,4 @@ RUN /bin/sh oaienv && \
cd cmake_targets && \
mkdir -p log && \
./build_oai -I -w USRP --install-optional-packages
+56
View File
@@ -0,0 +1,56 @@
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 -c --ninja \
--gNB --RU --nrUE -w SIMU\
--build-lib "telnetsrv" \
-w USRP -t Ethernet \
--build-e2 --cmake-opt -DKPM_VERSION=KPM_V3_00 --cmake-opt -DE2AP_VERSION=E2AP_V3 \
--noavx512 \
--build-tool-opt -k10 \
--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; \
# Try to install sipp from apt; if unavailable, build from source
if apt-cache show sipp >/dev/null 2>&1; then \
DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends sipp; \
else \
echo "sipp not in apt, building from source..."; \
DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
build-essential cmake git libncurses5-dev libpcap-dev libssl-dev libpcre3-dev; \
git clone --depth=1 https://github.com/SIPp/sipp.git /tmp/sipp; \
cd /tmp/sipp; cmake -DENABLE_SSL=1 -DENABLE_PCAP=1 -DCMAKE_BUILD_TYPE=Release .; \
make -j"$(nproc)"; make install; strip /usr/local/bin/sipp || true; \
rm -rf /tmp/sipp; \
fi; \
rm -rf /var/lib/apt/lists/*
# Traffic runner
COPY tools/scripts/traffic_runner.sh /usr/local/bin/traffic_runner
RUN chmod +x /usr/local/bin/traffic_runner
COPY flexric/etc/flexric /usr/local/etc/flexric/
COPY flexric/lib/flexric /usr/local/lib/flexric/
RUN echo "/usr/local/lib/flexric" > /etc/ld.so.conf.d/flexric.conf && ldconfig
ENTRYPOINT ["/bin/bash"]
+56
View File
@@ -0,0 +1,56 @@
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 -c --ninja \
--gNB --RU --nrUE -w SIMU\
--build-lib "telnetsrv enbscope uescope nrscope" \
-w USRP -t Ethernet \
--build-e2 --cmake-opt -DKPM_VERSION=KPM_V3_00 --cmake-opt -DE2AP_VERSION=E2AP_V3 \
--noavx512 \
--build-tool-opt -k10 \
--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
COPY flexric/etc/flexric /usr/local/etc/flexric/
COPY flexric/lib/flexric /usr/local/lib/flexric/
RUN echo "/usr/local/lib/flexric" > /etc/ld.so.conf.d/flexric.conf && ldconfig
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; \
# Try to install sipp from apt; if unavailable, build from source
if apt-cache show sipp >/dev/null 2>&1; then \
DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends sipp; \
else \
echo "sipp not in apt, building from source..."; \
DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
build-essential cmake git libncurses5-dev libpcap-dev libssl-dev libpcre3-dev; \
git clone --depth=1 https://github.com/SIPp/sipp.git /tmp/sipp; \
cd /tmp/sipp; cmake -DENABLE_SSL=1 -DENABLE_PCAP=1 -DCMAKE_BUILD_TYPE=Release .; \
make -j"$(nproc)"; make install; strip /usr/local/bin/sipp || true; \
rm -rf /tmp/sipp; \
fi; \
rm -rf /var/lib/apt/lists/*
# Traffic runner
COPY tools/scripts/traffic_runner.sh /usr/local/bin/traffic_runner
RUN chmod +x /usr/local/bin/traffic_runner
ENTRYPOINT ["/bin/bash"]
+62
View File
@@ -0,0 +1,62 @@
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 -c --ninja \
--gNB --RU --nrUE -w SIMU \
--build-lib "telnetsrv" \
-w USRP -t Ethernet \
--build-e2 --cmake-opt -DKPM_VERSION=KPM_V3_00 --cmake-opt -DE2AP_VERSION=E2AP_V3 \
--noavx512 \
--build-tool-opt -k10 \
--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; \
# Try to install sipp from apt; if unavailable, build from source
if apt-cache show sipp >/dev/null 2>&1; then \
DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends sipp; \
else \
echo "sipp not in apt, building from source..."; \
DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
build-essential cmake git libncurses5-dev libpcap-dev libssl-dev libpcre3-dev; \
git clone --depth=1 https://github.com/SIPp/sipp.git /tmp/sipp; \
cd /tmp/sipp; cmake -DENABLE_SSL=1 -DENABLE_PCAP=1 -DCMAKE_BUILD_TYPE=Release .; \
make -j"$(nproc)"; make install; strip /usr/local/bin/sipp || true; \
rm -rf /tmp/sipp; \
fi; \
rm -rf /var/lib/apt/lists/*
# Traffic runner
COPY tools/scripts/traffic_runner.sh /usr/local/bin/traffic_runner
RUN chmod +x /usr/local/bin/traffic_runner
# 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"]
+16
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,20 @@ void init_eNB_afterRU(void)
*/
//init_precoding_weights(RC.gNB[inst]);
init_gNB_Tpool(inst);
// Start O-RAN antenna control test server (port 9999)
// Allows runtime antenna muting for energy saving testing
// Connect with: nc localhost 9999
if (nr_antenna_control_start(&gNB->antenna_state, 9999)) {
LOG_I(PHY, "[ANTENNA_CTRL] Test control server started on port 9999\n");
}
// Connect MAC's antenna_state pointer to PHY's antenna_state
// This allows the scheduler to adapt MIMO layers based on active antennas
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");
}
}
}
+4
View File
@@ -158,6 +158,10 @@ void phy_init_nr_gNB(PHY_VARS_gNB *gNB)
init_DLSCH_struct(gNB);
/// O-RAN compliant antenna state initialization for energy saving
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));
+55
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,57 @@ void nr_beam_precoding(c16_t **txdataF,
15);
}
}
/**
* @brief NR beam precoding with O-RAN compliant antenna muting support
*
* This function extends nr_beam_precoding to support antenna muting for
* energy saving per O-RAN WG4 MPLANE-TRX-CONTROL specification.
*
* When an antenna is muted (via nr_antenna_state), the output buffer
* for that antenna is zeroed, effectively blanking transmission.
*
* @param txdataF Input frequency-domain data per antenna port
* @param txdataF_BF Output beamformed data per physical antenna
* @param frame_parms Frame parameters
* @param beam_weights Beamforming weights
* @param slot Current slot number
* @param symbol Current symbol number
* @param aa Physical antenna index
* @param nb_antenna_ports Number of antenna ports
* @param offset Frequency offset
* @param ant_state Pointer to antenna state (NULL = all antennas 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)
{
// 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);
}
}
+28
View File
@@ -103,6 +103,34 @@ 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,
@@ -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-mplane.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,178 @@
/*
* 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.h
* \brief O-RAN WG4 compliant antenna control interface
* \author OpenAirInterface
* \date 2025
* \version 2.0
*
* This module provides antenna control for MIMO muting / energy saving.
*
* Two modes of operation are supported:
*
* 1. O-RAN M-PLANE NETCONF (Production/O-RAN Compliant):
* When linked with the M-Plane library (radio/fhi_72/mplane/), uses
* real NETCONF sessions to control antenna masks via O-RAN YANG models:
* - o-ran-uplane-conf.yang tx-array-antenna-mask-config
* - o-ran-uplane-conf.yang rx-array-antenna-mask-config
* - Requires MPLANE-TRX-CONTROL feature support on RU
*
* 2. Test Socket Interface (Development/Testing):
* A simple TCP socket interface for testing without NETCONF infrastructure.
* NOT O-RAN compliant, but useful for development and simulation.
* Enable with nr_antenna_control_start_test_server().
*
* For O-RAN compliant deployments, use the M-Plane interface directly
* via radio/fhi_72/mplane/antenna-control-mplane.h
*/
#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 O-RAN M-Plane session handle (opaque)
*
* This is a forward declaration for ru_session_t from the M-Plane library.
* When the M-Plane library is linked, this allows direct NETCONF control.
*/
typedef struct ru_session ru_session_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,396 @@
/*
* 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_state.c
* \brief O-RAN compliant antenna state management implementation
* \author OpenAirInterface
* \date 2025
* \version 1.0
* \note Implements O-RAN WG4 MPLANE-TRX-CONTROL feature
*/
#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,271 @@
/*
* 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_state.h
* \brief O-RAN compliant antenna state management for MIMO muting / energy saving
* \author OpenAirInterface
* \date 2025
* \version 1.0
* \note Implements O-RAN WG4 MPLANE-TRX-CONTROL feature (o-ran-uplane-conf.yang)
* - tx-array-antenna-mask-config
* - rx-array-antenna-mask-config
* - mplane-trx-control-ant-mask-update notification
*/
#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 */
+5
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,10 @@ typedef struct PHY_VARS_gNB_s {
bool enable_analog_das;
/// O-RAN compliant antenna state for energy saving / MIMO muting
/// Implements WG4 MPLANE-TRX-CONTROL feature
nr_antenna_state_t antenna_state;
time_stats_t l1_tx_proc;
time_stats_t l1_rx_proc;
+101 -34
View File
@@ -34,6 +34,7 @@
#include "sched_nr.h"
#include "PHY/MODULATION/modulation_common.h"
#include "PHY/MODULATION/nr_modulation.h"
#include "PHY/NR_TRANSPORT/nr_antenna_state.h"
#include "common/utils/LOG/log.h"
#include "common/utils/system.h"
@@ -58,7 +59,7 @@ void nr_feptx0(RU_t *ru, int tti_tx, int first_symbol, int num_symbols, int aa)
if (aa == 0 && first_symbol == 0)
start_meas(&ru->ofdm_mod_stats);
slot_offset = get_samples_slot_timestamp(fp, slot);
slot_offset = fp->get_samples_slot_timestamp(slot, fp, 0);
slot_offsetF = first_symbol * fp->ofdm_symbol_size;
int abs_first_symbol = slot * fp->symbols_per_slot;
@@ -156,7 +157,7 @@ void nr_feptx_ofdm(RU_t *ru,int frame_tx,int tti_tx) {
int slot_sizeF = (fp->ofdm_symbol_size)*
((cyclic_prefix_type == 1) ? 12 : 14);
int slot = tti_tx;
int *txdata = &ru->common.txdata[aa][get_samples_slot_timestamp(fp, slot)];
int *txdata = &ru->common.txdata[aa][fp->get_samples_slot_timestamp(slot,fp,0)];
if (nr_slot_select(cfg,frame_tx,slot) == NR_UPLINK_SLOT) return;
@@ -169,13 +170,10 @@ void nr_feptx_ofdm(RU_t *ru,int frame_tx,int tti_tx) {
VCD_SIGNAL_DUMPER_DUMP_FUNCTION_BY_NAME(VCD_SIGNAL_DUMPER_FUNCTIONS_PHY_PROCEDURES_RU_FEPTX_OFDM , 0 );
LOG_D(PHY,
"feptx_ofdm (TXPATH): frame %d, slot %d: txp (time %p) %d dB, txp (freq) %d dB\n",
frame_tx,
slot,
txdata,
dB_fixed(signal_energy((int32_t *)txdata, get_samples_per_slot(slot, fp))),
dB_fixed(signal_energy_nodc((c16_t *)ru->common.txdataF_BF[aa], 2 * slot_sizeF)));
LOG_D(PHY,"feptx_ofdm (TXPATH): frame %d, slot %d: txp (time %p) %d dB, txp (freq) %d dB\n",
frame_tx,slot,txdata,dB_fixed(signal_energy((int32_t*)txdata,fp->get_samples_per_slot(
slot,fp))),dB_fixed(signal_energy_nodc((c16_t*)ru->common.txdataF_BF[aa],2*slot_sizeF)));
}
void nr_feptx_prec(RU_t *ru, int frame_tx, int slot_tx)
@@ -199,15 +197,42 @@ void nr_feptx_prec(RU_t *ru, int frame_tx, int slot_tx)
if (nr_slot_select(cfg,frame_tx,slot_tx) == NR_UPLINK_SLOT)
return;
int Ptx = cfg->carrier_config.num_tx_ant.value;
// If there is no digital beamforming we just need to copy the data to RU
// O-RAN compliant antenna muting (MPLANE-TRX-CONTROL)
nr_antenna_state_t *ant_state = &gNB->antenna_state;
// Check if ALL TX antennas are muted (complete transmission stop)
int active_tx_count = nr_get_active_tx_antenna_count(ant_state);
if (ru->config.dbt_config.num_dig_beams == 0 || ru->gNB_list[0]->common_vars.analog_bf) {
for (int b = 0; b < ru->num_beams_period; b++) {
for (int i = 0; i < Ptx; ++i) {
for (int i = 0; i < ru->nb_tx; ++i) {
int tx_idx = i + b * ru->nb_tx;
memcpy((void*)ru->common.txdataF_BF[tx_idx],
(void*)&gNB->common_vars.txdataF[b][i][txdataF_offset],
fp->samples_per_slot_wCP * sizeof(int32_t));
// Check if this TX antenna is muted for energy saving
if (!nr_is_tx_antenna_active(ant_state, i)) {
// Zero the output buffer for muted antenna - transmission blanking
memset((void*)ru->common.txdataF_BF[tx_idx], 0,
fp->samples_per_slot_wCP * sizeof(int32_t));
LOG_D(PHY, "[ANTENNA_MUTE] TX antenna %d muted in slot %d\n", i, slot_tx);
} else {
// Active antenna: copy data
// If antenna 0 is muted but this antenna is active, we need to copy
// SSB/common data from antenna 0's source to ensure SSB is transmitted
// on at least one active antenna (SSB is generated on port 0)
if (i > 0 && !nr_is_tx_antenna_active(ant_state, 0) && active_tx_count > 0) {
// Antenna 0 is muted, copy antenna 0's data to this active antenna
// This ensures SSB is transmitted on at least one antenna
memcpy((void*)ru->common.txdataF_BF[tx_idx],
(void*)&gNB->common_vars.txdataF[b][0][txdataF_offset],
fp->samples_per_slot_wCP * sizeof(int32_t));
LOG_D(PHY, "[ANTENNA_MUTE] Copying SSB from ant0 to active ant%d in slot %d\n", i, slot_tx);
} else {
// Normal copy for active antennas
memcpy((void*)ru->common.txdataF_BF[tx_idx],
(void*)&gNB->common_vars.txdataF[b][i][txdataF_offset],
fp->samples_per_slot_wCP * sizeof(int32_t));
}
}
}
}
} else {
@@ -238,19 +263,44 @@ void nr_feptx(void *arg)
if (tx_idx == 0)
start_meas(&ru->precoding_stats);
if (ru->gNB_list[0]->common_vars.analog_bf) {
memcpy(&ru->common.beam_id[bb][slot * fp->symbols_per_slot],
&ru->gNB_list[0]->common_vars.beam_id[bb][slot * fp->symbols_per_slot],
(fp->symbols_per_slot) * sizeof(int));
}
// O-RAN compliant antenna muting check (MPLANE-TRX-CONTROL)
// Check if this TX antenna is muted for energy saving
PHY_VARS_gNB *gNB = ru->gNB_list[0];
nr_antenna_state_t *ant_state = &gNB->antenna_state;
bool antenna_muted = !nr_is_tx_antenna_active(ant_state, aa);
int active_tx_count = nr_get_active_tx_antenna_count(ant_state);
// 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)
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 (antenna_muted) {
// Zero the output buffer for muted antenna - transmission blanking
memset((void*)&ru->common.txdataF_BF[tx_idx][txdataF_BF_offset], 0,
numSamples * sizeof(int32_t));
LOG_D(PHY, "[ANTENNA_MUTE] TX antenna %d muted, blanking slot %d symbols %d-%d\n",
aa, slot, startSymbol, startSymbol + numSymbols - 1);
} else {
// Normal processing for active antennas
if (ru->gNB_list[0]->common_vars.analog_bf) {
memcpy(&ru->common.beam_id[bb][slot * fp->symbols_per_slot],
&ru->gNB_list[0]->common_vars.beam_id[bb][slot * fp->symbols_per_slot],
(fp->symbols_per_slot) * sizeof(int));
}
// 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 antenna 0 is muted but this antenna is active, copy SSB from antenna 0
// SSB is generated only on antenna port 0
if (aa > 0 && !nr_is_tx_antenna_active(ant_state, 0) && active_tx_count > 0) {
memcpy((void*)&ru->common.txdataF_BF[tx_idx][txdataF_BF_offset],
(void*)&ru->gNB_list[0]->common_vars.txdataF[bb][0][txdataF_offset],
numSamples * sizeof(int32_t));
LOG_D(PHY, "[ANTENNA_MUTE] Copying SSB from ant0 to active ant%d, slot %d\n", aa, slot);
} else {
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 (tx_idx == 0)
@@ -337,13 +387,30 @@ void nr_fep(void* arg)
int idx = aid + beam * ru->nb_rx;
int offset = (slot % RU_RX_SLOT_DEPTH) * fp->symbols_per_slot * fp->ofdm_symbol_size;
for (int l = startSymbol; l <= endSymbol; l++)
nr_slot_fep_ul(fp,
ru->common.rxdata[idx],
&ru->common.rxdataF[idx][offset],
l,
slot,
ru->N_TA_offset);
// O-RAN compliant RX antenna muting check (MPLANE-TRX-CONTROL)
PHY_VARS_gNB *gNB = ru->gNB_list[0];
nr_antenna_state_t *ant_state = &gNB->antenna_state;
bool rx_muted = !nr_is_rx_antenna_active(ant_state, aid);
if (rx_muted) {
// Zero the output buffer for muted RX antenna - reception blanking
int numSymbols = endSymbol - startSymbol + 1;
memset(&ru->common.rxdataF[idx][offset + startSymbol * fp->ofdm_symbol_size], 0,
numSymbols * fp->ofdm_symbol_size * sizeof(c16_t));
LOG_D(PHY, "[ANTENNA_MUTE] RX antenna %d muted, blanking slot %d symbols %d-%d\n",
aid, slot, startSymbol, endSymbol);
} else {
// Normal FFT processing for active RX antennas
for (int l = startSymbol; l <= endSymbol; l++)
nr_slot_fep_ul(fp,
ru->common.rxdata[idx],
&ru->common.rxdataF[idx][offset],
l,
slot,
ru->N_TA_offset);
}
VCD_SIGNAL_DUMPER_DUMP_FUNCTION_BY_NAME(VCD_SIGNAL_DUMPER_FUNCTIONS_PHY_PROCEDURES_RU_FEPRX+aid, 0);
// Task completed in //
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -435,14 +435,14 @@ 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 pm_index = get_pm_index(nr_mac, UE, dl_bwp->dci_format, layers, nr_mac->radio_config.pdsch_AntennaPorts.XP);
int layers = get_dl_nrOfLayers(nr_mac, sched_ctrl, dl_bwp->dci_format);
// If the RI changed between current rtx and a previous transmission
// we need to verify if it is not decreased
// othwise it wouldn't be possible to transmit the same TBS
layers = (layers < new_sched.nrOfLayers) ? layers : new_sched.nrOfLayers;
pm_index = (layers < new_sched.nrOfLayers) ? pm_index : new_sched.pm_index;
// Recalculate PMI with the final layer count to ensure consistency
int pm_index = (layers < new_sched.nrOfLayers) ? get_pm_index(nr_mac, UE, dl_bwp->dci_format, layers, nr_mac->radio_config.pdsch_AntennaPorts.XP) : new_sched.pm_index;
const int coresetid = sched_ctrl->coreset->controlResourceSetId;
const int tda = get_dl_tda(nr_mac, slot);
@@ -720,7 +720,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,
@@ -865,7 +865,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,
@@ -929,6 +929,32 @@ static void nr_dlsch_preprocessor(gNB_MAC_INST *mac, post_process_pdsch_t *pp_pd
NR_ServingCellConfigCommon_t *scc = mac->common_channels[0].ServingCellConfigCommon;
int bw = scc->downlinkConfigCommon->frequencyInfoDL->scs_SpecificCarrierList.list.array[0]->carrierBandwidth;
// O-RAN MPLANE-TRX-CONTROL: Scale available resources based on active antennas
// This simulates MIMO capacity reduction when antennas are muted
if (mac->antenna_state) {
int active_tx = nr_get_active_tx_antenna_count(mac->antenna_state);
int configured_tx = mac->antenna_state->num_tx_antennas;
// If all TX antennas are muted, skip DL user scheduling entirely
// (SSB/SIB1 are still transmitted by PHY but no user data)
if (active_tx == 0) {
static int mute_log_counter = 0;
if (mute_log_counter++ % 10000 == 0)
LOG_W(NR_MAC, "[ANTENNA_MUTE] All TX antennas muted, skipping DL user scheduling\n");
return;
}
if (active_tx < configured_tx && configured_tx > 0) {
// Scale bandwidth proportionally to active antenna ratio
int scaled_bw = (bw * active_tx) / configured_tx;
if (scaled_bw < 5) scaled_bw = 5; // Minimum for SSB
static int log_counter = 0;
if (log_counter++ % 1000 == 0) // Log every 1000 calls to avoid spam
LOG_I(NR_MAC, "[ANTENNA_MUTE] Scaling DL capacity: %d/%d antennas -> %d/%d PRBs\n",
active_tx, configured_tx, scaled_bw, bw);
bw = scaled_bw;
}
}
int num_beams = mac->beam_info.beam_allocation ? mac->beam_info.beams_per_period : 1;
int n_rb_sched[num_beams];
for (int i = 0; i < num_beams; i++)
@@ -1022,7 +1048,22 @@ nfapi_nr_dl_tti_pdsch_pdu_rel15_t *prepare_pdsch_pdu(nfapi_nr_dl_tti_request_pdu
pdsch_pdu->precodingAndBeamforming.num_prgs = 1;
pdsch_pdu->precodingAndBeamforming.prg_size = pdsch_pdu->rbSize;
pdsch_pdu->precodingAndBeamforming.dig_bf_interfaces = 1;
pdsch_pdu->precodingAndBeamforming.prgs_list[0].pm_idx = sched_pdsch->pm_index;
// Defensive check: if pm_index > 0, verify it's in the valid range for nrOfLayers
// to prevent PHY crash from layer/PMI mismatch
int pm_idx = sched_pdsch->pm_index;
if (pm_idx > 0 && mac) {
int layers = sched_pdsch->nrOfLayers;
int prev_layers_size = 0;
for (int i = 1; i < layers; i++)
prev_layers_size += mac->precoding_matrix_size[i - 1];
int max_valid = prev_layers_size + mac->precoding_matrix_size[layers - 1];
if (pm_idx < 1 + prev_layers_size || pm_idx > max_valid) {
LOG_E(NR_MAC, "PM index %d invalid for %d layers (valid range: %d-%d), forcing identity matrix\n",
pm_idx, layers, 1 + prev_layers_size, max_valid);
pm_idx = 0; // Use identity matrix to prevent crash
}
}
pdsch_pdu->precodingAndBeamforming.prgs_list[0].pm_idx = pm_idx;
pdsch_pdu->precodingAndBeamforming.prgs_list[0].dig_bf_interface_list[0].beam_idx = beam_index;
return pdsch_pdu;
}
@@ -53,6 +53,9 @@
#include "SIMULATION/TOOLS/sim.h" // for taus
// Forward declaration for RLF handler defined in mac_rrc_dl_handler.c
void gnb_rlf_handler(int rnti);
#define ENABLE_MAC_PAYLOAD_DEBUG
#define DEBUG_gNB_SCHEDULER 1
@@ -127,14 +130,28 @@ 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
int requested_layers;
if(dci_format == NR_DL_DCI_FORMAT_1_0)
return 1;
requested_layers = 1;
else
return sched_ctrl->CSI_report.cri_ri_li_pmi_cqi_report.ri + 1;
requested_layers = sched_ctrl->CSI_report.cri_ri_li_pmi_cqi_report.ri + 1;
// O-RAN MPLANE-TRX-CONTROL: cap layers to active TX antennas
if (mac && mac->antenna_state) {
int effective = nr_get_effective_mimo_layers(mac->antenna_state, requested_layers);
// Ensure at least 1 layer (scheduler should skip if all antennas muted)
if (effective < 1) effective = 1;
if (effective < requested_layers) {
LOG_D(NR_MAC, "[ANTENNA_MUTE] Capping layers from %d to %d due to muted antennas\n",
requested_layers, effective);
}
return effective;
}
return requested_layers;
}
int get_ul_nrOfLayers(const NR_UE_sched_ctrl_t *sched_ctrl, const nr_dci_format_t dci_format)
@@ -193,7 +210,38 @@ uint16_t get_pm_index(const gNB_MAC_INST *nrmac,
{
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;
// The UE's PMI report (x1/x2) was calculated for RI+1 layers.
// If we're using a different number of layers, the PMI values are invalid.
// Use identity matrix in this case to avoid layer/PMI mismatch assertion.
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;
}
// O-RAN MPLANE-TRX-CONTROL: Handle antenna muting and recovery mode
if (nrmac->antenna_state) {
int active_tx = nr_get_active_tx_antenna_count(nrmac->antenna_state);
int configured_tx = nrmac->antenna_state->num_tx_antennas;
// During recovery mode after unmuting, use identity matrix to allow CSI update
if (nr_is_in_recovery_mode(nrmac->antenna_state)) {
LOG_D(NR_MAC, "[ANTENNA_MUTE] Recovery mode active, using identity matrix\n");
return 0;
}
// When antennas are muted and we're using fewer layers than configured,
// use identity matrix for robustness (UE's PMI based on old config)
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;
@@ -215,9 +263,10 @@ uint16_t get_pm_index(const gNB_MAC_INST *nrmac,
// 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 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)
@@ -246,8 +295,18 @@ uint16_t get_pm_index(const gNB_MAC_INST *nrmac,
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;
pm_index = 1 + prev_layers_size + lay_index;
}
// Bounds check: ensure pm_index is within valid range for the specified layers
// The valid range for 'layers' is [1 + prev_layers_size, 1 + prev_layers_size + precoding_matrix_size[layers-1] - 1]
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
@@ -436,16 +495,24 @@ static NR_SearchSpace_t *get_searchspace(NR_ServingCellConfigCommon_t *scc,
AssertFatal(false, "Couldn't find an adequate SearchSpace for target SearchSpace %d in BWP_DownlinkDedicated\n", target_ss);
}
// If looking for UE-specific SearchSpace but no dedicated BWP is configured,
// fall back to common SearchSpace (type 0 or 1)
NR_SearchSpace__searchSpaceType_PR fallback_ss = target_ss;
if (target_ss == NR_SearchSpace__searchSpaceType_PR_ue_Specific) {
LOG_D(NR_MAC, "UE-specific SearchSpace requested but no dedicated BWP configured, falling back to common SearchSpace\n");
fallback_ss = NR_SearchSpace__searchSpaceType_PR_common;
}
const int n = scc->downlinkConfigCommon->initialDownlinkBWP->pdcch_ConfigCommon->choice.setup->commonSearchSpaceList->list.count;
for (int i = 0; i < n; i++) {
NR_SearchSpace_t *ss =
scc->downlinkConfigCommon->initialDownlinkBWP->pdcch_ConfigCommon->choice.setup->commonSearchSpaceList->list.array[i];
if (ss->searchSpaceType->present == target_ss) {
if (ss->searchSpaceType->present == fallback_ss) {
AssertFatal(ss->controlResourceSetId, "searchSpaceId %ld has a NULL controlResourceSetId\n", ss->searchSpaceId);
return ss;
}
}
AssertFatal(false, "Couldn't find an adequate SearchSpace for target SearchSpace %d in ServingCellConfigCommon\n", target_ss);
AssertFatal(false, "Couldn't find an adequate SearchSpace for target SearchSpace %d (fallback %d) in ServingCellConfigCommon\n", target_ss, fallback_ss);
}
/// @brief Get CORESET resource block parameters from frequency domain resources bitmap.
@@ -3816,6 +3883,8 @@ bool prepare_initial_ul_rrc_message(gNB_MAC_INST *mac, NR_UE_info_t *UE)
const NR_RLC_BearerConfig_t *bearer = cellGroupConfig->rlc_BearerToAddModList->list.array[0];
DevAssert(bearer->servedRadioBearer->choice.srb_Identity == srb_id);
nr_rlc_add_srb(UE->rnti, bearer->servedRadioBearer->choice.srb_Identity, bearer);
// Register RLF handler for SRB1 to handle max retransmissions
nr_rlc_set_rlf_handler(UE->rnti, gnb_rlf_handler);
int priority = bearer->mac_LogicalChannelConfig->ul_SpecificParameters->priority;
nr_lc_config_t c = {.lcid = bearer->logicalChannelIdentity, .priority = priority};
nr_mac_add_lcid(&UE->UE_sched_ctrl, &c);
@@ -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);
@@ -2173,12 +2173,16 @@ static int pf_ul(gNB_MAC_INST *nrmac,
bwp_info_t bi = get_pusch_bwp_start_size(iterator->UE);
while (rbStart < bi.bwpSize && (rballoc_mask[rbStart + bi.bwpStart] & slbitmap))
rbStart++;
/* if it's for inactivity, min_grant_prb is enough, otherwise check what
* would be the maximum */
uint16_t max_rbSize = iterator->sched_inactive ? min_rb : bi.bwpSize;
/* uint16_t max_rbSize = min_rb; */
uint16_t available_rb = 1;
while (rbStart + available_rb < bi.bwpSize && !(rballoc_mask[rbStart + bi.bwpStart + available_rb] & slbitmap) && available_rb < max_rbSize)
available_rb++;
available_rb++;
/* LOG_I(NR_MAC, "[UE %04x] RB size: %u\n", iterator->UE->rnti, available_rb); */
if (rbStart + min_rb > bi.bwpSize || available_rb < min_rb) {
reset_beam_status(&nrmac->beam_info, frame, slot, iterator->UE->UE_beam_index, slots_per_frame, dci_beam.new_beam);
@@ -2660,6 +2664,27 @@ static void nr_ulsch_preprocessor(gNB_MAC_INST *nr_mac, post_process_pusch_t *pp
int num_beams = nr_mac->beam_info.beam_allocation ? nr_mac->beam_info.beams_per_period : 1;
int bw = scc->uplinkConfigCommon->frequencyInfoUL->scs_SpecificCarrierList.list.array[0]->carrierBandwidth;
// O-RAN MPLANE-TRX-CONTROL: Scale UL resources based on active RX antennas
// This simulates receive diversity reduction when antennas are muted
if (nr_mac->antenna_state) {
int active_rx = nr_get_active_rx_antenna_count(nr_mac->antenna_state);
int configured_rx = nr_mac->antenna_state->num_rx_antennas;
// If all RX antennas are muted, skip UL user scheduling entirely
if (active_rx == 0) {
static int mute_log_counter = 0;
if (mute_log_counter++ % 10000 == 0)
LOG_W(NR_MAC, "[ANTENNA_MUTE] All RX antennas muted, skipping UL user scheduling\n");
return;
}
if (active_rx < configured_rx && configured_rx > 0) {
int scaled_bw = (bw * active_rx) / configured_rx;
if (scaled_bw < 5) scaled_bw = 5;
LOG_D(NR_MAC, "[ANTENNA_MUTE] Scaling UL capacity: %d/%d antennas -> %d/%d PRBs\n",
active_rx, configured_rx, scaled_bw, bw);
bw = scaled_bw;
}
}
int average_agg_level = 4; // TODO find a better estimation
int max_dci = bw / (average_agg_level * NR_NB_REG_PER_CCE);
+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);
@@ -53,6 +53,34 @@ static instance_t get_f1_gtp_instance(void)
return inst->gtpInst;
}
/**
* @brief RLF handler for gNB: called when RLC max retransmissions reached
* @param rnti RNTI of the UE experiencing RLF
*/
void gnb_rlf_handler(int rnti)
{
if (RC.nrmac == NULL || RC.nrmac[0] == NULL) {
LOG_E(RLC, "UE %04x: RLF detected but MAC not initialized\n", rnti);
return;
}
gNB_MAC_INST *mac = RC.nrmac[0];
NR_UE_info_t *UE = find_nr_UE(&mac->UE_info, rnti);
if (UE == NULL) {
LOG_W(RLC, "UE %04x: RLF detected but UE not found in connected list\n", rnti);
return;
}
LOG_W(NR_MAC, "UE %04x: RLF detected on SRB1, requesting UE release\n", rnti);
// Request UE release through proper channels
bool requested = nr_mac_request_release_ue(mac, rnti);
if (!requested) {
LOG_W(NR_MAC, "UE %04x: Could not request release, releasing directly\n", rnti);
nr_mac_release_ue(mac, rnti);
}
}
static int drb_gtpu_create(instance_t instance,
uint32_t ue_id,
int incoming_id,
@@ -247,6 +275,10 @@ static int handle_ue_context_srbs_setup(NR_UE_info_t *UE,
const f1ap_srb_to_setup_t *srb = &req_srbs[i];
NR_RLC_BearerConfig_t *rlc_BearerConfig = get_bearerconfig_from_srb(srb, rlc_config);
nr_rlc_add_srb(UE->rnti, srb->id, rlc_BearerConfig);
// Register RLF handler for SRB to handle max retransmissions
if (srb->id == 1) { // SRB1
nr_rlc_set_rlf_handler(UE->rnti, gnb_rlf_handler);
}
int priority = rlc_BearerConfig->mac_LogicalChannelConfig->ul_SpecificParameters->priority;
nr_lc_config_t c = {.lcid = rlc_BearerConfig->logicalChannelIdentity, .priority = priority};
+5
View File
@@ -79,6 +79,7 @@
/* PHY */
#include "time_meas.h"
#include "PHY/NR_TRANSPORT/nr_antenna_state.h"
/* Interface */
#include "nfapi_nr_interface_scf.h"
@@ -979,6 +980,10 @@ typedef struct gNB_MAC_INST_s {
nr_mac_config_t radio_config;
nr_rlc_configuration_t rlc_config;
/// O-RAN antenna state for energy saving (MPLANE-TRX-CONTROL)
/// Points to PHY antenna state for dynamic layer adaptation
nr_antenna_state_t *antenna_state;
NR_UE_sched_ctrl_t *sched_ctrlCommon;
NR_sched_pdcch_t *sched_pdcch_otherSI;
uint16_t cset0_bwp_start;
+2 -3
View File
@@ -3938,9 +3938,8 @@ int encode_cellGroupConfig(NR_CellGroupConfig_t *cellGroupConfig, uint8_t *buffe
{
asn_enc_rval_t enc_rval = uper_encode_to_buffer(&asn_DEF_NR_CellGroupConfig, NULL, cellGroupConfig, buffer, max_buffer_size);
AssertFatal(enc_rval.encoded > 0 && enc_rval.encoded <= max_buffer_size * 8,
"ASN1 message encoding failed (%s, %lu)!\n",
enc_rval.failed_type->name,
enc_rval.encoded);
"ASN1 message encoding failed (%s, %lu)! max: %d\n",
enc_rval.failed_type->name, enc_rval.encoded, max_buffer_size);
return (enc_rval.encoded + 7) / 8;
}
+2 -1
View File
@@ -1835,10 +1835,11 @@ void nr_rlc_entity_am_recv_sdu(nr_rlc_entity_t *_entity,
if (entity->sdu_rejected != 0
&& entity->t_current > entity->t_log_buffer_full + 1000) {
LOG_E(RLC, "%d SDU rejected, SDU buffer full\n", entity->sdu_rejected);
/* LOG_E(RLC, "tx_size: %d t_current: %lu max: %d\n" , entity->tx_size + size, entity->t_current, entity->tx_maxsize); */
entity->sdu_rejected = 0;
entity->t_log_buffer_full = entity->t_current;
}
/* LOG_E(RLC, "tx_size: %d max: %d\n" , entity->tx_size + size, entity->tx_maxsize); */
if (entity->tx_size + size > entity->tx_maxsize) {
entity->sdu_rejected++;
entity->common.stats.txpdu_dd_pkts++;
+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-mplane.c
xml/get-xml.c
yang/get-yang.c
yang/create-yang-config.c
@@ -0,0 +1,398 @@
/*
* 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 antenna-control-mplane.c
* @brief Implementation of O-RAN WG4 M-Plane compliant antenna control
*
* This implements NETCONF-based antenna muting using the actual O-RAN
* YANG models (o-ran-uplane-conf.yang) and the MPLANE-TRX-CONTROL feature.
*/
#include "antenna-control-mplane.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;
}
@@ -0,0 +1,174 @@
/*
* 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 antenna-control-mplane.h
* @brief O-RAN WG4 M-Plane compliant antenna control via NETCONF
*
* This module implements the MPLANE-TRX-CONTROL feature as defined in
* O-RAN WG4 M-Plane specification and o-ran-uplane-conf.yang model.
*
* YANG paths used:
* /o-ran-uplane-conf:user-plane-configuration/tx-array-antenna-mask-config
* /o-ran-uplane-conf:user-plane-configuration/rx-array-antenna-mask-config
*
* References:
* - O-RAN.WG4.MP.0-v10.00 Section 20.3.2 (M-Plane based TRX control)
* - o-ran-uplane-conf.yang (grouping array-config-group)
* - o-ran-wg4-features.yang (MPLANE-TRX-CONTROL feature)
*/
#ifndef ANTENNA_CONTROL_MPLANE_H
#define ANTENNA_CONTROL_MPLANE_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_MPLANE_H */
@@ -249,11 +249,11 @@ log_config :
};
fhi_72 = {
dpdk_devices = ("0000:c1:11.0", "0000:c1:11.1"); # one VF can be used as well
dpdk_devices = ("0000:01:02.0", "0000:01:02.1"); # one VF can be used as well
system_core = 0;
io_core = 1;
worker_cores = (2);
ru_addr = ("e8:c7:4f:25:81:b3", "e8:c7:4f:25:81:b3");
ru_addr = ("e8:c7:4f:25:7f:f6", "e8:c7:4f:25:81:b3");
mtu = 9600; # note: 1500 works as well, with both E and F releases
fh_config = ({
T1a_cp_dl = (285, 429);
@@ -0,0 +1,176 @@
# OAI gNB configuration for rfsimulator with Open5GS
# 2x2 MIMO configuration for antenna muting testing
Active_gNBs = ( "gNB-OAI-Open5GS");
Asn1_verbosity = "none";
gNBs =
(
{
////////// Identification parameters:
gNB_ID = 0xe00;
gNB_name = "gNB-OAI-Open5GS";
// Tracking area code, 0x0000 and 0xfffe are reserved values
tracking_area_code = 1;
// Match Open5GS PLMN: MCC=999, MNC=70
plmn_list = ({ mcc = 999; mnc = 70; mnc_length = 2; snssaiList = ({ sst = 1 }) });
nr_cellid = 12345678L;
////////// Physical parameters:
// 2x2 MIMO configuration
pdsch_AntennaPorts_XP = 2;
pusch_AntennaPorts = 2;
do_CSIRS = 1;
do_SRS = 1;
min_rxtxtime = 6;
servingCellConfigCommon = (
{
physCellId = 0;
# Band 78, 106 PRBs, 30kHz SCS
absoluteFrequencySSB = 621312;
dl_frequencyBand = 78;
dl_absoluteFrequencyPointA = 620040;
dl_offstToCarrier = 0;
dl_subcarrierSpacing = 1;
dl_carrierBandwidth = 106;
initialDLBWPlocationAndBandwidth = 28875;
initialDLBWPsubcarrierSpacing = 1;
initialDLBWPcontrolResourceSetZero = 11;
initialDLBWPsearchSpaceZero = 0;
ul_frequencyBand = 78;
ul_offstToCarrier = 0;
ul_subcarrierSpacing = 1;
ul_carrierBandwidth = 106;
pMax = 20;
initialULBWPlocationAndBandwidth = 28875;
initialULBWPsubcarrierSpacing = 1;
# RACH Configuration
prach_ConfigurationIndex = 98;
prach_msg1_FDM = 0;
prach_msg1_FrequencyStart = 0;
zeroCorrelationZoneConfig = 12;
preambleReceivedTargetPower = -104;
preambleTransMax = 6;
powerRampingStep = 1;
ssb_perRACH_OccasionAndCB_PreamblesPerSSB_PR = 4;
ssb_perRACH_OccasionAndCB_PreamblesPerSSB = 15;
ra_ContentionResolutionTimer = 7;
rsrp_ThresholdSSB = 19;
prach_RootSequenceIndex_PR = 2;
prach_RootSequenceIndex = 1;
msg1_SubcarrierSpacing = 1,
restrictedSetConfig = 0,
msg3_DeltaPreamble = 1;
p0_NominalWithGrant = -90;
# PUCCH Configuration
pucchGroupHopping = 0;
hoppingId = 40;
p0_nominal = -90;
ssb_PositionsInBurst_Bitmap = 1;
ssb_periodicityServingCell = 2;
dmrs_TypeA_Position = 0;
subcarrierSpacing = 1;
# TDD Configuration (5ms period)
referenceSubcarrierSpacing = 1;
dl_UL_TransmissionPeriodicity = 6;
nrofDownlinkSlots = 7;
nrofDownlinkSymbols = 6;
nrofUplinkSlots = 2;
nrofUplinkSymbols = 4;
ssPBCH_BlockPower = -25;
}
);
# ------- SCTP definitions
SCTP :
{
SCTP_INSTREAMS = 2;
SCTP_OUTSTREAMS = 2;
};
////////// AMF parameters (Open5GS default):
amf_ip_address = ({ ipv4 = "127.0.0.5"; });
NETWORK_INTERFACES :
{
GNB_IPV4_ADDRESS_FOR_NG_AMF = "127.0.0.1";
GNB_IPV4_ADDRESS_FOR_NGU = "127.0.0.1";
GNB_PORT_FOR_S1U = 2152;
};
}
);
MACRLCs = (
{
num_cc = 1;
tr_s_preference = "local_L1";
tr_n_preference = "local_RRC";
pusch_TargetSNRx10 = 200;
pucch_TargetSNRx10 = 200;
}
);
L1s = (
{
num_cc = 1;
tr_n_preference = "local_mac";
prach_dtx_threshold = 120;
tx_amp_backoff_dB = 20;
}
);
RUs = (
{
local_rf = "yes"
nb_tx = 2
nb_rx = 2
att_tx = 0
att_rx = 0;
bands = [78];
max_pdschReferenceSignalPower = -27;
max_rxgain = 75;
eNB_instances = [0];
}
);
rfsimulator :
{
serveraddr = "server";
serverport = 4043;
options = ();
modelname = "AWGN";
};
security = {
ciphering_algorithms = ( "nea0" );
integrity_algorithms = ( "nia2", "nia0" );
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";
};
@@ -0,0 +1,246 @@
#!/bin/bash
# O-RAN Antenna Muting Test Script
# Tests MIMO antenna muting with rfsimulator and Open5GS
set -e
OAI_DIR="/home/maki/openairinterface5g"
BUILD_DIR="$OAI_DIR/cmake_targets/ran_build/build"
CONF_DIR="$OAI_DIR/targets/PROJECTS/GENERIC-NR-5GC/CONF"
GNB_CONF="$CONF_DIR/gnb.sa.band78.2x2.rfsim.open5gs.conf"
UE_CONF="$CONF_DIR/ue.open5gs.conf"
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m'
print_status() {
echo -e "${GREEN}[INFO]${NC} $1"
}
print_warning() {
echo -e "${YELLOW}[WARN]${NC} $1"
}
print_error() {
echo -e "${RED}[ERROR]${NC} $1"
}
check_open5gs() {
print_status "Checking Open5GS services..."
for service in amf upf smf; do
if ! systemctl is-active --quiet open5gs-${service}d; then
print_error "Open5GS $service is not running"
print_status "Starting Open5GS services..."
sudo systemctl start open5gs-amfd open5gs-upfd open5gs-smfd open5gs-nrfd
sleep 2
fi
done
print_status "Open5GS services are running"
}
setup_network() {
print_status "Setting up network routing for UE..."
# Enable IP forwarding
sudo sysctl -w net.ipv4.ip_forward=1 > /dev/null
# Add NAT for UE traffic (if not already added)
if ! sudo iptables -t nat -C POSTROUTING -s 10.45.0.0/16 ! -o ogstun -j MASQUERADE 2>/dev/null; then
sudo iptables -t nat -A POSTROUTING -s 10.45.0.0/16 ! -o ogstun -j MASQUERADE
print_status "Added NAT rule for UE subnet"
fi
}
start_gnb() {
print_status "Starting gNB with 2x2 MIMO configuration..."
print_status "Antenna control server will be available on port 9999"
cd $BUILD_DIR
sudo ./nr-softmodem -O $GNB_CONF --sa --rfsim 2>&1 | tee /tmp/gnb.log &
GNB_PID=$!
print_status "gNB started with PID $GNB_PID"
echo $GNB_PID > /tmp/gnb.pid
# Wait for gNB to initialize
sleep 5
}
start_ue() {
print_status "Starting UE..."
cd $BUILD_DIR
sudo ./nr-uesoftmodem -r 106 --numerology 1 --band 78 -C 3319680000 \
--rfsim --sa -O $UE_CONF 2>&1 | tee /tmp/ue.log &
UE_PID=$!
print_status "UE started with PID $UE_PID"
echo $UE_PID > /tmp/ue.pid
# Wait for UE to attach
sleep 10
}
check_connection() {
print_status "Checking UE connection..."
# Check if oaitun_ue1 interface exists
if ip link show oaitun_ue1 &>/dev/null; then
print_status "UE TUN interface is up"
UE_IP=$(ip addr show oaitun_ue1 | grep 'inet ' | awk '{print $2}' | cut -d/ -f1)
print_status "UE IP: $UE_IP"
return 0
else
print_warning "UE TUN interface not found yet"
return 1
fi
}
run_iperf_test() {
print_status "Running iperf test..."
# Start iperf server on UPF side
iperf3 -s -B 10.45.0.1 -D &>/dev/null &
IPERF_SRV_PID=$!
sleep 1
# Run iperf client from UE
print_status "Testing downlink throughput..."
sudo ip netns exec ue_ns iperf3 -c 10.45.0.1 -t 5 -R 2>/dev/null || \
iperf3 -c 10.45.0.1 -t 5 -R 2>/dev/null || \
print_warning "iperf test failed or UE not fully connected"
kill $IPERF_SRV_PID 2>/dev/null || true
}
test_antenna_muting() {
print_status "Testing antenna muting via control interface..."
# Wait for control server to be ready
sleep 2
# Get initial status
print_status "Initial antenna status:"
echo "STATUS" | nc -w 2 localhost 9999 || print_warning "Control server not responding"
echo ""
print_status "Muting TX antenna 1..."
echo "MUTE TX 1" | nc -w 2 localhost 9999 || true
sleep 1
print_status "Current antenna status after muting:"
echo "STATUS" | nc -w 2 localhost 9999 || true
echo ""
print_status "Statistics:"
echo "STATS" | nc -w 2 localhost 9999 || true
echo ""
print_status "Restoring TX antenna 1..."
echo "UNMUTE TX 1" | nc -w 2 localhost 9999 || true
echo ""
print_status "Final antenna status:"
echo "STATUS" | nc -w 2 localhost 9999 || true
}
cleanup() {
print_status "Cleaning up..."
if [ -f /tmp/gnb.pid ]; then
sudo kill $(cat /tmp/gnb.pid) 2>/dev/null || true
rm /tmp/gnb.pid
fi
if [ -f /tmp/ue.pid ]; then
sudo kill $(cat /tmp/ue.pid) 2>/dev/null || true
rm /tmp/ue.pid
fi
# Kill any remaining processes
sudo pkill -f nr-softmodem 2>/dev/null || true
sudo pkill -f nr-uesoftmodem 2>/dev/null || true
print_status "Cleanup complete"
}
show_usage() {
echo "O-RAN Antenna Muting Test Script"
echo ""
echo "Usage: $0 [command]"
echo ""
echo "Commands:"
echo " all - Run full test (gNB + UE + iperf + antenna muting)"
echo " gnb - Start gNB only"
echo " ue - Start UE only (requires gNB running)"
echo " mute - Test antenna muting (requires gNB running)"
echo " iperf - Run iperf test (requires gNB + UE running)"
echo " status - Check antenna status"
echo " cleanup - Stop all processes"
echo ""
echo "Antenna Control Commands (via nc localhost 9999):"
echo " MUTE TX <id> - Mute TX antenna"
echo " UNMUTE TX <id> - Unmute TX antenna"
echo " SET TX MASK <hex> - Set TX antenna bitmask"
echo " STATUS - Get current status"
echo " STATS - Get statistics"
echo " HELP - Show all commands"
}
# Main
case "${1:-all}" in
all)
trap cleanup EXIT
check_open5gs
setup_network
start_gnb
start_ue
# Wait for connection
for i in {1..30}; do
if check_connection; then
break
fi
sleep 2
done
test_antenna_muting
run_iperf_test
print_status "Test complete. Press Ctrl+C to exit."
wait
;;
gnb)
check_open5gs
setup_network
start_gnb
print_status "gNB running. Press Ctrl+C to stop."
wait
;;
ue)
start_ue
print_status "UE running. Press Ctrl+C to stop."
wait
;;
mute)
test_antenna_muting
;;
iperf)
run_iperf_test
;;
status)
echo "STATUS" | nc -w 2 localhost 9999
;;
cleanup)
cleanup
;;
*)
show_usage
;;
esac
@@ -0,0 +1,51 @@
# OAI nrUE configuration for rfsimulator with Open5GS
# IMSI matching subscriber registered in Open5GS
uicc0 = {
imsi = "999700000000001";
key = "fec86ba6eb707ed08905757b1bb44b8f";
opc = "C42449363BBAD02B66D16BC975D77CC1";
dnn = "internet";
nssai_sst = 1;
}
thread-pool = "-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1"
# Channel model configuration for rfsimulator
channelmod = {
max_chan = 10;
modellist = "modellist_rfsimu_1";
modellist_rfsimu_1 = (
{ # DL channel
model_name = "rfsimu_channel_enB0"
type = "AWGN";
ploss_dB = 20;
noise_power_dB = -4;
forgetfact = 0;
offset = 0;
ds_tdl = 0;
},
{ # UL channel
model_name = "rfsimu_channel_ue0"
type = "AWGN";
ploss_dB = 20;
noise_power_dB = -2;
forgetfact = 0;
offset = 0;
ds_tdl = 0;
}
);
};
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";
nas_log_level = "info";
};
+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 3M
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