diff --git a/common/utils/telnetsrv/telnetsrv_ci.c b/common/utils/telnetsrv/telnetsrv_ci.c index a2bcccd150..084e3b93f3 100644 --- a/common/utils/telnetsrv/telnetsrv_ci.c +++ b/common/utils/telnetsrv/telnetsrv_ci.c @@ -168,7 +168,7 @@ int fetch_du_by_ue_id(char *buf, int debug, telnet_printfunc_t prnt) nr_rrc_du_container_t *du = get_du_for_ue(RC.nrrrc[0], ue_id); if (du) { - prnt("gNB_DU_id %d is connected to ue_id %ld\n", du->setup_req->gNB_DU_id, ue_id); + prnt("gNB_DU_id %ld is connected to ue_id %ld\n", du->gNB_DU_id, ue_id); return 0; } else { ERROR_MSG_RET("No DU connected\n"); diff --git a/openair2/GNB_APP/gnb_config.c b/openair2/GNB_APP/gnb_config.c index 8edec8e84f..338f9ad8a5 100644 --- a/openair2/GNB_APP/gnb_config.c +++ b/openair2/GNB_APP/gnb_config.c @@ -2077,8 +2077,6 @@ gNB_RRC_INST *RCconfig_NRRRC() rrc->eth_params_s.transp_preference = ETH_UDP_MODE; } - rrc->nr_cellid = (uint64_t)*(GNBParamList.paramarray[i][GNB_NRCELLID_IDX].u64ptr); - if (strcmp(*(GNBParamList.paramarray[i][GNB_TRANSPORT_S_PREFERENCE_IDX].strptr), "local_mac") == 0) { } else if (strcmp(*(GNBParamList.paramarray[i][GNB_TRANSPORT_S_PREFERENCE_IDX].strptr), "cudu") == 0) { diff --git a/openair2/RRC/NR/nr_rrc_defs.h b/openair2/RRC/NR/nr_rrc_defs.h index 58f9ffd1fb..cf834d2c67 100644 --- a/openair2/RRC/NR/nr_rrc_defs.h +++ b/openair2/RRC/NR/nr_rrc_defs.h @@ -222,7 +222,6 @@ typedef struct gNB_RRC_UE_s { uint64_t ng_5G_S_TMSI_Part1; NR_EstablishmentCause_t establishment_cause; - uint64_t nr_cellid; /* Dynamic array of UE serving cells */ seq_arr_t serving_cells; /* ue_serving_cell_t */ @@ -409,11 +408,6 @@ typedef struct nr_rrc_du_container_t { char *gNB_DU_name; /* RRC version */ uint8_t rrc_ver[3]; - // Cell-specific information (F1AP Setup Request, MIB, SIB1, MeasurementTimingConfiguration) - f1ap_setup_req_t *setup_req; - NR_MIB_t *mib; - NR_SIB1_t *sib1; - NR_MeasurementTimingConfiguration_t *mtc; /* Cells, indexed by cell_id */ seq_arr_t cells; /* nr_rrc_cell_container_t* */ } nr_rrc_du_container_t; @@ -436,8 +430,6 @@ typedef struct gNB_RRC_INST_s { eth_params_t eth_params_s; uid_allocator_t uid_allocator; RB_HEAD(rrc_nr_ue_tree_s, rrc_gNB_ue_context_s) rrc_ue_head; // ue_context tree key search by rnti - /// NR cell id - uint64_t nr_cellid; // RRC configuration gNB_RrcConfigurationReq configuration; @@ -471,8 +463,12 @@ typedef struct gNB_RRC_INST_s { nr_rlc_configuration_t rlc_config; } gNB_RRC_INST; +/** Forward declaration for UE log macros */ +const ue_serving_cell_t *ue_get_pcell_entry(const gNB_RRC_UE_t *ue); + #define UE_LOG_FMT "(cellID %lx, UE ID %d RNTI %04x)" -#define UE_LOG_ARGS(ue_context) (ue_context)->nr_cellid, (ue_context)->rrc_ue_id, (ue_context)->rnti +#define UE_LOG_ARGS(ue_context) \ + (ue_get_pcell_entry(ue_context) ? ue_get_pcell_entry(ue_context)->nci : 0), (ue_context)->rrc_ue_id, (ue_context)->rnti #define LOG_UE_DL_EVENT(ue_context, fmt, ...) LOG_A(NR_RRC, "[DL] " UE_LOG_FMT " " fmt, UE_LOG_ARGS(ue_context) __VA_OPT__(,) __VA_ARGS__) #define LOG_UE_EVENT(ue_context, fmt, ...) LOG_A(NR_RRC, "[--] " UE_LOG_FMT " " fmt, UE_LOG_ARGS(ue_context) __VA_OPT__(,) __VA_ARGS__) diff --git a/openair2/RRC/NR/nr_rrc_proto.h b/openair2/RRC/NR/nr_rrc_proto.h index 1db6a4d236..5838e01b47 100644 --- a/openair2/RRC/NR/nr_rrc_proto.h +++ b/openair2/RRC/NR/nr_rrc_proto.h @@ -127,11 +127,11 @@ void init_delayed_action(delayed_action_state_t *delayed_action); /** @brief Prepare and send F1AP UE Context Setup Request for a target DU * @param rrc RRC instance * @param ue UE context - * @param du Target DU container containing cell and setup information + * @param cell Target cell container containing cell information * @param ho_prep_info Optional handover preparation information (NULL if not provided) */ void rrc_f1_ue_context_setup_for_target_du(const gNB_RRC_INST *rrc, gNB_RRC_UE_t *ue, - const nr_rrc_du_container_t *du, + const nr_rrc_cell_container_t *cell, const byte_array_t *ho_prep_info); #endif diff --git a/openair2/RRC/NR/rrc_gNB.c b/openair2/RRC/NR/rrc_gNB.c index 0dab76eae2..7a85c11c3c 100644 --- a/openair2/RRC/NR/rrc_gNB.c +++ b/openair2/RRC/NR/rrc_gNB.c @@ -82,6 +82,7 @@ #include "openair2/F1AP/lib/f1ap_ue_context.h" #include "rrc_gNB_NGAP.h" #include "rrc_gNB_du.h" +#include "rrc_cell_management.h" #include "rrc_gNB_mobility.h" #include "rrc_gNB_radio_bearers.h" #include "rrc_cell_management.h" @@ -533,16 +534,18 @@ void activate_srb(gNB_RRC_UE_t *UE, int srb_id) * This helper function initializes common fields that are shared between * different UE context setup scenarios (serving DU, target DU). * @param ue UE context - * @param du DU container for measurement timing configuration + * @param cell (primary serving) cell container * @param gNB_DU_ue_id Optional gNB-DU UE ID to include in the request (NULL if not provided) * @return F1AP UE Context Setup Request */ static f1ap_ue_context_setup_req_t rrc_fill_f1_ue_context_setup(gNB_RRC_UE_t *ue, - const nr_rrc_du_container_t *du, + const nr_rrc_cell_container_t *cell, const uint32_t *gNB_DU_ue_id) { f1ap_ue_context_setup_req_t req = { .gNB_CU_ue_id = ue->rrc_ue_id, - .servCellIndex = 0, + .servCellIndex = RRC_PCELL_INDEX, + .plmn = cell->info.plmn, + .nr_cellid = cell->info.cell_id, }; /* UE Aggregate Maximum Bit Rate Uplink is C-ifDRBSetup: 1 Gbps */ @@ -550,7 +553,7 @@ static f1ap_ue_context_setup_req_t rrc_fill_f1_ue_context_setup(gNB_RRC_UE_t *ue *req.gnb_du_ue_agg_mbr_ul = 1000000000; /* bps */ /* Measurement timing configuration */ - req.cu_to_du_rrc_info.meas_timing_config = get_meas_timing_config(du->mtc, ue->measConfig); + req.cu_to_du_rrc_info.meas_timing_config = get_meas_timing_config(cell->mtc, ue->measConfig); /* UE capabilities */ if (ue->ue_cap_buffer.len > 0) { @@ -564,13 +567,6 @@ static f1ap_ue_context_setup_req_t rrc_fill_f1_ue_context_setup(gNB_RRC_UE_t *ue *req.gNB_DU_ue_id = *gNB_DU_ue_id; } - /* PLMN and cell ID from DU setup request */ - const f1ap_served_cell_info_t *cell_info = &du->setup_req->cell[0].info; - req.plmn.mcc = cell_info->plmn.mcc; - req.plmn.mnc = cell_info->plmn.mnc; - req.plmn.mnc_digit_length = cell_info->plmn.mnc_digit_length; - req.nr_cellid = cell_info->nr_cellid; - return req; } @@ -772,22 +768,24 @@ void free_RRCReconfiguration_params(nr_rrc_reconfig_param_t params) NR_MeasConfig_t *nr_rrc_get_measconfig(const gNB_RRC_INST *rrc, uint64_t nr_cellid) { - 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_rrc_cell_container_t *cell = get_cell_by_cell_id(&((gNB_RRC_INST *)rrc)->cells, nr_cellid); + DevAssert(cell != NULL); - if (du->mtc != NULL) { + if (cell->mtc != NULL) { NR_ReportConfigToAddMod_t *rc_PER = NULL; NR_ReportConfigToAddMod_t *rc_A2 = NULL; seq_arr_t rc_A3_seq = {0}; seq_arr_t neigh_seq = {0}; seq_arr_init(&rc_A3_seq, sizeof(NR_ReportConfigToAddMod_t)); seq_arr_init(&neigh_seq, sizeof(nr_neighbour_cell_t)); - int scs = get_ssb_scs(cell_info); - int band = get_dl_band(cell_info); - const NR_MeasTimingList_t *mtlist = du->mtc->criticalExtensions.choice.c1->choice.measTimingConf->measTiming; + + // Get cell info from the cell container + int scs = cell->info.mode == NR_MODE_TDD ? cell->info.tdd.dlul.scs : cell->info.fdd.dl.scs; + int band = cell->info.mode == NR_MODE_TDD ? cell->info.tdd.dlul.band : cell->info.fdd.dl.band; + + const NR_MeasTimingList_t *mtlist = cell->mtc->criticalExtensions.choice.c1->choice.measTimingConf->measTiming; const NR_MeasTiming_t *mt = mtlist->list.array[0]; - const neighbour_cell_configuration_t *neighbour_config = get_neighbour_cell_config(rrc, cell_info->nr_cellid); + const neighbour_cell_configuration_t *neighbour_config = get_neighbour_cell_config(rrc, cell->info.cell_id); seq_arr_t *neighbour_cells = NULL; if (neighbour_config) neighbour_cells = neighbour_config->neighbour_cells; @@ -837,7 +835,7 @@ NR_MeasConfig_t *nr_rrc_get_measconfig(const gNB_RRC_INST *rrc, uint64_t nr_cell if (rrc->measurementConfiguration.a2_event) rc_A2 = prepare_a2_event_report(rrc->measurementConfiguration.a2_event); - NR_MeasConfig_t *result = get_MeasConfig(mt, band, scs, cell_info->nr_pci, rc_PER, rc_A2, &rc_A3_seq, &neigh_seq, neigh_a3_id); + NR_MeasConfig_t *result = get_MeasConfig(mt, band, scs, cell->info.pci, rc_PER, rc_A2, &rc_A3_seq, &neigh_seq, neigh_a3_id); // Clean up sequence arrays seq_arr_free(&rc_A3_seq, NULL); @@ -1060,12 +1058,14 @@ static void rrc_gNB_send_f1_drb_release_request(gNB_RRC_INST *rrc, gNB_RRC_UE_t f1ap_ue_context_mod_req_t req = { .gNB_CU_ue_id = ue_p->rrc_ue_id, .gNB_DU_ue_id = ue_data.secondary_ue, - .servCellIndex = 0, + .servCellIndex = RRC_PCELL_INDEX, }; req.plmn = malloc_or_fail(sizeof(*req.plmn)); *req.plmn = rrc->configuration.plmn[0]; + nr_rrc_cell_container_t *cell = rrc_get_pcell_for_ue(rrc, ue_p); + DevAssert(cell != NULL); req.nr_cellid = malloc_or_fail(sizeof(*req.nr_cellid)); - *req.nr_cellid = rrc->nr_cellid; + *req.nr_cellid = cell->info.cell_id; req.drbs_rel = malloc_or_fail(sizeof(*req.drbs_rel)); req.drbs_rel_len = n_drb_to_release; memcpy(req.drbs_rel, drb_to_release, n_drb_to_release * sizeof(int)); @@ -1192,7 +1192,7 @@ static void cuup_notify_reestablishment(gNB_RRC_INST *rrc, gNB_RRC_UE_t *ue_p) * @param du DU container containing cell information for key derivation */ static void rrc_gNB_generate_RRCReestablishment(rrc_gNB_ue_context_t *ue_context_pP, const rnti_t old_rnti, - const nr_rrc_du_container_t *du) + const nr_rrc_cell_container_t *cell) { module_id_t module_id = 0; gNB_RRC_INST *rrc = RC.nrrrc[module_id]; @@ -1200,16 +1200,19 @@ static void rrc_gNB_generate_RRCReestablishment(rrc_gNB_ue_context_t *ue_context uint8_t buffer[NR_RRC_BUF_SIZE] = {0}; uint8_t xid = rrc_gNB_get_next_transaction_identifier(module_id); ue_p->xids[xid] = RRC_REESTABLISH; - const f1ap_served_cell_info_t *cell_info = &du->setup_req->cell[0].info; - uint32_t ssb_arfcn = get_ssb_arfcn(du); - LOG_I(NR_RRC, "Reestablishment update key pci=%d, earfcn_dl=%u\n", cell_info->nr_pci, ssb_arfcn); + + nr_rrc_cell_container_t *pcell = rrc_get_pcell_for_ue(rrc, ue_p); + DevAssert(pcell != NULL); + DevAssert(pcell->info.cell_id == cell->info.cell_id); + uint32_t ssb_arfcn = get_ssb_arfcn(pcell); + LOG_I(NR_RRC, "Reestablishment update key pci=%d, earfcn_dl=%u\n", pcell->info.pci, ssb_arfcn); /* Per TS 33.501 6.9.2.3.4: When sending RRCReestablishment with nextHopChainingCount, * we're staying at the same NCC level (not advancing), so use horizontal derivation * from the currently active KgNB. Vertical derivation (from NH) is only used when * advancing to a new NCC level, which happens during handover or masterKeyUpdate. */ const uint8_t *base_key = ue_p->kgnb; - nr_derive_key_ng_ran_star(cell_info->nr_pci, ssb_arfcn, base_key, ue_p->kgnb); + nr_derive_key_ng_ran_star(pcell->info.pci, ssb_arfcn, base_key, ue_p->kgnb); int size = do_RRCReestablishment(ue_context_pP->ue_context.nh_ncc, buffer, NR_RRC_BUF_SIZE, xid); LOG_A(NR_RRC, "Send RRCReestablishment [%d bytes] to RNTI %04x\n", size, ue_p->rnti); @@ -1284,13 +1287,13 @@ static void rrc_gNB_process_RRCReestablishmentComplete(gNB_RRC_INST *rrc, /** Handle UE access on a different DU than the original one * Per 38.401 8.7: "If the UE accessed from a gNB-DU other than the original * one, the gNB-CU should trigger the UE Context Setup procedure". */ - /* UE is accessing a different DU - trigger UE Context Setup per TS 38.401 §8.7 */ - nr_rrc_du_container_t *du = get_du_by_assoc_id(rrc, assoc_id); - if (du == NULL) { - LOG_E(NR_RRC, "UE %d: cannot find DU for assoc_id %d\n", ue_p->rrc_ue_id, assoc_id); + nr_rrc_cell_container_t *cell = rrc_get_pcell_for_ue(rrc, ue_p); + // UE should have a PCell set at this point (set when RRCReestablishmentRequest was handled) + if (cell == NULL) { + LOG_E(NR_RRC, "UE %d: no PCell for reestablishment on different DU\n", ue_p->rrc_ue_id); return; } - rrc_f1_ue_context_setup_for_target_du(rrc, ue_p, du, NULL); + rrc_f1_ue_context_setup_for_target_du(rrc, ue_p, cell, NULL); } else { // Request DU to provide CellGroupConfig with reestablishRLC flags for re-establishment // According to TS 38.473 transparency requirements, CU should not construct or re-encode CellGroupConfig @@ -1406,9 +1409,17 @@ static void rrc_handle_RRCSetupRequest(gNB_RRC_INST *rrc, byte_array_t cgc = {.buf = msg->du2cu_rrc_container, .len = msg->du2cu_rrc_container_length}; store_cgc(UE, &cgc); UE->establishment_cause = rrcSetupRequest->establishmentCause; - UE->nr_cellid = msg->nr_cellid; + // Add PCell to serving_cells array + nr_rrc_cell_container_t *cell = get_cell_by_cell_id(&rrc->cells, msg->nr_cellid); + if (!cell) { + LOG_E(NR_RRC, "Cell %ld not found, rejecting UE\n", msg->nr_cellid); + rrc_gNB_generate_RRCReject(rrc, ue_context_p); + return; + } + /* Add PCell to serving_cells array */ + rrc_add_ue_serving_cell(UE, cell, RRC_PCELL_INDEX); UE->ongoing_reconfiguration = false; - UE->measConfig = nr_rrc_get_measconfig(rrc, UE->nr_cellid); + UE->measConfig = nr_rrc_get_measconfig(rrc, msg->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); } @@ -1436,7 +1447,8 @@ static rrc_gNB_ue_context_t *rrc_gNB_get_ue_context_source_cell(gNB_RRC_INST *rr if (!ue->ho_context || !ue->ho_context->source) continue; nr_ho_source_cu_t *source_ctx = ue->ho_context->source; - if (source_ctx->old_rnti == rntiP && source_ctx->du->setup_req->cell[0].info.nr_pci == pci) + const nr_rrc_cell_container_t *source_cell = source_ctx->cell; + if (source_cell && source_ctx->old_rnti == rntiP && source_cell->info.pci == pci) return ue_context_p; } return NULL; @@ -1456,6 +1468,28 @@ static void f1u_dl_gtp_rollback(gNB_RRC_UE_t *UE) } } +/** @brief Resolve previous/old cell (UE identity physCellId): same cell, else neighbor + * of current cell, else any cell with that PCI in this DU. */ +static const nr_rrc_cell_container_t *get_previous_cell_by_pci_in_du(gNB_RRC_INST *rrc, + const nr_rrc_cell_container_t *current_cell, + const nr_rrc_du_container_t *du, + uint16_t pci) +{ + if (current_cell->info.pci == pci) + return current_cell; + + const neighbour_cell_configuration_t *neigh_config = get_neighbour_cell_config(rrc, current_cell->info.cell_id); + if (neigh_config != NULL) { + const nr_neighbour_cell_t *neighbour = get_neighbour_cell_by_pci(neigh_config, pci); + if (neighbour != NULL) { + nr_rrc_cell_container_t *c = get_cell_by_cell_id(&rrc->cells, neighbour->nrcell_id); + if (c != NULL && c->assoc_id == du->assoc_id) + return c; + } + } + return rrc_get_cell_by_pci_for_du(&du->cells, pci); +} + static void rrc_handle_RRCReestablishmentRequest(gNB_RRC_INST *rrc, sctp_assoc_t assoc_id, const NR_RRCReestablishmentRequest_IEs_t *req, @@ -1486,15 +1520,17 @@ static void rrc_handle_RRCReestablishmentRequest(gNB_RRC_INST *rrc, goto fallback_rrc_setup; } - if (du->mib == NULL || du->sib1 == NULL) { - /* we don't have MIB/SIB1 of the DU, and therefore cannot generate the - * Reestablishment (as we would need the SSB's ARFCN, which we cannot - * compute). So generate RRC Setup instead */ - LOG_E(NR_RRC, "Reestablishment request: no MIB/SIB1 of DU present, cannot do reestablishment, force setup request\n"); - goto fallback_rrc_setup; + // Fetch current cell: where the reestablishment request was received + nr_rrc_cell_container_t *current_cell = get_cell_by_cell_id(&rrc->cells, msg->nr_cellid); + if (current_cell == NULL || current_cell->assoc_id != du->assoc_id) { + LOG_E(NR_RRC, + "received CCCH message, but no corresponding cell found for nr_cellid %ld in DU (assoc_id %d)\n", + msg->nr_cellid, + assoc_id); + return; } - if (du->mtc == NULL) { + if (current_cell->mtc == NULL) { // some UEs don't send MeasurementTimingConfiguration, so we don't know the // SSB ARFCN and can't do reestablishment. handle it gracefully by doing // RRC setup procedure instead @@ -1503,7 +1539,12 @@ static void rrc_handle_RRCReestablishmentRequest(gNB_RRC_INST *rrc, goto fallback_rrc_setup; } - // Try to find UE context by RNTI and assoc_id (normal case: same DU) + const nr_rrc_cell_container_t *cell = get_previous_cell_by_pci_in_du(rrc, current_cell, du, (uint16_t)physCellId); + if (cell == NULL) { + LOG_E(NR_RRC, "received CCCH message, but no corresponding cell found for PCI %ld in DU (assoc_id %d)\n", physCellId, assoc_id); + return; + } + ue_context_p = rrc_gNB_get_ue_context_by_rnti(rrc, assoc_id, old_rnti); if (ue_context_p == NULL) { // Fallback 1: Try to find UE by RNTI only (re-establishment on different DU scenario) @@ -1526,14 +1567,12 @@ static void rrc_handle_RRCReestablishmentRequest(gNB_RRC_INST *rrc, goto fallback_rrc_setup; } - /* should check phys cell ID to identify the correct cell */ - const f1ap_served_cell_info_t *cell_info = &du->setup_req->cell[0].info; - const f1ap_served_cell_info_t *previous_cell_info = get_cell_information_by_phycellId(physCellId); f1_ue_data_t ue_data = cu_get_f1_ue_data(UE->rrc_ue_id); nr_ho_source_cu_t *source_ctx = UE->ho_context ? UE->ho_context->source : NULL; - DevAssert(!source_ctx || source_ctx->du->setup_req->num_cells_available == 1); - bool ho_reestab_on_source = source_ctx && previous_cell_info->nr_cellid == source_ctx->du->setup_req->cell[0].info.nr_cellid; + DevAssert(!source_ctx || source_ctx->cell != NULL); + const nr_rrc_cell_container_t *source_cell = source_ctx ? source_ctx->cell : NULL; + bool ho_reestab_on_source = source_cell ? cell->info.cell_id == source_cell->info.cell_id : false; if (ho_reestab_on_source) { /* the UE came back on the source DU while doing handover, release at @@ -1553,11 +1592,11 @@ static void rrc_handle_RRCReestablishmentRequest(gNB_RRC_INST *rrc, store_cgc(UE, &source_ctx->old_cgc); /* update to old DU assoc id -- RNTI + secondary DU UE ID further below */ - ue_data.du_assoc_id = source_ctx->du->assoc_id; + ue_data.du_assoc_id = source_cell->assoc_id; bool success = cu_update_f1_ue_data(UE->rrc_ue_id, &ue_data); DevAssert(success); nr_rrc_finalize_ho(UE); - } else if (physCellId != cell_info->nr_pci) { + } else if (physCellId != cell->info.pci) { /* Check if this is a different DU scenario or "too fast movement" scenario */ if (assoc_id != ue_data.du_assoc_id) { /* Different DU scenario - physCellId differs because UE is re-establishing on a different DU. @@ -1569,7 +1608,7 @@ static void rrc_handle_RRCReestablishmentRequest(gNB_RRC_INST *rrc, "UE %d: Re-establishment on different DU (physCellId %ld from old cell != %d from new DU, du_assoc_id %d -> %d)\n", UE->rrc_ue_id, physCellId, - cell_info->nr_pci, + cell->info.pci, ue_data.du_assoc_id, assoc_id); @@ -1608,7 +1647,7 @@ static void rrc_handle_RRCReestablishmentRequest(gNB_RRC_INST *rrc, LOG_I(NR_RRC, "RRC Reestablishment Request from different physCellId (%ld) than current physCellId (%d), fallback to RRC setup\n", physCellId, - cell_info->nr_pci); + cell->info.pci); ngap_cause = NGAP_CAUSE_RADIO_NETWORK_RELEASE_DUE_TO_NGRAN_GENERATED_REASON; goto fallback_rrc_setup; } @@ -1618,12 +1657,24 @@ static void rrc_handle_RRCReestablishmentRequest(gNB_RRC_INST *rrc, // update with new RNTI, and update secondary UE association UE->rnti = msg->crnti; - UE->nr_cellid = msg->nr_cellid; + + /* Update PCell in serving_cells array */ + DevAssert(cell->info.cell_id == msg->nr_cellid); + // Reset the serving set to the new PCell, remove all serving cells from the current PCell's DU. + const ue_serving_cell_t *existing_pcell = ue_get_pcell_entry(UE); + DevAssert(existing_pcell != NULL); + rrc_remove_ue_scells_from_du(UE, existing_pcell->assoc_id); + ue_serving_cell_t *added = rrc_add_ue_serving_cell(UE, cell, RRC_PCELL_INDEX); + if (added == NULL) { + LOG_E(NR_RRC, "Reestablishment: failed to add PCell (cell %ld), rejecting reestablishment\n", cell->info.cell_id); + goto fallback_rrc_setup; + } + ue_data.secondary_ue = msg->gNB_DU_ue_id; bool success = cu_update_f1_ue_data(UE->rrc_ue_id, &ue_data); DevAssert(success); - rrc_gNB_generate_RRCReestablishment(ue_context_p, old_rnti, du); + rrc_gNB_generate_RRCReestablishment(ue_context_p, old_rnti, cell); return; fallback_rrc_setup: @@ -1715,13 +1766,34 @@ static void process_Event_Based_Measurement_Report(gNB_RRC_INST *rrc, neighbour_pci, neighbourCellRSRP); - const f1ap_served_cell_info_t *neigh_cell = get_cell_information_by_phycellId(neighbour_pci); - const f1ap_served_cell_info_t *serving_cell = get_cell_information_by_phycellId(scell_pci); - const neighbour_cell_configuration_t *cell = get_neighbour_cell_config(rrc, serving_cell->nr_cellid); + // Resolve source cell by PCI from UE's serving cells + const nr_rrc_cell_container_t *source_cell = NULL; + FOR_EACH_SEQ_ARR (ue_serving_cell_t *, scell, &ue->serving_cells) { + nr_rrc_cell_container_t *cell_by_id = get_cell_by_cell_id(&rrc->cells, scell->nci); + if (cell_by_id != NULL && cell_by_id->info.pci == scell_pci) { + source_cell = cell_by_id; + break; + } + } + if (source_cell == NULL) { + LOG_W(NR_RRC, + "UE %d: received measurement report but source cell with PCI %d not found in UE's serving cells, skipping\n", + ue->rrc_ue_id, + scell_pci); + continue; + } + + // Resolve target cell via neighbour configuration + const neighbour_cell_configuration_t *cell = get_neighbour_cell_config(rrc, source_cell->info.cell_id); const nr_neighbour_cell_t *neighbour = get_neighbour_cell_by_pci(cell, neighbour_pci); + const nr_rrc_cell_container_t *target_cell = NULL; + if (neighbour != NULL) { + // Resolve target cell by cell_id from neighbour config (may be NULL if no F1 connection) + target_cell = get_cell_by_cell_id(&rrc->cells, neighbour->nrcell_id); + } // CU does not have f1 connection with neighbour cell context. So check does serving cell has this phyCellId as a // neighbour. - if (!neigh_cell && neighbour) { + if (!target_cell && neighbour) { // No F1 connection but static neighbour configuration is available const nr_a3_event_t *a3_event_configuration = get_a3_configuration(rrc, neighbour->physicalCellId); /* if no A3 event configured for this physical cell ID, try to get the default one */ @@ -1740,12 +1812,9 @@ static void process_Event_Based_Measurement_Report(gNB_RRC_INST *rrc, } LOG_D(NR_RRC, "HO LOG: Trigger N2 HO for the neighbour gnb: %u cell: %lu\n", neighbour->gNB_ID, neighbour->nrcell_id); } - } else if (neigh_cell && neighbour) { + } else if (target_cell && neighbour) { /* we know the cell and are connected to the DU! */ - nr_rrc_du_container_t *source_du = get_du_by_cell_id(rrc, serving_cell->nr_cellid); - DevAssert(source_du); - nr_rrc_du_container_t *target_du = get_du_by_cell_id(rrc, neigh_cell->nr_cellid); - nr_rrc_trigger_f1_ho(rrc, ue, source_du, target_du); + nr_rrc_trigger_f1_ho(rrc, ue, source_cell, target_cell); } else { LOG_W(NR_RRC, "UE %d: received A3 event for stronger neighbor PCI %d, but no such neighbour in configuration\n", ue->rrc_ue_id, neighbour_pci); } @@ -2614,12 +2683,15 @@ static void rrc_CU_process_ue_context_release_request(MessageDef *msg_p, sctp_as gNB_RRC_UE_t *UE = &ue_context_p->ue_context; if (UE->ho_context != NULL) { nr_ho_source_cu_t *source_ctx = UE->ho_context->source; - bool from_source_du = source_ctx && source_ctx->du->assoc_id == assoc_id; + bool from_source_du = source_ctx && source_ctx->cell->assoc_id == assoc_id; if (from_source_du) { // we received release request from the source DU, but HO is still // ongoing; free the UE, and remove the HO context. - LOG_W(NR_RRC, "UE %d: release request from source DU ID %ld during HO, marking HO as complete\n", UE->rrc_ue_id, source_ctx->du->setup_req->gNB_DU_id); - RETURN_IF_INVALID_ASSOC_ID(source_ctx->du->assoc_id); + LOG_W(NR_RRC, + "UE %d: release request from source cell PCI %d during HO, marking HO as complete\n", + UE->rrc_ue_id, + source_ctx->cell->info.pci); + RETURN_IF_INVALID_ASSOC_ID(source_ctx->cell->assoc_id); f1ap_ue_context_rel_cmd_t cmd = { .gNB_CU_ue_id = UE->rrc_ue_id, .gNB_DU_ue_id = source_ctx->du_ue_id, @@ -2747,6 +2819,10 @@ static void rrc_CU_process_ue_context_modification_response(MessageDef *msg_p, i nr_ho_source_cu_t *source_ctx = UE->ho_context->source; DevAssert(source_ctx->old_rnti == UE->rnti); nr_rrc_apply_target_context(UE); + /* Update cell association after handover */ + if (!nr_rrc_update_cell_assoc_after_ho(rrc, UE)) { + return; + } } } @@ -2768,7 +2844,9 @@ static void rrc_CU_process_ue_modification_required(MessageDef *msg_p, instance_ } gNB_RRC_UE_t *UE = &ue_context_p->ue_context; - if (UE->ho_context && UE->ho_context->source && UE->ho_context->source->du && UE->ho_context->source->du->assoc_id == assoc_id) { + bool on_source_du = UE->ho_context && UE->ho_context->source && UE->ho_context->source->cell + && UE->ho_context->source->cell->assoc_id == assoc_id; + if (on_source_du) { LOG_W(NR_RRC, "UE %d: UE Context Modification Required during handover, ignoring message\n", UE->rrc_ue_id); return; } @@ -3606,14 +3684,14 @@ static int rrc_fill_f1_drb_to_setup(const gNB_RRC_INST *rrc, const gNB_RRC_UE_t * - Handover preparation information (if provided) * @param rrc RRC instance * @param ue UE context - * @param du Target DU container containing cell and setup information + * @param cell Target cell container containing cell information * @param ho_prep_info Optional handover preparation information */ void rrc_f1_ue_context_setup_for_target_du(const gNB_RRC_INST *rrc, gNB_RRC_UE_t *ue, - const nr_rrc_du_container_t *du, + const nr_rrc_cell_container_t *cell, const byte_array_t *ho_prep_info) { - DevAssert(du != NULL); + DevAssert(cell != NULL); /* Handle two scenarios: * 1. Handover (ho_prep_info provided): Prepare and copy handover preparation information * for the target DU. The target DU will assign a new gNB-DU UE ID in the response. @@ -3635,7 +3713,7 @@ void rrc_f1_ue_context_setup_for_target_du(const gNB_RRC_INST *rrc, du_ue_id = ue_data.secondary_ue; opt_du_ue_id = &du_ue_id; } - LOG_I(NR_RRC, "Triggering UE Context Setup for UE %d on DU %d\n", ue->rrc_ue_id, du->assoc_id); + LOG_I(NR_RRC, "Triggering UE Context Setup for UE %d on DU %d\n", ue->rrc_ue_id, cell->assoc_id); int nb_srb = 2; f1ap_srb_to_setup_t *srbs = calloc_or_fail(nb_srb, sizeof(*srbs)); f1ap_drb_to_setup_t *drbs = calloc_or_fail(MAX_DRBS_PER_UE, sizeof(*drbs)); @@ -3649,12 +3727,12 @@ void rrc_f1_ue_context_setup_for_target_du(const gNB_RRC_INST *rrc, /* Update measurement config for target DU */ free_MeasConfig(ue->measConfig); - ue->measConfig = nr_rrc_get_measconfig(rrc, du->setup_req->cell[0].info.nr_cellid); + ue->measConfig = nr_rrc_get_measconfig(rrc, cell->info.cell_id); 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); /* Fill common fields */ - f1ap_ue_context_setup_req_t req = rrc_fill_f1_ue_context_setup(ue, du, opt_du_ue_id); + f1ap_ue_context_setup_req_t req = rrc_fill_f1_ue_context_setup(ue, cell, opt_du_ue_id); /* Fill target DU specific fields */ req.srbs_len = 2; req.srbs = srbs; @@ -3663,8 +3741,8 @@ void rrc_f1_ue_context_setup_for_target_du(const gNB_RRC_INST *rrc, req.cu_to_du_rrc_info.meas_config = meas_config; req.cu_to_du_rrc_info.ho_prep_info = hpi; - RETURN_IF_INVALID_ASSOC_ID(du->assoc_id); - rrc->mac_rrc.ue_context_setup_request(du->assoc_id, &req); + RETURN_IF_INVALID_ASSOC_ID(cell->assoc_id); + rrc->mac_rrc.ue_context_setup_request(cell->assoc_id, &req); free_ue_context_setup_req(&req); } @@ -3689,7 +3767,8 @@ void rrc_f1_ue_context_setup_from_e1_response(const gNB_RRC_INST *rrc, AssertFatal(!NODE_IS_DU(rrc->node_type), "illegal node type DU!\n"); - nr_rrc_du_container_t *du = get_du_for_ue((gNB_RRC_INST *)rrc, ue_p->rrc_ue_id); + nr_rrc_cell_container_t *cell = rrc_get_pcell_for_ue((gNB_RRC_INST *)rrc, ue_p); + DevAssert(cell != NULL); int nb_srb = 1; f1ap_srb_to_setup_t *srbs = calloc_or_fail(nb_srb, sizeof(*srbs)); @@ -3706,7 +3785,7 @@ void rrc_f1_ue_context_setup_from_e1_response(const gNB_RRC_INST *rrc, const uint32_t du_ue_id = ue_data.secondary_ue; /* Fill common fields */ - f1ap_ue_context_setup_req_t ue_context_setup_req = rrc_fill_f1_ue_context_setup(ue_p, du, &du_ue_id); + f1ap_ue_context_setup_req_t ue_context_setup_req = rrc_fill_f1_ue_context_setup(ue_p, cell, &du_ue_id); /* Fill bearers information for serving DU */ ue_context_setup_req.srbs_len = nb_srb; ue_context_setup_req.srbs = srbs; diff --git a/openair2/RRC/NR/rrc_gNB_NGAP.c b/openair2/RRC/NR/rrc_gNB_NGAP.c index acb556d8f5..515091a459 100644 --- a/openair2/RRC/NR/rrc_gNB_NGAP.c +++ b/openair2/RRC/NR/rrc_gNB_NGAP.c @@ -92,6 +92,7 @@ #include "rrc_gNB_du.h" #include "rrc_cell_management.h" #include "common/utils/alg/find.h" +#include "common/utils/nr/nr_common.h" #ifdef E2_AGENT #include "openair2/E2AP/RAN_FUNCTION/O-RAN/ran_func_rc_extern.h" @@ -255,8 +256,9 @@ void rrc_gNB_send_NGAP_NAS_FIRST_REQ(gNB_RRC_INST *rrc, gNB_RRC_UE_t *UE, NR_RRC /* UE is connected to only one PLMN at a time: store this as the serving PLMN */ UE->serving_plmn = req->plmn; - // Cell ID (NR CGI) - req->nr_cell_id = UE->nr_cellid; + nr_rrc_cell_container_t *cell = rrc_get_pcell_for_ue(rrc, UE); + DevAssert(cell); + req->nr_cell_id = cell->info.cell_id; // PLMN (NR CGI and TAI) plmn_id_t *p = &req->plmn; LOG_I(NGAP, "Selected PLMN in the NG Initial UE Message: MCC=%03d MNC=%0*d\n", p->mcc, p->mnc_digit_length, p->mnc); @@ -748,8 +750,10 @@ void rrc_gNB_send_NGAP_UPLINK_NAS(gNB_RRC_INST *rrc, gNB_RRC_UE_t *UE, const NR_ NGAP_UPLINK_NAS(msg_p).nas_pdu.buf = buf; /* Fill PLMN and location info: use serving PLMN of the UE */ NGAP_UPLINK_NAS(msg_p).plmn = UE->serving_plmn; - NGAP_UPLINK_NAS(msg_p).nr_cell_id = rrc->nr_cellid; - NGAP_UPLINK_NAS(msg_p).tac = rrc->configuration.tac; + nr_rrc_cell_container_t *cell = rrc_get_pcell_for_ue(rrc, UE); + DevAssert(cell); + NGAP_UPLINK_NAS(msg_p).nr_cell_id = cell->info.cell_id; + NGAP_UPLINK_NAS(msg_p).tac = cell->info.tac != 0 ? cell->info.tac : rrc->configuration.tac; itti_send_msg_to_task(TASK_NGAP, rrc->module_id, msg_p); } @@ -1151,6 +1155,7 @@ int rrc_gNB_process_Handover_Request(gNB_RRC_INST *rrc, instance_t instance, nga // allocate context for target UE->ho_context = alloc_ho_ctx(HO_CTX_TARGET); + UE->ho_context->target->cell = cell; UE->ho_context->target->ho_trigger = nr_rrc_trigger_n2_ho_target; // Store IDs in UE context @@ -1173,7 +1178,7 @@ int rrc_gNB_process_Handover_Request(gNB_RRC_INST *rrc, instance_t instance, nga memset(UE->kgnb, 0, SECURITY_KEY_LENGTH); // Derive KgNB* const nr_rrc_cell_info_t *cell_info = &cell->info; - uint32_t ssb_arfcn = get_ssb_arfcn(du); + uint32_t ssb_arfcn = get_ssb_arfcn(cell); nr_derive_key_ng_ran_star(cell_info->pci, ssb_arfcn, UE->nh, UE->kgnb); UE->as_security_active = true; // Activate SRBs @@ -1458,24 +1463,28 @@ void rrc_gNB_send_NGAP_HANDOVER_REQUEST_ACKNOWLEDGE(gNB_RRC_INST *rrc, gNB_RRC_U void rrc_gNB_send_NGAP_HANDOVER_NOTIFY(gNB_RRC_INST *rrc, gNB_RRC_UE_t *UE) { LOG_I(NR_RRC, "Triggering NGAP Handover Notify\n"); - nr_rrc_du_container_t *du = get_du_by_cell_id(rrc, rrc->nr_cellid); + nr_rrc_cell_container_t *cell = rrc_get_pcell_for_ue(rrc, UE); + if (cell == NULL) { + LOG_E(NR_RRC, "Failed to send Handover Notify: no PCell found for UE %d\n", UE->rrc_ue_id); + return; + } + nr_rrc_du_container_t *du = get_du_for_ue(rrc, UE->rrc_ue_id); if (du == NULL) { - LOG_E(NR_RRC, "Failed to send Handover Notify: no DU found with NR Cell ID=%lu\n", rrc->nr_cellid); + LOG_E(NR_RRC, "Failed to send Handover Notify: no DU found for UE %d\n", UE->rrc_ue_id); return; } - MessageDef *msg_p = itti_alloc_new_message(TASK_RRC_GNB, 0, NGAP_HANDOVER_NOTIFY); ngap_handover_notify_t *ho_notify = &NGAP_HANDOVER_NOTIFY(msg_p); memset(ho_notify, 0, sizeof(*ho_notify)); ho_notify->gNB_ue_ngap_id = UE->rrc_ue_id; ho_notify->amf_ue_ngap_id = UE->amf_ue_ngap_id; - ho_notify->user_info.nrCellIdentity = rrc->nr_cellid; + ho_notify->user_info.nrCellIdentity = cell->info.cell_id; target_ran_node_id_t *target_ng_ran = &ho_notify->user_info.target_ng_ran; - target_ng_ran->tac = *du->setup_req->cell->info.tac; + target_ng_ran->tac = cell->info.tac; target_ng_ran->targetgNBId = rrc->node_id; - target_ng_ran->plmn_identity = du->setup_req->cell->info.plmn; + target_ng_ran->plmn_identity = cell->info.plmn; itti_send_msg_to_task(TASK_NGAP, rrc->module_id, msg_p); } @@ -1612,7 +1621,23 @@ int rrc_gNB_process_PAGING_IND(MessageDef *msg_p, instance_t instance) if (NODE_IS_CU(RC.nrrrc[instance]->node_type)) { MessageDef *m = itti_alloc_new_message(TASK_RRC_GNB, 0, F1AP_PAGING_IND); F1AP_PAGING_IND(m).plmn = *req_plmn; - F1AP_PAGING_IND (m).nr_cellid = RC.nrrrc[j]->nr_cellid; + // Construct 5G-S-TMSI-Part1 from paging identity to find UE + const fiveg_s_tmsi_t *s_tmsi = &msg->ue_paging_identity.s_tmsi; + uint64_t s_tmsi_part1 = nr_construct_5g_s_tmsi_part1(s_tmsi->amf_set_id, s_tmsi->amf_pointer, s_tmsi->m_tmsi); + // Try to find UE by 5G-S-TMSI-Part1 + rrc_gNB_ue_context_t *ue_context_p = rrc_gNB_ue_context_5g_s_tmsi_exist(RC.nrrrc[instance], s_tmsi_part1); + if (ue_context_p == NULL) { + LOG_W(NR_RRC, "UE not found by paging identity\n"); + return -1; + } + // UE found: use its associated cell + gNB_RRC_UE_t *UE = &ue_context_p->ue_context; + nr_rrc_cell_container_t *cell = rrc_get_pcell_for_ue(RC.nrrrc[instance], UE); + if (cell == NULL) { + LOG_W(NR_RRC, "UE %d (rnti %04x) found by paging identity but has no valid cell\n", UE->rrc_ue_id, UE->rnti); + return -1; + } + F1AP_PAGING_IND(m).nr_cellid = cell->info.cell_id; F1AP_PAGING_IND(m).ueidentityindexvalue = (uint16_t)(msg->ue_paging_identity.s_tmsi.m_tmsi % 1024); F1AP_PAGING_IND(m).fiveg_s_tmsi = msg->ue_paging_identity.s_tmsi.m_tmsi; F1AP_PAGING_IND(m).paging_drx = msg->paging_drx; diff --git a/openair2/RRC/NR/rrc_gNB_cuup.c b/openair2/RRC/NR/rrc_gNB_cuup.c index 1e3df71822..8292699a5c 100644 --- a/openair2/RRC/NR/rrc_gNB_cuup.c +++ b/openair2/RRC/NR/rrc_gNB_cuup.c @@ -43,6 +43,7 @@ #include "tree.h" #include "e1ap_interface_management.h" #include "rrc_gNB_NGAP.h" +#include "rrc_cell_management.h" static int cuup_compare(const nr_rrc_cuup_container_t *a, const nr_rrc_cuup_container_t *b) { diff --git a/openair2/RRC/NR/rrc_gNB_du.c b/openair2/RRC/NR/rrc_gNB_du.c index 6024079936..5b1be47d5e 100644 --- a/openair2/RRC/NR/rrc_gNB_du.c +++ b/openair2/RRC/NR/rrc_gNB_du.c @@ -79,10 +79,15 @@ static int ssb_arfcn_mtc(const NR_MeasurementTimingConfiguration_t *mtc) return mtlist->list.array[0]->frequencyAndTiming->carrierFreq; } -int get_ssb_arfcn(const struct nr_rrc_du_container_t *du) +/** @brief Find first cell owned by this DU (assoc_id) and return its SSB ARFCN + * @note Current assumption is that each DU serves only one cell*/ +int get_ssb_arfcn(const struct nr_rrc_cell_container_t *cell) { - DevAssert(du != NULL && du->mtc != NULL); - return ssb_arfcn_mtc(du->mtc); + DevAssert(cell != NULL); + if (cell->mtc != NULL) + return ssb_arfcn_mtc(cell->mtc); + AssertFatal(false, "no mtc found for cell %ld\n", cell->info.cell_id); + return 0; } static bool rrc_gNB_plmn_matches(const gNB_RRC_INST *rrc, const f1ap_served_cell_info_t *info) @@ -172,22 +177,6 @@ static neighbour_cell_configuration_t *get_cell_neighbour_list(const gNB_RRC_INS return (neighbour_cell_configuration_t *)it; } -const struct f1ap_served_cell_info_t *get_cell_information_by_phycellId(int phyCellId) -{ - gNB_RRC_INST *rrc = RC.nrrrc[0]; - nr_rrc_du_container_t *it = NULL; - RB_FOREACH (it, rrc_du_tree, &rrc->dus) { - for (int cellIdx = 0; cellIdx < it->setup_req->num_cells_available; cellIdx++) { - const f1ap_served_cell_info_t *cell_info = &(it->setup_req->cell[cellIdx].info); - if (cell_info->nr_pci == phyCellId) { - LOG_D(NR_RRC, "HO LOG: Found cell with phyCellId %d\n", phyCellId); - return cell_info; - } - } - } - return NULL; -} - static void is_intra_frequency_neighbour(void *ssb_arfcn, void *neighbour_cell) { uint32_t *ssb_arfcn_ptr = (uint32_t *)ssb_arfcn; @@ -201,12 +190,9 @@ static void is_intra_frequency_neighbour(void *ssb_arfcn, void *neighbour_cell) /** * @brief Labels neighbour cells if they are intra frequency to prepare meas config only for intra frequency ho * @param[in] rrc Pointer to RRC instance - * @param[in] du Pointer to DU container * @param[in] cell Pointer to cell container */ -static void label_intra_frequency_neighbours(gNB_RRC_INST *rrc, - const nr_rrc_du_container_t *du, - const nr_rrc_cell_container_t *cell) +static void label_intra_frequency_neighbours(gNB_RRC_INST *rrc, const nr_rrc_cell_container_t *cell) { if (!rrc->neighbour_cell_configuration) return; @@ -215,7 +201,7 @@ static void label_intra_frequency_neighbours(gNB_RRC_INST *rrc, if (!neighbour_cell_config) return; - uint32_t ssb_arfcn = get_ssb_arfcn(du); + uint32_t ssb_arfcn = get_ssb_arfcn(cell); LOG_D(NR_RRC, "Cell %lu (PCI %d, SSB ARFCN %u) has neighbour cell configuration, labeling intra-frequency neighbours\n", cell->info.cell_id, @@ -422,18 +408,11 @@ void rrc_gNB_process_f1_setup_req(f1ap_setup_req_t *req, sctp_assoc_t assoc_id) // DU is accepted, add it to tree and add cell to DU's cell array nr_rrc_du_container_t *du = calloc_or_fail(1, sizeof(*du)); - du->mtc = new->mtc; // TODO: remove in upcoming commit (kept for backward compatibility) du->assoc_id = assoc_id; du->gNB_DU_id = req->gNB_DU_id; du->gNB_DU_name = strdup(req->gNB_DU_name ? req->gNB_DU_name : "N/A"); memcpy(du->rrc_ver, req->rrc_ver, sizeof(du->rrc_ver)); - du->setup_req = calloc(1,sizeof(*du->setup_req)); - AssertFatal(du->setup_req, "out of memory\n"); - // Copy F1AP message - *du->setup_req = cp_f1ap_setup_request(req); - // MIB can be null and configured later via DU Configuration Update - du->mib = mib; - du->sib1 = sib1; + // Initialize cell array for this DU seq_arr_init(&du->cells, sizeof(nr_rrc_cell_container_t *)); nr_rrc_du_container_t *du_collision = rrc_add_du(rrc, du); @@ -471,7 +450,7 @@ void rrc_gNB_process_f1_setup_req(f1ap_setup_req_t *req, sctp_assoc_t assoc_id) } if (new->mib != NULL &&new->sib1 != NULL) - label_intra_frequency_neighbours(rrc, du, new); + label_intra_frequency_neighbours(rrc, new); f1ap_setup_resp_t resp = {.transaction_id = req->transaction_id, .num_cells_to_activate = 1, @@ -501,6 +480,9 @@ static int invalidate_du_connections(gNB_RRC_INST *rrc, sctp_assoc_t assoc_id) if (UE->ho_context != NULL) LOG_W(NR_RRC, "DU disconnected while handover for UE %d active\n", ue_id); + // Remove SCells from this DU for this UE + rrc_remove_ue_scells_from_du(UE, assoc_id); + f1_ue_data_t ue_data = cu_get_f1_ue_data(ue_id); if (ue_data.du_assoc_id != assoc_id) continue; /* this UE is on another DU */ @@ -689,8 +671,9 @@ void rrc_gNB_process_f1_du_configuration_update(f1ap_gnb_du_configuration_update LOG_I(NR_RRC, "update system information of DU %ld\n", du->gNB_DU_id); } } - if (du->mib != NULL && du->sib1 != NULL && cell != NULL) { - label_intra_frequency_neighbours(rrc, du, cell); + + if (cell && cell->mib != NULL && cell->sib1 != NULL && cell->assoc_id == du->assoc_id) { + label_intra_frequency_neighbours(rrc, cell); } } @@ -723,15 +706,6 @@ void rrc_CU_process_f1_lost_connection(gNB_RRC_INST *rrc, f1ap_lost_connection_t return; } - f1ap_setup_req_t *req = du->setup_req; - LOG_I(NR_RRC, "releasing DU ID %ld (%s) on assoc_id %d\n", req->gNB_DU_id, req->gNB_DU_name, assoc_id); - ASN_STRUCT_FREE(asn_DEF_NR_MIB, du->mib); - ASN_STRUCT_FREE(asn_DEF_NR_SIB1, du->sib1); - ASN_STRUCT_FREE(asn_DEF_NR_MeasurementTimingConfiguration, du->mtc); - if (du->setup_req) - free_f1ap_setup_request(du->setup_req); - free(du->setup_req); - // Clean up DU and associated cell resources rrc_cleanup_du(rrc, du); @@ -749,46 +723,48 @@ nr_rrc_du_container_t *get_du_for_ue(gNB_RRC_INST *rrc, uint32_t ue_id) return get_du_by_assoc_id(rrc, ue_data.du_assoc_id); } -/* \brief find DU by cell ID. Note: currently the CU is limited to one cell per - * DU, hence here, DU == cell. Modify this to look up a specific cell. */ -nr_rrc_du_container_t *get_du_by_cell_id(gNB_RRC_INST *rrc, uint64_t cell_id) -{ - nr_rrc_du_container_t *du = NULL; - RB_FOREACH(du, rrc_du_tree, &rrc->dus) { - if (cell_id == du->setup_req->cell[0].info.nr_cellid) - return du; - } - return NULL; -} - void dump_du_info(const gNB_RRC_INST *rrc, FILE *f) { fprintf(f, "%ld connected DUs \n", rrc->num_dus); - int i = 1; + int du_idx = 1; nr_rrc_du_container_t *du = NULL; - /* cast is necessary to eliminate warning "discards ‘const’ qualifier" */ + /* cast is necessary to eliminate warning "discards 'const' qualifier" */ RB_FOREACH(du, rrc_du_tree, &((gNB_RRC_INST *)rrc)->dus) { - const f1ap_setup_req_t *sr = du->setup_req; - fprintf(f, "[%d] DU ID %ld (%s) ", i++, sr->gNB_DU_id, sr->gNB_DU_name); + fprintf(f, "[%d] DU ID %ld (%s) ", du_idx++, du->gNB_DU_id, du->gNB_DU_name); if (du->assoc_id == -1) { fprintf(f, "integrated DU-CU"); } else { fprintf(f, "assoc_id %d", du->assoc_id); } - const f1ap_served_cell_info_t *info = &sr->cell[0].info; - fprintf(f, ": nrCellID %ld, PCI %d, SSB ARFCN %d\n", info->nr_cellid, info->nr_pci, get_ssb_arfcn(du)); - if (info->mode == F1AP_MODE_TDD) { - const f1ap_nr_frequency_info_t *fi = &info->tdd.freqinfo; - const f1ap_transmission_bandwidth_t *tb = &info->tdd.tbw; - fprintf(f, " TDD: band %d ARFCN %d SCS %d (kHz) PRB %d\n", fi->band, fi->arfcn, 15 * (1 << tb->scs), tb->nrb); + /* Print all cells for this DU */ + int cell_count = seq_arr_size(&du->cells); + + if (cell_count == 0) { + fprintf(f, ": no cells\n"); } else { - const f1ap_nr_frequency_info_t *dfi = &info->fdd.dl_freqinfo; - const f1ap_transmission_bandwidth_t *dtb = &info->fdd.dl_tbw; - fprintf(f, " FDD: DL band %d ARFCN %d SCS %d (kHz) PRB %d\n", dfi->band, dfi->arfcn, 15 * (1 << dtb->scs), dtb->nrb); - const f1ap_nr_frequency_info_t *ufi = &info->fdd.ul_freqinfo; - const f1ap_transmission_bandwidth_t *utb = &info->fdd.ul_tbw; - fprintf(f, " UL band %d ARFCN %d SCS %d (kHz) PRB %d\n", ufi->band, ufi->arfcn, 15 * (1 << utb->scs), utb->nrb); + fprintf(f, ": %d cell%s\n", cell_count, cell_count > 1 ? "s" : ""); + int cell_idx = 1; + FOR_EACH_SEQ_ARR (nr_rrc_cell_container_t **, cell_ptr, &du->cells) { + nr_rrc_cell_container_t *cell = *cell_ptr; + const char *mode = (cell->info.mode == NR_MODE_TDD) ? "TDD" : "FDD"; + fprintf(f, + " [%d] nrCellID %lu, PCI %d, Mode %s, SSB ARFCN %d\n", + cell_idx++, + cell->info.cell_id, + cell->info.pci, + mode, + ssb_arfcn_mtc(cell->mtc)); + if (cell->info.mode == NR_MODE_TDD) { + const nr_rrc_freq_info_t *tdd = &cell->info.tdd.dlul; + fprintf(f, " TDD: band %d ARFCN %d SCS %d (kHz) PRB %d\n", tdd->band, tdd->arfcn, 15 * (1 << tdd->scs), tdd->nrb); + } else { + const nr_rrc_freq_info_t *dl = &cell->info.fdd.dl; + const nr_rrc_freq_info_t *ul = &cell->info.fdd.ul; + fprintf(f, " DL: band %d ARFCN %d SCS %d (kHz) PRB %d\n", dl->band, dl->arfcn, 15 * (1 << dl->scs), dl->nrb); + fprintf(f, " UL: band %d ARFCN %d SCS %d (kHz) PRB %d\n", ul->band, ul->arfcn, 15 * (1 << ul->scs), ul->nrb); + } + } } } } diff --git a/openair2/RRC/NR/rrc_gNB_du.h b/openair2/RRC/NR/rrc_gNB_du.h index 4206096aa2..ce033e91e8 100644 --- a/openair2/RRC/NR/rrc_gNB_du.h +++ b/openair2/RRC/NR/rrc_gNB_du.h @@ -30,6 +30,7 @@ struct f1ap_setup_req_s; struct f1ap_lost_connection_t; struct gNB_RRC_INST_s; struct nr_rrc_du_container_t; +struct nr_rrc_cell_container_t; struct f1ap_gnb_du_configuration_update_s; struct f1ap_served_cell_info_t; @@ -38,14 +39,12 @@ void rrc_CU_process_f1_lost_connection(struct gNB_RRC_INST_s *rrc, struct f1ap_l void rrc_gNB_process_f1_du_configuration_update(struct f1ap_gnb_du_configuration_update_s *conf_up, sctp_assoc_t assoc_id); struct nr_rrc_du_container_t *get_du_for_ue(struct gNB_RRC_INST_s *rrc, uint32_t ue_id); -const struct f1ap_served_cell_info_t *get_cell_information_by_phycellId(int phyCellId); -struct nr_rrc_du_container_t *get_du_by_cell_id(struct gNB_RRC_INST_s *rrc, uint64_t cell_id); void dump_du_info(const struct gNB_RRC_INST_s *rrc, FILE *f); int get_dl_band(const struct f1ap_served_cell_info_t *cell_info); int get_ssb_scs(const struct f1ap_served_cell_info_t *cell_info); -int get_ssb_arfcn(const struct nr_rrc_du_container_t *du); +int get_ssb_arfcn(const struct nr_rrc_cell_container_t *cell); struct nr_rrc_du_container_t *find_target_du(struct gNB_RRC_INST_s *rrc, sctp_assoc_t source_assoc_id); diff --git a/openair2/RRC/NR/rrc_gNB_mobility.c b/openair2/RRC/NR/rrc_gNB_mobility.c index a2230c1146..d18937fbc5 100644 --- a/openair2/RRC/NR/rrc_gNB_mobility.c +++ b/openair2/RRC/NR/rrc_gNB_mobility.c @@ -27,6 +27,7 @@ #include "nr_rrc_proto.h" #include "rrc_gNB_du.h" +#include "rrc_cell_management.h" #include "rrc_gNB_radio_bearers.h" #include "rrc_gNB_UE_context.h" #include "openair2/LAYER2/NR_MAC_COMMON/nr_mac.h" @@ -37,6 +38,7 @@ #include "openair3/SECU/key_nas_deriver.h" #include "openair2/RRC/NR/rrc_gNB_NGAP.h" #include "NR_DL-DCCH-MessageType.h" +#include "rrc_cell_management.h" #ifdef E2_AGENT #include "openair2/E2AP/RAN_FUNCTION/O-RAN/ran_func_rc_extern.h" @@ -80,15 +82,12 @@ void nr_rrc_apply_target_context(gNB_RRC_UE_t *UE) /* update F1 data: secondary UE association and DU association */ ue_data.secondary_ue = target_ctx->du_ue_id; - ue_data.du_assoc_id = target_ctx->du->assoc_id; + ue_data.du_assoc_id = target_ctx->cell->assoc_id; bool success = cu_update_f1_ue_data(UE->rrc_ue_id, &ue_data); DevAssert(success); /* update UE RNTI */ UE->rnti = target_ctx->new_rnti; - - /* update UE NR cell ID */ - UE->nr_cellid = target_ctx->du->setup_req->cell[0].info.nr_cellid; } /* \brief Initiate a handover of UE to a specific target cell handled by this @@ -101,8 +100,7 @@ void nr_rrc_apply_target_context(gNB_RRC_UE_t *UE) * \param ho_ctxt contextual data for the type of handover (F1, N2, Xn) */ static void nr_initiate_handover(const gNB_RRC_INST *rrc, gNB_RRC_UE_t *ue, - const nr_rrc_du_container_t *source_du, - const nr_rrc_du_container_t *target_du, + const nr_rrc_cell_container_t *source_cell, byte_array_t *ho_prep_info, ho_req_ack_t ack, ho_success_t success, @@ -111,10 +109,10 @@ static void nr_initiate_handover(const gNB_RRC_INST *rrc, { DevAssert(rrc != NULL); DevAssert(ue != NULL); - DevAssert(target_du != NULL); - // source_du might be NULL -> inter-CU handover + // source_cell might be NULL -> inter-CU handover DevAssert(ho_prep_info->buf != NULL && ho_prep_info->len > 0); DevAssert(ue->ho_context); + DevAssert(ue->ho_context->target != NULL); // if any reconfiguration is ongoing, abort handover request for (int i = 0; i < NR_RRC_TRANSACTION_IDENTIFIER_NUMBER; ++i) { @@ -125,7 +123,9 @@ static void nr_initiate_handover(const gNB_RRC_INST *rrc, } nr_handover_context_t *ho_ctx = ue->ho_context; - ho_ctx->target->du = target_du; + const nr_rrc_cell_container_t *target_cell = ho_ctx->target->cell; + DevAssert(target_cell != NULL); + // we will know target->{du_ue_id,new_rnti} once we have UE ctxt setup // response ho_ctx->target->ho_req_ack = ack; @@ -133,14 +133,14 @@ static void nr_initiate_handover(const gNB_RRC_INST *rrc, ho_ctx->target->ho_failure = failure; const f1_ue_data_t ue_data = cu_get_f1_ue_data(ue->rrc_ue_id); - if (source_du != NULL) { - DevAssert(source_du->assoc_id == ue_data.du_assoc_id); - // we also have the source DU (F1 handover), store meta info + if (source_cell != NULL) { + DevAssert(source_cell->assoc_id == ue_data.du_assoc_id); ho_ctx->source->ho_cancel = cancel; - ho_ctx->source->du = source_du; + ho_ctx->source->cell = source_cell; ho_ctx->source->du_ue_id = ue_data.secondary_ue; ho_ctx->source->old_rnti = ue->rnti; + DevAssert(source_cell->assoc_id == ue_data.du_assoc_id); // Save the GTP-U tunnel info for source DU before process UE context setup request/response // since tunnel info will be updated by calling store_du_f1u_tunnel() in rrc_CU_process_ue_context_setup_response() @@ -153,14 +153,14 @@ static void nr_initiate_handover(const gNB_RRC_INST *rrc, } LOG_A(NR_RRC, - "Handover triggered for UE %u/RNTI %04x towards DU %ld/assoc_id %d/PCI %d\n", + "Handover triggered for UE %u/RNTI %04x towards cell %ld/assoc_id %d/PCI %d\n", ue->rrc_ue_id, ue->rnti, - target_du->setup_req->gNB_DU_id, - target_du->assoc_id, - target_du->setup_req->cell[0].info.nr_pci); + target_cell->info.cell_id, + target_cell->assoc_id, + target_cell->info.pci); - rrc_f1_ue_context_setup_for_target_du(rrc, ue, target_du, ho_prep_info); + rrc_f1_ue_context_setup_for_target_du(rrc, ue, target_cell, ho_prep_info); } typedef struct deliver_ue_ctxt_modification_data_t { @@ -249,15 +249,19 @@ static void nr_rrc_f1_ho_complete(gNB_RRC_INST *rrc, gNB_RRC_UE_t *UE) DevAssert(UE->ho_context != NULL); nr_ho_source_cu_t *source_ctx = UE->ho_context->source; DevAssert(source_ctx != NULL); - RETURN_IF_INVALID_ASSOC_ID(source_ctx->du->assoc_id); + RETURN_IF_INVALID_ASSOC_ID(source_ctx->cell->assoc_id); f1ap_ue_context_rel_cmd_t cmd = { .gNB_CU_ue_id = UE->rrc_ue_id, .gNB_DU_ue_id = source_ctx->du_ue_id, .cause = F1AP_CAUSE_RADIO_NETWORK, .cause_value = 5, // 5 = F1AP_CauseRadioNetwork_interaction_with_other_procedure }; - rrc->mac_rrc.ue_context_release_command(source_ctx->du->assoc_id, &cmd); - LOG_I(NR_RRC, "UE %d Handover: trigger release on DU assoc_id %d\n", UE->rrc_ue_id, source_ctx->du->assoc_id); + rrc->mac_rrc.ue_context_release_command(source_ctx->cell->assoc_id, &cmd); + LOG_I(NR_RRC, + "UE %d Handover: trigger release on cell PCI %d/assoc_id %d\n", + UE->rrc_ue_id, + source_ctx->cell->info.pci, + source_ctx->cell->assoc_id); } static void nr_rrc_cancel_f1_ho(gNB_RRC_INST *rrc, gNB_RRC_UE_t *UE) @@ -271,10 +275,13 @@ static void nr_rrc_cancel_f1_ho(gNB_RRC_INST *rrc, gNB_RRC_UE_t *UE) .cause = F1AP_CAUSE_RADIO_NETWORK, // better .cause_value = 5, // 5 = F1AP_CauseRadioNetwork_interaction_with_other_procedure }; - rrc->mac_rrc.ue_context_release_command(target_ctx->du->assoc_id, &cmd); + rrc->mac_rrc.ue_context_release_command(target_ctx->cell->assoc_id, &cmd); } -void nr_rrc_trigger_f1_ho(gNB_RRC_INST *rrc, gNB_RRC_UE_t *ue, nr_rrc_du_container_t *source_du, nr_rrc_du_container_t *target_du) +void nr_rrc_trigger_f1_ho(gNB_RRC_INST *rrc, + gNB_RRC_UE_t *ue, + const nr_rrc_cell_container_t *source_cell, + const nr_rrc_cell_container_t *target_cell) { DevAssert(rrc != NULL); DevAssert(ue != NULL); @@ -288,6 +295,7 @@ void nr_rrc_trigger_f1_ho(gNB_RRC_INST *rrc, gNB_RRC_UE_t *ue, nr_rrc_du_contain return; } ue->ho_context = alloc_ho_ctx(HO_CTX_BOTH); + ue->ho_context->target->cell = target_cell; // corresponds to a "handover request", 38.300 Sec 9.3.2.3 // see also 38.413 Sec 9.3.1.29 for information on source-CU to target-CU @@ -299,7 +307,7 @@ void nr_rrc_trigger_f1_ho(gNB_RRC_INST *rrc, gNB_RRC_UE_t *ue, nr_rrc_du_contain ho_success_t success = nr_rrc_f1_ho_complete; ho_cancel_t cancel = nr_rrc_cancel_f1_ho; byte_array_t hpi = {.buf = buf, .len = size}; - nr_initiate_handover(rrc, ue, source_du, target_du, &hpi, ack, success, cancel, NULL); + nr_initiate_handover(rrc, ue, source_cell, &hpi, ack, success, cancel, NULL); } void nr_rrc_finalize_ho(gNB_RRC_UE_t *ue) @@ -324,6 +332,11 @@ void nr_HO_F1_trigger_telnet(gNB_RRC_INST *rrc, uint32_t rrc_ue_id) LOG_E(NR_RRC, "cannot get source gNB-DU with assoc_id %d for UE %u\n", ue_data.du_assoc_id, ue->rrc_ue_id); return; } + nr_rrc_cell_container_t *source_cell = rrc_get_pcell_for_ue(rrc, ue); + if (source_cell == NULL) { + LOG_E(NR_RRC, "cannot get source cell for UE %u\n", ue->rrc_ue_id); + return; + } nr_rrc_du_container_t *target_du = find_target_du(rrc, source_du->assoc_id); if (target_du == NULL) { @@ -331,7 +344,19 @@ void nr_HO_F1_trigger_telnet(gNB_RRC_INST *rrc, uint32_t rrc_ue_id) return; } - nr_rrc_trigger_f1_ho(rrc, ue, source_du, target_du); + // For target cell, get the first cell from target DU + // (in future, this could be selected based on measurement) + nr_rrc_cell_container_t *target_cell = NULL; + FOR_EACH_SEQ_ARR (nr_rrc_cell_container_t **, cell_ptr, &target_du->cells) { + target_cell = *cell_ptr; + break; // Get first cell + } + if (target_cell == NULL) { + LOG_E(NR_RRC, "cannot get target cell for UE %u\n", ue->rrc_ue_id); + return; + } + + nr_rrc_trigger_f1_ho(rrc, ue, source_cell, target_cell); } /** @brief Generate the HandoverPreparationInformation to be carried @@ -395,6 +420,74 @@ static byte_array_t rrc_gNB_encode_HandoverCommand(gNB_RRC_UE_t *UE, gNB_RRC_INS return out; } +/** @brief Update cell association after handover: set PCell on target DU + * @param[in] rrc RRC instance + * @param[in] UE UE context + * @return true on success, false on failure + * + * Generic for all handover types (F1, N2, Xn). Behaviour depends on whether + * ho_context->source is set on this CU: + * - Source present (intra-CU): F1 HO, remove all serving cells belonging to + * the source DU, then add target cell as PCell. + * - Source absent (inter-CU): N2 or Xn handover (target CU created new UE + * on Handover Request). This UE context has no serving cells yet; add + * target cell as first PCell. */ +bool nr_rrc_update_cell_assoc_after_ho(gNB_RRC_INST *rrc, gNB_RRC_UE_t *UE) +{ + DevAssert(UE->ho_context); + DevAssert(UE->ho_context->target); + nr_ho_target_cu_t *target_ctx = UE->ho_context->target; + + /* Pre-check: ensure F1 UE data matches target cell before mutating state. */ + if (!cu_exists_f1_ue_data(UE->rrc_ue_id)) { + LOG_E(NR_RRC, "UE %d handover: no F1 UE data, cannot add PCell\n", UE->rrc_ue_id); + return false; + } + f1_ue_data_t ue_data = cu_get_f1_ue_data(UE->rrc_ue_id); + if (ue_data.du_assoc_id != target_ctx->cell->assoc_id) { + LOG_E(NR_RRC, + "UE %d handover: target cell assoc_id %d does not match F1 du_assoc_id %d\n", + UE->rrc_ue_id, + target_ctx->cell->assoc_id, + ue_data.du_assoc_id); + return false; + } + + if (UE->ho_context->source) { + /* F1 HO (intra-CU): migrate serving cells from source DU to target cell */ + nr_ho_source_cu_t *source_ctx = UE->ho_context->source; + + // Remove SCells that belong to the source DU during handover (cell group changes) + // This also removes PCell if it was on the source DU + rrc_remove_ue_scells_from_du(UE, source_ctx->cell->assoc_id); + + // Update PCell in serving_cells + ue_serving_cell_t *added = rrc_add_ue_serving_cell(UE, target_ctx->cell, RRC_PCELL_INDEX); + if (added == NULL) { + LOG_E(NR_RRC, + "UE %d handover: failed to add PCell (cell %ld) to serving cells\n", + UE->rrc_ue_id, + target_ctx->cell->info.cell_id); + return false; + } + } else { + /* Inter-CU (N2/Xn target): this UE context has no serving cells yet */ + const ue_serving_cell_t *existing_pcell = ue_get_pcell_entry(UE); + AssertFatal(existing_pcell == NULL, "UE context should not have PCell yet (inter-CU target)\n"); + + ue_serving_cell_t *added = rrc_add_ue_serving_cell(UE, target_ctx->cell, RRC_PCELL_INDEX); + if (added == NULL) { + LOG_E(NR_RRC, + "UE %d handover: failed to add PCell (cell %ld) to serving cells\n", + UE->rrc_ue_id, + target_ctx->cell->info.cell_id); + return false; + } + } + + return true; +} + /** @brief This callback is used by the target gNB * to trigger the Handover Request Acknowledge towards the AMF */ static void nr_rrc_n2_ho_acknowledge(gNB_RRC_INST *rrc, gNB_RRC_UE_t *UE) @@ -407,13 +500,23 @@ static void nr_rrc_n2_ho_acknowledge(gNB_RRC_INST *rrc, gNB_RRC_UE_t *UE) AssertFatal(previous_data.secondary_ue == -1, "there was already a DU present\n"); nr_rrc_apply_target_context(UE); + /* Update cell association after handover */ + nr_ho_target_cu_t *target = UE->ho_context->target; + if (!nr_rrc_update_cell_assoc_after_ho(rrc, UE)) { + ngap_handover_failure_t fail = {.amf_ue_ngap_id = UE->amf_ue_ngap_id, + .cause.type = NGAP_CAUSE_RADIO_NETWORK, + .cause.value = NGAP_CAUSE_RADIO_NETWORK_HO_FAILURE_IN_TARGET_5GC_NGRAN_NODE_OR_TARGET_SYSTEM}; + target->ho_failure(rrc, UE->rrc_ue_id, &fail); + return; + } + byte_array_t hoCommand = rrc_gNB_encode_HandoverCommand(UE, rrc); if (hoCommand.len < 0) { LOG_E(NR_RRC, "ASN1 message encoding failed: failed to generate Handover Command Message\n"); ngap_handover_failure_t fail = {.amf_ue_ngap_id = UE->amf_ue_ngap_id, .cause.type = NGAP_CAUSE_RADIO_NETWORK, .cause.value = NGAP_CAUSE_RADIO_NETWORK_HO_FAILURE_IN_TARGET_5GC_NGRAN_NODE_OR_TARGET_SYSTEM}; - UE->ho_context->target->ho_failure(rrc, UE->rrc_ue_id, &fail); + target->ho_failure(rrc, UE->rrc_ue_id, &fail); return; } else { LOG_D(NR_RRC, "HO LOG: Handover Command for UE %u Encoded (%ld bytes)\n", UE->rrc_ue_id, hoCommand.len); @@ -463,8 +566,7 @@ void nr_rrc_trigger_n2_ho_target(gNB_RRC_INST *rrc, gNB_RRC_UE_t *ue) ho_success_t success = nr_rrc_n2_ho_complete; ho_failure_t failure = nr_rrc_n2_ho_failure; - const nr_rrc_du_container_t *target_du = get_du_for_ue(rrc, ue->rrc_ue_id); - nr_initiate_handover(rrc, ue, NULL, target_du, &ue->ho_context->target->ue_ho_prep_info, ack, success, NULL, failure); + nr_initiate_handover(rrc, ue, NULL, &ue->ho_context->target->ue_ho_prep_info, ack, success, NULL, failure); FREE_AND_ZERO_BYTE_ARRAY(ue->ho_context->target->ue_ho_prep_info); NR_UE_NR_Capability_t *ue_cap = get_ue_nr_capability(ue->rnti, ue->ue_cap_buffer.buf, ue->ue_cap_buffer.len); @@ -495,7 +597,13 @@ void nr_rrc_trigger_n2_ho(gNB_RRC_INST *rrc, } ue->ho_context = alloc_ho_ctx(HO_CTX_SOURCE); - ue->ho_context->source->du = get_du_for_ue(rrc, ue->rrc_ue_id); + nr_rrc_cell_container_t *source_cell = rrc_get_pcell_for_ue(rrc, ue); + if (source_cell == NULL) { + LOG_E(NR_RRC, "No source cell found for UE %d\n", ue->rrc_ue_id); + free_byte_array(hoPrepInfo); + return; + } + ue->ho_context->source->cell = source_cell; ue->ho_context->source->ho_status_transfer = rrc_gNB_send_NGAP_ul_ran_status_transfer; ue->ho_context->source->ho_cancel = nr_rrc_n2_ho_cancel; @@ -514,42 +622,51 @@ void nr_HO_N2_trigger_telnet(gNB_RRC_INST *rrc, uint32_t neighbour_pci, uint32_t return; } gNB_RRC_UE_t *UE = &ue_context_p->ue_context; - - struct nr_rrc_du_container_t *du = get_du_for_ue(rrc, rrc_ue_id); + nr_rrc_du_container_t *du = get_du_for_ue(rrc, UE->rrc_ue_id); if (du == NULL) { LOG_E(NR_RRC, "N2 HO trigger failed for UE %d: Unknown DU\n", rrc_ue_id); return; } - uint16_t scell_pci = du->setup_req->cell[0].info.nr_pci; + nr_rrc_cell_container_t *pcell = rrc_get_pcell_for_ue(rrc, UE); + if (pcell == NULL) { + LOG_E(NR_RRC, "N2 HO trigger failed for UE %d: Unknown cell\n", rrc_ue_id); + return; + } + uint16_t scell_pci = pcell->info.pci; // Simulate handover on the same cell (testing purposes) if (neighbour_pci == scell_pci) { LOG_I(NR_RRC, "UE %d: trigger handover on the same cell PCI=%d\n", rrc_ue_id, neighbour_pci); nr_neighbour_cell_t neighbourConfig = { .isIntraFrequencyNeighbour = true, - .gNB_ID = du->setup_req->gNB_DU_id, - .nrcell_id = du->setup_req->cell[0].info.nr_cellid, - .physicalCellId = du->setup_req->cell[0].info.nr_pci, - .plmn = du->setup_req->cell[0].info.plmn, - .subcarrierSpacing = du->setup_req->cell[0].info.tdd.tbw.scs, + .gNB_ID = du->gNB_DU_id, + .nrcell_id = pcell->info.cell_id, + .physicalCellId = pcell->info.pci, + .plmn = pcell->info.plmn, + .subcarrierSpacing = pcell->info.mode == NR_MODE_TDD ? pcell->info.tdd.dlul.scs : pcell->info.fdd.dl.scs, }; nr_rrc_trigger_n2_ho(rrc, UE, neighbour_pci, &neighbourConfig); return; } - const f1ap_served_cell_info_t *scell_du = get_cell_information_by_phycellId(scell_pci); - DevAssert(scell_du); LOG_I(NR_RRC, "UE %d: triggered N2 HO, source PCI=%d to target PCI=%d\n", rrc_ue_id, scell_pci, neighbour_pci); - const neighbour_cell_configuration_t *cell = get_neighbour_cell_config(rrc, scell_du->nr_cellid); - const nr_neighbour_cell_t *neighbour = get_neighbour_cell_by_pci(cell, neighbour_pci); + const neighbour_cell_configuration_t *cell = get_neighbour_cell_config(rrc, pcell->info.cell_id); + if (cell == NULL) { + LOG_E(NR_RRC, + "N2 HO trigger failed for UE %d: could not find neighbour cell with Cell ID=%ld\n", + rrc_ue_id, + pcell->info.cell_id); + return; + } + const nr_neighbour_cell_t *neighbour = get_neighbour_cell_by_pci(cell, neighbour_pci); if (neighbour == NULL) { LOG_E(NR_RRC, "N2 HO trigger failed for UE %d: could not find neighbour cell with PCI=%d\n", rrc_ue_id, neighbour_pci); return; } - nr_rrc_trigger_n2_ho(rrc, UE, scell_du->nr_pci, neighbour); + nr_rrc_trigger_n2_ho(rrc, UE, pcell->info.pci, neighbour); } // This function detects if there are at least two different ssbFrequency values, and if so, returns meas_timing_config; diff --git a/openair2/RRC/NR/rrc_gNB_mobility.h b/openair2/RRC/NR/rrc_gNB_mobility.h index 456730060e..d4a51efeb9 100644 --- a/openair2/RRC/NR/rrc_gNB_mobility.h +++ b/openair2/RRC/NR/rrc_gNB_mobility.h @@ -28,7 +28,7 @@ /* forward declarations */ typedef struct gNB_RRC_INST_s gNB_RRC_INST; typedef struct gNB_RRC_UE_s gNB_RRC_UE_t; -typedef struct nr_rrc_du_container_t nr_rrc_du_container_t; +typedef struct nr_rrc_cell_container_t nr_rrc_cell_container_t; typedef struct NR_CellGroupConfig NR_CellGroupConfig_t; @@ -40,8 +40,8 @@ typedef int (*ho_status_transfer_t)(gNB_RRC_INST *rrc, const e1_pdcp_status_info_t *pdcp_status); typedef struct nr_ho_source_cu { - /// pointer to the (source) DU structure - const nr_rrc_du_container_t *du; + /// pointer to the (source) cell container + const nr_rrc_cell_container_t *cell; /// (source) DU UE ID; in F1, the DU UE ID will change to the new (target) DU /// UE ID during handover, and the CU needs to keep track of this in case of /// reestablishment @@ -67,8 +67,8 @@ typedef void (*ho_failure_t)(gNB_RRC_INST *rrc, uint32_t gnb_ue_id, ngap_handove typedef void (*ho_trigger_t)(gNB_RRC_INST *rrc, gNB_RRC_UE_t *ue); typedef struct nr_ho_target_cu { - /// pointer to the (target) DU structure - const nr_rrc_du_container_t *du; + /// pointer to the (target) cell container + const nr_rrc_cell_container_t *cell; /// (target) DU UE ID; the (source) DU UE ID will change to the new (target) /// DU UE ID after sending a reconfiguration, which is later than when /// receiving this ID @@ -95,7 +95,10 @@ typedef struct nr_handover_context_s { typedef enum { HO_CTX_BOTH, HO_CTX_SOURCE, HO_CTX_TARGET } ho_ctx_type_t; nr_handover_context_t *alloc_ho_ctx(ho_ctx_type_t type); -void nr_rrc_trigger_f1_ho(gNB_RRC_INST *rrc, gNB_RRC_UE_t *ue, nr_rrc_du_container_t *source_du, nr_rrc_du_container_t *target_du); +void nr_rrc_trigger_f1_ho(gNB_RRC_INST *rrc, + gNB_RRC_UE_t *ue, + const nr_rrc_cell_container_t *source_cell, + const nr_rrc_cell_container_t *target_cell); void nr_rrc_finalize_ho(gNB_RRC_UE_t *ue); void nr_rrc_n2_ho_failure(gNB_RRC_INST *rrc, uint32_t gnb_ue_id, ngap_handover_failure_t *msg); @@ -112,4 +115,6 @@ byte_array_t *get_meas_timing_config(const NR_MeasurementTimingConfiguration_t * void nr_rrc_apply_target_context(gNB_RRC_UE_t *UE); +bool nr_rrc_update_cell_assoc_after_ho(gNB_RRC_INST *rrc, gNB_RRC_UE_t *UE); + #endif /* RRC_GNB_MOBILITY_H_ */ diff --git a/openair2/RRC/NR/rrc_gNB_nsa.c b/openair2/RRC/NR/rrc_gNB_nsa.c index 88f06338d7..18513d14c5 100644 --- a/openair2/RRC/NR/rrc_gNB_nsa.c +++ b/openair2/RRC/NR/rrc_gNB_nsa.c @@ -62,6 +62,7 @@ #include "openair3/SECU/key_nas_deriver.h" #include "openair2/SDAP/nr_sdap/nr_sdap_entity.h" #include "rrc_gNB_du.h" +#include "rrc_cell_management.h" #include "rlc.h" #include "s1ap_messages_types.h" #include "tree.h" @@ -368,20 +369,37 @@ void rrc_add_nsa_user(gNB_RRC_INST *rrc, x2ap_ENDC_sgnb_addition_req_t *m, sctp_ *ue_agg_mbr_ul = 1000000000; byte_array_t *cg_configinfo = malloc_or_fail(sizeof(*cg_configinfo)); *cg_configinfo = cgci; + + f1_ue_data_t ue_data = cu_get_f1_ue_data(UE->rrc_ue_id); + RETURN_IF_INVALID_ASSOC_ID(ue_data.du_assoc_id); + nr_rrc_du_container_t *du = get_du_by_assoc_id(rrc, ue_data.du_assoc_id); + if (!du) { + LOG_E(NR_RRC, "UE %d: no valid cell for UE context setup (DU assoc_id %d not found)\n", UE->rrc_ue_id, ue_data.du_assoc_id); + return; + } + // Get the first cell from the DU (NSA assumption: 1 cell per DU) + if (seq_arr_size(&du->cells) == 0) { + LOG_E(NR_RRC, "UE %d: no cells available in DU (assoc_id %d)\n", UE->rrc_ue_id, ue_data.du_assoc_id); + return; + } + nr_rrc_cell_container_t *ue_cell = *(nr_rrc_cell_container_t **)seq_arr_at(&du->cells, 0); + // Add the cell as PCell + if (rrc_add_ue_serving_cell(UE, ue_cell, RRC_PCELL_INDEX) == NULL) { + LOG_E(NR_RRC, "UE %d: failed to add PCell (cell %ld) for UE context setup\n", UE->rrc_ue_id, ue_cell->info.cell_id); + return; + } f1ap_ue_context_setup_req_t req = { .gNB_CU_ue_id = UE->rrc_ue_id, .plmn.mcc = rrc->configuration.plmn[0].mcc, .plmn.mnc = rrc->configuration.plmn[0].mnc, .plmn.mnc_digit_length = rrc->configuration.plmn[0].mnc_digit_length, - .nr_cellid = rrc->nr_cellid, - .servCellIndex = 0, + .nr_cellid = ue_cell->info.cell_id, + .servCellIndex = RRC_PCELL_INDEX, .drbs_len = 1, .drbs = drb, .cu_to_du_rrc_info.cg_configinfo = cg_configinfo, .gnb_du_ue_agg_mbr_ul = ue_agg_mbr_ul, }; - f1_ue_data_t ue_data = cu_get_f1_ue_data(UE->rrc_ue_id); - RETURN_IF_INVALID_ASSOC_ID(ue_data.du_assoc_id); rrc->mac_rrc.ue_context_setup_request(ue_data.du_assoc_id, &req); free_ue_context_setup_req(&req); }