From c2d9fb358959776397a5dfa719d1200e845aef97 Mon Sep 17 00:00:00 2001 From: rmagueta Date: Thu, 10 Jul 2025 09:06:39 +0200 Subject: [PATCH] Add MeasGap Configuration in Radio config Add some helper functions that will be used by the MAC handler to fill the right MeasGap configuration based on Measurement Configuration sent by the CU, in the next commit. --- openair2/LAYER2/NR_MAC_gNB/nr_mac_gNB.h | 13 ++ openair2/LAYER2/NR_MAC_gNB/nr_radio_config.c | 195 +++++++++++++++++++ openair2/LAYER2/NR_MAC_gNB/nr_radio_config.h | 9 + 3 files changed, 217 insertions(+) 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