RRC multi-cell: complete migration to cell container architecture

Finalize the migration from single-cell-per-DU to the multi-cell
architecture: remove legacy DU/cell fields and refactor RRC procedures
to use cell containers and UE serving cells instead of DU-based lookups.

Data structure changes:
- Remove nr_cellid from gNB_RRC_UE_t; cell identity comes from the UE's
  serving_cells (PCell entry via ue_get_pcell_entry(ue)). UE_LOG_ARGS
  and LOG_UE_* macros take a PCell entry pointer for null-safe cell_id
  in logs; all call sites pass ue_get_pcell_entry(ue).
- Remove setup_req, mib, sib1, mtc from nr_rrc_du_container_t (held in
  cell containers); remove nr_cellid from gNB_RRC_INST and its
  assignment in gnb_config.c.

Function and signature updates:
- get_ssb_arfcn(): take cell container instead of DU.
- label_intra_frequency_neighbours(): drop du parameter, use cell only.
- Handover: Handover context (source and target) now stores cell
  pointers (source->cell, target->cell) instead of DU pointers. When
  the context is created (N2: on Handover Request; F1: in
  nr_rrc_trigger_f1_ho), the target cell is set in ho_context->target->cell
  and, for F1, the source cell in ho_context->source->cell.
  nr_initiate_handover() takes only source_cell and reads the target
  from ho_ctx->target->cell, so it no longer takes a target parameter.
  The target is known at handover creation time, while the UE’s
  serving_cells are only updated after F1 UE Context Setup Response.
- nr_rrc_trigger_f1_ho() and the N2 handover path pass cell pointers
  and set ho_context->target->cell (and for F1, source->cell) when
  the handover is triggered.

Procedure refactoring:
- Lookups: replace get_du_by_cell_id() with get_cell_by_cell_id()/
  get_cell_by_pci(); remove get_du_by_cell_id() and
  get_cell_information_by_phycellId().
  Rationale: cell-specific data (MIB, SIB1, mtc, info) now lives in
  cell containers, and a DU can host multiple cells. Callers need the
  cell (to use cell->info, cell->mtc, etc.), not the DU. Looking up
  by cell_id therefore returns the cell container
  (get_cell_by_cell_id()); when the DU is needed, use
  cell->assoc_id and get_du_by_assoc_id(). Looking up by PCI is
  scoped (e.g. get_cell_by_pci_for_du() for a given DU, or via the
  UE’s serving cells) because PCI is not globally unique.
- Setup/Reestablishment: resolve cell, register as PCell via
  rrc_add_ue_serving_cell(); reestablishment resolves cell by PCI
  (rrc_get_cell_by_pci_for_du), then rrc_remove_ue_scells_from_du +
  rrc_add_ue_serving_cell; validate_ue_cell_association() after update.
- Measurement config: use cell->info (SCS/band) and cell->mtc.
- Handover: use cell containers end-to-end; after UE context
  modification response call nr_rrc_update_cell_assoc_after_ho().
- NGAP: cell_id and TAC from UE PCell via rrc_get_pcell_for_ue();
  Handover Notify: get cell for message content, use get_du_for_ue() for
  DU existence check; paging resolves UE by 5G-S-TMSI-Part1 and uses that
  UE's PCell (rrc_get_pcell_for_ue) for F1AP_PAGING_IND nr_cellid.

Performance and robustness:
- Use rrc_get_pcell_for_ue() when cell data is needed; use
  get_du_for_ue() when only the DU is needed (F1/N2 HO trigger,
  Handover Notify DU check) for efficiency.
- Reestablishment: remove old PCell before adding new; error handling
  for PCell add and validation.

Code cleanup:
- F1 setup: stop copying/storing setup request in DU container; F1
  disconnect only frees cell containers via rrc_free_cell_container().
- dump_du_info(): iterate du->cells and show all cells per DU.
- telnetsrv_ci.c: use du->gNB_DU_id directly and fix format (%ld).
- UeContextSetupRequest servCellIndex set to RRC_PCELL_INDEX; remove
  servCellIndex from F1AP UE context modification request.
This commit is contained in:
Guido Casati
2026-02-05 11:44:10 +01:00
parent 5980dbcb40
commit d471046623
12 changed files with 444 additions and 230 deletions
+1 -1
View File
@@ -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");
-2
View File
@@ -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) {
+5 -9
View File
@@ -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__)
+2 -2
View File
@@ -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
+156 -77
View File
@@ -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;
+37 -12
View File
@@ -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;
+1
View File
@@ -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)
{
+48 -72
View File
@@ -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);
}
}
}
}
}
+2 -3
View File
@@ -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);
+159 -42
View File
@@ -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;
+11 -6
View File
@@ -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_ */
+22 -4
View File
@@ -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);
}