From 621aa3c12ea2921b6c4d9aae701471c82b610a04 Mon Sep 17 00:00:00 2001 From: Robert Schmidt Date: Thu, 10 Jul 2025 08:37:29 +0200 Subject: [PATCH 01/12] Cleanup possible transmission interrupt states --- openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_primitives.c | 1 + openair2/LAYER2/NR_MAC_gNB/nr_mac_gNB.h | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_primitives.c b/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_primitives.c index 36cb53b8f2..13dc711b77 100644 --- a/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_primitives.c +++ b/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_primitives.c @@ -3283,6 +3283,7 @@ void nr_mac_update_timers(module_id_t module_id, frame_t frame, slot_t slot) nr_timer_stop(&sched_ctrl->transm_interrupt); if (UE->interrupt_action == FOLLOW_OUTOFSYNC) nr_mac_trigger_ul_failure(sched_ctrl, UE->current_DL_BWP.scs); + /* else: default FOLLOW_INSYNC: nothing to do (UE is now active again) */ } } } diff --git a/openair2/LAYER2/NR_MAC_gNB/nr_mac_gNB.h b/openair2/LAYER2/NR_MAC_gNB/nr_mac_gNB.h index ea2cc617bd..704488f062 100644 --- a/openair2/LAYER2/NR_MAC_gNB/nr_mac_gNB.h +++ b/openair2/LAYER2/NR_MAC_gNB/nr_mac_gNB.h @@ -733,7 +733,7 @@ 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 enum interrupt_followup_action { FOLLOW_INSYNC, FOLLOW_INSYNC_RECONFIG, FOLLOW_OUTOFSYNC } interrupt_followup_action_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*/ typedef struct { From a13a8f01af31f5d30f531523d191a64aa6973614 Mon Sep 17 00:00:00 2001 From: rmagueta Date: Wed, 6 Nov 2024 12:07:44 +0000 Subject: [PATCH 02/12] Add F1AP procedures related to measurementTimingConfiguration --- openair2/COMMON/f1ap_messages_types.h | 1 + openair2/F1AP/lib/f1ap_ue_context.c | 25 ++++++++++++++++++- openair2/F1AP/tests/f1ap_lib_test.c | 2 ++ .../LAYER2/NR_MAC_gNB/mac_rrc_ul_direct.c | 1 - openair2/LAYER2/NR_MAC_gNB/mac_rrc_ul_f1ap.c | 1 - 5 files changed, 27 insertions(+), 3 deletions(-) diff --git a/openair2/COMMON/f1ap_messages_types.h b/openair2/COMMON/f1ap_messages_types.h index 89826e7ff5..e44e1dccb4 100644 --- a/openair2/COMMON/f1ap_messages_types.h +++ b/openair2/COMMON/f1ap_messages_types.h @@ -381,6 +381,7 @@ typedef struct f1ap_cu_to_du_rrc_info_s { byte_array_t *cg_configinfo; byte_array_t *ue_cap; byte_array_t *meas_config; + byte_array_t *meas_timing_config; byte_array_t *ho_prep_info; } f1ap_cu_to_du_rrc_info_t; diff --git a/openair2/F1AP/lib/f1ap_ue_context.c b/openair2/F1AP/lib/f1ap_ue_context.c index a1b5531c2b..9d8cbfe15f 100644 --- a/openair2/F1AP/lib/f1ap_ue_context.c +++ b/openair2/F1AP/lib/f1ap_ue_context.c @@ -57,8 +57,9 @@ static F1AP_CUtoDURRCInformation_t encode_cu_to_du_rrc_info(const f1ap_cu_to_du_ } /* optional: HandoverPreparationInformation */ + F1AP_ProtocolExtensionContainer_10696P60_t *p = NULL; if (cu2du->ho_prep_info) { - F1AP_ProtocolExtensionContainer_10696P60_t *p = calloc_or_fail(1, sizeof(*p)); + p = calloc_or_fail(1, sizeof(*p)); enc.iE_Extensions = (struct F1AP_ProtocolExtensionContainer *)p; asn1cSequenceAdd(p->list, F1AP_CUtoDURRCInformation_ExtIEs_t, ie_ext); ie_ext->id = F1AP_ProtocolIE_ID_id_HandoverPreparationInformation; @@ -68,6 +69,20 @@ static F1AP_CUtoDURRCInformation_t encode_cu_to_du_rrc_info(const f1ap_cu_to_du_ F1AP_HandoverPreparationInformation_t *hpi = &ie_ext->extensionValue.choice.HandoverPreparationInformation; OCTET_STRING_fromBuf(hpi, (const char *)ba->buf, ba->len); } + + /* optional: iE_Extensions */ + if (cu2du->meas_timing_config) { + if (!p) + p = calloc_or_fail(1, sizeof(*p)); + enc.iE_Extensions = (struct F1AP_ProtocolExtensionContainer *)p; + asn1cSequenceAdd(p->list, F1AP_CUtoDURRCInformation_ExtIEs_t, ie_ext); + ie_ext->id = F1AP_ProtocolIE_ID_id_MeasurementTimingConfiguration; + ie_ext->criticality = F1AP_Criticality_ignore; + ie_ext->extensionValue.present = F1AP_CUtoDURRCInformation_ExtIEs__extensionValue_PR_MeasurementTimingConfiguration; + const byte_array_t *ba = cu2du->meas_timing_config; + F1AP_MeasurementTimingConfiguration_t *mtc = &ie_ext->extensionValue.choice.MeasurementTimingConfiguration; + OCTET_STRING_fromBuf(mtc, (const char *)ba->buf, ba->len); + } return enc; } @@ -98,6 +113,11 @@ static bool decode_cu_to_du_rrc_info(f1ap_cu_to_du_rrc_info_t *dec, const F1AP_C const F1AP_HandoverPreparationInformation_t *hopi = &cu2du_info_ext->extensionValue.choice.HandoverPreparationInformation; *dec->ho_prep_info = create_byte_array(hopi->size, (uint8_t*) hopi->buf); break; + case F1AP_ProtocolIE_ID_id_MeasurementTimingConfiguration: + dec->meas_timing_config = calloc_or_fail(1, sizeof(*dec->meas_timing_config)); + const F1AP_MeasurementTimingConfiguration_t *mtc = &cu2du_info_ext->extensionValue.choice.MeasurementTimingConfiguration; + *dec->meas_timing_config = create_byte_array(mtc->size, (uint8_t *)mtc->buf); + break; default: PRINT_ERROR("received unsupported F1AP CUtoDURRCInfo extension container %ld\n", cu2du_info_ext->id); } @@ -112,6 +132,7 @@ static f1ap_cu_to_du_rrc_info_t cp_cu_to_du_rrc_info(const f1ap_cu_to_du_rrc_inf CP_OPT_BYTE_ARRAY(dst.cg_configinfo, src->cg_configinfo); CP_OPT_BYTE_ARRAY(dst.ue_cap, src->ue_cap); CP_OPT_BYTE_ARRAY(dst.meas_config, src->meas_config); + CP_OPT_BYTE_ARRAY(dst.meas_timing_config, src->meas_timing_config); CP_OPT_BYTE_ARRAY(dst.ho_prep_info, src->ho_prep_info); return dst; } @@ -126,6 +147,7 @@ static bool eq_cu_to_du_rrc_info(const f1ap_cu_to_du_rrc_info_t *a, const f1ap_c _F1_EQ_CHECK_OPTIONAL_IE(a, b, cg_configinfo, eq_ba); _F1_EQ_CHECK_OPTIONAL_IE(a, b, ue_cap, eq_ba); _F1_EQ_CHECK_OPTIONAL_IE(a, b, meas_config, eq_ba); + _F1_EQ_CHECK_OPTIONAL_IE(a, b, meas_timing_config, eq_ba); _F1_EQ_CHECK_OPTIONAL_IE(a, b, ho_prep_info, eq_ba); return true; } @@ -135,6 +157,7 @@ static void free_cu_to_du_rrc_info(f1ap_cu_to_du_rrc_info_t *cu2du) FREE_OPT_BYTE_ARRAY(cu2du->cg_configinfo); FREE_OPT_BYTE_ARRAY(cu2du->ue_cap); FREE_OPT_BYTE_ARRAY(cu2du->meas_config); + FREE_OPT_BYTE_ARRAY(cu2du->meas_timing_config); FREE_OPT_BYTE_ARRAY(cu2du->ho_prep_info); } diff --git a/openair2/F1AP/tests/f1ap_lib_test.c b/openair2/F1AP/tests/f1ap_lib_test.c index ebf90fc648..72ea809245 100644 --- a/openair2/F1AP/tests/f1ap_lib_test.c +++ b/openair2/F1AP/tests/f1ap_lib_test.c @@ -755,6 +755,7 @@ static void test_f1ap_ue_context_setup_request() orig.cu_to_du_rrc_info.cg_configinfo = get_malloced_test_ba("CG-CONFIGINFO"); orig.cu_to_du_rrc_info.ue_cap = get_malloced_test_ba("UE CAP"); orig.cu_to_du_rrc_info.meas_config = get_malloced_test_ba("UE MEASUREMENT CONFIGURATION"); + orig.cu_to_du_rrc_info.meas_timing_config = get_malloced_test_ba("MEASUREMENT TIMING CONFIGURATION"); orig.cu_to_du_rrc_info.ho_prep_info = get_malloced_test_ba("PREPARATION DATA FOR HANDOVER"); orig.srbs_len = 2; orig.srbs = calloc_or_fail(orig.srbs_len, sizeof(*orig.srbs)); @@ -996,6 +997,7 @@ static void test_f1ap_ue_context_modification_request() .cg_configinfo = get_malloced_test_ba("Modified"), .ue_cap = get_malloced_test_ba("UE forgot to send earlier"), .meas_config = get_malloced_test_ba("with updates from another cell"), + .meas_timing_config = get_malloced_test_ba("measurement timing configuration"), .ho_prep_info = get_malloced_test_ba("prepare to ho soon!"), }; _F1_MALLOC(orig.cu_to_du_rrc_info, cu2du); diff --git a/openair2/LAYER2/NR_MAC_gNB/mac_rrc_ul_direct.c b/openair2/LAYER2/NR_MAC_gNB/mac_rrc_ul_direct.c index cf6c5b2563..deec619e3d 100644 --- a/openair2/LAYER2/NR_MAC_gNB/mac_rrc_ul_direct.c +++ b/openair2/LAYER2/NR_MAC_gNB/mac_rrc_ul_direct.c @@ -93,7 +93,6 @@ static void ue_context_modification_required_direct(const f1ap_ue_context_modif_ du2cu->cellGroupConfig = malloc(du2cu->cellGroupConfig_length * sizeof(*du2cu->cellGroupConfig)); AssertFatal(du2cu->cellGroupConfig != NULL, "out of memory\n"); memcpy(du2cu->cellGroupConfig, required->du_to_cu_rrc_information->cellGroupConfig, du2cu->cellGroupConfig_length); - AssertFatal(required->du_to_cu_rrc_information->measGapConfig == NULL && required->du_to_cu_rrc_information->measGapConfig_length == 0, "not handled yet\n"); AssertFatal(required->du_to_cu_rrc_information->requestedP_MaxFR1 == NULL && required->du_to_cu_rrc_information->requestedP_MaxFR1_length == 0, "not handled yet\n"); } f1ap_msg->cause = required->cause; diff --git a/openair2/LAYER2/NR_MAC_gNB/mac_rrc_ul_f1ap.c b/openair2/LAYER2/NR_MAC_gNB/mac_rrc_ul_f1ap.c index 19bf8039b6..c7e23ed702 100644 --- a/openair2/LAYER2/NR_MAC_gNB/mac_rrc_ul_f1ap.c +++ b/openair2/LAYER2/NR_MAC_gNB/mac_rrc_ul_f1ap.c @@ -117,7 +117,6 @@ static void ue_context_modification_required_f1ap(const f1ap_ue_context_modif_re du2cu->cellGroupConfig = malloc(du2cu->cellGroupConfig_length * sizeof(*du2cu->cellGroupConfig)); AssertFatal(du2cu->cellGroupConfig != NULL, "out of memory\n"); memcpy(du2cu->cellGroupConfig, required->du_to_cu_rrc_information->cellGroupConfig, du2cu->cellGroupConfig_length); - AssertFatal(required->du_to_cu_rrc_information->measGapConfig == NULL && required->du_to_cu_rrc_information->measGapConfig_length == 0, "not handled yet\n"); AssertFatal(required->du_to_cu_rrc_information->requestedP_MaxFR1 == NULL && required->du_to_cu_rrc_information->requestedP_MaxFR1_length == 0, "not handled yet\n"); } f1ap_msg->cause = required->cause; From 7b8427e101f89fd7d018d945ffeefdb09d158c5a Mon Sep 17 00:00:00 2001 From: rmagueta Date: Thu, 10 Jul 2025 09:06:34 +0200 Subject: [PATCH 03/12] UE RRC: Print warning that measurement gaps are not supported --- openair2/RRC/NR_UE/rrc_UE.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/openair2/RRC/NR_UE/rrc_UE.c b/openair2/RRC/NR_UE/rrc_UE.c index da1b3d8ba0..87cb68303a 100644 --- a/openair2/RRC/NR_UE/rrc_UE.c +++ b/openair2/RRC/NR_UE/rrc_UE.c @@ -1871,8 +1871,7 @@ static void nr_rrc_ue_process_measConfig(rrcPerNB_t *rrc, NR_MeasConfig_t *const if (measConfig->measIdToAddModList) handle_measid_addmod(rrc, measConfig->measIdToAddModList, timers); - AssertFatal(!measConfig->measGapConfig, "Measurement gaps not yet supported\n"); - AssertFatal(!measConfig->measGapSharingConfig, "Measurement gaps not yet supported\n"); + LOG_W(NR_RRC, "Measurement gaps not yet supported!\n"); if (measConfig->s_MeasureConfig) { if (measConfig->s_MeasureConfig->present == NR_MeasConfig__s_MeasureConfig_PR_ssb_RSRP) { From c2d9fb358959776397a5dfa719d1200e845aef97 Mon Sep 17 00:00:00 2001 From: rmagueta Date: Thu, 10 Jul 2025 09:06:39 +0200 Subject: [PATCH 04/12] 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 From dfbb6b1509be3ce935383ff5d91c074e3cfee492 Mon Sep 17 00:00:00 2001 From: rmagueta Date: Thu, 10 Jul 2025 08:35:11 +0200 Subject: [PATCH 05/12] Handle Measurement Timing Configuration in DU/MAC handler --- openair2/LAYER2/NR_MAC_gNB/mac_rrc_dl_handler.c | 15 +++++++++++++-- openair2/LAYER2/NR_MAC_gNB/nr_mac_gNB.h | 1 + 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/openair2/LAYER2/NR_MAC_gNB/mac_rrc_dl_handler.c b/openair2/LAYER2/NR_MAC_gNB/mac_rrc_dl_handler.c index 30d3999f75..43b73705e9 100644 --- a/openair2/LAYER2/NR_MAC_gNB/mac_rrc_dl_handler.c +++ b/openair2/LAYER2/NR_MAC_gNB/mac_rrc_dl_handler.c @@ -625,7 +625,9 @@ void ue_context_setup_request(const f1ap_ue_context_setup_req_t *req) } else if (cu2du->ue_cap != NULL) { ue_cap = get_ue_nr_cap(*req->gNB_DU_ue_id, cu2du->ue_cap->buf, cu2du->ue_cap->len); } - AssertFatal(cu2du->meas_config == NULL, "MeasConfig not handled\n"); + NR_MeasurementTimingConfiguration_t *mtc = NULL; + if (cu2du->meas_timing_config != NULL) + mtc = get_nr_mtc(cu2du->meas_timing_config->buf, cu2du->meas_timing_config->len); /* 38.473: "For DC operation, the CG-ConfigInfo IE shall be included in the CU * to DU RRC Information IE at the gNB acting as secondary node" As of now, @@ -696,6 +698,16 @@ void ue_context_setup_request(const f1ap_ue_context_setup_req_t *req) int ss_type = cg_configinfo ? NR_SearchSpace__searchSpaceType_PR_ue_Specific: NR_SearchSpace__searchSpaceType_PR_common; configure_UE_BWP(mac, scc, UE, false, ss_type, -1, -1); + if (mtc) { + /* creates a suitable measGap config to be used in the gNB */ + UE->measgap_config = create_measgap_config(mtc, UE->current_DL_BWP.scs, mac->radio_config.minRXTXTIME); + /* encodes the measGapConfig created, if useful (or not!) */ + byte_array_t *mgc = calloc_or_fail(1, sizeof(*mgc)); + mgc->buf = calloc_or_fail(1, 1024); + mgc->len = encode_measgap_config(&UE->measgap_config, mgc->buf); + resp.du_to_cu_rrc_info.meas_gap_config = mgc; + } + NR_SCHED_UNLOCK(&mac->sched_lock); mac->mac_rrc.ue_context_setup_response(&resp); @@ -720,7 +732,6 @@ void ue_context_modification_request(const f1ap_ue_context_mod_req_t *req) byte_array_t *b = req->cu_to_du_rrc_info->ue_cap; ue_cap = get_ue_nr_cap(req->gNB_DU_ue_id, b->buf, b->len); } - AssertFatal(req->cu_to_du_rrc_info->meas_config == NULL, "MeasConfig not handled\n"); } NR_SCHED_LOCK(&mac->sched_lock); diff --git a/openair2/LAYER2/NR_MAC_gNB/nr_mac_gNB.h b/openair2/LAYER2/NR_MAC_gNB/nr_mac_gNB.h index 477b9ce0c0..84be6f4b5a 100644 --- a/openair2/LAYER2/NR_MAC_gNB/nr_mac_gNB.h +++ b/openair2/LAYER2/NR_MAC_gNB/nr_mac_gNB.h @@ -766,6 +766,7 @@ typedef struct { NR_SpCellConfig_t *reconfigSpCellConfig; interrupt_followup_action_t interrupt_action; NR_UE_NR_Capability_t *capability; + measgap_config_t measgap_config; // UE selected beam index uint8_t UE_beam_index; float ul_thr_ue; From 932b17766782c6f9bf7820a5986fb16e15f45be4 Mon Sep 17 00:00:00 2001 From: rmagueta Date: Thu, 10 Jul 2025 08:38:52 +0200 Subject: [PATCH 06/12] Add logic for measgap scheduling --- common/utils/nr/nr_common.c | 5 +++ common/utils/nr/nr_common.h | 6 +++ openair2/LAYER2/NR_MAC_gNB/gNB_scheduler.c | 1 + .../LAYER2/NR_MAC_gNB/gNB_scheduler_dlsch.c | 1 + .../NR_MAC_gNB/gNB_scheduler_primitives.c | 38 ++++++++++++++++++- openair2/LAYER2/NR_MAC_gNB/mac_proto.h | 2 + 6 files changed, 51 insertions(+), 2 deletions(-) diff --git a/common/utils/nr/nr_common.c b/common/utils/nr/nr_common.c index 5d98a99bb4..7875fe546a 100644 --- a/common/utils/nr/nr_common.c +++ b/common/utils/nr/nr_common.c @@ -1415,6 +1415,11 @@ uint32_t nr_timer_elapsed_time(const NR_timer_t *timer) return timer->counter; } +uint32_t nr_timer_remaining_time(const NR_timer_t *timer) +{ + return timer->target - timer->counter; +} + void nr_timer_setup(NR_timer_t *timer, const uint32_t target, const uint32_t step) { timer->target = target; diff --git a/common/utils/nr/nr_common.h b/common/utils/nr/nr_common.h index f98ce510a2..05ee80c18a 100644 --- a/common/utils/nr/nr_common.h +++ b/common/utils/nr/nr_common.h @@ -222,6 +222,12 @@ bool nr_timer_is_active(const NR_timer_t *timer); * @return Time passed since start of timer */ uint32_t nr_timer_elapsed_time(const NR_timer_t *timer); +/** + * @brief To return how much time is left until the timer expires + * @param timer Timer to be checked + * @return Time left until the timer expires + */ +uint32_t nr_timer_remaining_time(const NR_timer_t *timer); int set_default_nta_offset(frequency_range_t freq_range, uint32_t samples_per_subframe); diff --git a/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler.c b/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler.c index 39d75907be..faaa00ea3f 100644 --- a/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler.c +++ b/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler.c @@ -201,6 +201,7 @@ void gNB_dlsch_ulsch_scheduler(module_id_t module_idP, frame_t frame, slot_t slo LOG_I(NR_MAC, "Frame.Slot %d.%d\n%s\n", frame, slot, stats_output); } + nr_measgap_scheduling(gNB, frame, slot); nr_mac_update_timers(module_idP, frame, slot); if (wait_prach_completed || get_softmodem_params()->phy_test) { diff --git a/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_dlsch.c b/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_dlsch.c index 9c5a00e12c..dad58f52f0 100644 --- a/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_dlsch.c +++ b/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_dlsch.c @@ -628,6 +628,7 @@ static void pf_dl(module_id_t module_id, NR_sched_pdsch_t *sched_pdsch = &sched_ctrl->sched_pdsch; /* get the PID of a HARQ process awaiting retrnasmission, or -1 otherwise */ sched_pdsch->dl_harq_pid = sched_ctrl->retrans_dl_harq.head; + /* Calculate Throughput */ const float a = 0.01f; const uint32_t b = UE->mac_stats.dl.current_bytes; diff --git a/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_primitives.c b/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_primitives.c index 13dc711b77..b40329cd69 100644 --- a/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_primitives.c +++ b/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_primitives.c @@ -3158,6 +3158,37 @@ void nr_csirs_scheduling(int Mod_idP, frame_t frame, slot_t slot, nfapi_nr_dl_tt } } +void nr_measgap_scheduling(gNB_MAC_INST *nr_mac, frame_t frame, sub_frame_t slot) +{ + NR_SCHED_ENSURE_LOCKED(&nr_mac->sched_lock); + + NR_UEs_t *UE_info = &nr_mac->UE_info; + UE_iterator(UE_info->connected_ue_list, UE) { + measgap_config_t *mgc = &UE->measgap_config; + if (!mgc->enable) + continue; + + const int slots_frame = nr_mac->frame_structure.numb_slots_frame; + const frame_t f = (frame + (slot + mgc->n_slots_advance) / slots_frame) % 1024; + const slot_t s = (slot + mgc->n_slots_advance) % slots_frame; + + // TS 38 331 - Section 5.5.2.9 Measurement gap configuration + if (!(((f % (mgc->mgrp_ms / 10)) == (mgc->gapOffset / 10)) && (s == mgc->gapOffset % 10))) + continue; + + NR_timer_t *t = &UE->UE_sched_ctrl.transm_interrupt; + interrupt_followup_action_t a = nr_timer_is_active(t) ? UE->interrupt_action : FOLLOW_INSYNC; + // start a timer to stop scheduling UE during MeasGap, or extend timer for + // duration of measGap with existing follow-up action + // TODO: if the timer is running, it might be to stop scheduling of the UE afterwards, but then it does not make sense to stop + // scheduling the UE now, we should maybe just skip it? + if (!nr_timer_is_active(t) || nr_timer_remaining_time(t) < mgc->mgl_slots) { + nr_mac_trigger_ul_failure(&UE->UE_sched_ctrl, UE->current_DL_BWP.scs); /* set the UE to "not active" */ + nr_mac_interrupt_ue_transmission(nr_mac, UE, a, mgc->mgl_slots); + } + } +} + void nr_mac_clean_cellgroup(NR_CellGroupConfig_t *cell_group) { DevAssert(cell_group != NULL); @@ -3281,9 +3312,12 @@ void nr_mac_update_timers(module_id_t module_id, frame_t frame, slot_t slot) if (nr_timer_tick(&sched_ctrl->transm_interrupt)) { /* expired */ nr_timer_stop(&sched_ctrl->transm_interrupt); - if (UE->interrupt_action == FOLLOW_OUTOFSYNC) + if (UE->interrupt_action == FOLLOW_OUTOFSYNC) { nr_mac_trigger_ul_failure(sched_ctrl, UE->current_DL_BWP.scs); - /* else: default FOLLOW_INSYNC: nothing to do (UE is now active again) */ + } else { + DevAssert(UE->interrupt_action == FOLLOW_INSYNC); + nr_mac_reset_ul_failure(sched_ctrl); + } } } } diff --git a/openair2/LAYER2/NR_MAC_gNB/mac_proto.h b/openair2/LAYER2/NR_MAC_gNB/mac_proto.h index c6f9a2f6f9..ea07fbeea4 100644 --- a/openair2/LAYER2/NR_MAC_gNB/mac_proto.h +++ b/openair2/LAYER2/NR_MAC_gNB/mac_proto.h @@ -203,6 +203,8 @@ void nr_csirs_scheduling(int Mod_idP, frame_t frame, slot_t slot, nfapi_nr_dl_tt void nr_csi_meas_reporting(int Mod_idP, frame_t frameP, slot_t slotP); +void nr_measgap_scheduling(gNB_MAC_INST *nr_mac, frame_t frame, sub_frame_t slot); + int nr_acknack_scheduling(gNB_MAC_INST *mac, NR_UE_info_t *UE, frame_t frameP, From 6ce4d78c1908a0640dec9e72bd0a9720ecc8d616 Mon Sep 17 00:00:00 2001 From: rmagueta Date: Thu, 10 Jul 2025 09:58:07 +0200 Subject: [PATCH 07/12] Refactor get_MeasConfig(): create subfunctions --- openair2/RRC/NR/MESSAGES/asn1_msg.c | 78 +++++++++++++++++------------ 1 file changed, 47 insertions(+), 31 deletions(-) diff --git a/openair2/RRC/NR/MESSAGES/asn1_msg.c b/openair2/RRC/NR/MESSAGES/asn1_msg.c index 0f7ec6db1a..184b8d5888 100644 --- a/openair2/RRC/NR/MESSAGES/asn1_msg.c +++ b/openair2/RRC/NR/MESSAGES/asn1_msg.c @@ -956,6 +956,42 @@ int do_RRCReestablishmentComplete(uint8_t *buffer, size_t buffer_size, int64_t r return((enc_rval.encoded+7)/8); } +static NR_MeasObjectToAddMod_t *get_MeasObject(const struct NR_MeasTiming__frequencyAndTiming *ft, + int band, + NR_ARFCN_ValueNR_t ssbFrequency, + NR_MeasObjectId_t measObjectId) +{ + const NR_SSB_MTC_t *ssb_mtc = &ft->ssb_MeasurementTimingConfiguration; + NR_MeasObjectToAddMod_t *mo = calloc_or_fail(1, sizeof(*mo)); + mo->measObjectId = measObjectId; + mo->measObject.present = NR_MeasObjectToAddMod__measObject_PR_measObjectNR; + NR_MeasObjectNR_t *monr = calloc_or_fail(1, sizeof(*monr)); + asn1cCallocOne(monr->ssbFrequency, ssbFrequency); + asn1cCallocOne(monr->ssbSubcarrierSpacing, ft->ssbSubcarrierSpacing); + monr->referenceSignalConfig.ssb_ConfigMobility = calloc_or_fail(1, sizeof(*monr->referenceSignalConfig.ssb_ConfigMobility)); + monr->referenceSignalConfig.ssb_ConfigMobility->deriveSSB_IndexFromCell = true; + monr->absThreshSS_BlocksConsolidation = calloc_or_fail(1, sizeof(*monr->absThreshSS_BlocksConsolidation)); + asn1cCallocOne(monr->absThreshSS_BlocksConsolidation->thresholdRSRP, 36); + asn1cCallocOne(monr->nrofSS_BlocksToAverage, 8); + monr->smtc1 = calloc_or_fail(1, sizeof(*monr->smtc1)); + monr->smtc1->periodicityAndOffset = ssb_mtc->periodicityAndOffset; + monr->smtc1->duration = ssb_mtc->duration; + monr->quantityConfigIndex = 1; + monr->ext1 = calloc_or_fail(1, sizeof(*monr->ext1)); + asn1cCallocOne(monr->ext1->freqBandIndicatorNR, band); + mo->measObject.choice.measObjectNR = monr; + return mo; +} + +static NR_MeasIdToAddMod_t *get_MeasId(NR_MeasId_t measId, NR_ReportConfigId_t reportConfigId, NR_MeasObjectId_t measObjectId) +{ + NR_MeasIdToAddMod_t *measid = calloc_or_fail(1, sizeof(NR_MeasIdToAddMod_t)); + measid->measId = measId; + measid->reportConfigId = reportConfigId; + measid->measObjectId = measObjectId; + return measid; +} + NR_MeasConfig_t *get_MeasConfig(const NR_MeasTiming_t *mt, int band, int scs, @@ -964,15 +1000,15 @@ NR_MeasConfig_t *get_MeasConfig(const NR_MeasTiming_t *mt, seq_arr_t *rc_A3_seq, seq_arr_t *pci_seq) { - NR_MeasConfig_t *mc = calloc(1, sizeof(*mc)); - mc->measObjectToAddModList = calloc(1, sizeof(*mc->measObjectToAddModList)); - mc->reportConfigToAddModList = calloc(1, sizeof(*mc->reportConfigToAddModList)); - mc->measIdToAddModList = calloc(1, sizeof(*mc->measIdToAddModList)); + NR_MeasConfig_t *mc = calloc_or_fail(1, sizeof(*mc)); + mc->measObjectToAddModList = calloc_or_fail(1, sizeof(*mc->measObjectToAddModList)); + mc->reportConfigToAddModList = calloc_or_fail(1, sizeof(*mc->reportConfigToAddModList)); + mc->measIdToAddModList = calloc_or_fail(1, sizeof(*mc->measIdToAddModList)); if (rc_PER) asn1cSeqAdd(&mc->reportConfigToAddModList->list, rc_PER); - if (rc_A2) + if (rc_A2) asn1cSeqAdd(&mc->reportConfigToAddModList->list, rc_A2); if (rc_A3_seq) { @@ -984,29 +1020,13 @@ NR_MeasConfig_t *get_MeasConfig(const NR_MeasTiming_t *mt, DevAssert(mt != NULL && mt->frequencyAndTiming != NULL); const struct NR_MeasTiming__frequencyAndTiming *ft = mt->frequencyAndTiming; - const NR_SSB_MTC_t *ssb_mtc = &ft->ssb_MeasurementTimingConfiguration; // Measurement Objects: Specifies what is to be measured. For NR and inter-RAT E-UTRA measurements, this may include // cell-specific offsets, blacklisted cells to be ignored and whitelisted cells to consider for measurements. - NR_MeasObjectToAddMod_t *mo1 = calloc(1, sizeof(*mo1)); - mo1->measObjectId = 1; - mo1->measObject.present = NR_MeasObjectToAddMod__measObject_PR_measObjectNR; - NR_MeasObjectNR_t *monr1 = calloc(1, sizeof(*monr1)); - asn1cCallocOne(monr1->ssbFrequency, ft->carrierFreq); - asn1cCallocOne(monr1->ssbSubcarrierSpacing, ft->ssbSubcarrierSpacing); - monr1->referenceSignalConfig.ssb_ConfigMobility = calloc(1, sizeof(*monr1->referenceSignalConfig.ssb_ConfigMobility)); - monr1->referenceSignalConfig.ssb_ConfigMobility->deriveSSB_IndexFromCell = true; - monr1->absThreshSS_BlocksConsolidation = calloc(1, sizeof(*monr1->absThreshSS_BlocksConsolidation)); - asn1cCallocOne(monr1->absThreshSS_BlocksConsolidation->thresholdRSRP, 36); - asn1cCallocOne(monr1->nrofSS_BlocksToAverage, 8); - monr1->smtc1 = calloc(1, sizeof(*monr1->smtc1)); - monr1->smtc1->periodicityAndOffset = ssb_mtc->periodicityAndOffset; - monr1->smtc1->duration = ssb_mtc->duration; - monr1->quantityConfigIndex = 1; - monr1->ext1 = calloc(1, sizeof(*monr1->ext1)); - asn1cCallocOne(monr1->ext1->freqBandIndicatorNR, band); + NR_MeasObjectToAddMod_t *mo1 = get_MeasObject(ft, band, ft->carrierFreq, 1); if (pci_seq) { + NR_MeasObjectNR_t *monr1 = mo1->measObject.choice.measObjectNR; for (int i = 0; i < pci_seq->size; i++) { if (monr1->cellsToAddModList == NULL) { monr1->cellsToAddModList = calloc(1, sizeof(*monr1->cellsToAddModList)); @@ -1019,22 +1039,18 @@ NR_MeasConfig_t *get_MeasConfig(const NR_MeasTiming_t *mt, } } - mo1->measObject.choice.measObjectNR = monr1; asn1cSeqAdd(&mc->measObjectToAddModList->list, mo1); // Preparation of measId for (uint8_t reportIdx = 0; reportIdx < mc->reportConfigToAddModList->list.count; reportIdx++) { const NR_ReportConfigId_t reportId = mc->reportConfigToAddModList->list.array[reportIdx]->reportConfigId; - NR_MeasIdToAddMod_t *measid = calloc(1, sizeof(NR_MeasIdToAddMod_t)); - measid->measId = reportIdx + 1; - measid->reportConfigId = reportId; - measid->measObjectId = 1; + NR_MeasIdToAddMod_t *measid = get_MeasId(reportIdx + 1, reportId, 1); asn1cSeqAdd(&mc->measIdToAddModList->list, measid); } - mc->quantityConfig = calloc(1, sizeof(*mc->quantityConfig)); - mc->quantityConfig->quantityConfigNR_List = calloc(1, sizeof(*mc->quantityConfig->quantityConfigNR_List)); - NR_QuantityConfigNR_t *qcnr = calloc(1, sizeof(*qcnr)); + mc->quantityConfig = calloc_or_fail(1, sizeof(*mc->quantityConfig)); + mc->quantityConfig->quantityConfigNR_List = calloc_or_fail(1, sizeof(*mc->quantityConfig->quantityConfigNR_List)); + NR_QuantityConfigNR_t *qcnr = calloc_or_fail(1, sizeof(*qcnr)); asn1cCallocOne(qcnr->quantityConfigCell.ssb_FilterConfig.filterCoefficientRSRP, NR_FilterCoefficient_fc6); asn1cCallocOne(qcnr->quantityConfigCell.csi_RS_FilterConfig.filterCoefficientRSRP, NR_FilterCoefficient_fc6); asn1cSeqAdd(&mc->quantityConfig->quantityConfigNR_List->list, qcnr); From 5e0307d8452761082a0ef93b5828e1535c688a05 Mon Sep 17 00:00:00 2001 From: rmagueta Date: Thu, 10 Jul 2025 10:12:34 +0200 Subject: [PATCH 08/12] Refactor code, add inter-freq neighbors, avoid mem leak Previously, we only supported intra-frequency handovers, so the measurements where only for intra-frequency, reusing the same measurement object. Now that we would support inter-frequency handover, also add multiple MeasObjects for each neighbor. --- openair2/RRC/NR/MESSAGES/asn1_msg.c | 40 +++++++++++++++++++---------- openair2/RRC/NR/MESSAGES/asn1_msg.h | 2 +- openair2/RRC/NR/rrc_gNB.c | 8 +----- 3 files changed, 29 insertions(+), 21 deletions(-) diff --git a/openair2/RRC/NR/MESSAGES/asn1_msg.c b/openair2/RRC/NR/MESSAGES/asn1_msg.c index 184b8d5888..b3397ad518 100644 --- a/openair2/RRC/NR/MESSAGES/asn1_msg.c +++ b/openair2/RRC/NR/MESSAGES/asn1_msg.c @@ -998,7 +998,7 @@ NR_MeasConfig_t *get_MeasConfig(const NR_MeasTiming_t *mt, NR_ReportConfigToAddMod_t *rc_PER, NR_ReportConfigToAddMod_t *rc_A2, seq_arr_t *rc_A3_seq, - seq_arr_t *pci_seq) + seq_arr_t *neigh_seq) { NR_MeasConfig_t *mc = calloc_or_fail(1, sizeof(*mc)); mc->measObjectToAddModList = calloc_or_fail(1, sizeof(*mc->measObjectToAddModList)); @@ -1024,30 +1024,44 @@ NR_MeasConfig_t *get_MeasConfig(const NR_MeasTiming_t *mt, // Measurement Objects: Specifies what is to be measured. For NR and inter-RAT E-UTRA measurements, this may include // cell-specific offsets, blacklisted cells to be ignored and whitelisted cells to consider for measurements. NR_MeasObjectToAddMod_t *mo1 = get_MeasObject(ft, band, ft->carrierFreq, 1); - - if (pci_seq) { + if (neigh_seq) { NR_MeasObjectNR_t *monr1 = mo1->measObject.choice.measObjectNR; - for (int i = 0; i < pci_seq->size; i++) { - if (monr1->cellsToAddModList == NULL) { - monr1->cellsToAddModList = calloc(1, sizeof(*monr1->cellsToAddModList)); - } - - NR_CellsToAddMod_t *cell = calloc(1, sizeof(*cell)); - int *pci = (int *)seq_arr_at(pci_seq, i); - cell->physCellId = *pci; + if (monr1->cellsToAddModList == NULL) + monr1->cellsToAddModList = calloc_or_fail(1, sizeof(*monr1->cellsToAddModList)); + FOR_EACH_SEQ_ARR(nr_neighbour_cell_t *, n, neigh_seq) { + NR_CellsToAddMod_t *cell = calloc_or_fail(1, sizeof(*cell)); + cell->physCellId = n->physicalCellId; ASN_SEQUENCE_ADD(&monr1->cellsToAddModList->list, cell); } } - asn1cSeqAdd(&mc->measObjectToAddModList->list, mo1); // Preparation of measId - for (uint8_t reportIdx = 0; reportIdx < mc->reportConfigToAddModList->list.count; reportIdx++) { + uint8_t reportIdx = 0; + for (; reportIdx < mc->reportConfigToAddModList->list.count; reportIdx++) { const NR_ReportConfigId_t reportId = mc->reportConfigToAddModList->list.array[reportIdx]->reportConfigId; NR_MeasIdToAddMod_t *measid = get_MeasId(reportIdx + 1, reportId, 1); asn1cSeqAdd(&mc->measIdToAddModList->list, measid); } + // Preparation of measId for neighbour cells for periodic report + if (neigh_seq) { + int mo_id = 2; + FOR_EACH_SEQ_ARR(nr_neighbour_cell_t *, neigh_cell, neigh_seq) { + NR_MeasObjectToAddMod_t *mo_neighbour = get_MeasObject(ft, band, neigh_cell->absoluteFrequencySSB, mo_id); + NR_MeasObjectNR_t *monr = mo_neighbour->measObject.choice.measObjectNR; + monr->cellsToAddModList = calloc_or_fail(1, sizeof(*monr->cellsToAddModList)); + NR_CellsToAddMod_t *cell = calloc_or_fail(1, sizeof(*cell)); + cell->physCellId = neigh_cell->physicalCellId; + ASN_SEQUENCE_ADD(&monr->cellsToAddModList->list, cell); + asn1cSeqAdd(&mc->measObjectToAddModList->list, mo_neighbour); + NR_MeasIdToAddMod_t *measid = get_MeasId(reportIdx + 1, rc_PER->reportConfigId, mo_id); + asn1cSeqAdd(&mc->measIdToAddModList->list, measid); + reportIdx++; + mo_id++; + } + } + mc->quantityConfig = calloc_or_fail(1, sizeof(*mc->quantityConfig)); mc->quantityConfig->quantityConfigNR_List = calloc_or_fail(1, sizeof(*mc->quantityConfig->quantityConfigNR_List)); NR_QuantityConfigNR_t *qcnr = calloc_or_fail(1, sizeof(*qcnr)); diff --git a/openair2/RRC/NR/MESSAGES/asn1_msg.h b/openair2/RRC/NR/MESSAGES/asn1_msg.h index 3ac4695eba..8918384090 100644 --- a/openair2/RRC/NR/MESSAGES/asn1_msg.h +++ b/openair2/RRC/NR/MESSAGES/asn1_msg.h @@ -151,7 +151,7 @@ NR_MeasConfig_t *get_MeasConfig(const NR_MeasTiming_t *mt, NR_ReportConfigToAddMod_t *rc_PER, NR_ReportConfigToAddMod_t *rc_A2, seq_arr_t *rc_A3_seq, - seq_arr_t *pci_seq); + seq_arr_t *neigh_seq); void free_MeasConfig(NR_MeasConfig_t *mc); int do_NR_Paging(uint8_t Mod_id, uint8_t *buffer, uint32_t tmsi); diff --git a/openair2/RRC/NR/rrc_gNB.c b/openair2/RRC/NR/rrc_gNB.c index a6ba6f8cda..eeb18e8698 100644 --- a/openair2/RRC/NR/rrc_gNB.c +++ b/openair2/RRC/NR/rrc_gNB.c @@ -568,7 +568,6 @@ nr_rrc_reconfig_param_t get_RRCReconfiguration_params(gNB_RRC_INST *rrc, gNB_RRC NR_ReportConfigToAddMod_t *rc_PER = NULL; NR_ReportConfigToAddMod_t *rc_A2 = NULL; seq_arr_t *rc_A3_seq = NULL; - seq_arr_t *pci_seq = NULL; if (du->mtc != NULL) { int scs = get_ssb_scs(cell_info); int band = get_dl_band(cell_info); @@ -587,16 +586,11 @@ nr_rrc_reconfig_param_t get_RRCReconfiguration_params(gNB_RRC_INST *rrc, gNB_RRC If default one added once as a report, no need to add it again && duplication. */ rc_A3_seq = malloc(sizeof(seq_arr_t)); - pci_seq = malloc(sizeof(seq_arr_t)); seq_arr_init(rc_A3_seq, sizeof(NR_ReportConfigToAddMod_t)); - seq_arr_init(pci_seq, sizeof(int)); LOG_D(NR_RRC, "HO LOG: Preparing A3 Event Measurement Configuration!\n"); bool is_default_a3_added = false; for (int i = 0; i < neighbour_cells->size; i++) { nr_neighbour_cell_t *neighbourCell = (nr_neighbour_cell_t *)seq_arr_at(neighbour_cells, i); - if (!neighbourCell->isIntraFrequencyNeighbour) - continue; - seq_arr_push_back(pci_seq, &neighbourCell->physicalCellId, sizeof(int)); const nr_a3_event_t *a3Event = get_a3_configuration(rrc, neighbourCell->physicalCellId); if (!a3Event || is_default_a3_added) continue; @@ -610,7 +604,7 @@ nr_rrc_reconfig_param_t get_RRCReconfiguration_params(gNB_RRC_INST *rrc, gNB_RRC if (rrc->measurementConfiguration.a2_event) rc_A2 = prepare_a2_event_report(rrc->measurementConfiguration.a2_event); - measconfig = get_MeasConfig(mt, band, scs, rc_PER, rc_A2, rc_A3_seq, pci_seq); + measconfig = get_MeasConfig(mt, band, scs, rc_PER, rc_A2, rc_A3_seq, neighbour_cells); } if (UE->measConfig) From d5b9f81285a7b97a6413391d965e1eba6bef7816 Mon Sep 17 00:00:00 2001 From: rmagueta Date: Thu, 10 Jul 2025 10:12:42 +0200 Subject: [PATCH 09/12] Send MeasConfig to DU, Receive MeasGapConfig from DU --- openair2/RRC/NR/MESSAGES/asn1_msg.c | 14 +++++++ openair2/RRC/NR/MESSAGES/asn1_msg.h | 5 +++ openair2/RRC/NR/rrc_gNB.c | 60 +++++++++++++++++++++-------- 3 files changed, 64 insertions(+), 15 deletions(-) diff --git a/openair2/RRC/NR/MESSAGES/asn1_msg.c b/openair2/RRC/NR/MESSAGES/asn1_msg.c index b3397ad518..c016e4c899 100644 --- a/openair2/RRC/NR/MESSAGES/asn1_msg.c +++ b/openair2/RRC/NR/MESSAGES/asn1_msg.c @@ -1152,3 +1152,17 @@ int do_NR_HandoverPreparationInformation(const uint8_t *uecap_buf, int uecap_buf ASN_STRUCT_FREE(asn_DEF_NR_HandoverPreparationInformation, hpi); return (enc_rval.encoded + 7) / 8; } + +int do_NR_MeasConfig(const NR_MeasConfig_t *measconfig, uint8_t *buf, int buf_size) +{ + asn_enc_rval_t enc_rval = uper_encode_to_buffer(&asn_DEF_NR_MeasConfig, NULL, measconfig, buf, buf_size); + AssertFatal(enc_rval.encoded > 0, "ASN1 message encoding failed (%s, %lu)!\n", enc_rval.failed_type->name, enc_rval.encoded); + return (enc_rval.encoded + 7) / 8; +} + +int do_NR_MeasurementTimingConfiguration(const NR_MeasurementTimingConfiguration_t *mtc, uint8_t *buf, int buf_size) +{ + asn_enc_rval_t enc_rval = uper_encode_to_buffer(&asn_DEF_NR_MeasurementTimingConfiguration, NULL, mtc, buf, buf_size); + AssertFatal(enc_rval.encoded > 0, "ASN1 message encoding failed (%s, %lu)!\n", enc_rval.failed_type->name, enc_rval.encoded); + return (enc_rval.encoded + 7) / 8; +} diff --git a/openair2/RRC/NR/MESSAGES/asn1_msg.h b/openair2/RRC/NR/MESSAGES/asn1_msg.h index 8918384090..432db0e5cd 100644 --- a/openair2/RRC/NR/MESSAGES/asn1_msg.h +++ b/openair2/RRC/NR/MESSAGES/asn1_msg.h @@ -50,6 +50,7 @@ #include "NR_ReestablishmentCause.h" #include "NR_SRB-ToAddModList.h" #include "NR_SecurityConfig.h" +#include "NR_MeasurementTimingConfiguration.h" #include "ds/seq_arr.h" #include "ds/byte_array.h" #include "rrc_messages_types.h" @@ -120,6 +121,10 @@ int do_RRCSetupComplete(uint8_t *buffer, int do_NR_HandoverPreparationInformation(const uint8_t *uecap_buf, int uecap_buf_size, uint8_t *buf, int buf_size); +int do_NR_MeasConfig(const NR_MeasConfig_t *measconfig, uint8_t *buf, int buf_size); + +int do_NR_MeasurementTimingConfiguration(const NR_MeasurementTimingConfiguration_t *mtc, uint8_t *buf, int buf_size); + int do_RRCSetupRequest(uint8_t *buffer, size_t buffer_size, uint8_t *rv, uint64_t fiveG_S_TMSI_part1); int do_NR_RRCReconfigurationComplete_for_nsa(uint8_t *buffer, size_t buffer_size, NR_RRC_TransactionIdentifier_t Transaction_id); diff --git a/openair2/RRC/NR/rrc_gNB.c b/openair2/RRC/NR/rrc_gNB.c index eeb18e8698..991b19af50 100644 --- a/openair2/RRC/NR/rrc_gNB.c +++ b/openair2/RRC/NR/rrc_gNB.c @@ -557,14 +557,11 @@ void free_RRCReconfiguration_params(nr_rrc_reconfig_param_t params) FREE_AND_ZERO_BYTE_ARRAY(params.dedicated_NAS_msg_list[i]); } -/** @brief Prepare the instance of RRCReconfigurationParams to be pass to RRC encoding */ -nr_rrc_reconfig_param_t get_RRCReconfiguration_params(gNB_RRC_INST *rrc, gNB_RRC_UE_t *UE, uint8_t srb_reest_bitmap, bool drb_reestablish) +static NR_MeasConfig_t *nr_rrc_get_measconfig(const gNB_RRC_INST *rrc, const gNB_RRC_UE_t *UE) { - uint8_t xid = rrc_gNB_get_next_transaction_identifier(rrc->module_id); - nr_rrc_du_container_t *du = get_du_for_ue(rrc, UE->rrc_ue_id); + nr_rrc_du_container_t *du = get_du_for_ue((gNB_RRC_INST *)rrc, UE->rrc_ue_id); DevAssert(du != NULL); f1ap_served_cell_info_t *cell_info = &du->setup_req->cell[0].info; - NR_MeasConfig_t *measconfig = NULL; NR_ReportConfigToAddMod_t *rc_PER = NULL; NR_ReportConfigToAddMod_t *rc_A2 = NULL; seq_arr_t *rc_A3_seq = NULL; @@ -578,9 +575,7 @@ nr_rrc_reconfig_param_t get_RRCReconfiguration_params(gNB_RRC_INST *rrc, gNB_RRC if (neighbour_config) neighbour_cells = neighbour_config->neighbour_cells; - if (neighbour_cells - && rrc->measurementConfiguration.a3_event_list - && rrc->measurementConfiguration.a3_event_list->size > 0) { + if (neighbour_cells && rrc->measurementConfiguration.a3_event_list && rrc->measurementConfiguration.a3_event_list->size > 0) { /* Loop through neighbours and find related A3 configuration If no related A3 but there is default add the default one. If default one added once as a report, no need to add it again && duplication. @@ -591,7 +586,7 @@ nr_rrc_reconfig_param_t get_RRCReconfiguration_params(gNB_RRC_INST *rrc, gNB_RRC bool is_default_a3_added = false; for (int i = 0; i < neighbour_cells->size; i++) { nr_neighbour_cell_t *neighbourCell = (nr_neighbour_cell_t *)seq_arr_at(neighbour_cells, i); - const nr_a3_event_t *a3Event = get_a3_configuration(rrc, neighbourCell->physicalCellId); + const nr_a3_event_t *a3Event = get_a3_configuration((gNB_RRC_INST *)rrc, neighbourCell->physicalCellId); if (!a3Event || is_default_a3_added) continue; if (a3Event->pci == -1) @@ -603,14 +598,15 @@ nr_rrc_reconfig_param_t get_RRCReconfiguration_params(gNB_RRC_INST *rrc, gNB_RRC rc_PER = prepare_periodic_event_report(rrc->measurementConfiguration.per_event); if (rrc->measurementConfiguration.a2_event) rc_A2 = prepare_a2_event_report(rrc->measurementConfiguration.a2_event); - - measconfig = get_MeasConfig(mt, band, scs, rc_PER, rc_A2, rc_A3_seq, neighbour_cells); + return get_MeasConfig(mt, band, scs, rc_PER, rc_A2, rc_A3_seq, neighbour_cells); } + return NULL; +} - if (UE->measConfig) - free_MeasConfig(UE->measConfig); - - UE->measConfig = measconfig; +/** @brief Prepare the instance of RRCReconfigurationParams to be pass to RRC encoding */ +nr_rrc_reconfig_param_t get_RRCReconfiguration_params(gNB_RRC_INST *rrc, gNB_RRC_UE_t *UE, uint8_t srb_reest_bitmap, bool drb_reestablish) +{ + uint8_t xid = rrc_gNB_get_next_transaction_identifier(rrc->module_id); // Re-establish PDCP for SRB2 only NR_SRB_ToAddModList_t *SRBs = createSRBlist(UE, srb_reest_bitmap); @@ -1155,6 +1151,7 @@ static void rrc_handle_RRCSetupRequest(gNB_RRC_INST *rrc, UE->nr_cellid = msg->nr_cellid; UE->masterCellGroup = cellGroupConfig; UE->ongoing_reconfiguration = false; + UE->measConfig = nr_rrc_get_measconfig(rrc, UE); activate_srb(UE, 1); rrc_gNB_generate_RRCSetup(0, msg->crnti, ue_context_p, msg->du2cu_rrc_container, msg->du2cu_rrc_container_length); } @@ -2091,6 +2088,17 @@ static void rrc_CU_process_ue_context_setup_response(MessageDef *msg_p, instance uper_decode_complete(NULL, &asn_DEF_NR_CellGroupConfig, (void **)&cellGroupConfig, (uint8_t *)cgc->buf, cgc->len); AssertFatal(dec_rval.code == RC_OK && dec_rval.consumed > 0, "Cell group config decode error\n"); + if (resp->du_to_cu_rrc_info.meas_gap_config) { + NR_MeasGapConfig_t *measGapConfig = NULL; + asn_dec_rval_t dec_rval_mgc = uper_decode_complete(NULL, + &asn_DEF_NR_MeasGapConfig, + (void **)&measGapConfig, + (uint8_t *)resp->du_to_cu_rrc_info.meas_gap_config->buf, + resp->du_to_cu_rrc_info.meas_gap_config->len); + AssertFatal(dec_rval_mgc.code == RC_OK && dec_rval_mgc.consumed > 0, "measGapConfig decode error\n"); + UE->measConfig->measGapConfig = measGapConfig; + } + if (UE->masterCellGroup) { ASN_STRUCT_FREE(asn_DEF_NR_CellGroupConfig, UE->masterCellGroup); LOG_I(RRC, "UE %04x replacing existing CellGroupConfig with new one received from DU\n", UE->rnti); @@ -3039,6 +3047,28 @@ void rrc_gNB_generate_UeContextSetupRequest(const gNB_RRC_INST *rrc, *cu2du.ue_cap = copy_byte_array(ue_p->ue_cap_buffer); } + nr_rrc_du_container_t *du = get_du_for_ue((gNB_RRC_INST *)rrc, ue_p->rrc_ue_id); + byte_array_t *meas_timing_config = NULL; + if (du->mtc && ue_p->measConfig && ue_p->measConfig->measObjectToAddModList) { + NR_MeasObjectToAddModList_t *mo_list = ue_p->measConfig->measObjectToAddModList; + NR_ARFCN_ValueNR_t ssbFrequency0; + for (int i = 0; i < mo_list->list.count; i++) { + NR_MeasObjectToAddMod_t *mo = mo_list->list.array[i]; + if (mo->measObject.present == NR_MeasObjectToAddMod__measObject_PR_measObjectNR) { + NR_MeasObjectNR_t *monr = mo->measObject.choice.measObjectNR; + if (i == 0) { + ssbFrequency0 = *monr->ssbFrequency; + } else if (ssbFrequency0 != *monr->ssbFrequency) { + meas_timing_config = calloc_or_fail(1, sizeof(*meas_timing_config)); + meas_timing_config->buf = calloc_or_fail(1, NR_RRC_BUF_SIZE); + meas_timing_config->len = do_NR_MeasurementTimingConfiguration(du->mtc, meas_timing_config->buf, NR_RRC_BUF_SIZE); + cu2du.meas_timing_config = meas_timing_config; + break; + } + } + } + } + int nb_srb = 1; f1ap_srb_to_setup_t *srbs = calloc_or_fail(nb_srb, sizeof(*srbs)); srbs[0].id = 2; From 779d2bee5255e37d1a2f81e40a9cb8ed897398ce Mon Sep 17 00:00:00 2001 From: rmagueta Date: Mon, 14 Apr 2025 18:35:22 +0100 Subject: [PATCH 10/12] Get new measConfig and measurementTimingConfiguration for target gNB-DU during handover --- openair2/RRC/NR/nr_rrc_proto.h | 2 ++ openair2/RRC/NR/rrc_gNB.c | 6 +++--- openair2/RRC/NR/rrc_gNB_mobility.c | 15 +++++++++++++++ 3 files changed, 20 insertions(+), 3 deletions(-) diff --git a/openair2/RRC/NR/nr_rrc_proto.h b/openair2/RRC/NR/nr_rrc_proto.h index 094f1791c1..0d675ff525 100644 --- a/openair2/RRC/NR/nr_rrc_proto.h +++ b/openair2/RRC/NR/nr_rrc_proto.h @@ -72,6 +72,8 @@ void rrc_gNB_generate_dedicatedRRCReconfiguration_release(gNB_RRC_INST *rrc, uint32_t nas_length, uint8_t *nas_buffer); +NR_MeasConfig_t *nr_rrc_get_measconfig(const gNB_RRC_INST *rrc, uint64_t nr_cellid); + bool ue_associated_to_cuup(const gNB_RRC_INST *rrc, const gNB_RRC_UE_t *ue); sctp_assoc_t get_existing_cuup_for_ue(const gNB_RRC_INST *rrc, const gNB_RRC_UE_t *ue); sctp_assoc_t get_new_cuup_for_ue(const gNB_RRC_INST *rrc, const gNB_RRC_UE_t *ue, int sst, int sd); diff --git a/openair2/RRC/NR/rrc_gNB.c b/openair2/RRC/NR/rrc_gNB.c index 991b19af50..8172af28b9 100644 --- a/openair2/RRC/NR/rrc_gNB.c +++ b/openair2/RRC/NR/rrc_gNB.c @@ -557,9 +557,9 @@ void free_RRCReconfiguration_params(nr_rrc_reconfig_param_t params) FREE_AND_ZERO_BYTE_ARRAY(params.dedicated_NAS_msg_list[i]); } -static NR_MeasConfig_t *nr_rrc_get_measconfig(const gNB_RRC_INST *rrc, const gNB_RRC_UE_t *UE) +NR_MeasConfig_t *nr_rrc_get_measconfig(const gNB_RRC_INST *rrc, uint64_t nr_cellid) { - nr_rrc_du_container_t *du = get_du_for_ue((gNB_RRC_INST *)rrc, UE->rrc_ue_id); + nr_rrc_du_container_t *du = get_du_by_cell_id((gNB_RRC_INST *)rrc, nr_cellid); DevAssert(du != NULL); f1ap_served_cell_info_t *cell_info = &du->setup_req->cell[0].info; NR_ReportConfigToAddMod_t *rc_PER = NULL; @@ -1151,7 +1151,7 @@ static void rrc_handle_RRCSetupRequest(gNB_RRC_INST *rrc, UE->nr_cellid = msg->nr_cellid; UE->masterCellGroup = cellGroupConfig; UE->ongoing_reconfiguration = false; - UE->measConfig = nr_rrc_get_measconfig(rrc, UE); + UE->measConfig = nr_rrc_get_measconfig(rrc, UE->nr_cellid); activate_srb(UE, 1); rrc_gNB_generate_RRCSetup(0, msg->crnti, ue_context_p, msg->du2cu_rrc_container, msg->du2cu_rrc_container_length); } diff --git a/openair2/RRC/NR/rrc_gNB_mobility.c b/openair2/RRC/NR/rrc_gNB_mobility.c index 85c9597371..fe3214da2d 100644 --- a/openair2/RRC/NR/rrc_gNB_mobility.c +++ b/openair2/RRC/NR/rrc_gNB_mobility.c @@ -181,6 +181,19 @@ static void nr_initiate_handover(const gNB_RRC_INST *rrc, srbs[0].id = 1; srbs[1].id = 2; + free_MeasConfig(ue->measConfig); + ue->measConfig = nr_rrc_get_measconfig(rrc, target_du->setup_req->cell[0].info.nr_cellid); + byte_array_t *meas_config = calloc_or_fail(1, sizeof(*meas_config)); + meas_config->buf = calloc_or_fail(1, NR_RRC_BUF_SIZE); + meas_config->len = do_NR_MeasConfig(ue->measConfig, meas_config->buf, NR_RRC_BUF_SIZE); + + byte_array_t *meas_timing_config = NULL; + if (target_du->mtc) { + meas_timing_config = calloc_or_fail(1, sizeof(*meas_timing_config)); + meas_timing_config->buf = calloc_or_fail(1, NR_RRC_BUF_SIZE); + meas_timing_config->len = do_NR_MeasurementTimingConfiguration(target_du->mtc, meas_timing_config->buf, NR_RRC_BUF_SIZE); + } + byte_array_t *hpi = malloc_or_fail(sizeof(*hpi)); *hpi = copy_byte_array(*ho_prep_info); @@ -201,6 +214,8 @@ static void nr_initiate_handover(const gNB_RRC_INST *rrc, .drbs_len = nb_drb, .drbs = drbs, .cu_to_du_rrc_info.ho_prep_info = hpi, + .cu_to_du_rrc_info.meas_config = meas_config, + .cu_to_du_rrc_info.meas_timing_config = meas_timing_config, .gnb_du_ue_agg_mbr_ul = ue_agg_mbr, }; rrc->mac_rrc.ue_context_setup_request(target_du->assoc_id, &ue_context_setup_req); From b443ec4c52993c95a740752edd83d5df4427897d Mon Sep 17 00:00:00 2001 From: rmagueta Date: Wed, 16 Apr 2025 12:53:17 +0100 Subject: [PATCH 11/12] Get measurement of neighboring cells and measurement gaps working after RRCReestablishment --- openair2/LAYER2/NR_MAC_gNB/mac_rrc_dl_handler.c | 1 + openair2/RRC/NR/rrc_gNB.c | 1 + 2 files changed, 2 insertions(+) diff --git a/openair2/LAYER2/NR_MAC_gNB/mac_rrc_dl_handler.c b/openair2/LAYER2/NR_MAC_gNB/mac_rrc_dl_handler.c index 43b73705e9..fdbd886ad9 100644 --- a/openair2/LAYER2/NR_MAC_gNB/mac_rrc_dl_handler.c +++ b/openair2/LAYER2/NR_MAC_gNB/mac_rrc_dl_handler.c @@ -961,6 +961,7 @@ void dl_rrc_message_transfer(const f1ap_dl_rrc_message_t *dl_rrc) UE->CellGroup = oldUE->CellGroup; oldUE->CellGroup = NULL; UE->mac_stats = oldUE->mac_stats; + UE->measgap_config = oldUE->measgap_config; /* 38.331 5.3.7.2 says that the UE releases the spCellConfig, so we drop it * from the current configuration. It will be reapplied when the * reconfiguration has succeeded (indicated by the CU) */ diff --git a/openair2/RRC/NR/rrc_gNB.c b/openair2/RRC/NR/rrc_gNB.c index 8172af28b9..81f806a204 100644 --- a/openair2/RRC/NR/rrc_gNB.c +++ b/openair2/RRC/NR/rrc_gNB.c @@ -1032,6 +1032,7 @@ static void rrc_gNB_process_RRCReestablishmentComplete(gNB_RRC_INST *rrc, gNB_RR uint8_t new_xid = rrc_gNB_get_next_transaction_identifier(rrc->module_id); ue_p->xids[new_xid] = RRC_REESTABLISH_COMPLETE; nr_rrc_reconfig_param_t params = {.cell_group_config = cellGroupConfig, + .meas_config = ue_p->measConfig, .drb_config_list = DRBs, .srb_config_list = SRBs, .transaction_id = new_xid}; From c3f243ca829b964b5df45da801b66edf0f5b1df3 Mon Sep 17 00:00:00 2001 From: rmagueta Date: Wed, 25 Jun 2025 15:03:55 +0100 Subject: [PATCH 12/12] Inter-frequency handover documentation update --- doc/FEATURE_SET.md | 4 ++++ doc/RRC/rrc-dev.md | 4 ++-- doc/handover-tutorial.md | 2 +- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/doc/FEATURE_SET.md b/doc/FEATURE_SET.md index 3c7490d937..9faf2c3d3b 100644 --- a/doc/FEATURE_SET.md +++ b/doc/FEATURE_SET.md @@ -139,6 +139,10 @@ These modes of operation are supported: - evaluation of CQI report - MAC scheduling of SR reception - Intra-frequency handover +- Inter-frequency handover + - Measurement gaps are automatically computed at the DU if the CU has neighbor information and the configured + neighbors include cells operating on different frequencies + - DUs must be synchronized with each other for the measurements to be properly performed - Initial support for RedCap - Scheduling of SIBs (2, 19) diff --git a/doc/RRC/rrc-dev.md b/doc/RRC/rrc-dev.md index d511421b01..e6eb3f97e0 100644 --- a/doc/RRC/rrc-dev.md +++ b/doc/RRC/rrc-dev.md @@ -291,7 +291,7 @@ Note that while 38.331 requires only one RRC transaction to happen at a time, the 5G RRC does not actually ensure this; it only tries to prevent it by chaining of transactions and some checks. For instance, the Security Mode procedure is followed by a UE capability procedure, and some procedures are not -executed if others are ongoing (ex.: handover if there is reconfiguratio). +executed if others are ongoing (ex.: handover if there is reconfiguration). However, it might be possible to trigger a procedure while another is ongoing. As of now, no queueing mechanims exists to ensure only one operation is @@ -303,7 +303,7 @@ Handover-related data is stored in a per-UE structure of type `nr_handover_context_t`. It is a pointer and only set during handover operation. This data structure has in turn two pointers, one to the source CU (`nr_ho_source_cu_t`), and one to the target CU (`nr_ho_target_cu_t`). In F1, -both are present. In N2 and Xn, only one pointer is supposed to be set a the +both are present. In N2 and Xn, only one pointer is supposed to be set at the corresponding CU. `nr_ho_source_cu_t` contains notably a function pointer `ho_cancel` for diff --git a/doc/handover-tutorial.md b/doc/handover-tutorial.md index 5fe08be665..87a45ed1e5 100644 --- a/doc/handover-tutorial.md +++ b/doc/handover-tutorial.md @@ -123,7 +123,7 @@ You can do handover across DUs with a COTS UE. Note that these DUs should be separated by at least multiple meters to ensure that the UE will receive different signal strengths when moving between cells. -We only support intra-frequency handovers yet. We have verified with USRPs +We support both intra-frequency and inter-frequency handovers. We have verified with USRPs only, although other radios should work as well. For UEs, we verified Quectel modules and iPhones. Note, though, that not all