RRC: Trigger UE Context Setup upon re-establishment on different DU

As per TS 38.401 §8.7: if the UE accesses a DU different from the original
one during re-establishment, the CU must trigger UE Context Setup toward
the new DU. This refers to steps 9-10 and steps 9'-10'. Either one or the
other can be executed during re-establishment.

e.g. if this happens during F1 handover, it shall trigger a
UE Context Setup fallback on the CU.

This commit implements the required behavior:

1. Detection mechanism:
   - Add `old_du_assoc_id` field to `f1_ue_data_t` to store the original
     DU association ID
   - At RRCReestablishmentRequest: Store original `du_assoc_id` in
     `old_du_assoc_id` and update `du_assoc_id` to the new DU
   - At RRCReestablishmentComplete: Compare `old_du_assoc_id` with the
     `assoc_id` from the Complete message to detect different DU

2. UE Context Setup trigger:
   - In `rrc_gNB_process_RRCReestablishmentComplete()`: When different DU
     is detected, call `rrc_f1_ue_context_setup_for_target_du()` to trigger
     UE Context Setup procedure (per TS 38.401 §8.7, NOTE on steps 9-10)
     upon reception of UL RRC MESSAGE TRANSFER
   - In `rrc_CU_process_ue_modification_required()`: Refuse UE Context
     Modification Required when `old_du_assoc_id != 0` to avoid conflicts
     with the ongoing UE Context Setup procedure (per TS 38.401 §8.7, NOTE
     on steps 9'-10')

3. UE Context Setup:
   - Replace `clone_CellGroupConfig()` with `get_cellgroup_config()` in
     UE Context Setup handler
   - Call `update_cellgroup_for_reestablishment()` during UE Context Setup
     when `reestablish_rlc` flag is set to prepare CellGroupConfig with
     reestablishRLC flags
   - Clean up `old_du_assoc_id` in `rrc_CU_process_ue_context_setup_response()`
   - Add `free_ue_context_mod_req()` call after UE Context Modification request

4. Function signature updates:
   - Update `handle_rrcReestablishmentComplete()` to accept `gNB_DU_ue_id`
     and `assoc_id` parameters, change return type from `int` to `void`
   - Update `rrc_gNB_process_RRCReestablishmentComplete()` to accept
     `assoc_id` parameter
   - Update `rrc_gNB_decode_dcch()` to accept and forward `assoc_id` parameter

Re-establishment on different DU flow (TS 38.401 §8.7, steps 9-10):
1. UE sends RRCReestablishmentRequest to new DU
2. DU forwards to CU via UL RRC MESSAGE TRANSFER
3. CU detects different DU (`assoc_id != ue_data.du_assoc_id`):
   - Stores `old_du_assoc_id = ue_data.du_assoc_id` (original DU)
   - Updates `ue_data.du_assoc_id = assoc_id` (new DU)
4. CU sends RRCReestablishment to UE
5. UE sends RRCReestablishmentComplete to new DU
6. DU forwards to CU via UL RRC MESSAGE TRANSFER
7. CU detects different DU scenario (with old_du_assoc_id):
   - Triggers UE Context Setup Request to new DU via `rrc_f1_ue_context_setup_for_target_du()`
8. DU receives UE Context Setup Request:
   - Detects re-establishment (`UE->reestablish_rlc` flag)
   - Calls `update_cellgroup_for_reestablishment()` to set reestablishRLC flags
   - Encodes CellGroupConfig with reestablishRLC flags and sends to CU
9. CU receives UE Context Setup Response:
   - Stores encoded CellGroupConfig
   - Clears `old_du_assoc_id = 0`
