diff --git a/common/utils/telnetsrv/telnetsrv_ci.c b/common/utils/telnetsrv/telnetsrv_ci.c index 24dba02191..291f1f37a2 100644 --- a/common/utils/telnetsrv/telnetsrv_ci.c +++ b/common/utils/telnetsrv/telnetsrv_ci.c @@ -38,6 +38,7 @@ #include "openair2/LAYER2/nr_rlc/nr_rlc_ue_manager.h" #include "openair2/LAYER2/nr_rlc/nr_rlc_entity_am.h" #include "openair2/LAYER2/NR_MAC_gNB/mac_proto.h" +#include "openair2/RRC/NR/rrc_gNB_mobility.h" #define TELNETSERVERCODE #include "telnetsrv.h" @@ -196,6 +197,53 @@ int rrc_gNB_trigger_f1_ho(char *buf, int debug, telnet_printfunc_t prnt) return 0; } +extern void nr_HO_N2_trigger_telnet(gNB_RRC_INST *rrc, uint32_t neighbour_pci, uint32_t rrc_ue_id); + +/** @brief Trigger N2 handover for UE + * @param buf: Neighbour PCI, SCell PCI, RRC UE ID + * @param debug: Debug flag + * @param prnt: Print function + * @return 0 on success, -1 on failure */ +int rrc_gNB_trigger_n2_ho(char *buf, int debug, telnet_printfunc_t prnt) +{ + if (!RC.nrrrc) + ERROR_MSG_RET("no RRC present, cannot list counts\n"); + + if (!buf) { + ERROR_MSG_RET("Please provide neighbour cell id and ue id\n"); + } else { + // Parse neighbour cell PCI + char *token = strtok(buf, ","); + if (!token) { + ERROR_MSG_RET("Invalid input. Expected format: Neighbour PCI, ueId\n"); + } + uint32_t neighbour_pci = strtol(token, NULL, 10); + + // Parse ueId + token = strtok(NULL, ","); + if (!token) { + ERROR_MSG_RET("Missing UE ID\n"); + } + uint32_t ueId = strtol(token, NULL, 10); + + // Retrieve UE context + rrc_gNB_ue_context_t *ue_p = rrc_gNB_get_ue_context(RC.nrrrc[0], ueId); + if (!ue_p) { + ERROR_MSG_RET("UE with id %u not found\n", ueId); + } + gNB_RRC_UE_t *UE = &ue_p->ue_context; + + // Trigger N2 handover + nr_HO_N2_trigger_telnet(RC.nrrrc[0], neighbour_pci, UE->rrc_ue_id); + + // Print success message + prnt("RRC N2 handover triggered for UE %u with neighbour cell id %u\n", + ueId, + neighbour_pci); + } + return 0; +} + int force_ul_failure(char *buf, int debug, telnet_printfunc_t prnt) { if (!RC.nrmac) @@ -245,6 +293,7 @@ static telnetshell_cmddef_t cicmds[] = { {"trigger_f1_ho", "[rrc_ue_id(int,opt)]", rrc_gNB_trigger_f1_ho}, {"fetch_du_by_ue_id", "[rrc_ue_id(int,opt)]", fetch_du_by_ue_id}, {"get_current_bwp", "[rnti(hex,opt)]", get_current_bwp}, + {"trigger_n2_ho", "[neighbour_pci(uint32_t),ueId(uint32_t)]", rrc_gNB_trigger_n2_ho}, {"", "", NULL}, }; diff --git a/openair2/RRC/NR/rrc_gNB_mobility.c b/openair2/RRC/NR/rrc_gNB_mobility.c index 3c9bf2c17b..42214ecc55 100644 --- a/openair2/RRC/NR/rrc_gNB_mobility.c +++ b/openair2/RRC/NR/rrc_gNB_mobility.c @@ -549,3 +549,52 @@ void nr_rrc_trigger_n2_ho(gNB_RRC_INST *rrc, rrc_gNB_send_NGAP_HANDOVER_REQUIRED(rrc, ue, neighbour_config, hoPrepInfo); free_byte_array(hoPrepInfo); } + +extern const nr_neighbour_cell_t *get_neighbour_cell_by_pci(const neighbour_cell_configuration_t *cell, int pci); +extern const neighbour_cell_configuration_t *get_neighbour_cell_config(const gNB_RRC_INST *rrc, int cell_id); + +void nr_HO_N2_trigger_telnet(gNB_RRC_INST *rrc, uint32_t neighbour_pci, 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, "N2 HO trigger failed for UE %d: cannot find UE context\n", rrc_ue_id); + 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); + 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; + + // 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, + }; + 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); + + 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); +}