diff --git a/openair2/LAYER2/NR_MAC_gNB/nr_mac_gNB.h b/openair2/LAYER2/NR_MAC_gNB/nr_mac_gNB.h index 704488f062..477b9ce0c0 100644 --- a/openair2/LAYER2/NR_MAC_gNB/nr_mac_gNB.h +++ b/openair2/LAYER2/NR_MAC_gNB/nr_mac_gNB.h @@ -733,6 +733,19 @@ typedef struct nr_mac_rrc_ul_if_s { 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; +} measgap_config_t; + typedef enum interrupt_followup_action { FOLLOW_INSYNC, FOLLOW_OUTOFSYNC } interrupt_followup_action_t; /*! \brief UE list used by gNB to order UEs/CC for scheduling*/ diff --git a/openair2/LAYER2/NR_MAC_gNB/nr_radio_config.c b/openair2/LAYER2/NR_MAC_gNB/nr_radio_config.c index a64cd61afb..ad22b9dd63 100644 --- a/openair2/LAYER2/NR_MAC_gNB/nr_radio_config.c +++ b/openair2/LAYER2/NR_MAC_gNB/nr_radio_config.c @@ -4090,3 +4090,198 @@ NR_ReconfigurationWithSync_t *get_reconfiguration_with_sync(rnti_t rnti, uid_t u return reconfigurationWithSync; } + +static NR_MeasGapConfig_t *get_gap_config_from_smtc(const NR_SSB_MTC_t *ssb_mtc) +{ + NR_MeasGapConfig_t *measGapConfig = calloc_or_fail(1, sizeof(*measGapConfig)); + measGapConfig->ext1 = calloc_or_fail(1, sizeof(*measGapConfig->ext1)); + measGapConfig->ext1->gapUE = calloc_or_fail(1, sizeof(*measGapConfig->ext1->gapUE)); + measGapConfig->ext1->gapUE->present = NR_SetupRelease_GapConfig_PR_setup; + NR_GapConfig_t *gap_config = calloc_or_fail(1, sizeof(*gap_config)); + measGapConfig->ext1->gapUE->choice.setup = gap_config; + + // mgta = Measurement Gap Timing Advance, to provide sufficient time for the UE to re-tune its transceiver + // This allows the Measurement Gap to extend mgta ms either side of the SS/PBCH Measurement Window. + gap_config->mgta = NR_GapConfig__mgta_ms0dot5; + + // mgrp = Measurement Gap Repetition Period + // gapOffset = It defines the start of the Measurement Gaps relative to the start of the radio frame with SFN = 0 + // The Measurement Gaps need to be synchronized with the SS/PBCH transmissions which are to be measured + gap_config->mgrp = NR_GapConfig__mgrp_ms160; + switch (ssb_mtc->periodicityAndOffset.present) { + case NR_SSB_MTC__periodicityAndOffset_PR_sf20: + gap_config->gapOffset = ssb_mtc->periodicityAndOffset.choice.sf20; + break; + case NR_SSB_MTC__periodicityAndOffset_PR_sf40: + gap_config->gapOffset = ssb_mtc->periodicityAndOffset.choice.sf40; + break; + case NR_SSB_MTC__periodicityAndOffset_PR_sf80: + gap_config->gapOffset = ssb_mtc->periodicityAndOffset.choice.sf80; + break; + case NR_SSB_MTC__periodicityAndOffset_PR_sf160: + gap_config->gapOffset = ssb_mtc->periodicityAndOffset.choice.sf160; + break; + default: + LOG_W(NR_RRC, "SMTC periodicity higher than MGRP\n"); + // With this configuration, not all SSBs belong to the Measurement Gap. + if (ssb_mtc->periodicityAndOffset.present == NR_SSB_MTC__periodicityAndOffset_PR_sf5) { + gap_config->gapOffset = ssb_mtc->periodicityAndOffset.choice.sf5; + } else if (ssb_mtc->periodicityAndOffset.present == NR_SSB_MTC__periodicityAndOffset_PR_sf10) { + gap_config->gapOffset = ssb_mtc->periodicityAndOffset.choice.sf10; + } + } + + // mgl = Measurement Gap Length + // FIXME: At least the duration of the SMTC plus 2 times the mgta should be enough, + // however, at the moment it only works by setting the maximum value + gap_config->mgl = NR_GapConfig__mgl_ms6; + + return measGapConfig; +} + +NR_MeasurementTimingConfiguration_t *get_nr_mtc(uint8_t *buf, uint32_t len) +{ + if (buf == NULL || len == 0) + return NULL; + + NR_MeasurementTimingConfiguration_t *mtc = NULL; + asn_dec_rval_t dec_rval = uper_decode(NULL, &asn_DEF_NR_MeasurementTimingConfiguration, (void **)&mtc, buf, len, 0, 0); + if (dec_rval.code != RC_OK) { + LOG_E(NR_MAC, "cannot decode NR MeasurementTimingConfiguration, ignoring\n"); + ASN_STRUCT_FREE(asn_DEF_NR_MeasurementTimingConfiguration, mtc); + return NULL; + } + return mtc; +} + +/** @brief return Measurement Gap Repetition Period from ASN.1 config */ +static int get_mgrp(long mgrp) +{ + switch (mgrp) { + case NR_GapConfig__mgrp_ms20: + return 20; + case NR_GapConfig__mgrp_ms40: + return 40; + case NR_GapConfig__mgrp_ms80: + return 80; + case NR_GapConfig__mgrp_ms160: + return 160; + default: + LOG_E(NR_MAC, "Invalid MGRP %ld\n", mgrp); + return -1; + } +} + +/** @brief Return Measurement Gap Length from ASN.1 config */ +static float get_mgl(long mgl) +{ + switch (mgl) { + case NR_GapConfig__mgl_ms1dot5: + return 1.5; + case NR_GapConfig__mgl_ms3: + return 3.0; + case NR_GapConfig__mgl_ms3dot5: + return 3.5; + case NR_GapConfig__mgl_ms4: + return 4; + case NR_GapConfig__mgl_ms5dot5: + return 5.5; + case NR_GapConfig__mgl_ms6: + return 6; + default: + LOG_E(NR_MAC, "Invalid MGL %ld\n", mgl); + return -1; + } +} + +/** @brief Return Measurement Gap Timing Advance, from ASN.1 config */ +static float get_mgta(long mgta) +{ + switch (mgta) { + case NR_GapConfig__mgta_ms0: + return 0.0; + case NR_GapConfig__mgta_ms0dot25: + return 0.25; + case NR_GapConfig__mgta_ms0dot5: + return 0.5; + default: + LOG_E(NR_MAC, "Invalid MGTA %ld\n", mgta); + return -1; + } +} + +/** @brief Return Measurement Gap Configuration, from ASN.1 config */ +const NR_GapConfig_t *get_gap_config(const NR_MeasGapConfig_t *mgc) +{ + if (mgc->gapFR2 && mgc->gapFR2->present == NR_SetupRelease_GapConfig_PR_setup) + return mgc->gapFR2->choice.setup; + + // FR1 case + if (!mgc->ext1) + return NULL; + + const NR_SetupRelease_GapConfig_t *gapUE = mgc->ext1->gapUE; + if (gapUE && gapUE->present == NR_SetupRelease_GapConfig_PR_setup) + return gapUE->choice.setup; + + const NR_SetupRelease_GapConfig_t *gapFR1 = mgc->ext1->gapFR1; + if (gapFR1 && gapFR1->present == NR_SetupRelease_GapConfig_PR_setup) + return gapFR1->choice.setup; + + return NULL; +} + +measgap_config_t create_measgap_config(const NR_MeasurementTimingConfiguration_t *mtc, int scs, int min_rxtxtime) +{ + measgap_config_t mgc = {0}; + const NR_MeasTimingList_t *mtlist = mtc->criticalExtensions.choice.c1->choice.measTimingConf->measTiming; + const NR_MeasTiming_t *mt = mtlist->list.array[0]; + DevAssert(mt != NULL && mt->frequencyAndTiming != NULL); + const struct NR_MeasTiming__frequencyAndTiming *ft = mt->frequencyAndTiming; + const NR_SSB_MTC_t *ssb_mtc = &ft->ssb_MeasurementTimingConfiguration; + NR_MeasGapConfig_t *measGapConfig = get_gap_config_from_smtc(ssb_mtc); + const NR_GapConfig_t *gap_config = get_gap_config(measGapConfig); + if (!gap_config) + return mgc; + + mgc.mgrp_ms = get_mgrp(gap_config->mgrp); + DevAssert(mgc.mgrp_ms != -1); + mgc.mgrp = gap_config->mgrp; + + mgc.gapOffset = gap_config->gapOffset; + mgc.mgta = gap_config->mgta; + mgc.n_slots_mgta = ((int)(10 * get_mgta(gap_config->mgta)) << scs) / 10; + + // We start the timer K2 slots earlier to avoid scheduling feedback PUCCHs inside measGap + // or depending on the current min_rxtxtime earlier + // TS 38.214 - Table 6.1.2.1.1.2 + const int max_k2 = max(3, min_rxtxtime); + + mgc.n_slots_advance = mgc.n_slots_mgta + max_k2; + + mgc.mgl_ms = get_mgl(gap_config->mgl); + DevAssert(mgc.mgl_ms != -1); + mgc.mgl = gap_config->mgl; + mgc.mgl_slots = ((int)(10 * (mgc.mgl_ms + max_k2)) << scs) / 10; + + mgc.enable = true; + return mgc; +} + +int encode_measgap_config(const measgap_config_t *c, uint8_t *buf) +{ + NR_MeasGapConfig_t *measGapConfig = calloc_or_fail(1, sizeof(*measGapConfig)); + measGapConfig->ext1 = calloc_or_fail(1, sizeof(*measGapConfig->ext1)); + measGapConfig->ext1->gapUE = calloc_or_fail(1, sizeof(*measGapConfig->ext1->gapUE)); + measGapConfig->ext1->gapUE->present = NR_SetupRelease_GapConfig_PR_setup; + NR_GapConfig_t *gap_config = calloc_or_fail(1, sizeof(*gap_config)); + measGapConfig->ext1->gapUE->choice.setup = gap_config; + gap_config->mgta = c->mgta; + gap_config->mgrp = c->mgrp; + gap_config->gapOffset = c->gapOffset; + gap_config->mgl = c->mgl; + asn_enc_rval_t enc_rval_mgc = uper_encode_to_buffer(&asn_DEF_NR_MeasGapConfig, NULL, measGapConfig, buf, 1024); + AssertFatal(enc_rval_mgc.encoded > 0, "Could not encode CellGroup, failed element %s\n", enc_rval_mgc.failed_type->name); + ASN_STRUCT_FREE(asn_DEF_NR_MeasGapConfig, measGapConfig); + return (int)((enc_rval_mgc.encoded + 7) >> 3); +} diff --git a/openair2/LAYER2/NR_MAC_gNB/nr_radio_config.h b/openair2/LAYER2/NR_MAC_gNB/nr_radio_config.h index bdf56a3310..51c30c38b6 100644 --- a/openair2/LAYER2/NR_MAC_gNB/nr_radio_config.h +++ b/openair2/LAYER2/NR_MAC_gNB/nr_radio_config.h @@ -28,6 +28,8 @@ #include "NR_BCCH-DL-SCH-Message.h" #include "NR_CellGroupConfig.h" #include "NR_UE-NR-Capability.h" +#include "NR_MeasConfig.h" +#include "NR_MeasurementTimingConfiguration.h" #include "NR_UL-CCCH-Message.h" #include "f1ap_messages_types.h" #include "common/platform_types.h" @@ -42,6 +44,9 @@ typedef struct nr_mac_config_t nr_mac_config_t; struct nr_mac_timers; typedef struct nr_mac_timers nr_mac_timers_t; +struct measgap_config; +typedef struct measgap_config measgap_config_t; + void nr_rrc_config_dl_tda(struct NR_PDSCH_TimeDomainResourceAllocationList *pdsch_TimeDomainAllocationList, frame_type_t frame_type, NR_TDD_UL_DL_ConfigCommon_t *tdd_UL_DL_ConfigurationCommon, @@ -119,4 +124,8 @@ NR_RLC_BearerConfig_t *get_DRB_RLC_BearerConfig(long lcChannelId, long priority, const nr_rlc_configuration_t *default_rlc_config); +NR_MeasurementTimingConfiguration_t *get_nr_mtc(uint8_t *buf, uint32_t len); +measgap_config_t create_measgap_config(const NR_MeasurementTimingConfiguration_t *mtc, int scs, int min_rxtxtime); +int encode_measgap_config(const measgap_config_t *c, uint8_t *buf); + #endif