This commit is contained in:
2026-01-08 09:17:26 +00:00
parent 85fef10860
commit cf4ca86169
35 changed files with 2493 additions and 118 deletions
@@ -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 = {
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;
sm_dir = "/usr/local/lib/flexric/";
}
@@ -19,7 +19,7 @@ gNBs =
// 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 = 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;
@@ -235,12 +235,12 @@ servingCellConfigDedicated = ({
////////// 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 :
{
GNB_IPV4_ADDRESS_FOR_NG_AMF = "192.168.71.140";
GNB_IPV4_ADDRESS_FOR_NGU = "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.70.140"; # On full CN network
GNB_PORT_FOR_S1U = 2152; # Spec 2152
};
@@ -331,3 +331,6 @@ log_config :
ngap_log_level ="debug";
f1ap_log_level ="debug";
};
# Channel model for antenna muting capacity simulation
@include "/opt/oai-gnb/etc/channelmod_antenna_muting.conf"
@@ -0,0 +1,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;
# }
# );
# };
+190 -40
View File
@@ -8,30 +8,36 @@ It follows the `MPLANE-TRX-CONTROL` feature defined in `o-ran-uplane-conf.yang`.
## Architecture
```
┌─────────────────────────────────────────────────────────────────────┐
│ Control Interfaces │
│ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────────┐ │
│ │ O1 (NETCONF) │ │ Test Socket │ │ Future: rApp/A1 │ │
│ │ (Production) │ │ (Development) │ │ (Policy-based) │ │
│ └────────┬────────┘ └────────┬────────┘ └─────────┬───────────┘ │
│ │ │ │ │
│ └────────────────────┼─────────────────────┘ │
│ ▼ │
│ ┌──────────────────────────────────────────────────────────────┐ │
│ │ nr_antenna_state_t (Antenna State Manager) │ │
│ │ - tx_antenna_bitmask: O-RAN compliant antenna mask │ │
│ │ - rx_antenna_bitmask: O-RAN compliant antenna mask │ │
│ │ - Statistics for energy saving monitoring │ │
│ └──────────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────────────────────────────────────────────────────┐ │
│ │ PHY Layer Integration │ │
│ │ - nr_beam_precoding_with_muting(): Blanks muted antennas │ │
│ │ - Beamforming skipped for muted antennas │ │
│ │ - Output zeroed = transmission blank (O-RAN compliant) │ │
│ └──────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────────────┐
│ Control Interfaces │
│ │
│ ┌───────────────────────────┐ ┌─────────────────┐ ┌──────────────────┐ │
│ │ O-RAN M-Plane NETCONF │ │ Test Socket │ │ Future: rApp/A1 │ │
│ │ (Production/O-RAN) │ │ (Development) │ │ (Policy-based) │ │
│ │ │ │ │ │ │ │
│ │ Uses real NETCONF RPC: │ │ Simple TCP │ │ │ │
│ │ • edit-config │ │ socket on │ │ │ │
│ │ • validate │ │ port 9999 │ │ │ │
│ │ • commit │ │ │ │ │ │
│ └───────────┬───────────────┘ └────────┬────────┘ └────────┬─────────┘ │
│ │ │ │ │
│ └───────────────────────────┼────────────────────┘ │
│ ▼ │
│ ┌────────────────────────────────────────────────────────────────────────┐ │
│ │ nr_antenna_state_t (Antenna State Manager) │ │
│ │ - tx_antenna_bitmask: O-RAN compliant antenna mask │ │
│ │ - rx_antenna_bitmask: O-RAN compliant antenna mask │ │
│ │ - Statistics for energy saving monitoring │ │
│ └────────────────────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌────────────────────────────────────────────────────────────────────────┐ │
│ │ PHY Layer Integration │ │
│ │ - nr_beam_precoding_with_muting(): Blanks muted antennas │ │
│ │ - Beamforming skipped for muted antennas │ │
│ │ - Output zeroed = transmission blank (O-RAN compliant) │ │
│ └────────────────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────────────┘
```
## O-RAN Compliance
@@ -40,35 +46,106 @@ This implementation follows O-RAN Alliance specifications:
| 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` |
| `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 |
### YANG Model Paths Used
```yang
/o-ran-uplane-conf:user-plane-configuration/tx-array-antenna-mask-config
└── array-name # Reference to tx-arrays list
└── antenna-bitmask # Binary bitmask (bit N=1 means antenna N active)
/o-ran-uplane-conf:user-plane-configuration/rx-array-antenna-mask-config
└── array-name # Reference to rx-arrays list
└── antenna-bitmask # Binary bitmask
```
## Files
### Core State Management
- `openair1/PHY/NR_TRANSPORT/nr_antenna_state.h` - Antenna state API
- `openair1/PHY/NR_TRANSPORT/nr_antenna_state.c` - Implementation
- `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()`
## 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
#include "PHY/NR_TRANSPORT/nr_antenna_state.h"
#include "PHY/NR_TRANSPORT/nr_antenna_control.h"
// Initialize antenna state
nr_init_antenna_state(&gNB->antenna_state, 4, 4, "tx-array-0", "rx-array-0");
// Connect to M-Plane backend
nr_antenna_control_init_mplane(&gNB->antenna_state, ru_session);
// Changes via nr_set_tx_antenna_bitmask() will be synchronized to RU
nr_set_tx_antenna_bitmask(&gNB->antenna_state, 0x03); // Enable only antennas 0,1
nr_antenna_control_sync_to_ru(&gNB->antenna_state); // Push to RU via NETCONF
```
### Development Mode: Test Socket Interface
For testing without NETCONF infrastructure:
```c
#include "PHY/NR_TRANSPORT/nr_antenna_state.h"
#include "PHY/NR_TRANSPORT/nr_antenna_control.h"
// Initialize with 4 TX and 4 RX antennas
nr_init_antenna_state(&gNB->antenna_state, 4, 4, "tx-array-0", "rx-array-0");
// Optionally start test control server
#include "PHY/NR_TRANSPORT/nr_antenna_control.h"
nr_antenna_control_start(&gNB->antenna_state, 9999);
// Start test socket server (NOT O-RAN compliant - logs warning)
nr_antenna_control_start_test_server(&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:
```bash
@@ -82,10 +159,11 @@ Commands:
> MUTE TX 2 # Mute TX antenna 2
> MUTE TX 3 # Mute TX antenna 3
> SET TX MASK 03 # Set TX mask to 0x03 (antennas 0,1 active)
> SYNC # Sync to RU (M-Plane backend only)
> STATS # Show energy saving statistics
```
### 3. Use in Beamforming
### Use in Beamforming
```c
// Original (all antennas active):
@@ -97,6 +175,24 @@ nr_beam_precoding_with_muting(txdataF, txdataF_BF, fp, beam_weights,
&gNB->antenna_state);
```
## Building with M-Plane Support
To build with O-RAN M-Plane NETCONF support:
```bash
cd openairinterface5g/cmake_targets
./build_oai --ninja --gNB --nrUE -w ORAN_FH -t MPLANE
# Or manually:
cmake .. -DENABLE_ORAN_FH=ON -DORAN_FH_VARIANT=MPLANE
make -j$(nproc)
```
Dependencies for M-Plane:
- libnetconf2 >= 2.1.25
- libyang >= 2.1.4
- libssh
## Testing with rfsimulator
### Prerequisites
@@ -198,16 +294,70 @@ With 4x4 MIMO:
Throughput should decrease proportionally as antennas are muted.
## API Reference
### O-RAN M-Plane NETCONF API
```c
// Set TX antenna mask via NETCONF
antenna_ctrl_result_t mplane_set_tx_antenna_mask(
ru_session_t *ru_session, // Active NETCONF session
const char *array_name, // O-RAN TX array name
uint8_t antenna_bitmask, // Bitmask (bit N=1 -> antenna N active)
uint8_t num_antennas // Number of antennas in array
);
// Set RX antenna mask via NETCONF
antenna_ctrl_result_t mplane_set_rx_antenna_mask(
ru_session_t *ru_session,
const char *array_name,
uint8_t antenna_bitmask,
uint8_t num_antennas
);
// Check if RU supports MPLANE-TRX-CONTROL feature
bool mplane_supports_trx_control(ru_session_t *ru_session);
// Result codes
typedef enum {
ANTENNA_CTRL_SUCCESS = 0,
ANTENNA_CTRL_ERR_NO_SESSION,
ANTENNA_CTRL_ERR_FEATURE_NOT_SUPPORTED,
ANTENNA_CTRL_ERR_INVALID_ARRAY_NAME,
ANTENNA_CTRL_ERR_INVALID_MASK,
ANTENNA_CTRL_ERR_NETCONF_FAILED,
ANTENNA_CTRL_ERR_VALIDATION_FAILED,
ANTENNA_CTRL_ERR_COMMIT_FAILED
} antenna_ctrl_result_t;
```
### Unified Control Interface
```c
// Initialize with O-RAN M-Plane NETCONF backend (production)
bool nr_antenna_control_init_mplane(nr_antenna_state_t *ant_state, void *ru_session);
// Start test socket server (development - NOT O-RAN compliant)
bool nr_antenna_control_start_test_server(nr_antenna_state_t *ant_state, int port);
// Sync local state to RU (M-Plane backend only)
bool nr_antenna_control_sync_to_ru(nr_antenna_state_t *ant_state);
// Get current backend type
nr_antenna_ctrl_backend_t nr_antenna_control_get_backend(void);
```
## Future Work
1. **O1 NETCONF Integration**: Integrate with `radio/fhi_72/mplane/` for production O-RAN compliance
2. **rApp Development**: Create Non-RT RIC rApp for policy-based antenna control
3. **A1 Policy Interface**: Enable policy-based control from SMO
4. **Power Measurement**: Add actual power consumption metrics (requires hardware)
1. ~~**O1 NETCONF Integration**: Integrate with `radio/fhi_72/mplane/` for production O-RAN compliance~~ ✅ **DONE**
2. **NETCONF Notifications**: Subscribe to `mplane-trx-control-ant-mask-update` notifications
3. **rApp Development**: Create Non-RT RIC rApp for policy-based antenna control
4. **A1 Policy Interface**: Enable policy-based control from SMO
5. **Power Measurement**: Add actual power consumption metrics (requires hardware)
## References
- O-RAN WG4 M-Plane Specification
- O-RAN WG4 M-Plane Specification (O-RAN.WG4.MP.0-v10.00)
- `o-ran-uplane-conf.yang` - YANG model for U-plane configuration
- `o-ran-wg4-features.yang` - Feature definitions including `MPLANE-TRX-CONTROL`
- `radio/fhi_72/mplane/yang/models/` - O-RAN YANG models in this repository
+56
View File
@@ -0,0 +1,56 @@
FROM ran-base:latest AS ran-build
ARG BUILD_OPTION
RUN rm -Rf /oai-ran
WORKDIR /oai-ran
COPY . .
# build all targets so final targets can be created afterwards
RUN /bin/sh oaienv && \
cd cmake_targets && \
mkdir -p log && \
./build_oai -c --ninja \
--gNB --RU --nrUE -w SIMU\
--build-lib "telnetsrv" \
-w USRP -t Ethernet \
--build-e2 --cmake-opt -DKPM_VERSION=KPM_V3_00 --cmake-opt -DE2AP_VERSION=E2AP_V3 \
--noavx512 \
--build-tool-opt -k10 \
--cmake-opt -DCMAKE_C_FLAGS="-Werror" --cmake-opt -DCMAKE_CXX_FLAGS="-Werror" $BUILD_OPTION && \
echo "---- ldd on executables ----" && \
ldd ran_build/build/*softmodem* ran_build/build/nr-cuup ran_build/build/oairu && \
echo "---- ldd on shared libraries ----" && \
ldd ran_build/build/*.so
RUN set -eux; \
apt-get update; \
DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
ca-certificates curl wget iproute2 iputils-ping dnsutils jq \
iperf iperf3 ffmpeg gdb\
nghttp2-client || true; \
if ! command -v h2load >/dev/null 2>&1; then \
DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends nghttp2 || true; \
fi; \
# Try to install sipp from apt; if unavailable, build from source
if apt-cache show sipp >/dev/null 2>&1; then \
DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends sipp; \
else \
echo "sipp not in apt, building from source..."; \
DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
build-essential cmake git libncurses5-dev libpcap-dev libssl-dev libpcre3-dev; \
git clone --depth=1 https://github.com/SIPp/sipp.git /tmp/sipp; \
cd /tmp/sipp; cmake -DENABLE_SSL=1 -DENABLE_PCAP=1 -DCMAKE_BUILD_TYPE=Release .; \
make -j"$(nproc)"; make install; strip /usr/local/bin/sipp || true; \
rm -rf /tmp/sipp; \
fi; \
rm -rf /var/lib/apt/lists/*
# Traffic runner
COPY tools/scripts/traffic_runner.sh /usr/local/bin/traffic_runner
RUN chmod +x /usr/local/bin/traffic_runner
COPY flexric/etc/flexric /usr/local/etc/flexric/
COPY flexric/lib/flexric /usr/local/lib/flexric/
RUN echo "/usr/local/lib/flexric" > /etc/ld.so.conf.d/flexric.conf && ldconfig
ENTRYPOINT ["/bin/bash"]
+111
View File
@@ -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"]
+4 -4
View File
@@ -49,12 +49,12 @@ RUN set -eux; \
COPY tools/scripts/traffic_runner.sh /usr/local/bin/traffic_runner
RUN chmod +x /usr/local/bin/traffic_runner
# FlexRIC libraries (built by --build-e2)
# FlexRIC libraries (built by --build-e2) - only copy service model libraries (*_sm.so)
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 && \
cp -r /oai-ran/openair2/E2AP/flexric/build/src/sm/*.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 /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
RUN apt-get update && apt-get install -y netcat-openbsd && rm -rf /var/lib/apt/lists/*
+8
View File
@@ -48,6 +48,7 @@
#include "NR_PHY_INTERFACE/NR_IF_Module.h"
#include "PHY/INIT/nr_phy_init.h"
#include "PHY/MODULATION/nr_modulation.h"
#include "openair2/LAYER2/NR_MAC_gNB/nr_mac_gNB.h"
#include "PHY/NR_TRANSPORT/nr_antenna_control.h"
#include "PHY/NR_TRANSPORT/nr_transport_proto.h"
#include "PHY/TOOLS/tools_defs.h"
@@ -411,6 +412,13 @@ void init_eNB_afterRU(void)
if (nr_antenna_control_start(&gNB->antenna_state, 9999)) {
LOG_I(PHY, "[ANTENNA_CTRL] Test control server started on port 9999\n");
}
// Connect MAC's antenna_state pointer to PHY's antenna_state
// This allows the scheduler to adapt MIMO layers based on active antennas
if (inst < RC.nb_nr_macrlc_inst && RC.nrmac && RC.nrmac[inst]) {
RC.nrmac[inst]->antenna_state = &gNB->antenna_state;
LOG_I(PHY, "[ANTENNA_STATE] Connected MAC scheduler to PHY antenna state for dynamic layer adaptation\n");
}
}
}
-10
View File
@@ -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.
+226 -22
View File
@@ -20,10 +20,10 @@
*/
/*! \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
* \date 2025
* \version 1.0
* \version 2.0
*/
#include "nr_antenna_control.h"
@@ -40,15 +40,31 @@
#include <errno.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 {
pthread_t thread;
int server_fd;
nr_antenna_state_t *ant_state;
volatile bool running;
int port;
nr_antenna_ctrl_backend_t backend;
#ifdef OAI_MPLANE
ru_session_t *ru_session;
#else
void *ru_session;
#endif
} g_ctrl = {0};
// ============================================================================
// Test Socket Server Implementation (Development/Testing Mode)
// ============================================================================
// Process a single command and generate response
static void process_command(const char *cmd, char *response, size_t resp_size)
{
@@ -113,7 +129,8 @@ static void process_command(const char *cmd, char *response, size_t resp_size)
" Bitmask: 0x%02X\n"
"RX Array: %s\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->num_tx_antennas,
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->num_rx_antennas,
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) {
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);
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) {
snprintf(response, resp_size,
"=== O-RAN Antenna Control Test Interface ===\n"
"=== O-RAN Antenna Control Interface ===\n"
"Commands:\n"
" MUTE TX <ant_id> - Mute TX antenna (0-based)\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"
" STATS - Show energy saving 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"
" QUIT - Close connection\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) {
snprintf(response, resp_size, "BYE\n");
@@ -172,10 +218,11 @@ static void handle_client(int client_fd)
char buffer[256];
char response[1024];
// Send welcome message
// Send welcome message with warning about non-compliance
const char *welcome =
"=== OAI Antenna Control Server ===\n"
"O-RAN WG4 MPLANE-TRX-CONTROL Test Interface\n"
"=== OAI Antenna Control Test Server ===\n"
"WARNING: This is a TEST interface, NOT O-RAN compliant!\n"
"For production, use NETCONF via radio/fhi_72/mplane/\n"
"Type HELP for commands.\n\n";
send(client_fd, welcome, strlen(welcome), 0);
@@ -216,7 +263,7 @@ static void *server_thread(void *arg)
struct sockaddr_in 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) {
// Accept connection with timeout
@@ -241,10 +288,50 @@ static void *server_thread(void *arg)
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) {
LOG_W(PHY, "[ANTENNA_CTRL] Server already running\n");
LOG_W(PHY, "[ANTENNA_CTRL] Control interface already active\n");
return false;
}
@@ -253,8 +340,15 @@ bool nr_antenna_control_start(nr_antenna_state_t *ant_state, int port)
return false;
}
LOG_W(PHY, "[ANTENNA_CTRL] ======================================================\n");
LOG_W(PHY, "[ANTENNA_CTRL] Starting TEST socket server - NOT O-RAN COMPLIANT!\n");
LOG_W(PHY, "[ANTENNA_CTRL] For production O-RAN compliance, use M-Plane NETCONF\n");
LOG_W(PHY, "[ANTENNA_CTRL] See: radio/fhi_72/mplane/antenna-control-mplane.h\n");
LOG_W(PHY, "[ANTENNA_CTRL] ======================================================\n");
g_ctrl.ant_state = ant_state;
g_ctrl.port = (port > 0) ? port : NR_ANTENNA_CONTROL_PORT;
g_ctrl.port = (port > 0) ? port : NR_ANTENNA_CONTROL_TEST_PORT;
g_ctrl.backend = ANTENNA_CTRL_BACKEND_TEST_SOCKET;
// Create socket
g_ctrl.server_fd = socket(AF_INET, SOCK_STREAM, 0);
@@ -296,28 +390,43 @@ bool nr_antenna_control_start(nr_antenna_state_t *ant_state, int port)
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);
return true;
}
bool nr_antenna_control_start(nr_antenna_state_t *ant_state, int port)
{
LOG_W(PHY, "[ANTENNA_CTRL] nr_antenna_control_start() is DEPRECATED\n");
LOG_W(PHY, "[ANTENNA_CTRL] Use nr_antenna_control_init_mplane() for O-RAN compliance\n");
LOG_W(PHY, "[ANTENNA_CTRL] or nr_antenna_control_start_test_server() for testing\n");
return nr_antenna_control_start_test_server(ant_state, port);
}
void nr_antenna_control_stop(void)
{
if (!g_ctrl.running) return;
g_ctrl.running = false;
// Close server socket to unblock accept()
if (g_ctrl.server_fd >= 0) {
close(g_ctrl.server_fd);
g_ctrl.server_fd = -1;
if (g_ctrl.backend == ANTENNA_CTRL_BACKEND_TEST_SOCKET) {
// Close server socket to unblock accept()
if (g_ctrl.server_fd >= 0) {
close(g_ctrl.server_fd);
g_ctrl.server_fd = -1;
}
// Wait for thread
pthread_join(g_ctrl.thread, NULL);
}
// Wait for thread
pthread_join(g_ctrl.thread, NULL);
g_ctrl.backend = ANTENNA_CTRL_BACKEND_NONE;
g_ctrl.ant_state = NULL;
g_ctrl.ru_session = NULL;
LOG_I(PHY, "[ANTENNA_CTRL] Server stopped\n");
LOG_I(PHY, "[ANTENNA_CTRL] Control interface stopped\n");
}
bool nr_antenna_control_is_running(void)
@@ -325,3 +434,98 @@ bool nr_antenna_control_is_running(void)
return g_ctrl.running;
}
nr_antenna_ctrl_backend_t nr_antenna_control_get_backend(void)
{
return g_ctrl.backend;
}
bool nr_antenna_control_sync_to_ru(nr_antenna_state_t *ant_state)
{
#ifdef OAI_MPLANE
if (g_ctrl.backend != ANTENNA_CTRL_BACKEND_MPLANE || g_ctrl.ru_session == NULL) {
LOG_W(PHY, "[ANTENNA_CTRL] M-Plane backend not active, cannot sync to RU\n");
return false;
}
if (ant_state == NULL) {
ant_state = g_ctrl.ant_state;
}
if (ant_state == NULL) {
return false;
}
// Send TX antenna mask to RU
antenna_ctrl_result_t result;
result = mplane_set_tx_antenna_mask(g_ctrl.ru_session,
ant_state->tx_array_name,
nr_get_tx_antenna_bitmask(ant_state),
ant_state->num_tx_antennas);
if (result != ANTENNA_CTRL_SUCCESS) {
LOG_W(PHY, "[ANTENNA_CTRL] Failed to sync TX mask: %s\n",
antenna_ctrl_result_str(result));
return false;
}
// Send RX antenna mask to RU
result = mplane_set_rx_antenna_mask(g_ctrl.ru_session,
ant_state->rx_array_name,
nr_get_rx_antenna_bitmask(ant_state),
ant_state->num_rx_antennas);
if (result != ANTENNA_CTRL_SUCCESS) {
LOG_W(PHY, "[ANTENNA_CTRL] Failed to sync RX mask: %s\n",
antenna_ctrl_result_str(result));
return false;
}
LOG_I(PHY, "[ANTENNA_CTRL] Successfully synchronized antenna masks to RU\n");
return true;
#else
LOG_W(PHY, "[ANTENNA_CTRL] M-Plane not compiled in\n");
return false;
#endif
}
bool nr_antenna_control_sync_from_ru(nr_antenna_state_t *ant_state)
{
#ifdef OAI_MPLANE
if (g_ctrl.backend != ANTENNA_CTRL_BACKEND_MPLANE || g_ctrl.ru_session == NULL) {
LOG_W(PHY, "[ANTENNA_CTRL] M-Plane backend not active, cannot sync from RU\n");
return false;
}
if (ant_state == NULL) {
ant_state = g_ctrl.ant_state;
}
if (ant_state == NULL) {
return false;
}
uint8_t tx_mask, rx_mask;
antenna_ctrl_result_t result;
// Get TX antenna mask from RU
result = mplane_get_tx_antenna_mask(g_ctrl.ru_session,
ant_state->tx_array_name,
&tx_mask);
if (result == ANTENNA_CTRL_SUCCESS) {
nr_set_tx_antenna_bitmask(ant_state, tx_mask);
}
// Get RX antenna mask from RU
result = mplane_get_rx_antenna_mask(g_ctrl.ru_session,
ant_state->rx_array_name,
&rx_mask);
if (result == ANTENNA_CTRL_SUCCESS) {
nr_set_rx_antenna_bitmask(ant_state, rx_mask);
}
LOG_I(PHY, "[ANTENNA_CTRL] Synchronized antenna masks from RU: TX=0x%02X, RX=0x%02X\n",
tx_mask, rx_mask);
return true;
#else
LOG_W(PHY, "[ANTENNA_CTRL] M-Plane not compiled in\n");
return false;
#endif
}
+110 -17
View File
@@ -20,13 +20,29 @@
*/
/*! \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
* \date 2025
* \version 1.0
* \note This provides a simple socket-based interface to test antenna muting
* without requiring a full NETCONF/M-plane implementation.
* In production, use the O-RAN O1 interface via radio/fhi_72/mplane/
* \version 2.0
*
* This module provides antenna control for MIMO muting / energy saving.
*
* Two modes of operation are supported:
*
* 1. O-RAN M-PLANE NETCONF (Production/O-RAN Compliant):
* When linked with the M-Plane library (radio/fhi_72/mplane/), uses
* real NETCONF sessions to control antenna masks via O-RAN YANG models:
* - o-ran-uplane-conf.yang tx-array-antenna-mask-config
* - o-ran-uplane-conf.yang rx-array-antenna-mask-config
* - Requires MPLANE-TRX-CONTROL feature support on RU
*
* 2. Test Socket Interface (Development/Testing):
* A simple TCP socket interface for testing without NETCONF infrastructure.
* NOT O-RAN compliant, but useful for development and simulation.
* Enable with nr_antenna_control_start_test_server().
*
* For O-RAN compliant deployments, use the M-Plane interface directly
* via radio/fhi_72/mplane/antenna-control-mplane.h
*/
#ifndef NR_ANTENNA_CONTROL_H
@@ -39,22 +55,56 @@
extern "C" {
#endif
/// Default port for the antenna control server
#define NR_ANTENNA_CONTROL_PORT 9999
/// Default port for the test socket server (development mode only)
#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 simulates the O-RAN O1 interface for testing purposes.
* This is a forward declaration for ru_session_t from the M-Plane library.
* When the M-Plane library is linked, this allows direct NETCONF control.
*/
typedef struct ru_session ru_session_t;
/**
* @brief Initialize antenna control with O-RAN M-Plane NETCONF backend
*
* 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
* UNMUTE TX <ant_id> - Unmute TX antenna
* MUTE RX <ant_id> - Mute RX antenna
* UNMUTE RX <ant_id> - Unmute RX antenna
* SET TX MASK <hex> - Set TX antenna bitmask (O-RAN compliant)
* SET RX MASK <hex> - Set RX antenna bitmask (O-RAN compliant)
* SET TX MASK <hex> - Set TX antenna bitmask
* SET RX MASK <hex> - Set RX antenna bitmask
* STATUS - Get current antenna state
* STATS - Get energy saving statistics
* RESET STATS - Reset statistics
@@ -63,23 +113,66 @@ extern "C" {
* @param ant_state Pointer to the antenna state to control
* @param port TCP port to listen on (use 0 for default)
* @return true if server started successfully
*
* @note This function logs a warning that it's not O-RAN compliant
*/
bool nr_antenna_control_start_test_server(nr_antenna_state_t *ant_state, int port);
/**
* @brief Legacy function - start antenna control server
*
* @deprecated Use nr_antenna_control_init_mplane() for O-RAN compliance
* or nr_antenna_control_start_test_server() for testing.
*
* This function starts the test socket server for backwards compatibility.
* It logs a deprecation warning.
*/
bool nr_antenna_control_start(nr_antenna_state_t *ant_state, int port);
/**
* @brief Stop the antenna control 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);
/**
* @brief Check if antenna control server is running
* @return true if server is running
* @brief Check if antenna control is active
* @return true if control interface is running
*/
bool nr_antenna_control_is_running(void);
/**
* @brief Get current antenna control backend type
* @return Backend type currently in use
*/
nr_antenna_ctrl_backend_t nr_antenna_control_get_backend(void);
/**
* @brief Synchronize local antenna state to RU via M-Plane NETCONF
*
* Sends the current antenna bitmask configuration to the RU.
* Only applicable when using the M-Plane NETCONF backend.
*
* @param ant_state Pointer to the antenna state
* @return true if synchronization successful or not applicable
*/
bool nr_antenna_control_sync_to_ru(nr_antenna_state_t *ant_state);
/**
* @brief Synchronize RU antenna state to local via M-Plane NETCONF
*
* Reads the current antenna bitmask from the RU and updates local state.
* Only applicable when using the M-Plane NETCONF backend.
*
* @param ant_state Pointer to the antenna state
* @return true if synchronization successful or not applicable
*/
bool nr_antenna_control_sync_from_ru(nr_antenna_state_t *ant_state);
#ifdef __cplusplus
}
#endif
#endif /* NR_ANTENNA_CONTROL_H */
-1
View File
@@ -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)
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) {
for (int b = 0; b < ru->num_beams_period; b++) {
for (int i = 0; i < ru->nb_tx; ++i) {
@@ -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_UL_BWP_t *ul_bwp = &UE->current_UL_BWP;
NR_sched_pdsch_t new_sched = sched_ctrl->harq_processes[current_harq_pid].sched_pdsch;
int layers = get_dl_nrOfLayers(sched_ctrl, dl_bwp->dci_format);
int pm_index = get_pm_index(nr_mac, UE, dl_bwp->dci_format, layers, nr_mac->radio_config.pdsch_AntennaPorts.XP);
int layers = get_dl_nrOfLayers(nr_mac, sched_ctrl, dl_bwp->dci_format);
// If the RI changed between current rtx and a previous transmission
// we need to verify if it is not decreased
// othwise it wouldn't be possible to transmit the same TBS
layers = (layers < new_sched.nrOfLayers) ? layers : new_sched.nrOfLayers;
pm_index = (layers < new_sched.nrOfLayers) ? pm_index : new_sched.pm_index;
// Recalculate PMI with the final layer count to ensure consistency
int pm_index = (layers < new_sched.nrOfLayers) ? get_pm_index(nr_mac, UE, dl_bwp->dci_format, layers, nr_mac->radio_config.pdsch_AntennaPorts.XP) : new_sched.pm_index;
const int coresetid = sched_ctrl->coreset->controlResourceSetId;
const int tda = get_dl_tda(nr_mac, slot);
@@ -703,7 +703,7 @@ static void pf_dl(gNB_MAC_INST *mac,
sched_ctrl->dl_bler_stats.mcs = selected_mcs;
} else
selected_mcs = get_mcs_from_bler(bo, stats, &sched_ctrl->dl_bler_stats, max_mcs, frame);
int l = get_dl_nrOfLayers(sched_ctrl, current_BWP->dci_format);
int l = get_dl_nrOfLayers(mac, sched_ctrl, current_BWP->dci_format);
const uint8_t Qm = nr_get_Qm_dl(selected_mcs, current_BWP->mcsTableIdx);
const uint16_t R = nr_get_code_rate_dl(selected_mcs, current_BWP->mcsTableIdx);
uint32_t tbs = nr_compute_tbs(Qm,
@@ -848,7 +848,7 @@ static void pf_dl(gNB_MAC_INST *mac,
sched_ctrl->cce_index = CCEIndex;
fill_pdcch_vrb_map(mac, CC_id, &sched_ctrl->sched_pdcch, CCEIndex, sched_ctrl->aggregation_level, beam.idx);
int l = get_dl_nrOfLayers(sched_ctrl, dl_bwp->dci_format);
int l = get_dl_nrOfLayers(mac, sched_ctrl, dl_bwp->dci_format);
NR_sched_pdsch_t sched_pdsch = {
// rbSize below
.rbStart = rbStart,
@@ -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;
int bw = scc->downlinkConfigCommon->frequencyInfoDL->scs_SpecificCarrierList.list.array[0]->carrierBandwidth;
// O-RAN MPLANE-TRX-CONTROL: Scale available resources based on active antennas
// This simulates MIMO capacity reduction when antennas are muted
if (mac->antenna_state) {
int active_tx = nr_get_active_tx_antenna_count(mac->antenna_state);
int configured_tx = mac->antenna_state->num_tx_antennas;
// If all TX antennas are muted, skip DL user scheduling entirely
// (SSB/SIB1 are still transmitted by PHY but no user data)
if (active_tx == 0) {
static int mute_log_counter = 0;
if (mute_log_counter++ % 10000 == 0)
LOG_W(NR_MAC, "[ANTENNA_MUTE] All TX antennas muted, skipping DL user scheduling\n");
return;
}
if (active_tx < configured_tx && configured_tx > 0) {
// Scale bandwidth proportionally to active antenna ratio
int scaled_bw = (bw * active_tx) / configured_tx;
if (scaled_bw < 5) scaled_bw = 5; // Minimum for SSB
static int log_counter = 0;
if (log_counter++ % 1000 == 0) // Log every 1000 calls to avoid spam
LOG_I(NR_MAC, "[ANTENNA_MUTE] Scaling DL capacity: %d/%d antennas -> %d/%d PRBs\n",
active_tx, configured_tx, scaled_bw, bw);
bw = scaled_bw;
}
}
int num_beams = mac->beam_info.beam_allocation ? mac->beam_info.beams_per_period : 1;
int n_rb_sched[num_beams];
for (int i = 0; i < num_beams; i++)
@@ -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.prg_size = pdsch_pdu->rbSize;
pdsch_pdu->precodingAndBeamforming.dig_bf_interfaces = 1;
pdsch_pdu->precodingAndBeamforming.prgs_list[0].pm_idx = sched_pdsch->pm_index;
// Defensive check: if pm_index > 0, verify it's in the valid range for nrOfLayers
// to prevent PHY crash from layer/PMI mismatch
int pm_idx = sched_pdsch->pm_index;
if (pm_idx > 0 && mac) {
int layers = sched_pdsch->nrOfLayers;
int prev_layers_size = 0;
for (int i = 1; i < layers; i++)
prev_layers_size += mac->precoding_matrix_size[i - 1];
int max_valid = prev_layers_size + mac->precoding_matrix_size[layers - 1];
if (pm_idx < 1 + prev_layers_size || pm_idx > max_valid) {
LOG_E(NR_MAC, "PM index %d invalid for %d layers (valid range: %d-%d), forcing identity matrix\n",
pm_idx, layers, 1 + prev_layers_size, max_valid);
pm_idx = 0; // Use identity matrix to prevent crash
}
}
pdsch_pdu->precodingAndBeamforming.prgs_list[0].pm_idx = pm_idx;
pdsch_pdu->precodingAndBeamforming.prgs_list[0].dig_bf_interface_list[0].beam_idx = beam_index;
return pdsch_pdu;
}
@@ -53,6 +53,9 @@
#include "SIMULATION/TOOLS/sim.h" // for taus
// Forward declaration for RLF handler defined in mac_rrc_dl_handler.c
void gnb_rlf_handler(int rnti);
#define ENABLE_MAC_PAYLOAD_DEBUG
#define DEBUG_gNB_SCHEDULER 1
@@ -127,14 +130,28 @@ static void determine_aggregation_level_search_order(int agg_level_search_order[
static int nr_mac_interrupt_ue_transmission(gNB_MAC_INST *mac, NR_UE_info_t *UE, int slots, int slots_per_frame);
uint8_t get_dl_nrOfLayers(const NR_UE_sched_ctrl_t *sched_ctrl, const nr_dci_format_t dci_format)
uint8_t get_dl_nrOfLayers(const gNB_MAC_INST *mac, const NR_UE_sched_ctrl_t *sched_ctrl, const nr_dci_format_t dci_format)
{
// TODO check this but it should be enough for now
// if there is not csi report activated RI is 0 from initialization
int requested_layers;
if(dci_format == NR_DL_DCI_FORMAT_1_0)
return 1;
requested_layers = 1;
else
return sched_ctrl->CSI_report.cri_ri_li_pmi_cqi_report.ri + 1;
requested_layers = sched_ctrl->CSI_report.cri_ri_li_pmi_cqi_report.ri + 1;
// O-RAN MPLANE-TRX-CONTROL: cap layers to active TX antennas
if (mac && mac->antenna_state) {
int effective = nr_get_effective_mimo_layers(mac->antenna_state, requested_layers);
// Ensure at least 1 layer (scheduler should skip if all antennas muted)
if (effective < 1) effective = 1;
if (effective < requested_layers) {
LOG_D(NR_MAC, "[ANTENNA_MUTE] Capping layers from %d to %d due to muted antennas\n",
requested_layers, effective);
}
return effective;
}
return requested_layers;
}
int get_ul_nrOfLayers(const NR_UE_sched_ctrl_t *sched_ctrl, const nr_dci_format_t dci_format)
@@ -193,7 +210,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)
return 0; //identity matrix (basic 5G configuration handled by PMI report is with XP antennas)
const NR_UE_sched_ctrl_t *sched_ctrl = &UE->UE_sched_ctrl;
// The UE's PMI report (x1/x2) was calculated for RI+1 layers.
// If we're using a different number of layers, the PMI values are invalid.
// Use identity matrix in this case to avoid layer/PMI mismatch assertion.
int reported_ri_layers = sched_ctrl->CSI_report.cri_ri_li_pmi_cqi_report.ri + 1;
if (layers != reported_ri_layers) {
LOG_D(NR_MAC, "PMI mismatch: using %d layers but RI report is for %d layers, using identity matrix\n",
layers, reported_ri_layers);
return 0;
}
// O-RAN MPLANE-TRX-CONTROL: 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 nr_csi_report_t *csi_report = &UE->csi_report_template[report_id];
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
// elements from 1 to n for 1 layer
// elements from n+1 to m for 2 layers etc.
if (antenna_ports == 2)
return 1 + prev_layers_size + x2; // 0 for identity matrix
else {
int pm_index;
if (antenna_ports == 2) {
pm_index = 1 + prev_layers_size + x2; // 0 for identity matrix
} else {
int idx = layers - 1;
// the order of i1x in X1 report needs to be verified
// the standard is not very clear (Table 6.3.1.1.2-7 in 38.212)
@@ -246,8 +287,18 @@ uint16_t get_pm_index(const gNB_MAC_INST *nrmac,
get_K1_K2(N1, N2, &K1, &K2, layers);
// computing precoding matrix index according to rule set in allocation function init_codebook_gNB
lay_index = i2 + (i11 * max_i2) + (i12 * max_i2 * N1 * O1) + (k1 * max_i2 * N1 * O1 * N2 * O2) + (k2 * max_i2 * N1 * O1 * N2 * O2 * K1);
return 1 + prev_layers_size + lay_index;
pm_index = 1 + prev_layers_size + lay_index;
}
// Bounds check: ensure pm_index is within valid range for the specified layers
// The valid range for 'layers' is [1 + prev_layers_size, 1 + prev_layers_size + precoding_matrix_size[layers-1] - 1]
int max_valid_pm = prev_layers_size + nrmac->precoding_matrix_size[layers - 1];
if (pm_index < 1 || pm_index > max_valid_pm) {
LOG_W(NR_MAC, "PM index %d out of range [1, %d] for %d layers, using identity matrix\n",
pm_index, max_valid_pm, layers);
return 0;
}
return pm_index;
}
// look-up table for AMC. Based on BLER vs SNR curves from the nr_dlsim simulation
@@ -436,16 +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);
}
// If looking for UE-specific SearchSpace but no dedicated BWP is configured,
// fall back to common SearchSpace (type 0 or 1)
NR_SearchSpace__searchSpaceType_PR fallback_ss = target_ss;
if (target_ss == NR_SearchSpace__searchSpaceType_PR_ue_Specific) {
LOG_D(NR_MAC, "UE-specific SearchSpace requested but no dedicated BWP configured, falling back to common SearchSpace\n");
fallback_ss = NR_SearchSpace__searchSpaceType_PR_common;
}
const int n = scc->downlinkConfigCommon->initialDownlinkBWP->pdcch_ConfigCommon->choice.setup->commonSearchSpaceList->list.count;
for (int i = 0; i < n; i++) {
NR_SearchSpace_t *ss =
scc->downlinkConfigCommon->initialDownlinkBWP->pdcch_ConfigCommon->choice.setup->commonSearchSpaceList->list.array[i];
if (ss->searchSpaceType->present == target_ss) {
if (ss->searchSpaceType->present == fallback_ss) {
AssertFatal(ss->controlResourceSetId, "searchSpaceId %ld has a NULL controlResourceSetId\n", ss->searchSpaceId);
return ss;
}
}
AssertFatal(false, "Couldn't find an adequate SearchSpace for target SearchSpace %d in ServingCellConfigCommon\n", target_ss);
AssertFatal(false, "Couldn't find an adequate SearchSpace for target SearchSpace %d (fallback %d) in ServingCellConfigCommon\n", target_ss, fallback_ss);
}
/// @brief Get CORESET resource block parameters from frequency domain resources bitmap.
@@ -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];
DevAssert(bearer->servedRadioBearer->choice.srb_Identity == srb_id);
nr_rlc_add_srb(UE->rnti, bearer->servedRadioBearer->choice.srb_Identity, bearer);
// Register RLF handler for SRB1 to handle max retransmissions
nr_rlc_set_rlf_handler(UE->rnti, gnb_rlf_handler);
int priority = bearer->mac_LogicalChannelConfig->ul_SpecificParameters->priority;
nr_lc_config_t c = {.lcid = bearer->logicalChannelIdentity, .priority = priority};
nr_mac_add_lcid(&UE->UE_sched_ctrl, &c);
@@ -540,7 +540,7 @@ static void evaluate_sinr_report(gNB_MAC_INST *nrmac,
stats->num_sinr_meas++;
const int mcs_table = UE->current_DL_BWP.mcsTableIdx;
const int nrOfLayers = get_dl_nrOfLayers(sched_ctrl, UE->current_DL_BWP.dci_format);
const int nrOfLayers = get_dl_nrOfLayers(nrmac, sched_ctrl, UE->current_DL_BWP.dci_format);
sched_ctrl->dl_max_mcs = get_mcs_from_SINRx10(mcs_table, sinr_report->SINRx10[0], nrOfLayers);
LOG_D(MAC, "Reported SSB-SINR = %d.%d, dl_max_mcs %d\n", sinr_report->SINRx10[0] / 10, sinr_report->SINRx10[0] % 10, sched_ctrl->dl_max_mcs);
@@ -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 bw = scc->uplinkConfigCommon->frequencyInfoUL->scs_SpecificCarrierList.list.array[0]->carrierBandwidth;
// O-RAN MPLANE-TRX-CONTROL: Scale UL resources based on active RX antennas
// This simulates receive diversity reduction when antennas are muted
if (nr_mac->antenna_state) {
int active_rx = nr_get_active_rx_antenna_count(nr_mac->antenna_state);
int configured_rx = nr_mac->antenna_state->num_rx_antennas;
// If all RX antennas are muted, skip UL user scheduling entirely
if (active_rx == 0) {
static int mute_log_counter = 0;
if (mute_log_counter++ % 10000 == 0)
LOG_W(NR_MAC, "[ANTENNA_MUTE] All RX antennas muted, skipping UL user scheduling\n");
return;
}
if (active_rx < configured_rx && configured_rx > 0) {
int scaled_bw = (bw * active_rx) / configured_rx;
if (scaled_bw < 5) scaled_bw = 5;
LOG_D(NR_MAC, "[ANTENNA_MUTE] Scaling UL capacity: %d/%d antennas -> %d/%d PRBs\n",
active_rx, configured_rx, scaled_bw, bw);
bw = scaled_bw;
}
}
int average_agg_level = 4; // TODO find a better estimation
int max_dci = bw / (average_agg_level * NR_NB_REG_PER_CCE);
+1 -1
View File
@@ -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);
uint8_t get_mcs_from_cqi(int mcs_table, int cqi_table, int cqi_idx);
uint8_t get_dl_nrOfLayers(const NR_UE_sched_ctrl_t *sched_ctrl, const nr_dci_format_t dci_format);
uint8_t get_dl_nrOfLayers(const gNB_MAC_INST *mac, const NR_UE_sched_ctrl_t *sched_ctrl, const nr_dci_format_t dci_format);
int get_ul_nrOfLayers(const NR_UE_sched_ctrl_t *sched_ctrl, const nr_dci_format_t dci_format);
void free_sched_pucch_list(NR_UE_sched_ctrl_t *sched_ctrl);
@@ -53,6 +53,34 @@ static instance_t get_f1_gtp_instance(void)
return inst->gtpInst;
}
/**
* @brief RLF handler for gNB: called when RLC max retransmissions reached
* @param rnti RNTI of the UE experiencing RLF
*/
void gnb_rlf_handler(int rnti)
{
if (RC.nrmac == NULL || RC.nrmac[0] == NULL) {
LOG_E(RLC, "UE %04x: RLF detected but MAC not initialized\n", rnti);
return;
}
gNB_MAC_INST *mac = RC.nrmac[0];
NR_UE_info_t *UE = find_nr_UE(&mac->UE_info, rnti);
if (UE == NULL) {
LOG_W(RLC, "UE %04x: RLF detected but UE not found in connected list\n", rnti);
return;
}
LOG_W(NR_MAC, "UE %04x: RLF detected on SRB1, requesting UE release\n", rnti);
// Request UE release through proper channels
bool requested = nr_mac_request_release_ue(mac, rnti);
if (!requested) {
LOG_W(NR_MAC, "UE %04x: Could not request release, releasing directly\n", rnti);
nr_mac_release_ue(mac, rnti);
}
}
static int drb_gtpu_create(instance_t instance,
uint32_t ue_id,
int incoming_id,
@@ -247,6 +275,10 @@ static int handle_ue_context_srbs_setup(NR_UE_info_t *UE,
const f1ap_srb_to_setup_t *srb = &req_srbs[i];
NR_RLC_BearerConfig_t *rlc_BearerConfig = get_bearerconfig_from_srb(srb, rlc_config);
nr_rlc_add_srb(UE->rnti, srb->id, rlc_BearerConfig);
// Register RLF handler for SRB to handle max retransmissions
if (srb->id == 1) { // SRB1
nr_rlc_set_rlf_handler(UE->rnti, gnb_rlf_handler);
}
int priority = rlc_BearerConfig->mac_LogicalChannelConfig->ul_SpecificParameters->priority;
nr_lc_config_t c = {.lcid = rlc_BearerConfig->logicalChannelIdentity, .priority = priority};
+5
View File
@@ -79,6 +79,7 @@
/* PHY */
#include "time_meas.h"
#include "PHY/NR_TRANSPORT/nr_antenna_state.h"
/* Interface */
#include "nfapi_nr_interface_scf.h"
@@ -971,6 +972,10 @@ typedef struct gNB_MAC_INST_s {
nr_mac_config_t radio_config;
nr_rlc_configuration_t rlc_config;
/// O-RAN antenna state for energy saving (MPLANE-TRX-CONTROL)
/// Points to PHY antenna state for dynamic layer adaptation
nr_antenna_state_t *antenna_state;
NR_UE_sched_ctrl_t *sched_ctrlCommon;
NR_sched_pdcch_t *sched_pdcch_otherSI;
uint16_t cset0_bwp_start;
+1
View File
@@ -32,6 +32,7 @@ target_sources(oran_fhlib_5g_mplane PRIVATE
subscribe-mplane.c
config-mplane.c
ru-mplane-api.c
antenna-control-mplane.c
xml/get-xml.c
yang/get-yang.c
yang/create-yang-config.c
@@ -0,0 +1,398 @@
/*
* Licensed to the OpenAirInterface (OAI) Software Alliance under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The OpenAirInterface Software Alliance licenses this file to You under
* the OAI Public License, Version 1.1 (the "License"); you may not use this file
* except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.openairinterface.org/?page_id=698
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*-------------------------------------------------------------------------------
* For more information about the OpenAirInterface (OAI) Software Alliance:
* contact@openairinterface.org
*/
/**
* @file antenna-control-mplane.c
* @brief Implementation of O-RAN WG4 M-Plane compliant antenna control
*
* This implements NETCONF-based antenna muting using the actual O-RAN
* YANG models (o-ran-uplane-conf.yang) and the MPLANE-TRX-CONTROL feature.
*/
#include "antenna-control-mplane.h"
#include "ru-mplane-api.h"
#include "xml/get-xml.h"
#include "common/utils/assertions.h"
// Forward declaration - actual function in config-mplane.c
extern bool edit_val_commmit_rpc(ru_session_t *ru_session, const char *content);
// Forward declaration - actual function in get-mplane.c
extern bool get_mplane(ru_session_t *ru_session, char **answer);
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <libyang/libyang.h>
#include <nc_client.h>
const char *antenna_ctrl_result_str(antenna_ctrl_result_t result)
{
switch (result) {
case ANTENNA_CTRL_SUCCESS:
return "Success";
case ANTENNA_CTRL_ERR_NO_SESSION:
return "No active NETCONF session";
case ANTENNA_CTRL_ERR_FEATURE_NOT_SUPPORTED:
return "MPLANE-TRX-CONTROL feature not supported by RU";
case ANTENNA_CTRL_ERR_INVALID_ARRAY_NAME:
return "Invalid array name";
case ANTENNA_CTRL_ERR_INVALID_MASK:
return "Invalid antenna bitmask";
case ANTENNA_CTRL_ERR_NETCONF_FAILED:
return "NETCONF RPC failed";
case ANTENNA_CTRL_ERR_VALIDATION_FAILED:
return "Configuration validation failed";
case ANTENNA_CTRL_ERR_COMMIT_FAILED:
return "Configuration commit failed";
default:
return "Unknown error";
}
}
/**
* @brief Convert antenna bitmask to O-RAN binary format
*
* O-RAN uses binary type in YANG which is base64 encoded in XML.
* For simplicity, we encode the bitmask as hex bytes.
*
* Per O-RAN spec: "The antenna mask contains as many binary values as
* number of array elements associated with respective tx or rx-array.
* Value of '1' indicates antenna element shall be kept active and
* value of '0' indicates antenna element should be turned inactive."
*/
static char *bitmask_to_base64(uint8_t bitmask, uint8_t num_antennas)
{
// For up to 8 antennas, we need 1 byte
// Base64 encoding of 1 byte = 4 characters + null terminator
char *result = malloc(8);
if (result == NULL) return NULL;
// Mask to only include configured antennas
uint8_t valid_mask = (1 << num_antennas) - 1;
uint8_t masked_value = bitmask & valid_mask;
// Simple base64 encoding for single byte
// Input: masked_value (8 bits) -> padded to 6+6+4 bits for base64
static const char b64chars[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
// Encode single byte with padding
result[0] = b64chars[(masked_value >> 2) & 0x3F];
result[1] = b64chars[(masked_value << 4) & 0x30];
result[2] = '=';
result[3] = '=';
result[4] = '\0';
return result;
}
/**
* @brief Decode base64 to antenna bitmask
*/
static uint8_t base64_to_bitmask(const char *b64_str)
{
if (b64_str == NULL || strlen(b64_str) < 2) return 0xFF;
static const char b64chars[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
// Find index of first two base64 characters
const char *p1 = strchr(b64chars, b64_str[0]);
const char *p2 = strchr(b64chars, b64_str[1]);
if (p1 == NULL || p2 == NULL) return 0xFF;
uint8_t val1 = (uint8_t)(p1 - b64chars);
uint8_t val2 = (uint8_t)(p2 - b64chars);
// Reconstruct byte
return (val1 << 2) | (val2 >> 4);
}
bool mplane_create_antenna_mask_xml(ru_session_t *ru_session,
bool is_tx,
const char *array_name,
uint8_t antenna_bitmask,
uint8_t num_antennas,
char **xml_content)
{
if (ru_session == NULL || ru_session->ctx == NULL || array_name == NULL || xml_content == NULL) {
return false;
}
*xml_content = NULL;
LY_ERR ret = LY_SUCCESS;
struct lyd_node *root = NULL;
// Create root node for o-ran-uplane-conf
ret = lyd_new_path(NULL, (struct ly_ctx *)ru_session->ctx,
"/o-ran-uplane-conf:user-plane-configuration", NULL, 0, &root);
if (ret != LY_SUCCESS) {
MP_LOG_W("Failed to create root user-plane-configuration node.\n");
return false;
}
// Create the antenna mask configuration
const char *list_name = is_tx ? "tx-array-antenna-mask-config" : "rx-array-antenna-mask-config";
struct lyd_node *mask_node = NULL;
ret = lyd_new_list(root, NULL, list_name, 0, &mask_node, array_name);
if (ret != LY_SUCCESS) {
MP_LOG_W("Failed to create %s list node for array '%s'.\n", list_name, array_name);
lyd_free_tree(root);
return false;
}
// Convert bitmask to base64 for YANG binary type
char *b64_mask = bitmask_to_base64(antenna_bitmask, num_antennas);
if (b64_mask == NULL) {
MP_LOG_W("Failed to encode antenna bitmask.\n");
lyd_free_tree(root);
return false;
}
// Add antenna-bitmask leaf
ret = lyd_new_term(mask_node, NULL, "antenna-bitmask", b64_mask, 0, NULL);
free(b64_mask);
if (ret != LY_SUCCESS) {
MP_LOG_W("Failed to create antenna-bitmask node.\n");
lyd_free_tree(root);
return false;
}
// Print to XML
// Note: LYD_PRINT_SIBLINGS in libyang v2.1+, LYD_PRINT_WITHSIBLINGS in older v2, LYP_WITHSIBLINGS in v1
#ifdef MPLANE_V1
lyd_print_mem(xml_content, root, LYD_XML, LYP_WITHSIBLINGS);
#elif defined MPLANE_V2
#ifdef LYD_PRINT_SIBLINGS
lyd_print_mem(xml_content, root, LYD_XML, LYD_PRINT_SIBLINGS);
#else
lyd_print_mem(xml_content, root, LYD_XML, LYD_PRINT_WITHSIBLINGS);
#endif
#endif
lyd_free_tree(root);
if (*xml_content == NULL) {
MP_LOG_W("Failed to generate XML content.\n");
return false;
}
MP_LOG_I("Generated %s antenna mask XML for array '%s', mask=0x%02X\n",
is_tx ? "TX" : "RX", array_name, antenna_bitmask);
return true;
}
antenna_ctrl_result_t mplane_set_tx_antenna_mask(ru_session_t *ru_session,
const char *array_name,
uint8_t antenna_bitmask,
uint8_t num_antennas)
{
if (ru_session == NULL || ru_session->session == NULL) {
return ANTENNA_CTRL_ERR_NO_SESSION;
}
if (array_name == NULL || strlen(array_name) == 0) {
return ANTENNA_CTRL_ERR_INVALID_ARRAY_NAME;
}
if (num_antennas == 0 || num_antennas > 8) {
return ANTENNA_CTRL_ERR_INVALID_MASK;
}
// Generate XML for antenna mask configuration
char *xml_content = NULL;
if (!mplane_create_antenna_mask_xml(ru_session, true, array_name,
antenna_bitmask, num_antennas, &xml_content)) {
return ANTENNA_CTRL_ERR_NETCONF_FAILED;
}
MP_LOG_I("Setting TX antenna mask via NETCONF:\n%s\n", xml_content);
// Use existing M-Plane infrastructure to send edit-config, validate, commit
bool success = edit_val_commmit_rpc(ru_session, xml_content);
free(xml_content);
if (!success) {
MP_LOG_W("Failed to configure TX antenna mask for array '%s'\n", array_name);
return ANTENNA_CTRL_ERR_COMMIT_FAILED;
}
MP_LOG_I("Successfully configured TX antenna mask 0x%02X for array '%s'\n",
antenna_bitmask, array_name);
return ANTENNA_CTRL_SUCCESS;
}
antenna_ctrl_result_t mplane_set_rx_antenna_mask(ru_session_t *ru_session,
const char *array_name,
uint8_t antenna_bitmask,
uint8_t num_antennas)
{
if (ru_session == NULL || ru_session->session == NULL) {
return ANTENNA_CTRL_ERR_NO_SESSION;
}
if (array_name == NULL || strlen(array_name) == 0) {
return ANTENNA_CTRL_ERR_INVALID_ARRAY_NAME;
}
if (num_antennas == 0 || num_antennas > 8) {
return ANTENNA_CTRL_ERR_INVALID_MASK;
}
// Generate XML for antenna mask configuration
char *xml_content = NULL;
if (!mplane_create_antenna_mask_xml(ru_session, false, array_name,
antenna_bitmask, num_antennas, &xml_content)) {
return ANTENNA_CTRL_ERR_NETCONF_FAILED;
}
MP_LOG_I("Setting RX antenna mask via NETCONF:\n%s\n", xml_content);
// Use existing M-Plane infrastructure to send edit-config, validate, commit
bool success = edit_val_commmit_rpc(ru_session, xml_content);
free(xml_content);
if (!success) {
MP_LOG_W("Failed to configure RX antenna mask for array '%s'\n", array_name);
return ANTENNA_CTRL_ERR_COMMIT_FAILED;
}
MP_LOG_I("Successfully configured RX antenna mask 0x%02X for array '%s'\n",
antenna_bitmask, array_name);
return ANTENNA_CTRL_SUCCESS;
}
antenna_ctrl_result_t mplane_get_tx_antenna_mask(ru_session_t *ru_session,
const char *array_name,
uint8_t *antenna_bitmask)
{
if (ru_session == NULL || ru_session->session == NULL) {
return ANTENNA_CTRL_ERR_NO_SESSION;
}
if (array_name == NULL || antenna_bitmask == NULL) {
return ANTENNA_CTRL_ERR_INVALID_ARRAY_NAME;
}
// Get operational datastore
char *operational_ds = NULL;
if (!get_mplane(ru_session, &operational_ds)) {
return ANTENNA_CTRL_ERR_NETCONF_FAILED;
}
// Build XPath to find the antenna mask for this array
char xpath[256];
snprintf(xpath, sizeof(xpath),
"tx-array-antenna-mask-config[array-name='%s']/antenna-bitmask",
array_name);
// Extract the value from XML response
char *mask_b64 = get_ru_xml_node(operational_ds, "antenna-bitmask");
free(operational_ds);
if (mask_b64 == NULL) {
// No mask configured, assume all active
*antenna_bitmask = 0xFF;
return ANTENNA_CTRL_SUCCESS;
}
*antenna_bitmask = base64_to_bitmask(mask_b64);
free(mask_b64);
MP_LOG_I("Retrieved TX antenna mask 0x%02X for array '%s'\n",
*antenna_bitmask, array_name);
return ANTENNA_CTRL_SUCCESS;
}
antenna_ctrl_result_t mplane_get_rx_antenna_mask(ru_session_t *ru_session,
const char *array_name,
uint8_t *antenna_bitmask)
{
if (ru_session == NULL || ru_session->session == NULL) {
return ANTENNA_CTRL_ERR_NO_SESSION;
}
if (array_name == NULL || antenna_bitmask == NULL) {
return ANTENNA_CTRL_ERR_INVALID_ARRAY_NAME;
}
// Get operational datastore
char *operational_ds = NULL;
if (!get_mplane(ru_session, &operational_ds)) {
return ANTENNA_CTRL_ERR_NETCONF_FAILED;
}
// Extract the value from XML response (simplified - real impl would use XPath)
char *mask_b64 = get_ru_xml_node(operational_ds, "antenna-bitmask");
free(operational_ds);
if (mask_b64 == NULL) {
// No mask configured, assume all active
*antenna_bitmask = 0xFF;
return ANTENNA_CTRL_SUCCESS;
}
*antenna_bitmask = base64_to_bitmask(mask_b64);
free(mask_b64);
MP_LOG_I("Retrieved RX antenna mask 0x%02X for array '%s'\n",
*antenna_bitmask, array_name);
return ANTENNA_CTRL_SUCCESS;
}
bool mplane_supports_trx_control(ru_session_t *ru_session)
{
if (ru_session == NULL || ru_session->session == NULL) {
return false;
}
// Check if MPLANE-TRX-CONTROL feature is advertised in RU capabilities
// This is done by checking the server capabilities from the hello exchange
// or by querying the YANG library
struct nc_session *session = (struct nc_session *)ru_session->session;
const char * const *caps = nc_session_get_cpblts(session);
if (caps == NULL) {
return false;
}
// Look for o-ran-uplane-conf module with MPLANE-TRX-CONTROL feature
for (int i = 0; caps[i] != NULL; i++) {
if (strstr(caps[i], "o-ran-uplane-conf") != NULL) {
// Module is supported, assume feature is enabled
// A more complete implementation would query module features
MP_LOG_I("RU supports o-ran-uplane-conf module (MPLANE-TRX-CONTROL)\n");
return true;
}
}
MP_LOG_W("RU does not advertise o-ran-uplane-conf module\n");
return false;
}
@@ -0,0 +1,174 @@
/*
* Licensed to the OpenAirInterface (OAI) Software Alliance under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The OpenAirInterface Software Alliance licenses this file to You under
* the OAI Public License, Version 1.1 (the "License"); you may not use this file
* except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.openairinterface.org/?page_id=698
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*-------------------------------------------------------------------------------
* For more information about the OpenAirInterface (OAI) Software Alliance:
* contact@openairinterface.org
*/
/**
* @file antenna-control-mplane.h
* @brief O-RAN WG4 M-Plane compliant antenna control via NETCONF
*
* This module implements the MPLANE-TRX-CONTROL feature as defined in
* O-RAN WG4 M-Plane specification and o-ran-uplane-conf.yang model.
*
* YANG paths used:
* /o-ran-uplane-conf:user-plane-configuration/tx-array-antenna-mask-config
* /o-ran-uplane-conf:user-plane-configuration/rx-array-antenna-mask-config
*
* References:
* - O-RAN.WG4.MP.0-v10.00 Section 20.3.2 (M-Plane based TRX control)
* - o-ran-uplane-conf.yang (grouping array-config-group)
* - o-ran-wg4-features.yang (MPLANE-TRX-CONTROL feature)
*/
#ifndef ANTENNA_CONTROL_MPLANE_H
#define ANTENNA_CONTROL_MPLANE_H
#include "ru-mplane-api.h"
#include <stdint.h>
#include <stdbool.h>
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief O-RAN antenna mask configuration result
*/
typedef enum {
ANTENNA_CTRL_SUCCESS = 0,
ANTENNA_CTRL_ERR_NO_SESSION,
ANTENNA_CTRL_ERR_FEATURE_NOT_SUPPORTED,
ANTENNA_CTRL_ERR_INVALID_ARRAY_NAME,
ANTENNA_CTRL_ERR_INVALID_MASK,
ANTENNA_CTRL_ERR_NETCONF_FAILED,
ANTENNA_CTRL_ERR_VALIDATION_FAILED,
ANTENNA_CTRL_ERR_COMMIT_FAILED
} antenna_ctrl_result_t;
/**
* @brief Convert result code to string description
*/
const char *antenna_ctrl_result_str(antenna_ctrl_result_t result);
/**
* @brief Set TX antenna bitmask via NETCONF O1 interface
*
* Implements O-RAN WG4 MPLANE-TRX-CONTROL feature by sending
* NETCONF edit-config RPC to configure tx-array-antenna-mask-config.
*
* The operation sequence is:
* 1. Create XML configuration for o-ran-uplane-conf:tx-array-antenna-mask-config
* 2. Send NETCONF <edit-config> to candidate datastore
* 3. Send NETCONF <validate>
* 4. Send NETCONF <commit>
*
* @param ru_session Active NETCONF session to the RU
* @param array_name O-RAN TX array name (e.g., from tx-arrays list)
* @param antenna_bitmask Bitmask where bit N = 1 means antenna N is active
* @param num_antennas Number of antennas in the array
* @return Result code indicating success or specific error
*/
antenna_ctrl_result_t mplane_set_tx_antenna_mask(ru_session_t *ru_session,
const char *array_name,
uint8_t antenna_bitmask,
uint8_t num_antennas);
/**
* @brief Set RX antenna bitmask via NETCONF O1 interface
*
* Same as mplane_set_tx_antenna_mask but for RX arrays.
* Configures rx-array-antenna-mask-config in o-ran-uplane-conf.
*
* @param ru_session Active NETCONF session to the RU
* @param array_name O-RAN RX array name (e.g., from rx-arrays list)
* @param antenna_bitmask Bitmask where bit N = 1 means antenna N is active
* @param num_antennas Number of antennas in the array
* @return Result code indicating success or specific error
*/
antenna_ctrl_result_t mplane_set_rx_antenna_mask(ru_session_t *ru_session,
const char *array_name,
uint8_t antenna_bitmask,
uint8_t num_antennas);
/**
* @brief Get current TX antenna mask from RU via NETCONF
*
* Sends NETCONF <get> RPC to retrieve current tx-array-antenna-mask-config.
*
* @param ru_session Active NETCONF session to the RU
* @param array_name O-RAN TX array name
* @param antenna_bitmask Output: current antenna bitmask
* @return Result code indicating success or specific error
*/
antenna_ctrl_result_t mplane_get_tx_antenna_mask(ru_session_t *ru_session,
const char *array_name,
uint8_t *antenna_bitmask);
/**
* @brief Get current RX antenna mask from RU via NETCONF
*
* Sends NETCONF <get> RPC to retrieve current rx-array-antenna-mask-config.
*
* @param ru_session Active NETCONF session to the RU
* @param array_name O-RAN RX array name
* @param antenna_bitmask Output: current antenna bitmask
* @return Result code indicating success or specific error
*/
antenna_ctrl_result_t mplane_get_rx_antenna_mask(ru_session_t *ru_session,
const char *array_name,
uint8_t *antenna_bitmask);
/**
* @brief Check if RU supports MPLANE-TRX-CONTROL feature
*
* Queries the RU capabilities to verify MPLANE-TRX-CONTROL feature
* is advertised (defined in o-ran-wg4-features.yang).
*
* @param ru_session Active NETCONF session to the RU
* @return true if feature is supported, false otherwise
*/
bool mplane_supports_trx_control(ru_session_t *ru_session);
/**
* @brief Create antenna bitmask XML content for NETCONF edit-config
*
* Helper function to generate O-RAN compliant XML for antenna mask configuration.
* Uses libyang to create proper o-ran-uplane-conf structure.
*
* @param ru_session Active NETCONF session (for libyang context)
* @param is_tx true for TX array, false for RX array
* @param array_name O-RAN array name
* @param antenna_bitmask Antenna bitmask value
* @param num_antennas Number of antennas (for binary encoding)
* @param xml_content Output: allocated XML string (caller must free)
* @return true on success, false on error
*/
bool mplane_create_antenna_mask_xml(ru_session_t *ru_session,
bool is_tx,
const char *array_name,
uint8_t antenna_bitmask,
uint8_t num_antennas,
char **xml_content);
#ifdef __cplusplus
}
#endif
#endif /* ANTENNA_CONTROL_MPLANE_H */
@@ -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";
};
+1 -1
View File
@@ -41,7 +41,7 @@ iperf_udp_ul_forever() { ( iperf -u -c "$IPERF_HOST_UDP" -B "$OAITUN_IP" -p "$IP
# ---------------- profiles ----------------
do_boring() {
iperf_udp_dl_forever 10M
iperf_udp_dl_forever 15M
wait
# iperf -c "$IPERF_HOST" -p "$IPERF_PORT" -B "$OAITUN_IP" -u -b 25M -t "$LONG_T"
}