diff --git a/openair2/RRC/NR/rrc_cell_management.c b/openair2/RRC/NR/rrc_cell_management.c index eac0f38260..0df2665c04 100644 --- a/openair2/RRC/NR/rrc_cell_management.c +++ b/openair2/RRC/NR/rrc_cell_management.c @@ -27,6 +27,7 @@ #include "common/utils/alg/find.h" // for find_if #include "common/utils/ds/seq_arr.h" // for seq_arr_t, seq_arr_free #include "common/utils/LOG/log.h" // for LOG_I, LOG_W, LOG_E +#include "openair2/F1AP/f1ap_ids.h" // for cu_exists_f1_ue_data, cu_get_f1_ue_data #include "asn_application.h" // for ASN_STRUCT_FREE #include "NR_MIB.h" // for asn_DEF_NR_MIB #include "NR_SIB1.h" // for asn_DEF_NR_SIB1 @@ -263,3 +264,148 @@ void rrc_free_cell_container(nr_rrc_cell_container_t *cell) ASN_STRUCT_FREE(asn_DEF_NR_MeasurementTimingConfiguration, cell->mtc); free(cell); } + +static bool eq_serving_cell_id(const void *vval, const void *vit) +{ + const uint8_t *id = (const uint8_t *)vval; + const ue_serving_cell_t *elem = (const ue_serving_cell_t *)vit; + return elem->serving_cell_id == *id; +} + +/** @brief Get serving cell from UE's serving_cells array by serving_cell_id + * @param[in] ue UE context + * @param[in] serving_cell_id ServCellIndex (0 = PCell, 1-31 = SCell) + * @return Pointer to serving cell entry, or NULL if not found */ +ue_serving_cell_t *rrc_get_ue_serving_cell_by_id(const gNB_RRC_UE_t *ue, uint8_t serving_cell_id) +{ + DevAssert(ue); + elm_arr_t elm = find_if((seq_arr_t *)&ue->serving_cells, &serving_cell_id, eq_serving_cell_id); + if (elm.found) + return (ue_serving_cell_t *)elm.it; + return NULL; +} + +/** @brief Add a serving cell to UE's serving_cells array + * @param[in] ue UE context + * @param[in] cell Cell container (nci and assoc_id taken from here) + * @param[in] serving_cell_id ServCellIndex (0 = PCell, 1-31 = SCell) + * @return Pointer to the added or existing entry, or NULL on error + * @note Uniqueness: (serving_cell_id, nci) must be unique. Same pair returns existing; + * same serving_cell_id, different nci returns NULL. */ +ue_serving_cell_t *rrc_add_ue_serving_cell(gNB_RRC_UE_t *ue, const nr_rrc_cell_container_t *cell, uint8_t serving_cell_id) +{ + DevAssert(ue); + DevAssert(serving_cell_id < RRC_MAX_NUM_SERVING_CELLS); + + // Check maximum number of items in list + size_t current_count = seq_arr_size(&ue->serving_cells); + if (current_count >= RRC_MAX_NUM_SERVING_CELLS) { + LOG_E(NR_RRC, "Maximum number of serving cells (%d) reached\n", RRC_MAX_NUM_SERVING_CELLS); + return NULL; + } + + // Check if serving cell with this serving_cell_id already exists + ue_serving_cell_t *existing = rrc_get_ue_serving_cell_by_id(ue, serving_cell_id); + if (existing != NULL) { + // If it's the same nci, return existing + if (existing->nci == cell->info.cell_id) { + LOG_D(NR_RRC, "Serving cell %d already has cell %ld\n", serving_cell_id, cell->info.cell_id); + return existing; + } + // Reject if already exists with different nci + LOG_E(NR_RRC, "Serving cell with serving_cell_id %d already exists with different nci\n", serving_cell_id); + return NULL; + } + + LOG_I(NR_RRC, + "Adding new %s (cell %ld, assoc_id %d)\n", + serving_cell_id == RRC_PCELL_INDEX ? "PCell" : "SCell", + cell->info.cell_id, + cell->assoc_id); + ue_serving_cell_t new_cell = {.nci = cell->info.cell_id, .serving_cell_id = serving_cell_id, .assoc_id = cell->assoc_id}; + + size_t elt_size = sizeof(ue_serving_cell_t); + if (serving_cell_id == RRC_PCELL_INDEX) { + /* PCell must match F1 UE data (serving_cells and F1 UE data stay in sync). */ + DevAssert(cu_exists_f1_ue_data(ue->rrc_ue_id)); + DevAssert(cu_get_f1_ue_data(ue->rrc_ue_id).du_assoc_id == cell->assoc_id); + /* Ensure PCell is always at index 0 in serving_cells */ + size_t n = seq_arr_size(&ue->serving_cells); + seq_arr_push_back(&ue->serving_cells, &new_cell, elt_size); + if (n > 0) { + /* If there are existing cells, shift them right to make room for the new PCell */ + void *first = seq_arr_front(&ue->serving_cells); + memmove((uint8_t *)first + elt_size, first, n * elt_size); + memcpy(first, &new_cell, elt_size); + } + } else { + /* SCells are appended after PCell (if any) */ + seq_arr_push_back(&ue->serving_cells, &new_cell, elt_size); + } + return rrc_get_ue_serving_cell_by_id(ue, serving_cell_id); +} + +/** @brief Remove serving cells that belong to a specific DU + * @param[in] ue UE context + * @param[in] assoc_id Association ID of the DU + * @note Removes all serving cells (including PCell) that belong to the specified DU. + * Uses stored assoc_id for O(k) comparison without cell lookup. */ +void rrc_remove_ue_scells_from_du(gNB_RRC_UE_t *ue, sctp_assoc_t assoc_id) +{ + DevAssert(ue); + LOG_I(NR_RRC, "Removing SCells from DU (assoc_id=%d) for UE %d\n", assoc_id, ue->rrc_ue_id); + + // Collect indices of SCells to delete (don't modify array while iterating) + uint32_t indices_to_delete[RRC_MAX_NUM_SERVING_CELLS]; + size_t num_to_delete = 0; + uint32_t index = 0; + + FOR_EACH_SEQ_ARR (ue_serving_cell_t *, scell, &ue->serving_cells) { + // Use stored assoc_id for direct comparison + if (scell->assoc_id == assoc_id) { + indices_to_delete[num_to_delete++] = index; + } + index++; + } + + // Now delete the collected serving cells from end to beginning for safety + for (size_t i = num_to_delete; i > 0; i--) { + uint32_t idx = indices_to_delete[i - 1]; + void *it = seq_arr_at(&ue->serving_cells, idx); + seq_arr_erase(&ue->serving_cells, it); + } + +} + +/** @brief Get PCell serving cell entry for a UE based on serving_cells + * @param[in] ue UE context + * @return Pointer to serving cell entry for PCell, or NULL if not found + * @note PCell is always at index 0. */ +const ue_serving_cell_t *ue_get_pcell_entry(const gNB_RRC_UE_t *ue) +{ + DevAssert(ue); + size_t count = seq_arr_size(&ue->serving_cells); + if (count == 0) + return NULL; + + /* PCell is always the first serving cell */ + const ue_serving_cell_t *first = seq_arr_front(&ue->serving_cells); + DevAssert(first != NULL); + DevAssert(first->serving_cell_id == RRC_PCELL_INDEX); + return first; +} + +/** @brief Get UE's PCell container from the global cell tree + * @param[in] rrc RRC instance (global cell tree) + * @param[in] ue UE context + * @return Pointer to PCell container, or NULL if UE has no PCell or cell not in tree */ +nr_rrc_cell_container_t *rrc_get_pcell_for_ue(gNB_RRC_INST *rrc, const gNB_RRC_UE_t *ue) +{ + DevAssert(rrc != NULL); + DevAssert(ue != NULL); + const ue_serving_cell_t *pcell_entry = ue_get_pcell_entry(ue); + if (pcell_entry == NULL) + return NULL; + return get_cell_by_cell_id(&rrc->cells, pcell_entry->nci); +} + diff --git a/openair2/RRC/NR/rrc_cell_management.h b/openair2/RRC/NR/rrc_cell_management.h index 76790fda4f..99071cceb2 100644 --- a/openair2/RRC/NR/rrc_cell_management.h +++ b/openair2/RRC/NR/rrc_cell_management.h @@ -27,6 +27,16 @@ #include #include +/** @brief ServCellIndex value for Primary Cell (TS 38.331) + * @note ServCellIndex is a short identity used to uniquely identify a serving cell + * (PCell, PSCell, or SCell) across cell groups. Value 0 applies for the PCell. */ +#define RRC_PCELL_INDEX 0 + +/** @brief Maximum number of serving cells (TS 38.331) + * @note maxNrofServingCells = 32 (1 PCell + up to 31 SCells) + * @note ServCellIndex range: INTEGER (0..maxNrofServingCells-1) = 0..31 */ +#define RRC_MAX_NUM_SERVING_CELLS 32 + // Generate RB tree functions (prototypes) int rrc_cell_cmp(const struct nr_rrc_cell_container_t *a, const struct nr_rrc_cell_container_t *b); RB_PROTOTYPE(rrc_cell_tree, nr_rrc_cell_container_t, entries, rrc_cell_cmp); @@ -53,4 +63,11 @@ nr_rrc_cell_container_t *rrc_add_cell_to_du(seq_arr_t *cells, nr_rrc_cell_contai // Cell cleanup void rrc_free_cell_container(nr_rrc_cell_container_t *cell); +// UE serving cell functions +ue_serving_cell_t *rrc_add_ue_serving_cell(gNB_RRC_UE_t *ue, const nr_rrc_cell_container_t *cell, uint8_t serving_cell_id); +void rrc_remove_ue_scells_from_du(gNB_RRC_UE_t *ue, sctp_assoc_t assoc_id); +ue_serving_cell_t *rrc_get_ue_serving_cell_by_id(const gNB_RRC_UE_t *ue, uint8_t serving_cell_id); +const ue_serving_cell_t *ue_get_pcell_entry(const gNB_RRC_UE_t *ue); +nr_rrc_cell_container_t *rrc_get_pcell_for_ue(gNB_RRC_INST *rrc, const gNB_RRC_UE_t *ue); + #endif /* RRC_CELL_MANAGEMENT_H_ */ diff --git a/openair2/RRC/NR/rrc_gNB.c b/openair2/RRC/NR/rrc_gNB.c index fd92bca995..6b40d37b36 100644 --- a/openair2/RRC/NR/rrc_gNB.c +++ b/openair2/RRC/NR/rrc_gNB.c @@ -2664,6 +2664,7 @@ static void rrc_delete_ue_data(gNB_RRC_UE_t *UE) UE->redcap_cap = NULL; seq_arr_free(&UE->pduSessions, free_pdusession); seq_arr_free(&UE->drbs, free_drb); + seq_arr_free(&UE->serving_cells, NULL); } void rrc_remove_ue(gNB_RRC_INST *rrc, rrc_gNB_ue_context_t *ue_context_p) diff --git a/openair2/RRC/NR/rrc_gNB_UE_context.c b/openair2/RRC/NR/rrc_gNB_UE_context.c index 01163e319f..6c47a7478e 100644 --- a/openair2/RRC/NR/rrc_gNB_UE_context.c +++ b/openair2/RRC/NR/rrc_gNB_UE_context.c @@ -41,6 +41,7 @@ #include "openair2/F1AP/f1ap_ids.h" #include "tree.h" #include "rrc_gNB_radio_bearers.h" +#include "rrc_cell_management.h" static void rrc_gNB_ue_context_update_time(rrc_gNB_ue_context_t *ctxt) { @@ -224,6 +225,8 @@ rrc_gNB_ue_context_t *rrc_gNB_create_ue_context(sctp_assoc_t assoc_id, seq_arr_init(&ue->pduSessions, sizeof(rrc_pdu_session_param_t)); // Initialise setup DRBs list seq_arr_init(&ue->drbs, sizeof(drb_t)); + // Initialise serving cells list + seq_arr_init(&ue->serving_cells, sizeof(ue_serving_cell_t)); RB_INSERT(rrc_nr_ue_tree_s, &rrc_instance_pP->rrc_ue_head, ue_context_p); LOG_UE_EVENT(ue,