diff --git a/openair2/RRC/NR/rrc_gNB_du.c b/openair2/RRC/NR/rrc_gNB_du.c index e2806edf60..51abc95dd0 100644 --- a/openair2/RRC/NR/rrc_gNB_du.c +++ b/openair2/RRC/NR/rrc_gNB_du.c @@ -592,3 +592,31 @@ void dump_du_info(const gNB_RRC_INST *rrc, FILE *f) } } } + +nr_rrc_du_container_t *find_target_du(gNB_RRC_INST *rrc, sctp_assoc_t source_assoc_id) +{ + nr_rrc_du_container_t *target_du = NULL; + nr_rrc_du_container_t *it = NULL; + bool next_du = false; + RB_FOREACH (it, rrc_du_tree, &rrc->dus) { + if (next_du == false && source_assoc_id != it->assoc_id) { + continue; + } else if (source_assoc_id == it->assoc_id) { + next_du = true; + } else { + target_du = it; + break; + } + } + if (target_du == NULL) { + RB_FOREACH (it, rrc_du_tree, &rrc->dus) { + if (source_assoc_id == it->assoc_id) { + continue; + } else { + target_du = it; + break; + } + } + } + return target_du; +} diff --git a/openair2/RRC/NR/rrc_gNB_du.h b/openair2/RRC/NR/rrc_gNB_du.h index 2c0b5574ce..55a0d7c2c8 100644 --- a/openair2/RRC/NR/rrc_gNB_du.h +++ b/openair2/RRC/NR/rrc_gNB_du.h @@ -48,6 +48,8 @@ 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); +struct nr_rrc_du_container_t *find_target_du(struct gNB_RRC_INST_s *rrc, sctp_assoc_t source_assoc_id); + // the assoc_id might be 0 (if the DU goes offline). Below helper macro to // print an error and return from the function in that case #define RETURN_IF_INVALID_ASSOC_ID(assoc_id) \ diff --git a/openair2/RRC/NR/rrc_gNB_mobility.c b/openair2/RRC/NR/rrc_gNB_mobility.c index 72ae57b67e..cb6e851ebb 100644 --- a/openair2/RRC/NR/rrc_gNB_mobility.c +++ b/openair2/RRC/NR/rrc_gNB_mobility.c @@ -22,8 +22,16 @@ #include #include "assertions.h" + #include "rrc_gNB_mobility.h" +#include "nr_rrc_proto.h" +#include "rrc_gNB_du.h" +#include "rrc_gNB_radio_bearers.h" +#include "rrc_gNB_UE_context.h" +#include "openair2/F1AP/f1ap_ids.h" +#include "MESSAGES/asn1_msg.h" + typedef enum { HO_CTX_BOTH, HO_CTX_SOURCE, HO_CTX_TARGET } ho_ctx_type_t; static nr_handover_context_t *alloc_ho_ctx(ho_ctx_type_t type) { @@ -46,3 +54,190 @@ static void free_ho_ctx(nr_handover_context_t *ho_ctx) free(ho_ctx->target); free(ho_ctx); } + +/* \brief Initiate a handover of UE to a specific target cell handled by this + * CU. + * \param ue a UE context for which the handover should be triggered. The UE + * context must be non-null (if the HO request comes from "outside" (N2, Xn), + * the UE contex must be created first. + * \param target_du the DU towards which to handover. Note: currently, the CU + * is limited to one cell per DU, so DU and cell are equivalent here. + * \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, + uint8_t *ho_prep_buf, + uint32_t ho_prep_len, + ho_req_ack_t ack, + ho_success_t success, + ho_cancel_t cancel) +{ + DevAssert(rrc != NULL); + DevAssert(ue != NULL); + DevAssert(target_du != NULL); + // source_du might be NULL -> inter-CU handover + DevAssert(ho_prep_buf != NULL && ho_prep_len > 0); + + if (ue->ho_context != NULL) { + LOG_E(NR_RRC, "handover for UE %u ongoing, cannot trigger new\n", ue->rrc_ue_id); + return; + } + // if any reconfiguration is ongoing, abort handover request + for (int i = 0; i < NR_RRC_TRANSACTION_IDENTIFIER_NUMBER; ++i) { + if (ue->xids[i] != RRC_ACTION_NONE) { + LOG_E(NR_RRC, "UE %d: ongoig transaction %d (action %d), cannot trigger handover\n", ue->rrc_ue_id, i, ue->xids[i]); + return; + } + } + + // if we know the source DU (F1 handover), allocate the context for it, + // otherwise only for the target DU (N2, Xn) + ho_ctx_type_t ctx_type = source_du != NULL ? HO_CTX_BOTH : HO_CTX_TARGET; + nr_handover_context_t *ho_ctx = alloc_ho_ctx(ctx_type); + ho_ctx->target->du = target_du; + // we will know target->{du_ue_id,new_rnti} once we have UE ctxt setup + // response + ho_ctx->target->ho_req_ack = ack; + ho_ctx->target->ho_success = success; + + 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 + ho_ctx->source->ho_cancel = cancel; + + ho_ctx->source->du = source_du; + ho_ctx->source->du_ue_id = ue_data.secondary_ue; + ho_ctx->source->old_rnti = ue->rnti; + } + ue->ho_context = ho_ctx; + LOG_A(NR_RRC, + "Handover triggered for UE %u/RNTI %04x towards DU %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); + + cu_to_du_rrc_information_t cu2du = { + .handoverPreparationInfo = ho_prep_buf, + .handoverPreparationInfo_length = ho_prep_len, + }; + + f1ap_drb_to_be_setup_t drbs[32] = {0}; // maximum DRB can be 32 + int nb_drb = 0; + for (int i = 0; i < 32; ++i) { /* for each DRB */ + drb_t *rrc_drb = &ue->established_drbs[i]; + if (rrc_drb->status == DRB_INACTIVE) + continue; + + f1ap_drb_to_be_setup_t *drb = &drbs[nb_drb]; + nb_drb++; + drb->drb_id = rrc_drb->drb_id; + drb->rlc_mode = rrc->configuration.um_on_default_drb ? F1AP_RLC_MODE_UM_BIDIR : F1AP_RLC_MODE_AM; + memcpy(&drb->up_ul_tnl[0].tl_address, &rrc_drb->cuup_tunnel_config.addr.buffer, sizeof(uint8_t) * 4); + drb->up_ul_tnl[0].port = rrc->eth_params_s.my_portd; + drb->up_ul_tnl[0].teid = rrc_drb->cuup_tunnel_config.teid; + drb->up_ul_tnl_length = 1; + + /* fetch an existing PDU session for this DRB */ + rrc_pdu_session_param_t *pdu = find_pduSession_from_drbId(ue, drb->drb_id); + AssertFatal(pdu != NULL, "no PDU session for DRB ID %ld\n", drb->drb_id); + drb->nssai = pdu->param.nssai; + + // for the moment, we only support one QoS flow. Put a reminder in case + // this changes + AssertFatal(pdu->param.nb_qos == 1, "only 1 Qos flow supported\n"); + drb->drb_info.flows_to_be_setup_length = 1; + drb->drb_info.flows_mapped_to_drb = calloc(1, sizeof(f1ap_flows_mapped_to_drb_t)); + AssertFatal(drb->drb_info.flows_mapped_to_drb, "could not allocate memory\n"); + int qfi = rrc_drb->cnAssociation.sdap_config.mappedQoS_FlowsToAdd[0]; + DevAssert(qfi > 0); + drb->drb_info.flows_mapped_to_drb[0].qfi = qfi; + pdusession_level_qos_parameter_t *in_qos_char = get_qos_characteristics(qfi, pdu); + drb->drb_info.flows_mapped_to_drb[0].qos_params.qos_characteristics = get_qos_char_from_qos_flow_param(in_qos_char); + + /* the DRB QoS parameters: we just reuse the ones from the first flow */ + drb->drb_info.drb_qos = drb->drb_info.flows_mapped_to_drb[0].qos_params; + } + + f1ap_srb_to_be_setup_t srbs[2] = {{.srb_id = 1, .lcid = 1}, {.srb_id = 2, .lcid = 2}}; + f1ap_served_cell_info_t *cell_info = &target_du->setup_req->cell[0].info; + RETURN_IF_INVALID_ASSOC_ID(target_du->assoc_id); + f1ap_ue_context_setup_t ue_context_setup_req = { + .gNB_CU_ue_id = ue->rrc_ue_id, + .gNB_DU_ue_id = 0, /* TODO: this should be optional! */ + .plmn.mcc = cell_info->plmn.mcc, + .plmn.mnc = cell_info->plmn.mnc, + .plmn.mnc_digit_length = cell_info->plmn.mnc_digit_length, + .nr_cellid = cell_info->nr_cellid, + .servCellId = 0, // TODO: correct value? + .srbs_to_be_setup_length = 2, + .srbs_to_be_setup = srbs, + .drbs_to_be_setup_length = nb_drb, + .drbs_to_be_setup = drbs, + .cu_to_du_rrc_information = &cu2du, + }; + rrc->mac_rrc.ue_context_setup_request(target_du->assoc_id, &ue_context_setup_req); +} + +/* +void nr_rrc_trigger_n2_ho(gNB_RRC_INST *rrc, int nr_cgi, uint8_t *ho_prep_info, uint32_t *ho_prep_len, void *pdu_session) +{ + // call from outside (via NGAP message), with the request for handover for a + // (any) UE. This handler should, in order: + // + // 1. look up the target DU =du= via get_du_by_cell_id(nr_cgi) + // (and reject the request if it can't find the DU) + // 2. create a context for a new UE =UE=, based on pdu_session information + // 3. call nr_initiate_handover(rrc, UE, NULL, du, ho_prep_info, ho_prep_len); + // + // after success of the DU, send the result via =success_ptr= to the source + // CU. +} +*/ + +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) +{ + DevAssert(rrc != NULL); + DevAssert(ue != NULL); + + uint8_t buf[NR_RRC_BUF_SIZE]; + int size = do_NR_HandoverPreparationInformation(ue->ue_cap_buffer.buf, ue->ue_cap_buffer.len, buf, sizeof buf); + + // 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 + // information (Source NG-RAN Node to Target NG-RAN Node Transparent Container) + // here: target Cell is preselected, target CU has access to UE information + // and therefore also the PDU sessions. Orig RRC reconfiguration should be in + // handover preparation information + ho_req_ack_t ack = NULL; + ho_success_t success = NULL; + ho_cancel_t cancel = NULL; + nr_initiate_handover(rrc, ue, source_du, target_du, buf, size, ack, success, cancel); +} + +void nr_HO_F1_trigger_telnet(gNB_RRC_INST *rrc, uint32_t rrc_ue_id) +{ + rrc_gNB_ue_context_t *ue_context_p = rrc_gNB_get_ue_context(rrc, rrc_ue_id); + if (ue_context_p == NULL) { + LOG_E(NR_RRC, "cannot find UE context for UE ID %d\n", rrc_ue_id); + return; + } + gNB_RRC_UE_t *ue = &ue_context_p->ue_context; + nr_rrc_du_container_t *source_du = get_du_for_ue(rrc, ue->rrc_ue_id); + if (source_du == NULL) { + f1_ue_data_t ue_data = cu_get_f1_ue_data(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_du_container_t *target_du = find_target_du(rrc, source_du->assoc_id); + if (target_du == NULL) { + LOG_E(NR_RRC, "No target gNB-DU found. Handover for UE %u aborted.\n", ue->rrc_ue_id); + return; + } + + nr_rrc_trigger_f1_ho(rrc, ue, source_du, target_du); +} diff --git a/openair2/RRC/NR/rrc_gNB_mobility.h b/openair2/RRC/NR/rrc_gNB_mobility.h index fefa39982b..5811804009 100644 --- a/openair2/RRC/NR/rrc_gNB_mobility.h +++ b/openair2/RRC/NR/rrc_gNB_mobility.h @@ -66,4 +66,6 @@ typedef struct nr_handover_context_s { nr_ho_target_cu_t *target; } nr_handover_context_t; +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); + #endif /* RRC_GNB_MOBILITY_H_ */