RRC multi-cell: add UE serving cell management functions
Add functions to manage UE serving cells so the RRC can track which cells
a UE is using (PCell and SCells) for multi-cell UE context and future
SCell management.
- rrc_cell_management.c / rrc_cell_management.h:
* rrc_get_ue_serving_cell_by_id(): return serving cell by servCellIndex
(O(k) linear search, k ≤ 32).
* rrc_add_ue_serving_cell(ue, cell, serving_cell_id): add entry to UE
serving_cells with validation: reject duplicate servCellIndex with
different nci, enforce max 32 serving cells (TS 38.331), keep PCell
at index 0 for O(1) access; return existing entry if same
servCellIndex and nci.
* rrc_remove_ue_scells_from_du(ue, assoc_id): remove all serving cells
for the given DU (handover/DU disconnect); use stored assoc_id for
O(k) comparison.
* ue_get_pcell_entry(): return PCell entry (first element, O(1)).
* rrc_get_pcell_for_ue(): return PCell container from cell tree using
UE's PCell entry.
* Header: RRC_PCELL_INDEX (0), RRC_MAX_NUM_SERVING_CELLS (32), and
declarations for the above.
- UE context lifecycle:
* rrc_gNB_create_ue_context(): seq_arr_init(serving_cells).
* rrc_delete_ue_data(): seq_arr_free(serving_cells).
This commit is contained in:
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -27,6 +27,16 @@
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
/** @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_ */
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user