一旦
This commit is contained in:
@@ -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;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -4,7 +4,7 @@ Asn1_verbosity = "none";
|
|||||||
|
|
||||||
e2_agent = {
|
e2_agent = {
|
||||||
enabled = "yes";
|
enabled = "yes";
|
||||||
near_ric_ip_addr = "192.168.71.21";
|
near_ric_ip_addr = "192.168.71.21"; # FlexRIC Near-RT RIC
|
||||||
near_ric_port = 36422;
|
near_ric_port = 36422;
|
||||||
sm_dir = "/usr/local/lib/flexric/";
|
sm_dir = "/usr/local/lib/flexric/";
|
||||||
}
|
}
|
||||||
@@ -19,7 +19,7 @@ gNBs =
|
|||||||
// Tracking area code, 0x0000 and 0xfffe are reserved values
|
// Tracking area code, 0x0000 and 0xfffe are reserved values
|
||||||
tracking_area_code = 1;
|
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 = 0x1; }) });
|
||||||
plmn_list = ({ mcc = 208; mnc = 99; mnc_length = 2; snssaiList = ({ sst = 1; sd = 0xffffff; }) });
|
plmn_list = ({ mcc = 001; mnc = 01; mnc_length = 2; snssaiList = ({ sst = 1; sd = 0xffffff; }) });
|
||||||
|
|
||||||
nr_cellid = 12345678L;
|
nr_cellid = 12345678L;
|
||||||
|
|
||||||
@@ -235,12 +235,12 @@ servingCellConfigDedicated = ({
|
|||||||
|
|
||||||
|
|
||||||
////////// AMF parameters:
|
////////// AMF parameters:
|
||||||
amf_ip_address = ({ipv4 = "192.168.71.132";});
|
amf_ip_address = ({ipv4 = "192.168.70.132";}); # Full OAI CN AMF (duc-oai-amf)
|
||||||
|
|
||||||
NETWORK_INTERFACES :
|
NETWORK_INTERFACES :
|
||||||
{
|
{
|
||||||
GNB_IPV4_ADDRESS_FOR_NG_AMF = "192.168.71.140";
|
GNB_IPV4_ADDRESS_FOR_NG_AMF = "192.168.70.140"; # On full CN network
|
||||||
GNB_IPV4_ADDRESS_FOR_NGU = "192.168.71.140";
|
GNB_IPV4_ADDRESS_FOR_NGU = "192.168.70.140"; # On full CN network
|
||||||
GNB_PORT_FOR_S1U = 2152; # Spec 2152
|
GNB_PORT_FOR_S1U = 2152; # Spec 2152
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -331,3 +331,6 @@ log_config :
|
|||||||
ngap_log_level ="debug";
|
ngap_log_level ="debug";
|
||||||
f1ap_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,441 @@
|
|||||||
|
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 = 4;
|
||||||
|
pusch_AntennaPorts = 4;
|
||||||
|
do_CSIRS = 0; # used to be 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.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;
|
||||||
|
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 = 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"
|
||||||
|
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 - minimal noise for antenna muting tests
|
||||||
|
# channelmod = {
|
||||||
|
# max_chan = 10;
|
||||||
|
# modellist = "modellist_antenna_muting";
|
||||||
|
|
||||||
|
# modellist_antenna_muting = (
|
||||||
|
# {
|
||||||
|
# model_name = "rfsimu_channel_enB0"
|
||||||
|
# type = "AWGN";
|
||||||
|
# ploss_dB = 0;
|
||||||
|
# noise_power_dB = -50;
|
||||||
|
# forgetfact = 0;
|
||||||
|
# offset = 0;
|
||||||
|
# ds_tdl = 0;
|
||||||
|
# },
|
||||||
|
# {
|
||||||
|
# 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;
|
||||||
|
# }
|
||||||
|
# );
|
||||||
|
# };
|
||||||
+177
-27
@@ -8,30 +8,36 @@ It follows the `MPLANE-TRX-CONTROL` feature defined in `o-ran-uplane-conf.yang`.
|
|||||||
## Architecture
|
## Architecture
|
||||||
|
|
||||||
```
|
```
|
||||||
┌─────────────────────────────────────────────────────────────────────┐
|
┌─────────────────────────────────────────────────────────────────────────────┐
|
||||||
│ Control Interfaces │
|
│ Control Interfaces │
|
||||||
│ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────────┐ │
|
│ │
|
||||||
│ │ O1 (NETCONF) │ │ Test Socket │ │ Future: rApp/A1 │ │
|
│ ┌───────────────────────────┐ ┌─────────────────┐ ┌──────────────────┐ │
|
||||||
│ │ (Production) │ │ (Development) │ │ (Policy-based) │ │
|
│ │ 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) │ │
|
│ │ nr_antenna_state_t (Antenna State Manager) │ │
|
||||||
│ │ - tx_antenna_bitmask: O-RAN compliant antenna mask │ │
|
│ │ - tx_antenna_bitmask: O-RAN compliant antenna mask │ │
|
||||||
│ │ - rx_antenna_bitmask: O-RAN compliant antenna mask │ │
|
│ │ - rx_antenna_bitmask: O-RAN compliant antenna mask │ │
|
||||||
│ │ - Statistics for energy saving monitoring │ │
|
│ │ - Statistics for energy saving monitoring │ │
|
||||||
│ └──────────────────────────────────────────────────────────────┘ │
|
│ └────────────────────────────────────────────────────────────────────────┘ │
|
||||||
│ │ │
|
│ │ │
|
||||||
│ ▼ │
|
│ ▼ │
|
||||||
│ ┌──────────────────────────────────────────────────────────────┐ │
|
│ ┌────────────────────────────────────────────────────────────────────────┐ │
|
||||||
│ │ PHY Layer Integration │ │
|
│ │ PHY Layer Integration │ │
|
||||||
│ │ - nr_beam_precoding_with_muting(): Blanks muted antennas │ │
|
│ │ - nr_beam_precoding_with_muting(): Blanks muted antennas │ │
|
||||||
│ │ - Beamforming skipped for muted antennas │ │
|
│ │ - Beamforming skipped for muted antennas │ │
|
||||||
│ │ - Output zeroed = transmission blank (O-RAN compliant) │ │
|
│ │ - Output zeroed = transmission blank (O-RAN compliant) │ │
|
||||||
│ └──────────────────────────────────────────────────────────────┘ │
|
│ └────────────────────────────────────────────────────────────────────────┘ │
|
||||||
└─────────────────────────────────────────────────────────────────────┘
|
└─────────────────────────────────────────────────────────────────────────────┘
|
||||||
```
|
```
|
||||||
|
|
||||||
## O-RAN Compliance
|
## O-RAN Compliance
|
||||||
@@ -40,35 +46,106 @@ This implementation follows O-RAN Alliance specifications:
|
|||||||
|
|
||||||
| O-RAN Element | Implementation |
|
| O-RAN Element | Implementation |
|
||||||
|---------------|----------------|
|
|---------------|----------------|
|
||||||
| WG4 `o-ran-uplane-conf.yang` | `tx-array-antenna-mask-config` |
|
| 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` |
|
| `antenna-bitmask` parameter | `nr_antenna_state_t.tx_antenna_bitmask` |
|
||||||
| `MPLANE-TRX-CONTROL` feature | `nr_set_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 |
|
| 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
|
## Files
|
||||||
|
|
||||||
|
### Core State Management
|
||||||
- `openair1/PHY/NR_TRANSPORT/nr_antenna_state.h` - Antenna state API
|
- `openair1/PHY/NR_TRANSPORT/nr_antenna_state.h` - Antenna state API
|
||||||
- `openair1/PHY/NR_TRANSPORT/nr_antenna_state.c` - Implementation
|
- `openair1/PHY/NR_TRANSPORT/nr_antenna_state.c` - Implementation
|
||||||
- `openair1/PHY/NR_TRANSPORT/nr_antenna_control.h` - Test control interface
|
|
||||||
- `openair1/PHY/NR_TRANSPORT/nr_antenna_control.c` - Socket server for testing
|
### 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()`
|
- `openair1/PHY/MODULATION/beamforming.c` - Modified with `nr_beam_precoding_with_muting()`
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
### 1. Initialize Antenna State (in gNB init)
|
### 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
|
```c
|
||||||
#include "PHY/NR_TRANSPORT/nr_antenna_state.h"
|
#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
|
// Initialize with 4 TX and 4 RX antennas
|
||||||
nr_init_antenna_state(&gNB->antenna_state, 4, 4, "tx-array-0", "rx-array-0");
|
nr_init_antenna_state(&gNB->antenna_state, 4, 4, "tx-array-0", "rx-array-0");
|
||||||
|
|
||||||
// Optionally start test control server
|
// Start test socket server (NOT O-RAN compliant - logs warning)
|
||||||
#include "PHY/NR_TRANSPORT/nr_antenna_control.h"
|
nr_antenna_control_start_test_server(&gNB->antenna_state, 9999);
|
||||||
nr_antenna_control_start(&gNB->antenna_state, 9999);
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### 2. Control Antennas via Test Interface
|
**Warning**: The test socket interface is for development only and is NOT O-RAN compliant.
|
||||||
|
|
||||||
|
### Control Antennas via Test Interface
|
||||||
|
|
||||||
Connect with netcat:
|
Connect with netcat:
|
||||||
```bash
|
```bash
|
||||||
@@ -82,10 +159,11 @@ Commands:
|
|||||||
> MUTE TX 2 # Mute TX antenna 2
|
> MUTE TX 2 # Mute TX antenna 2
|
||||||
> MUTE TX 3 # Mute TX antenna 3
|
> MUTE TX 3 # Mute TX antenna 3
|
||||||
> SET TX MASK 03 # Set TX mask to 0x03 (antennas 0,1 active)
|
> 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
|
> STATS # Show energy saving statistics
|
||||||
```
|
```
|
||||||
|
|
||||||
### 3. Use in Beamforming
|
### Use in Beamforming
|
||||||
|
|
||||||
```c
|
```c
|
||||||
// Original (all antennas active):
|
// Original (all antennas active):
|
||||||
@@ -97,6 +175,24 @@ nr_beam_precoding_with_muting(txdataF, txdataF_BF, fp, beam_weights,
|
|||||||
&gNB->antenna_state);
|
&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
|
## Testing with rfsimulator
|
||||||
|
|
||||||
### Prerequisites
|
### Prerequisites
|
||||||
@@ -198,16 +294,70 @@ With 4x4 MIMO:
|
|||||||
|
|
||||||
Throughput should decrease proportionally as antennas are muted.
|
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
|
## Future Work
|
||||||
|
|
||||||
1. **O1 NETCONF Integration**: Integrate with `radio/fhi_72/mplane/` for production O-RAN compliance
|
1. ~~**O1 NETCONF Integration**: Integrate with `radio/fhi_72/mplane/` for production O-RAN compliance~~ ✅ **DONE**
|
||||||
2. **rApp Development**: Create Non-RT RIC rApp for policy-based antenna control
|
2. **NETCONF Notifications**: Subscribe to `mplane-trx-control-ant-mask-update` notifications
|
||||||
3. **A1 Policy Interface**: Enable policy-based control from SMO
|
3. **rApp Development**: Create Non-RT RIC rApp for policy-based antenna control
|
||||||
4. **Power Measurement**: Add actual power consumption metrics (requires hardware)
|
4. **A1 Policy Interface**: Enable policy-based control from SMO
|
||||||
|
5. **Power Measurement**: Add actual power consumption metrics (requires hardware)
|
||||||
|
|
||||||
## References
|
## References
|
||||||
|
|
||||||
- O-RAN WG4 M-Plane Specification
|
- 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-uplane-conf.yang` - YANG model for U-plane configuration
|
||||||
- `o-ran-wg4-features.yang` - Feature definitions including `MPLANE-TRX-CONTROL`
|
- `o-ran-wg4-features.yang` - Feature definitions including `MPLANE-TRX-CONTROL`
|
||||||
|
- `radio/fhi_72/mplane/yang/models/` - O-RAN YANG models in this repository
|
||||||
|
|||||||
@@ -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,111 @@
|
|||||||
|
#
|
||||||
|
# Dockerfile to rebuild nakaolab image with O-RAN antenna muting support
|
||||||
|
#
|
||||||
|
# Usage:
|
||||||
|
# cd /home/maki/openairinterface5g
|
||||||
|
# docker build -f docker/Dockerfile.nakaolab.rebuild -t nakaolab:latest .
|
||||||
|
#
|
||||||
|
|
||||||
|
FROM ubuntu:noble AS builder
|
||||||
|
|
||||||
|
ENV DEBIAN_FRONTEND=noninteractive
|
||||||
|
ENV TZ=Asia/Tokyo
|
||||||
|
|
||||||
|
# Install build dependencies
|
||||||
|
RUN apt-get update && apt-get install -y \
|
||||||
|
build-essential \
|
||||||
|
cmake \
|
||||||
|
ninja-build \
|
||||||
|
ccache \
|
||||||
|
git \
|
||||||
|
libsctp-dev \
|
||||||
|
libconfig-dev \
|
||||||
|
libboost-all-dev \
|
||||||
|
libblas-dev \
|
||||||
|
liblapack-dev \
|
||||||
|
liblapacke-dev \
|
||||||
|
libyaml-cpp-dev \
|
||||||
|
libusb-1.0-0-dev \
|
||||||
|
python3 \
|
||||||
|
python3-pip \
|
||||||
|
python3-dev \
|
||||||
|
xxd \
|
||||||
|
libpcre2-dev \
|
||||||
|
bison \
|
||||||
|
flex \
|
||||||
|
libssl-dev \
|
||||||
|
libatlas-base-dev \
|
||||||
|
&& pip3 install pyyaml --break-system-packages \
|
||||||
|
&& apt-get clean && rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
# Copy source code
|
||||||
|
WORKDIR /oai-ran
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
# Build gNB and UE with rfsimulator
|
||||||
|
RUN cd cmake_targets && \
|
||||||
|
./build_oai --ninja --gNB --nrUE -w SIMU -c && \
|
||||||
|
echo "Build complete"
|
||||||
|
|
||||||
|
# Build FlexRIC if exists
|
||||||
|
RUN if [ -d "openair2/E2AP/flexric" ]; then \
|
||||||
|
cd openair2/E2AP/flexric && \
|
||||||
|
mkdir -p build && cd build && \
|
||||||
|
cmake .. && make -j$(nproc) && \
|
||||||
|
make install; \
|
||||||
|
fi
|
||||||
|
|
||||||
|
#
|
||||||
|
# Final image
|
||||||
|
#
|
||||||
|
FROM ubuntu:noble
|
||||||
|
|
||||||
|
ENV DEBIAN_FRONTEND=noninteractive
|
||||||
|
ENV TZ=Asia/Tokyo
|
||||||
|
|
||||||
|
# Install runtime dependencies
|
||||||
|
RUN apt-get update && apt-get install -y \
|
||||||
|
libsctp1 \
|
||||||
|
libconfig9 \
|
||||||
|
libboost-chrono1.83.0 \
|
||||||
|
libboost-date-time1.83.0 \
|
||||||
|
libboost-filesystem1.83.0 \
|
||||||
|
libboost-regex1.83.0 \
|
||||||
|
libboost-serialization1.83.0 \
|
||||||
|
libboost-thread1.83.0 \
|
||||||
|
libboost-system1.83.0 \
|
||||||
|
libboost-program-options1.83.0 \
|
||||||
|
libblas3 \
|
||||||
|
liblapack3 \
|
||||||
|
libyaml-cpp0.8 \
|
||||||
|
libusb-1.0-0 \
|
||||||
|
net-tools \
|
||||||
|
iputils-ping \
|
||||||
|
iproute2 \
|
||||||
|
iperf \
|
||||||
|
iperf3 \
|
||||||
|
netcat-openbsd \
|
||||||
|
procps \
|
||||||
|
gdb \
|
||||||
|
python3 \
|
||||||
|
python3-six \
|
||||||
|
python3-requests \
|
||||||
|
&& apt-get clean && rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
# Copy built binaries and libraries
|
||||||
|
WORKDIR /oai-ran
|
||||||
|
COPY --from=builder /oai-ran/cmake_targets/ran_build /oai-ran/cmake_targets/ran_build
|
||||||
|
COPY --from=builder /oai-ran/targets /oai-ran/targets
|
||||||
|
COPY --from=builder /oai-ran/ci-scripts/conf_files /oai-ran/ci-scripts/conf_files
|
||||||
|
|
||||||
|
# Copy FlexRIC if built
|
||||||
|
COPY --from=builder /usr/local/lib/flexric /usr/local/lib/flexric
|
||||||
|
COPY --from=builder /usr/local/etc/flexric /usr/local/etc/flexric
|
||||||
|
RUN echo "/usr/local/lib/flexric" >> /etc/ld.so.conf.d/flexric.conf && ldconfig || true
|
||||||
|
|
||||||
|
# Copy FlexRIC binaries
|
||||||
|
RUN mkdir -p /oai-ran/openair2/E2AP/flexric/build/examples
|
||||||
|
COPY --from=builder /oai-ran/openair2/E2AP/flexric/build/examples /oai-ran/openair2/E2AP/flexric/build/examples
|
||||||
|
|
||||||
|
ENTRYPOINT ["/bin/bash"]
|
||||||
|
|
||||||
@@ -49,12 +49,12 @@ RUN set -eux; \
|
|||||||
COPY tools/scripts/traffic_runner.sh /usr/local/bin/traffic_runner
|
COPY tools/scripts/traffic_runner.sh /usr/local/bin/traffic_runner
|
||||||
RUN chmod +x /usr/local/bin/traffic_runner
|
RUN chmod +x /usr/local/bin/traffic_runner
|
||||||
|
|
||||||
# FlexRIC libraries (built by --build-e2)
|
# FlexRIC libraries (built by --build-e2) - only copy service model libraries (*_sm.so)
|
||||||
RUN mkdir -p /usr/local/etc/flexric /usr/local/lib/flexric && \
|
RUN mkdir -p /usr/local/etc/flexric /usr/local/lib/flexric && \
|
||||||
cp -r /oai-ran/openair2/E2AP/flexric/build/src/lib/*.so /usr/local/lib/flexric/ 2>/dev/null || true && \
|
find /oai-ran/openair2/E2AP/flexric/build -name "*_sm.so" -exec cp {} /usr/local/lib/flexric/ \; && \
|
||||||
cp -r /oai-ran/openair2/E2AP/flexric/build/src/sm/*.so /usr/local/lib/flexric/ 2>/dev/null || true && \
|
|
||||||
cp /oai-ran/openair2/E2AP/flexric/flexric.conf /usr/local/etc/flexric/ 2>/dev/null || true && \
|
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
|
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
|
# Install netcat for antenna control testing
|
||||||
RUN apt-get update && apt-get install -y netcat-openbsd && rm -rf /var/lib/apt/lists/*
|
RUN apt-get update && apt-get install -y netcat-openbsd && rm -rf /var/lib/apt/lists/*
|
||||||
|
|||||||
@@ -48,6 +48,7 @@
|
|||||||
#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_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"
|
||||||
@@ -411,6 +412,13 @@ void init_eNB_afterRU(void)
|
|||||||
if (nr_antenna_control_start(&gNB->antenna_state, 9999)) {
|
if (nr_antenna_control_start(&gNB->antenna_state, 9999)) {
|
||||||
LOG_I(PHY, "[ANTENNA_CTRL] Test control server started on port 9999\n");
|
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");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +0,0 @@
|
|||||||
[NEAR-RIC]
|
|
||||||
NEAR_RIC_IP = 192.168.71.21
|
|
||||||
NEAR_RIC_PORT = 36421
|
|
||||||
E2AP_SERVER_PORT = 36422
|
|
||||||
|
|
||||||
[XAPP]
|
|
||||||
DB_DIR = /tmp/
|
|
||||||
|
|
||||||
[LOG]
|
|
||||||
LEVEL = debug
|
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -20,10 +20,10 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
/*! \file PHY/NR_TRANSPORT/nr_antenna_control.c
|
/*! \file PHY/NR_TRANSPORT/nr_antenna_control.c
|
||||||
* \brief Test control interface implementation for antenna muting
|
* \brief Antenna control interface implementation with O-RAN M-Plane support
|
||||||
* \author OpenAirInterface
|
* \author OpenAirInterface
|
||||||
* \date 2025
|
* \date 2025
|
||||||
* \version 1.0
|
* \version 2.0
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "nr_antenna_control.h"
|
#include "nr_antenna_control.h"
|
||||||
@@ -40,15 +40,31 @@
|
|||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
#include <signal.h>
|
#include <signal.h>
|
||||||
|
|
||||||
// Server state
|
// 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 {
|
static struct {
|
||||||
pthread_t thread;
|
pthread_t thread;
|
||||||
int server_fd;
|
int server_fd;
|
||||||
nr_antenna_state_t *ant_state;
|
nr_antenna_state_t *ant_state;
|
||||||
volatile bool running;
|
volatile bool running;
|
||||||
int port;
|
int port;
|
||||||
|
nr_antenna_ctrl_backend_t backend;
|
||||||
|
#ifdef OAI_MPLANE
|
||||||
|
ru_session_t *ru_session;
|
||||||
|
#else
|
||||||
|
void *ru_session;
|
||||||
|
#endif
|
||||||
} g_ctrl = {0};
|
} g_ctrl = {0};
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Test Socket Server Implementation (Development/Testing Mode)
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
// Process a single command and generate response
|
// Process a single command and generate response
|
||||||
static void process_command(const char *cmd, char *response, size_t resp_size)
|
static void process_command(const char *cmd, char *response, size_t resp_size)
|
||||||
{
|
{
|
||||||
@@ -113,7 +129,8 @@ static void process_command(const char *cmd, char *response, size_t resp_size)
|
|||||||
" Bitmask: 0x%02X\n"
|
" Bitmask: 0x%02X\n"
|
||||||
"RX Array: %s\n"
|
"RX Array: %s\n"
|
||||||
" Antennas: %d configured, %d active\n"
|
" Antennas: %d configured, %d active\n"
|
||||||
" Bitmask: 0x%02X\n",
|
" Bitmask: 0x%02X\n"
|
||||||
|
"Backend: %s\n",
|
||||||
g_ctrl.ant_state->tx_array_name,
|
g_ctrl.ant_state->tx_array_name,
|
||||||
g_ctrl.ant_state->num_tx_antennas,
|
g_ctrl.ant_state->num_tx_antennas,
|
||||||
nr_get_active_tx_antenna_count(g_ctrl.ant_state),
|
nr_get_active_tx_antenna_count(g_ctrl.ant_state),
|
||||||
@@ -121,7 +138,10 @@ static void process_command(const char *cmd, char *response, size_t resp_size)
|
|||||||
g_ctrl.ant_state->rx_array_name,
|
g_ctrl.ant_state->rx_array_name,
|
||||||
g_ctrl.ant_state->num_rx_antennas,
|
g_ctrl.ant_state->num_rx_antennas,
|
||||||
nr_get_active_rx_antenna_count(g_ctrl.ant_state),
|
nr_get_active_rx_antenna_count(g_ctrl.ant_state),
|
||||||
nr_get_rx_antenna_bitmask(g_ctrl.ant_state));
|
nr_get_rx_antenna_bitmask(g_ctrl.ant_state),
|
||||||
|
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) {
|
else if (strcmp(p, "STATS") == 0) {
|
||||||
uint64_t tx_events, rx_events, muted_slots;
|
uint64_t tx_events, rx_events, muted_slots;
|
||||||
@@ -137,9 +157,30 @@ static void process_command(const char *cmd, char *response, size_t resp_size)
|
|||||||
nr_reset_antenna_stats(g_ctrl.ant_state);
|
nr_reset_antenna_stats(g_ctrl.ant_state);
|
||||||
snprintf(response, resp_size, "OK: Statistics reset\n");
|
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
|
||||||
|
uint8_t all_tx = (1 << g_ctrl.ant_state->num_tx_antennas) - 1;
|
||||||
|
uint8_t all_rx = (1 << g_ctrl.ant_state->num_rx_antennas) - 1;
|
||||||
|
nr_set_tx_antenna_bitmask(g_ctrl.ant_state, all_tx);
|
||||||
|
nr_set_rx_antenna_bitmask(g_ctrl.ant_state, all_rx);
|
||||||
|
snprintf(response, resp_size,
|
||||||
|
"OK: All antennas unmuted\n"
|
||||||
|
" TX mask: 0x%02X (%d antennas)\n"
|
||||||
|
" RX mask: 0x%02X (%d antennas)\n",
|
||||||
|
all_tx, g_ctrl.ant_state->num_tx_antennas,
|
||||||
|
all_rx, g_ctrl.ant_state->num_rx_antennas);
|
||||||
|
}
|
||||||
|
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) {
|
else if (strcmp(p, "HELP") == 0) {
|
||||||
snprintf(response, resp_size,
|
snprintf(response, resp_size,
|
||||||
"=== O-RAN Antenna Control Test Interface ===\n"
|
"=== O-RAN Antenna Control Interface ===\n"
|
||||||
"Commands:\n"
|
"Commands:\n"
|
||||||
" MUTE TX <ant_id> - Mute TX antenna (0-based)\n"
|
" MUTE TX <ant_id> - Mute TX antenna (0-based)\n"
|
||||||
" UNMUTE TX <ant_id> - Unmute TX antenna\n"
|
" UNMUTE TX <ant_id> - Unmute TX antenna\n"
|
||||||
@@ -150,10 +191,15 @@ static void process_command(const char *cmd, char *response, size_t resp_size)
|
|||||||
" STATUS - Show current antenna state\n"
|
" STATUS - Show current antenna state\n"
|
||||||
" STATS - Show energy saving statistics\n"
|
" STATS - Show energy saving statistics\n"
|
||||||
" RESET STATS - Reset statistics\n"
|
" RESET STATS - Reset statistics\n"
|
||||||
|
" RESET / UNMUTE ALL - Unmute all antennas (restore default)\n"
|
||||||
|
" SYNC - Sync state to RU (M-Plane only)\n"
|
||||||
" HELP - Show this help\n"
|
" HELP - Show this help\n"
|
||||||
" QUIT - Close connection\n"
|
" QUIT - Close connection\n"
|
||||||
"\n"
|
"\n"
|
||||||
"Example: SET TX MASK 03 (enables antennas 0,1; mutes 2,3)\n");
|
"Example: SET TX MASK 03 (enables antennas 0,1; mutes 2,3)\n"
|
||||||
|
"\n"
|
||||||
|
"NOTE: This test socket interface is NOT O-RAN compliant.\n"
|
||||||
|
" For production, use NETCONF via radio/fhi_72/mplane/\n");
|
||||||
}
|
}
|
||||||
else if (strcmp(p, "QUIT") == 0 || strcmp(p, "EXIT") == 0) {
|
else if (strcmp(p, "QUIT") == 0 || strcmp(p, "EXIT") == 0) {
|
||||||
snprintf(response, resp_size, "BYE\n");
|
snprintf(response, resp_size, "BYE\n");
|
||||||
@@ -172,10 +218,11 @@ static void handle_client(int client_fd)
|
|||||||
char buffer[256];
|
char buffer[256];
|
||||||
char response[1024];
|
char response[1024];
|
||||||
|
|
||||||
// Send welcome message
|
// Send welcome message with warning about non-compliance
|
||||||
const char *welcome =
|
const char *welcome =
|
||||||
"=== OAI Antenna Control Server ===\n"
|
"=== OAI Antenna Control Test Server ===\n"
|
||||||
"O-RAN WG4 MPLANE-TRX-CONTROL Test Interface\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";
|
"Type HELP for commands.\n\n";
|
||||||
send(client_fd, welcome, strlen(welcome), 0);
|
send(client_fd, welcome, strlen(welcome), 0);
|
||||||
|
|
||||||
@@ -216,7 +263,7 @@ static void *server_thread(void *arg)
|
|||||||
struct sockaddr_in client_addr;
|
struct sockaddr_in client_addr;
|
||||||
socklen_t addr_len = sizeof(client_addr);
|
socklen_t addr_len = sizeof(client_addr);
|
||||||
|
|
||||||
LOG_I(PHY, "[ANTENNA_CTRL] Server listening on port %d\n", g_ctrl.port);
|
LOG_I(PHY, "[ANTENNA_CTRL] Test server listening on port %d\n", g_ctrl.port);
|
||||||
|
|
||||||
while (g_ctrl.running) {
|
while (g_ctrl.running) {
|
||||||
// Accept connection with timeout
|
// Accept connection with timeout
|
||||||
@@ -241,10 +288,50 @@ static void *server_thread(void *arg)
|
|||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool nr_antenna_control_start(nr_antenna_state_t *ant_state, int port)
|
// ============================================================================
|
||||||
|
// 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) {
|
if (g_ctrl.running) {
|
||||||
LOG_W(PHY, "[ANTENNA_CTRL] Server already running\n");
|
LOG_W(PHY, "[ANTENNA_CTRL] Control interface already active\n");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -253,8 +340,15 @@ bool nr_antenna_control_start(nr_antenna_state_t *ant_state, int port)
|
|||||||
return false;
|
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.ant_state = ant_state;
|
||||||
g_ctrl.port = (port > 0) ? port : NR_ANTENNA_CONTROL_PORT;
|
g_ctrl.port = (port > 0) ? port : NR_ANTENNA_CONTROL_TEST_PORT;
|
||||||
|
g_ctrl.backend = ANTENNA_CTRL_BACKEND_TEST_SOCKET;
|
||||||
|
|
||||||
// Create socket
|
// Create socket
|
||||||
g_ctrl.server_fd = socket(AF_INET, SOCK_STREAM, 0);
|
g_ctrl.server_fd = socket(AF_INET, SOCK_STREAM, 0);
|
||||||
@@ -296,18 +390,28 @@ bool nr_antenna_control_start(nr_antenna_state_t *ant_state, int port)
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
LOG_I(PHY, "[ANTENNA_CTRL] Server started on port %d\n", g_ctrl.port);
|
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);
|
LOG_I(PHY, "[ANTENNA_CTRL] Connect with: nc localhost %d\n", g_ctrl.port);
|
||||||
|
|
||||||
return true;
|
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)
|
void nr_antenna_control_stop(void)
|
||||||
{
|
{
|
||||||
if (!g_ctrl.running) return;
|
if (!g_ctrl.running) return;
|
||||||
|
|
||||||
g_ctrl.running = false;
|
g_ctrl.running = false;
|
||||||
|
|
||||||
|
if (g_ctrl.backend == ANTENNA_CTRL_BACKEND_TEST_SOCKET) {
|
||||||
// Close server socket to unblock accept()
|
// Close server socket to unblock accept()
|
||||||
if (g_ctrl.server_fd >= 0) {
|
if (g_ctrl.server_fd >= 0) {
|
||||||
close(g_ctrl.server_fd);
|
close(g_ctrl.server_fd);
|
||||||
@@ -316,8 +420,13 @@ void nr_antenna_control_stop(void)
|
|||||||
|
|
||||||
// Wait for thread
|
// Wait for thread
|
||||||
pthread_join(g_ctrl.thread, NULL);
|
pthread_join(g_ctrl.thread, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
LOG_I(PHY, "[ANTENNA_CTRL] Server stopped\n");
|
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)
|
bool nr_antenna_control_is_running(void)
|
||||||
@@ -325,3 +434,98 @@ bool nr_antenna_control_is_running(void)
|
|||||||
return g_ctrl.running;
|
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
|
||||||
|
}
|
||||||
|
|||||||
@@ -20,13 +20,29 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
/*! \file PHY/NR_TRANSPORT/nr_antenna_control.h
|
/*! \file PHY/NR_TRANSPORT/nr_antenna_control.h
|
||||||
* \brief Test control interface for antenna muting (simulates O1 NETCONF interface)
|
* \brief O-RAN WG4 compliant antenna control interface
|
||||||
* \author OpenAirInterface
|
* \author OpenAirInterface
|
||||||
* \date 2025
|
* \date 2025
|
||||||
* \version 1.0
|
* \version 2.0
|
||||||
* \note This provides a simple socket-based interface to test antenna muting
|
*
|
||||||
* without requiring a full NETCONF/M-plane implementation.
|
* This module provides antenna control for MIMO muting / energy saving.
|
||||||
* In production, use the O-RAN O1 interface via radio/fhi_72/mplane/
|
*
|
||||||
|
* 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
|
#ifndef NR_ANTENNA_CONTROL_H
|
||||||
@@ -39,22 +55,56 @@
|
|||||||
extern "C" {
|
extern "C" {
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
/// Default port for the antenna control server
|
/// Default port for the test socket server (development mode only)
|
||||||
#define NR_ANTENNA_CONTROL_PORT 9999
|
#define NR_ANTENNA_CONTROL_TEST_PORT 9999
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Start the antenna control server thread
|
* @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)
|
||||||
*
|
*
|
||||||
* Creates a simple TCP server that accepts commands to control antenna state.
|
* This is a forward declaration for ru_session_t from the M-Plane library.
|
||||||
* This simulates the O-RAN O1 interface for testing purposes.
|
* 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
|
||||||
*
|
*
|
||||||
* Commands supported:
|
* 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
|
* MUTE TX <ant_id> - Mute TX antenna
|
||||||
* UNMUTE TX <ant_id> - Unmute TX antenna
|
* UNMUTE TX <ant_id> - Unmute TX antenna
|
||||||
* MUTE RX <ant_id> - Mute RX antenna
|
* MUTE RX <ant_id> - Mute RX antenna
|
||||||
* UNMUTE RX <ant_id> - Unmute RX antenna
|
* UNMUTE RX <ant_id> - Unmute RX antenna
|
||||||
* SET TX MASK <hex> - Set TX antenna bitmask (O-RAN compliant)
|
* SET TX MASK <hex> - Set TX antenna bitmask
|
||||||
* SET RX MASK <hex> - Set RX antenna bitmask (O-RAN compliant)
|
* SET RX MASK <hex> - Set RX antenna bitmask
|
||||||
* STATUS - Get current antenna state
|
* STATUS - Get current antenna state
|
||||||
* STATS - Get energy saving statistics
|
* STATS - Get energy saving statistics
|
||||||
* RESET STATS - Reset statistics
|
* RESET STATS - Reset statistics
|
||||||
@@ -63,23 +113,66 @@ extern "C" {
|
|||||||
* @param ant_state Pointer to the antenna state to control
|
* @param ant_state Pointer to the antenna state to control
|
||||||
* @param port TCP port to listen on (use 0 for default)
|
* @param port TCP port to listen on (use 0 for default)
|
||||||
* @return true if server started successfully
|
* @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);
|
bool nr_antenna_control_start(nr_antenna_state_t *ant_state, int port);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Stop the antenna control server
|
* @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);
|
void nr_antenna_control_stop(void);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Check if antenna control server is running
|
* @brief Check if antenna control is active
|
||||||
* @return true if server is running
|
* @return true if control interface is running
|
||||||
*/
|
*/
|
||||||
bool nr_antenna_control_is_running(void);
|
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
|
#ifdef __cplusplus
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#endif /* NR_ANTENNA_CONTROL_H */
|
#endif /* NR_ANTENNA_CONTROL_H */
|
||||||
|
|
||||||
|
|||||||
@@ -203,7 +203,6 @@ void nr_feptx_prec(RU_t *ru, int frame_tx, int slot_tx)
|
|||||||
// Check if ALL TX antennas are muted (complete transmission stop)
|
// Check if ALL TX antennas are muted (complete transmission stop)
|
||||||
int active_tx_count = nr_get_active_tx_antenna_count(ant_state);
|
int active_tx_count = nr_get_active_tx_antenna_count(ant_state);
|
||||||
|
|
||||||
// If there is no digital beamforming we just need to copy the data to RU
|
|
||||||
if (ru->config.dbt_config.num_dig_beams == 0 || ru->gNB_list[0]->common_vars.analog_bf) {
|
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 < ru->nb_tx; ++i) {
|
for (int i = 0; i < ru->nb_tx; ++i) {
|
||||||
|
|||||||
+1
-1
Submodule openair2/E2AP/flexric updated: 8f449513c5...06cbce66e8
@@ -433,14 +433,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);
|
||||||
@@ -703,7 +703,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,
|
||||||
@@ -848,7 +848,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,
|
||||||
@@ -906,6 +906,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++)
|
||||||
@@ -999,7 +1025,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,30 @@ 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: When antennas are muted, the UE's PMI report
|
||||||
|
// is based on the full antenna array and may be invalid. Use identity matrix.
|
||||||
|
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;
|
||||||
|
if (active_tx < configured_tx) {
|
||||||
|
LOG_D(NR_MAC, "[ANTENNA_MUTE] Using identity matrix: %d/%d TX antennas active\n",
|
||||||
|
active_tx, configured_tx);
|
||||||
|
return 0; // Use identity matrix when any antennas are muted
|
||||||
|
}
|
||||||
|
}
|
||||||
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 +255,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 +287,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 +487,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.
|
||||||
@@ -3827,6 +3886,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);
|
||||||
|
|||||||
@@ -540,7 +540,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);
|
||||||
|
|||||||
@@ -2662,6 +2662,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);
|
||||||
|
|
||||||
|
|||||||
@@ -398,7 +398,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"
|
||||||
@@ -971,6 +972,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;
|
||||||
|
|||||||
@@ -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 */
|
||||||
|
|
||||||
@@ -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";
|
||||||
|
};
|
||||||
|
|
||||||
@@ -41,7 +41,7 @@ iperf_udp_ul_forever() { ( iperf -u -c "$IPERF_HOST_UDP" -B "$OAITUN_IP" -p "$IP
|
|||||||
|
|
||||||
# ---------------- profiles ----------------
|
# ---------------- profiles ----------------
|
||||||
do_boring() {
|
do_boring() {
|
||||||
iperf_udp_dl_forever 10M
|
iperf_udp_dl_forever 15M
|
||||||
wait
|
wait
|
||||||
# iperf -c "$IPERF_HOST" -p "$IPERF_PORT" -B "$OAITUN_IP" -u -b 25M -t "$LONG_T"
|
# iperf -c "$IPERF_HOST" -p "$IPERF_PORT" -B "$OAITUN_IP" -u -b 25M -t "$LONG_T"
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user