Commit 6e4f1b7b authored by Guido Casati's avatar Guido Casati

Add N2 Handover decision and trigger preparation on source CU

(1) add N2 ho decision based on measurements
(2) add trigger for N2 HO on source CU
(3) generate NG Handover Preparation Information message,
    including RRCReconfiguration, to be passed to the
    NG Handover Required message
(4) call RRC callback for Handover Required message
Co-authored-by: default avatarbatuhan duyuler <batuhan.duyuler@firecell.io>
parent 20e534d4
...@@ -564,6 +564,43 @@ static NR_RRCReconfiguration_IEs_t *build_RRCReconfiguration_IEs(const nr_rrc_re ...@@ -564,6 +564,43 @@ static NR_RRCReconfiguration_IEs_t *build_RRCReconfiguration_IEs(const nr_rrc_re
return ie; return ie;
} }
static byte_array_t do_HO_RRCReconfiguration(nr_rrc_reconfig_param_t *params)
{
NR_RRCReconfiguration_IEs_t *ie = build_RRCReconfiguration_IEs(params);
byte_array_t msg = {.buf = NULL, .len = 0};
// Create the standalone RRCReconfiguration message
NR_RRCReconfiguration_t rrcReconf = {0};
rrcReconf.rrc_TransactionIdentifier = params->transaction_id;
rrcReconf.criticalExtensions.present = NR_RRCReconfiguration__criticalExtensions_PR_rrcReconfiguration;
rrcReconf.criticalExtensions.choice.rrcReconfiguration = ie;
// Encode the message
int val = uper_encode_to_new_buffer(&asn_DEF_NR_RRCReconfiguration, NULL, &rrcReconf, (void **)&msg.buf);
if (val <= 0) {
LOG_E(NR_RRC, "ASN1 RRCReconfiguration message encoding failed\n");
ASN_STRUCT_FREE_CONTENTS_ONLY(asn_DEF_NR_RRCReconfiguration, &rrcReconf);
return msg;
}
msg.len = val;
LOG_D(NR_RRC, "RRCReconfiguration: Encoded (%ld bytes)\n", msg.len);
// don't free what we did not allocate, so set fields with pointers to NULL
// if memory comes from outside
ie->measConfig = NULL;
if (ie->radioBearerConfig) {
ie->radioBearerConfig->srb3_ToRelease = NULL;
ie->radioBearerConfig->drb_ToReleaseList = NULL;
ie->radioBearerConfig->drb_ToAddModList = NULL;
ie->radioBearerConfig->srb_ToAddModList = NULL;
ie->radioBearerConfig->securityConfig = NULL;
}
ASN_STRUCT_FREE_CONTENTS_ONLY(asn_DEF_NR_RRCReconfiguration, &rrcReconf);
return msg;
}
byte_array_t do_RRCReconfiguration(const nr_rrc_reconfig_param_t *params) byte_array_t do_RRCReconfiguration(const nr_rrc_reconfig_param_t *params)
{ {
byte_array_t msg = {.buf = NULL, .len = 0}; byte_array_t msg = {.buf = NULL, .len = 0};
...@@ -1101,6 +1138,50 @@ NR_MeasConfig_t *get_MeasConfig(const NR_MeasTiming_t *mt, ...@@ -1101,6 +1138,50 @@ NR_MeasConfig_t *get_MeasConfig(const NR_MeasTiming_t *mt,
return mc; return mc;
} }
/** @brief HandoverPreparationInformation message (11.2.2 of TS 38.331)
* 1) generate RRCReconfiguration to be transferred
* 2) encodes UE Capabilities from UE Context
* 3) encodes AS context
* 4) generates HO Preparation Info */
byte_array_t get_HandoverPreparationInformation(nr_rrc_reconfig_param_t *params, int scell_pci)
{
// Buffer to return
byte_array_t buffer = {.buf = NULL, .len = 0};
/* Prepare handoverPreparationInformation IEs */
NR_HandoverPreparationInformation_t hoPrepInfo = {0};
hoPrepInfo.criticalExtensions.present = NR_HandoverPreparationInformation__criticalExtensions_PR_c1;
asn1cCalloc(hoPrepInfo.criticalExtensions.choice.c1, c1);
c1->present = NR_HandoverPreparationInformation__criticalExtensions__c1_PR_handoverPreparationInformation;
asn1cCalloc(c1->choice.handoverPreparationInformation, hpi);
/* Decode stored NR_UE_CapabilityRAT_ContainerList and add to IEs */
NR_UE_CapabilityRAT_ContainerList_t *clist = NULL;
asn_dec_rval_t dec_rval =
uper_decode(NULL, &asn_DEF_NR_UE_CapabilityRAT_ContainerList, (void **)&clist, params->ue_cap.buf, params->ue_cap.len, 0, 0);
if (dec_rval.code != RC_OK) {
LOG_W(NR_RRC, "Failed to decode UE capability container list for HandoverPreparationInformation, ignoring capabilities\n");
return buffer;
}
hpi->ue_CapabilityRAT_List = *clist;
free(clist);
/* sourceConfig: Encode RRCReconfiguration as used in the source cell */
byte_array_t msg = do_HO_RRCReconfiguration(params);
asn1cCalloc(hpi->sourceConfig, sourceConfig);
OCTET_STRING_fromBuf(&sourceConfig->rrcReconfiguration, (const char *)msg.buf, msg.len);
free_byte_array(msg);
if (LOG_DEBUGFLAG(DEBUG_ASN1))
xer_fprint(stdout, &asn_DEF_NR_HandoverPreparationInformation, (void **)&hoPrepInfo);
/* encode */
buffer.len = uper_encode_to_new_buffer(&asn_DEF_NR_HandoverPreparationInformation, NULL, (void *)&hoPrepInfo, (void *)&buffer.buf);
AssertFatal(buffer.len > 0, "ASN1 message encoding failed (%ld)!\n", buffer.len);
ASN_STRUCT_FREE_CONTENTS_ONLY(asn_DEF_NR_HandoverPreparationInformation, &hoPrepInfo);
return buffer;
}
void free_MeasConfig(NR_MeasConfig_t *mc) void free_MeasConfig(NR_MeasConfig_t *mc)
{ {
ASN_STRUCT_FREE(asn_DEF_NR_MeasConfig, mc); ASN_STRUCT_FREE(asn_DEF_NR_MeasConfig, mc);
......
...@@ -69,6 +69,7 @@ typedef struct { ...@@ -69,6 +69,7 @@ typedef struct {
NR_CellGroupConfig_t *cell_group_config; NR_CellGroupConfig_t *cell_group_config;
bool masterKeyUpdate; bool masterKeyUpdate;
int nextHopChainingCount; int nextHopChainingCount;
byte_array_t ue_cap;
} nr_rrc_reconfig_param_t; } nr_rrc_reconfig_param_t;
/* /*
...@@ -161,4 +162,6 @@ NR_MeasConfig_t *get_MeasConfig(const NR_MeasTiming_t *mt, ...@@ -161,4 +162,6 @@ NR_MeasConfig_t *get_MeasConfig(const NR_MeasTiming_t *mt,
void free_MeasConfig(NR_MeasConfig_t *mc); void free_MeasConfig(NR_MeasConfig_t *mc);
int do_NR_Paging(uint8_t Mod_id, uint8_t *buffer, uint32_t tmsi); int do_NR_Paging(uint8_t Mod_id, uint8_t *buffer, uint32_t tmsi);
byte_array_t get_HandoverPreparationInformation(nr_rrc_reconfig_param_t *params, int scell_pci);
#endif /* __RRC_NR_MESSAGES_ASN1_MSG__H__ */ #endif /* __RRC_NR_MESSAGES_ASN1_MSG__H__ */
...@@ -125,4 +125,7 @@ nr_rrc_reconfig_param_t get_RRCReconfiguration_params(gNB_RRC_INST *rrc, gNB_RRC ...@@ -125,4 +125,7 @@ nr_rrc_reconfig_param_t get_RRCReconfiguration_params(gNB_RRC_INST *rrc, gNB_RRC
pdusession_level_qos_parameter_t *get_qos_characteristics(const int qfi, rrc_pdu_session_param_t *pduSession); pdusession_level_qos_parameter_t *get_qos_characteristics(const int qfi, rrc_pdu_session_param_t *pduSession);
f1ap_qos_flow_param_t get_qos_char_from_qos_flow_param(const pdusession_level_qos_parameter_t *qos_param); f1ap_qos_flow_param_t get_qos_char_from_qos_flow_param(const pdusession_level_qos_parameter_t *qos_param);
void openair_rrc_gNB_configuration(gNB_RRC_INST *rrc, gNB_RrcConfigurationReq *configuration); void openair_rrc_gNB_configuration(gNB_RRC_INST *rrc, gNB_RrcConfigurationReq *configuration);
NR_SRB_ToAddModList_t *createSRBlist(gNB_RRC_UE_t *ue, uint8_t reestablish);
NR_DRB_ToAddModList_t *createDRBlist(gNB_RRC_UE_t *ue, bool reestablish);
#endif #endif
...@@ -307,7 +307,7 @@ unsigned int rrc_gNB_get_next_transaction_identifier(module_id_t gnb_mod_idP) ...@@ -307,7 +307,7 @@ unsigned int rrc_gNB_get_next_transaction_identifier(module_id_t gnb_mod_idP)
* for the radio bearer changes, with some expections for SRB1 (i.e. when resuming * for the radio bearer changes, with some expections for SRB1 (i.e. when resuming
* an RRC connection, or at the first reconfiguration after RRC connection * an RRC connection, or at the first reconfiguration after RRC connection
* reestablishment in NR, do not re-establish PDCP) */ * reestablishment in NR, do not re-establish PDCP) */
static NR_SRB_ToAddModList_t *createSRBlist(gNB_RRC_UE_t *ue, uint8_t reestablish) NR_SRB_ToAddModList_t *createSRBlist(gNB_RRC_UE_t *ue, uint8_t reestablish)
{ {
if (!ue->Srb[SRB1].Active) { if (!ue->Srb[SRB1].Active) {
LOG_E(NR_RRC, "Call SRB list while SRB1 doesn't exist\n"); LOG_E(NR_RRC, "Call SRB list while SRB1 doesn't exist\n");
...@@ -328,7 +328,7 @@ static NR_SRB_ToAddModList_t *createSRBlist(gNB_RRC_UE_t *ue, uint8_t reestablis ...@@ -328,7 +328,7 @@ static NR_SRB_ToAddModList_t *createSRBlist(gNB_RRC_UE_t *ue, uint8_t reestablis
return list; return list;
} }
static NR_DRB_ToAddModList_t *createDRBlist(gNB_RRC_UE_t *ue, bool reestablish) NR_DRB_ToAddModList_t *createDRBlist(gNB_RRC_UE_t *ue, bool reestablish)
{ {
NR_DRB_ToAddMod_t *DRB_config = NULL; NR_DRB_ToAddMod_t *DRB_config = NULL;
NR_DRB_ToAddModList_t *DRB_configList = CALLOC(sizeof(*DRB_configList), 1); NR_DRB_ToAddModList_t *DRB_configList = CALLOC(sizeof(*DRB_configList), 1);
...@@ -349,7 +349,7 @@ static NR_DRB_ToAddModList_t *createDRBlist(gNB_RRC_UE_t *ue, bool reestablish) ...@@ -349,7 +349,7 @@ static NR_DRB_ToAddModList_t *createDRBlist(gNB_RRC_UE_t *ue, bool reestablish)
return DRB_configList; return DRB_configList;
} }
static void freeSRBlist(NR_SRB_ToAddModList_t *l) void freeSRBlist(NR_SRB_ToAddModList_t *l)
{ {
ASN_STRUCT_FREE(asn_DEF_NR_SRB_ToAddModList, l); ASN_STRUCT_FREE(asn_DEF_NR_SRB_ToAddModList, l);
} }
...@@ -1383,6 +1383,7 @@ static void process_Event_Based_Measurement_Report(gNB_RRC_INST *rrc, ...@@ -1383,6 +1383,7 @@ static void process_Event_Based_Measurement_Report(gNB_RRC_INST *rrc,
int servingCellRSRP = 0; int servingCellRSRP = 0;
int neighbourCellRSRP = 0; int neighbourCellRSRP = 0;
int scell_pci = -1; int scell_pci = -1;
int best_rsrp = -10000;
switch (event_triggered->eventId.present) { switch (event_triggered->eventId.present) {
case NR_EventTriggerConfig__eventId_PR_eventA2: case NR_EventTriggerConfig__eventId_PR_eventA2:
...@@ -1443,14 +1444,20 @@ static void process_Event_Based_Measurement_Report(gNB_RRC_INST *rrc, ...@@ -1443,14 +1444,20 @@ static void process_Event_Based_Measurement_Report(gNB_RRC_INST *rrc,
// No F1 connection but static neighbour configuration is available // No F1 connection but static neighbour configuration is available
const nr_a3_event_t *a3_event_configuration = get_a3_configuration(rrc, neighbour->physicalCellId); const nr_a3_event_t *a3_event_configuration = get_a3_configuration(rrc, neighbour->physicalCellId);
// Additional check - This part can be modified according to additional cell specific Handover Margin // Additional check - This part can be modified according to additional cell specific Handover Margin
// a3-Offset: The actual value is field value * 0.5 dB.
if (a3_event_configuration if (a3_event_configuration
&& ((a3_event_configuration->a3_offset + a3_event_configuration->hysteresis) && ((a3_event_configuration->a3_offset * 0.5 + a3_event_configuration->hysteresis)
< (neighbourCellRSRP - servingCellRSRP))) { < (neighbourCellRSRP - servingCellRSRP))) {
if (neighbourCellRSRP > best_rsrp) {
// UE can send multiple neighbour cells A3 event report in 1 Meas Report. So, we need to find the best neighbour
best_rsrp = neighbourCellRSRP;
LOG_I(NR_RRC, "HO LOG: Serving Cell RSRP: %d - Best Neighbor RSRP: %d ! Trigger N2 HO\n", servingCellRSRP, best_rsrp);
nr_rrc_trigger_n2_ho(rrc, ue, scell_pci, neighbour);
}
LOG_D(NR_RRC, "HO LOG: Trigger N2 HO for the neighbour gnb: %u cell: %lu\n", neighbour->gNB_ID, neighbour->nrcell_id); 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 (neigh_cell && neighbour) {
/* we know the cell and are connected to the DU! */ /* we know the cell and are connected to the DU! */
gNB_RRC_INST *rrc = RC.nrrrc[0];
nr_rrc_du_container_t *source_du = get_du_by_cell_id(rrc, serving_cell->nr_cellid); nr_rrc_du_container_t *source_du = get_du_by_cell_id(rrc, serving_cell->nr_cellid);
DevAssert(source_du); DevAssert(source_du);
nr_rrc_du_container_t *target_du = get_du_by_cell_id(rrc, neigh_cell->nr_cellid); nr_rrc_du_container_t *target_du = get_du_by_cell_id(rrc, neigh_cell->nr_cellid);
...@@ -1459,7 +1466,6 @@ static void process_Event_Based_Measurement_Report(gNB_RRC_INST *rrc, ...@@ -1459,7 +1466,6 @@ static void process_Event_Based_Measurement_Report(gNB_RRC_INST *rrc,
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); 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);
} }
} }
} break; } break;
default: default:
LOG_D(NR_RRC, "NR_EventTriggerConfig__eventId_PR_NOTHING or Other event report\n"); LOG_D(NR_RRC, "NR_EventTriggerConfig__eventId_PR_NOTHING or Other event report\n");
......
...@@ -215,27 +215,12 @@ static void nr_initiate_handover(const gNB_RRC_INST *rrc, ...@@ -215,27 +215,12 @@ static void nr_initiate_handover(const gNB_RRC_INST *rrc,
free_ue_context_setup_req(&ue_context_setup_req); free_ue_context_setup_req(&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.
}
*/
typedef struct deliver_ue_ctxt_modification_data_t { typedef struct deliver_ue_ctxt_modification_data_t {
gNB_RRC_INST *rrc; gNB_RRC_INST *rrc;
f1ap_ue_context_mod_req_t *modification_req; f1ap_ue_context_mod_req_t *modification_req;
sctp_assoc_t assoc_id; sctp_assoc_t assoc_id;
} deliver_ue_ctxt_modification_data_t; } deliver_ue_ctxt_modification_data_t;
static void rrc_deliver_ue_ctxt_modif_req(void *deliver_pdu_data, ue_id_t ue_id, int srb_id, char *buf, int size, int sdu_id) static void rrc_deliver_ue_ctxt_modif_req(void *deliver_pdu_data, ue_id_t ue_id, int srb_id, char *buf, int size, int sdu_id)
{ {
DevAssert(deliver_pdu_data != NULL); DevAssert(deliver_pdu_data != NULL);
...@@ -399,3 +384,52 @@ void nr_HO_F1_trigger_telnet(gNB_RRC_INST *rrc, uint32_t rrc_ue_id) ...@@ -399,3 +384,52 @@ void nr_HO_F1_trigger_telnet(gNB_RRC_INST *rrc, uint32_t rrc_ue_id)
nr_rrc_trigger_f1_ho(rrc, ue, source_du, target_du); nr_rrc_trigger_f1_ho(rrc, ue, source_du, target_du);
} }
/** @brief Generate the HandoverPreparationInformation to be carried
* in the RRC Container (9.3.1.29 of 3GPP TS 38.413) of the Source
* NG-RAN Node to Target NG-RAN Node Transparent Container IE */
static byte_array_t rrc_gNB_generate_HandoverPreparationInformation(gNB_RRC_INST *rrc, gNB_RRC_UE_t *ue, int serving_pci)
{
nr_rrc_reconfig_param_t params = get_RRCReconfiguration_params(rrc, ue, 0, false);
params.ue_cap = ue->ue_cap_buffer;
byte_array_t hoPrepInfo = get_HandoverPreparationInformation(&params, serving_pci);
free_RRCReconfiguration_params(params);
if (hoPrepInfo.len < 0) {
LOG_E(NR_RRC, "HandoverPreparationInformation generation failed for UE %d\n", ue->rrc_ue_id);
return hoPrepInfo;
}
LOG_D(NR_RRC, "HO LOG: Handover Preparation for UE %lu Encoded (%zd bytes)\n", ue->amf_ue_ngap_id, hoPrepInfo.len);
return hoPrepInfo;
}
/** @brief Trigger N2 handover on source gNB:
* 1) Prepare RRC Container with HandoverPreparationInformation message
* 2) send NGAP Handover Required message */
void nr_rrc_trigger_n2_ho(gNB_RRC_INST *rrc,
gNB_RRC_UE_t *ue,
int serving_pci,
const nr_neighbour_cell_t *neighbour_config)
{
byte_array_t hoPrepInfo = rrc_gNB_generate_HandoverPreparationInformation(rrc, ue, serving_pci);
if (hoPrepInfo.len < 0) {
free_byte_array(hoPrepInfo);
LOG_E(NR_RRC, "Failed to trigger N2 handover on source gNB for UE %x\n", ue->rrc_ue_id);
return;
}
// allocate context for source
if (ue->ho_context != NULL) {
LOG_E(NR_RRC, "Ongoing handover for UE %d, cannot trigger new\n", ue->rrc_ue_id);
return;
}
ue->ho_context = alloc_ho_ctx(HO_CTX_SOURCE);
ue->ho_context->source->du = get_du_for_ue(rrc, ue->rrc_ue_id);
rrc_gNB_send_NGAP_HANDOVER_REQUIRED(rrc, ue, neighbour_config, hoPrepInfo);
free_byte_array(hoPrepInfo);
}
...@@ -23,6 +23,7 @@ ...@@ -23,6 +23,7 @@
#include <stdint.h> #include <stdint.h>
#include "common/utils/ds/byte_array.h" #include "common/utils/ds/byte_array.h"
#include "nr_rrc_defs.h"
/* forward declarations */ /* forward declarations */
typedef struct gNB_RRC_INST_s gNB_RRC_INST; typedef struct gNB_RRC_INST_s gNB_RRC_INST;
...@@ -78,4 +79,9 @@ nr_handover_context_t *alloc_ho_ctx(ho_ctx_type_t type); ...@@ -78,4 +79,9 @@ 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, nr_rrc_du_container_t *source_du, nr_rrc_du_container_t *target_du);
void nr_rrc_finalize_ho(gNB_RRC_UE_t *ue); void nr_rrc_finalize_ho(gNB_RRC_UE_t *ue);
void nr_rrc_trigger_n2_ho(gNB_RRC_INST *rrc,
gNB_RRC_UE_t *ue,
int serving_pci,
const nr_neighbour_cell_t *neighbour_config);
#endif /* RRC_GNB_MOBILITY_H_ */ #endif /* RRC_GNB_MOBILITY_H_ */
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment