From cd1aaab014ea5835b36bfd4e564c1634b633117f Mon Sep 17 00:00:00 2001 From: Robert Schmidt Date: Tue, 13 May 2025 18:09:20 +0200 Subject: [PATCH 1/7] Remove only UEs belonging to DU that disconnected The previous condition was wrong: if a UE did not have the assoc_id of the DU that connected (i.e., it was connected to another DU then the one disconnecting), it was marked for deletion. Instead, skip those UEs. --- openair2/RRC/NR/rrc_gNB_du.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/openair2/RRC/NR/rrc_gNB_du.c b/openair2/RRC/NR/rrc_gNB_du.c index a650a03604..ad1ae30e03 100644 --- a/openair2/RRC/NR/rrc_gNB_du.c +++ b/openair2/RRC/NR/rrc_gNB_du.c @@ -457,7 +457,9 @@ static int invalidate_du_connections(gNB_RRC_INST *rrc, sctp_assoc_t assoc_id) nr_rrc_finalize_ho(UE); } f1_ue_data_t ue_data = cu_get_f1_ue_data(ue_id); - if (ue_data.du_assoc_id == assoc_id && IS_SA_MODE(get_softmodem_params())) { + if (ue_data.du_assoc_id != assoc_id) + continue; /* this UE is on another DU */ + if (IS_SA_MODE(get_softmodem_params())) { /* this UE belongs to the DU that disconnected, set du_assoc_id to 0, * meaning DU is offline, then trigger release request */ ue_data.du_assoc_id = 0; From a6c319e84ba5399eccc1c4cc830f78ffcd06c821 Mon Sep 17 00:00:00 2001 From: Robert Schmidt Date: Wed, 14 May 2025 09:47:28 +0200 Subject: [PATCH 2/7] Update handover documentation Point out that an error message is expected, after user feedback pointing out this somewhat confusing behavior. --- doc/handover-tutorial.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/doc/handover-tutorial.md b/doc/handover-tutorial.md index a367de868a..5381cd0cc8 100644 --- a/doc/handover-tutorial.md +++ b/doc/handover-tutorial.md @@ -54,7 +54,11 @@ Start DU0: sudo ./nr-softmodem --rfsim -O ../../../targets/PROJECTS/GENERIC-NR-5GC/CONF/gnb-du.sa.band78.106prb.rfsim.pci0.conf --rfsimulator.serveraddr 127.0.0.1 -Start the UE, and let it connect completely: +This will show an error `[HW] connect() to 127.0.0.1:4043 failed, +errno(111)`. _This is expected_, because the RFsim server is at the UE (to be +able to serve two RFsim clients, one DU each; see below for more info). Proceed +by starting the UE, and let it connect completely (this should make the error +go away): sudo ./nr-uesoftmodem -C 3450720000 -r 106 --numerology 1 --ssb 516 -O --rfsim --rfsimulator.serveraddr server From 249812e10dd013a212cb7a8565c6bce0fd0aa53f Mon Sep 17 00:00:00 2001 From: Reem Bahsoun Date: Tue, 27 May 2025 10:21:39 +0200 Subject: [PATCH 3/7] Clarify HO doc: describe where to put neighbour configuration --- doc/handover-tutorial.md | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/doc/handover-tutorial.md b/doc/handover-tutorial.md index 5381cd0cc8..5fe08be665 100644 --- a/doc/handover-tutorial.md +++ b/doc/handover-tutorial.md @@ -146,7 +146,7 @@ neighbour relation of the DUs at the CU. To do so, proceed as follows: ``` cat nrRRC_stats.log ``` -1. Fill in the `neighbour-config.conf` configuration file as shown below, and +1. Fill in the [`neighbour-config.conf`](../ci-scripts/conf_files/neighbour-config.conf) configuration file as shown below, and `@include` it in the CU file. 1. Start the CU and both DUs. 1. Bring the phone close to one cell, and leave flight mode. It should connect @@ -161,7 +161,7 @@ triggered: - Make sure that both DUs use the same hardware. - Make sure that the UE sees both cells. For instance, you can switch to flight mode, go closer to the other DU, and switch off flight mode -- the UE should - connect to that second UE. + connect to that second DU. - We did not manage handover with every phone yet -- make sure you use one of the list provided above. @@ -250,6 +250,15 @@ nr_measurement_configuration = { }) }; ``` +`@include` this configuration file inside the gNB section of CU file as shown below. + +``` + plmn_list = ({ mcc = 222; mnc = 01; mnc_length = 2; snssaiList = ({ sst = 1, sd = 0xffffff })}); + + + @include "neighbour-config.conf" + +``` # Handovers triggers and NTN From 75be2a1a5957da991adf2cb531e62ff53b249a44 Mon Sep 17 00:00:00 2001 From: Robert Schmidt Date: Thu, 15 May 2025 12:53:14 +0200 Subject: [PATCH 4/7] When freeing UE contexts, check in both possible lists --- openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_RA.c | 2 +- openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_primitives.c | 9 ++++++--- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_RA.c b/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_RA.c index e398da1eaf..698986354a 100644 --- a/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_RA.c +++ b/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_RA.c @@ -2242,7 +2242,7 @@ void nr_release_ra_UE(gNB_MAC_INST *mac, rnti_t rnti) if (UE) { delete_nr_ue_data(UE, mac->common_channels, &UE_info->uid_allocator); } else { - LOG_W(NR_MAC,"Call to del rnti %04x, but not existing\n", rnti); + LOG_W(NR_MAC,"Call to release RA UE with rnti %04x, but not existing\n", rnti); } } diff --git a/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_primitives.c b/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_primitives.c index a447d9f91c..898722342b 100644 --- a/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_primitives.c +++ b/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_primitives.c @@ -2621,6 +2621,8 @@ NR_UE_info_t *remove_UE_from_list(int list_size, NR_UE_info_t *list[list_size], { for (int i = 0; i < list_size; i++) { NR_UE_info_t *curr_UE = list[i]; + if (!curr_UE) + break; /* reached the end of the list */ if (curr_UE->rnti != rnti) continue; @@ -2629,7 +2631,6 @@ NR_UE_info_t *remove_UE_from_list(int list_size, NR_UE_info_t *list[list_size], list[list_size - 1] = NULL; return curr_UE; } - LOG_E(NR_MAC, "UE %04x to be removed not found in list\n", rnti); return NULL; } @@ -2735,8 +2736,10 @@ void mac_remove_nr_ue(gNB_MAC_INST *nr_mac, rnti_t rnti) NR_SCHED_LOCK(&UE_info->mutex); NR_UE_info_t *UE = remove_UE_from_list(MAX_MOBILES_PER_GNB + 1, UE_info->connected_ue_list, rnti); NR_SCHED_UNLOCK(&UE_info->mutex); - - delete_nr_ue_data(UE, nr_mac->common_channels, &UE_info->uid_allocator); + if (UE) + delete_nr_ue_data(UE, nr_mac->common_channels, &UE_info->uid_allocator); + else + nr_release_ra_UE(nr_mac, rnti); } // all values passed to this function are in dB x10 From 67e9c9c26398f72b1d74c1cbcf7d80136468203d Mon Sep 17 00:00:00 2001 From: Robert Schmidt Date: Fri, 27 Jun 2025 08:00:08 +0200 Subject: [PATCH 5/7] RRC: auto-generate release request when no AMF Mark non-existing AMF UE NGAP ID through invalid INT64_MAX number (permitted range is up to 2^40-1). Check for this range in the RAN function, as we might otherwise pass the invalid AMF UE NGAP ID to the agent, which would assert with nr-softmodem: /oai-ran/openair2/E2AP/flexric/src/lib/3gpp/enc/enc_gnb.c:83: enc_gNB_UE_asn: Assertion `gnb->amf_ue_ngap_id < 1UL << 40' failed. In other words, don't send KPM messages for a UE which has no valid AMF UE NGAP ID yet (which follows from the fact that the spec requires an AMF UE NGAP ID; the previous behavior of simply sending 0 could be considered a bug, because it's not the UE's true AMF UE NGAP ID). --- openair2/E2AP/RAN_FUNCTION/O-RAN/ran_e2sm_ue_id.c | 7 ++++++- openair2/E2AP/RAN_FUNCTION/O-RAN/ran_func_kpm.c | 6 ++++++ openair2/RRC/NR/rrc_gNB.c | 10 ++++++---- openair2/RRC/NR/rrc_gNB_UE_context.c | 2 ++ 4 files changed, 20 insertions(+), 5 deletions(-) diff --git a/openair2/E2AP/RAN_FUNCTION/O-RAN/ran_e2sm_ue_id.c b/openair2/E2AP/RAN_FUNCTION/O-RAN/ran_e2sm_ue_id.c index c3f6277f6f..a81a01d394 100644 --- a/openair2/E2AP/RAN_FUNCTION/O-RAN/ran_e2sm_ue_id.c +++ b/openair2/E2AP/RAN_FUNCTION/O-RAN/ran_e2sm_ue_id.c @@ -30,7 +30,12 @@ ue_id_e2sm_t fill_e2sm_gnb_ue_id_data(const gNB_RRC_UE_t *rrc_ue_context, __attr // 6.2.3.16 // Mandatory // AMF UE NGAP ID - ue_id.gnb.amf_ue_ngap_id = rrc_ue_context->amf_ue_ngap_id; + // it seems that certain messages can be triggered before the ue has an AMF + // UE NGAP ID (e.g., RRC setup). In that case, the AMF UE NGAP ID is set to + // an invalid value. Below, check for this, and set 0 in that case (which + // historically used to be in amf_ue_ngap_id). + uint64_t auni = rrc_ue_context->amf_ue_ngap_id; + ue_id.gnb.amf_ue_ngap_id = auni < (1LL << 40) ? auni : 0; // Mandatory //GUAMI 6.2.3.17 diff --git a/openair2/E2AP/RAN_FUNCTION/O-RAN/ran_func_kpm.c b/openair2/E2AP/RAN_FUNCTION/O-RAN/ran_func_kpm.c index 4468cdf0b9..fd6578b3b2 100644 --- a/openair2/E2AP/RAN_FUNCTION/O-RAN/ran_func_kpm.c +++ b/openair2/E2AP/RAN_FUNCTION/O-RAN/ran_func_kpm.c @@ -209,6 +209,9 @@ static arr_ue_id_t filter_ues_by_s_nssai_in_cu(const test_info_lst_t test_info) struct rrc_gNB_ue_context_s* rrc_ue_context = NULL; RB_FOREACH(rrc_ue_context, rrc_nr_ue_tree_s, &RC.nrrrc[0]->rrc_ue_head) { gNB_RRC_UE_t *ue = &rrc_ue_context->ue_context; + /* UE has no AMF UE NGAP ID yet => can't send message */ + if (ue->amf_ue_ngap_id >= (1LL << 40)) + continue; for (int p = 0; p < ue->nb_of_pdusessions; ++p) { pdusession_t *pdu = &ue->pduSession[p].param; if (nssai_matches(pdu->nssai, sst, sd)) { @@ -296,6 +299,9 @@ static arr_ue_id_t filter_ues_by_s_nssai_in_du_or_monolithic(const test_info_lst arr_ue_id.ue_id[arr_ue_id.sz] = fill_ue_id_data[ngran_gNB_DU](NULL, rrc_ue_id.secondary_ue, 0); } else { rrc_gNB_ue_context_t* rrc_ue_context = rrc_gNB_get_ue_context_by_rnti(RC.nrrrc[0], -1, ue->rnti); + /* UE has no AMF UE NGAP ID yet => can't send message */ + if (rrc_ue_context->ue_context.amf_ue_ngap_id >= (1LL << 40)) + continue; arr_ue_id.ue_id[arr_ue_id.sz] = fill_ue_id_data[ngran_gNB](&rrc_ue_context->ue_context, 0, 0); } diff --git a/openair2/RRC/NR/rrc_gNB.c b/openair2/RRC/NR/rrc_gNB.c index 2ef11ce605..f9952d0dae 100644 --- a/openair2/RRC/NR/rrc_gNB.c +++ b/openair2/RRC/NR/rrc_gNB.c @@ -2146,12 +2146,14 @@ static void rrc_CU_process_ue_context_release_request(MessageDef *msg_p, sctp_as f1ap_ue_context_release_req_t *req = &F1AP_UE_CONTEXT_RELEASE_REQ(msg_p); gNB_RRC_INST *rrc = RC.nrrrc[instance]; rrc_gNB_ue_context_t *ue_context_p = rrc_gNB_get_ue_context(rrc, req->gNB_CU_ue_id); - // TODO what happens if no AMF connected? should also handle, set an_release true int srbid = 1; - if (!ue_context_p) { - LOG_W(RRC, "could not find UE context for CU UE ID %u: auto-generate release command\n", req->gNB_CU_ue_id); + // valid AMF UE NGAP ID range is 0..2^40-1 + if (!ue_context_p || ue_context_p->ue_context.amf_ue_ngap_id >= (1LL << 40)) { + const char *reason = !ue_context_p ? "could not find UE context" : "no AMF"; + LOG_W(RRC, "%s for CU UE ID %u: auto-generate release command\n", reason, req->gNB_CU_ue_id); uint8_t buffer[NR_RRC_BUF_SIZE] = {0}; int size = do_NR_RRCRelease(buffer, NR_RRC_BUF_SIZE, rrc_gNB_get_next_transaction_identifier(0)); + RETURN_IF_INVALID_ASSOC_ID(assoc_id); f1ap_ue_context_release_cmd_t ue_context_release_cmd = { .gNB_CU_ue_id = req->gNB_CU_ue_id, .gNB_DU_ue_id = req->gNB_DU_ue_id, @@ -2159,7 +2161,7 @@ static void rrc_CU_process_ue_context_release_request(MessageDef *msg_p, sctp_as .cause_value = 10, // 10 = F1AP_CauseRadioNetwork_normal_release .srb_id = srbid, }; - deliver_ue_ctxt_release_data_t data = {.rrc = rrc, .release_cmd = &ue_context_release_cmd}; + deliver_ue_ctxt_release_data_t data = {.rrc = rrc, .release_cmd = &ue_context_release_cmd, .assoc_id = assoc_id}; nr_pdcp_data_req_srb(req->gNB_CU_ue_id, srbid, rrc_gNB_mui++, size, buffer, rrc_deliver_ue_ctxt_release_cmd, &data); return; } diff --git a/openair2/RRC/NR/rrc_gNB_UE_context.c b/openair2/RRC/NR/rrc_gNB_UE_context.c index b39abd9a30..cb3a04fbce 100644 --- a/openair2/RRC/NR/rrc_gNB_UE_context.c +++ b/openair2/RRC/NR/rrc_gNB_UE_context.c @@ -200,6 +200,8 @@ rrc_gNB_ue_context_t *rrc_gNB_create_ue_context(sctp_assoc_t assoc_id, gNB_RRC_UE_t *ue = &ue_context_p->ue_context; ue->rnti = rnti; ue->random_ue_identity = ue_identityP; + // signal "no AMF UE NGAP ID" because valid range is 0..2^40-1 + ue->amf_ue_ngap_id = INT64_MAX; f1_ue_data_t ue_data = {.secondary_ue = du_ue_id, .du_assoc_id = assoc_id}; AssertFatal(!cu_exists_f1_ue_data(ue->rrc_ue_id), "UE F1 Context for ID %d already exists, logic bug\n", From 2ea3ab5050ec7635700220e42fed1f5df7a2579c Mon Sep 17 00:00:00 2001 From: Robert Schmidt Date: Fri, 27 Jun 2025 08:30:42 +0200 Subject: [PATCH 6/7] Introduce UE release request grace period It might happen that the CU does not answer a release request. In that case, the MAC would never free the UE context, potentially blocking the DU. Avoid this by introducing a "grace period" after which a UE context is automatically freed at the MAC (and RLC), currently at 10s after the 30s UL failure timer. --- .../NR_MAC_gNB/gNB_scheduler_primitives.c | 33 ++++++++++++++----- openair2/LAYER2/NR_MAC_gNB/mac_proto.h | 2 +- 2 files changed, 25 insertions(+), 10 deletions(-) diff --git a/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_primitives.c b/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_primitives.c index 898722342b..17263de461 100644 --- a/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_primitives.c +++ b/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_primitives.c @@ -3167,7 +3167,12 @@ void nr_mac_update_timers(module_id_t module_id, frame_t frame, slot_t slot) } /* check if UL failure and trigger release request if necessary */ - nr_mac_check_ul_failure(mac, UE->rnti, sched_ctrl); + bool released = nr_mac_check_ul_failure(mac, UE->rnti, sched_ctrl); + if (released) { + // go back to examine the next UE, which is at the position the current UE was + UE--; + continue; + } if (nr_timer_tick(&sched_ctrl->transm_interrupt)) { /* expired */ @@ -3360,6 +3365,8 @@ bool nr_mac_check_release(NR_UE_sched_ctrl_t *sched_ctrl, int rnti) return sched_ctrl->release_timer == 0; } +#define UL_FAILURE_REQ_GRACE 10000 +#define UL_FAILURE_TIMEOUT 30000 void nr_mac_trigger_ul_failure(NR_UE_sched_ctrl_t *sched_ctrl, NR_SubcarrierSpacing_t subcarrier_spacing) { if (sched_ctrl->ul_failure) { @@ -3367,8 +3374,9 @@ void nr_mac_trigger_ul_failure(NR_UE_sched_ctrl_t *sched_ctrl, NR_SubcarrierSpac return; } sched_ctrl->ul_failure = true; - // 30 seconds till triggering release request - sched_ctrl->ul_failure_timer = 30000 << subcarrier_spacing; + // UL_FAILURE_TIMEOUT ms till triggering release request + // UL_FAILURE_REQ_GRACE ms till automatically released after request + sched_ctrl->ul_failure_timer = (UL_FAILURE_REQ_GRACE + UL_FAILURE_TIMEOUT) << subcarrier_spacing; } void nr_mac_reset_ul_failure(NR_UE_sched_ctrl_t *sched_ctrl) @@ -3398,20 +3406,27 @@ bool nr_mac_request_release_ue(const gNB_MAC_INST *nrmac, int rnti) return true; } -void nr_mac_check_ul_failure(gNB_MAC_INST *nrmac, int rnti, NR_UE_sched_ctrl_t *sched_ctrl) +bool nr_mac_check_ul_failure(gNB_MAC_INST *nrmac, int rnti, NR_UE_sched_ctrl_t *sched_ctrl) { if (!sched_ctrl->ul_failure) - return; + return false; if (sched_ctrl->ul_failure_timer > 0) sched_ctrl->ul_failure_timer--; - /* to trigger only once: trigger when ul_failure_timer == 1, but timer will - * stop at 0 and we wait for a UE release command from upper layers */ - if (sched_ctrl->ul_failure_timer == 1) { + /* trigger once: trigger when ul_failure_timer == UL_FAILURE_REQ_GRACE( we + * wait for a UE release command from upper layers), and UE is automatically + * released when no answer after UL_FAILURE_REQ_GRACE */ + if (sched_ctrl->ul_failure_timer == UL_FAILURE_REQ_GRACE) { LOG_W(MAC, "UE %04x: request release after UL failure timer expiry\n", rnti); bool requested = nr_mac_request_release_ue(nrmac, rnti); if (!requested) - nr_mac_release_ue(nrmac, rnti); + sched_ctrl->ul_failure_timer = 0; /* force expiry */ } + if (sched_ctrl->ul_failure_timer == 0) { + LOG_W(NR_MAC, "UE %04x: no answer for release request, dropping UE\n", rnti); + nr_mac_release_ue(nrmac, rnti); + return true; + } + return false; } void nr_mac_trigger_reconfiguration(const gNB_MAC_INST *nrmac, const NR_UE_info_t *UE) diff --git a/openair2/LAYER2/NR_MAC_gNB/mac_proto.h b/openair2/LAYER2/NR_MAC_gNB/mac_proto.h index 6acf4ae1d8..190efa0d1b 100644 --- a/openair2/LAYER2/NR_MAC_gNB/mac_proto.h +++ b/openair2/LAYER2/NR_MAC_gNB/mac_proto.h @@ -445,7 +445,7 @@ bool nr_mac_request_release_ue(const gNB_MAC_INST *nrmac, int rnti); void nr_mac_trigger_ul_failure(NR_UE_sched_ctrl_t *sched_ctrl, NR_SubcarrierSpacing_t subcarrier_spacing); void nr_mac_reset_ul_failure(NR_UE_sched_ctrl_t *sched_ctrl); -void nr_mac_check_ul_failure(gNB_MAC_INST *nrmac, int rnti, NR_UE_sched_ctrl_t *sched_ctrl); +bool nr_mac_check_ul_failure(gNB_MAC_INST *nrmac, int rnti, NR_UE_sched_ctrl_t *sched_ctrl); void nr_mac_trigger_reconfiguration(const gNB_MAC_INST *nrmac, const NR_UE_info_t *UE); From b6a33777b91059afad54717e28a154b0329634ab Mon Sep 17 00:00:00 2001 From: Robert Schmidt Date: Wed, 2 Jul 2025 17:57:20 +0200 Subject: [PATCH 7/7] Fix: on release command, search attach list When the RA procedure fails at Msg4, we trigger UL failure and send a message to the CU to request release. There was a bug in that upon the ensueing release command, we did not search in the attach list (only connected list). However, the UE failing at Msg4 does not have transitioned to the connected list yet, thus not freeing the context. Fixes: 8f62baefa61e ("Use list of UEs during RA instead of array of RA processes)" --- openair2/LAYER2/NR_MAC_gNB/mac_rrc_dl_handler.c | 1 + 1 file changed, 1 insertion(+) diff --git a/openair2/LAYER2/NR_MAC_gNB/mac_rrc_dl_handler.c b/openair2/LAYER2/NR_MAC_gNB/mac_rrc_dl_handler.c index 5d7642c815..e8e727ff00 100644 --- a/openair2/LAYER2/NR_MAC_gNB/mac_rrc_dl_handler.c +++ b/openair2/LAYER2/NR_MAC_gNB/mac_rrc_dl_handler.c @@ -906,6 +906,7 @@ void ue_context_release_command(const f1ap_ue_context_release_cmd_t *cmd) gNB_MAC_INST *mac = RC.nrmac[0]; NR_SCHED_LOCK(&mac->sched_lock); NR_UE_info_t *UE = find_nr_UE(&mac->UE_info, cmd->gNB_DU_ue_id); + UE = UE ? UE : find_ra_UE(&mac->UE_info, cmd->gNB_DU_ue_id); if (UE == NULL) { NR_SCHED_UNLOCK(&mac->sched_lock); f1ap_ue_context_release_complete_t complete = {