Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0fada7ec83 | ||
|
|
274166f18c | ||
|
|
e90b943242 | ||
|
|
c28054a285 | ||
|
|
cf4ca86169 | ||
|
|
85fef10860 | ||
|
|
16fa6e766d | ||
|
|
f893e8f072 | ||
|
|
7d7a25e158 | ||
|
|
f309868f58 | ||
|
|
dbfbbf0017 | ||
|
|
25de29a0ab | ||
|
|
3e09a4e4e3 | ||
|
|
69dd18b09f | ||
|
|
4c0ae6c43b | ||
|
|
02dc8dadd4 | ||
|
|
63d2238429 | ||
|
|
524e26ede5 | ||
|
|
01db9857c4 | ||
|
|
aa353fdcff | ||
|
|
bdfe516aa7 | ||
|
|
f8f10665e7 | ||
|
|
ee3e027041 | ||
|
|
7aca2a5fdf | ||
|
|
636c124596 | ||
|
|
7a18e84f1b | ||
|
|
b30f738b01 | ||
|
|
ac4dbff76e |
@@ -5,3 +5,4 @@ common/utils/T/T_IDs.h
|
|||||||
common/utils/T/T_messages.txt.h
|
common/utils/T/T_messages.txt.h
|
||||||
common/utils/T/genids
|
common/utils/T/genids
|
||||||
common/utils/T/genids.o
|
common/utils/T/genids.o
|
||||||
|
artifacts
|
||||||
|
|||||||
@@ -873,6 +873,7 @@ set(PHY_SRC_RU
|
|||||||
${OPENAIR1_DIR}/PHY/MODULATION/gen_75KHz.cpp
|
${OPENAIR1_DIR}/PHY/MODULATION/gen_75KHz.cpp
|
||||||
${OPENAIR1_DIR}/PHY/MODULATION/beamforming.c
|
${OPENAIR1_DIR}/PHY/MODULATION/beamforming.c
|
||||||
${OPENAIR1_DIR}/PHY/MODULATION/compute_bf_weights.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/lte_init_ru.c
|
||||||
${OPENAIR1_DIR}/PHY/INIT/nr_init_ru.c
|
${OPENAIR1_DIR}/PHY/INIT/nr_init_ru.c
|
||||||
${OPENAIR1_DIR}/PHY/LTE_TRANSPORT/prach.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_prach.c
|
||||||
${OPENAIR1_DIR}/PHY/NR_TRANSPORT/nr_ulsch_llr_computation.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_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/ul_ref_seq_nr.c
|
||||||
${OPENAIR1_DIR}/PHY/NR_REFSIG/nr_dmrs_rx.c
|
${OPENAIR1_DIR}/PHY/NR_REFSIG/nr_dmrs_rx.c
|
||||||
${OPENAIR1_DIR}/PHY/NR_REFSIG/nr_gen_mod_table.c
|
${OPENAIR1_DIR}/PHY/NR_REFSIG/nr_gen_mod_table.c
|
||||||
|
|||||||
Executable
+15
@@ -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;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -9,20 +9,29 @@ channelmod = {
|
|||||||
model_name = "rfsimu_channel_enB0"
|
model_name = "rfsimu_channel_enB0"
|
||||||
type = "AWGN";
|
type = "AWGN";
|
||||||
ploss_dB = 0;
|
ploss_dB = 0;
|
||||||
noise_power_dB = -10;
|
noise_power_dB = -60;
|
||||||
forgetfact = 0;
|
# forgetfact = 0;
|
||||||
offset = 0;
|
# offset = 0;
|
||||||
ds_tdl = 0;
|
# ds_tdl = 0;
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
model_name = "rfsimu_channel_ue0"
|
model_name = "rfsimu_channel_ue0"
|
||||||
type = "AWGN";
|
type = "AWGN";
|
||||||
ploss_dB = 0;
|
# ploss_dB = 0;
|
||||||
noise_power_dB = 0;
|
# noise_power_dB = -30;
|
||||||
forgetfact = 0;
|
# forgetfact = 0;
|
||||||
offset = 0;
|
# offset = 0;
|
||||||
ds_tdl = 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 = (
|
modellist_rfsimu_2 = (
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -12,17 +12,17 @@ gNBs =
|
|||||||
|
|
||||||
gNB_name = "cu-rfsim";
|
gNB_name = "cu-rfsim";
|
||||||
|
|
||||||
// Tracking area code, 0x0000 and 0xfffe are reserved values
|
|
||||||
tracking_area_code = 1;
|
tracking_area_code = 1;
|
||||||
plmn_list = ({ mcc = 208; mnc = 99; mnc_length = 2; snssaiList = ({ sst = 1, sd = 0xffffff }) });
|
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;
|
nr_cellid = 12345678L;
|
||||||
|
|
||||||
tr_s_preference = "f1";
|
tr_s_preference = "f1";
|
||||||
|
|
||||||
local_s_address = "192.168.71.150";
|
local_s_address = "192.168.71.140";
|
||||||
remote_s_address = "192.168.71.171";
|
remote_s_address = "192.168.71.141";
|
||||||
local_s_portd = 2153;
|
local_s_portd = 2153;
|
||||||
remote_s_portd = 2153;
|
remote_s_portd = 2153;
|
||||||
|
|
||||||
@@ -40,9 +40,8 @@ gNBs =
|
|||||||
|
|
||||||
NETWORK_INTERFACES :
|
NETWORK_INTERFACES :
|
||||||
{
|
{
|
||||||
|
GNB_IPV4_ADDRESS_FOR_NG_AMF = "192.168.71.140";
|
||||||
GNB_IPV4_ADDRESS_FOR_NG_AMF = "192.168.71.150";
|
GNB_IPV4_ADDRESS_FOR_NGU = "192.168.71.140";
|
||||||
GNB_IPV4_ADDRESS_FOR_NGU = "192.168.71.150";
|
|
||||||
GNB_PORT_FOR_S1U = 2152; # Spec 2152
|
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 parameters:
|
||||||
amf_ip_address = ({ ipv4 = "192.168.70.132"; });
|
amf_ip_address = ({ ipv4 = "192.168.71.132"; });
|
||||||
|
|
||||||
NETWORK_INTERFACES :
|
NETWORK_INTERFACES :
|
||||||
{
|
{
|
||||||
GNB_IPV4_ADDRESS_FOR_NG_AMF = "192.168.70.151";
|
GNB_IPV4_ADDRESS_FOR_NG_AMF = "192.168.71.140";
|
||||||
GNB_IPV4_ADDRESS_FOR_NGU = "192.168.70.151";
|
GNB_IPV4_ADDRESS_FOR_NGU = "192.168.71.140";
|
||||||
GNB_PORT_FOR_S1U = 2152; # Spec 2152
|
GNB_PORT_FOR_S1U = 2152; # Spec 2152
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -169,6 +169,7 @@ MACRLCs = (
|
|||||||
tr_n_preference = "local_RRC";
|
tr_n_preference = "local_RRC";
|
||||||
pusch_TargetSNRx10 = 200;
|
pusch_TargetSNRx10 = 200;
|
||||||
pucch_TargetSNRx10 = 200;
|
pucch_TargetSNRx10 = 200;
|
||||||
|
min_grant_prb = 1;
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -230,7 +231,7 @@ log_config : {
|
|||||||
};
|
};
|
||||||
|
|
||||||
e2_agent = {
|
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 = "/path/where/the/SMs/are/located/"
|
||||||
sm_dir = "/usr/local/lib/flexric/"
|
sm_dir = "/usr/local/lib/flexric/"
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,5 +1,12 @@
|
|||||||
Active_gNBs:
|
Active_gNBs:
|
||||||
- gnb-rfsim
|
- 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, choice in: none, info, annoying
|
||||||
Asn1_verbosity: none
|
Asn1_verbosity: none
|
||||||
gNBs:
|
gNBs:
|
||||||
@@ -139,13 +146,13 @@ gNBs:
|
|||||||
GNB_IPV4_ADDRESS_FOR_NGU: 192.168.71.140
|
GNB_IPV4_ADDRESS_FOR_NGU: 192.168.71.140
|
||||||
GNB_PORT_FOR_S1U: 2152 # Spec 2152
|
GNB_PORT_FOR_S1U: 2152 # Spec 2152
|
||||||
|
|
||||||
|
|
||||||
MACRLCs:
|
MACRLCs:
|
||||||
- num_cc: 1
|
- num_cc: 1
|
||||||
tr_s_preference: local_L1
|
tr_s_preference: local_L1
|
||||||
tr_n_preference: local_RRC
|
tr_n_preference: local_RRC
|
||||||
pusch_TargetSNRx10: 200
|
pusch_TargetSNRx10: 200
|
||||||
pucch_TargetSNRx10: 200
|
pucch_TargetSNRx10: 200
|
||||||
|
min_grant_prb: 5
|
||||||
stats_max_ue: 17
|
stats_max_ue: 17
|
||||||
|
|
||||||
L1s:
|
L1s:
|
||||||
@@ -193,6 +200,6 @@ log_config:
|
|||||||
mac_log_level: info
|
mac_log_level: info
|
||||||
rlc_log_level: info
|
rlc_log_level: info
|
||||||
pdcp_log_level: info
|
pdcp_log_level: info
|
||||||
rrc_log_level: info
|
rrc_log_level: debug
|
||||||
ngap_log_level: debug
|
ngap_log_level: debug
|
||||||
f1ap_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, choice in: none, info, annoying
|
||||||
Asn1_verbosity = "none";
|
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 =
|
gNBs =
|
||||||
(
|
(
|
||||||
{
|
{
|
||||||
@@ -198,7 +205,7 @@ RUs = (
|
|||||||
max_rxgain = 75;
|
max_rxgain = 75;
|
||||||
eNB_instances = [0];
|
eNB_instances = [0];
|
||||||
#clock_src = "internal";
|
#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 :
|
rfsimulator :
|
||||||
{
|
{
|
||||||
serveraddr = "server";
|
serveraddr = "server";
|
||||||
serverport = 4043;
|
# serverport = 4043;
|
||||||
options = (); #("saviq"); or/and "chanmod"
|
options = (); #("saviq"); or/and "chanmod"
|
||||||
modelname = "AWGN";
|
modelname = "AWGN";
|
||||||
IQfile = "/tmp/rfsimulator.iqs";
|
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,5 +1,4 @@
|
|||||||
uicc0 = {
|
uicc0 = { # imsi is omitted
|
||||||
imsi = "208990100001100";
|
|
||||||
key = "fec86ba6eb707ed08905757b1bb44b8f";
|
key = "fec86ba6eb707ed08905757b1bb44b8f";
|
||||||
opc= "C42449363BBAD02B66D16BC975D77CC1";
|
opc= "C42449363BBAD02B66D16BC975D77CC1";
|
||||||
dnn= "oai";
|
dnn= "oai";
|
||||||
|
|||||||
@@ -4,27 +4,56 @@ uicc0:
|
|||||||
opc: C42449363BBAD02B66D16BC975D77CC1
|
opc: C42449363BBAD02B66D16BC975D77CC1
|
||||||
dnn: oai
|
dnn: oai
|
||||||
nssai_sst: 1
|
nssai_sst: 1
|
||||||
|
pdu_type: IPV4
|
||||||
|
|
||||||
thread-pool: "-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1"
|
thread-pool: "-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1"
|
||||||
|
|
||||||
#/* configuration for channel modelisation */
|
#/* configuration for channel modelisation */
|
||||||
#/* To be included in main config file when */
|
#/* Realistic channel model for antenna muting tests */
|
||||||
#/* channel modelisation is used (rfsimulator with chanmod options enabled) */
|
#/* With realistic noise/path loss, antenna muting will show throughput reduction */
|
||||||
channelmod:
|
channelmod:
|
||||||
max_chan: 10
|
max_chan: 10
|
||||||
modellist: modellist_rfsimu_1
|
modellist: modellist_rfsimu_1
|
||||||
modellist_rfsimu_1:
|
modellist_rfsimu_1:
|
||||||
- model_name: rfsimu_channel_enB0
|
- model_name: rfsimu_channel_enB0
|
||||||
type: AWGN
|
type: AWGN
|
||||||
ploss_dB: 20
|
ploss_dB: 20 # Realistic path loss
|
||||||
noise_power_dB: -4
|
noise_power_dB: -10 # Moderate noise to show antenna muting impact
|
||||||
forgetfact: 0
|
forgetfact: 0
|
||||||
offset: 0
|
offset: 0
|
||||||
ds_tdl: 0
|
ds_tdl: 0
|
||||||
- model_name: rfsimu_channel_ue0
|
- model_name: rfsimu_channel_ue0
|
||||||
type: AWGN
|
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
|
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
|
forgetfact: 0
|
||||||
offset: 0
|
offset: 0
|
||||||
ds_tdl: 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
|
||||||
+2
-35
@@ -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 for all the NFs
|
||||||
log_level:
|
log_level:
|
||||||
general: debug
|
general: debug
|
||||||
@@ -103,8 +70,8 @@ amf:
|
|||||||
# This really depends on if we want to keep the "mini" version or not
|
# This really depends on if we want to keep the "mini" version or not
|
||||||
support_features_options:
|
support_features_options:
|
||||||
enable_simple_scenario: yes # "no" by default with the normal deployment scenarios with AMF/SMF/UPF/AUSF/UDM/UDR/NRF.
|
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.
|
# 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.
|
# There's no NRF in this scenario, SMF info is taken from "nfs" section.
|
||||||
enable_nssf: no
|
enable_nssf: no
|
||||||
enable_smf_selection: no
|
enable_smf_selection: no
|
||||||
relative_capacity: 30
|
relative_capacity: 30
|
||||||
+1
-11
File diff suppressed because one or more lines are too long
@@ -0,0 +1,3 @@
|
|||||||
|
[mysqld]
|
||||||
|
wait_timeout = 2
|
||||||
|
interactive_timeout = 2
|
||||||
Executable
+650
@@ -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()
|
||||||
@@ -278,7 +278,7 @@ check_install_usrp_uhd_driver(){
|
|||||||
x=$((x + 1))
|
x=$((x + 1))
|
||||||
done
|
done
|
||||||
$SUDO apt-get update
|
$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
|
case "$(get_distribution_release)" in
|
||||||
"ubuntu20.04" | "ubuntu22.04")
|
"ubuntu20.04" | "ubuntu22.04")
|
||||||
$SUDO apt-get -y install libuhd-dev libuhd4.5.0 uhd-host
|
$SUDO apt-get -y install libuhd-dev libuhd4.5.0 uhd-host
|
||||||
|
|||||||
@@ -83,8 +83,8 @@
|
|||||||
#define MAX_NUM_SLICES 1024
|
#define MAX_NUM_SLICES 1024
|
||||||
|
|
||||||
// RLC Entity
|
// RLC Entity
|
||||||
#define RLC_TX_MAXSIZE 10000000
|
#define RLC_TX_MAXSIZE 100000000
|
||||||
#define RLC_RX_MAXSIZE 10000000
|
#define RLC_RX_MAXSIZE 100000000
|
||||||
#define SEND_MRW_ON 240
|
#define SEND_MRW_ON 240
|
||||||
#define MAX_ANT 8
|
#define MAX_ANT 8
|
||||||
// CBA constant
|
// CBA constant
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -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
|
FROM ubuntu:noble AS ran-base
|
||||||
ARG NEEDED_GIT_PROXY
|
ARG NEEDED_GIT_PROXY
|
||||||
ARG TARGETARCH
|
ARG TARGETARCH
|
||||||
ENV DEBIAN_FRONTEND=noninteractive
|
ENV DEBIAN_FRONTEND=noninteractive
|
||||||
ENV TZ=Europe/Paris
|
ENV TZ=Asia/Tokyo
|
||||||
ENV BUILD_UHD_FROM_SOURCE=True
|
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 && \
|
RUN apt-get update && \
|
||||||
apt-get upgrade --yes && \
|
apt-get upgrade --yes && \
|
||||||
apt-get install --yes \
|
apt-get install --yes \
|
||||||
@@ -46,28 +17,20 @@ RUN apt-get update && \
|
|||||||
xxd \
|
xxd \
|
||||||
libpcre2-dev \
|
libpcre2-dev \
|
||||||
python3-dev \
|
python3-dev \
|
||||||
|
python3-pip \
|
||||||
bison \
|
bison \
|
||||||
flex \
|
flex \
|
||||||
m4 \
|
m4 \
|
||||||
# python3-pip for conf template generation
|
|
||||||
python3-pip \
|
|
||||||
wget && \
|
wget && \
|
||||||
# --break-system-packages allows externally managed environment in pip
|
# --break-system-packages allows externally managed environment in pip
|
||||||
pip3 install --ignore-installed pyyaml --break-system-packages
|
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
|
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"
|
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
|
WORKDIR /oai-ran/cmake_targets/tools
|
||||||
COPY cmake_targets/tools/build_helper \
|
COPY cmake_targets/tools/build_helper \
|
||||||
cmake_targets/tools/uhd-4.x-tdd-patch.diff \
|
cmake_targets/tools/uhd-4.x-tdd-patch.diff \
|
||||||
@@ -84,3 +47,4 @@ RUN /bin/sh oaienv && \
|
|||||||
cd cmake_targets && \
|
cd cmake_targets && \
|
||||||
mkdir -p log && \
|
mkdir -p log && \
|
||||||
./build_oai -I -w USRP --install-optional-packages
|
./build_oai -I -w USRP --install-optional-packages
|
||||||
|
|
||||||
|
|||||||
@@ -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"]
|
||||||
@@ -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"]
|
||||||
@@ -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"]
|
||||||
@@ -48,6 +48,8 @@
|
|||||||
#include "NR_PHY_INTERFACE/NR_IF_Module.h"
|
#include "NR_PHY_INTERFACE/NR_IF_Module.h"
|
||||||
#include "PHY/INIT/nr_phy_init.h"
|
#include "PHY/INIT/nr_phy_init.h"
|
||||||
#include "PHY/MODULATION/nr_modulation.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/NR_TRANSPORT/nr_transport_proto.h"
|
||||||
#include "PHY/TOOLS/tools_defs.h"
|
#include "PHY/TOOLS/tools_defs.h"
|
||||||
#include "PHY/defs_RU.h"
|
#include "PHY/defs_RU.h"
|
||||||
@@ -402,6 +404,20 @@ void init_eNB_afterRU(void)
|
|||||||
*/
|
*/
|
||||||
//init_precoding_weights(RC.gNB[inst]);
|
//init_precoding_weights(RC.gNB[inst]);
|
||||||
init_gNB_Tpool(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");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -158,6 +158,10 @@ void phy_init_nr_gNB(PHY_VARS_gNB *gNB)
|
|||||||
|
|
||||||
init_DLSCH_struct(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*));
|
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++) {
|
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));
|
gNB->nr_srs_info[id] = (nr_srs_info_t *)malloc16_clear(sizeof(nr_srs_info_t));
|
||||||
|
|||||||
@@ -51,6 +51,7 @@
|
|||||||
#include "modulation_eNB.h"
|
#include "modulation_eNB.h"
|
||||||
#include "nr_modulation.h"
|
#include "nr_modulation.h"
|
||||||
#include "common/utils/LOG/vcd_signal_dumper.h"
|
#include "common/utils/LOG/vcd_signal_dumper.h"
|
||||||
|
#include "PHY/NR_TRANSPORT/nr_antenna_state.h"
|
||||||
|
|
||||||
int beam_precoding(int32_t **txdataF,
|
int beam_precoding(int32_t **txdataF,
|
||||||
int32_t **txdataF_BF,
|
int32_t **txdataF_BF,
|
||||||
@@ -143,3 +144,57 @@ void nr_beam_precoding(c16_t **txdataF,
|
|||||||
15);
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -103,6 +103,34 @@ void nr_beam_precoding(c16_t **txdataF,
|
|||||||
int nb_antenna_ports,
|
int nb_antenna_ports,
|
||||||
int offset);
|
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,
|
void apply_nr_rotation_TX(const NR_DL_FRAME_PARMS *fp,
|
||||||
c16_t *txdataF,
|
c16_t *txdataF,
|
||||||
const c16_t *symbol_rotation,
|
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 */
|
||||||
|
|
||||||
@@ -43,6 +43,7 @@
|
|||||||
#include "PHY/CODING/nrLDPC_extern.h"
|
#include "PHY/CODING/nrLDPC_extern.h"
|
||||||
#include "PHY/CODING/nrLDPC_decoder/nrLDPC_types.h"
|
#include "PHY/CODING/nrLDPC_decoder/nrLDPC_types.h"
|
||||||
#include "nfapi_nr_interface_scf.h"
|
#include "nfapi_nr_interface_scf.h"
|
||||||
|
#include "PHY/NR_TRANSPORT/nr_antenna_state.h"
|
||||||
|
|
||||||
#define MAX_NUM_RU_PER_gNB 8
|
#define MAX_NUM_RU_PER_gNB 8
|
||||||
#define MAX_PUCCH0_NID 8
|
#define MAX_PUCCH0_NID 8
|
||||||
@@ -445,6 +446,10 @@ typedef struct PHY_VARS_gNB_s {
|
|||||||
|
|
||||||
bool enable_analog_das;
|
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_tx_proc;
|
||||||
time_stats_t l1_rx_proc;
|
time_stats_t l1_rx_proc;
|
||||||
|
|
||||||
|
|||||||
@@ -34,6 +34,7 @@
|
|||||||
#include "sched_nr.h"
|
#include "sched_nr.h"
|
||||||
#include "PHY/MODULATION/modulation_common.h"
|
#include "PHY/MODULATION/modulation_common.h"
|
||||||
#include "PHY/MODULATION/nr_modulation.h"
|
#include "PHY/MODULATION/nr_modulation.h"
|
||||||
|
#include "PHY/NR_TRANSPORT/nr_antenna_state.h"
|
||||||
|
|
||||||
#include "common/utils/LOG/log.h"
|
#include "common/utils/LOG/log.h"
|
||||||
#include "common/utils/system.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)
|
if (aa == 0 && first_symbol == 0)
|
||||||
start_meas(&ru->ofdm_mod_stats);
|
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;
|
slot_offsetF = first_symbol * fp->ofdm_symbol_size;
|
||||||
|
|
||||||
int abs_first_symbol = slot * fp->symbols_per_slot;
|
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)*
|
int slot_sizeF = (fp->ofdm_symbol_size)*
|
||||||
((cyclic_prefix_type == 1) ? 12 : 14);
|
((cyclic_prefix_type == 1) ? 12 : 14);
|
||||||
int slot = tti_tx;
|
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;
|
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 );
|
VCD_SIGNAL_DUMPER_DUMP_FUNCTION_BY_NAME(VCD_SIGNAL_DUMPER_FUNCTIONS_PHY_PROCEDURES_RU_FEPTX_OFDM , 0 );
|
||||||
|
|
||||||
LOG_D(PHY,
|
LOG_D(PHY,"feptx_ofdm (TXPATH): frame %d, slot %d: txp (time %p) %d dB, txp (freq) %d dB\n",
|
||||||
"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(
|
||||||
frame_tx,
|
slot,fp))),dB_fixed(signal_energy_nodc((c16_t*)ru->common.txdataF_BF[aa],2*slot_sizeF)));
|
||||||
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)));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void nr_feptx_prec(RU_t *ru, int frame_tx, int slot_tx)
|
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)
|
if (nr_slot_select(cfg,frame_tx,slot_tx) == NR_UPLINK_SLOT)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
int Ptx = cfg->carrier_config.num_tx_ant.value;
|
// O-RAN compliant antenna muting (MPLANE-TRX-CONTROL)
|
||||||
// If there is no digital beamforming we just need to copy the data to RU
|
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) {
|
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 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;
|
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],
|
// Check if this TX antenna is muted for energy saving
|
||||||
fp->samples_per_slot_wCP * sizeof(int32_t));
|
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 {
|
} else {
|
||||||
@@ -238,19 +263,44 @@ void nr_feptx(void *arg)
|
|||||||
if (tx_idx == 0)
|
if (tx_idx == 0)
|
||||||
start_meas(&ru->precoding_stats);
|
start_meas(&ru->precoding_stats);
|
||||||
|
|
||||||
if (ru->gNB_list[0]->common_vars.analog_bf) {
|
// O-RAN compliant antenna muting check (MPLANE-TRX-CONTROL)
|
||||||
memcpy(&ru->common.beam_id[bb][slot * fp->symbols_per_slot],
|
// Check if this TX antenna is muted for energy saving
|
||||||
&ru->gNB_list[0]->common_vars.beam_id[bb][slot * fp->symbols_per_slot],
|
PHY_VARS_gNB *gNB = ru->gNB_list[0];
|
||||||
(fp->symbols_per_slot) * sizeof(int));
|
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 (antenna_muted) {
|
||||||
if (ru->config.dbt_config.num_dig_beams == 0 || ru->gNB_list[0]->common_vars.analog_bf)
|
// Zero the output buffer for muted antenna - transmission blanking
|
||||||
memcpy((void*)&ru->common.txdataF_BF[tx_idx][txdataF_BF_offset],
|
memset((void*)&ru->common.txdataF_BF[tx_idx][txdataF_BF_offset], 0,
|
||||||
(void*)&ru->gNB_list[0]->common_vars.txdataF[bb][aa][txdataF_offset],
|
numSamples * sizeof(int32_t));
|
||||||
numSamples * sizeof(int32_t));
|
LOG_D(PHY, "[ANTENNA_MUTE] TX antenna %d muted, blanking slot %d symbols %d-%d\n",
|
||||||
else {
|
aa, slot, startSymbol, startSymbol + numSymbols - 1);
|
||||||
AssertFatal(false, "This needs to be fixed by using appropriate beams from config\n");
|
} 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)
|
if (tx_idx == 0)
|
||||||
@@ -337,13 +387,30 @@ void nr_fep(void* arg)
|
|||||||
|
|
||||||
int idx = aid + beam * ru->nb_rx;
|
int idx = aid + beam * ru->nb_rx;
|
||||||
int offset = (slot % RU_RX_SLOT_DEPTH) * fp->symbols_per_slot * fp->ofdm_symbol_size;
|
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,
|
// O-RAN compliant RX antenna muting check (MPLANE-TRX-CONTROL)
|
||||||
ru->common.rxdata[idx],
|
PHY_VARS_gNB *gNB = ru->gNB_list[0];
|
||||||
&ru->common.rxdataF[idx][offset],
|
nr_antenna_state_t *ant_state = &gNB->antenna_state;
|
||||||
l,
|
bool rx_muted = !nr_is_rx_antenna_active(ant_state, aid);
|
||||||
slot,
|
|
||||||
ru->N_TA_offset);
|
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);
|
VCD_SIGNAL_DUMPER_DUMP_FUNCTION_BY_NAME(VCD_SIGNAL_DUMPER_FUNCTIONS_PHY_PROCEDURES_RU_FEPRX+aid, 0);
|
||||||
|
|
||||||
// Task completed in //
|
// Task completed in //
|
||||||
|
|||||||
+1
-1
Submodule openair2/E2AP/flexric updated: 6a595d8b24...13c6e07dbd
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_DL_BWP_t *dl_bwp = &UE->current_DL_BWP;
|
||||||
NR_UE_UL_BWP_t *ul_bwp = &UE->current_UL_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;
|
NR_sched_pdsch_t new_sched = sched_ctrl->harq_processes[current_harq_pid].sched_pdsch;
|
||||||
int layers = get_dl_nrOfLayers(sched_ctrl, dl_bwp->dci_format);
|
int layers = get_dl_nrOfLayers(nr_mac, sched_ctrl, dl_bwp->dci_format);
|
||||||
int pm_index = get_pm_index(nr_mac, UE, dl_bwp->dci_format, layers, nr_mac->radio_config.pdsch_AntennaPorts.XP);
|
|
||||||
|
|
||||||
// If the RI changed between current rtx and a previous transmission
|
// If the RI changed between current rtx and a previous transmission
|
||||||
// we need to verify if it is not decreased
|
// we need to verify if it is not decreased
|
||||||
// othwise it wouldn't be possible to transmit the same TBS
|
// othwise it wouldn't be possible to transmit the same TBS
|
||||||
layers = (layers < new_sched.nrOfLayers) ? layers : new_sched.nrOfLayers;
|
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 coresetid = sched_ctrl->coreset->controlResourceSetId;
|
||||||
const int tda = get_dl_tda(nr_mac, slot);
|
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;
|
sched_ctrl->dl_bler_stats.mcs = selected_mcs;
|
||||||
} else
|
} else
|
||||||
selected_mcs = get_mcs_from_bler(bo, stats, &sched_ctrl->dl_bler_stats, max_mcs, frame);
|
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 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);
|
const uint16_t R = nr_get_code_rate_dl(selected_mcs, current_BWP->mcsTableIdx);
|
||||||
uint32_t tbs = nr_compute_tbs(Qm,
|
uint32_t tbs = nr_compute_tbs(Qm,
|
||||||
@@ -865,7 +865,7 @@ static void pf_dl(gNB_MAC_INST *mac,
|
|||||||
sched_ctrl->cce_index = CCEIndex;
|
sched_ctrl->cce_index = CCEIndex;
|
||||||
fill_pdcch_vrb_map(mac, CC_id, &sched_ctrl->sched_pdcch, CCEIndex, sched_ctrl->aggregation_level, beam.idx);
|
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 = {
|
NR_sched_pdsch_t sched_pdsch = {
|
||||||
// rbSize below
|
// rbSize below
|
||||||
.rbStart = rbStart,
|
.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;
|
NR_ServingCellConfigCommon_t *scc = mac->common_channels[0].ServingCellConfigCommon;
|
||||||
int bw = scc->downlinkConfigCommon->frequencyInfoDL->scs_SpecificCarrierList.list.array[0]->carrierBandwidth;
|
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 num_beams = mac->beam_info.beam_allocation ? mac->beam_info.beams_per_period : 1;
|
||||||
int n_rb_sched[num_beams];
|
int n_rb_sched[num_beams];
|
||||||
for (int i = 0; i < num_beams; i++)
|
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.num_prgs = 1;
|
||||||
pdsch_pdu->precodingAndBeamforming.prg_size = pdsch_pdu->rbSize;
|
pdsch_pdu->precodingAndBeamforming.prg_size = pdsch_pdu->rbSize;
|
||||||
pdsch_pdu->precodingAndBeamforming.dig_bf_interfaces = 1;
|
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;
|
pdsch_pdu->precodingAndBeamforming.prgs_list[0].dig_bf_interface_list[0].beam_idx = beam_index;
|
||||||
return pdsch_pdu;
|
return pdsch_pdu;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -53,6 +53,9 @@
|
|||||||
|
|
||||||
#include "SIMULATION/TOOLS/sim.h" // for taus
|
#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 ENABLE_MAC_PAYLOAD_DEBUG
|
||||||
#define DEBUG_gNB_SCHEDULER 1
|
#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);
|
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
|
// TODO check this but it should be enough for now
|
||||||
// if there is not csi report activated RI is 0 from initialization
|
// if there is not csi report activated RI is 0 from initialization
|
||||||
|
int requested_layers;
|
||||||
if(dci_format == NR_DL_DCI_FORMAT_1_0)
|
if(dci_format == NR_DL_DCI_FORMAT_1_0)
|
||||||
return 1;
|
requested_layers = 1;
|
||||||
else
|
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)
|
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)
|
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)
|
return 0; //identity matrix (basic 5G configuration handled by PMI report is with XP antennas)
|
||||||
|
|
||||||
const NR_UE_sched_ctrl_t *sched_ctrl = &UE->UE_sched_ctrl;
|
const 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 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 nr_csi_report_t *csi_report = &UE->csi_report_template[report_id];
|
||||||
const int N1 = csi_report->N1;
|
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
|
// order of matrices depends on layers to be transmitted
|
||||||
// elements from 1 to n for 1 layer
|
// elements from 1 to n for 1 layer
|
||||||
// elements from n+1 to m for 2 layers etc.
|
// elements from n+1 to m for 2 layers etc.
|
||||||
if (antenna_ports == 2)
|
int pm_index;
|
||||||
return 1 + prev_layers_size + x2; // 0 for identity matrix
|
if (antenna_ports == 2) {
|
||||||
else {
|
pm_index = 1 + prev_layers_size + x2; // 0 for identity matrix
|
||||||
|
} else {
|
||||||
int idx = layers - 1;
|
int idx = layers - 1;
|
||||||
// the order of i1x in X1 report needs to be verified
|
// 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)
|
// 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);
|
get_K1_K2(N1, N2, &K1, &K2, layers);
|
||||||
// computing precoding matrix index according to rule set in allocation function init_codebook_gNB
|
// 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);
|
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
|
// 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);
|
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;
|
const int n = scc->downlinkConfigCommon->initialDownlinkBWP->pdcch_ConfigCommon->choice.setup->commonSearchSpaceList->list.count;
|
||||||
for (int i = 0; i < n; i++) {
|
for (int i = 0; i < n; i++) {
|
||||||
NR_SearchSpace_t *ss =
|
NR_SearchSpace_t *ss =
|
||||||
scc->downlinkConfigCommon->initialDownlinkBWP->pdcch_ConfigCommon->choice.setup->commonSearchSpaceList->list.array[i];
|
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);
|
AssertFatal(ss->controlResourceSetId, "searchSpaceId %ld has a NULL controlResourceSetId\n", ss->searchSpaceId);
|
||||||
return ss;
|
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.
|
/// @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];
|
const NR_RLC_BearerConfig_t *bearer = cellGroupConfig->rlc_BearerToAddModList->list.array[0];
|
||||||
DevAssert(bearer->servedRadioBearer->choice.srb_Identity == srb_id);
|
DevAssert(bearer->servedRadioBearer->choice.srb_Identity == srb_id);
|
||||||
nr_rlc_add_srb(UE->rnti, bearer->servedRadioBearer->choice.srb_Identity, bearer);
|
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;
|
int priority = bearer->mac_LogicalChannelConfig->ul_SpecificParameters->priority;
|
||||||
nr_lc_config_t c = {.lcid = bearer->logicalChannelIdentity, .priority = priority};
|
nr_lc_config_t c = {.lcid = bearer->logicalChannelIdentity, .priority = priority};
|
||||||
nr_mac_add_lcid(&UE->UE_sched_ctrl, &c);
|
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++;
|
stats->num_sinr_meas++;
|
||||||
|
|
||||||
const int mcs_table = UE->current_DL_BWP.mcsTableIdx;
|
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);
|
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);
|
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);
|
bwp_info_t bi = get_pusch_bwp_start_size(iterator->UE);
|
||||||
while (rbStart < bi.bwpSize && (rballoc_mask[rbStart + bi.bwpStart] & slbitmap))
|
while (rbStart < bi.bwpSize && (rballoc_mask[rbStart + bi.bwpStart] & slbitmap))
|
||||||
rbStart++;
|
rbStart++;
|
||||||
|
|
||||||
/* if it's for inactivity, min_grant_prb is enough, otherwise check what
|
/* if it's for inactivity, min_grant_prb is enough, otherwise check what
|
||||||
* would be the maximum */
|
* would be the maximum */
|
||||||
uint16_t max_rbSize = iterator->sched_inactive ? min_rb : bi.bwpSize;
|
uint16_t max_rbSize = iterator->sched_inactive ? min_rb : bi.bwpSize;
|
||||||
|
/* uint16_t max_rbSize = min_rb; */
|
||||||
uint16_t available_rb = 1;
|
uint16_t available_rb = 1;
|
||||||
while (rbStart + available_rb < bi.bwpSize && !(rballoc_mask[rbStart + bi.bwpStart + available_rb] & slbitmap) && available_rb < max_rbSize)
|
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) {
|
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);
|
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 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;
|
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 average_agg_level = 4; // TODO find a better estimation
|
||||||
int max_dci = bw / (average_agg_level * NR_NB_REG_PER_CCE);
|
int max_dci = bw / (average_agg_level * NR_NB_REG_PER_CCE);
|
||||||
|
|
||||||
|
|||||||
@@ -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);
|
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_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);
|
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);
|
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;
|
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,
|
static int drb_gtpu_create(instance_t instance,
|
||||||
uint32_t ue_id,
|
uint32_t ue_id,
|
||||||
int incoming_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];
|
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_BearerConfig_t *rlc_BearerConfig = get_bearerconfig_from_srb(srb, rlc_config);
|
||||||
nr_rlc_add_srb(UE->rnti, srb->id, rlc_BearerConfig);
|
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;
|
int priority = rlc_BearerConfig->mac_LogicalChannelConfig->ul_SpecificParameters->priority;
|
||||||
nr_lc_config_t c = {.lcid = rlc_BearerConfig->logicalChannelIdentity, .priority = priority};
|
nr_lc_config_t c = {.lcid = rlc_BearerConfig->logicalChannelIdentity, .priority = priority};
|
||||||
|
|||||||
@@ -79,6 +79,7 @@
|
|||||||
|
|
||||||
/* PHY */
|
/* PHY */
|
||||||
#include "time_meas.h"
|
#include "time_meas.h"
|
||||||
|
#include "PHY/NR_TRANSPORT/nr_antenna_state.h"
|
||||||
|
|
||||||
/* Interface */
|
/* Interface */
|
||||||
#include "nfapi_nr_interface_scf.h"
|
#include "nfapi_nr_interface_scf.h"
|
||||||
@@ -979,6 +980,10 @@ typedef struct gNB_MAC_INST_s {
|
|||||||
nr_mac_config_t radio_config;
|
nr_mac_config_t radio_config;
|
||||||
nr_rlc_configuration_t rlc_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_UE_sched_ctrl_t *sched_ctrlCommon;
|
||||||
NR_sched_pdcch_t *sched_pdcch_otherSI;
|
NR_sched_pdcch_t *sched_pdcch_otherSI;
|
||||||
uint16_t cset0_bwp_start;
|
uint16_t cset0_bwp_start;
|
||||||
|
|||||||
@@ -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);
|
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,
|
AssertFatal(enc_rval.encoded > 0 && enc_rval.encoded <= max_buffer_size * 8,
|
||||||
"ASN1 message encoding failed (%s, %lu)!\n",
|
"ASN1 message encoding failed (%s, %lu)! max: %d\n",
|
||||||
enc_rval.failed_type->name,
|
enc_rval.failed_type->name, enc_rval.encoded, max_buffer_size);
|
||||||
enc_rval.encoded);
|
|
||||||
return (enc_rval.encoded + 7) / 8;
|
return (enc_rval.encoded + 7) / 8;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1835,10 +1835,11 @@ void nr_rlc_entity_am_recv_sdu(nr_rlc_entity_t *_entity,
|
|||||||
if (entity->sdu_rejected != 0
|
if (entity->sdu_rejected != 0
|
||||||
&& entity->t_current > entity->t_log_buffer_full + 1000) {
|
&& 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, "%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->sdu_rejected = 0;
|
||||||
entity->t_log_buffer_full = entity->t_current;
|
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) {
|
if (entity->tx_size + size > entity->tx_maxsize) {
|
||||||
entity->sdu_rejected++;
|
entity->sdu_rejected++;
|
||||||
entity->common.stats.txpdu_dd_pkts++;
|
entity->common.stats.txpdu_dd_pkts++;
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ target_sources(oran_fhlib_5g_mplane PRIVATE
|
|||||||
subscribe-mplane.c
|
subscribe-mplane.c
|
||||||
config-mplane.c
|
config-mplane.c
|
||||||
ru-mplane-api.c
|
ru-mplane-api.c
|
||||||
|
antenna-control-mplane.c
|
||||||
xml/get-xml.c
|
xml/get-xml.c
|
||||||
yang/get-yang.c
|
yang/get-yang.c
|
||||||
yang/create-yang-config.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 = {
|
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;
|
system_core = 0;
|
||||||
io_core = 1;
|
io_core = 1;
|
||||||
worker_cores = (2);
|
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
|
mtu = 9600; # note: 1500 works as well, with both E and F releases
|
||||||
fh_config = ({
|
fh_config = ({
|
||||||
T1a_cp_dl = (285, 429);
|
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";
|
||||||
|
};
|
||||||
|
|
||||||
Executable
+98
@@ -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
|
||||||
Reference in New Issue
Block a user