diff --git a/CMakeLists.txt b/CMakeLists.txt
index 836e546a62..50da604e2a 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -1271,6 +1271,7 @@ set(L2_NR_SRC
${NR_RRC_DIR}/rrc_gNB_radio_bearers.c
${NR_RRC_DIR}/rrc_gNB_cuup.c
${NR_RRC_DIR}/rrc_gNB_du.c
+ ${NR_RRC_DIR}/rrc_gNB_mobility.c
)
set(L2_SRC_UE
diff --git a/common/utils/telnetsrv/telnetsrv_ci.c b/common/utils/telnetsrv/telnetsrv_ci.c
index 9f3c03c39a..700ff73135 100644
--- a/common/utils/telnetsrv/telnetsrv_ci.c
+++ b/common/utils/telnetsrv/telnetsrv_ci.c
@@ -71,30 +71,41 @@ int get_single_rnti(char *buf, int debug, telnet_printfunc_t prnt)
return 0;
}
+rrc_gNB_ue_context_t *get_single_rrc_ue(void)
+{
+ rrc_gNB_ue_context_t *ue = NULL;
+ rrc_gNB_ue_context_t *l = NULL;
+ int n = 0;
+ RB_FOREACH (l, rrc_nr_ue_tree_s, &RC.nrrrc[0]->rrc_ue_head) {
+ if (ue == NULL)
+ ue = l;
+ n++;
+ }
+ if (!ue) {
+ printf("could not find any UE in RRC\n");
+ }
+ if (n > 1) {
+ printf("more than one UE in RRC present\n");
+ ue = NULL;
+ }
+
+ return ue;
+}
+
int get_reestab_count(char *buf, int debug, telnet_printfunc_t prnt)
{
if (!RC.nrrrc)
ERROR_MSG_RET("no RRC present, cannot list counts\n");
rrc_gNB_ue_context_t *ue = NULL;
- int rnti = -1;
if (!buf) {
- rrc_gNB_ue_context_t *l = NULL;
- int n = 0;
- RB_FOREACH(l, rrc_nr_ue_tree_s, &RC.nrrrc[0]->rrc_ue_head) {
- if (ue == NULL) ue = l;
- n++;
- }
+ ue = get_single_rrc_ue();
if (!ue)
- ERROR_MSG_RET("could not find any UE in RRC\n");
- if (n > 1)
- ERROR_MSG_RET("more than one UE in RRC present\n");
+ ERROR_MSG_RET("no single UE in RRC present\n");
} else {
- rnti = strtol(buf, NULL, 16);
- if (rnti < 1 || rnti >= 0xfffe)
- ERROR_MSG_RET("RNTI needs to be [1,0xfffe]\n");
- ue = rrc_gNB_get_ue_context_by_rnti_any_du(RC.nrrrc[0], rnti);
+ ue_id_t ue_id = strtol(buf, NULL, 10);
+ ue = rrc_gNB_get_ue_context(RC.nrrrc[0], ue_id);
if (!ue)
- ERROR_MSG_RET("could not find UE with RNTI %04x in RRC\n");
+ ERROR_MSG_RET("could not find UE with ue_id %d in RRC\n");
}
prnt("UE RNTI %04x reestab %d reconfig %d\n",
@@ -125,11 +136,43 @@ int trigger_reestab(char *buf, int debug, telnet_printfunc_t prnt)
return 0;
}
+extern void nr_HO_F1_trigger_telnet(gNB_RRC_INST *rrc, uint32_t rrc_ue_id);
+/**
+ * @brief Trigger F1 handover for UE
+ * @param buf: RRC UE ID or NULL for the first UE in list
+ * @param debug: Debug flag
+ * @param prnt: Print function
+ * @return 0 on success, -1 on failure
+ */
+int rrc_gNB_trigger_f1_ho(char *buf, int debug, telnet_printfunc_t prnt)
+{
+ if (!RC.nrrrc)
+ ERROR_MSG_RET("no RRC present, cannot list counts\n");
+ rrc_gNB_ue_context_t *ue = NULL;
+ if (!buf) {
+ ue = get_single_rrc_ue();
+ if (!ue)
+ ERROR_MSG_RET("no single UE in RRC present\n");
+ } else {
+ ue_id_t ue_id = strtol(buf, NULL, 10);
+ ue = rrc_gNB_get_ue_context(RC.nrrrc[0], ue_id);
+ if (!ue)
+ ERROR_MSG_RET("could not find UE with ue_id %d in RRC\n", ue_id);
+ }
+
+ gNB_RRC_UE_t *UE = &ue->ue_context;
+ nr_HO_F1_trigger_telnet(RC.nrrrc[0], UE->rrc_ue_id);
+ prnt("RRC F1 handover triggered for UE %u\n", UE->rrc_ue_id);
+
+ return 0;
+}
+
static telnetshell_cmddef_t cicmds[] = {
- {"get_single_rnti", "", get_single_rnti},
- {"force_reestab", "[rnti(hex,opt)]", trigger_reestab},
- {"get_reestab_count", "[rnti(hex,opt)]", get_reestab_count},
- {"", "", NULL},
+ {"get_single_rnti", "", get_single_rnti},
+ {"force_reestab", "[rnti(hex,opt)]", trigger_reestab},
+ {"get_reestab_count", "[rnti(hex,opt)]", get_reestab_count},
+ {"trigger_f1_ho", "[rrc_ue_id(int,opt)]", rrc_gNB_trigger_f1_ho},
+ {"", "", NULL},
};
static telnetshell_vardef_t civars[] = {
diff --git a/doc/F1AP/F1-design.md b/doc/F1AP/F1-design.md
index 4690639d5b..06f8a3115b 100644
--- a/doc/F1AP/F1-design.md
+++ b/doc/F1AP/F1-design.md
@@ -59,8 +59,8 @@ This is the current status:
* SA
* Single cell per DU
* Multiple DUs connected to one CU (both CP and UP)
+ * Mobility between DUs connected to a single CU
- Not supported:
- * Mobility
* NSA
## F1-C
diff --git a/doc/FEATURE_SET.md b/doc/FEATURE_SET.md
index eb1027bb3e..21e2f5ca8d 100644
--- a/doc/FEATURE_SET.md
+++ b/doc/FEATURE_SET.md
@@ -137,6 +137,7 @@ These modes of operation are supported:
- evaluation of CQI report
- MAC scheduling of SR reception
- Support of up to 16 UEs (can be increased to 32)
+- Intra-frequency handover
## gNB RLC
@@ -221,6 +222,7 @@ These modes of operation are supported:
- Interface with RRC
- Interface with GTP-u (tunnel creation/handling for F1-U interface)
- One CU(-CP) can handle multiple DUs
+- Support for intra-CU mobility (across DUs)
## gNB E1AP
diff --git a/doc/MAC/mac-usage.md b/doc/MAC/mac-usage.md
index e6241202c4..4c7bf4df8e 100644
--- a/doc/MAC/mac-usage.md
+++ b/doc/MAC/mac-usage.md
@@ -143,10 +143,13 @@ In the last lines:
# Configuration of the MAC
-## Split-related options
+## Split-related options (running in a DU)
See [nFAPI documentation](../L2NFAPI.md) or [Aerial
-tutorial](../Aerial_FAPI_Split_Tutorial.md).
+tutorial](../Aerial_FAPI_Split_Tutorial.md) for information about the FAPI
+split.
+
+See [F1 documentation](../F1-design.md) for information about the F1 split.
## MAC scheduler-related configuration options
diff --git a/doc/README.md b/doc/README.md
index ba7e5fbd12..7fc28f061a 100644
--- a/doc/README.md
+++ b/doc/README.md
@@ -53,6 +53,7 @@ There is some general information in the [OpenAirInterface Gitlab Wiki](https://
- [How to run with E2 agent](../openair2/E2AP/README.md)
- [How to run the physical simulators](./physical-simulators.md)
- [How to setup OAI with Nvidia Aerial and Foxconn](./Aerial_FAPI_Split_Tutorial.md)
+- [How to do a handover](./handover-tutorial.md)
Legacy unmaintained files:
- [`L2NFAPI_NOS1.md`](./L2NFAPI_NOS1.md), [`L2NFAPI_S1.md`](./L2NFAPI_S1.md):
diff --git a/doc/RRC/ho.mmd b/doc/RRC/ho.mmd
new file mode 100644
index 0000000000..f86e36a087
--- /dev/null
+++ b/doc/RRC/ho.mmd
@@ -0,0 +1,9 @@
+block-beta
+ columns 5
+ space space DU0 space UE
+ CU space space space space
+ space space DU1 space pos["new pos"]
+ CU<-- "F1" -->DU0
+ CU<-- "F1" -->DU1
+ DU0-- "Uu" -->UE
+ UE-- "move" -->pos
diff --git a/doc/RRC/ho.png b/doc/RRC/ho.png
new file mode 100644
index 0000000000..dd93d6fd1a
Binary files /dev/null and b/doc/RRC/ho.png differ
diff --git a/doc/RRC/rrc-dev.md b/doc/RRC/rrc-dev.md
new file mode 100644
index 0000000000..d511421b01
--- /dev/null
+++ b/doc/RRC/rrc-dev.md
@@ -0,0 +1,318 @@
+This document contains documentation for the 5G RRC layer, destined towards
+developers. It explains the basic working of the RRC, and various UE procedure
+schemes (connection, reestablishment, handover) including their interworking
+with other layers.
+
+User documentation, such as general configuration options, are described in [a
+separate page](./rrc-usage.md).
+
+[[_TOC_]]
+
+# General
+
+5G RRC is basically an ITTI message queue with associated handlers. It
+sequentially reads received ITTI messages and handles them through the function
+`rrc_gnb_task()` (constituing the thread entry point function). In this
+function, one can see three main groups of messages in a big switch statement:
+
+- NGAP (messages starting with `NGAP_`)
+- F1AP (messages starting with `F1AP_`)
+- E1AP (messages starting with `E1AP_`)
+
+For each message, a corresponding handler is called. The messages are roughly
+named according to the 3GPP specification messages, so it should already be
+possible to find the message in the switch based on a message name in the spec.
+
+Note that RRC is inherently single-threaded, and processes messages in a FIFO
+order.
+
+# Sequence Diagrams of UE procedures
+
+The following section presents a number of common UE procedures for connection
+establishment&control, bearer establishment, etc. The intention is to help
+developers find specific functions within RRC in the context of these
+procedures. For more information on message handlers at F1 and E1 layers,
+please refer to the respective [F1 documentation](../F1AP/F1-design.md) and [E1
+documentation](../E1AP/E1-design.md).
+
+For more information on these procedures, please also refer to 3GPP TS 38.401
+(NG-RAN Architecture Description) and O-RAN.WG5.C.1-v11 (NR C-plane profile).
+
+A lot of the following diagrams would show an exchange between CU-CP, DU, and
+UE, in which the CU-CP forwards an "RRC Message" to the UE via an F1AP DL RRC
+Message Transfer through the DU, and correspondingly receives the "RRC Message
+Answer", as follows:
+
+```mermaid
+sequenceDiagram
+ participant ue as UE
+ participant du as DU
+ participant cucp as CU-CP
+ cucp->>du: F1AP DL RRC Msg Transfer (RRC Message)
+ du->>ue: RRC Message
+ ue->>du: RRC Message Answer
+ du->>cucp: F1AP UL RRC Msg Transfer (RRC Message Answer)
+```
+
+To better utilize horizontal space, these exchanges have been collapsed as
+follows, but should be read as the above exchange:
+
+```mermaid
+sequenceDiagram
+ participant ue as UE
+ participant du as DU
+ participant cucp as CU-CP
+ cucp->>ue: F1AP DL RRC Msg Transfer (RRC Message)
+ ue->>cucp: F1AP UL RRC Msg Transfer (RRC Message Answer)
+```
+
+## Initial connection setup/Registration
+
+This sequence diagram shows the principal steps for an initial UE connection.
+This can either happen through a _Registration Request_ (e.g., UE connects
+"from flight mode"), or a _Service Request_ (i.e., UE connects after leaving
+coverage). Both requests are handled similarly by the gNB, and basically
+distinguish themselves, from the point of view of the gNB, by having PDU
+sessions in the NGAP Initial Context Setup Request present (Service Request) or
+not (Registration Request, with following PDU Session Resource Setup Request
+procedure).
+
+```mermaid
+sequenceDiagram
+ participant ue as UE
+ participant du as DU
+ participant cucp as CU-CP
+ participant cuup as CU-UP
+ participant amf as AMF
+
+ ue->>du: Msg1/Preamble
+ du->>ue: Msg2/RAR
+ ue->>cucp: F1AP Initial UL RRC Msg Tr (RRC Setup Req, in Msg3)
+ Note over cucp: rrc_handle_RRCSetupRequest()
+ cucp->>ue: F1AP DL RRC Msg Transfer (RRC Setup, in Msg4)
+ ue->>cucp: F1AP UL RRC Msg Transfer (RRC Setup Complete)
+ Note over cucp: rrc_handle_RRCSetupComplete()
+%% TODO: when is RRC connected?
+ cucp->>amf: NGAP Initial UE Message (NAS Registration/Service Req)
+ Note over amf,ue: NAS Authentication Procedure (see 24.501)
+ Note over amf,ue: NAS Security Procedure (see 24.501)
+ amf->>cucp: NGAP Initial Context Setup Req
+ Note over cucp: rrc_gNB_process_NGAP_INITIAL_CONTEXT_SETUP_REQ()
+ Note over cucp: rrc_gNB_generate_SecurityModeCommand()
+ cucp->>ue: F1AP DL RRC Msg Transfer (RRC Security Mode Command)
+ ue->>cucp: F1AP UL RRC Msg Transfer (RRC Security Mode Complete)
+ Note over cucp: rrc_gNB_decode_dcch() (inline)
+ opt No UE Capabilities present
+ Note over cucp: rrc_gNB_generate_UECapabilityEnquiry()
+ cucp->>ue: F1AP DL RRC Msg Transfer (RRC UE Capability Enquiry)
+ ue->>cucp: F1AP UL RRC Msg Transfer (RRC UE Capability Information)
+ Note over cucp: handle_ueCapabilityInformation()
+ end
+ opt PDU Sessions in NGAP Initial Context Setup Req present
+ Note over cucp: trigger_bearer_setup()
+ cucp->>cuup: E1AP Bearer Context Setup Req
+ cuup->>cucp: E1AP Bearer Context Setup Resp
+ Note over cucp: rrc_gNB_process_e1_bearer_context_setup_resp()
+ cucp->>du: F1AP UE Context Setup Req
+ du->>cucp: F1AP UE Context Setup Resp
+ Note over cucp: rrc_CU_process_ue_context_setup_response()
+ Note over cucp: e1_send_bearer_updates()
+ cucp->>cuup: E1AP Bearer Context Modification Req
+ cucp->>ue: F1AP DL RRC Msg Transfer (RRC Reconfiguration)
+ cuup->>cucp: E1AP Bearer Context Modification Resp
+ ue->>cucp: F1AP UL RRC Msg Transfer (RRC Reconfiguration Complete)
+ Note over cucp: handle_rrcReconfigurationComplete()
+ end
+ Note over cucp: rrc_gNB_send_NGAP_INITIAL_CONTEXT_SETUP_RESP()
+ cucp->>amf: NGAP Initial Context Setup Resp (NAS Registration/Service Complete)
+```
+
+Note that if no PDU session is present in the NGAP Initial UE Context Setup
+Req, no F1AP UE Context Setup will be observed during this initial phase.
+
+If there is no PDU session set up during NGAP Initial Context Setup Req, the UE
+typically requests a PDU session(s) through a NAS procedure, which is basically
+the same code paths as the above optional PDU Session setup during an NGAP
+Initial Context Setup procedure:
+
+```mermaid
+sequenceDiagram
+ participant ue as UE
+ participant du as DU
+ participant cucp as CU-CP
+ participant cuup as CU-UP
+ participant amf as AMF
+
+ ue->>cucp: F1AP UL RRC Msg Transfer (RRC UL Information Transfer)
+ cucp->>amf: NGAP UL NAS Transport (NAS PDU Session Establishment Req)
+ amf->>cucp: NGAP PDU Session Resource Setup Req (NAS PDU Session Establishment Accept)
+ Note over cucp: rrc_gNB_process_NGAP_PDUSESSION_SETUP_REQ()
+ Note over cucp: trigger_bearer_setup()
+ cucp->>cuup: E1AP Bearer Context Setup Req
+ cuup->>cucp: E1AP Bearer Context Setup Resp
+ Note over cucp: rrc_gNB_process_e1_bearer_context_setup_resp()
+ cucp->>du: F1AP UE Context Setup Req
+ du->>cucp: F1AP UE Context Setup Resp
+ Note over cucp: rrc_CU_process_ue_context_setup_response()
+ Note over cucp: e1_send_bearer_updates()
+ cucp->>cuup: E1AP Bearer Context Modification Req
+ cucp->>ue: F1AP DL RRC Msg Transfer (RRC Reconfiguration + NAS PDU Session Establishment Accept)
+ cuup->>cucp: E1AP Bearer Context Modification Resp
+ ue->>cucp: F1AP UL RRC Msg Transfer (RRC Reconfiguration Complete)
+ Note over cucp: handle_rrcReconfigurationComplete()
+ Note over cucp: rrc_gNB_send_NGAP_PDUSESSION_SETUP_RESP()
+ cucp->>amf: NGAP PDU Session Resource Setup Resp
+```
+
+## Reestablishment
+
+The following sequence diagram shows the principal steps during a
+reestablishment request. When handling the RRC Reestablishment Request at the
+CU-CP, it notably checks if this UE is already known, if the UE is still at the
+same DU as it used to be previously (might change because of, or despite
+handover), and other validation checks. If all checks pass, the CU-CP continues
+with the reestablishment procedure; if any check fails, the CU-CP tries to
+release the old UE at the AMF, and continues with the "normal" connection setup
+(registration/service request) described above.
+
+```mermaid
+sequenceDiagram
+ participant ue as UE
+ participant du as DU
+ participant cucp as CU-CP
+ participant cuup as CU-UP
+ participant amf as AMF
+
+ ue->>du: Msg1/Preamble
+ du->>ue: Msg2/RAR
+ ue->>du: Msg3 w/ RRC Reestabishment Request (old C-RNTI/PCI)
+ Note over du: DU creates UE context for new RNTI
+ du->>cucp: F1AP Initial UL RRC Msg Tr (RRC Reestab Req, in Msg3)
+ Note over cucp: rrc_handle_RRCReestablishmentRequest()
+ alt UE known and at the original DU
+ Note over cucp: rrc_gNB_generate_RRCReestablishment()
SRB1 PDCP reestablishment
+ cucp->>du: F1AP DL RRC Msg Tr (old gNB-DU-UE ID)
+ Note over du: Reuse configuration of and delete old UE
(in dl_rrc_message_transfer())
+ du->>ue: RRC Reestablishment
+ ue->>du: RRC Reestablishment Complete
+ du->>cucp: F1AP UL RRC Msg Tr (RRC Reestab Complete)
+ Note over cucp: handle_rrcReestablishmentComplete()
+ Note over cucp: cuup_notify_reestablishment()
+ cucp->>cuup: E1AP Bearer Context Modification Req
+ cucp->>ue: F1AP DL RRC Msg Transfer (RRC Reconfiguration)
+ cuup->>cucp: E1AP Bearer Context Modification Resp
+ ue->>cucp: F1AP UL RRC Msg Transfer (RRC Reconfiguration Complete)
+ Note over cucp: handle_rrcReestablishmentComplete()
+ else Fallback*
+ cucp->>amf: NGAP UE Context Release Request
+ Note over cucp: rrc_gNB_generate_RRCSetup()
+ Note over ue,amf: Continue with connection setup (registration/service request)
+ end
+```
+
+## Handover
+
+The basic handover (HO) structure is as follows. In order to support various
+handover "message passing implementation" (F1AP, NGAP, XnAP), RRC employs
+callbacks to signal HO Accept (`ho_req_ack()`), HO Success (`ho_success()`),
+and HO Cancel (`ho_cancel()`). These can be used to trigger the corresponding
+functionality based on mentioned "message passing implementation".
+
+The following sequence diagram shows the basic functional execution of a
+successful handover in the case of an F1 handover. Note the callbacks as
+mentioned above:
+
+```mermaid
+sequenceDiagram
+ participant ue as UE
+ participant sdu as source DU
+ participant tdu as target DU
+ participant cucp as CU-CP
+ participant cuup as CU-UP
+
+ Note over ue,sdu: UE active on source DU
+ alt HO triggered through A3 event
+ ue->>sdu: RRC Measurement Report
+ sdu->>cucp: F1AP UL RRC Msg Transfer (RRC Measurement Report)
+ Note over cucp: Handover decision (A3 event trigger)
+ else Manual Trigger
+ Note over cucp: Handover decision (e.g., telnet)
+ end
+ Note over cucp: nr_rrc_trigger_f1_ho() ("on source CU")
+ Note over cucp: nr_initiate_handover() ("on target CU")
+ cucp->>tdu: F1AP UE Context Setup Req
+ Note over tdu: Create UE context
+ tdu->>cucp: F1AP UE Context Setup Resp (incl. CellGroupConfig)
+ Note over cucp: rrc_CU_process_ue_context_setup_response() ("on target CU")
+ Note over cucp: cuup_notify_reestablishment()
+ cucp->>cuup: E1AP Bearer Context Modification Req
+ cucp-->>cucp: callback: ho_req_ack()
+ Note over cucp: nr_rrc_f1_ho_acknowledge() ("on source CU")
+ cucp->>sdu: F1AP Context Modification Req (RRC Reconfiguration)
+ cuup->>cucp: E1AP Bearer Context Modification Resp
+ sdu->>ue: RRC Reconfiguration
+ sdu->>cucp: F1AP Context Modification Resp
+ Note over sdu: Stop scheduling UE
+ Note over ue: Resync
+ Note over ue,tdu: RA (Msg1 + Msg2)
+ ue->>tdu: RRC Reconfiguration Complete
+ tdu->>cucp: F1AP UL RRC Msg Transfer (RRC Reconfiguration Complete)
+ Note over cucp: handle_rrcReconfigurationComplete() ("on target CU")
+ cucp-->>cucp: callback: ho_success()
+ Note over cucp: nr_rrc_f1_ho_complete() ("on source CU")
+ cucp->>sdu: F1AP UE Context Release Command
+ sdu->>cucp: F1AP UE Context Release Complete
+ Note over ue,tdu: UE active on target DU
+```
+
+# Structures
+
+## Cells
+
+OAI 5G RRC does not actually handle multiple cells as of now, but multiple DUs,
+each being limited to one cell.
+
+Cell-related data is stored in `nr_rrc_du_container_t`, and kept in a tree
+indexed by the SCTP association ID.
+
+## CU-UPs
+
+CU-UP information is stored in `nr_rrc_cuup_container_t`, and kept in a tree
+indexed by the SCTP association ID.
+
+## Transactions
+
+The RRC keeps track of ongoing transaction (RRC procedures) through a per-UE
+array `xids`, which is indexed with a transaction ID `xid` in `[0,3]` to keep
+track of ongoing transactions. Upon RRC Reconfiguration Complete, these IDs are
+deleted.
+
+Note that while 38.331 requires only one RRC transaction to happen at a time,
+the 5G RRC does not actually ensure this; it only tries to prevent it by
+chaining of transactions and some checks. For instance, the Security Mode
+procedure is followed by a UE capability procedure, and some procedures are not
+executed if others are ongoing (ex.: handover if there is reconfiguratio).
+However, it might be possible to trigger a procedure while another is ongoing.
+
+As of now, no queueing mechanims exists to ensure only one operation is
+ongoing, which would likely also simplify the code.
+
+## Handover
+
+Handover-related data is stored in a per-UE structure of type
+`nr_handover_context_t`. It is a pointer and only set during handover
+operation. This data structure has in turn two pointers, one to the source CU
+(`nr_ho_source_cu_t`), and one to the target CU (`nr_ho_target_cu_t`). In F1,
+both are present. In N2 and Xn, only one pointer is supposed to be set a the
+corresponding CU.
+
+`nr_ho_source_cu_t` contains notably a function pointer `ho_cancel` for
+handover cancel. `nr_ho_target_cu_t` contains function pointers `ho_req_ack`
+for handover request acknowledge and `ho_success` for handover success. As can
+be seen in the sequence diagram above, either the "target CU" or "source CU"
+needs to do an operation, and a "switch" from target to source CU is done using
+these function pointers. For instance, in F1, the handover request acknowledge
+function pointers merely calls another (RRC) function which triggers a
+reconfiguration. In the case of N2, the handover request acknowledge function
+pointer should trigger the NGAP Handover Request Acknowledge, and the handover
+success function pointer should trigger the NGAP Handover Success message.
diff --git a/doc/RRC/rrc-usage.md b/doc/RRC/rrc-usage.md
new file mode 100644
index 0000000000..668c106a0d
--- /dev/null
+++ b/doc/RRC/rrc-usage.md
@@ -0,0 +1,99 @@
+This document describes the basic functioning of the 5G RRC layer, describes
+the periodic output, and explains the various configuration options that
+influence its behavior.
+
+Developer documentation, such as UE connection control flow, reestablishment, or handover, are described in [a separate page](./rrc-dev.md).
+
+[[_TOC_]]
+
+# General
+
+The RRC layer controls the basic connection setup of UEs as well as additional
+procedures. It is the fundamental building block of OAI's CU-CP, and interacts
+with lower layers (DU, basically MAC and RLC) through F1AP messages, and with
+the CU-UP through E1AP messages. More information can be found in the
+respective [F1AP page](../F1-design.md) and [E1AP page](../E1AP/E1-design.md).
+
+# Periodic output and interpretation
+
+Similarly to the scheduler, the RRC periodically prints information about
+connected UEs and DUs into file `nrRRC_stats.log` in the current working
+directory of the executable running the RRC (typically, `nr-softmodem`). The
+output lists first all UEs that are currently connected, and then all DUs, in
+order.
+
+For each UE, it prints:
+
+```
+UE 0 CU UE ID 1 DU UE ID 40352 RNTI 9da0 random identity c0f1ac9824000000:
+ last RRC activity: 5 seconds ago
+ PDU session 0 ID 10 status established
+ associated DU: DU assoc ID 8
+```
+
+where `UE 0` is the UE index, CU UE ID and DU UE IDs are the IDs used to
+exchange over F1 (cf. scheduler logs). Further, it shows RNTI, when the last
+RRC activity happened, the status of PDU sessions and which DU is associated
+(through the SCTP association ID).
+
+
+For each DU, it prints:
+
+```
+1 connected DUs
+[1] DU ID 3584 (gNB-OAI-DU) assoc_id 8: nrCellID 12345678, PCI 0, SSB ARFCN 641280
+ TDD: band 78 ARFCN 640008 SCS 30 (kHz) PRB 106
+```
+
+i.e., an index (`[1]`), the DU ID and it's name, the SCTP association ID
+(`assoc_id 8`, cf. UE information), and DU specific information for the cell
+(cell ID, physical cell identity/PCI, the SSB frequency in ARFCN notation, the
+band and Point A ARFCN, subcarrier spacing/SCS, and the number of resource
+blocks/PRB). Only one cell per DU is supported.
+
+As of now, it does not print information about connected CU-UPs or AMFs.
+
+# Configuration of the RRC
+
+## Split-related options (when running in a CU or CU-CP)
+
+See [F1 documentation](../F1-design.md) for information about the F1 split.
+See [E1 documentation](../E1AP/E1-design.md) for information about the E1 split.
+
+## RRC-specific configuration options
+
+In the `gNBs` section of the gNB/CU/CU-CP configuration file is the
+RRC-specific configuration
+
+### cell-specific options
+
+- `gNB_ID` and `gNB_name`: ID and name of the gNB
+- `tracking_area_code`: the current tracking area code in the range `[0x0001,
+ 0xfffd]`
+- `plmn`: the PLMN, which is a list of entries consisting of:
+ - `mcc`: mobile country code
+ - `mnc`: mobile network code
+ - `mnc_length`: length of mobile network code, allowed values: 2, 3
+ - `snssaiList`: list of NSSAI (network selection slice assistence
+ information, "slice ID"), which itself consists in
+ - `sst`: slice service type, in `[1,255]`
+ - `sd` (default `0xffffff`): slice differentiator, in `[0,0xffffff]`,
+ `0xffffff` is a reserved value and means "no SD"
+ Note that: SST=1, no SD is "eMBB"; SST=2, no SD is "URLLC"; SST=3, no SD
+ is "mMTC"
+- `enable_sdap` (default: false): enable the use of the SDAP layer. If
+ deactivated, a transparent SDAP header is prepended to packets, but no
+ further processing is being done.
+
+### UE-specific configuration
+
+- `um_on_default_drb` (default: false): use RLC UM instead of RLC AM on default
+ bearers
+- `drbs` (default: 0): the number of DRBs to allocate for a UE, only useful for
+ do-ra or phy-test testing
+
+### Neighbor-gNB configuration
+
+ TBD
+
+Refer to the [handover tutorial](../handover-tutorial.md) for more information.
diff --git a/doc/handover-tutorial.md b/doc/handover-tutorial.md
new file mode 100644
index 0000000000..8595fe05d4
--- /dev/null
+++ b/doc/handover-tutorial.md
@@ -0,0 +1,329 @@
+This tutorial explains how to perform handovers. For the moment, only F1
+handovers are supported.
+
+[[_TOC_]]
+
+# Considered setup for F1 handover
+
+We consider one CU and two DUs, connected over F1. The UE is initially
+connected over the radio interface ("Uu") to DU0. Via movement to a new
+position ("new pos"), it will trigger an event such that the CU triggers a
+handover of the UE from DU0 to DU1. Alternatively, a manual trigger can do the
+same.
+
+
+
+# Steps to run F1 handover with OAI UE
+
+Measurement reporting and processing of RRC Reconfiguration for Mobility are
+not completed at the UE. Nevertheless, it is possible to make simple handover
+tests without any radio setup, on a single PC, with the OAI UE, in RFsimulator.
+
+## Build with telnet support
+
+Since the UE does not support any measurement reporting, it cannot trigger a
+handover on its own; it has to be triggered manually through telnet. Thus,
+build both gNB and UE as well as activate the build of telnet to that purpose:
+
+ ./build_oai --ninja --nrUE --gNB --build-lib telnetsrv
+
+## Run the setup
+
+This tutorial assumes you have a core network running; [refer to the
+corresponding tutorial](./NR_SA_Tutorial_OAI_CN5G.md) if this is not the case
+yet.
+
+We will use the TDD configuration files in the repository for the
+[CU](../targets/PROJECTS/GENERIC-NR-5GC/CONF/gnb-cu.sa.f1.conf) as well for
+[DU0](../targets/PROJECTS/GENERIC-NR-5GC/CONF/gnb-du.sa.band78.106prb.rfsim.pci0.conf)
+and
+[DU1](../targets/PROJECTS/GENERIC-NR-5GC/CONF/gnb-du.sa.band78.106prb.rfsim.pci1.conf).
+Note how the DUs differ in their DU ID (for identification at the CU), nr
+cellid (global identification), physical cell ID (identification through UE),
+frequency (limitation at OAI UE), and IP address.
+
+Make sure that using RFsimulator and the CU and each DU, you can achieve a full
+connection of the UE (independently, i.e., running one DU a time). Once this is
+done, follow below steps to trigger a handover:
+
+Start the CU including telnet support:
+
+ sudo ./nr-softmodem --sa -O ../../../targets/PROJECTS/GENERIC-NR-5GC/CONF/gnb-cu.sa.f1.conf --telnetsrv --telnetsrv.shrmod ci
+
+Start DU0:
+
+ sudo ./nr-softmodem --sa --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:
+
+ sudo ./nr-uesoftmodem --sa -C 3450720000 -r 106 --numerology 1 --ssb 516 -O --rfsim --rfsimulator.serveraddr server
+
+Note how the RFsimulator roles have been switched, and RFsim server is at the
+UE side; _this is important_. Replace `` with the UE configuration
+matching your core. If you followed the CN and oaiUE tutorials, you can remove
+`-O ` and replace it with `--uicc0.imsi 001010000000001`.
+
+Once the UE is connected, start DU1:
+
+ sudo ./nr-softmodem --sa --rfsim -O ../../../targets/PROJECTS/GENERIC-NR-5GC/CONF/gnb-du.sa.band78.106prb.rfsim.pci1.conf --rfsimulator.serveraddr 127.0.0.1
+
+Once DU1 is online, you can trigger a handover by issuing this command
+
+ echo ci trigger_f1_ho | nc 127.0.0.1 9090 && echo
+
+You should see how the UE switches from one DU to another. See additional
+information further below.
+
+A number of remarks:
+
+1. It is important that you start DU0, UE, DU1 in order, and having UE connect
+ to DU0 before starting DU1. This is because we don't employ any channel
+ emulation, and the UE could not decode the SIB1 of DU0 to connect.
+1. The RFsimulator roles are switched. Typically, the gNB RFsim acts as the
+ server and the UE as a client. However, RFsim is limited to one server with
+ multiple clients. Since the UE should be able to connect to both DUs, it has
+ to act as the server, and both DUs are a client.
+1. If you see errors `could not open a socket` and/or `Could not start the RF
+ device`, this means that RFsim could not be started. Handover will not work;
+ please refer to the preceding point to fix this (i.e., run the UE as the
+ RFsim server).
+1. In some cases, if the RFsim server is at the UE, the whole system can block;
+ in this case, stop UE and all DUs and restart (the CU can keep running).
+
+## Additional information to the manual HO trigger
+
+You can trigger the handover manually by logging in through telnet:
+
+ telnet 127.0.0.1 9090
+
+and then manually typing `ci trigger_f1_ho`.
+
+The command using `nc` (`netcat`) above triggers handovers directly from bash.
+It does the same as logging in through telnet otherwise, and is a shorthand.
+
+The full command is
+
+ ci trigger_f1_ho [cu-ue-id]
+
+`cu-ue-id` is optional. If only one UE context is present in the RRC, it will
+trigger the handover for this UE in a round-robin fashion across all DUs. If
+there is only one DU, the handover request will be rejected. Similarly, if
+there are multiple UE contexts present at RRC, the handover will be rejected,
+and you have to manually type the CU UE ID. You can see a list of all UEs in
+the file `nrRRC_stats.log` that is printed periodically in the working
+directory of the CU.
+
+# Steps to run F1 handover with COTS UE
+
+You can do handover across DUs with a COTS UE. Note that these DUs should be
+separated by at least multiple meters to ensure that the UE will receive
+different signal strengths when moving between cells.
+
+We only support intra-frequency handovers yet. We have verified with USRPs
+only, although other radios should work as well.
+
+For UEs, we verified Quectel modules and iPhones. Note, though, that not all
+phones might work; for instance, we did not achieve handovers with a OnePlus
+Nord, yet.
+
+## Steps
+
+First, make sure that you can run both DUs with the CU independently. Use the
+same radio hardware for both radios to ensure that both cells can be received
+equally good by the UE.
+
+In order to enable handovers (triggered by the UE), you have to configure the
+neighbour relation of the DUs at the CU. To do so, proceed as follows:
+
+1. To simplify filling the right values in the neighbour configuration, you can
+ rely on the information the CU has about both DUs. Start the CU and both
+ DUs. Navigate to the directory from which you started the CU, and print RRC
+ statistics:
+ ```
+ cat nrRRC_stats.log
+ ```
+1. Fill in the `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
+ to the DU to which it is closer.
+1. Move the UE towards the other DU; it should trigger an "A3 event" (Neighbour
+ Becomes Better than Serving), and the CU will trigger the handover to the
+ other DU.
+
+The output on the terminal should be the same as with RFsim. If no handover is
+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.
+- We did not manage handover with every phone yet -- make sure you use one of
+ the list provided above.
+
+You can also force a handover through telnet as described above. (In fact, the
+decision about a handover is always at the network-side, the UE only "assists"
+through measurements telling the CU that one DU is stronger than others.
+Hence, "forcing" a handover just means that you manually trigger the handover,
+instead of waiting for UE measurement report.)
+
+## Example neighbour configuration
+
+Below is an example neighbour configuration. It is based on this DU information
+gathered from `nrRRC_stats.log` at the CU:
+
+```
+[1] DU ID 3585 (gNB-in-docker) assoc_id 4161: nrCellID 11111111, PCI 1, SSB ARFCN 643296
+ TDD: band 78 ARFCN 642024 SCS 30 (kHz) PRB 106
+[2] DU ID 3584 (gNB-in-docker) assoc_id 4163: nrCellID 12345678, PCI 0, SSB ARFCN 643296
+ TDD: band 78 ARFCN 642024 SCS 30 (kHz) PRB 106
+```
+
+Note how both DUs have one cell on the same frequency and the same radio
+configuration. From this, fill the neighbour list as shown below.
+
+Concretely, the first cell is `12345678` (on DU `[2]`), and it has `11111111`
+(on DU `[1]`) as its neighbour; hence in the first block, you fill
+`physical_cellId` and other values for DU `[1]`, and vice versa.
+
+The below configuration further enables periodic measurements, A2 event
+("Serving becomes worse than threshold"), and A3 events ("Neighbour Becomes
+Better than Serving"). The A2 event can be disabled by setting `enable = 0`. A3
+events cannot be disabled as of now. Further, the A3 events can be made
+specific to cells; `cell_id = -1` means "any cell".
+
+```
+neighbour_list = (
+ {
+ nr_cellid = 12345678;
+ neighbour_cell_configuration = (
+ {
+ gNB_ID = 0xe01;
+ nr_cellid = 11111111;
+ physical_cellId = 1;
+ absoluteFrequencySSB = 643296;
+ subcarrierSpacing = 1; #30 KHz
+ plmn = { mcc = 001; mnc = 01; mnc_length = 2};
+ tracking_area_code = 1;
+ }
+ )
+ },
+ {
+ nr_cellid = 11111111;
+ neighbour_cell_configuration = (
+ {
+ gNB_ID = 0xe00;
+ nr_cellid = 12345678;
+ physical_cellId = 0;
+ absoluteFrequencySSB = 643296;
+ subcarrierSpacing = 1; #30 KHz
+ plmn = { mcc = 001; mnc = 01; mnc_length = 2};
+ tracking_area_code = 1;
+ }
+ )
+ }
+ );
+
+
+nr_measurement_configuration = {
+ Periodical = {
+ enable = 1;
+ includeBeamMeasurements = 1;
+ maxNrofRS_IndexesToReport = 4;
+ };
+
+ A2 = {
+ enable = 1;
+ threshold = 60;
+ timeToTrigger = 1;
+ };
+
+ A3 = ({
+ cell_id = -1; #Default
+ offset = 10;
+ hysteresis = 0;
+ timeToTrigger = 1
+ })
+};
+```
+
+# Handovers triggers and NTN
+
+Typically, in terrestrial networks, channel measurements as well as criteria
+such as load in base stations, is used to determine when and where to handover
+a UE.
+
+## NTN
+
+Doppler spreading and time selectivity of the channel are already a challenge
+for conventional terrestrial networks. However, in the context of
+non-terrestrial networks (NTN), and low-earth orbit (LEO) systems, the
+satellites can have speeds up 7.56 km/s, which is much faster than 0.14 km/s of
+a high-speed train in terrestrial networks. Also, the delays in LEO are more varied
+and longer, and the path loss is larger, because the communication distances is up
+to 10 times longer than in terrestrial networks. Moreover, downlink in LEO
+presents a high interference from adjacent satellite beams, and all these
+features contribute to reduced received signal strength variation in these
+networks compared to terrestrial networks. Typically, the criteria used in
+algorithms presented in conventional terrestrial networks to trigger a handover
+from a gNB to another one is based on signal strength measurements (cf., A3
+event above). However, the reduced received signal strength variation in NTNs
+make these algorithms inefficient for LEO systems. Therefore, algorithms with
+criteria for handover triggering that address the specifics of LEO systems are
+crucial for an efficient handover processing that ensures a robust
+communication with low dropping probability. Some criteria for handovers in NTN
+are as follows:
+
+- **Measurement-based triggering**: This method is based on signal strength
+ measurement, and as stated above, it may not be efficient. The triggering
+ thresholds and which measurement events to use as triggers, as reference
+ signal received power (RSRP), reference signal received quality (RSRQ), or
+ received signal strength indicator (RSSI), should be configured. This method
+ relies on UE estimates and established channel estimation techniques, however
+ it would require neighbouring cell lists which can be hard because the
+ fast-moving of satellites leads to a fast cell coverage deviation.
+
+- **Location-based triggering**: This method is based on UE and satellite
+ location, which can be applied jointly (or not) with another trigger as the
+ measurement-based trigger. For instance, for a deterministic satellite
+ movement, it is possible to predict the configure triggering condition, and
+ the initial association of the UE can be performed based on the distance with
+ the nearest satellite, because we can know the location of the UE and NTN
+ satellite to compute the distance.
+
+- **Elevation angles of source and target cells based triggering**: This method
+ is similar to the previous one, but it is based on the largest elevation
+ angle.
+
+- **Time/timer-based triggering**: This method uses triggering conditions based
+ on UTC time or a timer-based solution, which can also be applied jointly (or
+ not) with another trigger as the measurement-based trigger. The timer-based
+ handover trigger considers the deterministic satellite movement to predict
+ the time duration for which the satellite’s footprint covers a certain zone.
+
+- **Timing advance value-based triggering**: This method uses the timing
+ advance value (independently or jointly with another trigger) to trigger a
+ handover to the target cell. It is appropriate to overcome the Random-Access
+ preamble reception issue, where the UE needs to pre-compensate the instant
+ which sends the preamble. However, UEs with GNSS support are required to
+ perform this method.
+
+## Simple location/time-based trigger
+
+A location-based handover trigger, somewhat aligned with 3GPP Rel.17, taking
+advantage of deterministic satellite movement, can be implemented whereby it is
+assumed that the locations of the UE and the NTN satellite are known. From a
+practical point of view, this means that we know how long it takes to perform
+the handover, and therefore we only need to implement a timer.
+
+It is possible to modify the source code to trigger a time-based handover, or
+combine with other methods. If all what is required is a trigger every 15
+seconds, you can also resort to the telnet-based handover trigger above, and
+run in a terminal:
+
+```bash
+while true; do
+ echo ci trigger_f1_ho | nc -N 127.0.0.1 9090 && echo
+ sleep 15
+done
+```
diff --git a/executables/nr-ue.c b/executables/nr-ue.c
index 01f18dc38d..a06c13a59a 100644
--- a/executables/nr-ue.c
+++ b/executables/nr-ue.c
@@ -38,6 +38,7 @@
#include "LAYER2/nr_rlc/nr_rlc_oai_api.h"
#include "RRC/NR/MESSAGES/asn1_msg.h"
#include "openair1/PHY/TOOLS/phy_scope_interface.h"
+#include "PHY/MODULATION/nr_modulation.h"
/*
* NR SLOT PROCESSING SEQUENCE
@@ -604,9 +605,23 @@ static int UE_dl_preprocessing(PHY_VARS_NR_UE *UE, const UE_nr_rxtx_proc_t *proc
// Start synchronization with a target gNB
if (UE->synch_request.received_synch_request == 1) {
+ fapi_nr_synch_request_t *synch_req = &UE->synch_request.synch_req;
UE->is_synchronized = 0;
- UE->UE_scan_carrier = UE->synch_request.synch_req.ssb_bw_scan;
- UE->target_Nid_cell = UE->synch_request.synch_req.target_Nid_cell;
+ UE->UE_scan_carrier = synch_req->ssb_bw_scan;
+ UE->target_Nid_cell = synch_req->target_Nid_cell;
+
+ uint64_t dl_bw = (12 * fp->N_RB_DL) * (15000 << fp->numerology_index);
+ uint64_t dl_CarrierFreq = (dl_bw >> 1) + (uint64_t)UE->nrUE_config.carrier_config.dl_frequency * 1000;
+ uint64_t ul_bw = (12 * fp->N_RB_UL) * (15000 << fp->numerology_index);
+ uint64_t ul_CarrierFreq = (ul_bw >> 1) + (uint64_t)UE->nrUE_config.carrier_config.uplink_frequency * 1000;
+ if (dl_CarrierFreq != fp->dl_CarrierFreq || ul_CarrierFreq != fp->ul_CarrierFreq) {
+ fp->dl_CarrierFreq = dl_CarrierFreq;
+ fp->ul_CarrierFreq = ul_CarrierFreq;
+ nr_rf_card_config_freq(&openair0_cfg[UE->rf_map.card], ul_CarrierFreq, dl_CarrierFreq, 0);
+ UE->rfdevice.trx_set_freq_func(&UE->rfdevice, &openair0_cfg[0]);
+ init_symbol_rotation(fp);
+ }
+
clean_UE_harq(UE);
UE->synch_request.received_synch_request = 0;
}
diff --git a/openair2/COMMON/f1ap_messages_types.h b/openair2/COMMON/f1ap_messages_types.h
index f159e63ca3..36c3ad4f76 100644
--- a/openair2/COMMON/f1ap_messages_types.h
+++ b/openair2/COMMON/f1ap_messages_types.h
@@ -442,6 +442,8 @@ typedef struct cu_to_du_rrc_information_s {
uint32_t uE_CapabilityRAT_ContainerList_length;
uint8_t * measConfig;
uint32_t measConfig_length;
+ uint8_t *handoverPreparationInfo;
+ uint32_t handoverPreparationInfo_length;
}cu_to_du_rrc_information_t;
typedef struct du_to_cu_rrc_information_s {
@@ -464,6 +466,11 @@ typedef enum ReconfigurationCompl_e {
RRCreconf_success = 2,
} ReconfigurationCompl_t;
+typedef enum TransmActionInd_e {
+ TransmActionInd_STOP,
+ TransmActionInd_RESTART,
+} TransmActionInd_t;
+
typedef struct f1ap_ue_context_setup_s {
uint32_t gNB_CU_ue_id;
uint32_t gNB_DU_ue_id;
@@ -478,6 +485,7 @@ typedef struct f1ap_ue_context_setup_s {
//uint8_t *du_to_cu_rrc_information;
du_to_cu_rrc_information_t *du_to_cu_rrc_information;
uint32_t du_to_cu_rrc_information_length;
+ uint16_t *crnti;
f1ap_drb_to_be_setup_t *drbs_to_be_setup;
uint8_t drbs_to_be_setup_length;
f1ap_drb_to_be_setup_t *drbs_to_be_modified;
@@ -494,6 +502,7 @@ typedef struct f1ap_ue_context_setup_s {
ReconfigurationCompl_t ReconfigComplOutcome;
uint8_t *rrc_container;
int rrc_container_length;
+ TransmActionInd_t *transm_action_ind;
} f1ap_ue_context_setup_t, f1ap_ue_context_modif_req_t, f1ap_ue_context_modif_resp_t;
typedef enum F1ap_Cause_e {
diff --git a/openair2/F1AP/f1ap_cu_ue_context_management.c b/openair2/F1AP/f1ap_cu_ue_context_management.c
index 65c34a10c1..f72ac3c588 100644
--- a/openair2/F1AP/f1ap_cu_ue_context_management.c
+++ b/openair2/F1AP/f1ap_cu_ue_context_management.c
@@ -276,6 +276,21 @@ int CU_send_UE_CONTEXT_SETUP_REQUEST(sctp_assoc_t assoc_id, f1ap_ue_context_setu
OCTET_STRING_fromBuf(measConfig, (const char*)f1ap_ue_context_setup_req->cu_to_du_rrc_information->measConfig,
f1ap_ue_context_setup_req->cu_to_du_rrc_information->measConfig_length);
}
+
+ /* optional */
+ /* HandoverPreparationInformation */
+ if (f1ap_ue_context_setup_req->cu_to_du_rrc_information->handoverPreparationInfo_length > 0) {
+ DevAssert(f1ap_ue_context_setup_req->cu_to_du_rrc_information->handoverPreparationInfo != NULL);
+ F1AP_ProtocolExtensionContainer_10696P60_t *p = calloc(1, sizeof(*p));
+ ie6->value.choice.CUtoDURRCInformation.iE_Extensions = (struct F1AP_ProtocolExtensionContainer *)p;
+ asn1cSequenceAdd(p->list, F1AP_CUtoDURRCInformation_ExtIEs_t, ie_ext);
+ ie_ext->id = F1AP_ProtocolIE_ID_id_HandoverPreparationInformation;
+ ie_ext->criticality = F1AP_Criticality_ignore;
+ ie_ext->extensionValue.present = F1AP_CUtoDURRCInformation_ExtIEs__extensionValue_PR_HandoverPreparationInformation;
+ OCTET_STRING_fromBuf(&ie_ext->extensionValue.choice.HandoverPreparationInformation,
+ (const char *)f1ap_ue_context_setup_req->cu_to_du_rrc_information->handoverPreparationInfo,
+ f1ap_ue_context_setup_req->cu_to_du_rrc_information->handoverPreparationInfo_length);
+ }
}
/* optional */
@@ -607,6 +622,12 @@ int CU_handle_UE_CONTEXT_SETUP_RESPONSE(instance_t instance, sctp_assoc_t assoc_
F1AP_ProtocolIE_ID_id_gNB_DU_UE_F1AP_ID, true);
f1ap_ue_context_setup_resp->gNB_DU_ue_id = ie->value.choice.GNB_DU_UE_F1AP_ID;
LOG_D(F1AP, "f1ap_ue_context_setup_resp->gNB_DU_ue_id is: %d \n", f1ap_ue_context_setup_resp->gNB_DU_ue_id);
+ F1AP_FIND_PROTOCOLIE_BY_ID(F1AP_UEContextSetupResponseIEs_t, ie, container, F1AP_ProtocolIE_ID_id_C_RNTI, false);
+ if (ie) {
+ f1ap_ue_context_setup_resp->crnti = calloc(1, sizeof(uint16_t));
+ *f1ap_ue_context_setup_resp->crnti = ie->value.choice.C_RNTI;
+ }
+
// DUtoCURRCInformation
F1AP_FIND_PROTOCOLIE_BY_ID(F1AP_UEContextSetupResponseIEs_t, ie, container,
F1AP_ProtocolIE_ID_id_DUtoCURRCInformation, true);
@@ -1008,12 +1029,15 @@ int CU_send_UE_CONTEXT_MODIFICATION_REQUEST(sctp_assoc_t assoc_id, f1ap_ue_conte
/* optional */
/* c7. TransmissionActionIndicator */
- if (0) {
+ if (f1ap_ue_context_modification_req->transm_action_ind != NULL) {
asn1cSequenceAdd(out->protocolIEs.list, F1AP_UEContextModificationRequestIEs_t, ie7);
ie7->id = F1AP_ProtocolIE_ID_id_TransmissionActionIndicator;
ie7->criticality = F1AP_Criticality_ignore;
ie7->value.present = F1AP_UEContextModificationRequestIEs__value_PR_TransmissionActionIndicator;
- ie7->value.choice.TransmissionActionIndicator = F1AP_TransmissionActionIndicator_stop;
+ ie7->value.choice.TransmissionActionIndicator = *f1ap_ue_context_modification_req->transm_action_ind;
+ // Stop is guaranteed to be 0, by restart might be different from 1
+ static_assert((int)F1AP_TransmissionActionIndicator_restart == (int)TransmActionInd_RESTART,
+ "mismatch of ASN.1 and internal representation\n");
}
/* optional */
diff --git a/openair2/F1AP/f1ap_du_ue_context_management.c b/openair2/F1AP/f1ap_du_ue_context_management.c
index ad728d5c89..98de177af1 100644
--- a/openair2/F1AP/f1ap_du_ue_context_management.c
+++ b/openair2/F1AP/f1ap_du_ue_context_management.c
@@ -133,8 +133,13 @@ int DU_handle_UE_CONTEXT_SETUP_REQUEST(instance_t instance, sctp_assoc_t assoc_i
/* GNB_DU_UE_F1AP_ID */
F1AP_UEContextSetupRequestIEs_t *ieDU_UE;
F1AP_FIND_PROTOCOLIE_BY_ID(F1AP_UEContextSetupRequestIEs_t, ieDU_UE, container,
- F1AP_ProtocolIE_ID_id_gNB_DU_UE_F1AP_ID, true);
- f1ap_ue_context_setup_req->gNB_DU_ue_id = ieDU_UE->value.choice.GNB_DU_UE_F1AP_ID;
+ F1AP_ProtocolIE_ID_id_gNB_DU_UE_F1AP_ID, false);
+ // this should be optional. At present, the DU uses the RNTI, and hence, this
+ // value cannot exceed 0xffff (or something below, whatever is a valid
+ // C-RNTI). So we use a value > 0xffff to mark optionality
+ f1ap_ue_context_setup_req->gNB_DU_ue_id = -1; // 0xffffffff
+ if (ieDU_UE != NULL)
+ f1ap_ue_context_setup_req->gNB_DU_ue_id = ieDU_UE->value.choice.GNB_DU_UE_F1AP_ID;
/* SpCell_ID */
F1AP_UEContextSetupRequestIEs_t *ieNet;
@@ -171,12 +176,34 @@ int DU_handle_UE_CONTEXT_SETUP_REQUEST(instance_t instance, sctp_assoc_t assoc_i
F1AP_ProtocolIE_ID_id_CUtoDURRCInformation, false);
if(ieCuRrcInfo!=NULL){
f1ap_ue_context_setup_req->cu_to_du_rrc_information = (cu_to_du_rrc_information_t *)calloc(1,sizeof(cu_to_du_rrc_information_t));
- if(ieCuRrcInfo->value.choice.CUtoDURRCInformation.uE_CapabilityRAT_ContainerList!=NULL){
- f1ap_ue_context_setup_req->cu_to_du_rrc_information->uE_CapabilityRAT_ContainerList = (uint8_t *)calloc(1,ieCuRrcInfo->value.choice.CUtoDURRCInformation.uE_CapabilityRAT_ContainerList->size);
- memcpy(f1ap_ue_context_setup_req->cu_to_du_rrc_information->uE_CapabilityRAT_ContainerList, ieCuRrcInfo->value.choice.CUtoDURRCInformation.uE_CapabilityRAT_ContainerList->buf, ieCuRrcInfo->value.choice.CUtoDURRCInformation.uE_CapabilityRAT_ContainerList->size);
- f1ap_ue_context_setup_req->cu_to_du_rrc_information->uE_CapabilityRAT_ContainerList_length = ieCuRrcInfo->value.choice.CUtoDURRCInformation.uE_CapabilityRAT_ContainerList->size;
+ cu_to_du_rrc_information_t *cu2du = f1ap_ue_context_setup_req->cu_to_du_rrc_information;
+ const F1AP_CUtoDURRCInformation_t *cu2duie = &ieCuRrcInfo->value.choice.CUtoDURRCInformation;
+ if(cu2duie->uE_CapabilityRAT_ContainerList!=NULL){
+ cu2du->uE_CapabilityRAT_ContainerList = calloc(1, cu2duie->uE_CapabilityRAT_ContainerList->size);
+ memcpy(cu2du->uE_CapabilityRAT_ContainerList,
+ cu2duie->uE_CapabilityRAT_ContainerList->buf,
+ cu2duie->uE_CapabilityRAT_ContainerList->size);
+ cu2du->uE_CapabilityRAT_ContainerList_length = cu2duie->uE_CapabilityRAT_ContainerList->size;
LOG_D(F1AP, "Size f1ap_ue_context_setup_req->cu_to_du_rrc_information->uE_CapabilityRAT_ContainerList_length: %d \n", f1ap_ue_context_setup_req->cu_to_du_rrc_information->uE_CapabilityRAT_ContainerList_length);
}
+ if (cu2duie->iE_Extensions != NULL) {
+ const F1AP_ProtocolExtensionContainer_10696P60_t *ext = (const F1AP_ProtocolExtensionContainer_10696P60_t *)cu2duie->iE_Extensions;
+ for (int i = 0; i < ext->list.count; ++i) {
+ const F1AP_CUtoDURRCInformation_ExtIEs_t *cu2du_info = ext->list.array[i];
+ switch (cu2du_info->id) {
+ case F1AP_ProtocolIE_ID_id_HandoverPreparationInformation:
+ DevAssert(cu2du_info->extensionValue.present == F1AP_CUtoDURRCInformation_ExtIEs__extensionValue_PR_HandoverPreparationInformation);
+ const F1AP_HandoverPreparationInformation_t *hopi = &cu2du_info->extensionValue.choice.MeasurementTimingConfiguration;
+ cu2du->handoverPreparationInfo = calloc_or_fail(1, hopi->size);
+ memcpy(cu2du->handoverPreparationInfo, hopi->buf, hopi->size);
+ cu2du->handoverPreparationInfo_length = hopi->size;
+ break;
+ default:
+ LOG_W(F1AP, "unsupported CUtoDURRCInformation_ExtIE %ld encountered, ignoring\n", cu2du_info->id);
+ break;
+ }
+ }
+ }
}
/* DRB */
@@ -353,15 +380,12 @@ int DU_send_UE_CONTEXT_SETUP_RESPONSE(sctp_assoc_t assoc_id, f1ap_ue_context_set
/* optional */
/* c4. C_RNTI */
- if (0) {
+ if (resp->crnti!=NULL) {
asn1cSequenceAdd(out->protocolIEs.list, F1AP_UEContextSetupResponseIEs_t, ie4);
ie4->id = F1AP_ProtocolIE_ID_id_C_RNTI;
ie4->criticality = F1AP_Criticality_ignore;
ie4->value.present = F1AP_UEContextSetupResponseIEs__value_PR_C_RNTI;
- //C_RNTI_TO_BIT_STRING(rntiP, &ie->value.choice.C_RNTI);
- ie4->value.choice.C_RNTI=0;
- AssertFatal(false, "not implemented\n");
- LOG_E(F1AP,"RNTI to code!\n");
+ ie4->value.choice.C_RNTI = *resp->crnti;
}
/* optional */
@@ -889,6 +913,21 @@ int DU_handle_UE_CONTEXT_MODIFICATION_REQUEST(instance_t instance, sctp_assoc_t
F1AP_FIND_PROTOCOLIE_BY_ID(F1AP_UEContextModificationRequestIEs_t, ieSrb, container,
F1AP_ProtocolIE_ID_id_SRBs_ToBeSetupMod_List, false);
+ F1AP_UEContextModificationRequestIEs_t *ieTxInd;
+ F1AP_FIND_PROTOCOLIE_BY_ID(F1AP_UEContextModificationRequestIEs_t,
+ ieTxInd,
+ container,
+ F1AP_ProtocolIE_ID_id_TransmissionActionIndicator,
+ false);
+
+ if (ieTxInd != NULL) {
+ f1ap_ue_context_modification_req->transm_action_ind = calloc_or_fail(1, sizeof(*f1ap_ue_context_modification_req->transm_action_ind));
+ *f1ap_ue_context_modification_req->transm_action_ind = ieTxInd->value.choice.TransmissionActionIndicator;
+ // Stop is guaranteed to be 0, by restart might be different from 1
+ static_assert((int)F1AP_TransmissionActionIndicator_restart == (int)TransmActionInd_RESTART,
+ "mismatch of ASN.1 and internal representation\n");
+ }
+
if(ieSrb != NULL) {
f1ap_ue_context_modification_req->srbs_to_be_setup_length = ieSrb->value.choice.SRBs_ToBeSetupMod_List.list.count;
f1ap_ue_context_modification_req->srbs_to_be_setup = calloc(f1ap_ue_context_modification_req->srbs_to_be_setup_length,
diff --git a/openair2/LAYER2/NR_MAC_UE/nr_ue_scheduler.c b/openair2/LAYER2/NR_MAC_UE/nr_ue_scheduler.c
index b15d80aa01..26b2f35e0f 100644
--- a/openair2/LAYER2/NR_MAC_UE/nr_ue_scheduler.c
+++ b/openair2/LAYER2/NR_MAC_UE/nr_ue_scheduler.c
@@ -2240,7 +2240,7 @@ static int get_nr_prach_info_from_ssb_index(prach_association_pattern_t *prach_a
ssb_info_p->mapped_ro[n_mapped_ro]->frame,
ssb_info_p->mapped_ro[n_mapped_ro]->slot,
prach_assoc_pattern->nb_of_frame);
- if ((slot == ssb_info_p->mapped_ro[n_mapped_ro]->slot) &&
+ if ((slot == ssb_info_p->mapped_ro[n_mapped_ro]->slot) && prach_assoc_pattern->prach_conf_period_list[0].nb_of_frame != 0 &&
(ssb_info_p->mapped_ro[n_mapped_ro]->frame == (frame % prach_assoc_pattern->nb_of_frame))) {
uint8_t prach_config_period_nb = ssb_info_p->mapped_ro[n_mapped_ro]->frame / prach_assoc_pattern->prach_conf_period_list[0].nb_of_frame;
uint8_t frame_nb_in_prach_config_period = ssb_info_p->mapped_ro[n_mapped_ro]->frame % prach_assoc_pattern->prach_conf_period_list[0].nb_of_frame;
diff --git a/openair2/LAYER2/NR_MAC_gNB/config.c b/openair2/LAYER2/NR_MAC_gNB/config.c
index bfbb5dcfb5..ea4fbed192 100644
--- a/openair2/LAYER2/NR_MAC_gNB/config.c
+++ b/openair2/LAYER2/NR_MAC_gNB/config.c
@@ -844,9 +844,9 @@ bool nr_mac_prepare_ra_ue(gNB_MAC_INST *nrmac, uint32_t rnti, NR_CellGroupConfig
/* Prepare a new CellGroupConfig to be applied for this UE. We cannot
* immediatly apply it, as we have to wait for the reconfiguration through RRC.
* This function sets up everything to apply the reconfiguration. Later, we
- * will trigger the timer with nr_mac_enable_ue_rrc_processing_timer(); upon
- * expiry nr_mac_apply_cellgroup() will apply the CellGroupConfig (radio config
- * etc). */
+ * will trigger the UE inactivity with nr_mac_interrupt_ue_transmission(); upon
+ * expiry, nr_mac_apply_cellgroup() will apply the CellGroupConfig (radio
+ * config etc). */
bool nr_mac_prepare_cellgroup_update(gNB_MAC_INST *nrmac, NR_UE_info_t *UE, NR_CellGroupConfig_t *CellGroup)
{
DevAssert(nrmac != NULL);
diff --git a/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_RA.c b/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_RA.c
index d6dfa19dd4..3e738db42a 100644
--- a/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_RA.c
+++ b/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_RA.c
@@ -40,7 +40,6 @@
#include "common/utils/LOG/vcd_signal_dumper.h"
#include "common/utils/nr/nr_common.h"
#include "UTIL/OPT/opt.h"
-#include "SIMULATION/TOOLS/sim.h" // for taus
/* rlc */
#include "openair2/LAYER2/nr_rlc/nr_rlc_oai_api.h"
@@ -677,16 +676,6 @@ static NR_RA_t *find_free_nr_RA(NR_RA_t *ra_base, int ra_count, uint16_t preambl
return NULL;
}
-static const NR_RA_t *find_nr_RA_rnti(const NR_RA_t *ra_base, int ra_count, rnti_t rnti)
-{
- for (int i = 0; i < ra_count; ++i) {
- const NR_RA_t *ra = &ra_base[i];
- if (ra->ra_state != nrRA_gNB_IDLE && ra->rnti == rnti)
- return ra;
- }
- return NULL;
-}
-
void nr_initiate_ra_proc(module_id_t module_idP,
int CC_id,
frame_t frameP,
@@ -712,23 +701,13 @@ void nr_initiate_ra_proc(module_id_t module_idP,
}
if (ra->rnti == 0) { // This condition allows for the usage of a preconfigured rnti for the CFRA
- int loop = 0;
- bool exist_connected_ue, exist_in_pending_ra_ue;
- rnti_t trial = 0;
- do {
- // 3GPP TS 38.321 version 15.13.0 Section 7.1 Table 7.1-1: RNTI values
- while (trial < 1 || trial > 0xffef)
- trial = (taus() % 0xffef) + 1;
- exist_connected_ue = find_nr_UE(&nr_mac->UE_info, trial) != NULL;
- exist_in_pending_ra_ue = find_nr_RA_rnti(cc->ra, sizeofArray(cc->ra), ra->rnti) != NULL;
- loop++;
- } while (loop < 100 && (exist_connected_ue || exist_in_pending_ra_ue) );
- if (loop == 100) {
+ bool rnti_found = nr_mac_get_new_rnti(&nr_mac->UE_info, cc->ra, sizeofArray(cc->ra), &ra->rnti);
+ if (!rnti_found) {
LOG_E(NR_MAC, "initialisation random access: no more available RNTIs for new UE\n");
+ nr_clear_ra_proc(ra);
NR_SCHED_UNLOCK(&nr_mac->sched_lock);
return;
}
- ra->rnti = trial;
}
ra->preamble_frame = frameP;
@@ -1698,12 +1677,9 @@ static void nr_generate_Msg2(module_id_t module_idP,
if (ra->cfra) {
NR_UE_info_t *UE = find_nr_UE(&RC.nrmac[module_idP]->UE_info, ra->rnti);
if (UE) {
- const NR_ServingCellConfig_t *servingCellConfig = UE->CellGroup ? UE->CellGroup->spCellConfig->spCellConfigDedicated : NULL;
- uint32_t delay_ms = servingCellConfig && servingCellConfig->downlinkBWP_ToAddModList
- ? NR_RRC_SETUP_DELAY_MS + NR_RRC_BWP_SWITCHING_DELAY_MS
- : NR_RRC_SETUP_DELAY_MS;
- NR_UE_sched_ctrl_t *sched_ctrl = &UE->UE_sched_ctrl;
- sched_ctrl->rrc_processing_timer = (delay_ms << ra->DL_BWP.scs);
+ int delay = nr_mac_get_reconfig_delay_slots(ra->DL_BWP.scs);
+ interrupt_followup_action_t action = UE->reconfigCellGroup ? FOLLOW_INSYNC_RECONFIG : FOLLOW_INSYNC;
+ nr_mac_interrupt_ue_transmission(RC.nrmac[module_idP], UE, action, delay);
}
}
@@ -2248,8 +2224,9 @@ static void nr_generate_Msg4_MsgB(module_id_t module_idP,
// 3GPP TS 38.331 Section 12 Table 12.1-1: UE performance requirements for RRC procedures for UEs
// Msg4 may transmit a RRCReconfiguration, for example when UE sends RRCReestablishmentComplete and MAC CE for C-RNTI in Msg3.
// In that case, gNB will generate a RRCReconfiguration that will be transmitted in Msg4, so we need to apply CellGroup after the Ack,
- // UE->apply_cellgroup was already set when processing RRCReestablishment message
- nr_mac_enable_ue_rrc_processing_timer(nr_mac, UE, UE->apply_cellgroup);
+ // UE->reconfigCellGroup was already set when processing RRCReestablishment message
+ int delay = nr_mac_get_reconfig_delay_slots(UE->current_UL_BWP.scs);
+ nr_mac_interrupt_ue_transmission(nr_mac, UE, UE->interrupt_action, delay);
nr_clear_ra_proc(ra);
} else {
@@ -2291,8 +2268,9 @@ static void nr_check_Msg4_MsgB_Ack(module_id_t module_id, int CC_id, frame_t fra
// 3GPP TS 38.331 Section 12 Table 12.1-1: UE performance requirements for RRC procedures for UEs
// Msg4 may transmit a RRCReconfiguration, for example when UE sends RRCReestablishmentComplete and MAC CE for C-RNTI in Msg3.
// In that case, gNB will generate a RRCReconfiguration that will be transmitted in Msg4, so we need to apply CellGroup after the Ack,
- // UE->apply_cellgroup was already set when processing RRCReestablishment message
- nr_mac_enable_ue_rrc_processing_timer(RC.nrmac[module_id], UE, UE->apply_cellgroup);
+ // UE->reconfigCellGroup already set when processing RRCReestablishment message
+ int delay = nr_mac_get_reconfig_delay_slots(UE->current_UL_BWP.scs);
+ nr_mac_interrupt_ue_transmission(RC.nrmac[module_id], UE, UE->interrupt_action, delay);
nr_clear_ra_proc(ra);
if (sched_ctrl->retrans_dl_harq.head >= 0) {
@@ -2457,8 +2435,9 @@ void nr_schedule_RA(module_id_t module_idP,
ra->contention_resolution_timer--;
if (ra->contention_resolution_timer < 0) {
LOG_W(NR_MAC, "(%d.%d) RA Contention Resolution timer expired for UE 0x%04x, RA procedure failed...\n", frameP, slotP, ra->rnti);
- nr_mac_release_ue(mac, ra->rnti);
- nr_mac_trigger_release_complete(mac, ra->rnti);
+ bool requested = nr_mac_request_release_ue(mac, ra->rnti);
+ if (!requested)
+ nr_mac_release_ue(mac, ra->rnti);
nr_clear_ra_proc(ra);
continue;
}
diff --git a/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_dlsch.c b/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_dlsch.c
index af2a2b24e7..ab9be4a271 100644
--- a/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_dlsch.c
+++ b/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_dlsch.c
@@ -334,9 +334,8 @@ static void nr_store_dlsch_buffer(module_id_t module_id, frame_t frame, sub_fram
const int lcid = c->lcid;
const uint16_t rnti = UE->rnti;
LOG_D(NR_MAC, "In %s: UE %x: LCID %d\n", __FUNCTION__, rnti, lcid);
- if (lcid == DL_SCH_LCID_DTCH && sched_ctrl->rrc_processing_timer > 0) {
+ if (lcid == DL_SCH_LCID_DTCH && nr_timer_is_active(&sched_ctrl->transm_interrupt))
continue;
- }
start_meas(&RC.nrmac[module_id]->rlc_status_ind);
sched_ctrl->rlc_status[lcid] = mac_rlc_status_ind(module_id,
rnti,
@@ -985,7 +984,7 @@ void nr_schedule_ue_spec(module_id_t module_id,
* Possible improvement: take the periodicity from input file.
* If such UE is not scheduled now, it will be by the preprocessor later.
* If we add the CE, ta_apply will be reset */
- if (frame == (sched_ctrl->ta_frame + 100) % 1024) {
+ if (frame == ((sched_ctrl->ta_frame + 100) % 1024) && !nr_timer_is_active(&sched_ctrl->transm_interrupt)) {
sched_ctrl->ta_apply = true; /* the timer is reset once TA CE is scheduled */
LOG_D(NR_MAC, "[UE %04x][%d.%d] UL timing alignment procedures: setting flag for Timing Advance command\n", UE->rnti, frame, slot);
}
diff --git a/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_primitives.c b/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_primitives.c
index 834e42d7a3..5930ef7f44 100644
--- a/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_primitives.c
+++ b/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_primitives.c
@@ -53,6 +53,8 @@
#include "uper_encoder.h"
#include "uper_decoder.h"
+#include "SIMULATION/TOOLS/sim.h" // for taus
+
#define ENABLE_MAC_PAYLOAD_DEBUG
#define DEBUG_gNB_SCHEDULER 1
@@ -2673,6 +2675,16 @@ void mac_remove_nr_ue(gNB_MAC_INST *nr_mac, rnti_t rnti)
memcpy(UE_info->list, newUEs, sizeof(UE_info->list));
NR_SCHED_UNLOCK(&UE_info->mutex);
+ const int CC_id = 0;
+ NR_COMMON_channels_t *cc = &nr_mac->common_channels[CC_id];
+ for (int i = 0; i < NR_NB_RA_PROC_MAX; i++) {
+ NR_RA_t *ra = &cc->ra[i];
+ if (ra->rnti == UE->rnti) {
+ nr_clear_ra_proc(ra);
+ ra->cfra = 0;
+ }
+ }
+
delete_nr_ue_data(UE, nr_mac->common_channels, &UE_info->uid_allocator);
}
@@ -2733,9 +2745,8 @@ void nr_csirs_scheduling(int Mod_idP, frame_t frame, sub_frame_t slot, int n_slo
if (UE_info->sched_csirs & (1 << dl_bwp->bwp_id))
continue;
NR_UE_sched_ctrl_t *sched_ctrl = &UE->UE_sched_ctrl;
- if (sched_ctrl->rrc_processing_timer > 0) {
+ if (nr_timer_is_active(&sched_ctrl->transm_interrupt))
continue;
- }
if (!UE->sc_info.csi_MeasConfig)
continue;
@@ -2949,28 +2960,41 @@ void nr_csirs_scheduling(int Mod_idP, frame_t frame, sub_frame_t slot, int n_slo
}
}
+static void nr_mac_clean_cellgroup(NR_CellGroupConfig_t *cell_group)
+{
+ /* remove a reconfigurationWithSync, we don't need it anymore */
+ if (cell_group->spCellConfig->reconfigurationWithSync != NULL) {
+ ASN_STRUCT_FREE(asn_DEF_NR_ReconfigurationWithSync, cell_group->spCellConfig->reconfigurationWithSync);
+ cell_group->spCellConfig->reconfigurationWithSync = NULL;
+ }
+ /* remove the rlc_BearerToReleaseList, we don't need it anymore */
+ if (cell_group->rlc_BearerToReleaseList != NULL) {
+ struct NR_CellGroupConfig__rlc_BearerToReleaseList *l = cell_group->rlc_BearerToReleaseList;
+ asn_sequence_del(&l->list, l->list.count, /* free */ 1);
+ free(cell_group->rlc_BearerToReleaseList);
+ cell_group->rlc_BearerToReleaseList = NULL;
+ }
+ /* remove reestablishRLC, we don't need it anymore */
+ for (int i = 0; i < cell_group->rlc_BearerToAddModList->list.count; ++i)
+ free_and_zero(cell_group->rlc_BearerToAddModList->list.array[i]->reestablishRLC);
+}
+
static void nr_mac_apply_cellgroup(gNB_MAC_INST *mac, NR_UE_info_t *UE, frame_t frame, sub_frame_t slot)
{
- LOG_D(NR_MAC, "%4d.%2d RNTI %04x: RRC processing timer expired\n", frame, slot, UE->rnti);
+ LOG_D(NR_MAC, "%4d.%2d RNTI %04x: UE inactivity timer expired\n", frame, slot, UE->rnti);
/* check if there is a new CellGroupConfig to be applied */
- if (UE->apply_cellgroup && UE->reconfigCellGroup != NULL) {
- LOG_D(NR_MAC, "%4d.%2d RNTI %04x: Apply CellGroupConfig after RRC processing timer expiry\n", frame, slot, UE->rnti);
+ if (UE->interrupt_action == FOLLOW_INSYNC_RECONFIG && UE->reconfigCellGroup != NULL) {
+ LOG_D(NR_MAC, "%4d.%2d RNTI %04x: Apply CellGroupConfig after UE inactivity\n", frame, slot, UE->rnti);
ASN_STRUCT_FREE(asn_DEF_NR_CellGroupConfig, UE->CellGroup);
UE->CellGroup = UE->reconfigCellGroup;
UE->reconfigCellGroup = NULL;
- UE->apply_cellgroup = false;
+ UE->interrupt_action = FOLLOW_INSYNC;
if (LOG_DEBUGFLAG(DEBUG_ASN1))
xer_fprint(stdout, &asn_DEF_NR_CellGroupConfig, (const void *)UE->CellGroup);
- /* remove the rlc_BearerToReleaseList, we don't need it anymore */
- if (UE->CellGroup->rlc_BearerToReleaseList != NULL) {
- struct NR_CellGroupConfig__rlc_BearerToReleaseList *l = UE->CellGroup->rlc_BearerToReleaseList;
- asn_sequence_del(&l->list, l->list.count, /* free */1);
- free(UE->CellGroup->rlc_BearerToReleaseList);
- UE->CellGroup->rlc_BearerToReleaseList = NULL;
- }
+ nr_mac_clean_cellgroup(UE->CellGroup);
}
NR_ServingCellConfigCommon_t *scc = mac->common_channels[0].ServingCellConfigCommon;
@@ -2987,31 +3011,42 @@ static void nr_mac_apply_cellgroup(gNB_MAC_INST *mac, NR_UE_info_t *UE, frame_t
}
}
-int nr_mac_enable_ue_rrc_processing_timer(gNB_MAC_INST *mac, NR_UE_info_t *UE, bool apply_cellgroup)
+int nr_mac_get_reconfig_delay_slots(NR_SubcarrierSpacing_t scs)
+{
+ /* we previously assumed a specific "slot ahead" value for the PHY processing
+ * time. However, we cannot always know it (e.g., third-party PHY), so simply
+ * assume a tentative worst-case slot processing time */
+ const uint16_t sl_ahead = 10;
+ /* 16ms seems to be the most common: See 38.331 Tab 12.1-1 */
+ int delay_ms = NR_RRC_RECONFIGURATION_DELAY_MS + NR_RRC_BWP_SWITCHING_DELAY_MS;
+ return (delay_ms << scs) + sl_ahead;
+}
+
+int nr_mac_interrupt_ue_transmission(gNB_MAC_INST *mac, NR_UE_info_t *UE, interrupt_followup_action_t action, int slots)
{
DevAssert(mac != NULL);
DevAssert(UE != NULL);
NR_SCHED_ENSURE_LOCKED(&mac->sched_lock);
- const uint16_t sl_ahead = mac->if_inst->sl_ahead;
- // TODO: account for BWP switch with NR_RRC_BWP_SWITCHING_DELAY_MS
- int delay = NR_RRC_RECONFIGURATION_DELAY_MS;
- NR_SubcarrierSpacing_t scs = UE->current_UL_BWP.scs;
+ nr_timer_setup(&UE->UE_sched_ctrl.transm_interrupt, slots, 1);
+ nr_timer_start(&UE->UE_sched_ctrl.transm_interrupt);
+ UE->interrupt_action = action;
- UE->UE_sched_ctrl.rrc_processing_timer = (delay << scs) + sl_ahead;
- UE->apply_cellgroup = apply_cellgroup;
- AssertFatal(!UE->apply_cellgroup || (UE->apply_cellgroup && UE->reconfigCellGroup),
- "logic bug: apply_cellgroup %d and UE->reconfigCellGroup %p: did you try to apply a cellGroup, while none is deposited?\n",
- UE->apply_cellgroup,
- UE->reconfigCellGroup);
-
- // it might happen that timing advance command should be sent during the RRC
- // processing timer. To prevent this, set a variable as if we would have just
+ // it might happen that timing advance command should be sent during the UE
+ // inactivity time. To prevent this, set a variable as if we would have just
// sent it. This way, another TA command will for sure be sent in some
- // frames, after RRC processing timer.
+ // frames, after the inactivity of the UE.
UE->UE_sched_ctrl.ta_frame = (mac->frame - 1 + 1024) % 1024;
- LOG_D(NR_MAC, "UE %04x: Activate RRC processing timer (%d ms)\n", UE->rnti, delay);
+ LOG_D(NR_MAC, "UE %04x: Interrupt UE transmission (%d slots) action %d reconfigCellGroup %p\n", UE->rnti, slots, UE->interrupt_action, UE->reconfigCellGroup);
+ return 0;
+}
+
+int nr_transmission_action_indicator_stop(gNB_MAC_INST *mac, NR_UE_info_t *UE_info)
+{
+ int delay = nr_mac_get_reconfig_delay_slots(UE_info->current_DL_BWP.scs);
+ nr_mac_interrupt_ue_transmission(mac, UE_info, FOLLOW_OUTOFSYNC, delay);
+ LOG_I(NR_MAC, "gNB-DU received the TransmissionActionIndicator with Stop value for UE %04x\n", UE_info->rnti);
return 0;
}
@@ -3073,9 +3108,12 @@ void nr_mac_update_timers(module_id_t module_id,
/* check if UL failure and trigger release request if necessary */
nr_mac_check_ul_failure(mac, UE->rnti, sched_ctrl);
- if (sched_ctrl->rrc_processing_timer > 0) {
- sched_ctrl->rrc_processing_timer--;
- if (sched_ctrl->rrc_processing_timer == 0)
+ if (nr_timer_tick(&sched_ctrl->transm_interrupt)) {
+ /* expired */
+ nr_timer_stop(&sched_ctrl->transm_interrupt);
+ if (UE->interrupt_action == FOLLOW_OUTOFSYNC)
+ nr_mac_trigger_ul_failure(sched_ctrl, UE->current_DL_BWP.scs);
+ else
nr_mac_apply_cellgroup(mac, UE, frame, slot);
}
}
@@ -3264,7 +3302,27 @@ void nr_mac_reset_ul_failure(NR_UE_sched_ctrl_t *sched_ctrl)
sched_ctrl->pusch_consecutive_dtx_cnt = 0;
}
-void nr_mac_check_ul_failure(const gNB_MAC_INST *nrmac, int rnti, NR_UE_sched_ctrl_t *sched_ctrl)
+/* \brief trigger a release request towards the CU.
+ * \returns true if release was requested, else false (in which case the CU
+ * does not know the UE, and we can safely remove it). */
+bool nr_mac_request_release_ue(const gNB_MAC_INST *nrmac, int rnti)
+{
+ if (!du_exists_f1_ue_data(rnti)) {
+ LOG_W(NR_MAC, "UE %04x: no CU-UE ID stored, cannot request release\n", rnti);
+ return false;
+ }
+ f1_ue_data_t ue_data = du_get_f1_ue_data(rnti);
+ f1ap_ue_context_release_req_t request = {
+ .gNB_CU_ue_id = ue_data.secondary_ue,
+ .gNB_DU_ue_id = rnti,
+ .cause = F1AP_CAUSE_RADIO_NETWORK,
+ .cause_value = F1AP_CauseRadioNetwork_rl_failure_others,
+ };
+ nrmac->mac_rrc.ue_context_release_request(&request);
+ return true;
+}
+
+void nr_mac_check_ul_failure(gNB_MAC_INST *nrmac, int rnti, NR_UE_sched_ctrl_t *sched_ctrl)
{
if (!sched_ctrl->ul_failure)
return;
@@ -3274,14 +3332,9 @@ void nr_mac_check_ul_failure(const gNB_MAC_INST *nrmac, int rnti, NR_UE_sched_ct
* stop at 0 and we wait for a UE release command from upper layers */
if (sched_ctrl->ul_failure_timer == 1) {
LOG_W(MAC, "UE %04x: request release after UL failure timer expiry\n", rnti);
- f1_ue_data_t ue_data = du_get_f1_ue_data(rnti);
- f1ap_ue_context_release_req_t request = {
- .gNB_CU_ue_id = ue_data.secondary_ue,
- .gNB_DU_ue_id = rnti,
- .cause = F1AP_CAUSE_RADIO_NETWORK,
- .cause_value = F1AP_CauseRadioNetwork_rl_failure_others,
- };
- nrmac->mac_rrc.ue_context_release_request(&request);
+ bool requested = nr_mac_request_release_ue(nrmac, rnti);
+ if (!requested)
+ nr_mac_release_ue(nrmac, rnti);
}
}
@@ -3382,3 +3435,26 @@ bool nr_mac_remove_lcid(NR_UE_sched_ctrl_t *sched_ctrl, long lcid)
seq_arr_erase(&sched_ctrl->lc_config, elm.it);
return true;
}
+
+static const NR_RA_t *find_nr_RA_rnti(const NR_RA_t *ra_base, int ra_count, rnti_t rnti)
+{
+ for (int i = 0; i < ra_count; ++i) {
+ const NR_RA_t *ra = &ra_base[i];
+ if (ra->ra_state != nrRA_gNB_IDLE && ra->rnti == rnti)
+ return ra;
+ }
+ return NULL;
+}
+
+bool nr_mac_get_new_rnti(NR_UEs_t *UEs, const NR_RA_t *ra_base, int ra_count, rnti_t *rnti)
+{
+ int loop = 0;
+ bool exist_connected_ue, exist_in_pending_ra_ue;
+ do {
+ *rnti = (taus() % 0xffef) + 1;
+ exist_connected_ue = find_nr_UE(UEs, *rnti) != NULL;
+ exist_in_pending_ra_ue = find_nr_RA_rnti(ra_base, ra_count, *rnti) != NULL;
+ loop++;
+ } while (loop < 100 && (exist_connected_ue || exist_in_pending_ra_ue));
+ return loop < 100; // nothing found: loop count 100
+}
diff --git a/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_srs.c b/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_srs.c
index 5045d6cbe8..945e078e37 100644
--- a/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_srs.c
+++ b/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_srs.c
@@ -533,7 +533,7 @@ void nr_schedule_srs(int module_id, frame_t frame, int slot)
sched_ctrl->sched_srs.srs_scheduled = false;
}
- if ((sched_ctrl->ul_failure && !get_softmodem_params()->phy_test) || sched_ctrl->rrc_processing_timer > 0) {
+ if ((sched_ctrl->ul_failure && !get_softmodem_params()->phy_test) || nr_timer_is_active(&sched_ctrl->transm_interrupt)) {
continue;
}
diff --git a/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_uci.c b/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_uci.c
index facef09b52..ee573e89a1 100644
--- a/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_uci.c
+++ b/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_uci.c
@@ -223,7 +223,7 @@ void nr_csi_meas_reporting(int Mod_idP,
NR_UE_sched_ctrl_t *sched_ctrl = &UE->UE_sched_ctrl;
NR_UE_UL_BWP_t *ul_bwp = &UE->current_UL_BWP;
const int n_slots_frame = nr_slots_per_frame[ul_bwp->scs];
- if ((sched_ctrl->rrc_processing_timer > 0) || (sched_ctrl->ul_failure && !get_softmodem_params()->phy_test)) {
+ if (nr_timer_is_active(&sched_ctrl->transm_interrupt) || (sched_ctrl->ul_failure && !get_softmodem_params()->phy_test)) {
continue;
}
const NR_CSI_MeasConfig_t *csi_measconfig = UE->sc_info.csi_MeasConfig;
@@ -1389,7 +1389,7 @@ void nr_sr_reporting(gNB_MAC_INST *nrmac, frame_t SFN, sub_frame_t slot)
NR_UE_sched_ctrl_t *sched_ctrl = &UE->UE_sched_ctrl;
NR_UE_UL_BWP_t *ul_bwp = &UE->current_UL_BWP;
const int n_slots_frame = nr_slots_per_frame[ul_bwp->scs];
- if (sched_ctrl->ul_failure || sched_ctrl->rrc_processing_timer > 0)
+ if (sched_ctrl->ul_failure || nr_timer_is_active(&sched_ctrl->transm_interrupt))
continue;
NR_PUCCH_Config_t *pucch_Config = ul_bwp->pucch_Config;
diff --git a/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_ulsch.c b/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_ulsch.c
index 323c81bb3d..cf78034d0e 100644
--- a/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_ulsch.c
+++ b/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_ulsch.c
@@ -561,12 +561,16 @@ static void abort_nr_ul_harq(NR_UE_info_t *UE, int8_t harq_pid)
sched_ctrl->sched_ul_bytes = 0;
}
-static bool get_UE_waiting_CFRA_msg3(const gNB_MAC_INST *gNB_mac, const int CC_id, const frame_t frame, const sub_frame_t slot)
+static bool get_UE_waiting_CFRA_msg3(const gNB_MAC_INST *gNB_mac,
+ const int CC_id,
+ const frame_t frame,
+ const sub_frame_t slot,
+ rnti_t rnti)
{
bool UE_waiting_CFRA_msg3 = false;
for (int i = 0; i < NR_NB_RA_PROC_MAX; i++) {
const NR_RA_t *ra = &gNB_mac->common_channels[CC_id].ra[i];
- if (ra->cfra == true && ra->ra_state == nrRA_WAIT_Msg3 && frame == ra->Msg3_frame && slot == ra->Msg3_slot) {
+ if (ra->cfra && ra->ra_state == nrRA_WAIT_Msg3 && frame == ra->Msg3_frame && slot == ra->Msg3_slot && rnti == ra->rnti) {
UE_waiting_CFRA_msg3 = true;
break;
}
@@ -584,7 +588,7 @@ void handle_nr_ul_harq(const int CC_idP,
NR_SCHED_LOCK(&nrmac->sched_lock);
NR_UE_info_t *UE = find_nr_UE(&nrmac->UE_info, crc_pdu->rnti);
- bool UE_waiting_CFRA_msg3 = get_UE_waiting_CFRA_msg3(nrmac, CC_idP, frame, slot);
+ bool UE_waiting_CFRA_msg3 = get_UE_waiting_CFRA_msg3(nrmac, CC_idP, frame, slot, crc_pdu->rnti);
if (!UE || UE_waiting_CFRA_msg3 == true) {
LOG_D(NR_MAC, "handle harq for rnti %04x, in RA process\n", crc_pdu->rnti);
@@ -700,7 +704,7 @@ static void _nr_rx_sdu(const module_id_t gnb_mod_idP,
const int pusch_failure_thres = gNB_mac->pusch_failure_thres;
NR_UE_info_t *UE = find_nr_UE(&gNB_mac->UE_info, current_rnti);
- bool UE_waiting_CFRA_msg3 = get_UE_waiting_CFRA_msg3(gNB_mac, CC_idP, frameP, slotP);
+ bool UE_waiting_CFRA_msg3 = get_UE_waiting_CFRA_msg3(gNB_mac, CC_idP, frameP, slotP, current_rnti);
if (UE && UE_waiting_CFRA_msg3 == false) {
@@ -1856,7 +1860,7 @@ static void pf_ul(module_id_t module_id,
const bool do_sched = nr_UE_is_to_be_scheduled(scc, 0, UE, sched_pusch->frame, sched_pusch->slot, nrmac->ulsch_max_frame_inactivity);
LOG_D(NR_MAC,"pf_ul: do_sched UE %04x => %s\n",UE->rnti,do_sched ? "yes" : "no");
- if ((B == 0 && !do_sched) || (sched_ctrl->rrc_processing_timer > 0)) {
+ if ((B == 0 && !do_sched) || nr_timer_is_active(&sched_ctrl->transm_interrupt)) {
continue;
}
diff --git a/openair2/LAYER2/NR_MAC_gNB/mac_proto.h b/openair2/LAYER2/NR_MAC_gNB/mac_proto.h
index da3b612561..c5df1782fd 100644
--- a/openair2/LAYER2/NR_MAC_gNB/mac_proto.h
+++ b/openair2/LAYER2/NR_MAC_gNB/mac_proto.h
@@ -53,8 +53,11 @@ void nr_mac_configure_sib19(gNB_MAC_INST *nrmac);
bool nr_mac_add_test_ue(gNB_MAC_INST *nrmac, uint32_t rnti, NR_CellGroupConfig_t *CellGroup);
bool nr_mac_prepare_ra_ue(gNB_MAC_INST *nrmac, uint32_t rnti, NR_CellGroupConfig_t *CellGroup);
+int nr_mac_get_reconfig_delay_slots(NR_SubcarrierSpacing_t scs);
+
bool nr_mac_prepare_cellgroup_update(gNB_MAC_INST *nrmac, NR_UE_info_t *UE, NR_CellGroupConfig_t *CellGroup);
-int nr_mac_enable_ue_rrc_processing_timer(gNB_MAC_INST *mac, NR_UE_info_t *UE, bool apply_cellGroup);
+int nr_mac_interrupt_ue_transmission(gNB_MAC_INST *mac, NR_UE_info_t *UE, interrupt_followup_action_t action, int slots);
+int nr_transmission_action_indicator_stop(gNB_MAC_INST *mac, NR_UE_info_t *UE_info);
void clear_nr_nfapi_information(gNB_MAC_INST *gNB,
int CC_idP,
@@ -461,10 +464,11 @@ void nr_mac_trigger_release_timer(NR_UE_sched_ctrl_t *sched_ctrl, NR_SubcarrierS
bool nr_mac_check_release(NR_UE_sched_ctrl_t *sched_ctrl, int rnti);
void nr_mac_trigger_release_complete(gNB_MAC_INST *mac, int rnti);
void nr_mac_release_ue(gNB_MAC_INST *mac, int rnti);
+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(const gNB_MAC_INST *nrmac, int rnti, 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);
void nr_mac_trigger_reconfiguration(const gNB_MAC_INST *nrmac, const NR_UE_info_t *UE);
@@ -474,4 +478,6 @@ void process_addmod_bearers_cellGroupConfig(NR_UE_sched_ctrl_t *sched_ctrl,
bool nr_mac_add_lcid(NR_UE_sched_ctrl_t *sched_ctrl, const nr_lc_config_t *c);
bool nr_mac_remove_lcid(NR_UE_sched_ctrl_t *sched_ctrl, long lcid);
+bool nr_mac_get_new_rnti(NR_UEs_t *UEs, const NR_RA_t *ra_base, int ra_count, rnti_t *rnti);
+
#endif /*__LAYER2_NR_MAC_PROTO_H__*/
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 51c655c791..63e7ff9d99 100644
--- a/openair2/LAYER2/NR_MAC_gNB/mac_rrc_dl_handler.c
+++ b/openair2/LAYER2/NR_MAC_gNB/mac_rrc_dl_handler.c
@@ -26,6 +26,7 @@
#include "openair2/F1AP/f1ap_common.h"
#include "openair2/LAYER2/nr_rlc/nr_rlc_oai_api.h"
#include "F1AP_CauseRadioNetwork.h"
+#include "NR_HandoverPreparationInformation.h"
#include "openair3/ocp-gtpu/gtp_itf.h"
#include "openair2/LAYER2/nr_pdcp/nr_pdcp_oai_api.h"
#include "lib/f1ap_interface_management.h"
@@ -235,8 +236,16 @@ static int handle_ue_context_srbs_setup(NR_UE_info_t *UE,
(*resp_srbs)[i].srb_id = srb->srb_id;
(*resp_srbs)[i].lcid = c.lcid;
- int ret = ASN_SEQUENCE_ADD(&cellGroupConfig->rlc_BearerToAddModList->list, rlc_BearerConfig);
- DevAssert(ret == 0);
+ if (rlc_BearerConfig->logicalChannelIdentity == 1) {
+ // CU asks to add SRB1: when creating a cellGroupConfig, we always add it
+ // (see get_initial_cellGroupConfig())
+ const struct NR_CellGroupConfig__rlc_BearerToAddModList *addmod = cellGroupConfig->rlc_BearerToAddModList;
+ DevAssert(addmod->list.count >= 1 && addmod->list.array[0]->logicalChannelIdentity == 1);
+ ASN_STRUCT_FREE(asn_DEF_NR_RLC_BearerConfig, rlc_BearerConfig);
+ } else {
+ int ret = ASN_SEQUENCE_ADD(&cellGroupConfig->rlc_BearerToAddModList->list, rlc_BearerConfig);
+ DevAssert(ret == 0);
+ }
}
return srbs_len;
}
@@ -373,6 +382,33 @@ static int handle_ue_context_drbs_release(NR_UE_info_t *UE,
return drbs_len;
}
+static NR_UE_NR_Capability_t *get_nr_cap(const NR_UE_CapabilityRAT_ContainerList_t *clist)
+{
+ for (int i = 0; i < clist->list.count; i++) {
+ const NR_UE_CapabilityRAT_Container_t *c = clist->list.array[i];
+ if (c->rat_Type != NR_RAT_Type_nr) {
+ LOG_W(NR_MAC, "ignoring capability of type %ld\n", c->rat_Type);
+ continue;
+ }
+
+ NR_UE_NR_Capability_t *cap = NULL;
+ asn_dec_rval_t dec_rval = uper_decode(NULL,
+ &asn_DEF_NR_UE_NR_Capability,
+ (void **)&cap,
+ c->ue_CapabilityRAT_Container.buf,
+ c->ue_CapabilityRAT_Container.size,
+ 0,
+ 0);
+ if (dec_rval.code != RC_OK) {
+ LOG_W(NR_MAC, "cannot decode NR UE capability, ignoring\n");
+ ASN_STRUCT_FREE(asn_DEF_NR_UE_NR_Capability, cap);
+ continue;
+ }
+ return cap;
+ }
+ return NULL;
+}
+
static NR_UE_NR_Capability_t *get_ue_nr_cap(int rnti, uint8_t *buf, uint32_t len)
{
if (buf == NULL || len == 0)
@@ -385,27 +421,37 @@ static NR_UE_NR_Capability_t *get_ue_nr_cap(int rnti, uint8_t *buf, uint32_t len
return NULL;
}
- NR_UE_NR_Capability_t *cap = NULL;
- for (int i = 0; i < clist->list.count; i++) {
- const NR_UE_CapabilityRAT_Container_t *c = clist->list.array[i];
- if (cap != NULL || c->rat_Type != NR_RAT_Type_nr) {
- LOG_W(NR_MAC, "UE RNTI %04x: ignoring capability of type %ld\n", rnti, c->rat_Type);
- continue;
- }
+ NR_UE_NR_Capability_t *cap = get_nr_cap(clist);
+ ASN_STRUCT_FREE(asn_DEF_NR_UE_CapabilityRAT_ContainerList, clist);
+ return cap;
+}
- asn_dec_rval_t dec_rval = uper_decode(NULL,
- &asn_DEF_NR_UE_NR_Capability,
- (void **)&cap,
- c->ue_CapabilityRAT_Container.buf,
- c->ue_CapabilityRAT_Container.size,
- 0,
- 0);
- if (dec_rval.code != RC_OK) {
- LOG_W(NR_MAC, "cannot decode NR UE capabilities of UE RNTI %04x, ignoring NR capability\n", rnti);
- cap = NULL;
- continue;
- }
+/* \brief return UE capabilties from HandoverPreparationInformation.
+ *
+ * The HandoverPreparationInformation contains more, but for the moment, let's
+ * keep it simple and only handle that. The function asserts if other IEs are
+ * present. */
+static NR_UE_NR_Capability_t *get_ue_nr_cap_from_ho_prep_info(uint8_t *buf, uint32_t len)
+{
+ if (buf == NULL || len == 0)
+ return NULL;
+ NR_HandoverPreparationInformation_t *hpi = NULL;
+ asn_dec_rval_t dec_rval = uper_decode_complete(NULL, &asn_DEF_NR_HandoverPreparationInformation, (void **)&hpi, buf, len);
+ if (dec_rval.code != RC_OK) {
+ LOG_W(NR_MAC, "cannot decode HandoverPreparationInformation, ignoring capabilities\n");
+ return NULL;
}
+ NR_UE_NR_Capability_t *cap = NULL;
+ if (hpi->criticalExtensions.present != NR_HandoverPreparationInformation__criticalExtensions_PR_c1
+ || hpi->criticalExtensions.choice.c1 == NULL
+ || hpi->criticalExtensions.choice.c1->present
+ != NR_HandoverPreparationInformation__criticalExtensions__c1_PR_handoverPreparationInformation
+ || hpi->criticalExtensions.choice.c1->choice.handoverPreparationInformation == NULL) {
+ } else {
+ const NR_HandoverPreparationInformation_IEs_t *hpi_ie = hpi->criticalExtensions.choice.c1->choice.handoverPreparationInformation;
+ cap = get_nr_cap(&hpi_ie->ue_CapabilityRAT_List);
+ }
+ ASN_STRUCT_FREE(asn_DEF_NR_HandoverPreparationInformation, hpi);
return cap;
}
@@ -421,6 +467,40 @@ NR_CellGroupConfig_t *clone_CellGroupConfig(const NR_CellGroupConfig_t *orig)
return cloned;
}
+static NR_UE_info_t *create_new_UE(gNB_MAC_INST *mac, uint32_t cu_id)
+{
+ int CC_id = 0;
+ const NR_COMMON_channels_t *cc = &mac->common_channels[CC_id];
+ rnti_t rnti;
+ bool found = nr_mac_get_new_rnti(&mac->UE_info, cc->ra, sizeofArray(cc->ra), &rnti);
+ if (!found)
+ return NULL;
+
+ NR_UE_info_t* UE = add_new_nr_ue(mac, rnti, NULL);
+ if (!UE)
+ return NULL;
+
+ f1_ue_data_t new_ue_data = {.secondary_ue = cu_id};
+ du_add_f1_ue_data(rnti, &new_ue_data);
+
+ const NR_ServingCellConfigCommon_t *scc = mac->common_channels[CC_id].ServingCellConfigCommon;
+ const NR_ServingCellConfig_t *sccd = mac->common_channels[CC_id].pre_ServingCellConfig;
+ NR_CellGroupConfig_t *cellGroupConfig = get_initial_cellGroupConfig(UE->uid, scc, sccd, &mac->radio_config);
+ cellGroupConfig->spCellConfig->reconfigurationWithSync = get_reconfiguration_with_sync(UE->rnti, UE->uid, scc);
+ // note: we don't pass the cellGroupConfig to add_new_nr_ue() because we need
+ // the uid to create the CellGroupConfig (which is in the UE context created
+ // by add_new_nr_ue(); it's a kind of chicken-and-egg problem), so below we
+ // complete the UE context with the information that add_new_nr_ue() would
+ // have added
+ UE->Msg4_MsgB_ACKed = true;
+ UE->CellGroup = cellGroupConfig;
+
+ nr_rlc_activate_srb0(UE->rnti, UE, NULL);
+ nr_mac_prepare_ra_ue(mac, rnti, UE->CellGroup);
+ /* SRB1 is added to RLC and MAC in the handler later */
+ return UE;
+}
+
void ue_context_setup_request(const f1ap_ue_context_setup_t *req)
{
gNB_MAC_INST *mac = RC.nrmac[0];
@@ -430,19 +510,34 @@ void ue_context_setup_request(const f1ap_ue_context_setup_t *req)
.gNB_DU_ue_id = req->gNB_DU_ue_id,
};
+ /* gNB-DU UE ID is optional. As of now, the F1AP module fills -1 (on uint, so
+ * 0xffffffff), but the DU uses the RNTI, so check if it's a legal RNTI in
+ * which case we consider; if not, assume no DU UE ID given */
+ bool ue_id_provided = resp.gNB_DU_ue_id > 0 && resp.gNB_DU_ue_id < 0xffff;
+
NR_UE_NR_Capability_t *ue_cap = NULL;
if (req->cu_to_du_rrc_information != NULL) {
- AssertFatal(req->cu_to_du_rrc_information->cG_ConfigInfo == NULL, "CG-ConfigInfo not handled\n");
- ue_cap = get_ue_nr_cap(req->gNB_DU_ue_id,
- req->cu_to_du_rrc_information->uE_CapabilityRAT_ContainerList,
- req->cu_to_du_rrc_information->uE_CapabilityRAT_ContainerList_length);
- AssertFatal(req->cu_to_du_rrc_information->measConfig == NULL, "MeasConfig not handled\n");
+ const cu_to_du_rrc_information_t *cu2du = req->cu_to_du_rrc_information;
+ AssertFatal(cu2du->cG_ConfigInfo == NULL, "CG-ConfigInfo not handled\n");
+ if (cu2du->handoverPreparationInfo != NULL) {
+ ue_cap = get_ue_nr_cap_from_ho_prep_info(cu2du->handoverPreparationInfo, cu2du->handoverPreparationInfo_length);
+ } else if (cu2du->uE_CapabilityRAT_ContainerList != NULL) {
+ ue_cap = get_ue_nr_cap(req->gNB_DU_ue_id, cu2du->uE_CapabilityRAT_ContainerList, cu2du->uE_CapabilityRAT_ContainerList_length);
+ }
+ AssertFatal(cu2du->measConfig == NULL, "MeasConfig not handled\n");
}
NR_SCHED_LOCK(&mac->sched_lock);
- NR_UE_info_t *UE = find_nr_UE(&RC.nrmac[0]->UE_info, req->gNB_DU_ue_id);
- AssertFatal(UE, "did not find UE with RNTI %04x, but UE Context Setup Failed not implemented\n", req->gNB_DU_ue_id);
+ NR_UE_info_t *UE = NULL;
+ if (!ue_id_provided) {
+ UE = create_new_UE(mac, req->gNB_CU_ue_id);
+ resp.gNB_DU_ue_id = UE->rnti;
+ resp.crnti = &UE->rnti;
+ } else {
+ UE = find_nr_UE(&mac->UE_info, req->gNB_DU_ue_id);
+ }
+ AssertFatal(UE, "no UE found or could not be created, but UE Context Setup Failed not implemented\n");
NR_CellGroupConfig_t *new_CellGroup = clone_CellGroupConfig(UE->CellGroup);
@@ -474,6 +569,14 @@ void ue_context_setup_request(const f1ap_ue_context_setup_t *req)
update_cellGroupConfig(new_CellGroup, UE->uid, UE->capability, &mac->radio_config, scc);
}
+ if (!ue_id_provided) {
+ /* new UE: tell the UE to reestablish RLC */
+ struct NR_CellGroupConfig__rlc_BearerToAddModList *addmod = new_CellGroup->rlc_BearerToAddModList;
+ for (int i = 0; i < addmod->list.count; ++i) {
+ asn1cCallocOne(addmod->list.array[i]->reestablishRLC, NR_RLC_BearerConfig__reestablishRLC_true);
+ }
+ }
+
resp.du_to_cu_rrc_information = calloc(1, sizeof(du_to_cu_rrc_information_t));
AssertFatal(resp.du_to_cu_rrc_information != NULL, "out of memory\n");
resp.du_to_cu_rrc_information->cellGroupConfig = calloc(1,1024);
@@ -586,6 +689,11 @@ void ue_context_modification_request(const f1ap_ue_context_modif_req_t *req)
} else {
ASN_STRUCT_FREE(asn_DEF_NR_CellGroupConfig, new_CellGroup); // we actually don't need it
}
+
+ if (req->transm_action_ind != NULL) {
+ AssertFatal(*req->transm_action_ind == TransmActionInd_STOP, "Transmission Action Indicator restart not handled yet\n");
+ nr_transmission_action_indicator_stop(mac, UE);
+ }
NR_SCHED_UNLOCK(&mac->sched_lock);
/* some sanity checks, since we use the same type for request and response */
@@ -726,7 +834,8 @@ void dl_rrc_message_transfer(const f1ap_dl_rrc_message_t *dl_rrc)
* the CellGroupConfig. Below, we trigger a timer, and the CellGroupConfig
* will be applied after its expiry in nr_mac_apply_cellgroup().*/
NR_SCHED_LOCK(&mac->sched_lock);
- nr_mac_enable_ue_rrc_processing_timer(mac, UE, /* apply_cellGroup = */ true);
+ int delay = nr_mac_get_reconfig_delay_slots(UE->current_UL_BWP.scs);
+ nr_mac_interrupt_ue_transmission(mac, UE, FOLLOW_INSYNC_RECONFIG, delay);
NR_SCHED_UNLOCK(&mac->sched_lock);
UE->expect_reconfiguration = false;
/* Re-establish RLC for all remaining bearers */
diff --git a/openair2/LAYER2/NR_MAC_gNB/mac_rrc_ul_f1ap.c b/openair2/LAYER2/NR_MAC_gNB/mac_rrc_ul_f1ap.c
index 2af81d0cfd..6d8f7a7115 100644
--- a/openair2/LAYER2/NR_MAC_gNB/mac_rrc_ul_f1ap.c
+++ b/openair2/LAYER2/NR_MAC_gNB/mac_rrc_ul_f1ap.c
@@ -112,6 +112,10 @@ static void ue_context_setup_response_f1ap(const f1ap_ue_context_setup_t *req, c
for (int i = 0; i < f1ap_msg->drbs_to_be_setup_length; ++i)
f1ap_msg->drbs_to_be_setup[i] = resp->drbs_to_be_setup[i];
}
+ if (resp->crnti) {
+ f1ap_msg->crnti = calloc(1, sizeof(uint16_t));
+ *f1ap_msg->crnti = *resp->crnti;
+ }
f1ap_msg->du_to_cu_rrc_information = malloc(sizeof(*resp->du_to_cu_rrc_information));
AssertFatal(f1ap_msg->du_to_cu_rrc_information != NULL, "out of memory\n");
diff --git a/openair2/LAYER2/NR_MAC_gNB/nr_mac_gNB.h b/openair2/LAYER2/NR_MAC_gNB/nr_mac_gNB.h
index aa07e8f474..e0cf53c5a0 100644
--- a/openair2/LAYER2/NR_MAC_gNB/nr_mac_gNB.h
+++ b/openair2/LAYER2/NR_MAC_gNB/nr_mac_gNB.h
@@ -43,6 +43,7 @@
#include
#include
#include "common/utils/ds/seq_arr.h"
+#include "common/utils/nr/nr_common.h"
#define NR_SCHED_LOCK(lock) \
do { \
@@ -664,8 +665,8 @@ typedef struct {
NR_list_t retrans_ul_harq;
NR_UE_mac_ce_ctrl_t UE_mac_ce_ctrl; // MAC CE related information
- /// Timer for RRC processing procedures
- uint32_t rrc_processing_timer;
+ /// Timer for RRC processing procedures and transmission activity
+ NR_timer_t transm_interrupt;
/// sri, ul_ri and tpmi based on SRS
nr_srs_feedback_t srs_feedback;
@@ -738,6 +739,8 @@ typedef struct nr_mac_rrc_ul_if_s {
initial_ul_rrc_message_transfer_func_t initial_ul_rrc_message_transfer;
} nr_mac_rrc_ul_if_t;
+typedef enum interrupt_followup_action { FOLLOW_INSYNC, FOLLOW_INSYNC_RECONFIG, FOLLOW_OUTOFSYNC } interrupt_followup_action_t;
+
/*! \brief UE list used by gNB to order UEs/CC for scheduling*/
typedef struct {
rnti_t rnti;
@@ -756,7 +759,7 @@ typedef struct {
/// reestablishRLC has to be signaled in RRCreconfiguration
bool reestablish_rlc;
NR_CellGroupConfig_t *reconfigCellGroup;
- bool apply_cellgroup;
+ interrupt_followup_action_t interrupt_action;
NR_UE_NR_Capability_t *capability;
// UE selected beam index
uint8_t UE_beam_index;
diff --git a/openair2/RRC/NR/MESSAGES/asn1_msg.c b/openair2/RRC/NR/MESSAGES/asn1_msg.c
index 8025fb2243..68a8c49774 100644
--- a/openair2/RRC/NR/MESSAGES/asn1_msg.c
+++ b/openair2/RRC/NR/MESSAGES/asn1_msg.c
@@ -80,6 +80,8 @@
#include "NR_PCCH-Message.h"
#include "NR_PagingRecord.h"
#include "NR_UE-CapabilityRequestFilterNR.h"
+#include "NR_HandoverPreparationInformation.h"
+#include "NR_HandoverPreparationInformation-IEs.h"
#include "common/utils/nr/nr_common.h"
#if defined(NR_Rel16)
#include "NR_SCS-SpecificCarrier.h"
@@ -1373,3 +1375,42 @@ int do_NR_Paging(uint8_t Mod_id, uint8_t *buffer, uint32_t tmsi)
return((enc_rval.encoded+7)/8);
}
+
+/* \brief generate HandoverPreparationInformation to be sent to the DU for
+ * handover. Takes uecap_buf in encoded form as (1) this is the form present at
+ * the CU already (2) we have to clone this information anyway, so can take it
+ * in encoded form which we decode + add to the handoverPreparationInformation */
+int do_NR_HandoverPreparationInformation(const uint8_t *uecap_buf, int uecap_buf_size, uint8_t *buf, int buf_size)
+{
+ NR_HandoverPreparationInformation_t *hpi = calloc(1, sizeof(*hpi));
+ AssertFatal(hpi != NULL, "out of memory\n");
+ hpi->criticalExtensions.present = NR_HandoverPreparationInformation__criticalExtensions_PR_c1;
+ hpi->criticalExtensions.choice.c1 = calloc(1, sizeof(*hpi->criticalExtensions.choice.c1));
+ AssertFatal(hpi->criticalExtensions.choice.c1 != NULL, "out of memory\n");
+ hpi->criticalExtensions.choice.c1->present =
+ NR_HandoverPreparationInformation__criticalExtensions__c1_PR_handoverPreparationInformation;
+ NR_HandoverPreparationInformation_IEs_t *hpi_ie = calloc(1, sizeof(*hpi_ie));
+ AssertFatal(hpi_ie != NULL, "out of memory\n");
+ hpi->criticalExtensions.choice.c1->choice.handoverPreparationInformation = hpi_ie;
+
+ NR_UE_CapabilityRAT_ContainerList_t *list = NULL;
+ asn_dec_rval_t dec_rval =
+ uper_decode_complete(NULL, &asn_DEF_NR_UE_CapabilityRAT_ContainerList, (void **)&list, uecap_buf, uecap_buf_size);
+ if (dec_rval.code == RC_OK) {
+ hpi_ie->ue_CapabilityRAT_List = *list;
+ free(list); /* list itself is not needed, members below will be freed in ASN_STRUCT_FREE */
+ } else {
+ /* problem with decoding, don't put a capability */
+ ASN_STRUCT_FREE(asn_DEF_NR_UE_CapabilityRAT_ContainerList, list);
+ list = NULL;
+ }
+
+ if (LOG_DEBUGFLAG(DEBUG_ASN1))
+ xer_fprint(stdout, &asn_DEF_NR_HandoverPreparationInformation, hpi);
+
+ asn_enc_rval_t enc_rval = uper_encode_to_buffer(&asn_DEF_NR_HandoverPreparationInformation, NULL, hpi, buf, buf_size);
+ AssertFatal(enc_rval.encoded > 0, "ASN1 message encoding failed (%s, %lu)!\n", enc_rval.failed_type->name, enc_rval.encoded);
+
+ ASN_STRUCT_FREE(asn_DEF_NR_HandoverPreparationInformation, hpi);
+ return (enc_rval.encoded + 7) / 8;
+}
diff --git a/openair2/RRC/NR/MESSAGES/asn1_msg.h b/openair2/RRC/NR/MESSAGES/asn1_msg.h
index 8609e20328..2f0b4febf8 100644
--- a/openair2/RRC/NR/MESSAGES/asn1_msg.h
+++ b/openair2/RRC/NR/MESSAGES/asn1_msg.h
@@ -109,6 +109,8 @@ int do_RRCSetupComplete(uint8_t *buffer,
const int dedicatedInfoNASLength,
const char *dedicatedInfoNAS);
+int do_NR_HandoverPreparationInformation(const uint8_t *uecap_buf, int uecap_buf_size, uint8_t *buf, int buf_size);
+
int do_RRCSetupRequest(uint8_t *buffer, size_t buffer_size, uint8_t *rv);
int do_NR_RRCReconfigurationComplete_for_nsa(uint8_t *buffer, size_t buffer_size, NR_RRC_TransactionIdentifier_t Transaction_id);
diff --git a/openair2/RRC/NR/mac_rrc_dl_f1ap.c b/openair2/RRC/NR/mac_rrc_dl_f1ap.c
index e2e094d866..70d36a144f 100644
--- a/openair2/RRC/NR/mac_rrc_dl_f1ap.c
+++ b/openair2/RRC/NR/mac_rrc_dl_f1ap.c
@@ -79,8 +79,8 @@ static void ue_context_setup_request_f1ap(sctp_assoc_t assoc_id, const f1ap_ue_c
AssertFatal(f1ap_msg->cu_to_du_rrc_information != NULL, "out of memory\n");
AssertFatal(req->cu_to_du_rrc_information->cG_ConfigInfo == NULL && req->cu_to_du_rrc_information->cG_ConfigInfo_length == 0, "cg_ConfigInfo not implemented\n");
AssertFatal(req->cu_to_du_rrc_information->measConfig == NULL && req->cu_to_du_rrc_information->measConfig_length == 0, "cg_ConfigInfo not implemented\n");
+ const cu_to_du_rrc_information_t *du2cu_req = req->cu_to_du_rrc_information;
if (req->cu_to_du_rrc_information->uE_CapabilityRAT_ContainerList != NULL) {
- const cu_to_du_rrc_information_t *du2cu_req = req->cu_to_du_rrc_information;
cu_to_du_rrc_information_t* du2cu_new = f1ap_msg->cu_to_du_rrc_information;
DevAssert(du2cu_req->uE_CapabilityRAT_ContainerList_length > 0);
du2cu_new->uE_CapabilityRAT_ContainerList_length = du2cu_req->uE_CapabilityRAT_ContainerList_length;
@@ -88,6 +88,13 @@ static void ue_context_setup_request_f1ap(sctp_assoc_t assoc_id, const f1ap_ue_c
AssertFatal(du2cu_new->uE_CapabilityRAT_ContainerList != NULL, "out of memory\n");
memcpy(du2cu_new->uE_CapabilityRAT_ContainerList, du2cu_req->uE_CapabilityRAT_ContainerList, du2cu_new->uE_CapabilityRAT_ContainerList_length);
}
+ if (req->cu_to_du_rrc_information->handoverPreparationInfo != NULL) {
+ cu_to_du_rrc_information_t *du2cu_new = f1ap_msg->cu_to_du_rrc_information;
+ DevAssert(du2cu_req->handoverPreparationInfo_length > 0);
+ du2cu_new->handoverPreparationInfo_length = du2cu_req->handoverPreparationInfo_length;
+ du2cu_new->handoverPreparationInfo = malloc_or_fail(du2cu_new->handoverPreparationInfo_length);
+ memcpy(du2cu_new->handoverPreparationInfo, du2cu_req->handoverPreparationInfo, du2cu_new->handoverPreparationInfo_length);
+ }
}
if (req->drbs_to_be_setup_length > 0) {
int n = req->drbs_to_be_setup_length;
@@ -161,6 +168,11 @@ static void ue_context_modification_request_f1ap(sctp_assoc_t assoc_id, const f1
f1ap_msg->rrc_container_length = req->rrc_container_length;
memcpy(f1ap_msg->rrc_container, req->rrc_container, req->rrc_container_length);
}
+ if (req->transm_action_ind != NULL) {
+ f1ap_msg->transm_action_ind = malloc(sizeof *f1ap_msg->transm_action_ind);
+ AssertFatal(f1ap_msg->transm_action_ind != NULL, "out of memory\n");
+ *f1ap_msg->transm_action_ind = *req->transm_action_ind;
+ }
itti_send_msg_to_task(TASK_CU_F1, 0, msg);
}
diff --git a/openair2/RRC/NR/nr_rrc_config.c b/openair2/RRC/NR/nr_rrc_config.c
index 201a0e84d4..dab37f6f1a 100644
--- a/openair2/RRC/NR/nr_rrc_config.c
+++ b/openair2/RRC/NR/nr_rrc_config.c
@@ -3293,46 +3293,8 @@ NR_CellGroupConfig_t *get_default_secondaryCellGroup(const NR_ServingCellConfigC
secondaryCellGroup->spCellConfig->reconfigurationWithSync =
calloc(1, sizeof(*secondaryCellGroup->spCellConfig->reconfigurationWithSync));
- NR_ReconfigurationWithSync_t *reconfigurationWithSync = secondaryCellGroup->spCellConfig->reconfigurationWithSync;
- reconfigurationWithSync->spCellConfigCommon = clone_ServingCellConfigCommon(servingcellconfigcommon);
- reconfigurationWithSync->newUE_Identity =
- (get_softmodem_params()->phy_test == 1) ? 0x1234 : (taus() & 0xffff);
- reconfigurationWithSync->t304 = NR_ReconfigurationWithSync__t304_ms2000;
- reconfigurationWithSync->rach_ConfigDedicated = NULL;
- reconfigurationWithSync->ext1 = NULL;
-
- // For 2-step contention-free random access procedure
- reconfigurationWithSync->rach_ConfigDedicated = calloc(1, sizeof(*reconfigurationWithSync->rach_ConfigDedicated));
- reconfigurationWithSync->rach_ConfigDedicated->present = NR_ReconfigurationWithSync__rach_ConfigDedicated_PR_uplink;
- reconfigurationWithSync->rach_ConfigDedicated->choice.uplink = calloc(1, sizeof(struct NR_RACH_ConfigDedicated));
- reconfigurationWithSync->rach_ConfigDedicated->choice.uplink->cfra = calloc(1, sizeof(struct NR_CFRA));
- reconfigurationWithSync->rach_ConfigDedicated->choice.uplink->ra_Prioritization = NULL;
- reconfigurationWithSync->rach_ConfigDedicated->choice.uplink->cfra->occasions = calloc(1, sizeof(struct NR_CFRA__occasions));
- memcpy(&reconfigurationWithSync->rach_ConfigDedicated->choice.uplink->cfra->occasions->rach_ConfigGeneric,
- &servingcellconfigcommon->uplinkConfigCommon->initialUplinkBWP->rach_ConfigCommon->choice.setup->rach_ConfigGeneric,
- sizeof(NR_RACH_ConfigGeneric_t));
- asn1cCallocOne(reconfigurationWithSync->rach_ConfigDedicated->choice.uplink->cfra->occasions->ssb_perRACH_Occasion,
- NR_CFRA__occasions__ssb_perRACH_Occasion_one);
- reconfigurationWithSync->rach_ConfigDedicated->choice.uplink->cfra->resources.present = NR_CFRA__resources_PR_ssb;
- reconfigurationWithSync->rach_ConfigDedicated->choice.uplink->cfra->resources.choice.ssb =
- calloc(1, sizeof(struct NR_CFRA__resources__ssb));
- reconfigurationWithSync->rach_ConfigDedicated->choice.uplink->cfra->resources.choice.ssb->ra_ssb_OccasionMaskIndex = 0;
-
- int n_ssb = 0;
- struct NR_CFRA_SSB_Resource *ssbElem[64];
- for (int i = 0; i < 64; i++) {
- if ((bitmap >> (63 - i)) & 0x01) {
- ssbElem[n_ssb] = calloc(1, sizeof(struct NR_CFRA_SSB_Resource));
- ssbElem[n_ssb]->ssb = i;
- ssbElem[n_ssb]->ra_PreambleIndex = 63 - (uid % 64);
- asn1cSeqAdd(&secondaryCellGroup->spCellConfig->reconfigurationWithSync->rach_ConfigDedicated->choice.uplink->cfra->resources
- .choice.ssb->ssb_ResourceList.list,
- ssbElem[n_ssb]);
- n_ssb++;
- }
- }
-
- secondaryCellGroup->spCellConfig->reconfigurationWithSync->rach_ConfigDedicated->choice.uplink->cfra->ext1 = NULL;
+ rnti_t rnti = get_softmodem_params()->phy_test == 1 ? 0x1234 : (taus() & 0xffff);
+ secondaryCellGroup->spCellConfig->reconfigurationWithSync = get_reconfiguration_with_sync(rnti, uid, servingcellconfigcommon);
secondaryCellGroup->spCellConfig->rlf_TimersAndConstants =
calloc(1, sizeof(*secondaryCellGroup->spCellConfig->rlf_TimersAndConstants));
@@ -3602,3 +3564,43 @@ NR_CellGroupConfig_t *get_default_secondaryCellGroup(const NR_ServingCellConfigC
}
return secondaryCellGroup;
}
+
+NR_ReconfigurationWithSync_t *get_reconfiguration_with_sync(rnti_t rnti, uid_t uid, const NR_ServingCellConfigCommon_t *scc)
+{
+ NR_ReconfigurationWithSync_t *reconfigurationWithSync = calloc(1, sizeof(*reconfigurationWithSync));
+ reconfigurationWithSync->newUE_Identity = rnti;
+ reconfigurationWithSync->t304 = NR_ReconfigurationWithSync__t304_ms2000;
+ reconfigurationWithSync->rach_ConfigDedicated = NULL;
+ reconfigurationWithSync->ext1 = NULL;
+
+ reconfigurationWithSync->spCellConfigCommon = clone_ServingCellConfigCommon(scc);
+
+ reconfigurationWithSync->rach_ConfigDedicated = calloc(1, sizeof(*reconfigurationWithSync->rach_ConfigDedicated));
+ reconfigurationWithSync->rach_ConfigDedicated->present = NR_ReconfigurationWithSync__rach_ConfigDedicated_PR_uplink;
+ NR_RACH_ConfigDedicated_t *uplink = calloc(1, sizeof(*uplink));
+ reconfigurationWithSync->rach_ConfigDedicated->choice.uplink = uplink;
+ uplink->ra_Prioritization = NULL;
+ uplink->cfra = calloc(1, sizeof(struct NR_CFRA));
+ uplink->cfra->ext1 = NULL;
+ uplink->cfra->occasions = calloc(1, sizeof(struct NR_CFRA__occasions));
+ memcpy(&uplink->cfra->occasions->rach_ConfigGeneric,
+ &scc->uplinkConfigCommon->initialUplinkBWP->rach_ConfigCommon->choice.setup->rach_ConfigGeneric,
+ sizeof(NR_RACH_ConfigGeneric_t));
+ asn1cCallocOne(uplink->cfra->occasions->ssb_perRACH_Occasion, NR_CFRA__occasions__ssb_perRACH_Occasion_one);
+ uplink->cfra->resources.present = NR_CFRA__resources_PR_ssb;
+ uplink->cfra->resources.choice.ssb = calloc(1, sizeof(struct NR_CFRA__resources__ssb));
+ uplink->cfra->resources.choice.ssb->ra_ssb_OccasionMaskIndex = 0;
+
+ uint64_t bitmap = get_ssb_bitmap(scc);
+ for (int i = 0; i < 64; i++) {
+ if (((bitmap >> (63 - i)) & 0x01) == 0)
+ continue;
+
+ NR_CFRA_SSB_Resource_t *ssbElem = calloc(1, sizeof(*ssbElem));
+ ssbElem->ssb = i;
+ ssbElem->ra_PreambleIndex = 63 - (uid % 64);
+ asn1cSeqAdd(&uplink->cfra->resources.choice.ssb->ssb_ResourceList.list, ssbElem);
+ }
+
+ return reconfigurationWithSync;
+}
diff --git a/openair2/RRC/NR/nr_rrc_config.h b/openair2/RRC/NR/nr_rrc_config.h
index 87640834e5..ff94f2ab7e 100644
--- a/openair2/RRC/NR/nr_rrc_config.h
+++ b/openair2/RRC/NR/nr_rrc_config.h
@@ -102,4 +102,6 @@ NR_CellGroupConfig_t *get_default_secondaryCellGroup(const NR_ServingCellConfigC
const nr_mac_config_t *configuration,
int uid);
+NR_ReconfigurationWithSync_t *get_reconfiguration_with_sync(rnti_t rnti, uid_t uid, const NR_ServingCellConfigCommon_t *scc);
+
#endif
diff --git a/openair2/RRC/NR/nr_rrc_defs.h b/openair2/RRC/NR/nr_rrc_defs.h
index b5b27a6f4d..14d08e9c45 100644
--- a/openair2/RRC/NR/nr_rrc_defs.h
+++ b/openair2/RRC/NR/nr_rrc_defs.h
@@ -69,14 +69,6 @@
#include "intertask_interface.h"
-/* TODO: be sure this include is correct.
- * It solves a problem of compilation of the RRH GW,
- * issue #186.
- */
- #include "as_message.h"
-
- #include "commonDef.h"
-
#define PROTOCOL_NR_RRC_CTXT_UE_FMT PROTOCOL_CTXT_FMT
#define PROTOCOL_NR_RRC_CTXT_UE_ARGS(CTXT_Pp) PROTOCOL_NR_CTXT_ARGS(CTXT_Pp)
@@ -120,23 +112,6 @@ typedef struct nr_e_rab_param_s {
uint8_t xid; // transaction_id
} __attribute__ ((__packed__)) nr_e_rab_param_t;
-
-typedef struct HANDOVER_INFO_NR_s {
- uint8_t ho_prepare;
- uint8_t ho_complete;
- uint8_t modid_s; //module_idP of serving cell
- uint8_t modid_t; //module_idP of target cell
- uint8_t ueid_s; //UE index in serving cell
- uint8_t ueid_t; //UE index in target cell
-
- // NR not define at this moment
- //AS_Config_t as_config; /* these two parameters are taken from 36.331 section 10.2.2: HandoverPreparationInformation-r8-IEs */
- //AS_Context_t as_context; /* They are mandatory for HO */
-
- uint8_t buf[NR_RRC_BUF_SIZE]; /* ASN.1 encoded handoverCommandMessage */
- int size; /* size of above message in bytes */
-} NR_HANDOVER_INFO;
-
typedef struct nr_rrc_guami_s {
uint16_t mcc;
uint16_t mnc;
@@ -222,6 +197,9 @@ typedef enum {
RRC_UECAPABILITY_ENQUIRY,
} rrc_action_t;
+/* forward declaration */
+typedef struct nr_handover_context_s nr_handover_context_t;
+
typedef struct gNB_RRC_UE_s {
time_t last_seen; // last time this UE has been accessed
@@ -230,7 +208,7 @@ typedef struct gNB_RRC_UE_s {
NR_SRB_INFO_TABLE_ENTRY Srb[NR_NUM_SRB];
NR_MeasConfig_t *measConfig;
- NR_HANDOVER_INFO *handover_info;
+ nr_handover_context_t *ho_context;
NR_MeasResults_t *measResults;
bool as_security_active;
diff --git a/openair2/RRC/NR/nr_rrc_proto.h b/openair2/RRC/NR/nr_rrc_proto.h
index 9c3d276394..576a1f636a 100644
--- a/openair2/RRC/NR/nr_rrc_proto.h
+++ b/openair2/RRC/NR/nr_rrc_proto.h
@@ -130,4 +130,7 @@ void rrc_gNB_generate_UeContextModificationRequest(const gNB_RRC_INST *rrc,
int n_rel_drbs,
const f1ap_drb_to_be_released_t *rel_drbs);
+pdusession_level_qos_parameter_t *get_qos_characteristics(const int qfi, rrc_pdu_session_param_t *pduSession);
+f1ap_qos_characteristics_t get_qos_char_from_qos_flow_param(const pdusession_level_qos_parameter_t *qos_param);
+
#endif
diff --git a/openair2/RRC/NR/rrc_gNB.c b/openair2/RRC/NR/rrc_gNB.c
index e960119d81..1ff2747ec4 100644
--- a/openair2/RRC/NR/rrc_gNB.c
+++ b/openair2/RRC/NR/rrc_gNB.c
@@ -77,6 +77,7 @@
#include "rrc_gNB_NGAP.h"
#include "rrc_gNB_du.h"
+#include "rrc_gNB_mobility.h"
#include "rrc_gNB_GTPV1U.h"
@@ -113,16 +114,6 @@ extern RAN_CONTEXT_t RC;
mui_t rrc_gNB_mui = 0;
-// 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) \
- { \
- if (assoc_id == 0) { \
- LOG_E(NR_RRC, "cannot send data: invalid assoc_id 0, DU offline\n"); \
- return; \
- } \
- }
-
///---------------------------------------------------------------------------------------------------------------///
///---------------------------------------------------------------------------------------------------------------///
@@ -482,6 +473,60 @@ static void rrc_gNB_process_RRCSetupComplete(gNB_RRC_INST *rrc, gNB_RRC_UE_t *UE
rrc_gNB_send_NGAP_NAS_FIRST_REQ(rrc, UE, rrcSetupComplete);
}
+static int rrc_gNB_encode_RRCReconfiguration(gNB_RRC_INST *rrc,
+ gNB_RRC_UE_t *UE,
+ uint8_t xid,
+ struct NR_RRCReconfiguration_v1530_IEs__dedicatedNAS_MessageList *nas_messages,
+ uint8_t *buf,
+ int max_len,
+ bool reestablish)
+{
+ NR_CellGroupConfig_t *cellGroupConfig = UE->masterCellGroup;
+ nr_rrc_du_container_t *du = get_du_for_ue(rrc, UE->rrc_ue_id);
+ DevAssert(du != NULL);
+ f1ap_served_cell_info_t *cell_info = &du->setup_req->cell[0].info;
+ NR_MeasConfig_t *measconfig = NULL;
+ if (du->mtc != NULL) {
+ int scs = get_ssb_scs(cell_info);
+ int band = get_dl_band(cell_info);
+ const NR_MeasTimingList_t *mtlist = du->mtc->criticalExtensions.choice.c1->choice.measTimingConf->measTiming;
+ const NR_MeasTiming_t *mt = mtlist->list.array[0];
+ const neighbour_cell_configuration_t *neighbour_config = get_neighbour_config(cell_info->nr_cellid);
+ seq_arr_t *neighbour_cells = NULL;
+ if (neighbour_config)
+ neighbour_cells = neighbour_config->neighbour_cells;
+
+ measconfig = get_MeasConfig(mt, band, scs, &rrc->measurementConfiguration, neighbour_cells);
+ }
+
+ if (UE->measConfig)
+ free_MeasConfig(UE->measConfig);
+
+ UE->measConfig = measconfig;
+
+ NR_SRB_ToAddModList_t *SRBs = createSRBlist(UE, reestablish);
+ NR_DRB_ToAddModList_t *DRBs = createDRBlist(UE, reestablish);
+
+ int size = do_RRCReconfiguration(UE,
+ buf,
+ max_len,
+ xid,
+ SRBs,
+ DRBs,
+ UE->DRB_ReleaseList,
+ NULL,
+ measconfig,
+ nas_messages,
+ cellGroupConfig);
+ LOG_DUMPMSG(NR_RRC, DEBUG_RRC, (char *)buf, size, "[MSG] RRC Reconfiguration\n");
+ freeSRBlist(SRBs);
+ freeDRBlist(DRBs);
+ ASN_STRUCT_FREE(asn_DEF_NR_DRB_ToReleaseList, UE->DRB_ReleaseList);
+ UE->DRB_ReleaseList = NULL;
+
+ return size;
+}
+
static void rrc_gNB_generate_dedicatedRRCReconfiguration(gNB_RRC_INST *rrc, gNB_RRC_UE_t *ue_p)
{
uint8_t xid = rrc_gNB_get_next_transaction_identifier(rrc->module_id);
@@ -518,51 +563,10 @@ static void rrc_gNB_generate_dedicatedRRCReconfiguration(gNB_RRC_INST *rrc, gNB_
for (int i = 0; i < ue_p->nb_of_pdusessions; i++)
clear_nas_pdu(&ue_p->pduSession[i].param.nas_pdu);
- NR_CellGroupConfig_t *cellGroupConfig = ue_p->masterCellGroup;
-
- const nr_rrc_du_container_t *du = get_du_for_ue(rrc, ue_p->rrc_ue_id);
- DevAssert(du != NULL);
- f1ap_served_cell_info_t *cell_info = &du->setup_req->cell[0].info;
- NR_MeasConfig_t *measconfig = NULL;
-
- if (du->mtc != NULL) {
- int scs = get_ssb_scs(cell_info);
- int band = get_dl_band(cell_info);
- const NR_MeasTimingList_t *mtlist = du->mtc->criticalExtensions.choice.c1->choice.measTimingConf->measTiming;
- const NR_MeasTiming_t *mt = mtlist->list.array[0];
- const neighbour_cell_configuration_t *neighbour_config = get_neighbour_config(cell_info->nr_cellid);
- seq_arr_t *neighbour_cells = NULL;
- if (neighbour_config)
- neighbour_cells = neighbour_config->neighbour_cells;
-
- measconfig = get_MeasConfig(mt, band, scs, &rrc->measurementConfiguration, neighbour_cells);
- }
-
- if (ue_p->measConfig)
- free_MeasConfig(ue_p->measConfig);
-
- ue_p->measConfig = measconfig;
-
uint8_t buffer[NR_RRC_BUF_SIZE] = {0};
- NR_SRB_ToAddModList_t *SRBs = createSRBlist(ue_p, false);
- NR_DRB_ToAddModList_t *DRBs = createDRBlist(ue_p, false);
-
- int size = do_RRCReconfiguration(ue_p,
- buffer,
- NR_RRC_BUF_SIZE,
- xid,
- SRBs,
- DRBs,
- ue_p->DRB_ReleaseList,
- NULL,
- measconfig,
- dedicatedNAS_MessageList,
- cellGroupConfig);
- LOG_DUMPMSG(NR_RRC, DEBUG_RRC, (char *)buffer, size, "[MSG] RRC Reconfiguration\n");
- freeSRBlist(SRBs);
- freeDRBlist(DRBs);
- ASN_STRUCT_FREE(asn_DEF_NR_DRB_ToReleaseList, ue_p->DRB_ReleaseList);
- ue_p->DRB_ReleaseList = NULL;
+ // TODO refactor dedicatedNAS_MessageList
+ int size = rrc_gNB_encode_RRCReconfiguration(rrc, ue_p, xid, dedicatedNAS_MessageList, buffer, sizeof(buffer), false);
+ DevAssert(size > 0 && size <= sizeof(buffer));
LOG_I(NR_RRC, "UE %d: Generate RRCReconfiguration (bytes %d, xid %d)\n", ue_p->rrc_ue_id, size, xid);
nr_rrc_transfer_protected_rrc_message(rrc, ue_p, DCCH, buffer, size);
@@ -906,6 +910,17 @@ static void rrc_gNB_process_RRCReestablishmentComplete(gNB_RRC_INST *rrc, gNB_RR
NR_RLC_BearerConfig__reestablishRLC_true);
}
+ /* TODO: remove a reconfigurationWithSync, we don't need it for
+ * reestablishment. The whole reason why this might be here is that we store
+ * the CellGroupConfig (after handover), and simply reuse it for
+ * reestablishment, instead of re-requesting the CellGroupConfig from the DU.
+ * Hence, add below hack; the solution would be to request the
+ * CellGroupConfig from the DU when doing reestablishment. */
+ if (cellGroupConfig->spCellConfig->reconfigurationWithSync != NULL) {
+ ASN_STRUCT_FREE(asn_DEF_NR_ReconfigurationWithSync, cellGroupConfig->spCellConfig->reconfigurationWithSync);
+ cellGroupConfig->spCellConfig->reconfigurationWithSync = NULL;
+ }
+
/* Re-establish SRB2 according to clause 5.3.5.6.3 of 3GPP TS 38.331
* (SRB1 is re-established with RRCReestablishment message)
*/
@@ -1027,9 +1042,18 @@ static void rrc_handle_RRCSetupRequest(gNB_RRC_INST *rrc,
return;
}
+ NR_CellGroupConfig_t *cellGroupConfig = NULL;
+ asn_dec_rval_t dec_rval = uper_decode_complete(NULL,
+ &asn_DEF_NR_CellGroupConfig,
+ (void **)&cellGroupConfig,
+ msg->du2cu_rrc_container,
+ msg->du2cu_rrc_container_length);
+ AssertFatal(dec_rval.code == RC_OK && dec_rval.consumed > 0, "Cell group config decode error\n");
+
gNB_RRC_UE_t *UE = &ue_context_p->ue_context;
UE = &ue_context_p->ue_context;
UE->establishment_cause = rrcSetupRequest->establishmentCause;
+ UE->masterCellGroup = cellGroupConfig;
activate_srb(UE, 1);
rrc_gNB_generate_RRCSetup(0, msg->crnti, ue_context_p, msg->du2cu_rrc_container, msg->du2cu_rrc_container_length);
}
@@ -1049,6 +1073,20 @@ static const char *get_reestab_cause(NR_ReestablishmentCause_t c)
return "UNKNOWN Failure (ASN.1 decoder error?)";
}
+static rrc_gNB_ue_context_t *rrc_gNB_get_ue_context_source_cell(gNB_RRC_INST *rrc_instance_pP, long pci, rnti_t rntiP)
+{
+ rrc_gNB_ue_context_t *ue_context_p;
+ RB_FOREACH(ue_context_p, rrc_nr_ue_tree_s, &rrc_instance_pP->rrc_ue_head) {
+ gNB_RRC_UE_t *ue = &ue_context_p->ue_context;
+ if (!ue->ho_context || !ue->ho_context->source)
+ continue;
+ nr_ho_source_cu_t *source_ctx = ue->ho_context->source;
+ if (source_ctx->old_rnti == rntiP && source_ctx->du->setup_req->cell[0].info.nr_pci == pci)
+ return ue_context_p;
+ }
+ return NULL;
+}
+
static void rrc_handle_RRCReestablishmentRequest(gNB_RRC_INST *rrc,
sctp_assoc_t assoc_id,
const NR_RRCReestablishmentRequest_IEs_t *req,
@@ -1059,7 +1097,12 @@ static void rrc_handle_RRCReestablishmentRequest(gNB_RRC_INST *rrc,
const long physCellId = req->ue_Identity.physCellId;
long ngap_cause = NGAP_CAUSE_RADIO_NETWORK_UNSPECIFIED; /* cause in case of NGAP release req */
rrc_gNB_ue_context_t *ue_context_p = NULL;
- LOG_I(NR_RRC, "UE %04x physCellId %ld NR_RRCReestablishmentRequest cause %s\n", msg->crnti, physCellId, scause);
+ LOG_I(NR_RRC,
+ "Reestablishment RNTI %04x req C-RNTI %04lx physCellId %ld cause %s\n",
+ msg->crnti,
+ req->ue_Identity.c_RNTI,
+ physCellId,
+ scause);
const nr_rrc_du_container_t *du = get_du_by_assoc_id(rrc, assoc_id);
if (du == NULL) {
@@ -1094,13 +1137,47 @@ static void rrc_handle_RRCReestablishmentRequest(gNB_RRC_INST *rrc,
rnti_t old_rnti = req->ue_Identity.c_RNTI;
ue_context_p = rrc_gNB_get_ue_context_by_rnti(rrc, assoc_id, old_rnti);
if (ue_context_p == NULL) {
- LOG_E(NR_RRC, "NR_RRCReestablishmentRequest without UE context, fallback to RRC setup\n");
- goto fallback_rrc_setup;
+ ue_context_p = rrc_gNB_get_ue_context_source_cell(rrc, physCellId, old_rnti);
+ if (ue_context_p == NULL) {
+ LOG_E(NR_RRC, "NR_RRCReestablishmentRequest without UE context, fallback to RRC setup\n");
+ goto fallback_rrc_setup;
+ }
}
gNB_RRC_UE_t *UE = &ue_context_p->ue_context;
+ /* should check phys cell ID to identify the correct cell */
const f1ap_served_cell_info_t *cell_info = &du->setup_req->cell[0].info;
- if (physCellId != cell_info->nr_pci) {
+ const f1ap_served_cell_info_t *previous_cell_info = get_cell_information_by_phycellId(physCellId);
+
+ nr_ho_source_cu_t *source_ctx = UE->ho_context ? UE->ho_context->source : NULL;
+ DevAssert(!source_ctx || source_ctx->du->setup_req->num_cells_available == 1);
+ bool ho_reestab_on_source = source_ctx && previous_cell_info->nr_cellid == source_ctx->du->setup_req->cell[0].info.nr_cellid;
+
+ if (ho_reestab_on_source) {
+ /* the UE came back on the source DU while doing handover, release at
+ * target DU and and update the association to the initial DU one */
+ LOG_W(NR_RRC, "handover for UE %d/RNTI %04x failed, rollback to original cell\n", UE->rrc_ue_id, UE->rnti);
+
+ // find the transaction of handover (the corresponding reconfig) and abort it
+ for (int i = 0; i < NR_RRC_TRANSACTION_IDENTIFIER_NUMBER; ++i) {
+ if (UE->xids[i] == RRC_DEDICATED_RECONF)
+ UE->xids[i] = RRC_ACTION_NONE;
+ }
+
+ source_ctx->ho_cancel(rrc, UE);
+
+ /* we need the original CellGroupConfig */
+ ASN_STRUCT_FREE(asn_DEF_NR_CellGroupConfig, UE->masterCellGroup);
+ UE->masterCellGroup = source_ctx->old_cellGroupConfig;
+ source_ctx->old_cellGroupConfig = NULL;
+
+ /* update to old DU assoc id -- RNTI + secondary DU UE ID further below */
+ f1_ue_data_t ue_data = cu_get_f1_ue_data(UE->rrc_ue_id);
+ ue_data.du_assoc_id = source_ctx->du->assoc_id;
+ cu_remove_f1_ue_data(UE->rrc_ue_id);
+ cu_add_f1_ue_data(UE->rrc_ue_id, &ue_data);
+ nr_rrc_finalize_ho(UE);
+ } else if (physCellId != cell_info->nr_pci) {
/* UE was moving from previous cell so quickly that RRCReestablishment for previous cell was received in this cell */
LOG_I(NR_RRC,
"RRC Reestablishment Request from different physCellId (%ld) than current physCellId (%d), fallback to RRC setup\n",
@@ -1160,7 +1237,7 @@ static void process_Periodical_Measurement_Report(gNB_RRC_UE_t *ue_ctxt, NR_Meas
measurementReport->criticalExtensions.choice.measurementReport = NULL;
}
-static void process_Event_Based_Measurement_Report(NR_ReportConfigNR_t *report, NR_MeasurementReport_t *measurementReport)
+static void process_Event_Based_Measurement_Report(gNB_RRC_UE_t *ue, NR_ReportConfigNR_t *report, NR_MeasurementReport_t *measurementReport)
{
NR_EventTriggerConfig_t *event_triggered = report->reportType.choice.eventTriggered;
@@ -1174,7 +1251,7 @@ static void process_Event_Based_Measurement_Report(NR_ReportConfigNR_t *report,
break;
case NR_EventTriggerConfig__eventId_PR_eventA3: {
- LOG_I(NR_RRC, "HO LOG: Event A3 Report - Neighbour Becomes Better than Serving!\n");
+ LOG_W(NR_RRC, "HO LOG: Event A3 Report - Neighbour Becomes Better than Serving!\n");
const NR_MeasResults_t *measResults = &measurementReport->criticalExtensions.choice.measurementReport->measResults;
for (int serving_cell_idx = 0; serving_cell_idx < measResults->measResultServingMOList.list.count; serving_cell_idx++) {
@@ -1213,13 +1290,13 @@ static void process_Event_Based_Measurement_Report(NR_ReportConfigNR_t *report,
neighbourCellId,
neighbourCellRSRP);
- const f1ap_served_cell_info_t *neighbour_cell_du_context = get_cell_information_by_phycellId(neighbourCellId);
- const f1ap_served_cell_info_t *serving_cell_du_context = get_cell_information_by_phycellId(servingCellId);
+ const f1ap_served_cell_info_t *neigh_cell = get_cell_information_by_phycellId(neighbourCellId);
+ const f1ap_served_cell_info_t *serving_cell = get_cell_information_by_phycellId(servingCellId);
const nr_neighbour_gnb_configuration_t *neighbour =
- get_neighbour_cell_information(serving_cell_du_context->nr_cellid, neighbourCellId);
+ get_neighbour_cell_information(serving_cell->nr_cellid, neighbourCellId);
// CU does not have f1 connection with neighbour cell context. So check does serving cell has this phyCellId as a
// neighbour.
- if (!neighbour_cell_du_context && neighbour) {
+ if (!neigh_cell && neighbour) {
// No F1 connection but static neighbour configuration is available
const nr_a3_event_t *a3_event_configuration = get_a3_configuration(neighbour->physicalCellId);
// Additional check - This part can be modified according to additional cell specific Handover Margin
@@ -1228,6 +1305,15 @@ static void process_Event_Based_Measurement_Report(NR_ReportConfigNR_t *report,
< (neighbourCellRSRP - servingCellRSRP))) {
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) {
+ /* 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);
+ DevAssert(source_du);
+ nr_rrc_du_container_t *target_du = get_du_by_cell_id(rrc, neigh_cell->nr_cellid);
+ nr_rrc_trigger_f1_ho(rrc, ue, source_du, target_du);
+ } else {
+ 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, neighbourCellId);
}
}
@@ -1289,12 +1375,10 @@ static void rrc_gNB_process_MeasurementReport(gNB_RRC_UE_t *UE, NR_MeasurementRe
if (report_config->choice.reportConfigNR->reportType.present == NR_ReportConfigNR__reportType_PR_periodical)
return process_Periodical_Measurement_Report(UE, measurementReport);
- if (report_config->choice.reportConfigNR->reportType.present != NR_ReportConfigNR__reportType_PR_eventTriggered) {
- LOG_D(NR_RRC, "Incoming Report Type: %d is not supported! \n", report_config->choice.reportConfigNR->reportType.present);
- return;
- }
+ if (report_config->choice.reportConfigNR->reportType.present == NR_ReportConfigNR__reportType_PR_eventTriggered)
+ return process_Event_Based_Measurement_Report(UE, report_config->choice.reportConfigNR, measurementReport);
- process_Event_Based_Measurement_Report(report_config->choice.reportConfigNR, measurementReport);
+ 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)
@@ -1546,6 +1630,14 @@ static void handle_rrcReconfigurationComplete(gNB_RRC_INST *rrc, gNB_RRC_UE_t *U
LOG_I(RRC, "UE %d: transaction %d still ongoing for action %d\n", UE->rrc_ue_id, i, UE->xids[i]);
}
}
+
+ if (UE->ho_context != NULL) {
+ LOG_A(NR_RRC, "handover for UE %d/RNTI %04x complete!\n", UE->rrc_ue_id, UE->rnti);
+ DevAssert(UE->ho_context->target != NULL);
+
+ UE->ho_context->target->ho_success(rrc, UE);
+ nr_rrc_finalize_ho(UE);
+ }
}
static void rrc_gNB_generate_UECapabilityEnquiry(gNB_RRC_INST *rrc, gNB_RRC_UE_t *ue)
@@ -1839,27 +1931,80 @@ static void rrc_CU_process_ue_context_setup_response(MessageDef *msg_p, instance
xer_fprint(stdout, &asn_DEF_NR_CellGroupConfig, UE->masterCellGroup);
if (resp->drbs_to_be_setup_length > 0) {
+ int num_drb = get_number_active_drbs(UE);
+ DevAssert(num_drb == 0 || num_drb == resp->drbs_to_be_setup_length);
+
/* Note: we would ideally check that SRB2 is acked, but at least LiteOn DU
* seems buggy and does not ack, so simply check that locally we activated */
AssertFatal(UE->Srb[1].Active && UE->Srb[2].Active, "SRBs 1 and 2 must be active during DRB Establishment");
store_du_f1u_tunnel(resp->drbs_to_be_setup, resp->drbs_to_be_setup_length, UE);
- e1_send_bearer_updates(rrc, UE, resp->drbs_to_be_setup_length, resp->drbs_to_be_setup);
+ if (num_drb == 0)
+ e1_send_bearer_updates(rrc, UE, resp->drbs_to_be_setup_length, resp->drbs_to_be_setup);
+ else
+ cuup_notify_reestablishment(rrc, UE);
}
- rrc_gNB_generate_dedicatedRRCReconfiguration(rrc, UE);
+ if (UE->ho_context == NULL) {
+ rrc_gNB_generate_dedicatedRRCReconfiguration(rrc, UE);
+ } else {
+ // case of handover
+ // handling of "target CU" information
+ DevAssert(UE->ho_context->target != NULL);
+ DevAssert(resp->crnti != NULL);
+ UE->ho_context->target->du_ue_id = resp->gNB_DU_ue_id;
+ UE->ho_context->target->new_rnti = *resp->crnti;
+
+ uint8_t xid = rrc_gNB_get_next_transaction_identifier(0);
+ UE->xids[xid] = RRC_DEDICATED_RECONF;
+ uint8_t buffer[NR_RRC_BUF_SIZE] = {0};
+ int size = rrc_gNB_encode_RRCReconfiguration(rrc, UE, xid, NULL, buffer, sizeof(buffer), true);
+ DevAssert(size > 0 && size <= sizeof(buffer));
+
+ // TODO N2 38.413 sec 9.3.1.21: admitted PDU sessions (in UE), Target to
+ // Source Transparent Container (9.3.1.21) which encodes the RRC
+ // reconfiguration above
+ UE->ho_context->target->ho_req_ack(rrc, UE, buffer, size);
+ }
}
-static void rrc_CU_process_ue_context_release_request(MessageDef *msg_p)
+static void rrc_CU_process_ue_context_release_request(MessageDef *msg_p, sctp_assoc_t assoc_id)
{
const int instance = 0;
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
if (!ue_context_p) {
LOG_E(RRC, "could not find UE context for CU UE ID %u, aborting transaction\n", req->gNB_CU_ue_id);
+ // TODO just request the DU to release to make it happy
return;
}
+ gNB_RRC_UE_t *UE = &ue_context_p->ue_context;
+ if (UE->ho_context != NULL) {
+ nr_ho_source_cu_t *source_ctx = UE->ho_context->source;
+ bool from_source_du = source_ctx && source_ctx->du->assoc_id == assoc_id;
+ if (from_source_du) {
+ // we received release request from the source DU, but HO is still
+ // ongoing; free the UE, and remove the HO context.
+ LOG_W(NR_RRC, "UE %d: release request from source DU ID %ld during HO, marking HO as complete\n", UE->rrc_ue_id, source_ctx->du->setup_req->gNB_DU_id);
+ RETURN_IF_INVALID_ASSOC_ID(source_ctx->du->assoc_id);
+ f1ap_ue_context_release_cmd_t cmd = {
+ .gNB_CU_ue_id = UE->rrc_ue_id,
+ .gNB_DU_ue_id = source_ctx->du_ue_id,
+ .cause = F1AP_CAUSE_RADIO_NETWORK,
+ .cause_value = 5, // 5 = F1AP_CauseRadioNetwork_interaction_with_other_procedure
+ .srb_id = DCCH,
+ };
+ rrc->mac_rrc.ue_context_release_command(assoc_id, &cmd);
+ nr_rrc_finalize_ho(UE);
+ return;
+ }
+ // if we receive the release request from the target DU (regardless if
+ // successful), we assume it is "genuine" and ask the AMF to release
+ nr_rrc_finalize_ho(UE);
+ }
+
/* TODO: marshall types correctly */
LOG_I(NR_RRC, "received UE Context Release Request for UE %u, forwarding to AMF\n", req->gNB_CU_ue_id);
rrc_gNB_send_NGAP_UE_CONTEXT_RELEASE_REQ(instance,
@@ -1945,9 +2090,27 @@ static void rrc_CU_process_ue_context_modification_response(MessageDef *msg_p, i
rrc_gNB_generate_dedicatedRRCReconfiguration(rrc, UE);
}
+
+ // Reconfiguration should have been sent to the UE, so it will attempt the
+ // handover. In the F1 case, update with new RNTI, and update secondary UE
+ // association, so we can receive the new UE from the target DU (in N2/Xn,
+ // nothing is to be done, we wait for confirmation to release the UE in the
+ // CU/DU)
+ if (UE->ho_context && UE->ho_context->target && UE->ho_context->source) {
+ nr_ho_target_cu_t *target_ctx = UE->ho_context->target;
+ f1_ue_data_t ue_data = cu_get_f1_ue_data(UE->rrc_ue_id);
+ ue_data.secondary_ue = target_ctx->du_ue_id;
+ ue_data.du_assoc_id = target_ctx->du->assoc_id;
+ cu_remove_f1_ue_data(UE->rrc_ue_id);
+ cu_add_f1_ue_data(UE->rrc_ue_id, &ue_data);
+ LOG_I(NR_RRC, "UE %d handover: update RNTI from %04x to %04x\n", UE->rrc_ue_id, UE->rnti, target_ctx->new_rnti);
+ nr_ho_source_cu_t *source_ctx = UE->ho_context->source;
+ DevAssert(source_ctx->old_rnti == UE->rnti);
+ UE->rnti = target_ctx->new_rnti;
+ }
}
-static void rrc_CU_process_ue_modification_required(MessageDef *msg_p, instance_t instance)
+static void rrc_CU_process_ue_modification_required(MessageDef *msg_p, instance_t instance, sctp_assoc_t assoc_id)
{
gNB_RRC_INST *rrc = RC.nrrrc[instance];
f1ap_ue_context_modif_required_t *required = &F1AP_UE_CONTEXT_MODIFICATION_REQUIRED(msg_p);
@@ -1964,6 +2127,12 @@ static void rrc_CU_process_ue_modification_required(MessageDef *msg_p, instance_
return;
}
+ gNB_RRC_UE_t *UE = &ue_context_p->ue_context;
+ if (UE->ho_context && UE->ho_context->source && UE->ho_context->source->du && UE->ho_context->source->du->assoc_id == assoc_id) {
+ LOG_W(NR_RRC, "UE %d: UE Context Modification Required during handover, ignoring message\n", UE->rrc_ue_id);
+ 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,
@@ -2011,7 +2180,7 @@ unsigned int mask_flip(unsigned int x) {
return((((x>>8) + (x<<8))&0xffff)>>6);
}
-static 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)
{
pdusession_t *pdu = &pduSession->param;
for (int i = 0; i < pdu->nb_qos; i++) {
@@ -2023,7 +2192,7 @@ static pdusession_level_qos_parameter_t *get_qos_characteristics(const int qfi,
}
/* \bref return F1AP QoS characteristics based on Qos flow parameters */
-static f1ap_qos_characteristics_t get_qos_char_from_qos_flow_param(const pdusession_level_qos_parameter_t *qos_param)
+f1ap_qos_characteristics_t get_qos_char_from_qos_flow_param(const pdusession_level_qos_parameter_t *qos_param)
{
f1ap_qos_characteristics_t qos_char = {0};
if (qos_param->fiveQI_type == DYNAMIC) {
@@ -2182,6 +2351,24 @@ static void print_rrc_meas(FILE *f, const NR_MeasResults_t *measresults)
} else {
fprintf(f, "NOT PROVIDED\n");
}
+
+ if (measresults->measResultNeighCells
+ && measresults->measResultNeighCells->present == NR_MeasResults__measResultNeighCells_PR_measResultListNR) {
+ NR_MeasResultListNR_t *meas_neigh = measresults->measResultNeighCells->choice.measResultListNR;
+ for (int i = 0; i < meas_neigh->list.count; ++i) {
+ NR_MeasResultNR_t *measresultneigh = meas_neigh->list.array[i];
+ NR_MeasQuantityResults_t *neigh_mqr = measresultneigh->measResult.cellResults.resultsSSB_Cell;
+ fprintf(f, " neighboring cell for phyCellId %ld:\n resultSSB:", *measresultneigh->physCellId);
+ if (mqr != NULL) {
+ const long rrsrp = *neigh_mqr->rsrp - 156;
+ const float rrsrq = (float)(*neigh_mqr->rsrq - 87) / 2.0f;
+ const float rsinr = (float)(*neigh_mqr->sinr - 46) / 2.0f;
+ fprintf(f, "RSRP %ld dBm RSRQ %.1f dB SINR %.1f dB\n", rrsrp, rrsrq, rsinr);
+ } else {
+ fprintf(f, "NOT PROVIDED\n");
+ }
+ }
+ }
}
static const char *get_pdusession_status_text(pdu_session_status_t status)
@@ -2345,11 +2532,11 @@ void *rrc_gnb_task(void *args_p) {
break;
case F1AP_UE_CONTEXT_MODIFICATION_REQUIRED:
- rrc_CU_process_ue_modification_required(msg_p, instance);
+ rrc_CU_process_ue_modification_required(msg_p, instance, msg_p->ittiMsgHeader.originInstance);
break;
case F1AP_UE_CONTEXT_RELEASE_REQ:
- rrc_CU_process_ue_context_release_request(msg_p);
+ rrc_CU_process_ue_context_release_request(msg_p, msg_p->ittiMsgHeader.originInstance);
break;
case F1AP_UE_CONTEXT_RELEASE_COMPLETE:
diff --git a/openair2/RRC/NR/rrc_gNB_du.c b/openair2/RRC/NR/rrc_gNB_du.c
index df0bf15a74..82b18f124b 100644
--- a/openair2/RRC/NR/rrc_gNB_du.c
+++ b/openair2/RRC/NR/rrc_gNB_du.c
@@ -24,6 +24,7 @@
#include "common/ran_context.h"
#include "nr_rrc_defs.h"
#include "rrc_gNB_UE_context.h"
+#include "rrc_gNB_mobility.h"
#include "openair2/F1AP/f1ap_common.h"
#include "openair2/F1AP/f1ap_ids.h"
#include "executables/softmodem-common.h"
@@ -395,7 +396,12 @@ static int invalidate_du_connections(gNB_RRC_INST *rrc, sctp_assoc_t assoc_id)
int count = 0;
rrc_gNB_ue_context_t *ue_context_p = NULL;
RB_FOREACH(ue_context_p, rrc_nr_ue_tree_s, &rrc->rrc_ue_head) {
- uint32_t ue_id = ue_context_p->ue_context.rrc_ue_id;
+ gNB_RRC_UE_t *UE = &ue_context_p->ue_context;
+ uint32_t ue_id = UE->rrc_ue_id;
+ if (UE->ho_context != NULL) {
+ LOG_W(NR_RRC, "DU disconnected while handover for UE %d active\n", ue_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) {
/* this UE belongs to the DU that disconnected, set du_assoc_id to 0,
@@ -559,6 +565,18 @@ nr_rrc_du_container_t *get_du_by_assoc_id(gNB_RRC_INST *rrc, sctp_assoc_t assoc_
return RB_FIND(rrc_du_tree, &rrc->dus, &e);
}
+/* \brief find DU by cell ID. Note: currently the CU is limited to one cell per
+ * DU, hence here, DU == cell. Modify this to look up a specific cell. */
+nr_rrc_du_container_t *get_du_by_cell_id(gNB_RRC_INST *rrc, uint64_t cell_id)
+{
+ nr_rrc_du_container_t *du = NULL;
+ RB_FOREACH(du, rrc_du_tree, &rrc->dus) {
+ if (cell_id == du->setup_req->cell[0].info.nr_cellid)
+ return du;
+ }
+ return NULL;
+}
+
void dump_du_info(const gNB_RRC_INST *rrc, FILE *f)
{
fprintf(f, "%ld connected DUs \n", rrc->num_dus);
@@ -590,3 +608,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 3ee0b611ef..bd3c4f6443 100644
--- a/openair2/RRC/NR/rrc_gNB_du.h
+++ b/openair2/RRC/NR/rrc_gNB_du.h
@@ -41,6 +41,7 @@ void rrc_gNB_process_f1_du_configuration_update(struct f1ap_gnb_du_configuration
struct nr_rrc_du_container_t *get_du_for_ue(struct gNB_RRC_INST_s *rrc, uint32_t ue_id);
struct nr_rrc_du_container_t *get_du_by_assoc_id(struct gNB_RRC_INST_s *rrc, sctp_assoc_t assoc_id);
const struct f1ap_served_cell_info_t *get_cell_information_by_phycellId(int phyCellId);
+struct nr_rrc_du_container_t *get_du_by_cell_id(struct gNB_RRC_INST_s *rrc, uint64_t cell_id);
void dump_du_info(const struct gNB_RRC_INST_s *rrc, FILE *f);
@@ -48,4 +49,16 @@ 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) \
+ { \
+ if (assoc_id == 0) { \
+ LOG_E(NR_RRC, "cannot send data: invalid assoc_id 0, DU offline\n"); \
+ return; \
+ } \
+ }
+
#endif /* RRC_GNB_DU_H_ */
diff --git a/openair2/RRC/NR/rrc_gNB_mobility.c b/openair2/RRC/NR/rrc_gNB_mobility.c
new file mode 100644
index 0000000000..137e7010c2
--- /dev/null
+++ b/openair2/RRC/NR/rrc_gNB_mobility.c
@@ -0,0 +1,360 @@
+/*
+ * Licensed to the OpenAirInterface (OAI) Software Alliance under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The OpenAirInterface Software Alliance licenses this file to You under
+ * the OAI Public License, Version 1.1 (the "License"); you may not use this file
+ * except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.openairinterface.org/?page_id=698
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *-------------------------------------------------------------------------------
+ * For more information about the OpenAirInterface (OAI) Software Alliance:
+ * contact@openairinterface.org
+ */
+
+#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"
+#include "nr_pdcp/nr_pdcp_oai_api.h"
+#include "openair3/SECU/key_nas_deriver.h"
+
+/* declared in rrc_gNB.c: should be cleaned up through helper function? useful? */
+extern mui_t rrc_gNB_mui;
+
+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)
+{
+ nr_handover_context_t *ho_ctx = calloc(1, sizeof(*ho_ctx));
+ AssertFatal(ho_ctx != NULL, "out of memory\n");
+ if (type == HO_CTX_SOURCE || type == HO_CTX_BOTH) {
+ ho_ctx->source = calloc(1, sizeof(*ho_ctx->source));
+ AssertFatal(ho_ctx->source != NULL, "out of memory\n");
+ }
+ if (type == HO_CTX_TARGET || type == HO_CTX_BOTH) {
+ ho_ctx->target = calloc(1, sizeof(*ho_ctx->target));
+ AssertFatal(ho_ctx->target != NULL, "out of memory\n");
+ }
+ return ho_ctx;
+}
+
+static void free_ho_ctx(nr_handover_context_t *ho_ctx)
+{
+ free(ho_ctx->source);
+ 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;
+
+ int result = asn_copy(&asn_DEF_NR_CellGroupConfig, (void **)&ho_ctx->source->old_cellGroupConfig, ue->masterCellGroup);
+ AssertFatal(result == 0, "error during asn_copy() of CellGroupConfig\n");
+ }
+ 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.
+}
+*/
+
+typedef struct deliver_ue_ctxt_modification_data_t {
+ gNB_RRC_INST *rrc;
+ f1ap_ue_context_modif_req_t *modification_req;
+ sctp_assoc_t assoc_id;
+} 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)
+{
+ DevAssert(deliver_pdu_data != NULL);
+ deliver_ue_ctxt_modification_data_t *data = deliver_pdu_data;
+ data->modification_req->rrc_container = (uint8_t*)buf;
+ data->modification_req->rrc_container_length = size;
+ data->rrc->mac_rrc.ue_context_modification_request(data->assoc_id, data->modification_req);
+}
+static void rrc_gNB_trigger_reconfiguration_for_handover(gNB_RRC_INST *rrc, gNB_RRC_UE_t *ue, uint8_t *rrc_reconf, int rrc_reconf_len)
+{
+ f1_ue_data_t ue_data = cu_get_f1_ue_data(ue->rrc_ue_id);
+
+ TransmActionInd_t transmission_action_indicator = TransmActionInd_STOP;
+ RETURN_IF_INVALID_ASSOC_ID(ue_data.du_assoc_id);
+ f1ap_ue_context_modif_req_t ue_context_modif_req = {
+ .gNB_CU_ue_id = ue->rrc_ue_id,
+ .gNB_DU_ue_id = ue_data.secondary_ue,
+ .plmn.mcc = rrc->configuration.mcc[0],
+ .plmn.mnc = rrc->configuration.mnc[0],
+ .plmn.mnc_digit_length = rrc->configuration.mnc_digit_length[0],
+ .nr_cellid = rrc->nr_cellid, // TODO target cell ID
+ .servCellId = 0, // TODO: correct value?
+ .ReconfigComplOutcome = RRCreconf_success,
+ .transm_action_ind = &transmission_action_indicator,
+ };
+ deliver_ue_ctxt_modification_data_t data = {.rrc = rrc,
+ .modification_req = &ue_context_modif_req,
+ .assoc_id = ue_data.du_assoc_id};
+ int srb_id = 1;
+ nr_pdcp_data_req_srb(ue->rrc_ue_id,
+ srb_id,
+ rrc_gNB_mui++,
+ rrc_reconf_len,
+ (unsigned char *const)rrc_reconf,
+ rrc_deliver_ue_ctxt_modif_req,
+ &data);
+}
+
+static void nr_rrc_f1_ho_acknowledge(gNB_RRC_INST *rrc, gNB_RRC_UE_t *UE, uint8_t *rrc_reconf_buf, uint32_t rrc_reconf_len)
+{
+ // N2/Xn HO: fill with UE caps, as-context, rrc reconf, send to source CU
+ // also, fill the UE->rnti from the new one (in F1 case, happens after
+ // confirmation of ue ctxt modif
+
+ // F1 HO: handling of "source CU" information
+ DevAssert(UE->ho_context->source != NULL);
+ rrc_gNB_trigger_reconfiguration_for_handover(rrc, UE, rrc_reconf_buf, rrc_reconf_len);
+ LOG_A(NR_RRC, "HO acknowledged: Send reconfiguration for UE %u/RNTI %04x...\n", UE->rrc_ue_id, UE->rnti);
+
+ /* Re-establish SRB2 according to clause 5.3.5.6.3 of 3GPP TS 38.331
+ * (SRB1 is re-established with RRCReestablishment message)
+ */
+ int srb_id = 2;
+ if (UE->Srb[srb_id].Active) {
+ nr_pdcp_entity_security_keys_and_algos_t security_parameters;
+ /* Derive the keys from kgnb */
+ nr_derive_key(RRC_ENC_ALG, UE->ciphering_algorithm, UE->kgnb, security_parameters.ciphering_key);
+ nr_derive_key(RRC_INT_ALG, UE->integrity_algorithm, UE->kgnb, security_parameters.integrity_key);
+ security_parameters.integrity_algorithm = UE->integrity_algorithm;
+ security_parameters.ciphering_algorithm = UE->ciphering_algorithm;
+ nr_pdcp_reestablishment(UE->rrc_ue_id, srb_id, true, &security_parameters);
+ }
+}
+
+static void nr_rrc_f1_ho_complete(gNB_RRC_INST *rrc, gNB_RRC_UE_t *UE)
+{
+ DevAssert(UE->ho_context != NULL);
+ nr_ho_source_cu_t *source_ctx = UE->ho_context->source;
+ DevAssert(source_ctx != NULL);
+ RETURN_IF_INVALID_ASSOC_ID(source_ctx->du->assoc_id);
+ f1ap_ue_context_release_cmd_t cmd = {
+ .gNB_CU_ue_id = UE->rrc_ue_id,
+ .gNB_DU_ue_id = source_ctx->du_ue_id,
+ .cause = F1AP_CAUSE_RADIO_NETWORK, // better
+ .cause_value = 5, // 5 = F1AP_CauseRadioNetwork_interaction_with_other_procedure
+ .srb_id = DCCH,
+ };
+ rrc->mac_rrc.ue_context_release_command(source_ctx->du->assoc_id, &cmd);
+ LOG_I(NR_RRC, "UE %d Handover: trigger release on DU assoc_id %d\n", UE->rrc_ue_id, source_ctx->du->assoc_id);
+}
+
+static void nr_rrc_cancel_f1_ho(gNB_RRC_INST *rrc, gNB_RRC_UE_t *UE)
+{
+ DevAssert(UE->ho_context != NULL);
+ nr_ho_target_cu_t *target_ctx = UE->ho_context->target;
+ DevAssert(target_ctx != NULL);
+ f1ap_ue_context_release_cmd_t cmd = {
+ .gNB_CU_ue_id = UE->rrc_ue_id,
+ .gNB_DU_ue_id = target_ctx->du_ue_id,
+ .cause = F1AP_CAUSE_RADIO_NETWORK, // better
+ .cause_value = 5, // 5 = F1AP_CauseRadioNetwork_interaction_with_other_procedure
+ .srb_id = DCCH,
+ };
+ rrc->mac_rrc.ue_context_release_command(target_ctx->du->assoc_id, &cmd);
+}
+
+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 = nr_rrc_f1_ho_acknowledge;
+ ho_success_t success = nr_rrc_f1_ho_complete;
+ ho_cancel_t cancel = nr_rrc_cancel_f1_ho;
+ nr_initiate_handover(rrc, ue, source_du, target_du, buf, size, ack, success, cancel);
+}
+
+void nr_rrc_finalize_ho(gNB_RRC_UE_t *ue)
+{
+ if (ue->ho_context->source)
+ ASN_STRUCT_FREE(asn_DEF_NR_CellGroupConfig, ue->ho_context->source->old_cellGroupConfig);
+ free_ho_ctx(ue->ho_context);
+ ue->ho_context = NULL;
+}
+
+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
new file mode 100644
index 0000000000..5046176c1d
--- /dev/null
+++ b/openair2/RRC/NR/rrc_gNB_mobility.h
@@ -0,0 +1,77 @@
+/* Licensed to the OpenAirInterface (OAI) Software Alliance under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The OpenAirInterface Software Alliance licenses this file to You under
+ * the OAI Public License, Version 1.1 (the "License"); you may not use this file
+ * except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.openairinterface.org/?page_id=698
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *-------------------------------------------------------------------------------
+ * For more information about the OpenAirInterface (OAI) Software Alliance:
+ * contact@openairinterface.org
+ */
+
+#ifndef RRC_GNB_MOBILITY_H_
+#define RRC_GNB_MOBILITY_H_
+
+#include
+
+/* forward declarations */
+typedef struct gNB_RRC_INST_s gNB_RRC_INST;
+typedef struct gNB_RRC_UE_s gNB_RRC_UE_t;
+typedef struct nr_rrc_du_container_t nr_rrc_du_container_t;
+
+typedef struct NR_CellGroupConfig NR_CellGroupConfig_t;
+
+typedef void (*ho_cancel_t)(gNB_RRC_INST *rrc, gNB_RRC_UE_t *ue);
+typedef struct nr_ho_source_cu {
+ /// pointer to the (source) DU structure
+ const nr_rrc_du_container_t *du;
+ /// (source) DU UE ID; in F1, the DU UE ID will change to the new (target) DU
+ /// UE ID during handover, and the CU needs to keep track of this in case of
+ /// reestablishment
+ uint32_t du_ue_id;
+ /// old (source) RNTI (to recognize a UE during reestablishment)
+ rnti_t old_rnti;
+ /// old (source) CellGroupConfig (to send the cellGroupConfig in case of
+ /// reestablishment)
+ NR_CellGroupConfig_t *old_cellGroupConfig;
+ /// function pointer to announce the handover cancellation, e.g.,
+ /// reestablishment
+ ho_cancel_t ho_cancel;
+} nr_ho_source_cu_t;
+
+/* acknowledgement of handover request. buf+len is the RRC Reconfiguration */
+typedef void (*ho_req_ack_t)(gNB_RRC_INST *rrc, gNB_RRC_UE_t *ue, uint8_t *buf, uint32_t len);
+typedef void (*ho_success_t)(gNB_RRC_INST *rrc, gNB_RRC_UE_t *ue);
+typedef struct nr_ho_target_cu {
+ /// pointer to the (target) DU structure
+ const nr_rrc_du_container_t *du;
+ /// (target) DU UE ID; the (source) DU UE ID will change to the new (target)
+ /// DU UE ID after sending a reconfiguration, which is later than when
+ /// receiving this ID
+ uint32_t du_ue_id;
+ /// new (target) RNTI (as for du_ue_id)
+ rnti_t new_rnti;
+ /// function pointer to announce handover request acknowledgment
+ ho_req_ack_t ho_req_ack;
+ /// function pointer to announce handover success
+ ho_success_t ho_success;
+} nr_ho_target_cu_t;
+
+typedef struct nr_handover_context_s {
+ nr_ho_source_cu_t *source;
+ 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);
+void nr_rrc_finalize_ho(gNB_RRC_UE_t *ue);
+
+#endif /* RRC_GNB_MOBILITY_H_ */
diff --git a/openair2/RRC/NR/rrc_gNB_radio_bearers.c b/openair2/RRC/NR/rrc_gNB_radio_bearers.c
index ab2bf13071..aec7865a1c 100644
--- a/openair2/RRC/NR/rrc_gNB_radio_bearers.c
+++ b/openair2/RRC/NR/rrc_gNB_radio_bearers.c
@@ -181,6 +181,15 @@ uint8_t get_next_available_drb_id(gNB_RRC_UE_t *ue)
return DRB_INACTIVE;
}
+int get_number_active_drbs(gNB_RRC_UE_t *ue)
+{
+ int n = 0;
+ for (int i = 0; i < MAX_DRBS_PER_UE; ++i)
+ if (ue->established_drbs[i].status != DRB_INACTIVE)
+ n++;
+ return n;
+}
+
bool drb_is_active(gNB_RRC_UE_t *ue, uint8_t drb_id)
{
drb_t *drb = get_drb(ue, drb_id);
diff --git a/openair2/RRC/NR/rrc_gNB_radio_bearers.h b/openair2/RRC/NR/rrc_gNB_radio_bearers.h
index 82502d8669..29fead6bea 100644
--- a/openair2/RRC/NR/rrc_gNB_radio_bearers.h
+++ b/openair2/RRC/NR/rrc_gNB_radio_bearers.h
@@ -61,6 +61,9 @@ drb_t *generateDRB(gNB_RRC_UE_t *ue,
/// @brief return the next available (inactive) DRB ID of UE ue
uint8_t get_next_available_drb_id(gNB_RRC_UE_t *ue);
+/// @brief returns the number of active DRBs for this UE
+int get_number_active_drbs(gNB_RRC_UE_t *ue);
+
/// @brief check if DRB with ID drb_id of UE ue is active
bool drb_is_active(gNB_RRC_UE_t *ue, uint8_t drb_id);
diff --git a/targets/PROJECTS/GENERIC-NR-5GC/CONF/gnb-cu.sa.f1.conf b/targets/PROJECTS/GENERIC-NR-5GC/CONF/gnb-cu.sa.f1.conf
new file mode 100644
index 0000000000..08b72bf6c6
--- /dev/null
+++ b/targets/PROJECTS/GENERIC-NR-5GC/CONF/gnb-cu.sa.f1.conf
@@ -0,0 +1,74 @@
+Active_gNBs = ( "cu-rfsim");
+# Asn1_verbosity, choice in: none, info, annoying
+Asn1_verbosity = "none";
+Num_Threads_PUSCH = 8;
+
+gNBs =
+(
+ {
+ ////////// Identification parameters:
+ gNB_ID = 0xe00;
+ gNB_name = "cu-rfsim";
+
+ // Tracking area code, 0x0000 and 0xfffe are reserved values
+ tracking_area_code = 1;
+ plmn_list = ({ mcc = 001; mnc = 01; mnc_length = 2; snssaiList = ({ sst = 1; }) });
+
+ nr_cellid = 12345678L;
+
+ tr_s_preference = "f1";
+
+ local_s_if_name = "lo";
+ local_s_address = "127.0.0.3";
+ remote_s_address = "0.0.0.0"; # multiple DUs
+ local_s_portc = 501;
+ local_s_portd = 2152;
+ remote_s_portc = 500;
+ remote_s_portd = 2152;
+
+
+ # ------- SCTP definitions
+ SCTP :
+ {
+ # Number of streams to use in input/output
+ SCTP_INSTREAMS = 2;
+ SCTP_OUTSTREAMS = 2;
+ };
+
+
+ ////////// AMF parameters:
+ amf_ip_address = ({ ipv4 = "192.168.70.132"; });
+
+ NETWORK_INTERFACES :
+ {
+ GNB_IPV4_ADDRESS_FOR_NG_AMF = "192.168.70.129/24";
+ GNB_IPV4_ADDRESS_FOR_NGU = "192.168.70.129/24";
+ GNB_PORT_FOR_S1U = 2152; # Spec 2152
+ };
+ }
+);
+
+security = {
+ # preferred ciphering algorithms
+ # the first one of the list that an UE supports in chosen
+ # valid values: nea0, nea1, nea2, nea3
+ ciphering_algorithms = ( "nea0" );
+
+ # preferred integrity algorithms
+ # the first one of the list that an UE supports in chosen
+ # valid values: nia0, nia1, nia2, nia3
+ integrity_algorithms = ( "nia2", "nia0" );
+
+ # setting 'drb_ciphering' to "no" disables ciphering for DRBs, no matter
+ # what 'ciphering_algorithms' configures; same thing for 'drb_integrity'
+ drb_ciphering = "yes";
+ drb_integrity = "no";
+};
+
+log_config : {
+ global_log_level = "info";
+ pdcp_log_level = "info";
+ rrc_log_level = "info";
+ f1ap_log_level = "info";
+ ngap_log_level = "info";
+};
diff --git a/targets/PROJECTS/GENERIC-NR-5GC/CONF/gnb-du.sa.band66.25prb.rfsim.pci0.conf b/targets/PROJECTS/GENERIC-NR-5GC/CONF/gnb-du.sa.band66.25prb.rfsim.pci0.conf
new file mode 100644
index 0000000000..abd624747d
--- /dev/null
+++ b/targets/PROJECTS/GENERIC-NR-5GC/CONF/gnb-du.sa.band66.25prb.rfsim.pci0.conf
@@ -0,0 +1,215 @@
+Active_gNBs = ( "du-rfsim");
+# Asn1_verbosity, choice in: none, info, annoying
+Asn1_verbosity = "none";
+
+gNBs =
+(
+ {
+ ////////// Identification parameters:
+ gNB_ID = 0xe00;
+ gNB_DU_ID = 0xe00;
+ gNB_name = "du-rfsim";
+
+ // Tracking area code, 0x0000 and 0xfffe are reserved values
+ tracking_area_code = 1;
+ plmn_list = ({ mcc = 208; mnc = 95; mnc_length = 2; snssaiList = ({ sst = 1; }) });
+
+ nr_cellid = 12345678L;
+
+ ////////// Physical parameters:
+
+ min_rxtxtime = 4;
+ sib1_tda = 15;
+
+ servingCellConfigCommon = (
+ {
+ #spCellConfigCommon
+
+ physCellId = 0;
+
+# downlinkConfigCommon
+ #frequencyInfoDL
+ # this is 3600 MHz + 43 PRBs@30kHz SCS (same as initial BWP)
+ absoluteFrequencySSB = 430610;
+ dl_frequencyBand = 66;
+ # this is 3600 MHz
+ dl_absoluteFrequencyPointA = 430000;
+ #scs-SpecificCarrierList
+ dl_offstToCarrier = 0;
+# subcarrierSpacing
+# 0=kHz15, 1=kHz30, 2=kHz60, 3=kHz120
+ dl_subcarrierSpacing = 0;
+ dl_carrierBandwidth = 25;
+ #initialDownlinkBWP
+ #genericParameters
+ # this is RBstart=27,L=48 (275*(L-1))+RBstart
+ initialDLBWPlocationAndBandwidth = 6600; # 6366 12925 12956 28875 12952
+# subcarrierSpacing
+# 0=kHz15, 1=kHz30, 2=kHz60, 3=kHz120
+ initialDLBWPsubcarrierSpacing = 0;
+ #pdcch-ConfigCommon
+ initialDLBWPcontrolResourceSetZero = 0;
+ initialDLBWPsearchSpaceZero = 0;
+
+ #uplinkConfigCommon
+ #frequencyInfoUL
+ ul_frequencyBand = 66;
+ # this is 1750 MHz
+ ul_absoluteFrequencyPointA = 350000;
+ #scs-SpecificCarrierList
+ ul_offstToCarrier = 0;
+# subcarrierSpacing
+# 0=kHz15, 1=kHz30, 2=kHz60, 3=kHz120
+ ul_subcarrierSpacing = 0;
+ ul_carrierBandwidth = 25;
+ pMax = 20;
+ #initialUplinkBWP
+ #genericParameters
+ initialULBWPlocationAndBandwidth = 6600;
+# subcarrierSpacing
+# 0=kHz15, 1=kHz30, 2=kHz60, 3=kHz120
+ initialULBWPsubcarrierSpacing = 0;
+ #rach-ConfigCommon
+ #rach-ConfigGeneric
+ prach_ConfigurationIndex = 98;
+#prach_msg1_FDM
+#0 = one, 1=two, 2=four, 3=eight
+ prach_msg1_FDM = 0;
+ prach_msg1_FrequencyStart = 0;
+ zeroCorrelationZoneConfig = 13;
+ preambleReceivedTargetPower = -96;
+#preamblTransMax (0...10) = (3,4,5,6,7,8,10,20,50,100,200)
+ preambleTransMax = 6;
+#powerRampingStep
+# 0=dB0,1=dB2,2=dB4,3=dB6
+ powerRampingStep = 1;
+#ra_ReponseWindow
+#1,2,4,8,10,20,40,80
+ ra_ResponseWindow = 4;
+#ssb_perRACH_OccasionAndCB_PreamblesPerSSB_PR
+#1=oneeighth,2=onefourth,3=half,4=one,5=two,6=four,7=eight,8=sixteen
+ ssb_perRACH_OccasionAndCB_PreamblesPerSSB_PR = 4;
+#oneHalf (0..15) 4,8,12,16,...60,64
+ ssb_perRACH_OccasionAndCB_PreamblesPerSSB = 15;
+#ra_ContentionResolutionTimer
+#(0..7) 8,16,24,32,40,48,56,64
+ ra_ContentionResolutionTimer = 7;
+ rsrp_ThresholdSSB = 19;
+#prach-RootSequenceIndex_PR
+#1 = 839, 2 = 139
+ prach_RootSequenceIndex_PR = 2;
+ prach_RootSequenceIndex = 1;
+ # SCS for msg1, can only be 15 for 30 kHz < 6 GHz, takes precendence over the one derived from prach-ConfigIndex
+ #
+ msg1_SubcarrierSpacing = 0,
+# restrictedSetConfig
+# 0=unrestricted, 1=restricted type A, 2=restricted type B
+ restrictedSetConfig = 0,
+
+ msg3_DeltaPreamble = 1;
+ p0_NominalWithGrant =-90;
+
+# pucch-ConfigCommon setup :
+# pucchGroupHopping
+# 0 = neither, 1= group hopping, 2=sequence hopping
+ pucchGroupHopping = 0;
+ hoppingId = 40;
+ p0_nominal = -90;
+
+ ssb_PositionsInBurst_Bitmap = 1;
+
+# ssb_periodicityServingCell
+# 0 = ms5, 1=ms10, 2=ms20, 3=ms40, 4=ms80, 5=ms160, 6=spare2, 7=spare1
+ ssb_periodicityServingCell = 2;
+
+# dmrs_TypeA_position
+# 0 = pos2, 1 = pos3
+ dmrs_TypeA_Position = 0;
+
+# subcarrierSpacing
+# 0=kHz15, 1=kHz30, 2=kHz60, 3=kHz120
+ subcarrierSpacing = 0;
+
+
+ #tdd-UL-DL-ConfigurationCommon
+# subcarrierSpacing
+# 0=kHz15, 1=kHz30, 2=kHz60, 3=kHz120
+ referenceSubcarrierSpacing = 0;
+ # pattern1
+ # dl_UL_TransmissionPeriodicity
+ # 0=ms0p5, 1=ms0p625, 2=ms1, 3=ms1p25, 4=ms2, 5=ms2p5, 6=ms5, 7=ms10
+ dl_UL_TransmissionPeriodicity = 6;
+ nrofDownlinkSlots = 7;
+ nrofDownlinkSymbols = 6;
+ nrofUplinkSlots = 2;
+ nrofUplinkSymbols = 4;
+
+ ssPBCH_BlockPower = -25;
+ }
+
+ );
+
+ # ------- SCTP definitions
+ SCTP :
+ {
+ # Number of streams to use in input/output
+ SCTP_INSTREAMS = 2;
+ SCTP_OUTSTREAMS = 2;
+ };
+ }
+);
+
+MACRLCs = ({
+ num_cc = 1;
+ tr_s_preference = "local_L1";
+ tr_n_preference = "f1";
+ local_n_if_name = "lo";
+ local_n_address = "127.0.0.4";
+ remote_n_address = "127.0.0.3";
+ local_n_portc = 500;
+ local_n_portd = 2152;
+ remote_n_portc = 501;
+ remote_n_portd = 2152;
+ pusch_FailureThres = 1000;
+});
+
+L1s = ({
+ num_cc = 1;
+ tr_n_preference = "local_mac";
+ prach_dtx_threshold = 200;
+ pucch0_dtx_threshold = 150;
+ ofdm_offset_divisor = 8; #set this to UINT_MAX for offset 0
+});
+
+RUs = ({
+ local_rf = "yes"
+ nb_tx = 1
+ nb_rx = 1
+ att_tx = 0
+ att_rx = 0;
+ bands = [66];
+ max_pdschReferenceSignalPower = -27;
+ max_rxgain = 114;
+ eNB_instances = [0];
+ clock_src = "internal";
+});
+
+rfsimulator: {
+ serveraddr = "server";
+ serverport = 4043;
+ options = (); #("saviq"); or/and "chanmod"
+ modelname = "AWGN";
+ IQfile = "/tmp/rfsimulator.iqs"
+}
+
+log_config: {
+ global_log_level = "info";
+ hw_log_level = "info";
+ nr_phy_log_level = "info";
+ nr_mac_log_level = "info";
+ rlc_log_level = "info";
+ pdcp_log_level = "info";
+ rrc_log_level = "info";
+ f1ap_log_level = "info";
+ ngap_log_level = "debug";
+};
diff --git a/targets/PROJECTS/GENERIC-NR-5GC/CONF/gnb-du.sa.band66.25prb.rfsim.pci1.conf b/targets/PROJECTS/GENERIC-NR-5GC/CONF/gnb-du.sa.band66.25prb.rfsim.pci1.conf
new file mode 100644
index 0000000000..8d2e46b44e
--- /dev/null
+++ b/targets/PROJECTS/GENERIC-NR-5GC/CONF/gnb-du.sa.band66.25prb.rfsim.pci1.conf
@@ -0,0 +1,215 @@
+Active_gNBs = ( "du-rfsim");
+# Asn1_verbosity, choice in: none, info, annoying
+Asn1_verbosity = "none";
+
+gNBs =
+(
+ {
+ ////////// Identification parameters:
+ gNB_ID = 0xe00;
+ gNB_DU_ID = 0xe01;
+ gNB_name = "du-rfsim";
+
+ // Tracking area code, 0x0000 and 0xfffe are reserved values
+ tracking_area_code = 1;
+ plmn_list = ({ mcc = 208; mnc = 95; mnc_length = 2; snssaiList = ({ sst = 1; }) });
+
+ nr_cellid = 11111111L;
+
+ ////////// Physical parameters:
+
+ min_rxtxtime = 4;
+ sib1_tda = 15;
+
+ servingCellConfigCommon = (
+ {
+ #spCellConfigCommon
+
+ physCellId = 1;
+
+# downlinkConfigCommon
+ #frequencyInfoDL
+ # this is 3600 MHz + 43 PRBs@30kHz SCS (same as initial BWP)
+ absoluteFrequencySSB = 435610;
+ dl_frequencyBand = 66;
+ # this is 3600 MHz
+ dl_absoluteFrequencyPointA = 435000;
+ #scs-SpecificCarrierList
+ dl_offstToCarrier = 0;
+# subcarrierSpacing
+# 0=kHz15, 1=kHz30, 2=kHz60, 3=kHz120
+ dl_subcarrierSpacing = 0;
+ dl_carrierBandwidth = 25;
+ #initialDownlinkBWP
+ #genericParameters
+ # this is RBstart=27,L=48 (275*(L-1))+RBstart
+ initialDLBWPlocationAndBandwidth = 6600; # 6366 12925 12956 28875 12952
+# subcarrierSpacing
+# 0=kHz15, 1=kHz30, 2=kHz60, 3=kHz120
+ initialDLBWPsubcarrierSpacing = 0;
+ #pdcch-ConfigCommon
+ initialDLBWPcontrolResourceSetZero = 0;
+ initialDLBWPsearchSpaceZero = 0;
+
+ #uplinkConfigCommon
+ #frequencyInfoUL
+ ul_frequencyBand = 66;
+ # this is 1750 MHz
+ ul_absoluteFrequencyPointA = 355000;
+ #scs-SpecificCarrierList
+ ul_offstToCarrier = 0;
+# subcarrierSpacing
+# 0=kHz15, 1=kHz30, 2=kHz60, 3=kHz120
+ ul_subcarrierSpacing = 0;
+ ul_carrierBandwidth = 25;
+ pMax = 20;
+ #initialUplinkBWP
+ #genericParameters
+ initialULBWPlocationAndBandwidth = 6600;
+# subcarrierSpacing
+# 0=kHz15, 1=kHz30, 2=kHz60, 3=kHz120
+ initialULBWPsubcarrierSpacing = 0;
+ #rach-ConfigCommon
+ #rach-ConfigGeneric
+ prach_ConfigurationIndex = 98;
+#prach_msg1_FDM
+#0 = one, 1=two, 2=four, 3=eight
+ prach_msg1_FDM = 0;
+ prach_msg1_FrequencyStart = 0;
+ zeroCorrelationZoneConfig = 13;
+ preambleReceivedTargetPower = -96;
+#preamblTransMax (0...10) = (3,4,5,6,7,8,10,20,50,100,200)
+ preambleTransMax = 6;
+#powerRampingStep
+# 0=dB0,1=dB2,2=dB4,3=dB6
+ powerRampingStep = 1;
+#ra_ReponseWindow
+#1,2,4,8,10,20,40,80
+ ra_ResponseWindow = 4;
+#ssb_perRACH_OccasionAndCB_PreamblesPerSSB_PR
+#1=oneeighth,2=onefourth,3=half,4=one,5=two,6=four,7=eight,8=sixteen
+ ssb_perRACH_OccasionAndCB_PreamblesPerSSB_PR = 4;
+#oneHalf (0..15) 4,8,12,16,...60,64
+ ssb_perRACH_OccasionAndCB_PreamblesPerSSB = 15;
+#ra_ContentionResolutionTimer
+#(0..7) 8,16,24,32,40,48,56,64
+ ra_ContentionResolutionTimer = 7;
+ rsrp_ThresholdSSB = 19;
+#prach-RootSequenceIndex_PR
+#1 = 839, 2 = 139
+ prach_RootSequenceIndex_PR = 2;
+ prach_RootSequenceIndex = 1;
+ # SCS for msg1, can only be 15 for 30 kHz < 6 GHz, takes precendence over the one derived from prach-ConfigIndex
+ #
+ msg1_SubcarrierSpacing = 0,
+# restrictedSetConfig
+# 0=unrestricted, 1=restricted type A, 2=restricted type B
+ restrictedSetConfig = 0,
+
+ msg3_DeltaPreamble = 1;
+ p0_NominalWithGrant =-90;
+
+# pucch-ConfigCommon setup :
+# pucchGroupHopping
+# 0 = neither, 1= group hopping, 2=sequence hopping
+ pucchGroupHopping = 0;
+ hoppingId = 40;
+ p0_nominal = -90;
+
+ ssb_PositionsInBurst_Bitmap = 1;
+
+# ssb_periodicityServingCell
+# 0 = ms5, 1=ms10, 2=ms20, 3=ms40, 4=ms80, 5=ms160, 6=spare2, 7=spare1
+ ssb_periodicityServingCell = 2;
+
+# dmrs_TypeA_position
+# 0 = pos2, 1 = pos3
+ dmrs_TypeA_Position = 0;
+
+# subcarrierSpacing
+# 0=kHz15, 1=kHz30, 2=kHz60, 3=kHz120
+ subcarrierSpacing = 0;
+
+
+ #tdd-UL-DL-ConfigurationCommon
+# subcarrierSpacing
+# 0=kHz15, 1=kHz30, 2=kHz60, 3=kHz120
+ referenceSubcarrierSpacing = 0;
+ # pattern1
+ # dl_UL_TransmissionPeriodicity
+ # 0=ms0p5, 1=ms0p625, 2=ms1, 3=ms1p25, 4=ms2, 5=ms2p5, 6=ms5, 7=ms10
+ dl_UL_TransmissionPeriodicity = 6;
+ nrofDownlinkSlots = 7;
+ nrofDownlinkSymbols = 6;
+ nrofUplinkSlots = 2;
+ nrofUplinkSymbols = 4;
+
+ ssPBCH_BlockPower = -25;
+ }
+
+ );
+
+ # ------- SCTP definitions
+ SCTP :
+ {
+ # Number of streams to use in input/output
+ SCTP_INSTREAMS = 2;
+ SCTP_OUTSTREAMS = 2;
+ };
+ }
+);
+
+MACRLCs = ({
+ num_cc = 1;
+ tr_s_preference = "local_L1";
+ tr_n_preference = "f1";
+ local_n_if_name = "lo";
+ local_n_address = "127.0.0.5";
+ remote_n_address = "127.0.0.3";
+ local_n_portc = 500;
+ local_n_portd = 2152;
+ remote_n_portc = 501;
+ remote_n_portd = 2152;
+ pusch_FailureThres = 1000;
+});
+
+L1s = ({
+ num_cc = 1;
+ tr_n_preference = "local_mac";
+ prach_dtx_threshold = 200;
+ pucch0_dtx_threshold = 150;
+ ofdm_offset_divisor = 8; #set this to UINT_MAX for offset 0
+});
+
+RUs = ({
+ local_rf = "yes"
+ nb_tx = 1
+ nb_rx = 1
+ att_tx = 0
+ att_rx = 0;
+ bands = [66];
+ max_pdschReferenceSignalPower = -27;
+ max_rxgain = 114;
+ eNB_instances = [0];
+ clock_src = "internal";
+});
+
+rfsimulator: {
+ serveraddr = "server";
+ serverport = 4043;
+ options = (); #("saviq"); or/and "chanmod"
+ modelname = "AWGN";
+ IQfile = "/tmp/rfsimulator.iqs"
+}
+
+log_config: {
+ global_log_level = "info";
+ hw_log_level = "info";
+ nr_phy_log_level = "info";
+ nr_mac_log_level = "info";
+ rlc_log_level = "info";
+ pdcp_log_level = "info";
+ rrc_log_level = "info";
+ f1ap_log_level = "info";
+ ngap_log_level = "debug";
+};
diff --git a/targets/PROJECTS/GENERIC-NR-5GC/CONF/gnb-du.sa.band78.106prb.rfsim.pci0.conf b/targets/PROJECTS/GENERIC-NR-5GC/CONF/gnb-du.sa.band78.106prb.rfsim.pci0.conf
new file mode 100644
index 0000000000..2e0f721504
--- /dev/null
+++ b/targets/PROJECTS/GENERIC-NR-5GC/CONF/gnb-du.sa.band78.106prb.rfsim.pci0.conf
@@ -0,0 +1,212 @@
+Active_gNBs = ( "du-rfsim");
+# Asn1_verbosity, choice in: none, info, annoying
+Asn1_verbosity = "none";
+
+gNBs =
+(
+ {
+ ////////// Identification parameters:
+ gNB_ID = 0xe00;
+ gNB_DU_ID = 0xe00;
+ gNB_name = "du-rfsim";
+
+ // Tracking area code, 0x0000 and 0xfffe are reserved values
+ tracking_area_code = 1;
+ plmn_list = ({ mcc = 001; mnc = 01; mnc_length = 2; snssaiList = ({ sst = 1; }) });
+
+ nr_cellid = 12345678L;
+
+ ////////// Physical parameters:
+
+ min_rxtxtime = 4;
+
+ servingCellConfigCommon = (
+ {
+ #spCellConfigCommon
+
+ physCellId = 0;
+
+# downlinkConfigCommon
+ #frequencyInfoDL
+ # this is 3600 MHz + 43 PRBs@30kHz SCS (same as initial BWP)
+ absoluteFrequencySSB = 630048;
+ dl_frequencyBand = 78;
+ # this is 3600 MHz
+ dl_absoluteFrequencyPointA = 628776;
+ #scs-SpecificCarrierList
+ dl_offstToCarrier = 0;
+# subcarrierSpacing
+# 0=kHz15, 1=kHz30, 2=kHz60, 3=kHz120
+ dl_subcarrierSpacing = 1;
+ dl_carrierBandwidth = 106;
+ #initialDownlinkBWP
+ #genericParameters
+ # this is RBstart=27,L=48 (275*(L-1))+RBstart
+ initialDLBWPlocationAndBandwidth = 28875; # 6366 12925 12956 28875 12952
+# subcarrierSpacing
+# 0=kHz15, 1=kHz30, 2=kHz60, 3=kHz120
+ initialDLBWPsubcarrierSpacing = 1;
+ #pdcch-ConfigCommon
+ initialDLBWPcontrolResourceSetZero = 12;
+ initialDLBWPsearchSpaceZero = 0;
+
+ #uplinkConfigCommon
+ #frequencyInfoUL
+ ul_frequencyBand = 78;
+ #scs-SpecificCarrierList
+ ul_offstToCarrier = 0;
+# subcarrierSpacing
+# 0=kHz15, 1=kHz30, 2=kHz60, 3=kHz120
+ ul_subcarrierSpacing = 1;
+ ul_carrierBandwidth = 106;
+ pMax = 20;
+ #initialUplinkBWP
+ #genericParameters
+ initialULBWPlocationAndBandwidth = 28875;
+# subcarrierSpacing
+# 0=kHz15, 1=kHz30, 2=kHz60, 3=kHz120
+ initialULBWPsubcarrierSpacing = 1;
+ #rach-ConfigCommon
+ #rach-ConfigGeneric
+ prach_ConfigurationIndex = 98;
+#prach_msg1_FDM
+#0 = one, 1=two, 2=four, 3=eight
+ prach_msg1_FDM = 0;
+ prach_msg1_FrequencyStart = 0;
+ zeroCorrelationZoneConfig = 13;
+ preambleReceivedTargetPower = -96;
+#preamblTransMax (0...10) = (3,4,5,6,7,8,10,20,50,100,200)
+ preambleTransMax = 6;
+#powerRampingStep
+# 0=dB0,1=dB2,2=dB4,3=dB6
+ powerRampingStep = 1;
+#ra_ReponseWindow
+#1,2,4,8,10,20,40,80
+ ra_ResponseWindow = 4;
+#ssb_perRACH_OccasionAndCB_PreamblesPerSSB_PR
+#1=oneeighth,2=onefourth,3=half,4=one,5=two,6=four,7=eight,8=sixteen
+ ssb_perRACH_OccasionAndCB_PreamblesPerSSB_PR = 4;
+#oneHalf (0..15) 4,8,12,16,...60,64
+ ssb_perRACH_OccasionAndCB_PreamblesPerSSB = 14;
+#ra_ContentionResolutionTimer
+#(0..7) 8,16,24,32,40,48,56,64
+ ra_ContentionResolutionTimer = 7;
+ rsrp_ThresholdSSB = 19;
+#prach-RootSequenceIndex_PR
+#1 = 839, 2 = 139
+ prach_RootSequenceIndex_PR = 2;
+ prach_RootSequenceIndex = 1;
+ # SCS for msg1, can only be 15 for 30 kHz < 6 GHz, takes precendence over the one derived from prach-ConfigIndex
+ #
+ msg1_SubcarrierSpacing = 1,
+# restrictedSetConfig
+# 0=unrestricted, 1=restricted type A, 2=restricted type B
+ restrictedSetConfig = 0,
+
+ msg3_DeltaPreamble = 1;
+ p0_NominalWithGrant =-90;
+
+# pucch-ConfigCommon setup :
+# pucchGroupHopping
+# 0 = neither, 1= group hopping, 2=sequence hopping
+ pucchGroupHopping = 0;
+ hoppingId = 40;
+ p0_nominal = -90;
+
+ ssb_PositionsInBurst_Bitmap = 1;
+
+# ssb_periodicityServingCell
+# 0 = ms5, 1=ms10, 2=ms20, 3=ms40, 4=ms80, 5=ms160, 6=spare2, 7=spare1
+ ssb_periodicityServingCell = 2;
+
+# dmrs_TypeA_position
+# 0 = pos2, 1 = pos3
+ dmrs_TypeA_Position = 0;
+
+# subcarrierSpacing
+# 0=kHz15, 1=kHz30, 2=kHz60, 3=kHz120
+ subcarrierSpacing = 1;
+
+
+ #tdd-UL-DL-ConfigurationCommon
+# subcarrierSpacing
+# 0=kHz15, 1=kHz30, 2=kHz60, 3=kHz120
+ referenceSubcarrierSpacing = 1;
+ # pattern1
+ # dl_UL_TransmissionPeriodicity
+ # 0=ms0p5, 1=ms0p625, 2=ms1, 3=ms1p25, 4=ms2, 5=ms2p5, 6=ms5, 7=ms10
+ dl_UL_TransmissionPeriodicity = 6;
+ nrofDownlinkSlots = 7;
+ nrofDownlinkSymbols = 6;
+ nrofUplinkSlots = 2;
+ nrofUplinkSymbols = 4;
+
+ ssPBCH_BlockPower = -25;
+ }
+
+ );
+
+ # ------- SCTP definitions
+ SCTP :
+ {
+ # Number of streams to use in input/output
+ SCTP_INSTREAMS = 2;
+ SCTP_OUTSTREAMS = 2;
+ };
+ }
+);
+
+MACRLCs = ({
+ num_cc = 1;
+ tr_s_preference = "local_L1";
+ tr_n_preference = "f1";
+ local_n_if_name = "lo";
+ local_n_address = "127.0.0.4";
+ remote_n_address = "127.0.0.3";
+ local_n_portc = 500;
+ local_n_portd = 2152;
+ remote_n_portc = 501;
+ remote_n_portd = 2152;
+ pusch_FailureThres = 1000;
+});
+
+L1s = ({
+ num_cc = 1;
+ tr_n_preference = "local_mac";
+ prach_dtx_threshold = 200;
+ pucch0_dtx_threshold = 150;
+ ofdm_offset_divisor = 8; #set this to UINT_MAX for offset 0
+});
+
+RUs = ({
+ local_rf = "yes"
+ nb_tx = 1
+ nb_rx = 1
+ att_tx = 0
+ att_rx = 0;
+ bands = [78];
+ max_pdschReferenceSignalPower = -27;
+ max_rxgain = 114;
+ eNB_instances = [0];
+ clock_src = "internal";
+});
+
+rfsimulator: {
+ serveraddr = "server";
+ serverport = 4043;
+ options = (); #("saviq"); or/and "chanmod"
+ modelname = "AWGN";
+ IQfile = "/tmp/rfsimulator.iqs"
+}
+
+log_config: {
+ global_log_level = "info";
+ hw_log_level = "info";
+ nr_phy_log_level = "info";
+ nr_mac_log_level = "info";
+ rlc_log_level = "info";
+ pdcp_log_level = "info";
+ rrc_log_level = "info";
+ f1ap_log_level = "info";
+ ngap_log_level = "debug";
+};
diff --git a/targets/PROJECTS/GENERIC-NR-5GC/CONF/gnb-du.sa.band78.106prb.rfsim.pci1.conf b/targets/PROJECTS/GENERIC-NR-5GC/CONF/gnb-du.sa.band78.106prb.rfsim.pci1.conf
new file mode 100644
index 0000000000..8e939887ce
--- /dev/null
+++ b/targets/PROJECTS/GENERIC-NR-5GC/CONF/gnb-du.sa.band78.106prb.rfsim.pci1.conf
@@ -0,0 +1,212 @@
+Active_gNBs = ( "du-rfsim");
+# Asn1_verbosity, choice in: none, info, annoying
+Asn1_verbosity = "none";
+
+gNBs =
+(
+ {
+ ////////// Identification parameters:
+ gNB_ID = 0xe00;
+ gNB_DU_ID = 0xe01;
+ gNB_name = "du-rfsim";
+
+ // Tracking area code, 0x0000 and 0xfffe are reserved values
+ tracking_area_code = 1;
+ plmn_list = ({ mcc = 001; mnc = 01; mnc_length = 2; snssaiList = ({ sst = 1; }) });
+
+ nr_cellid = 11111111L;
+
+ ////////// Physical parameters:
+
+ min_rxtxtime = 4;
+
+ servingCellConfigCommon = (
+ {
+ #spCellConfigCommon
+
+ physCellId = 1;
+
+# downlinkConfigCommon
+ #frequencyInfoDL
+ # this is 3600 MHz + 43 PRBs@30kHz SCS (same as initial BWP)
+ absoluteFrequencySSB = 643296;
+ dl_frequencyBand = 78;
+ # this is 3600 MHz
+ dl_absoluteFrequencyPointA = 642024;
+ #scs-SpecificCarrierList
+ dl_offstToCarrier = 0;
+# subcarrierSpacing
+# 0=kHz15, 1=kHz30, 2=kHz60, 3=kHz120
+ dl_subcarrierSpacing = 1;
+ dl_carrierBandwidth = 106;
+ #initialDownlinkBWP
+ #genericParameters
+ # this is RBstart=27,L=48 (275*(L-1))+RBstart
+ initialDLBWPlocationAndBandwidth = 28875; # 6366 12925 12956 28875 12952
+# subcarrierSpacing
+# 0=kHz15, 1=kHz30, 2=kHz60, 3=kHz120
+ initialDLBWPsubcarrierSpacing = 1;
+ #pdcch-ConfigCommon
+ initialDLBWPcontrolResourceSetZero = 12;
+ initialDLBWPsearchSpaceZero = 0;
+
+ #uplinkConfigCommon
+ #frequencyInfoUL
+ ul_frequencyBand = 78;
+ #scs-SpecificCarrierList
+ ul_offstToCarrier = 0;
+# subcarrierSpacing
+# 0=kHz15, 1=kHz30, 2=kHz60, 3=kHz120
+ ul_subcarrierSpacing = 1;
+ ul_carrierBandwidth = 106;
+ pMax = 20;
+ #initialUplinkBWP
+ #genericParameters
+ initialULBWPlocationAndBandwidth = 28875;
+# subcarrierSpacing
+# 0=kHz15, 1=kHz30, 2=kHz60, 3=kHz120
+ initialULBWPsubcarrierSpacing = 1;
+ #rach-ConfigCommon
+ #rach-ConfigGeneric
+ prach_ConfigurationIndex = 98;
+#prach_msg1_FDM
+#0 = one, 1=two, 2=four, 3=eight
+ prach_msg1_FDM = 0;
+ prach_msg1_FrequencyStart = 0;
+ zeroCorrelationZoneConfig = 13;
+ preambleReceivedTargetPower = -96;
+#preamblTransMax (0...10) = (3,4,5,6,7,8,10,20,50,100,200)
+ preambleTransMax = 6;
+#powerRampingStep
+# 0=dB0,1=dB2,2=dB4,3=dB6
+ powerRampingStep = 1;
+#ra_ReponseWindow
+#1,2,4,8,10,20,40,80
+ ra_ResponseWindow = 4;
+#ssb_perRACH_OccasionAndCB_PreamblesPerSSB_PR
+#1=oneeighth,2=onefourth,3=half,4=one,5=two,6=four,7=eight,8=sixteen
+ ssb_perRACH_OccasionAndCB_PreamblesPerSSB_PR = 4;
+#oneHalf (0..15) 4,8,12,16,...60,64
+ ssb_perRACH_OccasionAndCB_PreamblesPerSSB = 14;
+#ra_ContentionResolutionTimer
+#(0..7) 8,16,24,32,40,48,56,64
+ ra_ContentionResolutionTimer = 7;
+ rsrp_ThresholdSSB = 19;
+#prach-RootSequenceIndex_PR
+#1 = 839, 2 = 139
+ prach_RootSequenceIndex_PR = 2;
+ prach_RootSequenceIndex = 1;
+ # SCS for msg1, can only be 15 for 30 kHz < 6 GHz, takes precendence over the one derived from prach-ConfigIndex
+ #
+ msg1_SubcarrierSpacing = 1,
+# restrictedSetConfig
+# 0=unrestricted, 1=restricted type A, 2=restricted type B
+ restrictedSetConfig = 0,
+
+ msg3_DeltaPreamble = 1;
+ p0_NominalWithGrant =-90;
+
+# pucch-ConfigCommon setup :
+# pucchGroupHopping
+# 0 = neither, 1= group hopping, 2=sequence hopping
+ pucchGroupHopping = 0;
+ hoppingId = 40;
+ p0_nominal = -90;
+
+ ssb_PositionsInBurst_Bitmap = 3;
+
+# ssb_periodicityServingCell
+# 0 = ms5, 1=ms10, 2=ms20, 3=ms40, 4=ms80, 5=ms160, 6=spare2, 7=spare1
+ ssb_periodicityServingCell = 2;
+
+# dmrs_TypeA_position
+# 0 = pos2, 1 = pos3
+ dmrs_TypeA_Position = 0;
+
+# subcarrierSpacing
+# 0=kHz15, 1=kHz30, 2=kHz60, 3=kHz120
+ subcarrierSpacing = 1;
+
+
+ #tdd-UL-DL-ConfigurationCommon
+# subcarrierSpacing
+# 0=kHz15, 1=kHz30, 2=kHz60, 3=kHz120
+ referenceSubcarrierSpacing = 1;
+ # pattern1
+ # dl_UL_TransmissionPeriodicity
+ # 0=ms0p5, 1=ms0p625, 2=ms1, 3=ms1p25, 4=ms2, 5=ms2p5, 6=ms5, 7=ms10
+ dl_UL_TransmissionPeriodicity = 6;
+ nrofDownlinkSlots = 7;
+ nrofDownlinkSymbols = 6;
+ nrofUplinkSlots = 2;
+ nrofUplinkSymbols = 4;
+
+ ssPBCH_BlockPower = -25;
+ }
+
+ );
+
+ # ------- SCTP definitions
+ SCTP :
+ {
+ # Number of streams to use in input/output
+ SCTP_INSTREAMS = 2;
+ SCTP_OUTSTREAMS = 2;
+ };
+ }
+);
+
+MACRLCs = ({
+ num_cc = 1;
+ tr_s_preference = "local_L1";
+ tr_n_preference = "f1";
+ local_n_if_name = "lo";
+ local_n_address = "127.0.0.5";
+ remote_n_address = "127.0.0.3";
+ local_n_portc = 500;
+ local_n_portd = 2152;
+ remote_n_portc = 501;
+ remote_n_portd = 2152;
+ pusch_FailureThres = 1000;
+});
+
+L1s = ({
+ num_cc = 1;
+ tr_n_preference = "local_mac";
+ prach_dtx_threshold = 200;
+ pucch0_dtx_threshold = 150;
+ ofdm_offset_divisor = 8; #set this to UINT_MAX for offset 0
+});
+
+RUs = ({
+ local_rf = "yes"
+ nb_tx = 1
+ nb_rx = 1
+ att_tx = 0
+ att_rx = 0;
+ bands = [78];
+ max_pdschReferenceSignalPower = -27;
+ max_rxgain = 114;
+ eNB_instances = [0];
+ clock_src = "internal";
+});
+
+rfsimulator: {
+ serveraddr = "server";
+ serverport = 4043;
+ options = (); #("saviq"); or/and "chanmod"
+ modelname = "AWGN";
+ IQfile = "/tmp/rfsimulator.iqs"
+}
+
+log_config: {
+ global_log_level = "info";
+ hw_log_level = "info";
+ nr_phy_log_level = "info";
+ nr_mac_log_level = "info";
+ rlc_log_level = "info";
+ pdcp_log_level = "info";
+ rrc_log_level = "info";
+ f1ap_log_level = "info";
+ ngap_log_level = "debug";
+};