10. CU sends RRC Reconfiguration with transparent CellGroupConfig to UE
This commit is contained in:
Guido Casati
2026-01-29 10:17:42 +01:00
parent 806a15da01
commit c5787aa9a9
2 changed files with 124 additions and 26 deletions
@@ -733,7 +733,8 @@ void ue_context_setup_request(const f1ap_ue_context_setup_req_t *req)
AssertFatal(UE, "no UE found or could not be created, but UE Context Setup Failed not implemented\n");
resp.gNB_DU_ue_id = UE->rnti;
NR_CellGroupConfig_t *new_CellGroup = clone_CellGroupConfig(UE->CellGroup);
NR_CellGroupConfig_t *new_CellGroup = get_cellgroup_config(UE);
// Needed for DRB Setup (e.g., RLC might reduce SN size)
UE->capability = ue_cap;
@@ -757,6 +758,15 @@ void ue_context_setup_request(const f1ap_ue_context_setup_req_t *req)
update_cellGroupConfig(new_CellGroup, UE->uid, UE->capability, &mac->radio_config, scc);
}
/* During re-establishment, prepare CellGroupConfig for UE Context Setup response.
* Per TS 38.401 §8.7: when a UE re-establishes on a different DU, the CU triggers
* UE Context Setup on the new DU. The DU must respond with a CellGroupConfig that has
* reestablishRLC flags set for all RLC bearers except SRB1. This prepares the CellGroupConfig
* for transparent forwarding to the UE per TS 38.473 transparency requirements. */
if (UE->reestablish_rlc) {
update_cellgroup_for_reestablishment(UE, new_CellGroup);
}
if (!ue_id_provided && cg_configinfo == NULL) {
/* new UE: tell the UE to reestablish RLC */
struct NR_CellGroupConfig__rlc_BearerToAddModList *addmod = new_CellGroup->rlc_BearerToAddModList;
+113 -25
View File
@@ -1250,7 +1250,10 @@ static void rrc_gNB_generate_RRCReestablishment(rrc_gNB_ue_context_t *ue_context
}
/// @brief Function tha processes RRCReestablishmentComplete message sent by the UE, after RRCReestasblishment request.
static void rrc_gNB_process_RRCReestablishmentComplete(gNB_RRC_INST *rrc, gNB_RRC_UE_t *ue_p, const uint8_t xid)
static void rrc_gNB_process_RRCReestablishmentComplete(gNB_RRC_INST *rrc,
gNB_RRC_UE_t *ue_p,
const uint8_t xid,
const sctp_assoc_t assoc_id)
{
LOG_I(NR_RRC, "UE %d Processing NR_RRCReestablishmentComplete from UE\n", ue_p->rrc_ue_id);
@@ -1274,26 +1277,40 @@ static void rrc_gNB_process_RRCReestablishmentComplete(gNB_RRC_INST *rrc, gNB_RR
/* PDCP Reestablishment of DRBs according to 5.3.5.6.5 of 3GPP TS 38.331 (over E1) */
cuup_notify_reestablishment(rrc, ue_p);
// 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
f1_ue_data_t ue_data = cu_get_f1_ue_data(ue_p->rrc_ue_id);
RETURN_IF_INVALID_ASSOC_ID(ue_data.du_assoc_id);
f1ap_ue_context_mod_req_t ue_context_modif_req = {
.gNB_CU_ue_id = ue_p->rrc_ue_id,
.gNB_DU_ue_id = ue_data.secondary_ue,
};
bool different_du_detected = !ue_p->f1_ue_context_active;
if (different_du_detected) {
/** 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);
return;
}
rrc_f1_ue_context_setup_for_target_du(rrc, ue_p, du, 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
f1ap_ue_context_mod_req_t ue_context_modif_req = {
.gNB_CU_ue_id = ue_p->rrc_ue_id,
.gNB_DU_ue_id = ue_data.secondary_ue,
};
// Request CellGroupConfig from DU in the response
ue_context_modif_req.gNB_DU_Configuration_Query = calloc_or_fail(1, sizeof(bool));
*ue_context_modif_req.gNB_DU_Configuration_Query = true;
/** Send UE context modification request to DU, which will respond with CellGroupConfig
* containing reestablishRLC flags for re-establishment.
* The response will be handled in rrc_CU_process_ue_context_modification_response()
* which will store the encoded CellGroupConfig and trigger RRC Reconfiguration */
rrc->mac_rrc.ue_context_modification_request(ue_data.du_assoc_id, &ue_context_modif_req);
// Request CellGroupConfig from DU in the response
ue_context_modif_req.gNB_DU_Configuration_Query = calloc_or_fail(1, sizeof(*ue_context_modif_req.gNB_DU_Configuration_Query));
*ue_context_modif_req.gNB_DU_Configuration_Query = true;
/** Send UE context modification request to DU, which will respond with CellGroupConfig
* containing reestablishRLC flags for re-establishment.
* The response will be handled in rrc_CU_process_ue_context_modification_response()
* which will store the encoded CellGroupConfig and trigger RRC Reconfiguration */
rrc->mac_rrc.ue_context_modification_request(ue_data.du_assoc_id, &ue_context_modif_req);
free_ue_context_mod_req(&ue_context_modif_req);
}
}
int nr_rrc_reconfiguration_req(gNB_RRC_INST *rrc, gNB_RRC_UE_t *ue_p, const int dl_bwp_id, const int ul_bwp_id)
@@ -1546,11 +1563,46 @@ static void rrc_handle_RRCReestablishmentRequest(gNB_RRC_INST *rrc,
if (assoc_id != ue_data.du_assoc_id) {
/* Different DU scenario - physCellId differs because UE is re-establishing on a different DU.
* Re-establishment is allowed, UE Context Setup will be triggered at RRCReestablishmentComplete. */
// Store original du_assoc_id and du_ue_id for immediate release
const sctp_assoc_t old_du_assoc_id = ue_data.du_assoc_id;
const uint32_t old_du_ue_id = ue_data.secondary_ue;
LOG_I(NR_RRC,
"UE %d: Re-establishment on different DU (physCellId %ld from old cell != %d from new DU)\n",
"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->nr_pci,
ue_data.du_assoc_id,
assoc_id);
// Update to new DU
ue_data.du_assoc_id = assoc_id;
/* Release old DU context immediately per 3GPP TS 38.473 §8.3.3.2:
* "Interactions with UE Context Setup procedure:
* The UE Context Release procedure may be performed before the UE Context Setup
* procedure to release an existing UE-associated logical F1-connection and related
* resources in the gNB-DU" */
DevAssert(old_du_ue_id != 0);
RETURN_IF_INVALID_ASSOC_ID(old_du_assoc_id);
f1ap_ue_context_rel_cmd_t cmd = {
.gNB_CU_ue_id = UE->rrc_ue_id,
.gNB_DU_ue_id = old_du_ue_id,
.cause = F1AP_CAUSE_RADIO_NETWORK,
.cause_value = F1AP_CauseRadioNetwork_normal_release,
};
rrc->mac_rrc.ue_context_release_command(old_du_assoc_id, &cmd);
LOG_I(NR_RRC,
"UE %d: Re-establishment on different DU - releasing old DU immediately (assoc_id %d, DU UE ID %u)\n",
UE->rrc_ue_id,
old_du_assoc_id,
old_du_ue_id);
// on a new DU: we will have to send f1_ue_context again
UE->f1_ue_context_active = false;
// Update f1_ue_data after release
bool success = cu_update_f1_ue_data(UE->rrc_ue_id, &ue_data);
DevAssert(success);
} else {
/* Same DU but different physCellId - "too fast movement" scenario:
* UE was moving from previous cell so quickly that RRCReestablishment for previous cell was received in this cell */
@@ -1763,17 +1815,21 @@ static void rrc_gNB_process_MeasurementReport(gNB_RRC_INST *rrc, gNB_RRC_UE_t *U
LOG_E(NR_RRC, "Incoming Report Type: %d is not supported! \n", report_config->choice.reportConfigNR->reportType.present);
}
static int handle_rrcReestablishmentComplete(gNB_RRC_INST *rrc, gNB_RRC_UE_t *UE, const NR_RRCReestablishmentComplete_t *cplt)
static void handle_rrcReestablishmentComplete(gNB_RRC_INST *rrc,
gNB_RRC_UE_t *UE,
const uint32_t gNB_DU_ue_id,
const NR_RRCReestablishmentComplete_t *cplt,
const sctp_assoc_t assoc_id)
{
NR_RRCReestablishmentComplete__criticalExtensions_PR p = cplt->criticalExtensions.present;
if (p != NR_RRCReestablishmentComplete__criticalExtensions_PR_rrcReestablishmentComplete) {
LOG_E(NR_RRC, "UE %d: expected presence of rrcReestablishmentComplete, but message has %d\n", UE->rrc_ue_id, p);
return -1;
return;
}
rrc_gNB_process_RRCReestablishmentComplete(rrc, UE, cplt->rrc_TransactionIdentifier);
rrc_gNB_process_RRCReestablishmentComplete(rrc, UE, cplt->rrc_TransactionIdentifier, assoc_id);
UE->ue_reestablishment_counter++;
return 0;
}
/**
@@ -2079,7 +2135,7 @@ static void rrc_gNB_generate_UECapabilityEnquiry(gNB_RRC_INST *rrc, gNB_RRC_UE_t
nr_rrc_transfer_protected_rrc_message(rrc, ue, DL_SCH_LCID_DCCH, msg_id, buffer, size);
}
static int rrc_gNB_decode_dcch(gNB_RRC_INST *rrc, const f1ap_ul_rrc_message_t *msg)
static int rrc_gNB_decode_dcch(gNB_RRC_INST *rrc, const f1ap_ul_rrc_message_t *msg, const sctp_assoc_t assoc_id)
{
/* we look up by CU UE ID! Do NOT change back to RNTI! */
rrc_gNB_ue_context_t *ue_context_p = rrc_gNB_get_ue_context(rrc, msg->gNB_CU_ue_id);
@@ -2184,7 +2240,8 @@ static int rrc_gNB_decode_dcch(gNB_RRC_INST *rrc, const f1ap_ul_rrc_message_t *m
case NR_UL_DCCH_MessageType__c1_PR_rrcReestablishmentComplete:
LOG_UE_UL_EVENT(UE, "Received RRCReestablishmentComplete\n");
handle_rrcReestablishmentComplete(rrc, UE, ul_dcch_msg->message.choice.c1->choice.rrcReestablishmentComplete);
const NR_RRCReestablishmentComplete_t *rc = ul_dcch_msg->message.choice.c1->choice.rrcReestablishmentComplete;
handle_rrcReestablishmentComplete(rrc, UE, msg->gNB_DU_ue_id, rc, assoc_id);
break;
default:
@@ -2706,6 +2763,37 @@ static void rrc_CU_process_ue_modification_required(MessageDef *msg_p, instance_
return;
}
/* Check if UE is accessing a different DU than its current one */
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);
/** Refuse UE Context Modification Required from any DU if:
* UE Context Setup Request was sent but Setup Response hasn't arrived yet.
* This includes the case where UE is re-establishing on a different DU. The CU has
* already triggered UE Context Setup on the new DU (per TS 38.401 §8.7), and Setup Response
* is pending. Modification Required from any DU during this transition should be refused
* to avoid conflicts with the ongoing Setup procedure.
*
* Note: according to TS 38.401 §8.7 "it is assumed that the UE accessed the original gNB-DU
* where the UE context is available for that UE, and either steps 9-10 or steps 9’-10’
* may be executed or both could be skipped."
* By design, CU-CP will trigger UE Context Setup Modification Request and DU does not send
* Modification Required during re-establishment. */
if (!UE->f1_ue_context_active) {
LOG_W(NR_RRC,
"UE %d: UE Context Modification Required received while UE Context Setup is pending (CU UE ID %d), refusing\n",
UE->rrc_ue_id,
required->gNB_CU_ue_id);
f1ap_ue_context_modif_refuse_t refuse = {
.gNB_CU_ue_id = required->gNB_CU_ue_id,
.gNB_DU_ue_id = required->gNB_DU_ue_id,
.cause = F1AP_CAUSE_RADIO_NETWORK,
.cause_value = F1AP_CauseRadioNetwork_interaction_with_other_procedure,
};
rrc->mac_rrc.ue_context_modification_refuse(msg_p->ittiMsgHeader.originInstance, &refuse);
return;
}
if (required->du_to_cu_rrc_information && required->du_to_cu_rrc_information->cellGroupConfig) {
gNB_RRC_UE_t *UE = &ue_context_p->ue_context;
LOG_I(RRC,
@@ -3152,7 +3240,7 @@ void *rrc_gnb_task(void *args_p) {
/* Messages from PDCP */
/* From DU -> CU */
case F1AP_UL_RRC_MESSAGE:
rrc_gNB_decode_dcch(RC.nrrrc[instance], &F1AP_UL_RRC_MESSAGE(msg_p));
rrc_gNB_decode_dcch(RC.nrrrc[instance], &F1AP_UL_RRC_MESSAGE(msg_p), msg_p->ittiMsgHeader.originInstance);
free_ul_rrc_message_transfer(&F1AP_UL_RRC_MESSAGE(msg_p));
break;