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:
@@ -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");
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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__)
|
||||
|
||||
@@ -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
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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_ */
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user