From 312d75d759bff498b8bd1314503699f759545958 Mon Sep 17 00:00:00 2001 From: maki Date: Fri, 30 Jan 2026 23:38:37 +0900 Subject: [PATCH] update --- CMakeLists.txt | 2 + .../gnb.sa.band78.51prb.usrpb200.conf | 15 +- ci-scripts/conf_files/test_4x4.conf | 262 ++++ docker/Dockerfile.nakaolab.test | 44 + executables/nr-gnb.c | 10 + openair1/PHY/INIT/nr_init.c | 3 + openair1/PHY/MODULATION/beamforming.c | 33 + openair1/PHY/MODULATION/nr_modulation.h | 29 + openair1/PHY/NR_TRANSPORT/antenna_control.c | 18 - .../PHY/NR_TRANSPORT/nr_antenna_control.c | 532 +++++++ .../PHY/NR_TRANSPORT/nr_antenna_control.h | 123 ++ openair1/PHY/NR_TRANSPORT/nr_antenna_state.c | 366 +++++ openair1/PHY/NR_TRANSPORT/nr_antenna_state.h | 239 +++ openair1/PHY/defs_gNB.h | 3 + openair1/SCHED_NR/nr_ru_procedures.c | 34 +- openair2/E2AP/flexric | 2 +- .../LAYER2/NR_MAC_gNB/gNB_scheduler_dlsch.c | 6 +- .../NR_MAC_gNB/gNB_scheduler_primitives.c | 152 +- .../LAYER2/NR_MAC_gNB/gNB_scheduler_uci.c | 2 +- openair2/LAYER2/NR_MAC_gNB/mac_proto.h | 2 +- openair2/LAYER2/NR_MAC_gNB/nr_mac_gNB.h | 1373 +++++++++-------- radio/fhi_72/mplane/CMakeLists.txt | 1 + radio/fhi_72/mplane/antenna-control.c | 368 +++++ radio/fhi_72/mplane/antenna-control.h | 135 ++ tools/scripts/traffic_runner.sh | 98 ++ 25 files changed, 3076 insertions(+), 776 deletions(-) create mode 100644 ci-scripts/conf_files/test_4x4.conf create mode 100644 docker/Dockerfile.nakaolab.test delete mode 100644 openair1/PHY/NR_TRANSPORT/antenna_control.c create mode 100644 openair1/PHY/NR_TRANSPORT/nr_antenna_control.c create mode 100644 openair1/PHY/NR_TRANSPORT/nr_antenna_control.h create mode 100644 openair1/PHY/NR_TRANSPORT/nr_antenna_state.c create mode 100644 openair1/PHY/NR_TRANSPORT/nr_antenna_state.h create mode 100644 radio/fhi_72/mplane/antenna-control.c create mode 100644 radio/fhi_72/mplane/antenna-control.h create mode 100755 tools/scripts/traffic_runner.sh diff --git a/CMakeLists.txt b/CMakeLists.txt index 14d90a4c3b..8f2b1a59ee 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -873,6 +873,7 @@ set(PHY_SRC_RU ${OPENAIR1_DIR}/PHY/MODULATION/gen_75KHz.cpp ${OPENAIR1_DIR}/PHY/MODULATION/beamforming.c ${OPENAIR1_DIR}/PHY/MODULATION/compute_bf_weights.c + ${OPENAIR1_DIR}/PHY/NR_TRANSPORT/nr_antenna_state.c ${OPENAIR1_DIR}/PHY/INIT/lte_init_ru.c ${OPENAIR1_DIR}/PHY/INIT/nr_init_ru.c ${OPENAIR1_DIR}/PHY/LTE_TRANSPORT/prach.c @@ -948,6 +949,7 @@ set(PHY_SRC_UE ${OPENAIR1_DIR}/PHY/NR_TRANSPORT/nr_prach.c ${OPENAIR1_DIR}/PHY/NR_TRANSPORT/nr_ulsch_llr_computation.c ${OPENAIR1_DIR}/PHY/NR_TRANSPORT/nr_ulsch_demodulation.c + ${OPENAIR1_DIR}/PHY/NR_TRANSPORT/nr_antenna_control.c ${OPENAIR1_DIR}/PHY/NR_REFSIG/ul_ref_seq_nr.c ${OPENAIR1_DIR}/PHY/NR_REFSIG/nr_dmrs_rx.c ${OPENAIR1_DIR}/PHY/NR_REFSIG/nr_gen_mod_table.c diff --git a/ci-scripts/conf_files/gnb.sa.band78.51prb.usrpb200.conf b/ci-scripts/conf_files/gnb.sa.band78.51prb.usrpb200.conf index 0057d9b75f..eac4ade780 100644 --- a/ci-scripts/conf_files/gnb.sa.band78.51prb.usrpb200.conf +++ b/ci-scripts/conf_files/gnb.sa.band78.51prb.usrpb200.conf @@ -11,8 +11,7 @@ gNBs = // Tracking area code, 0x0000 and 0xfffe are reserved values tracking_area_code = 1; - plmn_list = ({ mcc = 222; mnc = 01; mnc_length = 2; snssaiList = ({ sst = 1, sd = 0xffffff }) }); - + plmn_list = ({ mcc = 001; mnc = 01; mnc_length = 2; snssaiList = ({ sst = 1; sd = 0xffffff}) }); nr_cellid = 12345678L; ////////// Physical parameters: @@ -188,8 +187,8 @@ L1s = ( RUs = ( { local_rf = "yes" - nb_tx = 1 - nb_rx = 1 + nb_tx = 4; + nb_rx = 4; att_tx = 14; att_rx = 14; bands = [78]; @@ -200,6 +199,14 @@ RUs = ( } ); +rfsimulator : +{ + serveraddr = "server"; + serverport = "4043"; + # options = ("saviq", "chanmod"); #("saviq"); or/and "chanmod" + # modelname = "AWGN"; + IQfile = "/tmp/rfsimulator.iqs"; +}; security = { # preferred ciphering algorithms diff --git a/ci-scripts/conf_files/test_4x4.conf b/ci-scripts/conf_files/test_4x4.conf new file mode 100644 index 0000000000..16740af047 --- /dev/null +++ b/ci-scripts/conf_files/test_4x4.conf @@ -0,0 +1,262 @@ +Active_gNBs = ( "gNB-OAI"); +# Asn1_verbosity, choice in: none, info, annoying +Asn1_verbosity = "none"; + +gNBs = +( + { + ////////// Identification parameters: + gNB_ID = 0xe00; + gNB_name = "gNB-OAI"; + + // Tracking area code, 0x0000 and 0xfffe are reserved values + tracking_area_code = 1; + plmn_list = ({ mcc = 001; mnc = 01; mnc_length = 2; snssaiList = ({ sst = 1; }) }); + + nr_cellid = 1; + + ////////// Physical parameters: + + pdsch_AntennaPorts_XP = 2; + pdsch_AntennaPorts_N1 = 1; + maxMIMO_layers = 2; + pusch_AntennaPorts = 4; + do_CSIRS = 1; + do_SRS = 0; + # force_UL256qam_off = 1; + + servingCellConfigCommon = ( + { + #spCellConfigCommon + + physCellId = 0; + # n_TimingAdvanceOffset = 0; +# downlinkConfigCommon + #frequencyInfoDL + # center frequency = 3350.01 MHz + # selected SSB frequency = 3349.92 MHz + absoluteFrequencySSB = 623328; + dl_frequencyBand = 78; + # frequency point A = 3300.87 MHz + dl_absoluteFrequencyPointA = 620052; + #scs-SpecificCarrierList + dl_offstToCarrier = 0; +# subcarrierSpacing +# 0=kHz15, 1=kHz30, 2=kHz60, 3=kHz120 + dl_subcarrierSpacing = 1; + dl_carrierBandwidth = 273; + #initialDownlinkBWP + #genericParameters + initialDLBWPlocationAndBandwidth = 1099; #38.101-1 Table 5.3.2-1 + # +# subcarrierSpacing +# 0=kHz15, 1=kHz30, 2=kHz60, 3=kHz120 + initialDLBWPsubcarrierSpacing = 1; + #pdcch-ConfigCommon + initialDLBWPcontrolResourceSetZero = 11; + initialDLBWPsearchSpaceZero = 0; + + #uplinkConfigCommon + #frequencyInfoUL + ul_frequencyBand = 78; + #scs-SpecificCarrierList + ul_offstToCarrier = 0; +# subcarrierSpacing +# 0=kHz15, 1=kHz30, 2=kHz60, 3=kHz120 + ul_subcarrierSpacing = 1; + ul_carrierBandwidth = 273; + pMax = 23; + #initialUplinkBWP + #genericParameters + initialULBWPlocationAndBandwidth = 1099; +# subcarrierSpacing +# 0=kHz15, 1=kHz30, 2=kHz60, 3=kHz120 + initialULBWPsubcarrierSpacing = 1; + #rach-ConfigCommon + #rach-ConfigGeneric + prach_ConfigurationIndex = 152; +#prach_msg1_FDM +#0 = one, 1=two, 2=four, 3=eight + prach_msg1_FDM = 0; + prach_msg1_FrequencyStart = 0; + zeroCorrelationZoneConfig = 0; + preambleReceivedTargetPower = -100; +#preamblTransMax (0...10) = (3,4,5,6,7,8,10,20,50,100,200) + preambleTransMax = 8; +#powerRampingStep +# 0=dB0,1=dB2,2=dB4,3=dB6 + powerRampingStep = 1; +#ssb_perRACH_OccasionAndCB_PreamblesPerSSB_PR +#1=oneeighth,2=onefourth,3=half,4=one,5=two,6=four,7=eight,8=sixteen + ssb_perRACH_OccasionAndCB_PreamblesPerSSB_PR = 4; +#one (0..15) 4,8,12,16,...60,64 + ssb_perRACH_OccasionAndCB_PreamblesPerSSB = 15; +#ra_ContentionResolutionTimer +#(0..7) 8,16,24,32,40,48,56,64 + ra_ContentionResolutionTimer = 7; + rsrp_ThresholdSSB = 19; +#prach-RootSequenceIndex_PR +#1 = 839, 2 = 139 + prach_RootSequenceIndex_PR = 2; + prach_RootSequenceIndex = 1; + # SCS for msg1, can only be 15 for 30 kHz < 6 GHz, takes precendence over the one derived from prach-ConfigIndex + # + msg1_SubcarrierSpacing = 1, +# restrictedSetConfig +# 0=unrestricted, 1=restricted type A, 2=restricted type B + restrictedSetConfig = 0, + +# this is the offset between the last PRACH preamble power and the Msg3 PUSCH, 2 times the field value in dB + msg3_DeltaPreamble = 2; + p0_NominalWithGrant = -96; + +# pucch-ConfigCommon setup : +# pucchGroupHopping +# 0 = neither, 1= group hopping, 2=sequence hopping + pucchGroupHopping = 0; + hoppingId = 0; + p0_nominal = -96; + + ssb_PositionsInBurst_Bitmap = 0x1; + +# ssb_periodicityServingCell +# 0 = ms5, 1=ms10, 2=ms20, 3=ms40, 4=ms80, 5=ms160, 6=spare2, 7=spare1 + ssb_periodicityServingCell = 2; + +# dmrs_TypeA_position +# 0 = pos2, 1 = pos3 + dmrs_TypeA_Position = 0; + +# subcarrierSpacing +# 0=kHz15, 1=kHz30, 2=kHz60, 3=kHz120 + subcarrierSpacing = 1; + + + #tdd-UL-DL-ConfigurationCommon +# subcarrierSpacing +# 0=kHz15, 1=kHz30, 2=kHz60, 3=kHz120 + referenceSubcarrierSpacing = 1; + # pattern1 + # dl_UL_TransmissionPeriodicity + # 0=ms0p5, 1=ms0p625, 2=ms1, 3=ms1p25, 4=ms2, 5=ms2p5, 6=ms5, 7=ms10 + dl_UL_TransmissionPeriodicity = 6; + nrofDownlinkSlots = 7; + nrofDownlinkSymbols = 6; + nrofUplinkSlots = 2; + nrofUplinkSymbols = 4; + + ssPBCH_BlockPower = -15; + } + + ); + + + # ------- SCTP definitions + SCTP : + { + # Number of streams to use in input/output + SCTP_INSTREAMS = 2; + SCTP_OUTSTREAMS = 2; + }; + + + ////////// AMF parameters: + amf_ip_address = ({ ipv4 = "192.168.71.132"; }); + + NETWORK_INTERFACES : + { + GNB_IPV4_ADDRESS_FOR_NG_AMF = "192.168.71.140"; + GNB_IPV4_ADDRESS_FOR_NGU = "192.168.71.140"; + GNB_PORT_FOR_S1U = 2152; # Spec 2152 + }; + } +); + +MACRLCs = ( +{ + num_cc = 1; + tr_s_preference = "local_L1"; + tr_n_preference = "local_RRC"; + pusch_TargetSNRx10 = 100; + pucch_TargetSNRx10 = 200; + dl_bler_target_upper = .35; + dl_bler_target_lower = .15; + ul_bler_target_upper = .35; + ul_bler_target_lower = .15; + pusch_FailureThres = 1000; + ul_max_mcs = 28; +} +); + +L1s = ( +{ + num_cc = 1; + tr_n_preference = "local_mac"; + prach_dtx_threshold = 90; + pucch0_dtx_threshold = 80; + pusch_dtx_threshold = 10; + max_ldpc_iterations = 15; + tx_amp_backoff_dB = 12; # needs to match O-RU configuration + L1_rx_thread_core = 3; + L1_tx_thread_core = 4; + phase_compensation = 0; # needs to match O-RU configuration +} +); + +RUs = ( +{ + local_rf = "yes"; + nb_tx = 4; + nb_rx = 4; + att_tx = 0; + att_rx = 0; + bands = [78]; + max_pdschReferenceSignalPower = -27; + max_rxgain = 75; + sf_extension = 0; + eNB_instances = [0]; + ru_thread_core = 5; + sl_ahead = 10; + tr_preference = "raw_if4p5"; # important: activate FHI7.2 + do_precoding = 0; # needs to match O-RU configuration +} +); + +security = { + # preferred ciphering algorithms + # the first one of the list that an UE supports in chosen + # valid values: nea0, nea1, nea2, nea3 + ciphering_algorithms = ( "nea0" ); + + # preferred integrity algorithms + # the first one of the list that an UE supports in chosen + # valid values: nia0, nia1, nia2, nia3 + integrity_algorithms = ( "nia2", "nia0" ); + + # setting 'drb_ciphering' to "no" disables ciphering for DRBs, no matter + # what 'ciphering_algorithms' configures; same thing for 'drb_integrity' + drb_ciphering = "yes"; + drb_integrity = "no"; +}; + +log_config : +{ + global_log_level = "info"; + hw_log_level = "info"; + phy_log_level = "info"; + mac_log_level = "info"; + rlc_log_level = "info"; + pdcp_log_level = "info"; + rrc_log_level = "info"; + ngap_log_level = "info"; + f1ap_log_level = "info"; +}; + +rfsimulator : +{ + serveraddr = "server"; + serverport = 4043; + options = (); #("saviq"); or/and "chanmod" + modelname = "AWGN"; + IQfile = "/tmp/rfsimulator.iqs"; +}; diff --git a/docker/Dockerfile.nakaolab.test b/docker/Dockerfile.nakaolab.test new file mode 100644 index 0000000000..d51e58fdba --- /dev/null +++ b/docker/Dockerfile.nakaolab.test @@ -0,0 +1,44 @@ +FROM ran-base:latest AS ran-build +ARG BUILD_OPTION + +RUN rm -Rf /oai-ran +WORKDIR /oai-ran +COPY . . + +# build all targets so final targets can be created afterwards +RUN /bin/sh oaienv && \ + cd cmake_targets && \ + mkdir -p log && \ + ./build_oai --ninja \ + --gNB --RU --nrUE -w SIMU \ + -w USRP -t Ethernet \ + --build-e2 --cmake-opt -DKPM_VERSION=KPM_V3_00 --cmake-opt -DE2AP_VERSION=E2AP_V3 \ + --cmake-opt -DCMAKE_C_FLAGS="-Werror" --cmake-opt -DCMAKE_CXX_FLAGS="-Werror" $BUILD_OPTION && \ + echo "---- ldd on executables ----" && \ + ldd ran_build/build/*softmodem* ran_build/build/nr-cuup ran_build/build/oairu && \ + echo "---- ldd on shared libraries ----" && \ + ldd ran_build/build/*.so + +RUN set -eux; \ + apt-get update; \ + DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \ + ca-certificates curl wget iproute2 iputils-ping dnsutils jq \ + iperf iperf3 ffmpeg gdb\ + nghttp2-client || true; \ + if ! command -v h2load >/dev/null 2>&1; then \ + DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends nghttp2 || true; \ + fi; \ + wget -O /usr/local/bin/sipp https://github.com/SIPp/sipp/releases/download/v3.7.7/sipp && chmod +x /usr/local/bin/sipp; \ + rm -rf /var/lib/apt/lists/* + +# FlexRIC libraries (built by --build-e2) - only copy service model libraries (*_sm.so) +RUN mkdir -p /usr/local/etc/flexric /usr/local/lib/flexric && \ + find /oai-ran/openair2/E2AP/flexric/build -name "*_sm.so" -exec cp {} /usr/local/lib/flexric/ \; && \ + cp /oai-ran/openair2/E2AP/flexric/flexric.conf /usr/local/etc/flexric/ 2>/dev/null || true && \ + echo "/usr/local/lib/flexric" > /etc/ld.so.conf.d/flexric.conf && ldconfig && \ + ls -la /usr/local/lib/flexric/ + +# Install netcat for antenna control testing +RUN apt-get update && apt-get install -y netcat-openbsd && rm -rf /var/lib/apt/lists/* + +ENTRYPOINT ["/bin/bash"] \ No newline at end of file diff --git a/executables/nr-gnb.c b/executables/nr-gnb.c index 226bf2775b..af9ea3cdd2 100644 --- a/executables/nr-gnb.c +++ b/executables/nr-gnb.c @@ -48,6 +48,8 @@ #include "NR_PHY_INTERFACE/NR_IF_Module.h" #include "PHY/INIT/nr_phy_init.h" #include "PHY/MODULATION/nr_modulation.h" +#include "openair2/LAYER2/NR_MAC_gNB/nr_mac_gNB.h" +#include "PHY/NR_TRANSPORT/nr_antenna_control.h" #include "PHY/NR_TRANSPORT/nr_transport_proto.h" #include "PHY/TOOLS/tools_defs.h" #include "PHY/defs_RU.h" @@ -402,6 +404,14 @@ void init_eNB_afterRU(void) */ //init_precoding_weights(RC.gNB[inst]); init_gNB_Tpool(inst); + + // antenna control + if (nr_antenna_control_start_test_server(&gNB->antenna_state, 9999)) + LOG_I(PHY, "[ANTENNA_CTRL] Test control server started on port 9999\n"); + if (inst < RC.nb_nr_macrlc_inst && RC.nrmac && RC.nrmac[inst]) { + RC.nrmac[inst]->antenna_state = &gNB->antenna_state; + LOG_I(PHY, "[ANTENNA_STATE] Connected MAC scheduler to PHY antenna state for dynamic layer adaptation\n"); + } } } diff --git a/openair1/PHY/INIT/nr_init.c b/openair1/PHY/INIT/nr_init.c index ad92ae6c34..c9e0fdf4c2 100644 --- a/openair1/PHY/INIT/nr_init.c +++ b/openair1/PHY/INIT/nr_init.c @@ -158,6 +158,9 @@ void phy_init_nr_gNB(PHY_VARS_gNB *gNB) init_DLSCH_struct(gNB); + nr_init_antenna_state(&gNB->antenna_state, Ptx, Prx, "tx-array-0", "rx-array-0"); + LOG_I(PHY, "[ANTENNA_STATE] Initialized with %d TX, %d RX antennas\n", Ptx, Prx); + gNB->nr_srs_info = (nr_srs_info_t **)malloc16_clear(gNB->max_nb_srs * sizeof(nr_srs_info_t*)); for (int id = 0; id < gNB->max_nb_srs; id++) { gNB->nr_srs_info[id] = (nr_srs_info_t *)malloc16_clear(sizeof(nr_srs_info_t)); diff --git a/openair1/PHY/MODULATION/beamforming.c b/openair1/PHY/MODULATION/beamforming.c index 398f5a0b6f..42b7bf85dd 100644 --- a/openair1/PHY/MODULATION/beamforming.c +++ b/openair1/PHY/MODULATION/beamforming.c @@ -51,6 +51,7 @@ #include "modulation_eNB.h" #include "nr_modulation.h" #include "common/utils/LOG/vcd_signal_dumper.h" +#include "PHY/NR_TRANSPORT/nr_antenna_state.h" int beam_precoding(int32_t **txdataF, int32_t **txdataF_BF, @@ -143,3 +144,35 @@ void nr_beam_precoding(c16_t **txdataF, 15); } } + +void nr_beam_precoding_with_muting(c16_t **txdataF, + c16_t **txdataF_BF, + NR_DL_FRAME_PARMS *frame_parms, + int32_t ***beam_weights, + int slot, + int symbol, + int aa, + int nb_antenna_ports, + int offset, + const nr_antenna_state_t *ant_state) +{ + // Clear output buffer first + memset(&txdataF_BF[aa][symbol * frame_parms->ofdm_symbol_size], 0, + sizeof(c16_t) * frame_parms->ofdm_symbol_size); + // O-RAN energy saving: skip precoding if antenna is muted + // This implements transmission blanking per WG4 spec + if (ant_state != NULL && !nr_is_tx_antenna_active(ant_state, aa)) { + // Antenna is muted - output remains zero (transmission blank) + LOG_D(PHY, "[ANTENNA_MUTE] Antenna %d muted, blanking transmission for symbol %d\n", + aa, symbol); + return; + } + // Normal precoding for active antennas + for (int p = 0; p < nb_antenna_ports; p++) { + multadd_cpx_vector((c16_t *)&txdataF[p][(symbol * frame_parms->ofdm_symbol_size) + offset], + (c16_t *)beam_weights[p][aa], + (c16_t *)&txdataF_BF[aa][symbol * frame_parms->ofdm_symbol_size], + frame_parms->ofdm_symbol_size, + 15); + } +} diff --git a/openair1/PHY/MODULATION/nr_modulation.h b/openair1/PHY/MODULATION/nr_modulation.h index 9e8ac3ba3c..d47f5cf419 100644 --- a/openair1/PHY/MODULATION/nr_modulation.h +++ b/openair1/PHY/MODULATION/nr_modulation.h @@ -103,6 +103,35 @@ void nr_beam_precoding(c16_t **txdataF, int nb_antenna_ports, int offset); + +/*! \brief NR beam precoding with O-RAN compliant antenna muting + * + * Extends nr_beam_precoding to support antenna muting for energy saving + * per O-RAN WG4 MPLANE-TRX-CONTROL specification (o-ran-uplane-conf.yang). + * When an antenna is muted, transmission is blanked (output zeroed). + * + * @param[in] txdataF Input frequency-domain data per antenna port + * @param[out] txdataF_BF Output beamformed data per physical antenna + * @param[in] frame_parms Frame parameters + * @param[in] beam_weights Beamforming weights + * @param[in] slot Current slot number + * @param[in] symbol Current symbol number + * @param[in] aa Physical antenna index + * @param[in] nb_antenna_ports Number of antenna ports + * @param[in] offset Frequency offset + * @param[in] ant_state Antenna state for muting control (NULL = all active) + */ +void nr_beam_precoding_with_muting(c16_t **txdataF, + c16_t **txdataF_BF, + NR_DL_FRAME_PARMS *frame_parms, + int32_t ***beam_weights, + int slot, + int symbol, + int aa, + int nb_antenna_ports, + int offset, + const nr_antenna_state_t *ant_state); + void apply_nr_rotation_TX(const NR_DL_FRAME_PARMS *fp, c16_t *txdataF, const c16_t *symbol_rotation, diff --git a/openair1/PHY/NR_TRANSPORT/antenna_control.c b/openair1/PHY/NR_TRANSPORT/antenna_control.c deleted file mode 100644 index a6de779a64..0000000000 --- a/openair1/PHY/NR_TRANSPORT/antenna_control.c +++ /dev/null @@ -1,18 +0,0 @@ -#include "nr_antenna_control.h" -#include "common/utils/LOG/log.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#ifdef OAI_MPLANE -#include "radio/fhi_72/mplane/antenna-control-mplane.h" -#include "radio/fhi_72/mplane/ru-mplane-api.h" -#endif - - diff --git a/openair1/PHY/NR_TRANSPORT/nr_antenna_control.c b/openair1/PHY/NR_TRANSPORT/nr_antenna_control.c new file mode 100644 index 0000000000..02750b02ca --- /dev/null +++ b/openair1/PHY/NR_TRANSPORT/nr_antenna_control.c @@ -0,0 +1,532 @@ +/* + * Licensed to the OpenAirInterface (OAI) Software Alliance under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The OpenAirInterface Software Alliance licenses this file to You under + * the OAI Public License, Version 1.1 (the "License"); you may not use this file + * except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.openairinterface.org/?page_id=698 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *------------------------------------------------------------------------------- + * For more information about the OpenAirInterface (OAI) Software Alliance: + * contact@openairinterface.org + */ + +/*! \file PHY/NR_TRANSPORT/nr_antenna_control.c + * \brief Antenna control interface implementation with O-RAN M-Plane support + * \author OpenAirInterface + * \date 2025 + * \version 2.0 + */ + +#include "nr_antenna_control.h" +#include "common/utils/LOG/log.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// Try to include M-Plane header if available +#ifdef OAI_MPLANE +#include "radio/fhi_72/mplane/antenna-control.h" +#include "radio/fhi_72/mplane/ru-mplane-api.h" +#endif + +// Global control state +static struct { + pthread_t thread; + int server_fd; + nr_antenna_state_t *ant_state; + volatile bool running; + int port; + nr_antenna_ctrl_backend_t backend; +#ifdef OAI_MPLANE + ru_session_t *ru_session; +#else + void *ru_session; +#endif +} g_ctrl = {0}; + +// ============================================================================ +// Test Socket Server Implementation (Development/Testing Mode) +// ============================================================================ + +// Process a single command and generate response +static void process_command(const char *cmd, char *response, size_t resp_size) +{ + char cmd_upper[256]; + strncpy(cmd_upper, cmd, sizeof(cmd_upper) - 1); + cmd_upper[sizeof(cmd_upper) - 1] = '\0'; + + // Convert to uppercase for case-insensitive matching + for (char *p = cmd_upper; *p; p++) { + if (*p >= 'a' && *p <= 'z') *p -= 32; + } + + // Trim whitespace + char *p = cmd_upper; + while (*p == ' ' || *p == '\t' || *p == '\r' || *p == '\n') p++; + char *end = p + strlen(p) - 1; + while (end > p && (*end == ' ' || *end == '\t' || *end == '\r' || *end == '\n')) *end-- = '\0'; + + if (strncmp(p, "MUTE TX ", 8) == 0) { + int ant_id = atoi(p + 8); + nr_mute_tx_antenna(g_ctrl.ant_state, ant_id); + snprintf(response, resp_size, "OK: TX antenna %d muted\n", ant_id); + } + else if (strncmp(p, "UNMUTE TX ", 10) == 0) { + int ant_id = atoi(p + 10); + nr_unmute_tx_antenna(g_ctrl.ant_state, ant_id); + snprintf(response, resp_size, "OK: TX antenna %d unmuted\n", ant_id); + } + else if (strncmp(p, "MUTE RX ", 8) == 0) { + int ant_id = atoi(p + 8); + nr_mute_rx_antenna(g_ctrl.ant_state, ant_id); + snprintf(response, resp_size, "OK: RX antenna %d muted\n", ant_id); + } + else if (strncmp(p, "UNMUTE RX ", 10) == 0) { + int ant_id = atoi(p + 10); + nr_unmute_rx_antenna(g_ctrl.ant_state, ant_id); + snprintf(response, resp_size, "OK: RX antenna %d unmuted\n", ant_id); + } + else if (strncmp(p, "SET TX MASK ", 12) == 0) { + unsigned int mask; + if (sscanf(p + 12, "%x", &mask) == 1 || sscanf(p + 12, "0X%x", &mask) == 1) { + uint8_t old = nr_set_tx_antenna_bitmask(g_ctrl.ant_state, (uint8_t)mask); + snprintf(response, resp_size, "OK: TX mask 0x%02X -> 0x%02X\n", old, (uint8_t)mask); + } else { + snprintf(response, resp_size, "ERROR: Invalid mask format. Use hex (e.g., 0F or 0x0F)\n"); + } + } + else if (strncmp(p, "SET RX MASK ", 12) == 0) { + unsigned int mask; + if (sscanf(p + 12, "%x", &mask) == 1 || sscanf(p + 12, "0X%x", &mask) == 1) { + uint8_t old = nr_set_rx_antenna_bitmask(g_ctrl.ant_state, (uint8_t)mask); + snprintf(response, resp_size, "OK: RX mask 0x%02X -> 0x%02X\n", old, (uint8_t)mask); + } else { + snprintf(response, resp_size, "ERROR: Invalid mask format. Use hex (e.g., 0F or 0x0F)\n"); + } + } + else if (strcmp(p, "STATUS") == 0) { + bool in_recovery = nr_is_in_recovery_mode(g_ctrl.ant_state); + snprintf(response, resp_size, + "=== Antenna Status ===\n" + "TX Array: %s\n" + " Antennas: %d configured, %d active\n" + " Bitmask: 0x%02X\n" + " Antenna 0 protected: %s\n" + "RX Array: %s\n" + " Antennas: %d configured, %d active\n" + " Bitmask: 0x%02X\n" + "Recovery Mode: %s\n" + "Backend: %s\n", + g_ctrl.ant_state->tx_array_name, + g_ctrl.ant_state->num_tx_antennas, + nr_get_active_tx_antenna_count(g_ctrl.ant_state), + nr_get_tx_antenna_bitmask(g_ctrl.ant_state), + g_ctrl.ant_state->config.protect_antenna0 ? "YES" : "NO", + g_ctrl.ant_state->rx_array_name, + g_ctrl.ant_state->num_rx_antennas, + nr_get_active_rx_antenna_count(g_ctrl.ant_state), + nr_get_rx_antenna_bitmask(g_ctrl.ant_state), + in_recovery ? "ACTIVE (use identity matrix)" : "OFF", + g_ctrl.backend == ANTENNA_CTRL_BACKEND_MPLANE ? "O-RAN M-Plane NETCONF" : + g_ctrl.backend == ANTENNA_CTRL_BACKEND_TEST_SOCKET ? "Test Socket (NOT O-RAN)" : + "None"); + } + else if (strcmp(p, "STATS") == 0) { + uint64_t tx_events, rx_events, muted_slots; + nr_get_antenna_stats(g_ctrl.ant_state, &tx_events, &rx_events, &muted_slots); + snprintf(response, resp_size, + "=== Energy Saving Statistics ===\n" + "TX mute events: %lu\n" + "RX mute events: %lu\n" + "Total muted slots: %lu\n", + tx_events, rx_events, muted_slots); + } + else if (strcmp(p, "RESET STATS") == 0) { + nr_reset_antenna_stats(g_ctrl.ant_state); + snprintf(response, resp_size, "OK: Statistics reset\n"); + } + else if (strcmp(p, "RESET") == 0 || strcmp(p, "UNMUTE ALL") == 0) { + // Reset all antennas to active state with recovery mode + nr_unmute_all_tx_antennas(g_ctrl.ant_state); + nr_unmute_all_rx_antennas(g_ctrl.ant_state); + snprintf(response, resp_size, + "OK: All antennas unmuted (recovery mode: %u ms)\n" + " TX mask: 0x%02X (%d antennas)\n" + " RX mask: 0x%02X (%d antennas)\n", + g_ctrl.ant_state->recovery.recovery_duration_ms, + nr_get_tx_antenna_bitmask(g_ctrl.ant_state), g_ctrl.ant_state->num_tx_antennas, + nr_get_rx_antenna_bitmask(g_ctrl.ant_state), g_ctrl.ant_state->num_rx_antennas); + } + else if (strcmp(p, "PROTECT ANT0 ON") == 0) { + nr_set_antenna0_protection(g_ctrl.ant_state, true); + snprintf(response, resp_size, "OK: Antenna 0 protection enabled (SSB)\n"); + } + else if (strcmp(p, "PROTECT ANT0 OFF") == 0) { + nr_set_antenna0_protection(g_ctrl.ant_state, false); + snprintf(response, resp_size, "OK: Antenna 0 protection disabled (WARNING: may cause UE sync loss)\n"); + } + else if (strcmp(p, "SYNC") == 0) { + // Synchronize to RU (only works with M-Plane backend) + if (nr_antenna_control_sync_to_ru(g_ctrl.ant_state)) { + snprintf(response, resp_size, "OK: Synchronized to RU\n"); + } else { + snprintf(response, resp_size, "ERROR: Sync failed (M-Plane backend required)\n"); + } + } + else if (strcmp(p, "HELP") == 0) { + snprintf(response, resp_size, + "=== Antenna Control ===\n" + "MUTE TX/RX - Mute antenna\n" + "UNMUTE TX/RX - Unmute antenna\n" + "SET TX/RX MASK - Set bitmask\n" + "STATUS - Show state\n" + "STATS - Show statistics\n" + "RESET - Unmute all\n" + "PROTECT ANT0 ON/OFF\n" + "HELP/QUIT\n"); + } + else if (strcmp(p, "QUIT") == 0 || strcmp(p, "EXIT") == 0) { + snprintf(response, resp_size, "BYE\n"); + } + else if (strlen(p) == 0) { + response[0] = '\0'; // Empty command, no response + } + else { + snprintf(response, resp_size, "ERROR: Unknown command '%s'. Type HELP for usage.\n", cmd); + } +} + +// Handle a single client connection +static void handle_client(int client_fd) +{ + char buffer[256]; + char response[1024]; + + // Send welcome message with warning about non-compliance + const char *welcome = + "=== OAI Antenna Control Test Server ===\n" + "WARNING: This is a TEST interface, NOT O-RAN compliant!\n" + "For production, use NETCONF via radio/fhi_72/mplane/\n" + "Type HELP for commands.\n\n"; + send(client_fd, welcome, strlen(welcome), 0); + + while (g_ctrl.running) { + // Send prompt + send(client_fd, "> ", 2, 0); + + // Receive command + ssize_t n = recv(client_fd, buffer, sizeof(buffer) - 1, 0); + if (n <= 0) break; + + buffer[n] = '\0'; + + // Process command + process_command(buffer, response, sizeof(response)); + + // Send response + if (strlen(response) > 0) { + send(client_fd, response, strlen(response), 0); + } + + // Check for quit + if (strstr(buffer, "QUIT") || strstr(buffer, "quit") || + strstr(buffer, "EXIT") || strstr(buffer, "exit")) { + break; + } + } + + close(client_fd); + LOG_I(PHY, "[ANTENNA_CTRL] Client disconnected\n"); +} + +// Server thread function +static void *server_thread(void *arg) +{ + (void)arg; + + struct sockaddr_in client_addr; + socklen_t addr_len = sizeof(client_addr); + + LOG_I(PHY, "[ANTENNA_CTRL] Test server listening on port %d\n", g_ctrl.port); + + while (g_ctrl.running) { + // Accept connection with timeout + fd_set read_fds; + FD_ZERO(&read_fds); + FD_SET(g_ctrl.server_fd, &read_fds); + + struct timeval tv = {.tv_sec = 1, .tv_usec = 0}; + int ret = select(g_ctrl.server_fd + 1, &read_fds, NULL, NULL, &tv); + + if (ret <= 0) continue; + + int client_fd = accept(g_ctrl.server_fd, (struct sockaddr *)&client_addr, &addr_len); + if (client_fd < 0) continue; + + LOG_I(PHY, "[ANTENNA_CTRL] Client connected from %s:%d\n", + inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port)); + + handle_client(client_fd); + } + + return NULL; +} + +// ============================================================================ +// Public API Implementation +// ============================================================================ + +bool nr_antenna_control_init_mplane(nr_antenna_state_t *ant_state, void *ru_session) +{ +#ifdef OAI_MPLANE + if (g_ctrl.running) { + LOG_W(PHY, "[ANTENNA_CTRL] Control interface already active\n"); + return false; + } + + if (ant_state == NULL || ru_session == NULL) { + LOG_E(PHY, "[ANTENNA_CTRL] Invalid parameters\n"); + return false; + } + + g_ctrl.ant_state = ant_state; + g_ctrl.ru_session = (ru_session_t *)ru_session; + g_ctrl.backend = ANTENNA_CTRL_BACKEND_MPLANE; + g_ctrl.running = true; + + // Check if RU supports TRX control feature + if (!mplane_supports_trx_control(g_ctrl.ru_session)) { + LOG_W(PHY, "[ANTENNA_CTRL] RU does not support MPLANE-TRX-CONTROL feature\n"); + LOG_W(PHY, "[ANTENNA_CTRL] Antenna muting may not work on this RU\n"); + } + + LOG_I(PHY, "[ANTENNA_CTRL] Initialized with O-RAN M-Plane NETCONF backend\n"); + LOG_I(PHY, "[ANTENNA_CTRL] TX array: %s, RX array: %s\n", + ant_state->tx_array_name, ant_state->rx_array_name); + + return true; +#else + LOG_E(PHY, "[ANTENNA_CTRL] M-Plane support not compiled in (OAI_MPLANE not defined)\n"); + LOG_E(PHY, "[ANTENNA_CTRL] Use nr_antenna_control_start_test_server() for testing\n"); + return false; +#endif +} + +bool nr_antenna_control_start_test_server(nr_antenna_state_t *ant_state, int port) +{ + if (g_ctrl.running) { + LOG_W(PHY, "[ANTENNA_CTRL] Control interface already active\n"); + return false; + } + + if (ant_state == NULL) { + LOG_E(PHY, "[ANTENNA_CTRL] Antenna state is NULL\n"); + return false; + } + + LOG_W(PHY, "[ANTENNA_CTRL] ======================================================\n"); + LOG_W(PHY, "[ANTENNA_CTRL] Starting TEST socket server - NOT O-RAN COMPLIANT!\n"); + LOG_W(PHY, "[ANTENNA_CTRL] For production O-RAN compliance, use M-Plane NETCONF\n"); + LOG_W(PHY, "[ANTENNA_CTRL] See: radio/fhi_72/mplane/antenna-control-mplane.h\n"); + LOG_W(PHY, "[ANTENNA_CTRL] ======================================================\n"); + + g_ctrl.ant_state = ant_state; + g_ctrl.port = (port > 0) ? port : NR_ANTENNA_CONTROL_TEST_PORT; + g_ctrl.backend = ANTENNA_CTRL_BACKEND_TEST_SOCKET; + + // Create socket + g_ctrl.server_fd = socket(AF_INET, SOCK_STREAM, 0); + if (g_ctrl.server_fd < 0) { + LOG_E(PHY, "[ANTENNA_CTRL] Failed to create socket: %s\n", strerror(errno)); + return false; + } + + // Allow address reuse + int opt = 1; + setsockopt(g_ctrl.server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); + + // Bind + struct sockaddr_in addr = { + .sin_family = AF_INET, + .sin_addr.s_addr = INADDR_ANY, + .sin_port = htons(g_ctrl.port) + }; + + if (bind(g_ctrl.server_fd, (struct sockaddr *)&addr, sizeof(addr)) < 0) { + LOG_E(PHY, "[ANTENNA_CTRL] Failed to bind port %d: %s\n", g_ctrl.port, strerror(errno)); + close(g_ctrl.server_fd); + return false; + } + + // Listen + if (listen(g_ctrl.server_fd, 1) < 0) { + LOG_E(PHY, "[ANTENNA_CTRL] Failed to listen: %s\n", strerror(errno)); + close(g_ctrl.server_fd); + return false; + } + + // Start server thread + g_ctrl.running = true; + if (pthread_create(&g_ctrl.thread, NULL, server_thread, NULL) != 0) { + LOG_E(PHY, "[ANTENNA_CTRL] Failed to create server thread\n"); + g_ctrl.running = false; + close(g_ctrl.server_fd); + return false; + } + + LOG_I(PHY, "[ANTENNA_CTRL] Test server started on port %d\n", g_ctrl.port); + LOG_I(PHY, "[ANTENNA_CTRL] Connect with: nc localhost %d\n", g_ctrl.port); + + return true; +} + +bool nr_antenna_control_start(nr_antenna_state_t *ant_state, int port) +{ + LOG_W(PHY, "[ANTENNA_CTRL] nr_antenna_control_start() is DEPRECATED\n"); + LOG_W(PHY, "[ANTENNA_CTRL] Use nr_antenna_control_init_mplane() for O-RAN compliance\n"); + LOG_W(PHY, "[ANTENNA_CTRL] or nr_antenna_control_start_test_server() for testing\n"); + + return nr_antenna_control_start_test_server(ant_state, port); +} + +void nr_antenna_control_stop(void) +{ + if (!g_ctrl.running) return; + + g_ctrl.running = false; + + if (g_ctrl.backend == ANTENNA_CTRL_BACKEND_TEST_SOCKET) { + // Close server socket to unblock accept() + if (g_ctrl.server_fd >= 0) { + close(g_ctrl.server_fd); + g_ctrl.server_fd = -1; + } + + // Wait for thread + pthread_join(g_ctrl.thread, NULL); + } + + g_ctrl.backend = ANTENNA_CTRL_BACKEND_NONE; + g_ctrl.ant_state = NULL; + g_ctrl.ru_session = NULL; + + LOG_I(PHY, "[ANTENNA_CTRL] Control interface stopped\n"); +} + +bool nr_antenna_control_is_running(void) +{ + return g_ctrl.running; +} + +nr_antenna_ctrl_backend_t nr_antenna_control_get_backend(void) +{ + return g_ctrl.backend; +} + +bool nr_antenna_control_sync_to_ru(nr_antenna_state_t *ant_state) +{ +#ifdef OAI_MPLANE + if (g_ctrl.backend != ANTENNA_CTRL_BACKEND_MPLANE || g_ctrl.ru_session == NULL) { + LOG_W(PHY, "[ANTENNA_CTRL] M-Plane backend not active, cannot sync to RU\n"); + return false; + } + + if (ant_state == NULL) { + ant_state = g_ctrl.ant_state; + } + + if (ant_state == NULL) { + return false; + } + + // Send TX antenna mask to RU + antenna_ctrl_result_t result; + result = mplane_set_tx_antenna_mask(g_ctrl.ru_session, + ant_state->tx_array_name, + nr_get_tx_antenna_bitmask(ant_state), + ant_state->num_tx_antennas); + if (result != ANTENNA_CTRL_SUCCESS) { + LOG_W(PHY, "[ANTENNA_CTRL] Failed to sync TX mask: %s\n", + antenna_ctrl_result_str(result)); + return false; + } + + // Send RX antenna mask to RU + result = mplane_set_rx_antenna_mask(g_ctrl.ru_session, + ant_state->rx_array_name, + nr_get_rx_antenna_bitmask(ant_state), + ant_state->num_rx_antennas); + if (result != ANTENNA_CTRL_SUCCESS) { + LOG_W(PHY, "[ANTENNA_CTRL] Failed to sync RX mask: %s\n", + antenna_ctrl_result_str(result)); + return false; + } + + LOG_I(PHY, "[ANTENNA_CTRL] Successfully synchronized antenna masks to RU\n"); + return true; +#else + LOG_W(PHY, "[ANTENNA_CTRL] M-Plane not compiled in\n"); + return false; +#endif +} + +bool nr_antenna_control_sync_from_ru(nr_antenna_state_t *ant_state) +{ +#ifdef OAI_MPLANE + if (g_ctrl.backend != ANTENNA_CTRL_BACKEND_MPLANE || g_ctrl.ru_session == NULL) { + LOG_W(PHY, "[ANTENNA_CTRL] M-Plane backend not active, cannot sync from RU\n"); + return false; + } + + if (ant_state == NULL) { + ant_state = g_ctrl.ant_state; + } + + if (ant_state == NULL) { + return false; + } + + uint8_t tx_mask, rx_mask; + antenna_ctrl_result_t result; + + // Get TX antenna mask from RU + result = mplane_get_tx_antenna_mask(g_ctrl.ru_session, + ant_state->tx_array_name, + &tx_mask); + if (result == ANTENNA_CTRL_SUCCESS) { + nr_set_tx_antenna_bitmask(ant_state, tx_mask); + } + + // Get RX antenna mask from RU + result = mplane_get_rx_antenna_mask(g_ctrl.ru_session, + ant_state->rx_array_name, + &rx_mask); + if (result == ANTENNA_CTRL_SUCCESS) { + nr_set_rx_antenna_bitmask(ant_state, rx_mask); + } + + LOG_I(PHY, "[ANTENNA_CTRL] Synchronized antenna masks from RU: TX=0x%02X, RX=0x%02X\n", + tx_mask, rx_mask); + return true; +#else + LOG_W(PHY, "[ANTENNA_CTRL] M-Plane not compiled in\n"); + return false; +#endif +} diff --git a/openair1/PHY/NR_TRANSPORT/nr_antenna_control.h b/openair1/PHY/NR_TRANSPORT/nr_antenna_control.h new file mode 100644 index 0000000000..bc2e061af2 --- /dev/null +++ b/openair1/PHY/NR_TRANSPORT/nr_antenna_control.h @@ -0,0 +1,123 @@ +#ifndef NR_ANTENNA_CONTROL_H +#define NR_ANTENNA_CONTROL_H + +#include "nr_antenna_state.h" +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/// Default port for the test socket server (development mode only) +#define NR_ANTENNA_CONTROL_TEST_PORT 9999 + +/** + * @brief Antenna control backend type + */ +typedef enum { + ANTENNA_CTRL_BACKEND_NONE = 0, ///< No external control + ANTENNA_CTRL_BACKEND_MPLANE, ///< O-RAN M-Plane NETCONF (production) + ANTENNA_CTRL_BACKEND_TEST_SOCKET ///< Test socket interface (development) +} nr_antenna_ctrl_backend_t; + +/** + * @brief Initialize antenna control with O-RAN M-Plane NETCONF backend + * + * This is the O-RAN compliant production interface. It connects the + * antenna state to a real NETCONF session to an O-RU. + * + * When antenna masks are changed via nr_set_tx_antenna_bitmask() or + * similar functions, the changes are automatically propagated to the + * RU via NETCONF edit-config operations. + * + * @param ant_state Pointer to the antenna state to control + * @param ru_session Active M-Plane NETCONF session (from radio/fhi_72/mplane/) + * @return true if initialization successful + */ +bool nr_antenna_control_init_mplane(nr_antenna_state_t *ant_state, + void *ru_session); + +/** + * @brief Start the test socket server (development mode only) + * + * Creates a simple TCP server for testing antenna control without + * NETCONF infrastructure. This is NOT O-RAN compliant. + * + * Commands supported (for backwards compatibility): + * MUTE TX - Mute TX antenna + * UNMUTE TX - Unmute TX antenna + * MUTE RX - Mute RX antenna + * UNMUTE RX - Unmute RX antenna + * SET TX MASK - Set TX antenna bitmask + * SET RX MASK - Set RX antenna bitmask + * STATUS - Get current antenna state + * STATS - Get energy saving statistics + * RESET STATS - Reset statistics + * QUIT - Close connection + * + * @param ant_state Pointer to the antenna state to control + * @param port TCP port to listen on (use 0 for default) + * @return true if server started successfully + * + * @note This function logs a warning that it's not O-RAN compliant + */ +bool nr_antenna_control_start_test_server(nr_antenna_state_t *ant_state, int port); + +/** + * @brief Legacy function - start antenna control server + * + * @deprecated Use nr_antenna_control_init_mplane() for O-RAN compliance + * or nr_antenna_control_start_test_server() for testing. + * + * This function starts the test socket server for backwards compatibility. + * It logs a deprecation warning. + */ +bool nr_antenna_control_start(nr_antenna_state_t *ant_state, int port); + +/** + * @brief Stop the antenna control interface + * + * Stops the test socket server if running. + * For M-Plane backend, this disconnects from NETCONF notifications. + */ +void nr_antenna_control_stop(void); + +/** + * @brief Check if antenna control is active + * @return true if control interface is running + */ +bool nr_antenna_control_is_running(void); + +/** + * @brief Get current antenna control backend type + * @return Backend type currently in use + */ +nr_antenna_ctrl_backend_t nr_antenna_control_get_backend(void); + +/** + * @brief Synchronize local antenna state to RU via M-Plane NETCONF + * + * Sends the current antenna bitmask configuration to the RU. + * Only applicable when using the M-Plane NETCONF backend. + * + * @param ant_state Pointer to the antenna state + * @return true if synchronization successful or not applicable + */ +bool nr_antenna_control_sync_to_ru(nr_antenna_state_t *ant_state); + +/** + * @brief Synchronize RU antenna state to local via M-Plane NETCONF + * + * Reads the current antenna bitmask from the RU and updates local state. + * Only applicable when using the M-Plane NETCONF backend. + * + * @param ant_state Pointer to the antenna state + * @return true if synchronization successful or not applicable + */ +bool nr_antenna_control_sync_from_ru(nr_antenna_state_t *ant_state); + +#ifdef __cplusplus +} +#endif + +#endif /* NR_ANTENNA_CONTROL_H */ diff --git a/openair1/PHY/NR_TRANSPORT/nr_antenna_state.c b/openair1/PHY/NR_TRANSPORT/nr_antenna_state.c new file mode 100644 index 0000000000..3ffed1c615 --- /dev/null +++ b/openair1/PHY/NR_TRANSPORT/nr_antenna_state.c @@ -0,0 +1,366 @@ +#include "nr_antenna_state.h" +#include "common/utils/LOG/log.h" +#include +#include + +// Helper to count set bits in a byte +static inline int popcount8(uint8_t x) { + int count = 0; + while (x) { + count += x & 1; + x >>= 1; + } + return count; +} + +// Get current timestamp in microseconds +static inline uint64_t get_timestamp_us(void) { + struct timespec ts; + clock_gettime(CLOCK_MONOTONIC, &ts); + return (uint64_t)ts.tv_sec * 1000000ULL + (uint64_t)ts.tv_nsec / 1000ULL; +} + +void nr_init_antenna_state(nr_antenna_state_t *state, + uint8_t num_tx, + uint8_t num_rx, + const char *tx_array_name, + const char *rx_array_name) +{ + if (state == NULL) return; + + memset(state, 0, sizeof(nr_antenna_state_t)); + + state->num_tx_antennas = (num_tx > NR_MAX_ANTENNAS) ? NR_MAX_ANTENNAS : num_tx; + state->num_rx_antennas = (num_rx > NR_MAX_ANTENNAS) ? NR_MAX_ANTENNAS : num_rx; + + // Initialize with all configured antennas active + // Create bitmask with num_tx/num_rx lowest bits set + state->tx_antenna_bitmask = (1 << state->num_tx_antennas) - 1; + state->rx_antenna_bitmask = (1 << state->num_rx_antennas) - 1; + + // Set O-RAN array names + if (tx_array_name) { + strncpy(state->tx_array_name, tx_array_name, sizeof(state->tx_array_name) - 1); + } else { + strncpy(state->tx_array_name, "tx-array-0", sizeof(state->tx_array_name) - 1); + } + + if (rx_array_name) { + strncpy(state->rx_array_name, rx_array_name, sizeof(state->rx_array_name) - 1); + } else { + strncpy(state->rx_array_name, "rx-array-0", sizeof(state->rx_array_name) - 1); + } + + pthread_mutex_init(&state->lock, NULL); + + state->stats.last_update_timestamp = get_timestamp_us(); + + // Initialize recovery mode + state->recovery.in_recovery = false; + state->recovery.recovery_start_ts = 0; + state->recovery.recovery_duration_ms = 500; // 500ms default recovery time + + // Protect antenna 0 by default (needed for SSB/PSS/SSS) + state->config.protect_antenna0 = true; + + LOG_I(PHY, "[ANTENNA_STATE] Initialized: TX antennas=%d (mask=0x%02X, array=%s), " + "RX antennas=%d (mask=0x%02X, array=%s), antenna0_protected=%d\n", + state->num_tx_antennas, state->tx_antenna_bitmask, state->tx_array_name, + state->num_rx_antennas, state->rx_antenna_bitmask, state->rx_array_name, + state->config.protect_antenna0); +} + +void nr_free_antenna_state(nr_antenna_state_t *state) +{ + if (state == NULL) return; + pthread_mutex_destroy(&state->lock); +} + +uint8_t nr_set_tx_antenna_bitmask(nr_antenna_state_t *state, uint8_t bitmask) +{ + if (state == NULL) return 0; + + pthread_mutex_lock(&state->lock); + + uint8_t old_bitmask = state->tx_antenna_bitmask; + + // Mask to only valid antenna bits + uint8_t valid_mask = (1 << state->num_tx_antennas) - 1; + state->tx_antenna_bitmask = bitmask & valid_mask; + + // Update statistics + if (state->tx_antenna_bitmask != old_bitmask) { + state->stats.tx_mute_events++; + state->stats.last_update_timestamp = get_timestamp_us(); + + LOG_I(PHY, "[ANTENNA_STATE] TX bitmask changed: 0x%02X -> 0x%02X (array=%s, active=%d/%d)\n", + old_bitmask, state->tx_antenna_bitmask, state->tx_array_name, + popcount8(state->tx_antenna_bitmask), state->num_tx_antennas); + } + + pthread_mutex_unlock(&state->lock); + + return old_bitmask; +} + +uint8_t nr_set_rx_antenna_bitmask(nr_antenna_state_t *state, uint8_t bitmask) +{ + if (state == NULL) return 0; + + pthread_mutex_lock(&state->lock); + + uint8_t old_bitmask = state->rx_antenna_bitmask; + + // Mask to only valid antenna bits + uint8_t valid_mask = (1 << state->num_rx_antennas) - 1; + state->rx_antenna_bitmask = bitmask & valid_mask; + + // Update statistics + if (state->rx_antenna_bitmask != old_bitmask) { + state->stats.rx_mute_events++; + state->stats.last_update_timestamp = get_timestamp_us(); + + LOG_I(PHY, "[ANTENNA_STATE] RX bitmask changed: 0x%02X -> 0x%02X (array=%s, active=%d/%d)\n", + old_bitmask, state->rx_antenna_bitmask, state->rx_array_name, + popcount8(state->rx_antenna_bitmask), state->num_rx_antennas); + } + + pthread_mutex_unlock(&state->lock); + + return old_bitmask; +} + +uint8_t nr_get_tx_antenna_bitmask(const nr_antenna_state_t *state) +{ + if (state == NULL) return 0xFF; + pthread_mutex_lock((pthread_mutex_t*)&state->lock); + uint8_t m = state->tx_antenna_bitmask; + pthread_mutex_unlock((pthread_mutex_t*)&state->lock); + return m; +} + +uint8_t nr_get_rx_antenna_bitmask(const nr_antenna_state_t *state) +{ + if (state == NULL) return 0xFF; + pthread_mutex_lock((pthread_mutex_t*)&state->lock); + uint8_t m = state->rx_antenna_bitmask; + pthread_mutex_unlock((pthread_mutex_t*)&state->lock); + return m; +} + +bool nr_is_tx_antenna_active(const nr_antenna_state_t *state, uint8_t ant_id) +{ + if (state == NULL || ant_id >= state->num_tx_antennas) return false; + return (state->tx_antenna_bitmask & (1 << ant_id)) != 0; +} + +bool nr_is_rx_antenna_active(const nr_antenna_state_t *state, uint8_t ant_id) +{ + if (state == NULL || ant_id >= state->num_rx_antennas) return false; + return (state->rx_antenna_bitmask & (1 << ant_id)) != 0; +} + +int nr_get_active_tx_antenna_count(const nr_antenna_state_t *state) +{ + if (state == NULL) return 0; + return popcount8(state->tx_antenna_bitmask); +} + +int nr_get_active_rx_antenna_count(const nr_antenna_state_t *state) +{ + if (state == NULL) return 0; + return popcount8(state->rx_antenna_bitmask); +} + +void nr_mute_tx_antenna(nr_antenna_state_t *state, uint8_t ant_id) +{ + if (state == NULL || ant_id >= state->num_tx_antennas) return; + + // Protect antenna 0 if configured (needed for SSB/PSS/SSS transmission) + if (ant_id == 0 && state->config.protect_antenna0) { + LOG_W(PHY, "[ANTENNA_STATE] Cannot mute TX antenna 0: protected for SSB transmission\n"); + LOG_W(PHY, "[ANTENNA_STATE] Use 'SET TX MASK' to override protection if needed\n"); + return; + } + + uint8_t new_mask = state->tx_antenna_bitmask & ~(1 << ant_id); + nr_set_tx_antenna_bitmask(state, new_mask); +} + +void nr_unmute_tx_antenna(nr_antenna_state_t *state, uint8_t ant_id) +{ + if (state == NULL || ant_id >= state->num_tx_antennas) return; + + uint8_t new_mask = state->tx_antenna_bitmask | (1 << ant_id); + nr_set_tx_antenna_bitmask(state, new_mask); +} + +void nr_mute_rx_antenna(nr_antenna_state_t *state, uint8_t ant_id) +{ + if (state == NULL || ant_id >= state->num_rx_antennas) return; + + uint8_t new_mask = state->rx_antenna_bitmask & ~(1 << ant_id); + nr_set_rx_antenna_bitmask(state, new_mask); +} + +void nr_unmute_rx_antenna(nr_antenna_state_t *state, uint8_t ant_id) +{ + if (state == NULL || ant_id >= state->num_rx_antennas) return; + + uint8_t new_mask = state->rx_antenna_bitmask | (1 << ant_id); + nr_set_rx_antenna_bitmask(state, new_mask); +} + +int nr_get_effective_mimo_layers(const nr_antenna_state_t *state, int requested_layers) +{ + if (state == NULL) return requested_layers; + + int active_tx = nr_get_active_tx_antenna_count(state); + + // MIMO layers cannot exceed active TX antennas + int effective = (requested_layers < active_tx) ? requested_layers : active_tx; + + // Must have at least 1 layer if any antenna is active + if (effective < 1 && active_tx > 0) { + effective = 1; + } + + return effective; +} + +void nr_log_antenna_state(const nr_antenna_state_t *state) +{ + if (state == NULL) { + LOG_I(PHY, "[ANTENNA_STATE] State is NULL\n"); + return; + } + + LOG_I(PHY, "[ANTENNA_STATE] === Antenna State Report ===\n"); + LOG_I(PHY, "[ANTENNA_STATE] TX Array: %s\n", state->tx_array_name); + LOG_I(PHY, "[ANTENNA_STATE] Configured: %d antennas\n", state->num_tx_antennas); + LOG_I(PHY, "[ANTENNA_STATE] Bitmask: 0x%02X\n", state->tx_antenna_bitmask); + LOG_I(PHY, "[ANTENNA_STATE] Active: %d antennas\n", nr_get_active_tx_antenna_count(state)); + LOG_I(PHY, "[ANTENNA_STATE] Individual: "); + for (int i = 0; i < state->num_tx_antennas; i++) { + LOG_I(PHY, "TX%d=%s ", i, nr_is_tx_antenna_active(state, i) ? "ON" : "OFF"); + } + LOG_I(PHY, "\n"); + + LOG_I(PHY, "[ANTENNA_STATE] RX Array: %s\n", state->rx_array_name); + LOG_I(PHY, "[ANTENNA_STATE] Configured: %d antennas\n", state->num_rx_antennas); + LOG_I(PHY, "[ANTENNA_STATE] Bitmask: 0x%02X\n", state->rx_antenna_bitmask); + LOG_I(PHY, "[ANTENNA_STATE] Active: %d antennas\n", nr_get_active_rx_antenna_count(state)); + + LOG_I(PHY, "[ANTENNA_STATE] Statistics:\n"); + LOG_I(PHY, "[ANTENNA_STATE] TX mute events: %lu\n", state->stats.tx_mute_events); + LOG_I(PHY, "[ANTENNA_STATE] RX mute events: %lu\n", state->stats.rx_mute_events); + LOG_I(PHY, "[ANTENNA_STATE] Total muted slots: %lu\n", state->stats.total_muted_slots); + LOG_I(PHY, "[ANTENNA_STATE] =============================\n"); +} + +void nr_get_antenna_stats(const nr_antenna_state_t *state, + uint64_t *tx_mute_events, + uint64_t *rx_mute_events, + uint64_t *total_muted_slots) +{ + if (state == NULL) return; + + if (tx_mute_events) *tx_mute_events = state->stats.tx_mute_events; + if (rx_mute_events) *rx_mute_events = state->stats.rx_mute_events; + if (total_muted_slots) *total_muted_slots = state->stats.total_muted_slots; +} + +void nr_reset_antenna_stats(nr_antenna_state_t *state) +{ + if (state == NULL) return; + + pthread_mutex_lock(&state->lock); + + state->stats.tx_mute_events = 0; + state->stats.rx_mute_events = 0; + state->stats.total_muted_slots = 0; + state->stats.energy_saved_estimate = 0; + state->stats.last_update_timestamp = get_timestamp_us(); + + LOG_I(PHY, "[ANTENNA_STATE] Statistics reset\n"); + + pthread_mutex_unlock(&state->lock); +} + +void nr_unmute_all_tx_antennas(nr_antenna_state_t *state) +{ + if (state == NULL) return; + + pthread_mutex_lock(&state->lock); + + uint8_t old_mask = state->tx_antenna_bitmask; + uint8_t all_on = (1 << state->num_tx_antennas) - 1; + state->tx_antenna_bitmask = all_on; + + // Enter recovery mode if we were muted + if (old_mask != all_on) { + state->recovery.in_recovery = true; + state->recovery.recovery_start_ts = get_timestamp_us(); + LOG_I(PHY, "[ANTENNA_STATE] Entering recovery mode for %u ms after unmuting TX antennas\n", + state->recovery.recovery_duration_ms); + } + + state->stats.tx_mute_events++; + state->stats.last_update_timestamp = get_timestamp_us(); + + LOG_I(PHY, "[ANTENNA_STATE] All TX antennas unmuted: 0x%02X -> 0x%02X (array=%s)\n", + old_mask, all_on, state->tx_array_name); + + pthread_mutex_unlock(&state->lock); +} + +void nr_unmute_all_rx_antennas(nr_antenna_state_t *state) +{ + if (state == NULL) return; + + pthread_mutex_lock(&state->lock); + + uint8_t old_mask = state->rx_antenna_bitmask; + uint8_t all_on = (1 << state->num_rx_antennas) - 1; + state->rx_antenna_bitmask = all_on; + + state->stats.rx_mute_events++; + state->stats.last_update_timestamp = get_timestamp_us(); + + LOG_I(PHY, "[ANTENNA_STATE] All RX antennas unmuted: 0x%02X -> 0x%02X (array=%s)\n", + old_mask, all_on, state->rx_array_name); + + pthread_mutex_unlock(&state->lock); +} + +bool nr_is_in_recovery_mode(const nr_antenna_state_t *state) +{ + if (state == NULL) return false; + + if (!state->recovery.in_recovery) return false; + + // Check if recovery period has elapsed + uint64_t now = get_timestamp_us(); + uint64_t elapsed_us = now - state->recovery.recovery_start_ts; + uint64_t duration_us = (uint64_t)state->recovery.recovery_duration_ms * 1000ULL; + + if (elapsed_us >= duration_us) { + // Recovery complete - cast away const for state update + nr_antenna_state_t *s = (nr_antenna_state_t *)state; + s->recovery.in_recovery = false; + LOG_I(PHY, "[ANTENNA_STATE] Recovery mode complete after %lu ms\n", elapsed_us / 1000); + return false; + } + + return true; +} + +void nr_set_antenna0_protection(nr_antenna_state_t *state, bool protect) +{ + if (state == NULL) return; + + pthread_mutex_lock(&state->lock); + state->config.protect_antenna0 = protect; + LOG_I(PHY, "[ANTENNA_STATE] Antenna 0 protection %s\n", protect ? "enabled" : "disabled"); + pthread_mutex_unlock(&state->lock); +} diff --git a/openair1/PHY/NR_TRANSPORT/nr_antenna_state.h b/openair1/PHY/NR_TRANSPORT/nr_antenna_state.h new file mode 100644 index 0000000000..eb810a8e2b --- /dev/null +++ b/openair1/PHY/NR_TRANSPORT/nr_antenna_state.h @@ -0,0 +1,239 @@ +#ifndef NR_ANTENNA_STATE_H +#define NR_ANTENNA_STATE_H + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/// Maximum number of physical antennas supported +#define NR_MAX_ANTENNAS 8 + +/// O-RAN compliant antenna state for TX/RX arrays +/// Mirrors o-ran-uplane-conf.yang: tx-array-antenna-mask-config +typedef struct { + /// Antenna bitmask per O-RAN WG4 spec: bit N = 1 means antenna N is active + /// Corresponds to o-ran-uplane-conf:antenna-bitmask + uint8_t tx_antenna_bitmask; + uint8_t rx_antenna_bitmask; + + /// Total number of configured antennas + uint8_t num_tx_antennas; + uint8_t num_rx_antennas; + + /// Thread safety for runtime updates + pthread_mutex_t lock; + + /// Energy saving statistics (for research/monitoring) + struct { + uint64_t tx_mute_events; ///< Number of TX mute operations + uint64_t rx_mute_events; ///< Number of RX mute operations + uint64_t total_muted_slots; ///< Total slots with at least one antenna muted + uint64_t energy_saved_estimate; ///< Estimated energy saved (arbitrary units) + uint64_t last_update_timestamp; ///< Last update time (for O1 notifications) + } stats; + + /// O-RAN compliance: array name for NETCONF identification + char tx_array_name[64]; + char rx_array_name[64]; + + /// Recovery mode: after unmuting, allow CSI to update before using full capacity + struct { + bool in_recovery; ///< Currently in recovery mode + uint64_t recovery_start_ts; ///< Timestamp when recovery started (us) + uint32_t recovery_duration_ms; ///< Recovery duration in milliseconds (default 500ms) + } recovery; + + /// Configuration flags + struct { + bool protect_antenna0; ///< Never mute antenna 0 (needed for SSB) + } config; + +} nr_antenna_state_t; + +/** + * @brief Initialize antenna state with all antennas active + * @param state Pointer to antenna state structure + * @param num_tx Number of TX antennas + * @param num_rx Number of RX antennas + * @param tx_array_name O-RAN array name for TX (e.g., "tx-array-0") + * @param rx_array_name O-RAN array name for RX (e.g., "rx-array-0") + */ +void nr_init_antenna_state(nr_antenna_state_t *state, + uint8_t num_tx, + uint8_t num_rx, + const char *tx_array_name, + const char *rx_array_name); + +/** + * @brief Free antenna state resources + * @param state Pointer to antenna state structure + */ +void nr_free_antenna_state(nr_antenna_state_t *state); + +/** + * @brief Set TX antenna bitmask (O-RAN O1 interface equivalent) + * + * This function implements the O-RAN WG4 MPLANE-TRX-CONTROL feature. + * Corresponds to NETCONF edit-config on tx-array-antenna-mask-config. + * + * @param state Pointer to antenna state structure + * @param bitmask Antenna bitmask (bit N = 1 means antenna N active) + * @return Previous bitmask value + */ +uint8_t nr_set_tx_antenna_bitmask(nr_antenna_state_t *state, uint8_t bitmask); + +/** + * @brief Set RX antenna bitmask (O-RAN O1 interface equivalent) + * @param state Pointer to antenna state structure + * @param bitmask Antenna bitmask (bit N = 1 means antenna N active) + * @return Previous bitmask value + */ +uint8_t nr_set_rx_antenna_bitmask(nr_antenna_state_t *state, uint8_t bitmask); + +/** + * @brief Get current TX antenna bitmask + * @param state Pointer to antenna state structure + * @return Current TX antenna bitmask + */ +uint8_t nr_get_tx_antenna_bitmask(const nr_antenna_state_t *state); + +/** + * @brief Get current RX antenna bitmask + * @param state Pointer to antenna state structure + * @return Current RX antenna bitmask + */ +uint8_t nr_get_rx_antenna_bitmask(const nr_antenna_state_t *state); + +/** + * @brief Check if a specific TX antenna is active + * @param state Pointer to antenna state structure + * @param ant_id Antenna index (0-based) + * @return true if antenna is active, false if muted + */ +bool nr_is_tx_antenna_active(const nr_antenna_state_t *state, uint8_t ant_id); + +/** + * @brief Check if a specific RX antenna is active + * @param state Pointer to antenna state structure + * @param ant_id Antenna index (0-based) + * @return true if antenna is active, false if muted + */ +bool nr_is_rx_antenna_active(const nr_antenna_state_t *state, uint8_t ant_id); + +/** + * @brief Get count of currently active TX antennas + * @param state Pointer to antenna state structure + * @return Number of active TX antennas + */ +int nr_get_active_tx_antenna_count(const nr_antenna_state_t *state); + +/** + * @brief Get count of currently active RX antennas + * @param state Pointer to antenna state structure + * @return Number of active RX antennas + */ +int nr_get_active_rx_antenna_count(const nr_antenna_state_t *state); + +/** + * @brief Mute a specific TX antenna + * @param state Pointer to antenna state structure + * @param ant_id Antenna index to mute (0-based) + */ +void nr_mute_tx_antenna(nr_antenna_state_t *state, uint8_t ant_id); + +/** + * @brief Unmute a specific TX antenna + * @param state Pointer to antenna state structure + * @param ant_id Antenna index to unmute (0-based) + */ +void nr_unmute_tx_antenna(nr_antenna_state_t *state, uint8_t ant_id); + +/** + * @brief Mute a specific RX antenna + * @param state Pointer to antenna state structure + * @param ant_id Antenna index to mute (0-based) + */ +void nr_mute_rx_antenna(nr_antenna_state_t *state, uint8_t ant_id); + +/** + * @brief Unmute a specific RX antenna + * @param state Pointer to antenna state structure + * @param ant_id Antenna index to unmute (0-based) + */ +void nr_unmute_rx_antenna(nr_antenna_state_t *state, uint8_t ant_id); + +/** + * @brief Get effective number of MIMO layers based on active antennas + * + * For MIMO, the number of layers is limited by active antennas. + * This function returns min(requested_layers, active_antennas). + * + * @param state Pointer to antenna state structure + * @param requested_layers Originally requested number of layers + * @return Effective number of layers that can be used + */ +int nr_get_effective_mimo_layers(const nr_antenna_state_t *state, int requested_layers); + +/** + * @brief Log antenna state for debugging + * @param state Pointer to antenna state structure + */ +void nr_log_antenna_state(const nr_antenna_state_t *state); + +/** + * @brief Get energy saving statistics + * @param state Pointer to antenna state structure + * @param tx_mute_events Output: number of TX mute events + * @param rx_mute_events Output: number of RX mute events + * @param total_muted_slots Output: total slots with muting + */ +void nr_get_antenna_stats(const nr_antenna_state_t *state, + uint64_t *tx_mute_events, + uint64_t *rx_mute_events, + uint64_t *total_muted_slots); + +/** + * @brief Reset energy saving statistics + * @param state Pointer to antenna state structure + */ +void nr_reset_antenna_stats(nr_antenna_state_t *state); + +/** + * @brief Unmute all TX antennas and enter recovery mode + * @param state Pointer to antenna state structure + * + * This initiates a recovery period during which the scheduler + * uses conservative settings to allow UE CSI reports to update. + */ +void nr_unmute_all_tx_antennas(nr_antenna_state_t *state); + +/** + * @brief Unmute all RX antennas + * @param state Pointer to antenna state structure + */ +void nr_unmute_all_rx_antennas(nr_antenna_state_t *state); + +/** + * @brief Check if system is in recovery mode after unmuting + * @param state Pointer to antenna state structure + * @return true if in recovery mode + */ +bool nr_is_in_recovery_mode(const nr_antenna_state_t *state); + +/** + * @brief Set antenna 0 protection (SSB antenna) + * @param state Pointer to antenna state structure + * @param protect true to prevent muting antenna 0 + */ +void nr_set_antenna0_protection(nr_antenna_state_t *state, bool protect); + +#ifdef __cplusplus +} +#endif + +#endif /* NR_ANTENNA_STATE_H */ + diff --git a/openair1/PHY/defs_gNB.h b/openair1/PHY/defs_gNB.h index f3350d707e..91308f2583 100644 --- a/openair1/PHY/defs_gNB.h +++ b/openair1/PHY/defs_gNB.h @@ -43,6 +43,7 @@ #include "PHY/CODING/nrLDPC_extern.h" #include "PHY/CODING/nrLDPC_decoder/nrLDPC_types.h" #include "nfapi_nr_interface_scf.h" +#include "PHY/NR_TRANSPORT/nr_antenna_state.h" #define MAX_NUM_RU_PER_gNB 8 #define MAX_PUCCH0_NID 8 @@ -445,6 +446,8 @@ typedef struct PHY_VARS_gNB_s { bool enable_analog_das; + nr_antenna_state_t antenna_state; + time_stats_t l1_tx_proc; time_stats_t l1_rx_proc; diff --git a/openair1/SCHED_NR/nr_ru_procedures.c b/openair1/SCHED_NR/nr_ru_procedures.c index 4cf05d5772..9540385244 100644 --- a/openair1/SCHED_NR/nr_ru_procedures.c +++ b/openair1/SCHED_NR/nr_ru_procedures.c @@ -46,6 +46,19 @@ #include +// Rate-limit TX power logs (every N slots per antenna task, startSymbol==0 only) +#ifndef TXF_PWR_LOG_PERIOD_FRAME +#define TXF_PWR_LOG_PERIOD_FRAME 100 +#endif + +static inline int count_nnz_c16(const c16_t *v, int n) +{ + int nnz = 0; + for (int i = 0; i < n; i++) + nnz += (v[i].r != 0) || (v[i].i != 0); + return nnz; +} + // RU OFDM Modulator gNodeB // OFDM modulation core routine, generates a first_symbol to first_symbol+num_symbols on a particular slot and TX antenna port void nr_feptx0(RU_t *ru, int tti_tx, int first_symbol, int num_symbols, int aa) @@ -246,14 +259,33 @@ void nr_feptx(void *arg) } // If there is no digital beamforming we just need to copy the data to RU - if (ru->config.dbt_config.num_dig_beams == 0 || ru->gNB_list[0]->common_vars.analog_bf) + if (ru->config.dbt_config.num_dig_beams == 0 || ru->gNB_list[0]->common_vars.analog_bf) { memcpy((void*)&ru->common.txdataF_BF[tx_idx][txdataF_BF_offset], (void*)&ru->gNB_list[0]->common_vars.txdataF[bb][aa][txdataF_offset], numSamples * sizeof(int32_t)); + } else { AssertFatal(false, "This needs to be fixed by using appropriate beams from config\n"); } + if (startSymbol == 0 && ((ru->proc.frame_tx % TXF_PWR_LOG_PERIOD_FRAME) == 0)) { + const c16_t *src = (const c16_t *)&ru->gNB_list[0]->common_vars.txdataF[bb][aa][txdataF_offset]; + const c16_t *dst = (const c16_t *)&ru->common.txdataF_BF[tx_idx][txdataF_BF_offset]; + + // signal_energy_nodc() in OAI expects "2*N" for c16_t buffers (see existing usage in nr_feptx_ofdm()). + const int src_pwr_db = dB_fixed(signal_energy_nodc((c16_t *)src, 2 * numSamples)); + const int dst_pwr_db = dB_fixed(signal_energy_nodc((c16_t *)dst, 2 * numSamples)); + const int src_nnz = count_nnz_c16(src, numSamples); + const int dst_nnz = count_nnz_c16(dst, numSamples); + + LOG_I(PHY, + "[TXF_PWR] frame=%d slot=%d beam=%d aa=%d tx_idx=%d sym0 len=%d: " + "SRC pwr=%d dB nnz=%d | DST pwr=%d dB nnz=%d\n", + ru->proc.frame_tx, slot, bb, aa, tx_idx, numSamples, + src_pwr_db, src_nnz, dst_pwr_db, dst_nnz); + } + + if (tx_idx == 0) stop_meas(&ru->precoding_stats); VCD_SIGNAL_DUMPER_DUMP_FUNCTION_BY_NAME(VCD_SIGNAL_DUMPER_FUNCTIONS_PHY_PROCEDURES_RU_FEPTX_PREC+feptx->aid , 0); diff --git a/openair2/E2AP/flexric b/openair2/E2AP/flexric index 6a595d8b24..87816cfa3e 160000 --- a/openair2/E2AP/flexric +++ b/openair2/E2AP/flexric @@ -1 +1 @@ -Subproject commit 6a595d8b243601b7148f60b5cb2cec47658e58c5 +Subproject commit 87816cfa3e749cd6b99d7c15748337af8383f69a diff --git a/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_dlsch.c b/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_dlsch.c index 5dd7e89fec..17941d4368 100644 --- a/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_dlsch.c +++ b/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_dlsch.c @@ -435,7 +435,7 @@ static bool allocate_dl_retransmission(gNB_MAC_INST *nr_mac, NR_UE_DL_BWP_t *dl_bwp = &UE->current_DL_BWP; NR_UE_UL_BWP_t *ul_bwp = &UE->current_UL_BWP; NR_sched_pdsch_t new_sched = sched_ctrl->harq_processes[current_harq_pid].sched_pdsch; - int layers = get_dl_nrOfLayers(sched_ctrl, dl_bwp->dci_format); + int layers = get_dl_nrOfLayers(nr_mac, sched_ctrl, dl_bwp->dci_format); int pm_index = get_pm_index(nr_mac, UE, dl_bwp->dci_format, layers, nr_mac->radio_config.pdsch_AntennaPorts.XP); // If the RI changed between current rtx and a previous transmission @@ -732,7 +732,7 @@ static void pf_dl(gNB_MAC_INST *mac, sched_ctrl->dl_bler_stats.mcs = selected_mcs; } else selected_mcs = get_mcs_from_bler(bo, stats, &sched_ctrl->dl_bler_stats, max_mcs, frame); - int l = get_dl_nrOfLayers(sched_ctrl, current_BWP->dci_format); + int l = get_dl_nrOfLayers(mac, sched_ctrl, current_BWP->dci_format); const uint8_t Qm = nr_get_Qm_dl(selected_mcs, current_BWP->mcsTableIdx); const uint16_t R = nr_get_code_rate_dl(selected_mcs, current_BWP->mcsTableIdx); uint32_t tbs = nr_compute_tbs(Qm, @@ -877,7 +877,7 @@ static void pf_dl(gNB_MAC_INST *mac, sched_ctrl->cce_index = CCEIndex; fill_pdcch_vrb_map(mac, CC_id, &sched_ctrl->sched_pdcch, CCEIndex, sched_ctrl->aggregation_level, beam.idx); - int l = get_dl_nrOfLayers(sched_ctrl, dl_bwp->dci_format); + int l = get_dl_nrOfLayers(mac, sched_ctrl, dl_bwp->dci_format); NR_sched_pdsch_t sched_pdsch = { // rbSize below .rbStart = rbStart, diff --git a/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_primitives.c b/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_primitives.c index 4207f6de93..95794a9542 100644 --- a/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_primitives.c +++ b/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_primitives.c @@ -127,14 +127,13 @@ static void determine_aggregation_level_search_order(int agg_level_search_order[ static int nr_mac_interrupt_ue_transmission(gNB_MAC_INST *mac, NR_UE_info_t *UE, int slots, int slots_per_frame); -uint8_t get_dl_nrOfLayers(const NR_UE_sched_ctrl_t *sched_ctrl, const nr_dci_format_t dci_format) +uint8_t get_dl_nrOfLayers(const gNB_MAC_INST *mac, const NR_UE_sched_ctrl_t *sched_ctrl, const nr_dci_format_t dci_format) { - // TODO check this but it should be enough for now - // if there is not csi report activated RI is 0 from initialization - if(dci_format == NR_DL_DCI_FORMAT_1_0) - return 1; - else - return sched_ctrl->CSI_report.cri_ri_li_pmi_cqi_report.ri + 1; + int layers = (dci_format == NR_DL_DCI_FORMAT_1_0) ? 1 + : sched_ctrl->CSI_report.cri_ri_li_pmi_cqi_report.ri + 1; + if (mac && mac->antenna_state) + return nr_get_effective_mimo_layers(mac->antenna_state, layers); + return layers; } int get_ul_nrOfLayers(const NR_UE_sched_ctrl_t *sched_ctrl, const nr_dci_format_t dci_format) @@ -191,63 +190,92 @@ uint16_t get_pm_index(const gNB_MAC_INST *nrmac, int layers, int xp_pdsch_antenna_ports) { - if (dci_format == NR_DL_DCI_FORMAT_1_0 || nrmac->identity_pm || xp_pdsch_antenna_ports == 1) - return 0; //identity matrix (basic 5G configuration handled by PMI report is with XP antennas) - const NR_UE_sched_ctrl_t *sched_ctrl = &UE->UE_sched_ctrl; - const int report_id = sched_ctrl->CSI_report.cri_ri_li_pmi_cqi_report.csi_report_id; - const nr_csi_report_t *csi_report = &UE->csi_report_template[report_id]; - const int N1 = csi_report->N1; - const int N2 = csi_report->N2; - const int antenna_ports = (N1 * N2) << 1; - if (antenna_ports < 2) - return 0; // single antenna port + if (dci_format == NR_DL_DCI_FORMAT_1_0 || nrmac->identity_pm || xp_pdsch_antenna_ports == 1) + return 0; //identity matrix (basic 5G configuration handled by PMI report is with XP antennas) + const NR_UE_sched_ctrl_t *sched_ctrl = &UE->UE_sched_ctrl; - int x1 = sched_ctrl->CSI_report.cri_ri_li_pmi_cqi_report.pmi_x1; - const int x2 = sched_ctrl->CSI_report.cri_ri_li_pmi_cqi_report.pmi_x2; - LOG_D(NR_MAC,"PMI report: x1 %d x2 %d layers: %d\n", x1, x2, layers); - - int prev_layers_size = 0; - for (int i = 1; i < layers; i++) - prev_layers_size += nrmac->precoding_matrix_size[i - 1]; - - // need to return PM index to matrix initialized in init_DL_MIMO_codebook - // index 0 is for identity matrix - // order of matrices depends on layers to be transmitted - // elements from 1 to n for 1 layer - // elements from n+1 to m for 2 layers etc. - if (antenna_ports == 2) - return 1 + prev_layers_size + x2; // 0 for identity matrix - else { - int idx = layers - 1; - // the order of i1x in X1 report needs to be verified - // the standard is not very clear (Table 6.3.1.1.2-7 in 38.212) - // it says: PMI wideband information fields X1 , from left to right - int bitlen = csi_report->csi_meas_bitlen.pmi_i13_bitlen[idx]; - if (layers == 1 && bitlen != 0) { - LOG_E(NR_MAC, "Invalid i13 bit length %d for single layer! It should be 0\n", bitlen); - return 0; + int reported_ri_layers = sched_ctrl->CSI_report.cri_ri_li_pmi_cqi_report.ri + 1; + if (layers != reported_ri_layers) { + LOG_D(NR_MAC, "PMI mismatch: using %d layers but RI report is for %d layers, using identity matrix\n", + layers, reported_ri_layers); + return 0; } - const int i13 = x1 & ((1 << bitlen) - 1); - x1 >>= bitlen; - bitlen = csi_report->csi_meas_bitlen.pmi_i12_bitlen[idx]; - const int i12 = x1 & ((1 << bitlen) - 1); - x1 >>= bitlen; - bitlen = csi_report->csi_meas_bitlen.pmi_i11_bitlen[idx]; - const int i11 = x1 & ((1 << bitlen) - 1); - const int i2 = x2; - int k1, k2; - get_k1_k2_indices(layers, N1, N2, i13, &k1, &k2); // get indices k1 and k2 for PHY matrix (not actual k1 and k2 values) - const int O2 = N2 == 1 ? 1 : 4; - int O1 = 4; //Horizontal beam oversampling = 4 for more than 2 antenna ports - int max_i2 = 0; - int lay_index = 0; - max_i2 = layers == 1 ? 4 : 2; - int K1, K2; - get_K1_K2(N1, N2, &K1, &K2, layers); - // computing precoding matrix index according to rule set in allocation function init_codebook_gNB - lay_index = i2 + (i11 * max_i2) + (i12 * max_i2 * N1 * O1) + (k1 * max_i2 * N1 * O1 * N2 * O2) + (k2 * max_i2 * N1 * O1 * N2 * O2 * K1); - return 1 + prev_layers_size + lay_index; - } + nr_antenna_state_t* ant_state = nrmac->antenna_state; + if (ant_state) { + int active_tx = nr_get_active_tx_antenna_count(ant_state); + int configured_tx = ant_state->num_tx_antennas; + if (nr_is_in_recovery_mode(ant_state)) { + LOG_D(NR_MAC, "[ANTENNA_MUTE] Recovery mode active, using identity matrix\n"); + return 0; + } + if (active_tx < configured_tx && layers < configured_tx) { + LOG_D(NR_MAC, "[ANTENNA_MUTE] Using identity matrix: %d/%d TX antennas active, %d layers\n", + active_tx, configured_tx, layers); + return 0; + } + } + + const int report_id = sched_ctrl->CSI_report.cri_ri_li_pmi_cqi_report.csi_report_id; + const nr_csi_report_t *csi_report = &UE->csi_report_template[report_id]; + const int N1 = csi_report->N1; + const int N2 = csi_report->N2; + const int antenna_ports = (N1 * N2) << 1; + if (antenna_ports < 2) return 0; // single antenna port + + int x1 = sched_ctrl->CSI_report.cri_ri_li_pmi_cqi_report.pmi_x1; + const int x2 = sched_ctrl->CSI_report.cri_ri_li_pmi_cqi_report.pmi_x2; + LOG_D(NR_MAC,"PMI report: x1 %d x2 %d layers: %d\n", x1, x2, layers); + + int prev_layers_size = 0; + for (int i = 1; i < layers; i++) + prev_layers_size += nrmac->precoding_matrix_size[i - 1]; + + // need to return PM index to matrix initialized in init_DL_MIMO_codebook + // index 0 is for identity matrix + // order of matrices depends on layers to be transmitted + // elements from 1 to n for 1 layer + // elements from n+1 to m for 2 layers etc. + int pm_index; + if (antenna_ports == 2) + pm_index = 1 + prev_layers_size + x2; // 0 for identity matrix + else { + int idx = layers - 1; + // the order of i1x in X1 report needs to be verified + // the standard is not very clear (Table 6.3.1.1.2-7 in 38.212) + // it says: PMI wideband information fields X1 , from left to right + int bitlen = csi_report->csi_meas_bitlen.pmi_i13_bitlen[idx]; + if (layers == 1 && bitlen != 0) { + LOG_E(NR_MAC, "Invalid i13 bit length %d for single layer! It should be 0\n", bitlen); + return 0; + } + const int i13 = x1 & ((1 << bitlen) - 1); + x1 >>= bitlen; + bitlen = csi_report->csi_meas_bitlen.pmi_i12_bitlen[idx]; + const int i12 = x1 & ((1 << bitlen) - 1); + x1 >>= bitlen; + bitlen = csi_report->csi_meas_bitlen.pmi_i11_bitlen[idx]; + const int i11 = x1 & ((1 << bitlen) - 1); + const int i2 = x2; + int k1, k2; + get_k1_k2_indices(layers, N1, N2, i13, &k1, &k2); // get indices k1 and k2 for PHY matrix (not actual k1 and k2 values) + const int O2 = N2 == 1 ? 1 : 4; + int O1 = 4; //Horizontal beam oversampling = 4 for more than 2 antenna ports + int max_i2 = 0; + int lay_index = 0; + max_i2 = layers == 1 ? 4 : 2; + int K1, K2; + get_K1_K2(N1, N2, &K1, &K2, layers); + // computing precoding matrix index according to rule set in allocation function init_codebook_gNB + lay_index = i2 + (i11 * max_i2) + (i12 * max_i2 * N1 * O1) + (k1 * max_i2 * N1 * O1 * N2 * O2) + (k2 * max_i2 * N1 * O1 * N2 * O2 * K1); + pm_index = 1 + prev_layers_size + lay_index; + } + int max_valid_pm = prev_layers_size + nrmac->precoding_matrix_size[layers - 1]; + if (pm_index < 1 || pm_index > max_valid_pm) { + LOG_W(NR_MAC, "PM index %d out of range [1, %d] for %d layers, using identity matrix\n", + pm_index, max_valid_pm, layers); + return 0; + } + return pm_index; } // look-up table for AMC. Based on BLER vs SNR curves from the nr_dlsim simulation diff --git a/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_uci.c b/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_uci.c index ac0803e1e3..40186823c3 100644 --- a/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_uci.c +++ b/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_uci.c @@ -544,7 +544,7 @@ static void evaluate_sinr_report(gNB_MAC_INST *nrmac, stats->num_sinr_meas++; const int mcs_table = UE->current_DL_BWP.mcsTableIdx; - const int nrOfLayers = get_dl_nrOfLayers(sched_ctrl, UE->current_DL_BWP.dci_format); + const int nrOfLayers = get_dl_nrOfLayers(nrmac, sched_ctrl, UE->current_DL_BWP.dci_format); sched_ctrl->dl_max_mcs = get_mcs_from_SINRx10(mcs_table, sinr_report->SINRx10[0], nrOfLayers); LOG_D(MAC, "Reported SSB-SINR = %d.%d, dl_max_mcs %d\n", sinr_report->SINRx10[0] / 10, sinr_report->SINRx10[0] % 10, sched_ctrl->dl_max_mcs); diff --git a/openair2/LAYER2/NR_MAC_gNB/mac_proto.h b/openair2/LAYER2/NR_MAC_gNB/mac_proto.h index efcabbc98a..96b58f3d7d 100644 --- a/openair2/LAYER2/NR_MAC_gNB/mac_proto.h +++ b/openair2/LAYER2/NR_MAC_gNB/mac_proto.h @@ -399,7 +399,7 @@ uint16_t get_pm_index(const gNB_MAC_INST *nrmac, int get_mcs_from_SINRx10(int mcs_table, int SINRx10, int Nl); uint8_t get_mcs_from_cqi(int mcs_table, int cqi_table, int cqi_idx); -uint8_t get_dl_nrOfLayers(const NR_UE_sched_ctrl_t *sched_ctrl, const nr_dci_format_t dci_format); +uint8_t get_dl_nrOfLayers(const gNB_MAC_INST *mac, const NR_UE_sched_ctrl_t *sched_ctrl, const nr_dci_format_t dci_format); int get_ul_nrOfLayers(const NR_UE_sched_ctrl_t *sched_ctrl, const nr_dci_format_t dci_format); void free_sched_pucch_list(NR_UE_sched_ctrl_t *sched_ctrl); diff --git a/openair2/LAYER2/NR_MAC_gNB/nr_mac_gNB.h b/openair2/LAYER2/NR_MAC_gNB/nr_mac_gNB.h index 136fb0365e..75d0b454c7 100644 --- a/openair2/LAYER2/NR_MAC_gNB/nr_mac_gNB.h +++ b/openair2/LAYER2/NR_MAC_gNB/nr_mac_gNB.h @@ -20,14 +20,14 @@ */ /*! \file mac.h -* \brief MAC data structures, constant, and function prototype -* \author Navid Nikaein and Raymond Knopp, WIE-TAI CHEN -* \date 2011, 2018 -* \version 0.5 -* \company Eurecom, NTUST -* \email navid.nikaein@eurecom.fr, kroempa@gmail.com + * \brief MAC data structures, constant, and function prototype + * \author Navid Nikaein and Raymond Knopp, WIE-TAI CHEN + * \date 2011, 2018 + * \version 0.5 + * \company Eurecom, NTUST + * \email navid.nikaein@eurecom.fr, kroempa@gmail.com -*/ + */ /** @defgroup _oai2 openair2 Reference Implementation * @ingroup _ref_implementation_ * @{ @@ -47,23 +47,23 @@ #include "common/utils/ds/byte_array.h" #include "openair2/LAYER2/nr_rlc/nr_rlc_configuration.h" -#define NR_SCHED_LOCK(lock) \ - do { \ - int rc = pthread_mutex_lock(lock); \ - AssertFatal(rc == 0, "error while locking scheduler mutex, pthread_mutex_lock() returned %d\n", rc); \ - } while (0) +#define NR_SCHED_LOCK(lock) \ + do { \ + int rc = pthread_mutex_lock(lock); \ + AssertFatal(rc == 0, "error while locking scheduler mutex, pthread_mutex_lock() returned %d\n", rc); \ + } while (0) -#define NR_SCHED_UNLOCK(lock) \ - do { \ - int rc = pthread_mutex_unlock(lock); \ - AssertFatal(rc == 0, "error while locking scheduler mutex, pthread_mutex_unlock() returned %d\n", rc); \ - } while (0) +#define NR_SCHED_UNLOCK(lock) \ + do { \ + int rc = pthread_mutex_unlock(lock); \ + AssertFatal(rc == 0, "error while locking scheduler mutex, pthread_mutex_unlock() returned %d\n", rc); \ + } while (0) -#define NR_SCHED_ENSURE_LOCKED(lock)\ - do {\ - int rc = pthread_mutex_trylock(lock); \ - AssertFatal(rc == EBUSY, "this function should be called with the scheduler mutex locked, pthread_mutex_trylock() returned %d\n", rc);\ - } while (0) +#define NR_SCHED_ENSURE_LOCKED(lock) \ + do { \ + int rc = pthread_mutex_trylock(lock); \ + AssertFatal(rc == EBUSY, "this function should be called with the scheduler mutex locked, pthread_mutex_trylock() returned %d\n", rc); \ + } while (0) /* Commmon */ #include "radio/COMMON/common_lib.h" @@ -79,6 +79,7 @@ /* PHY */ #include "time_meas.h" +#include "PHY/NR_TRANSPORT/nr_antenna_state.h" /* Interface */ #include "nfapi_nr_interface_scf.h" @@ -105,465 +106,465 @@ uint8_t nr_get_rv(int rel_round); /*! \brief NR_list_t is a "list" (of users, HARQ processes, slices, ...). * Especially useful in the scheduler and to keep "classes" of users. */ typedef struct { - int head; - int *next; - int tail; - int len; + int head; + int *next; + int tail; + int len; } NR_list_t; typedef enum { - nrRA_gNB_IDLE, - nrRA_Msg2, - nrRA_WAIT_MsgA_PUSCH, - nrRA_WAIT_Msg3, - nrRA_Msg3_retransmission, - nrRA_Msg4, - nrRA_MsgB, - nrRA_WAIT_Msg4_MsgB_ACK, + nrRA_gNB_IDLE, + nrRA_Msg2, + nrRA_WAIT_MsgA_PUSCH, + nrRA_WAIT_Msg3, + nrRA_Msg3_retransmission, + nrRA_Msg4, + nrRA_MsgB, + nrRA_WAIT_Msg4_MsgB_ACK, } RA_gNB_state_t; static const char *const nrra_text[] = {"IDLE", "Msg2", "WAIT_MsgA_PUSCH", "WAIT_Msg3", "Msg3_retransmission", "Msg4", "MsgB", "WAIT_Msg4_MsgB_ACK"}; typedef struct { - int idx; - bool new_beam; + int idx; + bool new_beam; } NR_beam_alloc_t; typedef struct nr_pdsch_AntennaPorts_t { - int N1; - int N2; - int XP; + int N1; + int N2; + int XP; } nr_pdsch_AntennaPorts_t; typedef struct nr_mac_timers { - int sr_ProhibitTimer; - int sr_TransMax; - int sr_ProhibitTimer_v1700; - int t300; - int t301; - int t310; - int n310; - int t311; - int n311; - int t319; + int sr_ProhibitTimer; + int sr_TransMax; + int sr_ProhibitTimer_v1700; + int t300; + int t301; + int t310; + int n310; + int t311; + int n311; + int t319; } nr_mac_timers_t; typedef struct nr_redcap_config { - int8_t cellBarredRedCap1Rx_r17; - int8_t cellBarredRedCap2Rx_r17; - uint8_t intraFreqReselectionRedCap_r17; + int8_t cellBarredRedCap1Rx_r17; + int8_t cellBarredRedCap2Rx_r17; + uint8_t intraFreqReselectionRedCap_r17; } nr_redcap_config_t; typedef struct { -int dl_FreqDensity0_0; -int dl_FreqDensity1_0; -int dl_TimeDensity0_0; -int dl_TimeDensity1_0; -int dl_TimeDensity2_0; -int dl_EpreRatio_0; -int dl_ReOffset_0; -int ul_FreqDensity0_0; -int ul_FreqDensity1_0; -int ul_TimeDensity0_0; -int ul_TimeDensity1_0; -int ul_TimeDensity2_0; -int ul_ReOffset_0; -int ul_MaxPorts_0; -int ul_Power_0; + int dl_FreqDensity0_0; + int dl_FreqDensity1_0; + int dl_TimeDensity0_0; + int dl_TimeDensity1_0; + int dl_TimeDensity2_0; + int dl_EpreRatio_0; + int dl_ReOffset_0; + int ul_FreqDensity0_0; + int ul_FreqDensity1_0; + int ul_TimeDensity0_0; + int ul_TimeDensity1_0; + int ul_TimeDensity2_0; + int ul_ReOffset_0; + int ul_MaxPorts_0; + int ul_Power_0; } nr_ptrs_config_t; typedef struct { - int id; - int scs; - int location_and_bw; + int id; + int scs; + int location_and_bw; } nr_bwp_config_t; typedef enum { - SSB_RSRP, - CRI_RSRP, - SSB_SINR, + SSB_RSRP, + CRI_RSRP, + SSB_SINR, } nr_config_report_type_t; typedef struct nr_mac_config_t { - nr_pdsch_AntennaPorts_t pdsch_AntennaPorts; - int pusch_AntennaPorts; - int minRXTXTIME; - int do_CSIRS; - int do_SRS; - int do_TCI; - int max_num_rsrp; - bool force_256qam_off; - bool force_UL256qam_off; - bool use_deltaMCS; - int maxMIMO_layers; - bool disable_harq; - //int pusch_TargetSNRx10; - //int pucch_TargetSNRx10; - nr_mac_timers_t timer_config; - int num_dlharq; - int num_ulharq; - // BWP information - int num_additional_bwps; - int first_active_bwp; - nr_bwp_config_t bwp_config[4]; - /// beamforming weight matrix size - int nb_bfw[2]; - int32_t *bw_list; - int num_agg_level_candidates[NUM_PDCCH_AGG_LEVELS]; - nr_redcap_config_t *redcap; - nr_ptrs_config_t *ptrs; - nr_config_report_type_t report_type; + nr_pdsch_AntennaPorts_t pdsch_AntennaPorts; + int pusch_AntennaPorts; + int minRXTXTIME; + int do_CSIRS; + int do_SRS; + int do_TCI; + int max_num_rsrp; + bool force_256qam_off; + bool force_UL256qam_off; + bool use_deltaMCS; + int maxMIMO_layers; + bool disable_harq; + //int pusch_TargetSNRx10; + //int pucch_TargetSNRx10; + nr_mac_timers_t timer_config; + int num_dlharq; + int num_ulharq; + // BWP information + int num_additional_bwps; + int first_active_bwp; + nr_bwp_config_t bwp_config[4]; + /// beamforming weight matrix size + int nb_bfw[2]; + int32_t *bw_list; + int num_agg_level_candidates[NUM_PDCCH_AGG_LEVELS]; + nr_redcap_config_t *redcap; + nr_ptrs_config_t *ptrs; + nr_config_report_type_t report_type; } nr_mac_config_t; typedef struct NR_preamble_ue { - uint8_t num_preambles; - uint8_t preamble_list[MAX_NUM_NR_PRACH_PREAMBLES]; + uint8_t num_preambles; + uint8_t preamble_list[MAX_NUM_NR_PRACH_PREAMBLES]; } NR_preamble_ue_t; typedef struct NR_sched_pdcch { - uint16_t BWPSize; - uint16_t BWPStart; - uint8_t CyclicPrefix; - uint8_t SubcarrierSpacing; - uint8_t StartSymbolIndex; - uint8_t CceRegMappingType; - uint8_t RegBundleSize; - uint8_t InterleaverSize; - uint16_t ShiftIndex; - uint8_t DurationSymbols; - uint16_t n_rb; - uint16_t rb_start; + uint16_t BWPSize; + uint16_t BWPStart; + uint8_t CyclicPrefix; + uint8_t SubcarrierSpacing; + uint8_t StartSymbolIndex; + uint8_t CceRegMappingType; + uint8_t RegBundleSize; + uint8_t InterleaverSize; + uint16_t ShiftIndex; + uint8_t DurationSymbols; + uint16_t n_rb; + uint16_t rb_start; } NR_sched_pdcch_t; /*! \brief gNB template for the Random access information */ typedef struct { - /// Flag to indicate this process is active - RA_gNB_state_t ra_state; - /// CORESET0 configured flag - int coreset0_configured; - /// Frame where preamble was received - int preamble_frame; - /// Slot where preamble was received - uint8_t preamble_slot; - /// Received preamble_index - uint8_t preamble_index; - /// Timing offset indicated by PHY - int16_t timing_offset; - /// Subframe where Msg2 is to be sent - uint8_t Msg2_slot; - /// Frame where Msg2 is to be sent - frame_t Msg2_frame; - /// Subframe where Msg3 is to be sent - slot_t Msg3_slot; - /// Frame where Msg3 is to be sent - frame_t Msg3_frame; - /// Msg3 time domain allocation index - int Msg3_tda_id; - /// Msg3 beam matrix index - NR_beam_alloc_t Msg3_beam; - /// harq_pid used for Msg4 transmission - uint8_t harq_pid; - /// RA RNTI allocated from received PRACH - uint16_t RA_rnti; - /// MsgB RNTI allocated from received MsgA - uint16_t MsgB_rnti; - /// Received UE Contention Resolution Identifier - uint8_t cont_res_id[6]; - /// Msg3 first RB - int msg3_first_rb; - /// Msg3 number of RB - int msg3_nb_rb; - /// Msg3 BWP start - int msg3_bwp_start; - /// Msg3 TPC command - uint8_t msg3_TPC; - /// Round of Msg3 HARQ - uint8_t msg3_round; - int msg3_startsymb; - int msg3_nbSymb; - /// MAC PDU length for Msg4 - int mac_pdu_length; - /// Preambles for contention-free access - NR_preamble_ue_t preambles; - int contention_resolution_timer; - nr_ra_type_t ra_type; - /// CFRA flag - bool cfra; + /// Flag to indicate this process is active + RA_gNB_state_t ra_state; + /// CORESET0 configured flag + int coreset0_configured; + /// Frame where preamble was received + int preamble_frame; + /// Slot where preamble was received + uint8_t preamble_slot; + /// Received preamble_index + uint8_t preamble_index; + /// Timing offset indicated by PHY + int16_t timing_offset; + /// Subframe where Msg2 is to be sent + uint8_t Msg2_slot; + /// Frame where Msg2 is to be sent + frame_t Msg2_frame; + /// Subframe where Msg3 is to be sent + slot_t Msg3_slot; + /// Frame where Msg3 is to be sent + frame_t Msg3_frame; + /// Msg3 time domain allocation index + int Msg3_tda_id; + /// Msg3 beam matrix index + NR_beam_alloc_t Msg3_beam; + /// harq_pid used for Msg4 transmission + uint8_t harq_pid; + /// RA RNTI allocated from received PRACH + uint16_t RA_rnti; + /// MsgB RNTI allocated from received MsgA + uint16_t MsgB_rnti; + /// Received UE Contention Resolution Identifier + uint8_t cont_res_id[6]; + /// Msg3 first RB + int msg3_first_rb; + /// Msg3 number of RB + int msg3_nb_rb; + /// Msg3 BWP start + int msg3_bwp_start; + /// Msg3 TPC command + uint8_t msg3_TPC; + /// Round of Msg3 HARQ + uint8_t msg3_round; + int msg3_startsymb; + int msg3_nbSymb; + /// MAC PDU length for Msg4 + int mac_pdu_length; + /// Preambles for contention-free access + NR_preamble_ue_t preambles; + int contention_resolution_timer; + nr_ra_type_t ra_type; + /// CFRA flag + bool cfra; } NR_RA_t; /*! \brief gNB common channels */ typedef struct { - frame_type_t frame_type; - NR_BCCH_BCH_Message_t *mib; - NR_BCCH_DL_SCH_Message_t *sib1; - seq_arr_t *du_SIBs; - NR_ServingCellConfigCommon_t *ServingCellConfigCommon; - /// Outgoing MIB PDU for PHY - uint8_t MIB_pdu[3]; - /// Outgoing BCCH pdu for PHY - uint8_t sib1_bcch_pdu[NR_MAX_SIB_LENGTH / 8]; - int sib1_bcch_length; - /// used for otherSIB data - uint8_t other_sib_bcch_pdu[2][NR_MAX_SIB_LENGTH / 8]; - int other_sib_bcch_length[2]; - /// VRB map for common channels - uint16_t vrb_map[MAX_NUM_BEAM_PERIODS][275]; - /// VRB map for common channels and PUSCH, dynamically allocated because - /// length depends on number of slots and RBs - uint16_t *vrb_map_UL[MAX_NUM_BEAM_PERIODS]; - ///Number of active SSBs - int num_active_ssb; - //Total available prach occasions per configuration period - int total_prach_occasions_per_config_period; - //Total available prach occasions - int total_prach_occasions; - //Max Association period - int association_period; - //SSB index - uint8_t ssb_index[MAX_NUM_OF_SSB]; - //CB preambles for each SSB - int cb_preambles_per_ssb; - /// Max prach length in slots - int prach_len; - nr_prach_info_t prach_info; + frame_type_t frame_type; + NR_BCCH_BCH_Message_t *mib; + NR_BCCH_DL_SCH_Message_t *sib1; + seq_arr_t *du_SIBs; + NR_ServingCellConfigCommon_t *ServingCellConfigCommon; + /// Outgoing MIB PDU for PHY + uint8_t MIB_pdu[3]; + /// Outgoing BCCH pdu for PHY + uint8_t sib1_bcch_pdu[NR_MAX_SIB_LENGTH / 8]; + int sib1_bcch_length; + /// used for otherSIB data + uint8_t other_sib_bcch_pdu[2][NR_MAX_SIB_LENGTH / 8]; + int other_sib_bcch_length[2]; + /// VRB map for common channels + uint16_t vrb_map[MAX_NUM_BEAM_PERIODS][275]; + /// VRB map for common channels and PUSCH, dynamically allocated because + /// length depends on number of slots and RBs + uint16_t *vrb_map_UL[MAX_NUM_BEAM_PERIODS]; + ///Number of active SSBs + int num_active_ssb; + //Total available prach occasions per configuration period + int total_prach_occasions_per_config_period; + //Total available prach occasions + int total_prach_occasions; + //Max Association period + int association_period; + //SSB index + uint8_t ssb_index[MAX_NUM_OF_SSB]; + //CB preambles for each SSB + int cb_preambles_per_ssb; + /// Max prach length in slots + int prach_len; + nr_prach_info_t prach_info; } NR_COMMON_channels_t; // SP ZP CSI-RS Resource Set Activation/Deactivation MAC CE typedef struct sp_zp_csirs { - bool is_scheduled; //ZP CSI-RS ACT/Deact MAC CE is scheduled - bool act_deact; //Activation/Deactivation indication - uint8_t serv_cell_id; //Identity of Serving cell for which MAC CE applies - uint8_t bwpid; //Downlink BWP id - uint8_t rsc_id; //SP ZP CSI-RS resource set + bool is_scheduled; //ZP CSI-RS ACT/Deact MAC CE is scheduled + bool act_deact; //Activation/Deactivation indication + uint8_t serv_cell_id; //Identity of Serving cell for which MAC CE applies + uint8_t bwpid; //Downlink BWP id + uint8_t rsc_id; //SP ZP CSI-RS resource set } sp_zp_csirs_t; //SP CSI-RS / CSI-IM Resource Set Activation/Deactivation MAC CE #define MAX_CSI_RESOURCE_SET 64 typedef struct csi_rs_im { - bool is_scheduled; - bool act_deact; - uint8_t serv_cellid; - uint8_t bwp_id; - bool im; - uint8_t csi_im_rsc_id; - uint8_t nzp_csi_rsc_id; - uint8_t nb_tci_resource_set_id; - uint8_t tci_state_id [ MAX_CSI_RESOURCE_SET ]; + bool is_scheduled; + bool act_deact; + uint8_t serv_cellid; + uint8_t bwp_id; + bool im; + uint8_t csi_im_rsc_id; + uint8_t nzp_csi_rsc_id; + uint8_t nb_tci_resource_set_id; + uint8_t tci_state_id [ MAX_CSI_RESOURCE_SET ]; } csi_rs_im_t; typedef struct tciStateInd { - bool is_scheduled; - uint32_t coresetId; - uint32_t tciStateId; + bool is_scheduled; + uint32_t coresetId; + uint32_t tciStateId; } tciStateInd_t; typedef struct pucchSpatialRelation { - bool is_scheduled; - uint8_t servingCellId; - uint8_t bwpId; - uint8_t pucchResourceId; - bool s0tos7_actDeact[8]; + bool is_scheduled; + uint8_t servingCellId; + uint8_t bwpId; + uint8_t pucchResourceId; + bool s0tos7_actDeact[8]; } pucchSpatialRelation_t; typedef struct SPCSIReportingpucch { - bool is_scheduled; - uint8_t servingCellId; - uint8_t bwpId; - bool s0tos3_actDeact[4]; + bool is_scheduled; + uint8_t servingCellId; + uint8_t bwpId; + bool s0tos3_actDeact[4]; } SPCSIReportingpucch_t; #define MAX_APERIODIC_TRIGGER_STATES 128 //38.331 typedef struct aperiodicCSI_triggerStateSelection { - bool is_scheduled; - uint8_t servingCellId; - uint8_t bwpId; - uint8_t highestTriggerStateSelected; - bool triggerStateSelection[MAX_APERIODIC_TRIGGER_STATES]; + bool is_scheduled; + uint8_t servingCellId; + uint8_t bwpId; + uint8_t highestTriggerStateSelected; + bool triggerStateSelection[MAX_APERIODIC_TRIGGER_STATES]; } aperiodicCSI_triggerStateSelection_t; #define MAX_TCI_STATES 128 //38.331 typedef struct pdschTciStatesActDeact { - bool is_scheduled; - uint8_t servingCellId; - uint8_t bwpId; - uint8_t highestTciStateActivated; - bool tciStateActDeact[MAX_TCI_STATES]; - uint8_t codepoint[8]; + bool is_scheduled; + uint8_t servingCellId; + uint8_t bwpId; + uint8_t highestTciStateActivated; + bool tciStateActDeact[MAX_TCI_STATES]; + uint8_t codepoint[8]; } pdschTciStatesActDeact_t; typedef struct UE_info { - sp_zp_csirs_t sp_zp_csi_rs; - csi_rs_im_t csi_im; - tciStateInd_t tci_state_ind; - pucchSpatialRelation_t pucch_spatial_relation; - SPCSIReportingpucch_t SP_CSI_reporting_pucch; - aperiodicCSI_triggerStateSelection_t aperi_CSI_trigger; - pdschTciStatesActDeact_t pdsch_TCI_States_ActDeact; + sp_zp_csirs_t sp_zp_csi_rs; + csi_rs_im_t csi_im; + tciStateInd_t tci_state_ind; + pucchSpatialRelation_t pucch_spatial_relation; + SPCSIReportingpucch_t SP_CSI_reporting_pucch; + aperiodicCSI_triggerStateSelection_t aperi_CSI_trigger; + pdschTciStatesActDeact_t pdsch_TCI_States_ActDeact; } NR_UE_mac_ce_ctrl_t; typedef struct NR_sched_pucch { - bool active; - int frame; - int ul_slot; - bool sr_flag; - int csi_bits; - bool simultaneous_harqcsi; - uint8_t dai_c; - uint8_t timing_indicator; - uint8_t resource_indicator; - int r_pucch; - int prb_start; - int second_hop_prb; - int nr_of_symb; - int start_symb; + bool active; + int frame; + int ul_slot; + bool sr_flag; + int csi_bits; + bool simultaneous_harqcsi; + uint8_t dai_c; + uint8_t timing_indicator; + uint8_t resource_indicator; + int r_pucch; + int prb_start; + int second_hop_prb; + int nr_of_symb; + int start_symb; } NR_sched_pucch_t; typedef struct NR_pusch_dmrs { - uint8_t N_PRB_DMRS; - uint8_t num_dmrs_symb; - uint16_t ul_dmrs_symb_pos; - uint8_t num_dmrs_cdm_grps_no_data; - nfapi_nr_dmrs_type_e dmrs_config_type; - int dmrs_scrambling_id; - int pusch_identity; - int scid; - int low_papr_sequence_number; - NR_PTRS_UplinkConfig_t *ptrsConfig; + uint8_t N_PRB_DMRS; + uint8_t num_dmrs_symb; + uint16_t ul_dmrs_symb_pos; + uint8_t num_dmrs_cdm_grps_no_data; + nfapi_nr_dmrs_type_e dmrs_config_type; + int dmrs_scrambling_id; + int pusch_identity; + int scid; + int low_papr_sequence_number; + NR_PTRS_UplinkConfig_t *ptrsConfig; } NR_pusch_dmrs_t; typedef struct NR_sched_pusch { - int frame; - int slot; + int frame; + int slot; - /// RB allocation within active uBWP - uint16_t rbSize; - uint16_t rbStart; + /// RB allocation within active uBWP + uint16_t rbSize; + uint16_t rbStart; - /// MCS - uint8_t mcs; + /// MCS + uint8_t mcs; - /// TBS-related info - uint16_t R; - uint8_t Qm; - uint32_t tb_size; + /// TBS-related info + uint16_t R; + uint8_t Qm; + uint32_t tb_size; - /// UL HARQ PID to use for this UE, or -1 for "any new" - int8_t ul_harq_pid; - uint8_t nrOfLayers; - int tpmi; + /// UL HARQ PID to use for this UE, or -1 for "any new" + int8_t ul_harq_pid; + uint8_t nrOfLayers; + int tpmi; - // time_domain_allocation is the index of a list of tda - int time_domain_allocation; - NR_tda_info_t tda_info; - NR_pusch_dmrs_t dmrs_info; - bwp_info_t bwp_info; - int phr_txpower_calc; + // time_domain_allocation is the index of a list of tda + int time_domain_allocation; + NR_tda_info_t tda_info; + NR_pusch_dmrs_t dmrs_info; + bwp_info_t bwp_info; + int phr_txpower_calc; } NR_sched_pusch_t; typedef struct NR_pdsch_dmrs { - uint8_t dmrs_ports_id; - uint8_t N_PRB_DMRS; - uint8_t N_DMRS_SLOT; - uint16_t dl_dmrs_symb_pos; - uint8_t numDmrsCdmGrpsNoData; - uint32_t scrambling_id; - int n_scid; - nfapi_nr_dmrs_type_e dmrsConfigType; - NR_PTRS_DownlinkConfig_t *phaseTrackingRS; + uint8_t dmrs_ports_id; + uint8_t N_PRB_DMRS; + uint8_t N_DMRS_SLOT; + uint16_t dl_dmrs_symb_pos; + uint8_t numDmrsCdmGrpsNoData; + uint32_t scrambling_id; + int n_scid; + nfapi_nr_dmrs_type_e dmrsConfigType; + NR_PTRS_DownlinkConfig_t *phaseTrackingRS; } NR_pdsch_dmrs_t; struct NR_UE_info; struct gNB_MAC_INST_s; typedef void (*feedback_action_t)(struct gNB_MAC_INST_s *mac, struct NR_UE_info *ue); typedef struct NR_sched_pdsch { - /// RB allocation within active BWP - uint16_t rbSize; - uint16_t rbStart; + /// RB allocation within active BWP + uint16_t rbSize; + uint16_t rbStart; - /// MCS-related infos - uint8_t mcs; + /// MCS-related infos + uint8_t mcs; - /// TBS-related info - uint16_t R; - uint8_t Qm; - uint32_t tb_size; + /// TBS-related info + uint16_t R; + uint8_t Qm; + uint32_t tb_size; - /// DL HARQ PID to use for this UE, or -1 for "any new" - int8_t dl_harq_pid; + /// DL HARQ PID to use for this UE, or -1 for "any new" + int8_t dl_harq_pid; - // pucch format allocation - int16_t pucch_allocation; + // pucch format allocation + int16_t pucch_allocation; - uint16_t pm_index; - uint8_t nrOfLayers; - bwp_info_t bwp_info; - NR_pdsch_dmrs_t dmrs_parms; - // time_domain_allocation is the index of a list of tda - int time_domain_allocation; - NR_tda_info_t tda_info; - feedback_action_t action; + uint16_t pm_index; + uint8_t nrOfLayers; + bwp_info_t bwp_info; + NR_pdsch_dmrs_t dmrs_parms; + // time_domain_allocation is the index of a list of tda + int time_domain_allocation; + NR_tda_info_t tda_info; + feedback_action_t action; } NR_sched_pdsch_t; typedef struct NR_UE_harq { - bool is_waiting; - uint8_t ndi; - uint8_t round; - uint16_t feedback_frame; - uint16_t feedback_slot; + bool is_waiting; + uint8_t ndi; + uint8_t round; + uint16_t feedback_frame; + uint16_t feedback_slot; - /* Transport block to be sent using this HARQ process */ - byte_array_t transportBlock; - uint32_t tb_size; // size of currently stored TB - bool start_tci_timer; - /// sched_pdsch keeps information on MCS etc used for the initial transmission - NR_sched_pdsch_t sched_pdsch; + /* Transport block to be sent using this HARQ process */ + byte_array_t transportBlock; + uint32_t tb_size; // size of currently stored TB + bool start_tci_timer; + /// sched_pdsch keeps information on MCS etc used for the initial transmission + NR_sched_pdsch_t sched_pdsch; } NR_UE_harq_t; //! fixme : need to enhace for the multiple TB CQI report typedef struct NR_bler_stats { - frame_t last_frame; - float bler; - uint8_t mcs; - uint64_t rounds[8]; + frame_t last_frame; + float bler; + uint8_t mcs; + uint64_t rounds[8]; } NR_bler_stats_t; // /*! As per spec 38.214 section 5.2.1.4.2 * - if the UE is configured with the higher layer parameter groupBasedBeamReporting set to 'disabled', the UE shall report in - a single report nrofReportedRS (higher layer configured) different CRI or SSBRI for each report setting. + a single report nrofReportedRS (higher layer configured) different CRI or SSBRI for each report setting. * - if the UE is configured with the higher layer parameter groupBasedBeamReporting set to 'enabled', the UE shall report in a - single reporting instance two different CRI or SSBRI for each report setting, where CSI-RS and/or SSB - resources can be received simultaneously by the UE either with a single spatial domain receive filter, or with - multiple simultaneous spatial domain receive filter + single reporting instance two different CRI or SSBRI for each report setting, where CSI-RS and/or SSB + resources can be received simultaneously by the UE either with a single spatial domain receive filter, or with + multiple simultaneous spatial domain receive filter */ #define MAX_NR_OF_REPORTED_RS 4 struct CRI_RI_LI_PMI_CQI { - uint8_t cri; - uint8_t ri; - uint8_t li; - uint8_t pmi_x1; - uint8_t pmi_x2; - uint8_t wb_cqi_1tb; - uint8_t wb_cqi_2tb; - uint8_t cqi_table; - uint8_t csi_report_id; - bool print_report; + uint8_t cri; + uint8_t ri; + uint8_t li; + uint8_t pmi_x1; + uint8_t pmi_x2; + uint8_t wb_cqi_1tb; + uint8_t wb_cqi_2tb; + uint8_t cqi_table; + uint8_t csi_report_id; + bool print_report; }; typedef struct RSRP_report { - uint8_t nr_reports; - uint8_t resource_id[MAX_NR_OF_REPORTED_RS]; - int RSRP[MAX_NR_OF_REPORTED_RS]; - int SINRx10[MAX_NR_OF_REPORTED_RS]; + uint8_t nr_reports; + uint8_t resource_id[MAX_NR_OF_REPORTED_RS]; + int RSRP[MAX_NR_OF_REPORTED_RS]; + int SINRx10[MAX_NR_OF_REPORTED_RS]; } RSRP_report_t; struct CSI_Report { - struct CRI_RI_LI_PMI_CQI cri_ri_li_pmi_cqi_report; - RSRP_report_t ssb_rsrp_report; - RSRP_report_t csirs_rsrp_report; + struct CRI_RI_LI_PMI_CQI cri_ri_li_pmi_cqi_report; + RSRP_report_t ssb_rsrp_report; + RSRP_report_t csirs_rsrp_report; }; #define MAX_SR_BITLEN 8 @@ -572,287 +573,287 @@ struct CSI_Report { the number of CSI_report will depend on number of CSI resource sets that are configured in CSI-ResourceConfig RRC IE From spec 38.331 from the IE CSI-ResourceConfig for SSB RSRP reporting we can configure only one resource set From spec 38.214 section 5.2.1.2 For periodic and semi-persistent CSI Resource Settings, the number of CSI-RS Resource Sets configured is limited to S=1 - */ +*/ #define MAX_CSI_RESOURCE_SET_IN_CSI_RESOURCE_CONFIG 16 typedef enum { - INACTIVE = 0, - ACTIVE_NOT_SCHED, - ACTIVE_SCHED + INACTIVE = 0, + ACTIVE_NOT_SCHED, + ACTIVE_SCHED } NR_UL_harq_states_t; typedef struct NR_UE_ul_harq { - bool is_waiting; - uint8_t ndi; - uint8_t round; - uint16_t feedback_slot; + bool is_waiting; + uint8_t ndi; + uint8_t round; + uint16_t feedback_slot; - /// sched_pusch keeps information on MCS etc used for the initial transmission - NR_sched_pusch_t sched_pusch; + /// sched_pusch keeps information on MCS etc used for the initial transmission + NR_sched_pusch_t sched_pusch; } NR_UE_ul_harq_t; typedef struct NR_QoS_config_s { - int fiveQI; - int priority; + int fiveQI; + int priority; } NR_QoS_config_t; typedef struct nr_lc_config { - uint8_t lcid; - /// flag if corresponding RB is suspended - bool suspended; - /// priority as specified in 38.321 - int priority; - /// associated NSSAI for DRB - nssai_t nssai; - /// QoS config for DRB - NR_QoS_config_t qos_config[NR_MAX_NUM_QFI]; + uint8_t lcid; + /// flag if corresponding RB is suspended + bool suspended; + /// priority as specified in 38.321 + int priority; + /// associated NSSAI for DRB + nssai_t nssai; + /// QoS config for DRB + NR_QoS_config_t qos_config[NR_MAX_NUM_QFI]; } nr_lc_config_t; /*! \brief scheduling control information set through an API */ #define MAX_CSI_REPORTS 48 typedef struct { - /// CCE index and aggregation, should be coherent with cce_list - NR_SearchSpace_t *search_space; - NR_ControlResourceSet_t *coreset; - NR_sched_pdcch_t sched_pdcch; + /// CCE index and aggregation, should be coherent with cce_list + NR_SearchSpace_t *search_space; + NR_ControlResourceSet_t *coreset; + NR_sched_pdcch_t sched_pdcch; - /// CCE index and Aggr. Level are shared for PUSCH/PDSCH allocation decisions - /// corresponding to the sched_pusch/sched_pdsch structures below - int cce_index; - int aggregation_level; - uint32_t dl_cce_fail, ul_cce_fail; + /// CCE index and Aggr. Level are shared for PUSCH/PDSCH allocation decisions + /// corresponding to the sched_pusch/sched_pdsch structures below + int cce_index; + int aggregation_level; + uint32_t dl_cce_fail, ul_cce_fail; - /// Array of PUCCH scheduling information - /// Its size depends on TDD configuration and max feedback time - /// There will be a structure for each UL slot in the active period determined by the size - NR_sched_pucch_t *sched_pucch; - int sched_pucch_size; + /// Array of PUCCH scheduling information + /// Its size depends on TDD configuration and max feedback time + /// There will be a structure for each UL slot in the active period determined by the size + NR_sched_pucch_t *sched_pucch; + int sched_pucch_size; - /// uplink bytes that are currently scheduled - int sched_ul_bytes; - /// estimation of the UL buffer size - int estimated_ul_buffer; + /// uplink bytes that are currently scheduled + int sched_ul_bytes; + /// estimation of the UL buffer size + int estimated_ul_buffer; - /// PHR info: power headroom level (dB) - int ph; - /// PHR info: power headroom level (dB) for 1 PRB - int ph0; + /// PHR info: power headroom level (dB) + int ph; + /// PHR info: power headroom level (dB) for 1 PRB + int ph0; - /// PHR info: nominal UE transmit power levels (dBm) - int pcmax; + /// PHR info: nominal UE transmit power levels (dBm) + int pcmax; - /// UE-estimated maximum MCS (from CSI-RS) - uint8_t dl_max_mcs; + /// UE-estimated maximum MCS (from CSI-RS) + uint8_t dl_max_mcs; - /// For UL synchronization: store last UL scheduling grant - frame_t last_ul_frame; - slot_t last_ul_slot; + /// For UL synchronization: store last UL scheduling grant + frame_t last_ul_frame; + slot_t last_ul_slot; - /// total amount of data awaiting for this UE - uint32_t num_total_bytes; - uint16_t dl_pdus_total; - /// per-LC status data - mac_rlc_status_resp_t rlc_status[NR_MAX_NUM_LCID]; + /// total amount of data awaiting for this UE + uint32_t num_total_bytes; + uint16_t dl_pdus_total; + /// per-LC status data + mac_rlc_status_resp_t rlc_status[NR_MAX_NUM_LCID]; - /// Estimation of HARQ from BLER - NR_bler_stats_t dl_bler_stats; - NR_bler_stats_t ul_bler_stats; + /// Estimation of HARQ from BLER + NR_bler_stats_t dl_bler_stats; + NR_bler_stats_t ul_bler_stats; - uint16_t ta_frame; - int16_t ta_update; - bool ta_apply; - uint8_t tpc0; - uint8_t tpc1; - int raw_rssi; - int pusch_snrx10; - int pucch_snrx10; - uint16_t ul_rssi; - int pusch_consecutive_dtx_cnt; - int pucch_consecutive_dtx_cnt; - bool ul_failure; - int ul_failure_timer; - int release_timer; - struct CSI_Report CSI_report; - bool SR; - /// information about every HARQ process - NR_UE_harq_t harq_processes[NR_MAX_HARQ_PROCESSES]; - /// HARQ processes that are free - NR_list_t available_dl_harq; - /// HARQ processes that await feedback - NR_list_t feedback_dl_harq; - /// HARQ processes that await retransmission - NR_list_t retrans_dl_harq; - /// information about every UL HARQ process - NR_UE_ul_harq_t ul_harq_processes[NR_MAX_HARQ_PROCESSES]; - /// UL HARQ processes that are free - NR_list_t available_ul_harq; - /// UL HARQ processes that await feedback - NR_list_t feedback_ul_harq; - /// UL HARQ processes that await retransmission - NR_list_t retrans_ul_harq; - NR_UE_mac_ce_ctrl_t UE_mac_ce_ctrl; // MAC CE related information + uint16_t ta_frame; + int16_t ta_update; + bool ta_apply; + uint8_t tpc0; + uint8_t tpc1; + int raw_rssi; + int pusch_snrx10; + int pucch_snrx10; + uint16_t ul_rssi; + int pusch_consecutive_dtx_cnt; + int pucch_consecutive_dtx_cnt; + bool ul_failure; + int ul_failure_timer; + int release_timer; + struct CSI_Report CSI_report; + bool SR; + /// information about every HARQ process + NR_UE_harq_t harq_processes[NR_MAX_HARQ_PROCESSES]; + /// HARQ processes that are free + NR_list_t available_dl_harq; + /// HARQ processes that await feedback + NR_list_t feedback_dl_harq; + /// HARQ processes that await retransmission + NR_list_t retrans_dl_harq; + /// information about every UL HARQ process + NR_UE_ul_harq_t ul_harq_processes[NR_MAX_HARQ_PROCESSES]; + /// UL HARQ processes that are free + NR_list_t available_ul_harq; + /// UL HARQ processes that await feedback + NR_list_t feedback_ul_harq; + /// UL HARQ processes that await retransmission + NR_list_t retrans_ul_harq; + NR_UE_mac_ce_ctrl_t UE_mac_ce_ctrl; // MAC CE related information - /// Timer for RRC processing procedures and transmission activity - NR_timer_t transm_interrupt; - NR_timer_t tci_beam_switch; + /// Timer for RRC processing procedures and transmission activity + NR_timer_t transm_interrupt; + NR_timer_t tci_beam_switch; - /// Timer for timeout before UE is set to UL failure (e.g., - /// "TransmissionActionIndicator" handling - NR_timer_t transm_timeout; + /// Timer for timeout before UE is set to UL failure (e.g., + /// "TransmissionActionIndicator" handling + NR_timer_t transm_timeout; - /// sri, ul_ri and tpmi based on SRS - nr_srs_feedback_t srs_feedback; + /// sri, ul_ri and tpmi based on SRS + nr_srs_feedback_t srs_feedback; - /// per-LC configuration - seq_arr_t lc_config; + /// per-LC configuration + seq_arr_t lc_config; - // pdcch closed loop adjust for PDCCH aggregation level, range <0, 1> - // 0 - good channel, 1 - bad channel - float pdcch_cl_adjust; + // pdcch closed loop adjust for PDCCH aggregation level, range <0, 1> + // 0 - good channel, 1 - bad channel + float pdcch_cl_adjust; } NR_UE_sched_ctrl_t; typedef struct NR_mac_dir_stats { - uint64_t lc_bytes[64]; - uint64_t rounds[8]; - uint64_t errors; - uint64_t total_bytes; - uint32_t current_bytes; - uint64_t total_sdu_bytes; - uint32_t total_rbs; - uint32_t total_rbs_retx; - uint32_t num_mac_sdu; - uint32_t current_rbs; + uint64_t lc_bytes[64]; + uint64_t rounds[8]; + uint64_t errors; + uint64_t total_bytes; + uint32_t current_bytes; + uint64_t total_sdu_bytes; + uint32_t total_rbs; + uint32_t total_rbs_retx; + uint32_t num_mac_sdu; + uint32_t current_rbs; } NR_mac_dir_stats_t; typedef struct NR_mac_stats { - NR_mac_dir_stats_t dl; - NR_mac_dir_stats_t ul; - uint32_t ulsch_DTX; - uint64_t ulsch_total_bytes_scheduled; - uint32_t pucch0_DTX; - int cumul_rsrp; - uint8_t num_rsrp_meas; - int cumul_sinrx10; - uint8_t num_sinr_meas; - char srs_stats[50]; // Statistics may differ depending on SRS usage - int pusch_snrx10; - int deltaMCS; - int NPRB; + NR_mac_dir_stats_t dl; + NR_mac_dir_stats_t ul; + uint32_t ulsch_DTX; + uint64_t ulsch_total_bytes_scheduled; + uint32_t pucch0_DTX; + int cumul_rsrp; + uint8_t num_rsrp_meas; + int cumul_sinrx10; + uint8_t num_sinr_meas; + char srs_stats[50]; // Statistics may differ depending on SRS usage + int pusch_snrx10; + int deltaMCS; + int NPRB; } NR_mac_stats_t; typedef struct NR_bler_options { - double upper; - double lower; - uint8_t min_mcs; - uint8_t max_mcs; - uint8_t harq_round_max; + double upper; + double lower; + uint8_t min_mcs; + uint8_t max_mcs; + uint8_t harq_round_max; } NR_bler_options_t; typedef struct nr_mac_rrc_ul_if_s { - f1_reset_du_initiated_func_t f1_reset; - f1_reset_acknowledge_cu_initiated_func_t f1_reset_acknowledge; - f1_setup_request_func_t f1_setup_request; - gnb_du_configuration_update_t gnb_du_configuration_update; - ue_context_setup_response_func_t ue_context_setup_response; - ue_context_modification_response_func_t ue_context_modification_response; - ue_context_modification_required_func_t ue_context_modification_required; - ue_context_release_request_func_t ue_context_release_request; - ue_context_release_complete_func_t ue_context_release_complete; - initial_ul_rrc_message_transfer_func_t initial_ul_rrc_message_transfer; + f1_reset_du_initiated_func_t f1_reset; + f1_reset_acknowledge_cu_initiated_func_t f1_reset_acknowledge; + f1_setup_request_func_t f1_setup_request; + gnb_du_configuration_update_t gnb_du_configuration_update; + ue_context_setup_response_func_t ue_context_setup_response; + ue_context_modification_response_func_t ue_context_modification_response; + ue_context_modification_required_func_t ue_context_modification_required; + ue_context_release_request_func_t ue_context_release_request; + ue_context_release_complete_func_t ue_context_release_complete; + initial_ul_rrc_message_transfer_func_t initial_ul_rrc_message_transfer; } nr_mac_rrc_ul_if_t; typedef struct measgap_config { - bool enable; - int mgrp_ms; - long mgrp; - long gapOffset; - long mgta; - int n_slots_mgta; - int n_slots_advance; - float mgl_ms; - long mgl; - int mgl_slots; + bool enable; + int mgrp_ms; + long mgrp; + long gapOffset; + long mgta; + int n_slots_mgta; + int n_slots_advance; + float mgl_ms; + long mgl; + int mgl_slots; } measgap_config_t; /*! \brief UE list used by gNB to order UEs/CC for scheduling*/ typedef struct NR_UE_info { - rnti_t rnti; - uid_t uid; // unique ID of this UE - /// scheduling control info - nr_csi_report_t csi_report_template[MAX_CSI_REPORTCONFIG]; - NR_UE_sched_ctrl_t UE_sched_ctrl; - NR_UE_DL_BWP_t current_DL_BWP; - NR_UE_UL_BWP_t current_UL_BWP; - NR_UE_ServingCell_Info_t sc_info; - NR_mac_stats_t mac_stats; - /// currently active CellGroupConfig - NR_CellGroupConfig_t *CellGroup; - /// in case of reconfiguration, new CellConfig to apply - NR_CellGroupConfig_t *reconfigCellGroup; - NR_UE_NR_Capability_t *capability; - measgap_config_t measgap_config; - // UE selected beam index - uint16_t UE_beam_index; - float ul_thr_ue; - float dl_thr_ue; - long pdsch_HARQ_ACK_Codebook; - bool is_redcap; - bool reestablish_rlc; - NR_RA_t *ra; - // 3GPP mandates that BWPs are enumerated consecutively, but we only send one (dedicated) - // BWP to the UE (and modify that BWP on reconfiguration); consequently, the BWP ID for a - // dedicated BWP is always 1 from the UE's point of view, even if the gNB has multiple BWPs. - // The below ID is the "true" (non-consecutive) BWP ID from the gNB's point of view - NR_BWP_Id_t local_bwp_id; + rnti_t rnti; + uid_t uid; // unique ID of this UE + /// scheduling control info + nr_csi_report_t csi_report_template[MAX_CSI_REPORTCONFIG]; + NR_UE_sched_ctrl_t UE_sched_ctrl; + NR_UE_DL_BWP_t current_DL_BWP; + NR_UE_UL_BWP_t current_UL_BWP; + NR_UE_ServingCell_Info_t sc_info; + NR_mac_stats_t mac_stats; + /// currently active CellGroupConfig + NR_CellGroupConfig_t *CellGroup; + /// in case of reconfiguration, new CellConfig to apply + NR_CellGroupConfig_t *reconfigCellGroup; + NR_UE_NR_Capability_t *capability; + measgap_config_t measgap_config; + // UE selected beam index + uint16_t UE_beam_index; + float ul_thr_ue; + float dl_thr_ue; + long pdsch_HARQ_ACK_Codebook; + bool is_redcap; + bool reestablish_rlc; + NR_RA_t *ra; + // 3GPP mandates that BWPs are enumerated consecutively, but we only send one (dedicated) + // BWP to the UE (and modify that BWP on reconfiguration); consequently, the BWP ID for a + // dedicated BWP is always 1 from the UE's point of view, even if the gNB has multiple BWPs. + // The below ID is the "true" (non-consecutive) BWP ID from the gNB's point of view + NR_BWP_Id_t local_bwp_id; } NR_UE_info_t; typedef struct { - /// scheduling control info - // last element always NULL - NR_UE_info_t *connected_ue_list[MAX_MOBILES_PER_GNB + 1]; - NR_UE_info_t *access_ue_list[NR_NB_RA_PROC_MAX + 1]; - // bitmap of CSI-RS already scheduled in current slot - int sched_csirs; - uid_allocator_t uid_allocator; + /// scheduling control info + // last element always NULL + NR_UE_info_t *connected_ue_list[MAX_MOBILES_PER_GNB + 1]; + NR_UE_info_t *access_ue_list[NR_NB_RA_PROC_MAX + 1]; + // bitmap of CSI-RS already scheduled in current slot + int sched_csirs; + uid_allocator_t uid_allocator; } NR_UEs_t; typedef enum { - NO_BEAM_MODE, - PRECONFIGURED_BEAM_IDX, - LOPHY_BEAM_IDX, + NO_BEAM_MODE, + PRECONFIGURED_BEAM_IDX, + LOPHY_BEAM_IDX, } nr_beam_mode_t; typedef struct { - /// list of allocated beams per period - int16_t **beam_allocation; - int beam_duration; // in slots - int beams_per_period; - int beam_allocation_size; - nr_beam_mode_t beam_mode; + /// list of allocated beams per period + int16_t **beam_allocation; + int beam_duration; // in slots + int beams_per_period; + int beam_allocation_size; + nr_beam_mode_t beam_mode; } NR_beam_info_t; #define UE_iterator(BaSe, VaR) for (NR_UE_info_t **VaR##pptr=BaSe, *VaR=*VaR##pptr; VaR; VaR=*(++VaR##pptr)) typedef struct { - /// current frame - frame_t frame; - /// current slot - slot_t slot; - /// FAPI DL req in which allocations are made - nfapi_nr_dl_tti_request_body_t *dl_req; - /// TX_data request holds the actual data - nfapi_nr_tx_data_request_t *TX_req; + /// current frame + frame_t frame; + /// current slot + slot_t slot; + /// FAPI DL req in which allocations are made + nfapi_nr_dl_tti_request_body_t *dl_req; + /// TX_data request holds the actual data + nfapi_nr_tx_data_request_t *TX_req; } post_process_pdsch_t; typedef struct { - /// current frame for DCI - frame_t frame; - /// current slot for DCI - slot_t slot; - /// FAPI UL_DCI.request in which allocations are to be made - nfapi_nr_ul_dci_request_t *ul_dci_req; - /// group PDCCH PDU per CORESET - nfapi_nr_dl_tti_pdcch_pdu_rel15_t *pdcch_pdu_coreset[MAX_NUM_CORESET]; + /// current frame for DCI + frame_t frame; + /// current slot for DCI + slot_t slot; + /// FAPI UL_DCI.request in which allocations are to be made + nfapi_nr_ul_dci_request_t *ul_dci_req; + /// group PDCCH PDU per CORESET + nfapi_nr_dl_tti_pdcch_pdu_rel15_t *pdcch_pdu_coreset[MAX_NUM_CORESET]; } post_process_pusch_t; /* forward declaration to use in nr_pp_impl_dl */ @@ -863,160 +864,162 @@ typedef void (*nr_pp_impl_dl)(gNB_MAC_INST *nr_mac, post_process_pdsch_t *pp_pds typedef void (*nr_pp_impl_ul)(gNB_MAC_INST *nr_mac, post_process_pusch_t *pp_pusch); typedef struct f1_config_t { - f1ap_setup_req_t *setup_req; - f1ap_setup_resp_t *setup_resp; - uint32_t gnb_id; // associated gNB's ID, not used in DU itself + f1ap_setup_req_t *setup_req; + f1ap_setup_resp_t *setup_resp; + uint32_t gnb_id; // associated gNB's ID, not used in DU itself } f1_config_t; typedef struct { - char *nvipc_shm_prefix; - int8_t nvipc_poll_core; + char *nvipc_shm_prefix; + int8_t nvipc_poll_core; } nvipc_params_t; typedef struct { - uint64_t total_prb_aggregate; - uint64_t used_prb_aggregate; + uint64_t total_prb_aggregate; + uint64_t used_prb_aggregate; } mac_stats_t; typedef struct dlul_mac_stats { - mac_stats_t dl; - mac_stats_t ul; + mac_stats_t dl; + mac_stats_t ul; } dlul_mac_stats_t; typedef struct { - NR_SearchSpace_t search_space[MAX_NUM_OF_SSB]; - NR_ControlResourceSet_t coreset; + NR_SearchSpace_t search_space[MAX_NUM_OF_SSB]; + NR_ControlResourceSet_t coreset; } NR_sched_ctrl_sib1_t; /// helper type to encapsulate a frame/slot combination in a single type. /// Currently only used in the UL preprocessor. Note: if you use this type /// further, please refactor it into a common type first. typedef struct fsn { - frame_t f; - slot_t s; + frame_t f; + slot_t s; } fsn_t; /*! \brief top level eNB MAC structure */ typedef struct gNB_MAC_INST_s { - /// Ethernet parameters for northbound midhaul interface - eth_params_t eth_params_n; - /// address for F1U to bind, ports in eth_params_n - char *f1u_addr; - /// Ethernet parameters for fronthaul interface - eth_params_t eth_params_s; - /// Nvipc parameters for FAPI interface with Aerial - nvipc_params_t nvipc_params_s; - /// Module - module_id_t Mod_id; - /// timing advance group - NR_TAG_t *tag; - /// Pointer to IF module instance for PHY - NR_IF_Module_t *if_inst; - pthread_t stats_thread; - /// Pusch target SNR - int pusch_target_snrx10; - /// RSSI threshold for power control. Limits power control commands when RSSI reaches threshold. - int pusch_rssi_threshold; - /// Pucch target SNR - int pucch_target_snrx10; - /// RSSI threshold for PUCCH power control. Limits power control commands when RSSI reaches threshold. - int pucch_rssi_threshold; - /// SNR threshold needed to put or not a PRB in the black list - int ul_prbblack_SNR_threshold; - /// PUCCH Failure threshold (compared to consecutive PUCCH DTX) - int pucch_failure_thres; - /// PUSCH Failure threshold (compared to consecutive PUSCH DTX) - int pusch_failure_thres; - /// Subcarrier Offset - int ssb_SubcarrierOffset; - int ssb_OffsetPointA; + /// Ethernet parameters for northbound midhaul interface + eth_params_t eth_params_n; + /// address for F1U to bind, ports in eth_params_n + char *f1u_addr; + /// Ethernet parameters for fronthaul interface + eth_params_t eth_params_s; + /// Nvipc parameters for FAPI interface with Aerial + nvipc_params_t nvipc_params_s; + /// Module + module_id_t Mod_id; + /// timing advance group + NR_TAG_t *tag; + /// Pointer to IF module instance for PHY + NR_IF_Module_t *if_inst; + pthread_t stats_thread; + /// Pusch target SNR + int pusch_target_snrx10; + /// RSSI threshold for power control. Limits power control commands when RSSI reaches threshold. + int pusch_rssi_threshold; + /// Pucch target SNR + int pucch_target_snrx10; + /// RSSI threshold for PUCCH power control. Limits power control commands when RSSI reaches threshold. + int pucch_rssi_threshold; + /// SNR threshold needed to put or not a PRB in the black list + int ul_prbblack_SNR_threshold; + /// PUCCH Failure threshold (compared to consecutive PUCCH DTX) + int pucch_failure_thres; + /// PUSCH Failure threshold (compared to consecutive PUSCH DTX) + int pusch_failure_thres; + /// Subcarrier Offset + int ssb_SubcarrierOffset; + int ssb_OffsetPointA; - /// Common cell resources - NR_COMMON_channels_t common_channels[NFAPI_CC_MAX]; - /// current PDU index (BCH,DLSCH) - uint16_t pdu_index[NFAPI_CC_MAX]; - /// UL PRBs blacklist - uint16_t ulprbbl[MAX_BWP_SIZE]; - /// NFAPI Config Request Structure - nfapi_nr_config_request_scf_t config[NFAPI_CC_MAX]; - /// a PDCCH PDU groups DCIs per BWP and CORESET. The following structure - /// keeps pointers to PDCCH PDUs within DL_req so that we can easily track - /// PDCCH PDUs per CC/BWP/CORESET - nfapi_nr_dl_tti_pdcch_pdu_rel15_t *pdcch_pdu_idx[NFAPI_CC_MAX][MAX_NUM_CORESET]; - /// NFAPI UL TTI Request Structure for future TTIs, dynamically allocated - /// because length depends on number of slots - nfapi_nr_ul_tti_request_t *UL_tti_req_ahead[NFAPI_CC_MAX]; - int UL_tti_req_ahead_size; - int vrb_map_UL_size; + /// Common cell resources + NR_COMMON_channels_t common_channels[NFAPI_CC_MAX]; + /// current PDU index (BCH,DLSCH) + uint16_t pdu_index[NFAPI_CC_MAX]; + /// UL PRBs blacklist + uint16_t ulprbbl[MAX_BWP_SIZE]; + /// NFAPI Config Request Structure + nfapi_nr_config_request_scf_t config[NFAPI_CC_MAX]; + /// a PDCCH PDU groups DCIs per BWP and CORESET. The following structure + /// keeps pointers to PDCCH PDUs within DL_req so that we can easily track + /// PDCCH PDUs per CC/BWP/CORESET + nfapi_nr_dl_tti_pdcch_pdu_rel15_t *pdcch_pdu_idx[NFAPI_CC_MAX][MAX_NUM_CORESET]; + /// NFAPI UL TTI Request Structure for future TTIs, dynamically allocated + /// because length depends on number of slots + nfapi_nr_ul_tti_request_t *UL_tti_req_ahead[NFAPI_CC_MAX]; + int UL_tti_req_ahead_size; + int vrb_map_UL_size; - NR_UEs_t UE_info; + NR_UEs_t UE_info; - // MAC function execution peformance profiler - /// processing time of gNB scheduler - time_stats_t gNB_scheduler; - /// processing time of gNB scheduler for Random access - time_stats_t schedule_ra; - /// processing time of gNB DLSCH scheduler - time_stats_t schedule_ulsch; // include preprocessor - /// processing time of gNB DLSCH scheduler - time_stats_t schedule_dlsch; // include rlc_data_req + MAC header + preprocessor - /// processing time of rlc_data_req - time_stats_t rlc_data_req; - /// processing time of nr_srs_ri_computation - time_stats_t nr_srs_ri_computation_timer; - /// processing time of nr_srs_tpmi_estimation - time_stats_t nr_srs_tpmi_computation_timer; - /// processing time of gNB ULSCH reception - time_stats_t rx_ulsch_sdu; // include rlc_data_ind + // MAC function execution peformance profiler + /// processing time of gNB scheduler + time_stats_t gNB_scheduler; + /// processing time of gNB scheduler for Random access + time_stats_t schedule_ra; + /// processing time of gNB DLSCH scheduler + time_stats_t schedule_ulsch; // include preprocessor + /// processing time of gNB DLSCH scheduler + time_stats_t schedule_dlsch; // include rlc_data_req + MAC header + preprocessor + /// processing time of rlc_data_req + time_stats_t rlc_data_req; + /// processing time of nr_srs_ri_computation + time_stats_t nr_srs_ri_computation_timer; + /// processing time of nr_srs_tpmi_estimation + time_stats_t nr_srs_tpmi_computation_timer; + /// processing time of gNB ULSCH reception + time_stats_t rx_ulsch_sdu; // include rlc_data_ind - NR_beam_info_t beam_info; + NR_beam_info_t beam_info; - /// maximum number of slots before a UE will be scheduled ULSCH automatically - uint32_t ulsch_max_frame_inactivity; - /// instance of the frame structure configuration - frame_structure_t frame_structure; + /// maximum number of slots before a UE will be scheduled ULSCH automatically + uint32_t ulsch_max_frame_inactivity; + /// instance of the frame structure configuration + frame_structure_t frame_structure; - /// DL preprocessor for differentiated scheduling - nr_pp_impl_dl pre_processor_dl; - /// UL preprocessor for differentiated scheduling - nr_pp_impl_ul pre_processor_ul; + /// DL preprocessor for differentiated scheduling + nr_pp_impl_dl pre_processor_dl; + /// UL preprocessor for differentiated scheduling + nr_pp_impl_ul pre_processor_ul; - nr_mac_config_t radio_config; - nr_rlc_configuration_t rlc_config; + nr_mac_config_t radio_config; + nr_rlc_configuration_t rlc_config; - NR_sched_ctrl_sib1_t *sched_ctrlSIB1; - NR_sched_pdcch_t *sched_pdcch_otherSI; - uint16_t cset0_bwp_start; - uint16_t cset0_bwp_size; - NR_Type0_PDCCH_CSS_config_t type0_PDCCH_CSS_config[MAX_NUM_OF_SSB]; + nr_antenna_state_t *antenna_state; - bool first_MIB; - NR_bler_options_t dl_bler; - NR_bler_options_t ul_bler; - uint16_t min_grant_prb; - bool identity_pm; - int precoding_matrix_size[NR_MAX_NB_LAYERS]; - int beam_index_list[MAX_NUM_OF_SSB]; - NR_sched_pdsch_t sib1_pdsch[MAX_NUM_OF_SSB]; + NR_sched_ctrl_sib1_t *sched_ctrlSIB1; + NR_sched_pdcch_t *sched_pdcch_otherSI; + uint16_t cset0_bwp_start; + uint16_t cset0_bwp_size; + NR_Type0_PDCCH_CSS_config_t type0_PDCCH_CSS_config[MAX_NUM_OF_SSB]; - /// dedicate UL TDA, common for all UEs - seq_arr_t ul_tda; - /// next UL slot to schedule - fsn_t ul_next; + bool first_MIB; + NR_bler_options_t dl_bler; + NR_bler_options_t ul_bler; + uint16_t min_grant_prb; + bool identity_pm; + int precoding_matrix_size[NR_MAX_NB_LAYERS]; + int beam_index_list[MAX_NUM_OF_SSB]; + NR_sched_pdsch_t sib1_pdsch[MAX_NUM_OF_SSB]; - nr_mac_rrc_ul_if_t mac_rrc; - f1_config_t f1_config; - int16_t frame; + /// dedicate UL TDA, common for all UEs + seq_arr_t ul_tda; + /// next UL slot to schedule + fsn_t ul_next; - /// number of UEs to exceed to disable stats - int stats_max_ue; - /// if stats are currently enabled - bool print_ue_stats; + nr_mac_rrc_ul_if_t mac_rrc; + f1_config_t f1_config; + int16_t frame; - pthread_mutex_t sched_lock; + /// number of UEs to exceed to disable stats + int stats_max_ue; + /// if stats are currently enabled + bool print_ue_stats; - dlul_mac_stats_t mac_stats; - uint64_t num_scheduled_prach_rx; + pthread_mutex_t sched_lock; + + dlul_mac_stats_t mac_stats; + uint64_t num_scheduled_prach_rx; } gNB_MAC_INST; #endif /*__LAYER2_NR_MAC_GNB_H__ */ diff --git a/radio/fhi_72/mplane/CMakeLists.txt b/radio/fhi_72/mplane/CMakeLists.txt index 26bbb7939e..71775d7fba 100644 --- a/radio/fhi_72/mplane/CMakeLists.txt +++ b/radio/fhi_72/mplane/CMakeLists.txt @@ -32,6 +32,7 @@ target_sources(oran_fhlib_5g_mplane PRIVATE subscribe-mplane.c config-mplane.c ru-mplane-api.c + antenna-control.c xml/get-xml.c yang/get-yang.c yang/create-yang-config.c diff --git a/radio/fhi_72/mplane/antenna-control.c b/radio/fhi_72/mplane/antenna-control.c new file mode 100644 index 0000000000..452ace1c12 --- /dev/null +++ b/radio/fhi_72/mplane/antenna-control.c @@ -0,0 +1,368 @@ +#include "antenna-control.h" +#include "ru-mplane-api.h" +#include "xml/get-xml.h" +#include "common/utils/assertions.h" + +// Forward declaration - actual function in config-mplane.c +extern bool edit_val_commmit_rpc(ru_session_t *ru_session, const char *content); + +// Forward declaration - actual function in get-mplane.c +extern bool get_mplane(ru_session_t *ru_session, char **answer); + +#include +#include +#include + +#include +#include + +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; +} diff --git a/radio/fhi_72/mplane/antenna-control.h b/radio/fhi_72/mplane/antenna-control.h new file mode 100644 index 0000000000..9845f70996 --- /dev/null +++ b/radio/fhi_72/mplane/antenna-control.h @@ -0,0 +1,135 @@ +#ifndef ANTENNA_CONTROL_H +#define ANTENNA_CONTROL_H + +#include "ru-mplane-api.h" +#include +#include + +#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 to candidate datastore + * 3. Send NETCONF + * 4. Send NETCONF + * + * @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 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 RPC to retrieve current rx-array-antenna-mask-config. + * + * @param ru_session Active NETCONF session to the RU + * @param array_name O-RAN RX array name + * @param antenna_bitmask Output: current antenna bitmask + * @return Result code indicating success or specific error + */ +antenna_ctrl_result_t mplane_get_rx_antenna_mask(ru_session_t *ru_session, + const char *array_name, + uint8_t *antenna_bitmask); + +/** + * @brief Check if RU supports MPLANE-TRX-CONTROL feature + * + * Queries the RU capabilities to verify MPLANE-TRX-CONTROL feature + * is advertised (defined in o-ran-wg4-features.yang). + * + * @param ru_session Active NETCONF session to the RU + * @return true if feature is supported, false otherwise + */ +bool mplane_supports_trx_control(ru_session_t *ru_session); + +/** + * @brief Create antenna bitmask XML content for NETCONF edit-config + * + * Helper function to generate O-RAN compliant XML for antenna mask configuration. + * Uses libyang to create proper o-ran-uplane-conf structure. + * + * @param ru_session Active NETCONF session (for libyang context) + * @param is_tx true for TX array, false for RX array + * @param array_name O-RAN array name + * @param antenna_bitmask Antenna bitmask value + * @param num_antennas Number of antennas (for binary encoding) + * @param xml_content Output: allocated XML string (caller must free) + * @return true on success, false on error + */ +bool mplane_create_antenna_mask_xml(ru_session_t *ru_session, + bool is_tx, + const char *array_name, + uint8_t antenna_bitmask, + uint8_t num_antennas, + char **xml_content); + +#ifdef __cplusplus +} +#endif + +#endif /* ANTENNA_CONTROL_H */ diff --git a/tools/scripts/traffic_runner.sh b/tools/scripts/traffic_runner.sh new file mode 100755 index 0000000000..d564e72a10 --- /dev/null +++ b/tools/scripts/traffic_runner.sh @@ -0,0 +1,98 @@ +#!/usr/bin/env bash +#!/bin/bash +# Continuous traffic generator for OAI UE containers (no intervals, runs virtually forever). +# Usage examples: +# ./traffic_runner.sh --profile boring +# PROFILE=bulk_download ./traffic_runner.sh + +set -euo pipefail + +PROFILE="${PROFILE:-}" + +# Fixed server coordinates +IPERF_HOST_UDP="192.168.71.129" +IPERF_HOST_TCP="192.168.71.130" +IPERF_PORT_UDP=5201 +IPERF_PORT_TCP=5202 +OAITUN_IP="$(ip -o -4 addr show dev oaitun_ue1 | awk '{print $4}' | cut -d/ -f1)" + +# Very long duration to approximate "forever" +LONG_T="${LONG_T:-31536000}" # 1 year in seconds + +# -------------- args -------------- +if [[ $# -gt 0 ]]; then + case "$1" in + --profile=*) PROFILE="${1#--profile=}"; shift ;; + --profile) PROFILE="${2-}"; shift 2 ;; + -p=*) PROFILE="${1#-p=}"; shift ;; + -p) PROFILE="${2-}"; shift 2 ;; + -*) ;; # ignore other flags + *) PROFILE="$1"; shift ;; + esac +fi + +[[ -n "$PROFILE" ]] || { echo "ERROR: --profile is required" >&2; exit 2; } + +iperf_tcp_dl_forever() { ( iperf -c "$IPERF_HOST_TCP" -B "$OAITUN_IP" -p "$IPERF_PORT_TCP" -R -t "$LONG_T" >/dev/null 2>&1 ); } +iperf_tcp_ul_forever() { ( iperf -c "$IPERF_HOST_TCP" -B "$OAITUN_IP" -p "$IPERF_PORT_TCP" -t "$LONG_T" >/dev/null 2>&1 ); } +# iperf_udp_dl_forever() { ( nohup iperf -u -c "$IPERF_HOST_UDP" -B "$OAITUN_IP" -p "$IPERF_PORT_UDP" -R -t "$LONG_T" >/dev/null 2>&1 ); } +iperf_udp_dl_forever() { ( iperf -u -c "$IPERF_HOST_UDP" -B "$OAITUN_IP" -p "$IPERF_PORT_UDP" -R -b $1 -t "$LONG_T" >/dev/null 2>&1 ); } +iperf_udp_ul_forever() { ( iperf -u -c "$IPERF_HOST_UDP" -B "$OAITUN_IP" -p "$IPERF_PORT_UDP" -b $1 -t "$LONG_T" >/dev/null 2>&1 ); } + +# ---------------- profiles ---------------- +do_boring() { + iperf_udp_dl_forever 15M + wait + # iperf -c "$IPERF_HOST" -p "$IPERF_PORT" -B "$OAITUN_IP" -u -b 25M -t "$LONG_T" +} + +do_browsing() { + iperf_tcp_dl_forever + iperf_tcp_ul_forever + wait +} + +do_video() { + iperf_udp_dl_forever 6M + wait +} + +do_bulk_download() { + iperf -c "$IPERF_HOST" -p "$IPERF_PORT" -B "$OAITUN_IP" -R -P 8 -w 1M -t "$LONG_T" + wait +} + +do_voip() { + iperf -c "$IPERF_HOST" -p "$IPERF_PORT" -B "$OAITUN_IP" -R -u -b 80K -t "$LONG_T"; + iperf -c "$IPERF_HOST" -p "$IPERF_PORT" -B "$OAITUN_IP" -u -b 80K -t "$LONG_T"; + wait +} + +do_iot_chatty() { + iperf -c "$IPERF_HOST" -p "$IPERF_PORT" -B "$OAITUN_IP" -R -u -b 40K -t "$LONG_T"; + iperf -c "$IPERF_HOST" -p "$IPERF_PORT" -B "$OAITUN_IP" -u -b 20K -t "$LONG_T"; + wait +} + +do_gaming() { + iperf -c "$IPERF_HOST" -p "$IPERF_PORT" -B "$OAITUN_IP" -R -u -b 120K -t "$LONG_T"; + iperf -c "$IPERF_HOST" -p "$IPERF_PORT" -B "$OAITUN_IP" -u -b 10K -t "$LONG_T"; + wait +} + +do_idle() { + tail -f /dev/null +} + +# -------------- dispatch -------------- +case "$PROFILE" in + boring) do_boring;; + browsing) do_browsing;; + video) do_video;; + bulk_download) do_bulk_download;; + voip) do_voip;; + iot_chatty) do_iot_chatty;; + gaming) do_gaming;; + idle) do_idle;; + *) echo "Unknown profile: $PROFILE" >&2; exit 1;; +esac