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. + +![F1 Handover setup](./RRC/ho.png) + +# 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"; +};