From d73a9148de07b03f368c1c000690b2be2a7d4fb3 Mon Sep 17 00:00:00 2001 From: Guido Casati Date: Wed, 12 Nov 2025 17:32:04 +0100 Subject: [PATCH 001/100] Refactor: add common helper functions to construct 5G-S-TMSI * 5G-S-TMSI construction is defined in 3GPP TS 23.003 and relevant at both gNB and UE side * Add helper functions to be later used in NG Paging * Adopt in NAS UE --- common/utils/nr/nr_common.c | 32 ++++++++++++++++++++++++++++++++ common/utils/nr/nr_common.h | 6 ++++++ openair3/NAS/NR_UE/nr_nas_msg.c | 3 ++- 3 files changed, 40 insertions(+), 1 deletion(-) diff --git a/common/utils/nr/nr_common.c b/common/utils/nr/nr_common.c index 96b8681cff..cfbb09e939 100644 --- a/common/utils/nr/nr_common.c +++ b/common/utils/nr/nr_common.c @@ -1487,3 +1487,35 @@ float get_beta_dmrs(int num_cdm_groups_no_data, bool is_type2) } return beta_dmrs_pusch; } + +/** @brief Construct full 5G-S-TMSI from 5G-S-TMSI components + * @param amf_set_id AMF Set ID (10 bits) + * @param amf_pointer AMF Pointer (6 bits) + * @param m_tmsi 5G-TMSI (32 bits) + * @return Full 5G-S-TMSI (48 bits) + * @note The 5G-S-TMSI is constructed as a 48-bit value: + * - Bits 38-47: AMF Set ID (10 bits) + * - Bits 32-37: AMF Pointer (6 bits) + * - Bits 0-31: 5G-TMSI (32 bits) + * @ref 3GPP TS 23.003 */ +uint64_t nr_construct_5g_s_tmsi(uint16_t amf_set_id, uint8_t amf_pointer, uint32_t m_tmsi) +{ + // Construct full 5G-S-TMSI: <5G-TMSI (32 bits)> + return ((uint64_t)amf_set_id << 38) | ((uint64_t)amf_pointer << 32) | m_tmsi; +} + +/** @brief Construct 5G-S-TMSI-Part1 from 5G-S-TMSI components + * @param amf_set_id AMF Set ID (10 bits) + * @param amf_pointer AMF Pointer (6 bits) + * @param m_tmsi 5G-TMSI (32 bits) + * @return 5G-S-TMSI-Part1 (rightmost 39 bits of the full 5G-S-TMSI) + * @note 5G-S-TMSI-Part1 is the rightmost 39 bits of the full 5G-S-TMSI: + * - Bits 32-37: AMF Pointer (6 bits) + * - Bits 0-31: 5G-TMSI (32 bits) + * @ref 3GPP TS 23.003 */ +uint64_t nr_construct_5g_s_tmsi_part1(uint16_t amf_set_id, uint8_t amf_pointer, uint32_t m_tmsi) +{ + // Construct full 5G-S-TMSI and extract Part1: rightmost 39 bits + uint64_t full_s_tmsi = nr_construct_5g_s_tmsi(amf_set_id, amf_pointer, m_tmsi); + return full_s_tmsi & ((1ULL << 39) - 1); +} diff --git a/common/utils/nr/nr_common.h b/common/utils/nr/nr_common.h index e62d66658c..42ea232140 100644 --- a/common/utils/nr/nr_common.h +++ b/common/utils/nr/nr_common.h @@ -357,6 +357,12 @@ frequency_range_t get_freq_range_from_band(uint16_t band); */ float get_beta_dmrs(int num_cdm_groups_no_data, bool is_type2); +/** @brief Construct full 5G-S-TMSI from 5G-S-TMSI components */ +uint64_t nr_construct_5g_s_tmsi(uint16_t amf_set_id, uint8_t amf_pointer, uint32_t m_tmsi); + +/** @brief Construct 5G-S-TMSI-Part1 from 5G-S-TMSI components */ +uint64_t nr_construct_5g_s_tmsi_part1(uint16_t amf_set_id, uint8_t amf_pointer, uint32_t m_tmsi); + #define CEILIDIV(a,b) ((a+b-1)/b) #define ROUNDIDIV(a,b) (((a<<1)+b)/(b<<1)) #define BOUNDED_EVAL(a, b, c) (min(c, max(a, b))) diff --git a/openair3/NAS/NR_UE/nr_nas_msg.c b/openair3/NAS/NR_UE/nr_nas_msg.c index 8f4f9bd280..64f9feef54 100644 --- a/openair3/NAS/NR_UE/nr_nas_msg.c +++ b/openair3/NAS/NR_UE/nr_nas_msg.c @@ -38,6 +38,7 @@ #include #include #include "common/utils/ds/byte_array.h" +#include "common/utils/nr/nr_common.h" #include "AuthenticationResponseParameter.h" #include "FGCNasMessageContainer.h" #include "FGSDeregistrationRequestUEOriginating.h" @@ -1787,7 +1788,7 @@ static void send_nas_5gmm_ind(instance_t instance, const Guti5GSMobileIdentity_t MessageDef *msg = itti_alloc_new_message(TASK_NAS_NRUE, 0, NAS_5GMM_IND); nas_5gmm_ind_t *ind = &NAS_5GMM_IND(msg); LOG_I(NR_RRC, "5G-GUTI: AMF pointer %u, AMF Set ID %u, 5G-TMSI %u \n", guti->amfpointer, guti->amfsetid, guti->tmsi); - ind->fiveG_STMSI = ((uint64_t)guti->amfsetid << 38) | ((uint64_t)guti->amfpointer << 32) | guti->tmsi; + ind->fiveG_STMSI = nr_construct_5g_s_tmsi(guti->amfsetid, guti->amfpointer, guti->tmsi); itti_send_msg_to_task(TASK_RRC_NRUE, instance, msg); } From 5d657c801c87b700d1a711fe3c77760d110236ba Mon Sep 17 00:00:00 2001 From: Guido Casati Date: Mon, 20 Oct 2025 12:31:52 +0200 Subject: [PATCH 002/100] Refactor (F1AP): align cell lists definitions with TS 38.473 * Commit applies to Setup Request, CU/DU Configuration Update * Set standard compliant max number of cells per DU * Allocate large lists on the heap to prevent stack overflow due to the large size * Update relevant free functions * Also, fix bug in eq_f1ap_cu_configuration_update Also, improve comments in struct definition. --- openair2/COMMON/f1ap_messages_types.h | 66 ++++---- openair2/F1AP/lib/f1ap_interface_management.c | 155 ++++++++++++------ openair2/F1AP/lib/f1ap_interface_management.h | 1 + openair2/F1AP/tests/f1ap_lib_test.c | 127 ++++++++------ openair2/GNB_APP/gnb_config.c | 1 + openair2/LAYER2/NR_MAC_gNB/config.c | 15 +- openair2/RRC/NR/rrc_gNB_du.c | 3 +- 7 files changed, 219 insertions(+), 149 deletions(-) diff --git a/openair2/COMMON/f1ap_messages_types.h b/openair2/COMMON/f1ap_messages_types.h index 0cd090ebc7..c891a44adc 100644 --- a/openair2/COMMON/f1ap_messages_types.h +++ b/openair2/COMMON/f1ap_messages_types.h @@ -74,12 +74,11 @@ */ #define F1AP_TRANSPORT_LAYER_ADDRESS_SIZE (160 / 8) - -// Note this should be 512 from maxval in 38.473 -#define F1AP_MAX_NB_CELLS 2 +/* F1 Setup Request (9.3.1.4 of 3GPP TS 38.473) */ +#define F1AP_MAX_NB_CELLS 512 #define F1AP_MAX_NO_OF_TNL_ASSOCIATIONS 32 -#define F1AP_MAX_NO_UE_ID 1024 +#define F1AP_MAX_NO_UE_ID 65536 /* 9.3.1.42 of 3GPP TS 38.473 - gNB-CU System Information */ #define F1AP_MAX_NO_SIB_TYPES 32 @@ -122,54 +121,53 @@ typedef struct f1ap_tdd_info_t { } f1ap_tdd_info_t; typedef struct f1ap_served_cell_info_t { - // NR CGI + // NR CGI (Mandatory) plmn_id_t plmn; uint64_t nr_cellid; // NR Global Cell Id - - // NR Physical Cell Ids + // NR PCI (Mandatory) uint16_t nr_pci; - - /* Tracking area code */ + // 5GS TAC (Optional) uint32_t *tac; - - // Number of slice support items (max 16, could be increased to as much as 1024) + // TAI Slice Support List (Optional) uint16_t num_ssi; nssai_t nssai[MAX_NUM_SLICES]; - + // NR-Mode-Info (Mandatory) f1ap_mode_t mode; union { f1ap_fdd_info_t fdd; f1ap_tdd_info_t tdd; }; - + // Measurement Timing Configuration (Mandatory) uint8_t *measurement_timing_config; int measurement_timing_config_len; } f1ap_served_cell_info_t; typedef struct f1ap_gnb_du_system_info_t { + // MIB message (Mandatory) uint8_t *mib; int mib_length; + // SIB1 message (Mandatory) uint8_t *sib1; int sib1_length; } f1ap_gnb_du_system_info_t; typedef struct f1ap_setup_req_s { - /// ulong transaction id + // Transaction ID (Mandatory) uint64_t transaction_id; - - // F1_Setup_Req payload + // gNB-DU ID (Mandatory) uint64_t gNB_DU_id; + // gNB-DU Name (Optional) char *gNB_DU_name; - - /// rrc version + // gNB-DU RRC version (Mandatory) uint8_t rrc_ver[3]; - - /// number of DU cells available - uint16_t num_cells_available; //0< num_cells_available <= 512; + // gNB-DU Served Cells List (0..1) + uint16_t num_cells_available; // (1..512) struct { + // Served Cell Information (Mandatory) f1ap_served_cell_info_t info; + // gNB-DU System Information (Optional) f1ap_gnb_du_system_info_t *sys_info; - } cell[F1AP_MAX_NB_CELLS]; + } *cell; } f1ap_setup_req_t; typedef struct f1ap_du_register_req_t { @@ -203,8 +201,8 @@ typedef struct f1ap_setup_resp_s { /// string holding gNB_CU_name char *gNB_CU_name; /// number of DU cells to activate - uint16_t num_cells_to_activate; //0< num_cells_to_activate <= 512; - served_cells_to_activate_t cells_to_activate[F1AP_MAX_NB_CELLS]; + uint16_t num_cells_to_activate; + served_cells_to_activate_t *cells_to_activate; /// rrc version uint8_t rrc_ver[3]; @@ -215,8 +213,8 @@ typedef struct f1ap_gnb_cu_configuration_update_s { /// Transaction ID uint64_t transaction_id; /// number of DU cells to activate - uint16_t num_cells_to_activate; //0< num_cells_to_activate/mod <= 512; - served_cells_to_activate_t cells_to_activate[F1AP_MAX_NB_CELLS]; + uint16_t num_cells_to_activate; + served_cells_to_activate_t *cells_to_activate; } f1ap_gnb_cu_configuration_update_t; typedef struct f1ap_setup_failure_s { @@ -236,7 +234,7 @@ typedef struct f1ap_gnb_cu_configuration_update_acknowledge_s { plmn_id_t plmn; uint64_t nr_cellid; uint16_t cause; - } cells_failed_to_be_activated[F1AP_MAX_NB_CELLS]; + } * cells_failed_to_be_activated; int have_criticality; uint16_t criticality_diagnostics; // gNB-CU TNL Association Setup List @@ -245,14 +243,14 @@ typedef struct f1ap_gnb_cu_configuration_update_acknowledge_s { // gNB-CU TNL Association Failed to Setup List uint16_t noofTNLAssociations_failed; f1ap_cp_tnl_t tnlAssociations_failed[F1AP_MAX_NO_OF_TNL_ASSOCIATIONS]; - // Dedicated SI Delivery Needed UE List + // Dedicated SI Delivery Needed UE List (max 65536 UE IDs) uint16_t noofDedicatedSIDeliveryNeededUEs; struct { uint32_t gNB_CU_ue_id; // NR CGI plmn_id_t ue_plmn; uint64_t ue_nr_cellid; - } dedicatedSIDeliveryNeededUEs[F1AP_MAX_NO_UE_ID]; + } * dedicatedSIDeliveryNeededUEs; } f1ap_gnb_cu_configuration_update_acknowledge_t; typedef struct f1ap_gnb_cu_configuration_update_failure_s { @@ -279,7 +277,7 @@ typedef struct f1ap_gnb_du_configuration_update_s { struct { f1ap_served_cell_info_t info; f1ap_gnb_du_system_info_t *sys_info; - } cell_to_add[F1AP_MAX_NB_CELLS]; + } * cell_to_add; /// int cells_to_modify uint16_t num_cells_to_modify; @@ -288,7 +286,7 @@ typedef struct f1ap_gnb_du_configuration_update_s { uint64_t old_nr_cellid; // NR Global Cell Id f1ap_served_cell_info_t info; f1ap_gnb_du_system_info_t *sys_info; - } cell_to_modify[F1AP_MAX_NB_CELLS]; + } * cell_to_modify; /// int cells_to_delete uint16_t num_cells_to_delete; @@ -296,9 +294,9 @@ typedef struct f1ap_gnb_du_configuration_update_s { // NR CGI plmn_id_t plmn; uint64_t nr_cellid; // NR Global Cell Id - } cell_to_delete[F1AP_MAX_NB_CELLS]; + } * cell_to_delete; - f1ap_cell_status_t status[F1AP_MAX_NB_CELLS]; + f1ap_cell_status_t *status; int num_status; /// gNB-DU unique ID, at least within a gNB-CU (0 .. 2^36 - 1) @@ -310,7 +308,7 @@ typedef struct f1ap_gnb_du_configuration_update_acknowledge_s { uint64_t transaction_id; /// number of DU cells to activate uint16_t num_cells_to_activate; // 0< num_cells_to_activate <= 512; - served_cells_to_activate_t cells_to_activate[F1AP_MAX_NB_CELLS]; + served_cells_to_activate_t *cells_to_activate; } f1ap_gnb_du_configuration_update_acknowledge_t; typedef struct f1ap_gnb_du_configuration_update_failure_s { diff --git a/openair2/F1AP/lib/f1ap_interface_management.c b/openair2/F1AP/lib/f1ap_interface_management.c index 2253493832..38de5e4dcc 100644 --- a/openair2/F1AP/lib/f1ap_interface_management.c +++ b/openair2/F1AP/lib/f1ap_interface_management.c @@ -842,8 +842,9 @@ bool decode_f1ap_setup_request(const F1AP_F1AP_PDU_t *pdu, f1ap_setup_req_t *out /* GNB_DU_Served_Cells_List */ out->num_cells_available = ie->value.choice.GNB_DU_Served_Cells_List.list.count; AssertError(out->num_cells_available > 0, return false, "at least 1 cell must be present"); + out->cell = calloc_or_fail(out->num_cells_available, sizeof(*out->cell)); /* Loop over gNB-DU Served Cells Items */ - for (int i = 0; i < out->num_cells_available; i++) { + for (int i = 0; i < out->num_cells_available && i < F1AP_MAX_NB_CELLS; i++) { F1AP_GNB_DU_Served_Cells_Item_t *served_cells_item = &(((F1AP_GNB_DU_Served_Cells_ItemIEs_t *)ie->value.choice.GNB_DU_Served_Cells_List.list.array[i]) ->value.choice.GNB_DU_Served_Cells_Item); @@ -952,7 +953,9 @@ f1ap_setup_req_t cp_f1ap_setup_request(const f1ap_setup_req_t *msg) cp.transaction_id = msg->transaction_id; /* num_cells_available */ cp.num_cells_available = msg->num_cells_available; - for (int n = 0; n < msg->num_cells_available; n++) { + if (cp.num_cells_available > 0) + cp.cell = calloc_or_fail(cp.num_cells_available, sizeof(*cp.cell)); + for (int n = 0; n < msg->num_cells_available && n < F1AP_MAX_NB_CELLS; n++) { /* cell.info */ cp.cell[n].info = copy_f1ap_served_cell_info(&msg->cell[n].info); /* cell.sys_info */ @@ -972,7 +975,7 @@ bool eq_f1ap_setup_request(const f1ap_setup_req_t *a, const f1ap_setup_req_t *b) _F1_EQ_CHECK_STR(a->gNB_DU_name, b->gNB_DU_name); _F1_EQ_CHECK_LONG(a->transaction_id, b->transaction_id); _F1_EQ_CHECK_INT(a->num_cells_available, b->num_cells_available); - for (int i = 0; i < a->num_cells_available; i++) { + for (int i = 0; i < a->num_cells_available && i < F1AP_MAX_NB_CELLS; i++) { if (!eq_f1ap_cell_info(&a->cell[i].info, &b->cell[i].info)) return false; if (!eq_f1ap_sys_info(a->cell[i].sys_info, b->cell[i].sys_info)) @@ -992,8 +995,11 @@ void free_f1ap_setup_request(const f1ap_setup_req_t *msg) { DevAssert(msg != NULL); free(msg->gNB_DU_name); - for (int i = 0; i < msg->num_cells_available; i++) { - free_f1ap_cell(&msg->cell[i].info, msg->cell[i].sys_info); + if (msg->cell != NULL) { + for (int i = 0; i < msg->num_cells_available; i++) { + free_f1ap_cell(&msg->cell[i].info, msg->cell[i].sys_info); + } + free(msg->cell); } } @@ -1032,15 +1038,16 @@ F1AP_F1AP_PDU_t *encode_f1ap_setup_response(const f1ap_setup_resp_t *msg) OCTET_STRING_fromBuf(&ie2->value.choice.GNB_CU_Name, msg->gNB_CU_name, strlen(msg->gNB_CU_name)); } // Cells to be Activated List (O) - for (int i = 0; i < msg->num_cells_to_activate; i++) { + if (msg->num_cells_to_activate > 0) { + DevAssert(msg->num_cells_to_activate <= F1AP_MAX_NB_CELLS); asn1cSequenceAdd(out->protocolIEs.list, F1AP_F1SetupResponseIEs_t, ie3); ie3->id = F1AP_ProtocolIE_ID_id_Cells_to_be_Activated_List; ie3->criticality = F1AP_Criticality_reject; ie3->value.present = F1AP_F1SetupResponseIEs__value_PR_Cells_to_be_Activated_List; - asn1cSequenceAdd(ie3->value.choice.Cells_to_be_Activated_List.list, - F1AP_Cells_to_be_Activated_List_ItemIEs_t, - cells_to_be_activated_ies); - encode_cells_to_activate(&msg->cells_to_activate[i], cells_to_be_activated_ies); + for (int i = 0; i < msg->num_cells_to_activate; i++) { + asn1cSequenceAdd(ie3->value.choice.Cells_to_be_Activated_List.list, F1AP_Cells_to_be_Activated_List_ItemIEs_t, cells); + encode_cells_to_activate(&msg->cells_to_activate[i], cells); + } } // gNB-CU RRC version (M) asn1cSequenceAdd(out->protocolIEs.list, F1AP_F1SetupResponseIEs_t, ie4); @@ -1131,7 +1138,8 @@ bool decode_f1ap_setup_response(const F1AP_F1AP_PDU_t *pdu, f1ap_setup_resp_t *o out->num_cells_to_activate = Cells_to_be_Activated_List->list.count; // Loop Cells to be Activated List Items (count >= 1) AssertError(out->num_cells_to_activate > 0, return false, "At least 1 cell must be present"); - for (int i = 0; i < out->num_cells_to_activate; i++) { + out->cells_to_activate = calloc_or_fail(out->num_cells_to_activate, sizeof(*out->cells_to_activate)); + for (int i = 0; i < out->num_cells_to_activate && i < F1AP_MAX_NB_CELLS; i++) { const F1AP_Cells_to_be_Activated_List_ItemIEs_t *itemIEs = (F1AP_Cells_to_be_Activated_List_ItemIEs_t *)Cells_to_be_Activated_List->list.array[i]; if (!decode_cells_to_activate(&out->cells_to_activate[i], itemIEs)) @@ -1157,7 +1165,7 @@ bool eq_f1ap_setup_response(const f1ap_setup_resp_t *a, const f1ap_setup_resp_t _F1_EQ_CHECK_INT(a->num_cells_to_activate, b->num_cells_to_activate); _F1_EQ_CHECK_LONG(a->transaction_id, b->transaction_id); if (a->num_cells_to_activate) { - for (int i = 0; i < a->num_cells_to_activate; i++) { + for (int i = 0; i < a->num_cells_to_activate && i < F1AP_MAX_NB_CELLS; i++) { const served_cells_to_activate_t *a_cell = &a->cells_to_activate[i]; const served_cells_to_activate_t *b_cell = &b->cells_to_activate[i]; _F1_EQ_CHECK_LONG(a_cell->nr_cellid, b_cell->nr_cellid); @@ -1197,7 +1205,8 @@ f1ap_setup_resp_t cp_f1ap_setup_response(const f1ap_setup_resp_t *msg) cp.transaction_id = msg->transaction_id; /* num_cells_available */ cp.num_cells_to_activate = msg->num_cells_to_activate; - for (int n = 0; n < msg->num_cells_to_activate; n++) { + cp.cells_to_activate = calloc_or_fail(cp.num_cells_to_activate, sizeof(*cp.cells_to_activate)); + for (int n = 0; n < msg->num_cells_to_activate && n < F1AP_MAX_NB_CELLS; n++) { /* cell.info */ served_cells_to_activate_t *cp_cell = &cp.cells_to_activate[n]; const served_cells_to_activate_t *msg_cell = &msg->cells_to_activate[n]; @@ -1230,6 +1239,7 @@ void free_f1ap_setup_response(const f1ap_setup_resp_t *msg) for (int j = 0; j < msg->cells_to_activate[i].num_SI; j++) if (msg->cells_to_activate[i].SI_msg[j].SI_container_length > 0) free(msg->cells_to_activate[i].SI_msg[j].SI_container); + free(msg->cells_to_activate); } /* ==================================== @@ -1373,6 +1383,7 @@ F1AP_F1AP_PDU_t *encode_f1ap_du_configuration_update(const f1ap_gnb_du_configura /* mandatory */ /* c2. Served_Cells_To_Add */ if (msg->num_cells_to_add > 0) { + DevAssert(msg->num_cells_to_add <= F1AP_MAX_NB_CELLS); asn1cSequenceAdd(out->protocolIEs.list, F1AP_GNBDUConfigurationUpdateIEs_t, ie2); ie2->id = F1AP_ProtocolIE_ID_id_Served_Cells_To_Add_List; ie2->criticality = F1AP_Criticality_reject; @@ -1397,6 +1408,7 @@ F1AP_F1AP_PDU_t *encode_f1ap_du_configuration_update(const f1ap_gnb_du_configura /* mandatory */ /* c3. Served_Cells_To_Modify */ if (msg->num_cells_to_modify > 0) { + DevAssert(msg->num_cells_to_modify <= F1AP_MAX_NB_CELLS); asn1cSequenceAdd(out->protocolIEs.list, F1AP_GNBDUConfigurationUpdateIEs_t, ie3); ie3->id = F1AP_ProtocolIE_ID_id_Served_Cells_To_Modify_List; ie3->criticality = F1AP_Criticality_reject; @@ -1426,6 +1438,7 @@ F1AP_F1AP_PDU_t *encode_f1ap_du_configuration_update(const f1ap_gnb_du_configura /* mandatory */ /* c4. Served_Cells_To_Delete */ if (msg->num_cells_to_delete > 0) { + DevAssert(msg->num_cells_to_delete <= F1AP_MAX_NB_CELLS); asn1cSequenceAdd(out->protocolIEs.list, F1AP_GNBDUConfigurationUpdateIEs_t, ie4); ie4->id = F1AP_ProtocolIE_ID_id_Served_Cells_To_Delete_List; ie4->criticality = F1AP_Criticality_reject; @@ -1447,6 +1460,7 @@ F1AP_F1AP_PDU_t *encode_f1ap_du_configuration_update(const f1ap_gnb_du_configura } if (msg->num_status > 0) { + DevAssert(msg->num_status <= F1AP_MAX_NB_CELLS); asn1cSequenceAdd(out->protocolIEs.list, F1AP_GNBDUConfigurationUpdateIEs_t, ie4); ie4->id = F1AP_ProtocolIE_ID_id_Cells_Status_List; ie4->criticality = F1AP_Criticality_reject; @@ -1507,7 +1521,8 @@ bool decode_f1ap_du_configuration_update(const F1AP_F1AP_PDU_t *pdu, f1ap_gnb_du /* Served Cells To Add List */ out->num_cells_to_add = ie->value.choice.Served_Cells_To_Add_List.list.count; AssertError(out->num_cells_to_add > 0, return false, "at least 1 cell to add shall to be present"); - for (int i = 0; i < out->num_cells_to_add; i++) { + out->cell_to_add = calloc_or_fail(out->num_cells_to_add, sizeof(*out->cell_to_add)); + for (int i = 0; i < out->num_cells_to_add && i < F1AP_MAX_NB_CELLS; i++) { F1AP_Served_Cells_To_Add_Item_t *served_cells_item = &((F1AP_Served_Cells_To_Add_ItemIEs_t *)ie->value.choice.Served_Cells_To_Add_List.list.array[i]) ->value.choice.Served_Cells_To_Add_Item; @@ -1525,7 +1540,8 @@ bool decode_f1ap_du_configuration_update(const F1AP_F1AP_PDU_t *pdu, f1ap_gnb_du /* Served Cells To Modify List (O) */ out->num_cells_to_modify = ie->value.choice.Served_Cells_To_Modify_List.list.count; AssertError(out->num_cells_to_modify > 0, return false, "at least 1 cell to modify shall to be present"); - for (int i = 0; i < out->num_cells_to_modify; i++) { + out->cell_to_modify = calloc_or_fail(out->num_cells_to_modify, sizeof(*out->cell_to_modify)); + for (int i = 0; i < out->num_cells_to_modify && i < F1AP_MAX_NB_CELLS; i++) { /* Served Cells To Modify List item (count >= 1) */ F1AP_Served_Cells_To_Modify_Item_t *served_cells_item = &((F1AP_Served_Cells_To_Modify_ItemIEs_t *)ie->value.choice.Served_Cells_To_Modify_List.list.array[i]) @@ -1550,7 +1566,8 @@ bool decode_f1ap_du_configuration_update(const F1AP_F1AP_PDU_t *pdu, f1ap_gnb_du /* Served Cells To Delete List */ out->num_cells_to_delete = ie->value.choice.Served_Cells_To_Delete_List.list.count; AssertError(out->num_cells_to_delete > 0, return false, "at least 1 cell to delete shall to be present"); - for (int i = 0; i < out->num_cells_to_delete; i++) { + out->cell_to_delete = calloc_or_fail(out->num_cells_to_delete, sizeof(*out->cell_to_delete)); + for (int i = 0; i < out->num_cells_to_delete && i < F1AP_MAX_NB_CELLS; i++) { F1AP_Served_Cells_To_Delete_Item_t *served_cells_item = &((F1AP_Served_Cells_To_Delete_ItemIEs_t *)ie->value.choice.Served_Cells_To_Delete_List.list.array[i]) ->value.choice.Served_Cells_To_Delete_Item; @@ -1565,7 +1582,8 @@ bool decode_f1ap_du_configuration_update(const F1AP_F1AP_PDU_t *pdu, f1ap_gnb_du case F1AP_ProtocolIE_ID_id_Cells_Status_List: /* Cells Status List (O) */ out->num_status = ie->value.choice.Cells_Status_List.list.count; - for (int i = 0; i < out->num_status; ++i) { + out->status = calloc_or_fail(out->num_status, sizeof(*out->status)); + for (int i = 0; i < out->num_status && i < F1AP_MAX_NB_CELLS; ++i) { const F1AP_Cells_Status_ItemIEs_t *csi_ie = (F1AP_Cells_Status_ItemIEs_t *)ie->value.choice.Cells_Status_List.list.array[i]; AssertError(csi_ie->value.present == F1AP_Cells_Status_ItemIEs__value_PR_Cells_Status_Item, @@ -1604,11 +1622,14 @@ void free_f1ap_du_configuration_update(const f1ap_gnb_du_configuration_update_t for (int i = 0; i < msg->num_cells_to_add; i++) { free_f1ap_cell(&msg->cell_to_add[i].info, msg->cell_to_add[i].sys_info); } + free(msg->cell_to_add); for (int i = 0; i < msg->num_cells_to_modify; i++) { free_f1ap_cell(&msg->cell_to_modify[i].info, msg->cell_to_modify[i].sys_info); } + free(msg->cell_to_modify); + free(msg->cell_to_delete); + free(msg->status); } - /** * @brief F1 gNB-DU Configuration Update check */ @@ -1621,7 +1642,7 @@ bool eq_f1ap_du_configuration_update(const f1ap_gnb_du_configuration_update_t *a _F1_EQ_CHECK_LONG(a->transaction_id, b->transaction_id); /* to add */ _F1_EQ_CHECK_INT(a->num_cells_to_add, b->num_cells_to_add); - for (int i = 0; i < a->num_cells_to_add; i++) { + for (int i = 0; i < a->num_cells_to_add && i < F1AP_MAX_NB_CELLS; i++) { if (!eq_f1ap_cell_info(&a->cell_to_add[i].info, &b->cell_to_add[i].info)) return false; if (!eq_f1ap_sys_info(a->cell_to_add[i].sys_info, b->cell_to_add[i].sys_info)) @@ -1629,14 +1650,14 @@ bool eq_f1ap_du_configuration_update(const f1ap_gnb_du_configuration_update_t *a } /* to delete */ _F1_EQ_CHECK_INT(a->num_cells_to_delete, b->num_cells_to_delete); - for (int i = 0; i < a->num_cells_to_delete; i++) { + for (int i = 0; i < a->num_cells_to_delete && i < F1AP_MAX_NB_CELLS; i++) { _F1_EQ_CHECK_LONG(a->cell_to_delete[i].nr_cellid, b->cell_to_delete[i].nr_cellid); if (!eq_f1ap_plmn(&a->cell_to_delete[i].plmn, &b->cell_to_delete[i].plmn)) return false; } /* to modify */ _F1_EQ_CHECK_INT(a->num_cells_to_modify, b->num_cells_to_modify); - for (int i = 0; i < a->num_cells_to_modify; i++) { + for (int i = 0; i < a->num_cells_to_modify && i < F1AP_MAX_NB_CELLS; i++) { if (!eq_f1ap_plmn(&a->cell_to_modify[i].old_plmn, &b->cell_to_modify[i].old_plmn)) return false; _F1_EQ_CHECK_LONG(a->cell_to_modify[i].old_nr_cellid, b->cell_to_modify[i].old_nr_cellid); @@ -1647,7 +1668,7 @@ bool eq_f1ap_du_configuration_update(const f1ap_gnb_du_configuration_update_t *a } /* cell status */ _F1_EQ_CHECK_INT(a->num_status, b->num_status); - for (int i = 0; i < a->num_status; ++i) { + for (int i = 0; i < a->num_status && i < F1AP_MAX_NB_CELLS; ++i) { const f1ap_cell_status_t *astatus = &a->status[i]; const f1ap_cell_status_t *bstatus = &b->status[i]; if (!eq_f1ap_plmn(&astatus->plmn, &bstatus->plmn)) @@ -1673,19 +1694,22 @@ f1ap_gnb_du_configuration_update_t cp_f1ap_du_configuration_update(const f1ap_gn cp.transaction_id = msg->transaction_id; /* to add */ cp.num_cells_to_add = msg->num_cells_to_add; - for (int i = 0; i < cp.num_cells_to_add; ++i) { + cp.cell_to_add = calloc_or_fail(cp.num_cells_to_add, sizeof(*cp.cell_to_add)); + for (int i = 0; i < cp.num_cells_to_add && i < F1AP_MAX_NB_CELLS; ++i) { cp.cell_to_add[i].info = copy_f1ap_served_cell_info(&msg->cell_to_add[i].info); cp.cell_to_add[i].sys_info = copy_f1ap_gnb_du_system_info(msg->cell_to_add[i].sys_info); } /* to delete */ cp.num_cells_to_delete = msg->num_cells_to_delete; - for (int i = 0; i < cp.num_cells_to_delete; i++) { + cp.cell_to_delete = calloc_or_fail(cp.num_cells_to_delete, sizeof(*cp.cell_to_delete)); + for (int i = 0; i < cp.num_cells_to_delete && i < F1AP_MAX_NB_CELLS; i++) { cp.cell_to_delete[i].nr_cellid = msg->cell_to_delete[i].nr_cellid; cp.cell_to_delete[i].plmn = msg->cell_to_delete[i].plmn; } /* to modify */ cp.num_cells_to_modify = msg->num_cells_to_modify; - for (int i = 0; i < cp.num_cells_to_modify; i++) { + cp.cell_to_modify = calloc_or_fail(cp.num_cells_to_modify, sizeof(*cp.cell_to_modify)); + for (int i = 0; i < cp.num_cells_to_modify && i < F1AP_MAX_NB_CELLS; i++) { cp.cell_to_modify[i].old_plmn = msg->cell_to_modify[i].old_plmn; cp.cell_to_modify[i].old_nr_cellid = msg->cell_to_modify[i].old_nr_cellid; cp.cell_to_modify[i].info = copy_f1ap_served_cell_info(&msg->cell_to_modify[i].info); @@ -1693,7 +1717,8 @@ f1ap_gnb_du_configuration_update_t cp_f1ap_du_configuration_update(const f1ap_gn } /* cell status */ cp.num_status = msg->num_status; - for (int i = 0; i < cp.num_status; ++i) + cp.status = calloc_or_fail(cp.num_status, sizeof(*cp.status)); + for (int i = 0; i < cp.num_status && i < F1AP_MAX_NB_CELLS; ++i) cp.status[i] = msg->status[i]; return cp; } @@ -1725,15 +1750,16 @@ F1AP_F1AP_PDU_t *encode_f1ap_cu_configuration_update(const f1ap_gnb_cu_configura ieC1->value.choice.TransactionID = msg->transaction_id; /* optional c2. Cells_to_be_Activated_List (O) */ - for (int i = 0; i < msg->num_cells_to_activate; i++) { + if (msg->num_cells_to_activate > 0) { asn1cSequenceAdd(cfgUpdate->protocolIEs.list, F1AP_GNBCUConfigurationUpdateIEs_t, ieC3); ieC3->id = F1AP_ProtocolIE_ID_id_Cells_to_be_Activated_List; ieC3->criticality = F1AP_Criticality_reject; ieC3->value.present = F1AP_GNBCUConfigurationUpdateIEs__value_PR_Cells_to_be_Activated_List; - asn1cSequenceAdd(ieC3->value.choice.Cells_to_be_Activated_List.list, - F1AP_Cells_to_be_Activated_List_ItemIEs_t, - cells_to_be_activated_ies); - encode_cells_to_activate(&msg->cells_to_activate[i], cells_to_be_activated_ies); + DevAssert(msg->num_cells_to_activate <= F1AP_MAX_NB_CELLS); + for (int i = 0; i < msg->num_cells_to_activate; i++) { + asn1cSequenceAdd(ieC3->value.choice.Cells_to_be_Activated_List.list, F1AP_Cells_to_be_Activated_List_ItemIEs_t, cells); + encode_cells_to_activate(&msg->cells_to_activate[i], cells); + } } return pdu; } @@ -1768,7 +1794,8 @@ bool decode_f1ap_cu_configuration_update(const F1AP_F1AP_PDU_t *pdu, f1ap_gnb_cu F1AP_Cells_to_be_Activated_List_t *Cells_to_be_Activated_List = &ie->value.choice.Cells_to_be_Activated_List; out->num_cells_to_activate = Cells_to_be_Activated_List->list.count; AssertError(out->num_cells_to_activate > 0, return false, "At least 1 cell to activate must be present"); - for (int i = 0; i < out->num_cells_to_activate; i++) { + out->cells_to_activate = calloc_or_fail(out->num_cells_to_activate, sizeof(*out->cells_to_activate)); + for (int i = 0; i < out->num_cells_to_activate && i < F1AP_MAX_NB_CELLS; i++) { if (!decode_cells_to_activate(&out->cells_to_activate[i], (F1AP_Cells_to_be_Activated_List_ItemIEs_t *)Cells_to_be_Activated_List->list.array[i])) return false; @@ -1786,8 +1813,9 @@ bool decode_f1ap_cu_configuration_update(const F1AP_F1AP_PDU_t *pdu, f1ap_gnb_cu void free_f1ap_cu_configuration_update(const f1ap_gnb_cu_configuration_update_t *msg) { for (int i = 0; i < msg->num_cells_to_activate; i++) - for (int j = 0; j < msg->cells_to_activate[j].num_SI; j++) + for (int j = 0; j < msg->cells_to_activate[i].num_SI; j++) free(msg->cells_to_activate[i].SI_msg[j].SI_container); + free(msg->cells_to_activate); } /** @@ -1798,16 +1826,18 @@ bool eq_f1ap_cu_configuration_update(const f1ap_gnb_cu_configuration_update_t *a _F1_EQ_CHECK_LONG(a->transaction_id, b->transaction_id); /* to activate */ _F1_EQ_CHECK_INT(a->num_cells_to_activate, b->num_cells_to_activate); - for (int i = 0; i < a->num_cells_to_activate; i++) { + for (int i = 0; i < a->num_cells_to_activate && i < F1AP_MAX_NB_CELLS; i++) { _F1_EQ_CHECK_LONG(a->cells_to_activate[i].nr_cellid, b->cells_to_activate[i].nr_cellid); _F1_EQ_CHECK_INT(a->cells_to_activate[i].nrpci, b->cells_to_activate[i].nrpci); if (!eq_f1ap_plmn(&a->cells_to_activate[i].plmn, &b->cells_to_activate[i].plmn)) return false; _F1_EQ_CHECK_INT(a->cells_to_activate[i].num_SI, b->cells_to_activate[i].num_SI); for (int s = 0; s < a->cells_to_activate[i].num_SI; s++) { - _F1_EQ_CHECK_INT(*a->cells_to_activate[i].SI_msg[s].SI_container, *a->cells_to_activate[i].SI_msg[s].SI_container); - _F1_EQ_CHECK_INT(a->cells_to_activate[i].SI_msg[s].SI_container_length, a->cells_to_activate[i].SI_msg[s].SI_container_length); - _F1_EQ_CHECK_INT(a->cells_to_activate[i].SI_msg[s].SI_type, a->cells_to_activate[i].SI_msg[s].SI_type); + f1ap_sib_msg_t *a_sib_msg = &a->cells_to_activate[i].SI_msg[s]; + f1ap_sib_msg_t *b_sib_msg = &b->cells_to_activate[i].SI_msg[s]; + _F1_EQ_CHECK_INT(*a_sib_msg->SI_container, *b_sib_msg->SI_container); + _F1_EQ_CHECK_INT(a_sib_msg->SI_container_length, b_sib_msg->SI_container_length); + _F1_EQ_CHECK_INT(a_sib_msg->SI_type, b_sib_msg->SI_type); } } return true; @@ -1822,7 +1852,8 @@ f1ap_gnb_cu_configuration_update_t cp_f1ap_cu_configuration_update(const f1ap_gn /* transaction_id */ cp.transaction_id = msg->transaction_id; cp.num_cells_to_activate = msg->num_cells_to_activate; - for (int i = 0; i < cp.num_cells_to_activate; i++) { + cp.cells_to_activate = calloc_or_fail(cp.num_cells_to_activate, sizeof(*cp.cells_to_activate)); + for (int i = 0; i < cp.num_cells_to_activate && i < F1AP_MAX_NB_CELLS; i++) { cp.cells_to_activate[i] = msg->cells_to_activate[i]; for (int s = 0; s < cp.cells_to_activate[i].num_SI; s++) { cp.cells_to_activate[i].SI_msg[s] = msg->cells_to_activate[i].SI_msg[s]; @@ -1862,6 +1893,7 @@ F1AP_F1AP_PDU_t *encode_f1ap_cu_configuration_update_acknowledge(const f1ap_gnb_ ie1->value.choice.TransactionID = msg->transaction_id; // Cells Failed to be Activated List (0..1) if (msg->num_cells_failed_to_be_activated > 0) { + DevAssert(msg->num_cells_failed_to_be_activated <= F1AP_MAX_NB_CELLS); asn1cSequenceAdd(out->protocolIEs.list, F1AP_GNBCUConfigurationUpdateAcknowledgeIEs_t, ie2); ie2->id = F1AP_ProtocolIE_ID_id_Cells_Failed_to_be_Activated_List; ie2->criticality = F1AP_Criticality_reject; @@ -1878,9 +1910,8 @@ F1AP_F1AP_PDU_t *encode_f1ap_cu_configuration_update_acknowledge(const f1ap_gnb_ p1->cause.choice.radioNetwork = msg->cells_failed_to_be_activated[i].cause; // NR CGI (M) const plmn_id_t *plmn = &msg->cells_failed_to_be_activated[i].plmn; - MCC_MNC_TO_PLMNID(plmn->mcc, plmn->mnc, plmn->mnc_digit_length, &(p1->nRCGI.pLMN_Identity)); - printf("plmn->mcc %d %d %d %ld \n", - p1->nRCGI.pLMN_Identity.buf[0], p1->nRCGI.pLMN_Identity.buf[1], p1->nRCGI.pLMN_Identity.buf[2], p1->nRCGI.pLMN_Identity.size); + F1AP_PLMN_Identity_t *p2 = &p1->nRCGI.pLMN_Identity; + MCC_MNC_TO_PLMNID(plmn->mcc, plmn->mnc, plmn->mnc_digit_length, p2); NR_CELL_ID_TO_BIT_STRING(msg->cells_failed_to_be_activated[i].nr_cellid, &(p1->nRCGI.nRCellIdentity)); } } @@ -1919,7 +1950,9 @@ bool decode_f1ap_cu_configuration_update_acknowledge(const F1AP_F1AP_PDU_t *pdu, F1AP_GNBCUConfigurationUpdateAcknowledgeIEs__value_PR_Cells_Failed_to_be_Activated_List); F1AP_Cells_Failed_to_be_Activated_List_t *cell_fail_list = &ie->value.choice.Cells_Failed_to_be_Activated_List; out->num_cells_failed_to_be_activated = cell_fail_list->list.count; - for (int j = 0; j < out->num_cells_failed_to_be_activated; j++) { + out->cells_failed_to_be_activated = + calloc_or_fail(out->num_cells_failed_to_be_activated, sizeof(*out->cells_failed_to_be_activated)); + for (int j = 0; j < out->num_cells_failed_to_be_activated && j < F1AP_MAX_NB_CELLS; j++) { const F1AP_Cells_Failed_to_be_Activated_List_ItemIEs_t *itemIE = (F1AP_Cells_Failed_to_be_Activated_List_ItemIEs_t *)cell_fail_list->list.array[j]; const F1AP_Cells_Failed_to_be_Activated_List_Item_t *item = &itemIE->value.choice.Cells_Failed_to_be_Activated_List_Item; @@ -1966,7 +1999,7 @@ bool eq_f1ap_cu_configuration_update_acknowledge(const f1ap_gnb_cu_configuration _F1_EQ_CHECK_LONG(a->transaction_id, b->transaction_id); // number of cells failed to be activated _F1_EQ_CHECK_INT(a->num_cells_failed_to_be_activated, b->num_cells_failed_to_be_activated); - for (int i = 0; i < a->num_cells_failed_to_be_activated; i++) { + for (int i = 0; i < a->num_cells_failed_to_be_activated && i < F1AP_MAX_NB_CELLS; i++) { if (!eq_f1ap_plmn(&a->cells_failed_to_be_activated[i].plmn, &b->cells_failed_to_be_activated[i].plmn)) return false; _F1_EQ_CHECK_LONG(a->cells_failed_to_be_activated[i].nr_cellid, b->cells_failed_to_be_activated[i].nr_cellid); @@ -1974,21 +2007,21 @@ bool eq_f1ap_cu_configuration_update_acknowledge(const f1ap_gnb_cu_configuration } // TNL Associations to setup _F1_EQ_CHECK_INT(a->noofTNLAssociations_to_setup, b->noofTNLAssociations_to_setup); - for (int i = 0; i < a->noofTNLAssociations_to_setup; i++) { + for (int i = 0; i < a->noofTNLAssociations_to_setup && i < F1AP_MAX_NO_OF_TNL_ASSOCIATIONS; i++) { // Explicit comparison of TNL Association fields _F1_EQ_CHECK_INT(a->tnlAssociations_to_setup[i].tl_address, b->tnlAssociations_to_setup[i].tl_address); _F1_EQ_CHECK_INT(a->tnlAssociations_to_setup[i].port, b->tnlAssociations_to_setup[i].port); } // TNL Associations failed to setup _F1_EQ_CHECK_INT(a->noofTNLAssociations_failed, b->noofTNLAssociations_failed); - for (int i = 0; i < a->noofTNLAssociations_failed; i++) { + for (int i = 0; i < a->noofTNLAssociations_failed && i < F1AP_MAX_NO_OF_TNL_ASSOCIATIONS; i++) { // Explicit comparison of TNL Association fields _F1_EQ_CHECK_INT(a->tnlAssociations_failed[i].tl_address, b->tnlAssociations_failed[i].tl_address); _F1_EQ_CHECK_INT(a->tnlAssociations_failed[i].port, b->tnlAssociations_failed[i].port); } // Dedicated SI Delivery Needed UE List _F1_EQ_CHECK_INT(a->noofDedicatedSIDeliveryNeededUEs, b->noofDedicatedSIDeliveryNeededUEs); - for (int i = 0; i < a->noofDedicatedSIDeliveryNeededUEs; i++) { + for (int i = 0; i < a->noofDedicatedSIDeliveryNeededUEs && i < F1AP_MAX_NO_UE_ID; i++) { _F1_EQ_CHECK_INT(a->dedicatedSIDeliveryNeededUEs[i].gNB_CU_ue_id, b->dedicatedSIDeliveryNeededUEs[i].gNB_CU_ue_id); if (!eq_f1ap_plmn(&a->dedicatedSIDeliveryNeededUEs[i].ue_plmn, &b->dedicatedSIDeliveryNeededUEs[i].ue_plmn)) return false; @@ -2008,23 +2041,34 @@ f1ap_gnb_cu_configuration_update_acknowledge_t cp_f1ap_cu_configuration_update_a cp.transaction_id = msg->transaction_id; // number of cells failed to be activated cp.num_cells_failed_to_be_activated = msg->num_cells_failed_to_be_activated; - for (int i = 0; i < cp.num_cells_failed_to_be_activated; i++) + cp.cells_failed_to_be_activated = calloc_or_fail(cp.num_cells_failed_to_be_activated, sizeof(*cp.cells_failed_to_be_activated)); + for (int i = 0; i < cp.num_cells_failed_to_be_activated && i < F1AP_MAX_NB_CELLS; i++) cp.cells_failed_to_be_activated[i] = msg->cells_failed_to_be_activated[i]; // TNL Associations to setup cp.noofTNLAssociations_to_setup = msg->noofTNLAssociations_to_setup; - for (int i = 0; i < cp.noofTNLAssociations_to_setup; i++) + for (int i = 0; i < cp.noofTNLAssociations_to_setup && i < F1AP_MAX_NO_OF_TNL_ASSOCIATIONS; i++) cp.tnlAssociations_to_setup[i] = msg->tnlAssociations_to_setup[i]; // TNL Associations failed to setup cp.noofTNLAssociations_failed = msg->noofTNLAssociations_failed; - for (int i = 0; i < cp.noofTNLAssociations_failed; i++) + for (int i = 0; i < cp.noofTNLAssociations_failed && i < F1AP_MAX_NO_OF_TNL_ASSOCIATIONS; i++) cp.tnlAssociations_failed[i] = msg->tnlAssociations_failed[i]; // Dedicated SI Delivery Needed UE List cp.noofDedicatedSIDeliveryNeededUEs = msg->noofDedicatedSIDeliveryNeededUEs; - for (int i = 0; i < cp.noofDedicatedSIDeliveryNeededUEs; i++) + cp.dedicatedSIDeliveryNeededUEs = calloc_or_fail(cp.noofDedicatedSIDeliveryNeededUEs, sizeof(*cp.dedicatedSIDeliveryNeededUEs)); + for (int i = 0; i < cp.noofDedicatedSIDeliveryNeededUEs && i < F1AP_MAX_NO_UE_ID; i++) cp.dedicatedSIDeliveryNeededUEs[i] = msg->dedicatedSIDeliveryNeededUEs[i]; return cp; } +/** + * @brief F1 gNB-CU Configuration Update Acknowledge memory management + */ +void free_f1ap_cu_configuration_update_acknowledge(const f1ap_gnb_cu_configuration_update_acknowledge_t *msg) +{ + free(msg->cells_failed_to_be_activated); + free(msg->dedicatedSIDeliveryNeededUEs); +} + /* ================================== * F1AP gNB-DU Configuration Ack * ================================== */ @@ -2094,10 +2138,11 @@ bool decode_f1ap_du_configuration_update_acknowledge(const F1AP_F1AP_PDU_t *pdu, _F1_EQ_CHECK_INT(ie->value.present, F1AP_GNBDUConfigurationUpdateAcknowledgeIEs__value_PR_Cells_to_be_Activated_List); F1AP_Cells_to_be_Activated_List_t *list = &ie->value.choice.Cells_to_be_Activated_List; out->num_cells_to_activate = list->list.count; - for (int j = 0; j < out->num_cells_to_activate; j++) { + out->cells_to_activate = calloc_or_fail(out->num_cells_to_activate, sizeof(*out->cells_to_activate)); + for (int j = 0; j < out->num_cells_to_activate && j < F1AP_MAX_NB_CELLS; j++) { const F1AP_Cells_to_be_Activated_List_ItemIEs_t *itemIE = (F1AP_Cells_to_be_Activated_List_ItemIEs_t *)list->list.array[j]; - decode_cells_to_activate(&out->cells_to_activate[0], itemIE); + decode_cells_to_activate(&out->cells_to_activate[j], itemIE); } break; } @@ -2120,7 +2165,7 @@ bool eq_f1ap_du_configuration_update_acknowledge(const f1ap_gnb_du_configuration // number of cells to activate _F1_EQ_CHECK_INT(a->num_cells_to_activate, b->num_cells_to_activate); // loop over cells to activate - for (int i = 0; i < a->num_cells_to_activate; i++) { + for (int i = 0; i < a->num_cells_to_activate && i < F1AP_MAX_NB_CELLS; i++) { _F1_EQ_CHECK_LONG(a->cells_to_activate[i].nr_cellid, b->cells_to_activate[i].nr_cellid); _F1_EQ_CHECK_INT(a->cells_to_activate[i].nrpci, b->cells_to_activate[i].nrpci); if (!eq_f1ap_plmn(&a->cells_to_activate[i].plmn, &b->cells_to_activate[i].plmn)) @@ -2148,7 +2193,8 @@ f1ap_gnb_du_configuration_update_acknowledge_t cp_f1ap_du_configuration_update_a // number of cells to activate cp.num_cells_to_activate = msg->num_cells_to_activate; // Loop through cells to activate - for (int i = 0; i < cp.num_cells_to_activate; i++) { + cp.cells_to_activate = calloc_or_fail(cp.num_cells_to_activate, sizeof(*cp.cells_to_activate)); + for (int i = 0; i < cp.num_cells_to_activate && i < F1AP_MAX_NB_CELLS; i++) { cp.cells_to_activate[i] = msg->cells_to_activate[i]; for (int s = 0; s < cp.cells_to_activate[i].num_SI; s++) { f1ap_sib_msg_t *cp_sib = &cp.cells_to_activate[i].SI_msg[s]; @@ -2175,4 +2221,5 @@ void free_f1ap_du_configuration_update_acknowledge(const f1ap_gnb_du_configurati } } } + free(msg->cells_to_activate); } diff --git a/openair2/F1AP/lib/f1ap_interface_management.h b/openair2/F1AP/lib/f1ap_interface_management.h index 1b484f3476..56f6579f2f 100644 --- a/openair2/F1AP/lib/f1ap_interface_management.h +++ b/openair2/F1AP/lib/f1ap_interface_management.h @@ -84,6 +84,7 @@ bool eq_f1ap_cu_configuration_update_acknowledge(const f1ap_gnb_cu_configuration const f1ap_gnb_cu_configuration_update_acknowledge_t *b); f1ap_gnb_cu_configuration_update_acknowledge_t cp_f1ap_cu_configuration_update_acknowledge( const f1ap_gnb_cu_configuration_update_acknowledge_t *msg); +void free_f1ap_cu_configuration_update_acknowledge(const f1ap_gnb_cu_configuration_update_acknowledge_t *msg); /* F1 gNB-DU Configuration Update Acknowledge */ struct F1AP_F1AP_PDU *encode_f1ap_du_configuration_update_acknowledge(const f1ap_gnb_du_configuration_update_acknowledge_t *msg); diff --git a/openair2/F1AP/tests/f1ap_lib_test.c b/openair2/F1AP/tests/f1ap_lib_test.c index cba787f4dc..e60d768b94 100644 --- a/openair2/F1AP/tests/f1ap_lib_test.c +++ b/openair2/F1AP/tests/f1ap_lib_test.c @@ -244,8 +244,9 @@ static void test_f1ap_setup_request(void) .rrc_ver[0] = 12, .rrc_ver[1] = 34, .rrc_ver[2] = 56, - .cell[0].info = info, }; + orig.cell = calloc_or_fail(orig.num_cells_available, sizeof(*orig.cell)); + orig.cell[0].info = info; orig.cell[0].sys_info = calloc_or_fail(1, sizeof(*orig.cell[0].sys_info)); *orig.cell[0].sys_info = sys_info; // encode @@ -322,12 +323,14 @@ static void test_f1ap_setup_response(void) .rrc_ver[1] = 34, .rrc_ver[2] = 56, .num_cells_to_activate = 1, - .cells_to_activate[0].nr_cellid = 123456, - .cells_to_activate[0].nrpci = 1, - .cells_to_activate[0].plmn.mcc = 12, - .cells_to_activate[0].plmn.mnc = 123, - .cells_to_activate[0].plmn.mnc_digit_length = 3, }; + /* Allocate memory for cells_to_activate array */ + orig.cells_to_activate = calloc_or_fail(orig.num_cells_to_activate, sizeof(*orig.cells_to_activate)); + orig.cells_to_activate[0].nr_cellid = 123456; + orig.cells_to_activate[0].nrpci = 1; + orig.cells_to_activate[0].plmn.mcc = 12; + orig.cells_to_activate[0].plmn.mnc = 123; + orig.cells_to_activate[0].plmn.mnc_digit_length = 3; /* Cells to activate */ if (orig.num_cells_to_activate) { /* SI_container */ @@ -556,30 +559,37 @@ static void test_f1ap_du_configuration_update(void) f1ap_gnb_du_configuration_update_t orig = { .transaction_id = 2, .num_cells_to_add = 1, - .cell_to_add[0].info = info2, .num_cells_to_modify = 1, - .cell_to_modify[0].info = info, - .cell_to_modify[0].old_nr_cellid = 1235UL, - .cell_to_modify[0].old_plmn.mcc = 208, - .cell_to_modify[0].old_plmn.mnc = 88, - .cell_to_modify[0].old_plmn.mnc_digit_length = 2, .num_cells_to_delete = 1, - .cell_to_delete[0].nr_cellid = 1234UL, - .cell_to_delete[0].plmn.mcc = 1, - .cell_to_delete[0].plmn.mnc = 1, - .cell_to_delete[0].plmn.mnc_digit_length = 3, .num_status = 2, - .status[0].nr_cellid = 542UL, - .status[0].plmn.mcc = 1, - .status[0].plmn.mnc = 2, - .status[0].plmn.mnc_digit_length = 3, - .status[0].service_state = F1AP_STATE_IN_SERVICE, - .status[1].nr_cellid = 33UL, - .status[1].plmn.mcc = 5, - .status[1].plmn.mnc = 13, - .status[1].plmn.mnc_digit_length = 2, - .status[1].service_state = F1AP_STATE_OUT_OF_SERVICE, }; + /* Allocate memory for arrays */ + orig.cell_to_add = calloc_or_fail(orig.num_cells_to_add, sizeof(*orig.cell_to_add)); + orig.cell_to_modify = calloc_or_fail(orig.num_cells_to_modify, sizeof(*orig.cell_to_modify)); + orig.cell_to_delete = calloc_or_fail(orig.num_cells_to_delete, sizeof(*orig.cell_to_delete)); + orig.status = calloc_or_fail(orig.num_status, sizeof(*orig.status)); + + /* Fill in the data */ + orig.cell_to_add[0].info = info2; + orig.cell_to_modify[0].info = info; + orig.cell_to_modify[0].old_nr_cellid = 1235UL; + orig.cell_to_modify[0].old_plmn.mcc = 208; + orig.cell_to_modify[0].old_plmn.mnc = 88; + orig.cell_to_modify[0].old_plmn.mnc_digit_length = 2; + orig.cell_to_delete[0].nr_cellid = 1234UL; + orig.cell_to_delete[0].plmn.mcc = 1; + orig.cell_to_delete[0].plmn.mnc = 1; + orig.cell_to_delete[0].plmn.mnc_digit_length = 3; + orig.status[0].nr_cellid = 542UL; + orig.status[0].plmn.mcc = 1; + orig.status[0].plmn.mnc = 2; + orig.status[0].plmn.mnc_digit_length = 3; + orig.status[0].service_state = F1AP_STATE_IN_SERVICE; + orig.status[1].nr_cellid = 33UL; + orig.status[1].plmn.mcc = 5; + orig.status[1].plmn.mnc = 13; + orig.status[1].plmn.mnc_digit_length = 2; + orig.status[1].service_state = F1AP_STATE_OUT_OF_SERVICE; orig.cell_to_modify[0].sys_info = calloc_or_fail(1, sizeof(*orig.cell_to_modify[0].sys_info)); *orig.cell_to_modify[0].sys_info = sys_info; orig.gNB_DU_ID = malloc_or_fail(sizeof(*orig.gNB_DU_ID)); @@ -614,18 +624,18 @@ static void test_f1ap_du_configuration_update_acknowledge(void) f1ap_gnb_du_configuration_update_acknowledge_t orig = { .transaction_id = 5, .num_cells_to_activate = 1, - .cells_to_activate = {{ - .nr_cellid = 987654321, - .nrpci = 50, - .plmn = {.mcc = 001, .mnc = 01, .mnc_digit_length = 2}, - .num_SI = 1, - .SI_msg = {{ - .SI_type = 7, - .SI_container_length = 10, - .SI_container = malloc(sizeof(uint8_t) * 10), - }}, - }}, }; + // Allocate memory for cells_to_activate array + orig.cells_to_activate = calloc_or_fail(orig.num_cells_to_activate, sizeof(*orig.cells_to_activate)); + orig.cells_to_activate[0].nr_cellid = 987654321; + orig.cells_to_activate[0].nrpci = 50; + orig.cells_to_activate[0].plmn.mcc = 001; + orig.cells_to_activate[0].plmn.mnc = 01; + orig.cells_to_activate[0].plmn.mnc_digit_length = 2; + orig.cells_to_activate[0].num_SI = 1; + orig.cells_to_activate[0].SI_msg[0].SI_type = 7; + orig.cells_to_activate[0].SI_msg[0].SI_container_length = 10; + orig.cells_to_activate[0].SI_msg[0].SI_container = malloc(sizeof(uint8_t) * 10); for (int i = 0; i < orig.cells_to_activate[0].SI_msg[0].SI_container_length; i++) { orig.cells_to_activate[0].SI_msg[0].SI_container[i] = i; } @@ -657,17 +667,21 @@ static void test_f1ap_du_configuration_update_acknowledge(void) static void test_f1ap_cu_configuration_update(void) { /* create message */ - f1ap_gnb_cu_configuration_update_t orig = {.transaction_id = 2, - .num_cells_to_activate = 1, - .cells_to_activate = {{.nr_cellid = 123456789, - .nrpci = 100, - .plmn = {.mcc = 001, .mnc = 01, .mnc_digit_length = 2}, - .num_SI = 1, - .SI_msg = {{ - .SI_type = 7, - .SI_container_length = 10, - .SI_container = malloc(sizeof(uint8_t) * 10), - }}}}}; + f1ap_gnb_cu_configuration_update_t orig = { + .transaction_id = 2, + .num_cells_to_activate = 1, + }; + // Allocate memory for cells_to_activate array + orig.cells_to_activate = calloc_or_fail(orig.num_cells_to_activate, sizeof(*orig.cells_to_activate)); + orig.cells_to_activate[0].nr_cellid = 123456789; + orig.cells_to_activate[0].nrpci = 100; + orig.cells_to_activate[0].plmn.mcc = 001; + orig.cells_to_activate[0].plmn.mnc = 01; + orig.cells_to_activate[0].plmn.mnc_digit_length = 2; + orig.cells_to_activate[0].num_SI = 1; + orig.cells_to_activate[0].SI_msg[0].SI_type = 7; + orig.cells_to_activate[0].SI_msg[0].SI_container_length = 10; + orig.cells_to_activate[0].SI_msg[0].SI_container = malloc(sizeof(uint8_t) * 10); F1AP_F1AP_PDU_t *f1enc = encode_f1ap_cu_configuration_update(&orig); F1AP_F1AP_PDU_t *f1dec = f1ap_encode_decode(f1enc); f1ap_msg_free(f1enc); @@ -697,10 +711,14 @@ static void test_f1ap_cu_configuration_update_acknowledge(void) f1ap_gnb_cu_configuration_update_acknowledge_t orig = { .transaction_id = 2, .num_cells_failed_to_be_activated = 1, - .cells_failed_to_be_activated = {{.nr_cellid = 123456789, - .plmn = {.mcc = 001, .mnc = 01, .mnc_digit_length = 2}, - .cause = F1AP_CAUSE_RADIO_NETWORK}} - }; + }; + // Allocate memory for cells_failed_to_be_activated array + orig.cells_failed_to_be_activated = calloc_or_fail(orig.num_cells_failed_to_be_activated, sizeof(*orig.cells_failed_to_be_activated)); + orig.cells_failed_to_be_activated[0].nr_cellid = 123456789; + orig.cells_failed_to_be_activated[0].plmn.mcc = 001; + orig.cells_failed_to_be_activated[0].plmn.mnc = 01; + orig.cells_failed_to_be_activated[0].plmn.mnc_digit_length = 2; + orig.cells_failed_to_be_activated[0].cause = F1AP_CAUSE_RADIO_NETWORK; // F1AP Enc/dec F1AP_F1AP_PDU_t *f1enc = encode_f1ap_cu_configuration_update_acknowledge(&orig); F1AP_F1AP_PDU_t *f1dec = f1ap_encode_decode(f1enc); @@ -713,13 +731,14 @@ static void test_f1ap_cu_configuration_update_acknowledge(void) // Equality check ret = eq_f1ap_cu_configuration_update_acknowledge(&orig, &decoded); AssertFatal(ret, "eq_f1ap_cu_configuration_update_acknowledge(): decoded message doesn't match\n"); - // No free needed + free_f1ap_cu_configuration_update_acknowledge(&decoded); // Deep copy f1ap_gnb_cu_configuration_update_acknowledge_t cp = cp_f1ap_cu_configuration_update_acknowledge(&orig); // Equality check ret = eq_f1ap_cu_configuration_update_acknowledge(&orig, &cp); AssertFatal(ret, "eq_f1ap_cu_configuration_update_acknowledge(): copied message doesn't match\n"); - // No free needed + free_f1ap_cu_configuration_update_acknowledge(&cp); + free_f1ap_cu_configuration_update_acknowledge(&orig); } static byte_array_t get_test_ba(const char *s) diff --git a/openair2/GNB_APP/gnb_config.c b/openair2/GNB_APP/gnb_config.c index 0b3845f5b9..fadb402a71 100644 --- a/openair2/GNB_APP/gnb_config.c +++ b/openair2/GNB_APP/gnb_config.c @@ -1161,6 +1161,7 @@ static f1ap_setup_req_t *RC_read_F1Setup(uint64_t id, req->gNB_DU_id = id; req->gNB_DU_name = strdup(name); req->num_cells_available = 1; + req->cell = calloc_or_fail(req->num_cells_available, sizeof(*req->cell)); req->cell[0].info = *info; LOG_I(GNB_APP, "F1AP: gNB idx %d gNB_DU_id %ld, gNB_DU_name %s, TAC %d MCC/MNC/length %d/%d/%d cellID %ld\n", diff --git a/openair2/LAYER2/NR_MAC_gNB/config.c b/openair2/LAYER2/NR_MAC_gNB/config.c index dfa68773a4..ca1a516c0f 100644 --- a/openair2/LAYER2/NR_MAC_gNB/config.c +++ b/openair2/LAYER2/NR_MAC_gNB/config.c @@ -1093,14 +1093,17 @@ void prepare_du_configuration_update(gNB_MAC_INST *mac, { /* send gNB-DU configuration update to RRC */ f1ap_gnb_du_configuration_update_t update = { - .transaction_id = 1, - .num_status = 1, - .status[0].plmn = info->plmn, - .status[0].nr_cellid = info->nr_cellid, - .status[0].service_state = F1AP_STATE_IN_SERVICE, + .transaction_id = 1, + .num_status = 1, + .status = calloc_or_fail(1, sizeof(f1ap_cell_status_t)), }; + update.status[0].plmn = info->plmn; + update.status[0].nr_cellid = info->nr_cellid; + update.status[0].service_state = F1AP_STATE_IN_SERVICE; + if (mib && sib1) { - update.num_cells_to_modify = 1, + update.num_cells_to_modify = 1; + update.cell_to_modify = calloc_or_fail(1, sizeof(*update.cell_to_modify)); update.cell_to_modify[0].old_nr_cellid = info->nr_cellid; update.cell_to_modify[0].info = *info; update.cell_to_modify[0].sys_info = get_sys_info(mib, sib1, NULL); diff --git a/openair2/RRC/NR/rrc_gNB_du.c b/openair2/RRC/NR/rrc_gNB_du.c index 246df60626..302b45cac0 100644 --- a/openair2/RRC/NR/rrc_gNB_du.c +++ b/openair2/RRC/NR/rrc_gNB_du.c @@ -436,7 +436,8 @@ void rrc_gNB_process_f1_setup_req(f1ap_setup_req_t *req, sctp_assoc_t assoc_id) f1ap_setup_resp_t resp = {.transaction_id = req->transaction_id, .num_cells_to_activate = 1, - .cells_to_activate[0] = cell}; + .cells_to_activate = calloc_or_fail(1, sizeof(*resp.cells_to_activate))}; + resp.cells_to_activate[0] = cell; int num = read_version(TO_STRING(NR_RRC_VERSION), &resp.rrc_ver[0], &resp.rrc_ver[1], &resp.rrc_ver[2]); AssertFatal(num == 3, "could not read RRC version string %s\n", TO_STRING(NR_RRC_VERSION)); if (rrc->node_name != NULL) From 4c56c6b2de196229d7043076938fb5a2d66c0bf7 Mon Sep 17 00:00:00 2001 From: Guido Casati Date: Thu, 5 Feb 2026 11:33:11 +0100 Subject: [PATCH 003/100] Refactor (RRC): improve UE context removal logging Replace generic UE event log with explicit INFO log including UE ID and RNTI when removing a UE context and centralize access to UE fields via a local pointer. rrc_delete_ue_data removes the all the list in the UE context, including the serving cells, and therefore the cell id should not be logged in the function responsible for the removal of the UE context, which at this point is already emptied and while the cells themselves are still there (in the DU/global cell structures), the lists in the UE context are empty. --- openair2/RRC/NR/rrc_gNB_UE_context.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/openair2/RRC/NR/rrc_gNB_UE_context.c b/openair2/RRC/NR/rrc_gNB_UE_context.c index 86838927ab..01163e319f 100644 --- a/openair2/RRC/NR/rrc_gNB_UE_context.c +++ b/openair2/RRC/NR/rrc_gNB_UE_context.c @@ -155,10 +155,11 @@ void rrc_gNB_remove_ue_context(gNB_RRC_INST *rrc_instance_pP, rrc_gNB_ue_context return; } - LOG_UE_EVENT(&ue_context_pP->ue_context, "Remove UE context\n"); + gNB_RRC_UE_t *ue = &ue_context_pP->ue_context; + LOG_I(NR_RRC, "[--] (UE ID %d RNTI %04x) Remove UE context\n", ue->rrc_ue_id, ue->rnti); RB_REMOVE(rrc_nr_ue_tree_s, &rrc_instance_pP->rrc_ue_head, ue_context_pP); - uid_linear_allocator_free(&rrc_instance_pP->uid_allocator, ue_context_pP->ue_context.rrc_ue_id - 1); - cu_remove_f1_ue_data(ue_context_pP->ue_context.rrc_ue_id); + uid_linear_allocator_free(&rrc_instance_pP->uid_allocator, ue->rrc_ue_id - 1); + cu_remove_f1_ue_data(ue->rrc_ue_id); rrc_gNB_free_mem_ue_context(ue_context_pP); } From c6229a9c7a11cd83b6657589a7f809cf72cdfc6b Mon Sep 17 00:00:00 2001 From: Guido Casati Date: Thu, 29 Jan 2026 14:39:03 +0100 Subject: [PATCH 004/100] RRC multi-cell: enforce unique PCI in per-cell neighbour lists Neighbour lists are built per serving cell in fill_neighbour_cell_configuration. Each neighbour in a list is identified by PCI; having two entries with the same PCI in one cell's list would be ambiguous and indicate a configuration error. This commit adds validation so that duplicate PCIs within a cell's neighbour list are detected at configuration load time and cause a fatal assertion with a clear error message. Changes: - Add eq_neighbour_pci() and get_neighbour_by_pci() using common/utils/alg/find.h to look up a neighbour by PCI in a seq_arr. - In fill_neighbour_cell_configuration(), before appending each cell's neighbour list, iterate over neighbours and assert that no PCI appears more than once --- openair2/GNB_APP/gnb_config.c | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/openair2/GNB_APP/gnb_config.c b/openair2/GNB_APP/gnb_config.c index fadb402a71..8edec8e84f 100644 --- a/openair2/GNB_APP/gnb_config.c +++ b/openair2/GNB_APP/gnb_config.c @@ -75,6 +75,7 @@ #include "s1ap_messages_types.h" #include "sctp_default_values.h" #include "seq_arr.h" +#include "common/utils/alg/find.h" #include "uper_encoder.h" #include "utils.h" #include "x2ap_messages_types.h" @@ -1851,6 +1852,22 @@ static seq_arr_t *fill_cu_sibs(paramdef_t *GNBparamarray) return SIBs; } +static bool eq_neighbour_pci(const void *vval, const void *vit) +{ + const int *pci = (const int *)vval; + const nr_neighbour_cell_t *neighbour = (const nr_neighbour_cell_t *)vit; + return neighbour->physicalCellId == *pci; +} + +/** @brief Find neighbour by PCI within a cell's neighbour list (first match) */ +static nr_neighbour_cell_t *get_neighbour_by_pci(seq_arr_t *neighbour_cells, int pci) +{ + elm_arr_t elm = find_if(neighbour_cells, &pci, eq_neighbour_pci); + if (elm.found) + return (nr_neighbour_cell_t *)elm.it; + return NULL; +} + static void fill_neighbour_cell_configuration(uint8_t gnb_idx, gNB_RRC_INST *rrc) { char gnbpath[MAX_OPTNAME_SIZE + 8]; @@ -1908,6 +1925,14 @@ static void fill_neighbour_cell_configuration(uint8_t gnb_idx, gNB_RRC_INST *rrc n.plmn.mcc = *NeighbourPlmn[GNB_MOBILE_COUNTRY_CODE_IDX].uptr; n.plmn.mnc = *NeighbourPlmn[GNB_MOBILE_NETWORK_CODE_IDX].uptr; n.plmn.mnc_digit_length = *NeighbourPlmn[GNB_MNC_DIGIT_LENGTH].uptr; + if (get_neighbour_by_pci(cell.neighbour_cells, n.physicalCellId) != NULL) { + LOG_E(GNB_APP, + "Cell %ld: duplicate PCI %d in neighbour list (nrcell_id %ld)\n", + cell.nr_cell_id, + n.physicalCellId, + n.nrcell_id); + AssertFatal(false, "PCI must be unique within a cell's neighbour list\n"); + } seq_arr_push_back(cell.neighbour_cells, &n, sizeof(n)); LOG_I(GNB_APP, " [%d] neighbor ID %d cellId %ld PCI %d SCS %d SSB ARFCN %u TAC %u PLMN %03u.%0*u\n", From b699884ae401f79e5e588254605751268f0b7d47 Mon Sep 17 00:00:00 2001 From: Guido Casati Date: Mon, 5 Jan 2026 19:21:05 +0100 Subject: [PATCH 005/100] RRC multi-cell: add data structure definitions for multi-cell architecture Add foundational data structures in nr_rrc_defs.h for multi-cell handling in RRC, separating DU management from cell management. Design uses a global cell tree for O(log N) lookups while keeping cell ownership in DUs for locality and maintenance. The implementation maintains backward compatibility and follows the same patterns as existing DU and CUUP management in the codebase, i.e. RB-trees with logarithmic complexity for scalable multi-cell deployments. - Cell container structures: * nr_rrc_cell_container_t: cell-specific data (cell_id, pci, plmn, tac, frequency info, mib, sib1, mtc) * nr_rrc_cell_info_t: TDD/FDD modes; frequency structs nr_rrc_freq_info_t, nr_rrc_tdd_info_t, nr_rrc_fdd_info_t - UE serving cell tracking: * ue_serving_cell_t: nci, serving_cell_id (ServCellIndex TS 38.331, 0=PCell, 1-31=SCell), assoc_id for fast DU lookup * gNB_RRC_UE_t: seq_arr_t serving_cells, with PCell always being the first in the sequence - DU container refactoring: * nr_rrc_du_container_t: DU-only fields (gNB_DU_id, gNB_DU_name, rrc_ver); seq_arr_t cells for per-DU iteration; setup_req/mib/sib1/mtc kept for backward compatibility - Global cell tree: * gNB_RRC_INST_s: RB_HEAD(rrc_cell_tree, nr_rrc_cell_container_t) cells, num_cells (data only; lookup helpers get_cell_by_cell_id/get_cell_by_pci to be added separately) --- openair2/RRC/NR/nr_rrc_defs.h | 78 ++++++++++++++++++++++++++++++++++- 1 file changed, 77 insertions(+), 1 deletion(-) diff --git a/openair2/RRC/NR/nr_rrc_defs.h b/openair2/RRC/NR/nr_rrc_defs.h index 46e1ef3384..58f9ffd1fb 100644 --- a/openair2/RRC/NR/nr_rrc_defs.h +++ b/openair2/RRC/NR/nr_rrc_defs.h @@ -166,6 +166,21 @@ typedef struct { pdusession_level_qos_parameter_t qos; } nr_rrc_qos_t; +/** @brief UE serving cell information + * @note ServCellIndex is a short identity used to uniquely identify a serving cell + * (PCell, PSCell, or SCell) across cell groups (TS 38.331). + * Value 0 applies for the PCell, while the SCellIndex that has previously + * been assigned applies for SCells. + * @note Range: 0..maxNrofServingCells-1 where maxNrofServingCells = 32 */ +typedef struct { + /* NR Cell Identity (cell_id) */ + uint64_t nci; + /* ServCellIndex (TS 38.331): 0 = PCell, 1-31 = SCell */ + uint8_t serving_cell_id; + /* SCTP association ID of the DU that owns this cell (for fast lookup) */ + sctp_assoc_t assoc_id; +} ue_serving_cell_t; + /* forward declaration */ typedef struct nr_handover_context_s nr_handover_context_t; @@ -208,6 +223,9 @@ typedef struct gNB_RRC_UE_s { NR_EstablishmentCause_t establishment_cause; uint64_t nr_cellid; + /* Dynamic array of UE serving cells */ + seq_arr_t serving_cells; /* ue_serving_cell_t */ + uint32_t rrc_ue_id; uint64_t amf_ue_ngap_id; // Globally Unique AMF Identifier @@ -335,15 +353,69 @@ typedef struct cucp_cuup_if_s { cucp_cuup_bearer_context_release_func_t bearer_context_release; } cucp_cuup_if_t; +typedef struct { + int band; + uint32_t arfcn; + uint8_t scs; + uint16_t nrb; +} nr_rrc_freq_info_t; + +typedef struct { + nr_rrc_freq_info_t dlul; +} nr_rrc_tdd_info_t; + +typedef struct { + nr_rrc_freq_info_t dl; + nr_rrc_freq_info_t ul; +} nr_rrc_fdd_info_t; + +typedef struct { + /* operating mode: TDD or FDD */ + enum { NR_MODE_TDD = 0, NR_MODE_FDD = 1 } mode; + uint64_t cell_id; + uint16_t pci; + union { + nr_rrc_tdd_info_t tdd; + nr_rrc_fdd_info_t fdd; + }; + plmn_id_t plmn; + uint16_t tac; +} nr_rrc_cell_info_t; + +typedef struct nr_rrc_cell_container_t { + /* Tree-related data */ + RB_ENTRY(nr_rrc_cell_container_t) entries; + /* transport association */ + sctp_assoc_t assoc_id; + /* Cell-only RRC-local info */ + nr_rrc_cell_info_t info; + /* MIB message (6.2.2 TS 38.331) */ + NR_MIB_t *mib; + /* SIB1 message (6.2.2 TS 38.331) */ + NR_SIB1_t *sib1; + /* MeasurementTimingConfiguration inter-node (TS 38.331) */ + NR_MeasurementTimingConfiguration_t *mtc; +} nr_rrc_cell_container_t; + typedef struct nr_rrc_du_container_t { /* Tree-related data */ RB_ENTRY(nr_rrc_du_container_t) entries; - + /* DU-only information */ + /* Transport association identifier for this DU */ sctp_assoc_t assoc_id; + /* DU identity */ + uint64_t gNB_DU_id; + /* DU name */ + char *gNB_DU_name; + /* RRC version */ + uint8_t rrc_ver[3]; + // Cell-specific information (F1AP Setup Request, MIB, SIB1, MeasurementTimingConfiguration) f1ap_setup_req_t *setup_req; NR_MIB_t *mib; NR_SIB1_t *sib1; NR_MeasurementTimingConfiguration_t *mtc; + /* Cells, indexed by cell_id */ + seq_arr_t cells; /* nr_rrc_cell_container_t* */ } nr_rrc_du_container_t; typedef struct nr_rrc_cuup_container_t { @@ -387,6 +459,10 @@ typedef struct gNB_RRC_INST_s { RB_HEAD(rrc_du_tree, nr_rrc_du_container_t) dus; // DUs, indexed by assoc_id size_t num_dus; + /* Global cell tree, indexed by cell_id */ + RB_HEAD(rrc_cell_tree, nr_rrc_cell_container_t) cells; + size_t num_cells; + RB_HEAD(rrc_cuup_tree, nr_rrc_cuup_container_t) cuups; // CU-UPs, indexed by assoc_id size_t num_cuups; From 3dfdc66c41320a4a4d4c3f984c6998dafffa3fb5 Mon Sep 17 00:00:00 2001 From: Guido Casati Date: Mon, 5 Jan 2026 19:39:10 +0100 Subject: [PATCH 006/100] RRC multi-cell: add cell management library with lookup and cleanup This commit introduces a new library module (rrc_cell_management.c/h) to centralise functions that provides cell lookup, DU-specific cell management, and cell lifecycle managements. The design uses a dual-tree architecture: - Global cell tree: RB-tree at RRC level (rrc->cells) keyed by cell_id, used for O(log N_CELL) lookups and for maintaining num_cells. - DU-specific storage: seq_arr_t per DU (du->cells) for iteration and management of cells belonging to each DU. API added: - Global tree: get_cell_by_cell_id()(finds cell using global RB-tree lookup (O(log N_CELL) complexity); rrc_add_cell(); rrc_rm_cell() (rrc_rm_cell removes from tree, decrements num_cells, and frees the cell via rrc_free_cell_container). - DU-local seq_arr: rrc_get_cell_for_du(), rrc_get_cell_by_pci_for_du(), rrc_add_cell_to_du() (with duplicate cell_id/PCI checks). - Cleanup: rrc_free_cell_container() (frees ASN.1 MIB/SIB1/MTC and container Build: add rrc_cell_management static library in openair2/RRC/NR; link it into nr_rrc from top-level CMakeLists.txt. --- CMakeLists.txt | 2 +- openair2/RRC/NR/CMakeLists.txt | 5 + openair2/RRC/NR/rrc_cell_management.c | 182 ++++++++++++++++++++++++++ openair2/RRC/NR/rrc_cell_management.h | 47 +++++++ 4 files changed, 235 insertions(+), 1 deletion(-) create mode 100644 openair2/RRC/NR/rrc_cell_management.c create mode 100644 openair2/RRC/NR/rrc_cell_management.h diff --git a/CMakeLists.txt b/CMakeLists.txt index bae1c590bc..5b0dae95ba 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -342,7 +342,7 @@ target_link_libraries(lte_rrc PRIVATE nr_rrc) #NR RRC ####### add_library(nr_rrc ${OPENAIR2_DIR}/RRC/NR/MESSAGES/asn1_msg.c) -target_link_libraries(nr_rrc PUBLIC asn1_nr_rrc asn1_lte_rrc) +target_link_libraries(nr_rrc PUBLIC asn1_nr_rrc asn1_lte_rrc rrc_cell_management) # S1AP ############## diff --git a/openair2/RRC/NR/CMakeLists.txt b/openair2/RRC/NR/CMakeLists.txt index 658bfeee5a..9ca17a42bd 100644 --- a/openair2/RRC/NR/CMakeLists.txt +++ b/openair2/RRC/NR/CMakeLists.txt @@ -1,5 +1,10 @@ add_subdirectory(MESSAGES) +# Library for RRC cell management functions +add_library(rrc_cell_management STATIC rrc_cell_management.c) +target_link_libraries(rrc_cell_management PUBLIC asn1_nr_rrc asn1_lte_rrc) +target_link_libraries(rrc_cell_management PRIVATE utils LOG asn1_nr_rrc_hdrs asn1_lte_rrc_hdrs) + if(ENABLE_TESTS) add_subdirectory(tests) endif() diff --git a/openair2/RRC/NR/rrc_cell_management.c b/openair2/RRC/NR/rrc_cell_management.c new file mode 100644 index 0000000000..ebe8f78961 --- /dev/null +++ b/openair2/RRC/NR/rrc_cell_management.c @@ -0,0 +1,182 @@ +/* + * 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 +#include "rrc_cell_management.h" +#include "common/utils/assertions.h" // for DevAssert +#include "common/utils/collection/tree.h" // for RB_FOREACH +#include "common/utils/alg/find.h" // for find_if +#include "common/utils/ds/seq_arr.h" // for seq_arr_t, seq_arr_free +#include "common/utils/LOG/log.h" // for LOG_I, LOG_W, LOG_E +#include "asn_application.h" // for ASN_STRUCT_FREE +#include "NR_MIB.h" // for asn_DEF_NR_MIB +#include "NR_SIB1.h" // for asn_DEF_NR_SIB1 +#include "NR_MeasurementTimingConfiguration.h" // for asn_DEF_NR_MeasurementTimingConfiguration + +/** @brief Comparison function for cell tree + * @param[in] a Pointer to first cell container + * @param[in] b Pointer to second cell container + * @return Comparison result */ +int rrc_cell_cmp(const struct nr_rrc_cell_container_t *a, const struct nr_rrc_cell_container_t *b) +{ + if (a->info.cell_id < b->info.cell_id) + return -1; + if (a->info.cell_id > b->info.cell_id) + return 1; + return 0; +} + +/** @brief Generate RB tree functions for cell tree */ +RB_GENERATE(rrc_cell_tree, nr_rrc_cell_container_t, entries, rrc_cell_cmp); + +/** @brief Find cell container by cell ID (lookup in global cell tree) + * @param[in] cells Pointer to global cell tree + * @param[in] cell_id Cell ID + * @return Pointer to the cell container, or NULL if not found + * @note O(log N_CELL) using RB-tree lookup */ +nr_rrc_cell_container_t *get_cell_by_cell_id(struct rrc_cell_tree *cells, const uint64_t cell_id) +{ + DevAssert(cells != NULL); + nr_rrc_cell_container_t search_key = {.info = {.cell_id = cell_id}}; + return RB_FIND(rrc_cell_tree, cells, &search_key); +} + +/** @brief Add cell to global cell tree and increment rrc->num_cells on success + * @param[in] rrc Pointer to RRC instance + * @param[in] cell Pointer to cell container to add + * @return NULL if inserted successfully; pointer to existing cell (collision) if duplicate cell_id */ +nr_rrc_cell_container_t *rrc_add_cell(gNB_RRC_INST *rrc, nr_rrc_cell_container_t *cell) +{ + DevAssert(rrc != NULL); + DevAssert(cell != NULL); + nr_rrc_cell_container_t *collision = RB_INSERT(rrc_cell_tree, &rrc->cells, cell); + if (collision == NULL) + rrc->num_cells++; + return collision; +} + +/** @brief Remove cell from global cell tree, decrement rrc->num_cells, and free the cell + * @param[in] rrc Pointer to RRC instance + * @param[in] cell Pointer to cell container to remove */ +void rrc_rm_cell(gNB_RRC_INST *rrc, nr_rrc_cell_container_t *cell) +{ + DevAssert(rrc != NULL); + DevAssert(cell != NULL); + nr_rrc_cell_container_t *removed = RB_REMOVE(rrc_cell_tree, &rrc->cells, cell); + AssertFatal(removed == cell, "Failed to remove cell %ld from tree\n", cell->info.cell_id); + rrc->num_cells--; + rrc_free_cell_container(cell); +} + +static bool eq_cell_id(const void *vval, const void *vit) +{ + const uint64_t *id = (const uint64_t *)vval; + const nr_rrc_cell_container_t **elem_ptr = (const nr_rrc_cell_container_t **)vit; + return (*elem_ptr) != NULL && (*elem_ptr)->info.cell_id == *id; +} + +/** @brief Get cell by cell_id from DU's cells array + * @param[in] cells Pointer to DU's cells sequence array + * @param[in] cell_id Cell ID to search for + * @return Pointer to the cell container, or NULL if not found */ +nr_rrc_cell_container_t *rrc_get_cell_for_du(seq_arr_t *cells, uint64_t cell_id) +{ + DevAssert(cells); + elm_arr_t elm = find_if(cells, &cell_id, eq_cell_id); + if (elm.found) { + // elm.it points to an element in seq_arr, which stores nr_rrc_cell_container_t* + nr_rrc_cell_container_t **cell_ptr = (nr_rrc_cell_container_t **)elm.it; + return *cell_ptr; + } + return NULL; +} + +static bool eq_pci_in_du(const void *vval, const void *vit) +{ + const uint16_t *pci = (const uint16_t *)vval; + const nr_rrc_cell_container_t **elem_ptr = (const nr_rrc_cell_container_t **)vit; + return (*elem_ptr) != NULL && (*elem_ptr)->info.pci == *pci; +} + +/** @brief Get cell by PCI from DU's cells array + * @param[in] cells Pointer to DU's cells sequence array + * @param[in] pci PCI to search for + * @return Pointer to the cell container, or NULL if not found + * @note PCI must be unique within a DU; returns the first match */ +nr_rrc_cell_container_t *rrc_get_cell_by_pci_for_du(const seq_arr_t *cells, uint16_t pci) +{ + DevAssert(cells); + elm_arr_t elm = find_if((seq_arr_t *)cells, &pci, eq_pci_in_du); + if (elm.found) { + // elm.it points to an element in seq_arr, which stores nr_rrc_cell_container_t* + nr_rrc_cell_container_t **cell_ptr = (nr_rrc_cell_container_t **)elm.it; + return *cell_ptr; + } + return NULL; +} + +/** @brief Add cell to DU's cells array + * @param[in] cells Pointer to DU's cells sequence array + * @param[in] cell Pointer to cell container to add + * @return Pointer to the added cell, or NULL on duplicate cell_id/PCI or error + * @note Rejects duplicate cell_id or PCI within the DU */ +nr_rrc_cell_container_t *rrc_add_cell_to_du(seq_arr_t *cells, nr_rrc_cell_container_t *cell) +{ + DevAssert(cells); + DevAssert(cell); + + // Check for duplicate cell_id within this DU + nr_rrc_cell_container_t *exists = rrc_get_cell_for_du(cells, cell->info.cell_id); + if (exists) { + LOG_E(NR_RRC, "Trying to add an already existing cell with ID=%lu\n", cell->info.cell_id); + return NULL; + } + + // Check for duplicate PCI within this DU (PCI must be unique within a DU) + nr_rrc_cell_container_t *existing_pci = rrc_get_cell_by_pci_for_du(cells, cell->info.pci); + if (existing_pci != NULL) { + LOG_E(NR_RRC, + "Trying to add cell with ID=%lu, but PCI %d already exists in this DU (cell ID %lu)\n", + cell->info.cell_id, + cell->info.pci, + existing_pci->info.cell_id); + return NULL; + } + + seq_arr_push_back(cells, &cell, sizeof(nr_rrc_cell_container_t *)); + LOG_I(NR_RRC, "Added cell %lu to DU cells array (total cells = %ld)\n", cell->info.cell_id, seq_arr_size(cells)); + + return cell; +} + +/** @brief Free a cell container and all its associated resources + * @param[in] cell Pointer to the cell container to free + * @note Frees ASN.1 structures (mib, sib1, mtc) and the cell container itself + * @note Does NOT remove the cell from the tree - caller must do RB_REMOVE first */ +void rrc_free_cell_container(nr_rrc_cell_container_t *cell) +{ + DevAssert(cell); + ASN_STRUCT_FREE(asn_DEF_NR_MIB, cell->mib); + ASN_STRUCT_FREE(asn_DEF_NR_SIB1, cell->sib1); + ASN_STRUCT_FREE(asn_DEF_NR_MeasurementTimingConfiguration, cell->mtc); + free(cell); +} diff --git a/openair2/RRC/NR/rrc_cell_management.h b/openair2/RRC/NR/rrc_cell_management.h new file mode 100644 index 0000000000..d083b3d174 --- /dev/null +++ b/openair2/RRC/NR/rrc_cell_management.h @@ -0,0 +1,47 @@ +/* + * 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_CELL_MANAGEMENT_H_ +#define RRC_CELL_MANAGEMENT_H_ + +#include "nr_rrc_defs.h" // for nr_rrc_du_container_t, nr_rrc_cell_container_t, RB_HEAD +#include "common/utils/collection/tree.h" +#include +#include + +// Generate RB tree functions (prototypes) +int rrc_cell_cmp(const struct nr_rrc_cell_container_t *a, const struct nr_rrc_cell_container_t *b); +RB_PROTOTYPE(rrc_cell_tree, nr_rrc_cell_container_t, entries, rrc_cell_cmp); + +// Cell management (global cell tree) +nr_rrc_cell_container_t *get_cell_by_cell_id(struct rrc_cell_tree *cells, const uint64_t cell_id); +nr_rrc_cell_container_t *rrc_add_cell(gNB_RRC_INST *rrc, nr_rrc_cell_container_t *cell); +void rrc_rm_cell(gNB_RRC_INST *rrc, nr_rrc_cell_container_t *cell); + +// Cell management (DU-specific cell tree) +nr_rrc_cell_container_t *rrc_get_cell_for_du(seq_arr_t *cells, uint64_t cell_id); +nr_rrc_cell_container_t *rrc_get_cell_by_pci_for_du(const seq_arr_t *cells, uint16_t pci); +nr_rrc_cell_container_t *rrc_add_cell_to_du(seq_arr_t *cells, nr_rrc_cell_container_t *cell); + +// Cell cleanup +void rrc_free_cell_container(nr_rrc_cell_container_t *cell); + +#endif /* RRC_CELL_MANAGEMENT_H_ */ From 6d0c7945c2b3891c1bde3801cb5e82a6ab740c4e Mon Sep 17 00:00:00 2001 From: Guido Casati Date: Tue, 6 Jan 2026 15:30:58 +0100 Subject: [PATCH 007/100] RRC multi-cell: extract DU management to cell management library Move DU tree management from rrc_gNB_du.c into rrc_cell_management so that DU and cell lookup logic live in one place, improving maintainability and testability (there is no DU lib). - rrc_cell_management: add du_compare(), RB_GENERATE(rrc_du_tree), get_du_by_assoc_id(), rrc_add_du(), rrc_rm_du(), rrc_cleanup_du(), rrc_free_du_container(); declare them in rrc_cell_management.h. - rrc_gNB_du: move du_compare(), RB_GENERATE, get_du_by_assoc_id() to rrc_cell_management --- openair2/RRC/NR/rrc_cell_management.c | 83 +++++++++++++++++++++++++++ openair2/RRC/NR/rrc_cell_management.h | 9 +++ openair2/RRC/NR/rrc_gNB.c | 1 + openair2/RRC/NR/rrc_gNB_du.c | 19 +----- openair2/RRC/NR/rrc_gNB_du.h | 1 - 5 files changed, 94 insertions(+), 19 deletions(-) diff --git a/openair2/RRC/NR/rrc_cell_management.c b/openair2/RRC/NR/rrc_cell_management.c index ebe8f78961..eac0f38260 100644 --- a/openair2/RRC/NR/rrc_cell_management.c +++ b/openair2/RRC/NR/rrc_cell_management.c @@ -32,6 +32,89 @@ #include "NR_SIB1.h" // for asn_DEF_NR_SIB1 #include "NR_MeasurementTimingConfiguration.h" // for asn_DEF_NR_MeasurementTimingConfiguration +/** @brief Comparison function for DU tree + * @param[in] a Pointer to first DU container + * @param[in] b Pointer to second DU container + * @return Comparison result */ +int du_compare(const nr_rrc_du_container_t *a, const nr_rrc_du_container_t *b) +{ + if (a->assoc_id > b->assoc_id) + return 1; + if (a->assoc_id == b->assoc_id) + return 0; + return -1; /* a->assoc_id < b->assoc_id */ +} + +// Generate RB tree functions for DU tree +RB_GENERATE(rrc_du_tree, nr_rrc_du_container_t, entries, du_compare); + +/** @brief Find DU by SCTP association ID (uses DU RB-tree lookup) + * @param[in] rrc Pointer to RRC instance + * @param[in] assoc_id SCTP association ID + * @return Pointer to the DU container, or NULL if not found + * @note O(log N_DU) complexity using DU RB-tree lookup */ +nr_rrc_du_container_t *get_du_by_assoc_id(gNB_RRC_INST *rrc, const sctp_assoc_t assoc_id) +{ + DevAssert(rrc != NULL); + nr_rrc_du_container_t e = {.assoc_id = assoc_id}; + return RB_FIND(rrc_du_tree, &rrc->dus, &e); +} + +/** @brief Add DU to RRC DU tree and increment rrc->num_dus on success + * @param[in] rrc Pointer to RRC instance + * @param[in] du Pointer to DU container to add + * @return NULL if inserted successfully; pointer to existing DU (collision) if duplicate assoc_id */ +nr_rrc_du_container_t *rrc_add_du(gNB_RRC_INST *rrc, nr_rrc_du_container_t *du) +{ + DevAssert(rrc != NULL); + DevAssert(du != NULL); + nr_rrc_du_container_t *collision = RB_INSERT(rrc_du_tree, &rrc->dus, du); + if (collision == NULL) + rrc->num_dus++; + return collision; +} + +/** @brief Free a DU container and its allocated fields (gNB_DU_name); free the DU itself + * @param[in] du Pointer to DU container to free + * @note Does NOT remove the DU from the tree or free du->cells; caller must do RB_REMOVE and seq_arr_free first */ +void rrc_free_du_container(nr_rrc_du_container_t *du) +{ + DevAssert(du != NULL); + free(du->gNB_DU_name); + free(du); +} + +/** @brief Remove DU from RRC DU tree, decrement rrc->num_dus, and free the DU and its resources + * @param[in] rrc Pointer to RRC instance + * @param[in] du Pointer to DU container to remove (caller must have removed and freed all cells first) */ +void rrc_rm_du(gNB_RRC_INST *rrc, nr_rrc_du_container_t *du) +{ + DevAssert(rrc != NULL); + DevAssert(du != NULL); + nr_rrc_du_container_t *removed = RB_REMOVE(rrc_du_tree, &rrc->dus, du); + AssertFatal(removed == du, "Failed to remove DU (assoc_id %d) from tree\n", du->assoc_id); + rrc->num_dus--; + seq_arr_free(&du->cells, NULL); + rrc_free_du_container(du); +} + +/** @brief Remove all cells from the DU (and from global cell tree), remove the DU from the tree, and free the DU + * @param[in] rrc Pointer to RRC instance + * @param[in] du Pointer to DU container to cleanup (removed from tree and freed) */ +void rrc_cleanup_du(gNB_RRC_INST *rrc, nr_rrc_du_container_t *du) +{ + DevAssert(rrc != NULL); + DevAssert(du != NULL); + for (size_t i = seq_arr_size(&du->cells); i > 0; i--) { + nr_rrc_cell_container_t **cell_ptr = seq_arr_at(&du->cells, i - 1); + nr_rrc_cell_container_t *cell = *cell_ptr; + LOG_I(NR_RRC, "Removing cell %lu from DU cells array\n", cell->info.cell_id); + seq_arr_erase(&du->cells, cell_ptr); + rrc_rm_cell(rrc, cell); + } + rrc_rm_du(rrc, du); +} + /** @brief Comparison function for cell tree * @param[in] a Pointer to first cell container * @param[in] b Pointer to second cell container diff --git a/openair2/RRC/NR/rrc_cell_management.h b/openair2/RRC/NR/rrc_cell_management.h index d083b3d174..76790fda4f 100644 --- a/openair2/RRC/NR/rrc_cell_management.h +++ b/openair2/RRC/NR/rrc_cell_management.h @@ -30,6 +30,15 @@ // Generate RB tree functions (prototypes) int rrc_cell_cmp(const struct nr_rrc_cell_container_t *a, const struct nr_rrc_cell_container_t *b); RB_PROTOTYPE(rrc_cell_tree, nr_rrc_cell_container_t, entries, rrc_cell_cmp); +int du_compare(const struct nr_rrc_du_container_t *a, const struct nr_rrc_du_container_t *b); +RB_PROTOTYPE(rrc_du_tree, nr_rrc_du_container_t, entries, du_compare); + +// DU management (global DU tree) +nr_rrc_du_container_t *rrc_add_du(gNB_RRC_INST *rrc, nr_rrc_du_container_t *du); +void rrc_rm_du(gNB_RRC_INST *rrc, nr_rrc_du_container_t *du); +void rrc_cleanup_du(gNB_RRC_INST *rrc, nr_rrc_du_container_t *du); +void rrc_free_du_container(nr_rrc_du_container_t *du); +nr_rrc_du_container_t *get_du_by_assoc_id(gNB_RRC_INST *rrc, const sctp_assoc_t assoc_id); // Cell management (global cell tree) nr_rrc_cell_container_t *get_cell_by_cell_id(struct rrc_cell_tree *cells, const uint64_t cell_id); diff --git a/openair2/RRC/NR/rrc_gNB.c b/openair2/RRC/NR/rrc_gNB.c index 6b914c27e9..fd92bca995 100644 --- a/openair2/RRC/NR/rrc_gNB.c +++ b/openair2/RRC/NR/rrc_gNB.c @@ -84,6 +84,7 @@ #include "rrc_gNB_du.h" #include "rrc_gNB_mobility.h" #include "rrc_gNB_radio_bearers.h" +#include "rrc_cell_management.h" #include "rrc_messages_types.h" #include "rrc_gNB_asn1.h" #include "seq_arr.h" diff --git a/openair2/RRC/NR/rrc_gNB_du.c b/openair2/RRC/NR/rrc_gNB_du.c index 302b45cac0..83e158546d 100644 --- a/openair2/RRC/NR/rrc_gNB_du.c +++ b/openair2/RRC/NR/rrc_gNB_du.c @@ -20,6 +20,7 @@ */ #include "rrc_gNB_du.h" +#include "rrc_cell_management.h" #include #include #include @@ -84,18 +85,6 @@ int get_ssb_arfcn(const struct nr_rrc_du_container_t *du) return ssb_arfcn_mtc(du->mtc); } -static int du_compare(const nr_rrc_du_container_t *a, const nr_rrc_du_container_t *b) -{ - if (a->assoc_id > b->assoc_id) - return 1; - if (a->assoc_id == b->assoc_id) - return 0; - return -1; /* a->assoc_id < b->assoc_id */ -} - -/* Tree management functions */ -RB_GENERATE/*_STATIC*/(rrc_du_tree, nr_rrc_du_container_t, entries, du_compare); - static bool rrc_gNB_plmn_matches(const gNB_RRC_INST *rrc, const f1ap_served_cell_info_t *info) { const gNB_RrcConfigurationReq *conf = &rrc->configuration; @@ -629,12 +618,6 @@ nr_rrc_du_container_t *get_du_for_ue(gNB_RRC_INST *rrc, uint32_t ue_id) return get_du_by_assoc_id(rrc, ue_data.du_assoc_id); } -nr_rrc_du_container_t *get_du_by_assoc_id(gNB_RRC_INST *rrc, sctp_assoc_t assoc_id) -{ - nr_rrc_du_container_t e = {.assoc_id = assoc_id}; - 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) diff --git a/openair2/RRC/NR/rrc_gNB_du.h b/openair2/RRC/NR/rrc_gNB_du.h index 514416211f..4206096aa2 100644 --- a/openair2/RRC/NR/rrc_gNB_du.h +++ b/openair2/RRC/NR/rrc_gNB_du.h @@ -38,7 +38,6 @@ void rrc_CU_process_f1_lost_connection(struct gNB_RRC_INST_s *rrc, struct f1ap_l void rrc_gNB_process_f1_du_configuration_update(struct f1ap_gnb_du_configuration_update_s *conf_up, sctp_assoc_t assoc_id); 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); From 2ff071a52e47d0541a8ba8756821a2a6070f2ccb Mon Sep 17 00:00:00 2001 From: Guido Casati Date: Tue, 6 Jan 2026 00:18:09 +0100 Subject: [PATCH 008/100] RRC multi-cell: add UE serving cell management functions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add functions to manage UE serving cells so the RRC can track which cells a UE is using (PCell and SCells) for multi-cell UE context and future SCell management. - rrc_cell_management.c / rrc_cell_management.h: * rrc_get_ue_serving_cell_by_id(): return serving cell by servCellIndex (O(k) linear search, k ≤ 32). * rrc_add_ue_serving_cell(ue, cell, serving_cell_id): add entry to UE serving_cells with validation: reject duplicate servCellIndex with different nci, enforce max 32 serving cells (TS 38.331), keep PCell at index 0 for O(1) access; return existing entry if same servCellIndex and nci. * rrc_remove_ue_scells_from_du(ue, assoc_id): remove all serving cells for the given DU (handover/DU disconnect); use stored assoc_id for O(k) comparison. * ue_get_pcell_entry(): return PCell entry (first element, O(1)). * rrc_get_pcell_for_ue(): return PCell container from cell tree using UE's PCell entry. * Header: RRC_PCELL_INDEX (0), RRC_MAX_NUM_SERVING_CELLS (32), and declarations for the above. - UE context lifecycle: * rrc_gNB_create_ue_context(): seq_arr_init(serving_cells). * rrc_delete_ue_data(): seq_arr_free(serving_cells). --- openair2/RRC/NR/rrc_cell_management.c | 146 ++++++++++++++++++++++++++ openair2/RRC/NR/rrc_cell_management.h | 17 +++ openair2/RRC/NR/rrc_gNB.c | 1 + openair2/RRC/NR/rrc_gNB_UE_context.c | 3 + 4 files changed, 167 insertions(+) diff --git a/openair2/RRC/NR/rrc_cell_management.c b/openair2/RRC/NR/rrc_cell_management.c index eac0f38260..0df2665c04 100644 --- a/openair2/RRC/NR/rrc_cell_management.c +++ b/openair2/RRC/NR/rrc_cell_management.c @@ -27,6 +27,7 @@ #include "common/utils/alg/find.h" // for find_if #include "common/utils/ds/seq_arr.h" // for seq_arr_t, seq_arr_free #include "common/utils/LOG/log.h" // for LOG_I, LOG_W, LOG_E +#include "openair2/F1AP/f1ap_ids.h" // for cu_exists_f1_ue_data, cu_get_f1_ue_data #include "asn_application.h" // for ASN_STRUCT_FREE #include "NR_MIB.h" // for asn_DEF_NR_MIB #include "NR_SIB1.h" // for asn_DEF_NR_SIB1 @@ -263,3 +264,148 @@ void rrc_free_cell_container(nr_rrc_cell_container_t *cell) ASN_STRUCT_FREE(asn_DEF_NR_MeasurementTimingConfiguration, cell->mtc); free(cell); } + +static bool eq_serving_cell_id(const void *vval, const void *vit) +{ + const uint8_t *id = (const uint8_t *)vval; + const ue_serving_cell_t *elem = (const ue_serving_cell_t *)vit; + return elem->serving_cell_id == *id; +} + +/** @brief Get serving cell from UE's serving_cells array by serving_cell_id + * @param[in] ue UE context + * @param[in] serving_cell_id ServCellIndex (0 = PCell, 1-31 = SCell) + * @return Pointer to serving cell entry, or NULL if not found */ +ue_serving_cell_t *rrc_get_ue_serving_cell_by_id(const gNB_RRC_UE_t *ue, uint8_t serving_cell_id) +{ + DevAssert(ue); + elm_arr_t elm = find_if((seq_arr_t *)&ue->serving_cells, &serving_cell_id, eq_serving_cell_id); + if (elm.found) + return (ue_serving_cell_t *)elm.it; + return NULL; +} + +/** @brief Add a serving cell to UE's serving_cells array + * @param[in] ue UE context + * @param[in] cell Cell container (nci and assoc_id taken from here) + * @param[in] serving_cell_id ServCellIndex (0 = PCell, 1-31 = SCell) + * @return Pointer to the added or existing entry, or NULL on error + * @note Uniqueness: (serving_cell_id, nci) must be unique. Same pair returns existing; + * same serving_cell_id, different nci returns NULL. */ +ue_serving_cell_t *rrc_add_ue_serving_cell(gNB_RRC_UE_t *ue, const nr_rrc_cell_container_t *cell, uint8_t serving_cell_id) +{ + DevAssert(ue); + DevAssert(serving_cell_id < RRC_MAX_NUM_SERVING_CELLS); + + // Check maximum number of items in list + size_t current_count = seq_arr_size(&ue->serving_cells); + if (current_count >= RRC_MAX_NUM_SERVING_CELLS) { + LOG_E(NR_RRC, "Maximum number of serving cells (%d) reached\n", RRC_MAX_NUM_SERVING_CELLS); + return NULL; + } + + // Check if serving cell with this serving_cell_id already exists + ue_serving_cell_t *existing = rrc_get_ue_serving_cell_by_id(ue, serving_cell_id); + if (existing != NULL) { + // If it's the same nci, return existing + if (existing->nci == cell->info.cell_id) { + LOG_D(NR_RRC, "Serving cell %d already has cell %ld\n", serving_cell_id, cell->info.cell_id); + return existing; + } + // Reject if already exists with different nci + LOG_E(NR_RRC, "Serving cell with serving_cell_id %d already exists with different nci\n", serving_cell_id); + return NULL; + } + + LOG_I(NR_RRC, + "Adding new %s (cell %ld, assoc_id %d)\n", + serving_cell_id == RRC_PCELL_INDEX ? "PCell" : "SCell", + cell->info.cell_id, + cell->assoc_id); + ue_serving_cell_t new_cell = {.nci = cell->info.cell_id, .serving_cell_id = serving_cell_id, .assoc_id = cell->assoc_id}; + + size_t elt_size = sizeof(ue_serving_cell_t); + if (serving_cell_id == RRC_PCELL_INDEX) { + /* PCell must match F1 UE data (serving_cells and F1 UE data stay in sync). */ + DevAssert(cu_exists_f1_ue_data(ue->rrc_ue_id)); + DevAssert(cu_get_f1_ue_data(ue->rrc_ue_id).du_assoc_id == cell->assoc_id); + /* Ensure PCell is always at index 0 in serving_cells */ + size_t n = seq_arr_size(&ue->serving_cells); + seq_arr_push_back(&ue->serving_cells, &new_cell, elt_size); + if (n > 0) { + /* If there are existing cells, shift them right to make room for the new PCell */ + void *first = seq_arr_front(&ue->serving_cells); + memmove((uint8_t *)first + elt_size, first, n * elt_size); + memcpy(first, &new_cell, elt_size); + } + } else { + /* SCells are appended after PCell (if any) */ + seq_arr_push_back(&ue->serving_cells, &new_cell, elt_size); + } + return rrc_get_ue_serving_cell_by_id(ue, serving_cell_id); +} + +/** @brief Remove serving cells that belong to a specific DU + * @param[in] ue UE context + * @param[in] assoc_id Association ID of the DU + * @note Removes all serving cells (including PCell) that belong to the specified DU. + * Uses stored assoc_id for O(k) comparison without cell lookup. */ +void rrc_remove_ue_scells_from_du(gNB_RRC_UE_t *ue, sctp_assoc_t assoc_id) +{ + DevAssert(ue); + LOG_I(NR_RRC, "Removing SCells from DU (assoc_id=%d) for UE %d\n", assoc_id, ue->rrc_ue_id); + + // Collect indices of SCells to delete (don't modify array while iterating) + uint32_t indices_to_delete[RRC_MAX_NUM_SERVING_CELLS]; + size_t num_to_delete = 0; + uint32_t index = 0; + + FOR_EACH_SEQ_ARR (ue_serving_cell_t *, scell, &ue->serving_cells) { + // Use stored assoc_id for direct comparison + if (scell->assoc_id == assoc_id) { + indices_to_delete[num_to_delete++] = index; + } + index++; + } + + // Now delete the collected serving cells from end to beginning for safety + for (size_t i = num_to_delete; i > 0; i--) { + uint32_t idx = indices_to_delete[i - 1]; + void *it = seq_arr_at(&ue->serving_cells, idx); + seq_arr_erase(&ue->serving_cells, it); + } + +} + +/** @brief Get PCell serving cell entry for a UE based on serving_cells + * @param[in] ue UE context + * @return Pointer to serving cell entry for PCell, or NULL if not found + * @note PCell is always at index 0. */ +const ue_serving_cell_t *ue_get_pcell_entry(const gNB_RRC_UE_t *ue) +{ + DevAssert(ue); + size_t count = seq_arr_size(&ue->serving_cells); + if (count == 0) + return NULL; + + /* PCell is always the first serving cell */ + const ue_serving_cell_t *first = seq_arr_front(&ue->serving_cells); + DevAssert(first != NULL); + DevAssert(first->serving_cell_id == RRC_PCELL_INDEX); + return first; +} + +/** @brief Get UE's PCell container from the global cell tree + * @param[in] rrc RRC instance (global cell tree) + * @param[in] ue UE context + * @return Pointer to PCell container, or NULL if UE has no PCell or cell not in tree */ +nr_rrc_cell_container_t *rrc_get_pcell_for_ue(gNB_RRC_INST *rrc, const gNB_RRC_UE_t *ue) +{ + DevAssert(rrc != NULL); + DevAssert(ue != NULL); + const ue_serving_cell_t *pcell_entry = ue_get_pcell_entry(ue); + if (pcell_entry == NULL) + return NULL; + return get_cell_by_cell_id(&rrc->cells, pcell_entry->nci); +} + diff --git a/openair2/RRC/NR/rrc_cell_management.h b/openair2/RRC/NR/rrc_cell_management.h index 76790fda4f..99071cceb2 100644 --- a/openair2/RRC/NR/rrc_cell_management.h +++ b/openair2/RRC/NR/rrc_cell_management.h @@ -27,6 +27,16 @@ #include #include +/** @brief ServCellIndex value for Primary Cell (TS 38.331) + * @note ServCellIndex is a short identity used to uniquely identify a serving cell + * (PCell, PSCell, or SCell) across cell groups. Value 0 applies for the PCell. */ +#define RRC_PCELL_INDEX 0 + +/** @brief Maximum number of serving cells (TS 38.331) + * @note maxNrofServingCells = 32 (1 PCell + up to 31 SCells) + * @note ServCellIndex range: INTEGER (0..maxNrofServingCells-1) = 0..31 */ +#define RRC_MAX_NUM_SERVING_CELLS 32 + // Generate RB tree functions (prototypes) int rrc_cell_cmp(const struct nr_rrc_cell_container_t *a, const struct nr_rrc_cell_container_t *b); RB_PROTOTYPE(rrc_cell_tree, nr_rrc_cell_container_t, entries, rrc_cell_cmp); @@ -53,4 +63,11 @@ nr_rrc_cell_container_t *rrc_add_cell_to_du(seq_arr_t *cells, nr_rrc_cell_contai // Cell cleanup void rrc_free_cell_container(nr_rrc_cell_container_t *cell); +// UE serving cell functions +ue_serving_cell_t *rrc_add_ue_serving_cell(gNB_RRC_UE_t *ue, const nr_rrc_cell_container_t *cell, uint8_t serving_cell_id); +void rrc_remove_ue_scells_from_du(gNB_RRC_UE_t *ue, sctp_assoc_t assoc_id); +ue_serving_cell_t *rrc_get_ue_serving_cell_by_id(const gNB_RRC_UE_t *ue, uint8_t serving_cell_id); +const ue_serving_cell_t *ue_get_pcell_entry(const gNB_RRC_UE_t *ue); +nr_rrc_cell_container_t *rrc_get_pcell_for_ue(gNB_RRC_INST *rrc, const gNB_RRC_UE_t *ue); + #endif /* RRC_CELL_MANAGEMENT_H_ */ diff --git a/openair2/RRC/NR/rrc_gNB.c b/openair2/RRC/NR/rrc_gNB.c index fd92bca995..6b40d37b36 100644 --- a/openair2/RRC/NR/rrc_gNB.c +++ b/openair2/RRC/NR/rrc_gNB.c @@ -2664,6 +2664,7 @@ static void rrc_delete_ue_data(gNB_RRC_UE_t *UE) UE->redcap_cap = NULL; seq_arr_free(&UE->pduSessions, free_pdusession); seq_arr_free(&UE->drbs, free_drb); + seq_arr_free(&UE->serving_cells, NULL); } void rrc_remove_ue(gNB_RRC_INST *rrc, rrc_gNB_ue_context_t *ue_context_p) diff --git a/openair2/RRC/NR/rrc_gNB_UE_context.c b/openair2/RRC/NR/rrc_gNB_UE_context.c index 01163e319f..6c47a7478e 100644 --- a/openair2/RRC/NR/rrc_gNB_UE_context.c +++ b/openair2/RRC/NR/rrc_gNB_UE_context.c @@ -41,6 +41,7 @@ #include "openair2/F1AP/f1ap_ids.h" #include "tree.h" #include "rrc_gNB_radio_bearers.h" +#include "rrc_cell_management.h" static void rrc_gNB_ue_context_update_time(rrc_gNB_ue_context_t *ctxt) { @@ -224,6 +225,8 @@ rrc_gNB_ue_context_t *rrc_gNB_create_ue_context(sctp_assoc_t assoc_id, seq_arr_init(&ue->pduSessions, sizeof(rrc_pdu_session_param_t)); // Initialise setup DRBs list seq_arr_init(&ue->drbs, sizeof(drb_t)); + // Initialise serving cells list + seq_arr_init(&ue->serving_cells, sizeof(ue_serving_cell_t)); RB_INSERT(rrc_nr_ue_tree_s, &rrc_instance_pP->rrc_ue_head, ue_context_p); LOG_UE_EVENT(ue, From c453c342ed0d2b87b0599fe57998d275ba1582b2 Mon Sep 17 00:00:00 2001 From: Guido Casati Date: Wed, 12 Nov 2025 14:03:41 +0100 Subject: [PATCH 009/100] RRC multi-cell: add integration tests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add integration tests for RRC cell and DU management and UE serving-cell logic so that multi-cell behaviour and regressions can be validated without running the full stack. * test_du_lookup() – DU container lookup by assoc_id: - Multiple DU creation and insertion into RRC DU tree - Lookup of existing DUs by assoc_id - Non-existing and invalid assoc_ids - Cleanup of DU containers and cells * test_cell_lookup() – Cell management and UE association: - Cell lookup by cell_id and PCI (global and per-DU) - DU-specific lookup: rrc_get_cell_for_du(), rrc_get_cell_by_pci_for_du() - Duplicate cell and PCI collision handling (including PCI reuse across DUs and rejection of duplicate PCI within same DU) - Cell–DU association (add/remove cells from DU containers) - UE serving cell: add PCell/SCells, get by ID, get PCell, max serving cells limit, single PCell enforcement - validate_ue_cell_association(), rrc_remove_ue_scells_from_du() - Edge cases and error handling Add openair2/RRC/NR/tests/rrc_cell_management_test.c and extend openair2/RRC/NR/tests/CMakeLists.txt to build and register the test. --- openair2/RRC/NR/tests/CMakeLists.txt | 16 + .../RRC/NR/tests/rrc_cell_management_test.c | 460 ++++++++++++++++++ 2 files changed, 476 insertions(+) create mode 100644 openair2/RRC/NR/tests/rrc_cell_management_test.c diff --git a/openair2/RRC/NR/tests/CMakeLists.txt b/openair2/RRC/NR/tests/CMakeLists.txt index f7a4a95b1b..f68501c99e 100644 --- a/openair2/RRC/NR/tests/CMakeLists.txt +++ b/openair2/RRC/NR/tests/CMakeLists.txt @@ -5,3 +5,19 @@ add_test(NAME rrc_bearers_test COMMAND rrc_bearers_test) target_include_directories(rrc_bearers_test PRIVATE ./common/utils/ds common/utils/LOG) target_link_libraries(rrc_bearers_test PRIVATE ds alg asn1_e1ap asn1_lte_rrc_hdrs asn1_nr_rrc_hdrs LOG T) target_compile_definitions(rrc_bearers_test PRIVATE ENABLE_TESTS) + +add_executable(rrc_cell_management_test rrc_cell_management_test.c) +add_dependencies(tests rrc_cell_management_test) +add_test(NAME rrc_cell_management_test COMMAND rrc_cell_management_test) +target_include_directories(rrc_cell_management_test PRIVATE ./common/utils/ds common/utils/LOG) +target_link_libraries(rrc_cell_management_test PRIVATE + rrc_cell_management + f1ap + ds + alg + LOG + utils + asn1_nr_rrc_hdrs + asn1_lte_rrc_hdrs +) +target_compile_definitions(rrc_cell_management_test PRIVATE ENABLE_TESTS) diff --git a/openair2/RRC/NR/tests/rrc_cell_management_test.c b/openair2/RRC/NR/tests/rrc_cell_management_test.c new file mode 100644 index 0000000000..b3aa5e6881 --- /dev/null +++ b/openair2/RRC/NR/tests/rrc_cell_management_test.c @@ -0,0 +1,460 @@ +/* + * 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 +#include +#include "common/utils/utils.h" // for calloc_or_fail +#include "common/utils/assertions.h" // for AssertFatal, DevAssert +#include "common/utils/collection/tree.h" // for RB tree macros +#include "openair2/RRC/NR/rrc_cell_management.h" // for cell management functions +#include "openair2/RRC/NR/rrc_gNB_du.h" // for get_du_by_assoc_id +#include "common/utils/LOG/log.h" // for LOG_I, LOG_A, etc. +#include "common/utils/ds/seq_arr.h" // for seq_arr_free +#include "openair2/F1AP/f1ap_ids.h" // for cu_init_f1_ue_data, cu_add_f1_ue_data + +// Test constants +static const sctp_assoc_t ASSOC_ID_DU1 = 8; +static const sctp_assoc_t ASSOC_ID_DU2 = 9; +static const sctp_assoc_t ASSOC_ID_DU3 = 10; +static const uint64_t DU_ID_1 = 1001; +static const uint64_t DU_ID_2 = 1002; +static const uint64_t DU_ID_3 = 1003; +static const uint64_t CELL_ID_1 = 12345678; +static const uint64_t CELL_ID_2 = 87654321; +static const uint64_t CELL_ID_3 = 11111111; +static const uint64_t CELL_ID_4 = 22222222; +static const uint64_t CELL_ID_5 = 33333333; +static const uint64_t CELL_ID_6 = 44444444; +static const uint64_t CELL_ID_7 = 55555555; +static const uint64_t CELL_ID_NONEXISTENT = 99999999; +static const uint16_t PCI_1 = 1; +static const uint16_t PCI_2 = 4; +static const uint16_t PCI_3 = 5; +static const uint16_t PCI_10 = 10; +static const uint16_t PCI_20 = 20; +static const uint16_t PCI_30 = 30; +static const uint16_t PCI_40 = 40; +static const uint16_t PCI_NONEXISTENT = 99; +static const uint8_t SERVING_CELL_ID_SCELL1 = 1; +static const uint8_t SERVING_CELL_ID_SCELL2 = 2; +static const uint32_t UE_ID = 1; + +// Mock functions and variables +configmodule_interface_t *uniqCfg = NULL; +softmodem_params_t *get_softmodem_params(void) +{ + return NULL; +} + +void exit_function(const char *file, const char *function, const int line, const char *s, const int assert) +{ + printf("detected error at %s:%d:%s: %s\n", file, line, function, s); + abort(); +} + +// Helper function to create a test cell container +static nr_rrc_cell_container_t *create_test_cell(uint64_t cell_id, uint16_t pci, sctp_assoc_t assoc_id) +{ + nr_rrc_cell_container_t *cell = calloc_or_fail(1, sizeof(*cell)); + cell->info.cell_id = cell_id; + cell->info.pci = pci; + cell->assoc_id = assoc_id; + cell->info.mode = NR_MODE_TDD; + cell->info.plmn.mcc = 222; + cell->info.plmn.mnc = 1; + cell->info.tac = 1; + return cell; +} + +// Helper function to create a test DU container +static nr_rrc_du_container_t *create_test_du(sctp_assoc_t assoc_id, uint64_t du_id, const char *name) +{ + nr_rrc_du_container_t *du = calloc_or_fail(1, sizeof(*du)); + du->assoc_id = assoc_id; + du->gNB_DU_id = du_id; + du->gNB_DU_name = strdup(name); + AssertFatal(du->gNB_DU_name != NULL, "strdup failed for DU name"); + seq_arr_init(&du->cells, sizeof(nr_rrc_cell_container_t *)); + return du; +} + +// Helper function to add DU to RRC instance +static void add_du_to_rrc(gNB_RRC_INST *rrc, nr_rrc_du_container_t *du) +{ + nr_rrc_du_container_t *collision = rrc_add_du(rrc, du); + AssertFatal(collision == NULL, "rrc_add_du should succeed for new DU (assoc_id %d)", du->assoc_id); +} + +// Helper function to find cell by PCI in global tree (for testing PCI reuse across network) +// Returns first match; PCI is not globally unique +static nr_rrc_cell_container_t *get_cell_by_pci(struct rrc_cell_tree *cells, const uint16_t pci) +{ + nr_rrc_cell_container_t *cell = NULL; + RB_FOREACH (cell, rrc_cell_tree, cells) { + if (cell->info.pci == pci) + return cell; + } + return NULL; +} + +/** @brief Test DU lookup by assoc_id + * @note Tests the functions: + * - get_du_by_assoc_id() */ +static void test_du_lookup(void) +{ + LOG_I(NR_RRC, "Testing DU lookup by assoc_id\n"); + + gNB_RRC_INST rrc = {0}; + RB_INIT(&rrc.dus); + RB_INIT(&rrc.cells); + + /* Sub-test 1: Create multiple DUs and insert into RRC DU tree */ + struct { + nr_rrc_du_container_t *du; + sctp_assoc_t assoc_id; + uint64_t du_id; + } dus[] = { + {create_test_du(ASSOC_ID_DU1, DU_ID_1, "DU-1"), ASSOC_ID_DU1, DU_ID_1}, + {create_test_du(ASSOC_ID_DU2, DU_ID_2, "DU-2"), ASSOC_ID_DU2, DU_ID_2}, + {create_test_du(ASSOC_ID_DU3, DU_ID_3, "DU-3"), ASSOC_ID_DU3, DU_ID_3}, + }; + + for (size_t i = 0; i < sizeof(dus) / sizeof(dus[0]); i++) { + add_du_to_rrc(&rrc, dus[i].du); + } + + /* Sub-test 2: Test lookup existing DUs */ + for (size_t i = 0; i < sizeof(dus) / sizeof(dus[0]); i++) { + nr_rrc_du_container_t *found = get_du_by_assoc_id(&rrc, dus[i].assoc_id); + AssertFatal(found == dus[i].du, "DU with assoc_id %d should be found", dus[i].assoc_id); + AssertFatal(found->gNB_DU_id == dus[i].du_id, "DU ID should match (expected %lu, got %lu)", dus[i].du_id, found->gNB_DU_id); + } + + /* Sub-test 3: Test lookup non-existing assoc_id */ + static const sctp_assoc_t ASSOC_ID_NONEXISTENT = 99; + nr_rrc_du_container_t *found = get_du_by_assoc_id(&rrc, ASSOC_ID_NONEXISTENT); + AssertFatal(found == NULL, "Non-existing assoc_id %d should return NULL", ASSOC_ID_NONEXISTENT); + + /* Sub-test 4: Test invalid assoc_id (0 is considered invalid) */ + static const sctp_assoc_t ASSOC_ID_INVALID = 0; + found = get_du_by_assoc_id(&rrc, ASSOC_ID_INVALID); + AssertFatal(found == NULL, "Invalid assoc_id %d should return NULL", ASSOC_ID_INVALID); + + LOG_A(NR_RRC, "DU lookup test passed\n"); + + /* Sub-test 5: Cleanup with free of DUs and their cell containers */ + for (size_t i = 0; i < sizeof(dus) / sizeof(dus[0]); i++) { + rrc_cleanup_du(&rrc, dus[i].du); + } +} + +/** @brief Test cell lookup, cell-DU association, and UE cell association + * @note Tests the functions: + * - get_cell_by_cell_id() + * - rrc_get_cell_for_du() + * - rrc_get_cell_by_pci_for_du() + * - rrc_add_cell_to_du() + * - rrc_add_ue_serving_cell() + * - rrc_get_ue_serving_cell_by_id() + * - rrc_get_pcell_for_ue() + * - rrc_remove_ue_scells_from_du() + * - rrc_free_cell_container() */ +static void test_cell_lookup(void) +{ + LOG_I(NR_RRC, "Testing cell lookup functions\n"); + + cu_init_f1_ue_data(); + + gNB_RRC_INST rrc = {0}; + RB_INIT(&rrc.dus); + RB_INIT(&rrc.cells); + + nr_rrc_du_container_t *du1 = create_test_du(ASSOC_ID_DU1, DU_ID_1, "DU-1"); + nr_rrc_du_container_t *du2 = create_test_du(ASSOC_ID_DU2, DU_ID_2, "DU-2"); + add_du_to_rrc(&rrc, du1); + add_du_to_rrc(&rrc, du2); + + /* Sub-test 1: Create multiple cells per DU and insert into global cell tree and DU's seq_arr */ + struct { + nr_rrc_cell_container_t *cell; + uint64_t cell_id; + uint16_t pci; + nr_rrc_du_container_t *du; + } cells[] = { + {create_test_cell(CELL_ID_1, PCI_10, ASSOC_ID_DU1), CELL_ID_1, PCI_10, du1}, + {create_test_cell(CELL_ID_2, PCI_20, ASSOC_ID_DU1), CELL_ID_2, PCI_20, du1}, + {create_test_cell(CELL_ID_3, PCI_1, ASSOC_ID_DU1), CELL_ID_3, PCI_1, du1}, + {create_test_cell(CELL_ID_4, PCI_2, ASSOC_ID_DU2), CELL_ID_4, PCI_2, du2}, + {create_test_cell(CELL_ID_5, PCI_3, ASSOC_ID_DU2), CELL_ID_5, PCI_3, du2}, + }; + + // Insert cells: check for duplicate cell_id first, then insert into global tree and DU's seq_arr + for (size_t i = 0; i < sizeofArray(cells); i++) { + nr_rrc_cell_container_t *existing = get_cell_by_cell_id(&rrc.cells, cells[i].cell_id); + AssertFatal(existing == NULL, "Cell ID %lu should not exist before insertion", cells[i].cell_id); + // Insert into global cell tree + nr_rrc_cell_container_t *collision = rrc_add_cell(&rrc, cells[i].cell); + AssertFatal(collision == NULL, "rrc_add_cell should succeed for new cell (ID %lu)", cells[i].cell_id); + // Insert into DU's seq_arr + nr_rrc_cell_container_t *added = rrc_add_cell_to_du(&cells[i].du->cells, cells[i].cell); + AssertFatal(added != NULL, "rrc_add_cell_to_du should succeed for new cell (ID %lu)", cells[i].cell_id); + } + + nr_rrc_cell_container_t *found; + /* Sub-test 2: Test lookup by cell_id */ + found = get_cell_by_cell_id(&rrc.cells, CELL_ID_1); + AssertFatal(found != NULL && found == cells[0].cell, "Cell ID %lu should be found", CELL_ID_1); + found = get_cell_by_cell_id(&rrc.cells, CELL_ID_4); + AssertFatal(found != NULL && found == cells[3].cell, "Cell ID %lu should be found", CELL_ID_4); + + /* Sub-test 3: Test lookup by PCI (returns first match) */ + found = get_cell_by_pci(&rrc.cells, PCI_10); + AssertFatal(found != NULL && found->info.cell_id == CELL_ID_1, + "Cell with PCI %d should be found (expected cell_id %lu)", + PCI_10, + CELL_ID_1); + + /* Sub-test 4: Test lookup non-existing cell */ + found = get_cell_by_cell_id(&rrc.cells, CELL_ID_NONEXISTENT); + AssertFatal(found == NULL, "Non-existing cell_id should return NULL"); + found = get_cell_by_pci(&rrc.cells, PCI_NONEXISTENT); + AssertFatal(found == NULL, "Non-existing PCI should return NULL"); + + /* Sub-test 5: Test rrc_get_cell_for_du() - DU-specific cell lookup */ + found = rrc_get_cell_for_du(&du1->cells, CELL_ID_1); + AssertFatal(found != NULL && found->info.cell_id == CELL_ID_1, "rrc_get_cell_for_du should find cell in DU1"); + found = rrc_get_cell_for_du(&du2->cells, CELL_ID_4); + AssertFatal(found != NULL && found->info.cell_id == CELL_ID_4, "rrc_get_cell_for_du should find cell in DU2"); + // Test non-existing cell in DU + nr_rrc_cell_container_t *not_found = rrc_get_cell_for_du(&du1->cells, CELL_ID_NONEXISTENT); + AssertFatal(not_found == NULL, "rrc_get_cell_for_du should return NULL for non-existing cell"); + // Test cell from different DU + not_found = rrc_get_cell_for_du(&du1->cells, CELL_ID_4); + AssertFatal(not_found == NULL, "rrc_get_cell_for_du should return NULL for cell from different DU"); + + /* Sub-test 6: Test rrc_get_cell_by_pci_for_du() - DU-specific PCI lookup */ + nr_rrc_cell_container_t *cell_in_du = rrc_get_cell_by_pci_for_du(&du1->cells, PCI_10); + AssertFatal(cell_in_du != NULL, "rrc_get_cell_by_pci_for_du should find cell with PCI %d in DU1", PCI_10); + AssertFatal(cell_in_du->info.cell_id == CELL_ID_1, + "Found cell should have correct cell_id (expected %lu, got %lu)", + CELL_ID_1, + cell_in_du->info.cell_id); + cell_in_du = rrc_get_cell_by_pci_for_du(&du2->cells, PCI_2); + AssertFatal(cell_in_du != NULL, "rrc_get_cell_by_pci_for_du should find cell with PCI %d in DU2", PCI_2); + AssertFatal(cell_in_du->info.cell_id == CELL_ID_4, + "Found cell should have correct cell_id (expected %lu, got %lu)", + CELL_ID_4, + cell_in_du->info.cell_id); + // Test non-existing PCI in DU + cell_in_du = rrc_get_cell_by_pci_for_du(&du1->cells, PCI_NONEXISTENT); + AssertFatal(cell_in_du == NULL, "rrc_get_cell_by_pci_for_du should return NULL for non-existing PCI"); + // Test PCI from different DU (should not find it in this DU) + cell_in_du = rrc_get_cell_by_pci_for_du(&du1->cells, PCI_2); + AssertFatal(cell_in_du == NULL, "rrc_get_cell_by_pci_for_du should return NULL for PCI from different DU"); + + /* Sub-test 8: Test PCI reuse across different DUs (allowed globally) */ + // Add a cell to DU2 with the same PCI as a cell in DU1 (PCI reuse is allowed globally) + nr_rrc_cell_container_t *cell_on_du2_same_pci = create_test_cell(CELL_ID_6, PCI_10, ASSOC_ID_DU2); + nr_rrc_cell_container_t *already_in_tree = get_cell_by_cell_id(&rrc.cells, CELL_ID_6); + AssertFatal(already_in_tree == NULL, "Cell ID %lu should not exist before insertion", CELL_ID_6); + // Insert into global cell tree (PCI reuse is allowed) + nr_rrc_cell_container_t *tree_collision = rrc_add_cell(&rrc, cell_on_du2_same_pci); + AssertFatal(tree_collision == NULL, "rrc_add_cell should succeed for new cell (ID %lu)", CELL_ID_6); + // Add to DU2 (should succeed - PCI reuse across DUs is allowed) + nr_rrc_cell_container_t *added_to_du2 = rrc_add_cell_to_du(&du2->cells, cell_on_du2_same_pci); + AssertFatal(added_to_du2 != NULL, "rrc_add_cell_to_du should succeed for cell with PCI reused from different DU"); + // Verify both cells with same PCI exist in global tree (fetch by cell_id) + nr_rrc_cell_container_t *cell1 = get_cell_by_cell_id(&rrc.cells, CELL_ID_1); + nr_rrc_cell_container_t *cell6 = get_cell_by_cell_id(&rrc.cells, CELL_ID_6); + AssertFatal(cell1 != NULL && cell6 != NULL, "Both cells with same PCI should exist in global tree"); + AssertFatal(cell1->info.pci == PCI_10 && cell6->info.pci == PCI_10, "Both cells should have PCI %d", PCI_10); + // get_cell_by_pci returns first match only; verify it returns one of them + nr_rrc_cell_container_t *first_match_global = get_cell_by_pci(&rrc.cells, PCI_10); + AssertFatal(first_match_global != NULL, "get_cell_by_pci should find a cell with PCI %d", PCI_10); + AssertFatal(first_match_global == cell1 || first_match_global == cell6, + "get_cell_by_pci should return one of the two cells with PCI %d", + PCI_10); + // Verify each DU can find its own cell by PCI + nr_rrc_cell_container_t *cell_in_du1 = rrc_get_cell_by_pci_for_du(&du1->cells, PCI_10); + AssertFatal(cell_in_du1 != NULL && cell_in_du1->info.cell_id == CELL_ID_1, "DU1 should find its own cell with PCI %d", PCI_10); + nr_rrc_cell_container_t *cell_in_du2 = rrc_get_cell_by_pci_for_du(&du2->cells, PCI_10); + AssertFatal(cell_in_du2 != NULL && cell_in_du2->info.cell_id == CELL_ID_6, "DU2 should find its own cell with PCI %d", PCI_10); + + /* Sub-test 9: Test PCI reuse within same DU (should be rejected) */ + nr_rrc_cell_container_t *cell_with_duplicate_pci = create_test_cell(CELL_ID_NONEXISTENT, PCI_10, ASSOC_ID_DU1); + // Try to add to DU1 - should fail because PCI_10 already exists in DU1 + nr_rrc_cell_container_t *add_result = rrc_add_cell_to_du(&du1->cells, cell_with_duplicate_pci); + AssertFatal(add_result == NULL, "rrc_add_cell_to_du should reject cell with duplicate PCI within same DU"); + rrc_free_cell_container(cell_with_duplicate_pci); + + /* Sub-test 10: Verify cell counts per DU */ + static const int expected_du1_cells = 3; + static const int expected_du2_cells = 3; // includes cell_on_du2_same_pci + size_t count_du1 = seq_arr_size(&du1->cells); + size_t count_du2 = seq_arr_size(&du2->cells); + AssertFatal(count_du1 == expected_du1_cells, "DU 1 should have %d cells (got %zu)", expected_du1_cells, count_du1); + AssertFatal(count_du2 == expected_du2_cells, "DU 2 should have %d cells (got %zu)", expected_du2_cells, count_du2); + + /* Sub-test 11: Test UE cell association - create test UE context */ + gNB_RRC_UE_t ue = {0}; + ue.rrc_ue_id = UE_ID; + seq_arr_init(&ue.serving_cells, sizeof(ue_serving_cell_t)); + + /* Register F1 UE data for this UE at the CU */ + f1_ue_data_t f1_data = { + .secondary_ue = 0, + .e1_assoc_id = 0, + .du_assoc_id = ASSOC_ID_DU1, + }; + bool f1_added = cu_add_f1_ue_data(UE_ID, &f1_data); + AssertFatal(f1_added, "cu_add_f1_ue_data should succeed for test UE"); + + /* Sub-test 12: Test rrc_add_ue_serving_cell() - Add PCell, ue_get_pcell_entry(), rrc_get_pcell_for_ue() */ + ue_serving_cell_t *added_pcell = rrc_add_ue_serving_cell(&ue, cells[0].cell, RRC_PCELL_INDEX); + AssertFatal(added_pcell != NULL && added_pcell->nci == CELL_ID_1 && added_pcell->serving_cell_id == RRC_PCELL_INDEX, + "rrc_add_ue_serving_cell should succeed"); + const ue_serving_cell_t *pcell_entry = ue_get_pcell_entry(&ue); + AssertFatal(pcell_entry != NULL && pcell_entry->nci == CELL_ID_1 && pcell_entry->serving_cell_id == RRC_PCELL_INDEX, + "ue_get_pcell_entry should return correct PCell entry"); + nr_rrc_cell_container_t *pcell = rrc_get_pcell_for_ue(&rrc, &ue); + AssertFatal(pcell == cells[0].cell, "rrc_get_pcell_for_ue should return same cell as PCell"); + + /* Sub-test 13: Test adding SCells and rrc_get_ue_serving_cell_by_id() */ + ue_serving_cell_t *added_scell = rrc_add_ue_serving_cell(&ue, cells[1].cell, SERVING_CELL_ID_SCELL1); + AssertFatal(added_scell != NULL && added_scell->serving_cell_id == SERVING_CELL_ID_SCELL1, + "rrc_add_ue_serving_cell should succeed for SCell"); + AssertFatal(added_scell->assoc_id == ASSOC_ID_DU1, + "SCell should have correct assoc_id (expected %d, got %d)", + ASSOC_ID_DU1, + added_scell->assoc_id); + // Test rrc_get_ue_serving_cell_by_id() for PCell and SCell + ue_serving_cell_t *found_serving_cell = rrc_get_ue_serving_cell_by_id(&ue, RRC_PCELL_INDEX); + AssertFatal(found_serving_cell != NULL && found_serving_cell == added_pcell, "rrc_get_ue_serving_cell_by_id should find PCell"); + found_serving_cell = rrc_get_ue_serving_cell_by_id(&ue, SERVING_CELL_ID_SCELL1); + AssertFatal(found_serving_cell != NULL && found_serving_cell == added_scell, "rrc_get_ue_serving_cell_by_id should find SCell"); + // Test non-existing serving cell ID + ue_serving_cell_t *not_found_serving = rrc_get_ue_serving_cell_by_id(&ue, SERVING_CELL_ID_SCELL2); + AssertFatal(not_found_serving == NULL, "rrc_get_ue_serving_cell_by_id should return NULL for non-existing serving cell ID"); + static const size_t expected_serving_cells_2 = 2; + AssertFatal(seq_arr_size(&ue.serving_cells) == expected_serving_cells_2, + "UE should have %zu serving cells (got %zu)", + expected_serving_cells_2, + seq_arr_size(&ue.serving_cells)); + + // Add second SCell + nr_rrc_cell_container_t *scell2 = create_test_cell(CELL_ID_7, PCI_30, ASSOC_ID_DU1); + nr_rrc_cell_container_t *already_present = get_cell_by_cell_id(&rrc.cells, CELL_ID_7); + AssertFatal(already_present == NULL, "Cell ID %lu should not exist before insertion", CELL_ID_7); + // Insert into global cell tree + nr_rrc_cell_container_t *collision = rrc_add_cell(&rrc, scell2); + AssertFatal(collision == NULL, "rrc_add_cell should succeed for new cell (ID %lu)", CELL_ID_7); + // Insert into DU's seq_arr + nr_rrc_cell_container_t *added_scell2_du = rrc_add_cell_to_du(&du1->cells, scell2); + AssertFatal(added_scell2_du != NULL, "rrc_add_cell_to_du should succeed for new cell (ID %lu)", CELL_ID_7); + ue_serving_cell_t *added_scell2 = rrc_add_ue_serving_cell(&ue, scell2, SERVING_CELL_ID_SCELL2); + AssertFatal(added_scell2 != NULL, "rrc_add_ue_serving_cell should add new SCell"); + AssertFatal(added_scell2->serving_cell_id == SERVING_CELL_ID_SCELL2, "New SCell should have correct serving_cell_id (expected %d, got %d)", SERVING_CELL_ID_SCELL2, added_scell2->serving_cell_id); + AssertFatal(added_scell2->assoc_id == ASSOC_ID_DU1, + "SCell should have correct assoc_id (expected %d, got %d)", + ASSOC_ID_DU1, + added_scell2->assoc_id); + + /* Sub-test 14: Test multiple PCell enforcement - adding another PCell should fail if one already exists */ + nr_rrc_cell_container_t *test_cell = create_test_cell(CELL_ID_NONEXISTENT, PCI_NONEXISTENT, ASSOC_ID_DU1); + AssertFatal(rrc_add_ue_serving_cell(&ue, test_cell, RRC_PCELL_INDEX) == NULL, + "Adding another PCell when one already exists should fail"); + AssertFatal(rrc_get_pcell_for_ue(&rrc, &ue) == cells[0].cell, + "PCell should still be the original one when adding duplicate PCell fails"); + rrc_free_cell_container(test_cell); + + /* Sub-test 15: Test rrc_add_ue_serving_cell() with existing PCell (same nci, should return existing) */ + ue_serving_cell_t *existing_pcell = rrc_add_ue_serving_cell(&ue, cells[0].cell, RRC_PCELL_INDEX); + AssertFatal(existing_pcell != NULL && existing_pcell->nci == CELL_ID_1, + "rrc_add_ue_serving_cell should succeed for existing PCell with same nci"); + AssertFatal(existing_pcell == added_pcell, "Should return existing PCell entry, not create new one"); + + /* Sub-test 16: Test maximum serving cells limit */ + static const uint64_t test_cell_id_base = 10000000; + static const uint8_t starting_serving_cell_id = 3; + nr_rrc_cell_container_t *test_cells[RRC_MAX_NUM_SERVING_CELLS] = {0}; + size_t test_cell_count = 0; + for (uint8_t i = starting_serving_cell_id; i < RRC_MAX_NUM_SERVING_CELLS; i++) { + uint64_t test_cell_id = test_cell_id_base + i; + uint16_t test_pci = PCI_40 + i; + test_cells[test_cell_count] = create_test_cell(test_cell_id, test_pci, ASSOC_ID_DU1); + // Insert into global cell tree + nr_rrc_cell_container_t *existing = rrc_add_cell(&rrc, test_cells[test_cell_count]); + if (existing != NULL) { + LOG_E(NR_RRC, "Cell %lu already exists in tree, skipping insertion\n", test_cell_id); + rrc_free_cell_container(test_cells[test_cell_count]); + test_cells[test_cell_count] = NULL; + break; + } + // Insert into DU's seq_arr + rrc_add_cell_to_du(&du1->cells, test_cells[test_cell_count]); + if (rrc_add_ue_serving_cell(&ue, test_cells[test_cell_count], i) == NULL) + break; + test_cell_count++; + } + size_t final_count = seq_arr_size(&ue.serving_cells); + AssertFatal(final_count <= RRC_MAX_NUM_SERVING_CELLS, + "Should not exceed max serving cells (got %zu, max %d)", + final_count, + RRC_MAX_NUM_SERVING_CELLS); + + /* Sub-test 17: Test rrc_remove_ue_scells_from_du() */ + size_t before_remove = seq_arr_size(&ue.serving_cells); + rrc_remove_ue_scells_from_du(&ue, ASSOC_ID_DU1); + AssertFatal(seq_arr_size(&ue.serving_cells) < before_remove, "Should remove some serving cells"); + AssertFatal(ue_get_pcell_entry(&ue) == NULL, "PCell should be removed when it has matching assoc_id"); + + /* Sub-test 18: Test rrc_get_pcell_for_ue() edge case (UE with no serving cells) */ + gNB_RRC_UE_t ue_empty = {0}; + seq_arr_init(&ue_empty.serving_cells, sizeof(ue_serving_cell_t)); + AssertFatal(rrc_get_pcell_for_ue(&rrc, &ue_empty) == NULL, + "rrc_get_pcell_for_ue should return NULL for UE with no serving cells"); + seq_arr_free(&ue_empty.serving_cells, NULL); + + LOG_A(NR_RRC, "Cell lookup test passed\n"); + + /* Sub-test 19: Cleanup */ + seq_arr_free(&ue.serving_cells, NULL); + rrc_cleanup_du(&rrc, du1); + rrc_cleanup_du(&rrc, du2); +} + +int main() +{ + // Initialize logging system + logInit(); + set_log(NR_RRC, OAILOG_INFO); + + LOG_I(NR_RRC, "Starting RRC Cell Management Tests\n"); + + // DU lookup tests + test_du_lookup(); + // Cell lookup, cell-DU association, and UE cell association tests + test_cell_lookup(); + + LOG_A(NR_RRC, "All RRC Cell Management tests passed successfully!\n"); + return 0; +} From 95d35b9b7d5fae76e2e10cce95ba1e8254ce3608 Mon Sep 17 00:00:00 2001 From: Guido Casati Date: Tue, 6 Jan 2026 16:45:52 +0100 Subject: [PATCH 010/100] RRC multi-cell: integrate cell containers into F1 setup flow Refactor F1 setup processing to create and manage cell containers during DU initialization, establishing the foundation for multi-cell architecture where cells are managed independently from DUs. Why: Cell information is currently tied to F1AP structures (f1ap_served_cell_info_t), which complicates multi-cell support. Introducing cell containers at F1 setup decouples RRC cell management from F1AP and allows cells to be managed independently while keeping existing code paths working. Key changes: 1. Cell container creation (cp_f1_served_cell_info_to_cell): New helper copying F1AP served cell info into nr_rrc_cell_container_t (cell_id, pci, plmn, tac, TDD/FDD frequency info). Extracts and stores MTC in the cell. 2. Function signature updates: get_cell_neighbour_list() and label_intra_frequency_neighbours() now take nr_rrc_cell_container_t* and use cell->info; improved logging (cell_id, PCI, SSB ARFCN). 3. valid_du_in_neighbour_configs: Removed ssb_arfcn parameter; MTC is extracted and freed inside the function. 4. F1 Setup (rrc_gNB_process_f1_setup_req): Initialize DU cell tree (RB_INIT(&du->cells)). Populate DU-only fields (gNB_DU_id, gNB_DU_name, rrc_ver). Flow: - create cell with cp_f1_served_cell_info_to_cell - add to global tree via rrc_add_cell() - add DU via rrc_add_du() - attach cell to DU with rrc_add_cell_to_du(). Store MIB/SIB1/MTC in cell. Use cell container for neighbour labeling and SIB encoding. 5. Duplicate cell detection: Use rrc_add_cell() for global cell tree insert; collision return indicates duplicate cell_id. Clearer error messages for Cell ID vs PCI conflicts. Use F1AP_CauseRadioNetwork_cell_not_available for cell conflicts. 6. DU configuration update: Use get_cell_by_cell_id() to find cell, check assoc_id, pass cell container to label_intra_frequency_neighbours(). 7. DU disconnection: Use rrc_cleanup_du() to remove cells from global tree and free resources; logging uses DU fields (gNB_DU_id, gNB_DU_name). 8. Global cell tree: Initialize rrc->cells in openair_rrc_gNB_configuration(). Backward compatibility: setup_req still built via cp_f1ap_setup_request(); du->mib, du->sib1, du->mtc remain set (shared with cell container). --- openair2/RRC/NR/rrc_gNB.c | 1 + openair2/RRC/NR/rrc_gNB_du.c | 161 +++++++++++++++++++++++------------ 2 files changed, 107 insertions(+), 55 deletions(-) diff --git a/openair2/RRC/NR/rrc_gNB.c b/openair2/RRC/NR/rrc_gNB.c index 6b40d37b36..0dab76eae2 100644 --- a/openair2/RRC/NR/rrc_gNB.c +++ b/openair2/RRC/NR/rrc_gNB.c @@ -370,6 +370,7 @@ void openair_rrc_gNB_configuration(gNB_RRC_INST *rrc, gNB_RrcConfigurationReq *c RB_INIT(&rrc->rrc_ue_head); RB_INIT(&rrc->cuups); RB_INIT(&rrc->dus); + RB_INIT(&rrc->cells); rrc->configuration = *configuration; } diff --git a/openair2/RRC/NR/rrc_gNB_du.c b/openair2/RRC/NR/rrc_gNB_du.c index 83e158546d..79ebdd89d9 100644 --- a/openair2/RRC/NR/rrc_gNB_du.c +++ b/openair2/RRC/NR/rrc_gNB_du.c @@ -161,13 +161,13 @@ static int nr_cell_id_match(const void *key, const void *element) return 1; } -static neighbour_cell_configuration_t *get_cell_neighbour_list(const gNB_RRC_INST *rrc, const f1ap_served_cell_info_t *cell_info) +static neighbour_cell_configuration_t *get_cell_neighbour_list(const gNB_RRC_INST *rrc, const nr_rrc_cell_container_t *cell) { void *base = seq_arr_front(rrc->neighbour_cell_configuration); size_t nmemb = seq_arr_size(rrc->neighbour_cell_configuration); size_t size = sizeof(neighbour_cell_configuration_t); - void *it = bsearch((void *)&cell_info->nr_cellid, base, nmemb, size, nr_cell_id_match); + void *it = bsearch((void *)&cell->info.cell_id, base, nmemb, size, nr_cell_id_match); return (neighbour_cell_configuration_t *)it; } @@ -201,28 +201,39 @@ static void is_intra_frequency_neighbour(void *ssb_arfcn, void *neighbour_cell) /** * @brief Labels neighbour cells if they are intra frequency to prepare meas config only for intra frequency ho * @param[in] rrc Pointer to RRC instance - * @param[in] cell_info Pointer to cell information + * @param[in] du Pointer to DU container + * @param[in] cell Pointer to cell container */ static void label_intra_frequency_neighbours(gNB_RRC_INST *rrc, const nr_rrc_du_container_t *du, - const f1ap_served_cell_info_t *cell_info) + const nr_rrc_cell_container_t *cell) { if (!rrc->neighbour_cell_configuration) return; - neighbour_cell_configuration_t *neighbour_cell_config = get_cell_neighbour_list(rrc, cell_info); + neighbour_cell_configuration_t *neighbour_cell_config = get_cell_neighbour_list(rrc, cell); if (!neighbour_cell_config) return; - LOG_D(NR_RRC, "HO LOG: Cell: %lu has neighbour cell configuration!\n", cell_info->nr_cellid); uint32_t ssb_arfcn = get_ssb_arfcn(du); + LOG_D(NR_RRC, + "Cell %lu (PCI %d, SSB ARFCN %u) has neighbour cell configuration, labeling intra-frequency neighbours\n", + cell->info.cell_id, + cell->info.pci, + ssb_arfcn); seq_arr_t *cell_neighbour_list = neighbour_cell_config->neighbour_cells; for_each(cell_neighbour_list, (void *)&ssb_arfcn, is_intra_frequency_neighbour); } -static bool valid_du_in_neighbour_configs(const seq_arr_t *neighbour_cell_configuration, const f1ap_served_cell_info_t *cell, int ssb_arfcn) +static bool valid_du_in_neighbour_configs(const seq_arr_t *neighbour_cell_configuration, const f1ap_served_cell_info_t *cell) { + // MTC is mandatory, but some DUs don't send it in the F1 Setup Request, so + // "tolerate" this behavior, despite it being mandatory + NR_MeasurementTimingConfiguration_t *mtc = extract_mtc(cell->measurement_timing_config, cell->measurement_timing_config_len); + int ssb_arfcn = ssb_arfcn_mtc(mtc); + ASN_STRUCT_FREE(asn_DEF_NR_MeasurementTimingConfiguration, mtc); + for (int c = 0; c < neighbour_cell_configuration->size; c++) { const neighbour_cell_configuration_t *neighbour_config = seq_arr_at(neighbour_cell_configuration, c); for (int ni = 0; ni < neighbour_config->neighbour_cells->size; ni++) { @@ -278,6 +289,44 @@ static bool valid_du_in_neighbour_configs(const seq_arr_t *neighbour_cell_config return true; } +static void cp_f1_served_cell_info_to_cell(nr_rrc_cell_container_t *dst, const f1ap_served_cell_info_t *src) +{ + DevAssert(dst != NULL); + DevAssert(src != NULL); + + // Copy basic cell information + dst->info.cell_id = src->nr_cellid; + dst->info.pci = src->nr_pci; + dst->info.plmn = src->plmn; + dst->info.tac = src->tac ? *src->tac : 0; + + // Copy frequency information based on mode + if (src->mode == F1AP_MODE_TDD) { + dst->info.mode = NR_MODE_TDD; + dst->info.tdd.dlul.band = src->tdd.freqinfo.band; + dst->info.tdd.dlul.arfcn = src->tdd.freqinfo.arfcn; + dst->info.tdd.dlul.scs = src->tdd.tbw.scs; + dst->info.tdd.dlul.nrb = src->tdd.tbw.nrb; + } else { + dst->info.mode = NR_MODE_FDD; + /* DL */ + dst->info.fdd.dl.band = src->fdd.dl_freqinfo.band; + dst->info.fdd.dl.arfcn = src->fdd.dl_freqinfo.arfcn; + dst->info.fdd.dl.scs = src->fdd.dl_tbw.scs; + dst->info.fdd.dl.nrb = src->fdd.dl_tbw.nrb; + /* UL */ + dst->info.fdd.ul.band = src->fdd.ul_freqinfo.band; + dst->info.fdd.ul.arfcn = src->fdd.ul_freqinfo.arfcn; + dst->info.fdd.ul.scs = src->fdd.ul_tbw.scs; + dst->info.fdd.ul.nrb = src->fdd.ul_tbw.nrb; + } + + // Extract and store MTC + if (src->measurement_timing_config_len > 0) { + dst->mtc = extract_mtc(src->measurement_timing_config, src->measurement_timing_config_len); + } +} + void rrc_gNB_process_f1_setup_req(f1ap_setup_req_t *req, sctp_assoc_t assoc_id) { AssertFatal(assoc_id != 0, "illegal assoc_id == 0: should be -1 (monolithic) or >0 (split)\n"); @@ -315,46 +364,22 @@ void rrc_gNB_process_f1_setup_req(f1ap_setup_req_t *req, sctp_assoc_t assoc_id) } nr_rrc_du_container_t *it = NULL; RB_FOREACH(it, rrc_du_tree, &rrc->dus) { - if (it->setup_req->gNB_DU_id == req->gNB_DU_id) { + if (it->gNB_DU_id == req->gNB_DU_id) { LOG_E(NR_RRC, "gNB-DU ID: existing DU %s on assoc_id %d already has ID %ld, rejecting requesting gNB-DU\n", - it->setup_req->gNB_DU_name, + it->gNB_DU_name, it->assoc_id, - it->setup_req->gNB_DU_id); - fail.cause = F1AP_CauseMisc_unspecified; - rrc->mac_rrc.f1_setup_failure(assoc_id, &fail); - return; - } - // note: we assume that each DU contains only one cell; otherwise, we would - // need to check every cell in the requesting DU to any existing cell. - const f1ap_served_cell_info_t *exist_info = &it->setup_req->cell[0].info; - const f1ap_served_cell_info_t *new_info = &req->cell[0].info; - if (exist_info->nr_cellid == new_info->nr_cellid || exist_info->nr_pci == new_info->nr_pci) { - LOG_E(NR_RRC, - "existing DU %s on assoc_id %d already has cellID %ld/physCellId %d, rejecting requesting gNB-DU with cellID %ld/physCellId %d\n", - it->setup_req->gNB_DU_name, - it->assoc_id, - exist_info->nr_cellid, - exist_info->nr_pci, - new_info->nr_cellid, - new_info->nr_pci); + it->gNB_DU_id); fail.cause = F1AP_CauseMisc_unspecified; rrc->mac_rrc.f1_setup_failure(assoc_id, &fail); return; } } - // MTC is mandatory, but some DUs don't send it in the F1 Setup Request, so - // "tolerate" this behavior, despite it being mandatory - NR_MeasurementTimingConfiguration_t *mtc = - extract_mtc(cell_info->measurement_timing_config, cell_info->measurement_timing_config_len); - - if (rrc->neighbour_cell_configuration - && !valid_du_in_neighbour_configs(rrc->neighbour_cell_configuration, cell_info, ssb_arfcn_mtc(mtc))) { + if (rrc->neighbour_cell_configuration && !valid_du_in_neighbour_configs(rrc->neighbour_cell_configuration, cell_info)) { LOG_E(NR_RRC, "problem with DU %ld in neighbor configuration, rejecting DU\n", req->gNB_DU_id); f1ap_setup_failure_t fail = {.cause = F1AP_CauseMisc_unspecified}; rrc->mac_rrc.f1_setup_failure(assoc_id, &fail); - ASN_STRUCT_FREE(asn_DEF_NR_MeasurementTimingConfiguration, mtc); return; } @@ -367,21 +392,41 @@ void rrc_gNB_process_f1_setup_req(f1ap_setup_req_t *req, sctp_assoc_t assoc_id) LOG_W(NR_RRC, "rejecting DU ID %ld\n", req->gNB_DU_id); fail.cause = F1AP_CauseProtocol_semantic_error; rrc->mac_rrc.f1_setup_failure(assoc_id, &fail); - ASN_STRUCT_FREE(asn_DEF_NR_MeasurementTimingConfiguration, mtc); return; } } LOG_I(NR_RRC, "Accepting DU %ld (%s), sending F1 Setup Response\n", req->gNB_DU_id, req->gNB_DU_name); LOG_I(NR_RRC, "DU uses RRC version %u.%u.%u\n", req->rrc_ver[0], req->rrc_ver[1], req->rrc_ver[2]); - // we accept the DU - nr_rrc_du_container_t *du = calloc(1, sizeof(*du)); - AssertFatal(du, "out of memory\n"); - du->assoc_id = assoc_id; + /* Create cell and add to global tree */ + nr_rrc_cell_container_t *new = calloc_or_fail(1, sizeof(*new)); + new->assoc_id = assoc_id; + cp_f1_served_cell_info_to_cell(new, cell_info); + new->mib = mib; + new->sib1 = sib1; + // Add cell to DU's cell array + nr_rrc_cell_container_t *collision = rrc_add_cell(rrc, new); + if (collision != NULL) { + nr_rrc_du_container_t *existing_du = get_du_by_assoc_id(rrc, collision->assoc_id); + const char *du_name = existing_du ? existing_du->gNB_DU_name : "unknown"; + LOG_E(NR_RRC, + "Cell ID %lu already exists in DU %s (assoc_id %d), rejecting requesting gNB-DU\n", + new->info.cell_id, + du_name, + collision->assoc_id); + rrc_free_cell_container(new); + fail.cause = F1AP_CauseRadioNetwork_cell_not_available; + rrc->mac_rrc.f1_setup_failure(assoc_id, &fail); + return; + } - /* ITTI will free the setup request message via free(). So the memory - * "inside" of the message will remain, but the "outside" container no, so - * allocate memory and copy it in */ + // DU is accepted, add it to tree and add cell to DU's cell array + nr_rrc_du_container_t *du = calloc_or_fail(1, sizeof(*du)); + du->mtc = new->mtc; // TODO: remove in upcoming commit (kept for backward compatibility) + du->assoc_id = assoc_id; + du->gNB_DU_id = req->gNB_DU_id; + du->gNB_DU_name = strdup(req->gNB_DU_name ? req->gNB_DU_name : "N/A"); + memcpy(du->rrc_ver, req->rrc_ver, sizeof(du->rrc_ver)); du->setup_req = calloc(1,sizeof(*du->setup_req)); AssertFatal(du->setup_req, "out of memory\n"); // Copy F1AP message @@ -389,9 +434,14 @@ void rrc_gNB_process_f1_setup_req(f1ap_setup_req_t *req, sctp_assoc_t assoc_id) // MIB can be null and configured later via DU Configuration Update du->mib = mib; du->sib1 = sib1; - du->mtc = mtc; - RB_INSERT(rrc_du_tree, &rrc->dus, du); - rrc->num_dus++; + // Initialize cell array for this DU + seq_arr_init(&du->cells, sizeof(nr_rrc_cell_container_t *)); + nr_rrc_du_container_t *du_collision = rrc_add_du(rrc, du); + AssertFatal(du_collision == NULL, "rrc_add_du should succeed for new DU (assoc_id %d)", assoc_id); + + nr_rrc_cell_container_t *added = rrc_add_cell_to_du(&du->cells, new); + AssertFatal(added != NULL, "Failed to add cell %ld to DU %ld\n", new->info.cell_id, du->gNB_DU_id); + LOG_I(NR_RRC, "DU %ld: Added cell %ld\n", du->gNB_DU_id, new->info.cell_id); served_cells_to_activate_t cell = { .plmn = cell_info->plmn, @@ -407,7 +457,7 @@ void rrc_gNB_process_f1_setup_req(f1ap_setup_req_t *req, sctp_assoc_t assoc_id) nr_SIBs_t *sib = (nr_SIBs_t *)seq_arr_at(sibs, i); switch (sib->SIB_type) { case 2: { - NR_SSB_MTC_t *ssbmtc = get_ssb_mtc(mtc); + NR_SSB_MTC_t *ssbmtc = get_ssb_mtc(new->mtc); sib->SIB_size = do_SIB2_NR(&sib->SIB_buffer, ssbmtc); cell.SI_msg[cell.num_SI].SI_container = sib->SIB_buffer; cell.SI_msg[cell.num_SI].SI_container_length = sib->SIB_size; @@ -420,8 +470,8 @@ void rrc_gNB_process_f1_setup_req(f1ap_setup_req_t *req, sctp_assoc_t assoc_id) } } - if (du->mib != NULL && du->sib1 != NULL) - label_intra_frequency_neighbours(rrc, du, cell_info); + if (new->mib != NULL &&new->sib1 != NULL) + label_intra_frequency_neighbours(rrc, du, new); f1ap_setup_resp_t resp = {.transaction_id = req->transaction_id, .num_cells_to_activate = 1, @@ -557,8 +607,10 @@ void rrc_gNB_process_f1_du_configuration_update(f1ap_gnb_du_configuration_update } } if (du->mib != NULL && du->sib1 != NULL) { - const f1ap_served_cell_info_t *cell_info = &du->setup_req->cell[0].info; - label_intra_frequency_neighbours(rrc, du, cell_info); + nr_rrc_cell_container_t *cell = get_cell_by_cell_id(&rrc->cells, conf_up->cell_to_modify[0].old_nr_cellid); + if (cell != NULL && cell->assoc_id == du->assoc_id) { + label_intra_frequency_neighbours(rrc, du, cell); + } } } @@ -599,10 +651,9 @@ void rrc_CU_process_f1_lost_connection(gNB_RRC_INST *rrc, f1ap_lost_connection_t if (du->setup_req) free_f1ap_setup_request(du->setup_req); free(du->setup_req); - nr_rrc_du_container_t *removed = RB_REMOVE(rrc_du_tree, &rrc->dus, du); - DevAssert(removed != NULL); - free(du); - rrc->num_dus--; + + // Clean up DU and associated cell resources + rrc_cleanup_du(rrc, du); int num = invalidate_du_connections(rrc, assoc_id); if (num > 0) { From 15c79b0288696f08b110dcd4314422572fdbde1f Mon Sep 17 00:00:00 2001 From: Guido Casati Date: Tue, 6 Jan 2026 12:15:54 +0100 Subject: [PATCH 011/100] RRC multi-cell: adopt cell management in F1 DU configuration update procedures Migrate F1 DU configuration update procedures in openair2/RRC/NR/rrc_gNB_du.c to use the cell management API instead of accessing cell data through setup_req, completing the transition to the new multi-cell architecture. 1. update_cell_info: - Refactored to use cell management API: takes RRC instance, old cell_id, and new cell info; finds cell via get_cell_by_cell_id() internally. - Returns cell container pointer (or NULL on failure) instead of void. - When cell_id changes: removes cell from RB tree (old key), updates via cp_f1_served_cell_info_to_cell(), re-inserts (new key); validates no duplicate cell_id/PCI before allowing change. - Frees old MTC when new measurement timing config is present. - Encapsulates cell lookup and update logic. 2. rrc_gNB_process_f1_du_configuration_update: - Uses update_cell_info() for cell modify; validates cell belongs to DU (assoc_id) after update. - Duplicate checking via get_cell_by_cell_id() and rrc_get_cell_by_pci_for_du() - For cells to add: validates duplicate cell_id globally and duplicate PCI within DU; rejects with logging. - MIB/SIB1 updated in cell container (cell->mib, cell->sib1); uses du->gNB_DU_id; passes cell to label_intra_frequency_neighbours(); removes direct setup_req->cell[0] access. Backward compatibility: setup_req remains until migration is completed; cell-specific data is managed in the cell container and accessed via the cell management API. Improves separation of concerns and enables proper multi-cell support. --- openair2/RRC/NR/rrc_gNB_du.c | 160 ++++++++++++++++++++++++++--------- 1 file changed, 120 insertions(+), 40 deletions(-) diff --git a/openair2/RRC/NR/rrc_gNB_du.c b/openair2/RRC/NR/rrc_gNB_du.c index 79ebdd89d9..6024079936 100644 --- a/openair2/RRC/NR/rrc_gNB_du.c +++ b/openair2/RRC/NR/rrc_gNB_du.c @@ -500,6 +500,7 @@ static int invalidate_du_connections(gNB_RRC_INST *rrc, sctp_assoc_t assoc_id) 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); + f1_ue_data_t ue_data = cu_get_f1_ue_data(ue_id); if (ue_data.du_assoc_id != assoc_id) continue; /* this UE is on another DU */ @@ -526,26 +527,77 @@ static int invalidate_du_connections(gNB_RRC_INST *rrc, sctp_assoc_t assoc_id) return count; } -static void update_cell_info(nr_rrc_du_container_t *du, const f1ap_served_cell_info_t *new_ci) +/** @brief Update cell information from F1AP DU configuration update + * @param[in] rrc RRC instance (needed to get DU and check PCI duplicates) + * @param[in] old_nci The cell_id used to find the cell in the tree + * @param[in] new_ci Pointer to the new cell information from F1AP message + * @return Pointer to the updated cell container, or NULL on failure + * @note RB tree manipulation: The cell tree is keyed by cell_id. If the cell_id changes + * during update, the node must be removed from the tree before updating (using the + * old cell_id) and re-inserted after updating (using the new cell_id). This is + * necessary because RB trees maintain ordering based on the key value. If cell_id + * doesn't change, no tree manipulation is needed. */ +static nr_rrc_cell_container_t *update_cell_info(gNB_RRC_INST *rrc, const uint64_t old_nci, const f1ap_served_cell_info_t *new_ci) { - DevAssert(du != NULL); + DevAssert(rrc != NULL); DevAssert(new_ci != NULL); - AssertFatal(du->setup_req->num_cells_available == 1, "expected 1 cell for DU, but has %d\n", du->setup_req->num_cells_available); - f1ap_served_cell_info_t *ci = &du->setup_req->cell[0].info; - // make sure no memory is allocated - free_f1ap_cell(ci, NULL); - *ci = copy_f1ap_served_cell_info(new_ci); - - NR_MeasurementTimingConfiguration_t *new_mtc = - extract_mtc(new_ci->measurement_timing_config, new_ci->measurement_timing_config_len); - if (new_mtc != NULL) { - ASN_STRUCT_FREE(asn_DEF_NR_MeasurementTimingConfiguration, du->mtc); - du->mtc = new_mtc; - } else { - LOG_E(NR_RRC, "error decoding MeasurementTimingConfiguration during cell update, ignoring new config\n"); - ASN_STRUCT_FREE(asn_DEF_NR_MeasurementTimingConfiguration, new_mtc); + struct rrc_cell_tree *cells = &rrc->cells; + // Find the cell by old cell_id from the update message + nr_rrc_cell_container_t *cell = get_cell_by_cell_id(cells, old_nci); + if (cell == NULL) { + LOG_W(NR_RRC, "no cell with ID %ld found, ignoring gNB-DU configuration update\n", old_nci); + return NULL; } + + // Check if PCI will change - if so, check for duplicate PCI within the same DU + bool pci_changed = (cell->info.pci != new_ci->nr_pci); + if (pci_changed) { + nr_rrc_du_container_t *du = get_du_by_assoc_id(rrc, cell->assoc_id); + if (du != NULL) { + // Check if new PCI would conflict with another cell in this DU (excluding current cell) + nr_rrc_cell_container_t *existing_pci = rrc_get_cell_by_pci_for_du(&du->cells, new_ci->nr_pci); + if (existing_pci != NULL && existing_pci != cell) { + LOG_E(NR_RRC, + "Cannot change PCI from %d to %d: PCI %d already exists in this DU (cell ID %lu)\n", + cell->info.pci, + new_ci->nr_pci, + new_ci->nr_pci, + existing_pci->info.cell_id); + return NULL; + } + } + } + + // Check if cell_id will change - if so, need to update tree structure + bool cell_id_changed = (old_nci != new_ci->nr_cellid); + if (cell_id_changed) { + // Check if new cell_id would create a duplicate (excluding current cell) + nr_rrc_cell_container_t *existing = get_cell_by_cell_id(cells, new_ci->nr_cellid); + if (existing != NULL && existing != cell) { + LOG_E(NR_RRC, "Cannot change cell_id from %ld to %ld: new cell_id already exists\n", old_nci, new_ci->nr_cellid); + return NULL; + } + // Remove from tree before updating cell_id + nr_rrc_cell_container_t *removed = RB_REMOVE(rrc_cell_tree, cells, cell); + AssertFatal(removed == cell, "Failed to remove cell %ld from tree\n", old_nci); + } + + // Free old MTC before updating + if (new_ci->measurement_timing_config_len > 0) + ASN_STRUCT_FREE(asn_DEF_NR_MeasurementTimingConfiguration, cell->mtc); + + // Update cell information from the new F1AP served cell info + cp_f1_served_cell_info_to_cell(cell, new_ci); + + // If cell_id changed, re-insert into tree with new cell_id + if (cell_id_changed) { + nr_rrc_cell_container_t *existing = RB_INSERT(rrc_cell_tree, cells, cell); + AssertFatal(existing == NULL, "Failed to insert cell %ld into tree\n", cell->info.cell_id); + LOG_I(NR_RRC, "Updated cell_id from %ld to %ld\n", old_nci, cell->info.cell_id); + } + + return cell; } void rrc_gNB_process_f1_du_configuration_update(f1ap_gnb_du_configuration_update_t *conf_up, sctp_assoc_t assoc_id) @@ -562,55 +614,83 @@ void rrc_gNB_process_f1_du_configuration_update(f1ap_gnb_du_configuration_update nr_rrc_du_container_t *du = get_du_by_assoc_id(rrc, assoc_id); AssertError(du != NULL, return, "no DU found for assoc_id %d\n", assoc_id); - - const f1ap_served_cell_info_t *info = &du->setup_req->cell[0].info; - if (conf_up->num_cells_to_add > 0) { - // Here we check if the number of cell limit is respectet, otherwise send failure - LOG_W(NR_RRC, "du_configuration_update->cells_to_add_list is not supported yet"); + // Validate each cell to add for uniqueness (cell_id globally; PCI within this DU) + for (uint16_t i = 0; i < conf_up->num_cells_to_add; i++) { + f1ap_served_cell_info_t *cell_info = &conf_up->cell_to_add[i].info; + // Check for duplicate cell_id globally + nr_rrc_cell_container_t *existing = get_cell_by_cell_id(&rrc->cells, cell_info->nr_cellid); + if (existing != NULL) { + nr_rrc_du_container_t *existing_du = get_du_by_assoc_id(rrc, existing->assoc_id); + const char *du_name = existing_du ? existing_du->gNB_DU_name : "unknown"; + LOG_E(NR_RRC, + "Cell ID %lu already exists in DU %s (assoc_id %d), rejecting cell addition\n", + cell_info->nr_cellid, + du_name, + existing->assoc_id); + /** @todo send failure message */ + return; + } + // Check for duplicate PCI within this DU + nr_rrc_cell_container_t *existing_pci = rrc_get_cell_by_pci_for_du(&du->cells, cell_info->nr_pci); + if (existing_pci != NULL) { + LOG_E(NR_RRC, + "PCI %d already exists in this DU (cell ID %lu), rejecting cell addition (cell ID %lu)\n", + cell_info->nr_pci, + existing_pci->info.cell_id, + cell_info->nr_cellid); + /** @todo send failure message */ + return; + } } if (conf_up->num_cells_to_modify > 0) { // here the old nrcgi is used to find the cell information, if it exist then we modify consequently otherwise we fail AssertFatal(conf_up->num_cells_to_modify == 1, "cannot handle more than one cell!\n"); - - if (info->nr_cellid != conf_up->cell_to_modify[0].old_nr_cellid) { - LOG_W(NR_RRC, "no cell with ID %ld found, ignoring gNB-DU configuration update\n", conf_up->cell_to_modify[0].old_nr_cellid); - return; - } + const uint64_t old_nci = conf_up->cell_to_modify[0].old_nr_cellid; + const f1ap_served_cell_info_t *new_ci = &conf_up->cell_to_modify[0].info; // verify the new plmn of the cell - if (!rrc_gNB_plmn_matches(rrc, &conf_up->cell_to_modify[0].info)) { + if (!rrc_gNB_plmn_matches(rrc, new_ci)) { LOG_W(NR_RRC, "PLMN does not match, ignoring gNB-DU configuration update\n"); return; } - update_cell_info(du, &conf_up->cell_to_modify[0].info); + nr_rrc_cell_container_t *cell = update_cell_info(rrc, old_nci, new_ci); + if (cell == NULL) { + LOG_W(NR_RRC, "Failed to update cell %ld, ignoring gNB-DU configuration update\n", old_nci); + return; + } + if (cell->assoc_id != du->assoc_id) { + LOG_W(NR_RRC, + "cell %ld belongs to different DU (assoc_id %d vs %d), ignoring gNB-DU configuration update\n", + old_nci, + cell->assoc_id, + du->assoc_id); + return; + } const f1ap_gnb_du_system_info_t *sys_info = conf_up->cell_to_modify[0].sys_info; if (sys_info != NULL && sys_info->mib != NULL && !(sys_info->sib1 == NULL && IS_SA_MODE(get_softmodem_params()))) { // MIB is mandatory, so will be overwritten. SIB1 is optional, so will // only be overwritten if present in sys_info - ASN_STRUCT_FREE(asn_DEF_NR_MIB, du->mib); + ASN_STRUCT_FREE(asn_DEF_NR_MIB, cell->mib); if (sys_info->sib1 != NULL) { - ASN_STRUCT_FREE(asn_DEF_NR_SIB1, du->sib1); - du->sib1 = NULL; + ASN_STRUCT_FREE(asn_DEF_NR_SIB1, cell->sib1); + cell->sib1 = NULL; } NR_MIB_t *mib = NULL; - if (!extract_sys_info(sys_info, &mib, &du->sib1)) { - LOG_W(NR_RRC, "cannot update sys_info for DU %ld\n", du->setup_req->gNB_DU_id); + if (!extract_sys_info(sys_info, &mib, &cell->sib1)) { + LOG_W(NR_RRC, "cannot update sys_info for DU %ld\n", du->gNB_DU_id); } else { DevAssert(mib != NULL); - du->mib = mib; - LOG_I(NR_RRC, "update system information of DU %ld\n", du->setup_req->gNB_DU_id); + cell->mib = mib; + LOG_I(NR_RRC, "update system information of DU %ld\n", du->gNB_DU_id); } } - if (du->mib != NULL && du->sib1 != NULL) { - nr_rrc_cell_container_t *cell = get_cell_by_cell_id(&rrc->cells, conf_up->cell_to_modify[0].old_nr_cellid); - if (cell != NULL && cell->assoc_id == du->assoc_id) { - label_intra_frequency_neighbours(rrc, du, cell); - } + if (du->mib != NULL && du->sib1 != NULL && cell != NULL) { + label_intra_frequency_neighbours(rrc, du, cell); } } From 5980dbcb408ca1c8a9bbcb0c0420be5d984c7500 Mon Sep 17 00:00:00 2001 From: Guido Casati Date: Tue, 6 Jan 2026 21:39:25 +0100 Subject: [PATCH 012/100] RRC multi-cell: use cell lookups in NGAP Handover Request handling In rrc_gNB_NGAP.c, rrc_gNB_process_Handover_Request() is updated to resolve the target by cell instead of by DU, then resolve the DU from the cell's assoc_id. This aligns Handover Request with the new cell-centric model and cell management APIs. - Resolve target with get_cell_by_cell_id(&rrc->cells, msg->nr_cell_id) instead of get_du_by_cell_id(); on failure, log "no cell found" and send NGAP handover failure. - Resolve DU with get_du_by_assoc_id(rrc, cell->assoc_id); add a failure path (log, NGAP handover failure, return -1) when no DU exists for the cell's assoc_id. - Use cell->info (nr_rrc_cell_info_t) for logging and for key derivation (nr_derive_key_ng_ran_star); use du->assoc_id for UE context creation instead of du->setup_req->cell[0].info. get_ssb_arfcn(du) is unchanged and still takes the DU; other DU-based lookups are kept where still required. --- openair2/RRC/NR/rrc_gNB_NGAP.c | 29 +++++++++++++++++++++-------- 1 file changed, 21 insertions(+), 8 deletions(-) diff --git a/openair2/RRC/NR/rrc_gNB_NGAP.c b/openair2/RRC/NR/rrc_gNB_NGAP.c index bf8a797a49..acb556d8f5 100644 --- a/openair2/RRC/NR/rrc_gNB_NGAP.c +++ b/openair2/RRC/NR/rrc_gNB_NGAP.c @@ -90,6 +90,7 @@ #include "uper_encoder.h" #include "rrc_gNB_mobility.h" #include "rrc_gNB_du.h" +#include "rrc_cell_management.h" #include "common/utils/alg/find.h" #ifdef E2_AGENT @@ -1102,10 +1103,11 @@ int rrc_gNB_process_Handover_Request(gNB_RRC_INST *rrc, instance_t instance, nga return -1; } - struct nr_rrc_du_container_t *du = get_du_by_cell_id(rrc, msg->nr_cell_id); - if (du == NULL) { + // Get cell by cell_id + nr_rrc_cell_container_t *cell = get_cell_by_cell_id(&rrc->cells, msg->nr_cell_id); + if (cell == NULL) { /* Cell Not Found! Return HO Request Failure*/ - LOG_E(RRC, "Failed to process Handover Request: no DU found with NR Cell ID=%lu \n", msg->nr_cell_id); + LOG_E(RRC, "Failed to process Handover Request: no cell found with NR Cell ID=%lu \n", msg->nr_cell_id); ngap_handover_failure_t fail = { .amf_ue_ngap_id = msg->amf_ue_ngap_id, .cause.type = NGAP_CAUSE_RADIO_NETWORK, @@ -1115,6 +1117,18 @@ int rrc_gNB_process_Handover_Request(gNB_RRC_INST *rrc, instance_t instance, nga return -1; } + struct nr_rrc_du_container_t *du = get_du_by_assoc_id(rrc, cell->assoc_id); + if(du == NULL) { + LOG_E(NR_RRC, "Failed to process Handover Request: no DU found with assoc_id=%d\n", cell->assoc_id); + ngap_handover_failure_t fail = { + .amf_ue_ngap_id = msg->amf_ue_ngap_id, + .cause.type = NGAP_CAUSE_RADIO_NETWORK, + .cause.value = NGAP_CAUSE_RADIO_NETWORK_HO_FAILURE_IN_TARGET_5GC_NGRAN_NODE_OR_TARGET_SYSTEM, + }; + rrc_gNB_send_NGAP_HANDOVER_FAILURE(rrc, &fail); + return -1; + } + // Validate PLMN from GUAMI against allowed PLMN list const plmn_id_t *serving_plmn = get_serving_plmn(rrc, &msg->guami.plmn); if (!serving_plmn) { @@ -1128,12 +1142,11 @@ int rrc_gNB_process_Handover_Request(gNB_RRC_INST *rrc, instance_t instance, nga return -1; } - uint16_t pci = du->setup_req->cell[0].info.nr_pci; + uint16_t pci = cell->info.pci; LOG_I(NR_RRC, "Received Handover Request (on NR Cell ID=%lu, PCI=%u) \n", msg->nr_cell_id, pci); // Create UE context - sctp_assoc_t curr_assoc_id = du->assoc_id; - rrc_gNB_ue_context_t *ue_context_p = rrc_gNB_create_ue_context(curr_assoc_id, UINT16_MAX, rrc, UINT64_MAX, UINT32_MAX); + rrc_gNB_ue_context_t *ue_context_p = rrc_gNB_create_ue_context(du->assoc_id, UINT16_MAX, rrc, UINT64_MAX, UINT32_MAX); gNB_RRC_UE_t *UE = &ue_context_p->ue_context; // allocate context for target @@ -1159,9 +1172,9 @@ int rrc_gNB_process_Handover_Request(gNB_RRC_INST *rrc, instance_t instance, nga // Reset KgNB memset(UE->kgnb, 0, SECURITY_KEY_LENGTH); // Derive KgNB* - const f1ap_served_cell_info_t *cell_info = &du->setup_req->cell[0].info; + const nr_rrc_cell_info_t *cell_info = &cell->info; uint32_t ssb_arfcn = get_ssb_arfcn(du); - nr_derive_key_ng_ran_star(cell_info->nr_pci, ssb_arfcn, UE->nh, UE->kgnb); + nr_derive_key_ng_ran_star(cell_info->pci, ssb_arfcn, UE->nh, UE->kgnb); UE->as_security_active = true; // Activate SRBs activate_srb(UE, SRB1); From d4710466230af3876c69298160478d9400afcd45 Mon Sep 17 00:00:00 2001 From: Guido Casati Date: Tue, 6 Jan 2026 12:30:07 +0100 Subject: [PATCH 013/100] RRC multi-cell: complete migration to cell container architecture MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Finalize the migration from single-cell-per-DU to the multi-cell architecture: remove legacy DU/cell fields and refactor RRC procedures to use cell containers and UE serving cells instead of DU-based lookups. Data structure changes: - Remove nr_cellid from gNB_RRC_UE_t; cell identity comes from the UE's serving_cells (PCell entry via ue_get_pcell_entry(ue)). UE_LOG_ARGS and LOG_UE_* macros take a PCell entry pointer for null-safe cell_id in logs; all call sites pass ue_get_pcell_entry(ue). - Remove setup_req, mib, sib1, mtc from nr_rrc_du_container_t (held in cell containers); remove nr_cellid from gNB_RRC_INST and its assignment in gnb_config.c. Function and signature updates: - get_ssb_arfcn(): take cell container instead of DU. - label_intra_frequency_neighbours(): drop du parameter, use cell only. - Handover: Handover context (source and target) now stores cell pointers (source->cell, target->cell) instead of DU pointers. When the context is created (N2: on Handover Request; F1: in nr_rrc_trigger_f1_ho), the target cell is set in ho_context->target->cell and, for F1, the source cell in ho_context->source->cell. nr_initiate_handover() takes only source_cell and reads the target from ho_ctx->target->cell, so it no longer takes a target parameter. The target is known at handover creation time, while the UE’s serving_cells are only updated after F1 UE Context Setup Response. - nr_rrc_trigger_f1_ho() and the N2 handover path pass cell pointers and set ho_context->target->cell (and for F1, source->cell) when the handover is triggered. Procedure refactoring: - Lookups: replace get_du_by_cell_id() with get_cell_by_cell_id()/ get_cell_by_pci(); remove get_du_by_cell_id() and get_cell_information_by_phycellId(). Rationale: cell-specific data (MIB, SIB1, mtc, info) now lives in cell containers, and a DU can host multiple cells. Callers need the cell (to use cell->info, cell->mtc, etc.), not the DU. Looking up by cell_id therefore returns the cell container (get_cell_by_cell_id()); when the DU is needed, use cell->assoc_id and get_du_by_assoc_id(). Looking up by PCI is scoped (e.g. get_cell_by_pci_for_du() for a given DU, or via the UE’s serving cells) because PCI is not globally unique. - Setup/Reestablishment: resolve cell, register as PCell via rrc_add_ue_serving_cell(); reestablishment resolves cell by PCI (rrc_get_cell_by_pci_for_du), then rrc_remove_ue_scells_from_du + rrc_add_ue_serving_cell; validate_ue_cell_association() after update. - Measurement config: use cell->info (SCS/band) and cell->mtc. - Handover: use cell containers end-to-end; after UE context modification response call nr_rrc_update_cell_assoc_after_ho(). - NGAP: cell_id and TAC from UE PCell via rrc_get_pcell_for_ue(); Handover Notify: get cell for message content, use get_du_for_ue() for DU existence check; paging resolves UE by 5G-S-TMSI-Part1 and uses that UE's PCell (rrc_get_pcell_for_ue) for F1AP_PAGING_IND nr_cellid. Performance and robustness: - Use rrc_get_pcell_for_ue() when cell data is needed; use get_du_for_ue() when only the DU is needed (F1/N2 HO trigger, Handover Notify DU check) for efficiency. - Reestablishment: remove old PCell before adding new; error handling for PCell add and validation. Code cleanup: - F1 setup: stop copying/storing setup request in DU container; F1 disconnect only frees cell containers via rrc_free_cell_container(). - dump_du_info(): iterate du->cells and show all cells per DU. - telnetsrv_ci.c: use du->gNB_DU_id directly and fix format (%ld). - UeContextSetupRequest servCellIndex set to RRC_PCELL_INDEX; remove servCellIndex from F1AP UE context modification request. --- common/utils/telnetsrv/telnetsrv_ci.c | 2 +- openair2/GNB_APP/gnb_config.c | 2 - openair2/RRC/NR/nr_rrc_defs.h | 14 +- openair2/RRC/NR/nr_rrc_proto.h | 4 +- openair2/RRC/NR/rrc_gNB.c | 233 +++++++++++++++++--------- openair2/RRC/NR/rrc_gNB_NGAP.c | 49 ++++-- openair2/RRC/NR/rrc_gNB_cuup.c | 1 + openair2/RRC/NR/rrc_gNB_du.c | 120 ++++++------- openair2/RRC/NR/rrc_gNB_du.h | 5 +- openair2/RRC/NR/rrc_gNB_mobility.c | 201 +++++++++++++++++----- openair2/RRC/NR/rrc_gNB_mobility.h | 17 +- openair2/RRC/NR/rrc_gNB_nsa.c | 26 ++- 12 files changed, 444 insertions(+), 230 deletions(-) diff --git a/common/utils/telnetsrv/telnetsrv_ci.c b/common/utils/telnetsrv/telnetsrv_ci.c index a2bcccd150..084e3b93f3 100644 --- a/common/utils/telnetsrv/telnetsrv_ci.c +++ b/common/utils/telnetsrv/telnetsrv_ci.c @@ -168,7 +168,7 @@ int fetch_du_by_ue_id(char *buf, int debug, telnet_printfunc_t prnt) nr_rrc_du_container_t *du = get_du_for_ue(RC.nrrrc[0], ue_id); if (du) { - prnt("gNB_DU_id %d is connected to ue_id %ld\n", du->setup_req->gNB_DU_id, ue_id); + prnt("gNB_DU_id %ld is connected to ue_id %ld\n", du->gNB_DU_id, ue_id); return 0; } else { ERROR_MSG_RET("No DU connected\n"); diff --git a/openair2/GNB_APP/gnb_config.c b/openair2/GNB_APP/gnb_config.c index 8edec8e84f..338f9ad8a5 100644 --- a/openair2/GNB_APP/gnb_config.c +++ b/openair2/GNB_APP/gnb_config.c @@ -2077,8 +2077,6 @@ gNB_RRC_INST *RCconfig_NRRRC() rrc->eth_params_s.transp_preference = ETH_UDP_MODE; } - rrc->nr_cellid = (uint64_t)*(GNBParamList.paramarray[i][GNB_NRCELLID_IDX].u64ptr); - if (strcmp(*(GNBParamList.paramarray[i][GNB_TRANSPORT_S_PREFERENCE_IDX].strptr), "local_mac") == 0) { } else if (strcmp(*(GNBParamList.paramarray[i][GNB_TRANSPORT_S_PREFERENCE_IDX].strptr), "cudu") == 0) { diff --git a/openair2/RRC/NR/nr_rrc_defs.h b/openair2/RRC/NR/nr_rrc_defs.h index 58f9ffd1fb..cf834d2c67 100644 --- a/openair2/RRC/NR/nr_rrc_defs.h +++ b/openair2/RRC/NR/nr_rrc_defs.h @@ -222,7 +222,6 @@ typedef struct gNB_RRC_UE_s { uint64_t ng_5G_S_TMSI_Part1; NR_EstablishmentCause_t establishment_cause; - uint64_t nr_cellid; /* Dynamic array of UE serving cells */ seq_arr_t serving_cells; /* ue_serving_cell_t */ @@ -409,11 +408,6 @@ typedef struct nr_rrc_du_container_t { char *gNB_DU_name; /* RRC version */ uint8_t rrc_ver[3]; - // Cell-specific information (F1AP Setup Request, MIB, SIB1, MeasurementTimingConfiguration) - f1ap_setup_req_t *setup_req; - NR_MIB_t *mib; - NR_SIB1_t *sib1; - NR_MeasurementTimingConfiguration_t *mtc; /* Cells, indexed by cell_id */ seq_arr_t cells; /* nr_rrc_cell_container_t* */ } nr_rrc_du_container_t; @@ -436,8 +430,6 @@ typedef struct gNB_RRC_INST_s { eth_params_t eth_params_s; uid_allocator_t uid_allocator; RB_HEAD(rrc_nr_ue_tree_s, rrc_gNB_ue_context_s) rrc_ue_head; // ue_context tree key search by rnti - /// NR cell id - uint64_t nr_cellid; // RRC configuration gNB_RrcConfigurationReq configuration; @@ -471,8 +463,12 @@ typedef struct gNB_RRC_INST_s { nr_rlc_configuration_t rlc_config; } gNB_RRC_INST; +/** Forward declaration for UE log macros */ +const ue_serving_cell_t *ue_get_pcell_entry(const gNB_RRC_UE_t *ue); + #define UE_LOG_FMT "(cellID %lx, UE ID %d RNTI %04x)" -#define UE_LOG_ARGS(ue_context) (ue_context)->nr_cellid, (ue_context)->rrc_ue_id, (ue_context)->rnti +#define UE_LOG_ARGS(ue_context) \ + (ue_get_pcell_entry(ue_context) ? ue_get_pcell_entry(ue_context)->nci : 0), (ue_context)->rrc_ue_id, (ue_context)->rnti #define LOG_UE_DL_EVENT(ue_context, fmt, ...) LOG_A(NR_RRC, "[DL] " UE_LOG_FMT " " fmt, UE_LOG_ARGS(ue_context) __VA_OPT__(,) __VA_ARGS__) #define LOG_UE_EVENT(ue_context, fmt, ...) LOG_A(NR_RRC, "[--] " UE_LOG_FMT " " fmt, UE_LOG_ARGS(ue_context) __VA_OPT__(,) __VA_ARGS__) diff --git a/openair2/RRC/NR/nr_rrc_proto.h b/openair2/RRC/NR/nr_rrc_proto.h index 1db6a4d236..5838e01b47 100644 --- a/openair2/RRC/NR/nr_rrc_proto.h +++ b/openair2/RRC/NR/nr_rrc_proto.h @@ -127,11 +127,11 @@ void init_delayed_action(delayed_action_state_t *delayed_action); /** @brief Prepare and send F1AP UE Context Setup Request for a target DU * @param rrc RRC instance * @param ue UE context - * @param du Target DU container containing cell and setup information + * @param cell Target cell container containing cell information * @param ho_prep_info Optional handover preparation information (NULL if not provided) */ void rrc_f1_ue_context_setup_for_target_du(const gNB_RRC_INST *rrc, gNB_RRC_UE_t *ue, - const nr_rrc_du_container_t *du, + const nr_rrc_cell_container_t *cell, const byte_array_t *ho_prep_info); #endif diff --git a/openair2/RRC/NR/rrc_gNB.c b/openair2/RRC/NR/rrc_gNB.c index 0dab76eae2..7a85c11c3c 100644 --- a/openair2/RRC/NR/rrc_gNB.c +++ b/openair2/RRC/NR/rrc_gNB.c @@ -82,6 +82,7 @@ #include "openair2/F1AP/lib/f1ap_ue_context.h" #include "rrc_gNB_NGAP.h" #include "rrc_gNB_du.h" +#include "rrc_cell_management.h" #include "rrc_gNB_mobility.h" #include "rrc_gNB_radio_bearers.h" #include "rrc_cell_management.h" @@ -533,16 +534,18 @@ void activate_srb(gNB_RRC_UE_t *UE, int srb_id) * This helper function initializes common fields that are shared between * different UE context setup scenarios (serving DU, target DU). * @param ue UE context - * @param du DU container for measurement timing configuration + * @param cell (primary serving) cell container * @param gNB_DU_ue_id Optional gNB-DU UE ID to include in the request (NULL if not provided) * @return F1AP UE Context Setup Request */ static f1ap_ue_context_setup_req_t rrc_fill_f1_ue_context_setup(gNB_RRC_UE_t *ue, - const nr_rrc_du_container_t *du, + const nr_rrc_cell_container_t *cell, const uint32_t *gNB_DU_ue_id) { f1ap_ue_context_setup_req_t req = { .gNB_CU_ue_id = ue->rrc_ue_id, - .servCellIndex = 0, + .servCellIndex = RRC_PCELL_INDEX, + .plmn = cell->info.plmn, + .nr_cellid = cell->info.cell_id, }; /* UE Aggregate Maximum Bit Rate Uplink is C-ifDRBSetup: 1 Gbps */ @@ -550,7 +553,7 @@ static f1ap_ue_context_setup_req_t rrc_fill_f1_ue_context_setup(gNB_RRC_UE_t *ue *req.gnb_du_ue_agg_mbr_ul = 1000000000; /* bps */ /* Measurement timing configuration */ - req.cu_to_du_rrc_info.meas_timing_config = get_meas_timing_config(du->mtc, ue->measConfig); + req.cu_to_du_rrc_info.meas_timing_config = get_meas_timing_config(cell->mtc, ue->measConfig); /* UE capabilities */ if (ue->ue_cap_buffer.len > 0) { @@ -564,13 +567,6 @@ static f1ap_ue_context_setup_req_t rrc_fill_f1_ue_context_setup(gNB_RRC_UE_t *ue *req.gNB_DU_ue_id = *gNB_DU_ue_id; } - /* PLMN and cell ID from DU setup request */ - const f1ap_served_cell_info_t *cell_info = &du->setup_req->cell[0].info; - req.plmn.mcc = cell_info->plmn.mcc; - req.plmn.mnc = cell_info->plmn.mnc; - req.plmn.mnc_digit_length = cell_info->plmn.mnc_digit_length; - req.nr_cellid = cell_info->nr_cellid; - return req; } @@ -772,22 +768,24 @@ void free_RRCReconfiguration_params(nr_rrc_reconfig_param_t params) NR_MeasConfig_t *nr_rrc_get_measconfig(const gNB_RRC_INST *rrc, uint64_t nr_cellid) { - nr_rrc_du_container_t *du = get_du_by_cell_id((gNB_RRC_INST *)rrc, nr_cellid); - DevAssert(du != NULL); - f1ap_served_cell_info_t *cell_info = &du->setup_req->cell[0].info; + nr_rrc_cell_container_t *cell = get_cell_by_cell_id(&((gNB_RRC_INST *)rrc)->cells, nr_cellid); + DevAssert(cell != NULL); - if (du->mtc != NULL) { + if (cell->mtc != NULL) { NR_ReportConfigToAddMod_t *rc_PER = NULL; NR_ReportConfigToAddMod_t *rc_A2 = NULL; seq_arr_t rc_A3_seq = {0}; seq_arr_t neigh_seq = {0}; seq_arr_init(&rc_A3_seq, sizeof(NR_ReportConfigToAddMod_t)); seq_arr_init(&neigh_seq, sizeof(nr_neighbour_cell_t)); - 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; + + // Get cell info from the cell container + int scs = cell->info.mode == NR_MODE_TDD ? cell->info.tdd.dlul.scs : cell->info.fdd.dl.scs; + int band = cell->info.mode == NR_MODE_TDD ? cell->info.tdd.dlul.band : cell->info.fdd.dl.band; + + const NR_MeasTimingList_t *mtlist = cell->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_cell_config(rrc, cell_info->nr_cellid); + const neighbour_cell_configuration_t *neighbour_config = get_neighbour_cell_config(rrc, cell->info.cell_id); seq_arr_t *neighbour_cells = NULL; if (neighbour_config) neighbour_cells = neighbour_config->neighbour_cells; @@ -837,7 +835,7 @@ NR_MeasConfig_t *nr_rrc_get_measconfig(const gNB_RRC_INST *rrc, uint64_t nr_cell if (rrc->measurementConfiguration.a2_event) rc_A2 = prepare_a2_event_report(rrc->measurementConfiguration.a2_event); - NR_MeasConfig_t *result = get_MeasConfig(mt, band, scs, cell_info->nr_pci, rc_PER, rc_A2, &rc_A3_seq, &neigh_seq, neigh_a3_id); + NR_MeasConfig_t *result = get_MeasConfig(mt, band, scs, cell->info.pci, rc_PER, rc_A2, &rc_A3_seq, &neigh_seq, neigh_a3_id); // Clean up sequence arrays seq_arr_free(&rc_A3_seq, NULL); @@ -1060,12 +1058,14 @@ static void rrc_gNB_send_f1_drb_release_request(gNB_RRC_INST *rrc, gNB_RRC_UE_t f1ap_ue_context_mod_req_t req = { .gNB_CU_ue_id = ue_p->rrc_ue_id, .gNB_DU_ue_id = ue_data.secondary_ue, - .servCellIndex = 0, + .servCellIndex = RRC_PCELL_INDEX, }; req.plmn = malloc_or_fail(sizeof(*req.plmn)); *req.plmn = rrc->configuration.plmn[0]; + nr_rrc_cell_container_t *cell = rrc_get_pcell_for_ue(rrc, ue_p); + DevAssert(cell != NULL); req.nr_cellid = malloc_or_fail(sizeof(*req.nr_cellid)); - *req.nr_cellid = rrc->nr_cellid; + *req.nr_cellid = cell->info.cell_id; req.drbs_rel = malloc_or_fail(sizeof(*req.drbs_rel)); req.drbs_rel_len = n_drb_to_release; memcpy(req.drbs_rel, drb_to_release, n_drb_to_release * sizeof(int)); @@ -1192,7 +1192,7 @@ static void cuup_notify_reestablishment(gNB_RRC_INST *rrc, gNB_RRC_UE_t *ue_p) * @param du DU container containing cell information for key derivation */ static void rrc_gNB_generate_RRCReestablishment(rrc_gNB_ue_context_t *ue_context_pP, const rnti_t old_rnti, - const nr_rrc_du_container_t *du) + const nr_rrc_cell_container_t *cell) { module_id_t module_id = 0; gNB_RRC_INST *rrc = RC.nrrrc[module_id]; @@ -1200,16 +1200,19 @@ static void rrc_gNB_generate_RRCReestablishment(rrc_gNB_ue_context_t *ue_context uint8_t buffer[NR_RRC_BUF_SIZE] = {0}; uint8_t xid = rrc_gNB_get_next_transaction_identifier(module_id); ue_p->xids[xid] = RRC_REESTABLISH; - const f1ap_served_cell_info_t *cell_info = &du->setup_req->cell[0].info; - uint32_t ssb_arfcn = get_ssb_arfcn(du); - LOG_I(NR_RRC, "Reestablishment update key pci=%d, earfcn_dl=%u\n", cell_info->nr_pci, ssb_arfcn); + + nr_rrc_cell_container_t *pcell = rrc_get_pcell_for_ue(rrc, ue_p); + DevAssert(pcell != NULL); + DevAssert(pcell->info.cell_id == cell->info.cell_id); + uint32_t ssb_arfcn = get_ssb_arfcn(pcell); + LOG_I(NR_RRC, "Reestablishment update key pci=%d, earfcn_dl=%u\n", pcell->info.pci, ssb_arfcn); /* Per TS 33.501 6.9.2.3.4: When sending RRCReestablishment with nextHopChainingCount, * we're staying at the same NCC level (not advancing), so use horizontal derivation * from the currently active KgNB. Vertical derivation (from NH) is only used when * advancing to a new NCC level, which happens during handover or masterKeyUpdate. */ const uint8_t *base_key = ue_p->kgnb; - nr_derive_key_ng_ran_star(cell_info->nr_pci, ssb_arfcn, base_key, ue_p->kgnb); + nr_derive_key_ng_ran_star(pcell->info.pci, ssb_arfcn, base_key, ue_p->kgnb); int size = do_RRCReestablishment(ue_context_pP->ue_context.nh_ncc, buffer, NR_RRC_BUF_SIZE, xid); LOG_A(NR_RRC, "Send RRCReestablishment [%d bytes] to RNTI %04x\n", size, ue_p->rnti); @@ -1284,13 +1287,13 @@ static void rrc_gNB_process_RRCReestablishmentComplete(gNB_RRC_INST *rrc, /** Handle UE access on a different DU than the original one * Per 38.401 8.7: "If the UE accessed from a gNB-DU other than the original * one, the gNB-CU should trigger the UE Context Setup procedure". */ - /* UE is accessing a different DU - trigger UE Context Setup per TS 38.401 §8.7 */ - nr_rrc_du_container_t *du = get_du_by_assoc_id(rrc, assoc_id); - if (du == NULL) { - LOG_E(NR_RRC, "UE %d: cannot find DU for assoc_id %d\n", ue_p->rrc_ue_id, assoc_id); + nr_rrc_cell_container_t *cell = rrc_get_pcell_for_ue(rrc, ue_p); + // UE should have a PCell set at this point (set when RRCReestablishmentRequest was handled) + if (cell == NULL) { + LOG_E(NR_RRC, "UE %d: no PCell for reestablishment on different DU\n", ue_p->rrc_ue_id); return; } - rrc_f1_ue_context_setup_for_target_du(rrc, ue_p, du, NULL); + rrc_f1_ue_context_setup_for_target_du(rrc, ue_p, cell, NULL); } else { // Request DU to provide CellGroupConfig with reestablishRLC flags for re-establishment // According to TS 38.473 transparency requirements, CU should not construct or re-encode CellGroupConfig @@ -1406,9 +1409,17 @@ static void rrc_handle_RRCSetupRequest(gNB_RRC_INST *rrc, byte_array_t cgc = {.buf = msg->du2cu_rrc_container, .len = msg->du2cu_rrc_container_length}; store_cgc(UE, &cgc); UE->establishment_cause = rrcSetupRequest->establishmentCause; - UE->nr_cellid = msg->nr_cellid; + // Add PCell to serving_cells array + nr_rrc_cell_container_t *cell = get_cell_by_cell_id(&rrc->cells, msg->nr_cellid); + if (!cell) { + LOG_E(NR_RRC, "Cell %ld not found, rejecting UE\n", msg->nr_cellid); + rrc_gNB_generate_RRCReject(rrc, ue_context_p); + return; + } + /* Add PCell to serving_cells array */ + rrc_add_ue_serving_cell(UE, cell, RRC_PCELL_INDEX); UE->ongoing_reconfiguration = false; - UE->measConfig = nr_rrc_get_measconfig(rrc, UE->nr_cellid); + UE->measConfig = nr_rrc_get_measconfig(rrc, msg->nr_cellid); activate_srb(UE, 1); rrc_gNB_generate_RRCSetup(0, msg->crnti, ue_context_p, msg->du2cu_rrc_container, msg->du2cu_rrc_container_length); } @@ -1436,7 +1447,8 @@ static rrc_gNB_ue_context_t *rrc_gNB_get_ue_context_source_cell(gNB_RRC_INST *rr 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) + const nr_rrc_cell_container_t *source_cell = source_ctx->cell; + if (source_cell && source_ctx->old_rnti == rntiP && source_cell->info.pci == pci) return ue_context_p; } return NULL; @@ -1456,6 +1468,28 @@ static void f1u_dl_gtp_rollback(gNB_RRC_UE_t *UE) } } +/** @brief Resolve previous/old cell (UE identity physCellId): same cell, else neighbor + * of current cell, else any cell with that PCI in this DU. */ +static const nr_rrc_cell_container_t *get_previous_cell_by_pci_in_du(gNB_RRC_INST *rrc, + const nr_rrc_cell_container_t *current_cell, + const nr_rrc_du_container_t *du, + uint16_t pci) +{ + if (current_cell->info.pci == pci) + return current_cell; + + const neighbour_cell_configuration_t *neigh_config = get_neighbour_cell_config(rrc, current_cell->info.cell_id); + if (neigh_config != NULL) { + const nr_neighbour_cell_t *neighbour = get_neighbour_cell_by_pci(neigh_config, pci); + if (neighbour != NULL) { + nr_rrc_cell_container_t *c = get_cell_by_cell_id(&rrc->cells, neighbour->nrcell_id); + if (c != NULL && c->assoc_id == du->assoc_id) + return c; + } + } + return rrc_get_cell_by_pci_for_du(&du->cells, pci); +} + static void rrc_handle_RRCReestablishmentRequest(gNB_RRC_INST *rrc, sctp_assoc_t assoc_id, const NR_RRCReestablishmentRequest_IEs_t *req, @@ -1486,15 +1520,17 @@ static void rrc_handle_RRCReestablishmentRequest(gNB_RRC_INST *rrc, goto fallback_rrc_setup; } - if (du->mib == NULL || du->sib1 == NULL) { - /* we don't have MIB/SIB1 of the DU, and therefore cannot generate the - * Reestablishment (as we would need the SSB's ARFCN, which we cannot - * compute). So generate RRC Setup instead */ - LOG_E(NR_RRC, "Reestablishment request: no MIB/SIB1 of DU present, cannot do reestablishment, force setup request\n"); - goto fallback_rrc_setup; + // Fetch current cell: where the reestablishment request was received + nr_rrc_cell_container_t *current_cell = get_cell_by_cell_id(&rrc->cells, msg->nr_cellid); + if (current_cell == NULL || current_cell->assoc_id != du->assoc_id) { + LOG_E(NR_RRC, + "received CCCH message, but no corresponding cell found for nr_cellid %ld in DU (assoc_id %d)\n", + msg->nr_cellid, + assoc_id); + return; } - if (du->mtc == NULL) { + if (current_cell->mtc == NULL) { // some UEs don't send MeasurementTimingConfiguration, so we don't know the // SSB ARFCN and can't do reestablishment. handle it gracefully by doing // RRC setup procedure instead @@ -1503,7 +1539,12 @@ static void rrc_handle_RRCReestablishmentRequest(gNB_RRC_INST *rrc, goto fallback_rrc_setup; } - // Try to find UE context by RNTI and assoc_id (normal case: same DU) + const nr_rrc_cell_container_t *cell = get_previous_cell_by_pci_in_du(rrc, current_cell, du, (uint16_t)physCellId); + if (cell == NULL) { + LOG_E(NR_RRC, "received CCCH message, but no corresponding cell found for PCI %ld in DU (assoc_id %d)\n", physCellId, assoc_id); + return; + } + ue_context_p = rrc_gNB_get_ue_context_by_rnti(rrc, assoc_id, old_rnti); if (ue_context_p == NULL) { // Fallback 1: Try to find UE by RNTI only (re-establishment on different DU scenario) @@ -1526,14 +1567,12 @@ static void rrc_handle_RRCReestablishmentRequest(gNB_RRC_INST *rrc, goto fallback_rrc_setup; } - /* should check phys cell ID to identify the correct cell */ - const f1ap_served_cell_info_t *cell_info = &du->setup_req->cell[0].info; - const f1ap_served_cell_info_t *previous_cell_info = get_cell_information_by_phycellId(physCellId); f1_ue_data_t ue_data = cu_get_f1_ue_data(UE->rrc_ue_id); 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; + DevAssert(!source_ctx || source_ctx->cell != NULL); + const nr_rrc_cell_container_t *source_cell = source_ctx ? source_ctx->cell : NULL; + bool ho_reestab_on_source = source_cell ? cell->info.cell_id == source_cell->info.cell_id : false; if (ho_reestab_on_source) { /* the UE came back on the source DU while doing handover, release at @@ -1553,11 +1592,11 @@ static void rrc_handle_RRCReestablishmentRequest(gNB_RRC_INST *rrc, store_cgc(UE, &source_ctx->old_cgc); /* update to old DU assoc id -- RNTI + secondary DU UE ID further below */ - ue_data.du_assoc_id = source_ctx->du->assoc_id; + ue_data.du_assoc_id = source_cell->assoc_id; bool success = cu_update_f1_ue_data(UE->rrc_ue_id, &ue_data); DevAssert(success); nr_rrc_finalize_ho(UE); - } else if (physCellId != cell_info->nr_pci) { + } else if (physCellId != cell->info.pci) { /* Check if this is a different DU scenario or "too fast movement" scenario */ if (assoc_id != ue_data.du_assoc_id) { /* Different DU scenario - physCellId differs because UE is re-establishing on a different DU. @@ -1569,7 +1608,7 @@ static void rrc_handle_RRCReestablishmentRequest(gNB_RRC_INST *rrc, "UE %d: Re-establishment on different DU (physCellId %ld from old cell != %d from new DU, du_assoc_id %d -> %d)\n", UE->rrc_ue_id, physCellId, - cell_info->nr_pci, + cell->info.pci, ue_data.du_assoc_id, assoc_id); @@ -1608,7 +1647,7 @@ static void rrc_handle_RRCReestablishmentRequest(gNB_RRC_INST *rrc, LOG_I(NR_RRC, "RRC Reestablishment Request from different physCellId (%ld) than current physCellId (%d), fallback to RRC setup\n", physCellId, - cell_info->nr_pci); + cell->info.pci); ngap_cause = NGAP_CAUSE_RADIO_NETWORK_RELEASE_DUE_TO_NGRAN_GENERATED_REASON; goto fallback_rrc_setup; } @@ -1618,12 +1657,24 @@ static void rrc_handle_RRCReestablishmentRequest(gNB_RRC_INST *rrc, // update with new RNTI, and update secondary UE association UE->rnti = msg->crnti; - UE->nr_cellid = msg->nr_cellid; + + /* Update PCell in serving_cells array */ + DevAssert(cell->info.cell_id == msg->nr_cellid); + // Reset the serving set to the new PCell, remove all serving cells from the current PCell's DU. + const ue_serving_cell_t *existing_pcell = ue_get_pcell_entry(UE); + DevAssert(existing_pcell != NULL); + rrc_remove_ue_scells_from_du(UE, existing_pcell->assoc_id); + ue_serving_cell_t *added = rrc_add_ue_serving_cell(UE, cell, RRC_PCELL_INDEX); + if (added == NULL) { + LOG_E(NR_RRC, "Reestablishment: failed to add PCell (cell %ld), rejecting reestablishment\n", cell->info.cell_id); + goto fallback_rrc_setup; + } + ue_data.secondary_ue = msg->gNB_DU_ue_id; bool success = cu_update_f1_ue_data(UE->rrc_ue_id, &ue_data); DevAssert(success); - rrc_gNB_generate_RRCReestablishment(ue_context_p, old_rnti, du); + rrc_gNB_generate_RRCReestablishment(ue_context_p, old_rnti, cell); return; fallback_rrc_setup: @@ -1715,13 +1766,34 @@ static void process_Event_Based_Measurement_Report(gNB_RRC_INST *rrc, neighbour_pci, neighbourCellRSRP); - const f1ap_served_cell_info_t *neigh_cell = get_cell_information_by_phycellId(neighbour_pci); - const f1ap_served_cell_info_t *serving_cell = get_cell_information_by_phycellId(scell_pci); - const neighbour_cell_configuration_t *cell = get_neighbour_cell_config(rrc, serving_cell->nr_cellid); + // Resolve source cell by PCI from UE's serving cells + const nr_rrc_cell_container_t *source_cell = NULL; + FOR_EACH_SEQ_ARR (ue_serving_cell_t *, scell, &ue->serving_cells) { + nr_rrc_cell_container_t *cell_by_id = get_cell_by_cell_id(&rrc->cells, scell->nci); + if (cell_by_id != NULL && cell_by_id->info.pci == scell_pci) { + source_cell = cell_by_id; + break; + } + } + if (source_cell == NULL) { + LOG_W(NR_RRC, + "UE %d: received measurement report but source cell with PCI %d not found in UE's serving cells, skipping\n", + ue->rrc_ue_id, + scell_pci); + continue; + } + + // Resolve target cell via neighbour configuration + const neighbour_cell_configuration_t *cell = get_neighbour_cell_config(rrc, source_cell->info.cell_id); const nr_neighbour_cell_t *neighbour = get_neighbour_cell_by_pci(cell, neighbour_pci); + const nr_rrc_cell_container_t *target_cell = NULL; + if (neighbour != NULL) { + // Resolve target cell by cell_id from neighbour config (may be NULL if no F1 connection) + target_cell = get_cell_by_cell_id(&rrc->cells, neighbour->nrcell_id); + } // CU does not have f1 connection with neighbour cell context. So check does serving cell has this phyCellId as a // neighbour. - if (!neigh_cell && neighbour) { + if (!target_cell && neighbour) { // No F1 connection but static neighbour configuration is available const nr_a3_event_t *a3_event_configuration = get_a3_configuration(rrc, neighbour->physicalCellId); /* if no A3 event configured for this physical cell ID, try to get the default one */ @@ -1740,12 +1812,9 @@ static void process_Event_Based_Measurement_Report(gNB_RRC_INST *rrc, } LOG_D(NR_RRC, "HO LOG: Trigger N2 HO for the neighbour gnb: %u cell: %lu\n", neighbour->gNB_ID, neighbour->nrcell_id); } - } else if (neigh_cell && neighbour) { + } else if (target_cell && neighbour) { /* we know the cell and are connected to the DU! */ - 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); + nr_rrc_trigger_f1_ho(rrc, ue, source_cell, target_cell); } 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, neighbour_pci); } @@ -2614,12 +2683,15 @@ static void rrc_CU_process_ue_context_release_request(MessageDef *msg_p, sctp_as 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; + bool from_source_du = source_ctx && source_ctx->cell->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); + LOG_W(NR_RRC, + "UE %d: release request from source cell PCI %d during HO, marking HO as complete\n", + UE->rrc_ue_id, + source_ctx->cell->info.pci); + RETURN_IF_INVALID_ASSOC_ID(source_ctx->cell->assoc_id); f1ap_ue_context_rel_cmd_t cmd = { .gNB_CU_ue_id = UE->rrc_ue_id, .gNB_DU_ue_id = source_ctx->du_ue_id, @@ -2747,6 +2819,10 @@ static void rrc_CU_process_ue_context_modification_response(MessageDef *msg_p, i nr_ho_source_cu_t *source_ctx = UE->ho_context->source; DevAssert(source_ctx->old_rnti == UE->rnti); nr_rrc_apply_target_context(UE); + /* Update cell association after handover */ + if (!nr_rrc_update_cell_assoc_after_ho(rrc, UE)) { + return; + } } } @@ -2768,7 +2844,9 @@ static void rrc_CU_process_ue_modification_required(MessageDef *msg_p, instance_ } 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) { + bool on_source_du = UE->ho_context && UE->ho_context->source && UE->ho_context->source->cell + && UE->ho_context->source->cell->assoc_id == assoc_id; + if (on_source_du) { LOG_W(NR_RRC, "UE %d: UE Context Modification Required during handover, ignoring message\n", UE->rrc_ue_id); return; } @@ -3606,14 +3684,14 @@ static int rrc_fill_f1_drb_to_setup(const gNB_RRC_INST *rrc, const gNB_RRC_UE_t * - Handover preparation information (if provided) * @param rrc RRC instance * @param ue UE context - * @param du Target DU container containing cell and setup information + * @param cell Target cell container containing cell information * @param ho_prep_info Optional handover preparation information */ void rrc_f1_ue_context_setup_for_target_du(const gNB_RRC_INST *rrc, gNB_RRC_UE_t *ue, - const nr_rrc_du_container_t *du, + const nr_rrc_cell_container_t *cell, const byte_array_t *ho_prep_info) { - DevAssert(du != NULL); + DevAssert(cell != NULL); /* Handle two scenarios: * 1. Handover (ho_prep_info provided): Prepare and copy handover preparation information * for the target DU. The target DU will assign a new gNB-DU UE ID in the response. @@ -3635,7 +3713,7 @@ void rrc_f1_ue_context_setup_for_target_du(const gNB_RRC_INST *rrc, du_ue_id = ue_data.secondary_ue; opt_du_ue_id = &du_ue_id; } - LOG_I(NR_RRC, "Triggering UE Context Setup for UE %d on DU %d\n", ue->rrc_ue_id, du->assoc_id); + LOG_I(NR_RRC, "Triggering UE Context Setup for UE %d on DU %d\n", ue->rrc_ue_id, cell->assoc_id); int nb_srb = 2; f1ap_srb_to_setup_t *srbs = calloc_or_fail(nb_srb, sizeof(*srbs)); f1ap_drb_to_setup_t *drbs = calloc_or_fail(MAX_DRBS_PER_UE, sizeof(*drbs)); @@ -3649,12 +3727,12 @@ void rrc_f1_ue_context_setup_for_target_du(const gNB_RRC_INST *rrc, /* Update measurement config for target DU */ free_MeasConfig(ue->measConfig); - ue->measConfig = nr_rrc_get_measconfig(rrc, du->setup_req->cell[0].info.nr_cellid); + ue->measConfig = nr_rrc_get_measconfig(rrc, cell->info.cell_id); byte_array_t *meas_config = calloc_or_fail(1, sizeof(*meas_config)); meas_config->buf = calloc_or_fail(1, NR_RRC_BUF_SIZE); meas_config->len = do_NR_MeasConfig(ue->measConfig, meas_config->buf, NR_RRC_BUF_SIZE); /* Fill common fields */ - f1ap_ue_context_setup_req_t req = rrc_fill_f1_ue_context_setup(ue, du, opt_du_ue_id); + f1ap_ue_context_setup_req_t req = rrc_fill_f1_ue_context_setup(ue, cell, opt_du_ue_id); /* Fill target DU specific fields */ req.srbs_len = 2; req.srbs = srbs; @@ -3663,8 +3741,8 @@ void rrc_f1_ue_context_setup_for_target_du(const gNB_RRC_INST *rrc, req.cu_to_du_rrc_info.meas_config = meas_config; req.cu_to_du_rrc_info.ho_prep_info = hpi; - RETURN_IF_INVALID_ASSOC_ID(du->assoc_id); - rrc->mac_rrc.ue_context_setup_request(du->assoc_id, &req); + RETURN_IF_INVALID_ASSOC_ID(cell->assoc_id); + rrc->mac_rrc.ue_context_setup_request(cell->assoc_id, &req); free_ue_context_setup_req(&req); } @@ -3689,7 +3767,8 @@ void rrc_f1_ue_context_setup_from_e1_response(const gNB_RRC_INST *rrc, AssertFatal(!NODE_IS_DU(rrc->node_type), "illegal node type DU!\n"); - nr_rrc_du_container_t *du = get_du_for_ue((gNB_RRC_INST *)rrc, ue_p->rrc_ue_id); + nr_rrc_cell_container_t *cell = rrc_get_pcell_for_ue((gNB_RRC_INST *)rrc, ue_p); + DevAssert(cell != NULL); int nb_srb = 1; f1ap_srb_to_setup_t *srbs = calloc_or_fail(nb_srb, sizeof(*srbs)); @@ -3706,7 +3785,7 @@ void rrc_f1_ue_context_setup_from_e1_response(const gNB_RRC_INST *rrc, const uint32_t du_ue_id = ue_data.secondary_ue; /* Fill common fields */ - f1ap_ue_context_setup_req_t ue_context_setup_req = rrc_fill_f1_ue_context_setup(ue_p, du, &du_ue_id); + f1ap_ue_context_setup_req_t ue_context_setup_req = rrc_fill_f1_ue_context_setup(ue_p, cell, &du_ue_id); /* Fill bearers information for serving DU */ ue_context_setup_req.srbs_len = nb_srb; ue_context_setup_req.srbs = srbs; diff --git a/openair2/RRC/NR/rrc_gNB_NGAP.c b/openair2/RRC/NR/rrc_gNB_NGAP.c index acb556d8f5..515091a459 100644 --- a/openair2/RRC/NR/rrc_gNB_NGAP.c +++ b/openair2/RRC/NR/rrc_gNB_NGAP.c @@ -92,6 +92,7 @@ #include "rrc_gNB_du.h" #include "rrc_cell_management.h" #include "common/utils/alg/find.h" +#include "common/utils/nr/nr_common.h" #ifdef E2_AGENT #include "openair2/E2AP/RAN_FUNCTION/O-RAN/ran_func_rc_extern.h" @@ -255,8 +256,9 @@ void rrc_gNB_send_NGAP_NAS_FIRST_REQ(gNB_RRC_INST *rrc, gNB_RRC_UE_t *UE, NR_RRC /* UE is connected to only one PLMN at a time: store this as the serving PLMN */ UE->serving_plmn = req->plmn; - // Cell ID (NR CGI) - req->nr_cell_id = UE->nr_cellid; + nr_rrc_cell_container_t *cell = rrc_get_pcell_for_ue(rrc, UE); + DevAssert(cell); + req->nr_cell_id = cell->info.cell_id; // PLMN (NR CGI and TAI) plmn_id_t *p = &req->plmn; LOG_I(NGAP, "Selected PLMN in the NG Initial UE Message: MCC=%03d MNC=%0*d\n", p->mcc, p->mnc_digit_length, p->mnc); @@ -748,8 +750,10 @@ void rrc_gNB_send_NGAP_UPLINK_NAS(gNB_RRC_INST *rrc, gNB_RRC_UE_t *UE, const NR_ NGAP_UPLINK_NAS(msg_p).nas_pdu.buf = buf; /* Fill PLMN and location info: use serving PLMN of the UE */ NGAP_UPLINK_NAS(msg_p).plmn = UE->serving_plmn; - NGAP_UPLINK_NAS(msg_p).nr_cell_id = rrc->nr_cellid; - NGAP_UPLINK_NAS(msg_p).tac = rrc->configuration.tac; + nr_rrc_cell_container_t *cell = rrc_get_pcell_for_ue(rrc, UE); + DevAssert(cell); + NGAP_UPLINK_NAS(msg_p).nr_cell_id = cell->info.cell_id; + NGAP_UPLINK_NAS(msg_p).tac = cell->info.tac != 0 ? cell->info.tac : rrc->configuration.tac; itti_send_msg_to_task(TASK_NGAP, rrc->module_id, msg_p); } @@ -1151,6 +1155,7 @@ int rrc_gNB_process_Handover_Request(gNB_RRC_INST *rrc, instance_t instance, nga // allocate context for target UE->ho_context = alloc_ho_ctx(HO_CTX_TARGET); + UE->ho_context->target->cell = cell; UE->ho_context->target->ho_trigger = nr_rrc_trigger_n2_ho_target; // Store IDs in UE context @@ -1173,7 +1178,7 @@ int rrc_gNB_process_Handover_Request(gNB_RRC_INST *rrc, instance_t instance, nga memset(UE->kgnb, 0, SECURITY_KEY_LENGTH); // Derive KgNB* const nr_rrc_cell_info_t *cell_info = &cell->info; - uint32_t ssb_arfcn = get_ssb_arfcn(du); + uint32_t ssb_arfcn = get_ssb_arfcn(cell); nr_derive_key_ng_ran_star(cell_info->pci, ssb_arfcn, UE->nh, UE->kgnb); UE->as_security_active = true; // Activate SRBs @@ -1458,24 +1463,28 @@ void rrc_gNB_send_NGAP_HANDOVER_REQUEST_ACKNOWLEDGE(gNB_RRC_INST *rrc, gNB_RRC_U void rrc_gNB_send_NGAP_HANDOVER_NOTIFY(gNB_RRC_INST *rrc, gNB_RRC_UE_t *UE) { LOG_I(NR_RRC, "Triggering NGAP Handover Notify\n"); - nr_rrc_du_container_t *du = get_du_by_cell_id(rrc, rrc->nr_cellid); + nr_rrc_cell_container_t *cell = rrc_get_pcell_for_ue(rrc, UE); + if (cell == NULL) { + LOG_E(NR_RRC, "Failed to send Handover Notify: no PCell found for UE %d\n", UE->rrc_ue_id); + return; + } + nr_rrc_du_container_t *du = get_du_for_ue(rrc, UE->rrc_ue_id); if (du == NULL) { - LOG_E(NR_RRC, "Failed to send Handover Notify: no DU found with NR Cell ID=%lu\n", rrc->nr_cellid); + LOG_E(NR_RRC, "Failed to send Handover Notify: no DU found for UE %d\n", UE->rrc_ue_id); return; } - MessageDef *msg_p = itti_alloc_new_message(TASK_RRC_GNB, 0, NGAP_HANDOVER_NOTIFY); ngap_handover_notify_t *ho_notify = &NGAP_HANDOVER_NOTIFY(msg_p); memset(ho_notify, 0, sizeof(*ho_notify)); ho_notify->gNB_ue_ngap_id = UE->rrc_ue_id; ho_notify->amf_ue_ngap_id = UE->amf_ue_ngap_id; - ho_notify->user_info.nrCellIdentity = rrc->nr_cellid; + ho_notify->user_info.nrCellIdentity = cell->info.cell_id; target_ran_node_id_t *target_ng_ran = &ho_notify->user_info.target_ng_ran; - target_ng_ran->tac = *du->setup_req->cell->info.tac; + target_ng_ran->tac = cell->info.tac; target_ng_ran->targetgNBId = rrc->node_id; - target_ng_ran->plmn_identity = du->setup_req->cell->info.plmn; + target_ng_ran->plmn_identity = cell->info.plmn; itti_send_msg_to_task(TASK_NGAP, rrc->module_id, msg_p); } @@ -1612,7 +1621,23 @@ int rrc_gNB_process_PAGING_IND(MessageDef *msg_p, instance_t instance) if (NODE_IS_CU(RC.nrrrc[instance]->node_type)) { MessageDef *m = itti_alloc_new_message(TASK_RRC_GNB, 0, F1AP_PAGING_IND); F1AP_PAGING_IND(m).plmn = *req_plmn; - F1AP_PAGING_IND (m).nr_cellid = RC.nrrrc[j]->nr_cellid; + // Construct 5G-S-TMSI-Part1 from paging identity to find UE + const fiveg_s_tmsi_t *s_tmsi = &msg->ue_paging_identity.s_tmsi; + uint64_t s_tmsi_part1 = nr_construct_5g_s_tmsi_part1(s_tmsi->amf_set_id, s_tmsi->amf_pointer, s_tmsi->m_tmsi); + // Try to find UE by 5G-S-TMSI-Part1 + rrc_gNB_ue_context_t *ue_context_p = rrc_gNB_ue_context_5g_s_tmsi_exist(RC.nrrrc[instance], s_tmsi_part1); + if (ue_context_p == NULL) { + LOG_W(NR_RRC, "UE not found by paging identity\n"); + return -1; + } + // UE found: use its associated cell + gNB_RRC_UE_t *UE = &ue_context_p->ue_context; + nr_rrc_cell_container_t *cell = rrc_get_pcell_for_ue(RC.nrrrc[instance], UE); + if (cell == NULL) { + LOG_W(NR_RRC, "UE %d (rnti %04x) found by paging identity but has no valid cell\n", UE->rrc_ue_id, UE->rnti); + return -1; + } + F1AP_PAGING_IND(m).nr_cellid = cell->info.cell_id; F1AP_PAGING_IND(m).ueidentityindexvalue = (uint16_t)(msg->ue_paging_identity.s_tmsi.m_tmsi % 1024); F1AP_PAGING_IND(m).fiveg_s_tmsi = msg->ue_paging_identity.s_tmsi.m_tmsi; F1AP_PAGING_IND(m).paging_drx = msg->paging_drx; diff --git a/openair2/RRC/NR/rrc_gNB_cuup.c b/openair2/RRC/NR/rrc_gNB_cuup.c index 1e3df71822..8292699a5c 100644 --- a/openair2/RRC/NR/rrc_gNB_cuup.c +++ b/openair2/RRC/NR/rrc_gNB_cuup.c @@ -43,6 +43,7 @@ #include "tree.h" #include "e1ap_interface_management.h" #include "rrc_gNB_NGAP.h" +#include "rrc_cell_management.h" static int cuup_compare(const nr_rrc_cuup_container_t *a, const nr_rrc_cuup_container_t *b) { diff --git a/openair2/RRC/NR/rrc_gNB_du.c b/openair2/RRC/NR/rrc_gNB_du.c index 6024079936..5b1be47d5e 100644 --- a/openair2/RRC/NR/rrc_gNB_du.c +++ b/openair2/RRC/NR/rrc_gNB_du.c @@ -79,10 +79,15 @@ static int ssb_arfcn_mtc(const NR_MeasurementTimingConfiguration_t *mtc) return mtlist->list.array[0]->frequencyAndTiming->carrierFreq; } -int get_ssb_arfcn(const struct nr_rrc_du_container_t *du) +/** @brief Find first cell owned by this DU (assoc_id) and return its SSB ARFCN + * @note Current assumption is that each DU serves only one cell*/ +int get_ssb_arfcn(const struct nr_rrc_cell_container_t *cell) { - DevAssert(du != NULL && du->mtc != NULL); - return ssb_arfcn_mtc(du->mtc); + DevAssert(cell != NULL); + if (cell->mtc != NULL) + return ssb_arfcn_mtc(cell->mtc); + AssertFatal(false, "no mtc found for cell %ld\n", cell->info.cell_id); + return 0; } static bool rrc_gNB_plmn_matches(const gNB_RRC_INST *rrc, const f1ap_served_cell_info_t *info) @@ -172,22 +177,6 @@ static neighbour_cell_configuration_t *get_cell_neighbour_list(const gNB_RRC_INS return (neighbour_cell_configuration_t *)it; } -const struct f1ap_served_cell_info_t *get_cell_information_by_phycellId(int phyCellId) -{ - gNB_RRC_INST *rrc = RC.nrrrc[0]; - nr_rrc_du_container_t *it = NULL; - RB_FOREACH (it, rrc_du_tree, &rrc->dus) { - for (int cellIdx = 0; cellIdx < it->setup_req->num_cells_available; cellIdx++) { - const f1ap_served_cell_info_t *cell_info = &(it->setup_req->cell[cellIdx].info); - if (cell_info->nr_pci == phyCellId) { - LOG_D(NR_RRC, "HO LOG: Found cell with phyCellId %d\n", phyCellId); - return cell_info; - } - } - } - return NULL; -} - static void is_intra_frequency_neighbour(void *ssb_arfcn, void *neighbour_cell) { uint32_t *ssb_arfcn_ptr = (uint32_t *)ssb_arfcn; @@ -201,12 +190,9 @@ static void is_intra_frequency_neighbour(void *ssb_arfcn, void *neighbour_cell) /** * @brief Labels neighbour cells if they are intra frequency to prepare meas config only for intra frequency ho * @param[in] rrc Pointer to RRC instance - * @param[in] du Pointer to DU container * @param[in] cell Pointer to cell container */ -static void label_intra_frequency_neighbours(gNB_RRC_INST *rrc, - const nr_rrc_du_container_t *du, - const nr_rrc_cell_container_t *cell) +static void label_intra_frequency_neighbours(gNB_RRC_INST *rrc, const nr_rrc_cell_container_t *cell) { if (!rrc->neighbour_cell_configuration) return; @@ -215,7 +201,7 @@ static void label_intra_frequency_neighbours(gNB_RRC_INST *rrc, if (!neighbour_cell_config) return; - uint32_t ssb_arfcn = get_ssb_arfcn(du); + uint32_t ssb_arfcn = get_ssb_arfcn(cell); LOG_D(NR_RRC, "Cell %lu (PCI %d, SSB ARFCN %u) has neighbour cell configuration, labeling intra-frequency neighbours\n", cell->info.cell_id, @@ -422,18 +408,11 @@ void rrc_gNB_process_f1_setup_req(f1ap_setup_req_t *req, sctp_assoc_t assoc_id) // DU is accepted, add it to tree and add cell to DU's cell array nr_rrc_du_container_t *du = calloc_or_fail(1, sizeof(*du)); - du->mtc = new->mtc; // TODO: remove in upcoming commit (kept for backward compatibility) du->assoc_id = assoc_id; du->gNB_DU_id = req->gNB_DU_id; du->gNB_DU_name = strdup(req->gNB_DU_name ? req->gNB_DU_name : "N/A"); memcpy(du->rrc_ver, req->rrc_ver, sizeof(du->rrc_ver)); - du->setup_req = calloc(1,sizeof(*du->setup_req)); - AssertFatal(du->setup_req, "out of memory\n"); - // Copy F1AP message - *du->setup_req = cp_f1ap_setup_request(req); - // MIB can be null and configured later via DU Configuration Update - du->mib = mib; - du->sib1 = sib1; + // Initialize cell array for this DU seq_arr_init(&du->cells, sizeof(nr_rrc_cell_container_t *)); nr_rrc_du_container_t *du_collision = rrc_add_du(rrc, du); @@ -471,7 +450,7 @@ void rrc_gNB_process_f1_setup_req(f1ap_setup_req_t *req, sctp_assoc_t assoc_id) } if (new->mib != NULL &&new->sib1 != NULL) - label_intra_frequency_neighbours(rrc, du, new); + label_intra_frequency_neighbours(rrc, new); f1ap_setup_resp_t resp = {.transaction_id = req->transaction_id, .num_cells_to_activate = 1, @@ -501,6 +480,9 @@ static int invalidate_du_connections(gNB_RRC_INST *rrc, sctp_assoc_t assoc_id) if (UE->ho_context != NULL) LOG_W(NR_RRC, "DU disconnected while handover for UE %d active\n", ue_id); + // Remove SCells from this DU for this UE + rrc_remove_ue_scells_from_du(UE, assoc_id); + f1_ue_data_t ue_data = cu_get_f1_ue_data(ue_id); if (ue_data.du_assoc_id != assoc_id) continue; /* this UE is on another DU */ @@ -689,8 +671,9 @@ void rrc_gNB_process_f1_du_configuration_update(f1ap_gnb_du_configuration_update LOG_I(NR_RRC, "update system information of DU %ld\n", du->gNB_DU_id); } } - if (du->mib != NULL && du->sib1 != NULL && cell != NULL) { - label_intra_frequency_neighbours(rrc, du, cell); + + if (cell && cell->mib != NULL && cell->sib1 != NULL && cell->assoc_id == du->assoc_id) { + label_intra_frequency_neighbours(rrc, cell); } } @@ -723,15 +706,6 @@ void rrc_CU_process_f1_lost_connection(gNB_RRC_INST *rrc, f1ap_lost_connection_t return; } - f1ap_setup_req_t *req = du->setup_req; - LOG_I(NR_RRC, "releasing DU ID %ld (%s) on assoc_id %d\n", req->gNB_DU_id, req->gNB_DU_name, assoc_id); - ASN_STRUCT_FREE(asn_DEF_NR_MIB, du->mib); - ASN_STRUCT_FREE(asn_DEF_NR_SIB1, du->sib1); - ASN_STRUCT_FREE(asn_DEF_NR_MeasurementTimingConfiguration, du->mtc); - if (du->setup_req) - free_f1ap_setup_request(du->setup_req); - free(du->setup_req); - // Clean up DU and associated cell resources rrc_cleanup_du(rrc, du); @@ -749,46 +723,48 @@ nr_rrc_du_container_t *get_du_for_ue(gNB_RRC_INST *rrc, uint32_t ue_id) return get_du_by_assoc_id(rrc, ue_data.du_assoc_id); } -/* \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); - int i = 1; + int du_idx = 1; nr_rrc_du_container_t *du = NULL; - /* cast is necessary to eliminate warning "discards ‘const’ qualifier" */ + /* cast is necessary to eliminate warning "discards 'const' qualifier" */ RB_FOREACH(du, rrc_du_tree, &((gNB_RRC_INST *)rrc)->dus) { - const f1ap_setup_req_t *sr = du->setup_req; - fprintf(f, "[%d] DU ID %ld (%s) ", i++, sr->gNB_DU_id, sr->gNB_DU_name); + fprintf(f, "[%d] DU ID %ld (%s) ", du_idx++, du->gNB_DU_id, du->gNB_DU_name); if (du->assoc_id == -1) { fprintf(f, "integrated DU-CU"); } else { fprintf(f, "assoc_id %d", du->assoc_id); } - const f1ap_served_cell_info_t *info = &sr->cell[0].info; - fprintf(f, ": nrCellID %ld, PCI %d, SSB ARFCN %d\n", info->nr_cellid, info->nr_pci, get_ssb_arfcn(du)); - if (info->mode == F1AP_MODE_TDD) { - const f1ap_nr_frequency_info_t *fi = &info->tdd.freqinfo; - const f1ap_transmission_bandwidth_t *tb = &info->tdd.tbw; - fprintf(f, " TDD: band %d ARFCN %d SCS %d (kHz) PRB %d\n", fi->band, fi->arfcn, 15 * (1 << tb->scs), tb->nrb); + /* Print all cells for this DU */ + int cell_count = seq_arr_size(&du->cells); + + if (cell_count == 0) { + fprintf(f, ": no cells\n"); } else { - const f1ap_nr_frequency_info_t *dfi = &info->fdd.dl_freqinfo; - const f1ap_transmission_bandwidth_t *dtb = &info->fdd.dl_tbw; - fprintf(f, " FDD: DL band %d ARFCN %d SCS %d (kHz) PRB %d\n", dfi->band, dfi->arfcn, 15 * (1 << dtb->scs), dtb->nrb); - const f1ap_nr_frequency_info_t *ufi = &info->fdd.ul_freqinfo; - const f1ap_transmission_bandwidth_t *utb = &info->fdd.ul_tbw; - fprintf(f, " UL band %d ARFCN %d SCS %d (kHz) PRB %d\n", ufi->band, ufi->arfcn, 15 * (1 << utb->scs), utb->nrb); + fprintf(f, ": %d cell%s\n", cell_count, cell_count > 1 ? "s" : ""); + int cell_idx = 1; + FOR_EACH_SEQ_ARR (nr_rrc_cell_container_t **, cell_ptr, &du->cells) { + nr_rrc_cell_container_t *cell = *cell_ptr; + const char *mode = (cell->info.mode == NR_MODE_TDD) ? "TDD" : "FDD"; + fprintf(f, + " [%d] nrCellID %lu, PCI %d, Mode %s, SSB ARFCN %d\n", + cell_idx++, + cell->info.cell_id, + cell->info.pci, + mode, + ssb_arfcn_mtc(cell->mtc)); + if (cell->info.mode == NR_MODE_TDD) { + const nr_rrc_freq_info_t *tdd = &cell->info.tdd.dlul; + fprintf(f, " TDD: band %d ARFCN %d SCS %d (kHz) PRB %d\n", tdd->band, tdd->arfcn, 15 * (1 << tdd->scs), tdd->nrb); + } else { + const nr_rrc_freq_info_t *dl = &cell->info.fdd.dl; + const nr_rrc_freq_info_t *ul = &cell->info.fdd.ul; + fprintf(f, " DL: band %d ARFCN %d SCS %d (kHz) PRB %d\n", dl->band, dl->arfcn, 15 * (1 << dl->scs), dl->nrb); + fprintf(f, " UL: band %d ARFCN %d SCS %d (kHz) PRB %d\n", ul->band, ul->arfcn, 15 * (1 << ul->scs), ul->nrb); + } + } } } } diff --git a/openair2/RRC/NR/rrc_gNB_du.h b/openair2/RRC/NR/rrc_gNB_du.h index 4206096aa2..ce033e91e8 100644 --- a/openair2/RRC/NR/rrc_gNB_du.h +++ b/openair2/RRC/NR/rrc_gNB_du.h @@ -30,6 +30,7 @@ struct f1ap_setup_req_s; struct f1ap_lost_connection_t; struct gNB_RRC_INST_s; struct nr_rrc_du_container_t; +struct nr_rrc_cell_container_t; struct f1ap_gnb_du_configuration_update_s; struct f1ap_served_cell_info_t; @@ -38,14 +39,12 @@ void rrc_CU_process_f1_lost_connection(struct gNB_RRC_INST_s *rrc, struct f1ap_l void rrc_gNB_process_f1_du_configuration_update(struct f1ap_gnb_du_configuration_update_s *conf_up, sctp_assoc_t assoc_id); struct nr_rrc_du_container_t *get_du_for_ue(struct gNB_RRC_INST_s *rrc, uint32_t ue_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); 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); +int get_ssb_arfcn(const struct nr_rrc_cell_container_t *cell); struct nr_rrc_du_container_t *find_target_du(struct gNB_RRC_INST_s *rrc, sctp_assoc_t source_assoc_id); diff --git a/openair2/RRC/NR/rrc_gNB_mobility.c b/openair2/RRC/NR/rrc_gNB_mobility.c index a2230c1146..d18937fbc5 100644 --- a/openair2/RRC/NR/rrc_gNB_mobility.c +++ b/openair2/RRC/NR/rrc_gNB_mobility.c @@ -27,6 +27,7 @@ #include "nr_rrc_proto.h" #include "rrc_gNB_du.h" +#include "rrc_cell_management.h" #include "rrc_gNB_radio_bearers.h" #include "rrc_gNB_UE_context.h" #include "openair2/LAYER2/NR_MAC_COMMON/nr_mac.h" @@ -37,6 +38,7 @@ #include "openair3/SECU/key_nas_deriver.h" #include "openair2/RRC/NR/rrc_gNB_NGAP.h" #include "NR_DL-DCCH-MessageType.h" +#include "rrc_cell_management.h" #ifdef E2_AGENT #include "openair2/E2AP/RAN_FUNCTION/O-RAN/ran_func_rc_extern.h" @@ -80,15 +82,12 @@ void nr_rrc_apply_target_context(gNB_RRC_UE_t *UE) /* update F1 data: secondary UE association and DU association */ ue_data.secondary_ue = target_ctx->du_ue_id; - ue_data.du_assoc_id = target_ctx->du->assoc_id; + ue_data.du_assoc_id = target_ctx->cell->assoc_id; bool success = cu_update_f1_ue_data(UE->rrc_ue_id, &ue_data); DevAssert(success); /* update UE RNTI */ UE->rnti = target_ctx->new_rnti; - - /* update UE NR cell ID */ - UE->nr_cellid = target_ctx->du->setup_req->cell[0].info.nr_cellid; } /* \brief Initiate a handover of UE to a specific target cell handled by this @@ -101,8 +100,7 @@ void nr_rrc_apply_target_context(gNB_RRC_UE_t *UE) * \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, + const nr_rrc_cell_container_t *source_cell, byte_array_t *ho_prep_info, ho_req_ack_t ack, ho_success_t success, @@ -111,10 +109,10 @@ static void nr_initiate_handover(const gNB_RRC_INST *rrc, { DevAssert(rrc != NULL); DevAssert(ue != NULL); - DevAssert(target_du != NULL); - // source_du might be NULL -> inter-CU handover + // source_cell might be NULL -> inter-CU handover DevAssert(ho_prep_info->buf != NULL && ho_prep_info->len > 0); DevAssert(ue->ho_context); + DevAssert(ue->ho_context->target != NULL); // if any reconfiguration is ongoing, abort handover request for (int i = 0; i < NR_RRC_TRANSACTION_IDENTIFIER_NUMBER; ++i) { @@ -125,7 +123,9 @@ static void nr_initiate_handover(const gNB_RRC_INST *rrc, } nr_handover_context_t *ho_ctx = ue->ho_context; - ho_ctx->target->du = target_du; + const nr_rrc_cell_container_t *target_cell = ho_ctx->target->cell; + DevAssert(target_cell != NULL); + // we will know target->{du_ue_id,new_rnti} once we have UE ctxt setup // response ho_ctx->target->ho_req_ack = ack; @@ -133,14 +133,14 @@ static void nr_initiate_handover(const gNB_RRC_INST *rrc, ho_ctx->target->ho_failure = failure; 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 + if (source_cell != NULL) { + DevAssert(source_cell->assoc_id == ue_data.du_assoc_id); ho_ctx->source->ho_cancel = cancel; - ho_ctx->source->du = source_du; + ho_ctx->source->cell = source_cell; ho_ctx->source->du_ue_id = ue_data.secondary_ue; ho_ctx->source->old_rnti = ue->rnti; + DevAssert(source_cell->assoc_id == ue_data.du_assoc_id); // Save the GTP-U tunnel info for source DU before process UE context setup request/response // since tunnel info will be updated by calling store_du_f1u_tunnel() in rrc_CU_process_ue_context_setup_response() @@ -153,14 +153,14 @@ static void nr_initiate_handover(const gNB_RRC_INST *rrc, } LOG_A(NR_RRC, - "Handover triggered for UE %u/RNTI %04x towards DU %ld/assoc_id %d/PCI %d\n", + "Handover triggered for UE %u/RNTI %04x towards cell %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); + target_cell->info.cell_id, + target_cell->assoc_id, + target_cell->info.pci); - rrc_f1_ue_context_setup_for_target_du(rrc, ue, target_du, ho_prep_info); + rrc_f1_ue_context_setup_for_target_du(rrc, ue, target_cell, ho_prep_info); } typedef struct deliver_ue_ctxt_modification_data_t { @@ -249,15 +249,19 @@ 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); + RETURN_IF_INVALID_ASSOC_ID(source_ctx->cell->assoc_id); f1ap_ue_context_rel_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 }; - 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); + rrc->mac_rrc.ue_context_release_command(source_ctx->cell->assoc_id, &cmd); + LOG_I(NR_RRC, + "UE %d Handover: trigger release on cell PCI %d/assoc_id %d\n", + UE->rrc_ue_id, + source_ctx->cell->info.pci, + source_ctx->cell->assoc_id); } static void nr_rrc_cancel_f1_ho(gNB_RRC_INST *rrc, gNB_RRC_UE_t *UE) @@ -271,10 +275,13 @@ static void nr_rrc_cancel_f1_ho(gNB_RRC_INST *rrc, gNB_RRC_UE_t *UE) .cause = F1AP_CAUSE_RADIO_NETWORK, // better .cause_value = 5, // 5 = F1AP_CauseRadioNetwork_interaction_with_other_procedure }; - rrc->mac_rrc.ue_context_release_command(target_ctx->du->assoc_id, &cmd); + rrc->mac_rrc.ue_context_release_command(target_ctx->cell->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) +void nr_rrc_trigger_f1_ho(gNB_RRC_INST *rrc, + gNB_RRC_UE_t *ue, + const nr_rrc_cell_container_t *source_cell, + const nr_rrc_cell_container_t *target_cell) { DevAssert(rrc != NULL); DevAssert(ue != NULL); @@ -288,6 +295,7 @@ void nr_rrc_trigger_f1_ho(gNB_RRC_INST *rrc, gNB_RRC_UE_t *ue, nr_rrc_du_contain return; } ue->ho_context = alloc_ho_ctx(HO_CTX_BOTH); + ue->ho_context->target->cell = target_cell; // 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 @@ -299,7 +307,7 @@ void nr_rrc_trigger_f1_ho(gNB_RRC_INST *rrc, gNB_RRC_UE_t *ue, nr_rrc_du_contain ho_success_t success = nr_rrc_f1_ho_complete; ho_cancel_t cancel = nr_rrc_cancel_f1_ho; byte_array_t hpi = {.buf = buf, .len = size}; - nr_initiate_handover(rrc, ue, source_du, target_du, &hpi, ack, success, cancel, NULL); + nr_initiate_handover(rrc, ue, source_cell, &hpi, ack, success, cancel, NULL); } void nr_rrc_finalize_ho(gNB_RRC_UE_t *ue) @@ -324,6 +332,11 @@ void nr_HO_F1_trigger_telnet(gNB_RRC_INST *rrc, uint32_t 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_cell_container_t *source_cell = rrc_get_pcell_for_ue(rrc, ue); + if (source_cell == NULL) { + LOG_E(NR_RRC, "cannot get source cell for UE %u\n", ue->rrc_ue_id); + return; + } nr_rrc_du_container_t *target_du = find_target_du(rrc, source_du->assoc_id); if (target_du == NULL) { @@ -331,7 +344,19 @@ void nr_HO_F1_trigger_telnet(gNB_RRC_INST *rrc, uint32_t rrc_ue_id) return; } - nr_rrc_trigger_f1_ho(rrc, ue, source_du, target_du); + // For target cell, get the first cell from target DU + // (in future, this could be selected based on measurement) + nr_rrc_cell_container_t *target_cell = NULL; + FOR_EACH_SEQ_ARR (nr_rrc_cell_container_t **, cell_ptr, &target_du->cells) { + target_cell = *cell_ptr; + break; // Get first cell + } + if (target_cell == NULL) { + LOG_E(NR_RRC, "cannot get target cell for UE %u\n", ue->rrc_ue_id); + return; + } + + nr_rrc_trigger_f1_ho(rrc, ue, source_cell, target_cell); } /** @brief Generate the HandoverPreparationInformation to be carried @@ -395,6 +420,74 @@ static byte_array_t rrc_gNB_encode_HandoverCommand(gNB_RRC_UE_t *UE, gNB_RRC_INS return out; } +/** @brief Update cell association after handover: set PCell on target DU + * @param[in] rrc RRC instance + * @param[in] UE UE context + * @return true on success, false on failure + * + * Generic for all handover types (F1, N2, Xn). Behaviour depends on whether + * ho_context->source is set on this CU: + * - Source present (intra-CU): F1 HO, remove all serving cells belonging to + * the source DU, then add target cell as PCell. + * - Source absent (inter-CU): N2 or Xn handover (target CU created new UE + * on Handover Request). This UE context has no serving cells yet; add + * target cell as first PCell. */ +bool nr_rrc_update_cell_assoc_after_ho(gNB_RRC_INST *rrc, gNB_RRC_UE_t *UE) +{ + DevAssert(UE->ho_context); + DevAssert(UE->ho_context->target); + nr_ho_target_cu_t *target_ctx = UE->ho_context->target; + + /* Pre-check: ensure F1 UE data matches target cell before mutating state. */ + if (!cu_exists_f1_ue_data(UE->rrc_ue_id)) { + LOG_E(NR_RRC, "UE %d handover: no F1 UE data, cannot add PCell\n", UE->rrc_ue_id); + return false; + } + f1_ue_data_t ue_data = cu_get_f1_ue_data(UE->rrc_ue_id); + if (ue_data.du_assoc_id != target_ctx->cell->assoc_id) { + LOG_E(NR_RRC, + "UE %d handover: target cell assoc_id %d does not match F1 du_assoc_id %d\n", + UE->rrc_ue_id, + target_ctx->cell->assoc_id, + ue_data.du_assoc_id); + return false; + } + + if (UE->ho_context->source) { + /* F1 HO (intra-CU): migrate serving cells from source DU to target cell */ + nr_ho_source_cu_t *source_ctx = UE->ho_context->source; + + // Remove SCells that belong to the source DU during handover (cell group changes) + // This also removes PCell if it was on the source DU + rrc_remove_ue_scells_from_du(UE, source_ctx->cell->assoc_id); + + // Update PCell in serving_cells + ue_serving_cell_t *added = rrc_add_ue_serving_cell(UE, target_ctx->cell, RRC_PCELL_INDEX); + if (added == NULL) { + LOG_E(NR_RRC, + "UE %d handover: failed to add PCell (cell %ld) to serving cells\n", + UE->rrc_ue_id, + target_ctx->cell->info.cell_id); + return false; + } + } else { + /* Inter-CU (N2/Xn target): this UE context has no serving cells yet */ + const ue_serving_cell_t *existing_pcell = ue_get_pcell_entry(UE); + AssertFatal(existing_pcell == NULL, "UE context should not have PCell yet (inter-CU target)\n"); + + ue_serving_cell_t *added = rrc_add_ue_serving_cell(UE, target_ctx->cell, RRC_PCELL_INDEX); + if (added == NULL) { + LOG_E(NR_RRC, + "UE %d handover: failed to add PCell (cell %ld) to serving cells\n", + UE->rrc_ue_id, + target_ctx->cell->info.cell_id); + return false; + } + } + + return true; +} + /** @brief This callback is used by the target gNB * to trigger the Handover Request Acknowledge towards the AMF */ static void nr_rrc_n2_ho_acknowledge(gNB_RRC_INST *rrc, gNB_RRC_UE_t *UE) @@ -407,13 +500,23 @@ static void nr_rrc_n2_ho_acknowledge(gNB_RRC_INST *rrc, gNB_RRC_UE_t *UE) AssertFatal(previous_data.secondary_ue == -1, "there was already a DU present\n"); nr_rrc_apply_target_context(UE); + /* Update cell association after handover */ + nr_ho_target_cu_t *target = UE->ho_context->target; + if (!nr_rrc_update_cell_assoc_after_ho(rrc, UE)) { + ngap_handover_failure_t fail = {.amf_ue_ngap_id = UE->amf_ue_ngap_id, + .cause.type = NGAP_CAUSE_RADIO_NETWORK, + .cause.value = NGAP_CAUSE_RADIO_NETWORK_HO_FAILURE_IN_TARGET_5GC_NGRAN_NODE_OR_TARGET_SYSTEM}; + target->ho_failure(rrc, UE->rrc_ue_id, &fail); + return; + } + byte_array_t hoCommand = rrc_gNB_encode_HandoverCommand(UE, rrc); if (hoCommand.len < 0) { LOG_E(NR_RRC, "ASN1 message encoding failed: failed to generate Handover Command Message\n"); ngap_handover_failure_t fail = {.amf_ue_ngap_id = UE->amf_ue_ngap_id, .cause.type = NGAP_CAUSE_RADIO_NETWORK, .cause.value = NGAP_CAUSE_RADIO_NETWORK_HO_FAILURE_IN_TARGET_5GC_NGRAN_NODE_OR_TARGET_SYSTEM}; - UE->ho_context->target->ho_failure(rrc, UE->rrc_ue_id, &fail); + target->ho_failure(rrc, UE->rrc_ue_id, &fail); return; } else { LOG_D(NR_RRC, "HO LOG: Handover Command for UE %u Encoded (%ld bytes)\n", UE->rrc_ue_id, hoCommand.len); @@ -463,8 +566,7 @@ void nr_rrc_trigger_n2_ho_target(gNB_RRC_INST *rrc, gNB_RRC_UE_t *ue) ho_success_t success = nr_rrc_n2_ho_complete; ho_failure_t failure = nr_rrc_n2_ho_failure; - const nr_rrc_du_container_t *target_du = get_du_for_ue(rrc, ue->rrc_ue_id); - nr_initiate_handover(rrc, ue, NULL, target_du, &ue->ho_context->target->ue_ho_prep_info, ack, success, NULL, failure); + nr_initiate_handover(rrc, ue, NULL, &ue->ho_context->target->ue_ho_prep_info, ack, success, NULL, failure); FREE_AND_ZERO_BYTE_ARRAY(ue->ho_context->target->ue_ho_prep_info); NR_UE_NR_Capability_t *ue_cap = get_ue_nr_capability(ue->rnti, ue->ue_cap_buffer.buf, ue->ue_cap_buffer.len); @@ -495,7 +597,13 @@ void nr_rrc_trigger_n2_ho(gNB_RRC_INST *rrc, } ue->ho_context = alloc_ho_ctx(HO_CTX_SOURCE); - ue->ho_context->source->du = get_du_for_ue(rrc, ue->rrc_ue_id); + nr_rrc_cell_container_t *source_cell = rrc_get_pcell_for_ue(rrc, ue); + if (source_cell == NULL) { + LOG_E(NR_RRC, "No source cell found for UE %d\n", ue->rrc_ue_id); + free_byte_array(hoPrepInfo); + return; + } + ue->ho_context->source->cell = source_cell; ue->ho_context->source->ho_status_transfer = rrc_gNB_send_NGAP_ul_ran_status_transfer; ue->ho_context->source->ho_cancel = nr_rrc_n2_ho_cancel; @@ -514,42 +622,51 @@ void nr_HO_N2_trigger_telnet(gNB_RRC_INST *rrc, uint32_t neighbour_pci, uint32_t return; } gNB_RRC_UE_t *UE = &ue_context_p->ue_context; - - struct nr_rrc_du_container_t *du = get_du_for_ue(rrc, rrc_ue_id); + nr_rrc_du_container_t *du = get_du_for_ue(rrc, UE->rrc_ue_id); if (du == NULL) { LOG_E(NR_RRC, "N2 HO trigger failed for UE %d: Unknown DU\n", rrc_ue_id); return; } - uint16_t scell_pci = du->setup_req->cell[0].info.nr_pci; + nr_rrc_cell_container_t *pcell = rrc_get_pcell_for_ue(rrc, UE); + if (pcell == NULL) { + LOG_E(NR_RRC, "N2 HO trigger failed for UE %d: Unknown cell\n", rrc_ue_id); + return; + } + uint16_t scell_pci = pcell->info.pci; // Simulate handover on the same cell (testing purposes) if (neighbour_pci == scell_pci) { LOG_I(NR_RRC, "UE %d: trigger handover on the same cell PCI=%d\n", rrc_ue_id, neighbour_pci); nr_neighbour_cell_t neighbourConfig = { .isIntraFrequencyNeighbour = true, - .gNB_ID = du->setup_req->gNB_DU_id, - .nrcell_id = du->setup_req->cell[0].info.nr_cellid, - .physicalCellId = du->setup_req->cell[0].info.nr_pci, - .plmn = du->setup_req->cell[0].info.plmn, - .subcarrierSpacing = du->setup_req->cell[0].info.tdd.tbw.scs, + .gNB_ID = du->gNB_DU_id, + .nrcell_id = pcell->info.cell_id, + .physicalCellId = pcell->info.pci, + .plmn = pcell->info.plmn, + .subcarrierSpacing = pcell->info.mode == NR_MODE_TDD ? pcell->info.tdd.dlul.scs : pcell->info.fdd.dl.scs, }; nr_rrc_trigger_n2_ho(rrc, UE, neighbour_pci, &neighbourConfig); return; } - const f1ap_served_cell_info_t *scell_du = get_cell_information_by_phycellId(scell_pci); - DevAssert(scell_du); LOG_I(NR_RRC, "UE %d: triggered N2 HO, source PCI=%d to target PCI=%d\n", rrc_ue_id, scell_pci, neighbour_pci); - const neighbour_cell_configuration_t *cell = get_neighbour_cell_config(rrc, scell_du->nr_cellid); - const nr_neighbour_cell_t *neighbour = get_neighbour_cell_by_pci(cell, neighbour_pci); + const neighbour_cell_configuration_t *cell = get_neighbour_cell_config(rrc, pcell->info.cell_id); + if (cell == NULL) { + LOG_E(NR_RRC, + "N2 HO trigger failed for UE %d: could not find neighbour cell with Cell ID=%ld\n", + rrc_ue_id, + pcell->info.cell_id); + return; + } + const nr_neighbour_cell_t *neighbour = get_neighbour_cell_by_pci(cell, neighbour_pci); if (neighbour == NULL) { LOG_E(NR_RRC, "N2 HO trigger failed for UE %d: could not find neighbour cell with PCI=%d\n", rrc_ue_id, neighbour_pci); return; } - nr_rrc_trigger_n2_ho(rrc, UE, scell_du->nr_pci, neighbour); + nr_rrc_trigger_n2_ho(rrc, UE, pcell->info.pci, neighbour); } // This function detects if there are at least two different ssbFrequency values, and if so, returns meas_timing_config; diff --git a/openair2/RRC/NR/rrc_gNB_mobility.h b/openair2/RRC/NR/rrc_gNB_mobility.h index 456730060e..d4a51efeb9 100644 --- a/openair2/RRC/NR/rrc_gNB_mobility.h +++ b/openair2/RRC/NR/rrc_gNB_mobility.h @@ -28,7 +28,7 @@ /* 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_rrc_cell_container_t nr_rrc_cell_container_t; typedef struct NR_CellGroupConfig NR_CellGroupConfig_t; @@ -40,8 +40,8 @@ typedef int (*ho_status_transfer_t)(gNB_RRC_INST *rrc, const e1_pdcp_status_info_t *pdcp_status); typedef struct nr_ho_source_cu { - /// pointer to the (source) DU structure - const nr_rrc_du_container_t *du; + /// pointer to the (source) cell container + const nr_rrc_cell_container_t *cell; /// (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 @@ -67,8 +67,8 @@ typedef void (*ho_failure_t)(gNB_RRC_INST *rrc, uint32_t gnb_ue_id, ngap_handove typedef void (*ho_trigger_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; + /// pointer to the (target) cell container + const nr_rrc_cell_container_t *cell; /// (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 @@ -95,7 +95,10 @@ typedef struct nr_handover_context_s { typedef enum { HO_CTX_BOTH, HO_CTX_SOURCE, HO_CTX_TARGET } ho_ctx_type_t; nr_handover_context_t *alloc_ho_ctx(ho_ctx_type_t type); -void nr_rrc_trigger_f1_ho(gNB_RRC_INST *rrc, gNB_RRC_UE_t *ue, nr_rrc_du_container_t *source_du, nr_rrc_du_container_t *target_du); +void nr_rrc_trigger_f1_ho(gNB_RRC_INST *rrc, + gNB_RRC_UE_t *ue, + const nr_rrc_cell_container_t *source_cell, + const nr_rrc_cell_container_t *target_cell); void nr_rrc_finalize_ho(gNB_RRC_UE_t *ue); void nr_rrc_n2_ho_failure(gNB_RRC_INST *rrc, uint32_t gnb_ue_id, ngap_handover_failure_t *msg); @@ -112,4 +115,6 @@ byte_array_t *get_meas_timing_config(const NR_MeasurementTimingConfiguration_t * void nr_rrc_apply_target_context(gNB_RRC_UE_t *UE); +bool nr_rrc_update_cell_assoc_after_ho(gNB_RRC_INST *rrc, gNB_RRC_UE_t *UE); + #endif /* RRC_GNB_MOBILITY_H_ */ diff --git a/openair2/RRC/NR/rrc_gNB_nsa.c b/openair2/RRC/NR/rrc_gNB_nsa.c index 88f06338d7..18513d14c5 100644 --- a/openair2/RRC/NR/rrc_gNB_nsa.c +++ b/openair2/RRC/NR/rrc_gNB_nsa.c @@ -62,6 +62,7 @@ #include "openair3/SECU/key_nas_deriver.h" #include "openair2/SDAP/nr_sdap/nr_sdap_entity.h" #include "rrc_gNB_du.h" +#include "rrc_cell_management.h" #include "rlc.h" #include "s1ap_messages_types.h" #include "tree.h" @@ -368,20 +369,37 @@ void rrc_add_nsa_user(gNB_RRC_INST *rrc, x2ap_ENDC_sgnb_addition_req_t *m, sctp_ *ue_agg_mbr_ul = 1000000000; byte_array_t *cg_configinfo = malloc_or_fail(sizeof(*cg_configinfo)); *cg_configinfo = cgci; + + f1_ue_data_t ue_data = cu_get_f1_ue_data(UE->rrc_ue_id); + RETURN_IF_INVALID_ASSOC_ID(ue_data.du_assoc_id); + nr_rrc_du_container_t *du = get_du_by_assoc_id(rrc, ue_data.du_assoc_id); + if (!du) { + LOG_E(NR_RRC, "UE %d: no valid cell for UE context setup (DU assoc_id %d not found)\n", UE->rrc_ue_id, ue_data.du_assoc_id); + return; + } + // Get the first cell from the DU (NSA assumption: 1 cell per DU) + if (seq_arr_size(&du->cells) == 0) { + LOG_E(NR_RRC, "UE %d: no cells available in DU (assoc_id %d)\n", UE->rrc_ue_id, ue_data.du_assoc_id); + return; + } + nr_rrc_cell_container_t *ue_cell = *(nr_rrc_cell_container_t **)seq_arr_at(&du->cells, 0); + // Add the cell as PCell + if (rrc_add_ue_serving_cell(UE, ue_cell, RRC_PCELL_INDEX) == NULL) { + LOG_E(NR_RRC, "UE %d: failed to add PCell (cell %ld) for UE context setup\n", UE->rrc_ue_id, ue_cell->info.cell_id); + return; + } f1ap_ue_context_setup_req_t req = { .gNB_CU_ue_id = UE->rrc_ue_id, .plmn.mcc = rrc->configuration.plmn[0].mcc, .plmn.mnc = rrc->configuration.plmn[0].mnc, .plmn.mnc_digit_length = rrc->configuration.plmn[0].mnc_digit_length, - .nr_cellid = rrc->nr_cellid, - .servCellIndex = 0, + .nr_cellid = ue_cell->info.cell_id, + .servCellIndex = RRC_PCELL_INDEX, .drbs_len = 1, .drbs = drb, .cu_to_du_rrc_info.cg_configinfo = cg_configinfo, .gnb_du_ue_agg_mbr_ul = ue_agg_mbr_ul, }; - f1_ue_data_t ue_data = cu_get_f1_ue_data(UE->rrc_ue_id); - RETURN_IF_INVALID_ASSOC_ID(ue_data.du_assoc_id); rrc->mac_rrc.ue_context_setup_request(ue_data.du_assoc_id, &req); free_ue_context_setup_req(&req); } From bd2fc0d237ff12373a7016d6137bebdf6ac97003 Mon Sep 17 00:00:00 2001 From: Guido Casati Date: Thu, 5 Feb 2026 16:23:51 +0100 Subject: [PATCH 014/100] RRC multi-cell: enable multiple cell in F1 Setup Remove the single-cell limit in F1 Setup Request processing and refactor to loop-based cell handling. Move cell validation (PLMN check, neighbor configuration validation, system info extraction), cell creation, SIB encoding, and neighbor labeling inside a loop over all available cells. Update F1 Setup Response to include all cells instead of hardcoding to 1. Add warning log when more than one cell is received. Add proper cleanup with free_f1ap_setup_response() on error paths. Impact: - F1 Setup now accepts and processes multiple cells from a single DU, enabling multi-cell deployments - All cells are validated, created, and included in the setup response --- openair2/RRC/NR/rrc_gNB_du.c | 209 ++++++++++++++++++----------------- 1 file changed, 107 insertions(+), 102 deletions(-) diff --git a/openair2/RRC/NR/rrc_gNB_du.c b/openair2/RRC/NR/rrc_gNB_du.c index 5b1be47d5e..f50c7a2c6e 100644 --- a/openair2/RRC/NR/rrc_gNB_du.c +++ b/openair2/RRC/NR/rrc_gNB_du.c @@ -324,30 +324,9 @@ void rrc_gNB_process_f1_setup_req(f1ap_setup_req_t *req, sctp_assoc_t assoc_id) f1ap_setup_failure_t fail = {.transaction_id = F1AP_get_next_transaction_identifier(0, 0)}; // check: - // - it is one cell // - PLMN and Cell ID matches // - no previous DU with the same ID // else reject - if (req->num_cells_available != 1) { - LOG_E(NR_RRC, "can only handle on DU cell, but gNB_DU %ld has %d\n", req->gNB_DU_id, req->num_cells_available); - fail.cause = F1AP_CauseRadioNetwork_gNB_CU_Cell_Capacity_Exceeded; - rrc->mac_rrc.f1_setup_failure(assoc_id, &fail); - return; - } - f1ap_served_cell_info_t *cell_info = &req->cell[0].info; - if (!rrc_gNB_plmn_matches(rrc, cell_info)) { - LOG_E(NR_RRC, - "PLMN mismatch: CU %03d.%0*d, DU %03d%0*d\n", - rrc->configuration.plmn[0].mcc, - rrc->configuration.plmn[0].mnc_digit_length, - rrc->configuration.plmn[0].mnc, - cell_info->plmn.mcc, - cell_info->plmn.mnc_digit_length, - cell_info->plmn.mnc); - fail.cause = F1AP_CauseRadioNetwork_plmn_not_served_by_the_gNB_CU; - rrc->mac_rrc.f1_setup_failure(assoc_id, &fail); - return; - } nr_rrc_du_container_t *it = NULL; RB_FOREACH(it, rrc_du_tree, &rrc->dus) { if (it->gNB_DU_id == req->gNB_DU_id) { @@ -361,50 +340,7 @@ void rrc_gNB_process_f1_setup_req(f1ap_setup_req_t *req, sctp_assoc_t assoc_id) return; } } - - if (rrc->neighbour_cell_configuration && !valid_du_in_neighbour_configs(rrc->neighbour_cell_configuration, cell_info)) { - LOG_E(NR_RRC, "problem with DU %ld in neighbor configuration, rejecting DU\n", req->gNB_DU_id); - f1ap_setup_failure_t fail = {.cause = F1AP_CauseMisc_unspecified}; - rrc->mac_rrc.f1_setup_failure(assoc_id, &fail); - return; - } - - const f1ap_gnb_du_system_info_t *sys_info = req->cell[0].sys_info; - NR_MIB_t *mib = NULL; - NR_SIB1_t *sib1 = NULL; - - if (sys_info != NULL && sys_info->mib != NULL && !(sys_info->sib1 == NULL && IS_SA_MODE(get_softmodem_params()))) { - if (!extract_sys_info(sys_info, &mib, &sib1)) { - LOG_W(NR_RRC, "rejecting DU ID %ld\n", req->gNB_DU_id); - fail.cause = F1AP_CauseProtocol_semantic_error; - rrc->mac_rrc.f1_setup_failure(assoc_id, &fail); - return; - } - } - LOG_I(NR_RRC, "Accepting DU %ld (%s), sending F1 Setup Response\n", req->gNB_DU_id, req->gNB_DU_name); - LOG_I(NR_RRC, "DU uses RRC version %u.%u.%u\n", req->rrc_ver[0], req->rrc_ver[1], req->rrc_ver[2]); - - /* Create cell and add to global tree */ - nr_rrc_cell_container_t *new = calloc_or_fail(1, sizeof(*new)); - new->assoc_id = assoc_id; - cp_f1_served_cell_info_to_cell(new, cell_info); - new->mib = mib; - new->sib1 = sib1; - // Add cell to DU's cell array - nr_rrc_cell_container_t *collision = rrc_add_cell(rrc, new); - if (collision != NULL) { - nr_rrc_du_container_t *existing_du = get_du_by_assoc_id(rrc, collision->assoc_id); - const char *du_name = existing_du ? existing_du->gNB_DU_name : "unknown"; - LOG_E(NR_RRC, - "Cell ID %lu already exists in DU %s (assoc_id %d), rejecting requesting gNB-DU\n", - new->info.cell_id, - du_name, - collision->assoc_id); - rrc_free_cell_container(new); - fail.cause = F1AP_CauseRadioNetwork_cell_not_available; - rrc->mac_rrc.f1_setup_failure(assoc_id, &fail); - return; - } + LOG_I(NR_RRC, "Accepting DU %ld (%s) (RRC version %u.%u.%u)\n", req->gNB_DU_id, req->gNB_DU_name, req->rrc_ver[0], req->rrc_ver[1], req->rrc_ver[2]); // DU is accepted, add it to tree and add cell to DU's cell array nr_rrc_du_container_t *du = calloc_or_fail(1, sizeof(*du)); @@ -418,48 +354,117 @@ void rrc_gNB_process_f1_setup_req(f1ap_setup_req_t *req, sctp_assoc_t assoc_id) nr_rrc_du_container_t *du_collision = rrc_add_du(rrc, du); AssertFatal(du_collision == NULL, "rrc_add_du should succeed for new DU (assoc_id %d)", assoc_id); - nr_rrc_cell_container_t *added = rrc_add_cell_to_du(&du->cells, new); - AssertFatal(added != NULL, "Failed to add cell %ld to DU %ld\n", new->info.cell_id, du->gNB_DU_id); - LOG_I(NR_RRC, "DU %ld: Added cell %ld\n", du->gNB_DU_id, new->info.cell_id); - - served_cells_to_activate_t cell = { - .plmn = cell_info->plmn, - .nr_cellid = cell_info->nr_cellid, - .nrpci = cell_info->nr_pci, - .num_SI = 0, - }; - - // Encode CU SIBs and configure setup response with sysinfo - seq_arr_t *sibs = rrc->SIBs; - if (sibs) { - for (int i = 0; i < sibs->size; i++) { - nr_SIBs_t *sib = (nr_SIBs_t *)seq_arr_at(sibs, i); - switch (sib->SIB_type) { - case 2: { - NR_SSB_MTC_t *ssbmtc = get_ssb_mtc(new->mtc); - sib->SIB_size = do_SIB2_NR(&sib->SIB_buffer, ssbmtc); - cell.SI_msg[cell.num_SI].SI_container = sib->SIB_buffer; - cell.SI_msg[cell.num_SI].SI_container_length = sib->SIB_size; - cell.SI_msg[cell.num_SI].SI_type = sib->SIB_type; - cell.num_SI++; - } break; - default: - AssertFatal(false, "SIB%d not handled yet\n", sib->SIB_type); - } - } - } - - if (new->mib != NULL &&new->sib1 != NULL) - label_intra_frequency_neighbours(rrc, new); - + // Build F1 Setup Response f1ap_setup_resp_t resp = {.transaction_id = req->transaction_id, - .num_cells_to_activate = 1, - .cells_to_activate = calloc_or_fail(1, sizeof(*resp.cells_to_activate))}; - resp.cells_to_activate[0] = cell; + .num_cells_to_activate = req->num_cells_available, + .cells_to_activate = calloc_or_fail(req->num_cells_available, sizeof(*resp.cells_to_activate))}; int num = read_version(TO_STRING(NR_RRC_VERSION), &resp.rrc_ver[0], &resp.rrc_ver[1], &resp.rrc_ver[2]); AssertFatal(num == 3, "could not read RRC version string %s\n", TO_STRING(NR_RRC_VERSION)); if (rrc->node_name != NULL) resp.gNB_CU_name = strdup(rrc->node_name); + + if (req->num_cells_available > 1) { + LOG_W(NR_RRC, "Received F1 Setup Request with %u cells, only one cell is supported\n", req->num_cells_available); + } + for (int i = 0; i < req->num_cells_available; i++) { + f1ap_served_cell_info_t *cell_info = &req->cell[i].info; + if (!rrc_gNB_plmn_matches(rrc, cell_info)) { + LOG_E(NR_RRC, + "PLMN mismatch: CU %03d.%0*d, DU %03d%0*d\n", + rrc->configuration.plmn[0].mcc, + rrc->configuration.plmn[0].mnc_digit_length, + rrc->configuration.plmn[0].mnc, + cell_info->plmn.mcc, + cell_info->plmn.mnc_digit_length, + cell_info->plmn.mnc); + fail.cause = F1AP_CauseRadioNetwork_plmn_not_served_by_the_gNB_CU; + rrc->mac_rrc.f1_setup_failure(assoc_id, &fail); + free_f1ap_setup_response(&resp); + return; + } + + if (rrc->neighbour_cell_configuration && !valid_du_in_neighbour_configs(rrc->neighbour_cell_configuration, cell_info)) { + LOG_E(NR_RRC, "problem with DU %ld in neighbor configuration, rejecting DU\n", req->gNB_DU_id); + f1ap_setup_failure_t fail = {.cause = F1AP_CauseMisc_unspecified}; + rrc->mac_rrc.f1_setup_failure(assoc_id, &fail); + free_f1ap_setup_response(&resp); + return; + } + + const f1ap_gnb_du_system_info_t *sys_info = req->cell[i].sys_info; + NR_MIB_t *mib = NULL; + NR_SIB1_t *sib1 = NULL; + if (sys_info != NULL && sys_info->mib != NULL && !(sys_info->sib1 == NULL && IS_SA_MODE(get_softmodem_params()))) { + if (!extract_sys_info(sys_info, &mib, &sib1)) { + LOG_W(NR_RRC, "rejecting DU ID %ld\n", req->gNB_DU_id); + fail.cause = F1AP_CauseProtocol_semantic_error; + rrc->mac_rrc.f1_setup_failure(assoc_id, &fail); + free_f1ap_setup_response(&resp); + return; + } + } + + /* Create cell and add to global tree */ + nr_rrc_cell_container_t *new = calloc_or_fail(1, sizeof(*new)); + new->assoc_id = assoc_id; + cp_f1_served_cell_info_to_cell(new, cell_info); + new->mib = mib; + new->sib1 = sib1; + // Add cell to DU's cell array + nr_rrc_cell_container_t *collision = rrc_add_cell(rrc, new); + if (collision != NULL) { + nr_rrc_du_container_t *existing_du = get_du_by_assoc_id(rrc, collision->assoc_id); + const char *du_name = existing_du ? existing_du->gNB_DU_name : "unknown"; + LOG_E(NR_RRC, + "Cell ID %lu already exists in DU %s (assoc_id %d), rejecting requesting gNB-DU\n", + new->info.cell_id, + du_name, + collision->assoc_id); + rrc_free_cell_container(new); + fail.cause = F1AP_CauseRadioNetwork_cell_not_available; + rrc->mac_rrc.f1_setup_failure(assoc_id, &fail); + free_f1ap_setup_response(&resp); + return; + } + + nr_rrc_cell_container_t *added = rrc_add_cell_to_du(&du->cells, new); + AssertFatal(added != NULL, "Failed to add cell %ld to DU %ld\n", new->info.cell_id, du->gNB_DU_id); + LOG_I(NR_RRC, "DU %ld: Added cell %ld\n", du->gNB_DU_id, new->info.cell_id); + + served_cells_to_activate_t cell = { + .plmn = cell_info->plmn, + .nr_cellid = cell_info->nr_cellid, + .nrpci = cell_info->nr_pci, + .num_SI = 0, + }; + + // Encode CU SIBs and configure setup response with sysinfo + seq_arr_t *sibs = rrc->SIBs; + if (sibs) { + for (int i = 0; i < sibs->size; i++) { + nr_SIBs_t *sib = (nr_SIBs_t *)seq_arr_at(sibs, i); + switch (sib->SIB_type) { + case 2: { + NR_SSB_MTC_t *ssbmtc = get_ssb_mtc(new->mtc); + sib->SIB_size = do_SIB2_NR(&sib->SIB_buffer, ssbmtc); + cell.SI_msg[cell.num_SI].SI_container = sib->SIB_buffer; + cell.SI_msg[cell.num_SI].SI_container_length = sib->SIB_size; + cell.SI_msg[cell.num_SI].SI_type = sib->SIB_type; + cell.num_SI++; + } break; + default: + AssertFatal(false, "SIB%d not handled yet\n", sib->SIB_type); + } + } + } + + if (new->mib != NULL &&new->sib1 != NULL) + label_intra_frequency_neighbours(rrc, new); + + resp.cells_to_activate[i] = cell; + } + + LOG_I(NR_RRC, "DU %ld (%s): sending F1 Setup Response\n", req->gNB_DU_id, req->gNB_DU_name); rrc->mac_rrc.f1_setup_response(assoc_id, &resp); free_f1ap_setup_response(&resp); From 9bfe62b85b26788cee21960e1cfd9a4c09c5e910 Mon Sep 17 00:00:00 2001 From: Guido Casati Date: Tue, 6 Jan 2026 11:08:28 +0100 Subject: [PATCH 015/100] RRC multi-cell (finalise): make internal helper functions static After introduction of new cell management design, these helper functions are now only used internally within their respective modules and do not need to be part of the public API. They are made static and their declarations are removed from public headers. Changes: - f1ap_interface_management.c: Make free_f1ap_cell() and copy_f1ap_served_cell_info() static - f1ap_interface_management.h: Remove declarations for the above functions - rrc_gNB_du.c: Make get_dl_band() and get_ssb_scs() static - rrc_gNB_du.h: Remove declarations for get_dl_band() and get_ssb_scs() --- openair2/F1AP/lib/f1ap_interface_management.c | 4 ++-- openair2/F1AP/lib/f1ap_interface_management.h | 2 -- openair2/RRC/NR/rrc_gNB_du.c | 4 ++-- openair2/RRC/NR/rrc_gNB_du.h | 2 -- 4 files changed, 4 insertions(+), 8 deletions(-) diff --git a/openair2/F1AP/lib/f1ap_interface_management.c b/openair2/F1AP/lib/f1ap_interface_management.c index 38de5e4dcc..55b4972d4b 100644 --- a/openair2/F1AP/lib/f1ap_interface_management.c +++ b/openair2/F1AP/lib/f1ap_interface_management.c @@ -423,7 +423,7 @@ static F1AP_ProtocolExtensionContainer_11023P34_t *write_slice_info(int num_ssi, /** * @brief F1AP Setup Request memory management */ -void free_f1ap_cell(const f1ap_served_cell_info_t *info, const f1ap_gnb_du_system_info_t *sys_info) +static void free_f1ap_cell(const f1ap_served_cell_info_t *info, const f1ap_gnb_du_system_info_t *sys_info) { if (sys_info) { free(sys_info->mib); @@ -886,7 +886,7 @@ bool decode_f1ap_setup_request(const F1AP_F1AP_PDU_t *pdu, f1ap_setup_req_t *out return true; } -f1ap_served_cell_info_t copy_f1ap_served_cell_info(const f1ap_served_cell_info_t *src) +static f1ap_served_cell_info_t copy_f1ap_served_cell_info(const f1ap_served_cell_info_t *src) { f1ap_served_cell_info_t dst = { .plmn = src->plmn, diff --git a/openair2/F1AP/lib/f1ap_interface_management.h b/openair2/F1AP/lib/f1ap_interface_management.h index 56f6579f2f..942a9932ad 100644 --- a/openair2/F1AP/lib/f1ap_interface_management.h +++ b/openair2/F1AP/lib/f1ap_interface_management.h @@ -46,8 +46,6 @@ bool decode_f1ap_setup_request(const struct F1AP_F1AP_PDU *pdu, f1ap_setup_req_t f1ap_setup_req_t cp_f1ap_setup_request(const f1ap_setup_req_t *msg); bool eq_f1ap_setup_request(const f1ap_setup_req_t *a, const f1ap_setup_req_t *b); void free_f1ap_setup_request(const f1ap_setup_req_t *msg); -f1ap_served_cell_info_t copy_f1ap_served_cell_info(const f1ap_served_cell_info_t *src); -void free_f1ap_cell(const f1ap_served_cell_info_t *info, const f1ap_gnb_du_system_info_t *sys_info); /* F1 Setup Response */ struct F1AP_F1AP_PDU *encode_f1ap_setup_response(const f1ap_setup_resp_t *msg); diff --git a/openair2/RRC/NR/rrc_gNB_du.c b/openair2/RRC/NR/rrc_gNB_du.c index f50c7a2c6e..f2e66257f6 100644 --- a/openair2/RRC/NR/rrc_gNB_du.c +++ b/openair2/RRC/NR/rrc_gNB_du.c @@ -53,12 +53,12 @@ #include "utils.h" #include "xer_encoder.h" -int get_dl_band(const struct f1ap_served_cell_info_t *cell_info) +static int get_dl_band(const struct f1ap_served_cell_info_t *cell_info) { return cell_info->mode == F1AP_MODE_TDD ? cell_info->tdd.freqinfo.band : cell_info->fdd.dl_freqinfo.band; } -int get_ssb_scs(const struct f1ap_served_cell_info_t *cell_info) +static int get_ssb_scs(const struct f1ap_served_cell_info_t *cell_info) { return cell_info->mode == F1AP_MODE_TDD ? cell_info->tdd.tbw.scs : cell_info->fdd.dl_tbw.scs; } diff --git a/openair2/RRC/NR/rrc_gNB_du.h b/openair2/RRC/NR/rrc_gNB_du.h index ce033e91e8..8942af9af5 100644 --- a/openair2/RRC/NR/rrc_gNB_du.h +++ b/openair2/RRC/NR/rrc_gNB_du.h @@ -42,8 +42,6 @@ struct nr_rrc_du_container_t *get_du_for_ue(struct gNB_RRC_INST_s *rrc, uint32_t void dump_du_info(const struct gNB_RRC_INST_s *rrc, FILE *f); -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_cell_container_t *cell); struct nr_rrc_du_container_t *find_target_du(struct gNB_RRC_INST_s *rrc, sctp_assoc_t source_assoc_id); From e75399707c6a9e8d36310111afd52e224115e97d Mon Sep 17 00:00:00 2001 From: Guido Casati Date: Tue, 6 Jan 2026 11:11:30 +0100 Subject: [PATCH 016/100] RRC multi-cell (finalise): add informative comments and logging This commit adds documentation comments and logging to improve code readability and debugging capabilities for the multi-cell architecture. Changes: - gnb_config.c: Add logging when configuring DU with cell ID and PCI - rrc_gNB_du.c: Add documentation comment for ssb_arfcn_mtc() - rrc_gNB_du.c: Add documentation comment for dump_du_info() --- openair2/GNB_APP/gnb_config.c | 1 + openair2/RRC/NR/rrc_gNB_du.c | 8 ++++++++ 2 files changed, 9 insertions(+) diff --git a/openair2/GNB_APP/gnb_config.c b/openair2/GNB_APP/gnb_config.c index 338f9ad8a5..b84f927f82 100644 --- a/openair2/GNB_APP/gnb_config.c +++ b/openair2/GNB_APP/gnb_config.c @@ -1684,6 +1684,7 @@ void RCconfig_nr_macrlc(configmodule_interface_t *cfg) seq_arr_t *du_SIBs = RC.nrmac[0]->common_channels[0].du_SIBs; f1ap_setup_req_t *req = RC_read_F1Setup(gnb_du_id, name, &info, scc, mib, sib1, du_SIBs); AssertFatal(req != NULL, "could not read F1 Setup information\n"); + LOG_I(GNB_APP, "Configured DU: cell ID %lu, PCI %d\n", info.nr_cellid, info.nr_pci); RC.nrmac[0]->f1_config.setup_req = req; RC.nrmac[0]->f1_config.gnb_id = gnb_id; diff --git a/openair2/RRC/NR/rrc_gNB_du.c b/openair2/RRC/NR/rrc_gNB_du.c index f2e66257f6..10d2e4131c 100644 --- a/openair2/RRC/NR/rrc_gNB_du.c +++ b/openair2/RRC/NR/rrc_gNB_du.c @@ -72,6 +72,9 @@ static NR_SSB_MTC_t *get_ssb_mtc(const NR_MeasurementTimingConfiguration_t *mtc) return ssb_mtc; } +/** @brief Return the frequency of the SS block of the cell for which this message is included, + * or of other SS blocks within the same carrier. MeasurementTimingConfiguration message is + * used to convey assistance information for measurement timing between nodes (e.g F1 setup) */ static int ssb_arfcn_mtc(const NR_MeasurementTimingConfiguration_t *mtc) { /* format has been verified when accepting MeasurementTimingConfiguration */ @@ -728,6 +731,11 @@ nr_rrc_du_container_t *get_du_for_ue(gNB_RRC_INST *rrc, uint32_t ue_id) return get_du_by_assoc_id(rrc, ue_data.du_assoc_id); } +/** @brief Dump DU and cell information to file + * @param[in] rrc RRC instance + * @param[in] f File handle to write to + * @note Complexity: O(d + N_CELL) = O(N_CELL) where d = number of DUs, N_CELL = total number of cells across all DUs + * @note Called periodically (every 1 second) for stats logging */ void dump_du_info(const gNB_RRC_INST *rrc, FILE *f) { fprintf(f, "%ld connected DUs \n", rrc->num_dus); From 337cc4bbe734d66618631a0d4c66b2f23e4fc0b7 Mon Sep 17 00:00:00 2001 From: Guido Casati Date: Tue, 11 Nov 2025 13:26:57 +0100 Subject: [PATCH 017/100] Doc: update RRC documentation with new multi-cell architecture Expand RRC developer and usage documentation to match the current multi-cell design with separate DU and cell management. - rrc-dev.md: Document DU and cell data structures and key functions (get_du_by_assoc_id, get_cell_by_cell_id, rrc_add_cell_to_du, etc.); add DU and cell lifecycle (F1 Setup, config update, disconnection) and UE cell association (serving cells, PCell/SCells) with sequence diagrams; add handover and cell association update flow; update UE context description. - rrc-usage.md: Update DU/cell list output format and description (e.g. integrated DU-CU) and multi-cell per DU wording. --- doc/RRC/rrc-dev.md | 252 ++++++++++++++++++++++++++++++++++++++++++- doc/RRC/rrc-usage.md | 18 ++-- 2 files changed, 258 insertions(+), 12 deletions(-) diff --git a/doc/RRC/rrc-dev.md b/doc/RRC/rrc-dev.md index f5c4956c9d..2299394e0c 100644 --- a/doc/RRC/rrc-dev.md +++ b/doc/RRC/rrc-dev.md @@ -390,13 +390,255 @@ sequenceDiagram ## Structures -### Cells +### DUs and Cells -OAI 5G RRC does not actually handle multiple cells as of now, but multiple DUs, -each being limited to one cell. +OAI 5G RRC is enabling support for multiple DUs, with each DU potentially +serving multiple cells (though currently each DU is limited to one cell in practice). +The architecture separates DU management from cell management to enable future +multi-cell per DU capability. -Cell-related data is stored in `nr_rrc_du_container_t`, and kept in a tree -indexed by the SCTP association ID. +#### DU Management + +DU-related data is stored in `nr_rrc_du_container_t`, and kept in a red-black +tree indexed by the unique SCTP association ID (`assoc_id`). Each DU container stores: +- DU identity and name +- SCTP association ID for F1 interface communication +- RRC version information +- Sequential array of cell pointers (`cells`) - stores pointers to cells belonging to this DU (seq_arr_t) + +Key Functions: +- `get_du_by_assoc_id()` - Lookup DU by SCTP association ID (O(log d) tree lookup) +- `get_du_for_ue()` - Get DU associated with a specific UE +- `find_target_du()` - Find a target DU for handover operations + +#### Cell Management + +Cell-related data is stored in `nr_rrc_cell_container_t`. Cells are stored in two data structures: + +1. Global cell tree (`rrc->cells`): Red-black tree indexed by `cell_id`, containing all cells from all DUs. Used for efficient O(log N_CELL) lookups across all DUs. PCI reuse across the network is allowed and this is reflected in the tree. +2. DU cell array (`du->cells`): Sequential array (seq_arr_t) of cell pointers, storing only cells belonging to that specific DU. Used for DU-specific operations. PCI must be unique within a DU. + +Each cell container stores: +- Cell identity (NR Cell ID) and PCI (Physical Cell ID) +- Link to serving DU via the unique `assoc_id` +- Cell-specific information (PLMN, TAC, frequency, mode TDD/FDD) +- MIB, SIB1, and MeasurementTimingConfiguration messages + +Key Functions: +- `get_cell_by_cell_id()` - Lookup cell by NR Cell ID using global cell tree +- `rrc_get_cell_for_du()` - Lookup cell by cell_id within a specific DU's cell array +- `rrc_get_cell_by_pci_for_du()` - Lookup cell by PCI within DU's cells array +- `rrc_add_cell_to_du()` - Add cell to DU's sequential array +- `rrc_free_cell_container()` - Free cell container and associated ASN.1 structures + +Architecture Notes: +- The `assoc_id` field (in cell and DUs containers) links cells to their serving DUs +- When a DU connects via F1 Setup, cells are added to both the global tree and the DU's array +- When a DU disconnects, cells are removed from both structures +- Global tree enables efficient cross-DU cell lookups (O(log N_CELL)) +- DU array enables efficient per-DU cell iteration (O(k) where k=cells per DU) +- Each DU maintains a sequential array of cell pointers + +##### DU and Cell Lifecycle + +The following diagram shows the lifecycle of DUs and their associated cells, including the main F1AP messages and internal operations: + +```mermaid +sequenceDiagram + participant CellTree as Cell Tree + participant DUTree as DU Tree + participant CU as CU-CP + participant DU as gNB-DU + participant UE as UE + + Note over DU,CellTree: DU Connection & Cell Registration + DU->>CU: F1AP F1 Setup Request(DU ID, Cell Info, MIB/SIB1) + Note over CU: Validate: PLMN match with CU configuration + alt PLMN mismatch + CU->>DU: F1AP F1 Setup Failure (PLMN not served) + end + Note over CU: Validate: DU ID uniqueness (RB_FOREACH gNB_DU_id in DU tree) + alt DU ID already exists + CU->>DU: F1AP F1 Setup Failure (Unspecified) + end + Note over CU: Validate: Neighbour cell configuration (if configured) + alt Neighbour config invalid + CU->>DU: F1AP F1 Setup Failure (Unspecified) + end + Note over CU: Extract MIB/SIB1 from system info (if present) + alt System info extraction fails + CU->>DU: F1AP F1 Setup Failure (Semantic error) + end + alt All Validations Success + CU->>CU: Create cell container (nr_rrc_cell_container_t), set assoc_id, copy cell info, set MIB/SIB1 + CU->>CellTree: rrc_add_cell(rrc, new) - RB_INSERT into global tree, increment rrc->num_cells + alt Duplicate cell_id (collision) + CU->>DU: F1AP F1 Setup Failure (Cell not available) + else Cell added to tree + CU->>CU: Create DU container (nr_rrc_du_container_t) + CU->>CU: seq_arr_init(&du->cells) - Initialize DU's cell array + CU->>DUTree: rrc_add_du(rrc, du) - RB_INSERT(du), increment rrc->num_dus + CU->>CU: rrc_add_cell_to_du(&du->cells, new) - Add cell to DU's array + Note over CU,UE: Cell available for UE association + CU->>CU: Encode CU SIBs (if configured) + CU->>DU: F1AP F1 Setup Response(Cells to Activate, CU SIBs) + Note over CU,CellTree: DU and cell now active + end + end + + UE->>CU: RRC Setup Request + Note over CU: rrc_handle_RRCSetupRequest() + CU->>CU: get_cell_by_cell_id(&rrc->cells) - Use global tree + alt Cell not found + CU->>UE: RRC Reject + end + Note over CU: UE Cell Association + CU->>CU: rrc_add_ue_serving_cell(UE, cell, RRC_PCELL_INDEX) + + Note over DU,CellTree: Optional: Cell Configuration Update + opt DU sends configuration update + DU->>CU: F1AP DU Configuration Update(Add/Modify/Delete cells) + Note over CU: get_du_by_assoc_id(assoc_id) + loop For each cell to add + CU->>CU: get_cell_by_cell_id(&rrc->cells) - Check cell_id uniqueness globally + alt Duplicate cell_id found + Note over CU: Reject and return + end + CU->>CU: rrc_get_cell_by_pci_for_du(&du->cells) - Check PCI unique within DU + alt Duplicate PCI in DU + Note over CU: Reject and return + end + end + loop Cell Modification + CU->>CU: get_cell_by_cell_id(cells, old_nr_cellid) - Find cell by old cell_id + CU->>CU: update_cell_info(rrc, old_nci, new_ci) - Update in place + Note over CU: If cell_id changes: RB_REMOVE then re-insert after update + Note over CU: Free old MTC if new measurement timing config provided + Note over CU: If sys_info present: extract MIB/SIB1 and set on cell + end + CU->>DU: F1AP DU Configuration Update Acknowledge + end + + Note over DU,CellTree: DU Disconnection & Cell Cleanup + DU-->>CU: F1AP Lost Connection(SCTP connection lost) + Note over CU: rrc_CU_process_f1_lost_connection() + CU->>DUTree: RB_FIND(du) - Find DU by assoc_id using temporary struct + alt DU not found + Note over CU: Log warning and return + end + Note over CU: rrc_cleanup_du() then invalidate_du_connections() + CU->>CU: Iterate cells in DU's array (last to first) + loop For each cell in DU's array + CU->>CU: seq_arr_erase(&du->cells, cell_ptr) - Remove from DU's array + CU->>CellTree: rrc_rm_cell(): RB_REMOVE(cell), decrement num_cells, rrc_free_cell_container() + end + CU->>DUTree: rrc_rm_du(): RB_REMOVE(du) - Remove DU from tree + CU->>CU: Decrement rrc->num_dus counter + CU->>CU: seq_arr_free(&du->cells) - Free DU's cell array + CU->>CU: rrc_free_du_container() - Free DU container + Note over CU: invalidate_du_connections() + loop For each UE: + CU->>CU: rrc_remove_ue_scells_from_du() - Remove SCells from disconnected DU + alt UE belongs to disconnected DU + CU->>CU: Set du_assoc_id = 0 (mark DU offline) + CU->>CU: Trigger NGAP UE Context Release Request + end + end + Note over CU,CellTree: DU and all cells removed +``` + +Key Functions: +- `rrc_gNB_process_f1_setup_req()` - Handles F1 Setup Request, creates DU and cell containers. + Validates PLMN match, DU ID uniqueness, and cell_id/PCI uniqueness before creating containers. +- `rrc_gNB_process_f1_du_configuration_update()` - Handles cell configuration updates. + Currently supports cell modification (MIB/SIB1 updates) and validates cell additions, but cell addition + and deletion are not yet fully implemented. +- `rrc_CU_process_f1_lost_connection()` - Handles DU disconnection. Calls `rrc_cleanup_du()` to + remove all cells and the DU from their trees and free resources, then + `invalidate_du_connections()` to clean up UE associations (e.g. trigger NGAP UE Context Release Request for UEs on that DU). + +#### UE Cell Association Management + +The RRC maintains a per-UE association with serving cells, tracking which cells +a UE is currently using. This replaces the previous single-cell assumption and +enables proper multi-cell support where each UE can have multiple serving cells +(one PCell and up to 31 SCells). The servCellIndex (TS 38.331) is tracked per-UE +in the `ue_serving_cell_t` structure in the UE context (the same cell can have +different servCellIndex values for different UEs). + +Data Structures: +- `ue_serving_cell_t`: Stores serving cell information (nci, servCellIndex, assoc_id) +- `gNB_RRC_UE_t.serving_cells`: Dynamic array (seq_arr_t) of serving cell entries. PCell is always at index 0. + +Key Functions: +- `rrc_add_ue_serving_cell()` - Adds a new serving cell to UE's serving_cells array. +- `rrc_get_ue_serving_cell_by_id()` - Retrieves serving cell entry by servCellIndex. +- `ue_get_pcell_entry()` - Returns the PCell serving cell entry (first element in serving_cells). +- `rrc_remove_ue_scells_from_du()` - Removes all serving cells belonging to a specific DU via assoc_id (e.g. during handover or DU disconnection). + +##### Handover and Cell Association Updates + +During handover, the UE's serving cell list is updated so the PCell reflects the target cell and source-DU cells are removed. + +###### F1 handover (inter-DU, same CU-CP) + +Cell association is updated when the **source** DU sends F1AP UE Context Modification Response (after it has sent the RRC Reconfiguration to the UE). The CU-CP is a single RRC instance; both source DU and target DU are under the same CU. + +```mermaid +sequenceDiagram + participant SourceDU as Source DU + participant CU as CU-CP + + Note over CU,CU: F1 handover: cell association update on Context Modification Response + SourceDU->>CU: F1AP UE Context Modification Response + Note over CU: rrc_CU_process_ue_context_modification_response() + Note over CU: Check: ho_context && source && target (F1 HO) + + alt F1 handover in progress + CU->>CU: nr_rrc_apply_target_context(UE) + Note over CU: F1 UE data: du_assoc_id = target DU, secondary_ue = target DU UE ID, RNTI = target RNTI + CU->>CU: nr_rrc_update_cell_assoc_after_ho(rrc, UE) + Note over CU: F1 branch (ho_context->source present): + CU->>CU: rrc_remove_ue_scells_from_du(UE, source_ctx->cell->assoc_id) + Note over CU: Remove all serving cells (incl. PCell) belonging to source DU + CU->>CU: rrc_add_ue_serving_cell(UE, target_ctx->cell, RRC_PCELL_INDEX) + Note over CU: Handover complete: PCell = target cell, source DU cells removed + end +``` + +###### N2 handover (inter-gNB: source CU vs target CU) + +Cell association is updated only on the target CU-CP, when the target DU sends F1AP UE Context Setup Response. The UE context was created for handover, so there is no existing serving cell to remove. The flow is triggered inside the handover request acknowledge callback. + +- Target CU-CP: Receives HANDOVER REQUEST, sets up bearer and F1 UE context on target DU; when the target DU sends F1AP UE Context Setup Response, the target CU runs the cell-association update and then sends HANDOVER REQUEST ACKNOWLEDGE. + +```mermaid +sequenceDiagram + participant TargetDU as Target DU + participant TargetCU as Target CU-CP + + Note over TargetCU: N2 handover: cell association update + Note over TargetCU: F1AP UE Context Setup Resp from target DU (in nr_rrc_n2_ho_acknowledge) + TargetDU->>TargetCU: F1AP UE Context Setup Response + Note over TargetCU: rrc_CU_process_ue_context_setup_response() then callback ho_req_ack() + Note over TargetCU: nr_rrc_n2_ho_acknowledge(rrc, UE) + + TargetCU->>TargetCU: nr_rrc_apply_target_context(UE) + Note over TargetCU: F1 UE data: du_assoc_id = target DU, secondary_ue = target DU UE ID, RNTI = target RNTI + TargetCU->>TargetCU: nr_rrc_update_cell_assoc_after_ho(rrc, UE) + Note over TargetCU: N2 branch (ho_context->source NULL): no SCells to remove + TargetCU->>TargetCU: rrc_add_ue_serving_cell(UE, target_ctx->cell, RRC_PCELL_INDEX) + Note over TargetCU: Then: encode Handover Command, send NGAP HANDOVER REQUEST ACKNOWLEDGE +``` + +## UE Context + +UE context information is stored in `gNB_RRC_UE_t`, which includes: +- Serving cells tracking: Dynamic array of serving cells (PCell + SCells) +- Security context: Keys, algorithms, and security state +- Radio bearers: SRB and DRB configurations +- PDU sessions: Active PDU session information +- Handover context: Temporary data during handover procedures ### CU-UPs diff --git a/doc/RRC/rrc-usage.md b/doc/RRC/rrc-usage.md index bae31e864a..f9188de05a 100644 --- a/doc/RRC/rrc-usage.md +++ b/doc/RRC/rrc-usage.md @@ -41,15 +41,19 @@ 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 +[1] DU ID 3584 (gNB-OAI) integrated DU-CU: 1 cell + [1] nrCellID 12345678, PCI 0, Mode TDD, 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. +The output shows: +- Total number of connected DUs +- For each DU: an index (`[1]`), the DU ID and name, connection type (either `integrated DU-CU` for monolithic deployments or `assoc_id X` for split deployments), and the number of cells served by this DU +- For each cell served by the DU: a cell index (`[1]`), the NR Cell ID, Physical Cell ID (PCI), operating mode (TDD or FDD), and SSB ARFCN +- Cell-specific frequency information: for TDD mode, the band, Point A ARFCN, subcarrier spacing (SCS), and number of resource blocks (PRB); for FDD mode, separate DL and UL frequency information + +The RRC enables the support of multiple cells per DU (though currently each DU +typically serves one cell in practice). As of now, it does not print information about connected CU-UPs or AMFs. From a2a354a44734e441f9c3f15de58f3e9dd324151d Mon Sep 17 00:00:00 2001 From: francescomani Date: Wed, 28 Jan 2026 16:50:37 +0100 Subject: [PATCH 018/100] fix analog beamforming fapi structure generation --- openair2/LAYER2/NR_MAC_gNB/config.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openair2/LAYER2/NR_MAC_gNB/config.c b/openair2/LAYER2/NR_MAC_gNB/config.c index dfa68773a4..2bdf0563a7 100644 --- a/openair2/LAYER2/NR_MAC_gNB/config.c +++ b/openair2/LAYER2/NR_MAC_gNB/config.c @@ -708,7 +708,7 @@ static void config_common(gNB_MAC_INST *nrmac, const nr_mac_config_t *config, NR // precoding matrix configuration (to be improved) cfg->pmi_list = init_DL_MIMO_codebook(nrmac, pdsch_AntennaPorts); - if (nrmac->beam_info.beam_mode == PRECONFIGURED_BEAM_IDX) { + if (nrmac->beam_info.beam_mode != NO_BEAM_MODE) { LOG_I(NR_MAC, "Configuring analog beamforming in config_request message\n"); cfg->analog_beamforming_ve.num_beams_period_vendor_ext.tl.tag = NFAPI_NR_FAPI_NUM_BEAMS_PERIOD_VENDOR_EXTENSION_TAG; cfg->analog_beamforming_ve.num_beams_period_vendor_ext.value = nrmac->beam_info.beams_per_period; From 1271f166e06fbef628f6b396d6f532e2ba14f913 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=BAben=20Soares=20Silva?= Date: Fri, 6 Feb 2026 10:09:02 +0000 Subject: [PATCH 019/100] Add undocumented Aerial ERROR.indication vendor specific error codes --- .../nfapi/public_inc/nfapi_nr_interface_scf.h | 27 ++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/nfapi/open-nFAPI/nfapi/public_inc/nfapi_nr_interface_scf.h b/nfapi/open-nFAPI/nfapi/public_inc/nfapi_nr_interface_scf.h index 10e6b7c14a..fcd1968c25 100644 --- a/nfapi/open-nFAPI/nfapi/public_inc/nfapi_nr_interface_scf.h +++ b/nfapi/open-nFAPI/nfapi/public_inc/nfapi_nr_interface_scf.h @@ -716,7 +716,13 @@ typedef enum { X(NFAPI_NR_PHY_API_MSG_TX_ERR, 0X8) #ifdef ENABLE_AERIAL +// Error codes obtained from Aerial L1 file +// cuPHY-CP/scfl2adapter/lib/scf_5g_fapi/scf_5g_fapi.h #define AERIAL_ERROR_LIST \ + X(SCF_ERROR_CODE_MSG_INVALID_PHY_ID, 0x9) \ + X(SCF_ERROR_CODE_MSG_UNINSTANTIATED_PHY, 0xA) \ + X(SCF_ERROR_CODE_MSG_INVALID_DFE_Profile, 0xB) \ + X(SCF_ERROR_CODE_MSG_PHY_PROFILE_SELECTION, 0xC) \ X(AERIAL_ERROR_CODE_FAPI_END, 0x32) \ X(AERIAL_ERROR_CODE_L1_PROC_OBJ_UNAVAILABLE_ERR, 0x33) \ X(AERIAL_ERROR_CODE_MSG_LATE_SLOT_ERR, 0x34) \ @@ -729,7 +735,26 @@ typedef enum { X(AERIAL_ERROR_CODE_L1_P1_EXIT_ERROR, 0x3B) \ X(AERIAL_ERROR_CODE_L1_P2_EXIT_ERROR, 0x3C) \ X(AERIAL_ERROR_CODE_L1_DL_CH_ERROR, 0x3D) \ - X(AERIAL_ERROR_CODE_L1_UL_CH_ERROR, 0x3E) + X(AERIAL_ERROR_CODE_L1_UL_CH_ERROR, 0x3E) \ + X(SCF_ERROR_CODE_EARLY_HARQ_TIMING_ERROR, 0x3F) \ + X(SCF_ERROR_CODE_SRS_CHEST_BUFF_BAD_STATE, 0x40) \ + X(SCF_ERROR_CODE_BEAM_ID_OUT_OF_RANGE, 0x41) \ + X(SCF_ERROR_CODE_PTP_SVC_ERROR, 0x42) \ + X(SCF_ERROR_CODE_PTP_SYNCED, 0x43) \ + X(SCF_ERROR_CODE_L1_MISSING_UL_IQ, 0x44) \ + X(SCF_ERROR_CODE_MSG_CAPACITY_EXCEEDED, 0x45) \ + X(SCF_ERROR_CODE_RHOCP_PTP_EVENTS_ERROR, 0x46) \ + X(SCF_ERROR_CODE_RHOCP_PTP_EVENTS_SYNCED, 0x47) \ + X(SCF_FAPI_SSB_PBCH_L1_LIMIT_EXCEEDED, 0x81) \ + X(SCF_FAPI_PDCCH_L1_LIMIT_EXCEEDED, 0x82) \ + X(SCF_FAPI_PDSCH_L1_LIMIT_EXCEEDED, 0x84) \ + X(SCF_FAPI_CSIRS_L1_LIMIT_EXCEEDED, 0x88) \ + X(SCF_FAPI_PUSCH_L1_LIMIT_EXCEEDED, 0xC1) \ + X(SCF_FAPI_PUCCH_L1_LIMIT_EXCEEDED, 0xC2) \ + X(SCF_FAPI_SRS_L1_LIMIT_EXCEEDED, 0xC4) \ + X(SCF_FAPI_PRACH_L1_LIMIT_EXCEEDED, 0xC8) \ + X(SCF_ERROR_CODE_RELEASED_HARQ_BUFFER_INFO, 0xD0) + #else #define AERIAL_ERROR_LIST #endif From 824d9035c159ee5305d4fb5ddcd0131edb4dab09 Mon Sep 17 00:00:00 2001 From: Shubhika Garg Date: Fri, 6 Feb 2026 09:54:31 +0100 Subject: [PATCH 020/100] fix(ci): remove git reset command from Jenkinsfile - By default, eNB_CommitID is set as 'develop' in the config - When OAI-FLEXRIC-RAN-Integration-Test is triggered by upstream FlexRIC push events, it takes `develop` - Triggered by RAN takes commit SHA where `origin/SHA` does not work --- ci-scripts/Jenkinsfile | 1 - 1 file changed, 1 deletion(-) diff --git a/ci-scripts/Jenkinsfile b/ci-scripts/Jenkinsfile index 3112b8f2cf..abb2e3942c 100644 --- a/ci-scripts/Jenkinsfile +++ b/ci-scripts/Jenkinsfile @@ -115,7 +115,6 @@ pipeline { } else { sh "git fetch" sh "git checkout -f ${commitID}" - sh "git reset --hard origin/${commitID}" } } else { echo "\u274C Some parameters are missing" From 0b500d412a83c695ac172583f5929efbd64b5e76 Mon Sep 17 00:00:00 2001 From: francescomani Date: Mon, 5 Jan 2026 12:11:20 +0100 Subject: [PATCH 021/100] adding a function to check the received RRC configuration (function to be expanded in later commits) also move functions to check RRC configuration to a new file not to overload the already big rrc_UE.c --- CMakeLists.txt | 1 + openair2/RRC/NR_UE/rrc_UE.c | 37 ++++++++++++++++++++----------- openair2/RRC/NR_UE/rrc_proto.h | 2 +- openair2/RRC/NR_UE/verify_RRC.c | 39 +++++++++++++++++++++++++++++++++ 4 files changed, 65 insertions(+), 14 deletions(-) create mode 100644 openair2/RRC/NR_UE/verify_RRC.c diff --git a/CMakeLists.txt b/CMakeLists.txt index bae1c590bc..9883366ba6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1201,6 +1201,7 @@ set(NR_L2_SRC_UE ${NR_UE_RRC_DIR}/L2_interface_ue.c ${NR_UE_RRC_DIR}/main_ue.c ${NR_UE_RRC_DIR}/rrc_UE.c + ${NR_UE_RRC_DIR}/verify_RRC.c ${NR_UE_RRC_DIR}/rrc_timers_and_constants.c ${NR_UE_RRC_DIR}/rrc_sl_preconfig.c ) diff --git a/openair2/RRC/NR_UE/rrc_UE.c b/openair2/RRC/NR_UE/rrc_UE.c index 4860e0b63e..01a5b378be 100644 --- a/openair2/RRC/NR_UE/rrc_UE.c +++ b/openair2/RRC/NR_UE/rrc_UE.c @@ -1006,8 +1006,14 @@ static void nr_rrc_process_reconfigurationWithSync(NR_UE_RRC_INST_t *rrc, } } -static void nr_rrc_cellgroup_configuration(NR_UE_RRC_INST_t *rrc, NR_CellGroupConfig_t *cgConfig, int gNB_index, bool dedicatedsib1) +static bool nr_rrc_cellgroup_configuration(NR_UE_RRC_INST_t *rrc, NR_CellGroupConfig_t *cgConfig, int gNB_index, bool dedicatedsib1) { + if (!check_cellgroup_config(cgConfig)) { + // if we received a configuration not supported or with some wrong combination + // we call the function for RLF (re-establishment if security is activated, going to IDLE otherwise) + handle_rlf_detection(rrc); + return false; + } NR_SpCellConfig_t *spCellConfig = cgConfig->spCellConfig; if(spCellConfig) { NR_ServingCellConfig_t *spCellConfigDedicated = spCellConfig->spCellConfigDedicated; @@ -1059,14 +1065,10 @@ static void nr_rrc_cellgroup_configuration(NR_UE_RRC_INST_t *rrc, NR_CellGroupCo } nr_rrc_manage_rlc_bearers(rrc, cgConfig); - - if (cgConfig->ext1) - AssertFatal(cgConfig->ext1->reportUplinkTxDirectCurrent == NULL, "Reporting of UplinkTxDirectCurrent not implemented\n"); - AssertFatal(cgConfig->sCellToReleaseList == NULL, "Secondary serving cell release not implemented\n"); - AssertFatal(cgConfig->sCellToAddModList == NULL, "Secondary serving cell addition not implemented\n"); + return true; } -static void nr_rrc_ue_process_masterCellGroup(NR_UE_RRC_INST_t *rrc, +static bool nr_rrc_ue_process_masterCellGroup(NR_UE_RRC_INST_t *rrc, OCTET_STRING_t *masterCellGroup, long *fullConfig, int gNB_index) @@ -1080,13 +1082,15 @@ static void nr_rrc_ue_process_masterCellGroup(NR_UE_RRC_INST_t *rrc, masterCellGroup->size, 0, 0); if ((dec_rval.code != RC_OK) && (dec_rval.consumed == 0)) { LOG_E(NR_RRC, "CellGroupConfig decode error\n"); - return; + return false; } if (LOG_DEBUGFLAG(DEBUG_ASN1)) { xer_fprint(stdout, &asn_DEF_NR_CellGroupConfig, (const void *) cellGroupConfig); } - nr_rrc_cellgroup_configuration(rrc, cellGroupConfig, gNB_index, false); + bool ret = nr_rrc_cellgroup_configuration(rrc, cellGroupConfig, gNB_index, false); + if (!ret) + return false; LOG_D(RRC, "Sending CellGroupConfig to MAC the pointer will be managed by mac\n"); nr_mac_rrc_message_t rrc_msg = {0}; @@ -1097,6 +1101,7 @@ static void nr_rrc_ue_process_masterCellGroup(NR_UE_RRC_INST_t *rrc, mac_msg->hfn = rrc->current_hfn; mac_msg->frame = rrc->current_frame; nr_rrc_send_msg_to_mac(rrc, &rrc_msg); + return true; } static bool nr_rrc_process_reconfiguration_v1530(NR_UE_RRC_INST_t *rrc, NR_RRCReconfiguration_v1530_IEs_t *rec_1530, int gNB_index) @@ -1105,8 +1110,11 @@ static bool nr_rrc_process_reconfiguration_v1530(NR_UE_RRC_INST_t *rrc, NR_RRCRe // TODO perform the full configuration procedure as specified in 5.3.5.11 of 331 LOG_E(NR_RRC, "RRCReconfiguration includes fullConfig but this is not implemented yet\n"); } - if (rec_1530->masterCellGroup) - nr_rrc_ue_process_masterCellGroup(rrc, rec_1530->masterCellGroup, rec_1530->fullConfig, gNB_index); + if (rec_1530->masterCellGroup) { + bool ret = nr_rrc_ue_process_masterCellGroup(rrc, rec_1530->masterCellGroup, rec_1530->fullConfig, gNB_index); + if (!ret) + return false; + } if (rec_1530->masterKeyUpdate) { as_security_key_update(rrc, rec_1530->masterKeyUpdate); nr_pdcp_entity_security_keys_and_algos_t sp = get_security_rrc_parameters(rrc, true); @@ -1548,7 +1556,8 @@ static void nr_rrc_ue_process_rrcReconfiguration(NR_UE_RRC_INST_t *rrc, int gNB_ if (LOG_DEBUGFLAG(DEBUG_ASN1)) xer_fprint(stdout, &asn_DEF_NR_CellGroupConfig, (const void *) cellGroupConfig); - nr_rrc_cellgroup_configuration(rrc, cellGroupConfig, gNB_index, dedicatedsib1); + bool ret = nr_rrc_cellgroup_configuration(rrc, cellGroupConfig, gNB_index, dedicatedsib1); + AssertFatal(ret, "CellGroup has wrong configuration for the UE. Unexpected\n"); AssertFatal(!IS_SA_MODE(get_softmodem_params()), "secondaryCellGroup only used in NSA for now\n"); nr_mac_rrc_message_t rrc_msg = {0}; rrc_msg.payload_type = NR_MAC_RRC_CONFIG_CG; @@ -2152,7 +2161,9 @@ static void nr_rrc_process_rrcsetup(NR_UE_RRC_INST_t *rrc, const NR_RRCSetup_t * nr_rrc_rrcsetup_fallback(rrc); // perform the cell group configuration procedure in accordance with the received masterCellGroup - nr_rrc_ue_process_masterCellGroup(rrc, &rrcSetup->criticalExtensions.choice.rrcSetup->masterCellGroup, NULL, 0); + bool ret = nr_rrc_ue_process_masterCellGroup(rrc, &rrcSetup->criticalExtensions.choice.rrcSetup->masterCellGroup, NULL, 0); + if (!ret) + return; // perform the radio bearer configuration procedure in accordance with the received radioBearerConfig nr_rrc_ue_process_RadioBearerConfig(rrc, &rrcSetup->criticalExtensions.choice.rrcSetup->radioBearerConfig); diff --git a/openair2/RRC/NR_UE/rrc_proto.h b/openair2/RRC/NR_UE/rrc_proto.h index ab14f704b9..34b3d7e2c7 100644 --- a/openair2/RRC/NR_UE/rrc_proto.h +++ b/openair2/RRC/NR_UE/rrc_proto.h @@ -99,7 +99,7 @@ void nr_rrc_ue_decode_NR_SBCCH_SL_BCH_Message(NR_UE_RRC_INST_t *rrc, const uint16_t rx_slss_id); void nr_rrc_set_mac_queue(instance_t instance, notifiedFIFO_t *mac_input_nf); - +bool check_cellgroup_config(const NR_CellGroupConfig_t *cgConfig); /** @}*/ #endif diff --git a/openair2/RRC/NR_UE/verify_RRC.c b/openair2/RRC/NR_UE/verify_RRC.c new file mode 100644 index 0000000000..819023a638 --- /dev/null +++ b/openair2/RRC/NR_UE/verify_RRC.c @@ -0,0 +1,39 @@ +/* + * 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 "rrc_proto.h" + +bool check_cellgroup_config(const NR_CellGroupConfig_t *cgConfig) +{ + if (cgConfig->ext1 && cgConfig->ext1->reportUplinkTxDirectCurrent) { + LOG_E(NR_RRC, "Reporting of UplinkTxDirectCurrent not implemented\n"); + return false; + } + if (cgConfig->sCellToReleaseList) { + LOG_E(NR_RRC, "Secondary serving cell release not implemented\n"); + return false; + } + if (cgConfig->sCellToAddModList) { + LOG_E(NR_RRC, "Secondary serving cell addition not implemented\n"); + return false; + } + return true; +} From 833ff548c538d158c13b6adeaa19294a6eb1cbfc Mon Sep 17 00:00:00 2001 From: francescomani Date: Wed, 21 Jan 2026 03:20:16 +0100 Subject: [PATCH 022/100] checking consistency in CSI-ResourceConfigId for channel and interference measurements in received RRC configuration This handles issue #1018 --- openair2/RRC/NR_UE/verify_RRC.c | 87 +++++++++++++++++++++++++++++++++ 1 file changed, 87 insertions(+) diff --git a/openair2/RRC/NR_UE/verify_RRC.c b/openair2/RRC/NR_UE/verify_RRC.c index 819023a638..7ed13dde40 100644 --- a/openair2/RRC/NR_UE/verify_RRC.c +++ b/openair2/RRC/NR_UE/verify_RRC.c @@ -21,8 +21,95 @@ #include "rrc_proto.h" +static bool check_resourcesForInterference(const NR_CSI_MeasConfig_t *meas, NR_CSI_ResourceConfigId_t res_id, bool is_CSIIM) +{ + NR_CSI_ResourceConfig_t *res = NULL; + if (meas->csi_ResourceConfigToAddModList) { + for (int i = 0; i < meas->csi_ResourceConfigToAddModList->list.count; i++) { + if (meas->csi_ResourceConfigToAddModList->list.array[i]->csi_ResourceConfigId == res_id) + res = meas->csi_ResourceConfigToAddModList->list.array[i]; + } + } + if (!res) { + LOG_E(NR_RRC, "No CSI-Resource matching with resourcesForInterference set in report config\n"); + return false; + } else { + if (is_CSIIM) { + if (res->csi_RS_ResourceSetList.present != NR_CSI_ResourceConfig__csi_RS_ResourceSetList_PR_csi_IM_ResourceSetList) { + LOG_E(NR_RRC, "resourcesForInterference doesn't point to CSI-IM resource\n"); + return false; + } + } else { + if (res->csi_RS_ResourceSetList.present != NR_CSI_ResourceConfig__csi_RS_ResourceSetList_PR_nzp_CSI_RS_SSB + || !res->csi_RS_ResourceSetList.choice.nzp_CSI_RS_SSB->nzp_CSI_RS_ResourceSetList) { + LOG_E(NR_RRC, "NZP CSI-RS resourcesForInterference doesn't point to NZP CSI-RS resource\n"); + return false; + } + } + } + return true; +} + +static bool check_resourcesForChannelMeasurement(const NR_CSI_MeasConfig_t *meas, + NR_CSI_ResourceConfigId_t res_id, + struct NR_CSI_ReportConfig__reportQuantity quantity) +{ + NR_CSI_ResourceConfig_t *res = NULL; + if (meas->csi_ResourceConfigToAddModList) { + for (int i = 0; i < meas->csi_ResourceConfigToAddModList->list.count; i++) { + if (meas->csi_ResourceConfigToAddModList->list.array[i]->csi_ResourceConfigId == res_id) + res = meas->csi_ResourceConfigToAddModList->list.array[i]; + } + } + if (!res) { + LOG_E(NR_RRC, "No CSI-Resource matching with resourcesForChannelMeasurement set in report config\n"); + return false; + } else { + if (res->csi_RS_ResourceSetList.present != NR_CSI_ResourceConfig__csi_RS_ResourceSetList_PR_nzp_CSI_RS_SSB) { + LOG_E(NR_RRC, "resourcesForChannelMeasurement doesn't point to CSI-RS-SSB resource\n"); + return false; + } else { + if (!res->csi_RS_ResourceSetList.choice.nzp_CSI_RS_SSB->nzp_CSI_RS_ResourceSetList + && (quantity.present != NR_CSI_ReportConfig__reportQuantity_PR_ssb_Index_RSRP + && quantity.present != NR_CSI_ReportConfig__reportQuantity_PR_NOTHING + && quantity.present != NR_CSI_ReportConfig__reportQuantity_PR_none)) { + LOG_E(NR_RRC, "resourcesForChannelMeasurement is for CRI measurement but doen't point to an CRI-RS resource\n"); + return false; + } + } + } + return true; +} + +static bool check_csi_report_consistency(const NR_CSI_MeasConfig_t *meas) +{ + if (!meas->csi_ReportConfigToAddModList) + return true; + for (int i = 0; i < meas->csi_ReportConfigToAddModList->list.count; i++) { + NR_CSI_ReportConfig_t *csirep = meas->csi_ReportConfigToAddModList->list.array[i]; + if (!check_resourcesForChannelMeasurement(meas, csirep->resourcesForChannelMeasurement, csirep->reportQuantity)) + return false; + if (csirep->csi_IM_ResourcesForInterference) { + if (!check_resourcesForInterference(meas, *csirep->csi_IM_ResourcesForInterference, true)) + return false; + } + if (csirep->nzp_CSI_RS_ResourcesForInterference) { + if (!check_resourcesForInterference(meas, *csirep->nzp_CSI_RS_ResourcesForInterference, false)) + return false; + } + } + return true; +} + bool check_cellgroup_config(const NR_CellGroupConfig_t *cgConfig) { + if (cgConfig->spCellConfig && + cgConfig->spCellConfig->spCellConfigDedicated && + cgConfig->spCellConfig->spCellConfigDedicated->csi_MeasConfig) { + if (!check_csi_report_consistency(cgConfig->spCellConfig->spCellConfigDedicated->csi_MeasConfig->choice.setup)) + return false; + } + if (cgConfig->ext1 && cgConfig->ext1->reportUplinkTxDirectCurrent) { LOG_E(NR_RRC, "Reporting of UplinkTxDirectCurrent not implemented\n"); return false; From 562fcd322ff35ff0e8bdc3b332a4cd09f1e1ff0e Mon Sep 17 00:00:00 2001 From: francescomani Date: Wed, 21 Jan 2026 05:13:39 +0100 Subject: [PATCH 023/100] checking SRS symbol mapping consistency to handle #1016 --- openair2/RRC/NR_UE/verify_RRC.c | 52 +++++++++++++++++++++++++++++---- 1 file changed, 47 insertions(+), 5 deletions(-) diff --git a/openair2/RRC/NR_UE/verify_RRC.c b/openair2/RRC/NR_UE/verify_RRC.c index 7ed13dde40..bd9e98ad09 100644 --- a/openair2/RRC/NR_UE/verify_RRC.c +++ b/openair2/RRC/NR_UE/verify_RRC.c @@ -101,13 +101,55 @@ static bool check_csi_report_consistency(const NR_CSI_MeasConfig_t *meas) return true; } +static bool check_srs_config(NR_SRS_Config_t *srs_Config) +{ + if (srs_Config->srs_ResourceToAddModList) { + for (int i = 0; i < srs_Config->srs_ResourceToAddModList->list.count; i++) { + NR_SRS_Resource_t *res = srs_Config->srs_ResourceToAddModList->list.array[i]; + int start = NR_NUMBER_OF_SYMBOLS_PER_SLOT - res->resourceMapping.startPosition - 1; + int num = 1 << res->resourceMapping.nrofSymbols; + if (start + num > NR_NUMBER_OF_SYMBOLS_PER_SLOT) { + LOG_E(NR_RRC, "The configured SRS resource exceeds the slot boundary\n"); + return false; + } + } + } + return true; +} + +static bool check_ul_bwp_config(NR_BWP_UplinkDedicated_t *bwp_Config) +{ + if (bwp_Config->srs_Config && bwp_Config->srs_Config->choice.setup) { + if (!check_srs_config(bwp_Config->srs_Config->choice.setup)) + return false; + } + return true; +} + bool check_cellgroup_config(const NR_CellGroupConfig_t *cgConfig) { - if (cgConfig->spCellConfig && - cgConfig->spCellConfig->spCellConfigDedicated && - cgConfig->spCellConfig->spCellConfigDedicated->csi_MeasConfig) { - if (!check_csi_report_consistency(cgConfig->spCellConfig->spCellConfigDedicated->csi_MeasConfig->choice.setup)) - return false; + if (cgConfig->spCellConfig && cgConfig->spCellConfig->spCellConfigDedicated) { + NR_ServingCellConfig_t *spCellConfigDedicated = cgConfig->spCellConfig->spCellConfigDedicated; + if (spCellConfigDedicated->csi_MeasConfig) { + if (!check_csi_report_consistency(spCellConfigDedicated->csi_MeasConfig->choice.setup)) + return false; + } + if (spCellConfigDedicated->uplinkConfig) { + NR_UplinkConfig_t *ul_Config = spCellConfigDedicated->uplinkConfig; + if (ul_Config->initialUplinkBWP) { + if (!check_ul_bwp_config(ul_Config->initialUplinkBWP)) + return false; + } + if (ul_Config->uplinkBWP_ToAddModList) { + for (int i = 0; i < ul_Config->uplinkBWP_ToAddModList->list.count; i++) { + NR_BWP_Uplink_t *ul_bwp = ul_Config->uplinkBWP_ToAddModList->list.array[i]; + if (ul_bwp->bwp_Dedicated) { + if (!check_ul_bwp_config(ul_bwp->bwp_Dedicated)) + return false; + } + } + } + } } if (cgConfig->ext1 && cgConfig->ext1->reportUplinkTxDirectCurrent) { From 296fb2bd7257853919577ca4dc9ecfe4ad634abf Mon Sep 17 00:00:00 2001 From: francescomani Date: Mon, 26 Jan 2026 16:58:17 +0100 Subject: [PATCH 024/100] checking csi-rs resource mapping consistency to handle #1017 --- openair2/RRC/NR_UE/verify_RRC.c | 150 +++++++++++++++++++++++++++++++- 1 file changed, 148 insertions(+), 2 deletions(-) diff --git a/openair2/RRC/NR_UE/verify_RRC.c b/openair2/RRC/NR_UE/verify_RRC.c index bd9e98ad09..b33da53dae 100644 --- a/openair2/RRC/NR_UE/verify_RRC.c +++ b/openair2/RRC/NR_UE/verify_RRC.c @@ -81,9 +81,133 @@ static bool check_resourcesForChannelMeasurement(const NR_CSI_MeasConfig_t *meas return true; } +static bool check_csi_resourceMapping_consistency(NR_CSI_RS_ResourceMapping_t resourceMapping) +{ + bool valid = true; + struct NR_CSI_RS_ResourceMapping__density *density = &resourceMapping.density; + switch(resourceMapping.frequencyDomainAllocation.present) { + case NR_CSI_RS_ResourceMapping__frequencyDomainAllocation_PR_row1: + if (resourceMapping.nrofPorts != NR_CSI_RS_ResourceMapping__nrofPorts_p1) + valid = false; + else if (resourceMapping.cdm_Type != NR_CSI_RS_ResourceMapping__cdm_Type_noCDM) + valid = false; + else if (density->present != NR_CSI_RS_ResourceMapping__density_PR_three + && density->present != NR_CSI_RS_ResourceMapping__density_PR_NOTHING) + valid = false; + break; + case NR_CSI_RS_ResourceMapping__frequencyDomainAllocation_PR_row2: + if (resourceMapping.nrofPorts != NR_CSI_RS_ResourceMapping__nrofPorts_p1) + valid = false; + else if (resourceMapping.cdm_Type != NR_CSI_RS_ResourceMapping__cdm_Type_noCDM) + valid = false; + else if (density->present != NR_CSI_RS_ResourceMapping__density_PR_dot5 + && density->present != NR_CSI_RS_ResourceMapping__density_PR_one) + valid = false; + break; + case NR_CSI_RS_ResourceMapping__frequencyDomainAllocation_PR_row4: + if (resourceMapping.nrofPorts != NR_CSI_RS_ResourceMapping__nrofPorts_p4) + valid = false; + else if (resourceMapping.cdm_Type != NR_CSI_RS_ResourceMapping__cdm_Type_fd_CDM2) + valid = false; + else if (density->present != NR_CSI_RS_ResourceMapping__density_PR_one + && density->present != NR_CSI_RS_ResourceMapping__density_PR_NOTHING) + valid = false; + break; + case NR_CSI_RS_ResourceMapping__frequencyDomainAllocation_PR_other: + switch(resourceMapping.nrofPorts) { + case NR_CSI_RS_ResourceMapping__nrofPorts_p1: + valid = false; // 1 port is row 1 or 2 not other + break; + case NR_CSI_RS_ResourceMapping__nrofPorts_p2: + if (resourceMapping.cdm_Type != NR_CSI_RS_ResourceMapping__cdm_Type_fd_CDM2) + valid = false; + else if (density->present != NR_CSI_RS_ResourceMapping__density_PR_dot5 + && density->present != NR_CSI_RS_ResourceMapping__density_PR_one) + valid = false; + break; + case NR_CSI_RS_ResourceMapping__nrofPorts_p4: + // row 5 -> l0+1 + if (resourceMapping.firstOFDMSymbolInTimeDomain == 13) + valid = false; + else if (resourceMapping.cdm_Type != NR_CSI_RS_ResourceMapping__cdm_Type_fd_CDM2) + valid = false; + else if (density->present != NR_CSI_RS_ResourceMapping__density_PR_one + && density->present != NR_CSI_RS_ResourceMapping__density_PR_NOTHING) + valid = false; + break; + case NR_CSI_RS_ResourceMapping__nrofPorts_p8: { + uint8_t freq = BIT_STRING_to_uint8(&resourceMapping.frequencyDomainAllocation.choice.other); + if (resourceMapping.cdm_Type == NR_CSI_RS_ResourceMapping__cdm_Type_cdm4_FD2_TD2 || count_bits(&freq, 1) != 4) { + // row 7 and 8 -> l0+1 + if (resourceMapping.firstOFDMSymbolInTimeDomain == 13) + valid = false; + } + if (resourceMapping.cdm_Type != NR_CSI_RS_ResourceMapping__cdm_Type_fd_CDM2 + && resourceMapping.cdm_Type != NR_CSI_RS_ResourceMapping__cdm_Type_cdm4_FD2_TD2) + valid = false; + else if (density->present != NR_CSI_RS_ResourceMapping__density_PR_one + && density->present != NR_CSI_RS_ResourceMapping__density_PR_NOTHING) + valid = false; + break; + } + case NR_CSI_RS_ResourceMapping__nrofPorts_p12: + if (resourceMapping.cdm_Type == NR_CSI_RS_ResourceMapping__cdm_Type_cdm4_FD2_TD2) { + // row 10 -> l0+1 + if (resourceMapping.firstOFDMSymbolInTimeDomain == 13) + valid = false; + } else if (resourceMapping.cdm_Type != NR_CSI_RS_ResourceMapping__cdm_Type_fd_CDM2) + valid = false; + else if (density->present != NR_CSI_RS_ResourceMapping__density_PR_one + && density->present != NR_CSI_RS_ResourceMapping__density_PR_NOTHING) + valid = false; + break; + case NR_CSI_RS_ResourceMapping__nrofPorts_p16: + // l0 + 1 in all cases + if (resourceMapping.firstOFDMSymbolInTimeDomain == 13) + valid = false; + else if (resourceMapping.cdm_Type != NR_CSI_RS_ResourceMapping__cdm_Type_fd_CDM2 + && resourceMapping.cdm_Type != NR_CSI_RS_ResourceMapping__cdm_Type_cdm4_FD2_TD2) + valid = false; + else if (density->present != NR_CSI_RS_ResourceMapping__density_PR_dot5 + && density->present != NR_CSI_RS_ResourceMapping__density_PR_one) + valid = false; + case NR_CSI_RS_ResourceMapping__nrofPorts_p24: + case NR_CSI_RS_ResourceMapping__nrofPorts_p32: + if (resourceMapping.cdm_Type == NR_CSI_RS_ResourceMapping__cdm_Type_noCDM) + valid = false; + else if (resourceMapping.cdm_Type == NR_CSI_RS_ResourceMapping__cdm_Type_cdm4_FD2_TD2 + || resourceMapping.cdm_Type == NR_CSI_RS_ResourceMapping__cdm_Type_fd_CDM2) { + // row 13, 14 and 16, 17 -> l0+1 and l1 (which should be larger than l0+1 + if (resourceMapping.firstOFDMSymbolInTimeDomain == 13) + valid = false; + if (!resourceMapping.firstOFDMSymbolInTimeDomain2 + || *resourceMapping.firstOFDMSymbolInTimeDomain2 <= resourceMapping.firstOFDMSymbolInTimeDomain + 1) + valid = false; + } else { + // row 15 and 18 -> l0+3 + if (resourceMapping.firstOFDMSymbolInTimeDomain > 10) + valid = false; + } + if (valid + && (density->present != NR_CSI_RS_ResourceMapping__density_PR_dot5 + && density->present != NR_CSI_RS_ResourceMapping__density_PR_one)) + valid = false; + break; + default: + valid = false; + } + break; + default: + valid = false; + } + if (!valid) + return false; + return true; +} + static bool check_csi_report_consistency(const NR_CSI_MeasConfig_t *meas) { - if (!meas->csi_ReportConfigToAddModList) + if (!meas || !meas->csi_ReportConfigToAddModList) return true; for (int i = 0; i < meas->csi_ReportConfigToAddModList->list.count; i++) { NR_CSI_ReportConfig_t *csirep = meas->csi_ReportConfigToAddModList->list.array[i]; @@ -101,6 +225,28 @@ static bool check_csi_report_consistency(const NR_CSI_MeasConfig_t *meas) return true; } +static bool check_csi_resource_consistency(const NR_CSI_MeasConfig_t *meas) +{ + if (!meas || !meas->nzp_CSI_RS_ResourceToAddModList) + return true; + for (int i = 0; i < meas->nzp_CSI_RS_ResourceToAddModList->list.count; i++) { + NR_NZP_CSI_RS_Resource_t *res = meas->nzp_CSI_RS_ResourceToAddModList->list.array[i]; + if (!check_csi_resourceMapping_consistency(res->resourceMapping)) + return false; + } + return true; +} + +static bool check_csi_MeasConfig(struct NR_SetupRelease_CSI_MeasConfig *csi_MeasConfig) +{ + const NR_CSI_MeasConfig_t *meas = csi_MeasConfig->choice.setup; + if (!check_csi_report_consistency(meas)) + return false; + if (!check_csi_resource_consistency(meas)) + return false; + return true; +} + static bool check_srs_config(NR_SRS_Config_t *srs_Config) { if (srs_Config->srs_ResourceToAddModList) { @@ -131,7 +277,7 @@ bool check_cellgroup_config(const NR_CellGroupConfig_t *cgConfig) if (cgConfig->spCellConfig && cgConfig->spCellConfig->spCellConfigDedicated) { NR_ServingCellConfig_t *spCellConfigDedicated = cgConfig->spCellConfig->spCellConfigDedicated; if (spCellConfigDedicated->csi_MeasConfig) { - if (!check_csi_report_consistency(spCellConfigDedicated->csi_MeasConfig->choice.setup)) + if (!check_csi_MeasConfig(spCellConfigDedicated->csi_MeasConfig)) return false; } if (spCellConfigDedicated->uplinkConfig) { From 9b381f5d10490f85a83f3a916553f7da03d569c3 Mon Sep 17 00:00:00 2001 From: francescomani Date: Wed, 4 Feb 2026 16:31:46 +0100 Subject: [PATCH 025/100] declare RLF to re-establish or restart the connection if the ASN1 decoding fails --- openair2/RRC/NR_UE/rrc_UE.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/openair2/RRC/NR_UE/rrc_UE.c b/openair2/RRC/NR_UE/rrc_UE.c index 01a5b378be..2c0c4a25ac 100644 --- a/openair2/RRC/NR_UE/rrc_UE.c +++ b/openair2/RRC/NR_UE/rrc_UE.c @@ -1082,6 +1082,9 @@ static bool nr_rrc_ue_process_masterCellGroup(NR_UE_RRC_INST_t *rrc, masterCellGroup->size, 0, 0); if ((dec_rval.code != RC_OK) && (dec_rval.consumed == 0)) { LOG_E(NR_RRC, "CellGroupConfig decode error\n"); + // if the ASN1 decoding fails for the received CellGroup configuration + // we call the function for RLF (re-establishment if security is activated, going to IDLE otherwise) + handle_rlf_detection(rrc); return false; } if (LOG_DEBUGFLAG(DEBUG_ASN1)) { @@ -1552,6 +1555,9 @@ static void nr_rrc_ue_process_rrcReconfiguration(NR_UE_RRC_INST_t *rrc, int gNB_ LOG_E(NR_RRC, "\n"); // free the memory SEQUENCE_free(&asn_DEF_NR_CellGroupConfig, (void *)cellGroupConfig, 1); + // if the ASN1 decoding fails for the received CellGroup configuration + // we call the function for RLF (re-establishment if security is activated, going to IDLE otherwise) + handle_rlf_detection(rrc); } else { if (LOG_DEBUGFLAG(DEBUG_ASN1)) xer_fprint(stdout, &asn_DEF_NR_CellGroupConfig, (const void *) cellGroupConfig); From 1d5e2d84c2367af7f1d4c1819032ed190e73740d Mon Sep 17 00:00:00 2001 From: francescomani Date: Thu, 5 Feb 2026 15:01:55 +0100 Subject: [PATCH 026/100] add safety check to prevent segfault when dealing with DMRS mapping type for DLSCH and ULSCH (#1019) --- openair2/LAYER2/NR_MAC_UE/nr_ue_procedures.c | 45 ++++++++++++++++---- openair2/LAYER2/NR_MAC_UE/nr_ue_scheduler.c | 19 ++++++--- 2 files changed, 50 insertions(+), 14 deletions(-) diff --git a/openair2/LAYER2/NR_MAC_UE/nr_ue_procedures.c b/openair2/LAYER2/NR_MAC_UE/nr_ue_procedures.c index 672bc3c3a2..6bd3ad9fbe 100644 --- a/openair2/LAYER2/NR_MAC_UE/nr_ue_procedures.c +++ b/openair2/LAYER2/NR_MAC_UE/nr_ue_procedures.c @@ -843,10 +843,23 @@ static int nr_ue_process_dci_dl_10(NR_UE_MAC_INST_t *mac, dlsch_pdu->number_symbols = tda_info.nrOfSymbols; dlsch_pdu->start_symbol = tda_info.startSymbolIndex; - struct NR_DMRS_DownlinkConfig *dl_dmrs_config = NULL; - if (pdsch_config) - dl_dmrs_config = (tda_info.mapping_type == typeA) ? pdsch_config->dmrs_DownlinkForPDSCH_MappingTypeA->choice.setup - : pdsch_config->dmrs_DownlinkForPDSCH_MappingTypeB->choice.setup; + mappingType_t type = tda_info.mapping_type; + NR_DMRS_DownlinkConfig_t *dl_dmrs_config = NULL; + if (pdsch_config) { + if (type == typeA) { + if (!pdsch_config->dmrs_DownlinkForPDSCH_MappingTypeA) { + LOG_E(MAC, "Invalid PDSCH DMRS configuration, expected typeA but not configured\n"); + return -1; + } else + dl_dmrs_config = pdsch_config->dmrs_DownlinkForPDSCH_MappingTypeA->choice.setup; + } else { // typeB + if (!pdsch_config->dmrs_DownlinkForPDSCH_MappingTypeB) { + LOG_E(MAC, "Invalid PDSCH DMRS configuration, expected typeB but not configured\n"); + return -1; + } else + dl_dmrs_config = pdsch_config->dmrs_DownlinkForPDSCH_MappingTypeB->choice.setup; + } + } dlsch_pdu->nscid = 0; if (dl_dmrs_config && dl_dmrs_config->scramblingID0) @@ -1184,9 +1197,23 @@ static int nr_ue_process_dci_dl_11(NR_UE_MAC_INST_t *mac, dlsch_pdu->number_symbols = tda_info.nrOfSymbols; dlsch_pdu->start_symbol = tda_info.startSymbolIndex; - struct NR_DMRS_DownlinkConfig *dl_dmrs_config = (tda_info.mapping_type == typeA) - ? pdsch_Config->dmrs_DownlinkForPDSCH_MappingTypeA->choice.setup - : pdsch_Config->dmrs_DownlinkForPDSCH_MappingTypeB->choice.setup; + mappingType_t type = tda_info.mapping_type; + NR_DMRS_DownlinkConfig_t *dl_dmrs_config = NULL; + if (pdsch_Config) { + if (type == typeA) { + if (!pdsch_Config->dmrs_DownlinkForPDSCH_MappingTypeA) { + LOG_E(MAC, "Invalid PDSCH DMRS configuration, expected typeA but not configured\n"); + return -1; + } else + dl_dmrs_config = pdsch_Config->dmrs_DownlinkForPDSCH_MappingTypeA->choice.setup; + } else { // typeB + if (!pdsch_Config->dmrs_DownlinkForPDSCH_MappingTypeB) { + LOG_E(MAC, "Invalid PDSCH DMRS configuration, expected typeB but not configured\n"); + return -1; + } else + dl_dmrs_config = pdsch_Config->dmrs_DownlinkForPDSCH_MappingTypeB->choice.setup; + } + } switch (dci->dmrs_sequence_initialization.val) { case 0: @@ -1417,9 +1444,9 @@ static int nr_ue_process_dci_dl_11(NR_UE_MAC_INST_t *mac, dlsch_pdu->tbslbrm = nr_compute_tbslbrm(dlsch_pdu->mcs_table, sc_info->dl_bw_tbslbrm, nl_tbslbrm); /*PTRS configuration */ dlsch_pdu->pduBitmap = 0; - if (pdsch_Config->dmrs_DownlinkForPDSCH_MappingTypeA->choice.setup->phaseTrackingRS != NULL) { + if (dl_dmrs_config->phaseTrackingRS != NULL) { bool valid_ptrs_setup = - set_dl_ptrs_values(pdsch_Config->dmrs_DownlinkForPDSCH_MappingTypeA->choice.setup->phaseTrackingRS->choice.setup, + set_dl_ptrs_values(dl_dmrs_config->phaseTrackingRS->choice.setup, dlsch_pdu->number_rbs, dlsch_pdu->mcs, dlsch_pdu->mcs_table, diff --git a/openair2/LAYER2/NR_MAC_UE/nr_ue_scheduler.c b/openair2/LAYER2/NR_MAC_UE/nr_ue_scheduler.c index fcc977d9e5..d06c8302a6 100644 --- a/openair2/LAYER2/NR_MAC_UE/nr_ue_scheduler.c +++ b/openair2/LAYER2/NR_MAC_UE/nr_ue_scheduler.c @@ -598,12 +598,21 @@ int nr_config_pusch_pdu(NR_UE_MAC_INST_t *mac, } int mappingtype = tda_info->mapping_type; - NR_DMRS_UplinkConfig_t *NR_DMRS_ulconfig = NULL; - if(pusch_Config) { - NR_DMRS_ulconfig = (mappingtype == NR_PUSCH_TimeDomainResourceAllocation__mappingType_typeA) - ? pusch_Config->dmrs_UplinkForPUSCH_MappingTypeA->choice.setup - : pusch_Config->dmrs_UplinkForPUSCH_MappingTypeB->choice.setup; + if (pusch_Config) { + if (mappingtype == NR_PUSCH_TimeDomainResourceAllocation__mappingType_typeA) { + if (!pusch_Config->dmrs_UplinkForPUSCH_MappingTypeA) { + LOG_E(MAC, "Invalid PUSCH DMRS configuration, expected typeA but not configured\n"); + return -1; + } else + NR_DMRS_ulconfig = pusch_Config->dmrs_UplinkForPUSCH_MappingTypeA->choice.setup; + } else { // typeB + if (!pusch_Config->dmrs_UplinkForPUSCH_MappingTypeB) { + LOG_E(MAC, "Invalid PDSCH DMRS configuration, expected typeB but not configured\n"); + return -1; + } else + NR_DMRS_ulconfig = pusch_Config->dmrs_UplinkForPUSCH_MappingTypeB->choice.setup; + } } pusch_config_pdu->scid = 0; From 70726903f3985c552acda06c33f610226c0fe5ec Mon Sep 17 00:00:00 2001 From: Nika Ghaderi Date: Mon, 22 Sep 2025 15:50:45 +0000 Subject: [PATCH 027/100] Configure CMake for CUDA and SSE simulations This commit adds the initial infrastructure for building accelerated simulation components. - Adds the CUDA_ENABLE option and logic to find the CUDA toolkit. - Adds the CHANNEL_SSE option to enable SSE-specific code paths. - Sets required properties on the SIMU target to allow linking with CUDA-compiled objects. - Creates the initial oai_cuda.h header for the public API. --- CMakeLists.txt | 42 +++++++++++++++++++- openair1/SIMULATION/CMakeLists.txt | 1 + openair1/SIMULATION/TOOLS/CMakeLists.txt | 31 +++++++++++++++ openair1/SIMULATION/TOOLS/oai_cuda.h | 49 ++++++++++++++++++++++++ 4 files changed, 122 insertions(+), 1 deletion(-) create mode 100644 openair1/SIMULATION/TOOLS/CMakeLists.txt create mode 100644 openair1/SIMULATION/TOOLS/oai_cuda.h diff --git a/CMakeLists.txt b/CMakeLists.txt index bae1c590bc..73aa506f33 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -24,6 +24,21 @@ project (OpenAirInterface LANGUAGES C CXX) set(CMAKE_CXX_STANDARD 17) set(OAI_VERSION 2.4.0) +option(CUDA_ENABLE "Enable CUDA accelerated channel simulation" OFF) +if(CUDA_ENABLE) + find_package(CUDA REQUIRED) + find_package(CUDAToolkit REQUIRED) + message(STATUS "CUDA explicitly enabled, building with GPU acceleration support.") + + enable_language(CUDA) + + if(CMAKE_SYSTEM_PROCESSOR STREQUAL "aarch64") + set(CMAKE_CUDA_ARCHITECTURES 90) + endif() + + add_compile_definitions(ENABLE_CUDA) +endif() + ######################################################### # Base directories, compatible with legacy OAI building # ######################################################### @@ -1687,6 +1702,17 @@ set (SIMUSRC add_library(SIMU STATIC ${SIMUSRC} ) target_include_directories(SIMU PUBLIC ${OPENAIR1_DIR}/SIMULATION/TOOLS ${OPENAIR1_DIR}/SIMULATION/RF) +option(CHANNEL_SSE "Enable SSE optimizations for channel simulation" OFF) +if(CHANNEL_SSE) + message(STATUS "SSE-optimized channel simulation enabled.") + target_compile_definitions(SIMU PRIVATE CHANNEL_SSE) +endif() + +if(CUDA_FOUND) + set_property(TARGET SIMU PROPERTY CUDA_ARCHITECTURES ${CMAKE_CUDA_ARCHITECTURES}) + set_property(TARGET SIMU PROPERTY LINKER_LANGUAGE CXX) +endif() + include_directories("${NFAPI_DIR}/nfapi/public_inc") include_directories("${NFAPI_DIR}/common/public_inc") include_directories("${NFAPI_DIR}/pnf/public_inc") @@ -2084,10 +2110,19 @@ target_link_libraries(nr_srssim PRIVATE m pthread ITTI dl nr_ue_phy_meas physim_common ) +if(CUDA_ENABLE) + if (TARGET oai_cuda_lib) + target_link_libraries(nr_dlsim PRIVATE oai_cuda_lib) + target_link_libraries(nr_ulsim PRIVATE oai_cuda_lib) + + target_compile_definitions(nr_dlsim PRIVATE ENABLE_CUDA) + target_compile_definitions(nr_ulsim PRIVATE ENABLE_CUDA) + endif() +endif() + # these simulators do not compile: # dlsim_tm7 pbchsim scansim mbmssim pdcchsim pucchsim prachsim syncsim foreach(myExe dlsim ulsim) - add_executable(${myExe} ${OPENAIR1_DIR}/SIMULATION/LTE_PHY/${myExe}.c ${OPENAIR_DIR}/common/utils/lte/ue_power.c @@ -2197,6 +2232,11 @@ add_subdirectory(openair3) add_subdirectory(radio) add_subdirectory(tests) +if(TARGET oai_cuda_lib) + message(STATUS "CUDA library 'oai_cuda_lib' found, linking to targets...") + target_link_libraries(nr_dlsim PRIVATE oai_cuda_lib) + target_link_libraries(nr_ulsim PRIVATE oai_cuda_lib) +endif() if(PACKAGING_LTE OR PACKAGING_NR OR PACKAGING_COMMON OR PACKAGING_USRP OR PACKAGING_PHYSIM) include("tools/packages/packages.cmake") diff --git a/openair1/SIMULATION/CMakeLists.txt b/openair1/SIMULATION/CMakeLists.txt index b93118ccb7..a9497b0658 100644 --- a/openair1/SIMULATION/CMakeLists.txt +++ b/openair1/SIMULATION/CMakeLists.txt @@ -1,3 +1,4 @@ if(ENABLE_PHYSIM_TESTS) add_subdirectory(tests) + add_subdirectory(TOOLS) endif() diff --git a/openair1/SIMULATION/TOOLS/CMakeLists.txt b/openair1/SIMULATION/TOOLS/CMakeLists.txt new file mode 100644 index 0000000000..26397509fd --- /dev/null +++ b/openair1/SIMULATION/TOOLS/CMakeLists.txt @@ -0,0 +1,31 @@ +#/* +# * 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 +# */ + +if(CUDA_ENABLE) + add_library(oai_cuda_lib STATIC + ) + + target_link_libraries(oai_cuda_lib PUBLIC CUDA::toolkit) + + target_include_directories(oai_cuda_lib PUBLIC + ${CMAKE_CURRENT_SOURCE_DIR} + ) +endif() diff --git a/openair1/SIMULATION/TOOLS/oai_cuda.h b/openair1/SIMULATION/TOOLS/oai_cuda.h new file mode 100644 index 0000000000..37055a472b --- /dev/null +++ b/openair1/SIMULATION/TOOLS/oai_cuda.h @@ -0,0 +1,49 @@ +/* + * 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 __OAI_CUDA_H__ +#define __OAI_CUDA_H__ + +#include + + +#ifdef __NVCC__ +#else + #include "PHY/TOOLS/tools_defs.h" +#endif + +#ifdef __NVCC__ + #include + // Kernels go here. +#endif // __NVCC__ + + +#ifdef __cplusplus +extern "C" { +#endif + + // Cuda function declarations will go here. + +#ifdef __cplusplus +} +#endif + +#endif // __OAI_CUDA_H__ From c54f0f795509bbb926666f904ca67c37ffc542c3 Mon Sep 17 00:00:00 2001 From: Nika Ghaderi Date: Mon, 22 Sep 2025 15:57:42 +0000 Subject: [PATCH 028/100] Refactor and extend CPU channel model with float support This commit refactors the CPU-based channel model for improved reusability and extends it with single-precision floating-point support. This serves as a performance and correctness baseline for the new CUDA implementation. Key changes include: - The original double-based multipath_channel function is refactored to remove hardcoded antenna dimensions from its signature. It now accepts dynamically sized arrays, making it more flexible for use in simulators like dlsim and ulsim. - A new multipath_channel_float function is introduced to operate on float data types, matching the GPU's native format. - A corresponding add_noise_float function is also added to complete the floating-point CPU pipeline. - The new float-based functions are designed to work with an interleaved IQ sample format ([I, Q, I, Q]), which is more suitable for vectorized (SSE) and GPU processing. --- openair1/SIMULATION/TOOLS/multipath_channel.c | 423 ++++++++++++------ openair1/SIMULATION/TOOLS/phase_noise.c | 31 +- openair1/SIMULATION/TOOLS/sim.h | 34 +- 3 files changed, 350 insertions(+), 138 deletions(-) diff --git a/openair1/SIMULATION/TOOLS/multipath_channel.c b/openair1/SIMULATION/TOOLS/multipath_channel.c index aeaa4f811f..de12663f1f 100644 --- a/openair1/SIMULATION/TOOLS/multipath_channel.c +++ b/openair1/SIMULATION/TOOLS/multipath_channel.c @@ -28,147 +28,144 @@ uint8_t multipath_channel_nosigconv(channel_desc_t *desc) { - random_channel(desc,0); - return(1); + random_channel(desc, 0); + return (1); +} + +void interleave_channel_output(float **rx_sig_re, float **rx_sig_im, float **output_interleaved, int nb_rx, int num_samples) +{ + for (int i = 0; i < nb_rx; i++) { + for (int j = 0; j < num_samples; j++) { + output_interleaved[i][2 * j] = rx_sig_re[i][j]; + output_interleaved[i][2 * j + 1] = rx_sig_im[i][j]; + } + } } //#define CHANNEL_SSE + #ifdef CHANNEL_SSE -void __attribute__ ((no_sanitize_address)) multipath_channel(channel_desc_t *desc, - double tx_sig_re[NB_ANTENNAS_TX][30720*2], - double tx_sig_im[NB_ANTENANS_TX][30720*2], - double rx_sig_re[NB_ANTENNAS_RX][30720*2], - double rx_sig_im[NB_ANTENNAS_RX][30720*2], - uint32_t length, - uint8_t keep_channel, - int log_channel) +void __attribute__((no_sanitize_address)) multipath_channel(channel_desc_t *desc, + double **tx_sig_re, + double **tx_sig_im, + double **rx_sig_re, + double **rx_sig_im, + uint32_t length, + uint8_t keep_channel, + int log_channel) { + int i, ii, j, l; + // int simd_length; + // int simd_length, tail_start; + simde__m128d rx_tmp128_re, rx_tmp128_im, tx128_re, tx128_im, ch128_r, ch128_i, pathloss128; - int i,ii,j,l; - int length1, length2, tail; - simde__m128d rx_tmp128_re_f, rx_tmp128_im_f, rx_tmp128_re, rx_tmp128_im, rx_tmp128_1, rx_tmp128_2, rx_tmp128_3, rx_tmp128_4, - tx128_re, tx128_im, ch128_x, ch128_y, pathloss128; - - double path_loss = pow(10,desc->path_loss_dB/20); + double path_loss = pow(10, desc->path_loss_dB / 20); uint64_t dd = desc->channel_offset; pathloss128 = simde_mm_set1_pd(path_loss); -#ifdef DEBUG_CH - printf("[CHANNEL] keep = %d : path_loss = %g (%f), nb_rx %d, nb_tx %d, dd %lu, len %d \n",keep_channel,path_loss,desc->path_loss_dB,desc->nb_rx,desc->nb_tx,dd,desc->channel_length); -#endif - - if (keep_channel) { - // do nothing - keep channel - } else { - random_channel(desc,0); + if (keep_channel == 0) { + random_channel(desc, 0); } - start_meas(&desc->convolution); + // simd_length = (length - dd) / 2; + // tail_start = simd_length * 2; -#ifdef DEBUG_CH + for (i = 0; i < (int)(length - dd); i += 2) { + for (ii = 0; ii < desc->nb_rx; ii++) { + rx_tmp128_re = simde_mm_setzero_pd(); + rx_tmp128_im = simde_mm_setzero_pd(); - for (int l = 0; l<(int)desc->channel_length; l++) { - printf("%p (%f,%f) ",desc->ch[0],desc->ch[0][l].x,desc->ch[0][l].y); - } - - printf("\n"); -#endif - - tail = ((int)length-dd)%2; - - if(tail) - length1 = ((int)length-dd)-1; - else - length1 = ((int)length-dd); - - length2 = length1/2; - - for (i=0; inb_rx; ii++) { - // rx_tmp.x = 0; - // rx_tmp.y = 0; - rx_tmp128_re_f = simde_mm_setzero_pd(); - rx_tmp128_im_f = simde_mm_setzero_pd(); - - for (j=0; jnb_tx; j++) { - for (l = 0; l<(int)desc->channel_length; l++) { - if ((i>=0) && (i-l)>=0) { //SIMD correct only if length1 > 2*channel_length...which is almost always satisfied - // tx.x = tx_sig_re[j][i-l]; - // tx.y = tx_sig_im[j][i-l]; - tx128_re = simde_mm_loadu_pd(&tx_sig_re[j][2 * i - l]); // tx_sig_re[j][i-l+1], tx_sig_re[j][i-l] - tx128_im = simde_mm_loadu_pd(&tx_sig_im[j][2 * i - l]); - } else { - //tx.x =0; - //tx.y =0; - tx128_re = simde_mm_setzero_pd(); - tx128_im = simde_mm_setzero_pd(); + for (i = 0; i < (int)(length - dd); i += 2) { + for (ii = 0; ii < desc->nb_rx; ii++) { + rx_tmp128_re = simde_mm_setzero_pd(); + rx_tmp128_im = simde_mm_setzero_pd(); + for (j = 0; j < desc->nb_tx; j++) { + for (l = 0; l < (int)desc->channel_length; l++) { + double tx_re[2] = {0.0, 0.0}, tx_im[2] = {0.0, 0.0}; + if ((i - l) >= 0) + tx_re[0] = tx_sig_re[j][i - l]; + if ((i + 1 - l) >= 0) + tx_re[1] = tx_sig_re[j][i + 1 - l]; + if ((i - l) >= 0) + tx_im[0] = tx_sig_im[j][i - l]; + if ((i + 1 - l) >= 0) + tx_im[1] = tx_sig_im[j][i + 1 - l]; + tx128_re = simde_mm_loadu_pd(tx_re); + tx128_im = simde_mm_loadu_pd(tx_im); + ch128_r = simde_mm_set1_pd(desc->ch[ii + (j * desc->nb_rx)][l].r); + ch128_i = simde_mm_set1_pd(desc->ch[ii + (j * desc->nb_rx)][l].i); + rx_tmp128_re = + simde_mm_add_pd(rx_tmp128_re, + simde_mm_sub_pd(simde_mm_mul_pd(tx128_re, ch128_r), simde_mm_mul_pd(tx128_im, ch128_i))); + rx_tmp128_im = + simde_mm_add_pd(rx_tmp128_im, + simde_mm_add_pd(simde_mm_mul_pd(tx128_re, ch128_i), simde_mm_mul_pd(tx128_im, ch128_r))); + } } - - ch128_x = simde_mm_set1_pd(desc->ch[ii + (j * desc->nb_rx)][l].x); - ch128_y = simde_mm_set1_pd(desc->ch[ii + (j * desc->nb_rx)][l].y); - // rx_tmp.x += (tx.x * desc->ch[ii+(j*desc->nb_rx)][l].x) - (tx.y * desc->ch[ii+(j*desc->nb_rx)][l].y); - // rx_tmp.y += (tx.y * desc->ch[ii+(j*desc->nb_rx)][l].x) + (tx.x * desc->ch[ii+(j*desc->nb_rx)][l].y); - rx_tmp128_1 = simde_mm_mul_pd(tx128_re, ch128_x); - rx_tmp128_2 = simde_mm_mul_pd(tx128_re, ch128_y); - rx_tmp128_3 = simde_mm_mul_pd(tx128_im, ch128_x); - rx_tmp128_4 = simde_mm_mul_pd(tx128_im, ch128_y); - rx_tmp128_re = simde_mm_sub_pd(rx_tmp128_1, rx_tmp128_4); - rx_tmp128_im = simde_mm_add_pd(rx_tmp128_2, rx_tmp128_3); - rx_tmp128_re_f = simde_mm_add_pd(rx_tmp128_re_f, rx_tmp128_re); - rx_tmp128_im_f = simde_mm_add_pd(rx_tmp128_im_f, rx_tmp128_im); - } //l - } // j - - //rx_sig_re[ii][i+dd] = rx_tmp.x*path_loss; - //rx_sig_im[ii][i+dd] = rx_tmp.y*path_loss; - rx_tmp128_re_f = simde_mm_mul_pd(rx_tmp128_re_f, pathloss128); - rx_tmp128_im_f = simde_mm_mul_pd(rx_tmp128_im_f, pathloss128); - simde_mm_storeu_pd(&rx_sig_re[ii][2 * i + dd], rx_tmp128_re_f); // max index: length-dd -1 + dd = length -1 - simde_mm_storeu_pd(&rx_sig_im[ii][2 * i + dd], rx_tmp128_im_f); - /* - if ((ii==0)&&((i%32)==0)) { - printf("%p %p %f,%f => %e,%e\n",rx_sig_re[ii],rx_sig_im[ii],rx_tmp.x,rx_tmp.y,rx_sig_re[ii][i-dd],rx_sig_im[ii][i-dd]); + simde_mm_storeu_pd(&rx_sig_re[ii][i + dd], simde_mm_mul_pd(rx_tmp128_re, pathloss128)); + simde_mm_storeu_pd(&rx_sig_im[ii][i + dd], simde_mm_mul_pd(rx_tmp128_im, pathloss128)); + } } - */ - //rx_sig_re[ii][i] = sqrt(.5)*(tx_sig_re[0][i] + tx_sig_re[1][i]); - //rx_sig_im[ii][i] = sqrt(.5)*(tx_sig_im[0][i] + tx_sig_im[1][i]); - } // ii } // i - stop_meas(&desc->convolution); - + // Handle the final sample if the length is odd + if ((length - dd) % 2) { + int i_tail = length - dd - 1; + for (ii = 0; ii < desc->nb_rx; ii++) { + struct complexd rx_tmp = {0}; + for (j = 0; j < desc->nb_tx; j++) { + struct complexd *chan = desc->ch[ii + (j * desc->nb_rx)]; + for (l = 0; l < (int)desc->channel_length; l++) { + if ((i_tail - l) >= 0) { + struct complexd tx; + tx.r = tx_sig_re[j][i_tail - l]; + tx.i = tx_sig_im[j][i_tail - l]; + rx_tmp.r += (tx.r * chan[l].r) - (tx.i * chan[l].i); + rx_tmp.i += (tx.i * chan[l].r) + (tx.r * chan[l].i); + } + } + } + rx_sig_re[ii][i_tail + dd] = rx_tmp.r * path_loss; + rx_sig_im[ii][i_tail + dd] = rx_tmp.i * path_loss; + } + } } #else -void __attribute__ ((no_sanitize_address)) multipath_channel(channel_desc_t *desc, - double *tx_sig_re[NB_ANTENNAS_TX], - double *tx_sig_im[NB_ANTENNAS_TX], - double *rx_sig_re[NB_ANTENNAS_RX], - double *rx_sig_im[NB_ANTENNAS_RX], - uint32_t length, - uint8_t keep_channel, - int log_channel) +void __attribute__((no_sanitize_address)) multipath_channel(channel_desc_t *desc, + double **tx_sig_re, + double **tx_sig_im, + double **rx_sig_re, + double **rx_sig_im, + uint32_t length, + uint8_t keep_channel, + int log_channel) { - - double path_loss = pow(10,desc->path_loss_dB/20); + double path_loss = pow(10, desc->path_loss_dB / 20); uint64_t dd = desc->channel_offset; #ifdef DEBUG_CH printf("[CHANNEL] keep = %d : path_loss = %g (%f), nb_rx %d, nb_tx %d, dd %lu, len %d \n", - keep_channel, path_loss, desc->path_loss_dB, desc->nb_rx, desc->nb_tx, dd, desc->channel_length); + keep_channel, + path_loss, + desc->path_loss_dB, + desc->nb_rx, + desc->nb_tx, + dd, + desc->channel_length); #endif if (keep_channel) { // do nothing - keep channel } else { - random_channel(desc,0); + random_channel(desc, 0); } #ifdef DEBUG_CH - for (int l = 0; l<(int)desc->channel_length; l++) { + for (int l = 0; l < (int)desc->channel_length; l++) { printf("ch[%i] = (%f, %f)\n", l, desc->ch[0][l].r, desc->ch[0][l].i); } #endif @@ -178,27 +175,27 @@ void __attribute__ ((no_sanitize_address)) multipath_channel(channel_desc_t *des get_cexp_doppler(cexp_doppler, desc, length); } - for (int i=0; i<((int)length-dd); i++) { - for (int ii=0; iinb_rx; ii++) { - struct complexd rx_tmp={0}; - for (int j=0; jnb_tx; j++) { - struct complexd *chan=desc->ch[ii+(j*desc->nb_rx)]; - for (int l = 0; l<(int)desc->channel_length; l++) { - if ((i>=0) && (i-l)>=0) { + for (int i = 0; i < ((int)length - dd); i++) { + for (int ii = 0; ii < desc->nb_rx; ii++) { + struct complexd rx_tmp = {0}; + for (int j = 0; j < desc->nb_tx; j++) { + struct complexd *chan = desc->ch[ii + (j * desc->nb_rx)]; + for (int l = 0; l < (int)desc->channel_length; l++) { + if ((i >= 0) && (i - l) >= 0) { struct complexd tx; - tx.r = tx_sig_re[j][i-l]; - tx.i = tx_sig_im[j][i-l]; + tx.r = tx_sig_re[j][i - l]; + tx.i = tx_sig_im[j][i - l]; rx_tmp.r += (tx.r * chan[l].r) - (tx.i * chan[l].i); rx_tmp.i += (tx.i * chan[l].r) + (tx.r * chan[l].i); } - #if 0 +#if 0 if (i==0 && log_channel == 1) { printf("channel[%d][%d][%d] = %f dB \t(%e, %e)\n", ii, j, l, 10 * log10(pow(chan[l].r, 2.0) + pow(chan[l].i, 2.0)), chan[l].r, chan[l].i); } - #endif - } //l - } // j +#endif + } // l + } // j #if 0 if (desc->max_Doppler != 0.0) rx_tmp = cdMul(rx_tmp, cexp_doppler[i]); @@ -206,26 +203,190 @@ void __attribute__ ((no_sanitize_address)) multipath_channel(channel_desc_t *des #ifdef DOPPLER_DEBUG printf("[k %2i] cexp_doppler = (%7.4f, %7.4f), abs(cexp_doppler) = %.4f\n", - i, - cexp_doppler[i].r, - cexp_doppler[i].i, - sqrt(cexp_doppler[i].r * cexp_doppler[i].r + cexp_doppler[i].i * cexp_doppler[i].i)); + i, + cexp_doppler[i].r, + cexp_doppler[i].i, + sqrt(cexp_doppler[i].r * cexp_doppler[i].r + cexp_doppler[i].i * cexp_doppler[i].i)); #endif - rx_sig_re[ii][i+dd] = rx_tmp.r*path_loss; - rx_sig_im[ii][i+dd] = rx_tmp.i*path_loss; + rx_sig_re[ii][i + dd] = rx_tmp.r * path_loss; + rx_sig_im[ii][i + dd] = rx_tmp.i * path_loss; #ifdef DEBUG_CH - if ((i%32)==0) { - printf("rx aa %d: %f, %f => %e, %e\n", - ii, rx_tmp.r, rx_tmp.i, rx_sig_re[ii][i-dd], rx_sig_im[ii][i-dd]); - } -#endif - //rx_sig_re[ii][i] = sqrt(.5)*(tx_sig_re[0][i] + tx_sig_re[1][i]); - //rx_sig_im[ii][i] = sqrt(.5)*(tx_sig_im[0][i] + tx_sig_im[1][i]); + if ((i % 32) == 0) { + printf("rx aa %d: %f, %f => %e, %e\n", ii, rx_tmp.r, rx_tmp.i, rx_sig_re[ii][i - dd], rx_sig_im[ii][i - dd]); + } +#endif + // rx_sig_re[ii][i] = sqrt(.5)*(tx_sig_re[0][i] + tx_sig_re[1][i]); + // rx_sig_im[ii][i] = sqrt(.5)*(tx_sig_im[0][i] + tx_sig_im[1][i]); } // ii } // i } #endif +#ifdef CHANNEL_SSE +void __attribute__((no_sanitize_address)) multipath_channel_float(channel_desc_t *desc, + float **tx_sig_interleaved, + float **rx_sig_re, + float **rx_sig_im, + uint32_t length, + uint8_t keep_channel, + int log_channel) +{ + int i, ii, j, l; + simde__m128 rx_tmp128_re, rx_tmp128_im, tx128_re, tx128_im, ch128_r, ch128_i, pathloss128; + uint64_t dd = desc->channel_offset; + if (keep_channel == 0) { + random_channel(desc, 0); + } + + struct complexd cexp_doppler[length]; + if (desc->max_Doppler != 0.0) { + get_cexp_doppler(cexp_doppler, desc, length); + } + + if (dd >= length) { + return; // No samples to process + } + + for (i = 0; i <= (int)(length - dd) - 4; i += 4) { + for (ii = 0; ii < desc->nb_rx; ii++) { + rx_tmp128_re = simde_mm_setzero_ps(); + rx_tmp128_im = simde_mm_setzero_ps(); + + for (j = 0; j < desc->nb_tx; j++) { + for (l = 0; l < (int)desc->channel_length; l++) { + int idx = i - l; + if (idx >= 0) { + // Load 2 complex numbers (4 floats) => {I0, Q0, I1, Q1} + simde__m128 vec0 = simde_mm_loadu_ps(&tx_sig_interleaved[j][2 * idx]); + simde__m128 vec1 = simde_mm_loadu_ps(&tx_sig_interleaved[j][2 * (idx + 2)]); + tx128_re = simde_mm_shuffle_ps(vec0, vec1, SIMDE_MM_SHUFFLE(2, 0, 2, 0)); // {I0,I1,I2,I3} + tx128_im = simde_mm_shuffle_ps(vec0, vec1, SIMDE_MM_SHUFFLE(3, 1, 3, 1)); // {Q0,Q1,Q2,Q3} + } else { + tx128_re = simde_mm_setzero_ps(); + tx128_im = simde_mm_setzero_ps(); + } + + ch128_r = simde_mm_set1_ps((float)desc->ch[ii + (j * desc->nb_rx)][l].r); + ch128_i = simde_mm_set1_ps((float)desc->ch[ii + (j * desc->nb_rx)][l].i); + + // re = (tx_re * ch_r) - (tx_im * ch_i) + rx_tmp128_re = simde_mm_add_ps(rx_tmp128_re, + simde_mm_sub_ps(simde_mm_mul_ps(tx128_re, ch128_r), simde_mm_mul_ps(tx128_im, ch128_i))); + // im = (tx_re * ch_i) + (tx_im * ch_r) + rx_tmp128_im = simde_mm_add_ps(rx_tmp128_im, + simde_mm_add_ps(simde_mm_mul_ps(tx128_re, ch128_i), simde_mm_mul_ps(tx128_im, ch128_r))); + } + } + +#if 0 + if (desc->max_Doppler != 0.0) { + float doppler_re[4] = {(float)cexp_doppler[i].r, (float)cexp_doppler[i+1].r, (float)cexp_doppler[i+2].r, (float)cexp_doppler[i+3].r}; + float doppler_im[4] = {(float)cexp_doppler[i].i, (float)cexp_doppler[i+1].i, (float)cexp_doppler[i+2].i, (float)cexp_doppler[i+3].i}; + + simde__m128 doppler128_r = simde_mm_loadu_ps(doppler_re); + simde__m128 doppler128_i = simde_mm_loadu_ps(doppler_im); + + simde__m128 temp_re = rx_tmp128_re; + simde__m128 temp_im = rx_tmp128_im; + + rx_tmp128_re = simde_mm_sub_ps(simde_mm_mul_ps(temp_re, doppler128_r), simde_mm_mul_ps(temp_im, doppler128_i)); + rx_tmp128_im = simde_mm_add_ps(simde_mm_mul_ps(temp_im, doppler128_r), simde_mm_mul_ps(temp_re, doppler128_i)); + } +#endif + simde_mm_storeu_ps(&rx_sig_re[ii][i + dd], rx_tmp128_re); + simde_mm_storeu_ps(&rx_sig_im[ii][i + dd], rx_tmp128_im); + } + } + + // Handle remaining samples (tail loop) that are not a multiple of 4 + for (; i < (int)(length - dd); i++) { + for (ii = 0; ii < desc->nb_rx; ii++) { + struct complexf rx_tmp = {0.0f, 0.0f}; + for (j = 0; j < desc->nb_tx; j++) { + struct complexd *chan = desc->ch[ii + (j * desc->nb_rx)]; + for (l = 0; l < (int)desc->channel_length; l++) { + if ((i - l) >= 0) { + struct complexf tx; + tx.r = tx_sig_interleaved[j][2 * (i - l)]; + tx.i = tx_sig_interleaved[j][2 * (i - l) + 1]; + rx_tmp.r += (tx.r * (float)chan[l].r) - (tx.i * (float)chan[l].i); + rx_tmp.i += (tx.i * (float)chan[l].r) + (tx.r * (float)chan[l].i); + } + } + } +#if 0 + if (desc->max_Doppler != 0.0) { + struct complexf doppler_factor = {(float)cexp_doppler[i].r, (float)cexp_doppler[i].i}; + struct complexf temp = rx_tmp; + rx_tmp.r = (temp.r * doppler_factor.r) - (temp.i * doppler_factor.i); + rx_tmp.i = (temp.i * doppler_factor.r) + (temp.r * doppler_factor.i); + } +#endif + rx_sig_re[ii][i + dd] = rx_tmp.r; + rx_sig_im[ii][i + dd] = rx_tmp.i; + } + } +} + +#else +void multipath_channel_float(channel_desc_t *desc, + float **tx_sig_interleaved, + float **rx_sig_re, + float **rx_sig_im, + uint32_t length, + uint8_t keep_channel, + int log_channel) +{ + uint64_t dd = desc->channel_offset; + + if (keep_channel == 0) { + random_channel(desc, 0); + } + + struct complexd cexp_doppler[length]; + if (desc->max_Doppler != 0.0) { + get_cexp_doppler(cexp_doppler, desc, length); + } + + if (dd >= length) { + return; + } + + for (int i = 0; i < ((int)length - dd); i++) { + for (int ii = 0; ii < desc->nb_rx; ii++) { + struct complexf rx_tmp = {0.0f, 0.0f}; + + for (int j = 0; j < desc->nb_tx; j++) { + struct complexd *chan = desc->ch[ii + (j * desc->nb_rx)]; + + for (int l = 0; l < (int)desc->channel_length; l++) { + if ((i - l) >= 0) { + struct complexf tx; + tx.r = tx_sig_interleaved[j][2 * (i - l)]; + tx.i = tx_sig_interleaved[j][2 * (i - l) + 1]; + rx_tmp.r += (tx.r * (float)chan[l].r) - (tx.i * (float)chan[l].i); + rx_tmp.i += (tx.i * (float)chan[l].r) + (tx.r * (float)chan[l].i); + } + } // l (channel_length) + } // j (nb_tx) + +#if 0 + if (desc->max_Doppler != 0.0) { + // Perform complex multiplication: rx_tmp = rx_tmp * cexp_doppler[i] + struct complexf doppler_factor = {(float)cexp_doppler[i].r, (float)cexp_doppler[i].i}; + struct complexf temp = rx_tmp; + rx_tmp.r = (temp.r * doppler_factor.r) - (temp.i * doppler_factor.i); + rx_tmp.i = (temp.i * doppler_factor.r) + (temp.r * doppler_factor.i); + } +#endif + rx_sig_re[ii][i + dd] = rx_tmp.r; + rx_sig_im[ii][i + dd] = rx_tmp.i; + + } // ii (nb_rx) + } // i (length) +} + +#endif diff --git a/openair1/SIMULATION/TOOLS/phase_noise.c b/openair1/SIMULATION/TOOLS/phase_noise.c index 5bb6df3b5e..e443629ee1 100644 --- a/openair1/SIMULATION/TOOLS/phase_noise.c +++ b/openair1/SIMULATION/TOOLS/phase_noise.c @@ -22,12 +22,11 @@ #include #include "sim.h" - /* linear phase noise model */ void phase_noise(double ts, int16_t *InRe, int16_t *InIm) { - static uint64_t i=0; - double fd = 300;//0.01*30000 + static uint64_t i = 0; + double fd = 300; // 0.01*30000 double phase = (double)(i * fd * ts); c16_t rot = get_sin_cos(phase); c16_t val = {.r = *InRe, .i = *InIm}; @@ -61,3 +60,29 @@ void add_noise(c16_t **rxdata, } } } + +void add_noise_float(c16_t **rxdata, + const float **r_re, + const float **r_im, + const float sigma, + const int length, + const int slot_offset, + const double ts, + const int delay, + bool apply_phase_noise, + const uint8_t nb_antennas_rx) +{ + float sqrt_sigma_div2 = sqrtf(sigma / 2.0f); + + for (int i = 0; i < length; i++) { + for (int ap = 0; ap < nb_antennas_rx; ap++) { + c16_t *rxd = &rxdata[ap][slot_offset + i + delay]; + rxd->r = (int16_t)(r_re[ap][i] + (gaussZiggurat(0.0, 1.0) * sqrt_sigma_div2)); + rxd->i = (int16_t)(r_im[ap][i] + (gaussZiggurat(0.0, 1.0) * sqrt_sigma_div2)); + + if (apply_phase_noise) { + phase_noise(ts, &rxd->r, &rxd->i); + } + } + } +} diff --git a/openair1/SIMULATION/TOOLS/sim.h b/openair1/SIMULATION/TOOLS/sim.h index d31109a836..24f1c053f9 100644 --- a/openair1/SIMULATION/TOOLS/sim.h +++ b/openair1/SIMULATION/TOOLS/sim.h @@ -412,13 +412,39 @@ int random_channel(channel_desc_t *desc, uint8_t abstraction_flag); */ void multipath_channel(channel_desc_t *desc, - double *tx_sig_re[NB_ANTENNAS_TX], - double *tx_sig_im[NB_ANTENNAS_TX], - double *rx_sig_re[NB_ANTENNAS_RX], - double *rx_sig_im[NB_ANTENNAS_RX], + double **tx_sig_re, + double **tx_sig_im, + double **rx_sig_re, + double **rx_sig_im, uint32_t length, uint8_t keep_channel, int log_channel); + +void multipath_channel_float(channel_desc_t *desc, + float **tx_sig_interleaved, + float **rx_sig_re, + float **rx_sig_im, + uint32_t length, + uint8_t keep_channel, + int log_channel); + +void interleave_channel_output(float **rx_sig_re, + float **rx_sig_im, + float **output_interleaved, + int nb_rx, + int num_samples); + +void add_noise_float(c16_t **rxdata, + const float **r_re, + const float **r_im, + const float sigma2, + const int length, + const int slot_offset, + const double ts, + const int delay, + bool apply_phase_noise, + const uint8_t nb_antennas_rx); + /* \fn double compute_pbch_sinr(channel_desc_t *desc, channel_desc_t *desc_i1, From 77d854bdf3156a21426cfba2ddfc1dae5bce07ab Mon Sep 17 00:00:00 2001 From: Nika Ghaderi Date: Mon, 22 Sep 2025 16:05:43 +0000 Subject: [PATCH 029/100] Implement CUDA Multipath Channel Simulation This commit introduces the CUDA implementation of the multipath channel model in `multipath_channel.cu`. - Implements `multipath_channel_kernel` using shared memory for efficient, high-performance convolution. - Provides a C-callable wrapper, `multipath_channel_cuda`, to orchestrate the full pipeline of data transfers and kernel launches. - The wrapper includes support for multiple memory models (Explicit Copy, Unified Memory, ATS) via preprocessor directives to adapt to different build configurations. - Adds a `interleave_channel_output_cuda` utility to convert signal data from the CPU's planar format to the GPU's interleaved format. --- CMakeLists.txt | 33 +++ openair1/SIMULATION/TOOLS/CMakeLists.txt | 1 + .../SIMULATION/TOOLS/multipath_channel.cu | 223 ++++++++++++++++++ openair1/SIMULATION/TOOLS/oai_cuda.h | 30 ++- 4 files changed, 284 insertions(+), 3 deletions(-) create mode 100644 openair1/SIMULATION/TOOLS/multipath_channel.cu diff --git a/CMakeLists.txt b/CMakeLists.txt index 73aa506f33..faacffe21a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -36,6 +36,23 @@ if(CUDA_ENABLE) set(CMAKE_CUDA_ARCHITECTURES 90) endif() + option(USE_UNIFIED_MEMORY "Enable CUDA Unified Memory path" OFF) + option(USE_ATS_MEMORY "Enable ATS Hybrid Memory path" ON) + + if(USE_UNIFIED_MEMORY AND USE_ATS_MEMORY) + message(FATAL_ERROR "Cannot enable both USE_UNIFIED_MEMORY and USE_ATS_MEMORY at the same time.") + endif() + + if(USE_UNIFIED_MEMORY) + add_compile_definitions(USE_UNIFIED_MEMORY) + message(STATUS "CUDA Unified Memory path enabled.") + elseif(USE_ATS_MEMORY) + add_compile_definitions(USE_ATS_MEMORY) + message(STATUS "CUDA ATS Hybrid Memory path enabled.") + else() + message(STATUS "CUDA Explicit Copy path enabled (default ATS was overridden).") + endif() + add_compile_definitions(ENABLE_CUDA) endif() @@ -297,6 +314,22 @@ if(GIT_FOUND) ) endif() +option(USE_UNIFIED_MEMORY "Enable CUDA Unified Memory path instead of explicit copies" OFF) + +option(USE_ATS_MEMORY "Enable Hybrid ATS (CPU->GPU) and Explicit Copy (GPU->CPU) path" OFF) + +if(USE_UNIFIED_MEMORY AND USE_ATS_MEMORY) + message(FATAL_ERROR "Cannot enable both USE_UNIFIED_MEMORY and USE_ATS_MEMORY at the same time.") +endif() + +if(USE_UNIFIED_MEMORY) + add_compile_definitions(USE_UNIFIED_MEMORY) + message(STATUS "CUDA Unified Memory path enabled.") +elseif(USE_ATS_MEMORY) + add_compile_definitions(USE_ATS_MEMORY) + message(STATUS "CUDA ATS Hybrid Memory path enabled.") +endif() + # Debug related options ######################################### # asn1c skeletons have hardcoded this flag to make customized debug logs diff --git a/openair1/SIMULATION/TOOLS/CMakeLists.txt b/openair1/SIMULATION/TOOLS/CMakeLists.txt index 26397509fd..1a7b5e8f81 100644 --- a/openair1/SIMULATION/TOOLS/CMakeLists.txt +++ b/openair1/SIMULATION/TOOLS/CMakeLists.txt @@ -21,6 +21,7 @@ if(CUDA_ENABLE) add_library(oai_cuda_lib STATIC + multipath_channel.cu ) target_link_libraries(oai_cuda_lib PUBLIC CUDA::toolkit) diff --git a/openair1/SIMULATION/TOOLS/multipath_channel.cu b/openair1/SIMULATION/TOOLS/multipath_channel.cu new file mode 100644 index 0000000000..e292462b7b --- /dev/null +++ b/openair1/SIMULATION/TOOLS/multipath_channel.cu @@ -0,0 +1,223 @@ +/* + * 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 +#include "oai_cuda.h" + +#define CHECK_CUDA(val) checkCuda((val), #val, __FILE__, __LINE__) +static void checkCuda(cudaError_t result, const char *const func, const char *const file, const int line) +{ + if (result != cudaSuccess) { + fprintf(stderr, + "CUDA Error at %s:%d code=%d(%s) \"%s\" \n", + file, + line, + static_cast(result), + cudaGetErrorString(result), + func); + cudaDeviceReset(); + exit(EXIT_FAILURE); + } +} + +__device__ __forceinline__ float2 complex_mul(float2 a, float2 b) +{ + return make_float2(a.x * b.x - a.y * b.y, a.x * b.y + a.y * b.x); +} + +__device__ __forceinline__ float2 complex_add(float2 a, float2 b) +{ + return make_float2(a.x + b.x, a.y + b.y); +} + +__global__ void interleave_output_kernel(const float *__restrict__ rx_re, + const float *__restrict__ rx_im, + float2 *__restrict__ output_interleaved, + int num_total_samples) +{ + const int i = blockIdx.x * blockDim.x + threadIdx.x; + if (i < num_total_samples) { + output_interleaved[i].x = rx_re[i]; + output_interleaved[i].y = rx_im[i]; + } +} + +__global__ void multipath_channel_kernel(const float2 *__restrict__ d_channel_coeffs, + const float *__restrict__ tx_sig, + float2 *__restrict__ rx_sig, + int num_samples, + int channel_length, + int nb_tx, + int nb_rx) +{ + extern __shared__ float2 tx_shared[]; + const int i = blockIdx.x * blockDim.x + threadIdx.x; + const int rx_ant_idx = blockIdx.y; + const int padding_len = channel_length - 1; + const int padded_num_samples = num_samples + padding_len; + + if (i >= num_samples) + return; + + float2 rx_tmp = make_float2(0.0f, 0.0f); + + for (int j = 0; j < nb_tx; j++) { + const int tid = threadIdx.x; + const int block_start_idx = blockIdx.x * blockDim.x; + const int shared_mem_size = blockDim.x + channel_length - 1; + + for (int k = tid; k < shared_mem_size; k += blockDim.x) { + int load_idx = block_start_idx + k; + int interleaved_idx = 2 * (j * padded_num_samples + load_idx); + tx_shared[k] = make_float2(tx_sig[interleaved_idx], tx_sig[interleaved_idx + 1]); + } + __syncthreads(); + + for (int l = 0; l < channel_length; l++) { + float2 tx_sample = tx_shared[tid + (channel_length - 1) - l]; + int chan_link_idx = rx_ant_idx + (j * nb_rx); + float2 chan_weight = d_channel_coeffs[chan_link_idx * channel_length + l]; + rx_tmp = complex_add(rx_tmp, complex_mul(tx_sample, chan_weight)); + } + __syncthreads(); + } + + rx_sig[rx_ant_idx * num_samples + i].x = rx_tmp.x; + rx_sig[rx_ant_idx * num_samples + i].y = rx_tmp.y; +} + +extern "C" { + +void multipath_channel_cuda(float **rx_sig_re, + float **rx_sig_im, + int nb_tx, + int nb_rx, + int channel_length, + uint32_t length, + uint64_t channel_offset, + float *h_channel_coeffs, + void *d_tx_sig_void, + void *d_rx_sig_void, + void *d_channel_coeffs_void, + void *h_tx_sig_pinned_void) +{ + float *d_tx_sig = (float *)d_tx_sig_void; + float2 *d_rx_sig = (float2 *)d_rx_sig_void; + float2 *d_channel_coeffs = (float2 *)d_channel_coeffs_void; + int num_samples = length - (int)channel_offset; + float *kernel_input_ptr; + + const int padding_len = channel_length - 1; + const size_t total_padded_tx_bytes = nb_tx * (num_samples + padding_len) * 2 * sizeof(float); + +#if defined(USE_UNIFIED_MEMORY) || defined(USE_ATS_MEMORY) + kernel_input_ptr = (float *)h_tx_sig_pinned_void; +#else // EXPLICIT COPY + float *h_tx_sig_pinned = (float *)h_tx_sig_pinned_void; + CHECK_CUDA(cudaMemcpy(d_tx_sig, h_tx_sig_pinned, total_padded_tx_bytes, cudaMemcpyHostToDevice)); + kernel_input_ptr = d_tx_sig; +#endif + + size_t channel_size_bytes = nb_tx * nb_rx * channel_length * sizeof(float2); + CHECK_CUDA(cudaMemcpy(d_channel_coeffs, h_channel_coeffs, channel_size_bytes, cudaMemcpyHostToDevice)); + + dim3 threadsPerBlock(512, 1); + dim3 numBlocks((num_samples + threadsPerBlock.x - 1) / threadsPerBlock.x, nb_rx); + size_t sharedMemSize = (threadsPerBlock.x + channel_length - 1) * sizeof(float2); + multipath_channel_kernel<<>>(d_channel_coeffs, + kernel_input_ptr, + d_rx_sig, + num_samples, + channel_length, + nb_tx, + nb_rx); + +#if defined(USE_UNIFIED_MEMORY) + CHECK_CUDA(cudaDeviceSynchronize()); + for (int ii = 0; ii < nb_rx; ii++) { + for (int i = 0; i < num_samples; i++) { + float2 result = d_rx_sig[ii * num_samples + i]; + rx_sig_re[ii][i + channel_offset] = result.x; + rx_sig_im[ii][i + channel_offset] = result.y; + } + } +#else + CHECK_CUDA(cudaDeviceSynchronize()); + float2 *h_rx_sig = (float2 *)malloc(nb_rx * num_samples * sizeof(float2)); + CHECK_CUDA(cudaMemcpy(h_rx_sig, d_rx_sig, nb_rx * num_samples * sizeof(float2), cudaMemcpyDeviceToHost)); + for (int ii = 0; ii < nb_rx; ii++) { + for (int i = 0; i < num_samples; i++) { + float2 result = h_rx_sig[ii * num_samples + i]; + rx_sig_re[ii][i + channel_offset] = result.x; + rx_sig_im[ii][i + channel_offset] = result.y; + } + } + free(h_rx_sig); +#endif +} + +void interleave_channel_output_cuda(float **rx_sig_re, + float **rx_sig_im, + void **output_interleaved_void, + int nb_rx, + int num_samples) +{ + float2 **output_interleaved = (float2 **)output_interleaved_void; + + int num_total_samples = nb_rx * num_samples; + size_t total_bytes_per_plane = num_total_samples * sizeof(float); + size_t total_bytes_interleaved = num_total_samples * sizeof(float2); + + float *h_re_flat = (float *)malloc(total_bytes_per_plane); + float *h_im_flat = (float *)malloc(total_bytes_per_plane); + float2 *h_out_flat = (float2 *)malloc(total_bytes_interleaved); + + for (int i = 0; i < nb_rx; i++) { + memcpy(h_re_flat + i * num_samples, rx_sig_re[i], num_samples * sizeof(float)); + memcpy(h_im_flat + i * num_samples, rx_sig_im[i], num_samples * sizeof(float)); + } + float *d_re, *d_im; + float2 *d_out; + CHECK_CUDA(cudaMalloc((void **)&d_re, total_bytes_per_plane)); + CHECK_CUDA(cudaMalloc((void **)&d_im, total_bytes_per_plane)); + CHECK_CUDA(cudaMalloc((void **)&d_out, total_bytes_interleaved)); + CHECK_CUDA(cudaMemcpy(d_re, h_re_flat, total_bytes_per_plane, cudaMemcpyHostToDevice)); + CHECK_CUDA(cudaMemcpy(d_im, h_im_flat, total_bytes_per_plane, cudaMemcpyHostToDevice)); + + int threadsPerBlock = 512; + int blocksPerGrid = (num_total_samples + threadsPerBlock - 1) / threadsPerBlock; + interleave_output_kernel<<>>(d_re, d_im, d_out, num_total_samples); + + CHECK_CUDA(cudaMemcpy(h_out_flat, d_out, total_bytes_interleaved, cudaMemcpyDeviceToHost)); + + for (int i = 0; i < nb_rx; i++) { + memcpy(output_interleaved[i], h_out_flat + i * num_samples, num_samples * sizeof(float2)); + } + free(h_re_flat); + free(h_im_flat); + free(h_out_flat); + CHECK_CUDA(cudaFree(d_re)); + CHECK_CUDA(cudaFree(d_im)); + CHECK_CUDA(cudaFree(d_out)); +} + +} // extern "C" diff --git a/openair1/SIMULATION/TOOLS/oai_cuda.h b/openair1/SIMULATION/TOOLS/oai_cuda.h index 37055a472b..f711e26ea8 100644 --- a/openair1/SIMULATION/TOOLS/oai_cuda.h +++ b/openair1/SIMULATION/TOOLS/oai_cuda.h @@ -32,7 +32,17 @@ #ifdef __NVCC__ #include - // Kernels go here. + __device__ float2 complex_mul(float2 a, float2 b); + + __global__ void multipath_channel_kernel( + const float2* __restrict__ d_channel_coeffs, + const float* __restrict__ tx_sig, + float2* __restrict__ rx_sig, + int num_samples, + int channel_length, + int nb_tx, + int nb_rx); + #endif // __NVCC__ @@ -40,8 +50,22 @@ extern "C" { #endif - // Cuda function declarations will go here. - + void multipath_channel_cuda( + float **rx_sig_re, float **rx_sig_im, + int nb_tx, int nb_rx, int channel_length, + uint32_t length, uint64_t channel_offset, + float *h_channel_coeffs, + void *d_tx_sig, void *d_rx_sig, + void *d_channel_coeffs, + void *h_tx_sig_pinned + ); + + void interleave_channel_output_cuda(float **rx_sig_re, + float **rx_sig_im, + void **output_interleaved, + int nb_rx, + int num_samples); + #ifdef __cplusplus } #endif From 97a14c39604908ebe61fea8ddfd5ebaa2d986edc Mon Sep 17 00:00:00 2001 From: Nika Ghaderi Date: Mon, 22 Sep 2025 16:21:39 +0000 Subject: [PATCH 030/100] Add Unit Test for CUDA Multipath Channel Kernel This commit adds a new standalone unit test, `test_multipath`, to validate the CUDA implementation of the multipath channel. The test performs the following steps: - Generates a random input signal and channel model. - Runs the CPU version (`multipath_channel_float`) to get a baseline result. - Runs the CUDA version (`multipath_channel_cuda`) with the same inputs. - Compares the two outputs by calculating the Mean Squared Error (MSE) and ensures it is below a small tolerance. --- openair1/PHY/TOOLS/tests/CMakeLists.txt | 14 +- openair1/PHY/TOOLS/tests/test_multipath.c | 289 ++++++++++++++++++++++ 2 files changed, 302 insertions(+), 1 deletion(-) create mode 100644 openair1/PHY/TOOLS/tests/test_multipath.c diff --git a/openair1/PHY/TOOLS/tests/CMakeLists.txt b/openair1/PHY/TOOLS/tests/CMakeLists.txt index 9a73f1ae8e..a2fcb4d511 100644 --- a/openair1/PHY/TOOLS/tests/CMakeLists.txt +++ b/openair1/PHY/TOOLS/tests/CMakeLists.txt @@ -38,4 +38,16 @@ add_executable(test_sse_intrinsics test_sse_intrinsics.cpp) target_link_libraries(test_sse_intrinsics PRIVATE GTest::gtest LOG minimal_lib) add_dependencies(tests test_sse_intrinsics) add_test(NAME test_sse_intrinsics - COMMAND ./test_sse_intrinsics) \ No newline at end of file + COMMAND ./test_sse_intrinsics) + +if(CUDA_ENABLE) +add_executable(test_multipath test_multipath.c) +target_link_libraries(test_multipath PRIVATE UTIL SIMU LOG CONFIG_LIB shlib_loader m oai_cuda_lib) + +add_dependencies(tests test_multipath) +add_test(NAME test_multipath COMMAND test_multipath) + +if (TARGET oai_cuda_lib) + target_compile_definitions(test_multipath PRIVATE ENABLE_CUDA) +endif() +endif() diff --git a/openair1/PHY/TOOLS/tests/test_multipath.c b/openair1/PHY/TOOLS/tests/test_multipath.c new file mode 100644 index 0000000000..6b1258146b --- /dev/null +++ b/openair1/PHY/TOOLS/tests/test_multipath.c @@ -0,0 +1,289 @@ +/* + * 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 +#include +#include +#include +#include "PHY/TOOLS/tools_defs.h" +#include "SIMULATION/TOOLS/sim.h" +#include +#include "SIMULATION/TOOLS/oai_cuda.h" +#include "common/utils/LOG/log.h" +#include "common/utils/utils.h" + +configmodule_interface_t *uniqCfg = NULL; + +void exit_function(const char *file, const char *function, const int line, const char *s, const int assert_not_exit) +{ + fprintf(stderr, "Exit function called from %s:%d in %s(). Message: %s\n", file, line, function, s); + exit(1); +} + +void generate_random_signal_interleaved(float **sig_interleaved, int nb_ant, int num_samples) +{ + for (int i = 0; i < nb_ant; i++) { + for (int j = 0; j < num_samples; j++) { + sig_interleaved[i][2 * j] = (float)rand() / (float)RAND_MAX * 2.0f - 1.0f; + sig_interleaved[i][2 * j + 1] = (float)rand() / (float)RAND_MAX * 2.0f - 1.0f; + } + } +} + +channel_desc_t *create_manual_channel_desc(int nb_tx, int nb_rx, int channel_length) +{ + channel_desc_t *desc = (channel_desc_t *)calloc(1, sizeof(channel_desc_t)); + desc->nb_tx = nb_tx; + desc->nb_rx = nb_rx; + desc->channel_length = channel_length; + desc->path_loss_dB = 10.0; + desc->channel_offset = 0; + int num_links = nb_tx * nb_rx; + desc->ch = (struct complexd **)malloc(num_links * sizeof(struct complexd *)); + float path_loss = (float)pow(10, desc->path_loss_dB / 20.0); + for (int i = 0; i < num_links; i++) { + desc->ch[i] = (struct complexd *)malloc(channel_length * sizeof(struct complexd)); + for (int l = 0; l < channel_length; l++) { + desc->ch[i][l].r = (double)rand() / (double)RAND_MAX * path_loss; + desc->ch[i][l].i = (double)rand() / (double)RAND_MAX * path_loss; + } + } + return desc; +} + +void free_manual_channel_desc(channel_desc_t *desc) +{ + if (!desc) + return; + int num_links = desc->nb_tx * desc->nb_rx; + for (int i = 0; i < num_links; i++) + free(desc->ch[i]); + free(desc->ch); + free(desc); +} + +int verify_results(float **re_cpu, float **im_cpu, float **re_gpu, float **im_gpu, int nb_rx, int num_samples) +{ + double total_error = 0.0; + for (int i = 0; i < nb_rx; i++) { + for (int j = 0; j < num_samples; j++) { + double err_re = re_cpu[i][j] - re_gpu[i][j]; + double err_im = im_cpu[i][j] - im_gpu[i][j]; + total_error += (err_re * err_re) + (err_im * err_im); + } + } + double mse = total_error / (nb_rx * num_samples); + return (mse < 1e-9) ? 0 : 1; +} + +int main(int argc, char **argv) +{ + logInit(); + randominit(0); + + int nb_tx_configs[] = {1, 2, 4, 8}; + int nb_rx_configs[] = {1, 2, 4, 8}; + int num_samples_configs[] = {30720, 61440, 122880}; + int channel_length_configs[] = {16, 32}; + char *channel_type_names[] = {"Short Channel", "Long Channel"}; + int num_trials = 100; + + printf("Starting Multipath Channel Benchmark (CPU vs. Interleaved CUDA)\n"); + printf("Averaging each test case over %d trials.\n", num_trials); + printf( + "----------------------------------------------------------------------------------------------------------------------------" + "--------------\n"); + printf("%-15s | %-15s | %-15s | %-15s | %-15s | %-15s | %-10s\n", + "Channel Type", + "MIMO Config", + "Signal Length", + "CPU Time (us)", + "CUDA Time (us)", + "Speedup", + "Verification"); + printf( + "----------------------------------------------------------------------------------------------------------------------------" + "--------------\n"); + + for (int c = 0; c < sizeof(channel_length_configs) / sizeof(int); c++) { + for (int s = 0; s < sizeof(num_samples_configs) / sizeof(int); s++) { + for (int m = 0; m < sizeof(nb_tx_configs) / sizeof(int); m++) { + int nb_tx = nb_tx_configs[m]; + int nb_rx = nb_rx_configs[m]; + int num_samples = num_samples_configs[s]; + int channel_length = channel_length_configs[c]; + + char mimo_str[16]; + sprintf(mimo_str, "%dx%d", nb_tx, nb_rx); + + channel_desc_t *chan_desc = create_manual_channel_desc(nb_tx, nb_rx, channel_length); + + float **s_interleaved = malloc(nb_tx * sizeof(float *)); + float **r_re_cpu = malloc(nb_rx * sizeof(float *)); + float **r_im_cpu = malloc(nb_rx * sizeof(float *)); + float **r_re_gpu = malloc(nb_rx * sizeof(float *)); + float **r_im_gpu = malloc(nb_rx * sizeof(float *)); + + for (int i = 0; i < nb_tx; i++) { + s_interleaved[i] = malloc(num_samples * 2 * sizeof(float)); + } + for (int i = 0; i < nb_rx; i++) { + r_re_cpu[i] = malloc(num_samples * sizeof(float)); + r_im_cpu[i] = malloc(num_samples * sizeof(float)); + r_re_gpu[i] = malloc(num_samples * sizeof(float)); + r_im_gpu[i] = malloc(num_samples * sizeof(float)); + } + + void *d_tx_sig = NULL, *d_rx_sig = NULL, *d_channel_coeffs_gpu = NULL, *h_tx_sig_pinned = NULL; + const int num_samples_to_process = num_samples - chan_desc->channel_offset; + const int padding_len = channel_length - 1; + const size_t channel_buffer_size = nb_tx * nb_rx * channel_length * sizeof(float2); + const size_t tx_buffer_bytes = nb_tx * (num_samples_to_process + padding_len) * 2 * sizeof(float); + const size_t rx_buffer_bytes = nb_rx * num_samples_to_process * sizeof(float2); + +#if defined(USE_UNIFIED_MEMORY) + cudaMallocManaged(&d_tx_sig, tx_buffer_bytes, cudaMemAttachGlobal); + cudaMallocManaged(&d_rx_sig, rx_buffer_bytes, cudaMemAttachGlobal); + cudaMallocManaged(&d_channel_coeffs_gpu, channel_buffer_size, cudaMemAttachGlobal); + h_tx_sig_pinned = d_tx_sig; +#elif defined(USE_ATS_MEMORY) + h_tx_sig_pinned = malloc(tx_buffer_bytes); + d_tx_sig = NULL; + cudaMalloc(&d_rx_sig, rx_buffer_bytes); + cudaMalloc(&d_channel_coeffs_gpu, channel_buffer_size); +#else // EXPLICIT COPY + cudaMalloc(&d_tx_sig, tx_buffer_bytes); + cudaMalloc(&d_rx_sig, rx_buffer_bytes); + cudaMalloc(&d_channel_coeffs_gpu, channel_buffer_size); + cudaMallocHost(&h_tx_sig_pinned, tx_buffer_bytes); +#endif + + double total_cpu_ns = 0; + double total_gpu_ns = 0; + + int num_links = chan_desc->nb_tx * chan_desc->nb_rx; + float *h_channel_coeffs = (float *)malloc(num_links * channel_length * sizeof(float2)); + for (int link = 0; link < num_links; link++) { + for (int l = 0; l < channel_length; l++) { + int idx = link * channel_length + l; + ((float2 *)h_channel_coeffs)[idx].x = (float)chan_desc->ch[link][l].r; + ((float2 *)h_channel_coeffs)[idx].y = (float)chan_desc->ch[link][l].i; + } + } + + struct timespec start, end; + + generate_random_signal_interleaved(s_interleaved, nb_tx, num_samples); + + const int padded_num_samples = num_samples + padding_len; + float *h_tx_ptr = (float *)h_tx_sig_pinned; + memset(h_tx_ptr, 0, tx_buffer_bytes); + for (int j = 0; j < nb_tx; j++) { + float *data_start_ptr = h_tx_ptr + (j * padded_num_samples + padding_len) * 2; + memcpy(data_start_ptr, s_interleaved[j], num_samples * 2 * sizeof(float)); + } + + float **tx_sig_for_cpu = malloc(nb_tx * sizeof(float *)); + for (int j = 0; j < nb_tx; j++) { + tx_sig_for_cpu[j] = h_tx_ptr + (j * padded_num_samples + padding_len) * 2; + } + + multipath_channel_float(chan_desc, tx_sig_for_cpu, r_re_cpu, r_im_cpu, num_samples, 1, 0); + + clock_gettime(CLOCK_MONOTONIC, &start); + for (int t = 0; t < num_trials; t++) { + multipath_channel_float(chan_desc, tx_sig_for_cpu, r_re_cpu, r_im_cpu, num_samples, 1, 0); + } + clock_gettime(CLOCK_MONOTONIC, &end); + total_cpu_ns = (end.tv_sec - start.tv_sec) * 1e9 + (end.tv_nsec - start.tv_nsec); + free(tx_sig_for_cpu); + + clock_gettime(CLOCK_MONOTONIC, &start); + for (int t = 0; t < num_trials; t++) { + multipath_channel_cuda(r_re_gpu, + r_im_gpu, + nb_tx, + nb_rx, + channel_length, + num_samples, + chan_desc->channel_offset, + h_channel_coeffs, + d_tx_sig, + d_rx_sig, + d_channel_coeffs_gpu, + h_tx_sig_pinned); + } + cudaDeviceSynchronize(); + clock_gettime(CLOCK_MONOTONIC, &end); + total_gpu_ns = (end.tv_sec - start.tv_sec) * 1e9 + (end.tv_nsec - start.tv_nsec); + + int verification_passed = (verify_results(r_re_cpu, r_im_cpu, r_re_gpu, r_im_gpu, nb_rx, num_samples) == 0); + double avg_cpu_us = (total_cpu_ns / num_trials) / 1000.0; + double avg_gpu_us = (total_gpu_ns / num_trials) / 1000.0; + double speedup = (avg_gpu_us > 0) ? (avg_cpu_us / avg_gpu_us) : 0; + + printf("%-15s | %-15s | %-15d | %-15.2f | %-15.2f | %-15.2fx | %-10s\n", + channel_type_names[c], + mimo_str, + num_samples, + avg_cpu_us, + avg_gpu_us, + speedup, + (verification_passed ? "PASSED" : "FAILED")); + + free(h_channel_coeffs); + for (int i = 0; i < nb_tx; i++) { + free(s_interleaved[i]); + } + for (int i = 0; i < nb_rx; i++) { + free(r_re_cpu[i]); + free(r_im_cpu[i]); + free(r_re_gpu[i]); + free(r_im_gpu[i]); + } + free(s_interleaved); + free(r_re_cpu); + free(r_im_cpu); + free(r_re_gpu); + free(r_im_gpu); +#if defined(USE_UNIFIED_MEMORY) + cudaFree(d_tx_sig); +#elif defined(USE_ATS_MEMORY) + free(h_tx_sig_pinned); +#else // EXPLICIT COPY + cudaFree(d_tx_sig); + cudaFreeHost(h_tx_sig_pinned); +#endif + cudaFree(d_rx_sig); + cudaFree(d_channel_coeffs_gpu); + free_manual_channel_desc(chan_desc); + } + } + } + + printf( + "----------------------------------------------------------------------------------------------------------------------------" + "--------------\n"); + printf("Benchmark finished.\n"); + + return 0; +} From 257b296e412422c9668a04e39debb03951dfb864 Mon Sep 17 00:00:00 2001 From: Nika Ghaderi Date: Wed, 24 Sep 2025 17:34:09 +0000 Subject: [PATCH 031/100] Implement CUDA Noise and Phase Noise Kernel This commit adds the CUDA implementation for adding Additive White Gaussian Noise (AWGN) and phase noise to the signal. The code is located in phase_noise.cu. - The `add_noise_and_phase_noise_kernel` uses the NVIDIA cuRAND library for efficient parallel random number generation. - Helper functions are included to create, initialize, and destroy the cuRAND states required by the kernel. - Placeholder for a C-callable wrapper, `add_noise_cuda`, is also provided. --- openair1/SIMULATION/TOOLS/CMakeLists.txt | 1 + openair1/SIMULATION/TOOLS/oai_cuda.h | 99 ++++++++---- openair1/SIMULATION/TOOLS/phase_noise.cu | 190 +++++++++++++++++++++++ 3 files changed, 263 insertions(+), 27 deletions(-) create mode 100644 openair1/SIMULATION/TOOLS/phase_noise.cu diff --git a/openair1/SIMULATION/TOOLS/CMakeLists.txt b/openair1/SIMULATION/TOOLS/CMakeLists.txt index 1a7b5e8f81..a1937dfa64 100644 --- a/openair1/SIMULATION/TOOLS/CMakeLists.txt +++ b/openair1/SIMULATION/TOOLS/CMakeLists.txt @@ -22,6 +22,7 @@ if(CUDA_ENABLE) add_library(oai_cuda_lib STATIC multipath_channel.cu + phase_noise.cu ) target_link_libraries(oai_cuda_lib PUBLIC CUDA::toolkit) diff --git a/openair1/SIMULATION/TOOLS/oai_cuda.h b/openair1/SIMULATION/TOOLS/oai_cuda.h index f711e26ea8..9a590d0f8e 100644 --- a/openair1/SIMULATION/TOOLS/oai_cuda.h +++ b/openair1/SIMULATION/TOOLS/oai_cuda.h @@ -24,47 +24,92 @@ #include - #ifdef __NVCC__ +typedef struct complex16 { + int16_t r; + int16_t i; +} c16_t; #else - #include "PHY/TOOLS/tools_defs.h" +#include "PHY/TOOLS/tools_defs.h" #endif #ifdef __NVCC__ - #include - __device__ float2 complex_mul(float2 a, float2 b); +#include +__device__ float2 complex_mul(float2 a, float2 b); - __global__ void multipath_channel_kernel( - const float2* __restrict__ d_channel_coeffs, - const float* __restrict__ tx_sig, - float2* __restrict__ rx_sig, - int num_samples, - int channel_length, - int nb_tx, - int nb_rx); +__global__ void multipath_channel_kernel(const float2 *__restrict__ d_channel_coeffs, + const float *__restrict__ tx_sig, + float2 *__restrict__ rx_sig, + int num_samples, + int channel_length, + int nb_tx, + int nb_rx); + +__global__ void add_noise_and_phase_noise_kernel(const float2 *__restrict__ r_sig, + short2 *__restrict__ output_sig, + curandState_t *states, + int num_samples, + float sigma, + float pn_std_dev, + bool apply_phase_noise); #endif // __NVCC__ - #ifdef __cplusplus extern "C" { #endif - void multipath_channel_cuda( - float **rx_sig_re, float **rx_sig_im, - int nb_tx, int nb_rx, int channel_length, - uint32_t length, uint64_t channel_offset, - float *h_channel_coeffs, - void *d_tx_sig, void *d_rx_sig, - void *d_channel_coeffs, - void *h_tx_sig_pinned - ); +void multipath_channel_cuda(float **rx_sig_re, + float **rx_sig_im, + int nb_tx, + int nb_rx, + int channel_length, + uint32_t length, + uint64_t channel_offset, + float *h_channel_coeffs, + void *d_tx_sig, + void *d_rx_sig, + void *d_channel_coeffs, + void *h_tx_sig_pinned); - void interleave_channel_output_cuda(float **rx_sig_re, - float **rx_sig_im, - void **output_interleaved, - int nb_rx, - int num_samples); +void add_noise_cuda(const float **r_re, + const float **r_im, + c16_t **output_signal, + int num_samples, + int nb_rx, + float sigma2, + double ts, + int slot_offset, + int delay, + uint16_t pdu_bit_map, + uint16_t ptrs_bit_map, + void *d_r_sig, + void *d_output_sig, + void *d_curand_states, + void *h_r_sig_pinned, + void *h_output_sig_pinned); + +/** + * \brief Interleaves separate real and imaginary signal buffers into a complex format. + * + * This utility function takes separate planar buffers for the real (I) and imaginary (Q) + * components of a signal and combines them into a single interleaved buffer of float2 + * structs, which is a common format for CUDA processing. + * + * \param[in] rx_sig_re Array of pointers to the real parts of the input signals. + * \param[in] rx_sig_im Array of pointers to the imaginary parts of the input signals. + * \param[out] output_interleaved Pointer to an array of pointers where the interleaved output will be stored. + * \param[in] nb_rx The number of receive antennas (i.e., the number of signals). + * \param[in] num_samples The number of samples per signal. + * + * \note The caller is responsible for allocating memory for the `output_interleaved` buffer. It should + * point to an array of `nb_rx` pointers, where each of those pointers is allocated with a size of + * `num_samples * sizeof(float2)`. + */ +void interleave_channel_output_cuda(float **rx_sig_re, float **rx_sig_im, void **output_interleaved, int nb_rx, int num_samples); + +void *create_and_init_curand_states_cuda(int num_elements, unsigned long long seed); +void destroy_curand_states_cuda(void *d_curand_states); #ifdef __cplusplus } diff --git a/openair1/SIMULATION/TOOLS/phase_noise.cu b/openair1/SIMULATION/TOOLS/phase_noise.cu new file mode 100644 index 0000000000..3bca71fdf0 --- /dev/null +++ b/openair1/SIMULATION/TOOLS/phase_noise.cu @@ -0,0 +1,190 @@ +/* + * 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 +#include +#include +#include +#include "oai_cuda.h" + +#define CHECK_CUDA(val) checkCuda((val), __FILE__, __LINE__) +static void checkCuda(cudaError_t result, const char *file, int line) +{ + if (result != cudaSuccess) { + fprintf(stderr, "CUDA Error at %s:%d: %s\n", file, line, cudaGetErrorString(result)); + exit(1); + } +} + +__device__ __forceinline__ float2 complex_mul(float2 a, float2 b) +{ + return make_float2(a.x * b.x - a.y * b.y, a.x * b.y + a.y * b.x); +} + +__global__ void init_curand_states_kernel(curandState_t *states, unsigned long long seed, int num_elements) +{ + int idx = blockIdx.x * blockDim.x + threadIdx.x; + if (idx < num_elements) { + curand_init(seed, idx, 0, &states[idx]); + } +} + +__global__ void add_noise_and_phase_noise_kernel(const float2 *__restrict__ r_sig, + short2 *__restrict__ output_sig, + curandState_t *states, + int num_samples, + float sigma, + float pn_std_dev, + bool apply_phase_noise) +{ + const int i = blockIdx.x * blockDim.x + threadIdx.x; + const int ii = blockIdx.y; + + if (i >= num_samples) + return; + + // Each thread handles one sample and its corresponding cuRAND state + curandState_t local_state = states[ii * num_samples + i]; + + float2 noisy_signal = r_sig[ii * num_samples + i]; + + float2 awgn = curand_normal2(&local_state); + noisy_signal.x += awgn.x * sigma; + noisy_signal.y += awgn.y * sigma; + + float2 final_signal = noisy_signal; + + if (apply_phase_noise) { + float phase_error = curand_normal(&local_state) * pn_std_dev; + float cos_phi, sin_phi; + __sincosf(phase_error, &sin_phi, &cos_phi); + float2 phase_rot = make_float2(cos_phi, sin_phi); + final_signal = complex_mul(noisy_signal, phase_rot); + } + + states[ii * num_samples + i] = local_state; + output_sig[ii * num_samples + i] = make_short2((short)fmaxf(-32768.0f, fminf(32767.0f, final_signal.x)), + (short)fmaxf(-32768.0f, fminf(32767.0f, final_signal.y))); +} + +extern "C" { + +void add_noise_cuda(const float **r_re, + const float **r_im, + c16_t **output_signal, + int num_samples, + int nb_rx, + float sigma2, + double ts, + int slot_offset, + int delay, + uint16_t pdu_bit_map, + uint16_t ptrs_bit_map, + void *d_r_sig_void, + void *d_output_sig_void, + void *d_curand_states_void, + void *h_r_sig_void, + void *h_output_temp_void) +{ + float2 *d_r_sig = (float2 *)d_r_sig_void; + short2 *d_output_sig = (short2 *)d_output_sig_void; + curandState_t *d_curand_states = (curandState_t *)d_curand_states_void; + + float2 *kernel_input_ptr; + +#if defined(USE_UNIFIED_MEMORY) + for (int ii = 0; ii < nb_rx; ii++) { + for (int i = 0; i < num_samples; i++) { + d_r_sig[ii * num_samples + i] = make_float2(r_re[ii][i], r_im[ii][i]); + } + } + kernel_input_ptr = d_r_sig; +#elif defined(USE_ATS_MEMORY) + float2 *h_r_sig = (float2 *)h_r_sig_void; + for (int ii = 0; ii < nb_rx; ii++) { + for (int i = 0; i < num_samples; i++) { + h_r_sig[ii * num_samples + i] = make_float2(r_re[ii][i], r_im[ii][i]); + } + } + kernel_input_ptr = h_r_sig; +#else // EXPLICIT COPY + float2 *h_r_sig = (float2 *)h_r_sig_void; + for (int ii = 0; ii < nb_rx; ii++) { + for (int i = 0; i < num_samples; i++) { + h_r_sig[ii * num_samples + i] = make_float2(r_re[ii][i], r_im[ii][i]); + } + } + CHECK_CUDA(cudaMemcpy(d_r_sig, h_r_sig, nb_rx * num_samples * sizeof(float2), cudaMemcpyHostToDevice)); + kernel_input_ptr = d_r_sig; +#endif + + dim3 threadsPerBlock(256, 1); + dim3 numBlocks((num_samples + threadsPerBlock.x - 1) / threadsPerBlock.x, nb_rx); + float pn_variance = 1e-5f * 2.0f * 3.1415926535f * 300.0f * (float)ts; + bool apply_phase_noise = (pdu_bit_map & ptrs_bit_map); + add_noise_and_phase_noise_kernel<<>>(kernel_input_ptr, + d_output_sig, + d_curand_states, + num_samples, + sqrtf(sigma2 / 2.0f), + sqrtf(pn_variance), + apply_phase_noise); + +#if defined(USE_UNIFIED_MEMORY) + CHECK_CUDA(cudaDeviceSynchronize()); + for (int ii = 0; ii < nb_rx; ii++) { + for (int i = 0; i < num_samples; i++) { + short2 result = d_output_sig[ii * num_samples + i]; + output_signal[ii][i + slot_offset + delay].r = result.x; + output_signal[ii][i + slot_offset + delay].i = result.y; + } + } +#else + short2 *h_output_temp = (short2 *)h_output_temp_void; + CHECK_CUDA(cudaMemcpy(h_output_temp, d_output_sig, nb_rx * num_samples * sizeof(short2), cudaMemcpyDeviceToHost)); + CHECK_CUDA(cudaDeviceSynchronize()); + for (int ii = 0; ii < nb_rx; ii++) { + memcpy(output_signal[ii] + slot_offset + delay, h_output_temp + ii * num_samples, num_samples * sizeof(short2)); + } +#endif +} + +void *create_and_init_curand_states_cuda(int num_elements, unsigned long long seed) +{ + void *d_states_void; + CHECK_CUDA(cudaMalloc(&d_states_void, num_elements * sizeof(curandState_t))); + + int threads = 256; + int blocks = (num_elements + threads - 1) / threads; + init_curand_states_kernel<<>>((curandState_t *)d_states_void, seed, num_elements); + CHECK_CUDA(cudaDeviceSynchronize()); + + return d_states_void; +} + +void destroy_curand_states_cuda(void *d_curand_states) +{ + if (d_curand_states) { + CHECK_CUDA(cudaFree(d_curand_states)); + } +} +} // extern "C" From 7101dc04d19832c1d3a9860445a8b2541507fa0a Mon Sep 17 00:00:00 2001 From: Nika Ghaderi Date: Wed, 24 Sep 2025 17:35:38 +0000 Subject: [PATCH 032/100] Add Performance Benchmark for Noise Generation This commit adds a new standalone benchmark, `test_noise`, to measure and compare the performance of the CPU and CUDA implementations of noise generation. The benchmark's methodology is as follows: - It iterates through various configurations, including different numbers of receive antennas and signal lengths. - For each configuration, it runs both the CPU (`add_noise_float`) and CUDA (`add_noise_cuda`) functions multiple times to get a stable average execution time. - It prints a formatted table comparing the CPU and GPU latencies and calculates the resulting speedup. --- openair1/PHY/TOOLS/tests/CMakeLists.txt | 6 + openair1/PHY/TOOLS/tests/test_noise.c | 186 ++++++++++++++++++++++++ 2 files changed, 192 insertions(+) create mode 100644 openair1/PHY/TOOLS/tests/test_noise.c diff --git a/openair1/PHY/TOOLS/tests/CMakeLists.txt b/openair1/PHY/TOOLS/tests/CMakeLists.txt index a2fcb4d511..384a911d36 100644 --- a/openair1/PHY/TOOLS/tests/CMakeLists.txt +++ b/openair1/PHY/TOOLS/tests/CMakeLists.txt @@ -47,6 +47,12 @@ target_link_libraries(test_multipath PRIVATE UTIL SIMU LOG CONFIG_LIB shlib_load add_dependencies(tests test_multipath) add_test(NAME test_multipath COMMAND test_multipath) +add_executable(test_noise test_noise.c) +target_link_libraries(test_noise PRIVATE UTIL SIMU PHY_COMMON LOG CONFIG_LIB shlib_loader m oai_cuda_lib) + +add_dependencies(tests test_noise) +add_test(NAME test_noise COMMAND test_noise) + if (TARGET oai_cuda_lib) target_compile_definitions(test_multipath PRIVATE ENABLE_CUDA) endif() diff --git a/openair1/PHY/TOOLS/tests/test_noise.c b/openair1/PHY/TOOLS/tests/test_noise.c new file mode 100644 index 0000000000..70d4a15ada --- /dev/null +++ b/openair1/PHY/TOOLS/tests/test_noise.c @@ -0,0 +1,186 @@ +/* + * 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 +#include +#include +#include +#include "PHY/TOOLS/tools_defs.h" +#include "SIMULATION/TOOLS/sim.h" +#include "SIMULATION/TOOLS/oai_cuda.h" +#include "common/utils/LOG/log.h" +#include "common/utils/utils.h" +#include + +configmodule_interface_t *uniqCfg = NULL; + +void exit_function(const char *file, const char *function, const int line, const char *s, const int assert_not_exit) +{ + fprintf(stderr, "Exit function called from %s:%d in %s(). Message: %s\n", file, line, function, s); + exit(1); +} + +void generate_random_signal(float **sig_re, float **sig_im, int nb_ant, int num_samples) +{ + for (int i = 0; i < nb_ant; i++) { + for (int j = 0; j < num_samples; j++) { + sig_re[i][j] = (float)((rand() % 20000) - 10000); + sig_im[i][j] = (float)((rand() % 20000) - 10000); + } + } +} + +int main(int argc, char **argv) +{ + logInit(); + randominit(0); + + int nb_rx_configs[] = {1, 2, 4, 8}; + int num_samples_configs[] = {30720, 61440, 122880}; + int num_trials = 100; + float snr_db = 10.0f; + + printf("Starting Noise Generation Benchmark (CPU vs. CUDA)\n"); + printf("Averaging each test case over %d trials.\n", num_trials); + printf("---------------------------------------------------------------------------------------------\n"); + printf("%-15s | %-15s | %-15s | %-15s | %-15s\n", "Antennas", "Signal Length", "CPU Time (us)", "CUDA Time (us)", "Speedup"); + printf("---------------------------------------------------------------------------------------------\n"); + + for (int r = 0; r < sizeof(nb_rx_configs) / sizeof(int); r++) { + for (int s = 0; s < sizeof(num_samples_configs) / sizeof(int); s++) { + int nb_rx = nb_rx_configs[r]; + int num_samples = num_samples_configs[s]; + + float **r_re = malloc(nb_rx * sizeof(float *)); + float **r_im = malloc(nb_rx * sizeof(float *)); + for (int i = 0; i < nb_rx; i++) { + r_re[i] = malloc(num_samples * sizeof(float)); + r_im[i] = malloc(num_samples * sizeof(float)); + } + + c16_t **output_cpu = malloc(nb_rx * sizeof(c16_t *)); + c16_t **output_gpu = malloc(nb_rx * sizeof(c16_t *)); + output_cpu[0] = malloc(nb_rx * num_samples * sizeof(c16_t)); + output_gpu[0] = malloc(nb_rx * num_samples * sizeof(c16_t)); + for (int i = 1; i < nb_rx; i++) { + output_cpu[i] = output_cpu[0] + i * num_samples; + output_gpu[i] = output_gpu[0] + i * num_samples; + } + + void *d_r_sig, *d_output_sig, *d_curand_states; + void *h_r_sig_pinned, *h_output_sig_pinned; + +#if defined(USE_UNIFIED_MEMORY) + cudaMallocManaged(&d_r_sig, nb_rx * num_samples * sizeof(float) * 2, cudaMemAttachGlobal); + cudaMallocManaged(&d_output_sig, nb_rx * num_samples * sizeof(short) * 2, cudaMemAttachGlobal); + + int deviceId; + cudaGetDevice(&deviceId); + cudaMemAdvise(d_r_sig, nb_rx * num_samples * sizeof(float) * 2, cudaMemAdviseSetReadMostly, deviceId); + cudaMemAdvise(d_output_sig, nb_rx * num_samples * sizeof(short) * 2, cudaMemAdviseSetPreferredLocation, deviceId); + cudaMemAdvise(d_output_sig, nb_rx * num_samples * sizeof(short) * 2, cudaMemAdviseSetAccessedBy, cudaCpuDeviceId); + + // Pinned memory is not used in the UM path for this wrapper + h_r_sig_pinned = NULL; + h_output_sig_pinned = NULL; +#else + cudaMalloc(&d_r_sig, nb_rx * num_samples * sizeof(float) * 2); + cudaMalloc(&d_output_sig, nb_rx * num_samples * sizeof(short) * 2); + cudaMallocHost(&h_r_sig_pinned, nb_rx * num_samples * sizeof(float) * 2); + cudaMallocHost(&h_output_sig_pinned, nb_rx * num_samples * sizeof(short) * 2); +#endif + d_curand_states = create_and_init_curand_states_cuda(nb_rx * num_samples, time(NULL)); + + double total_cpu_ns = 0; + double total_gpu_ns = 0; + + double ts = 1.0 / 30.72e6; + float signal_power = 1.0f; + float sigma2 = signal_power / powf(10.0f, snr_db / 10.0f); + + for (int t = 0; t < num_trials; t++) { + generate_random_signal(r_re, r_im, nb_rx, num_samples); + + struct timespec start, end; + + clock_gettime(CLOCK_MONOTONIC, &start); + add_noise_float(output_cpu, (const float **)r_re, (const float **)r_im, sigma2, num_samples, 0, ts, 0, false, nb_rx); + clock_gettime(CLOCK_MONOTONIC, &end); + total_cpu_ns += (end.tv_sec - start.tv_sec) * 1e9 + (end.tv_nsec - start.tv_nsec); + + clock_gettime(CLOCK_MONOTONIC, &start); + add_noise_cuda((const float **)r_re, + (const float **)r_im, + output_gpu, + num_samples, + nb_rx, + sigma2, + ts, + 0, + 0, + 0, + 0, + d_r_sig, + d_output_sig, + d_curand_states, + h_r_sig_pinned, + h_output_sig_pinned); + cudaDeviceSynchronize(); + clock_gettime(CLOCK_MONOTONIC, &end); + total_gpu_ns += (end.tv_sec - start.tv_sec) * 1e9 + (end.tv_nsec - start.tv_nsec); + } + + double avg_cpu_us = (total_cpu_ns / num_trials) / 1000.0; + double avg_gpu_us = (total_gpu_ns / num_trials) / 1000.0; + double speedup = (avg_gpu_us > 0) ? (avg_cpu_us / avg_gpu_us) : 0; + + printf("%-15d | %-15d | %-15.2f | %-15.2f | %-15.2fx\n", nb_rx, num_samples, avg_cpu_us, avg_gpu_us, speedup); + + for (int i = 0; i < nb_rx; i++) { + free(r_re[i]); + free(r_im[i]); + } + free(r_re); + free(r_im); + free(output_cpu[0]); + free(output_gpu[0]); + free(output_cpu); + free(output_gpu); + +#if defined(USE_UNIFIED_MEMORY) + cudaFree(d_r_sig); + cudaFree(d_output_sig); +#else + cudaFree(d_r_sig); + cudaFree(d_output_sig); + cudaFreeHost(h_r_sig_pinned); + cudaFreeHost(h_output_sig_pinned); +#endif + destroy_curand_states_cuda(d_curand_states); + } + } + + printf("---------------------------------------------------------------------------------------------\n"); + printf("Benchmark finished.\n"); + + return 0; +} From 2cb960df4e21c02f86470b86aad0c5d7a3e0bb19 Mon Sep 17 00:00:00 2001 From: Nika Ghaderi Date: Wed, 24 Sep 2025 17:00:58 +0000 Subject: [PATCH 033/100] Create End-to-End CUDA Channel Simulation Pipeline This commit introduces the main C-callable pipeline function, `run_channel_pipeline_cuda`, located in a new `channel_pipeline.cu` file. This function serves as the primary entry point for the GPU channel simulation. It orchestrates the entire sequence of operations: - Manages Host-to-Device data transfers for the input signal and channel coefficients. - Launches the `multipath_channel_kernel` and the `add_noise_and_phase_noise_kernel` in the correct order. - Manages the Device-to-Host data transfer for the final output signal. - Synchronizes to ensure completion before returning. --- openair1/SIMULATION/TOOLS/CMakeLists.txt | 1 + openair1/SIMULATION/TOOLS/channel_pipeline.cu | 166 ++++++++++++++++++ openair1/SIMULATION/TOOLS/oai_cuda.h | 22 +++ 3 files changed, 189 insertions(+) create mode 100644 openair1/SIMULATION/TOOLS/channel_pipeline.cu diff --git a/openair1/SIMULATION/TOOLS/CMakeLists.txt b/openair1/SIMULATION/TOOLS/CMakeLists.txt index a1937dfa64..d104c0ccdc 100644 --- a/openair1/SIMULATION/TOOLS/CMakeLists.txt +++ b/openair1/SIMULATION/TOOLS/CMakeLists.txt @@ -23,6 +23,7 @@ if(CUDA_ENABLE) add_library(oai_cuda_lib STATIC multipath_channel.cu phase_noise.cu + channel_pipeline.cu ) target_link_libraries(oai_cuda_lib PUBLIC CUDA::toolkit) diff --git a/openair1/SIMULATION/TOOLS/channel_pipeline.cu b/openair1/SIMULATION/TOOLS/channel_pipeline.cu new file mode 100644 index 0000000000..49d229a2b6 --- /dev/null +++ b/openair1/SIMULATION/TOOLS/channel_pipeline.cu @@ -0,0 +1,166 @@ +/* + * 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 +#include "oai_cuda.h" + +#define CHECK_CUDA(val) checkCuda((val), #val, __FILE__, __LINE__) +static void checkCuda(cudaError_t result, const char *const func, const char *const file, const int line) +{ + if (result != cudaSuccess) { + fprintf(stderr, + "CUDA Error at %s:%d code=%d(%s) \"%s\" \n", + file, + line, + static_cast(result), + cudaGetErrorString(result), + func); + cudaDeviceReset(); + exit(EXIT_FAILURE); + } +} + +__global__ void sum_outputs_kernel(const short2 *__restrict__ *__restrict__ individual_outputs, + short2 *__restrict__ final_summed_output, + int num_channels, + int num_samples_per_antenna) +{ + const int i = blockIdx.x * blockDim.x + threadIdx.x; + if (i >= num_samples_per_antenna) + return; + + float2 sum = make_float2(0.0f, 0.0f); + + for (int c = 0; c < num_channels; c++) { + sum.x += individual_outputs[c][i].x; + sum.y += individual_outputs[c][i].y; + } + + final_summed_output[i].x = (short)fmaxf(-32768.0f, fminf(32767.0f, sum.x)); + final_summed_output[i].y = (short)fmaxf(-32768.0f, fminf(32767.0f, sum.y)); +} + +extern "C" { +void run_channel_pipeline_cuda(c16_t **output_signal, + int nb_tx, + int nb_rx, + int channel_length, + uint32_t num_samples, + float *h_channel_coeffs, + float sigma2, + double ts, + uint16_t pdu_bit_map, + uint16_t ptrs_bit_map, + int slot_offset, + int delay, + void *d_tx_sig_void, + void *d_intermediate_sig_void, + void *d_final_output_void, + void *d_curand_states_void, + void *h_tx_sig_pinned_void, + void *h_final_output_pinned_void, + void *d_channel_coeffs_void) +{ + // --- Cast void pointers --- + float2 *d_intermediate_sig = (float2 *)d_intermediate_sig_void; + short2 *d_final_output = (short2 *)d_final_output_void; + curandState_t *d_curand_states = (curandState_t *)d_curand_states_void; + float2 *d_channel_coeffs = (float2 *)d_channel_coeffs_void; + + const int padding_len = channel_length - 1; + const size_t padded_stride_bytes = (num_samples + padding_len) * 2 * sizeof(float); + const size_t total_padded_tx_bytes = nb_tx * padded_stride_bytes; + + float *kernel_input_ptr; +#if defined(USE_UNIFIED_MEMORY) || defined(USE_ATS_MEMORY) + kernel_input_ptr = (float *)h_tx_sig_pinned_void; +#else + float *d_tx_sig = (float *)d_tx_sig_void; + float *h_tx_sig_pinned = (float *)h_tx_sig_pinned_void; + CHECK_CUDA(cudaMemcpy(d_tx_sig, h_tx_sig_pinned, total_padded_tx_bytes, cudaMemcpyHostToDevice)); + + kernel_input_ptr = d_tx_sig; +#endif + + size_t channel_size_bytes = nb_tx * nb_rx * channel_length * sizeof(float2); + CHECK_CUDA(cudaMemcpy(d_channel_coeffs, h_channel_coeffs, channel_size_bytes, cudaMemcpyHostToDevice)); + + dim3 threads_multipath(512, 1); + dim3 blocks_multipath((num_samples + threads_multipath.x - 1) / threads_multipath.x, nb_rx); + size_t sharedMemSize = (threads_multipath.x + channel_length - 1) * sizeof(float2); + multipath_channel_kernel<<>>(d_channel_coeffs, + kernel_input_ptr, + d_intermediate_sig, + num_samples, + channel_length, + nb_tx, + nb_rx); + + dim3 threads_noise(256, 1); + dim3 blocks_noise((num_samples + threads_noise.x - 1) / threads_noise.x, nb_rx); + float pn_variance = 1e-5f * 2.0f * 3.1415926535f * 300.0f * (float)ts; + bool apply_phase_noise = (pdu_bit_map & ptrs_bit_map); + add_noise_and_phase_noise_kernel<<>>(d_intermediate_sig, + d_final_output, + d_curand_states, + num_samples, + sqrtf(sigma2 / 2.0f), + sqrtf(pn_variance), + apply_phase_noise); + + cudaDeviceSynchronize(); + + // If output_signal is NULL, the caller intends to keep the data on the GPU + // for further processing (e.g., summing outputs). Otherwise, copy back to host. + if (output_signal != NULL) { +#if defined(USE_UNIFIED_MEMORY) + short2 *h_final_output_pinned = (short2 *)h_final_output_pinned_void; + for (int ii = 0; ii < nb_rx; ii++) { + memcpy(output_signal[ii] + slot_offset + delay, h_final_output_pinned + ii * num_samples, num_samples * sizeof(short2)); + } +#else + short2 *h_final_output_pinned = (short2 *)h_final_output_pinned_void; + CHECK_CUDA(cudaMemcpy(h_final_output_pinned, d_final_output, nb_rx * num_samples * sizeof(short2), cudaMemcpyDeviceToHost)); + for (int ii = 0; ii < nb_rx; ii++) { + memcpy(output_signal[ii] + slot_offset + delay, h_final_output_pinned + ii * num_samples, num_samples * sizeof(short2)); + } +#endif + } +} + +void sum_channel_outputs_cuda(void **d_individual_outputs, void *d_final_output, int num_channels, int nb_rx, int num_samples) +{ + void **d_ptr_array; + size_t ptr_array_size = num_channels * sizeof(void *); + CHECK_CUDA(cudaMalloc(&d_ptr_array, ptr_array_size)); + + CHECK_CUDA(cudaMemcpy(d_ptr_array, d_individual_outputs, ptr_array_size, cudaMemcpyHostToDevice)); + + int num_total_samples = nb_rx * num_samples; + dim3 threads(256, 1); + dim3 blocks((num_total_samples + threads.x - 1) / threads.x, 1); + + sum_outputs_kernel<<>>((const short2 **)d_ptr_array, (short2 *)d_final_output, num_channels, num_total_samples); + + CHECK_CUDA(cudaFree(d_ptr_array)); +} +} // extern "C" diff --git a/openair1/SIMULATION/TOOLS/oai_cuda.h b/openair1/SIMULATION/TOOLS/oai_cuda.h index 9a590d0f8e..aa6fe2db28 100644 --- a/openair1/SIMULATION/TOOLS/oai_cuda.h +++ b/openair1/SIMULATION/TOOLS/oai_cuda.h @@ -89,6 +89,28 @@ void add_noise_cuda(const float **r_re, void *h_r_sig_pinned, void *h_output_sig_pinned); +void run_channel_pipeline_cuda(c16_t **output_signal, + int nb_tx, + int nb_rx, + int channel_length, + uint32_t num_samples, + float *h_channel_coeffs, + float sigma2, + double ts, + uint16_t pdu_bit_map, + uint16_t ptrs_bit_map, + int slot_offset, + int delay, + void *d_tx_sig, + void *d_intermediate_sig, + void *d_final_output, + void *d_curand_states, + void *h_tx_sig_pinned, + void *h_final_output_pinned, + void *d_channel_coeffs); + +void sum_channel_outputs_cuda(void **d_individual_outputs, void *d_final_output, int num_channels, int nb_rx, int num_samples); + /** * \brief Interleaves separate real and imaginary signal buffers into a complex format. * From d43d267626dc3dadc2aaedfbc293f2b5b92723d2 Mon Sep 17 00:00:00 2001 From: Nika Ghaderi Date: Wed, 24 Sep 2025 17:38:30 +0000 Subject: [PATCH 034/100] Add Performance Benchmark for Full Channel Pipeline This commit adds a performance benchmark for the end-to-end channel simulation pipeline, named `test_channel_simulation`. Its methodology is as follows: - It systematically iterates through a matrix of test cases, varying MIMO configuration (Tx/Rx antennas), signal length, and channel length. - For each case, it runs both the full CPU pipeline (`multipath_channel_float` followed by `add_noise_float`) and the single `run_channel_pipeline_cuda` function. - It measures the average execution time for both implementations over many trials and calculates the resulting speedup. This provides a performance characterization of the GPU pipeline across different workloads and directly compares it to the CPU baseline. --- openair1/PHY/TOOLS/tests/CMakeLists.txt | 8 + .../PHY/TOOLS/tests/test_channel_simulation.c | 309 ++++++++++++++++++ 2 files changed, 317 insertions(+) create mode 100644 openair1/PHY/TOOLS/tests/test_channel_simulation.c diff --git a/openair1/PHY/TOOLS/tests/CMakeLists.txt b/openair1/PHY/TOOLS/tests/CMakeLists.txt index 384a911d36..b52476e43a 100644 --- a/openair1/PHY/TOOLS/tests/CMakeLists.txt +++ b/openair1/PHY/TOOLS/tests/CMakeLists.txt @@ -53,7 +53,15 @@ target_link_libraries(test_noise PRIVATE UTIL SIMU PHY_COMMON LOG CONFIG_LIB shl add_dependencies(tests test_noise) add_test(NAME test_noise COMMAND test_noise) +add_executable(test_channel_simulation test_channel_simulation.c) +target_link_libraries(test_channel_simulation PRIVATE UTIL SIMU PHY_COMMON LOG CONFIG_LIB shlib_loader m oai_cuda_lib) + +add_dependencies(tests test_channel_simulation) +add_test(NAME test_channel_simulation COMMAND test_channel_simulation) + if (TARGET oai_cuda_lib) target_compile_definitions(test_multipath PRIVATE ENABLE_CUDA) + target_compile_definitions(test_noise PRIVATE ENABLE_CUDA) + target_compile_definitions(test_channel_simulation PRIVATE ENABLE_CUDA) endif() endif() diff --git a/openair1/PHY/TOOLS/tests/test_channel_simulation.c b/openair1/PHY/TOOLS/tests/test_channel_simulation.c new file mode 100644 index 0000000000..8e66350ce7 --- /dev/null +++ b/openair1/PHY/TOOLS/tests/test_channel_simulation.c @@ -0,0 +1,309 @@ +/* + * 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 +#include +#include +#include + +#include "PHY/TOOLS/tools_defs.h" +#include "SIMULATION/TOOLS/sim.h" +#include "SIMULATION/TOOLS/oai_cuda.h" +#include "common/utils/LOG/log.h" +#include "common/utils/utils.h" +#include + +configmodule_interface_t *uniqCfg = NULL; + +void exit_function(const char *file, const char *function, const int line, const char *s, const int assert_not_exit) +{ + fprintf(stderr, "Exit function called from %s:%d in %s(). Message: %s\n", file, line, function, s); + exit(1); +} + +void generate_random_signal_interleaved(float **sig_interleaved, int nb_ant, int num_samples) +{ + for (int i = 0; i < nb_ant; i++) { + for (int j = 0; j < num_samples; j++) { + sig_interleaved[i][2 * j] = (float)((rand() % 2000) - 1000); + sig_interleaved[i][2 * j + 1] = (float)((rand() % 2000) - 1000); + } + } +} + +channel_desc_t *create_manual_channel_desc(int nb_tx, int nb_rx, int channel_length) +{ + channel_desc_t *desc = (channel_desc_t *)calloc(1, sizeof(channel_desc_t)); + desc->nb_tx = nb_tx; + desc->nb_rx = nb_rx; + desc->channel_length = channel_length; + desc->path_loss_dB = 0.0; + desc->channel_offset = 0; + int num_links = nb_tx * nb_rx; + float path_loss = (float)pow(10, desc->path_loss_dB / 20.0); + desc->ch = (struct complexd **)malloc(num_links * sizeof(struct complexd *)); + for (int i = 0; i < num_links; i++) { + desc->ch[i] = (struct complexd *)malloc(channel_length * sizeof(struct complexd)); + for (int l = 0; l < channel_length; l++) { + desc->ch[i][l].r = ((double)rand() / (double)RAND_MAX * 0.1) * path_loss; + desc->ch[i][l].i = ((double)rand() / (double)RAND_MAX * 0.1) * path_loss; + } + } + return desc; +} + +void free_manual_channel_desc(channel_desc_t *desc) +{ + if (!desc) + return; + int num_links = desc->nb_tx * desc->nb_rx; + for (int i = 0; i < num_links; i++) + free(desc->ch[i]); + free(desc->ch); + free(desc); +} + +int main(int argc, char **argv) +{ + logInit(); + randominit(0); + + int nb_tx_configs[] = {1, 2, 4, 8}; + int nb_rx_configs[] = {1, 2, 4, 8}; + int num_samples_configs[] = {30720, 61440, 122880}; + int channel_length_configs[] = {16, 32}; + char *channel_type_names[] = {"Short Channel", "Long Channel"}; + int num_trials = 50; + float snr_db = 15.0f; + + printf("Starting Full Channel Pipeline Benchmark (Multipath + Noise)\n"); + printf("Averaging each test case over %d trials.\n", num_trials); + printf( + "----------------------------------------------------------------------------------------------------------------------\n"); + printf("%-15s | %-15s | %-15s | %-20s | %-20s | %-15s\n", + "Channel Type", + "MIMO Config", + "Signal Length", + "CPU Pipeline (us)", + "GPU Pipeline (us)", + "Overall Speedup"); + printf( + "----------------------------------------------------------------------------------------------------------------------\n"); + + for (int c = 0; c < sizeof(channel_length_configs) / sizeof(int); c++) { + for (int s = 0; s < sizeof(num_samples_configs) / sizeof(int); s++) { + for (int m = 0; m < sizeof(nb_tx_configs) / sizeof(int); m++) { + int nb_tx = nb_tx_configs[m]; + int nb_rx = nb_rx_configs[m]; + int num_samples = num_samples_configs[s]; + int channel_length = channel_length_configs[c]; + + char mimo_str[16]; + sprintf(mimo_str, "%dx%d", nb_tx, nb_rx); + + channel_desc_t *chan_desc = create_manual_channel_desc(nb_tx, nb_rx, channel_length); + + float **tx_sig_interleaved = malloc(nb_tx * sizeof(float *)); + float **rx_multipath_re_cpu = malloc(nb_rx * sizeof(float *)); + float **rx_multipath_im_cpu = malloc(nb_rx * sizeof(float *)); + c16_t **output_cpu = malloc(nb_rx * sizeof(c16_t *)); + c16_t **output_gpu = malloc(nb_rx * sizeof(c16_t *)); + + for (int i = 0; i < nb_tx; i++) { + tx_sig_interleaved[i] = malloc(num_samples * 2 * sizeof(float)); + } + for (int i = 0; i < nb_rx; i++) { + rx_multipath_re_cpu[i] = malloc(num_samples * sizeof(float)); + rx_multipath_im_cpu[i] = malloc(num_samples * sizeof(float)); + } + output_cpu[0] = malloc(nb_rx * num_samples * sizeof(c16_t)); + output_gpu[0] = malloc(nb_rx * num_samples * sizeof(c16_t)); + for (int i = 1; i < nb_rx; i++) { + output_cpu[i] = output_cpu[0] + i * num_samples; + output_gpu[i] = output_gpu[0] + i * num_samples; + } + + void *d_tx_sig = NULL, *d_rx_sig = NULL, *d_output_noise = NULL, *d_curand_states = NULL; + void *h_tx_sig_pinned = NULL, *h_output_sig_pinned = NULL, *d_channel_coeffs_gpu = NULL; + + const int padding_len = channel_length - 1; + const size_t padded_tx_buffer_bytes = nb_tx * (num_samples + padding_len) * 2 * sizeof(float); + const size_t rx_buffer_bytes = nb_rx * num_samples * sizeof(float2); // Intermediate buffer + const size_t output_buffer_bytes = nb_rx * num_samples * sizeof(short2); + const size_t channel_buffer_size = nb_tx * nb_rx * channel_length * sizeof(float2); + +#if defined(USE_UNIFIED_MEMORY) + cudaMallocManaged(&d_tx_sig, padded_tx_buffer_bytes, cudaMemAttachGlobal); + cudaMallocManaged(&d_rx_sig, rx_buffer_bytes, cudaMemAttachGlobal); + cudaMallocManaged(&d_output_noise, output_buffer_bytes, cudaMemAttachGlobal); + cudaMallocManaged(&d_channel_coeffs_gpu, channel_buffer_size, cudaMemAttachGlobal); + h_tx_sig_pinned = d_tx_sig; + h_output_sig_pinned = d_output_noise; +#elif defined(USE_ATS_MEMORY) + h_tx_sig_pinned = malloc(padded_tx_buffer_bytes); + d_tx_sig = NULL; + cudaMalloc(&d_rx_sig, rx_buffer_bytes); + cudaMalloc(&d_output_noise, output_buffer_bytes); + cudaMalloc(&d_channel_coeffs_gpu, channel_buffer_size); + h_output_sig_pinned = malloc(output_buffer_bytes); +#else + cudaMalloc(&d_tx_sig, padded_tx_buffer_bytes); + cudaMalloc(&d_rx_sig, rx_buffer_bytes); + cudaMalloc(&d_output_noise, output_buffer_bytes); + cudaMalloc(&d_channel_coeffs_gpu, channel_buffer_size); + cudaMallocHost(&h_tx_sig_pinned, padded_tx_buffer_bytes); + cudaMallocHost(&h_output_sig_pinned, output_buffer_bytes); +#endif + + d_curand_states = create_and_init_curand_states_cuda(nb_rx * num_samples, time(NULL)); + + double ts = 1.0 / 30.72e6; + float sigma2 = 1.0f / powf(10.0f, snr_db / 10.0f); + int num_links = nb_tx * nb_rx; + float *h_channel_coeffs = (float *)malloc(num_links * channel_length * sizeof(float2)); + for (int link = 0; link < num_links; link++) { + for (int l = 0; l < channel_length; l++) { + int idx = link * channel_length + l; + ((float2 *)h_channel_coeffs)[idx].x = (float)chan_desc->ch[link][l].r; + ((float2 *)h_channel_coeffs)[idx].y = (float)chan_desc->ch[link][l].i; + } + } + + double total_cpu_ns = 0; + double total_gpu_ns = 0; + + for (int t = 0; t < num_trials; t++) { + generate_random_signal_interleaved(tx_sig_interleaved, nb_tx, num_samples); + struct timespec start, end; + + const int padding_len = channel_length - 1; + const int padded_num_samples = num_samples + padding_len; + float *h_tx_ptr = (float *)h_tx_sig_pinned; + memset(h_tx_ptr, 0, padded_tx_buffer_bytes); + for (int j = 0; j < nb_tx; j++) { + float *data_start_ptr = h_tx_ptr + (j * padded_num_samples + padding_len) * 2; + memcpy(data_start_ptr, tx_sig_interleaved[j], num_samples * 2 * sizeof(float)); + } + + clock_gettime(CLOCK_MONOTONIC, &start); + float **tx_sig_for_cpu = malloc(nb_tx * sizeof(float *)); + for (int j = 0; j < nb_tx; j++) { + tx_sig_for_cpu[j] = h_tx_ptr + (j * padded_num_samples + padding_len) * 2; + } + multipath_channel_float(chan_desc, tx_sig_for_cpu, rx_multipath_re_cpu, rx_multipath_im_cpu, num_samples, 1, 0); + free(tx_sig_for_cpu); + add_noise_float(output_cpu, + (const float **)rx_multipath_re_cpu, + (const float **)rx_multipath_im_cpu, + sigma2, + num_samples, + 0, + ts, + 0, + false, + nb_rx); + clock_gettime(CLOCK_MONOTONIC, &end); + total_cpu_ns += (end.tv_sec - start.tv_sec) * 1e9 + (end.tv_nsec - start.tv_nsec); + + clock_gettime(CLOCK_MONOTONIC, &start); + run_channel_pipeline_cuda(output_gpu, + nb_tx, + nb_rx, + channel_length, + num_samples, + h_channel_coeffs, + sigma2, + ts, + 0, + 0, + 0, + 0, + d_tx_sig, + d_rx_sig, + d_output_noise, + d_curand_states, + h_tx_sig_pinned, + h_output_sig_pinned, + d_channel_coeffs_gpu); + cudaDeviceSynchronize(); + clock_gettime(CLOCK_MONOTONIC, &end); + total_gpu_ns += (end.tv_sec - start.tv_sec) * 1e9 + (end.tv_nsec - start.tv_nsec); + } + + double avg_cpu_us = (total_cpu_ns / num_trials) / 1000.0; + double avg_gpu_us = (total_gpu_ns / num_trials) / 1000.0; + double speedup = (avg_gpu_us > 0) ? (avg_cpu_us / avg_gpu_us) : 0; + + printf("%-15s | %-15s | %-15d | %-20.2f | %-20.2f | %-15.2fx\n", + channel_type_names[c], + mimo_str, + num_samples, + avg_cpu_us, + avg_gpu_us, + speedup); + + free(h_channel_coeffs); + for (int i = 0; i < nb_tx; i++) { + free(tx_sig_interleaved[i]); + } + for (int i = 0; i < nb_rx; i++) { + free(rx_multipath_re_cpu[i]); + free(rx_multipath_im_cpu[i]); + } + free(tx_sig_interleaved); + free(rx_multipath_re_cpu); + free(rx_multipath_im_cpu); + free(output_cpu[0]); + free(output_gpu[0]); + free(output_cpu); + free(output_gpu); + +#if defined(USE_UNIFIED_MEMORY) + cudaFree(d_tx_sig); // In UM, h_tx_sig_pinned is an alias for d_tx_sig + cudaFree(d_rx_sig); + cudaFree(d_output_noise); +#elif defined(USE_ATS_MEMORY) + free(h_tx_sig_pinned); + cudaFree(d_rx_sig); + cudaFree(d_output_noise); + free(h_output_sig_pinned); +#else // EXPLICIT COPY + cudaFree(d_tx_sig); + cudaFree(d_rx_sig); + cudaFree(d_output_noise); + cudaFreeHost(h_tx_sig_pinned); + cudaFreeHost(h_output_sig_pinned); +#endif + + cudaFree(d_channel_coeffs_gpu); + destroy_curand_states_cuda(d_curand_states); + free_manual_channel_desc(chan_desc); + } + } + } + + printf( + "----------------------------------------------------------------------------------------------------------------------\n"); + printf("Pipeline benchmark finished.\n"); + + return 0; +} From affab00c90611923ece3b5d5141441354a0a1c63 Mon Sep 17 00:00:00 2001 From: Nika Ghaderi Date: Wed, 24 Sep 2025 17:39:22 +0000 Subject: [PATCH 035/100] Add Batched and Streamed Functions for Scalability This commit extends the CUDA API with new functions designed for high-throughput scalability testing and advanced concurrency. Key additions include: - Batched versions of the kernels (`..._batched`) that use the `blockIdx.z` grid dimension to process many independent channel simulations in parallel. - A C-callable wrapper, `run_channel_pipeline_cuda_batched`, to orchestrate the execution of these new kernels. - A "streamed" version of the pipeline, `run_channel_pipeline_cuda_streamed`, which accepts a `cudaStream_t` as an argument. This allows the calling application to manage the execution stream, enabling overlap with other GPU or CPU tasks. --- openair1/SIMULATION/TOOLS/channel_pipeline.cu | 130 ++++++++++++++++++ .../SIMULATION/TOOLS/multipath_channel.cu | 49 +++++++ openair1/SIMULATION/TOOLS/oai_cuda.h | 32 +++++ openair1/SIMULATION/TOOLS/phase_noise.cu | 44 ++++++ 4 files changed, 255 insertions(+) diff --git a/openair1/SIMULATION/TOOLS/channel_pipeline.cu b/openair1/SIMULATION/TOOLS/channel_pipeline.cu index 49d229a2b6..81c9ba0734 100644 --- a/openair1/SIMULATION/TOOLS/channel_pipeline.cu +++ b/openair1/SIMULATION/TOOLS/channel_pipeline.cu @@ -23,6 +23,23 @@ #include #include "oai_cuda.h" +__global__ void multipath_channel_kernel_batched(const float2 *__restrict__ d_channel_coeffs, + const float2 *__restrict__ tx_sig, + float2 *__restrict__ rx_sig, + int num_samples, + int channel_length, + int nb_tx, + int nb_rx); + +__global__ void add_noise_and_phase_noise_kernel_batched(const float2 *__restrict__ r_sig, + short2 *__restrict__ output_sig, + curandState_t *states, + int num_samples, + int nb_rx, + float sigma, + float pn_std_dev, + bool apply_phase_noise); + #define CHECK_CUDA(val) checkCuda((val), #val, __FILE__, __LINE__) static void checkCuda(cudaError_t result, const char *const func, const char *const file, const int line) { @@ -163,4 +180,117 @@ void sum_channel_outputs_cuda(void **d_individual_outputs, void *d_final_output, CHECK_CUDA(cudaFree(d_ptr_array)); } + +void run_channel_pipeline_cuda_streamed(int nb_tx, + int nb_rx, + int channel_length, + uint32_t num_samples, + float *h_channel_coeffs, + float sigma2, + double ts, + uint16_t pdu_bit_map, + uint16_t ptrs_bit_map, + void *d_tx_sig_void, + void *d_intermediate_sig_void, + void *d_final_output_void, + void *d_curand_states_void, + void *h_tx_sig_pinned_void, + void *d_channel_coeffs_void, + void *stream_void) +{ + cudaStream_t stream = (cudaStream_t)stream_void; + float2 *d_intermediate_sig = (float2 *)d_intermediate_sig_void; + short2 *d_final_output = (short2 *)d_final_output_void; + curandState_t *d_curand_states = (curandState_t *)d_curand_states_void; + float2 *d_channel_coeffs = (float2 *)d_channel_coeffs_void; + float *kernel_input_ptr; + +#if defined(USE_UNIFIED_MEMORY) || defined(USE_ATS_MEMORY) + kernel_input_ptr = (float *)h_tx_sig_pinned_void; +#else + float *d_tx_sig = (float *)d_tx_sig_void; + float *h_tx_sig_pinned = (float *)h_tx_sig_pinned_void; + + const int padding_len = channel_length - 1; + const size_t total_padded_tx_bytes = nb_tx * (num_samples + padding_len) * 2 * sizeof(float); + + CHECK_CUDA(cudaMemcpyAsync(d_tx_sig, h_tx_sig_pinned, total_padded_tx_bytes, cudaMemcpyHostToDevice, stream)); + kernel_input_ptr = d_tx_sig; +#endif + + size_t channel_size_bytes = nb_tx * nb_rx * channel_length * sizeof(float2); + CHECK_CUDA(cudaMemcpyAsync(d_channel_coeffs, h_channel_coeffs, channel_size_bytes, cudaMemcpyHostToDevice, stream)); + + dim3 threads_multipath(512, 1); + dim3 blocks_multipath((num_samples + threads_multipath.x - 1) / threads_multipath.x, nb_rx); + size_t sharedMemSize = (threads_multipath.x + channel_length - 1) * sizeof(float2); + multipath_channel_kernel<<>>(d_channel_coeffs, + kernel_input_ptr, + d_intermediate_sig, + num_samples, + channel_length, + nb_tx, + nb_rx); + + dim3 threads_noise(256, 1); + dim3 blocks_noise((num_samples + threads_noise.x - 1) / threads_noise.x, nb_rx); + float pn_variance = 1e-5f * 2.0f * 3.1415926535f * 300.0f * (float)ts; + bool apply_phase_noise = (pdu_bit_map & ptrs_bit_map); + add_noise_and_phase_noise_kernel<<>>(d_intermediate_sig, + d_final_output, + d_curand_states, + num_samples, + sqrtf(sigma2 / 2.0f), + sqrtf(pn_variance), + apply_phase_noise); +} + +void run_channel_pipeline_cuda_batched(int num_channels, + int nb_tx, + int nb_rx, + int channel_length, + uint32_t num_samples, + void *d_channel_coeffs_batch, + float sigma2, + double ts, + uint16_t pdu_bit_map, + uint16_t ptrs_bit_map, + void *d_tx_sig_batch, + void *d_intermediate_sig_batch, + void *d_final_output_batch, + void *d_curand_states) +{ + float2 *d_tx = (float2 *)d_tx_sig_batch; + float2 *d_intermediate = (float2 *)d_intermediate_sig_batch; + short2 *d_final = (short2 *)d_final_output_batch; + float2 *d_coeffs = (float2 *)d_channel_coeffs_batch; + curandState_t *d_states = (curandState_t *)d_curand_states; + + dim3 threads_multipath(512, 1, 1); + dim3 blocks_multipath((num_samples + threads_multipath.x - 1) / threads_multipath.x, nb_rx, num_channels); + size_t sharedMemSize = (threads_multipath.x + channel_length - 1) * sizeof(float2); + + multipath_channel_kernel_batched<<>>(d_coeffs, + d_tx, + d_intermediate, + num_samples, + channel_length, + nb_tx, + nb_rx); + + dim3 threads_noise(256, 1, 1); + dim3 blocks_noise((num_samples + threads_noise.x - 1) / threads_noise.x, nb_rx, num_channels); + float pn_variance = 1e-5f * 2.0f * 3.1415926535f * 300.0f * (float)ts; + bool apply_phase_noise = (pdu_bit_map & ptrs_bit_map); + add_noise_and_phase_noise_kernel_batched<<>>(d_intermediate, + d_final, + d_states, + num_samples, + nb_rx, + sqrtf(sigma2 / 2.0f), + sqrtf(pn_variance), + apply_phase_noise); + + // Synchronization happens in the benchmark (test_channel_scalability) after this call returns. +} } // extern "C" diff --git a/openair1/SIMULATION/TOOLS/multipath_channel.cu b/openair1/SIMULATION/TOOLS/multipath_channel.cu index e292462b7b..7294ccc694 100644 --- a/openair1/SIMULATION/TOOLS/multipath_channel.cu +++ b/openair1/SIMULATION/TOOLS/multipath_channel.cu @@ -105,6 +105,55 @@ __global__ void multipath_channel_kernel(const float2 *__restrict__ d_channel_co rx_sig[rx_ant_idx * num_samples + i].y = rx_tmp.y; } +__global__ void multipath_channel_kernel_batched(const float2 *__restrict__ d_channel_coeffs, + const float2 *__restrict__ tx_sig, + float2 *__restrict__ rx_sig, + int num_samples, + int channel_length, + int nb_tx, + int nb_rx) +{ + extern __shared__ float2 tx_shared[]; + + const int i = blockIdx.x * blockDim.x + threadIdx.x; + const int rx_ant_idx = blockIdx.y; + const int c = blockIdx.z; + + if (i >= num_samples) + return; + + float2 rx_tmp = make_float2(0.0f, 0.0f); + + const int padding_len = channel_length - 1; + const int padded_num_samples = num_samples + padding_len; + + const int channel_tx_offset = c * nb_tx * padded_num_samples; + const int channel_rx_offset = c * nb_rx * num_samples; + + for (int j = 0; j < nb_tx; j++) { + const int tid = threadIdx.x; + const int block_start_idx = blockIdx.x * blockDim.x; + const int shared_mem_size = blockDim.x + channel_length - 1; + + for (int k = tid; k < shared_mem_size; k += blockDim.x) { + int load_idx = block_start_idx + k; + tx_shared[k] = tx_sig[channel_tx_offset + j * padded_num_samples + load_idx]; + } + __syncthreads(); + + for (int l = 0; l < channel_length; l++) { + float2 tx_sample = tx_shared[tid + (channel_length - 1) - l]; + int chan_link_idx = (c * nb_tx * nb_rx) + (rx_ant_idx + j * nb_rx); + float2 chan_weight = d_channel_coeffs[chan_link_idx * channel_length + l]; + rx_tmp = complex_add(rx_tmp, complex_mul(tx_sample, chan_weight)); + } + __syncthreads(); + } + + rx_sig[channel_rx_offset + rx_ant_idx * num_samples + i].x = rx_tmp.x; + rx_sig[channel_rx_offset + rx_ant_idx * num_samples + i].y = rx_tmp.y; +} + extern "C" { void multipath_channel_cuda(float **rx_sig_re, diff --git a/openair1/SIMULATION/TOOLS/oai_cuda.h b/openair1/SIMULATION/TOOLS/oai_cuda.h index aa6fe2db28..1b3f3d0ae1 100644 --- a/openair1/SIMULATION/TOOLS/oai_cuda.h +++ b/openair1/SIMULATION/TOOLS/oai_cuda.h @@ -109,6 +109,38 @@ void run_channel_pipeline_cuda(c16_t **output_signal, void *h_final_output_pinned, void *d_channel_coeffs); +void run_channel_pipeline_cuda_batched(int num_channels, + int nb_tx, + int nb_rx, + int channel_length, + uint32_t num_samples, + void *d_channel_coeffs_batch, + float sigma2, + double ts, + uint16_t pdu_bit_map, + uint16_t ptrs_bit_map, + void *d_tx_sig_batch, + void *d_intermediate_sig_batch, + void *d_final_output_batch, + void *d_curand_states); + +void run_channel_pipeline_cuda_streamed(int nb_tx, + int nb_rx, + int channel_length, + uint32_t num_samples, + float *h_channel_coeffs, + float sigma2, + double ts, + uint16_t pdu_bit_map, + uint16_t ptrs_bit_map, + void *d_tx_sig_void, + void *d_intermediate_sig_void, + void *d_final_output_void, + void *d_curand_states_void, + void *h_tx_sig_pinned_void, + void *d_channel_coeffs_void, + void *stream_void); + void sum_channel_outputs_cuda(void **d_individual_outputs, void *d_final_output, int num_channels, int nb_rx, int num_samples); /** diff --git a/openair1/SIMULATION/TOOLS/phase_noise.cu b/openair1/SIMULATION/TOOLS/phase_noise.cu index 3bca71fdf0..587c16ad69 100644 --- a/openair1/SIMULATION/TOOLS/phase_noise.cu +++ b/openair1/SIMULATION/TOOLS/phase_noise.cu @@ -86,6 +86,50 @@ __global__ void add_noise_and_phase_noise_kernel(const float2 *__restrict__ r_si (short)fmaxf(-32768.0f, fminf(32767.0f, final_signal.y))); } +__global__ void add_noise_and_phase_noise_kernel_batched(const float2 *__restrict__ r_sig, + short2 *__restrict__ output_sig, + curandState_t *states, + int num_samples, + int nb_rx, + float sigma, + float pn_std_dev, + bool apply_phase_noise) +{ + const int i = blockIdx.x * blockDim.x + threadIdx.x; // Sample index + const int ii = blockIdx.y; // RX antenna index + const int c = blockIdx.z; // Channel index + + if (i >= num_samples) + return; + + const int flat_output_idx = (c * nb_rx * num_samples) + (ii * num_samples) + i; + // Index for the smaller, reused pool of cuRAND states (does NOT include channel) + const int state_pool_idx = (ii * num_samples) + i; + + curandState_t local_state = states[state_pool_idx]; + + float2 noisy_signal = r_sig[flat_output_idx]; + + float2 awgn = curand_normal2(&local_state); + noisy_signal.x += awgn.x * sigma; + noisy_signal.y += awgn.y * sigma; + + float2 final_signal = noisy_signal; + + if (apply_phase_noise) { + float phase_error = curand_normal(&local_state) * pn_std_dev; + float cos_phi, sin_phi; + __sincosf(phase_error, &sin_phi, &cos_phi); + float2 phase_rot = make_float2(cos_phi, sin_phi); + final_signal = complex_mul(noisy_signal, phase_rot); + } + + states[state_pool_idx] = local_state; + + output_sig[flat_output_idx] = make_short2((short)fmaxf(-32768.0f, fminf(32767.0f, final_signal.x)), + (short)fmaxf(-32768.0f, fminf(32767.0f, final_signal.y))); +} + extern "C" { void add_noise_cuda(const float **r_re, From 44875e1c580e05fbbfb6dfa9de13a36da0fbef42 Mon Sep 17 00:00:00 2001 From: Nika Ghaderi Date: Tue, 23 Sep 2025 08:53:00 +0000 Subject: [PATCH 036/100] Add Scalability Benchmark This commit introduces a new, configurable benchmark, `test_channel_scalability`, designed to measure the performance and scalability of the CUDA channel simulation pipeline under various conditions. This benchmark provides several key features: - It supports multiple GPU execution models, including a high-throughput "batch" mode, an asynchronous "stream" mode, and a "serial" mode for baseline measurements. - It includes an optional interference simulation mode, activated by the `--sum-outputs` flag, which sums the outputs of multiple parallel channels. - Simulation parameters (MIMO configuration, signal length, channel length, and batch size) are configurable via command-line arguments. - It runs the full simulation pipeline on both the CPU and the GPU, reporting the average execution time for each and calculating the overall speedup. --- openair1/PHY/TOOLS/tests/CMakeLists.txt | 7 + .../TOOLS/tests/test_channel_scalability.c | 645 ++++++++++++++++++ 2 files changed, 652 insertions(+) create mode 100644 openair1/PHY/TOOLS/tests/test_channel_scalability.c diff --git a/openair1/PHY/TOOLS/tests/CMakeLists.txt b/openair1/PHY/TOOLS/tests/CMakeLists.txt index b52476e43a..3750cacaa3 100644 --- a/openair1/PHY/TOOLS/tests/CMakeLists.txt +++ b/openair1/PHY/TOOLS/tests/CMakeLists.txt @@ -59,9 +59,16 @@ target_link_libraries(test_channel_simulation PRIVATE UTIL SIMU PHY_COMMON LOG C add_dependencies(tests test_channel_simulation) add_test(NAME test_channel_simulation COMMAND test_channel_simulation) +add_executable(test_channel_scalability test_channel_scalability.c) +target_link_libraries(test_channel_scalability PRIVATE UTIL SIMU PHY_COMMON LOG CONFIG_LIB shlib_loader m oai_cuda_lib) + +add_dependencies(tests test_channel_scalability) +add_test(NAME test_channel_scalability COMMAND test_channel_scalability) + if (TARGET oai_cuda_lib) target_compile_definitions(test_multipath PRIVATE ENABLE_CUDA) target_compile_definitions(test_noise PRIVATE ENABLE_CUDA) target_compile_definitions(test_channel_simulation PRIVATE ENABLE_CUDA) + target_compile_definitions(test_channel_scalability PRIVATE ENABLE_CUDA) endif() endif() diff --git a/openair1/PHY/TOOLS/tests/test_channel_scalability.c b/openair1/PHY/TOOLS/tests/test_channel_scalability.c new file mode 100644 index 0000000000..08d50795e3 --- /dev/null +++ b/openair1/PHY/TOOLS/tests/test_channel_scalability.c @@ -0,0 +1,645 @@ +/* + * 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 +#include +#include +#include +#include + +#include "PHY/TOOLS/tools_defs.h" +#include "SIMULATION/TOOLS/sim.h" +#include "SIMULATION/TOOLS/oai_cuda.h" +#include "common/utils/LOG/log.h" +#include "common/utils/utils.h" +#include + +configmodule_interface_t *uniqCfg = NULL; + +void exit_function(const char *file, const char *function, const int line, const char *s, const int assert_not_exit) +{ + fprintf(stderr, "Exit function called from %s:%d in %s(). Message: %s\n", file, line, function, s); + exit(1); +} + +void generate_random_signal_interleaved(float **sig_interleaved, int nb_ant, int num_samples) +{ + for (int i = 0; i < nb_ant; i++) { + for (int j = 0; j < num_samples; j++) { + sig_interleaved[i][2 * j] = (float)((rand() % 2000) - 1000); // Real part (I) + sig_interleaved[i][2 * j + 1] = (float)((rand() % 2000) - 1000); // Imaginary part (Q) + } + } +} + +channel_desc_t *create_manual_channel_desc(int nb_tx, int nb_rx, int channel_length) +{ + channel_desc_t *desc = (channel_desc_t *)calloc(1, sizeof(channel_desc_t)); + desc->nb_tx = nb_tx; + desc->nb_rx = nb_rx; + desc->channel_length = channel_length; + desc->path_loss_dB = 0.0; + desc->channel_offset = 0; + int num_links = nb_tx * nb_rx; + float path_loss = (float)pow(10, desc->path_loss_dB / 20.0); + desc->ch = (struct complexd **)malloc(num_links * sizeof(struct complexd *)); + for (int i = 0; i < num_links; i++) { + desc->ch[i] = (struct complexd *)malloc(channel_length * sizeof(struct complexd)); + for (int l = 0; l < channel_length; l++) { + desc->ch[i][l].r = ((double)rand() / (double)RAND_MAX * 0.1) * path_loss; + desc->ch[i][l].i = ((double)rand() / (double)RAND_MAX * 0.1) * path_loss; + } + } + return desc; +} + +void free_manual_channel_desc(channel_desc_t *desc) +{ + if (!desc) + return; + int num_links = desc->nb_tx * desc->nb_rx; + for (int i = 0; i < num_links; i++) { + if (desc->ch[i]) + free(desc->ch[i]); + } + if (desc->ch) + free(desc->ch); + free(desc); +} + +int main(int argc, char **argv) +{ + logInit(); + randominit(0); + + int num_channels = 1; + int nb_tx = 4; + int nb_rx = 4; + int num_samples = 122880; + int channel_length = 32; + int num_trials = 50; + int sum_outputs = 0; + char mode_str[10] = "batch"; + + struct option long_options[] = {{"num-channels", required_argument, 0, 'c'}, + {"nb-tx", required_argument, 0, 't'}, + {"nb-rx", required_argument, 0, 'r'}, + {"num-samples", required_argument, 0, 's'}, + {"ch-len", required_argument, 0, 'l'}, + {"trials", required_argument, 0, 'n'}, + {"sum-outputs", no_argument, 0, 'S'}, + {"mode", required_argument, 0, 'm'}, + {"help", no_argument, 0, 'h'}, + {0, 0, 0, 0}}; + + int opt; + while ((opt = getopt_long(argc, argv, "c:t:r:s:l:n:Sm:h", long_options, NULL)) != -1) { + switch (opt) { + case 'c': + num_channels = atoi(optarg); + break; + case 't': + nb_tx = atoi(optarg); + break; + case 'r': + nb_rx = atoi(optarg); + break; + case 's': + num_samples = atoi(optarg); + break; + case 'l': + channel_length = atoi(optarg); + break; + case 'n': + num_trials = atoi(optarg); + break; + case 'S': + sum_outputs = 1; + break; + case 'm': + strncpy(mode_str, optarg, sizeof(mode_str) - 1); + break; + case 'h': + printf("Usage: %s [options]\n", argv[0]); + printf(" -c, --num-channels Number of parallel channels to simulate (Default: 1)\n"); + printf(" -t, --nb-tx Number of transmit antennas (Default: 4)\n"); + printf(" -r, --nb-rx Number of receive antennas (Default: 4)\n"); + printf(" -s, --num-samples Number of samples (Default: 30720)\n"); + printf(" -l, --ch-len Channel length (Default: 32)\n"); + printf(" -n, --trials Number of trials for averaging (Default: 50)\n"); + printf(" -S, --sum-outputs Enable summation of outputs for interference simulation.\n"); + printf(" -m, --mode GPU execution mode (Default: batch)\n"); + printf(" -h, --help Show this help message\n"); + return 0; + default: + exit(1); + } + } + + // --- MEMORY ALLOCATION --- + // HOST MEMORY + int num_tx_signals = sum_outputs ? num_channels : 1; + float *tx_sig_data = malloc(num_tx_signals * nb_tx * num_samples * 2 * sizeof(float)); + float ***tx_sig_interleaved = malloc(num_tx_signals * sizeof(float **)); + for (int i = 0; i < num_tx_signals; i++) { + tx_sig_interleaved[i] = malloc(nb_tx * sizeof(float *)); + for (int j = 0; j < nb_tx; j++) { + tx_sig_interleaved[i][j] = tx_sig_data + (i * nb_tx + j) * num_samples * 2; + } + } + + float *rx_multipath_data = malloc(nb_rx * num_samples * 2 * sizeof(float)); + float **rx_multipath_re_cpu = malloc(nb_rx * sizeof(float *)); + float **rx_multipath_im_cpu = malloc(nb_rx * sizeof(float *)); + for (int i = 0; i < nb_rx; i++) { + rx_multipath_re_cpu[i] = rx_multipath_data + i * num_samples; + rx_multipath_im_cpu[i] = rx_multipath_data + (nb_rx + i) * num_samples; + } + + c16_t *output_cpu_data = malloc(num_channels * nb_rx * num_samples * sizeof(c16_t)); + c16_t ***output_cpu = malloc(num_channels * sizeof(c16_t **)); + for (int c = 0; c < num_channels; c++) { + output_cpu[c] = malloc(nb_rx * sizeof(c16_t *)); + for (int i = 0; i < nb_rx; i++) { + output_cpu[c][i] = output_cpu_data + (c * nb_rx + i) * num_samples; + } + } + + channel_desc_t **channels = malloc(num_channels * sizeof(channel_desc_t *)); + + // Define some realistic default values for the channel model + // double sampling_rate = 122.88e6; + // double channel_bandwidth = 100e6; + // uint64_t center_freq = 3.5e9; + // double ue_speed_kmh = 3.0; + // double max_doppler = (ue_speed_kmh * 1000.0 / 3600.0) * center_freq / 3e8; + + for (int c = 0; c < num_channels; c++) { + // Use the full channel descriptor initialization with all 13 arguments + // channels[c] = new_channel_desc_scm(nb_tx, + // nb_rx, + // TDL_A, // channel_model + // sampling_rate, + // center_freq, + // channel_bandwidth, + // 1e-7, // DS_TDL (Delay Spread) + // max_doppler, // maxDoppler + // CORR_LEVEL_LOW, // corr_level + // 0.0, // forgetting_factor + // 0, // channel_offset + // 0.0, // path_loss_dB + // -100.0); // noise_power_dB + + channels[c] = create_manual_channel_desc(nb_tx, nb_rx, channel_length); + if (!channels[c]) { + fprintf(stderr, "Error creating channel descriptor %d\n", c); + exit(1); + } + } + + // DEVICE MEMORY + void *d_tx_sig = NULL, *d_rx_sig = NULL, *d_curand_states = NULL, *h_tx_sig_pinned = NULL, *h_output_sig_pinned = NULL, + *d_channel_coeffs_gpu = NULL, **d_individual_gpu_outputs = NULL, *d_summed_gpu_output = NULL; + c16_t **output_gpu = NULL; + + void *d_tx_sig_batch = NULL, *d_intermediate_sig_batch = NULL, *d_final_output_batch = NULL, *d_channel_coeffs_batch = NULL; + float2 *h_channel_coeffs_batch = NULL; + float *h_channel_coeffs = NULL; + float2 *h_tx_sig_batch_interleaved = NULL; + + const int max_taps = 256; + const int padding_len = max_taps - 1; + const int padded_num_samples = num_samples + padding_len; + + // Sizes for batch mode + size_t tx_batch_bytes = num_channels * nb_tx * padded_num_samples * sizeof(float2); + size_t intermediate_batch_bytes = num_channels * nb_rx * num_samples * sizeof(float2); + size_t final_batch_bytes = num_channels * nb_rx * num_samples * sizeof(short2); + size_t channel_batch_bytes = num_channels * nb_tx * nb_rx * max_taps * sizeof(float2); + + // Sizes for serial/stream mode + size_t tx_bytes = nb_tx * padded_num_samples * 2 * sizeof(float); + size_t rx_bytes = nb_rx * num_samples * sizeof(float2); + size_t output_bytes = nb_rx * num_samples * sizeof(short2); + size_t channel_bytes = nb_tx * nb_rx * max_taps * sizeof(float2); + + if (strcmp(mode_str, "batch") == 0) { + h_channel_coeffs_batch = malloc(channel_batch_bytes); +#if defined(USE_UNIFIED_MEMORY) + printf("Memory Mode: Unified Memory\n"); + cudaMallocManaged(&d_tx_sig_batch, tx_batch_bytes, cudaMemAttachGlobal); + cudaMallocManaged(&d_intermediate_sig_batch, intermediate_batch_bytes, cudaMemAttachGlobal); + cudaMallocManaged(&d_final_output_batch, final_batch_bytes, cudaMemAttachGlobal); + cudaMallocManaged(&d_channel_coeffs_batch, channel_batch_bytes, cudaMemAttachGlobal); +#elif defined(USE_ATS_MEMORY) + printf("Memory Mode: ATS\n"); + d_tx_sig_batch = malloc(tx_batch_bytes); + cudaMalloc(&d_intermediate_sig_batch, intermediate_batch_bytes); + cudaMalloc(&d_final_output_batch, final_batch_bytes); + cudaMalloc(&d_channel_coeffs_batch, channel_batch_bytes); +#else + printf("Memory Mode: Explicit Copy\n"); + cudaMalloc(&d_tx_sig_batch, tx_batch_bytes); + cudaMalloc(&d_intermediate_sig_batch, intermediate_batch_bytes); + cudaMalloc(&d_final_output_batch, final_batch_bytes); + cudaMalloc(&d_channel_coeffs_batch, channel_batch_bytes); + h_tx_sig_batch_interleaved = (float2 *)malloc(tx_batch_bytes); +#endif + + if (sum_outputs) { + cudaMalloc(&d_summed_gpu_output, final_batch_bytes); + } + + } else { + // Serial & Stream + h_channel_coeffs = malloc(channel_bytes); + d_individual_gpu_outputs = malloc(num_channels * sizeof(void *)); + +#if defined(USE_UNIFIED_MEMORY) + printf("Memory Mode: Unified Memory\n"); + cudaMallocManaged(&d_channel_coeffs_gpu, channel_bytes, cudaMemAttachGlobal); + cudaMallocManaged(&d_tx_sig, tx_bytes, cudaMemAttachGlobal); + cudaMallocManaged(&d_rx_sig, rx_bytes, cudaMemAttachGlobal); + for (int c = 0; c < num_channels; c++) { + cudaMallocManaged(&d_individual_gpu_outputs[c], output_bytes, cudaMemAttachGlobal); + } + if (sum_outputs) { + cudaMallocManaged(&d_summed_gpu_output, output_bytes, cudaMemAttachGlobal); + } + h_tx_sig_pinned = d_tx_sig; +#elif defined(USE_ATS_MEMORY) + printf("Memory Mode: ATS\n"); + cudaMalloc(&d_channel_coeffs_gpu, channel_bytes); + cudaMalloc(&d_rx_sig, rx_bytes); + h_tx_sig_pinned = malloc(tx_bytes); + d_tx_sig = NULL; + for (int c = 0; c < num_channels; c++) { + cudaMalloc(&d_individual_gpu_outputs[c], output_bytes); + } + if (sum_outputs) { + cudaMalloc(&d_summed_gpu_output, output_bytes); + } + if (strcmp(mode_str, "serial") == 0) { + h_output_sig_pinned = malloc(output_bytes); + } +#else + printf("Memory Mode: Explicit Copy\n"); + cudaMalloc(&d_channel_coeffs_gpu, channel_bytes); + cudaMalloc(&d_tx_sig, tx_bytes); + cudaMalloc(&d_rx_sig, rx_bytes); + cudaMallocHost(&h_tx_sig_pinned, tx_bytes); + for (int c = 0; c < num_channels; c++) { + cudaMalloc(&d_individual_gpu_outputs[c], output_bytes); + } + if (sum_outputs) { + cudaMalloc(&d_summed_gpu_output, output_bytes); + } + if (strcmp(mode_str, "serial") == 0) { + cudaMallocHost(&h_output_sig_pinned, output_bytes); + } +#endif + + if (strcmp(mode_str, "serial") == 0) { + output_gpu = malloc(nb_rx * sizeof(c16_t *)); + output_gpu[0] = malloc(nb_rx * num_samples * sizeof(c16_t)); + for (int i = 1; i < nb_rx; i++) + output_gpu[i] = output_gpu[0] + i * num_samples; + } + } + + d_curand_states = create_and_init_curand_states_cuda(nb_rx * num_samples, time(NULL)); + + double total_cpu_ns = 0; + double total_gpu_ns = 0; + + for (int t = 0; t < num_trials; t++) { + if (sum_outputs) { + d_individual_gpu_outputs = malloc(num_channels * sizeof(void *)); + } + for (int i = 0; i < num_tx_signals; i++) { + generate_random_signal_interleaved(tx_sig_interleaved[i], nb_tx, num_samples); + } + for (int c = 0; c < num_channels; c++) + random_channel(channels[c], 0); + + struct timespec start, end; + + // --- CPU RUN --- + clock_gettime(CLOCK_MONOTONIC, &start); + for (int c = 0; c < num_channels; c++) { + float **current_tx = sum_outputs ? tx_sig_interleaved[c] : tx_sig_interleaved[0]; + multipath_channel_float(channels[c], current_tx, rx_multipath_re_cpu, rx_multipath_im_cpu, num_samples, 1, 0); + add_noise_float(output_cpu[c], + (const float **)rx_multipath_re_cpu, + (const float **)rx_multipath_im_cpu, + 0.1, + num_samples, + 0, + 0, + 0, + false, + nb_rx); + } + if (sum_outputs) { + c16_t *final_sum_cpu = calloc(nb_rx * num_samples, sizeof(c16_t)); + for (int c = 0; c < num_channels; c++) { + for (int i = 0; i < nb_rx * num_samples; i++) { + final_sum_cpu[i].r += output_cpu[c][0][i].r; + final_sum_cpu[i].i += output_cpu[c][0][i].i; + } + } + free(final_sum_cpu); + } + clock_gettime(CLOCK_MONOTONIC, &end); + total_cpu_ns += (end.tv_sec - start.tv_sec) * 1e9 + (end.tv_nsec - start.tv_nsec); + + // --- GPU RUN --- + clock_gettime(CLOCK_MONOTONIC, &start); + if (strcmp(mode_str, "batch") == 0) { + for (int c = 0; c < num_channels; c++) { + for (int link = 0; link < nb_tx * nb_rx; link++) { + for (int l = 0; l < channel_length; l++) { + int batch_idx = (c * nb_tx * nb_rx * max_taps) + (link * max_taps) + l; + h_channel_coeffs_batch[batch_idx].x = (float)channels[c]->ch[link][l].r; + h_channel_coeffs_batch[batch_idx].y = (float)channels[c]->ch[link][l].i; + } + } + } + } + + if (strcmp(mode_str, "batch") == 0) { +#if defined(USE_UNIFIED_MEMORY) || defined(USE_ATS_MEMORY) + float2 *tx_batch_ptr = (float2 *)d_tx_sig_batch; + memset(tx_batch_ptr, 0, tx_batch_bytes); + for (int c = 0; c < num_channels; c++) { + float **current_tx = sum_outputs ? tx_sig_interleaved[c] : tx_sig_interleaved[0]; + for (int j = 0; j < nb_tx; j++) { + float2 *data_start_ptr = tx_batch_ptr + (c * nb_tx + j) * padded_num_samples + padding_len; + for (int i = 0; i < num_samples; i++) { + data_start_ptr[i] = make_float2(current_tx[j][2 * i], current_tx[j][2 * i + 1]); + } + } + } +#else // EXPLICIT COPY + memset(h_tx_sig_batch_interleaved, 0, tx_batch_bytes); + for (int c = 0; c < num_channels; c++) { + float **current_tx = sum_outputs ? tx_sig_interleaved[c] : tx_sig_interleaved[0]; + for (int j = 0; j < nb_tx; j++) { + float2 *data_start_ptr = h_tx_sig_batch_interleaved + (c * nb_tx + j) * padded_num_samples + padding_len; + for (int i = 0; i < num_samples; i++) { + data_start_ptr[i] = make_float2(current_tx[j][2 * i], current_tx[j][2 * i + 1]); + } + } + } + cudaMemcpy(d_tx_sig_batch, h_tx_sig_batch_interleaved, tx_batch_bytes, cudaMemcpyHostToDevice); +#endif + + cudaMemcpy(d_channel_coeffs_batch, h_channel_coeffs_batch, channel_batch_bytes, cudaMemcpyHostToDevice); + + run_channel_pipeline_cuda_batched(num_channels, + nb_tx, + nb_rx, + channel_length, + num_samples, + d_channel_coeffs_batch, + 0.1, + 0, + 0xFFFF, + 0xFFFF, + d_tx_sig_batch, + d_intermediate_sig_batch, + d_final_output_batch, + d_curand_states); + cudaDeviceSynchronize(); + + if (sum_outputs) { + for (int c = 0; c < num_channels; c++) { + d_individual_gpu_outputs[c] = d_final_output_batch + c * nb_rx * num_samples * sizeof(short2); + } + sum_channel_outputs_cuda(d_individual_gpu_outputs, d_summed_gpu_output, num_channels, nb_rx, num_samples); + } + + } else { + if (strcmp(mode_str, "stream") == 0) { + cudaStream_t streams[num_channels]; + for (int c = 0; c < num_channels; c++) + cudaStreamCreateWithFlags(&streams[c], cudaStreamNonBlocking); + + for (int c = 0; c < num_channels; c++) { + for (int link = 0; link < nb_tx * nb_rx; link++) { + for (int l = 0; l < channels[c]->channel_length; l++) { + int idx = link * max_taps + l; + ((float2 *)h_channel_coeffs)[idx].x = (float)channels[c]->ch[link][l].r; + ((float2 *)h_channel_coeffs)[idx].y = (float)channels[c]->ch[link][l].i; + } + } + + float *h_tx_ptr = (float *)h_tx_sig_pinned; + float **current_tx = sum_outputs ? tx_sig_interleaved[c] : tx_sig_interleaved[0]; + memset(h_tx_ptr, 0, tx_bytes); + for (int j = 0; j < nb_tx; j++) { + float *data_start_ptr = h_tx_ptr + (j * padded_num_samples + padding_len) * 2; + memcpy(data_start_ptr, current_tx[j], num_samples * 2 * sizeof(float)); + } + + run_channel_pipeline_cuda_streamed(nb_tx, + nb_rx, + channels[c]->channel_length, + num_samples, + h_channel_coeffs, + 0.1, + 0, + 0xFFFF, + 0xFFFF, + d_tx_sig, + d_rx_sig, + d_individual_gpu_outputs[c], + d_curand_states, + h_tx_sig_pinned, + d_channel_coeffs_gpu, + (void *)streams[c]); + } + if (sum_outputs) { + sum_channel_outputs_cuda(d_individual_gpu_outputs, d_summed_gpu_output, num_channels, nb_rx, num_samples); + } + cudaDeviceSynchronize(); + for (int c = 0; c < num_channels; c++) + cudaStreamDestroy(streams[c]); + + } else if (strcmp(mode_str, "serial") == 0) { + for (int c = 0; c < num_channels; c++) { + float *h_tx_ptr = (float *)h_tx_sig_pinned; + float **current_tx = sum_outputs ? tx_sig_interleaved[c] : tx_sig_interleaved[0]; + + memset(h_tx_ptr, 0, tx_bytes); + for (int j = 0; j < nb_tx; j++) { + float *data_start_ptr = h_tx_ptr + (j * padded_num_samples + padding_len) * 2; + memcpy(data_start_ptr, current_tx[j], num_samples * 2 * sizeof(float)); + } + + for (int link = 0; link < nb_tx * nb_rx; link++) { + for (int l = 0; l < channels[c]->channel_length; l++) { + int idx = link * max_taps + l; + ((float2 *)h_channel_coeffs)[idx].x = (float)channels[c]->ch[link][l].r; + ((float2 *)h_channel_coeffs)[idx].y = (float)channels[c]->ch[link][l].i; + } + } + + void *host_output_ptr_for_pipeline = h_output_sig_pinned; +#if defined(USE_UNIFIED_MEMORY) + host_output_ptr_for_pipeline = d_individual_gpu_outputs[c]; +#endif + + run_channel_pipeline_cuda(output_gpu, + nb_tx, + nb_rx, + channels[c]->channel_length, + num_samples, + h_channel_coeffs, + 0.1, + 0, + 0xFFFF, + 0xFFFF, + 0, + 0, + d_tx_sig, + d_rx_sig, + d_individual_gpu_outputs[c], + d_curand_states, + h_tx_sig_pinned, + host_output_ptr_for_pipeline, + d_channel_coeffs_gpu); + } + } + } + cudaDeviceSynchronize(); + clock_gettime(CLOCK_MONOTONIC, &end); + total_gpu_ns += (end.tv_sec - start.tv_sec) * 1e9 + (end.tv_nsec - start.tv_nsec); + } + + double avg_cpu_us = (total_cpu_ns / num_trials) / 1000.0; + double avg_gpu_us = (total_gpu_ns / num_trials) / 1000.0; + double speedup = (avg_cpu_us > 0 && avg_gpu_us > 0) ? (avg_cpu_us / avg_gpu_us) : 0; + double avg_cpu_per_channel_us = avg_cpu_us / num_channels; + double avg_gpu_per_channel_us = avg_gpu_us / num_channels; + double total_samples_processed = (double)num_channels * nb_rx * num_samples; + double gpu_throughput_gsps = total_samples_processed / (avg_gpu_us * 1000.0); + char val_str[30]; + + printf("\n--- Final Benchmark Results ---\n\n"); + printf("+----------------------------------+--------------------------+\n"); + printf("| %-32s | %-24s |\n", "Configuration", "Value"); + printf("+----------------------------------+--------------------------+\n"); + printf("| %-32s | %-24d |\n", "Parallel Channels", num_channels); + snprintf(val_str, sizeof(val_str), "%d x %d", nb_tx, nb_rx); + printf("| %-32s | %-24s |\n", "MIMO Configuration (Tx x Rx)", val_str); + printf("| %-32s | %-24d |\n", "Signal Length (Samples)", num_samples); + printf("| %-32s | %-24d |\n", "Trials per configuration", num_trials); + printf("+----------------------------------+--------------------------+\n"); + printf("| %-32s | %-24s |\n", "Performance Metric", "Value"); + printf("+----------------------------------+--------------------------+\n"); + printf("| %-32s | %-24.2f |\n", "Total CPU Time (us)", avg_cpu_us); + printf("| %-32s | %-24.2f |\n", "Total GPU Time (us)", avg_gpu_us); + printf("| %-32s | %-24.2f |\n", "Avg Time per Channel - CPU (us)", avg_cpu_per_channel_us); + printf("| %-32s | %-24.2f |\n", "Avg Time per Channel - GPU (us)", avg_gpu_per_channel_us); + snprintf(val_str, sizeof(val_str), "%.2fx", speedup); + printf("| %-32s | %-24s |\n", "Speedup (CPU/GPU)", val_str); + printf("| %-32s | %-24.3f |\n", "GPU Throughput (GSPS)", gpu_throughput_gsps); + printf("+----------------------------------+--------------------------+\n"); + + free(tx_sig_data); + for (int i = 0; i < num_tx_signals; i++) { + free(tx_sig_interleaved[i]); + } + free(tx_sig_interleaved); + + free(rx_multipath_data); + free(rx_multipath_re_cpu); + free(rx_multipath_im_cpu); + + free(output_cpu_data); + for (int c = 0; c < num_channels; c++) { + free(output_cpu[c]); + free_manual_channel_desc(channels[c]); + } + free(output_cpu); + free(channels); + + if (strcmp(mode_str, "batch") == 0) { + free(h_channel_coeffs_batch); + if (sum_outputs) { + cudaFree(d_summed_gpu_output); + } +#if defined(USE_ATS_MEMORY) + free(d_tx_sig_batch); +#else + cudaFree(d_tx_sig_batch); + if (h_tx_sig_batch_interleaved) + free(h_tx_sig_batch_interleaved); +#endif + cudaFree(d_intermediate_sig_batch); + cudaFree(d_final_output_batch); + cudaFree(d_channel_coeffs_batch); + } else { // Serial & Stream Cleanup + free(h_channel_coeffs); + cudaFree(d_channel_coeffs_gpu); + + for (int c = 0; c < num_channels; c++) { + cudaFree(d_individual_gpu_outputs[c]); + } + free(d_individual_gpu_outputs); + + if (sum_outputs) { + cudaFree(d_summed_gpu_output); + } + +#if defined(USE_UNIFIED_MEMORY) + cudaFree(d_tx_sig); + cudaFree(d_rx_sig); +#elif defined(USE_ATS_MEMORY) + free(h_tx_sig_pinned); + cudaFree(d_rx_sig); +#else // EXPLICIT COPY + cudaFreeHost(h_tx_sig_pinned); + cudaFree(d_tx_sig); + cudaFree(d_rx_sig); +#endif + + if (strcmp(mode_str, "serial") == 0) { + if (h_output_sig_pinned) { +#if defined(USE_ATS_MEMORY) + free(h_output_sig_pinned); +#elif defined(USE_EXPLICIT_COPY) + cudaFreeHost(h_output_sig_pinned); +#endif + } + if (output_gpu) { + free(output_gpu[0]); + free(output_gpu); + } + } + } + destroy_curand_states_cuda(d_curand_states); + + printf("Benchmark finished.\n"); + return 0; +} From b40d2f60c5c31cf827cc3fd168f68715bbaa2d66 Mon Sep 17 00:00:00 2001 From: Jaroslava Fiedlerova Date: Tue, 20 Jan 2026 10:08:17 +0000 Subject: [PATCH 037/100] Adjust output reporting of test_channel_scalability This modification enables us to use the same analysis function in CI as we use for physims. --- openair1/PHY/TOOLS/tests/test_channel_scalability.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/openair1/PHY/TOOLS/tests/test_channel_scalability.c b/openair1/PHY/TOOLS/tests/test_channel_scalability.c index 08d50795e3..ca95e8c02e 100644 --- a/openair1/PHY/TOOLS/tests/test_channel_scalability.c +++ b/openair1/PHY/TOOLS/tests/test_channel_scalability.c @@ -557,13 +557,13 @@ int main(int argc, char **argv) printf("+----------------------------------+--------------------------+\n"); printf("| %-32s | %-24s |\n", "Performance Metric", "Value"); printf("+----------------------------------+--------------------------+\n"); - printf("| %-32s | %-24.2f |\n", "Total CPU Time (us)", avg_cpu_us); - printf("| %-32s | %-24.2f |\n", "Total GPU Time (us)", avg_gpu_us); - printf("| %-32s | %-24.2f |\n", "Avg Time per Channel - CPU (us)", avg_cpu_per_channel_us); - printf("| %-32s | %-24.2f |\n", "Avg Time per Channel - GPU (us)", avg_gpu_per_channel_us); + printf("| %-32s %-19.2f %-4s |\n", "Total CPU Time", avg_cpu_us, "[us]"); + printf("| %-32s %-19.2f %-4s |\n", "Total GPU Time", avg_gpu_us, "[us]"); + printf("| %-32s %-19.2f %-4s |\n", "Avg Time per Channel - CPU", avg_cpu_per_channel_us, "[us]"); + printf("| %-32s %-19.2f %-4s |\n", "Avg Time per Channel - GPU", avg_gpu_per_channel_us, "[us]"); snprintf(val_str, sizeof(val_str), "%.2fx", speedup); - printf("| %-32s | %-24s |\n", "Speedup (CPU/GPU)", val_str); - printf("| %-32s | %-24.3f |\n", "GPU Throughput (GSPS)", gpu_throughput_gsps); + printf("| %-32s %-24s |\n", "Speedup (CPU/GPU)", val_str); + printf("| %-32s %-24.3f |\n", "GPU Throughput (GSPS)", gpu_throughput_gsps); printf("+----------------------------------+--------------------------+\n"); free(tx_sig_data); From d38188e153d42adc1c203446b47a3c8fd913004e Mon Sep 17 00:00:00 2001 From: Guido Casati Date: Wed, 21 Jan 2026 12:30:56 +0100 Subject: [PATCH 038/100] RRC: remove unused drbs configuration parameter - Delete drbs field from gNB_RrcConfigurationReq and its use in RCconfig_NRRRC() - Remove GNB_CONFIG_STRING_DRBS, help text, index, and GNBPARAMS_CHECK entry from gnb_paramdef.h - Drop DU-side verification and DRB count logging in gnb_config.c --- doc/RRC/rrc-usage.md | 2 -- openair2/COMMON/rrc_messages_types.h | 1 - openair2/GNB_APP/gnb_config.c | 3 --- openair2/GNB_APP/gnb_paramdef.h | 33 ++++++++++++---------------- 4 files changed, 14 insertions(+), 25 deletions(-) diff --git a/doc/RRC/rrc-usage.md b/doc/RRC/rrc-usage.md index bae31e864a..05f55d5123 100644 --- a/doc/RRC/rrc-usage.md +++ b/doc/RRC/rrc-usage.md @@ -94,8 +94,6 @@ the [MAC configuration](../MAC/mac-usage.md) as well for SIB 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 diff --git a/openair2/COMMON/rrc_messages_types.h b/openair2/COMMON/rrc_messages_types.h index cfc6914f4b..6b69959168 100644 --- a/openair2/COMMON/rrc_messages_types.h +++ b/openair2/COMMON/rrc_messages_types.h @@ -422,7 +422,6 @@ typedef struct NRRrcConfigurationReq_s { bool um_on_default_drb; bool enable_sdap; - int drbs; } gNB_RrcConfigurationReq; typedef struct NRDuDlReq_s { diff --git a/openair2/GNB_APP/gnb_config.c b/openair2/GNB_APP/gnb_config.c index 0b3845f5b9..01c44650ae 100644 --- a/openair2/GNB_APP/gnb_config.c +++ b/openair2/GNB_APP/gnb_config.c @@ -717,7 +717,6 @@ void RCconfig_verify(configmodule_interface_t *cfg, ngran_node_t node_type) } else if (NODE_IS_DU(node_type)) { // verify that there is no bearer config verify_gnb_param_notset(gnbp, GNB_ENABLE_SDAP_IDX, GNB_CONFIG_STRING_ENABLE_SDAP); - verify_gnb_param_notset(gnbp, GNB_DRBS, GNB_CONFIG_STRING_DRBS); verify_section_notset(cfg, GNB_CONFIG_STRING_GNB_LIST ".[0]", GNB_CONFIG_STRING_AMF_IP_ADDRESS); verify_section_notset(cfg, NULL, CONFIG_STRING_SECURITY); @@ -2092,9 +2091,7 @@ gNB_RRC_INST *RCconfig_NRRRC() nrrrc_config.num_plmn = set_plmn_config(nrrrc_config.plmn, k); nrrrc_config.enable_sdap = *GNBParamList.paramarray[i][GNB_ENABLE_SDAP_IDX].iptr; LOG_I(GNB_APP, "SDAP layer is %s\n", nrrrc_config.enable_sdap ? "enabled" : "disabled"); - nrrrc_config.drbs = *GNBParamList.paramarray[i][GNB_DRBS].iptr; nrrrc_config.um_on_default_drb = *(GNBParamList.paramarray[i][GNB_UMONDEFAULTDRB_IDX].uptr); - LOG_I(GNB_APP, "Data Radio Bearer count %d\n", nrrrc_config.drbs); }// }//End for (k=0; k Date: Wed, 17 Sep 2025 11:42:39 +0200 Subject: [PATCH 039/100] E1AP: add DRB To Remove list to E1 Bearer Context Modification lib Add DRB To Remove List IE to PDU Session To Modify list, which is sent in the Bearer Context Modification request. This allows the CUCP to inform the CUUP about DRBs to be released in a specific PDU session during a MODIFY procedure. Changes: * Add struct definition * Add enc/dec/eq/cp func * Add test --- openair2/COMMON/e1ap_messages_types.h | 9 +++++++ .../E1AP/lib/e1ap_bearer_context_management.c | 24 +++++++++++++++++++ openair2/E1AP/lib/e1ap_lib_includes.h | 2 ++ openair2/E1AP/tests/e1ap_lib_test.c | 2 ++ openair2/RRC/NR/rrc_gNB.c | 1 + 5 files changed, 38 insertions(+) diff --git a/openair2/COMMON/e1ap_messages_types.h b/openair2/COMMON/e1ap_messages_types.h index 711c126dab..01a5d6b260 100644 --- a/openair2/COMMON/e1ap_messages_types.h +++ b/openair2/COMMON/e1ap_messages_types.h @@ -409,6 +409,12 @@ typedef struct DRB_nGRAN_to_modify_s { qos_flow_to_setup_t qosFlows[E1AP_MAX_NUM_QOS_FLOWS]; } DRB_nGRAN_to_mod_t; +/* DRB To Remove Item (NG-RAN) clause 9.3.1.11 */ +typedef struct { + // DRB ID (M) + long id; +} drb_to_remove_t; + typedef enum e1ap_indication_e { SECURITY_REQUIRED = 0, SECURITY_PREFERRED, @@ -472,6 +478,9 @@ typedef struct pdu_session_to_mod_s { long numDRB2Modify; // DRB To Modify Item (1..) DRB_nGRAN_to_mod_t DRBnGRanModList[E1AP_MAX_NUM_DRBS]; + // DRB To Remove List (0..maxnoofDRBs) + int n_drb_to_remove; + drb_to_remove_t drbs_to_remove[E1AP_MAX_NUM_DRBS]; } pdu_session_to_mod_t; /** PDU Session Resource To Remove List (3GPP TS 38.463 clause 9.3.3.12) */ diff --git a/openair2/E1AP/lib/e1ap_bearer_context_management.c b/openair2/E1AP/lib/e1ap_bearer_context_management.c index f2593f103c..95245d8492 100644 --- a/openair2/E1AP/lib/e1ap_bearer_context_management.c +++ b/openair2/E1AP/lib/e1ap_bearer_context_management.c @@ -1465,6 +1465,14 @@ static E1AP_PDU_Session_Resource_To_Modify_Item_t e1_encode_pdu_session_to_mod_i *ie1_2 = e1_encode_qos_flow_to_setup(k); } } + // DRB To Remove List (O) + if (in->n_drb_to_remove > 0) { + asn1cCalloc(out.dRB_To_Remove_List_NG_RAN, drb2Remove_List); + for (int r = 0; r < in->n_drb_to_remove; r++) { + asn1cSequenceAdd(drb2Remove_List->list, E1AP_DRB_To_Remove_Item_NG_RAN_t, drb2Rem); + drb2Rem->dRB_ID = in->drbs_to_remove[r].id; + } + } return out; } @@ -1746,6 +1754,15 @@ static bool e1_decode_pdu_session_to_mod_item(pdu_session_to_mod_t *out, const E } } } + // DRB To Remove List (O) + if (in->dRB_To_Remove_List_NG_RAN) { + E1AP_DRB_To_Remove_List_NG_RAN_t *rm = in->dRB_To_Remove_List_NG_RAN; + out->n_drb_to_remove = rm->list.count; + for (int r = 0; r < rm->list.count; r++) { + E1AP_DRB_To_Remove_Item_NG_RAN_t *item = rm->list.array[r]; + out->drbs_to_remove[r].id = item->dRB_ID; + } + } return true; } @@ -1931,6 +1948,9 @@ static pdu_session_to_mod_t cp_pdu_session_to_mod_item(const pdu_session_to_mod_ // DRB to modify list for (int j = 0; j < msg->numDRB2Modify; j++) cp.DRBnGRanModList[j] = cp_drb_to_mod_item(&msg->DRBnGRanModList[j]); + // DRB to remove list + for (int r = 0; r < msg->n_drb_to_remove; r++) + cp.drbs_to_remove[r] = msg->drbs_to_remove[r]; _E1_CP_OPTIONAL_IE(&cp, msg, securityIndication); _E1_CP_OPTIONAL_IE(&cp, msg, UP_TL_information); return cp; @@ -2021,6 +2041,10 @@ static bool eq_pdu_session_to_mod_item(const pdu_session_to_mod_t *a, const pdu_ return false; } _E1_EQ_CHECK_OPTIONAL_PTR(a, b, securityIndication); + _E1_EQ_CHECK_INT(a->n_drb_to_remove, b->n_drb_to_remove); + for (int r = 0; r < a->n_drb_to_remove; r++) { + _E1_EQ_CHECK_LONG(a->drbs_to_remove[r].id, b->drbs_to_remove[r].id); + } if (a->securityIndication && b->securityIndication) { if (!eq_security_ind(a->securityIndication, b->securityIndication)) return false; diff --git a/openair2/E1AP/lib/e1ap_lib_includes.h b/openair2/E1AP/lib/e1ap_lib_includes.h index adafbe0ae5..f58875a306 100644 --- a/openair2/E1AP/lib/e1ap_lib_includes.h +++ b/openair2/E1AP/lib/e1ap_lib_includes.h @@ -76,6 +76,8 @@ #include "E1AP_PDU-Session-Resource-To-Modify-Item.h" #include "E1AP_DRB-To-Modify-List-NG-RAN.h" #include "E1AP_DRB-To-Modify-Item-NG-RAN.h" +#include "E1AP_DRB-To-Remove-List-NG-RAN.h" +#include "E1AP_DRB-To-Remove-Item-NG-RAN.h" #include "E1AP_PDU-Session-Resource-To-Setup-Mod-Item.h" #include "E1AP_DRB-To-Setup-Mod-Item-NG-RAN.h" #include "E1AP_PDU-Session-Resource-To-Remove-Item.h" diff --git a/openair2/E1AP/tests/e1ap_lib_test.c b/openair2/E1AP/tests/e1ap_lib_test.c index 50116514fb..e11cacc241 100644 --- a/openair2/E1AP/tests/e1ap_lib_test.c +++ b/openair2/E1AP/tests/e1ap_lib_test.c @@ -515,6 +515,8 @@ static void test_bearer_context_modification_request(void) .sessionId = 1, .numDRB2Modify = 1, .DRBnGRanModList[0] = drb_to_mod, + .n_drb_to_remove = 1, + .drbs_to_remove[0].id = 2, }; DRB_nGRAN_to_setup_t drb_to_setup = { diff --git a/openair2/RRC/NR/rrc_gNB.c b/openair2/RRC/NR/rrc_gNB.c index 6b914c27e9..2434cabba3 100644 --- a/openair2/RRC/NR/rrc_gNB.c +++ b/openair2/RRC/NR/rrc_gNB.c @@ -2879,6 +2879,7 @@ static int fill_drb_to_be_setup_from_e1_resp(const gNB_RRC_INST *rrc, /* pass QoS info to MAC */ int nb_qos_flows = drb_config->numQosFlowSetup; AssertFatal(nb_qos_flows > 0, "must map at least one flow to a DRB\n"); + DevAssert(nb_qos_flows <= MAX_QOS_FLOWS); drb->nr.flows_len = nb_qos_flows; drb->nr.flows = calloc_or_fail(nb_qos_flows, sizeof(*drb->nr.flows)); for (int j = 0; j < nb_qos_flows; j++) { From c679b5953b494124735dae74c3fa33dba1efc7e4 Mon Sep 17 00:00:00 2001 From: Guido Casati Date: Tue, 9 Dec 2025 16:18:58 +0100 Subject: [PATCH 040/100] E1AP: rename numQosFlow2Setup to numQosFlowsMod in DRB To Modify List A DRB To Modify Item contains a Flow Mapping Information, which overrides previous mapping information. For consistency, rename the count member to reflect a QoS Flow modification, since it is not limited to setup only. - Rename DRB flow mapping counters to `numQosFlowsMod` for a DRB To Modify List - Adjust encode/decode/equality + E1AP tests --- openair2/COMMON/e1ap_messages_types.h | 4 ++-- openair2/E1AP/lib/e1ap_bearer_context_management.c | 8 ++++---- openair2/E1AP/tests/e1ap_lib_test.c | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/openair2/COMMON/e1ap_messages_types.h b/openair2/COMMON/e1ap_messages_types.h index 01a5d6b260..37f29c1f4c 100644 --- a/openair2/COMMON/e1ap_messages_types.h +++ b/openair2/COMMON/e1ap_messages_types.h @@ -404,8 +404,8 @@ typedef struct DRB_nGRAN_to_modify_s { // DL UP Transport Layer Information (O) (clause 9.3.1.13, 9.3.2.1) int numDlUpParam; up_params_t DlUpParamList[E1AP_MAX_NUM_UP_PARAM]; - // QoS Flows Information To Modify (O) (clause 9.3.1.25, 9.3.1.26) - int numQosFlow2Setup; + // Flow Mapping Information (O) (clause 9.3.1.25, 9.3.1.26) + int numQosFlowsMod; qos_flow_to_setup_t qosFlows[E1AP_MAX_NUM_QOS_FLOWS]; } DRB_nGRAN_to_mod_t; diff --git a/openair2/E1AP/lib/e1ap_bearer_context_management.c b/openair2/E1AP/lib/e1ap_bearer_context_management.c index 95245d8492..ee832ef85a 100644 --- a/openair2/E1AP/lib/e1ap_bearer_context_management.c +++ b/openair2/E1AP/lib/e1ap_bearer_context_management.c @@ -1459,7 +1459,7 @@ static E1AP_PDU_Session_Resource_To_Modify_Item_t e1_encode_pdu_session_to_mod_i } } // QoS Flows to setup (O) - for (const qos_flow_to_setup_t *k = j->qosFlows; k < j->qosFlows + j->numQosFlow2Setup; k++) { + for (const qos_flow_to_setup_t *k = j->qosFlows; k < j->qosFlows + j->numQosFlowsMod; k++) { asn1cCalloc(drb2Mod->flow_Mapping_Information, flow_Mapping_Information); asn1cSequenceAdd(flow_Mapping_Information->list, E1AP_QoS_Flow_QoS_Parameter_Item_t, ie1_2); *ie1_2 = e1_encode_qos_flow_to_setup(k); @@ -1748,7 +1748,7 @@ static bool e1_decode_pdu_session_to_mod_item(pdu_session_to_mod_t *out, const E // QoS Flows Information To Be Setup (O) if (drb2Mod->flow_Mapping_Information) { E1AP_QoS_Flow_QoS_Parameter_List_t *qos2SetupList = drb2Mod->flow_Mapping_Information; - drb->numQosFlow2Setup = qos2SetupList->list.count; + drb->numQosFlowsMod = qos2SetupList->list.count; for (int k = 0; k < qos2SetupList->list.count; k++) { CHECK_E1AP_DEC(e1_decode_qos_flow_to_setup(drb->qosFlows + k, qos2SetupList->list.array[k])); } @@ -1992,8 +1992,8 @@ static bool eq_security_ind(security_indication_t *a, security_indication_t *b) static bool eq_drb_to_mod(const DRB_nGRAN_to_mod_t *a, const DRB_nGRAN_to_mod_t *b) { _E1_EQ_CHECK_LONG(a->id, b->id); - _E1_EQ_CHECK_INT(a->numQosFlow2Setup, b->numQosFlow2Setup); - for (int i = 0; i < a->numQosFlow2Setup; i++) { + _E1_EQ_CHECK_INT(a->numQosFlowsMod, b->numQosFlowsMod); + for (int i = 0; i < a->numQosFlowsMod; i++) { if (!eq_qos_flow(&a->qosFlows[i], &b->qosFlows[i])) return false; } diff --git a/openair2/E1AP/tests/e1ap_lib_test.c b/openair2/E1AP/tests/e1ap_lib_test.c index e11cacc241..6a94e0df23 100644 --- a/openair2/E1AP/tests/e1ap_lib_test.c +++ b/openair2/E1AP/tests/e1ap_lib_test.c @@ -505,7 +505,7 @@ static void test_bearer_context_modification_request(void) .pdcp_config = malloc_or_fail(sizeof(*drb_to_mod.pdcp_config)), .pdcp_sn_status_requested = true, .pdcp_status = malloc_or_fail(sizeof(*drb_to_mod.pdcp_status)), - .numQosFlow2Setup = 1, + .numQosFlowsMod = 1, .qosFlows[0] = dummy_qos_flows, }; *drb_to_mod.pdcp_config = dummy_pdcp_config; From fa0542f845ae9968a11025406552bd1e0570f899 Mon Sep 17 00:00:00 2001 From: Guido Casati Date: Mon, 1 Dec 2025 11:38:24 +0100 Subject: [PATCH 041/100] NGAP: fix NGAP PDU Session Modify transfer encoding Encode response/unsuccessful transfers using existing stack-allocated transfer structs. --- openair3/NGAP/ngap_gNB_nas_procedures.c | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/openair3/NGAP/ngap_gNB_nas_procedures.c b/openair3/NGAP/ngap_gNB_nas_procedures.c index 7bf7753668..f24acd79ff 100644 --- a/openair3/NGAP/ngap_gNB_nas_procedures.c +++ b/openair3/NGAP/ngap_gNB_nas_procedures.c @@ -1115,12 +1115,11 @@ int ngap_gNB_pdusession_modify_resp(instance_t instance, ngap_pdusession_modify_ qos->qosFlowIdentifier = pdusession_modify_resp_p->pdusessions[i].qos[qos_flow_index].qfi; } asn_encode_to_new_buffer_result_t res = {0}; - NGAP_PDUSessionResourceModifyResponseTransfer_t *transfer_p = NULL; - res = asn_encode_to_new_buffer(NULL, ATS_ALIGNED_CANONICAL_PER, &asn_DEF_NGAP_PDUSessionResourceModifyResponseTransfer, transfer_p); + res = asn_encode_to_new_buffer(NULL, ATS_ALIGNED_CANONICAL_PER, &asn_DEF_NGAP_PDUSessionResourceModifyResponseTransfer, &transfer); item->pDUSessionResourceModifyResponseTransfer.buf = res.buffer; item->pDUSessionResourceModifyResponseTransfer.size = res.result.encoded; - ASN_STRUCT_FREE_CONTENTS_ONLY(asn_DEF_NGAP_PDUSessionResourceModifyResponseTransfer, transfer_p); + ASN_STRUCT_FREE_CONTENTS_ONLY(asn_DEF_NGAP_PDUSessionResourceModifyResponseTransfer, &transfer); NGAP_DEBUG("pdusession_modify_resp: modified pdusession ID %ld\n", item->pDUSessionID); } @@ -1143,12 +1142,11 @@ int ngap_gNB_pdusession_modify_resp(instance_t instance, ngap_pdusession_modify_ encode_ngap_cause(&pdusessionTransfer.cause, &pdusession_modify_resp_p->pdusessions_failed[i].cause); asn_encode_to_new_buffer_result_t res = {0}; - NGAP_PDUSessionResourceModifyUnsuccessfulTransfer_t *pdusessionTransfer_p = NULL; res = asn_encode_to_new_buffer(NULL, ATS_ALIGNED_CANONICAL_PER, &asn_DEF_NGAP_PDUSessionResourceModifyUnsuccessfulTransfer, &pdusessionTransfer); item->pDUSessionResourceModifyUnsuccessfulTransfer.buf = res.buffer; item->pDUSessionResourceModifyUnsuccessfulTransfer.size = res.result.encoded; - ASN_STRUCT_FREE_CONTENTS_ONLY(asn_DEF_NGAP_PDUSessionResourceModifyUnsuccessfulTransfer, pdusessionTransfer_p); + ASN_STRUCT_FREE_CONTENTS_ONLY(asn_DEF_NGAP_PDUSessionResourceModifyUnsuccessfulTransfer, &pdusessionTransfer); NGAP_INFO("pdusession_modify_resp: failed pdusession ID %ld\n", item->pDUSessionID); } From 53c0d2d30643be52e744c7df12bf9f3579214568 Mon Sep 17 00:00:00 2001 From: Guido Casati Date: Tue, 22 Apr 2025 23:51:00 +0200 Subject: [PATCH 042/100] NGAP: refactor PDU Session Resource Modify struct definition This commit refactors the PDU Session Resource Modify Response message structures to improve naming, documentation, and compliance with 3GPP TS 38.413 9.2.1.6. Changes in NGAP: - Rename qos_flow_tobe_modified_t to qos_flow_addmod_response_item_t to better reflect its purpose as a QoS Flow Add or Modify Response Item IE (as per specs) - Add mandatory AMF UE NGAP ID field to ngap_pdusession_modify_resp_t structure - Optimize field types: change nb_of_pdusessions and nb_of_pdusessions_failed from uint16_t to uint8_t (NGAP_MAX_PDU_SESSION limit) (ref 3GPP TS 38.413, 9.2.1.6) - Update PDU Session Modify Response encoder to use amf_ue_ngap_id from the message structure (pdusession_modify_resp_p->amf_ue_ngap_id) instead of retrieving it from UE context (ue_context_p->amf_ue_ngap_id) - Add comprehensive comments with 3GPP TS 38.413 section references throughout Changes in RRC/NGAP: - Populate amf_ue_ngap_id field in PDU Session Modify Response message from UE context - Update type reference to qos_flow_addmod_response_item_t --- openair2/COMMON/ngap_messages_types.h | 27 ++++++++++++++----------- openair2/RRC/NR/rrc_gNB_NGAP.c | 3 ++- openair3/NGAP/ngap_gNB_nas_procedures.c | 2 +- 3 files changed, 18 insertions(+), 14 deletions(-) diff --git a/openair2/COMMON/ngap_messages_types.h b/openair2/COMMON/ngap_messages_types.h index b7c6c281ec..111a131f39 100644 --- a/openair2/COMMON/ngap_messages_types.h +++ b/openair2/COMMON/ngap_messages_types.h @@ -226,18 +226,21 @@ typedef struct pdusession_setup_s { pdusession_associate_qosflow_t associated_qos_flows[MAX_QOS_FLOWS]; } pdusession_setup_t; +/* QoS Flow Add or Modify Response Item (3GPP TS 38.413 9.2.1.6) */ typedef struct qos_flow_tobe_modified_s { - uint8_t qfi; // 0~63 -} qos_flow_tobe_modified_t; + // QoS Flow Identifier + uint8_t qfi; +} qos_flow_addmod_response_item_t; +/* PDU Session Resource Modify Response Item (3GPP TS 38.413 9.2.1.6) */ typedef struct pdusession_modify_s { - /* Unique pdusession_id for the UE. */ + // PDU Session ID uint8_t pdusession_id; - + /* PDU Session Resource Modify Response Transfer */ + // QoS Flow Add or Modify Response List uint8_t nb_of_qos_flow; - // qos_flow_add_or_modify - qos_flow_tobe_modified_t qos[MAX_QOS_FLOWS]; + qos_flow_addmod_response_item_t qos[MAX_QOS_FLOWS]; } pdusession_modify_t; /* Cause (9.3.1.2 of 3GPP TS 38.413) */ @@ -889,17 +892,17 @@ typedef struct ngap_pdusession_modify_req_s { pdusession_resource_item_t pdusession[NGAP_MAX_PDU_SESSION]; } ngap_pdusession_modify_req_t; +/* 9.2.1.6 of 3GPP TS 38.413 */ typedef struct ngap_pdusession_modify_resp_s { + // RAN UE NGAP ID uint32_t gNB_ue_ngap_id; - - /* Number of pdusession modify-ed in the list */ + // AMF UE NGAP ID + uint64_t amf_ue_ngap_id; + // PDU Session Resource Modify Response List (0..256) uint16_t nb_of_pdusessions; - /* list of pdusession modify-ed by RRC layers */ pdusession_modify_t pdusessions[NGAP_MAX_PDU_SESSION]; - - /* Number of pdusession failed to be modify in list */ + // PDU Session Resource Failed to Modify List (0..256) uint16_t nb_of_pdusessions_failed; - /* list of pdusessions that failed to be modify */ pdusession_failed_t pdusessions_failed[NGAP_MAX_PDU_SESSION]; } ngap_pdusession_modify_resp_t; diff --git a/openair2/RRC/NR/rrc_gNB_NGAP.c b/openair2/RRC/NR/rrc_gNB_NGAP.c index bf8a797a49..3792b0e999 100644 --- a/openair2/RRC/NR/rrc_gNB_NGAP.c +++ b/openair2/RRC/NR/rrc_gNB_NGAP.c @@ -993,6 +993,7 @@ int rrc_gNB_send_NGAP_PDUSESSION_MODIFY_RESP(gNB_RRC_INST *rrc, gNB_RRC_UE_t *UE LOG_I(NR_RRC, "send message NGAP_PDUSESSION_MODIFY_RESP \n"); resp->gNB_ue_ngap_id = UE->rrc_ue_id; + resp->amf_ue_ngap_id = UE->amf_ue_ngap_id; FOR_EACH_SEQ_ARR(rrc_pdu_session_param_t *, session, &UE->pduSessions) { if (xid != session->xid) { @@ -1015,7 +1016,7 @@ int rrc_gNB_send_NGAP_PDUSESSION_MODIFY_RESP(gNB_RRC_INST *rrc, gNB_RRC_UE_t *UE p->pdusession_id = session->param.pdusession_id; FOR_EACH_SEQ_ARR(nr_rrc_qos_t *, qos_session, &session->param.qos) { DevAssert(p->nb_of_qos_flow < MAX_QOS_FLOWS); - qos_flow_tobe_modified_t *q = &p->qos[p->nb_of_qos_flow++]; + qos_flow_addmod_response_item_t *q = &p->qos[p->nb_of_qos_flow++]; q->qfi = qos_session->qos.qfi; } LOG_I(NR_RRC, diff --git a/openair3/NGAP/ngap_gNB_nas_procedures.c b/openair3/NGAP/ngap_gNB_nas_procedures.c index f24acd79ff..8a2cfeead5 100644 --- a/openair3/NGAP/ngap_gNB_nas_procedures.c +++ b/openair3/NGAP/ngap_gNB_nas_procedures.c @@ -1084,7 +1084,7 @@ int ngap_gNB_pdusession_modify_resp(instance_t instance, ngap_pdusession_modify_ ie->id = NGAP_ProtocolIE_ID_id_AMF_UE_NGAP_ID; ie->criticality = NGAP_Criticality_ignore; ie->value.present = NGAP_PDUSessionResourceModifyResponseIEs__value_PR_AMF_UE_NGAP_ID; - asn_uint642INTEGER(&ie->value.choice.AMF_UE_NGAP_ID, ue_context_p->amf_ue_ngap_id); + asn_uint642INTEGER(&ie->value.choice.AMF_UE_NGAP_ID, pdusession_modify_resp_p->amf_ue_ngap_id); asn1cSeqAdd(&out->protocolIEs.list, ie); } /* mandatory */ From 2923e50ed0dcae12a565a11609e4f6fe5c8f7e05 Mon Sep 17 00:00:00 2001 From: Guido Casati Date: Fri, 12 Dec 2025 15:39:23 +0100 Subject: [PATCH 043/100] RRC/E1AP: Fix SDAP header mapping in E1AP and test callback header handling MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This commit fixes two related issues: 1. SDAP Header Mapping Bug (cucp_cuup_handler.c): The original code incorrectly mapped SDAP header configuration from internal E1AP representation to ASN.1 enum values. The internal representation (`bearer_context_sdap_config_t`) uses true=present/false=absent, while ASN.1 enum (`E1AP_SDAP_Configuration_t`) uses 0=present/1=absent. The fix correctly inverts the mapping: - Internal true (1) → ASN.1 0 (present) - Internal false (0) → ASN.1 1 (absent) The commit also clarifies internal representation in the stack. 2. Test Overhead Adjustment (nr-cuup-load-test.c): The change in (1) led to a failure in nr_cuup_functional_test with assertion: "size % 4 == 0" in recv_ng() - Changed UL packet overhead from 3 bytes to 2 bytes in sender_thread() - Before the SDAP fix, the test incorrectly accounted for 3 bytes: * 2 bytes PDCP header (correct) * 1 byte SDAP header (incorrectly included) - After the SDAP fix, SDAP headers are correctly set to absent, so only 2 bytes (PDCP header) are needed This ensures the test sends packets with the correct header size, matching what CU-UP expects after the SDAP configuration fix. The load test does not enable SDAP headers, therefore the original 3-byte overhead was possibly a workaround for the SDAP header bug. With the bug fixed, the test correctly uses 2-byte overhead (PDCP header). --- openair2/COMMON/e1ap_messages_types.h | 4 ++-- openair2/E1AP/lib/e1ap_bearer_context_management.c | 12 ++++++------ openair2/LAYER2/nr_pdcp/cucp_cuup_handler.c | 7 +++++-- openair2/RRC/NR/rrc_gNB_NGAP.c | 4 ++-- tests/nr-cuup/nr-cuup-load-test.c | 4 +++- 5 files changed, 18 insertions(+), 13 deletions(-) diff --git a/openair2/COMMON/e1ap_messages_types.h b/openair2/COMMON/e1ap_messages_types.h index 37f29c1f4c..48cfef98f3 100644 --- a/openair2/COMMON/e1ap_messages_types.h +++ b/openair2/COMMON/e1ap_messages_types.h @@ -268,8 +268,8 @@ typedef struct up_params_s { /* IE SDAP Configuration (clause 9.3.1.39 of 3GPP TS 38.463) */ typedef struct bearer_context_sdap_config_s { long defaultDRB; - long sDAP_Header_UL; - long sDAP_Header_DL; + bool sDAP_Header_UL; + bool sDAP_Header_DL; } bearer_context_sdap_config_t; /* IE PDCP Configuration (clause 9.3.1.38 of 3GPP TS 38.463) */ diff --git a/openair2/E1AP/lib/e1ap_bearer_context_management.c b/openair2/E1AP/lib/e1ap_bearer_context_management.c index ee832ef85a..15c472b10d 100644 --- a/openair2/E1AP/lib/e1ap_bearer_context_management.c +++ b/openair2/E1AP/lib/e1ap_bearer_context_management.c @@ -192,8 +192,8 @@ static E1AP_SDAP_Configuration_t e1_encode_sdap_config(const bearer_context_sdap { E1AP_SDAP_Configuration_t out = {0}; out.defaultDRB = in->defaultDRB ? E1AP_DefaultDRB_true : E1AP_DefaultDRB_false; - out.sDAP_Header_UL = in->sDAP_Header_UL; - out.sDAP_Header_DL = in->sDAP_Header_DL; + out.sDAP_Header_UL = in->sDAP_Header_UL ? E1AP_SDAP_Header_UL_present : E1AP_SDAP_Header_UL_absent; + out.sDAP_Header_DL = in->sDAP_Header_DL ? E1AP_SDAP_Header_DL_present : E1AP_SDAP_Header_DL_absent; return out; } @@ -201,8 +201,8 @@ static E1AP_SDAP_Configuration_t e1_encode_sdap_config(const bearer_context_sdap static bool e1_decode_sdap_config(bearer_context_sdap_config_t *out, const E1AP_SDAP_Configuration_t *in) { out->defaultDRB = in->defaultDRB == E1AP_DefaultDRB_true; - out->sDAP_Header_UL = in->sDAP_Header_UL; - out->sDAP_Header_DL = in->sDAP_Header_DL; + out->sDAP_Header_UL = in->sDAP_Header_UL == E1AP_SDAP_Header_UL_present; + out->sDAP_Header_DL = in->sDAP_Header_DL == E1AP_SDAP_Header_DL_present; return true; } @@ -212,8 +212,8 @@ static bool e1_decode_sdap_config(bearer_context_sdap_config_t *out, const E1AP_ static bool eq_sdap_config(const bearer_context_sdap_config_t *a, const bearer_context_sdap_config_t *b) { _E1_EQ_CHECK_LONG(a->defaultDRB, b->defaultDRB); - _E1_EQ_CHECK_LONG(a->sDAP_Header_UL, b->sDAP_Header_UL); - _E1_EQ_CHECK_LONG(a->sDAP_Header_DL, b->sDAP_Header_DL); + _E1_EQ_CHECK_INT(a->sDAP_Header_UL, b->sDAP_Header_UL); + _E1_EQ_CHECK_INT(a->sDAP_Header_DL, b->sDAP_Header_DL); return true; } diff --git a/openair2/LAYER2/nr_pdcp/cucp_cuup_handler.c b/openair2/LAYER2/nr_pdcp/cucp_cuup_handler.c index ecdf3dfd72..feb4ea3eff 100644 --- a/openair2/LAYER2/nr_pdcp/cucp_cuup_handler.c +++ b/openair2/LAYER2/nr_pdcp/cucp_cuup_handler.c @@ -60,8 +60,11 @@ static void fill_DRB_configList_e1(NR_DRB_ToAddModList_t *DRB_configList, const asn1cCalloc(ie->cnAssociation->choice.sdap_Config, sdap_config); sdap_config->pdu_Session = pdu->sessionId; /* SDAP */ - sdap_config->sdap_HeaderDL = drb->sdap_config.sDAP_Header_DL; - sdap_config->sdap_HeaderUL = drb->sdap_config.sDAP_Header_UL; + /* Convert from internal representation to ASN.1 enum: + * Internal: false=absent, true=present (or uninitialized=false means absent) + * ASN.1: 0=present, 1=absent */ + sdap_config->sdap_HeaderDL = drb->sdap_config.sDAP_Header_DL ? NR_SDAP_Config__sdap_HeaderDL_present : NR_SDAP_Config__sdap_HeaderDL_absent; + sdap_config->sdap_HeaderUL = drb->sdap_config.sDAP_Header_UL ? NR_SDAP_Config__sdap_HeaderUL_present : NR_SDAP_Config__sdap_HeaderUL_absent; sdap_config->defaultDRB = drb->sdap_config.defaultDRB; asn1cCalloc(sdap_config->mappedQoS_FlowsToAdd, FlowsToAdd); for (int j = 0; j < drb->numQosFlow2Setup; j++) { diff --git a/openair2/RRC/NR/rrc_gNB_NGAP.c b/openair2/RRC/NR/rrc_gNB_NGAP.c index 3792b0e999..ce87d56661 100644 --- a/openair2/RRC/NR/rrc_gNB_NGAP.c +++ b/openair2/RRC/NR/rrc_gNB_NGAP.c @@ -299,8 +299,8 @@ static DRB_nGRAN_to_setup_t fill_e1_drb_to_setup(const drb_t *rrc_drb, drb_ngran.id = rrc_drb->drb_id; drb_ngran.sdap_config.defaultDRB = true; - drb_ngran.sdap_config.sDAP_Header_UL = session->sdap_config.header_ul_absent ? 1 : 0; - drb_ngran.sdap_config.sDAP_Header_DL = session->sdap_config.header_dl_absent ? 1 : 0; + drb_ngran.sdap_config.sDAP_Header_UL = session->sdap_config.header_ul_absent ? false : true; + drb_ngran.sdap_config.sDAP_Header_DL = session->sdap_config.header_dl_absent ? false : true; drb_ngran.pdcp_config = set_bearer_context_pdcp_config(rrc_drb->pdcp_config, um_on_default_drb, redcap_cap); diff --git a/tests/nr-cuup/nr-cuup-load-test.c b/tests/nr-cuup/nr-cuup-load-test.c index 0774b26b1b..047d316afc 100644 --- a/tests/nr-cuup/nr-cuup-load-test.c +++ b/tests/nr-cuup/nr-cuup-load-test.c @@ -418,7 +418,9 @@ static void *sender_thread(void *v) { struct thr_data *d = v; - size_t oh = d->is_ul ? 3 : 0; + // Overhead: PDCP header (2 bytes) for UL packets + // SDAP headers are disabled, so only PDCP header is added + size_t oh = d->is_ul ? 2 : 0; size_t packet_len = d->data_len + oh; uint8_t buf[packet_len]; memset(buf, 0, packet_len); From 7982374f6a4428b32899da151af8f91cc2360dcd Mon Sep 17 00:00:00 2001 From: Guido Casati Date: Wed, 27 Aug 2025 12:00:35 +0200 Subject: [PATCH 044/100] RRC/NGAP: add utility function to fill E1 QoS to setup list from NGAP Introduce fill_e1_qos_flow_to_setup to translate pdusession_level_qos_parameter_t (NGAP) into qos_flow_to_setup_t (E1), including ARP and QoS characteristics (dynamic/non-dynamic). Refactor fill_e1_drb_to_setup to use the new helper for building QoS flows. * Only include QoS flows whose drb_id matches the target DRB. * Add DevAssert(drb_ngran.numQosFlow2Setup > 0) to ensure at least one QoS flow is added. This change centralizes and simplifies QoS mapping logic between NGAP inputs and E1 DRB configuration. It ensures correctness by associating QoS flows to the appropriate DRB. Major changes: - Extract QoS flow mapping logic into fill_e1_qos_flow_to_setup helper function - Filter QoS flows by matching drb_id before adding to DRB setup list - Replace inline mapping code with helper function call - Add assertion to verify at least one QoS flow is added per DRB --- openair2/RRC/NR/rrc_gNB_NGAP.c | 48 +++++++++++++++++++++------------- 1 file changed, 30 insertions(+), 18 deletions(-) diff --git a/openair2/RRC/NR/rrc_gNB_NGAP.c b/openair2/RRC/NR/rrc_gNB_NGAP.c index ce87d56661..b18964ebe5 100644 --- a/openair2/RRC/NR/rrc_gNB_NGAP.c +++ b/openair2/RRC/NR/rrc_gNB_NGAP.c @@ -289,6 +289,31 @@ void rrc_gNB_send_NGAP_NAS_FIRST_REQ(gNB_RRC_INST *rrc, gNB_RRC_UE_t *UE, NR_RRC itti_send_msg_to_task(TASK_NGAP, rrc->module_id, message_p); } +/// @brief set QoS Flows to Setup in E1 DRB To Setup List +static qos_flow_to_setup_t fill_e1_qos_flow_to_setup(const pdusession_level_qos_parameter_t *qos) +{ + qos_flow_to_setup_t qos_flow = { .qfi = qos->qfi }; + // ARP + ngran_allocation_retention_priority_t *arp_out = &qos_flow.qos_params.alloc_reten_priority; + const qos_arp_t *arp_in = &qos->arp; + arp_out->priority_level = arp_in->priority_level; + arp_out->preemption_capability = arp_in->pre_emp_capability; + arp_out->preemption_vulnerability = arp_in->pre_emp_vulnerability; + // QoS Characteristics + qos_characteristics_t *qos_characteristics = &qos_flow.qos_params.qos_characteristics; + if (qos->fiveQI_type == NON_DYNAMIC) { + qos_characteristics->non_dynamic.fiveqi = qos->fiveQI; + qos_characteristics->non_dynamic.qos_priority_level = qos->qos_priority; + } else { + qos_characteristics->dynamic.fiveqi = qos->fiveQI; + qos_characteristics->dynamic.qos_priority_level = qos->qos_priority; + // NOTE: missing packet error rate and delay budget + } + qos_characteristics->qos_type = qos->fiveQI_type; + + return qos_flow; +} + /** @brief Returns an instance of E1AP DRB To Setup List */ static DRB_nGRAN_to_setup_t fill_e1_drb_to_setup(const drb_t *rrc_drb, const pdusession_t *session, @@ -310,26 +335,13 @@ static DRB_nGRAN_to_setup_t fill_e1_drb_to_setup(const drb_t *rrc_drb, } FOR_EACH_SEQ_ARR(nr_rrc_qos_t *, qos, &session->qos) { - pdusession_level_qos_parameter_t *qos_session = &qos->qos; + if (qos->drb_id != drb_ngran.id) + continue; + const pdusession_level_qos_parameter_t *qos_session = &qos->qos; DevAssert(drb_ngran.numQosFlow2Setup < MAX_QOS_FLOWS); - qos_flow_to_setup_t *qos_flow = &drb_ngran.qosFlows[drb_ngran.numQosFlow2Setup++]; - qos_characteristics_t *qos_char = &qos_flow->qos_params.qos_characteristics; - qos_flow->qfi = qos_session->qfi; - qos_char->qos_type = qos_session->fiveQI_type; - if (qos_char->qos_type == DYNAMIC) { - qos_char->dynamic.fiveqi = qos_session->fiveQI; - qos_char->dynamic.qos_priority_level = qos_session->qos_priority; - } else { - qos_char->non_dynamic.fiveqi = qos_session->fiveQI; - qos_char->non_dynamic.qos_priority_level = qos_session->qos_priority; - } - - ngran_allocation_retention_priority_t *arp_out = &qos_flow->qos_params.alloc_reten_priority; - qos_arp_t *arp_in = &qos_session->arp; - arp_out->priority_level = arp_in->priority_level; - arp_out->preemption_capability = arp_in->pre_emp_capability; - arp_out->preemption_vulnerability = arp_in->pre_emp_vulnerability; + drb_ngran.qosFlows[drb_ngran.numQosFlow2Setup++] = fill_e1_qos_flow_to_setup(qos_session); } + DevAssert(drb_ngran.numQosFlow2Setup > 0); return drb_ngran; } From eaf27acb3b844d772203ac9db355b722306e2710 Mon Sep 17 00:00:00 2001 From: Guido Casati Date: Thu, 12 Jun 2025 11:04:34 +0200 Subject: [PATCH 045/100] RRC/NGAP: refactor rrc_gNB_send_NGAP_PDUSESSION_MODIFY_RESP This commit refactors the rrc_gNB_send_NGAP_PDUSESSION_MODIFY_RESP function to improve code clarity, fix bugs, and ensure compliance with 3GPP TS 38.413 section 9.2.1.6. The main improvement is ensuring the PDU Session Resource Modify Response message is always sent with mandatory IEs, even when there are no PDU sessions to report in the optional lists. Key changes: - Initialize message structure parameters to 0 by leveraging structure initialization, ensuring clean state - Replace local counter variables (pdu_sessions_done, pdu_sessions_failed) with direct access to resp->nb_of_pdusessions and resp->nb_of_pdusessions_failed, eliminating redundant state tracking and improving code clarity - Remove conditional message sending: always send the response message (per 3GPP TS 38.413 9.2.1.6, PDUSessionResourceModifyResponse must always be sent with mandatory IEs: AMF-UE-NGAP-ID and RAN-UE-NGAP-ID, even when optional lists are empty) - Fix duplicate assignment: remove redundant p->pdusession_id assignment (was assigned twice in the PDU_SESSION_STATUS_DONE case) - Improve failed PDU session handling: use direct structure assignment (failed->cause = session->cause) instead of field-by-field copying, making the code more maintainable - Remove failed PDU session upon response - Enhance logging: improve log messages for better debugging and clarity - Add function documentation comment --- openair2/RRC/NR/rrc_gNB_NGAP.c | 47 ++++++++++++---------------------- 1 file changed, 16 insertions(+), 31 deletions(-) diff --git a/openair2/RRC/NR/rrc_gNB_NGAP.c b/openair2/RRC/NR/rrc_gNB_NGAP.c index b18964ebe5..e1c1bc93dc 100644 --- a/openair2/RRC/NR/rrc_gNB_NGAP.c +++ b/openair2/RRC/NR/rrc_gNB_NGAP.c @@ -985,25 +985,22 @@ int rrc_gNB_process_NGAP_PDUSESSION_MODIFY_REQ(MessageDef *msg_p, instance_t ins return (0); } +/** @brief Send PDU Session Resource Setup Response (9.2.1.6 3GPP TS 38.413) + * Direction: Direction: NG-RAN node → AMF */ int rrc_gNB_send_NGAP_PDUSESSION_MODIFY_RESP(gNB_RRC_INST *rrc, gNB_RRC_UE_t *UE, uint8_t xid) { - MessageDef *msg_p = NULL; - uint16_t pdu_sessions_failed = 0; - uint16_t pdu_sessions_done = 0; - if (!seq_arr_size(&UE->pduSessions)) { LOG_W(NR_RRC, "UE %d: No PDU sessions in the list, don't send NG PDU Session Modify Response\n", UE->rrc_ue_id); return -1; } - msg_p = itti_alloc_new_message (TASK_RRC_GNB, rrc->module_id, NGAP_PDUSESSION_MODIFY_RESP); + MessageDef *msg_p = itti_alloc_new_message(TASK_RRC_GNB, rrc->module_id, NGAP_PDUSESSION_MODIFY_RESP); if (msg_p == NULL) { LOG_E(NR_RRC, "itti_alloc_new_message failed, msg_p is NULL \n"); return (-1); } - ngap_pdusession_modify_resp_t *resp = &NGAP_PDUSESSION_MODIFY_RESP(msg_p); - LOG_I(NR_RRC, "send message NGAP_PDUSESSION_MODIFY_RESP \n"); + ngap_pdusession_modify_resp_t *resp = &NGAP_PDUSESSION_MODIFY_RESP(msg_p); resp->gNB_ue_ngap_id = UE->rrc_ue_id; resp->amf_ue_ngap_id = UE->amf_ue_ngap_id; @@ -1018,47 +1015,35 @@ int rrc_gNB_send_NGAP_PDUSESSION_MODIFY_RESP(gNB_RRC_INST *rrc, gNB_RRC_UE_t *UE continue; } if (session->status == PDU_SESSION_STATUS_DONE) { - LOG_I(NR_RRC, "Successfully modified PDU Session %d \n", session->param.pdusession_id); + LOG_I(NR_RRC, "PDU Session Modify successful (pdusession_id=%d) \n", session->param.pdusession_id); // Update status session->status = PDU_SESSION_STATUS_ESTABLISHED; session->cause.type = NGAP_CAUSE_NOTHING; // Fill response - DevAssert(pdu_sessions_done < NGAP_MAX_PDU_SESSION); - pdusession_modify_t *p = &resp->pdusessions[pdu_sessions_done++]; + DevAssert(resp->nb_of_pdusessions <= NGAP_MAX_PDU_SESSION); + pdusession_modify_t *p = &resp->pdusessions[resp->nb_of_pdusessions++]; p->pdusession_id = session->param.pdusession_id; FOR_EACH_SEQ_ARR(nr_rrc_qos_t *, qos_session, &session->param.qos) { DevAssert(p->nb_of_qos_flow < MAX_QOS_FLOWS); qos_flow_addmod_response_item_t *q = &p->qos[p->nb_of_qos_flow++]; q->qfi = qos_session->qos.qfi; } - LOG_I(NR_RRC, - "Modify Resp (msg index %d, status %d, xid %d): nb_of_pduSessions %ld, pdusession_id %d \n ", - pdu_sessions_done, - session->status, - xid, - seq_arr_size(&UE->pduSessions), - p->pdusession_id); + p->pdusession_id = session->param.pdusession_id; } else if ((session->status == PDU_SESSION_STATUS_NEW) || (session->status == PDU_SESSION_STATUS_ESTABLISHED)) { LOG_D(NR_RRC, "PDU SESSION is NEW or already ESTABLISHED\n"); } else if (session->status == PDU_SESSION_STATUS_FAILED) { - DevAssert(pdu_sessions_failed < NGAP_MAX_PDU_SESSION); - resp->pdusessions_failed[pdu_sessions_failed].pdusession_id = session->param.pdusession_id; - resp->pdusessions_failed[pdu_sessions_failed].cause.type = session->cause.type; - resp->pdusessions_failed[pdu_sessions_failed].cause.value = session->cause.value; - pdu_sessions_failed++; + DevAssert(resp->nb_of_pdusessions_failed <= NGAP_MAX_PDU_SESSION); + pdusession_failed_t *failed = &resp->pdusessions_failed[resp->nb_of_pdusessions_failed++]; + failed->pdusession_id = session->param.pdusession_id; + failed->cause = session->cause; + rm_pduSession(&UE->pduSessions, &UE->drbs, session->param.pdusession_id); } else LOG_W(NR_RRC, "Modify pdu session %d, unknown state %d \n ", session->param.pdusession_id, session->status); } - resp->nb_of_pdusessions = pdu_sessions_done; - resp->nb_of_pdusessions_failed = pdu_sessions_failed; - - if (pdu_sessions_done > 0 || pdu_sessions_failed > 0) { - LOG_D(NR_RRC, "NGAP_PDUSESSION_MODIFY_RESP: sending the message (total pdu session %ld)\n", seq_arr_size(&UE->pduSessions)); - itti_send_msg_to_task (TASK_NGAP, rrc->module_id, msg_p); - } else { - itti_free (ITTI_MSG_ORIGIN_ID(msg_p), msg_p); - } + // Send message to NGAP (always send: if no PDU sessions, only mandatory IEs) + LOG_D(NR_RRC, "Send NG PDU Session Modify Response (nb_of_pdusessions %d, xid %d)\n", resp->nb_of_pdusessions, xid); + itti_send_msg_to_task(TASK_NGAP, rrc->module_id, msg_p); return 0; } From 2ed28c84b35bccd882904aee628d24f78c86d92f Mon Sep 17 00:00:00 2001 From: Guido Casati Date: Wed, 23 Apr 2025 01:22:55 +0200 Subject: [PATCH 046/100] RRC/NGAP: refactor rrc_gNB_process_NGAP_PDUSESSION_MODIFY_REQ TS 38.413 8.2.3 PDU Session Modify: > For each PDU session which failed to be modified, the PDU Session Resource Modify > Unsuccessful Transfer IE shall be included containing the failure cause > If the NG-RAN node receives a PDU SESSION RESOURCE MODIFY REQUEST message containing > some PDU Session ID IEs (in the PDU Session Resource Modify Request List IE) that the > NG-RAN node does not recognize, the NG-RAN node shall report the corresponding invalid > PDU sessions as failed in the PDU SESSION RESOURCE MODIFY RESPONSE message > with an appropriate cause value Changes in the commit: * add rrc_gNB_ngap_pdusession_mod_failure helper function to handle abnormal conditions and send NGAP response with (all) failed PDU sessions * improve error handling: send NGAP response instead of silently returning -1 when UE context lookup fails (unknown RAN UE NGAP ID) * add AMF UE NGAP ID validation: check stored AMF UE NGAP ID matches request and send failure response if mismatch (per 3GPP TS 38.413) * handle unrecognized PDU Session IDs per 3GPP TS 38.413 8.2.3.4: add to failed list to be sent in the reponse message * simplify failure path: reuse rrc_gNB_send_NGAP_PDUSESSION_MODIFY_RESP instead of manually constructing response message * remove failed PDU sessions from RRC list in rrc_gNB_send_NGAP_PDUSESSION_MODIFY_RESP * remove redundant code and improve code organization * add bounds checking * improve function signature --- openair2/RRC/NR/rrc_gNB.c | 2 +- openair2/RRC/NR/rrc_gNB_NGAP.c | 75 +++++++++++++++++++++------------- openair2/RRC/NR/rrc_gNB_NGAP.h | 2 +- 3 files changed, 48 insertions(+), 31 deletions(-) diff --git a/openair2/RRC/NR/rrc_gNB.c b/openair2/RRC/NR/rrc_gNB.c index 2434cabba3..960f076e1d 100644 --- a/openair2/RRC/NR/rrc_gNB.c +++ b/openair2/RRC/NR/rrc_gNB.c @@ -3262,7 +3262,7 @@ void *rrc_gnb_task(void *args_p) { break; case NGAP_PDUSESSION_MODIFY_REQ: - rrc_gNB_process_NGAP_PDUSESSION_MODIFY_REQ(msg_p, instance); + rrc_gNB_process_NGAP_PDUSESSION_MODIFY_REQ(&NGAP_PDUSESSION_MODIFY_REQ(msg_p), instance); break; case NGAP_PDUSESSION_RELEASE_COMMAND: diff --git a/openair2/RRC/NR/rrc_gNB_NGAP.c b/openair2/RRC/NR/rrc_gNB_NGAP.c index e1c1bc93dc..7307216877 100644 --- a/openair2/RRC/NR/rrc_gNB_NGAP.c +++ b/openair2/RRC/NR/rrc_gNB_NGAP.c @@ -923,33 +923,64 @@ static void nr_rrc_update_pdusession(pdusession_t *dst, const pdusession_resourc } //------------------------------------------------------------------------------ -int rrc_gNB_process_NGAP_PDUSESSION_MODIFY_REQ(MessageDef *msg_p, instance_t instance) -//------------------------------------------------------------------------------ +/** @brief Handle abnormal conditions in PDU Session Modify procedure. + * Send a response with failed of PDU sessions only, for all PDU Sessions in the request */ +static int rrc_gNB_ngap_pdusession_mod_failure(int module_id, const ngap_pdusession_modify_req_t *req, const ngap_cause_t cause) { - rrc_gNB_ue_context_t *ue_context_p = NULL; - - ngap_pdusession_modify_req_t *req = &NGAP_PDUSESSION_MODIFY_REQ(msg_p); + MessageDef *msg_p = itti_alloc_new_message (TASK_RRC_GNB, module_id, NGAP_PDUSESSION_MODIFY_RESP); + if (msg_p == NULL) { + LOG_E(NR_RRC, "itti_alloc_new_message failed, msg_p is NULL \n"); + return -1; + } + ngap_pdusession_modify_resp_t *resp = &NGAP_PDUSESSION_MODIFY_RESP(msg_p); + resp->amf_ue_ngap_id = req->amf_ue_ngap_id; + resp->gNB_ue_ngap_id = req->gNB_ue_ngap_id; + resp->nb_of_pdusessions_failed = req->nb_pdusessions_tomodify; + resp->pdusessions_failed->cause = cause; + for (int i = 0; i < req->nb_pdusessions_tomodify; i++) + resp->pdusessions_failed[i].pdusession_id = req->pdusession[i].pdusession_id; + itti_send_msg_to_task(TASK_NGAP, module_id, msg_p); + return 0; +} +int rrc_gNB_process_NGAP_PDUSESSION_MODIFY_REQ(const ngap_pdusession_modify_req_t *req, instance_t instance) +{ gNB_RRC_INST *rrc = RC.nrrrc[instance]; - ue_context_p = rrc_gNB_get_ue_context(rrc, req->gNB_ue_ngap_id); + + rrc_gNB_ue_context_t *ue_context_p = rrc_gNB_get_ue_context(rrc, req->gNB_ue_ngap_id); if (ue_context_p == NULL) { LOG_W(NR_RRC, "[gNB %ld] In NGAP_PDUSESSION_MODIFY_REQ: unknown UE from NGAP ids (%u)\n", instance, req->gNB_ue_ngap_id); - // TO implement return setup failed + ngap_cause_t cause = {.type = NGAP_CAUSE_RADIO_NETWORK, .value = NGAP_CAUSE_RADIO_NETWORK_INCONSISTENT_REMOTE_UE_NGAP_ID}; + rrc_gNB_ngap_pdusession_mod_failure(rrc->module_id, req, cause); return (-1); } gNB_RRC_UE_t *UE = &ue_context_p->ue_context; + if (UE->amf_ue_ngap_id != req->amf_ue_ngap_id) { + LOG_W(NR_RRC, + "Stored amf_ue_ngap_id %ld for UE %x does not match the requested one %ld\n", + UE->amf_ue_ngap_id, + UE->rrc_ue_id, + req->amf_ue_ngap_id); + ngap_cause_t cause = {.type = NGAP_CAUSE_RADIO_NETWORK, .value = NGAP_CAUSE_RADIO_NETWORK_INCONSISTENT_REMOTE_UE_NGAP_ID}; + rrc_gNB_ngap_pdusession_mod_failure(rrc->module_id, req, cause); + return -1; + } + + uint8_t xid = rrc_gNB_get_next_transaction_identifier(rrc->module_id); bool all_failed = true; + DevAssert(req->nb_pdusessions_tomodify <= NGAP_MAX_PDU_SESSION); for (int i = 0; i < req->nb_pdusessions_tomodify; i++) { const pdusession_resource_item_t *sessMod = &req->pdusession[i]; rrc_pdu_session_param_t *session = find_pduSession(&UE->pduSessions, sessMod->pdusession_id); if (!session) { + /* 8.2.3.4 3GPP TS 38.413: If the NG-RAN node receives unrecognized PDU Session ID IEs, + the NG-RAN node shall report the corresponding invalid PDU sessions as failed. So we add it to the list. */ LOG_W(NR_RRC, "Requested modification of non-existing PDU session, refusing modification\n"); - rrc_pdu_session_param_t to_add = {0}; - to_add.status = PDU_SESSION_STATUS_FAILED; - to_add.param.pdusession_id = sessMod->pdusession_id; - to_add.cause.type = NGAP_CAUSE_RADIO_NETWORK; - to_add.cause.value = NGAP_CAUSE_RADIO_NETWORK_UNKNOWN_PDU_SESSION_ID; - seq_arr_push_back(&UE->pduSessions, &to_add, sizeof(to_add)); + ngap_cause_t cause = {.type = NGAP_CAUSE_RADIO_NETWORK, .value = NGAP_CAUSE_RADIO_NETWORK_UNKNOWN_PDU_SESSION_ID}; + pdusession_t session = {.pdusession_id = sessMod->pdusession_id}; + rrc_pdu_session_param_t *added = add_pduSession(&UE->pduSessions, &session); + added->status = PDU_SESSION_STATUS_FAILED; + added->cause = cause; } else { all_failed = false; session->status = PDU_SESSION_STATUS_NEW; @@ -961,26 +992,12 @@ int rrc_gNB_process_NGAP_PDUSESSION_MODIFY_REQ(MessageDef *msg_p, instance_t ins if (!all_failed) { rrc_gNB_modify_dedicatedRRCReconfiguration(rrc, UE); } else { - LOG_I(NR_RRC, - "pdu session modify failed, fill NGAP_PDUSESSION_MODIFY_RESP with the pdu session information that failed to modify \n"); MessageDef *msg_fail_p = itti_alloc_new_message(TASK_RRC_GNB, 0, NGAP_PDUSESSION_MODIFY_RESP); if (msg_fail_p == NULL) { LOG_E(NR_RRC, "itti_alloc_new_message failed, msg_fail_p is NULL \n"); - return (-1); + return -1; } - ngap_pdusession_modify_resp_t *msg = &NGAP_PDUSESSION_MODIFY_RESP(msg_fail_p); - msg->gNB_ue_ngap_id = req->gNB_ue_ngap_id; - msg->nb_of_pdusessions = 0; - - FOR_EACH_SEQ_ARR(rrc_pdu_session_param_t *, session, &UE->pduSessions) { - if (session->status == PDU_SESSION_STATUS_FAILED) { - msg->pdusessions_failed[msg->nb_of_pdusessions_failed].pdusession_id = session->param.pdusession_id; - msg->pdusessions_failed[msg->nb_of_pdusessions_failed].cause.type = session->cause.type; - msg->pdusessions_failed[msg->nb_of_pdusessions_failed].cause.value = session->cause.value; - msg->nb_of_pdusessions_failed++; - } - } - itti_send_msg_to_task(TASK_NGAP, instance, msg_fail_p); + rrc_gNB_send_NGAP_PDUSESSION_MODIFY_RESP(rrc, UE, xid); } return (0); } diff --git a/openair2/RRC/NR/rrc_gNB_NGAP.h b/openair2/RRC/NR/rrc_gNB_NGAP.h index c3f692ff5f..2530b3a803 100644 --- a/openair2/RRC/NR/rrc_gNB_NGAP.h +++ b/openair2/RRC/NR/rrc_gNB_NGAP.h @@ -60,7 +60,7 @@ void rrc_gNB_send_NGAP_PDUSESSION_SETUP_RESP(gNB_RRC_INST *rrc, gNB_RRC_UE_t *UE void rrc_gNB_process_NGAP_PDUSESSION_SETUP_REQ(MessageDef *msg_p, instance_t instance); -int rrc_gNB_process_NGAP_PDUSESSION_MODIFY_REQ(MessageDef *msg_p, instance_t instance); +int rrc_gNB_process_NGAP_PDUSESSION_MODIFY_REQ(const ngap_pdusession_modify_req_t *req, instance_t instance); int rrc_gNB_send_NGAP_PDUSESSION_MODIFY_RESP(gNB_RRC_INST *rrc, gNB_RRC_UE_t *UE, uint8_t xid); From 5ad1f14cb816baa9f86b0496fb0f32e54956b379 Mon Sep 17 00:00:00 2001 From: Guido Casati Date: Tue, 9 Dec 2025 21:52:25 +0100 Subject: [PATCH 047/100] RRC: refactor F1 DRB setup list filling into shared helpers Extract common code from `fill_drb_to_be_setup_from_e1_resp` and `rrc_fill_f1_drb_to_setup` into reusable helper functions to reduce duplication and improve maintainability. This refactoring enables multi-QoS flow handling per DRB and prepares helpers for upcoming PDU Session Modify changes. Impact: - Enables multi-QoS flow support: `rrc_fill_f1_drb_to_setup` now collects all QFIs mapped to a DRB instead of only the first one - Reduces code duplication between E1 response and direct DRB setup code paths - Improves maintainability by centralizing F1 DRB setup logic Major changes: - Add `fill_f1_drb_info_nr`: fills DRB information IE including QoS flows from QFI list - Add `fill_f1_drb_to_be_setup`: creates F1 DRB to setup structure from RRC DRB, PDU session, and QFI list - Add `fill_f1_drbs_from_e1`: processes DRBs from E1 response and extracts QFIs from drb_config->qosFlows - Refactor `fill_drb_to_be_setup_from_e1_resp` to use `fill_f1_drbs_from_e1` - Refactor `rrc_fill_f1_drb_to_setup` to collect all QFIs mapped to each DRB and use `fill_f1_drb_to_be_setup` --- openair2/RRC/NR/nr_rrc_proto.h | 1 + openair2/RRC/NR/rrc_gNB.c | 190 ++++++++++++++++++++------------- 2 files changed, 115 insertions(+), 76 deletions(-) diff --git a/openair2/RRC/NR/nr_rrc_proto.h b/openair2/RRC/NR/nr_rrc_proto.h index 1db6a4d236..28fffd2cda 100644 --- a/openair2/RRC/NR/nr_rrc_proto.h +++ b/openair2/RRC/NR/nr_rrc_proto.h @@ -39,6 +39,7 @@ #include "NR_CG-ConfigInfo.h" #include "NR_RRCReconfiguration.h" #include "RRC/NR/MESSAGES/asn1_msg.h" +#include "f1ap_messages_types.h" #define SRB1 1 #define SRB2 2 diff --git a/openair2/RRC/NR/rrc_gNB.c b/openair2/RRC/NR/rrc_gNB.c index 960f076e1d..db83bb67d2 100644 --- a/openair2/RRC/NR/rrc_gNB.c +++ b/openair2/RRC/NR/rrc_gNB.c @@ -2852,6 +2852,104 @@ f1ap_qos_flow_param_t get_qos_char_from_qos_flow_param(const pdusession_level_qo return qos_char; } +static f1ap_drb_info_nr_t fill_f1_drb_info_nr(pdusession_t *pdu, const uint8_t *qfis, const int num_qfis) +{ + f1ap_drb_info_nr_t drb_info = {0}; + drb_info.nssai = pdu->nssai; + drb_info.flows_len = num_qfis; + drb_info.flows = calloc_or_fail(num_qfis, sizeof(*drb_info.flows)); + for (int i = 0; i < num_qfis; i++) { + int qfi = qfis[i]; + /* find the stored QoS flow parameters for this QFI */ + nr_rrc_qos_t *qos_param = find_qos(&pdu->qos, qfi); + DevAssert(qos_param); + /* QoS flows Flows Mapped to DRB Item */ + f1ap_drb_flows_mapped_t *flow = &drb_info.flows[i]; + flow->qfi = qfi; + flow->param = get_qos_char_from_qos_flow_param(&qos_param->qos); + } + /* DRB QoS IE: we just reuse the ones from the first flow */ + drb_info.drb_qos = drb_info.flows[0].param; + return drb_info; +} + +/** @brief Fill F1 DRB to Be Setup List (UE Context Setup Request) + * @param rrc RRC instance + * @param rrc_drb RRC DRB structure + * @param pdu PDU session parameters + * @param qfis List of QFIs + * @param num_qfis Number of QFIs in list + * @return Filled F1 DRB to setup structure */ +static f1ap_drb_to_setup_t fill_f1_drb_to_be_setup(const gNB_RRC_INST *rrc, + const drb_t *rrc_drb, + rrc_pdu_session_param_t *pdu, + const uint8_t *qfis, + int num_qfis) +{ + DevAssert(rrc); + DevAssert(rrc_drb); + DevAssert(pdu); + DevAssert(qfis); + DevAssert(num_qfis > 0); + DevAssert(num_qfis <= MAX_QOS_FLOWS); + + f1ap_drb_to_setup_t drb = {0}; + + drb.id = rrc_drb->drb_id; + drb.qos_choice = F1AP_QOS_CHOICE_NR; + /* DRB Info (including QoS flows) */ + drb.nr = fill_f1_drb_info_nr(&pdu->param, qfis, num_qfis); + + /* tunnel info */ + if (rrc_drb->cuup_tunnel_config.addr.length != sizeof(in_addr_t)) { + LOG_W(RRC, + "IPv4 address expected (length == %zu), got length %d\n", + sizeof(in_addr_t), + rrc_drb->cuup_tunnel_config.addr.length); + } + memcpy(&drb.up_ul_tnl[0].tl_address, rrc_drb->cuup_tunnel_config.addr.buffer, sizeof(in_addr_t)); + drb.up_ul_tnl[0].teid = rrc_drb->cuup_tunnel_config.teid; + drb.up_ul_tnl_len = 1; + + /* RLC mode */ + drb.rlc_mode = rrc->configuration.um_on_default_drb ? F1AP_RLC_MODE_UM_BIDIR : F1AP_RLC_MODE_AM; + + /* PDCP SN length */ + const nr_pdcp_configuration_t *pdcp = &rrc_drb->pdcp_config; + DevAssert(pdcp->drb.sn_size == 18 || pdcp->drb.sn_size == 12); + drb.dl_pdcp_sn_len = malloc_or_fail(sizeof(*drb.dl_pdcp_sn_len)); + *drb.dl_pdcp_sn_len = pdcp->drb.sn_size == 18 ? F1AP_PDCP_SN_18B : F1AP_PDCP_SN_12B; + drb.ul_pdcp_sn_len = malloc_or_fail(sizeof(*drb.ul_pdcp_sn_len)); + *drb.ul_pdcp_sn_len = pdcp->drb.sn_size == 18 ? F1AP_PDCP_SN_18B : F1AP_PDCP_SN_12B; + + return drb; +} + +/** @brief Helper function to process DRBs from a PDU session's DRB setup list */ +static int fill_f1_drbs_from_e1(const gNB_RRC_INST *rrc, + gNB_RRC_UE_t *UE, + rrc_pdu_session_param_t *pdu, + const DRB_nGRAN_setup_t *drb_list, + const int drb_list_len, + f1ap_drb_to_setup_t drbs[32]) +{ + int nb_drb = 0; + for (int i = 0; i < drb_list_len; i++) { + const DRB_nGRAN_setup_t *drb_config = &drb_list[i]; + drb_t *rrc_drb = get_drb(&UE->drbs, drb_config->id); + DevAssert(rrc_drb); + int nb_qos_flows = drb_config->numQosFlowSetup; + DevAssert(nb_qos_flows <= MAX_QOS_FLOWS); + AssertFatal(nb_qos_flows > 0, "must map at least one flow to a DRB\n"); + uint8_t qfis[MAX_QOS_FLOWS] = {0}; + for (int j = 0; j < nb_qos_flows; j++) + qfis[j] = drb_config->qosFlows[j].qfi; + drbs[nb_drb++] = fill_f1_drb_to_be_setup(rrc, rrc_drb, pdu, qfis, nb_qos_flows); + } + DevAssert(nb_drb < MAX_DRBS_PER_UE); + return nb_drb; +} + /* \brief fills a list of DRBs to be setup from a number of PDU sessions in E1 * bearer setup response */ static int fill_drb_to_be_setup_from_e1_resp(const gNB_RRC_INST *rrc, @@ -2862,56 +2960,12 @@ static int fill_drb_to_be_setup_from_e1_resp(const gNB_RRC_INST *rrc, { int nb_drb = 0; for (int p = 0; p < numPduSession; ++p) { - rrc_pdu_session_param_t *pdu = find_pduSession(&UE->pduSessions, pduSession[p].id); + const pdu_session_setup_t *session = &pduSession[p]; + rrc_pdu_session_param_t *pdu = find_pduSession(&UE->pduSessions, session->id); DevAssert(pdu); - for (int i = 0; i < pduSession[p].numDRBSetup; i++) { - const DRB_nGRAN_setup_t *drb_config = &pduSession[p].DRBnGRanList[i]; - drb_t *rrc_drb = get_drb(&UE->drbs, pduSession[p].DRBnGRanList[i].id); - nr_pdcp_configuration_t pdcp = rrc_drb->pdcp_config; - DevAssert(rrc_drb); - - DevAssert(nb_drb < MAX_DRBS_PER_UE); - f1ap_drb_to_setup_t *drb = &drbs[nb_drb]; - drb->id = rrc_drb->drb_id; - - drb->qos_choice = F1AP_QOS_CHOICE_NR; - - /* pass QoS info to MAC */ - int nb_qos_flows = drb_config->numQosFlowSetup; - AssertFatal(nb_qos_flows > 0, "must map at least one flow to a DRB\n"); - DevAssert(nb_qos_flows <= MAX_QOS_FLOWS); - drb->nr.flows_len = nb_qos_flows; - drb->nr.flows = calloc_or_fail(nb_qos_flows, sizeof(*drb->nr.flows)); - for (int j = 0; j < nb_qos_flows; j++) { - int qfi = drb_config->qosFlows[j].qfi; - /* find the QoS characteristics stored at RRC based on QFI returned - * from E1. We expect this to correspond to a QFI we passed to E1 - * previously, so we expect it to exist. */ - nr_rrc_qos_t *qos_param = find_qos(&pdu->param.qos, qfi); - DevAssert(qos_param); - f1ap_drb_flows_mapped_t *flow = &drb->nr.flows[j]; - flow->qfi = qfi; - flow->param = get_qos_char_from_qos_flow_param(&qos_param->qos); - } - /* the DRB QoS parameters: we just reuse the ones from the first flow */ - drb->nr.drb_qos = drb->nr.flows[0].param; - - /* pass NSSAI info to MAC */ - drb->nr.nssai = pdu->param.nssai; - - drb->up_ul_tnl[0].tl_address = drb_config->UpParamList[0].tl_info.tlAddress; - drb->up_ul_tnl[0].teid = drb_config->UpParamList[0].tl_info.teId; - drb->up_ul_tnl_len = 1; - drb->rlc_mode = rrc->configuration.um_on_default_drb ? F1AP_RLC_MODE_UM_BIDIR : F1AP_RLC_MODE_AM; - DevAssert(pdcp.drb.sn_size == 18 || pdcp.drb.sn_size == 12); - drb->dl_pdcp_sn_len = malloc_or_fail(sizeof(*drb->dl_pdcp_sn_len)); - *drb->dl_pdcp_sn_len = pdcp.drb.sn_size == 18 ? F1AP_PDCP_SN_18B : F1AP_PDCP_SN_12B; - drb->ul_pdcp_sn_len = malloc_or_fail(sizeof(*drb->ul_pdcp_sn_len)); - *drb->ul_pdcp_sn_len = pdcp.drb.sn_size == 18 ? F1AP_PDCP_SN_18B : F1AP_PDCP_SN_12B; - - nb_drb++; - } + nb_drb += fill_f1_drbs_from_e1(rrc, UE, pdu, session->DRBnGRanList, session->numDRBSetup, drbs); } + DevAssert(nb_drb < MAX_DRBS_PER_UE); return nb_drb; } @@ -3555,39 +3609,23 @@ static int rrc_fill_f1_drb_to_setup(const gNB_RRC_INST *rrc, const gNB_RRC_UE_t FOR_EACH_SEQ_ARR(drb_t *, rrc_drb, &ue->drbs) { DevAssert(nb_drb < MAX_DRBS_PER_UE); - f1ap_drb_to_setup_t *drb = &drbs[nb_drb]; - nr_pdcp_configuration_t *pdcp = &rrc_drb->pdcp_config; - nb_drb++; /* fetch an existing PDU session for this DRB */ rrc_pdu_session_param_t *pdu = find_pduSession_from_drbId((gNB_RRC_UE_t *)ue, rrc_drb->drb_id); AssertFatal(pdu != NULL, "no PDU session for DRB ID %d\n", rrc_drb->drb_id); - drb->id = rrc_drb->drb_id; + /* Collect all QFIs mapped to this DRB */ + uint8_t qfis[MAX_QOS_FLOWS] = {0}; + int num_qfis = 0; + FOR_EACH_SEQ_ARR (nr_rrc_qos_t *, qos, &pdu->param.qos) { + if (qos->drb_id == rrc_drb->drb_id) { + DevAssert(num_qfis < MAX_QOS_FLOWS); + qfis[num_qfis++] = qos->qos.qfi; + } + } + AssertFatal(num_qfis > 0, "no QoS flows mapped to DRB ID %d\n", rrc_drb->drb_id); - drb->qos_choice = F1AP_QOS_CHOICE_NR; - drb->nr.nssai = pdu->param.nssai; - drb->nr.flows_len = 1; - drb->nr.flows = calloc_or_fail(1, sizeof(*drb->nr.flows)); - - /* QoS flow associated with this DRB: use first QoS flow */ - AssertFatal(seq_arr_size(&pdu->param.qos) == 1, "only 1 Qos flow supported\n"); - nr_rrc_qos_t *qos_param = (nr_rrc_qos_t *)seq_arr_at(&pdu->param.qos, 0); - DevAssert(qos_param->qos.qfi > 0); - drb->nr.flows[0].qfi = qos_param->qos.qfi; - drb->nr.flows[0].param = get_qos_char_from_qos_flow_param(&qos_param->qos); - /* the DRB QoS parameters: reuse the ones from the first flow */ - drb->nr.drb_qos = drb->nr.flows[0].param; - - memcpy(&drb->up_ul_tnl[0].tl_address, &rrc_drb->cuup_tunnel_config.addr.buffer, sizeof(uint8_t) * 4); - drb->up_ul_tnl[0].teid = rrc_drb->cuup_tunnel_config.teid; - drb->up_ul_tnl_len = 1; - - drb->rlc_mode = rrc->configuration.um_on_default_drb ? F1AP_RLC_MODE_UM_BIDIR : F1AP_RLC_MODE_AM; - DevAssert(pdcp->drb.sn_size == 18 || pdcp->drb.sn_size == 12); - drb->dl_pdcp_sn_len = malloc_or_fail(sizeof(*drb->dl_pdcp_sn_len)); - *drb->dl_pdcp_sn_len = pdcp->drb.sn_size == 18 ? F1AP_PDCP_SN_18B : F1AP_PDCP_SN_12B; - drb->ul_pdcp_sn_len = malloc_or_fail(sizeof(*drb->ul_pdcp_sn_len)); - *drb->ul_pdcp_sn_len = pdcp->drb.sn_size == 18 ? F1AP_PDCP_SN_18B : F1AP_PDCP_SN_12B; + /* Use the common function to fill DRB setup */ + drbs[nb_drb++] = fill_f1_drb_to_be_setup(rrc, rrc_drb, pdu, qfis, num_qfis); } return nb_drb; } From 1ef9812e59ee4a1697ddfcf0ca5639ffb52652e7 Mon Sep 17 00:00:00 2001 From: Jaroslava Fiedlerova Date: Tue, 20 Jan 2026 10:11:48 +0000 Subject: [PATCH 048/100] Add ctests for test_channel_scalability For now, test in the following configuration: - 16 parallel channels - channel length of 32 and 128 - with 61440 and 122880 samples - 12 trials per each test (due to CUDA kernel loading delay in first iterations) - multiple MIMO configurations: 2TX - 2/4/8/32 RX, 2/4/8/32 TX - 2RX --- ci-scripts/cls_oaicitest.py | 2 +- openair1/SIMULATION/tests/CMakeLists.txt | 70 ++++++++++++++++++++++++ 2 files changed, 71 insertions(+), 1 deletion(-) diff --git a/ci-scripts/cls_oaicitest.py b/ci-scripts/cls_oaicitest.py index cf37e591c6..237e795937 100644 --- a/ci-scripts/cls_oaicitest.py +++ b/ci-scripts/cls_oaicitest.py @@ -255,7 +255,7 @@ def Deploy_Physim(ctx, HTML, node, workdir, script, options): logging.debug(f'Running physims on server {node} workdir {workdir}') with cls_cmd.getConnection(node) as c: sys_info = c.exec_script("scripts/sys-info.sh", 5) - ret = c.exec_script(script, 1000, options) + ret = c.exec_script(script, 1500, options) logging.debug(f'"{script}" finished with code {ret.returncode}, output:\n{ret.stdout}') HTML.CreateHtmlTestRowQueue('Query system info', 'OK', [sys_info.stdout]) with cls_cmd.getConnection(node) as ssh: diff --git a/openair1/SIMULATION/tests/CMakeLists.txt b/openair1/SIMULATION/tests/CMakeLists.txt index 0c2ab8dd39..ebeb9f3e54 100644 --- a/openair1/SIMULATION/tests/CMakeLists.txt +++ b/openair1/SIMULATION/tests/CMakeLists.txt @@ -414,6 +414,76 @@ add_physim_test(physim.5g-offload.nr_ulsim.testT2.9 "T2 LDPC offload: SNR = 30, endif() +#################################################################################### +###### cuda-specific tests ###### +#################################################################################### + +if (CUDA_ENABLE) +# --- Test Suite 1: 61440 samples, 2/4/8/32 TX - 2/4/8/32 RX --- +add_physim_test(cuda.5g.channelsim.test_channel_scalability.test1 "16 channels 61440 samples 2T2R Batch" test_channel_scalability -c 16 -t 2 -r 2 -s 61440 -l 32 -m batch -n 12) +add_physim_test(cuda.5g.channelsim.test_channel_scalability.test2 "16 channels 61440 samples 2T2R Stream" test_channel_scalability -c 16 -t 2 -r 2 -s 61440 -l 32 -m stream -n 12) +add_physim_test(cuda.5g.channelsim.test_channel_scalability.test3 "16 channels 61440 samples 2T2R Serial" test_channel_scalability -c 16 -t 2 -r 2 -s 61440 -l 32 -m serial -n 12) +add_physim_test(cuda.5g.channelsim.test_channel_scalability.test4 "16 channels 61440 samples 4T2R Batch" test_channel_scalability -c 16 -t 4 -r 2 -s 61440 -l 32 -m batch -n 12) +add_physim_test(cuda.5g.channelsim.test_channel_scalability.test5 "16 channels 61440 samples 4T2R Stream" test_channel_scalability -c 16 -t 4 -r 2 -s 61440 -l 32 -m stream -n 12) +add_physim_test(cuda.5g.channelsim.test_channel_scalability.test6 "16 channels 61440 samples 4T2R Serial" test_channel_scalability -c 16 -t 4 -r 2 -s 61440 -l 32 -m serial -n 12) +add_physim_test(cuda.5g.channelsim.test_channel_scalability.test7 "16 channels 61440 samples 8T2R Batch" test_channel_scalability -c 16 -t 8 -r 2 -s 61440 -l 32 -m batch -n 12) +add_physim_test(cuda.5g.channelsim.test_channel_scalability.test8 "16 channels 61440 samples 8T2R Stream" test_channel_scalability -c 16 -t 8 -r 2 -s 61440 -l 32 -m stream -n 12) +add_physim_test(cuda.5g.channelsim.test_channel_scalability.test9 "16 channels 61440 samples 8T2R Serial" test_channel_scalability -c 16 -t 8 -r 2 -s 61440 -l 32 -m serial -n 12) +add_physim_test(cuda.5g.channelsim.test_channel_scalability.test10 "16 channels 61440 samples 32T2R Batch" test_channel_scalability -c 16 -t 32 -r 2 -s 61440 -l 32 -m batch -n 12) +add_physim_test(cuda.5g.channelsim.test_channel_scalability.test11 "16 channels 61440 samples 32T2R Stream" test_channel_scalability -c 16 -t 32 -r 2 -s 61440 -l 32 -m stream -n 12) +add_physim_test(cuda.5g.channelsim.test_channel_scalability.test12 "16 channels 61440 samples 32T2R Serial" test_channel_scalability -c 16 -t 32 -r 2 -s 61440 -l 32 -m serial -n 12) +add_physim_test(cuda.5g.channelsim.test_channel_scalability.test13 "16 channels 61440 samples 2T4R Batch" test_channel_scalability -c 16 -t 2 -r 4 -s 61440 -l 32 -m batch -n 12) +add_physim_test(cuda.5g.channelsim.test_channel_scalability.test14 "16 channels 61440 samples 2T4R Stream" test_channel_scalability -c 16 -t 2 -r 4 -s 61440 -l 32 -m stream -n 12) +add_physim_test(cuda.5g.channelsim.test_channel_scalability.test15 "16 channels 61440 samples 2T4R Serial" test_channel_scalability -c 16 -t 2 -r 4 -s 61440 -l 32 -m serial -n 12) +add_physim_test(cuda.5g.channelsim.test_channel_scalability.test16 "16 channels 61440 samples 2T8R Batch" test_channel_scalability -c 16 -t 2 -r 8 -s 61440 -l 32 -m batch -n 12) +add_physim_test(cuda.5g.channelsim.test_channel_scalability.test17 "16 channels 61440 samples 2T8R Stream" test_channel_scalability -c 16 -t 2 -r 8 -s 61440 -l 32 -m stream -n 12) +add_physim_test(cuda.5g.channelsim.test_channel_scalability.test18 "16 channels 61440 samples 2T8R Serial" test_channel_scalability -c 16 -t 2 -r 8 -s 61440 -l 32 -m serial -n 12) +add_physim_test(cuda.5g.channelsim.test_channel_scalability.test19 "16 channels 61440 samples 2T32R Batch" test_channel_scalability -c 16 -t 2 -r 32 -s 61440 -l 32 -m batch -n 12) +add_physim_test(cuda.5g.channelsim.test_channel_scalability.test20 "16 channels 61440 samples 2T32R Stream" test_channel_scalability -c 16 -t 2 -r 32 -s 61440 -l 32 -m stream -n 12) +add_physim_test(cuda.5g.channelsim.test_channel_scalability.test21 "16 channels 61440 samples 2T32R Serial" test_channel_scalability -c 16 -t 2 -r 32 -s 61440 -l 32 -m serial -n 12) + +# --- Test Suite 2: 122880 samples, 2/4/8/32 TX - 2/4/8/32 RX --- +add_physim_test(cuda.5g.channelsim.test_channel_scalability.test22 "16 channels 122880 samples 2T2R Batch" test_channel_scalability -c 16 -t 2 -r 2 -s 122880 -l 32 -m batch -n 12) +add_physim_test(cuda.5g.channelsim.test_channel_scalability.test23 "16 channels 122880 samples 2T2R Stream" test_channel_scalability -c 16 -t 2 -r 2 -s 122880 -l 32 -m stream -n 12) +add_physim_test(cuda.5g.channelsim.test_channel_scalability.test24 "16 channels 122880 samples 2T2R Serial" test_channel_scalability -c 16 -t 2 -r 2 -s 122880 -l 32 -m serial -n 12) +add_physim_test(cuda.5g.channelsim.test_channel_scalability.test25 "16 channels 122880 samples 4T2R Batch" test_channel_scalability -c 16 -t 4 -r 2 -s 122880 -l 32 -m batch -n 12) +add_physim_test(cuda.5g.channelsim.test_channel_scalability.test26 "16 channels 122880 samples 4T2R Stream" test_channel_scalability -c 16 -t 4 -r 2 -s 122880 -l 32 -m stream -n 12) +add_physim_test(cuda.5g.channelsim.test_channel_scalability.test27 "16 channels 122880 samples 4T2R Serial" test_channel_scalability -c 16 -t 4 -r 2 -s 122880 -l 32 -m serial -n 12) +add_physim_test(cuda.5g.channelsim.test_channel_scalability.test28 "16 channels 122880 samples 8T2R Batch" test_channel_scalability -c 16 -t 8 -r 2 -s 122880 -l 32 -m batch -n 12) +add_physim_test(cuda.5g.channelsim.test_channel_scalability.test29 "16 channels 122880 samples 8T2R Stream" test_channel_scalability -c 16 -t 8 -r 2 -s 122880 -l 32 -m stream -n 12) +add_physim_test(cuda.5g.channelsim.test_channel_scalability.test30 "16 channels 122880 samples 8T2R Serial" test_channel_scalability -c 16 -t 8 -r 2 -s 122880 -l 32 -m serial -n 12) +add_physim_test(cuda.5g.channelsim.test_channel_scalability.test31 "16 channels 122880 samples 32T2R Batch" test_channel_scalability -c 16 -t 32 -r 2 -s 122880 -l 32 -m batch -n 12) +add_physim_test(cuda.5g.channelsim.test_channel_scalability.test32 "16 channels 122880 samples 32T2R Stream" test_channel_scalability -c 16 -t 32 -r 2 -s 122880 -l 32 -m stream -n 12) +add_physim_test(cuda.5g.channelsim.test_channel_scalability.test33 "16 channels 122880 samples 32T2R Serial" test_channel_scalability -c 16 -t 32 -r 2 -s 122880 -l 32 -m serial -n 12) +add_physim_test(cuda.5g.channelsim.test_channel_scalability.test34 "16 channels 122880 samples 2T4R Batch" test_channel_scalability -c 16 -t 2 -r 4 -s 122880 -l 32 -m batch -n 12) +add_physim_test(cuda.5g.channelsim.test_channel_scalability.test35 "16 channels 122880 samples 2T4R Stream" test_channel_scalability -c 16 -t 2 -r 4 -s 122880 -l 32 -m stream -n 12) +add_physim_test(cuda.5g.channelsim.test_channel_scalability.test36 "16 channels 122880 samples 2T4R Serial" test_channel_scalability -c 16 -t 2 -r 4 -s 122880 -l 32 -m serial -n 12) +add_physim_test(cuda.5g.channelsim.test_channel_scalability.test37 "16 channels 122880 samples 2T8R Batch" test_channel_scalability -c 16 -t 2 -r 8 -s 122880 -l 32 -m batch -n 12) +add_physim_test(cuda.5g.channelsim.test_channel_scalability.test38 "16 channels 122880 samples 2T8R Stream" test_channel_scalability -c 16 -t 2 -r 8 -s 122880 -l 32 -m stream -n 12) +add_physim_test(cuda.5g.channelsim.test_channel_scalability.test39 "16 channels 122880 samples 2T8R Serial" test_channel_scalability -c 16 -t 2 -r 8 -s 122880 -l 32 -m serial -n 12) +add_physim_test(cuda.5g.channelsim.test_channel_scalability.test40 "16 channels 122880 samples 2T32R Batch" test_channel_scalability -c 16 -t 2 -r 32 -s 122880 -l 32 -m batch -n 12) +add_physim_test(cuda.5g.channelsim.test_channel_scalability.test41 "16 channels 122880 samples 2T32R Stream" test_channel_scalability -c 16 -t 2 -r 32 -s 122880 -l 32 -m stream -n 12) +add_physim_test(cuda.5g.channelsim.test_channel_scalability.test42 "16 channels 122880 samples 2T32R Serial" test_channel_scalability -c 16 -t 2 -r 32 -s 122880 -l 32 -m serial -n 12) + +# --- Test Suite 3: 61440 samples, 128 channel length, 2/4/8/32 TX - 2/4/8/32 RX -- +add_physim_test(cuda.5g.channelsim.test_channel_scalability.test43 "16 channels 61440 samples long channel 2T2R Batch" test_channel_scalability -c 16 -t 2 -r 2 -s 61440 -l 128 -m batch -n 12) +add_physim_test(cuda.5g.channelsim.test_channel_scalability.test44 "16 channels 61440 samples long channel 2T2R Stream" test_channel_scalability -c 16 -t 2 -r 2 -s 61440 -l 128 -m stream -n 12) +add_physim_test(cuda.5g.channelsim.test_channel_scalability.test45 "16 channels 61440 samples long channel 2T2R Serial" test_channel_scalability -c 16 -t 2 -r 2 -s 61440 -l 128 -m serial -n 12) +add_physim_test(cuda.5g.channelsim.test_channel_scalability.test46 "16 channels 61440 samples long channel 4T2R Batch" test_channel_scalability -c 16 -t 4 -r 2 -s 61440 -l 128 -m batch -n 12) +add_physim_test(cuda.5g.channelsim.test_channel_scalability.test47 "16 channels 61440 samples long channel 4T2R Stream" test_channel_scalability -c 16 -t 4 -r 2 -s 61440 -l 128 -m stream -n 12) +add_physim_test(cuda.5g.channelsim.test_channel_scalability.test48 "16 channels 61440 samples long channel 4T2R Serial" test_channel_scalability -c 16 -t 4 -r 2 -s 61440 -l 128 -m serial -n 12) +add_physim_test(cuda.5g.channelsim.test_channel_scalability.test49 "16 channels 61440 samples long channel 8T2R Batch" test_channel_scalability -c 16 -t 8 -r 2 -s 61440 -l 128 -m batch -n 12) +add_physim_test(cuda.5g.channelsim.test_channel_scalability.test50 "16 channels 61440 samples long channel 8T2R Stream" test_channel_scalability -c 16 -t 8 -r 2 -s 61440 -l 128 -m stream -n 12) +add_physim_test(cuda.5g.channelsim.test_channel_scalability.test51 "16 channels 61440 samples long channel 8T2R Serial" test_channel_scalability -c 16 -t 8 -r 2 -s 61440 -l 128 -m serial -n 12) +add_physim_test(cuda.5g.channelsim.test_channel_scalability.test52 "16 channels 61440 samples long channel 2T4R Batch" test_channel_scalability -c 16 -t 2 -r 4 -s 61440 -l 128 -m batch -n 12) +add_physim_test(cuda.5g.channelsim.test_channel_scalability.test53 "16 channels 61440 samples long channel 2T4R Stream" test_channel_scalability -c 16 -t 2 -r 4 -s 61440 -l 128 -m stream -n 12) +add_physim_test(cuda.5g.channelsim.test_channel_scalability.test54 "16 channels 61440 samples long channel 2T4R Serial" test_channel_scalability -c 16 -t 2 -r 4 -s 61440 -l 128 -m serial -n 12) +add_physim_test(cuda.5g.channelsim.test_channel_scalability.test55 "16 channels 61440 samples long channel 2T8R Batch" test_channel_scalability -c 16 -t 2 -r 8 -s 61440 -l 128 -m batch -n 12) +add_physim_test(cuda.5g.channelsim.test_channel_scalability.test56 "16 channels 61440 samples long channel 2T8R Stream" test_channel_scalability -c 16 -t 2 -r 8 -s 61440 -l 128 -m stream -n 12) +add_physim_test(cuda.5g.channelsim.test_channel_scalability.test57 "16 channels 61440 samples long channel 2T8R Serial" test_channel_scalability -c 16 -t 2 -r 8 -s 61440 -l 128 -m serial -n 12) + +endif() + if(PHYSIM_CHECK_FILES) foreach(PHYSIM_CHECK_FILE IN LISTS PHYSIM_CHECK_FILES) include("${CMAKE_CURRENT_SOURCE_DIR}/${PHYSIM_CHECK_FILE}") From 4d7ed951794b74b62842fec396ce03c0203f4a47 Mon Sep 17 00:00:00 2001 From: Jaroslava Fiedlerova Date: Tue, 20 Jan 2026 10:16:47 +0000 Subject: [PATCH 049/100] CI: Add cmake file for CUDA channel simulation ctest threshold check --- .../SIMULATION/tests/ThresholdsCuda.cmake | 296 ++++++++++++++++++ 1 file changed, 296 insertions(+) create mode 100644 openair1/SIMULATION/tests/ThresholdsCuda.cmake diff --git a/openair1/SIMULATION/tests/ThresholdsCuda.cmake b/openair1/SIMULATION/tests/ThresholdsCuda.cmake new file mode 100644 index 0000000000..9f4aab802f --- /dev/null +++ b/openair1/SIMULATION/tests/ThresholdsCuda.cmake @@ -0,0 +1,296 @@ +# ============================================================ +# Test Suite 1: 61440 samples, short channel (l=32) +# ============================================================ + +check_threshold(cuda.5g.channelsim.test_channel_scalability.test1 "Total CPU Time" "< 142200") +check_threshold(cuda.5g.channelsim.test_channel_scalability.test1 "Total GPU Time" "< 5700") +check_threshold_range(cuda.5g.channelsim.test_channel_scalability.test1 "Avg Time per Channel - CPU" LOWER 100 UPPER 8900) +check_threshold_range(cuda.5g.channelsim.test_channel_scalability.test1 "Avg Time per Channel - GPU" LOWER 100 UPPER 400) + +check_threshold(cuda.5g.channelsim.test_channel_scalability.test2 "Total CPU Time" "< 142200") +check_threshold(cuda.5g.channelsim.test_channel_scalability.test2 "Total GPU Time" "< 2700") +check_threshold_range(cuda.5g.channelsim.test_channel_scalability.test2 "Avg Time per Channel - CPU" LOWER 100 UPPER 8900) +check_threshold_range(cuda.5g.channelsim.test_channel_scalability.test2 "Avg Time per Channel - GPU" LOWER 100 UPPER 200) + +check_threshold(cuda.5g.channelsim.test_channel_scalability.test3 "Total CPU Time" "< 142200") +check_threshold(cuda.5g.channelsim.test_channel_scalability.test3 "Total GPU Time" "< 5300") +check_threshold_range(cuda.5g.channelsim.test_channel_scalability.test3 "Avg Time per Channel - CPU" LOWER 100 UPPER 8900) +check_threshold_range(cuda.5g.channelsim.test_channel_scalability.test3 "Avg Time per Channel - GPU" LOWER 100 UPPER 300) + +check_threshold(cuda.5g.channelsim.test_channel_scalability.test4 "Total CPU Time" "< 268000") +check_threshold(cuda.5g.channelsim.test_channel_scalability.test4 "Total GPU Time" "< 12000") +check_threshold_range(cuda.5g.channelsim.test_channel_scalability.test4 "Avg Time per Channel - CPU" LOWER 100 UPPER 16700) +check_threshold_range(cuda.5g.channelsim.test_channel_scalability.test4 "Avg Time per Channel - GPU" LOWER 100 UPPER 800) + +check_threshold(cuda.5g.channelsim.test_channel_scalability.test5 "Total CPU Time" "< 268500") +check_threshold(cuda.5g.channelsim.test_channel_scalability.test5 "Total GPU Time" "< 6400") +check_threshold_range(cuda.5g.channelsim.test_channel_scalability.test5 "Avg Time per Channel - CPU" LOWER 100 UPPER 16700) +check_threshold_range(cuda.5g.channelsim.test_channel_scalability.test5 "Avg Time per Channel - GPU" LOWER 100 UPPER 400) + +check_threshold(cuda.5g.channelsim.test_channel_scalability.test6 "Total CPU Time" "< 268200") +check_threshold(cuda.5g.channelsim.test_channel_scalability.test6 "Total GPU Time" "< 9200") +check_threshold_range(cuda.5g.channelsim.test_channel_scalability.test6 "Avg Time per Channel - CPU" LOWER 100 UPPER 16700) +check_threshold_range(cuda.5g.channelsim.test_channel_scalability.test6 "Avg Time per Channel - GPU" LOWER 100 UPPER 600) + +check_threshold(cuda.5g.channelsim.test_channel_scalability.test7 "Total CPU Time" "< 522200") +check_threshold(cuda.5g.channelsim.test_channel_scalability.test7 "Total GPU Time" "< 21800") +check_threshold_range(cuda.5g.channelsim.test_channel_scalability.test7 "Avg Time per Channel - CPU" LOWER 100 UPPER 32600) +check_threshold_range(cuda.5g.channelsim.test_channel_scalability.test7 "Avg Time per Channel - GPU" LOWER 100 UPPER 1400) + +check_threshold(cuda.5g.channelsim.test_channel_scalability.test8 "Total CPU Time" "< 522200") +check_threshold(cuda.5g.channelsim.test_channel_scalability.test8 "Total GPU Time" "< 17400") +check_threshold_range(cuda.5g.channelsim.test_channel_scalability.test8 "Avg Time per Channel - CPU" LOWER 100 UPPER 32600) +check_threshold_range(cuda.5g.channelsim.test_channel_scalability.test8 "Avg Time per Channel - GPU" LOWER 100 UPPER 1100) + +check_threshold(cuda.5g.channelsim.test_channel_scalability.test9 "Total CPU Time" "< 522200") +check_threshold(cuda.5g.channelsim.test_channel_scalability.test9 "Total GPU Time" "< 25300") +check_threshold_range(cuda.5g.channelsim.test_channel_scalability.test9 "Avg Time per Channel - CPU" LOWER 100 UPPER 32600) +check_threshold_range(cuda.5g.channelsim.test_channel_scalability.test9 "Avg Time per Channel - GPU" LOWER 100 UPPER 1600) + +check_threshold(cuda.5g.channelsim.test_channel_scalability.test10 "Total CPU Time" "< 2065800") +check_threshold(cuda.5g.channelsim.test_channel_scalability.test10 "Total GPU Time" "< 79400") +check_threshold_range(cuda.5g.channelsim.test_channel_scalability.test10 "Avg Time per Channel - CPU" LOWER 100 UPPER 129100) +check_threshold_range(cuda.5g.channelsim.test_channel_scalability.test10 "Avg Time per Channel - GPU" LOWER 100 UPPER 5000) + +check_threshold(cuda.5g.channelsim.test_channel_scalability.test11 "Total CPU Time" "< 2069700") +check_threshold(cuda.5g.channelsim.test_channel_scalability.test11 "Total GPU Time" "< 66800") +check_threshold_range(cuda.5g.channelsim.test_channel_scalability.test11 "Avg Time per Channel - CPU" LOWER 100 UPPER 129400) +check_threshold_range(cuda.5g.channelsim.test_channel_scalability.test11 "Avg Time per Channel - GPU" LOWER 100 UPPER 4200) + +check_threshold(cuda.5g.channelsim.test_channel_scalability.test12 "Total CPU Time" "< 2066500") +check_threshold(cuda.5g.channelsim.test_channel_scalability.test12 "Total GPU Time" "< 86800") +check_threshold_range(cuda.5g.channelsim.test_channel_scalability.test12 "Avg Time per Channel - CPU" LOWER 100 UPPER 129200) +check_threshold_range(cuda.5g.channelsim.test_channel_scalability.test12 "Avg Time per Channel - GPU" LOWER 100 UPPER 5400) + +check_threshold(cuda.5g.channelsim.test_channel_scalability.test13 "Total CPU Time" "< 283700") +check_threshold(cuda.5g.channelsim.test_channel_scalability.test13 "Total GPU Time" "< 6600") +check_threshold_range(cuda.5g.channelsim.test_channel_scalability.test13 "Avg Time per Channel - CPU" LOWER 100 UPPER 17700) +check_threshold_range(cuda.5g.channelsim.test_channel_scalability.test13 "Avg Time per Channel - GPU" LOWER 100 UPPER 400) + +check_threshold(cuda.5g.channelsim.test_channel_scalability.test14 "Total CPU Time" "< 283800") +check_threshold(cuda.5g.channelsim.test_channel_scalability.test14 "Total GPU Time" "< 3000") +check_threshold_range(cuda.5g.channelsim.test_channel_scalability.test14 "Avg Time per Channel - CPU" LOWER 100 UPPER 17700) +check_threshold_range(cuda.5g.channelsim.test_channel_scalability.test14 "Avg Time per Channel - GPU" LOWER 100 UPPER 200) + +check_threshold(cuda.5g.channelsim.test_channel_scalability.test15 "Total CPU Time" "< 283800") +check_threshold(cuda.5g.channelsim.test_channel_scalability.test15 "Total GPU Time" "< 8700") +check_threshold_range(cuda.5g.channelsim.test_channel_scalability.test15 "Avg Time per Channel - CPU" LOWER 100 UPPER 17700) +check_threshold_range(cuda.5g.channelsim.test_channel_scalability.test15 "Avg Time per Channel - GPU" LOWER 100 UPPER 500) + +check_threshold(cuda.5g.channelsim.test_channel_scalability.test16 "Total CPU Time" "< 566900") +check_threshold(cuda.5g.channelsim.test_channel_scalability.test16 "Total GPU Time" "< 7080") +check_threshold_range(cuda.5g.channelsim.test_channel_scalability.test16 "Avg Time per Channel - CPU" LOWER 100 UPPER 35400) +check_threshold_range(cuda.5g.channelsim.test_channel_scalability.test16 "Avg Time per Channel - GPU" LOWER 100 UPPER 400) + +check_threshold(cuda.5g.channelsim.test_channel_scalability.test17 "Total CPU Time" "< 566800") +check_threshold(cuda.5g.channelsim.test_channel_scalability.test17 "Total GPU Time" "< 4200") +check_threshold_range(cuda.5g.channelsim.test_channel_scalability.test17 "Avg Time per Channel - CPU" LOWER 100 UPPER 35400) +check_threshold_range(cuda.5g.channelsim.test_channel_scalability.test17 "Avg Time per Channel - GPU" LOWER 100 UPPER 300) + +check_threshold(cuda.5g.channelsim.test_channel_scalability.test18 "Total CPU Time" "< 566500") +check_threshold(cuda.5g.channelsim.test_channel_scalability.test18 "Total GPU Time" "< 16740") +check_threshold_range(cuda.5g.channelsim.test_channel_scalability.test18 "Avg Time per Channel - CPU" LOWER 100 UPPER 35400) +check_threshold_range(cuda.5g.channelsim.test_channel_scalability.test18 "Avg Time per Channel - GPU" LOWER 100 UPPER 1040) + +check_threshold(cuda.5g.channelsim.test_channel_scalability.test19 "Total CPU Time" "< 2280200") +check_threshold(cuda.5g.channelsim.test_channel_scalability.test19 "Total GPU Time" "< 10390") +check_threshold_range(cuda.5g.channelsim.test_channel_scalability.test19 "Avg Time per Channel - CPU" LOWER 100 UPPER 142500) +check_threshold_range(cuda.5g.channelsim.test_channel_scalability.test19 "Avg Time per Channel - GPU" LOWER 100 UPPER 650) + +check_threshold(cuda.5g.channelsim.test_channel_scalability.test20 "Total CPU Time" "< 2279300") +check_threshold(cuda.5g.channelsim.test_channel_scalability.test20 "Total GPU Time" "< 5760") +check_threshold_range(cuda.5g.channelsim.test_channel_scalability.test20 "Avg Time per Channel - CPU" LOWER 100 UPPER 142500) +check_threshold_range(cuda.5g.channelsim.test_channel_scalability.test20 "Avg Time per Channel - GPU" LOWER 100 UPPER 400) + +check_threshold(cuda.5g.channelsim.test_channel_scalability.test21 "Total CPU Time" "< 2282200") +check_threshold(cuda.5g.channelsim.test_channel_scalability.test21 "Total GPU Time" "< 38900") +check_threshold_range(cuda.5g.channelsim.test_channel_scalability.test21 "Avg Time per Channel - CPU" LOWER 100 UPPER 142600) +check_threshold_range(cuda.5g.channelsim.test_channel_scalability.test21 "Avg Time per Channel - GPU" LOWER 100 UPPER 2400) + +# ============================================================ +# Test Suite 2: 122880 samples, short channel (l=32) +# ============================================================ + +check_threshold(cuda.5g.channelsim.test_channel_scalability.test22 "Total CPU Time" "< 283400") +check_threshold(cuda.5g.channelsim.test_channel_scalability.test22 "Total GPU Time" "< 10130") +check_threshold_range(cuda.5g.channelsim.test_channel_scalability.test22 "Avg Time per Channel - CPU" LOWER 100 UPPER 17700) +check_threshold_range(cuda.5g.channelsim.test_channel_scalability.test22 "Avg Time per Channel - GPU" LOWER 100 UPPER 600) + +check_threshold(cuda.5g.channelsim.test_channel_scalability.test23 "Total CPU Time" "< 283700") +check_threshold(cuda.5g.channelsim.test_channel_scalability.test23 "Total GPU Time" "< 6300") +check_threshold_range(cuda.5g.channelsim.test_channel_scalability.test23 "Avg Time per Channel - CPU" LOWER 100 UPPER 17700) +check_threshold_range(cuda.5g.channelsim.test_channel_scalability.test23 "Avg Time per Channel - GPU" LOWER 100 UPPER 400) + +check_threshold(cuda.5g.channelsim.test_channel_scalability.test24 "Total CPU Time" "< 284000") +check_threshold(cuda.5g.channelsim.test_channel_scalability.test24 "Total GPU Time" "< 12490") +check_threshold_range(cuda.5g.channelsim.test_channel_scalability.test24 "Avg Time per Channel - CPU" LOWER 100 UPPER 17700) +check_threshold_range(cuda.5g.channelsim.test_channel_scalability.test24 "Avg Time per Channel - GPU" LOWER 100 UPPER 800) + +check_threshold(cuda.5g.channelsim.test_channel_scalability.test25 "Total CPU Time" "< 535500") +check_threshold(cuda.5g.channelsim.test_channel_scalability.test25 "Total GPU Time" "< 24940") +check_threshold_range(cuda.5g.channelsim.test_channel_scalability.test25 "Avg Time per Channel - CPU" LOWER 100 UPPER 33400) +check_threshold_range(cuda.5g.channelsim.test_channel_scalability.test25 "Avg Time per Channel - GPU" LOWER 100 UPPER 1560) + +check_threshold(cuda.5g.channelsim.test_channel_scalability.test26 "Total CPU Time" "< 547200") +check_threshold(cuda.5g.channelsim.test_channel_scalability.test26 "Total GPU Time" "< 15600") +check_threshold_range(cuda.5g.channelsim.test_channel_scalability.test26 "Avg Time per Channel - CPU" LOWER 100 UPPER 34200) +check_threshold_range(cuda.5g.channelsim.test_channel_scalability.test26 "Avg Time per Channel - GPU" LOWER 100 UPPER 1000) + +check_threshold(cuda.5g.channelsim.test_channel_scalability.test27 "Total CPU Time" "< 536000") +check_threshold(cuda.5g.channelsim.test_channel_scalability.test27 "Total GPU Time" "< 29030") +check_threshold_range(cuda.5g.channelsim.test_channel_scalability.test27 "Avg Time per Channel - CPU" LOWER 100 UPPER 33500) +check_threshold_range(cuda.5g.channelsim.test_channel_scalability.test27 "Avg Time per Channel - GPU" LOWER 100 UPPER 1800) + +check_threshold(cuda.5g.channelsim.test_channel_scalability.test28 "Total CPU Time" "< 1044020") +check_threshold(cuda.5g.channelsim.test_channel_scalability.test28 "Total GPU Time" "< 52100") +check_threshold_range(cuda.5g.channelsim.test_channel_scalability.test28 "Avg Time per Channel - CPU" LOWER 100 UPPER 65200) +check_threshold_range(cuda.5g.channelsim.test_channel_scalability.test28 "Avg Time per Channel - GPU" LOWER 100 UPPER 3260) + +check_threshold(cuda.5g.channelsim.test_channel_scalability.test29 "Total CPU Time" "< 1043540") +check_threshold(cuda.5g.channelsim.test_channel_scalability.test29 "Total GPU Time" "< 38890") +check_threshold_range(cuda.5g.channelsim.test_channel_scalability.test29 "Avg Time per Channel - CPU" LOWER 100 UPPER 65200) +check_threshold_range(cuda.5g.channelsim.test_channel_scalability.test29 "Avg Time per Channel - GPU" LOWER 100 UPPER 2430) + +check_threshold(cuda.5g.channelsim.test_channel_scalability.test30 "Total CPU Time" "< 1043740") +check_threshold(cuda.5g.channelsim.test_channel_scalability.test30 "Total GPU Time" "< 43320") +check_threshold_range(cuda.5g.channelsim.test_channel_scalability.test30 "Avg Time per Channel - CPU" LOWER 100 UPPER 65200) +check_threshold_range(cuda.5g.channelsim.test_channel_scalability.test30 "Avg Time per Channel - GPU" LOWER 100 UPPER 2710) + +check_threshold(cuda.5g.channelsim.test_channel_scalability.test31 "Total CPU Time" "< 4131800") +check_threshold(cuda.5g.channelsim.test_channel_scalability.test31 "Total GPU Time" "< 211840") +check_threshold_range(cuda.5g.channelsim.test_channel_scalability.test31 "Avg Time per Channel - CPU" LOWER 100 UPPER 258200) +check_threshold_range(cuda.5g.channelsim.test_channel_scalability.test31 "Avg Time per Channel - GPU" LOWER 100 UPPER 13240) + +check_threshold(cuda.5g.channelsim.test_channel_scalability.test32 "Total CPU Time" "< 4136320") +check_threshold(cuda.5g.channelsim.test_channel_scalability.test32 "Total GPU Time" "< 110880") +check_threshold_range(cuda.5g.channelsim.test_channel_scalability.test32 "Avg Time per Channel - CPU" LOWER 100 UPPER 258520) +check_threshold_range(cuda.5g.channelsim.test_channel_scalability.test32 "Avg Time per Channel - GPU" LOWER 100 UPPER 6930) + +check_threshold(cuda.5g.channelsim.test_channel_scalability.test33 "Total CPU Time" "< 4134310") +check_threshold(cuda.5g.channelsim.test_channel_scalability.test33 "Total GPU Time" "< 150710") +check_threshold_range(cuda.5g.channelsim.test_channel_scalability.test33 "Avg Time per Channel - CPU" LOWER 100 UPPER 258400) +check_threshold_range(cuda.5g.channelsim.test_channel_scalability.test33 "Avg Time per Channel - GPU" LOWER 100 UPPER 9420) + +# ============================================================ +# Test Suite 2 & 3 continued +# ============================================================ + +check_threshold(cuda.5g.channelsim.test_channel_scalability.test34 "Total CPU Time" "< 569000") +check_threshold(cuda.5g.channelsim.test_channel_scalability.test34 "Total GPU Time" "< 10460") +check_threshold_range(cuda.5g.channelsim.test_channel_scalability.test34 "Avg Time per Channel - CPU" LOWER 100 UPPER 35600) +check_threshold_range(cuda.5g.channelsim.test_channel_scalability.test34 "Avg Time per Channel - GPU" LOWER 100 UPPER 654) + +check_threshold(cuda.5g.channelsim.test_channel_scalability.test35 "Total CPU Time" "< 568900") +check_threshold(cuda.5g.channelsim.test_channel_scalability.test35 "Total GPU Time" "< 6890") +check_threshold_range(cuda.5g.channelsim.test_channel_scalability.test35 "Avg Time per Channel - CPU" LOWER 100 UPPER 35600) +check_threshold_range(cuda.5g.channelsim.test_channel_scalability.test35 "Avg Time per Channel - GPU" LOWER 100 UPPER 400) + +check_threshold(cuda.5g.channelsim.test_channel_scalability.test36 "Total CPU Time" "< 569100") +check_threshold(cuda.5g.channelsim.test_channel_scalability.test36 "Total GPU Time" "< 20770") +check_threshold_range(cuda.5g.channelsim.test_channel_scalability.test36 "Avg Time per Channel - CPU" LOWER 100 UPPER 35600) +check_threshold_range(cuda.5g.channelsim.test_channel_scalability.test36 "Avg Time per Channel - GPU" LOWER 100 UPPER 1300) + +check_threshold(cuda.5g.channelsim.test_channel_scalability.test37 "Total CPU Time" "< 1137170") +check_threshold(cuda.5g.channelsim.test_channel_scalability.test37 "Total GPU Time" "< 11250") +check_threshold_range(cuda.5g.channelsim.test_channel_scalability.test37 "Avg Time per Channel - CPU" LOWER 100 UPPER 71100) +check_threshold_range(cuda.5g.channelsim.test_channel_scalability.test37 "Avg Time per Channel - GPU" LOWER 100 UPPER 700) + +check_threshold(cuda.5g.channelsim.test_channel_scalability.test38 "Total CPU Time" "< 1136670") +check_threshold(cuda.5g.channelsim.test_channel_scalability.test38 "Total GPU Time" "< 7050") +check_threshold_range(cuda.5g.channelsim.test_channel_scalability.test38 "Avg Time per Channel - CPU" LOWER 100 UPPER 71000) +check_threshold_range(cuda.5g.channelsim.test_channel_scalability.test38 "Avg Time per Channel - GPU" LOWER 100 UPPER 400) + +check_threshold(cuda.5g.channelsim.test_channel_scalability.test39 "Total CPU Time" "< 1139410") +check_threshold(cuda.5g.channelsim.test_channel_scalability.test39 "Total GPU Time" "< 30100") +check_threshold_range(cuda.5g.channelsim.test_channel_scalability.test39 "Avg Time per Channel - CPU" LOWER 100 UPPER 71200) +check_threshold_range(cuda.5g.channelsim.test_channel_scalability.test39 "Avg Time per Channel - GPU" LOWER 100 UPPER 1900) + +check_threshold(cuda.5g.channelsim.test_channel_scalability.test40 "Total CPU Time" "< 4632760") +check_threshold(cuda.5g.channelsim.test_channel_scalability.test40 "Total GPU Time" "< 18070") +check_threshold_range(cuda.5g.channelsim.test_channel_scalability.test40 "Avg Time per Channel - CPU" LOWER 100 UPPER 289500) +check_threshold_range(cuda.5g.channelsim.test_channel_scalability.test40 "Avg Time per Channel - GPU" LOWER 100 UPPER 1130) + +check_threshold(cuda.5g.channelsim.test_channel_scalability.test41 "Total CPU Time" "< 4656730") +check_threshold(cuda.5g.channelsim.test_channel_scalability.test41 "Total GPU Time" "< 10540") +check_threshold_range(cuda.5g.channelsim.test_channel_scalability.test41 "Avg Time per Channel - CPU" LOWER 100 UPPER 291000) +check_threshold_range(cuda.5g.channelsim.test_channel_scalability.test41 "Avg Time per Channel - GPU" LOWER 100 UPPER 700) + +check_threshold(cuda.5g.channelsim.test_channel_scalability.test42 "Total CPU Time" "< 4658160") +check_threshold(cuda.5g.channelsim.test_channel_scalability.test42 "Total GPU Time" "< 72580") +check_threshold_range(cuda.5g.channelsim.test_channel_scalability.test42 "Avg Time per Channel - CPU" LOWER 100 UPPER 291100) +check_threshold_range(cuda.5g.channelsim.test_channel_scalability.test42 "Avg Time per Channel - GPU" LOWER 100 UPPER 4540) + +check_threshold(cuda.5g.channelsim.test_channel_scalability.test43 "Total CPU Time" "< 527200") +check_threshold(cuda.5g.channelsim.test_channel_scalability.test43 "Total GPU Time" "< 5280") +check_threshold_range(cuda.5g.channelsim.test_channel_scalability.test43 "Avg Time per Channel - CPU" LOWER 100 UPPER 33000) +check_threshold_range(cuda.5g.channelsim.test_channel_scalability.test43 "Avg Time per Channel - GPU" LOWER 100 UPPER 330) + +check_threshold(cuda.5g.channelsim.test_channel_scalability.test44 "Total CPU Time" "< 526800") +check_threshold(cuda.5g.channelsim.test_channel_scalability.test44 "Total GPU Time" "< 3020") +check_threshold_range(cuda.5g.channelsim.test_channel_scalability.test44 "Avg Time per Channel - CPU" LOWER 100 UPPER 32900) +check_threshold_range(cuda.5g.channelsim.test_channel_scalability.test44 "Avg Time per Channel - GPU" LOWER 100 UPPER 200) + +check_threshold(cuda.5g.channelsim.test_channel_scalability.test45 "Total CPU Time" "< 527100") +check_threshold(cuda.5g.channelsim.test_channel_scalability.test45 "Total GPU Time" "< 5660") +check_threshold_range(cuda.5g.channelsim.test_channel_scalability.test45 "Avg Time per Channel - CPU" LOWER 100 UPPER 32900) +check_threshold_range(cuda.5g.channelsim.test_channel_scalability.test45 "Avg Time per Channel - GPU" LOWER 100 UPPER 400) + +check_threshold(cuda.5g.channelsim.test_channel_scalability.test46 "Total CPU Time" "< 1039350") +check_threshold(cuda.5g.channelsim.test_channel_scalability.test46 "Total GPU Time" "< 10230") +check_threshold_range(cuda.5g.channelsim.test_channel_scalability.test46 "Avg Time per Channel - CPU" LOWER 100 UPPER 65000) +check_threshold_range(cuda.5g.channelsim.test_channel_scalability.test46 "Avg Time per Channel - GPU" LOWER 100 UPPER 640) + +check_threshold(cuda.5g.channelsim.test_channel_scalability.test47 "Total CPU Time" "< 1039770") +check_threshold(cuda.5g.channelsim.test_channel_scalability.test47 "Total GPU Time" "< 6780") +check_threshold_range(cuda.5g.channelsim.test_channel_scalability.test47 "Avg Time per Channel - CPU" LOWER 100 UPPER 65000) +check_threshold_range(cuda.5g.channelsim.test_channel_scalability.test47 "Avg Time per Channel - GPU" LOWER 100 UPPER 400) + +check_threshold(cuda.5g.channelsim.test_channel_scalability.test48 "Total CPU Time" "< 1039390") +check_threshold(cuda.5g.channelsim.test_channel_scalability.test48 "Total GPU Time" "< 12420") +check_threshold_range(cuda.5g.channelsim.test_channel_scalability.test48 "Avg Time per Channel - CPU" LOWER 100 UPPER 65000) +check_threshold_range(cuda.5g.channelsim.test_channel_scalability.test48 "Avg Time per Channel - GPU" LOWER 100 UPPER 800) + +check_threshold(cuda.5g.channelsim.test_channel_scalability.test49 "Total CPU Time" "< 2042770") +check_threshold(cuda.5g.channelsim.test_channel_scalability.test49 "Total GPU Time" "< 19430") +check_threshold_range(cuda.5g.channelsim.test_channel_scalability.test49 "Avg Time per Channel - CPU" LOWER 100 UPPER 127700) +check_threshold_range(cuda.5g.channelsim.test_channel_scalability.test49 "Avg Time per Channel - GPU" LOWER 100 UPPER 1200) + +check_threshold(cuda.5g.channelsim.test_channel_scalability.test50 "Total CPU Time" "< 2040890") +check_threshold(cuda.5g.channelsim.test_channel_scalability.test50 "Total GPU Time" "< 17950") +check_threshold_range(cuda.5g.channelsim.test_channel_scalability.test50 "Avg Time per Channel - CPU" LOWER 100 UPPER 127600) +check_threshold_range(cuda.5g.channelsim.test_channel_scalability.test50 "Avg Time per Channel - GPU" LOWER 100 UPPER 1100) + +check_threshold(cuda.5g.channelsim.test_channel_scalability.test51 "Total CPU Time" "< 2045820") +check_threshold(cuda.5g.channelsim.test_channel_scalability.test51 "Total GPU Time" "< 26450") +check_threshold_range(cuda.5g.channelsim.test_channel_scalability.test51 "Avg Time per Channel - CPU" LOWER 100 UPPER 127900) +check_threshold_range(cuda.5g.channelsim.test_channel_scalability.test51 "Avg Time per Channel - GPU" LOWER 100 UPPER 1700) + +check_threshold(cuda.5g.channelsim.test_channel_scalability.test52 "Total CPU Time" "< 1052280") +check_threshold(cuda.5g.channelsim.test_channel_scalability.test52 "Total GPU Time" "< 6120") +check_threshold_range(cuda.5g.channelsim.test_channel_scalability.test52 "Avg Time per Channel - CPU" LOWER 100 UPPER 65800) +check_threshold_range(cuda.5g.channelsim.test_channel_scalability.test52 "Avg Time per Channel - GPU" LOWER 100 UPPER 400) + +check_threshold(cuda.5g.channelsim.test_channel_scalability.test53 "Total CPU Time" "< 1052410") +check_threshold(cuda.5g.channelsim.test_channel_scalability.test53 "Total GPU Time" "< 3170") +check_threshold_range(cuda.5g.channelsim.test_channel_scalability.test53 "Avg Time per Channel - CPU" LOWER 100 UPPER 65800) +check_threshold_range(cuda.5g.channelsim.test_channel_scalability.test53 "Avg Time per Channel - GPU" LOWER 100 UPPER 200) + +check_threshold(cuda.5g.channelsim.test_channel_scalability.test54 "Total CPU Time" "< 1052480") +check_threshold(cuda.5g.channelsim.test_channel_scalability.test54 "Total GPU Time" "< 9340") +check_threshold_range(cuda.5g.channelsim.test_channel_scalability.test54 "Avg Time per Channel - CPU" LOWER 100 UPPER 65800) +check_threshold_range(cuda.5g.channelsim.test_channel_scalability.test54 "Avg Time per Channel - GPU" LOWER 100 UPPER 600) + +check_threshold(cuda.5g.channelsim.test_channel_scalability.test55 "Total CPU Time" "< 2058590") +check_threshold(cuda.5g.channelsim.test_channel_scalability.test55 "Total GPU Time" "< 6960") +check_threshold_range(cuda.5g.channelsim.test_channel_scalability.test55 "Avg Time per Channel - CPU" LOWER 100 UPPER 128700) +check_threshold_range(cuda.5g.channelsim.test_channel_scalability.test55 "Avg Time per Channel - GPU" LOWER 100 UPPER 400) + +check_threshold(cuda.5g.channelsim.test_channel_scalability.test56 "Total CPU Time" "< 2058740") +check_threshold(cuda.5g.channelsim.test_channel_scalability.test56 "Total GPU Time" "< 4450") +check_threshold_range(cuda.5g.channelsim.test_channel_scalability.test56 "Avg Time per Channel - CPU" LOWER 100 UPPER 128700) +check_threshold_range(cuda.5g.channelsim.test_channel_scalability.test56 "Avg Time per Channel - GPU" LOWER 100 UPPER 300) + +check_threshold(cuda.5g.channelsim.test_channel_scalability.test57 "Total CPU Time" "< 2059020") +check_threshold(cuda.5g.channelsim.test_channel_scalability.test57 "Total GPU Time" "< 18250") +check_threshold_range(cuda.5g.channelsim.test_channel_scalability.test57 "Avg Time per Channel - CPU" LOWER 100 UPPER 128700) +check_threshold_range(cuda.5g.channelsim.test_channel_scalability.test57 "Avg Time per Channel - GPU" LOWER 100 UPPER 1100) From 400e7b7ea371aa6ecc8ca5c766d9d9bf90208877 Mon Sep 17 00:00:00 2001 From: Jaroslava Fiedlerova Date: Thu, 29 Jan 2026 12:21:00 +0000 Subject: [PATCH 050/100] CI: Add Dockerfile for channel simulation build on GH with CUDA support --- .../docker/Dockerfile.channelsim.ubuntu | 52 +++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 ci-scripts/docker/Dockerfile.channelsim.ubuntu diff --git a/ci-scripts/docker/Dockerfile.channelsim.ubuntu b/ci-scripts/docker/Dockerfile.channelsim.ubuntu new file mode 100644 index 0000000000..72551d75d7 --- /dev/null +++ b/ci-scripts/docker/Dockerfile.channelsim.ubuntu @@ -0,0 +1,52 @@ +#/* +# * 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 +# */ +#--------------------------------------------------------------------- +# +# Dockerfile for the Open-Air-Interface BUILD service +# Valid for Ubuntu 24.04 +# +#--------------------------------------------------------------------- +FROM nvidia/cuda:12.9.1-devel-ubuntu22.04 AS cuda-image +FROM ran-base:develop AS ran-tests + +RUN rm -Rf /oai-ran +COPY --from=cuda-image /usr/local/cuda/ /usr/local/cuda/ + +# Set the LD_LIBRARY_PATH to ensure the system can find the copied libraries. +# This is crucial for applications that use CUDA. +ENV LD_LIBRARY_PATH=/usr/local/cuda/lib64:/usr/local/cuda/targets/sbsa-linux/lib +ENV PATH=/usr/local/cuda/bin:$PATH +ENV C_INCLUDE_PATH=/usr/local/cuda/include + +WORKDIR /oai-ran +COPY . . + +WORKDIR /oai-ran/build +RUN cmake -GNinja -DENABLE_PHYSIM_TESTS=ON -DENABLE_TESTS=ON \ + -DSANITIZE_UNDEFINED=OFF -DSANITIZE_ADDRESS=OFF \ + -DCMAKE_C_FLAGS=-Werror -DCMAKE_CXX_FLAGS=-Werror \ + -DPHYSIM_CHECK_FILES="ThresholdsCuda.cmake" \ + -DCUDA_ENABLE=ON \ + -DUSE_UNIFIED_MEMORY=ON \ + -DUSE_ATS_MEMORY=OFF \ + -DCMAKE_RUNTIME_OUTPUT_DIRECTORY=/oai-ran/cmake_targets/ran_build/build \ + .. && \ + ninja test_channel_scalability From 34e072823dfa4bf2cf9efafc91a0675029a82917 Mon Sep 17 00:00:00 2001 From: Jaroslava Fiedlerova Date: Thu, 29 Jan 2026 12:52:00 +0000 Subject: [PATCH 051/100] CI: Unify PhySim build/deploy actions and select script from XML --- ci-scripts/main.py | 4 ++-- ci-scripts/xml_class_list.yml | 3 +-- ci-scripts/xml_files/physim_gracehopper.xml | 5 +++-- ci-scripts/xml_files/physim_timed_gracehopper.xml | 3 ++- ci-scripts/xml_files/t2_offload_physim_enc_dec.xml | 3 ++- 5 files changed, 10 insertions(+), 8 deletions(-) diff --git a/ci-scripts/main.py b/ci-scripts/main.py index 1995ff545b..d86af05871 100644 --- a/ci-scripts/main.py +++ b/ci-scripts/main.py @@ -202,9 +202,9 @@ def ExecuteActionWithParam(action, ctx, node): workdir = CLUSTER.eNBSourceCodePath success = cls_oaicitest.Deploy_Physim(ctx, HTML, node, workdir, script, options) - elif action == 'Build_Deploy_Docker_PhySim' or action == 'Build_Deploy_Source_PhySim': + elif action == 'Build_Deploy_PhySim': ctest_opt = test.findtext('ctest-opt') or '' - script = "scripts/docker-build-and-deploy-physims.sh" if action == 'Build_Deploy_Docker_PhySim' else 'scripts/source-deploy-physims.sh' + script = test.findtext('script') options = f"{CONTAINERS.eNBSourceCodePath} {ctest_opt}" workdir = CONTAINERS.eNBSourceCodePath success = cls_oaicitest.Deploy_Physim(ctx, HTML, node, workdir, script, options) diff --git a/ci-scripts/xml_class_list.yml b/ci-scripts/xml_class_list.yml index a20b78fc99..e8fa42dcd3 100755 --- a/ci-scripts/xml_class_list.yml +++ b/ci-scripts/xml_class_list.yml @@ -24,8 +24,7 @@ - Undeploy_Object - Cppcheck_Analysis - Deploy_Run_OC_PhySim - - Build_Deploy_Docker_PhySim - - Build_Deploy_Source_PhySim + - Build_Deploy_PhySim - LicenceAndFormattingCheck - Push_Local_Registry - Pull_Local_Registry diff --git a/ci-scripts/xml_files/physim_gracehopper.xml b/ci-scripts/xml_files/physim_gracehopper.xml index 533696d28c..bebdb3a9c2 100644 --- a/ci-scripts/xml_files/physim_gracehopper.xml +++ b/ci-scripts/xml_files/physim_gracehopper.xml @@ -32,8 +32,9 @@ - Build_Deploy_Docker_PhySim - Build and Deploy PhySim + Build_Deploy_PhySim + Build and Deploy PhySim in Docker + localhost -R 5g -E "nr_dlsim|nr_ulsim|ldpctest" -j32 diff --git a/ci-scripts/xml_files/physim_timed_gracehopper.xml b/ci-scripts/xml_files/physim_timed_gracehopper.xml index 9844f6ebb3..c6772fa216 100644 --- a/ci-scripts/xml_files/physim_timed_gracehopper.xml +++ b/ci-scripts/xml_files/physim_timed_gracehopper.xml @@ -32,8 +32,9 @@ - Build_Deploy_Docker_PhySim + Build_Deploy_PhySim Build and Deploy PhySim with timing check + localhost -R 5g -R "nr_dlsim|nr_ulsim|ldpctest" -E physim.5g.nr_dlsim.mcs.mimo.test9 -j1 diff --git a/ci-scripts/xml_files/t2_offload_physim_enc_dec.xml b/ci-scripts/xml_files/t2_offload_physim_enc_dec.xml index afac50ba16..80b933aa4a 100644 --- a/ci-scripts/xml_files/t2_offload_physim_enc_dec.xml +++ b/ci-scripts/xml_files/t2_offload_physim_enc_dec.xml @@ -32,8 +32,9 @@ - Build_Deploy_Source_PhySim + Build_Deploy_PhySim Build and Deploy PhySim T2 Offload tests + caracal -R 5g-offload -j1 From 2c4885c66242b4e4c7dfdf13a2b4af9df8f34d0a Mon Sep 17 00:00:00 2001 From: Jaroslava Fiedlerova Date: Thu, 29 Jan 2026 13:05:19 +0000 Subject: [PATCH 052/100] CI: Add XML for channel simulator testing --- .../docker-build-and-deploy-chansim.sh | 46 +++++++++++++++++++ .../xml_files/physim_timed_gracehopper.xml | 2 +- .../test_channel_sim_gracehopper.xml | 43 +++++++++++++++++ 3 files changed, 90 insertions(+), 1 deletion(-) create mode 100755 ci-scripts/scripts/docker-build-and-deploy-chansim.sh create mode 100644 ci-scripts/xml_files/test_channel_sim_gracehopper.xml diff --git a/ci-scripts/scripts/docker-build-and-deploy-chansim.sh b/ci-scripts/scripts/docker-build-and-deploy-chansim.sh new file mode 100755 index 0000000000..991517330c --- /dev/null +++ b/ci-scripts/scripts/docker-build-and-deploy-chansim.sh @@ -0,0 +1,46 @@ +#!/bin/bash + +function die() { echo $@; exit 1; } +[ $# -ge 1 ] || die "usage: $0 [ctest-options]" + +IMAGE=ran-channelsim:ci-temp +CONTAINER=ran-channelsim +function cleanup-docker() { + docker stop ${CONTAINER} + docker rm ${CONTAINER} + docker rmi ${IMAGE} + docker volume prune --force +} +trap cleanup-docker EXIT + +set -x +DIR=$1 +RESULT_DIR=${DIR}/ +shift +CTEST_OPT=$@ + +# build channel simulator +docker build --progress=plain --tag ${IMAGE} --file ${DIR}/ci-scripts/docker/Dockerfile.channelsim.ubuntu ${DIR} &>> ${RESULT_DIR}/channelsim_build_log.txt +if [ $? -ne 0 ]; then + echo "build of channel simulator failed" + exit 1 +fi + +# get a JSON description of all tests to run +docker run -a STDOUT --workdir /oai-ran/build/ --env LD_LIBRARY_PATH=/oai-ran/build/ --rm --name ${CONTAINER} ${IMAGE} ctest ${CTEST_OPT} --show-only=json-v1 &> ${RESULT_DIR}/desc-tests.json +JSON_RES=$? + +# run the actual tests: we don't suppy --rm as we have to copy the files +# similar to unit tests, we can't mount the file where we write physims-5g-run.xml to +# as it would write a file as root, but this script is run as a normal user +docker run -a STDOUT --gpus all --workdir /oai-ran/build/ --env LD_LIBRARY_PATH=/oai-ran/build/ --name ${CONTAINER} ${IMAGE} ctest ${CTEST_OPT} --output-junit results-run.xml --test-output-size-passed 100000 --test-output-size-failed 100000 &>> ${RESULT_DIR}/channelsim_log.txt +RUN_RES=$? +docker cp ${CONTAINER}:/oai-ran/build/results-run.xml ${RESULT_DIR}/ +docker cp ${CONTAINER}:/oai-ran/build/Testing/Temporary/LastTestsFailed.log ${RESULT_DIR}/ +docker cp ${CONTAINER}:/oai-ran/build/Testing/Temporary/LastTest.log ${RESULT_DIR}/ + +# if both were successful, return 0 +# TODO not sure +#[[ $JSON_RES -eq 0 && $RUN_RES -eq 0 ]] && exit 0 + +exit 0 diff --git a/ci-scripts/xml_files/physim_timed_gracehopper.xml b/ci-scripts/xml_files/physim_timed_gracehopper.xml index c6772fa216..6813c02775 100644 --- a/ci-scripts/xml_files/physim_timed_gracehopper.xml +++ b/ci-scripts/xml_files/physim_timed_gracehopper.xml @@ -34,7 +34,7 @@ Build_Deploy_PhySim Build and Deploy PhySim with timing check - + localhost -R 5g -R "nr_dlsim|nr_ulsim|ldpctest" -E physim.5g.nr_dlsim.mcs.mimo.test9 -j1 diff --git a/ci-scripts/xml_files/test_channel_sim_gracehopper.xml b/ci-scripts/xml_files/test_channel_sim_gracehopper.xml new file mode 100644 index 0000000000..cd6419acec --- /dev/null +++ b/ci-scripts/xml_files/test_channel_sim_gracehopper.xml @@ -0,0 +1,43 @@ + + + channelsim-tab + ChannelSim + wrench + + + Create_Workspace + Create new Workspace + localhost + + + + Build_Deploy_PhySim + Build and Deploy Test Channel Simulation + + localhost + -R cuda.5g.channelsim + + + + From 50411ad86db611dd04302da1c762a30f12ea76c4 Mon Sep 17 00:00:00 2001 From: Jaroslava Fiedlerova Date: Tue, 3 Feb 2026 14:24:19 +0000 Subject: [PATCH 053/100] CI: build and run CUDA channel simulation unit tests --- ci-scripts/docker/Dockerfile.channelsim.ubuntu | 2 +- openair1/SIMULATION/tests/CMakeLists.txt | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/ci-scripts/docker/Dockerfile.channelsim.ubuntu b/ci-scripts/docker/Dockerfile.channelsim.ubuntu index 72551d75d7..9aab73f652 100644 --- a/ci-scripts/docker/Dockerfile.channelsim.ubuntu +++ b/ci-scripts/docker/Dockerfile.channelsim.ubuntu @@ -49,4 +49,4 @@ RUN cmake -GNinja -DENABLE_PHYSIM_TESTS=ON -DENABLE_TESTS=ON \ -DUSE_ATS_MEMORY=OFF \ -DCMAKE_RUNTIME_OUTPUT_DIRECTORY=/oai-ran/cmake_targets/ran_build/build \ .. && \ - ninja test_channel_scalability + ninja test_channel_scalability test_multipath test_noise diff --git a/openair1/SIMULATION/tests/CMakeLists.txt b/openair1/SIMULATION/tests/CMakeLists.txt index ebeb9f3e54..afd7dcffd5 100644 --- a/openair1/SIMULATION/tests/CMakeLists.txt +++ b/openair1/SIMULATION/tests/CMakeLists.txt @@ -419,6 +419,9 @@ endif() #################################################################################### if (CUDA_ENABLE) +add_physim_test(cuda.5g.channelsim.test_multipath "unitary test designed specifically to validate the multipath_channel_cuda" test_multipath) +add_physim_test(cuda.5g.channelsim.test_noise "unitary test that focuses on a single component: the add_noise_cuda function" test_noise) + # --- Test Suite 1: 61440 samples, 2/4/8/32 TX - 2/4/8/32 RX --- add_physim_test(cuda.5g.channelsim.test_channel_scalability.test1 "16 channels 61440 samples 2T2R Batch" test_channel_scalability -c 16 -t 2 -r 2 -s 61440 -l 32 -m batch -n 12) add_physim_test(cuda.5g.channelsim.test_channel_scalability.test2 "16 channels 61440 samples 2T2R Stream" test_channel_scalability -c 16 -t 2 -r 2 -s 61440 -l 32 -m stream -n 12) From 036793f55ee8e428e43bdb6df27bd5b8f551b5b9 Mon Sep 17 00:00:00 2001 From: Jaroslava Fiedlerova Date: Fri, 6 Feb 2026 11:14:28 +0000 Subject: [PATCH 054/100] CI: ensure proper alignment of ctest log report --- openair1/SIMULATION/tests/analyze-timing.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openair1/SIMULATION/tests/analyze-timing.sh b/openair1/SIMULATION/tests/analyze-timing.sh index f88b595a97..c1fb9cd48b 100755 --- a/openair1/SIMULATION/tests/analyze-timing.sh +++ b/openair1/SIMULATION/tests/analyze-timing.sh @@ -47,7 +47,7 @@ while [ $# -gt 0 ]; do } else { r = "FAIL"; } - RESULTS['${NUM}']=sprintf("CHECK %-35s %7.2f %-8s %s", "'${PATTERN}'", meas, " '${COND}'", r); + RESULTS['${NUM}']=sprintf("CHECK %-40s %10.2f %-10s %s", "'${PATTERN}'", meas, "'${COND}'", r); } ' From 33ab261ac8f537aba7476a558f4fb00ac57711f8 Mon Sep 17 00:00:00 2001 From: Jaroslava Fiedlerova Date: Wed, 21 Jan 2026 13:14:57 +0000 Subject: [PATCH 055/100] Add RAN-Channel-Simulation into parent CI pipeline --- ci-scripts/Jenkinsfile-GitLab-Container | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/ci-scripts/Jenkinsfile-GitLab-Container b/ci-scripts/Jenkinsfile-GitLab-Container index e395973b25..9e4e6dd2ff 100644 --- a/ci-scripts/Jenkinsfile-GitLab-Container +++ b/ci-scripts/Jenkinsfile-GitLab-Container @@ -334,6 +334,29 @@ pipeline { } } } + stage ("Channel-Simulation") { + when { expression {do5Gtest || do5GUeTest} } + steps { + script { + triggerSlaveJob ('RAN-Channel-Simulation', 'Channel-Simulation') + } + } + post { + always { + script { + // Using a unique variable name for each test stage to avoid overwriting on a global variable + // due to parallel-time concurrency + channelSimStatus = finalizeSlaveJob('RAN-Channel-Simulation') + } + } + failure { + script { + currentBuild.result = 'FAILURE' + failingStages += channelSimStatus + } + } + } + } stage ("PhySim-Cluster-4G") { when { expression {do4Gtest} } steps { From 85f8d703763452ca098b1d5cbd0f0cff23c85c2f Mon Sep 17 00:00:00 2001 From: Bartosz Podrygajlo Date: Thu, 5 Feb 2026 13:25:33 +0100 Subject: [PATCH 056/100] Fix for split 7.2 DU configurations including DU & RU ethernet addresses Fix parsing of DU configurations that include both RU and DU ethernet addresses in the fhi_72 section. The extraneous DU addresses are used in MPlane to configure the RU. --- doc/ORAN_FHI7.2_Tutorial.md | 2 ++ radio/fhi_72/oran-config.c | 59 ++++++++++++++++--------------------- radio/fhi_72/oran-params.h | 13 +++++++- 3 files changed, 40 insertions(+), 34 deletions(-) diff --git a/doc/ORAN_FHI7.2_Tutorial.md b/doc/ORAN_FHI7.2_Tutorial.md index a241fbb3f5..b02f2973a6 100644 --- a/doc/ORAN_FHI7.2_Tutorial.md +++ b/doc/ORAN_FHI7.2_Tutorial.md @@ -1031,6 +1031,7 @@ Edit the sample OAI gNB configuration file and check following parameters: * `prach_config`: PRACH-specific configuration * `eAxC_offset`: PRACH antenna offset; if not set, default value of `N = max(Nrx,Ntx)` is used * `kbar`: the PRACH guard interval, provided in RU + * `app_id`: `DU` or `RU`. Sets the application `id` value in xRAN. Use the default value: `DU`. Layer mapping (eAxC offsets) happens as follows: - For PUSCH/PDSCH, the layers are mapped to `[0,1,...,Nrx-1]/[0,1,...,Ntx-1]` where `Nrx/Ntx` is the @@ -1491,6 +1492,7 @@ fhi_72 = { * `dpdk_iova_mode`: [*] * `owdm_enable`: [*] * `fh_config`: only DU delay profile (`T1a` and `Ta4`) + * `app_id`: [*] [*] see [Configure OAI gNB](#configure-oai-gnb) for more details diff --git a/radio/fhi_72/oran-config.c b/radio/fhi_72/oran-config.c index 8283b63a7d..89fd2e56db 100644 --- a/radio/fhi_72/oran-config.c +++ b/radio/fhi_72/oran-config.c @@ -461,14 +461,18 @@ char bbdev_dev[32] = ""; char bbdev_vfio_vf_token[64] = ""; #endif -static bool set_fh_io_cfg(struct xran_io_cfg *io_cfg, const paramdef_t *fhip, int nump, const int num_rus, const int is_du) +static bool set_fh_io_cfg(struct xran_io_cfg *io_cfg, const paramdef_t *fhip, int nump, const int num_rus) { DevAssert(fhip != NULL); int num_dev = gpd(fhip, nump, ORAN_CONFIG_DPDK_DEVICES)->numelt; AssertFatal(num_dev > 0, "need to provide DPDK devices for O-RAN 7.2 Fronthaul\n"); AssertFatal(num_dev < 17, "too many DPDK devices for O-RAN 7.2 Fronthaul\n"); - io_cfg->id = 1 - is_du; // 0 -> xran as O-DU; 1 -> xran as O-RU + int app_id_index = config_paramidx_fromname((paramdef_t *)fhip, nump, ORAN_CONFIG_APP_ID); + AssertFatal(app_id_index >= 0,"Index for %s config option not found!\n", ORAN_CONFIG_APP_ID); + io_cfg->id = config_get_processedint(config_get_if(), (paramdef_t *)&fhip[app_id_index]); + LOG_A(PHY, "Initializing XRAN layer as %s\n", io_cfg->id == XRAN_APP_ID_O_DU ? "O-DU" : "O-RU"); + io_cfg->num_vfs = num_dev; // number of VFs for C-plane and U-plane (should be even); max = XRAN_VF_MAX io_cfg->num_rxq = 1; // number of RX queues per VF for (int i = 0; i < num_dev; ++i) { @@ -541,16 +545,16 @@ static bool set_fh_io_cfg(struct xran_io_cfg *io_cfg, const paramdef_t *fhip, in /* if RU does support, io_cfg->eowd_cmn[0] should only be filled as id = O_DU; io_cfg->eowd_cmn[1] only used if id = O_RU */ const uint16_t owdm_enable = *gpd(fhip, nump, ORAN_CONFIG_ECPRI_OWDM)->uptr; if (owdm_enable) { - io_cfg->eowd_cmn[0].initiator_en = is_du ? 1 : 0; // 1 -> initiator (always O-DU), 0 -> recipient (always O-RU) - io_cfg->eowd_cmn[0].numberOfSamples = 8; // total number of samples to be collected and averaged per port - io_cfg->eowd_cmn[0].filterType = 0; // 0 -> simple average based on number of measurements; not used in xran in both E and F releases - io_cfg->eowd_cmn[0].responseTo = 10000000; // response timeout in [ns] - io_cfg->eowd_cmn[0].measVf = 0; // VF using the OWD transmitter; within xran, the measurements are calculated per each supported VF, but starts from measVf - io_cfg->eowd_cmn[0].measState = 0; // the state of the OWD transmitter; 0 -> OWDMTX_INIT (enum xran_owdm_tx_state) - io_cfg->eowd_cmn[0].measId = 0; // measurement ID to be used by the transmitter - io_cfg->eowd_cmn[0].measMethod = 0; // measurement method; 0 -> XRAN_REQUEST (enum xran_owd_meas_method) - io_cfg->eowd_cmn[0].owdm_enable = 1; // 1 -> enabled; 0 -> disabled - io_cfg->eowd_cmn[0].owdm_PlLength = 40; // payload in the measurement packet; 40 <= PlLength <= 1400 + io_cfg->eowd_cmn[XRAN_APP_ID_O_DU].initiator_en = io_cfg->id == XRAN_APP_ID_O_DU ? 1 : 0; // 1 -> initiator (always O-DU), 0 -> recipient (always O-RU) + io_cfg->eowd_cmn[XRAN_APP_ID_O_DU].numberOfSamples = 8; // total number of samples to be collected and averaged per port + io_cfg->eowd_cmn[XRAN_APP_ID_O_DU].filterType = 0; // 0 -> simple average based on number of measurements; not used in xran in both E and F releases + io_cfg->eowd_cmn[XRAN_APP_ID_O_DU].responseTo = 10000000; // response timeout in [ns] + io_cfg->eowd_cmn[XRAN_APP_ID_O_DU].measVf = 0; // VF using the OWD transmitter; within xran, the measurements are calculated per each supported VF, but starts from measVf + io_cfg->eowd_cmn[XRAN_APP_ID_O_DU].measState = 0; // the state of the OWD transmitter; 0 -> OWDMTX_INIT (enum xran_owdm_tx_state) + io_cfg->eowd_cmn[XRAN_APP_ID_O_DU].measId = 0; // measurement ID to be used by the transmitter + io_cfg->eowd_cmn[XRAN_APP_ID_O_DU].measMethod = 0; // measurement method; 0 -> XRAN_REQUEST (enum xran_owd_meas_method) + io_cfg->eowd_cmn[XRAN_APP_ID_O_DU].owdm_enable = 1; // 1 -> enabled; 0 -> disabled + io_cfg->eowd_cmn[XRAN_APP_ID_O_DU].owdm_PlLength = 40; // payload in the measurement packet; 40 <= PlLength <= 1400 } /* eCPRI OWDM per port variables for O-DU; this parameter is filled within xran library */ // eowd_port[0][XRAN_VF_MAX] @@ -679,19 +683,6 @@ static bool set_fh_init(void *mplane_api, struct xran_fh_init *fh_init, enum xra const int nfh = sizeofArray(FHconfigs); config_getlist(config_get_if(), &FH_ConfigList, FHconfigs, nfh, aprefix); - int num_rus = FH_ConfigList.numelt; // based on the number of fh_config sections -> number of RUs - int is_du = 0; - - int num_ru_addr = gpd(fhip, nump, ORAN_CONFIG_RU_ADDR)->numelt; - int num_du_addr = gpd(fhip, nump, ORAN_CONFIG_DU_ADDR)->numelt; - if (num_ru_addr > 0 && num_du_addr == 0) - is_du = 1; - else if (num_du_addr > 0 && num_ru_addr == 0) - is_du = 0; - else - AssertFatal(false, "Illegal node configuration, num_du_addr %d, num_ru_addr %d\n", num_du_addr, num_ru_addr); - fh_init->xran_ports = is_du == 1 ? num_rus : num_du_addr; - #ifdef OAI_MPLANE ru_session_list_t *ru_session_list = (ru_session_list_t *)mplane_api; int num_rus = ru_session_list->num_rus; @@ -718,8 +709,10 @@ static bool set_fh_init(void *mplane_api, struct xran_fh_init *fh_init, enum xra } } #else + int num_rus = FH_ConfigList.numelt; // based on the number of fh_config sections -> number of RUs + fh_init->xran_ports = num_rus; - if (!set_fh_io_cfg(&fh_init->io_cfg, fhip, nump, num_rus, is_du)) + if (!set_fh_io_cfg(&fh_init->io_cfg, fhip, nump, num_rus)) return false; if (!set_fh_eaxcid_conf(&fh_init->eAxCId_conf, xran_cat)) return false; @@ -728,13 +721,10 @@ static bool set_fh_init(void *mplane_api, struct xran_fh_init *fh_init, enum xra of DL fragments (nPrbElm) needed for transmission of one symbol. */ fh_init->mtu = *gpd(fhip, nump, ORAN_CONFIG_MTU)->uptr; - fh_init->p_o_du_addr = NULL; // DPDK retreives DU MAC address within the xran library with rte_eth_macaddr_get() function - - char **ru_addrs,**du_addrs; - - if (is_du > 0) { + if (fh_init->io_cfg.id == XRAN_APP_ID_O_DU) { + int num_ru_addr = gpd(fhip, nump, ORAN_CONFIG_RU_ADDR)->numelt; fh_init->p_o_ru_addr = calloc(num_ru_addr, sizeof(struct rte_ether_addr)); - ru_addrs = gpd(fhip, nump, ORAN_CONFIG_RU_ADDR)->strlistptr; + char **ru_addrs = gpd(fhip, nump, ORAN_CONFIG_RU_ADDR)->strlistptr; AssertFatal(fh_init->p_o_ru_addr != NULL, "out of memory\n"); for (int i = 0; i < num_ru_addr; ++i) { struct rte_ether_addr *ea = (struct rte_ether_addr *)fh_init->p_o_ru_addr; @@ -743,10 +733,12 @@ static bool set_fh_init(void *mplane_api, struct xran_fh_init *fh_init, enum xra return false; } } + // DPDK retreives DU MAC address within the xran library with rte_eth_macaddr_get() function fh_init->p_o_du_addr = NULL; } else { + int num_du_addr = gpd(fhip, nump, ORAN_CONFIG_DU_ADDR)->numelt; fh_init->p_o_du_addr = calloc(num_du_addr, sizeof(struct rte_ether_addr)); - du_addrs = gpd(fhip, nump, ORAN_CONFIG_DU_ADDR)->strlistptr; + char **du_addrs = gpd(fhip, nump, ORAN_CONFIG_DU_ADDR)->strlistptr; AssertFatal(fh_init->p_o_du_addr != NULL, "out of memory\n"); for (int i = 0; i < num_du_addr; ++i) { struct rte_ether_addr *ea = (struct rte_ether_addr *)fh_init->p_o_du_addr; @@ -755,6 +747,7 @@ static bool set_fh_init(void *mplane_api, struct xran_fh_init *fh_init, enum xra return false; } } + // DPDK retreives RU MAC address within the xran library with rte_eth_macaddr_get() function fh_init->p_o_ru_addr = NULL; } #endif diff --git a/radio/fhi_72/oran-params.h b/radio/fhi_72/oran-params.h index 9f12418909..caa56ccca4 100644 --- a/radio/fhi_72/oran-params.h +++ b/radio/fhi_72/oran-params.h @@ -32,7 +32,7 @@ #define ORAN_CONFIG_IO_CORE "io_core" #define ORAN_CONFIG_WORKER_CORES "worker_cores" #define ORAN_CONFIG_DU_KEYPAIR "du_key_pair" // only needed for M-plane -#define ORAN_CONFIG_DU_ADDR "du_addr" // only needed for M-plane +#define ORAN_CONFIG_DU_ADDR "du_addr" // needed for M-plane or O-RU #define ORAN_CONFIG_VLAN_TAG "vlan_tag" // only needed for M-plane #define ORAN_CONFIG_RU_ADDR "ru_addr" // not needed if M-plane used #define ORAN_CONFIG_RU_USERNAME "ru_username" // only needed for M-plane @@ -44,6 +44,14 @@ #define ORAN_CONFIG_DPDK_MEM_SIZE "dpdk_mem_size" #define ORAN_CONFIG_DPDK_IOVA_MODE "dpdk_iova_mode" #define ORAN_CONFIG_ECPRI_OWDM "owdm_enable" +#define ORAN_CONFIG_APP_ID "app_id" + +#define APP_ID_O_DU "DU" +#define APP_ID_O_RU "RU" + +// Provide a definition since xran is missing one +#define XRAN_APP_ID_O_DU 0 +#define XRAN_APP_ID_O_RU 1 // clang-format off // TODO: PCI addr check @@ -66,6 +74,7 @@ {ORAN_CONFIG_DPDK_MEM_SIZE, "DPDK huge page pre-allocation in MiB\n", 0, .uptr=NULL, .defuintval=8192, TYPE_UINT, 0}, \ {ORAN_CONFIG_DPDK_IOVA_MODE, "DPDK IOVA mode\n", 0, .strptr=NULL, .defstrval="PA", TYPE_STRING, 0}, \ {ORAN_CONFIG_ECPRI_OWDM, "eCPRI One-Way Delay Measurements\n", PARAMFLAG_BOOL, .uptr=NULL, .defuintval=0, TYPE_UINT, 0}, \ + {ORAN_CONFIG_APP_ID, "Either DU or RU", 0, .strptr=NULL, .defstrval=APP_ID_O_DU, TYPE_STRING, 0} \ } // clang-format off @@ -88,6 +97,8 @@ { .s3a = { config_checkstr_assign_integer, \ {"PA", "VA"}, {0, 1}, 2} }, \ { .s5 = { NULL } }, \ + { .s3a = { config_checkstr_assign_integer, \ + {APP_ID_O_DU, APP_ID_O_RU}, {XRAN_APP_ID_O_DU, XRAN_APP_ID_O_RU}, 2} }, \ } From 3d41d28f1d6d048746d3a2c443f4bcdaebc553a9 Mon Sep 17 00:00:00 2001 From: Guido Casati Date: Fri, 29 Aug 2025 16:47:51 +0200 Subject: [PATCH 057/100] RRC: enhance AMBR management in CU-CP According to TS 38.413: * AMBR is conditional to PDU Session Resource to setup in the Initial UE Context Setup procedure * The UE Aggregate Maximum Bit Rate IE should be sent to the NG-RAN node if the AMF has not sent it previously. If it is included in the PDU SESSION RESOURCE SETUP REQUEST message, the NG-RAN node shall store it in the UE context * Range is INTEGER [0..4,000,000,000,000] To this purpose, changes in the commit: * Init AMBR to out of range value when creating UE context * Store AMBR in UE context whenever is received * Assert if not set at the time of Bearer Setup (should be known at that stage) --- openair2/RRC/NR/nr_rrc_defs.h | 8 ++++++++ openair2/RRC/NR/rrc_gNB.c | 4 ++-- openair2/RRC/NR/rrc_gNB_NGAP.c | 24 +++++++++++++++++++----- openair2/RRC/NR/rrc_gNB_UE_context.c | 2 ++ 4 files changed, 31 insertions(+), 7 deletions(-) diff --git a/openair2/RRC/NR/nr_rrc_defs.h b/openair2/RRC/NR/nr_rrc_defs.h index 46e1ef3384..08e0d27888 100644 --- a/openair2/RRC/NR/nr_rrc_defs.h +++ b/openair2/RRC/NR/nr_rrc_defs.h @@ -166,6 +166,11 @@ typedef struct { pdusession_level_qos_parameter_t qos; } nr_rrc_qos_t; +typedef struct { + uint64_t dl_br; + uint64_t ul_br; +} nr_rrc_ambr_t; + /* forward declaration */ typedef struct nr_handover_context_s nr_handover_context_t; @@ -244,6 +249,9 @@ typedef struct gNB_RRC_UE_s { int n_initial_pdu; pdusession_t *initial_pdus; + // Aggregate Maximum Bit Rate + nr_rrc_ambr_t ambr; + /* Nas Pdu */ byte_array_t nas_pdu; diff --git a/openair2/RRC/NR/rrc_gNB.c b/openair2/RRC/NR/rrc_gNB.c index db83bb67d2..39c1d31490 100644 --- a/openair2/RRC/NR/rrc_gNB.c +++ b/openair2/RRC/NR/rrc_gNB.c @@ -1963,7 +1963,7 @@ static void handle_ueCapabilityInformation(gNB_RRC_INST *rrc, gNB_RRC_UE_t *UE, * up security and request capabilities, so trigger PDU sessions now. The * UE NAS message will be forwarded in the corresponding reconfiguration, * the Initial context setup response after reconfiguration complete. */ - if (!trigger_bearer_setup(rrc, UE, UE->n_initial_pdu, UE->initial_pdus, 0)) { + if (!trigger_bearer_setup(rrc, UE, UE->n_initial_pdu, UE->initial_pdus, UE->ambr.dl_br)) { LOG_W(NR_RRC, "Failed to setup bearers for UE %d: send Initial Context Setup Response\n", UE->rrc_ue_id); rrc_gNB_send_NGAP_INITIAL_CONTEXT_SETUP_RESP(rrc, UE); rrc_forward_ue_nas_message(rrc, UE); @@ -2213,7 +2213,7 @@ static int rrc_gNB_decode_dcch(gNB_RRC_INST *rrc, const f1ap_ul_rrc_message_t *m * to set up security, so trigger PDU sessions now. The UE NAS * message will be forwarded in the corresponding reconfiguration, * the Initial context setup response after reconfiguration complete. */ - if (!trigger_bearer_setup(rrc, UE, UE->n_initial_pdu, UE->initial_pdus, 0)) { + if (!trigger_bearer_setup(rrc, UE, UE->n_initial_pdu, UE->initial_pdus, UE->ambr.dl_br)) { LOG_W(NR_RRC, "Failed to setup bearers for UE %d: send Initial Context Setup Response\n", UE->rrc_ue_id); rrc_gNB_send_NGAP_INITIAL_CONTEXT_SETUP_RESP(rrc, UE); rrc_forward_ue_nas_message(rrc, UE); diff --git a/openair2/RRC/NR/rrc_gNB_NGAP.c b/openair2/RRC/NR/rrc_gNB_NGAP.c index 7307216877..f91e346f02 100644 --- a/openair2/RRC/NR/rrc_gNB_NGAP.c +++ b/openair2/RRC/NR/rrc_gNB_NGAP.c @@ -367,6 +367,10 @@ static nr_sdap_configuration_t get_sdap_config(const bool enable_sdap) */ bool trigger_bearer_setup(gNB_RRC_INST *rrc, gNB_RRC_UE_t *UE, int n, pdusession_t *sessions, uint64_t ueAggMaxBitRateDownlink) { + if (ueAggMaxBitRateDownlink == UINT64_MAX) { + LOG_E(NR_RRC, "UE %d: UE aggregate maximum bitrate must be known by the NG-RAN node\n", UE->rrc_ue_id); + return false; + } AssertFatal(UE->as_security_active, "logic bug: security should be active when activating DRBs\n"); e1ap_bearer_setup_req_t bearer_req = {0}; @@ -497,12 +501,15 @@ int rrc_gNB_process_NGAP_INITIAL_CONTEXT_SETUP_REQ(MessageDef *msg_p, instance_t UE->nas_pdu = req->nas_pdu; if (req->nb_of_pdusessions > 0) { + AssertFatal(req->has_ue_ambr, "UE aggregate maximum bitrate is required when there are PDU sessions to setup"); /* if there are PDU sessions to setup, store them to be created once * security (and UE capabilities) are received */ UE->n_initial_pdu = req->nb_of_pdusessions; UE->initial_pdus = calloc_or_fail(UE->n_initial_pdu, sizeof(*UE->initial_pdus)); for (int i = 0; i < UE->n_initial_pdu; ++i) cp_pdusession_resource_item_to_pdusession(&UE->initial_pdus[i], &req->pdusession[i]); + UE->ambr.dl_br = req->ue_ambr.br_dl; + UE->ambr.ul_br = req->ue_ambr.br_ul; } /* security */ @@ -525,7 +532,7 @@ int rrc_gNB_process_NGAP_INITIAL_CONTEXT_SETUP_REQ(MessageDef *msg_p, instance_t // do not remove the above allocation which is reused here: this is used // in handle_rrcReconfigurationComplete() to know that we need to send a // Initial context setup response message - if (!trigger_bearer_setup(rrc, UE, UE->n_initial_pdu, UE->initial_pdus, 0)) { + if (!trigger_bearer_setup(rrc, UE, UE->n_initial_pdu, UE->initial_pdus, UE->ambr.dl_br)) { LOG_W(NR_RRC, "UE %d: reject PDU Session Setup in Initial Context Setup Response\n", UE->rrc_ue_id); ngap_cause_t cause = {.type = NGAP_CAUSE_RADIO_NETWORK, .value = NGAP_CAUSE_RADIO_NETWORK_RESOURCES_NOT_AVAILABLE_FOR_THE_SLICE}; send_ngap_initial_context_setup_resp_fail(rrc->module_id, req, cause); @@ -878,9 +885,13 @@ void rrc_gNB_process_NGAP_PDUSESSION_SETUP_REQ(MessageDef *msg_p, instance_t ins for (int i = 0; i < msg->nb_pdusessions_tosetup; ++i) cp_pdusession_resource_item_to_pdusession(&to_setup[i], &msg->pdusession[i]); - uint64_t dl_ambr = msg->has_ue_ambr ? msg->ueAggMaxBitRate.br_dl : 0; + /** Set AMBR if provided (optional) */ + if (msg->has_ue_ambr) { + UE->ambr.dl_br = msg->ueAggMaxBitRate.br_dl; + UE->ambr.ul_br = msg->ueAggMaxBitRate.br_ul; + } - if (!trigger_bearer_setup(rrc, UE, msg->nb_pdusessions_tosetup, to_setup, dl_ambr)) { + if (!trigger_bearer_setup(rrc, UE, msg->nb_pdusessions_tosetup, to_setup, UE->ambr.dl_br)) { // Reject PDU Session Resource setup if there's no CU-UP associated LOG_W(NR_RRC, "UE %d: reject PDU Session Setup in PDU Session Resource Setup Response\n", UE->rrc_ue_id); ngap_cause_t cause = {.type = NGAP_CAUSE_RADIO_NETWORK, .value = NGAP_CAUSE_RADIO_NETWORK_RESOURCES_NOT_AVAILABLE_FOR_THE_SLICE}; @@ -1199,7 +1210,11 @@ int rrc_gNB_process_Handover_Request(gNB_RRC_INST *rrc, instance_t instance, nga cp_pdusession_transfer_to_pdusession(pdu, &ho_pdu->pdusessionTransfer); } - if (!trigger_bearer_setup(rrc, UE, msg->nb_of_pdusessions, to_setup, msg->ue_ambr.br_dl)) { + // Store UE aggregate maximum bitrate + UE->ambr.dl_br = msg->ue_ambr.br_dl; + UE->ambr.ul_br = msg->ue_ambr.br_ul; + + if (!trigger_bearer_setup(rrc, UE, msg->nb_of_pdusessions, to_setup, UE->ambr.dl_br)) { LOG_E(NR_RRC, "Failed to establish PDU session: handover failed\n"); ngap_handover_failure_t fail = { @@ -1211,7 +1226,6 @@ int rrc_gNB_process_Handover_Request(gNB_RRC_INST *rrc, instance_t instance, nga rrc_remove_ue(rrc, ue_context_p); return -1; } - return 0; } diff --git a/openair2/RRC/NR/rrc_gNB_UE_context.c b/openair2/RRC/NR/rrc_gNB_UE_context.c index 86838927ab..c05b8a33a8 100644 --- a/openair2/RRC/NR/rrc_gNB_UE_context.c +++ b/openair2/RRC/NR/rrc_gNB_UE_context.c @@ -218,6 +218,8 @@ rrc_gNB_ue_context_t *rrc_gNB_create_ue_context(sctp_assoc_t assoc_id, ue->rrc_ue_id); bool success = cu_add_f1_ue_data(ue->rrc_ue_id, &ue_data); DevAssert(success); + ue->ambr.dl_br = UINT64_MAX; + ue->ambr.ul_br = UINT64_MAX; // Initialise setup PDU Sessions list seq_arr_init(&ue->pduSessions, sizeof(rrc_pdu_session_param_t)); From d1150d90e139f4e4fcce9bbc5353eaf009b17075 Mon Sep 17 00:00:00 2001 From: Guido Casati Date: Wed, 21 Jan 2026 12:15:49 +0100 Subject: [PATCH 058/100] NR RRC/SDAP: enable SDAP by default and update CI/docs - Change gNB RRC enable_sdap default to true - Update RRC and RUNMODEM docs to reflect new default behavior - Clean up CI gNB configs by removing redundant enable_sdap=1 - Keep explicit SDAP disable config for NFAPI gNB VNF --- ci-scripts/conf_files/gnb.sa.band66.106prb.rfsim.conf | 2 +- .../conf_files/untested/gnb.sa.band66.fr1.106PRB.usrpn300.conf | 2 +- doc/RRC/rrc-usage.md | 2 +- doc/RUNMODEM.md | 2 +- openair2/GNB_APP/gnb_paramdef.h | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/ci-scripts/conf_files/gnb.sa.band66.106prb.rfsim.conf b/ci-scripts/conf_files/gnb.sa.band66.106prb.rfsim.conf index bae2bc15ff..fc957d9eef 100644 --- a/ci-scripts/conf_files/gnb.sa.band66.106prb.rfsim.conf +++ b/ci-scripts/conf_files/gnb.sa.band66.106prb.rfsim.conf @@ -18,7 +18,7 @@ gNBs = ////////// Physical parameters: min_rxtxtime = 6; - enable_sdap = 1; + enable_sdap = 0; servingCellConfigCommon = ( { diff --git a/ci-scripts/conf_files/untested/gnb.sa.band66.fr1.106PRB.usrpn300.conf b/ci-scripts/conf_files/untested/gnb.sa.band66.fr1.106PRB.usrpn300.conf index 6715202229..22a65995d5 100644 --- a/ci-scripts/conf_files/untested/gnb.sa.band66.fr1.106PRB.usrpn300.conf +++ b/ci-scripts/conf_files/untested/gnb.sa.band66.fr1.106PRB.usrpn300.conf @@ -18,7 +18,7 @@ gNBs = ////////// Physical parameters: min_rxtxtime = 6; - enable_sdap = 1; + enable_sdap = 0; servingCellConfigCommon = ( { diff --git a/doc/RRC/rrc-usage.md b/doc/RRC/rrc-usage.md index 05f55d5123..baeee1480d 100644 --- a/doc/RRC/rrc-usage.md +++ b/doc/RRC/rrc-usage.md @@ -84,7 +84,7 @@ the [MAC configuration](../MAC/mac-usage.md) as well for SIB configuration. `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 +- `enable_sdap` (default: true): enable the use of the SDAP layer. If deactivated, a transparent SDAP header is prepended to packets, but no further processing is being done. - `cu_sibs` (default: `[]`) list of SIBs to give to the DU for transmission. diff --git a/doc/RUNMODEM.md b/doc/RUNMODEM.md index 1515172b24..b747775860 100644 --- a/doc/RUNMODEM.md +++ b/doc/RUNMODEM.md @@ -544,7 +544,7 @@ sudo ./nr-uesoftmodem --do-ra #### Run OAI with SDAP & Custom DRBs -To run OAI gNB with SDAP, simply include `--gNBs.[0].enable_sdap 1` to the binary's arguments. +SDAP is enabled by default. To disable SDAP, include `--gNBs.[0].enable_sdap 0` to the binary's arguments. The DRB creation is dependent on the 5QI. If the 5QI corresponds to a GBR Flow it assigns a dedicated data radio bearer. diff --git a/openair2/GNB_APP/gnb_paramdef.h b/openair2/GNB_APP/gnb_paramdef.h index 09a19b2d33..8b140c5aac 100644 --- a/openair2/GNB_APP/gnb_paramdef.h +++ b/openair2/GNB_APP/gnb_paramdef.h @@ -170,7 +170,7 @@ typedef enum { {GNB_CONFIG_STRING_ULPRBBLACKLIST, NULL, 0, .strptr=NULL,.defstrval="", TYPE_STRING, 0}, \ {GNB_CONFIG_STRING_UMONDEFAULTDRB, NULL, PARAMFLAG_BOOL, .uptr=NULL, .defuintval=0, TYPE_UINT, 0}, \ {GNB_CONFIG_STRING_FORCE256QAMOFF, GNB_CONFIG_HLP_FORCE256QAMOFF, PARAMFLAG_BOOL, .iptr=NULL, .defintval=0, TYPE_INT, 0}, \ -{GNB_CONFIG_STRING_ENABLE_SDAP, GNB_CONFIG_HLP_STRING_ENABLE_SDAP, PARAMFLAG_BOOL,.iptr=NULL, .defintval=0, TYPE_INT, 0}, \ +{GNB_CONFIG_STRING_ENABLE_SDAP, GNB_CONFIG_HLP_STRING_ENABLE_SDAP, PARAMFLAG_BOOL,.iptr=NULL, .defintval=1, TYPE_INT, 0}, \ {GNB_CONFIG_STRING_GNB_DU_ID, GNB_CONFIG_HLP_GNB_DU_ID, 0, .u64ptr=NULL, .defint64val=1, TYPE_UINT64, 0}, \ {GNB_CONFIG_STRING_GNB_CU_UP_ID, GNB_CONFIG_HLP_GNB_CU_UP_ID, 0, .u64ptr=NULL, .defint64val=1, TYPE_UINT64, 0}, \ {GNB_CONFIG_STRING_USE_DELTA_MCS, GNB_CONFIG_HLP_USE_DELTA_MCS, 0, .iptr=NULL, .defintval=0, TYPE_INT, 0}, \ From ccf5021072d1bcb3af6cd4b6c7dbbf2008b0e405 Mon Sep 17 00:00:00 2001 From: Merkebu Girmay Date: Thu, 30 Oct 2025 07:55:33 -0700 Subject: [PATCH 059/100] radio/vrtsim: Add CIRDB support for precomputed channel taps Integrate Channel Impulse Response Database (CIRDB) to enable playback of precomputed realistic channel taps in vrtsim, with runtime selection via YAML metadata. Features: - Read precomputed channel taps from binary database with YAML sidecar - Runtime enable via --vrtsim.cirdb with optional paths (cirdb_yaml, cirdb_file, cirdb_path) - Channel parameter configuration: model_id (TDL-A..E), delay spread (ds_ns), UE speed (speed_mps) - Fallback to existing channel model options when disabled - Time-driven snapshot progression based on elapsed simulation time Fixes: - RNG initialization: Add randominit() call before gaussZiggurat() - FlatBuffers linking: Handle both 'FlatBuffers' and 'flatbuffers' package names when working with external channel emmulator - Address review comments: use pthread directly, update calloc_or_fail, simplify CMake --- radio/vrtsim/CMakeLists.txt | 30 +- radio/vrtsim/cirdb_provider.c | 348 ++++++++++++++++++ radio/vrtsim/cirdb_provider.h | 60 +++ radio/vrtsim/cirdb_yaml.cpp | 152 ++++++++ radio/vrtsim/cirdb_yaml.h | 61 +++ radio/vrtsim/vrtsim.c | 92 ++++- .../gnb.sa.band78.fr1.106PRB.usrpb210.conf | 12 + 7 files changed, 745 insertions(+), 10 deletions(-) create mode 100644 radio/vrtsim/cirdb_provider.c create mode 100644 radio/vrtsim/cirdb_provider.h create mode 100644 radio/vrtsim/cirdb_yaml.cpp create mode 100644 radio/vrtsim/cirdb_yaml.h diff --git a/radio/vrtsim/CMakeLists.txt b/radio/vrtsim/CMakeLists.txt index 71d18cc5bc..9726743361 100644 --- a/radio/vrtsim/CMakeLists.txt +++ b/radio/vrtsim/CMakeLists.txt @@ -1,13 +1,36 @@ +# Noise device static lib add_library(noise_device STATIC noise_device.c) target_link_libraries(noise_device PRIVATE log_headers) -add_library(vrtsim MODULE vrtsim.c) -set_target_properties(vrtsim PROPERTIES LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}) -target_link_libraries(vrtsim PRIVATE SIMU shm_td_iq_channel actor noise_device log_headers) +# vrtsim module +add_library(vrtsim MODULE + vrtsim.c + cirdb_provider.c + cirdb_yaml.cpp +) +set_target_properties(vrtsim PROPERTIES LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}) + +find_package(yaml-cpp REQUIRED) + +# Link libraries - always link yaml-cpp +target_link_libraries(vrtsim + PRIVATE + SIMU + shm_td_iq_channel + actor + noise_device + log_headers + pthread + yaml-cpp +) + +# Optional taps client add_boolean_option(OAI_VRTSIM_TAPS_CLIENT OFF "Enable taps client" ON) if (OAI_VRTSIM_TAPS_CLIENT) + target_compile_definitions(vrtsim PRIVATE ENABLE_TAPS_CLIENT=1) target_link_libraries(vrtsim PRIVATE taps_client) + file(DOWNLOAD "https://gitlab.eurecom.fr/oai/raytracing-channel-emulator/-/raw/59589ad2a534881810b2aa7dd81e63066245bb8d/server/api/taps.fbs?inline=false" ${CMAKE_SOURCE_DIR}/taps.fbs @@ -21,6 +44,7 @@ if (OAI_VRTSIM_TAPS_CLIENT) DEPENDS ${TAPS_IF_PATH} COMMENT "Generating flatbuffers API from ${TAPS_IF_PATH}" ) + add_library(taps_api INTERFACE) target_include_directories(taps_api INTERFACE ${CMAKE_CURRENT_BINARY_DIR}) diff --git a/radio/vrtsim/cirdb_provider.c b/radio/vrtsim/cirdb_provider.c new file mode 100644 index 0000000000..39a7bff29c --- /dev/null +++ b/radio/vrtsim/cirdb_provider.c @@ -0,0 +1,348 @@ +/* + * 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 +#include +#include +#include +#include +#include +#include + +#include "common/utils/LOG/log.h" +#include "common/utils/assertions.h" +#include "SIMULATION/TOOLS/sim.h" + +#include "cirdb_provider.h" +#include "cirdb_yaml.h" + +#define NUM_TAPS_BUFFERS 4 +#define MAX_L_PUBLISH 8 + +static inline double dabs(double x) +{ + return x < 0.0 ? -x : x; +} + +typedef struct { + void *taps_blob; /* contiguous complexf for all links */ + channel_desc_t *ch; /* ch_ps points into taps_blob */ +} cirdb_buffer_t; + +typedef struct { + /* I/O */ + FILE *fh; + + /* Selected entry meta */ + int model_id; + int n_tx; + int n_rx; + int L_full; + int S; + double fs_hz; + double snapshot_dt_s; + float ds_ns; + float speed_mps; + uint64_t sel_offset; + uint64_t sel_nbytes; + + /* Publication control */ + uint32_t L_out; + uint32_t snap_idx; + + /* Shape in publication */ + int num_tx; + int num_rx; + + /* Double buffering for readers */ + cirdb_buffer_t bufs[NUM_TAPS_BUFFERS]; + int cur; + + /* Temporary read buffer for one snapshot of full L_full taps */ + void *snapshot_tmp; + size_t snapshot_tmp_bytes; + + /* Published pointer location owned by caller */ + channel_desc_t **channel_desc_out; + + /* Bookkeeping of last step computed from elapsed time */ + int64_t last_step_applied; /* -1 until first update */ +} cirdb_g; + +static cirdb_g G; + +static inline void fread_exact(void *dst, size_t sz, FILE *fh) +{ + size_t r = fread(dst, 1, sz, fh); + if (r != sz) { + LOG_E(HW, "CIRDB short read: expected %zu got %zu errno=%d\n", sz, r, errno); + abort(); + } +} + +static inline size_t cf_bytes(size_t n) +{ + return n * sizeof(struct complexf); +} + +/* Point channel_desc ch_ps into a compacted taps buffer */ +static void point_channel_desc(channel_desc_t *ch, struct complexf *base, int num_tx, int num_rx, size_t L_out) +{ + for (int aarx = 0; aarx < num_rx; aarx++) { + for (int aatx = 0; aatx < num_tx; aatx++) { + int link = aarx + num_rx * aatx; + ch->ch_ps[link] = &base[link * L_out]; + } + } + ch->channel_length = L_out; + ch->path_loss_dB = 0; + ch->nb_tx = num_tx; + ch->nb_rx = num_rx; +} + +/* Copy first L_out taps per link from a full L_full snapshot */ +static void compact_L_out(struct complexf *dst, + const struct complexf *src_full, + int num_tx, + int num_rx, + size_t L_full, + size_t L_out) +{ + for (int aatx = 0; aatx < num_tx; aatx++) { + for (int aarx = 0; aarx < num_rx; aarx++) { + size_t link = aarx + num_rx * aatx; + const struct complexf *src = &src_full[link * L_full]; + struct complexf *dst_link = &dst[link * L_out]; + memcpy(dst_link, src, cf_bytes(L_out)); + } + } +} + +/* Load snapshot index s into the next publication buffer and flip */ +static void load_snapshot_and_publish(uint32_t s) +{ + uint64_t stride = (uint64_t)G.n_tx * G.n_rx * G.L_full * sizeof(struct complexf); + uint64_t off = G.sel_offset + (uint64_t)s * stride; + + if (fseeko(G.fh, (off_t)off, SEEK_SET) != 0) { + LOG_E(HW, "CIRDB fseoko failed errno=%d\n", errno); + abort(); + } + fread_exact(G.snapshot_tmp, (size_t)stride, G.fh); + + int next = (G.cur + 1) % NUM_TAPS_BUFFERS; + cirdb_buffer_t *buf = &G.bufs[next]; + + compact_L_out((struct complexf *)buf->taps_blob, (const struct complexf *)G.snapshot_tmp, G.n_tx, G.n_rx, G.L_full, G.L_out); + + point_channel_desc(buf->ch, (struct complexf *)buf->taps_blob, G.n_tx, G.n_rx, G.L_out); + + *G.channel_desc_out = buf->ch; + G.cur = next; +} + +void cirdb_connect(int id, + int num_tx_antennas, + int num_rx_antennas, + const cirdb_select_opts_t *sel, + channel_desc_t **channel_desc_out) +{ + (void)id; + memset(&G, 0, sizeof(G)); + G.channel_desc_out = channel_desc_out; + G.num_tx = num_tx_antennas; + G.num_rx = num_rx_antennas; + G.cur = 0; + G.last_step_applied = -1; + + /* Require explicit paths in cirdb_select_opts_t */ + AssertFatal(sel != NULL, "cirdb_select_opts_t must not be NULL\n"); + AssertFatal(sel->bin_path && sel->bin_path[0], "CIRDB requires explicit bin_path in cirdb_select_opts_t\n"); + AssertFatal(sel->yaml_path && sel->yaml_path[0], "CIRDB requires explicit yaml_path in cirdb_select_opts_t\n"); + + const char *use_bin = sel->bin_path; + const char *sidecar = sel->yaml_path; + + LOG_I(HW, "CIRDB: using database %s with metadata %s\n", use_bin, sidecar); + + G.fh = fopen(use_bin, "rb"); + AssertFatal(G.fh != NULL, "Cannot open CIRDB database file '%s': %s\n", use_bin, strerror(errno)); + + /* Selection request */ + int want_model_id = sel->want_model_id; + AssertFatal(want_model_id >= 0 && want_model_id <= 4, + "Invalid model_id=%d (valid: 0..4)\n", + want_model_id); + float want_ds = (sel && sel->want_ds_ns > 0) ? sel->want_ds_ns : -1.0f; + float want_speed = (sel && sel->want_speed_mps > 0) ? sel->want_speed_mps : -1.0f; + + LOG_I(HW, + "CIRDB: Searching for entry with model_id=%d, antennas=%dx%d, DS=%.1fns, speed=%.1fm/s\n", + want_model_id, + G.num_tx, + G.num_rx, + want_ds, + want_speed); + + cirdb_entry_meta_t m = (cirdb_entry_meta_t){0}; + cirdb_select_req_t req = {.want_model_id = want_model_id, + .want_tx = G.num_tx, + .want_rx = G.num_rx, + .want_ds_ns = want_ds, + .want_speed_mps = want_speed, + .allow_shape_swap = 0, + .w_ds = 1.0f, + .w_speed = 0.2f, + .yaml_path = sidecar}; + + int ok = cirdb_yaml_select(&req, &m); + + // STRICT validation: fail if no match found + AssertFatal(ok > 0, + "No suitable CIR entry found in YAML %s\n" + " Requested: model_id=%d, antennas=%dx%d, DS=%.1fns, speed=%.1fm/s\n" + " Possible causes:\n" + " - No entry with model_id=%d in database\n" + " - No entry with antenna configuration %dx%d in database\n" + " Check the YAML file for available entries\n", + sidecar, + want_model_id, + G.num_tx, + G.num_rx, + want_ds, + want_speed, + want_model_id, + G.num_tx, + G.num_rx); + + // STRICT validation: antenna dimensions must match exactly + AssertFatal(m.n_tx == G.num_tx && m.n_rx == G.num_rx, + "CIRDB antenna mismatch: requested %dx%d but selected entry has %dx%d\n" + " This should never happen - indicates a bug in selection logic\n", + G.num_tx, + G.num_rx, + m.n_tx, + m.n_rx); + + // STRICT validation: model_id must match if specified + if (want_model_id >= 0) { + AssertFatal(m.model_id == want_model_id, + "CIRDB model_id mismatch: requested %d but selected entry has %d\n" + " This should never happen - indicates a bug in selection logic\n", + want_model_id, + m.model_id); + } + + // FLEXIBLE: warn if delay spread doesn't match exactly (using closest available) + if (want_ds > 0 && dabs(m.ds_ns - want_ds) > 0.1) { + LOG_W(HW, "CIRDB: Requested delay spread %.1f ns not available in database, using closest match: %.1f ns\n", want_ds, m.ds_ns); + } + + // FLEXIBLE: warn if speed doesn't match exactly (using closest available) + if (want_speed > 0 && dabs(m.speed_mps - want_speed) > 0.1) { + LOG_W(HW, + "CIRDB: Requested speed %.1f m/s not available in database, using closest match: %.1f m/s\n", + want_speed, + m.speed_mps); + } + + G.model_id = m.model_id; + G.n_tx = m.n_tx; + G.n_rx = m.n_rx; + G.L_full = m.L; + G.S = m.S; + G.fs_hz = m.fs_hz; + G.snapshot_dt_s = m.snapshot_dt_s; + G.ds_ns = m.ds_ns; + G.speed_mps = m.speed_mps; + G.sel_offset = m.offset_bytes; + G.sel_nbytes = m.nbytes; + G.L_out = (m.L <= MAX_L_PUBLISH ? (uint32_t)m.L : MAX_L_PUBLISH); + G.snap_idx = 0; + + G.snapshot_tmp_bytes = (size_t)G.n_tx * G.n_rx * G.L_full * sizeof(struct complexf); + G.snapshot_tmp = calloc_or_fail(1, G.snapshot_tmp_bytes); + + for (int i = 0; i < NUM_TAPS_BUFFERS; i++) { + size_t blob_cf = (size_t)G.n_tx * G.n_rx * G.L_out; + G.bufs[i].taps_blob = calloc_or_fail(blob_cf, sizeof(struct complexf)); + G.bufs[i].ch = calloc_or_fail(1, sizeof(channel_desc_t)); + G.bufs[i].ch->ch_ps = calloc_or_fail(G.n_rx * G.n_tx, sizeof(struct complexf *)); + G.bufs[i].ch->nb_tx = G.n_tx; + G.bufs[i].ch->nb_rx = G.n_rx; + } + + if (G.S > 0) { + load_snapshot_and_publish(0); + } + + LOG_I(HW, + "CIRDB: Selected entry - model=%d DS=%.3fns shape=%ux%u L=%u/%u S=%u fs=%.0f dt=%.6fs speed=%.3fm/s\n", + G.model_id, + G.ds_ns, + G.n_tx, + G.n_rx, + G.L_out, + G.L_full, + G.S, + G.fs_hz, + G.snapshot_dt_s, + G.speed_mps); +} + +void cirdb_update(uint64_t ns_since_start) +{ + if (!G.channel_desc_out || !*G.channel_desc_out) + return; + if (G.S <= 0) + return; + + double dt_s = (G.snapshot_dt_s > 0.0 ? G.snapshot_dt_s : 0.5); + if (dt_s <= 0.0) + dt_s = 0.5; + + double steps_f = (ns_since_start * 1e-9) / dt_s; + int64_t step = (int64_t)(steps_f >= 0.0 ? steps_f : 0.0); + + if (step != G.last_step_applied) { + uint32_t s = (uint32_t)(step % G.S); + load_snapshot_and_publish(s); + G.snap_idx = s; + G.last_step_applied = step; + } +} + +void cirdb_stop(void) +{ + for (int i = 0; i < NUM_TAPS_BUFFERS; i++) { + free(G.bufs[i].taps_blob); + if (G.bufs[i].ch) { + free(G.bufs[i].ch->ch_ps); + free(G.bufs[i].ch); + } + } + free(G.snapshot_tmp); + if (G.fh) + fclose(G.fh); + memset(&G, 0, sizeof(G)); + G.last_step_applied = -1; +} diff --git a/radio/vrtsim/cirdb_provider.h b/radio/vrtsim/cirdb_provider.h new file mode 100644 index 0000000000..42e49f3c5e --- /dev/null +++ b/radio/vrtsim/cirdb_provider.h @@ -0,0 +1,60 @@ +/* + * 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 OAI_VRTSIM_CIRDB_PROVIDER_H +#define OAI_VRTSIM_CIRDB_PROVIDER_H + +#include +#include "SIMULATION/TOOLS/sim.h" + +/* + Selection and timing options: + + want_model_id: TDL family index. Allowed: 0 (TDL-A), 1 (TDL-B), 2 (TDL-C), 3 (TDL-D), 4 (TDL-E). + Set < 0 to express no preference. + want_ds_ns: RMS delay spread in ns. Typical values: 30, 100, 300. Nonnegative float. + Set < 0 to express no preference. + want_speed_mps: UE speed in m/s. Typical: walking ≈ 1.5, urban 3..10, vehicular 10..30. + Set < 0 to express no preference. +*/ +typedef struct { + const char *yaml_path; /* optional absolute path to vrtsim.yaml */ + const char *bin_path; /* optional absolute path to cir_db.bin */ + + int want_model_id; /* 0..4, or <0 for no preference */ + float want_ds_ns; /* nonnegative, or <0 for no preference */ + float want_speed_mps; /* nonnegative, or <0 for no preference */ +} cirdb_select_opts_t; + +/* Initialize provider and publish snapshot 0 through channel_desc_out. */ +void cirdb_connect(int id, + int num_tx_antennas, + int num_rx_antennas, + const cirdb_select_opts_t *sel, + channel_desc_t **channel_desc_out); + +/* Advance snapshot selection based on elapsed nanoseconds since the start. */ +void cirdb_update(uint64_t ns_since_start); + +/* Tear down and free resources. */ +void cirdb_stop(void); + +#endif /* OAI_VRTSIM_CIRDB_PROVIDER_H */ diff --git a/radio/vrtsim/cirdb_yaml.cpp b/radio/vrtsim/cirdb_yaml.cpp new file mode 100644 index 0000000000..aa95b09505 --- /dev/null +++ b/radio/vrtsim/cirdb_yaml.cpp @@ -0,0 +1,152 @@ +/* + * 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 "cirdb_yaml.h" +#include +#include +#include +extern "C" { +#include "common/utils/LOG/log.h" +} + +static inline double dabs(double x) +{ + return x < 0 ? -x : x; +} + +int cirdb_yaml_scan(const char *yaml_path) +{ + YAML::Node root; + try { + root = YAML::LoadFile(yaml_path); + } catch (const YAML::Exception &e) { + LOG_E(HW, "CIRDB YAML parse error in %s: %s\n", yaml_path, e.what()); + return -1; + } + auto ents = root["entries"]; + if (!ents || !ents.IsSequence()) { + LOG_E(HW, "CIRDB YAML: missing or invalid 'entries' in %s\n", yaml_path); + return -1; + } + + int n = static_cast(ents.size()); + LOG_I(HW, "CIRDB YAML: %d entries in %s\n", n, yaml_path); + return n; +} + +int cirdb_yaml_select(const cirdb_select_req_t *req, cirdb_entry_meta_t *out) +{ + if (!req || !out || !req->yaml_path) { + LOG_E(HW, "CIRDB YAML: invalid arguments to cirdb_yaml_select\n"); + return -1; + } + + YAML::Node root; + try { + root = YAML::LoadFile(req->yaml_path); + } catch (const YAML::Exception &e) { + LOG_E(HW, "CIRDB YAML parse error in %s: %s\n", req->yaml_path, e.what()); + return -1; + } + + auto ents = root["entries"]; + if (!ents || !ents.IsSequence()) { + LOG_E(HW, "CIRDB YAML: missing or invalid 'entries' in %s\n", req->yaml_path); + return -1; + } + + bool found = false; + double best_cost = 1e300; + cirdb_entry_meta_t best{}; + + // Determine if specific parameters are required + bool want_specific_model = (req->want_model_id >= 0); + bool want_specific_antennas = (req->want_tx > 0 && req->want_rx > 0); + + for (std::size_t i = 0; i < ents.size(); ++i) { + const YAML::Node e = ents[i]; + int pair_order = e["pair_order"].as(0); + + int model_id = e["model_id"].as(-1); + + // STRICT: model_id must match if specified + if (want_specific_model && model_id != req->want_model_id) + continue; + + int n_tx = e["n_tx"].as(0); + int n_rx = e["n_rx"].as(0); + + // STRICT: antenna dimensions must match exactly if specified + bool shape_ok = true; + if (want_specific_antennas) { + shape_ok = (n_tx == req->want_tx && n_rx == req->want_rx); + bool shape_swap_ok = req->allow_shape_swap ? (n_tx == req->want_rx && n_rx == req->want_tx) : false; + shape_ok = shape_ok || shape_swap_ok; + + if (!shape_ok) + continue; // Skip entries with wrong antenna configuration + } + + // STRICT: antenna dimensions must match exactly if specified + if (want_specific_antennas) { + if (n_tx != req->want_tx || n_rx != req->want_rx) + continue; // Skip entries with wrong antenna configuration + } + + float ds_ns = e["ds_ns"].as(0.0f); + float sp = e["speed_mps"].as(0.0f); + + // FLEXIBLE: delay spread and speed - find closest match using cost function + double cds = 0.0; + double csp = 0.0; + + if (req->want_ds_ns > 0) + cds = dabs((double)ds_ns - (double)req->want_ds_ns); + + if (req->want_speed_mps > 0) + csp = dabs((double)sp - (double)req->want_speed_mps); + + double cost = (req->w_ds > 0 ? req->w_ds : 1.0) * cds + (req->w_speed > 0 ? req->w_speed : 0.2) * csp; + + if (!found || cost < best_cost) { + best_cost = cost; + found = true; + + best.model_id = model_id; + best.n_tx = n_tx; + best.n_rx = n_rx; + best.L = e["L"].as(0); + best.S = e["S"].as(0); + best.fs_hz = e["fs_hz"].as(0.0); + best.snapshot_dt_s = e["snapshot_dt_s"].as(0.0); + best.ds_ns = ds_ns; + best.speed_mps = sp; + best.pair_order = pair_order; + best.offset_bytes = e["offset_bytes"].as(0); + best.nbytes = e["nbytes"].as(0); + } + } + + if (!found) + return 0; + *out = best; + return 1; +} diff --git a/radio/vrtsim/cirdb_yaml.h b/radio/vrtsim/cirdb_yaml.h new file mode 100644 index 0000000000..e3cf61f8cf --- /dev/null +++ b/radio/vrtsim/cirdb_yaml.h @@ -0,0 +1,61 @@ +/* + * 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 + */ + +#pragma once +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct { + int want_model_id; // 0..4 for TDL-A..E, or -1 for any + int want_tx; // exact match + int want_rx; // exact match + float want_ds_ns; // nearest + float want_speed_mps; // nearest + int allow_shape_swap; // allow TX/RX swap when matching + float w_ds; // DS distance weight + float w_speed; // speed distance weight + const char *yaml_path; // sidecar path +} cirdb_select_req_t; + +typedef struct { + int model_id; + int n_tx; + int n_rx; + int L; // taps per link in file + int S; // snapshots + double fs_hz; + double snapshot_dt_s; + float ds_ns; + float speed_mps; + int pair_order; + uint64_t offset_bytes; + uint64_t nbytes; +} cirdb_entry_meta_t; + +int cirdb_yaml_select(const cirdb_select_req_t *req, cirdb_entry_meta_t *out); +int cirdb_yaml_scan(const char *yaml_path); + +#ifdef __cplusplus +} +#endif diff --git a/radio/vrtsim/vrtsim.c b/radio/vrtsim/vrtsim.c index f2f9cfef1d..89e3b431aa 100644 --- a/radio/vrtsim/vrtsim.c +++ b/radio/vrtsim/vrtsim.c @@ -51,6 +51,8 @@ #include "noise_device.h" #include "simde/x86/avx512.h" #include "taps_client.h" +#include "cirdb_provider.h" +#include "cirdb_yaml.h" // Simulator role typedef enum { ROLE_SERVER = 1, ROLE_CLIENT } role; @@ -79,6 +81,15 @@ typedef enum { ROLE_SERVER = 1, ROLE_CLIENT } role; {"chanmod", "Enable channel modelling", 0, .iptr = &vrtsim_state->chanmod, .defintval = 0, TYPE_INT, 0}, \ {"taps-socket", TAPS_SOCKET_HLP, 0, .strptr = &vrtsim_state->taps_socket, .defstrval = NULL, TYPE_STRING, 0}, \ {"client-num-rx-antennas", CLIENT_NUM_RX_HLP, 0, .iptr = &vrtsim_state->client_num_rx_antennas, .defintval = 1, TYPE_INT, 0}, \ + /* CIR DB enable and paths */ \ + {"cirdb", "Use CIR database for channel taps (1 yes, 0 no)", 0, .iptr = &vrtsim_state->use_cirdb, .defintval = 0, TYPE_INT, 0}, \ + {"cirdb-path", "Directory that holds vrtsim.yaml and cir_db.bin", 0, .strptr = &vrtsim_state->cirdb_path, .defstrval = NULL, TYPE_STRING, 0}, \ + {"cirdb_yaml", "Absolute path to CIR DB YAML file (optional, overrides cirdb-path)", 0, .strptr = &vrtsim_state->cirdb_yaml, .defstrval = NULL, TYPE_STRING, 0}, \ + {"cirdb_file", "Absolute path to CIR DB binary file (optional, overrides cirdb-path)", 0, .strptr = &vrtsim_state->cirdb_file, .defstrval = NULL, TYPE_STRING, 0}, \ + /* CIR DB selection knobs */ \ + {"cirdb_model_id", "Preferred TDL model id 0..4", 0, .iptr = &vrtsim_state->cirdb_model_id, .defintval = 0, TYPE_INT, 0}, \ + {"cirdb_ds_ns", "Desired RMS delay spread in ns", 0, .dblptr = &vrtsim_state->cirdb_ds_ns, .defdblval = 10.0, TYPE_DOUBLE, 0}, \ + {"cirdb_speed_mps", "Desired speed in m/s", 0, .dblptr = &vrtsim_state->cirdb_speed_mps, .defdblval = 1.5, TYPE_DOUBLE, 0}, \ }; // clang-format on @@ -126,6 +137,14 @@ typedef struct { char *taps_socket; int client_num_rx_antennas; struct timespec start_ts; + /* CIR DB state */ + int use_cirdb; + char *cirdb_path; + char *cirdb_yaml; + char *cirdb_file; + int cirdb_model_id; + double cirdb_ds_ns; + double cirdb_speed_mps; } vrtsim_state_t; static void histogram_add(histogram_t *histogram, double diff) @@ -166,7 +185,7 @@ static void load_channel_model(vrtsim_state_t *vrtsim_state) char *model_name = vrtsim_state->role == ROLE_CLIENT ? "client_tx_channel_model" : "server_tx_channel_model"; vrtsim_state->channel_desc = find_channel_desc_fromname(model_name); AssertFatal(vrtsim_state->channel_desc != NULL, - "Could not find model name %s. Make sure it is present in the config file", + "Could not find model name %s. Make sure it is present in the config file\n", model_name); LOG_I(HW, "Channel model %s parameters: path_loss_dB=%.2f, nb_tx=%d, nb_rx=%d, channel_length=%d\n", @@ -201,6 +220,9 @@ static void vrtsim_readconfig(vrtsim_state_t *vrtsim_state) AssertFatal(false, "Invalid configuration: Build with OAI_VRTSIM_TAPS_CLIENT to use taps socket\n"); } #endif + if (vrtsim_state->use_cirdb) { + LOG_A(HW, "VRTSIM: CIR DB is enabled at runtime\n"); + } } static void *vrtsim_timing_job(void *arg) @@ -305,7 +327,7 @@ static int vrtsim_connect(openair0_device_t *device) // Handle channel modelling after number of RX antennas are known int num_tx_stats = 1; - if (vrtsim_state->chanmod || vrtsim_state->taps_socket) { + if (vrtsim_state->chanmod || vrtsim_state->taps_socket || vrtsim_state->use_cirdb) { vrtsim_state->channel_modelling_actors = calloc_or_fail(vrtsim_state->peer_info.num_rx_antennas, sizeof(Actor_t)); for (int i = 0; i < vrtsim_state->peer_info.num_rx_antennas; i++) { init_actor(&vrtsim_state->channel_modelling_actors[i], "chanmod", -1); @@ -316,6 +338,53 @@ static int vrtsim_connect(openair0_device_t *device) device->openair0_cfg[0].tx_num_channels, vrtsim_state->peer_info.num_rx_antennas, &vrtsim_state->channel_desc); + } else if (vrtsim_state->use_cirdb) { + const char *yaml_path = NULL; + const char *bin_path = NULL; + + if (vrtsim_state->cirdb_yaml && vrtsim_state->cirdb_yaml[0]) + yaml_path = vrtsim_state->cirdb_yaml; + if (vrtsim_state->cirdb_file && vrtsim_state->cirdb_file[0]) + bin_path = vrtsim_state->cirdb_file; + + char yaml_buf[PATH_MAX]; + char bin_buf[PATH_MAX]; + + if (!yaml_path || !bin_path) { + const char *base = vrtsim_state->cirdb_path; + if (base && base[0]) { + if (!yaml_path) { + snprintf(yaml_buf, sizeof(yaml_buf), "%s/%s", base, "vrtsim.yaml"); + yaml_path = yaml_buf; + } + if (!bin_path) { + snprintf(bin_buf, sizeof(bin_buf), "%s/%s", base, "cir_db.bin"); + bin_path = bin_buf; + } + } + } + + cirdb_select_opts_t sel = (cirdb_select_opts_t){0}; + sel.yaml_path = yaml_path; + sel.bin_path = bin_path; + AssertFatal(vrtsim_state->cirdb_model_id >= 0 && vrtsim_state->cirdb_model_id <= 4, + "Invalid cirdb_model_id=%d (valid: 0..4)\n", + vrtsim_state->cirdb_model_id); + sel.want_model_id = vrtsim_state->cirdb_model_id; + sel.want_ds_ns = (float)(vrtsim_state->cirdb_ds_ns > 0.0 ? vrtsim_state->cirdb_ds_ns : -1.0); + sel.want_speed_mps = (float)(vrtsim_state->cirdb_speed_mps > 0.0 ? vrtsim_state->cirdb_speed_mps : -1.0); + + LOG_A(HW, + "VRTSIM: CIR DB select yaml='%s' bin='%s'\n", + sel.yaml_path ? sel.yaml_path : "(auto)", + sel.bin_path ? sel.bin_path : "(auto)"); + + cirdb_connect(0, + device->openair0_cfg[0].tx_num_channels, + vrtsim_state->peer_info.num_rx_antennas, + &sel, + &vrtsim_state->channel_desc); + LOG_A(HW, "VRTSIM: channel taps via CIR DB\n"); } else { load_channel_model(vrtsim_state); } @@ -392,9 +461,15 @@ static void perform_channel_modelling(void *arg) return; } + if (vrtsim_state->use_cirdb) { + double seconds = (double)channel_modelling_args->timestamp / vrtsim_state->sample_rate; + uint64_t elapsed_ns = (uint64_t)(seconds * 1e9 + 0.5); + cirdb_update(elapsed_ns); + } + cf_t channel_impulse_response[nb_tx_ant][channel_desc->channel_length]; cf_t *channel_impulse_response_p[nb_tx_ant]; - if (!vrtsim_state->taps_socket) { + if (!vrtsim_state->taps_socket && !vrtsim_state->use_cirdb) { const float pathloss_linear = powf(10, channel_desc->path_loss_dB / 20.0); // Convert channel impulse response to float + apply pathloss for (int aatx = 0; aatx < nb_tx_ant; aatx++) { @@ -543,7 +618,7 @@ static int vrtsim_write(openair0_device_t *device, AssertFatal(timestamp >= 0, "Timestamp must be non-negative, got %ld\n", timestamp); timestamp -= device->openair0_cfg->command_line_sample_advance; vrtsim_state_t *vrtsim_state = (vrtsim_state_t *)device->priv; - bool channel_modelling = vrtsim_state->chanmod || vrtsim_state->taps_socket; + bool channel_modelling = vrtsim_state->chanmod || vrtsim_state->taps_socket || vrtsim_state->use_cirdb; return channel_modelling ? vrtsim_write_with_chanmod(vrtsim_state, timestamp, samplesVoid, nsamps, nbAnt, flags) : vrtsim_write_internal(vrtsim_state, timestamp, (c16_t *)samplesVoid[0], nsamps, 0, flags, 0); } @@ -609,7 +684,7 @@ static void vrtsim_end(openair0_device_t *device) } tx_timing_t *tx_timing = vrtsim_state->tx_timing; - if (vrtsim_state->chanmod || vrtsim_state->taps_socket) { + if (vrtsim_state->chanmod || vrtsim_state->taps_socket || vrtsim_state->use_cirdb) { for (int i = 0; i < vrtsim_state->peer_info.num_rx_antennas; i++) { shutdown_actor(&vrtsim_state->channel_modelling_actors[i]); } @@ -623,7 +698,9 @@ static void vrtsim_end(openair0_device_t *device) } tx_timing->average_tx_budget /= vrtsim_state->peer_info.num_rx_antennas; free_noise_device(); - if (vrtsim_state->taps_socket) { + if (vrtsim_state->use_cirdb) { + cirdb_stop(); + } else if (vrtsim_state->taps_socket) { taps_client_stop(); } } @@ -711,8 +788,9 @@ __attribute__((__visibility__("default"))) int device_init(openair0_device_t *de vrtsim_state->tx_num_channels = openair0_cfg->tx_num_channels; vrtsim_state->rx_num_channels = openair0_cfg->rx_num_channels; - if (vrtsim_state->chanmod || vrtsim_state->taps_socket) { + if (vrtsim_state->chanmod || vrtsim_state->taps_socket || vrtsim_state->use_cirdb) { init_channelmod(); + randominit(); int noise_power_dBFS = get_noise_power_dBFS(); int16_t noise_power = noise_power_dBFS == INVALID_DBFS_VALUE ? 0 : (int16_t)(32767.0 / powf(10.0, .05 * -noise_power_dBFS)); LOG_A(HW, "VRTSIM: Noise power %d sample value\n", noise_power); diff --git a/targets/PROJECTS/GENERIC-NR-5GC/CONF/gnb.sa.band78.fr1.106PRB.usrpb210.conf b/targets/PROJECTS/GENERIC-NR-5GC/CONF/gnb.sa.band78.fr1.106PRB.usrpb210.conf index 7f750d7259..ec036d30ca 100644 --- a/targets/PROJECTS/GENERIC-NR-5GC/CONF/gnb.sa.band78.fr1.106PRB.usrpb210.conf +++ b/targets/PROJECTS/GENERIC-NR-5GC/CONF/gnb.sa.band78.fr1.106PRB.usrpb210.conf @@ -248,3 +248,15 @@ e2_agent = { #sm_dir = "/path/where/the/SMs/are/located/" sm_dir = "/usr/local/lib/flexric/" }; + +vrtsim : +{ + #role = "server"; # "server" or "client" + #client-num-rx-antennas = 1; # publish expected client RX count + #cirdb = 1; # Enable CIR DB taps + # cirdb_yaml = "/path/to/vrtsim.yaml"; + # cirdb_file = "/path/to/cir_db.bin"; + cirdb_model_id = 0; # 0..4 for 3GPP TDL-A..E + cirdb_ds_ns = 10.0; # RMS delay spread in ns (10, 30) + cirdb_speed_mps = 1.5; # UE speed in m/s (1.5, 30) +}; From a707094196ae6b3e52a32440b77af2ff38fd8c5f Mon Sep 17 00:00:00 2001 From: Merkebu Girmay Date: Fri, 16 Jan 2026 22:56:14 -0800 Subject: [PATCH 060/100] vrtsim: implement batched TX writes for channel modelling - Move buffer allocation per-batch - Convert and write per batch - Fix saved_samples gap handling bug --- radio/vrtsim/vrtsim.c | 48 +++++++++++++++++++++++-------------------- 1 file changed, 26 insertions(+), 22 deletions(-) diff --git a/radio/vrtsim/vrtsim.c b/radio/vrtsim/vrtsim.c index 89e3b431aa..9a0cd779dc 100644 --- a/radio/vrtsim/vrtsim.c +++ b/radio/vrtsim/vrtsim.c @@ -450,11 +450,6 @@ static void perform_channel_modelling(void *arg) int nb_tx_ant = channel_modelling_args->nbAnt; c16_t **input_samples = (c16_t **)channel_modelling_args->samples; - int aligned_nsamps = ceil_mod(nsamps, (512 / 8) / sizeof(cf_t)); - cf_t samples[aligned_nsamps] __attribute__((aligned(64))); - // Apply noise from global settings - get_noise_vector((float *)samples, nsamps * 2); - channel_desc_t *channel_desc = vrtsim_state->channel_desc; if (channel_desc == NULL) { @@ -488,16 +483,25 @@ static void perform_channel_modelling(void *arg) } for (int batch_index = 0; batch_index < channel_modelling_args->num_batches; batch_index++) { - int start_sample = batch_index * channel_modelling_args->batch_size; - int num_samples = min(channel_modelling_args->batch_size, nsamps - start_sample); + const int start_sample = batch_index * channel_modelling_args->batch_size; + const int num_samples = min(channel_modelling_args->batch_size, nsamps - start_sample); if (start_sample >= nsamps) { break; } + + const int aligned_batch = ceil_mod(num_samples, (512 / 8) / sizeof(cf_t)); + cf_t samples[aligned_batch] __attribute__((aligned(64))); + memset(samples, 0, sizeof(samples)); + + // Apply noise from global settings (only valid samples) + get_noise_vector((float *)samples, num_samples * 2); + for (int aatx = 0; aatx < nb_tx_ant; aatx++) { - for (int i = start_sample; i < start_sample + num_samples; i++) { - cf_t *impulse_response = channel_impulse_response_p[aatx]; + cf_t *impulse_response = channel_impulse_response_p[aatx]; + for (int i = 0; i < num_samples; i++) { + const int gi = start_sample + i; for (int l = 0; l < channel_desc->channel_length; l++) { - int idx = i - l; + const int idx = gi - l; // TODO: Use AVX2 for this c16_t tx_input = idx >= 0 ? input_samples[aatx][idx] : channel_modelling_args->saved_samples[aatx][SAVED_SAMPLES_LEN + idx]; @@ -507,31 +511,32 @@ static void perform_channel_modelling(void *arg) } } - // Convert to c16_t - c16_t samples_out[aligned_nsamps] __attribute__((aligned(64))); - #if defined(__AVX512F__) - for (int i = 0; i < aligned_nsamps / 8; i++) { + // Convert to c16_t (only once per batch) + c16_t samples_out[aligned_batch] __attribute__((aligned(64))); +#if defined(__AVX512F__) + for (int i = 0; i < aligned_batch / 8; i++) { simde__m512 *in = (simde__m512 *)&samples[i * 8]; simde__m256i *out = (simde__m256i *)&samples_out[i * 8]; *out = simde_mm512_cvtsepi32_epi16(simde_mm512_cvtps_epi32(*in)); } - #elif defined(__AVX2__) - for (int i = 0; i < aligned_nsamps / 4; i++) { +#elif defined(__AVX2__) + for (int i = 0; i < aligned_batch / 4; i++) { simde__m256 *in = (simde__m256 *)&samples[i * 4]; simde__m128i *out = (simde__m128i *)&samples_out[i * 4]; *out = simde_mm256_cvtsepi32_epi16(simde_mm256_cvtps_epi32(*in)); } - #else - for (int i = 0; i < nsamps; i++) { +#else + for (int i = 0; i < num_samples; i++) { samples_out[i].r = lroundf(samples[i].r); samples_out[i].i = lroundf(samples[i].i); } - #endif +#endif + // Write the batch as a single contiguous transfer vrtsim_write_internal(channel_modelling_args->vrtsim_state, - channel_modelling_args->timestamp, + channel_modelling_args->timestamp + start_sample, samples_out, - channel_modelling_args->nsamps, + num_samples, aarx, channel_modelling_args->flags, aarx); @@ -582,7 +587,6 @@ static int vrtsim_write_with_chanmod(vrtsim_state_t *vrtsim_state, for (int aatx = 0; aatx < nbAnt; aatx++) memcpy(&args->saved_samples[aatx][0], saved_samples[aatx], sizeof(c16_t) * SAVED_SAMPLES_LEN); } - memcpy(args->saved_samples, saved_samples, sizeof(saved_samples)); pushNotifiedFIFO(&vrtsim_state->channel_modelling_actors[aarx].fifo, task); } From f15994da14c316f2cbcff61b4596df88c7859eae Mon Sep 17 00:00:00 2001 From: francescomani Date: Thu, 29 Jan 2026 12:39:14 +0100 Subject: [PATCH 061/100] improvement in config file parameter to set analog beamforming --- .../conf_files/gnb.sa.band257.u3.66prb.rfsim.conf | 2 +- doc/MAC/mac-usage.md | 2 +- doc/analog_beamforming.md | 4 ++-- openair2/GNB_APP/MACRLC_nr_paramdef.h | 7 +++++-- openair2/GNB_APP/gnb_config.c | 12 ++++-------- ...a.band77.273prb.fhi72.4x4-das-benetel650_650.conf | 2 +- 6 files changed, 14 insertions(+), 15 deletions(-) diff --git a/ci-scripts/conf_files/gnb.sa.band257.u3.66prb.rfsim.conf b/ci-scripts/conf_files/gnb.sa.band257.u3.66prb.rfsim.conf index 0c1ca120d5..569d887cbb 100644 --- a/ci-scripts/conf_files/gnb.sa.band257.u3.66prb.rfsim.conf +++ b/ci-scripts/conf_files/gnb.sa.band257.u3.66prb.rfsim.conf @@ -175,7 +175,7 @@ MACRLCs = ({ tr_n_preference = "local_RRC"; pusch_TargetSNRx10 = 200; pucch_TargetSNRx10 = 200; - set_analog_beamforming = 2; + set_analog_beamforming = "lophy"; beam_duration = 1; beams_per_period = 1; beam_weights = [0]; // single SSB -> one analog beam diff --git a/doc/MAC/mac-usage.md b/doc/MAC/mac-usage.md index 8dd15f845a..94c813d7ac 100644 --- a/doc/MAC/mac-usage.md +++ b/doc/MAC/mac-usage.md @@ -229,7 +229,7 @@ In the `MACRLCs` section of the gNB/DU configuration file: (see `ulsch_max_frame_inactivity`) or after scheduling request (SR) * `identity_precoding_matrix` (default 0=false): flag to enable to use only the identity precoding matrix in DL precoding -* `set_analog_beamforming` (default 0=false): flag to enable analog +* `set_analog_beamforming` (default "none"): parameter to enable analog beamforming (for more information [`analog_beamforming.md`](../analog_beamforming.md)) * `beam_duration` (default 1): duration/number of consecutive slots for a given set of beams, depending on hardware switching performance diff --git a/doc/analog_beamforming.md b/doc/analog_beamforming.md index b71b6376c3..bb0e7a1009 100644 --- a/doc/analog_beamforming.md +++ b/doc/analog_beamforming.md @@ -19,12 +19,12 @@ Analog beamforming implementation also allows to enable distributed antenna syst A set of parameters in configuration files controls the implementation of analog beamforming and instructs the scheduler on how to behave in such scenarios. Since most notably this technique in 5G is employed in FR2, the configuration file example currently available is a RFsim one for band 261. [Config file example](../ci-scripts/conf_files/gnb.sa.band257.u3.66prb.rfsim.conf) In the `MACRLC` section of configuration files, there are three new parameters: `set_analog_beamforming`, `beam_duration` and `beams_per_period`. The explanation of these parameters is here provided: -- `set_analog_beamforming` can be set a value equal to 1 or 2 to activate or 0 to desactivate analog beamforming (default value is 0) +- `set_analog_beamforming` can be set to "none", "preconfigured" or "lophy" (default value is "none") - `beam_duration` is the number of slots (currently minimum duration of a beam) the scheduler is tied to a beam (default value is 1) - `beams_per_period` is the number of concurrent beams the RU can handle in the beam duration (default value is 1) - `beam_weights` is a vector field containing the set of beam indices to be provided by the OAI L1 to the RU is also required. In current implementation, the number of beam indices should be equal to the number of SSBs transmitted -Setting analog beamforming to 1 or 2 changes the way FAPI beam index is treated. By setting 1, we instruct L1 to look up in Hi-PHY preconfigured DBM beam index. By setting 2, we instruct L2 to directly signal to Lo-PHY the beam index (e.g. over 7.2x fronthaul). +Setting analog beamforming to "preconfigured" or "lophy" changes the way FAPI beam index is treated. By setting "preconfigured", we instruct L1 to look up in Hi-PHY preconfigured DBM beam index. By setting "lophy", we instruct L2 to directly signal to Lo-PHY the beam index (e.g. over 7.2x fronthaul). DAS is enabled by setting to 1 the parameter `enable_das` in the L1 section of the configuration file. In case of DAS enabled, the field `beam_weights` in `MACRLC` section can be omitted and the number of beams per period equals the total number of beams. diff --git a/openair2/GNB_APP/MACRLC_nr_paramdef.h b/openair2/GNB_APP/MACRLC_nr_paramdef.h index 6bd02cf74a..cb46836cb4 100644 --- a/openair2/GNB_APP/MACRLC_nr_paramdef.h +++ b/openair2/GNB_APP/MACRLC_nr_paramdef.h @@ -143,7 +143,7 @@ {CONFIG_STRING_MACRLC_LOCAL_N_ADDRESS_F1U, NULL, 0, .strptr=NULL, .defstrval=NULL, TYPE_STRING, 0}, \ {CONFIG_STRING_MACRLC_TRANSPORT_S_SHM_PREFIX, NULL, 0, .strptr=NULL, .defstrval="nvipc", TYPE_STRING, 0}, \ {CONFIG_STRING_MACRLC_TRANSPORT_S_POLL_CORE, NULL, 0, .i8ptr=NULL, .defintval=-1, TYPE_INT8, 0}, \ - {CONFIG_STRING_MACRLC_ANALOG_BEAMFORMING, HLP_MACRLC_AB, 0, .u8ptr=NULL, .defintval=0, TYPE_UINT8, 0}, \ + {CONFIG_STRING_MACRLC_ANALOG_BEAMFORMING, HLP_MACRLC_AB, 0, .strptr=NULL, .defstrval="none", TYPE_STRING, 0}, \ {CONFIG_STRING_MACRLC_BEAM_DURATION, HLP_MACRLC_BEAM_DURATION, 0, .u8ptr=NULL, .defintval=1, TYPE_UINT8, 0}, \ {CONFIG_STRING_MACRLC_BEAMS_PERIOD, HLP_MACRLC_BEAMS_PERIOD, 0, .u8ptr=NULL, .defintval=1, TYPE_UINT8, 0}, \ {CONFIG_STRING_MACRLC_BEAM_WEIGHTS_LIST, NULL, 0, .iptr=NULL, .defintarrayval=0, TYPE_INTARRAY,0}, \ @@ -236,7 +236,10 @@ { .s5 = { NULL } }, \ { .s5 = { NULL } }, \ { .s2 = { NULL } }, \ - { .s5 = { NULL } }, \ + { .s3a = { config_checkstr_assign_integer, \ + {"none", "preconfigured", "lophy"}, \ + {NO_BEAM_MODE, PRECONFIGURED_BEAM_IDX, LOPHY_BEAM_IDX}, \ + 3 } }, \ { .s5 = { NULL } }, \ { .s5 = { NULL } }, \ { .s5 = { NULL } }, \ diff --git a/openair2/GNB_APP/gnb_config.c b/openair2/GNB_APP/gnb_config.c index 0b3845f5b9..6ba1949fba 100644 --- a/openair2/GNB_APP/gnb_config.c +++ b/openair2/GNB_APP/gnb_config.c @@ -1623,9 +1623,9 @@ void RCconfig_nr_macrlc(configmodule_interface_t *cfg) LOG_I(NR_PHY, "Copying %d blacklisted PRB to L1 context\n", num_ulprbbl); memcpy(RC.nrmac[j]->ulprbbl, prbbl, MAX_BWP_SIZE * sizeof(prbbl[0])); } - int ab = *MacRLC_ParamList.paramarray[j][MACRLC_ANALOG_BEAMFORMING_IDX].u8ptr; - if (ab > 0) { - if (ab == 1) + RC.nrmac[j]->beam_info.beam_mode = config_get_processedint(cfg, &MacRLC_ParamList.paramarray[j][MACRLC_ANALOG_BEAMFORMING_IDX]); + if (RC.nrmac[j]->beam_info.beam_mode != NO_BEAM_MODE) { + if (RC.nrmac[j]->beam_info.beam_mode == PRECONFIGURED_BEAM_IDX) AssertFatal(NFAPI_MODE == NFAPI_MONOLITHIC, "Analog beamforming only supported for monolithic scenario\n"); NR_beam_info_t *beam_info = &RC.nrmac[j]->beam_info; int beams_per_period = *MacRLC_ParamList.paramarray[j][MACRLC_ANALOG_BEAMS_PERIOD_IDX].u8ptr; @@ -1633,10 +1633,6 @@ void RCconfig_nr_macrlc(configmodule_interface_t *cfg) beam_info->beam_duration = *MacRLC_ParamList.paramarray[j][MACRLC_ANALOG_BEAM_DURATION_IDX].u8ptr; beam_info->beams_per_period = beams_per_period; beam_info->beam_allocation_size = -1; // to be initialized once we have information on frame configuration - // TODO: Indicate this to MAC via FAPI TLV 0x0164. - beam_info->beam_mode = ab == 1 ? PRECONFIGURED_BEAM_IDX : LOPHY_BEAM_IDX; - } else { - RC.nrmac[j]->beam_info.beam_mode = NO_BEAM_MODE; } // TODO config_isparamset doesn't seem to work for array types, checking numelt instead int n = MacRLC_ParamList.paramarray[j][MACRLC_BEAMWEIGHTS_IDX].numelt; @@ -1646,7 +1642,7 @@ void RCconfig_nr_macrlc(configmodule_interface_t *cfg) AssertFatal(*(L1_ParamList.paramarray[j][L1_ANALOG_DAS].uptr) == 0, "No need to set beam weights in case of DAS\n"); } int num_beam = n; - if (!ab) { + if (RC.nrmac[j]->beam_info.beam_mode == PRECONFIGURED_BEAM_IDX) { AssertFatal(n % num_tx == 0, "Error! Number of beam input needs to be multiple of TX antennas\n"); num_beam = n / num_tx; } diff --git a/targets/PROJECTS/GENERIC-NR-5GC/CONF/gnb-du.sa.band77.273prb.fhi72.4x4-das-benetel650_650.conf b/targets/PROJECTS/GENERIC-NR-5GC/CONF/gnb-du.sa.band77.273prb.fhi72.4x4-das-benetel650_650.conf index 4a5553006a..a16d392ac4 100644 --- a/targets/PROJECTS/GENERIC-NR-5GC/CONF/gnb-du.sa.band77.273prb.fhi72.4x4-das-benetel650_650.conf +++ b/targets/PROJECTS/GENERIC-NR-5GC/CONF/gnb-du.sa.band77.273prb.fhi72.4x4-das-benetel650_650.conf @@ -181,7 +181,7 @@ MACRLCs = ( pusch_FailureThres = 100; ul_max_mcs = 28; min_grant_prb = 1; - set_analog_beamforming = 1; + set_analog_beamforming = "lophy"; beam_duration = 1; beams_per_period = 2; } From b9df7eb84f3772ef88be206ce0ef4fb654f4e09c Mon Sep 17 00:00:00 2001 From: Nika Ghaderi Date: Thu, 5 Feb 2026 17:09:20 +0000 Subject: [PATCH 062/100] add markdown report for gpu benchmark --- doc/README.md | 1 + .../SIMULATION/TOOLS/DOC/gpu_acceleration.md | 212 ++++++++++++++++++ 2 files changed, 213 insertions(+) create mode 100644 openair1/SIMULATION/TOOLS/DOC/gpu_acceleration.md diff --git a/doc/README.md b/doc/README.md index 022174a4e7..589bf44833 100644 --- a/doc/README.md +++ b/doc/README.md @@ -44,6 +44,7 @@ There is some general information in the [OpenAirInterface Gitlab Wiki](https:// - [How to run a 4G setup using L1 simulator](./L1SIM.md) _Note: we recommend the RFsimulator_ - [How to use the L2 simulator](./L2NFAPI.md) - [How to use the OAI channel simulator](../openair1/SIMULATION/TOOLS/DOC/channel_simulation.md) +- [How to use GPU-accelerated channel simulation](../openair1/SIMULATION/TOOLS/DOC/gpu_acceleration.md) - [How to use multiple BWPs](./RUN_NR_multiple_BWPs.md) - [How to run OAI-VNF and OAI-PNF](./nfapi.md): how to run the FAPI/nFAPI split, including some general remarks on FAPI/nFAPI. diff --git a/openair1/SIMULATION/TOOLS/DOC/gpu_acceleration.md b/openair1/SIMULATION/TOOLS/DOC/gpu_acceleration.md new file mode 100644 index 0000000000..a34d289bb0 --- /dev/null +++ b/openair1/SIMULATION/TOOLS/DOC/gpu_acceleration.md @@ -0,0 +1,212 @@ +# GPU-Accelerated Channel Simulation + +[[_TOC_]] + +## Overview + +This document describes the CUDA-based GPU acceleration pipeline for the OAI channel simulation. The primary goal of this feature is to offload the computationally intensive `multipath_channel` convolution and `add_noise` functions from the CPU to the GPU. This overcomes existing performance bottlenecks and enables large-scale, real-time physical layer simulation that is not feasible with the CPU-based models. + +The result is a complete, high-performance pipeline integrated into the OAI simulators. The feature is supported by a new benchmark suite created to validate correctness and analyze performance across a wide range of channel models and hardware configurations. + +## Implementation + +This feature is implemented across a set of new CUDA source files and integrated into the existing OAI simulation framework. + +### Multipath Channel Convolution (`multipath_channel.cu`) + +The core of the channel simulation is the multipath convolution. On the CPU, this is a sequential process where each output sample is calculated by iterating through all channel taps and accumulating the results. To parallelize this on the GPU, we assign one CUDA thread to calculate one output sample. + +The key to making this parallel approach efficient is the use of a tiled convolution pattern with `__shared__` memory. Instead of having every thread read from slow global memory for each step of the convolution, threads in a block cooperate to pre-fetch all the data they will need into the on-chip shared memory. + +To further optimize this, the responsibility for handling boundary conditions (the "halo" of past samples) has been moved from the GPU kernel to the host. The host code now prepares a larger input buffer that is **pre-padded** with `channel_length - 1` zeros before the actual signal data. This allows the kernel to be significantly simplified by removing the previous boundary-checking logic. + +```cpp +// Snippet from the multipath_channel_kernel in multipath_channel.cu + +// Step 1: Each thread cooperates to load a tile of the pre-padded signal +// into fast shared memory. +const int padding_len = channel_length - 1; +const int padded_num_samples = num_samples + padding_len; + +for (int k = tid; k < shared_mem_size; k += blockDim.x) { + int load_idx = block_start_idx + k; + // Unconditional load from the padded buffer + int interleaved_idx = 2 * (j * padded_num_samples + load_idx); + tx_shared[k] = make_float2(tx_sig[interleaved_idx], tx_sig[interleaved_idx + 1]); +} +__syncthreads(); +``` + +Once the data is staged in fast shared memory, each thread can perform the full convolution for its assigned output sample. The inner loop iterates through the `channel_length` taps, and all reads of the input signal (`tx_sample`) come from the `tx_shared` buffer. + +```cpp +// Snippet from multipath_channel_kernel in multipath_channel.cu +// Step 2: Each thread performs convolution using the data in shared memory + for (int l = 0; l < channel_length; l++) { + // Read the historical transmit sample from the shared memory + float2 tx_sample = tx_shared[tid + (channel_length - 1) - l]; + + // Get the corresponding channel weight from global memory + int chan_link_idx = ii + (j * nb_rx); + float2 chan_weight = d_channel_coeffs[chan_link_idx * channel_length + l]; + + // Perform the complex multiply-accumulate + rx_tmp = complex_add(rx_tmp, complex_mul(tx_sample, chan_weight)); + } + __syncthreads(); +} + +// Write the final result from the thread's private register to global memory... +``` + +### Noise Generation (`phase_noise.cu`) + +After the multipath convolution, the next stage of the pipeline is to add realistic noise to the signal. This includes both Additive White Gaussian Noise (AWGN) and phase noise. + +The CPU implementation performs this in a simple loop. For each sample of the signal, it calls a standard C library function to generate pseudo-random numbers, scales them appropriately, and adds them to the sample. This process is sequential and computationally inexpensive for a single signal but becomes a bottleneck when simulating many channels that all require high-quality random numbers simultaneously. + +The GPU port follows the same parallelization strategy as the multipath kernel: one CUDA thread is assigned to process one single sample. The main challenge is generating millions of high-quality random numbers in parallel. To achieve this, the implementation relies on NVIDIA's **`cuRAND`** library. + +The `cuRAND` library is stateful, meaning each thread needs its own unique generator "state" to produce an independent sequence of random numbers. In our implementation, a pool of `curandState_t` objects is initialized once in global memory. When the noise kernel is launched, each thread is assigned a unique state from this pool. The thread uses its state to generate random numbers, and then writes the updated state back to global memory for the next use. + +A simplified view of this logic within the `add_noise_and_phase_noise_kernel` is shown below: + +```cpp +// Simplified snippet from add_noise_and_phase_noise_kernel in phase_noise.cu +__global__ void add_noise_and_phase_noise_kernel(...) +{ + // Each thread calculates its unique sample index 'i' and antenna index 'ii'... + + // Load this thread's unique random number generator state + curandState_t local_state = states[ii * num_samples + i]; + + // Generate random numbers using the local state + // Generates two normal-distributed numbers for AWGN (real and imaginary) + float2 awgn = curand_normal2(&local_state); + // Generates one normal-distributed number for phase noise + float phase_error = curand_normal(&local_state) * pn_std_dev; + + // Apply noise to the input signal from the previous kernel stage, and then aplu phase_error + + // Write the updated state back to global memory for the next simulation run + states[ii * num_samples + i]= local_state; + + // Clamp the final floating-point value and store it as a short integer + output_sig[ii * num_samples + i]= make_short2(...); +} +``` + +### Pipeline Orchestration (`channel_pipeline.cu`) + +The `channel_pipeline.cu` file serves two primary functions: providing an interoperable API and orchestrating kernel execution. + +Its first purpose is to act as an abstraction layer between the host C/C++ code of the OAI simulator and the CUDA C++ device code. It exposes a C-style Application Programming Interface (API) using `extern "C"`, which allows the standard C-based components to call the GPU pipeline without needing to handle CUDA-specific syntax or complexities. + +Its second purpose is to orchestrate the execution of the individual CUDA kernels. It defines the logical flow of the channel simulation by launching the `multipath_channel_kernel` and `add_noise_and_phase_noise_kernel` in sequence and managing the data dependencies between them. + +The functions within this file, such as `run_channel_pipeline_cuda`, implement a consistent workflow. Upon being called, they first cast the generic `void*` pointers received from the C host into their specific CUDA data types. The implementation then contains the conditional logic, via preprocessor directives, to support the various memory models. Following data setup, the functions define the CUDA execution grid and block dimensions and launch the two kernels sequentially. The output of the multipath kernel is directed to an intermediate buffer, which serves as the input for the subsequent noise kernel. Finally, the functions manage synchronization with the host and handle the transfer of the final results from the GPU back to host memory. + +*The external API of these pipeline functions has been streamlined. The function signatures no longer require a separate `tx_sig_interleaved` parameter, instead relying on a single, pre-padded host buffer (`h_tx_sig_pinned`) as the authoritative source for the input signal, which simplifies the data flow from the simulators.* + +--- + +## Memory Management Models + +To provide flexibility and enable performance testing on different hardware architectures, the project now supports three distinct memory management models. The desired model is selected at build time by passing a flag to the CMake build command. + +### ATS Hybrid Model (`-DUSE_ATS_MEMORY=ON`) + +This is a hybrid approach that leverages Address Translation Services (ATS), a hardware feature allowing the GPU to directly access host-allocated memory. In our implementation, the large input signal buffer is allocated in host memory using `malloc`. The GPU kernel is then able to read this data directly from the host, eliminating the need for an initial Host-to-Device `cudaMemcpy` for that buffer. Intermediate and output buffers are still allocated on the device, and the final result is copied back to the host explicitly. Due to its better performance compared to the other methods, it's also the default behaviour when no flag is specified. + +### Unified Memory Model (`-DUSE_UNIFIED_MEMORY=ON`) + +This model simplifies memory management by creating a single, managed memory space that is accessible to both the CPU and the GPU. Memory is allocated once using `cudaMallocManaged`. The CUDA runtime and driver then automatically handle the migration of data to whichever processor is accessing it (e.g., moving data to the GPU's memory when a kernel is launched). This removes the need for most explicit `cudaMemcpy` calls. While the CUDA driver automatically migrates data on-demand, the implementation includes further optimizations to guide this process for better performance. `cudaMemAdvise` is used to provide performance hints to the driver, such as setting a buffer's preferred location to the GPU or marking it as mostly read-only. Additionally, `cudaMemPrefetchAsync` is also used to explicitly move data to the GPU's memory ahead of time. This ensures the data is already local when a kernel begins execution, which helps to hide the latency of the data migration. + +### Explicit Copy Model + +This is the traditional CUDA programming model. The host and device have separate and distinct memory spaces. The programmer is responsible for all data management. Memory must be allocated on the device using `cudaMalloc`, and data must be manually transferred between the host and device using explicit `cudaMemcpy` calls before and after a kernel launch. This configuration can be run with other flags turned off by using `-DUSE_ATS_MEMORY=OFF` and `-DUSE_UNIFIED_MEMORY=OFF`. + +----- + +## Project Integration + +To ensure the feature is modular, the integration into the OAI project was handled at both the build system and source code levels. All of the new CUDA source files (`channel_pipeline.cu`, `multipath_channel.cu`, `phase_noise.cu`) are compiled into a single static library named `oai_cuda_lib`. This is defined in the main `CMakeLists.txt` file and is controlled by the `CUDA_ENABLE` CMake option, which is disabled by default. When this option is activated (`-DCUDA_ENABLE=ON`), the build system compiles the CUDA library and also defines a global `ENABLE_CUDA` preprocessor macro that is visible to the rest of the project. + +--- + +## Benchmark and Analysis Suite + +To validate the correctness, measure performance, and analyze the scalability of the GPU pipeline, a dedicated suite of tests was developed. The source code for these benchmarks is located in the `openair1/PHY/TOOLS/tests/` directory. This suite includes both focused unitary tests for individual components and a comprehensive benchmark for the end-to-end pipeline, which is the primary tool for performance evaluation. + +### `test_channel_scalability.c` + +This program is the main tool for evaluating the end-to-end GPU channel simulation pipeline. It's designed to be highly configurable, allowing for the simulation of a wide variety of workloads and execution strategies. + +#### Configuration and Usage + +The benchmark is configured at runtime using a set of command-line flags. + +| Flag | Long Version | Argument | Description | +| :--- | :--- | :--- | :--- | +| `-c` | `--num-channels` | `` | Sets the number of independent channels to simulate. | +| `-t` | `--nb-tx` | `` | Sets the number of transmit antennas. | +| `-r` | `--nb-rx` | `` | Sets the number of receive antennas. | +| `-s` | `--num-samples` | `` | Sets the number of samples per signal. | +| `-l` | `--ch-len` | `` | Sets the length of the channel impulse response. | +| `-n` | `--trials` | `` | Sets the number of times each test is run for averaging. | +| `-S` | `--sum-outputs` | (none) | Enables interference simulation by summing channel outputs. (Using unique input signals) | +| `-m` | `--mode` | `` | Sets the GPU execution mode: `serial`, `stream`, or `batch`. | +| `-h` | `--help` | (none) | Displays the help message with all options. | + +#### Execution Modes + +The `-m` or `--mode` flag controls the strategy used to execute the pipeline for multiple channels: + + * **`serial`**: This mode processes each channel sequentially. It loops through all channels and makes a synchronous call to `run_channel_pipeline_cuda` for each one, waiting for it to complete before starting the next. + * **`stream`**: This mode is designed to test concurrency. It launches all channel simulations asynchronously into separate CUDA streams by calling `run_channel_pipeline_cuda_streamed` in a loop. This allows the GPU hardware to overlap the execution of many kernels. + * **`batch`**: It aggregates the data for all channels on the host and makes a single call to `run_channel_pipeline_cuda_batched`. This launches one massive, unified kernel to process all channels at once, minimizing CPU overhead and maximizing GPU throughput. + +### `test_channel_simulation.c` + +The `test_channel_simulation.c` program was one of the initial benchmarks created for this project. Its primary function is to perform a direct performance comparison between the full, sequential CPU-based channel pipeline and the original synchronous GPU-based pipeline implemented in `run_channel_pipeline_cuda`. It served as an early-stage tool to validate the end-to-end functionality of the basic GPU port and to obtain initial speedup measurements before more advanced optimizations were developed. + +### `test_multipath.c` + +The `test_multipath.c` program is a unitary test designed specifically to validate the `multipath_channel_cuda` function in isolation. The key feature of this test is the `verify_results` function, which performs a sample-by-sample comparison of the output arrays from both versions and calculates the Mean Squared Error (MSE). If the error is below a predefined tolerance, the test is marked as *"PASSED"* in the final report, confirming that the GPU kernel is producing numerically correct results. + +### `test_noise.c` + +Similar to the multipath test, `test_noise.c` is a unitary test that focuses on a single component: the `add_noise_cuda` function. Its purpose is to validate the correctness of the GPU-based noise generation and measure its performance against the equivalent CPU version. + +----- + +### Direct CPU vs. GPU Speedup + +To provide a clear, baseline performance comparison, the `test_channel_simulation` benchmark was run. This tool directly compares the execution time of the sequential, `float`-based CPU pipeline against the baseline synchronous GPU pipeline (`run_channel_pipeline_cuda`). The following tests were executed on a GH200 server using the ATS memory model. The results demonstrate the performance gains achieved by offloading the channel simulation to the GPU. + +| Channel Type | MIMO Config | Signal Length | CPU Pipeline (µs) | GPU Pipeline (µs) | Overall Speedup | +| :--- | :--- | :--- | :--- | :--- | :--- | +Short Channel | 1x1 | 30720 | 654.12 | 45.60 | 14.34 x +Short Channel | 2x2 | 30720 | 2145.07 | 57.48 | 37.32 x +Short Channel | 4x4 | 30720 | 7611.24 | 84.20 | 90.40 x +Short Channel | 8x8 | 30720 | 28190.40 | 148.07 | 190.39 x +Short Channel | 1x1 | 61440 | 1305.85 | 54.16 | 24.11 x +Short Channel | 2x2 | 61440 | 4290.31 | 80.34 | 53.40 x +Short Channel | 4x4 | 61440 | 15264.63 | 136.62 | 111.73 x +Short Channel | 8x8 | 61440 | 56561.05 | 255.82 | 221.10 x +Short Channel | 1x1 | 122880 | 2617.06 | 79.33 | 32.99 x +Short Channel | 2x2 | 122880 | 8585.16 | 129.69 | 66.20 x +Short Channel | 4x4 | 122880 | 30543.07 | 233.36 | 130.88 x +Short Channel | 8x8 | 122880 | 113185.64 | 468.10 | 241.80 x +Long Channel | 1x1 | 30720 | 1068.25 | 44.37 | 24.08 x +Long Channel | 2x2 | 30720 | 3795.86 | 58.86 | 64.49 x +Long Channel | 4x4 | 30720 | 14209.40 | 89.08 | 159.51 x +Long Channel | 8x8 | 30720 | 54579.90 | 164.72 | 331.36 x +Long Channel | 1x1 | 61440 | 2139.73 | 56.02 | 38.20 x +Long Channel | 2x2 | 61440 | 7608.75 | 83.18 | 91.47 x +Long Channel | 4x4 | 61440 | 28463.66 | 145.80 | 195.23 x +Long Channel | 8x8 | 61440 | 109785.85 | 289.96 | 378.62 x +Long Channel | 1x1 | 122880 | 4267.25 | 80.65 | 52.91 x +Long Channel | 2x2 | 122880 | 15209.56 | 135.12 | 112.56 x +Long Channel | 4x4 | 122880 | 56951.08 | 249.47 | 228.29 x +Long Channel | 8x8 | 122880 | 222368.44 | 524.97 | 423.59 x From 66e55acca06587fb44e5bdc7d0644bda4fd64daf Mon Sep 17 00:00:00 2001 From: Jaroslava Fiedlerova Date: Tue, 10 Feb 2026 09:02:47 +0000 Subject: [PATCH 063/100] CI: Add new pipeline to CI doc --- doc/TESTBenches.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/doc/TESTBenches.md b/doc/TESTBenches.md index 42558449b3..a180cae60d 100644 --- a/doc/TESTBenches.md +++ b/doc/TESTBenches.md @@ -234,6 +234,10 @@ information on how the images are built. - groot (CU+DU0) + B210, rocket (DU1) + B210, raspix (Quectel RM520N UE), OAI CN5G - OpenShift cluster for CN deployment - Attenuator (mini circuits RC4DAT-6G-60) - controlled from rocket +- [RAN-Channel-Simulation](https://jenkins-oai.eurecom.fr/job/RAN-Channel-Simulation/) + ~5G-NR + - gracehopper1-oai + - run channel simulation on CPU and GPU using test_channel_scalability ### RAN-CI-NSA-Trigger From 7b86f61383bd25e1a0818ccbd7562b8878f4665a Mon Sep 17 00:00:00 2001 From: Eric Mittag Date: Fri, 30 Jan 2026 16:30:28 +0100 Subject: [PATCH 064/100] Add support for Debian 12 in build_oai --- cmake_targets/tools/build_helper | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/cmake_targets/tools/build_helper b/cmake_targets/tools/build_helper index eeffc9d4f1..246f43400a 100755 --- a/cmake_targets/tools/build_helper +++ b/cmake_targets/tools/build_helper @@ -107,6 +107,7 @@ check_supported_distribution() { "ubuntu22.04") return 0 ;; "ubuntu20.04") return 0 ;; "debian11") return 0 ;; + "debian12") return 0 ;; "fedora41") return 0 ;; "fedora40") return 0 ;; "rhel8.2") return 0 ;; @@ -453,7 +454,7 @@ check_install_additional_tools (){ local optional_packages="" if [[ "$OS_BASEDISTRO" == "debian" ]]; then case "$(get_distribution_release)" in - "ubuntu20.04" | "ubuntu22.04" | "debian11" ) + "ubuntu20.04" | "ubuntu22.04" | "debian11" | "debian12" ) optional_packages="python3 python3-pip python3-dev python3-scipy python3-matplotlib python3-pyroute2 universal-ctags" ;; esac @@ -496,7 +497,7 @@ check_install_oai_software() { if [[ "$OS_BASEDISTRO" == "debian" ]]; then $SUDO apt install -y software-properties-common case "$(get_distribution_release)" in - "debian11") + "debian11" | "debian12" ) specific_packages="libz-dev" ;; esac From 15ee75980b5777ff24f72e6176a0c22df055fdbc Mon Sep 17 00:00:00 2001 From: Eric Mittag Date: Fri, 30 Jan 2026 16:31:14 +0100 Subject: [PATCH 065/100] Set ARMv8.2-A (Cortex-A53) specific compile options --- CMakeLists.txt | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 14d90a4c3b..7affa46b57 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -158,6 +158,8 @@ elseif (CMAKE_SYSTEM_PROCESSOR STREQUAL "aarch64") set(C_FLAGS_PROCESSOR "${C_FLAGS_PROCESSOR} -gdwarf-2 -mcpu=neoverse-n1 -lgcc -lrt") elseif (CPUPART MATCHES "0xd49") # Neoverse-N2 set(C_FLAGS_PROCESSOR "${C_FLAGS_PROCESSOR} -gdwarf-2 -mcpu=neoverse-n2 -ftree-vectorize -lgcc -lrt") + elseif (CPUPART MATCHES "0xd03") # Cortex-A53 + set(C_FLAGS_PROCESSOR "${C_FLAGS_PROCESSOR} -gdwarf-2 -mcpu=cortex-a53 -lgcc -lrt") else () set(C_FLAGS_PROCESSOR "${C_FLAGS_PROCESSOR} -gdwarf-2 -mcpu=native -lgcc -lrt") endif() @@ -166,7 +168,12 @@ else() endif() if (NOT CROSS_COMPILE) - set(C_FLAGS_PROCESSOR "${C_FLAGS_PROCESSOR} -march=native") + if (CPUPART MATCHES "0xd03") + # Cortex-A53 does not support ARMv8.2-A extensions + set(C_FLAGS_PROCESSOR "${C_FLAGS_PROCESSOR} -march=armv8-a+simd") + else() + set(C_FLAGS_PROCESSOR "${C_FLAGS_PROCESSOR} -march=native") + endif() else () set(C_FLAGS_PROCESSOR "${C_FLAGS_PROCESSOR} -march=armv8.2-a") endif() From d7ea319b591df1562ddddefbcc09e1fac7a9b3bb Mon Sep 17 00:00:00 2001 From: Eric Mittag Date: Fri, 30 Jan 2026 16:32:17 +0100 Subject: [PATCH 066/100] cmac0_prec128()/rotate_cpx_vector(): Add ARMv8.0-A fallback The current code, on ARM, uses ARMv8.1-A RDM instructions. Add a fallback for processors that do not have this instruction set. --- openair1/PHY/MODULATION/nr_modulation.c | 18 +++++++++++++++++- openair1/PHY/TOOLS/tools_defs.h | 17 ++++++++++++++++- 2 files changed, 33 insertions(+), 2 deletions(-) diff --git a/openair1/PHY/MODULATION/nr_modulation.c b/openair1/PHY/MODULATION/nr_modulation.c index eb4dc50bc9..562c43895b 100644 --- a/openair1/PHY/MODULATION/nr_modulation.c +++ b/openair1/PHY/MODULATION/nr_modulation.c @@ -760,6 +760,8 @@ static inline __attribute__((always_inline)) int16x8_t cmac0_prec128(int16x8_t x // int16x8_t xr = vuzp1q_s16(x, x); // even lanes int16x8_t xi = vuzp2q_s16(x, x); // odd lanes +#ifdef __ARM_FEATURE_QRDMX + // ARMv8.1-A: Use RDM instructions // real = ar*br - ai*bi (Q15 scaling via high-half doubling muls) int16x8_t real = vqdmulhq_s16(xr, wr); // ≈ round((2*xr*wr)/2^16) real = vqrdmlshq_s16(real, xi, wi); // real -= round((2*xi*wi)/2^16) @@ -767,7 +769,21 @@ static inline __attribute__((always_inline)) int16x8_t cmac0_prec128(int16x8_t x // imag = ar*bi + ai*br int16x8_t imag = vqdmulhq_s16(xr, wi); imag = vqrdmlahq_s16(imag, xi, wr); // imag += round((2*xi*wr)/2^16) - // +#else + // ARMv8.0-A fallback: Use standard 32-bit multiply + int32x4_t real_lo = vmull_s16(vget_low_s16(xr), vget_low_s16(wr)); + int32x4_t real_hi = vmull_s16(vget_high_s16(xr), vget_high_s16(wr)); + real_lo = vmlsl_s16(real_lo, vget_low_s16(xi), vget_low_s16(wi)); + real_hi = vmlsl_s16(real_hi, vget_high_s16(xi), vget_high_s16(wi)); + + int32x4_t imag_lo = vmull_s16(vget_low_s16(xr), vget_low_s16(wi)); + int32x4_t imag_hi = vmull_s16(vget_high_s16(xr), vget_high_s16(wi)); + imag_lo = vmlal_s16(imag_lo, vget_low_s16(xi), vget_low_s16(wr)); + imag_hi = vmlal_s16(imag_hi, vget_high_s16(xi), vget_high_s16(wr)); + + int16x8_t real = vcombine_s16(vqrshrn_n_s32(real_lo, 15), vqrshrn_n_s32(real_hi, 15)); + int16x8_t imag = vcombine_s16(vqrshrn_n_s32(imag_lo, 15), vqrshrn_n_s32(imag_hi, 15)); +#endif // Re-interleave [real, imag] int16x8x2_t produ = vzipq_s16(real, imag); return produ.val[0]; diff --git a/openair1/PHY/TOOLS/tools_defs.h b/openair1/PHY/TOOLS/tools_defs.h index 36fc496545..40f6ca46f6 100644 --- a/openair1/PHY/TOOLS/tools_defs.h +++ b/openair1/PHY/TOOLS/tools_defs.h @@ -816,7 +816,8 @@ static inline void rotate_cpx_vector(const c16_t *const x, const c16_t *const al // Split interleaved -> separate real/imag int16x8_t br = vuzp1q_s16(x_128[i], x_128[i]); int16x8_t bi = vuzp2q_s16(x_128[i], x_128[i]); - +#ifdef __ARM_FEATURE_QRDMX + // ARMv8.1-A: Use RDM instructions (rounding doubling multiply) // Start with the two “diagonal” products using high-half, doubling, sat: // x = round( (2*ar*br) / 2^16 ), y = round( (2*ar*bi) / 2^16 ) int16x8_t real = vqdmulhq_s16(ar, br); @@ -827,7 +828,21 @@ static inline void rotate_cpx_vector(const c16_t *const x, const c16_t *const al // imag += round( (2*ai*br) / 2^16 ) imag = vqrdmlahq_s16(imag, ai, br); +#else + // ARMv8.0-A fallback: Use standard 32-bit multiply + int32x4_t real_lo = vmull_s16(vget_low_s16(ar), vget_low_s16(br)); + int32x4_t real_hi = vmull_s16(vget_high_s16(ar), vget_high_s16(br)); + real_lo = vmlsl_s16(real_lo, vget_low_s16(ai), vget_low_s16(bi)); + real_hi = vmlsl_s16(real_hi, vget_high_s16(ai), vget_high_s16(bi)); + int32x4_t imag_lo = vmull_s16(vget_low_s16(ar), vget_low_s16(bi)); + int32x4_t imag_hi = vmull_s16(vget_high_s16(ar), vget_high_s16(bi)); + imag_lo = vmlal_s16(imag_lo, vget_low_s16(ai), vget_low_s16(br)); + imag_hi = vmlal_s16(imag_hi, vget_high_s16(ai), vget_high_s16(br)); + + int16x8_t real = vcombine_s16(vqrshrn_n_s32(real_lo, 15), vqrshrn_n_s32(real_hi, 15)); + int16x8_t imag = vcombine_s16(vqrshrn_n_s32(imag_lo, 15), vqrshrn_n_s32(imag_hi, 15)); +#endif // Re-interleave [real, imag] int16x8x2_t z = vzipq_s16(real, imag); From 8410ecac29e910f74f0df0dd73a5c938c1166054 Mon Sep 17 00:00:00 2001 From: francescomani Date: Tue, 10 Feb 2026 16:26:24 +0100 Subject: [PATCH 067/100] remove VCD_SIGNAL_DUMPER_DUMP_FUNCTION_BY_NAME from gNB code --- executables/nr-ru.c | 74 ++++++++------------------------------------- 1 file changed, 12 insertions(+), 62 deletions(-) diff --git a/executables/nr-ru.c b/executables/nr-ru.c index f8d44ff4c0..397f940745 100644 --- a/executables/nr-ru.c +++ b/executables/nr-ru.c @@ -48,7 +48,6 @@ #include "common/utils/LOG/log.h" #include "common/utils/time_manager/time_manager.h" -#include "common/utils/LOG/vcd_signal_dumper.h" #include /* these variables have to be defined before including ENB_APP/enb_paramdef.h and GNB_APP/gnb_paramdef.h */ @@ -244,9 +243,8 @@ int connect_rau(RU_t *ru) { /* Southbound Fronthaul functions, RCC/RAU */ // southbound IF5 fronthaul for 16-bit OAI format -void fh_if5_south_out(RU_t *ru, int frame, int slot, uint64_t timestamp) { - if (ru == RC.ru[0]) - VCD_SIGNAL_DUMPER_DUMP_VARIABLE_BY_NAME(VCD_SIGNAL_DUMPER_VARIABLES_TRX_TST, ru->proc.timestamp_tx & 0xffffffff); +void fh_if5_south_out(RU_t *ru, int frame, int slot, uint64_t timestamp) +{ int offset = get_samples_slot_timestamp(ru->nr_frame_parms, slot); void *buffs[ru->nb_tx]; for (int aid = 0; aid < ru->nb_tx; aid++) @@ -266,10 +264,8 @@ void fh_if5_south_out(RU_t *ru, int frame, int slot, uint64_t timestamp) { } // southbound IF4p5 fronthaul -void fh_if4p5_south_out(RU_t *ru, int frame, int slot, uint64_t timestamp) { - if (ru == RC.ru[0]) - VCD_SIGNAL_DUMPER_DUMP_VARIABLE_BY_NAME(VCD_SIGNAL_DUMPER_VARIABLES_TRX_TST, ru->proc.timestamp_tx & 0xffffffff); - +void fh_if4p5_south_out(RU_t *ru, int frame, int slot, uint64_t timestamp) +{ LOG_D(PHY,"Sending IF4p5 for frame %d subframe %d\n",ru->proc.frame_tx,ru->proc.tti_tx); if ((nr_slot_select(&ru->config, ru->proc.frame_tx, ru->proc.tti_tx) & NR_DOWNLINK_SLOT) > 0) @@ -281,12 +277,10 @@ void fh_if4p5_south_out(RU_t *ru, int frame, int slot, uint64_t timestamp) { // Synchronous if5 from south -void fh_if5_south_in(RU_t *ru, - int *frame, - int *tti) { +void fh_if5_south_in(RU_t *ru, int *frame, int *tti) +{ NR_DL_FRAME_PARMS *fp = ru->nr_frame_parms; RU_proc_t *proc = &ru->proc; - VCD_SIGNAL_DUMPER_DUMP_FUNCTION_BY_NAME( VCD_SIGNAL_DUMPER_FUNCTIONS_RECV_IF5, 1 ); start_meas(&ru->rx_fhaul); ru->ifdevice.trx_read_func2(&ru->ifdevice, &proc->timestamp_rx, NULL, get_samples_per_slot(*tti, fp)); @@ -339,9 +333,6 @@ void fh_if5_south_in(RU_t *ru, proc->first_rx, ru->rx_fhaul.p_time / (cpu_freq_GHz * 1000.0), rxmeas.tv_nsec); - VCD_SIGNAL_DUMPER_DUMP_VARIABLE_BY_NAME( VCD_SIGNAL_DUMPER_VARIABLES_TRX_TS, proc->timestamp_rx&0xffffffff ); - VCD_SIGNAL_DUMPER_DUMP_FUNCTION_BY_NAME( VCD_SIGNAL_DUMPER_FUNCTIONS_RECV_IF5, 0 ); - } // Synchronous if4p5 from south @@ -398,15 +389,7 @@ void fh_if4p5_south_in(RU_t *ru, *slot = proc->tti_rx; } - if (ru == RC.ru[0]) { - VCD_SIGNAL_DUMPER_DUMP_VARIABLE_BY_NAME( VCD_SIGNAL_DUMPER_VARIABLES_FRAME_NUMBER_RX0_RU, f ); - VCD_SIGNAL_DUMPER_DUMP_VARIABLE_BY_NAME( VCD_SIGNAL_DUMPER_VARIABLES_TTI_NUMBER_RX0_RU, sl); - VCD_SIGNAL_DUMPER_DUMP_VARIABLE_BY_NAME( VCD_SIGNAL_DUMPER_VARIABLES_FRAME_NUMBER_TX0_RU, proc->frame_tx ); - VCD_SIGNAL_DUMPER_DUMP_VARIABLE_BY_NAME( VCD_SIGNAL_DUMPER_VARIABLES_TTI_NUMBER_TX0_RU, proc->tti_tx ); - } - proc->symbol_mask[proc->tti_rx] = 0; - VCD_SIGNAL_DUMPER_DUMP_VARIABLE_BY_NAME( VCD_SIGNAL_DUMPER_VARIABLES_TRX_TS, proc->timestamp_rx&0xffffffff ); LOG_D(PHY,"RU %d: fh_if4p5_south_in sleeping ...\n",ru->idx); } @@ -448,7 +431,8 @@ void fh_if4p5_south_asynch_in(RU_t *ru,int *frame,int *slot) { // RRU IF4p5 TX fronthaul receiver. Assumes an if_device on input and if or rf device on output // receives one subframe's worth of IF4p5 OFDM symbols and OFDM modulates -void fh_if4p5_north_in(RU_t *ru,int *frame,int *slot) { +void fh_if4p5_north_in(RU_t *ru,int *frame,int *slot) +{ uint32_t symbol_number=0; uint32_t symbol_mask, symbol_mask_full; uint16_t packet_type; @@ -461,12 +445,6 @@ void fh_if4p5_north_in(RU_t *ru,int *frame,int *slot) { recv_IF4p5(ru, frame, slot, &packet_type, &symbol_number); symbol_mask = symbol_mask | (1<frame_tx_unwrap) * fp->samples_per_subframe * 10 + get_samples_slot_timestamp(fp, slot_tx); LOG_D(PHY, "RU %d/%d TST %lu, frame %d, subframe %d\n", ru->idx, 0, proc->timestamp_tx, frame_tx, slot_tx); - // dump VCD output for first RU in list - if (ru == RC.ru[0]) { - VCD_SIGNAL_DUMPER_DUMP_VARIABLE_BY_NAME( VCD_SIGNAL_DUMPER_VARIABLES_FRAME_NUMBER_TX0_RU, frame_tx ); - VCD_SIGNAL_DUMPER_DUMP_VARIABLE_BY_NAME( VCD_SIGNAL_DUMPER_VARIABLES_TTI_NUMBER_TX0_RU, slot_tx ); - } - if (ru->feptx_ofdm) ru->feptx_ofdm(ru, frame_tx, slot_tx); @@ -538,18 +510,16 @@ void fh_if4p5_north_asynch_in(RU_t *ru,int *frame,int *slot) { ru->fh_south_out(ru, frame_tx, slot_tx, proc->timestamp_tx); } -void fh_if5_north_out(RU_t *ru) { +void fh_if5_north_out(RU_t *ru) +{ /// **** send_IF5 of rxdata to BBU **** /// - VCD_SIGNAL_DUMPER_DUMP_FUNCTION_BY_NAME( VCD_SIGNAL_DUMPER_FUNCTIONS_SEND_IF5, 1 ); AssertFatal(1 == 0, "Shouldn't get here\n"); } // RRU IF4p5 northbound interface (RX) -void fh_if4p5_north_out(RU_t *ru) { +void fh_if4p5_north_out(RU_t *ru) +{ RU_proc_t *proc=&ru->proc; - if (ru->idx == 0) - VCD_SIGNAL_DUMPER_DUMP_VARIABLE_BY_NAME(VCD_SIGNAL_DUMPER_VARIABLES_TTI_NUMBER_RX0_RU, proc->tti_rx); - start_meas(&ru->tx_fhaul); send_IF4p5(ru, proc->frame_rx, proc->tti_rx, IF4p5_PULFFT); stop_meas(&ru->tx_fhaul); @@ -569,15 +539,12 @@ static void rx_rf(RU_t *ru, int *frame, int *slot) for (int i = 0; i < nb; i++) rxp[i] = (void *)&ru->common.rxdata[i][get_samples_slot_timestamp(fp, *slot)]; - VCD_SIGNAL_DUMPER_DUMP_FUNCTION_BY_NAME(VCD_SIGNAL_DUMPER_FUNCTIONS_TRX_READ, 1); openair0_timestamp_t old_ts = proc->timestamp_rx; LOG_D(PHY,"Reading %d samples for slot %d (%p)\n", samples_per_slot, *slot, rxp[0]); openair0_timestamp_t ts; unsigned int rxs; rxs = ru->rfdevice.trx_read_func(&ru->rfdevice, &ts, rxp, samples_per_slot, nb); - - VCD_SIGNAL_DUMPER_DUMP_FUNCTION_BY_NAME( VCD_SIGNAL_DUMPER_FUNCTIONS_TRX_READ, 0 ); proc->timestamp_rx = ts-ru->ts_offset; if (rxs != samples_per_slot) @@ -617,12 +584,6 @@ static void rx_rf(RU_t *ru, int *frame, int *slot) proc->tti_tx, fp->slots_per_frame); - // dump VCD output for first RU in list - if (ru == RC.ru[0]) { - VCD_SIGNAL_DUMPER_DUMP_VARIABLE_BY_NAME( VCD_SIGNAL_DUMPER_VARIABLES_FRAME_NUMBER_RX0_RU, proc->frame_rx ); - VCD_SIGNAL_DUMPER_DUMP_VARIABLE_BY_NAME( VCD_SIGNAL_DUMPER_VARIABLES_TTI_NUMBER_RX0_RU, proc->tti_rx ); - } - if (proc->first_rx == 0) { if (proc->tti_rx != *slot) { LOG_E(PHY, @@ -653,8 +614,6 @@ static void rx_rf(RU_t *ru, int *frame, int *slot) metadata mt = {.slot = *slot, .frame = *frame}; gNBscopeCopyWithMetadata(ru, gNbTimeDomainSamples, rxp[0], sizeof(c16_t), 1, samples_per_slot, 0, &mt); - VCD_SIGNAL_DUMPER_DUMP_VARIABLE_BY_NAME( VCD_SIGNAL_DUMPER_VARIABLES_TRX_TS, (proc->timestamp_rx+ru->ts_offset)&0xffffffff ); - if (rxs != samples_per_slot) { //exit_fun( "problem receiving samples" ); LOG_E(PHY, "problem receiving samples\n"); @@ -773,17 +732,11 @@ void tx_rf(RU_t *ru, int frame,int slot, uint64_t timestamp) const int flags = flags_burst | (flags_gpio << 4); proc->first_tx = 0; - VCD_SIGNAL_DUMPER_DUMP_VARIABLE_BY_NAME(VCD_SIGNAL_DUMPER_VARIABLES_TRX_WRITE_FLAGS, flags); - VCD_SIGNAL_DUMPER_DUMP_VARIABLE_BY_NAME(VCD_SIGNAL_DUMPER_VARIABLES_FRAME_NUMBER_TX0_RU, frame); - VCD_SIGNAL_DUMPER_DUMP_VARIABLE_BY_NAME(VCD_SIGNAL_DUMPER_VARIABLES_TTI_NUMBER_TX0_RU, slot); - int nt = ru->nb_tx * ru->num_beams_period; void *txp[nt]; for (int i = 0; i < nt; i++) txp[i] = (void *)&ru->common.txdata[i][get_samples_slot_timestamp(fp, slot)] - sf_extension * sizeof(int32_t); - VCD_SIGNAL_DUMPER_DUMP_VARIABLE_BY_NAME(VCD_SIGNAL_DUMPER_VARIABLES_TRX_TST, (timestamp + ru->ts_offset) & 0xffffffff); - VCD_SIGNAL_DUMPER_DUMP_FUNCTION_BY_NAME(VCD_SIGNAL_DUMPER_FUNCTIONS_TRX_WRITE, 1); // prepare tx buffer pointers uint32_t txs = ru->rfdevice.trx_write_func(&ru->rfdevice, timestamp + ru->ts_offset - sf_extension, @@ -804,7 +757,6 @@ void tx_rf(RU_t *ru, int frame,int slot, uint64_t timestamp) siglen + sf_extension, txs, 10 * log10((double)signal_energy(txp[0], siglen + sf_extension))); - VCD_SIGNAL_DUMPER_DUMP_FUNCTION_BY_NAME(VCD_SIGNAL_DUMPER_FUNCTIONS_TRX_WRITE, 0); } static void fill_rf_config(RU_t *ru, char *rf_config_file) @@ -1208,10 +1160,8 @@ void *ru_thread(void *param) // Do PRACH RU processing prach_item_t *p = find_nr_prach(&gNB->prach_list, proc->frame_rx, proc->tti_rx, SEARCH_EXIST); if (p) { - VCD_SIGNAL_DUMPER_DUMP_FUNCTION_BY_NAME( VCD_SIGNAL_DUMPER_FUNCTIONS_PHY_RU_PRACH_RX, 1 ); // need to extract RACH data for lqter processing by rx_nr_prach() rx_nr_prach_ru(p, ru->common.rxdata, ru->nr_frame_parms, ru->N_TA_offset); - VCD_SIGNAL_DUMPER_DUMP_FUNCTION_BY_NAME(VCD_SIGNAL_DUMPER_FUNCTIONS_PHY_RU_PRACH_RX, 0); } // end if (prach_id >= 0) } // end if (ru->feprx) } // end if (slot_type == NR_UPLINK_SLOT || slot_type == NR_MIXED_SLOT) { From 33cc04b3e18dcb33280496867f88e9f4343e7630 Mon Sep 17 00:00:00 2001 From: Merkebu Girmay Date: Fri, 23 Jan 2026 18:13:51 -0800 Subject: [PATCH 068/100] Add CIRDB playback test pipeline Increase retx threshold in vrtsim with CIRDB. --- .../xml_files/container_5g_vrtsim_cirdb.xml | 130 +++++++++++++ .../5g_vrtsim_cirdb/docker-compose.yaml | 183 ++++++++++++++++++ 2 files changed, 313 insertions(+) create mode 100644 ci-scripts/xml_files/container_5g_vrtsim_cirdb.xml create mode 100644 ci-scripts/yaml_files/5g_vrtsim_cirdb/docker-compose.yaml diff --git a/ci-scripts/xml_files/container_5g_vrtsim_cirdb.xml b/ci-scripts/xml_files/container_5g_vrtsim_cirdb.xml new file mode 100644 index 0000000000..84a6a527f7 --- /dev/null +++ b/ci-scripts/xml_files/container_5g_vrtsim_cirdb.xml @@ -0,0 +1,130 @@ + + + vrtsim-5gnr-cirdb + Monolithic gNB vrtsim + CIRDB + wrench + + + Pull_Local_Registry + Pull Images from Local Registry + localhost + oai-gnb oai-nr-ue + arm_ + + + + Create_Workspace + Create new Workspace + localhost + + + + Deploy_Object + Deploy OAI 5G CoreNetwork + localhost + ci-scripts/yaml_files/5g_vrtsim_cirdb + mysql oai-amf oai-smf oai-upf oai-ext-dn + + + + Deploy_Object + Deploy OAI 5G gNB+UE RF sim SA + localhost + ci-scripts/yaml_files/5g_vrtsim_cirdb + oai-gnb oai-nr-ue + + + + Attach_UE + Attach OAI UE (Wait for IP) + rfsim5g_ue + localhost + + + + Ping + Ping ext-dn from NR-UE + rfsim5g_ue + localhost + rfsim5g_ext_dn + localhost + -c 20 -i 0.25 + 5 + + + + Ping + Ping NR-UE from ext-dn + rfsim5g_ext_dn + localhost + rfsim5g_ue + localhost + -c 20 -i 0.25 + 5 + + + + Iperf + Iperf (DL/3Mbps/UDP)(20 sec) + -u -b 3M -t 20 -R + rfsim5g_ue + localhost + rfsim5g_ext_dn + localhost + 5 + 90 + + + + Iperf + Iperf (UL/1Mbps/UDP)(20 sec) + -u -b 1M -t 20 + rfsim5g_ue + localhost + rfsim5g_ext_dn + localhost + 5 + 90 + + + + Undeploy_Object + true + Undeploy all OAI 5G stack + localhost + ci-scripts/yaml_files/5g_vrtsim_cirdb + 10,0,0,0 + 1,0,0,0 + + + + Clean_Test_Server_Images + true + Clean Test Images on Test Server + localhost + oai-gnb oai-nr-ue + + + + diff --git a/ci-scripts/yaml_files/5g_vrtsim_cirdb/docker-compose.yaml b/ci-scripts/yaml_files/5g_vrtsim_cirdb/docker-compose.yaml new file mode 100644 index 0000000000..0d7b509450 --- /dev/null +++ b/ci-scripts/yaml_files/5g_vrtsim_cirdb/docker-compose.yaml @@ -0,0 +1,183 @@ +services: + # CIR Generator service - generates CIRDB files on every CI run + cir-generator: + image: oaisoftwarealliance/cir-generator:test + environment: + GENERATOR_ARGS: "--demo" + OUTPUT: "--out /opt/output/cir_db.bin" + volumes: + - cirdb_data:/opt/output + + mysql: + container_name: "rfsim5g-mysql" + image: mysql:8.0 + init: true + volumes: + - ../5g_rfsimulator/oai_db.sql:/docker-entrypoint-initdb.d/oai_db.sql + - ../5g_rfsimulator/mysql-healthcheck.sh:/tmp/mysql-healthcheck.sh + environment: + - TZ=Europe/Paris + - MYSQL_DATABASE=oai_db + - MYSQL_USER=test + - MYSQL_PASSWORD=test + - MYSQL_ROOT_PASSWORD=linux + healthcheck: + test: /bin/bash -c "/tmp/mysql-healthcheck.sh" + interval: 10s + timeout: 5s + start_period: 10s + start_interval: 500ms + retries: 30 + networks: + public_net: + ipv4_address: 192.168.71.131 + + oai-amf: + container_name: "rfsim5g-oai-amf" + image: oaisoftwarealliance/oai-amf:v2.1.10 + environment: + - TZ=Europe/paris + volumes: + - ../5g_rfsimulator/mini_nonrf_config.yaml:/openair-amf/etc/config.yaml + depends_on: + - mysql + networks: + public_net: + ipv4_address: 192.168.71.132 + + oai-smf: + container_name: "rfsim5g-oai-smf" + image: oaisoftwarealliance/oai-smf:v2.1.10 + environment: + - TZ=Europe/Paris + volumes: + - ../5g_rfsimulator/mini_nonrf_config.yaml:/openair-smf/etc/config.yaml + depends_on: + - oai-amf + networks: + public_net: + ipv4_address: 192.168.71.133 + + oai-upf: + container_name: "rfsim5g-oai-upf" + image: oaisoftwarealliance/oai-upf:v2.1.10 + init: true + environment: + - TZ=Europe/Paris + volumes: + - ../5g_rfsimulator/mini_nonrf_config.yaml:/openair-upf/etc/config.yaml + depends_on: + - oai-smf + cap_add: + - NET_ADMIN + - SYS_ADMIN + cap_drop: + - ALL + privileged: true + networks: + public_net: + ipv4_address: 192.168.71.134 + interface_name: eth0 + traffic_net: + ipv4_address: 192.168.72.134 + interface_name: eth1 + + oai-ext-dn: + privileged: true + container_name: rfsim5g-oai-ext-dn + image: oaisoftwarealliance/trf-gen-cn5g:latest + init: true + entrypoint: /bin/bash -c \ + "iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE;"\ + "ip route add 12.1.1.0/24 via 192.168.72.134 dev eth0; sleep infinity" + depends_on: + - oai-upf + networks: + traffic_net: + ipv4_address: 192.168.72.135 + healthcheck: + test: /bin/bash -c "ping -c 2 192.168.72.134" + interval: 10s + timeout: 5s + retries: 5 + start_period: 10s + start_interval: 500ms + + oai-gnb: + image: ${REGISTRY-oaisoftwarealliance/}${GNB_IMG:-oai-gnb}:${TAG:-develop} + container_name: rfsim5g-oai-gnb + cap_drop: + - ALL + environment: + USE_ADDITIONAL_OPTIONS: -E --log_config.global_log_options level,nocolor,time --device.name vrtsim --vrtsim.role server --vrtsim.cirdb 1 --vrtsim.cirdb_file /cirdb/cir_db.bin --vrtsim.cirdb_yaml /cirdb/cir_db.yaml --vrtsim.cirdb_model_id 0 --vrtsim.cirdb_ds_ns 1 --vrtsim.cirdb_speed_mps 1.5 + depends_on: + cir-generator: + condition: service_completed_successfully + oai-ext-dn: + condition: service_started + networks: + public_net: + ipv4_address: 192.168.71.140 + volumes: + - ../../conf_files/gnb.sa.band78.106prb.vrtsim.2x2.yaml:/opt/oai-gnb/etc/gnb.yaml + - cirdb_data:/cirdb:ro + - tmp_data:/tmp/ + healthcheck: + test: /bin/bash -c "pgrep nr-softmodem" + interval: 10s + timeout: 5s + start_period: 10s + start_interval: 500ms + retries: 5 + ipc: host + + oai-nr-ue: + image: ${REGISTRY-oaisoftwarealliance/}${NRUE_IMG:-oai-nr-ue}:${TAG:-develop} + container_name: rfsim5g-oai-nr-ue + cap_drop: + - ALL + cap_add: + - NET_ADMIN + - NET_RAW + environment: + USE_ADDITIONAL_OPTIONS: -E -r 106 --numerology 1 --band 78 -C 3619200000 --ssb 516 --uicc0.imsi 208990100001100 --log_config.global_log_options level,nocolor,time --device.name vrtsim --vrtsim.role client + depends_on: + - oai-gnb + networks: + public_net: + ipv4_address: 192.168.71.150 + devices: + - /dev/net/tun:/dev/net/tun + volumes: + - ../../conf_files/nrue.vrtsim.chanmod.yaml:/opt/oai-nr-ue/etc/nr-ue.yaml + - tmp_data:/tmp/ + healthcheck: + test: /bin/bash -c "pgrep nr-uesoftmodem" + interval: 10s + timeout: 5s + retries: 5 + start_period: 10s + start_interval: 500ms + ipc: host + +networks: + public_net: + driver: bridge + name: rfsim5g-oai-public-net + ipam: + config: + - subnet: 192.168.71.128/26 + driver_opts: + com.docker.network.bridge.name: "rfsim5g-public" + traffic_net: + driver: bridge + name: rfsim5g-oai-traffic-net + ipam: + config: + - subnet: 192.168.72.128/26 + driver_opts: + com.docker.network.bridge.name: "rfsim5g-traffic" + +volumes: + tmp_data: + cirdb_data: From 0a447fd304a8693f275b914e10a17a64887e178f Mon Sep 17 00:00:00 2001 From: francescomani Date: Tue, 10 Feb 2026 16:50:57 +0100 Subject: [PATCH 069/100] remove VCD_SIGNAL_DUMPER_DUMP_FUNCTION_BY_NAME from NR code and remaining unused header include --- executables/main_nr_ru.c | 2 - executables/nr-uesoftmodem.c | 9 ----- .../nrLDPC_coding_segment_decoder.c | 1 - .../nrLDPC_coding_segment_encoder.c | 1 - .../nrLDPC_coding_xdma/nrLDPC_coding_xdma.c | 1 - openair1/PHY/INIT/nr_init_ru.c | 1 - openair1/PHY/INIT/nr_init_ue.c | 1 - openair1/PHY/NR_TRANSPORT/nr_dlsch.c | 2 - openair1/PHY/NR_TRANSPORT/nr_dlsch_coding.c | 5 --- openair1/PHY/NR_TRANSPORT/nr_prach_common.c | 7 ---- openair1/PHY/NR_TRANSPORT/nr_scrambling.c | 1 - openair1/PHY/NR_TRANSPORT/nr_ulsch_decoding.c | 3 -- openair1/PHY/NR_TRANSPORT/pucch_rx.c | 1 - .../PHY/NR_UE_ESTIMATION/nr_adjust_synch_ue.c | 4 -- .../PHY/NR_UE_TRANSPORT/nr_dlsch_decoding.c | 5 --- .../PHY/NR_UE_TRANSPORT/nr_initial_sync.c | 3 -- openair1/PHY/NR_UE_TRANSPORT/nr_prach.c | 3 -- .../PHY/NR_UE_TRANSPORT/nr_ulsch_coding.c | 10 ----- openair1/PHY/NR_UE_TRANSPORT/nr_ulsch_ue.c | 1 - openair1/PHY/NR_UE_TRANSPORT/pss_nr.c | 7 ---- openair1/PHY/NR_UE_TRANSPORT/pucch_nr.c | 1 - openair1/PHY/NR_UE_TRANSPORT/pucch_nr.h | 2 - openair1/SCHED_NR/nr_prach_procedures.c | 3 -- openair1/SCHED_NR/nr_ru_procedures.c | 36 ++++------------- openair1/SCHED_NR/phy_procedures_nr_gNB.c | 11 ----- openair1/SCHED_NR_UE/phy_procedures_nr_ue.c | 40 +------------------ .../SCHED_NR_UE/phy_procedures_nr_ue_sl.c | 12 ------ openair1/SIMULATION/NR_PHY/dlschsim.c | 17 +------- openair2/LAYER2/NR_MAC_UE/nr_ue_procedures.c | 4 -- openair2/LAYER2/NR_MAC_gNB/gNB_scheduler.c | 3 -- openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_RA.c | 4 -- .../LAYER2/NR_MAC_gNB/gNB_scheduler_bch.c | 1 - .../NR_MAC_gNB/gNB_scheduler_primitives.c | 1 - openair2/RRC/NR_UE/rrc_UE.c | 10 ----- radio/fhi_72/oran_isolate.c | 1 - 35 files changed, 10 insertions(+), 204 deletions(-) diff --git a/executables/main_nr_ru.c b/executables/main_nr_ru.c index d8f2a02235..1aab4e95f6 100644 --- a/executables/main_nr_ru.c +++ b/executables/main_nr_ru.c @@ -33,8 +33,6 @@ #include "PHY/phy_extern.h" #include "PHY/TOOLS/phy_scope_interface.h" #include "common/utils/LOG/log.h" -#include "common/utils/LOG/vcd_signal_dumper.h" -// #include "PHY/INIT/phy_init.h" #include "openair2/ENB_APP/enb_paramdef.h" #include "system.h" #include "nfapi/oai_integration/vendor_ext.h" diff --git a/executables/nr-uesoftmodem.c b/executables/nr-uesoftmodem.c index a1dcaed52c..a0e06850b7 100644 --- a/executables/nr-uesoftmodem.c +++ b/executables/nr-uesoftmodem.c @@ -60,7 +60,6 @@ unsigned short config_frames[4] = {2,9,11,13}; #endif #include "common/utils/LOG/log.h" #include "common/utils/time_manager/time_manager.h" -#include "common/utils/LOG/vcd_signal_dumper.h" #include "UTIL/OPT/opt.h" #include "LAYER2/nr_pdcp/nr_pdcp_oai_api.h" @@ -166,8 +165,6 @@ static void get_options(configmodule_interface_t *cfg) paramdef_t cmdline_params[] = CMDLINE_NRUEPARAMS_DESC; int numparams = sizeofArray(cmdline_params); config_get(cfg, cmdline_params, numparams, NULL); - if (nrUE_params.vcdflag > 0) - ouput_vcd = 1; AssertFatal(nrUE_params.extra_pdu_id == -1, "Add additional PDU sessions in uicc.pdu_sessions array instead\n"); } @@ -285,9 +282,6 @@ int main(int argc, char **argv) int ret_loader = load_nrLDPC_coding_interface(NULL, &nrLDPC_coding_interface); AssertFatal(ret_loader == 0, "error loading LDPC library\n"); - if (ouput_vcd) { - vcd_signal_dumper_init("/tmp/openair_dump_nrUE.vcd"); - } // strdup to put the sring in the core file for post mortem identification char *pckg = strdup(OAI_PACKAGE_VERSION); LOG_I(HW, "Version: %s\n", pckg); @@ -479,9 +473,6 @@ int main(int argc, char **argv) LOG_W(NR_PHY, "Returned from ITTI signal handler\n"); oai_exit = 1; - if (ouput_vcd) - vcd_signal_dumper_close(); - if (PHY_vars_UE_g && PHY_vars_UE_g[0]) { for (int CC_id = 0; CC_id < MAX_NUM_CCs; CC_id++) { PHY_VARS_NR_UE *phy_vars = PHY_vars_UE_g[0][CC_id]; diff --git a/openair1/PHY/CODING/nrLDPC_coding/nrLDPC_coding_segment/nrLDPC_coding_segment_decoder.c b/openair1/PHY/CODING/nrLDPC_coding/nrLDPC_coding_segment/nrLDPC_coding_segment_decoder.c index 374ecb4b19..00f1787859 100644 --- a/openair1/PHY/CODING/nrLDPC_coding/nrLDPC_coding_segment/nrLDPC_coding_segment_decoder.c +++ b/openair1/PHY/CODING/nrLDPC_coding/nrLDPC_coding_segment/nrLDPC_coding_segment_decoder.c @@ -37,7 +37,6 @@ #include "PHY/NR_TRANSPORT/nr_dlsch.h" #include "SCHED_NR/sched_nr.h" #include "defs.h" -#include "common/utils/LOG/vcd_signal_dumper.h" #include "common/utils/LOG/log.h" #include diff --git a/openair1/PHY/CODING/nrLDPC_coding/nrLDPC_coding_segment/nrLDPC_coding_segment_encoder.c b/openair1/PHY/CODING/nrLDPC_coding/nrLDPC_coding_segment/nrLDPC_coding_segment_encoder.c index 5d0e90e46b..c0cb6af49f 100644 --- a/openair1/PHY/CODING/nrLDPC_coding/nrLDPC_coding_segment/nrLDPC_coding_segment_encoder.c +++ b/openair1/PHY/CODING/nrLDPC_coding/nrLDPC_coding_segment/nrLDPC_coding_segment_encoder.c @@ -34,7 +34,6 @@ #include "PHY/NR_TRANSPORT/nr_transport_common_proto.h" #include "PHY/NR_TRANSPORT/nr_dlsch.h" #include "SCHED_NR/sched_nr.h" -#include "common/utils/LOG/vcd_signal_dumper.h" #include "common/utils/LOG/log.h" #include "common/utils/nr/nr_common.h" #include diff --git a/openair1/PHY/CODING/nrLDPC_coding/nrLDPC_coding_xdma/nrLDPC_coding_xdma.c b/openair1/PHY/CODING/nrLDPC_coding/nrLDPC_coding_xdma/nrLDPC_coding_xdma.c index 8aa1a95cbe..7a13087610 100644 --- a/openair1/PHY/CODING/nrLDPC_coding/nrLDPC_coding_xdma/nrLDPC_coding_xdma.c +++ b/openair1/PHY/CODING/nrLDPC_coding/nrLDPC_coding_xdma/nrLDPC_coding_xdma.c @@ -40,7 +40,6 @@ #include "PHY/defs_gNB.h" #include "SCHED_NR/sched_nr.h" #include "common/utils/LOG/log.h" -#include "common/utils/LOG/vcd_signal_dumper.h" #include "defs.h" // #define DEBUG_ULSCH_DECODING // #define gNB_DEBUG_TRACE diff --git a/openair1/PHY/INIT/nr_init_ru.c b/openair1/PHY/INIT/nr_init_ru.c index d82ff3c919..787d05697c 100644 --- a/openair1/PHY/INIT/nr_init_ru.c +++ b/openair1/PHY/INIT/nr_init_ru.c @@ -22,7 +22,6 @@ #include "phy_init.h" #include "PHY/phy_extern.h" #include "SCHED/sched_common.h" -#include "common/utils/LOG/vcd_signal_dumper.h" #include "assertions.h" #include #include "openair1/PHY/defs_RU.h" diff --git a/openair1/PHY/INIT/nr_init_ue.c b/openair1/PHY/INIT/nr_init_ue.c index e5c8e975c9..7b21102bbf 100644 --- a/openair1/PHY/INIT/nr_init_ue.c +++ b/openair1/PHY/INIT/nr_init_ue.c @@ -22,7 +22,6 @@ #include "nr_phy_init.h" #include "openair1/PHY/defs_RU.h" #include "openair1/PHY/impl_defs_nr.h" -#include "common/utils/LOG/vcd_signal_dumper.h" #include "assertions.h" #include "PHY/MODULATION/nr_modulation.h" #include "PHY/NR_UE_TRANSPORT/nr_transport_ue.h" diff --git a/openair1/PHY/NR_TRANSPORT/nr_dlsch.c b/openair1/PHY/NR_TRANSPORT/nr_dlsch.c index b8663aa435..9018aabfde 100644 --- a/openair1/PHY/NR_TRANSPORT/nr_dlsch.c +++ b/openair1/PHY/NR_TRANSPORT/nr_dlsch.c @@ -36,7 +36,6 @@ #include "PHY/MODULATION/nr_modulation.h" #include "PHY/NR_REFSIG/dmrs_nr.h" #include "PHY/NR_REFSIG/ptrs_nr.h" -#include "common/utils/LOG/vcd_signal_dumper.h" #include "common/utils/nr/nr_common.h" #include "executables/softmodem-common.h" #include "SCHED_NR/sched_nr.h" @@ -628,7 +627,6 @@ static int do_one_dlsch(unsigned char *input_ptr, PHY_VARS_gNB *gNB, NR_gNB_DLSC /// Modulation start_meas(dlsch_modulation_stats); nr_modulation(scrambled_output, encoded_length, Qm, (int16_t *)mod_symbs[codeWord]); - VCD_SIGNAL_DUMPER_DUMP_FUNCTION_BY_NAME(VCD_SIGNAL_DUMPER_FUNCTIONS_gNB_PDSCH_MODULATION, 0); stop_meas(dlsch_modulation_stats); #ifdef DEBUG_DLSCH printf("PDSCH Modulation: Qm %d(%d)\n", Qm, nb_re); diff --git a/openair1/PHY/NR_TRANSPORT/nr_dlsch_coding.c b/openair1/PHY/NR_TRANSPORT/nr_dlsch_coding.c index f74685ed71..71f5759396 100644 --- a/openair1/PHY/NR_TRANSPORT/nr_dlsch_coding.c +++ b/openair1/PHY/NR_TRANSPORT/nr_dlsch_coding.c @@ -33,7 +33,6 @@ #include "PHY/NR_TRANSPORT/nr_transport_common_proto.h" #include "PHY/NR_TRANSPORT/nr_dlsch.h" #include "SCHED_NR/sched_nr.h" -#include "common/utils/LOG/vcd_signal_dumper.h" #include "common/utils/LOG/log.h" #include "common/utils/nr/nr_common.h" #include @@ -118,8 +117,6 @@ int nr_dlsch_encoding(PHY_VARS_gNB *gNB, time_stats_t *dlsch_interleaving_stats, time_stats_t *dlsch_segmentation_stats) { - VCD_SIGNAL_DUMPER_DUMP_FUNCTION_BY_NAME(VCD_SIGNAL_DUMPER_FUNCTIONS_gNB_DLSCH_ENCODING, VCD_FUNCTION_IN); - nrLDPC_TB_encoding_parameters_t TBs[n_dlsch]; memset(TBs, 0, sizeof(TBs)); @@ -291,7 +288,5 @@ int nr_dlsch_encoding(PHY_VARS_gNB *gNB, // merge_meas(, &segment_parameters->ts_ldpc_encode); } } - - VCD_SIGNAL_DUMPER_DUMP_FUNCTION_BY_NAME(VCD_SIGNAL_DUMPER_FUNCTIONS_gNB_DLSCH_ENCODING, VCD_FUNCTION_OUT); return 0; } diff --git a/openair1/PHY/NR_TRANSPORT/nr_prach_common.c b/openair1/PHY/NR_TRANSPORT/nr_prach_common.c index b680d826fa..7a7c64a048 100644 --- a/openair1/PHY/NR_TRANSPORT/nr_prach_common.c +++ b/openair1/PHY/NR_TRANSPORT/nr_prach_common.c @@ -30,13 +30,11 @@ * \warning */ #include "PHY/sse_intrin.h" -#include "common/utils/LOG/vcd_signal_dumper.h" #include "PHY/impl_defs_nr.h" #include "PHY/defs_nr_UE.h" #include "PHY/NR_TRANSPORT/nr_prach.h" #include "PHY/NR_TRANSPORT/nr_transport_common_proto.h" #include "common/utils/LOG/log.h" -#include "common/utils/LOG/vcd_signal_dumper.h" #include "T.h" /* Extended Euclidean Algorithm to compute modulo inverse */ @@ -89,9 +87,6 @@ void compute_nr_prach_seq(uint8_t short_sequence, uint8_t num_sequences, uint8_t int N_ZC; const uint16_t* prach_root_sequence_map; - - VCD_SIGNAL_DUMPER_DUMP_FUNCTION_BY_NAME(VCD_SIGNAL_DUMPER_FUNCTIONS_PHY_UE_COMPUTE_PRACH, VCD_FUNCTION_IN); - LOG_D(PHY,"compute_prach_seq: prach short sequence %x, num_sequences %d, rootSequenceIndex %d\n", short_sequence, num_sequences, rootSequenceIndex); N_ZC = (short_sequence) ? NR_PRACH_SEQ_LEN_S : NR_PRACH_SEQ_LEN_L; @@ -132,6 +127,4 @@ void compute_nr_prach_seq(uint8_t short_sequence, uint8_t num_sequences, uint8_t X_u[i][k] = ru; } } - - VCD_SIGNAL_DUMPER_DUMP_FUNCTION_BY_NAME(VCD_SIGNAL_DUMPER_FUNCTIONS_PHY_UE_COMPUTE_PRACH, VCD_FUNCTION_OUT); } diff --git a/openair1/PHY/NR_TRANSPORT/nr_scrambling.c b/openair1/PHY/NR_TRANSPORT/nr_scrambling.c index 9577f943e4..5197242633 100644 --- a/openair1/PHY/NR_TRANSPORT/nr_scrambling.c +++ b/openair1/PHY/NR_TRANSPORT/nr_scrambling.c @@ -21,7 +21,6 @@ #include "nr_transport_common_proto.h" #include "PHY/NR_REFSIG/nr_refsig.h" -#include "common/utils/LOG/vcd_signal_dumper.h" #define DEBUG_SCRAMBLING(a) //#define DEBUG_SCRAMBLING(a) a void nr_codeword_scrambling(uint8_t *in, diff --git a/openair1/PHY/NR_TRANSPORT/nr_ulsch_decoding.c b/openair1/PHY/NR_TRANSPORT/nr_ulsch_decoding.c index a7d5206dd0..403847035c 100644 --- a/openair1/PHY/NR_TRANSPORT/nr_ulsch_decoding.c +++ b/openair1/PHY/NR_TRANSPORT/nr_ulsch_decoding.c @@ -36,7 +36,6 @@ #include "PHY/NR_TRANSPORT/nr_dlsch.h" #include "SCHED_NR/sched_nr.h" #include "defs.h" -#include "common/utils/LOG/vcd_signal_dumper.h" #include "common/utils/LOG/log.h" #include // #define DEBUG_ULSCH_DECODING @@ -115,8 +114,6 @@ int nr_ulsch_decoding(PHY_VARS_gNB *phy_vars_gNB, uint8_t *ULSCH_ids, int nb_pusch) { - VCD_SIGNAL_DUMPER_DUMP_FUNCTION_BY_NAME(VCD_SIGNAL_DUMPER_FUNCTIONS_PHY_gNB_ULSCH_DECODING, 1); - nrLDPC_TB_decoding_parameters_t TBs[nb_pusch]; memset(TBs, 0, sizeof(TBs)); nrLDPC_slot_decoding_parameters_t slot_parameters = {.frame = frame, diff --git a/openair1/PHY/NR_TRANSPORT/pucch_rx.c b/openair1/PHY/NR_TRANSPORT/pucch_rx.c index 8e2076df2f..e814fe1c86 100644 --- a/openair1/PHY/NR_TRANSPORT/pucch_rx.c +++ b/openair1/PHY/NR_TRANSPORT/pucch_rx.c @@ -46,7 +46,6 @@ #include "PHY/NR_TRANSPORT/nr_transport_proto.h" #include "PHY/NR_REFSIG/nr_refsig.h" #include "common/utils/LOG/log.h" -#include "common/utils/LOG/vcd_signal_dumper.h" #include "nfapi/oai_integration/vendor_ext.h" #include "nfapi/oai_integration/vendor_ext.h" #include "SCHED_NR/sched_nr.h" diff --git a/openair1/PHY/NR_UE_ESTIMATION/nr_adjust_synch_ue.c b/openair1/PHY/NR_UE_ESTIMATION/nr_adjust_synch_ue.c index 7bcd8264f8..a407282c1b 100644 --- a/openair1/PHY/NR_UE_ESTIMATION/nr_adjust_synch_ue.c +++ b/openair1/PHY/NR_UE_ESTIMATION/nr_adjust_synch_ue.c @@ -25,7 +25,6 @@ #include "PHY/impl_defs_top.h" #include "executables/nr-uesoftmodem.h" -#include "common/utils/LOG/vcd_signal_dumper.h" //#define DEBUG_PHY @@ -44,8 +43,6 @@ int nr_adjust_synch_ue(NR_DL_FRAME_PARMS *frame_parms, { int max_val = 0, max_pos = 0; - VCD_SIGNAL_DUMPER_DUMP_FUNCTION_BY_NAME(VCD_SIGNAL_DUMPER_FUNCTIONS_UE_ADJUST_SYNCH, VCD_FUNCTION_IN); - // search for maximum position within the cyclic prefix for (int i = -frame_parms->nb_prefix_samples; i < frame_parms->nb_prefix_samples; i++) { int temp = 0; @@ -95,6 +92,5 @@ int nr_adjust_synch_ue(NR_DL_FRAME_PARMS *frame_parms, ue->max_pos_iir += -round(sampleShift * PID_P) * 32768; ue->max_pos_acc += max_pos; - VCD_SIGNAL_DUMPER_DUMP_FUNCTION_BY_NAME(VCD_SIGNAL_DUMPER_FUNCTIONS_UE_ADJUST_SYNCH, VCD_FUNCTION_OUT); return sample_shift; } diff --git a/openair1/PHY/NR_UE_TRANSPORT/nr_dlsch_decoding.c b/openair1/PHY/NR_UE_TRANSPORT/nr_dlsch_decoding.c index be71e435a0..56b86dc4f5 100644 --- a/openair1/PHY/NR_UE_TRANSPORT/nr_dlsch_decoding.c +++ b/openair1/PHY/NR_UE_TRANSPORT/nr_dlsch_decoding.c @@ -22,7 +22,6 @@ /*! \file PHY/NR_UE_TRANSPORT/nr_dlsch_decoding_slot.c */ -#include "common/utils/LOG/vcd_signal_dumper.h" #include "PHY/defs_nr_UE.h" #include "SCHED_NR_UE/harq_nr.h" #include "PHY/CODING/coding_extern.h" @@ -144,8 +143,6 @@ void nr_dlsch_decoding(PHY_VARS_NR_UE *phy_vars_ue, TB_parameters->Qm, Coderate); - VCD_SIGNAL_DUMPER_DUMP_FUNCTION_BY_NAME(VCD_SIGNAL_DUMPER_FUNCTIONS_DLSCH_SEGMENTATION, VCD_FUNCTION_IN); - if (harq_process->first_rx == 1) { // This is a new packet, so compute quantities regarding segmentation nr_segmentation(NULL, @@ -180,8 +177,6 @@ void nr_dlsch_decoding(PHY_VARS_NR_UE *phy_vars_ue, } max_num_segments = max(max_num_segments, TB_parameters->C); - VCD_SIGNAL_DUMPER_DUMP_FUNCTION_BY_NAME(VCD_SIGNAL_DUMPER_FUNCTIONS_DLSCH_SEGMENTATION, VCD_FUNCTION_OUT); - if (LOG_DEBUGFLAG(DEBUG_DLSCH_DECOD)) LOG_I(PHY, "Segmentation: C %d, K %d\n", harq_process->C, harq_process->K); diff --git a/openair1/PHY/NR_UE_TRANSPORT/nr_initial_sync.c b/openair1/PHY/NR_UE_TRANSPORT/nr_initial_sync.c index e747b86528..4b4b4b7be8 100644 --- a/openair1/PHY/NR_UE_TRANSPORT/nr_initial_sync.c +++ b/openair1/PHY/NR_UE_TRANSPORT/nr_initial_sync.c @@ -35,7 +35,6 @@ #include "nr_transport_proto_ue.h" #include "PHY/NR_UE_ESTIMATION/nr_estimation.h" #include "SCHED_NR_UE/defs.h" -#include "common/utils/LOG/vcd_signal_dumper.h" #include "common/utils/nr/nr_common.h" #include "common_lib.h" @@ -483,7 +482,6 @@ nr_initial_sync_t nr_initial_sync(UE_nr_rxtx_proc_t *proc, if (ue->if_inst && ue->if_inst->dl_indication) ue->if_inst->dl_indication(&dl_indication); - VCD_SIGNAL_DUMPER_DUMP_FUNCTION_BY_NAME(VCD_SIGNAL_DUMPER_FUNCTIONS_NR_INITIAL_UE_SYNC, VCD_FUNCTION_IN); LOG_D(PHY, "nr_initial sync ue RB_DL %d\n", fp->N_RB_DL); @@ -554,7 +552,6 @@ nr_initial_sync_t nr_initial_sync(UE_nr_rxtx_proc_t *proc, LOG_A(PHY, "Initial sync successful, PCI: %d\n", fp->Nid_cell); } // exit_fun("debug exit"); - VCD_SIGNAL_DUMPER_DUMP_FUNCTION_BY_NAME(VCD_SIGNAL_DUMPER_FUNCTIONS_NR_INITIAL_UE_SYNC, VCD_FUNCTION_OUT); if (res) return res->syncRes; else diff --git a/openair1/PHY/NR_UE_TRANSPORT/nr_prach.c b/openair1/PHY/NR_UE_TRANSPORT/nr_prach.c index 05a251d999..d55b81e0c1 100644 --- a/openair1/PHY/NR_UE_TRANSPORT/nr_prach.c +++ b/openair1/PHY/NR_UE_TRANSPORT/nr_prach.c @@ -30,15 +30,12 @@ * \warning */ #include "PHY/sse_intrin.h" -#include "common/utils/LOG/vcd_signal_dumper.h" - #include "PHY/impl_defs_nr.h" #include "PHY/defs_nr_UE.h" #include "PHY/NR_UE_TRANSPORT/nr_transport_proto_ue.h" #include "PHY/NR_TRANSPORT/nr_transport_common_proto.h" #include "common/utils/LOG/log.h" -#include "common/utils/LOG/vcd_signal_dumper.h" #include "T.h" diff --git a/openair1/PHY/NR_UE_TRANSPORT/nr_ulsch_coding.c b/openair1/PHY/NR_UE_TRANSPORT/nr_ulsch_coding.c index d68ae6e9ae..029c1c5d38 100644 --- a/openair1/PHY/NR_UE_TRANSPORT/nr_ulsch_coding.c +++ b/openair1/PHY/NR_UE_TRANSPORT/nr_ulsch_coding.c @@ -31,7 +31,6 @@ #include "PHY/CODING/nrLDPC_coding/nrLDPC_coding_interface.h" #include "PHY/NR_UE_TRANSPORT/nr_transport_ue.h" #include "executables/nr-uesoftmodem.h" -#include "common/utils/LOG/vcd_signal_dumper.h" #include "PHY/log_tools.h" int nr_ulsch_pre_encoding(PHY_VARS_NR_UE *ue, @@ -137,7 +136,6 @@ int nr_ulsch_pre_encoding(PHY_VARS_NR_UE *ue, harq_process->BG = pusch_pdu->ldpcBaseGraph; - VCD_SIGNAL_DUMPER_DUMP_FUNCTION_BY_NAME(VCD_SIGNAL_DUMPER_FUNCTIONS_NR_SEGMENTATION, VCD_FUNCTION_IN); start_meas_nr_ue_phy(ue, ULSCH_SEGMENTATION_STATS); harq_process->Kb = nr_segmentation(harq_process->payload_AB, harq_process->c, @@ -152,7 +150,6 @@ int nr_ulsch_pre_encoding(PHY_VARS_NR_UE *ue, return (-1); } stop_meas_nr_ue_phy(ue, ULSCH_SEGMENTATION_STATS); - VCD_SIGNAL_DUMPER_DUMP_FUNCTION_BY_NAME(VCD_SIGNAL_DUMPER_FUNCTIONS_NR_SEGMENTATION, VCD_FUNCTION_OUT); } // pusch_id return 0; } @@ -167,7 +164,6 @@ int nr_ulsch_encoding(PHY_VARS_NR_UE *ue, uint16_t number_dmrs_symbols) { start_meas_nr_ue_phy(ue, ULSCH_ENCODING_STATS); - VCD_SIGNAL_DUMPER_DUMP_FUNCTION_BY_NAME(VCD_SIGNAL_DUMPER_FUNCTIONS_NR_UE_ULSCH_ENCODING, VCD_FUNCTION_IN); nrLDPC_TB_encoding_parameters_t TBs[nb_ulsch]; memset(TBs, 0, sizeof(TBs)); @@ -237,9 +233,6 @@ int nr_ulsch_encoding(PHY_VARS_NR_UE *ue, } // pusch_id ///////////////////////// | LDCP coding | //////////////////////////////////// - - VCD_SIGNAL_DUMPER_DUMP_FUNCTION_BY_NAME(VCD_SIGNAL_DUMPER_FUNCTIONS_LDPC_ENCODER_OPTIM, VCD_FUNCTION_IN); - ue->nrLDPC_coding_interface.nrLDPC_coding_encoder(&slot_parameters); for (uint8_t pusch_id = 0; pusch_id < nb_ulsch; pusch_id++) { @@ -252,9 +245,6 @@ int nr_ulsch_encoding(PHY_VARS_NR_UE *ue, } } - VCD_SIGNAL_DUMPER_DUMP_FUNCTION_BY_NAME(VCD_SIGNAL_DUMPER_FUNCTIONS_LDPC_ENCODER_OPTIM, VCD_FUNCTION_OUT); - - VCD_SIGNAL_DUMPER_DUMP_FUNCTION_BY_NAME(VCD_SIGNAL_DUMPER_FUNCTIONS_NR_UE_ULSCH_ENCODING, VCD_FUNCTION_OUT); stop_meas_nr_ue_phy(ue, ULSCH_ENCODING_STATS); return 0; } diff --git a/openair1/PHY/NR_UE_TRANSPORT/nr_ulsch_ue.c b/openair1/PHY/NR_UE_TRANSPORT/nr_ulsch_ue.c index 6708cef338..804529ffbf 100644 --- a/openair1/PHY/NR_UE_TRANSPORT/nr_ulsch_ue.c +++ b/openair1/PHY/NR_UE_TRANSPORT/nr_ulsch_ue.c @@ -39,7 +39,6 @@ #include "PHY/MODULATION/modulation_common.h" #include "common/utils/assertions.h" #include "common/utils/nr/nr_common.h" -#include "common/utils/LOG/vcd_signal_dumper.h" #include "PHY/NR_TRANSPORT/nr_transport_common_proto.h" #include "PHY/NR_TRANSPORT/nr_sch_dmrs.h" #include "PHY/defs_nr_common.h" diff --git a/openair1/PHY/NR_UE_TRANSPORT/pss_nr.c b/openair1/PHY/NR_UE_TRANSPORT/pss_nr.c index 08ed65945c..e4b68a4d66 100644 --- a/openair1/PHY/NR_UE_TRANSPORT/pss_nr.c +++ b/openair1/PHY/NR_UE_TRANSPORT/pss_nr.c @@ -39,7 +39,6 @@ #include "PHY/defs_nr_UE.h" #include "PHY/NR_REFSIG/ss_pbch_nr.h" -#include "common/utils/LOG/vcd_signal_dumper.h" #define DEFINE_VARIABLES_PSS_NR_H #include "PHY/NR_REFSIG/pss_nr.h" @@ -391,7 +390,6 @@ int pss_synchro_nr(const c16_t **rxdata, int *pssPeak, int *pssAvg) { - VCD_SIGNAL_DUMPER_DUMP_FUNCTION_BY_NAME(VCD_SIGNAL_DUMPER_FUNCTIONS_PSS_SYNCHRO_NR, VCD_FUNCTION_IN); #ifdef DBG_PSS_NR LOG_M("rxdata0_rand.m","rxd0_rand", &PHY_vars_UE->common_vars.rxdata[0][0], frame_parms->samples_per_frame, 1, 1); @@ -406,13 +404,9 @@ int pss_synchro_nr(const c16_t **rxdata, start_meas(&generic_time[TIME_PSS]); - VCD_SIGNAL_DUMPER_DUMP_FUNCTION_BY_NAME(VCD_SIGNAL_DUMPER_FUNCTIONS_PSS_SEARCH_TIME_NR, VCD_FUNCTION_IN); - int synchro_position = pss_search_time_nr(rxdata, frame_parms, pssTime, fo_flag, is, target_Nid_cell, nid2, f_off, pssPeak, pssAvg, -1, -1); - VCD_SIGNAL_DUMPER_DUMP_FUNCTION_BY_NAME(VCD_SIGNAL_DUMPER_FUNCTIONS_PSS_SEARCH_TIME_NR, VCD_FUNCTION_OUT); - #if TEST_SYNCHRO_TIMING_PSS stop_meas(&generic_time[TIME_PSS]); @@ -444,7 +438,6 @@ int pss_synchro_nr(const c16_t **rxdata, } #endif - VCD_SIGNAL_DUMPER_DUMP_FUNCTION_BY_NAME(VCD_SIGNAL_DUMPER_FUNCTIONS_PSS_SYNCHRO_NR, VCD_FUNCTION_OUT); return synchro_position; } diff --git a/openair1/PHY/NR_UE_TRANSPORT/pucch_nr.c b/openair1/PHY/NR_UE_TRANSPORT/pucch_nr.c index b6b20cefaf..bbb5c81873 100644 --- a/openair1/PHY/NR_UE_TRANSPORT/pucch_nr.c +++ b/openair1/PHY/NR_UE_TRANSPORT/pucch_nr.c @@ -39,7 +39,6 @@ #include "PHY/NR_TRANSPORT/nr_transport_common_proto.h" #include #include "common/utils/LOG/log.h" -#include "common/utils/LOG/vcd_signal_dumper.h" #include "openair1/PHY/NR_REFSIG/nr_refsig.h" #include "T.h" diff --git a/openair1/PHY/NR_UE_TRANSPORT/pucch_nr.h b/openair1/PHY/NR_UE_TRANSPORT/pucch_nr.h index 1f24b589f4..7f769872de 100644 --- a/openair1/PHY/NR_UE_TRANSPORT/pucch_nr.h +++ b/openair1/PHY/NR_UE_TRANSPORT/pucch_nr.h @@ -39,8 +39,6 @@ //#include "PHY/extern.h" #include "common/utils/LOG/log.h" -#include "common/utils/LOG/vcd_signal_dumper.h" - #include "T.h" #define ONE_OVER_SQRT2 23170 // 32767/sqrt(2) = 23170 (ONE_OVER_SQRT2) diff --git a/openair1/SCHED_NR/nr_prach_procedures.c b/openair1/SCHED_NR/nr_prach_procedures.c index d5d6a8860e..bc0c7b9461 100644 --- a/openair1/SCHED_NR/nr_prach_procedures.c +++ b/openair1/SCHED_NR/nr_prach_procedures.c @@ -35,7 +35,6 @@ #include "nfapi_nr_interface_scf.h" #include "nfapi_pnf.h" #include "common/utils/LOG/log.h" -#include "common/utils/LOG/vcd_signal_dumper.h" #include "assertions.h" #include @@ -48,7 +47,6 @@ int get_nr_prach_duration(uint8_t prach_format) void L1_nr_prach_procedures(PHY_VARS_gNB *gNB, int frame, int slot, nfapi_nr_rach_indication_t *rach_ind) { - VCD_SIGNAL_DUMPER_DUMP_FUNCTION_BY_NAME(VCD_SIGNAL_DUMPER_FUNCTIONS_PHY_ENB_PRACH_RX,1); rach_ind->sfn = frame; rach_ind->slot = slot; rach_ind->number_of_pdus = 0; @@ -129,5 +127,4 @@ void L1_nr_prach_procedures(PHY_VARS_gNB *gNB, int frame, int slot, nfapi_nr_rac rach_ind->slot = prach_start_slot; LOG_D(NR_PHY_RACH, "Freeing PRACH entry\n"); free_nr_prach_entry(&gNB->prach_list, prach_id); - VCD_SIGNAL_DUMPER_DUMP_FUNCTION_BY_NAME(VCD_SIGNAL_DUMPER_FUNCTIONS_PHY_ENB_PRACH_RX,0); } diff --git a/openair1/SCHED_NR/nr_ru_procedures.c b/openair1/SCHED_NR/nr_ru_procedures.c index 4cf05d5772..eb2c83c10a 100644 --- a/openair1/SCHED_NR/nr_ru_procedures.c +++ b/openair1/SCHED_NR/nr_ru_procedures.c @@ -38,7 +38,6 @@ #include "common/utils/LOG/log.h" #include "common/utils/system.h" -#include "common/utils/LOG/vcd_signal_dumper.h" #include "T.h" @@ -55,8 +54,6 @@ void nr_feptx0(RU_t *ru, int tti_tx, int first_symbol, int num_symbols, int aa) unsigned int slot_offset,slot_offsetF; int slot = tti_tx; - //VCD_SIGNAL_DUMPER_DUMP_FUNCTION_BY_NAME(VCD_SIGNAL_DUMPER_FUNCTIONS_PHY_PROCEDURES_RU_FEPTX_OFDM+(first_symbol!=0?1:0) , 1 ); - if (aa == 0 && first_symbol == 0) start_meas(&ru->ofdm_mod_stats); slot_offset = get_samples_slot_timestamp(fp, slot); @@ -142,13 +139,11 @@ void nr_feptx0(RU_t *ru, int tti_tx, int first_symbol, int num_symbols, int aa) if (aa == 0 && first_symbol == 0) stop_meas(&ru->ofdm_mod_stats); - - //VCD_SIGNAL_DUMPER_DUMP_FUNCTION_BY_NAME(VCD_SIGNAL_DUMPER_FUNCTIONS_PHY_PROCEDURES_RU_FEPTX_OFDM+(first_symbol!=0?1:0), 0); } // RU FEP TX OFDM modulation, single-thread -void nr_feptx_ofdm(RU_t *ru,int frame_tx,int tti_tx) { - +void nr_feptx_ofdm(RU_t *ru,int frame_tx,int tti_tx) +{ nfapi_nr_config_request_scf_t *cfg = &ru->gNB_list[0]->gNB_config; NR_DL_FRAME_PARMS *fp=ru->nr_frame_parms; int cyclic_prefix_type = NFAPI_CP_NORMAL; @@ -159,17 +154,11 @@ void nr_feptx_ofdm(RU_t *ru,int frame_tx,int tti_tx) { int slot = tti_tx; int *txdata = &ru->common.txdata[aa][get_samples_slot_timestamp(fp, slot)]; - if (nr_slot_select(cfg,frame_tx,slot) == NR_UPLINK_SLOT) return; - - VCD_SIGNAL_DUMPER_DUMP_FUNCTION_BY_NAME(VCD_SIGNAL_DUMPER_FUNCTIONS_PHY_PROCEDURES_RU_FEPTX_OFDM , 1 ); - - - // LOG_D(HW,"Frame %d: Generating slot %d\n",frame,next_slot); + if (nr_slot_select(cfg,frame_tx,slot) == NR_UPLINK_SLOT) + return; nr_feptx0(ru,slot,0,NR_NUMBER_OF_SYMBOLS_PER_SLOT,aa); - VCD_SIGNAL_DUMPER_DUMP_FUNCTION_BY_NAME(VCD_SIGNAL_DUMPER_FUNCTIONS_PHY_PROCEDURES_RU_FEPTX_OFDM , 0 ); - LOG_D(PHY, "feptx_ofdm (TXPATH): frame %d, slot %d: txp (time %p) %d dB, txp (freq) %d dB\n", frame_tx, @@ -233,7 +222,6 @@ void nr_feptx(void *arg) int txdataF_offset = (slot * fp->samples_per_slot_wCP) + startSymbol * fp->ofdm_symbol_size; int txdataF_BF_offset = startSymbol * fp->ofdm_symbol_size; - VCD_SIGNAL_DUMPER_DUMP_FUNCTION_BY_NAME(VCD_SIGNAL_DUMPER_FUNCTIONS_PHY_PROCEDURES_RU_FEPTX_PREC+feptx->aid , 1); int tx_idx = aa + bb * ru->nb_tx; if (tx_idx == 0) @@ -256,9 +244,8 @@ void nr_feptx(void *arg) if (tx_idx == 0) stop_meas(&ru->precoding_stats); - VCD_SIGNAL_DUMPER_DUMP_FUNCTION_BY_NAME(VCD_SIGNAL_DUMPER_FUNCTIONS_PHY_PROCEDURES_RU_FEPTX_PREC+feptx->aid , 0); - ////////////FEPTX//////////// + ////////////FEPTX//////////// nr_feptx0(ru, slot, startSymbol, numSymbols, tx_idx); // Task completed in // @@ -271,8 +258,6 @@ void nr_feptx_tp(RU_t *ru, int frame_tx, int slot) nfapi_nr_config_request_scf_t *cfg = &ru->gNB_list[0]->gNB_config; if (nr_slot_select(cfg, frame_tx, slot) == NR_UPLINK_SLOT) return; - if (ru->idx == 0) - VCD_SIGNAL_DUMPER_DUMP_FUNCTION_BY_NAME( VCD_SIGNAL_DUMPER_FUNCTIONS_PHY_PROCEDURES_RU_FEPTX_OFDM, 1); start_meas(&ru->ofdm_total_stats); int nt = ru->nb_tx * ru->num_beams_period; @@ -316,8 +301,6 @@ void nr_feptx_tp(RU_t *ru, int frame_tx, int slot) join_task_ans(&ans); stop_meas(&ru->ofdm_total_stats); - if (ru->idx == 0) - VCD_SIGNAL_DUMPER_DUMP_FUNCTION_BY_NAME( VCD_SIGNAL_DUMPER_FUNCTIONS_PHY_PROCEDURES_RU_FEPTX_OFDM, 0); } // core RX FEP routine, called by threads in RU thread-pool @@ -340,10 +323,9 @@ void nr_fep(void *arg) } // RU RX FEP using thread-pool -void nr_fep_tp(RU_t *ru, int slot) { - - int nbfeprx=0; - if (ru->idx == 0) VCD_SIGNAL_DUMPER_DUMP_FUNCTION_BY_NAME( VCD_SIGNAL_DUMPER_FUNCTIONS_PHY_PROCEDURES_RU_FEPRX, 1 ); +void nr_fep_tp(RU_t *ru, int slot) +{ + int nbfeprx = 0; start_meas(&ru->ofdm_demod_stats); int nt = ru->nb_rx * ru->num_beams_period; @@ -384,6 +366,4 @@ void nr_fep_tp(RU_t *ru, int slot) { join_task_ans(&ans); stop_meas(&ru->ofdm_demod_stats); - if (ru->idx == 0) VCD_SIGNAL_DUMPER_DUMP_FUNCTION_BY_NAME( VCD_SIGNAL_DUMPER_FUNCTIONS_PHY_PROCEDURES_RU_FEPRX, 0 ); } - diff --git a/openair1/SCHED_NR/phy_procedures_nr_gNB.c b/openair1/SCHED_NR/phy_procedures_nr_gNB.c index 5f75a04b05..ead0b624f7 100644 --- a/openair1/SCHED_NR/phy_procedures_nr_gNB.c +++ b/openair1/SCHED_NR/phy_procedures_nr_gNB.c @@ -28,7 +28,6 @@ #include "PHY/NR_ESTIMATION/nr_ul_estimation.h" #include "nfapi/open-nFAPI/nfapi/public_inc/nfapi_interface.h" #include "common/utils/LOG/log.h" -#include "common/utils/LOG/vcd_signal_dumper.h" #include "PHY/INIT/nr_phy_init.h" #include "PHY/MODULATION/nr_modulation.h" #include "T.h" @@ -264,8 +263,6 @@ void phy_procedures_gNB_TX(PHY_VARS_gNB *gNB, if ((cfg->cell_config.frame_duplex_type.value == TDD) && (nr_slot_select(cfg,frame,slot) == NR_UPLINK_SLOT)) return; - VCD_SIGNAL_DUMPER_DUMP_FUNCTION_BY_NAME(VCD_SIGNAL_DUMPER_FUNCTIONS_PHY_PROCEDURES_gNB_TX + gNB->CC_id, 1); - // clear the transmit data array and beam index for the current slot for (int i = 0; i < gNB->common_vars.num_beams_period; i++) { for (int aa = 0; aa < cfg->carrier_config.num_tx_ant.value; aa++) { @@ -326,10 +323,8 @@ void phy_procedures_gNB_TX(PHY_VARS_gNB *gNB, } if (num_pdsch > 0) { - VCD_SIGNAL_DUMPER_DUMP_FUNCTION_BY_NAME(VCD_SIGNAL_DUMPER_FUNCTIONS_GENERATE_DLSCH,1); LOG_D(PHY, "PDSCH generation started (%d) in frame %d.%d\n", num_pdsch, frame, slot); nr_generate_pdsch(gNB, num_pdsch, gNB->dlsch, frame, slot); - VCD_SIGNAL_DUMPER_DUMP_FUNCTION_BY_NAME(VCD_SIGNAL_DUMPER_FUNCTIONS_GENERATE_DLSCH,0); } //apply the OFDM symbol rotation here @@ -354,8 +349,6 @@ void phy_procedures_gNB_TX(PHY_VARS_gNB *gNB, } } stop_meas(&gNB->phase_comp_stats); - - VCD_SIGNAL_DUMPER_DUMP_FUNCTION_BY_NAME(VCD_SIGNAL_DUMPER_FUNCTIONS_PHY_PROCEDURES_gNB_TX + gNB->CC_id, 0); } static int nr_ulsch_procedures(PHY_VARS_gNB *gNB, int frame_rx, int slot_rx, bool *ulsch_to_decode, NR_UL_IND_t *UL_INFO) @@ -1002,7 +995,6 @@ int phy_procedures_gNB_uespec_RX(PHY_VARS_gNB *gNB, int frame_rx, int slot_rx, N const uint16_t ofdm_symbol_size = frame_parms->ofdm_symbol_size; const int nb_symb = frame_parms->symbols_per_slot; const uint8_t nb_antennas_rx = frame_parms->nb_antennas_rx; - VCD_SIGNAL_DUMPER_DUMP_FUNCTION_BY_NAME(VCD_SIGNAL_DUMPER_FUNCTIONS_PHY_PROCEDURES_gNB_UESPEC_RX,1); LOG_D(PHY,"phy_procedures_gNB_uespec_RX frame %d, slot %d\n",frame_rx,slot_rx); { // Mask of occupied RBs, per symbol and PRB @@ -1115,7 +1107,6 @@ int phy_procedures_gNB_uespec_RX(PHY_VARS_gNB *gNB, int frame_rx, int slot_rx, N pusch_decode_done = 1; - VCD_SIGNAL_DUMPER_DUMP_FUNCTION_BY_NAME(VCD_SIGNAL_DUMPER_FUNCTIONS_NR_RX_PUSCH, 1); start_meas(&gNB->rx_pusch_stats); nr_rx_pusch_tp(gNB, ULSCH_id, frame_rx, slot_rx, ulsch->harq_pid, ulsch->beam_nb); NR_gNB_PUSCH *pusch_vars = &gNB->pusch_vars[ULSCH_id]; @@ -1162,7 +1153,6 @@ int phy_procedures_gNB_uespec_RX(PHY_VARS_gNB *gNB, int frame_rx, int slot_rx, N } ulsch_to_decode[ULSCH_id] = true; stop_meas(&gNB->rx_pusch_stats); - VCD_SIGNAL_DUMPER_DUMP_FUNCTION_BY_NAME(VCD_SIGNAL_DUMPER_FUNCTIONS_NR_RX_PUSCH, 0); // LOG_M("rxdataF_comp.m","rxF_comp",gNB->pusch_vars[0]->rxdataF_comp[0],6900,1,1); // LOG_M("rxdataF_ext.m","rxF_ext",gNB->pusch_vars[0]->rxdataF_ext[0],6900,1,1); } @@ -1394,7 +1384,6 @@ int phy_procedures_gNB_uespec_RX(PHY_VARS_gNB *gNB, int frame_rx, int slot_rx, N T_BUFFER(&gNB->common_vars.rxdataF[0][0][0], nb_symb * ofdm_symbol_size * 4)); } - VCD_SIGNAL_DUMPER_DUMP_FUNCTION_BY_NAME(VCD_SIGNAL_DUMPER_FUNCTIONS_PHY_PROCEDURES_gNB_UESPEC_RX,0); return pusch_DTX; } diff --git a/openair1/SCHED_NR_UE/phy_procedures_nr_ue.c b/openair1/SCHED_NR_UE/phy_procedures_nr_ue.c index c21dafc1b3..c080076b4a 100644 --- a/openair1/SCHED_NR_UE/phy_procedures_nr_ue.c +++ b/openair1/SCHED_NR_UE/phy_procedures_nr_ue.c @@ -70,7 +70,6 @@ fifo_dump_emos_UE emos_dump_UE; #endif -#include "common/utils/LOG/vcd_signal_dumper.h" #include "UTIL/OPT/opt.h" #include "intertask_interface.h" #include "T.h" @@ -372,7 +371,6 @@ void phy_procedures_nrUE_TX(PHY_VARS_NR_UE *ue, const UE_nr_rxtx_proc_t *proc, n AssertFatal(ue->CC_id == 0, "Transmission on secondary CCs is not supported yet\n"); - VCD_SIGNAL_DUMPER_DUMP_FUNCTION_BY_NAME(VCD_SIGNAL_DUMPER_FUNCTIONS_PHY_PROCEDURES_UE_TX,VCD_FUNCTION_IN); #if T_TRACER T(T_UE_PHY_UL_TICK, T_INT(ue->Mod_id), T_INT(frame_tx % 1024), T_INT(slot_tx)); #endif @@ -416,7 +414,6 @@ void phy_procedures_nrUE_TX(PHY_VARS_NR_UE *ue, const UE_nr_rxtx_proc_t *proc, n LOG_D(PHY, "****** end TX-Chain for AbsSubframe %d.%d ******\n", proc->frame_tx, proc->nr_slot_tx); - VCD_SIGNAL_DUMPER_DUMP_FUNCTION_BY_NAME(VCD_SIGNAL_DUMPER_FUNCTIONS_PHY_PROCEDURES_UE_TX, VCD_FUNCTION_OUT); stop_meas_nr_ue_phy(ue, PHY_PROC_TX); } @@ -430,7 +427,6 @@ static void nr_ue_measurement_procedures(uint16_t l, NR_DL_FRAME_PARMS *frame_parms=&ue->frame_parms; int nr_slot_rx = proc->nr_slot_rx; int gNB_id = proc->gNB_id; - VCD_SIGNAL_DUMPER_DUMP_FUNCTION_BY_NAME(VCD_SIGNAL_DUMPER_FUNCTIONS_UE_MEASUREMENT_PROCEDURES, VCD_FUNCTION_IN); if (l == 2) { LOG_D(PHY,"Doing UE measurement procedures in symbol l %u Ncp %d nr_slot_rx %d, rxdata %p\n", @@ -459,18 +455,10 @@ static void nr_ue_measurement_procedures(uint16_t l, if (( nr_slot_rx == 2) && (l==(2-frame_parms->Ncp))) { // AGC - - VCD_SIGNAL_DUMPER_DUMP_FUNCTION_BY_NAME(VCD_SIGNAL_DUMPER_FUNCTIONS_UE_GAIN_CONTROL, VCD_FUNCTION_IN); - - //printf("start adjust gain power avg db %d\n", ue->measurements.rx_power_avg_dB[gNB_id]); phy_adjust_gain_nr (ue,ue->measurements.rx_power_avg_dB[gNB_id],gNB_id); - VCD_SIGNAL_DUMPER_DUMP_FUNCTION_BY_NAME(VCD_SIGNAL_DUMPER_FUNCTIONS_UE_GAIN_CONTROL, VCD_FUNCTION_OUT); - -} - - VCD_SIGNAL_DUMPER_DUMP_FUNCTION_BY_NAME(VCD_SIGNAL_DUMPER_FUNCTIONS_UE_MEASUREMENT_PROCEDURES, VCD_FUNCTION_OUT); + } } static int nr_ue_pbch_procedures(PHY_VARS_NR_UE *ue, @@ -487,8 +475,6 @@ static int nr_ue_pbch_procedures(PHY_VARS_NR_UE *ue, int nr_slot_rx = proc->nr_slot_rx; int gNB_id = proc->gNB_id; - VCD_SIGNAL_DUMPER_DUMP_FUNCTION_BY_NAME(VCD_SIGNAL_DUMPER_FUNCTIONS_UE_PBCH_PROCEDURES, VCD_FUNCTION_IN); - LOG_D(PHY,"[UE %d] Frame %d Slot %d, Trying PBCH (NidCell %d, gNB_id %d)\n",ue->Mod_id,frame_rx,nr_slot_rx,ue->frame_parms.Nid_cell,gNB_id); fapiPbch_t result; int hf_frame_bit, ssb_index, symb_offset; @@ -525,7 +511,6 @@ static int nr_ue_pbch_procedures(PHY_VARS_NR_UE *ue, } else { LOG_E(PHY, "[UE %d] frame %d, nr_slot_rx %d, Error decoding PBCH!\n", ue->Mod_id, frame_rx, nr_slot_rx); } - VCD_SIGNAL_DUMPER_DUMP_FUNCTION_BY_NAME(VCD_SIGNAL_DUMPER_FUNCTIONS_UE_PBCH_PROCEDURES, VCD_FUNCTION_OUT); TracyCZoneEnd(ctx); return ret; } @@ -1086,7 +1071,6 @@ int pbch_processing(PHY_VARS_NR_UE *ue, const UE_nr_rxtx_proc_t *proc, nr_phy_da int sampleShift = INT_MAX; nr_ue_dlsch_init(phy_data->dlsch, NR_MAX_NB_LAYERS>4 ? 2:1, ue->max_ldpc_iterations); - VCD_SIGNAL_DUMPER_DUMP_FUNCTION_BY_NAME(VCD_SIGNAL_DUMPER_FUNCTIONS_PHY_PROCEDURES_UE_RX, VCD_FUNCTION_IN); #if T_TRACER T(T_UE_PHY_DL_TICK, T_INT(ue->Mod_id), T_INT(frame_rx % 1024), T_INT(nr_slot_rx)); #endif @@ -1111,7 +1095,6 @@ int pbch_processing(PHY_VARS_NR_UE *ue, const UE_nr_rxtx_proc_t *proc, nr_phy_da int ssb_slot_2 = (ssb_period == 0) ? ssb_slot + (fp->slots_per_frame >> 1) : -1; if (ssb_slot == nr_slot_rx || ssb_slot_2 == nr_slot_rx) { - VCD_SIGNAL_DUMPER_DUMP_FUNCTION_BY_NAME(VCD_SIGNAL_DUMPER_FUNCTIONS_UE_SLOT_FEP_PBCH, VCD_FUNCTION_IN); LOG_D(PHY," ------ PBCH ChannelComp/LLR: frame.slot %d.%d ------ \n", frame_rx%1024, nr_slot_rx); __attribute__ ((aligned(32))) struct complex16 dl_ch_estimates[fp->nb_antennas_rx][estimateSz]; @@ -1189,7 +1172,6 @@ int pbch_processing(PHY_VARS_NR_UE *ue, const UE_nr_rxtx_proc_t *proc, nr_phy_da LOG_D(PHY, "Doing N0 measurements in %s\n", __FUNCTION__); nr_ue_rrc_measurements(ue, proc, rxdataF); - VCD_SIGNAL_DUMPER_DUMP_FUNCTION_BY_NAME(VCD_SIGNAL_DUMPER_FUNCTIONS_UE_SLOT_FEP_PBCH, VCD_FUNCTION_OUT); } } } @@ -1279,7 +1261,6 @@ void pdsch_processing(PHY_VARS_NR_UE *ue, const UE_nr_rxtx_proc_t *proc, nr_phy_ } if (dlsch[0].active) { - VCD_SIGNAL_DUMPER_DUMP_FUNCTION_BY_NAME(VCD_SIGNAL_DUMPER_FUNCTIONS_UE_SLOT_FEP_PDSCH, VCD_FUNCTION_IN); fapi_nr_dl_config_dlsch_pdu_rel15_t *dlsch_config = &dlsch[0].dlsch_config; uint16_t nb_symb_sch = dlsch_config->number_symbols; uint16_t start_symb_sch = dlsch_config->start_symbol; @@ -1292,7 +1273,6 @@ void pdsch_processing(PHY_VARS_NR_UE *ue, const UE_nr_rxtx_proc_t *proc, nr_phy_ slot_fep_map[m] = true; } } - VCD_SIGNAL_DUMPER_DUMP_FUNCTION_BY_NAME(VCD_SIGNAL_DUMPER_FUNCTIONS_UE_SLOT_FEP_PDSCH, VCD_FUNCTION_OUT); const uint8_t nb_re_dmrs = get_num_dmrs_re_per_rb(dlsch_config->dmrsConfigType, dlsch_config->n_dmrs_cdm_groups); uint16_t dmrs_len = get_num_dmrs(dlsch_config->dlDmrsSymbPos); @@ -1322,18 +1302,13 @@ void pdsch_processing(PHY_VARS_NR_UE *ue, const UE_nr_rxtx_proc_t *proc, nr_phy_ for (int i = 0; i < nb_codewords; i++) llr[i] = (int16_t *)malloc16_clear(rx_llr_buf_sz * sizeof(int16_t)); - VCD_SIGNAL_DUMPER_DUMP_FUNCTION_BY_NAME(VCD_SIGNAL_DUMPER_FUNCTIONS_PDSCH_PROC_C, VCD_FUNCTION_IN); // it returns -1 in case of internal failure, or 0 in case of normal result int ret_pdsch = nr_ue_pdsch_procedures(ue, proc, dlsch, llr, rxdataF, G); TracyCPlot("pdsch mcs", dlsch->dlsch_config.mcs); - VCD_SIGNAL_DUMPER_DUMP_FUNCTION_BY_NAME(VCD_SIGNAL_DUMPER_FUNCTIONS_PDSCH_PROC_C, VCD_FUNCTION_OUT); - UEscopeCopy(ue, pdschLlr, llr[0], sizeof(int16_t), 1, G, 0); LOG_D(PHY, "DLSCH data reception at nr_slot_rx: %d\n", nr_slot_rx); - VCD_SIGNAL_DUMPER_DUMP_FUNCTION_BY_NAME(VCD_SIGNAL_DUMPER_FUNCTIONS_PDSCH_PROC, VCD_FUNCTION_IN); - start_meas_nr_ue_phy(ue, DLSCH_PROCEDURES_STATS); if (ret_pdsch >= 0) { @@ -1361,7 +1336,6 @@ void pdsch_processing(PHY_VARS_NR_UE *ue, const UE_nr_rxtx_proc_t *proc, nr_phy_ if (ue->phy_sim_pdsch_llr) memcpy(ue->phy_sim_pdsch_llr, llr[0], sizeof(int16_t) * rx_llr_buf_sz); - VCD_SIGNAL_DUMPER_DUMP_FUNCTION_BY_NAME(VCD_SIGNAL_DUMPER_FUNCTIONS_PDSCH_PROC, VCD_FUNCTION_OUT); for (int i=0; iframe_parms.nb_antennas_rx, rxdataF_sz, 0); } @@ -1403,8 +1374,6 @@ static void nr_ue_prach_procedures(PHY_VARS_NR_UE *ue, const UE_nr_rxtx_proc_t * int frame_tx = proc->frame_tx, nr_slot_tx = proc->nr_slot_tx, prach_power; // tx_amp uint8_t mod_id = ue->Mod_id; - VCD_SIGNAL_DUMPER_DUMP_FUNCTION_BY_NAME(VCD_SIGNAL_DUMPER_FUNCTIONS_PHY_PROCEDURES_UE_TX_PRACH, VCD_FUNCTION_IN); - NR_UE_PRACH *prach_var = ue->prach_vars[gNB_id]; if (prach_var->active) { fapi_nr_ul_config_prach_pdu *prach_pdu = &prach_var->prach_pdu; @@ -1423,8 +1392,6 @@ static void nr_ue_prach_procedures(PHY_VARS_NR_UE *ue, const UE_nr_rxtx_proc_t * prach_var->amp = AMP; - VCD_SIGNAL_DUMPER_DUMP_FUNCTION_BY_NAME(VCD_SIGNAL_DUMPER_FUNCTIONS_UE_GENERATE_PRACH, VCD_FUNCTION_IN); - start_meas_nr_ue_phy(ue, PRACH_GEN_STATS); prach_power = generate_nr_prach(ue, gNB_id, frame_tx, nr_slot_tx, txData); stop_meas_nr_ue_phy(ue, PRACH_GEN_STATS); @@ -1436,8 +1403,6 @@ static void nr_ue_prach_procedures(PHY_VARS_NR_UE *ue, const UE_nr_rxtx_proc_t * ue->phy_cpu_stats.cpu_time_stats[PRACH_GEN_STATS].p_time / (cpuf * 1000.0)); } - VCD_SIGNAL_DUMPER_DUMP_FUNCTION_BY_NAME(VCD_SIGNAL_DUMPER_FUNCTIONS_UE_GENERATE_PRACH, VCD_FUNCTION_OUT); - LOG_D(PHY, "In %s: [UE %d][RAPROC][%d.%d]: Generated PRACH Msg1 (TX power PRACH %d dBm, digital power %d dBW (amp %d)\n", __FUNCTION__, @@ -1457,7 +1422,4 @@ static void nr_ue_prach_procedures(PHY_VARS_NR_UE *ue, const UE_nr_rxtx_proc_t * // set as inactive in the last slot prach_var->active = !(nr_slot_tx == (prach_pdu->prach_slot + prach_var->num_prach_slots - 1)); } - - VCD_SIGNAL_DUMPER_DUMP_FUNCTION_BY_NAME(VCD_SIGNAL_DUMPER_FUNCTIONS_PHY_PROCEDURES_UE_TX_PRACH, VCD_FUNCTION_OUT); - } diff --git a/openair1/SCHED_NR_UE/phy_procedures_nr_ue_sl.c b/openair1/SCHED_NR_UE/phy_procedures_nr_ue_sl.c index dbdaaa4689..1e2151d1d3 100644 --- a/openair1/SCHED_NR_UE/phy_procedures_nr_ue_sl.c +++ b/openair1/SCHED_NR_UE/phy_procedures_nr_ue_sl.c @@ -24,7 +24,6 @@ #include "PHY/defs_nr_UE.h" #include #include "common/utils/LOG/log.h" -#include "common/utils/LOG/vcd_signal_dumper.h" #include "UTIL/OPT/opt.h" #include "intertask_interface.h" #include "T.h" @@ -121,8 +120,6 @@ static int nr_ue_psbch_procedures(PHY_VARS_NR_UE *ue, sl_nr_ue_phy_params_t *sl_phy_params = &ue->SL_UE_PHY_PARAMS; uint16_t rx_slss_id = sl_phy_params->sl_config.sl_sync_source.rx_slss_id; - // VCD_SIGNAL_DUMPER_DUMP_FUNCTION_BY_NAME(VCD_SIGNAL_DUMPER_FUNCTIONS_UE_PSBCH_PROCEDURES, VCD_FUNCTION_IN); - LOG_D(NR_PHY, "[UE %d] Frame %d Slot %d, Trying PSBCH (SLSS ID %d)\n", ue->Mod_id, @@ -165,7 +162,6 @@ static int nr_ue_psbch_procedures(PHY_VARS_NR_UE *ue, if (ue->if_inst && ue->if_inst->sl_indication) ue->if_inst->sl_indication(&sl_indication); - // VCD_SIGNAL_DUMPER_DUMP_FUNCTION_BY_NAME(VCD_SIGNAL_DUMPER_FUNCTIONS_UE_PSBCH_PROCEDURES, VCD_FUNCTION_OUT); return ret; } @@ -177,7 +173,6 @@ int psbch_pscch_processing(PHY_VARS_NR_UE *ue, const UE_nr_rxtx_proc_t *proc, nr NR_DL_FRAME_PARMS *fp = &sl_phy_params->sl_frame_params; int sampleShift = INT_MAX; - // VCD_SIGNAL_DUMPER_DUMP_FUNCTION_BY_NAME(VCD_SIGNAL_DUMPER_FUNCTIONS_PHY_PROCEDURES_UE_RX_SL, VCD_FUNCTION_IN); start_meas(&sl_phy_params->phy_proc_sl_rx); LOG_D(NR_PHY, " ****** Sidelink RX-Chain for Frame.Slot %d.%d ****** \n", frame_rx % 1024, nr_slot_rx); @@ -188,7 +183,6 @@ int psbch_pscch_processing(PHY_VARS_NR_UE *ue, const UE_nr_rxtx_proc_t *proc, nr if (phy_data->sl_rx_action == SL_NR_CONFIG_TYPE_RX_PSBCH) { const int estimateSz = fp->symbols_per_slot * fp->ofdm_symbol_size; - // VCD_SIGNAL_DUMPER_DUMP_FUNCTION_BY_NAME(VCD_SIGNAL_DUMPER_FUNCTIONS_UE_SLOT_FEP_PSBCH, VCD_FUNCTION_IN); LOG_D(NR_PHY, " ----- PSBCH RX TTI: frame.slot %d.%d ------ \n", frame_rx % 1024, nr_slot_rx); __attribute__((aligned(32))) struct complex16 dl_ch_estimates[fp->nb_antennas_rx][estimateSz]; @@ -243,7 +237,6 @@ int psbch_pscch_processing(PHY_VARS_NR_UE *ue, const UE_nr_rxtx_proc_t *proc, nr LOG_D(NR_PHY, "Doing N0 measurements in %s\n", __FUNCTION__); // nr_ue_rrc_measurements(ue, proc, rxdataF); - // VCD_SIGNAL_DUMPER_DUMP_FUNCTION_BY_NAME(VCD_SIGNAL_DUMPER_FUNCTIONS_UE_SLOT_FEP_PSBCH, VCD_FUNCTION_OUT); if (frame_rx % 64 == 0) { LOG_I(NR_PHY, "============================================\n"); @@ -275,8 +268,6 @@ void phy_procedures_nrUE_SL_TX(PHY_VARS_NR_UE *ue, const UE_nr_rxtx_proc_t *proc sl_nr_ue_phy_params_t *sl_phy_params = &ue->SL_UE_PHY_PARAMS; NR_DL_FRAME_PARMS *fp = &sl_phy_params->sl_frame_params; - // VCD_SIGNAL_DUMPER_DUMP_FUNCTION_BY_NAME(VCD_SIGNAL_DUMPER_FUNCTIONS_PHY_PROCEDURES_UE_TX_SL,VCD_FUNCTION_IN); - const int samplesF_per_slot = NR_SYMBOLS_PER_SLOT * fp->ofdm_symbol_size; c16_t txdataF_buf[fp->nb_antennas_tx * samplesF_per_slot] __attribute__((aligned(32))); memset(txdataF_buf, 0, sizeof(txdataF_buf)); @@ -326,8 +317,5 @@ void phy_procedures_nrUE_SL_TX(PHY_VARS_NR_UE *ue, const UE_nr_rxtx_proc_t *proc } LOG_D(NR_PHY, "****** end Sidelink TX-Chain for AbsSubframe %d.%d ******\n", frame_tx, slot_tx); - - // VCD_SIGNAL_DUMPER_DUMP_FUNCTION_BY_NAME(VCD_SIGNAL_DUMPER_FUNCTIONS_PHY_PROCEDURES_UE_TX_SL, VCD_FUNCTION_OUT); stop_meas(&sl_phy_params->phy_proc_sl_tx); - } diff --git a/openair1/SIMULATION/NR_PHY/dlschsim.c b/openair1/SIMULATION/NR_PHY/dlschsim.c index cd952a2ecb..fa1ee27db1 100644 --- a/openair1/SIMULATION/NR_PHY/dlschsim.c +++ b/openair1/SIMULATION/NR_PHY/dlschsim.c @@ -28,7 +28,6 @@ #include "common/ran_context.h" #include "common/config/config_userapi.h" #include "common/utils/LOG/log.h" -#include "common/utils/LOG/vcd_signal_dumper.h" #include "common/utils/load_module_shlib.h" #include "T.h" #include "PHY/defs_gNB.h" @@ -143,7 +142,7 @@ int main(int argc, char **argv) randominit(); int c; - while ((c = getopt(argc, argv, "--:O:df:hpVg:i:j:n:l:m:r:s:S:y:z:M:N:F:R:P:L:X:")) != -1) { + while ((c = getopt(argc, argv, "--:O:df:hpg:i:j:n:l:m:r:s:S:y:z:M:N:F:R:P:L:X:")) != -1) { /* ignore long options starting with '--' and their arguments that are handled by configmodule */ /* with this opstring getopt returns 1 for non-option arguments, refer to 'man 3 getopt' */ @@ -215,10 +214,6 @@ int main(int argc, char **argv) #endif break; - case 'V': - ouput_vcd = 1; - break; - case 'S': snr1 = atof(optarg); snr1set = 1; @@ -319,7 +314,6 @@ int main(int argc, char **argv) printf("%s -h(elp) -p(extended_prefix) -N cell_id -f output_filename -F input_filename -g channel_model -n n_frames -t Delayspread -s snr0 -S snr1 -y TXant -z RXant -i Intefrence0 -j Interference1 -A interpolation_file -C(alibration offset dB) -N CellId\n", argv[0]); printf("-h This message\n"); printf("-p Use extended prefix mode\n"); - printf("-V Enable VCD dumb functions\n"); //printf("-d Use TDD\n"); printf("-n Number of frames to simulate\n"); printf("-s Starting SNR, runs from SNR0 to SNR0 + 5 dB. If n_frames is 1 then just SNR is simulated\n"); @@ -351,9 +345,6 @@ int main(int argc, char **argv) if (snr1set == 0) snr1 = snr0 + 10; - if (ouput_vcd) - vcd_signal_dumper_init("/tmp/openair_dump_nr_dlschsim.vcd"); - gNB2UE = new_channel_desc_scm(n_tx, n_rx, channel_model, @@ -562,8 +553,6 @@ int main(int argc, char **argv) exit(-1); #endif - vcd_signal_dumper_dump_function_by_name(VCD_SIGNAL_DUMPER_FUNCTIONS_DLSCH_DECODING0, VCD_FUNCTION_IN); - int a_segments = MAX_NUM_NR_DLSCH_SEGMENTS_PER_LAYER*NR_MAX_NB_LAYERS; //number of segments to be allocated int num_rb = dlsch0_ue->dlsch_config.number_rbs; if (num_rb != 273) { @@ -585,8 +574,6 @@ int main(int argc, char **argv) 1, DLSCH_ids); - vcd_signal_dumper_dump_function_by_name(VCD_SIGNAL_DUMPER_FUNCTIONS_DLSCH_DECODING0, VCD_FUNCTION_OUT); - if (dlsch0_ue->last_iteration_cnt > dlsch0_ue->max_ldpc_iterations) n_errors++; @@ -657,8 +644,6 @@ int main(int argc, char **argv) if (input_fd) fclose(input_fd); - if (ouput_vcd) - vcd_signal_dumper_close(); end_configmodule(uniqCfg); loader_reset(); logTerm(); diff --git a/openair2/LAYER2/NR_MAC_UE/nr_ue_procedures.c b/openair2/LAYER2/NR_MAC_UE/nr_ue_procedures.c index 672bc3c3a2..9e3b75d006 100644 --- a/openair2/LAYER2/NR_MAC_UE/nr_ue_procedures.c +++ b/openair2/LAYER2/NR_MAC_UE/nr_ue_procedures.c @@ -53,7 +53,6 @@ #include "assertions.h" #include "oai_asn1.h" #include "common/utils/LOG/log.h" -#include "common/utils/LOG/vcd_signal_dumper.h" #include "LAYER2/nr_rlc/nr_rlc_oai_api.h" // #define DEBUG_MIB @@ -4208,8 +4207,6 @@ int nr_write_ce_ulsch_pdu(uint8_t *mac_ce, void nr_ue_send_sdu(NR_UE_MAC_INST_t *mac, nr_downlink_indication_t *dl_info, int pdu_id) { - VCD_SIGNAL_DUMPER_DUMP_FUNCTION_BY_NAME(VCD_SIGNAL_DUMPER_FUNCTIONS_UE_SEND_SDU, VCD_FUNCTION_IN); - LOG_D(NR_MAC, "In [%d.%d] Handling DLSCH PDU type %d\n", dl_info->frame, @@ -4237,5 +4234,4 @@ void nr_ue_send_sdu(NR_UE_MAC_INST_t *mac, nr_downlink_indication_t *dl_info, in default : AssertFatal(false, "Invalid DLSCH PDU type\n"); } - VCD_SIGNAL_DUMPER_DUMP_FUNCTION_BY_NAME(VCD_SIGNAL_DUMPER_FUNCTIONS_UE_SEND_SDU, VCD_FUNCTION_OUT); } diff --git a/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler.c b/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler.c index 6a9cce656a..4df7f690a1 100644 --- a/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler.c +++ b/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler.c @@ -36,7 +36,6 @@ #include "common/utils/LOG/log.h" #include "common/utils/nr/nr_common.h" -#include "common/utils/LOG/vcd_signal_dumper.h" #include "UTIL/OPT/opt.h" #include "openair2/X2AP/x2ap_eNB.h" @@ -159,7 +158,6 @@ void gNB_dlsch_ulsch_scheduler(module_id_t module_idP, frame_t frame, slot_t slo gNB->frame = frame; start_meas(&gNB->gNB_scheduler); - VCD_SIGNAL_DUMPER_DUMP_FUNCTION_BY_NAME(VCD_SIGNAL_DUMPER_FUNCTIONS_gNB_DLSCH_ULSCH_SCHEDULER,VCD_FUNCTION_IN); for (int CC_id = 0; CC_id < MAX_NUM_CCs; CC_id++) { int num_beams = 1; @@ -264,5 +262,4 @@ void gNB_dlsch_ulsch_scheduler(module_id_t module_idP, frame_t frame, slot_t slo stop_meas(&gNB->gNB_scheduler); NR_SCHED_UNLOCK(&gNB->sched_lock); - VCD_SIGNAL_DUMPER_DUMP_FUNCTION_BY_NAME(VCD_SIGNAL_DUMPER_FUNCTIONS_gNB_DLSCH_ULSCH_SCHEDULER,VCD_FUNCTION_OUT); } diff --git a/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_RA.c b/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_RA.c index 1657a492fe..8d1eda288b 100644 --- a/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_RA.c +++ b/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_RA.c @@ -37,7 +37,6 @@ /* Utils */ #include "common/utils/LOG/log.h" -#include "common/utils/LOG/vcd_signal_dumper.h" #include "common/utils/nr/nr_common.h" #include "UTIL/OPT/opt.h" @@ -696,8 +695,6 @@ void nr_initiate_ra_proc(module_id_t module_idP, int16_t timing_offset, uint32_t preamble_power) { - VCD_SIGNAL_DUMPER_DUMP_FUNCTION_BY_NAME(VCD_SIGNAL_DUMPER_FUNCTIONS_INITIATE_RA_PROC, 1); - gNB_MAC_INST *nr_mac = RC.nrmac[module_idP]; NR_SCHED_LOCK(&nr_mac->sched_lock); @@ -766,7 +763,6 @@ void nr_initiate_ra_proc(module_id_t module_idP, UE->UE_beam_index = get_beam_from_ssbidx(nr_mac, cc->ssb_index[n_ssb]); LOG_I(NR_MAC, "UE %04x: Sync beam index %d\n", UE->rnti, UE->UE_beam_index); NR_SCHED_UNLOCK(&nr_mac->sched_lock); - VCD_SIGNAL_DUMPER_DUMP_FUNCTION_BY_NAME(VCD_SIGNAL_DUMPER_FUNCTIONS_INITIATE_RA_PROC, 0); } static void start_ra_contention_resolution_timer(NR_RA_t *ra, const long ra_ContentionResolutionTimer, const int K2, const int scs) diff --git a/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_bch.c b/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_bch.c index b3e3236161..501543e4be 100644 --- a/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_bch.c +++ b/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_bch.c @@ -34,7 +34,6 @@ #include "NR_MAC_gNB/nr_mac_gNB.h" #include "NR_MAC_gNB/mac_proto.h" #include "common/utils/LOG/log.h" -#include "common/utils/LOG/vcd_signal_dumper.h" #include "UTIL/OPT/opt.h" #include "common/utils/nr/nr_common.h" diff --git a/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_primitives.c b/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_primitives.c index 2dea54ea4a..7742b06294 100644 --- a/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_primitives.c +++ b/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_primitives.c @@ -35,7 +35,6 @@ #include "NR_MAC_gNB/nr_mac_gNB.h" #include "NR_MAC_gNB/mac_proto.h" #include "common/utils/LOG/log.h" -#include "common/utils/LOG/vcd_signal_dumper.h" #include "common/utils/nr/nr_common.h" #include "UTIL/OPT/opt.h" diff --git a/openair2/RRC/NR_UE/rrc_UE.c b/openair2/RRC/NR_UE/rrc_UE.c index 4860e0b63e..86cfb87a3c 100644 --- a/openair2/RRC/NR_UE/rrc_UE.c +++ b/openair2/RRC/NR_UE/rrc_UE.c @@ -61,7 +61,6 @@ #include "openair3/SECU/key_nas_deriver.h" #include "common/utils/LOG/log.h" -#include "common/utils/LOG/vcd_signal_dumper.h" #ifndef CELLULAR #include "RRC/NR/MESSAGES/asn1_msg.h" @@ -294,8 +293,6 @@ static void nr_rrc_process_ntnconfig(NR_UE_RRC_INST_t *rrc, NR_UE_RRC_SI_INFO *S static void nr_decode_SI(NR_UE_RRC_SI_INFO *SI_info, NR_SystemInformation_t *si, NR_UE_RRC_INST_t *rrc, int hfn, int frame) { - VCD_SIGNAL_DUMPER_DUMP_FUNCTION_BY_NAME(VCD_SIGNAL_DUMPER_FUNCTIONS_RRC_UE_DECODE_SI, VCD_FUNCTION_IN); - // Dump contents if (si->criticalExtensions.present == NR_SystemInformation__criticalExtensions_PR_systemInformation || si->criticalExtensions.present == NR_SystemInformation__criticalExtensions_PR_criticalExtensionsFuture_r16) { @@ -386,7 +383,6 @@ static void nr_decode_SI(NR_UE_RRC_SI_INFO *SI_info, NR_SystemInformation_t *si, sib19_msg->can_start_ra = rrc->is_NTN_UE; nr_rrc_send_msg_to_mac(rrc, &rrc_msg); } - VCD_SIGNAL_DUMPER_DUMP_FUNCTION_BY_NAME(VCD_SIGNAL_DUMPER_FUNCTIONS_RRC_UE_DECODE_SI, VCD_FUNCTION_OUT); } static void nr_rrc_ue_prepare_RRCSetupRequest(NR_UE_RRC_INST_t *rrc) @@ -2014,7 +2010,6 @@ static void nr_rrc_ue_decode_NR_BCCH_DL_SCH_Message(NR_UE_RRC_INST_t *rrc, if (Sdu_len == 0) // decoding failed in L2 return; - VCD_SIGNAL_DUMPER_DUMP_FUNCTION_BY_NAME(VCD_SIGNAL_DUMPER_FUNCTIONS_UE_DECODE_BCCH, VCD_FUNCTION_IN); NR_BCCH_DL_SCH_Message_t *bcch_message = NULL; asn_dec_rval_t dec_rval = uper_decode_complete(NULL, &asn_DEF_NR_BCCH_DL_SCH_Message, @@ -2027,7 +2022,6 @@ static void nr_rrc_ue_decode_NR_BCCH_DL_SCH_Message(NR_UE_RRC_INST_t *rrc, log_dump(NR_RRC, Sdu, Sdu_len, LOG_DUMP_CHAR," Received bytes:\n"); // free the memory SEQUENCE_free(&asn_DEF_NR_BCCH_DL_SCH_Message, (void *)bcch_message, 1); - VCD_SIGNAL_DUMPER_DUMP_FUNCTION_BY_NAME( VCD_SIGNAL_DUMPER_FUNCTIONS_UE_DECODE_BCCH, VCD_FUNCTION_OUT ); return; } @@ -2053,7 +2047,6 @@ static void nr_rrc_ue_decode_NR_BCCH_DL_SCH_Message(NR_UE_RRC_INST_t *rrc, } } SEQUENCE_free(&asn_DEF_NR_BCCH_DL_SCH_Message, bcch_message, ASFM_FREE_EVERYTHING); - VCD_SIGNAL_DUMPER_DUMP_FUNCTION_BY_NAME( VCD_SIGNAL_DUMPER_FUNCTIONS_UE_DECODE_BCCH, VCD_FUNCTION_OUT ); } static void rrc_ue_generate_RRCSetupComplete(const NR_UE_RRC_INST_t *rrc, const uint8_t Transaction_id) @@ -2232,7 +2225,6 @@ static int8_t nr_rrc_ue_decode_ccch(NR_UE_RRC_INST_t *rrc, const NRRrcMacCcchDat NR_DL_CCCH_Message_t *dl_ccch_msg = NULL; asn_dec_rval_t dec_rval; int rval=0; - VCD_SIGNAL_DUMPER_DUMP_FUNCTION_BY_NAME(VCD_SIGNAL_DUMPER_FUNCTIONS_UE_DECODE_CCCH, VCD_FUNCTION_IN); LOG_D(RRC, "[NR UE%ld] Decoding DL-CCCH message (%d bytes), State %d\n", rrc->ue_id, ind->sdu_size, rrc->nrRrcState); dec_rval = uper_decode(NULL, &asn_DEF_NR_DL_CCCH_Message, (void **)&dl_ccch_msg, ind->sdu, ind->sdu_size, 0, 0); @@ -2242,7 +2234,6 @@ static int8_t nr_rrc_ue_decode_ccch(NR_UE_RRC_INST_t *rrc, const NRRrcMacCcchDat if ((dec_rval.code != RC_OK) && (dec_rval.consumed == 0)) { LOG_E(RRC, "[UE %ld] Failed to decode DL-CCCH-Message (%zu bytes)\n", rrc->ue_id, dec_rval.consumed); - VCD_SIGNAL_DUMPER_DUMP_FUNCTION_BY_NAME(VCD_SIGNAL_DUMPER_FUNCTIONS_UE_DECODE_CCCH, VCD_FUNCTION_OUT); return -1; } @@ -2273,7 +2264,6 @@ static int8_t nr_rrc_ue_decode_ccch(NR_UE_RRC_INST_t *rrc, const NRRrcMacCcchDat } ASN_STRUCT_FREE(asn_DEF_NR_DL_CCCH_Message, dl_ccch_msg); - VCD_SIGNAL_DUMPER_DUMP_FUNCTION_BY_NAME(VCD_SIGNAL_DUMPER_FUNCTIONS_UE_DECODE_CCCH, VCD_FUNCTION_OUT); return rval; } diff --git a/radio/fhi_72/oran_isolate.c b/radio/fhi_72/oran_isolate.c index 89b16d7198..c9991549b1 100644 --- a/radio/fhi_72/oran_isolate.c +++ b/radio/fhi_72/oran_isolate.c @@ -29,7 +29,6 @@ #include "xran_sync_api.h" #include "common/utils/LOG/log.h" -#include "common/utils/LOG/vcd_signal_dumper.h" #include "openair1/PHY/defs_gNB.h" #include "openair1/PHY/NR_TRANSPORT/nr_transport_proto.h" #include "oaioran.h" From abd2bc0e4523b20893850a3ad9242be27ad1bac5 Mon Sep 17 00:00:00 2001 From: Jaroslava Fiedlerova Date: Wed, 11 Feb 2026 11:20:03 +0100 Subject: [PATCH 070/100] CI: Rename RAN-SA-2x2-Module-CN5G pipeline RAN-SA-2x2-Module-CN5G performs tests not only with 2x2 but also with a 4x4 MIMO configuration. The pipeline name has been generalized to RAN-SA-Multi-Antenna-CN5G to cover multiple antenna configurations. --- ci-scripts/Jenkinsfile-GitLab-Container | 8 ++++---- doc/TESTBenches.md | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/ci-scripts/Jenkinsfile-GitLab-Container b/ci-scripts/Jenkinsfile-GitLab-Container index e395973b25..d89c8a878f 100644 --- a/ci-scripts/Jenkinsfile-GitLab-Container +++ b/ci-scripts/Jenkinsfile-GitLab-Container @@ -703,11 +703,11 @@ pipeline { } } } - stage ("SA-2x2-Module-CN5G") { + stage ("SA-Multi-Antenna-CN5G") { when { expression {do5Gtest} } steps { script { - triggerSlaveJob ('RAN-SA-2x2-Module-CN5G', 'SA-2x2-Module-CN5G') + triggerSlaveJob ('RAN-SA-Multi-Antenna-CN5G', 'SA-Multi-Antenna-CN5G') } } post { @@ -715,13 +715,13 @@ pipeline { script { // Using a unique variable name for each test stage to avoid overwriting on a global variable // due to parallel-time concurrency - saTDD2x2Status = finalizeSlaveJob('RAN-SA-2x2-Module-CN5G') + saMultiAntennaStatus = finalizeSlaveJob('RAN-SA-Multi-Antenna-CN5G') } } failure { script { currentBuild.result = 'FAILURE' - failingStages += saTDD2x2Status + failingStages += saMultiAntennaStatus } } } diff --git a/doc/TESTBenches.md b/doc/TESTBenches.md index 42558449b3..ea38ae5135 100644 --- a/doc/TESTBenches.md +++ b/doc/TESTBenches.md @@ -220,7 +220,7 @@ information on how the images are built. ~5G-NR - 5G-NR SA test setup: OAI VNF + PNF/NVIDIA CUBB on gracehopper1-oai + Foxconn RU, up2 + COTS UE (Quectel RM520N), OAI CN5G - container images for gNB deployment -- [RAN-SA-2x2-Module-CN5G](https://jenkins-oai.eurecom.fr/view/RAN/job/RAN-SA-2x2-Module-CN5G/) +- [RAN-SA-Multi-Antenna-CN5G](https://jenkins-oai.eurecom.fr/view/RAN/job/RAN-SA-Multi-Antenna-CN5G/) ~5G-NR - matix + N310 (gNB), up2 + COTS UE (Quectel RM520N), OAI 5GC deployed in docker on matix - NR performance tests: 2x2 configuration, 60 MHz and 100 MHz bandwidth From b2093089ab1f56b175cab4760a1fd096fc1ed3e3 Mon Sep 17 00:00:00 2001 From: rmagueta Date: Wed, 11 Feb 2026 11:24:21 +0000 Subject: [PATCH 071/100] Fix SRS power computation --- openair1/PHY/NR_ESTIMATION/nr_ul_channel_estimation.c | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/openair1/PHY/NR_ESTIMATION/nr_ul_channel_estimation.c b/openair1/PHY/NR_ESTIMATION/nr_ul_channel_estimation.c index f9203f488f..d4432ef308 100644 --- a/openair1/PHY/NR_ESTIMATION/nr_ul_channel_estimation.c +++ b/openair1/PHY/NR_ESTIMATION/nr_ul_channel_estimation.c @@ -992,17 +992,8 @@ int nr_srs_channel_estimation(int ant, // Compute wideband SNR on the symbol 0 int tot_subcarriers = m_SRS_b * NR_NB_SC_PER_RB; - uint16_t subcarrier_abs = first_subcarrier + nr_srs_info->k_0_p[p_index][0]; - if (subcarrier_abs + tot_subcarriers < ofdm_symbol_size_half) { - *signal_power = signal_energy_nodc(&srs_estimated_channel_freq[subcarrier_abs], tot_subcarriers) / tot_subcarriers; - } else { - int size1 = ofdm_symbol_size_half - subcarrier_abs; - int size2 = tot_subcarriers - size1; - uint64_t signal_power_p1 = signal_energy_nodc(&srs_estimated_channel_freq[subcarrier_abs], size1) * size1; - uint64_t signal_power_p2 = signal_energy_nodc(&srs_estimated_channel_freq[ofdm_symbol_size_half], size2) * size2; - *signal_power = (signal_power_p1 + signal_power_p2) / tot_subcarriers; - } + *signal_power = signal_energy_nodc(&srs_estimated_channel_freq[subcarrier_abs], tot_subcarriers); if (*signal_power == 0) { LOG_W(NR_PHY, "Received SRS signal power is 0\n"); From c5834e6c1bab5f049e1acc6401fb09aebf7e7fa5 Mon Sep 17 00:00:00 2001 From: Robert Schmidt Date: Thu, 5 Feb 2026 00:14:42 +0100 Subject: [PATCH 072/100] Fix nr_srssim linking problems when building with E2 support In this case, there are a number of linker errors: /usr/bin/ld: libL2_NR.a(rrc_gNB.c.o): in function `nr_rrc_transfer_protected_rrc_message': /home/richie/oai/openair2/RRC/NR/rrc_gNB.c:330:(.text+0x6f6): undefined reference to `signal_rrc_msg' However, nr_srssim does not need RRC, while it is being linked in. Thus, link without nr_rrc and L2. For the remaining two linker problems: - provide a definition of NR_IF_Module_init() as in most other simulators - move rrc_get_max_nr_csrs() to nr_common.c. It is already declared in the corresponding header, and itself does not have anything to do with RRC radio configuration, therefore it does not belong to nr_radio_config.c --- CMakeLists.txt | 2 +- common/utils/nr/nr_common.c | 25 ++++++++++++++++++++ openair1/SIMULATION/NR_PHY/srssim.c | 2 ++ openair2/LAYER2/NR_MAC_gNB/nr_radio_config.c | 25 -------------------- 4 files changed, 28 insertions(+), 26 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index bae1c590bc..154493f6d9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2080,7 +2080,7 @@ add_executable(nr_srssim ) target_link_libraries(nr_srssim PRIVATE - -Wl,--start-group UTIL SIMU PHY_COMMON PHY_NR_COMMON PHY_NR PHY_NR_UE SCHED_NR_LIB SCHED_NR_UE_LIB MAC_UE_NR MAC_NR_COMMON nr_rrc L2_NR -lz -Wl,--end-group + -Wl,--start-group UTIL SIMU PHY_COMMON PHY_NR_COMMON PHY_NR PHY_NR_UE SCHED_NR_LIB SCHED_NR_UE_LIB MAC_UE_NR MAC_NR_COMMON -lz -Wl,--end-group m pthread ITTI dl nr_ue_phy_meas physim_common ) diff --git a/common/utils/nr/nr_common.c b/common/utils/nr/nr_common.c index 96b8681cff..2beee06c26 100644 --- a/common/utils/nr/nr_common.c +++ b/common/utils/nr/nr_common.c @@ -1447,6 +1447,31 @@ unsigned short get_N_b_srs(int c_srs, int b_srs) { return srs_bandwidth_config[c_srs][b_srs][1]; } +// TODO: Implement to b_SRS = 1 and b_SRS = 2 +long rrc_get_max_nr_csrs(const int max_rbs, const long b_SRS) +{ + if(b_SRS>0) { + LOG_E(NR_RRC,"rrc_get_max_nr_csrs(): Not implemented yet for b_SRS>0\n"); + return 0; // This c_srs is always valid + } + + const uint16_t m_SRS[64] = { 4, 8, 12, 16, 16, 20, 24, 24, 28, 32, 36, 40, 48, 48, 52, 56, 60, 64, 72, 72, 76, 80, 88, + 96, 96, 104, 112, 120, 120, 120, 128, 128, 128, 132, 136, 144, 144, 144, 144, 152, 160, + 160, 160, 168, 176, 184, 192, 192, 192, 192, 208, 216, 224, 240, 240, 240, 240, 256, 256, + 256, 264, 272, 272, 272 }; + + long c_srs = 0; + uint16_t m = 4; + for(int c = 1; c<64; c++) { + if(m_SRS[c]>m && m_SRS[c]ext1->maxMIMO_Layers = min(maxMIMO_layers, ue_supported_layers); } -// TODO: Implement to b_SRS = 1 and b_SRS = 2 -long rrc_get_max_nr_csrs(const int max_rbs, const long b_SRS) { - - if(b_SRS>0) { - LOG_E(NR_RRC,"rrc_get_max_nr_csrs(): Not implemented yet for b_SRS>0\n"); - return 0; // This c_srs is always valid - } - - const uint16_t m_SRS[64] = { 4, 8, 12, 16, 16, 20, 24, 24, 28, 32, 36, 40, 48, 48, 52, 56, 60, 64, 72, 72, 76, 80, 88, - 96, 96, 104, 112, 120, 120, 120, 128, 128, 128, 132, 136, 144, 144, 144, 144, 152, 160, - 160, 160, 168, 176, 184, 192, 192, 192, 192, 208, 216, 224, 240, 240, 240, 240, 256, 256, - 256, 264, 272, 272, 272 }; - - long c_srs = 0; - uint16_t m = 4; - for(int c = 1; c<64; c++) { - if(m_SRS[c]>m && m_SRS[c] Date: Thu, 5 Feb 2026 08:29:06 +0100 Subject: [PATCH 073/100] Remove option --disable-hardware-dependency --- cmake_targets/build_oai | 13 +------------ cmake_targets/tools/build_helper | 4 ---- 2 files changed, 1 insertion(+), 16 deletions(-) diff --git a/cmake_targets/build_oai b/cmake_targets/build_oai index c6d1276e88..44ed349594 100755 --- a/cmake_targets/build_oai +++ b/cmake_targets/build_oai @@ -38,7 +38,6 @@ conf_nvram_path=$OPENAIR_DIR/openair3/NAS/TOOLS/ue_eurecom_test_sfr.conf HW="" BUILD_DIR=ran_build -DISABLE_HARDWARE_DEPENDENCY="False" CMAKE_BUILD_TYPE="RelWithDebInfo" CMAKE_CMD="$CMAKE" OPTIONAL_LIBRARIES="telnetsrv enbscope uescope nrscope ldpc_cuda ldpc_aal ldpc_xdma websrv oai_iqplayer imscope imscope_record" @@ -328,10 +327,6 @@ function main() { CMAKE_CMD="$CMAKE_CMD -DT_TRACER=False" echo_info "Disabling the T tracer" shift 1;; - --disable-hardware-dependency) - echo_info "Disabling hardware dependency for compiling software" - DISABLE_HARDWARE_DEPENDENCY="True" - shift 1;; --ue-trace) CMAKE_CMD="$CMAKE_CMD -DUE_DEBUG_TRACE=False" echo_info "Enabling UE trace for debug" @@ -427,23 +422,17 @@ function main() { if [ "$HW" == "OAI_USRP" ] ; then echo_info "installing packages for USRP support" check_install_usrp_uhd_driver - if [ ! -v BUILD_UHD_FROM_SOURCE ] && [ ! "$DISABLE_HARDWARE_DEPENDENCY" == "True" ]; then + if [ ! -v BUILD_UHD_FROM_SOURCE ]; then install_usrp_uhd_driver $UHD_IMAGES_DIR fi fi if [ "$HW" == "OAI_BLADERF" ] ; then echo_info "installing packages for BLADERF support" check_install_bladerf_driver - if [ ! "$DISABLE_HARDWARE_DEPENDENCY" == "True" ]; then - flash_firmware_bladerf - fi fi if [ "$HW" == "OAI_IRIS" ] ; then echo_info "installing packages for IRIS support" check_install_soapy - #if [ ! "$DISABLE_HARDWARE_DEPENDENCY" == "True" ]; then - # flash_firmware_iris - #fi fi fi diff --git a/cmake_targets/tools/build_helper b/cmake_targets/tools/build_helper index 3443d06c43..5301af660d 100755 --- a/cmake_targets/tools/build_helper +++ b/cmake_targets/tools/build_helper @@ -368,10 +368,6 @@ check_install_bladerf_driver(){ fi } -flash_firmware_bladerf() { - $SUDO bladeRF-cli --flash-firmware /usr/share/Nuand/bladeRF/bladeRF_fw.img -} - check_install_lmssdr_driver(){ if ( [ -d "/usr/local/include/lime" ] && [ -f "/usr/local/include/lime/LimeSuite.h" ] ) From 38975ef1207c55b285cda842618c0df9f3af751f Mon Sep 17 00:00:00 2001 From: Robert Schmidt Date: Thu, 5 Feb 2026 02:04:18 +0100 Subject: [PATCH 074/100] Allow installation of multiple hardwares in same build_oai invocation There is no reason to not allow ./build_oai -I -w USRP -w BLADERF To install USRP and BladeRF together (which will be used in the CI in a later commit). Fix this. --- cmake_targets/build_oai | 40 +++++++++++++++++++++------------------- 1 file changed, 21 insertions(+), 19 deletions(-) diff --git a/cmake_targets/build_oai b/cmake_targets/build_oai index 44ed349594..c9e268407d 100755 --- a/cmake_targets/build_oai +++ b/cmake_targets/build_oai @@ -36,7 +36,7 @@ set_openair_env # Variables for UE data generation conf_nvram_path=$OPENAIR_DIR/openair3/NAS/TOOLS/ue_eurecom_test_sfr.conf -HW="" +HWs="" BUILD_DIR=ran_build CMAKE_BUILD_TYPE="RelWithDebInfo" CMAKE_CMD="$CMAKE" @@ -253,22 +253,22 @@ function main() { -w | --hardware) case "$2" in "USRP" | "BLADERF" | "LMSSDR" | "IRIS") - HW="OAI_"$2 + HWs+=" OAI_"$2 TARGET_LIST="$TARGET_LIST oai_${2,,}devif" # ,, makes lowercase CMAKE_CMD="$CMAKE_CMD -DOAI_$2=ON" ;; "SIMU") - HW="OAI_"$2 + HWs+=" OAI_"$2 TARGET_LIST="$TARGET_LIST rfsimulator" CMAKE_CMD="$CMAKE_CMD -DOAI_$2=ON" ;; "AW2SORI") - HW="OAI_"$2 + HWs=" OAI_"$2 TARGET_LIST="$TARGET_LIST aw2sori_transpro" CMAKE_CMD="$CMAKE_CMD -DOAI_$2=ON" ;; "AERIAL"|"Aerial"|"aerial") - HW="OAI_AERIAL" + HWs+=" OAI_AERIAL" CMAKE_CMD="$CMAKE_CMD -DOAI_AERIAL=ON" ;; "None") @@ -419,21 +419,23 @@ function main() { echo_info "installing LTTng" install_lttng $1 fi - if [ "$HW" == "OAI_USRP" ] ; then - echo_info "installing packages for USRP support" - check_install_usrp_uhd_driver - if [ ! -v BUILD_UHD_FROM_SOURCE ]; then - install_usrp_uhd_driver $UHD_IMAGES_DIR + for HW in $HWs; do + if [ "$HW" == "OAI_USRP" ] ; then + echo_info "installing packages for USRP support" + check_install_usrp_uhd_driver + if [ ! -v BUILD_UHD_FROM_SOURCE ]; then + install_usrp_uhd_driver $UHD_IMAGES_DIR + fi + elif [ "$HW" == "OAI_BLADERF" ] ; then + echo_info "installing packages for BLADERF support" + check_install_bladerf_driver + elif [ "$HW" == "OAI_IRIS" ] ; then + echo_info "installing packages for IRIS support" + check_install_soapy + else + echo_warning "no installation scripts for hardware $HW" fi - fi - if [ "$HW" == "OAI_BLADERF" ] ; then - echo_info "installing packages for BLADERF support" - check_install_bladerf_driver - fi - if [ "$HW" == "OAI_IRIS" ] ; then - echo_info "installing packages for IRIS support" - check_install_soapy - fi + done fi if [ "$INSTALL_OPTIONAL" = "1" ] ; then From 4e5d92401b0cae846012f060d55b1bf99a3309c3 Mon Sep 17 00:00:00 2001 From: Robert Schmidt Date: Wed, 4 Feb 2026 09:33:03 +0100 Subject: [PATCH 075/100] Add Debian 12 to list of supported distributions --- cmake_targets/tools/build_helper | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/cmake_targets/tools/build_helper b/cmake_targets/tools/build_helper index 5301af660d..4e832891eb 100755 --- a/cmake_targets/tools/build_helper +++ b/cmake_targets/tools/build_helper @@ -107,6 +107,7 @@ check_supported_distribution() { "ubuntu22.04") return 0 ;; "ubuntu20.04") return 0 ;; "debian11") return 0 ;; + "debian12") return 0 ;; "fedora41") return 0 ;; "fedora40") return 0 ;; "rhel8.2") return 0 ;; @@ -449,7 +450,7 @@ check_install_additional_tools (){ local optional_packages="" if [[ "$OS_BASEDISTRO" == "debian" ]]; then case "$(get_distribution_release)" in - "ubuntu20.04" | "ubuntu22.04" | "debian11" ) + "ubuntu20.04" | "ubuntu22.04" | "debian11" | "debian12") optional_packages="python3 python3-pip python3-dev python3-scipy python3-matplotlib python3-pyroute2 universal-ctags" ;; esac @@ -492,7 +493,7 @@ check_install_oai_software() { if [[ "$OS_BASEDISTRO" == "debian" ]]; then $SUDO apt install -y software-properties-common case "$(get_distribution_release)" in - "debian11") + "debian11" | "debian12") specific_packages="libz-dev" ;; esac From 93c37e96e6ee3bfe861b1760d190816b788e53e5 Mon Sep 17 00:00:00 2001 From: Robert Schmidt Date: Wed, 4 Feb 2026 11:32:27 +0100 Subject: [PATCH 076/100] Make BladeRF library compile Fixes: dd8a25524a97 ("remove dead code in common_lib.h") --- radio/BLADERF/bladerf_lib.c | 1 - 1 file changed, 1 deletion(-) diff --git a/radio/BLADERF/bladerf_lib.c b/radio/BLADERF/bladerf_lib.c index 2298287b5a..708d43583e 100644 --- a/radio/BLADERF/bladerf_lib.c +++ b/radio/BLADERF/bladerf_lib.c @@ -1127,7 +1127,6 @@ int device_init(openair0_device_t *device, openair0_config_t *openair0_cfg) bladerf_log_set_verbosity(BLADERF_LOG_LEVEL_INFO); printf("BLADERF: Initializing openair0_device_t\n"); - device->Mod_id = num_devices++; device->type = BLADERF_DEV; device->trx_start_func = trx_brf_start; device->trx_end_func = trx_brf_end; From 5fdec24b52b7abf34bcc91b70301b79401ec4f7a Mon Sep 17 00:00:00 2001 From: Robert Schmidt Date: Wed, 4 Feb 2026 09:35:03 +0100 Subject: [PATCH 077/100] Delete unused or useless code Note: device->Mod_id is deleted in !3831. The calibration table has no purpose. Co-authored-by: Brice Robert --- radio/BLADERF/bladerf_lib.c | 778 +++--------------------------------- radio/BLADERF/bladerf_lib.h | 122 ------ 2 files changed, 59 insertions(+), 841 deletions(-) delete mode 100644 radio/BLADERF/bladerf_lib.h diff --git a/radio/BLADERF/bladerf_lib.c b/radio/BLADERF/bladerf_lib.c index 708d43583e..5bb2f069dc 100644 --- a/radio/BLADERF/bladerf_lib.c +++ b/radio/BLADERF/bladerf_lib.c @@ -19,60 +19,72 @@ * contact@openairinterface.org */ -/** bladerf_lib.c - * - * Author: navid nikaein - */ - - #include #include -#include -#include "bladerf_lib.h" -#include "math.h" -#include "PHY/sse_intrin.h" +#include + +#include +#include "common_lib.h" + +/*! \brief BladeRF specific data structure */ +typedef struct { + //! opaque BladeRF device struct. An empty ("") or NULL device identifier will result in the first encountered device being opened + //! (using the first discovered backend) + struct bladerf *dev; + + //! Number of buffers + unsigned int num_buffers; + //! Buffer size + unsigned int buffer_size; + //! Number of transfers + unsigned int num_transfers; + //! RX timeout + unsigned int rx_timeout_ms; + //! TX timeout + unsigned int tx_timeout_ms; + //! Metadata for RX + struct bladerf_metadata meta_rx; + //! Metadata for TX + struct bladerf_metadata meta_tx; + //! Sample rate + unsigned int sample_rate; + + // -------------------------------- + // Debug and output control + // -------------------------------- + //! Number of underflows + int num_underflows; + //! Number of overflows + int num_overflows; + //! number of RX errors + int num_rx_errors; + //! Number of TX errors + int num_tx_errors; + + //! timestamp of current TX + uint64_t tx_current_ts; + //! timestamp of current RX + uint64_t rx_current_ts; + //! number of actual samples transmitted + uint64_t tx_actual_nsamps; + //! number of actual samples received + uint64_t rx_actual_nsamps; + //! number of TX samples + uint64_t tx_nsamps; + //! number of RX samples + uint64_t rx_nsamps; + //! number of TX count + uint64_t tx_count; + //! number of RX count + uint64_t rx_count; +} brf_state_t; + +int brf_error(int status); /** @addtogroup _BLADERF_PHY_RF_INTERFACE_ * @{ */ -//! Number of BladeRF devices -int num_devices=0; - -/*These items configure the underlying asynch stream used by the the sync interface. - */ - -/*! \brief BladeRF Init function (not used at the moment) - * \param device RF frontend parameters set by application - * \returns 0 on success - */ -int trx_brf_init(openair0_device_t *device) -{ - return 0; -} - - -/*! \brief get current timestamp - *\param device the hardware to use - *\param module the bladeRf module - *\returns timestamp of BladeRF - */ -openair0_timestamp_t trx_get_timestamp(openair0_device_t *device, bladerf_module module) -{ - int status; - struct bladerf_metadata meta; - brf_state_t *brf = (brf_state_t*)device->priv; - memset(&meta, 0, sizeof(meta)); - - if ((status=bladerf_get_timestamp(brf->dev, module, &meta.timestamp)) != 0) { - fprintf(stderr,"Failed to get current %s timestamp: %s\n",(module == BLADERF_MODULE_RX ) ? "RX" : "TX", bladerf_strerror(status)); - return -1; - } // else {printf("Current RX timestampe 0x%016"PRIx64"\n", meta.timestamp); } - - return meta.timestamp; -} - - /*! \brief Start BladeRF * \param device the hardware to use * \returns 0 on success @@ -314,624 +326,6 @@ int trx_brf_write_init(openair0_device_t *device) return 0; } -#define RXDCLENGTH 16384 -int16_t cos_fsover8[8] = {2047, 1447, 0, -1448, -2047, -1448, 0, 1447}; -int16_t cos_3fsover8[8] = {2047, -1448, 0, 1447, -2047, 1447, 0, -1448}; - -/*! \brief calibration table for BladeRF */ -rx_gain_calib_table_t calib_table_fx4[] = { - {2300000000.0,53.5}, - {1880000000.0,57.0}, - {816000000.0,73.0}, - {-1,0} -}; - - -/*! \brief set RX gain offset from calibration table - * \param openair0_cfg RF frontend parameters set by application - * \param chain_index RF chain ID - */ -void set_rx_gain_offset(openair0_config_t *openair0_cfg, - int chain_index) -{ - int i=0; - // loop through calibration table to find best adjustment factor for RX frequency - double min_diff = 6e9,diff; - - while (openair0_cfg->rx_gain_calib_table[i].freq>0) { - diff = fabs(openair0_cfg->rx_freq[chain_index] - openair0_cfg->rx_gain_calib_table[i].freq); - printf("cal %d: freq %f, offset %f, diff %f\n", - i, - openair0_cfg->rx_gain_calib_table[i].freq, - openair0_cfg->rx_gain_calib_table[i].offset,diff); - if (min_diff > diff) { - min_diff = diff; - openair0_cfg->rx_gain_offset[chain_index] = openair0_cfg->rx_gain_calib_table[i].offset; - } - i++; - } - -} - - -/*! \brief Calibrate LMSSDR RF - * \param device the hardware to use - */ -void calibrate_rf(openair0_device_t *device) -{ - /* TODO: this function does not seem to work. Disabled until fixed. */ - return; - - brf_state_t *brf = (brf_state_t *)device->priv; - openair0_timestamp_t ptimestamp; - int16_t *calib_buffp,*calib_tx_buffp; - int16_t calib_buff[2*RXDCLENGTH]; - int16_t calib_tx_buff[2*RXDCLENGTH]; - int i,j,offI,offQ,offIold,offQold,offInew,offQnew,offphase,offphaseold,offphasenew,offgain,offgainold,offgainnew; - int32_t meanI,meanQ,meanIold,meanQold; - int cnt=0,loop; - - // put TX on a far-away frequency to avoid interference in RX band - bladerf_set_frequency(brf->dev,BLADERF_MODULE_TX, (unsigned int) device->openair0_cfg->rx_freq[0] + 200e6); - // Set gains to close to max - bladerf_set_gain(brf->dev, BLADERF_MODULE_RX, 60); - bladerf_set_gain(brf->dev, BLADERF_MODULE_TX, 60); - - // fill TX buffer with fs/8 complex sinusoid - j=0; - for (i=0; idev,BLADERF_MODULE_RX,BLADERF_CORR_LMS_DCOFF_I,offIold); - bladerf_set_correction(brf->dev,BLADERF_MODULE_RX,BLADERF_CORR_LMS_DCOFF_Q,offQold); - for (i=0; i<10; i++) - trx_brf_read(device, &ptimestamp, (void **)&calib_buffp, RXDCLENGTH, 0); - - for (meanIold=meanQold=i=j=0; i (%d,%d)\n",offIold,offQold,meanIold,meanQold); - - offI=offQ=-2048; - bladerf_set_correction(brf->dev,BLADERF_MODULE_RX,BLADERF_CORR_LMS_DCOFF_I,offI); - bladerf_set_correction(brf->dev,BLADERF_MODULE_RX,BLADERF_CORR_LMS_DCOFF_Q,offQ); - for (i=0; i<10; i++) - trx_brf_read(device, &ptimestamp, (void **)&calib_buffp, RXDCLENGTH, 0); - - for (meanI=meanQ=i=j=0; i (%d,%d)\n",offI,offQ,meanI,meanQ); - - while (cnt++ < 12) { - - offInew=(offIold+offI)>>1; - offQnew=(offQold+offQ)>>1; - - if (meanI*meanI < meanIold*meanIold) { - meanIold = meanI; - offIold = offI; - printf("[BRF] *** RX DC: offI %d => %d\n",offIold,meanI); - } - if (meanQ*meanQ < meanQold*meanQold) { - meanQold = meanQ; - offQold = offQ; - printf("[BRF] *** RX DC: offQ %d => %d\n",offQold,meanQ); - } - offI = offInew; - offQ = offQnew; - bladerf_set_correction(brf->dev,BLADERF_MODULE_RX,BLADERF_CORR_LMS_DCOFF_I,offI); - bladerf_set_correction(brf->dev,BLADERF_MODULE_RX,BLADERF_CORR_LMS_DCOFF_Q,offQ); - - for (i=0; i<10; i++) - trx_brf_read(device, &ptimestamp, (void **)&calib_buffp, RXDCLENGTH, 0); - - for (meanI=meanQ=i=j=0; i (%d,%d)\n",offI,offQ,meanI,meanQ); - } - - printf("[BRF] RX DC: (%d,%d) => (%d,%d)\n",offIold,offQold,meanIold,meanQold); - bladerf_set_correction(brf->dev,BLADERF_MODULE_RX,BLADERF_CORR_LMS_DCOFF_I,offIold); - bladerf_set_correction(brf->dev,BLADERF_MODULE_RX,BLADERF_CORR_LMS_DCOFF_Q,offQold); - - // TX DC offset - // PUT TX as f_RX + fs/4 - // loop back BLADERF_LB_RF_LNA1 - bladerf_set_frequency(brf->dev,BLADERF_MODULE_TX, (unsigned int) device->openair0_cfg->rx_freq[0] + (unsigned int) device->openair0_cfg->sample_rate/4); - bladerf_set_loopback (brf->dev,BLADERF_LB_RF_LNA1); - - offIold=2048; - bladerf_set_correction(brf->dev,BLADERF_MODULE_TX,BLADERF_CORR_LMS_DCOFF_I,offIold); - for (i=0; i<10; i++) { - trx_brf_read(device, &ptimestamp, (void **)&calib_buffp, RXDCLENGTH, 0); - trx_brf_write(device,ptimestamp+5*RXDCLENGTH, (void **)&calib_tx_buffp, RXDCLENGTH, 0, 0); - } - for (meanIold=meanQold=i=j=0; i (%d,%d)\n",offIold,meanIold,meanQold); - - offI=-2048; - bladerf_set_correction(brf->dev,BLADERF_MODULE_TX,BLADERF_CORR_LMS_DCOFF_I,offI); - for (i=0; i<10; i++) { - trx_brf_read(device, &ptimestamp, (void **)&calib_buffp, RXDCLENGTH, 0); - trx_brf_write(device,ptimestamp+5*RXDCLENGTH, (void **)&calib_tx_buffp, RXDCLENGTH, 0, 0); - } - for (meanI=meanQ=i=j=0; i (%d,%d)\n",offI,meanI,meanQ); - cnt = 0; - while (cnt++ < 12) { - - offInew=(offIold+offI)>>1; - if (meanI*meanI+meanQ*meanQ < meanIold*meanIold +meanQold*meanQold) { - printf("[BRF] TX DC (offI): ([%d,%d]) => %d : %d\n",offIold,offI,offInew,meanI*meanI+meanQ*meanQ); - meanIold = meanI; - meanQold = meanQ; - offIold = offI; - } - offI = offInew; - bladerf_set_correction(brf->dev,BLADERF_MODULE_TX,BLADERF_CORR_LMS_DCOFF_I,offI); - - for (i=0; i<10; i++) { - trx_brf_read(device, &ptimestamp, (void **)&calib_buffp, RXDCLENGTH, 0); - trx_brf_write(device,ptimestamp+5*RXDCLENGTH, (void **)&calib_tx_buffp, RXDCLENGTH, 0, 0); - } - for (meanI=meanQ=i=j=0; i (%d,%d)\n",offI,meanI,meanQ); - } - - bladerf_set_correction(brf->dev,BLADERF_MODULE_TX,BLADERF_CORR_LMS_DCOFF_I,offIold); - - offQold=2048; - bladerf_set_correction(brf->dev,BLADERF_MODULE_TX,BLADERF_CORR_LMS_DCOFF_Q,offQold); - for (i=0; i<10; i++) { - trx_brf_read(device, &ptimestamp, (void **)&calib_buffp, RXDCLENGTH, 0); - trx_brf_write(device,ptimestamp+5*RXDCLENGTH, (void **)&calib_tx_buffp, RXDCLENGTH, 0, 0); - } - // project on fs/4 - for (meanIold=meanQold=i=j=0; i (%d,%d)\n",offQold,meanIold,meanQold); - - offQ=-2048; - bladerf_set_correction(brf->dev,BLADERF_MODULE_TX,BLADERF_CORR_LMS_DCOFF_Q,offQ); - for (i=0; i<10; i++) { - trx_brf_read(device, &ptimestamp, (void **)&calib_buffp, RXDCLENGTH, 0); - trx_brf_write(device,ptimestamp+5*RXDCLENGTH, (void **)&calib_tx_buffp, RXDCLENGTH, 0, 0); - } - for (meanI=meanQ=i=j=0; i (%d,%d)\n",offQ,meanI,meanQ); - - cnt=0; - while (cnt++ < 12) { - - offQnew=(offQold+offQ)>>1; - if (meanI*meanI+meanQ*meanQ < meanIold*meanIold +meanQold*meanQold) { - printf("[BRF] TX DC (offQ): ([%d,%d]) => %d : %d\n",offQold,offQ,offQnew,meanI*meanI+meanQ*meanQ); - - meanIold = meanI; - meanQold = meanQ; - offQold = offQ; - } - offQ = offQnew; - bladerf_set_correction(brf->dev,BLADERF_MODULE_TX,BLADERF_CORR_LMS_DCOFF_Q,offQ); - - for (i=0; i<10; i++) { - trx_brf_read(device, &ptimestamp, (void **)&calib_buffp, RXDCLENGTH, 0); - trx_brf_write(device,ptimestamp+5*RXDCLENGTH, (void **)&calib_tx_buffp, RXDCLENGTH, 0, 0); - } - for (meanI=meanQ=i=j=0; i (%d,%d)\n",offQ,meanI,meanQ); - } - - printf("[BRF] TX DC: (%d,%d) => (%d,%d)\n",offIold,offQold,meanIold,meanQold); - - bladerf_set_correction(brf->dev,BLADERF_MODULE_TX,BLADERF_CORR_LMS_DCOFF_Q,offQold); - - // TX IQ imbalance - for (loop=0; loop<2; loop++) { - offphaseold=4096; - bladerf_set_correction(brf->dev,BLADERF_MODULE_TX,BLADERF_CORR_FPGA_PHASE,offphaseold); - for (i=0; i<10; i++) { - trx_brf_read(device, &ptimestamp, (void **)&calib_buffp, RXDCLENGTH, 0); - trx_brf_write(device,ptimestamp+5*RXDCLENGTH, (void **)&calib_tx_buffp, RXDCLENGTH, 0, 0); - } - // project on fs/8 (Image of TX signal in +ve frequencies) - for (meanIold=meanQold=i=j=0; i>11; - meanQold+= (calib_buff[j]*cos_fsover8[(i+2)&7] + calib_buff[j+1]*cos_fsover8[i&7])>>11; - j+=2; - } - - meanIold/=RXDCLENGTH; - meanQold/=RXDCLENGTH; - printf("[BRF] TX IQ (offphase): %d => (%d,%d)\n",offphaseold,meanIold,meanQold); - - offphase=-4096; - bladerf_set_correction(brf->dev,BLADERF_MODULE_TX,BLADERF_CORR_FPGA_PHASE,offphase); - for (i=0; i<10; i++) { - trx_brf_read(device, &ptimestamp, (void **)&calib_buffp, RXDCLENGTH, 0); - trx_brf_write(device,ptimestamp+5*RXDCLENGTH, (void **)&calib_tx_buffp, RXDCLENGTH, 0, 0); - } - // project on fs/8 (Image of TX signal in +ve frequencies) - for (meanI=meanQ=i=j=0; i>11; - meanQ+= (calib_buff[j]*cos_fsover8[(i+2)&7] + calib_buff[j+1]*cos_fsover8[i&7])>>11; - j+=2; - } - - meanI/=RXDCLENGTH; - meanQ/=RXDCLENGTH; - printf("[BRF] TX IQ (offphase): %d => (%d,%d)\n",offphase,meanI,meanQ); - - cnt=0; - while (cnt++ < 13) { - - offphasenew=(offphaseold+offphase)>>1; - printf("[BRF] TX IQ (offphase): ([%d,%d]) => %d : %d\n",offphaseold,offphase,offphasenew,meanI*meanI+meanQ*meanQ); - if (meanI*meanI+meanQ*meanQ < meanIold*meanIold +meanQold*meanQold) { - - - meanIold = meanI; - meanQold = meanQ; - offphaseold = offphase; - } - offphase = offphasenew; - bladerf_set_correction(brf->dev,BLADERF_MODULE_TX,BLADERF_CORR_FPGA_PHASE,offphase); - - for (i=0; i<10; i++) { - trx_brf_read(device, &ptimestamp, (void **)&calib_buffp, RXDCLENGTH, 0); - trx_brf_write(device,ptimestamp+5*RXDCLENGTH, (void **)&calib_tx_buffp, RXDCLENGTH, 0, 0); - } - // project on fs/8 (Image of TX signal in +ve frequencies) - for (meanI=meanQ=i=j=0; i>11; - meanQ+= (calib_buff[j]*cos_fsover8[(i+2)&7] + calib_buff[j+1]*cos_fsover8[i&7])>>11; - j+=2; - } - meanI/=RXDCLENGTH; - meanQ/=RXDCLENGTH; - - // printf("[BRF] TX DC (offQ): %d => (%d,%d)\n",offQ,meanI,meanQ); - } - - printf("[BRF] TX IQ offphase: %d => (%d,%d)\n",offphaseold,meanIold,meanQold); - - bladerf_set_correction(brf->dev,BLADERF_MODULE_TX,BLADERF_CORR_FPGA_PHASE,offphaseold); - - offgainold=4096; - bladerf_set_correction(brf->dev,BLADERF_MODULE_TX,BLADERF_CORR_FPGA_GAIN,offgainold); - for (i=0; i<10; i++) { - trx_brf_read(device, &ptimestamp, (void **)&calib_buffp, RXDCLENGTH, 0); - trx_brf_write(device,ptimestamp+5*RXDCLENGTH, (void **)&calib_tx_buffp, RXDCLENGTH, 0, 0); - } - // project on fs/8 (Image of TX signal in +ve frequencies) - for (meanIold=meanQold=i=j=0; i>11; - meanQold+= (calib_buff[j]*cos_fsover8[(i+2)&7] + calib_buff[j+1]*cos_fsover8[i&7])>>11; - j+=2; - } - - meanIold/=RXDCLENGTH; - meanQold/=RXDCLENGTH; - printf("[BRF] TX IQ (offgain): %d => (%d,%d)\n",offgainold,meanIold,meanQold); - - offgain=-4096; - bladerf_set_correction(brf->dev,BLADERF_MODULE_TX,BLADERF_CORR_FPGA_GAIN,offgain); - for (i=0; i<10; i++) { - trx_brf_read(device, &ptimestamp, (void **)&calib_buffp, RXDCLENGTH, 0); - trx_brf_write(device,ptimestamp+5*RXDCLENGTH, (void **)&calib_tx_buffp, RXDCLENGTH, 0, 0); - } - // project on fs/8 (Image of TX signal in +ve frequencies) - for (meanI=meanQ=i=j=0; i>11; - meanQ+= (calib_buff[j]*cos_fsover8[(i+2)&7] + calib_buff[j+1]*cos_fsover8[i&7])>>11; - j+=2; - } - - meanI/=RXDCLENGTH; - meanQ/=RXDCLENGTH; - printf("[BRF] TX IQ (offgain): %d => (%d,%d)\n",offgain,meanI,meanQ); - - cnt=0; - while (cnt++ < 13) { - - offgainnew=(offgainold+offgain)>>1; - if (meanI*meanI+meanQ*meanQ < meanIold*meanIold +meanQold*meanQold) { - printf("[BRF] TX IQ (offgain): ([%d,%d]) => %d : %d\n",offgainold,offgain,offgainnew,meanI*meanI+meanQ*meanQ); - - meanIold = meanI; - meanQold = meanQ; - offgainold = offgain; - } - offgain = offgainnew; - bladerf_set_correction(brf->dev,BLADERF_MODULE_TX,BLADERF_CORR_FPGA_GAIN,offgain); - - for (i=0; i<10; i++) { - trx_brf_read(device, &ptimestamp, (void **)&calib_buffp, RXDCLENGTH, 0); - trx_brf_write(device,ptimestamp+5*RXDCLENGTH, (void **)&calib_tx_buffp, RXDCLENGTH, 0, 0); - } - // project on fs/8 (Image of TX signal in +ve frequencies) - for (meanI=meanQ=i=j=0; i>11; - meanQ+= (calib_buff[j]*cos_fsover8[(i+2)&7] + calib_buff[j+1]*cos_fsover8[i&7])>>11; - j+=2; - } - meanI/=RXDCLENGTH; - meanQ/=RXDCLENGTH; - - // printf("[BRF] TX DC (offQ): %d => (%d,%d)\n",offQ,meanI,meanQ); - } - - printf("[BRF] TX IQ offgain: %d => (%d,%d)\n",offgainold,meanIold,meanQold); - - bladerf_set_correction(brf->dev,BLADERF_MODULE_TX,BLADERF_CORR_FPGA_GAIN,offgainold); - } - - // RX IQ imbalance - for (loop=0; loop<2; loop++) { - offphaseold=4096; - bladerf_set_correction(brf->dev,BLADERF_MODULE_RX,BLADERF_CORR_FPGA_PHASE,offphaseold); - for (i=0; i<10; i++) { - trx_brf_read(device, &ptimestamp, (void **)&calib_buffp, RXDCLENGTH, 0); - trx_brf_write(device,ptimestamp+5*RXDCLENGTH, (void **)&calib_tx_buffp, RXDCLENGTH, 0, 0); - } - // project on -3fs/8 (Image of TX signal in -ve frequencies) - for (meanIold=meanQold=i=j=0; i>11; - meanQold+= (calib_buff[j]*cos_3fsover8[(i+2)&7] + calib_buff[j+1]*cos_3fsover8[i&7])>>11; - j+=2; - } - - meanIold/=RXDCLENGTH; - meanQold/=RXDCLENGTH; - printf("[BRF] RX IQ (offphase): %d => (%d,%d)\n",offphaseold,meanIold,meanQold); - - offphase=-4096; - bladerf_set_correction(brf->dev,BLADERF_MODULE_RX,BLADERF_CORR_FPGA_PHASE,offphase); - for (i=0; i<10; i++) { - trx_brf_read(device, &ptimestamp, (void **)&calib_buffp, RXDCLENGTH, 0); - trx_brf_write(device,ptimestamp+5*RXDCLENGTH, (void **)&calib_tx_buffp, RXDCLENGTH, 0, 0); - } - // project on -3fs/8 (Image of TX signal in -ve frequencies) - for (meanI=meanQ=i=j=0; i>11; - meanQ+= (calib_buff[j]*cos_3fsover8[(i+2)&7] + calib_buff[j+1]*cos_3fsover8[i&7])>>11; - j+=2; - } - - meanI/=RXDCLENGTH; - meanQ/=RXDCLENGTH; - printf("[BRF] RX IQ (offphase): %d => (%d,%d)\n",offphase,meanI,meanQ); - - cnt=0; - while (cnt++ < 13) { - - offphasenew=(offphaseold+offphase)>>1; - printf("[BRF] RX IQ (offphase): ([%d,%d]) => %d : %d\n",offphaseold,offphase,offphasenew,meanI*meanI+meanQ*meanQ); - if (meanI*meanI+meanQ*meanQ < meanIold*meanIold +meanQold*meanQold) { - - - meanIold = meanI; - meanQold = meanQ; - offphaseold = offphase; - } - offphase = offphasenew; - bladerf_set_correction(brf->dev,BLADERF_MODULE_RX,BLADERF_CORR_FPGA_PHASE,offphase); - - for (i=0; i<10; i++) { - trx_brf_read(device, &ptimestamp, (void **)&calib_buffp, RXDCLENGTH, 0); - trx_brf_write(device,ptimestamp+5*RXDCLENGTH, (void **)&calib_tx_buffp, RXDCLENGTH, 0, 0); - } - // project on -3fs/8 (Image of TX signal in -ve frequencies) - for (meanI=meanQ=i=j=0; i>11; - meanQ+= (calib_buff[j]*cos_3fsover8[(i+2)&7] + calib_buff[j+1]*cos_3fsover8[i&7])>>11; - j+=2; - } - meanI/=RXDCLENGTH; - meanQ/=RXDCLENGTH; - - // printf("[BRF] TX DC (offQ): %d => (%d,%d)\n",offQ,meanI,meanQ); - } - - printf("[BRF] RX IQ offphase: %d => (%d,%d)\n",offphaseold,meanIold,meanQold); - - bladerf_set_correction(brf->dev,BLADERF_MODULE_RX,BLADERF_CORR_FPGA_PHASE,offphaseold); - - offgainold=4096; - bladerf_set_correction(brf->dev,BLADERF_MODULE_RX,BLADERF_CORR_FPGA_GAIN,offgainold); - for (i=0; i<10; i++) { - trx_brf_read(device, &ptimestamp, (void **)&calib_buffp, RXDCLENGTH, 0); - trx_brf_write(device,ptimestamp+5*RXDCLENGTH, (void **)&calib_tx_buffp, RXDCLENGTH, 0,0); - } - // project on -3fs/8 (Image of TX signal in +ve frequencies) - for (meanIold=meanQold=i=j=0; i>11; - meanQold+= (calib_buff[j]*cos_3fsover8[(i+2)&7] + calib_buff[j+1]*cos_3fsover8[i&7])>>11; - j+=2; - } - - meanIold/=RXDCLENGTH; - meanQold/=RXDCLENGTH; - printf("[BRF] RX IQ (offgain): %d => (%d,%d)\n",offgainold,meanIold,meanQold); - - offgain=-4096; - bladerf_set_correction(brf->dev,BLADERF_MODULE_RX,BLADERF_CORR_FPGA_GAIN,offgain); - for (i=0; i<10; i++) { - trx_brf_read(device, &ptimestamp, (void **)&calib_buffp, RXDCLENGTH, 0); - trx_brf_write(device,ptimestamp+5*RXDCLENGTH, (void **)&calib_tx_buffp, RXDCLENGTH, 0, 0); - } - // project on 3fs/8 (Image of TX signal in -ve frequencies) - for (meanI=meanQ=i=j=0; i>11; - meanQ+= (calib_buff[j]*cos_3fsover8[(i+2)&7] + calib_buff[j+1]*cos_3fsover8[i&7])>>11; - j+=2; - } - - meanI/=RXDCLENGTH; - meanQ/=RXDCLENGTH; - printf("[BRF] RX IQ (offgain): %d => (%d,%d)\n",offgain,meanI,meanQ); - - cnt=0; - while (cnt++ < 13) { - - offgainnew=(offgainold+offgain)>>1; - if (meanI*meanI+meanQ*meanQ < meanIold*meanIold +meanQold*meanQold) { - printf("[BRF] RX IQ (offgain): ([%d,%d]) => %d : %d\n",offgainold,offgain,offgainnew,meanI*meanI+meanQ*meanQ); - - meanIold = meanI; - meanQold = meanQ; - offgainold = offgain; - } - offgain = offgainnew; - bladerf_set_correction(brf->dev,BLADERF_MODULE_RX,BLADERF_CORR_FPGA_GAIN,offgain); - - for (i=0; i<10; i++) { - trx_brf_read(device, &ptimestamp, (void **)&calib_buffp, RXDCLENGTH, 0); - trx_brf_write(device,ptimestamp+5*RXDCLENGTH, (void **)&calib_tx_buffp, RXDCLENGTH, 0, 0); - } - // project on -3fs/8 (Image of TX signal in -ve frequencies) - for (meanI=meanQ=i=j=0; i>11; - meanQ+= (calib_buff[j]*cos_3fsover8[(i+2)&7] + calib_buff[j+1]*cos_3fsover8[i&7])>>11; - j+=2; - } - meanI/=RXDCLENGTH; - meanQ/=RXDCLENGTH; - - // printf("[BRF] TX DC (offQ): %d => (%d,%d)\n",offQ,meanI,meanQ); - } - - printf("[BRF] RX IQ offgain: %d => (%d,%d)\n",offgainold,meanIold,meanQold); - - bladerf_set_correction(brf->dev,BLADERF_MODULE_RX,BLADERF_CORR_FPGA_GAIN,offgainold); - } - - bladerf_set_frequency(brf->dev,BLADERF_MODULE_TX, (unsigned int) device->openair0_cfg->tx_freq[0]); - bladerf_set_loopback(brf->dev,BLADERF_LB_NONE); - bladerf_set_gain(brf->dev, BLADERF_MODULE_RX, (unsigned int) device->openair0_cfg->rx_gain[0]-device->openair0_cfg[0].rx_gain_offset[0]); - bladerf_set_gain(brf->dev, BLADERF_MODULE_TX, (unsigned int) device->openair0_cfg->tx_gain[0]); - // LOG_M("blade_rf_test.m","rxs",calib_buff,RXDCLENGTH,1,1); -} - - /*! \brief Initialize Openair BLADERF target. It returns 0 if OK * \param device the hardware to use * \param openair0_cfg RF frontend parameters set by application @@ -969,7 +363,6 @@ int device_init(openair0_device_t *device, openair0_config_t *openair0_cfg) exit(-1); break; } - openair0_cfg->rx_gain_calib_table = calib_table_fx4; // The number of buffers to use in the underlying data stream brf->num_buffers = 128; @@ -1030,7 +423,6 @@ int device_init(openair0_device_t *device, openair0_config_t *openair0_cfg) } else printf("[BRF] set RX bandwidth to %u, %u\n",(unsigned int)openair0_cfg->rx_bw*2, actual_value); - set_rx_gain_offset(&openair0_cfg[0],0); if ((status=bladerf_set_gain(brf->dev, BLADERF_MODULE_RX, (int) openair0_cfg->rx_gain[0]-openair0_cfg[0].rx_gain_offset[0])) != 0) { fprintf(stderr,"Failed to set RX gain: %s\n",bladerf_strerror(status)); brf_error(status); @@ -1097,24 +489,6 @@ int device_init(openair0_device_t *device, openair0_config_t *openair0_cfg) } else printf("[BRF] RX module enabled \n"); - // calibrate - -#if 0 - /* those calls fail for "Nuand bladeRF 2.0 (bladerf2)", so commented, - * but let's keep the code in case it's needed (for other boards, - * for anything, unfortunately we don't have other boards to test - * right now) - */ - if ((status=bladerf_calibrate_dc(brf->dev, BLADERF_DC_CAL_LPF_TUNING)) != 0 || - (status=bladerf_calibrate_dc(brf->dev, BLADERF_DC_CAL_TX_LPF)) != 0 || - (status=bladerf_calibrate_dc(brf->dev, BLADERF_DC_CAL_RX_LPF)) != 0 || - (status=bladerf_calibrate_dc(brf->dev, BLADERF_DC_CAL_RXVGA2)) != 0) { - fprintf(stderr, "[BRF] error calibrating\n"); - brf_error(status); - } else - printf("[BRF] calibration OK\n"); -#endif - /* set log to info, available log levels are: * - BLADERF_LOG_LEVEL_VERBOSE * - BLADERF_LOG_LEVEL_DEBUG @@ -1141,8 +515,6 @@ int device_init(openair0_device_t *device, openair0_config_t *openair0_cfg) device->openair0_cfg = openair0_cfg; device->priv = (void *)brf; - calibrate_rf(device); - // memcpy((void*)&device->openair0_cfg,(void*)&openair0_cfg[0],sizeof(openair0_config_t)); if ((status=bladerf_enable_module(brf->dev, BLADERF_MODULE_TX, false)) != 0) { @@ -1168,36 +540,4 @@ int brf_error(int status) exit(-1); return status; // or status error code } - - -/*! \brief Open BladeRF from serial port - * \param serial name of serial port on which to open BladeRF device - * \returns bladerf device structure - */ -struct bladerf * open_bladerf_from_serial(const char *serial) -{ - int status; - struct bladerf *dev; - struct bladerf_devinfo info; - /* Initialize all fields to "don't care" wildcard values. - * - * Immediately passing this to bladerf_open_with_devinfo() would cause - * libbladeRF to open any device on any available backend. */ - bladerf_init_devinfo(&info); - /* Specify the desired device's serial number, while leaving all other - * fields in the info structure wildcard values */ - strncpy(info.serial, serial, BLADERF_SERIAL_LENGTH - 1); - info.serial[BLADERF_SERIAL_LENGTH - 1] = '\0'; - status = bladerf_open_with_devinfo(&dev, &info); - - if (status == BLADERF_ERR_NODEV) { - printf("No devices available with serial=%s\n", serial); - return NULL; - } else if (status != 0) { - fprintf(stderr, "Failed to open device with serial=%s (%s)\n", serial, bladerf_strerror(status)); - return NULL; - } else { - return dev; - } -} /*@}*/ diff --git a/radio/BLADERF/bladerf_lib.h b/radio/BLADERF/bladerf_lib.h deleted file mode 100644 index a3ba44ee6c..0000000000 --- a/radio/BLADERF/bladerf_lib.h +++ /dev/null @@ -1,122 +0,0 @@ -/* - * 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 - */ - -/** bladerf_lib.h - * - * Author: navid nikaein - */ - -#ifndef BLADERF_LIB_H -#define BLADERF_LIB_H - -#include - -#include "common_lib.h" - -/** @addtogroup _BLADERF_PHY_RF_INTERFACE_ - * @{ - */ - -/*! \brief BladeRF specific data structure */ -typedef struct { - - //! opaque BladeRF device struct. An empty ("") or NULL device identifier will result in the first encountered device being opened (using the first discovered backend) - struct bladerf *dev; - - //! Number of buffers - unsigned int num_buffers; - //! Buffer size - unsigned int buffer_size; - //! Number of transfers - unsigned int num_transfers; - //! RX timeout - unsigned int rx_timeout_ms; - //! TX timeout - unsigned int tx_timeout_ms; - //! Metadata for RX - struct bladerf_metadata meta_rx; - //!Metadata for TX - struct bladerf_metadata meta_tx; - //! Sample rate - unsigned int sample_rate; - //! time offset between transmiter timestamp and receiver timestamp; - double tdiff; - //! TX number of forward samples use brf_time_offset to get this value - int tx_forward_nsamps; //166 for 20Mhz - - - // -------------------------------- - // Debug and output control - // -------------------------------- - //! Number of underflows - int num_underflows; - //! Number of overflows - int num_overflows; - //! number of sequential errors - int num_seq_errors; - //! number of RX errors - int num_rx_errors; - //! Number of TX errors - int num_tx_errors; - - //! timestamp of current TX - uint64_t tx_current_ts; - //! timestamp of current RX - uint64_t rx_current_ts; - //! number of actual samples transmitted - uint64_t tx_actual_nsamps; - //! number of actual samples received - uint64_t rx_actual_nsamps; - //! number of TX samples - uint64_t tx_nsamps; - //! number of RX samples - uint64_t rx_nsamps; - //! number of TX count - uint64_t tx_count; - //! number of RX count - uint64_t rx_count; - //! timestamp of RX packet - openair0_timestamp_t rx_timestamp; - -} brf_state_t; -/* - * func prototypes - */ -/*! \brief BladeRF Init function (not used at the moment) - * \param device RF frontend parameters set by application - */ -int trx_brf_init(openair0_device_t *device); - -/*! \brief get current timestamp - *\param device the hardware to use - *\param module the bladeRf module - */ -openair0_timestamp_t trx_get_timestamp(openair0_device_t *device, bladerf_module module); - -/*! \brief bladeRF error report - * \param status - * \returns 0 on success - */ -int brf_error(int status); - -/*@}*/ - -#endif /* BLADERF_LIB_H */ From 4fdcb11edda9efcd08fe527933a421d4cdeef8f4 Mon Sep 17 00:00:00 2001 From: Brice Robert Date: Wed, 4 Feb 2026 10:25:19 +0100 Subject: [PATCH 078/100] BladeRF: Use global constant for used sampling format --- radio/BLADERF/bladerf_lib.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/radio/BLADERF/bladerf_lib.c b/radio/BLADERF/bladerf_lib.c index 5bb2f069dc..affaef3b61 100644 --- a/radio/BLADERF/bladerf_lib.c +++ b/radio/BLADERF/bladerf_lib.c @@ -85,6 +85,8 @@ int brf_error(int status); * @{ */ +const bladerf_format format = BLADERF_FORMAT_SC16_Q11_META; + /*! \brief Start BladeRF * \param device the hardware to use * \returns 0 on success @@ -98,7 +100,7 @@ int trx_brf_start(openair0_device_t *device) if ((status = bladerf_sync_config(brf->dev, BLADERF_MODULE_TX, - BLADERF_FORMAT_SC16_Q11_META, + format, brf->num_buffers, brf->buffer_size, brf->num_transfers, @@ -108,7 +110,7 @@ int trx_brf_start(openair0_device_t *device) } if ((status = bladerf_sync_config(brf->dev, BLADERF_MODULE_RX, - BLADERF_FORMAT_SC16_Q11_META, + format, brf->num_buffers, brf->buffer_size, brf->num_transfers, From f14aa8283c2bfde2058dfe986f7f3b8083b3e437 Mon Sep 17 00:00:00 2001 From: Robert Schmidt Date: Wed, 4 Feb 2026 10:25:40 +0100 Subject: [PATCH 079/100] Active BladeRF on startup only, introduce BLADERF_CHECK Don't enable radios twice, and only do it on device startup. Simplify that function by using a new BLADERF_CHECK macro that checks return codes and that will be reused in the following commits. --- radio/BLADERF/bladerf_lib.c | 106 +++++++++++++----------------------- 1 file changed, 39 insertions(+), 67 deletions(-) diff --git a/radio/BLADERF/bladerf_lib.c b/radio/BLADERF/bladerf_lib.c index affaef3b61..8326d83084 100644 --- a/radio/BLADERF/bladerf_lib.c +++ b/radio/BLADERF/bladerf_lib.c @@ -85,55 +85,60 @@ int brf_error(int status); * @{ */ +#define BLADERF_CHECK(COND, FMT, ...) \ + if (COND) { \ + LOG_I(HW, FMT "\n" __VA_OPT__(, ) __VA_ARGS__); \ + } else { \ + LOG_E(HW, "Failed: " FMT ": %s\n" __VA_OPT__(, ) __VA_ARGS__, bladerf_strerror(status)); \ + return -1; \ + } + const bladerf_format format = BLADERF_FORMAT_SC16_Q11_META; /*! \brief Start BladeRF * \param device the hardware to use * \returns 0 on success */ -int trx_brf_start(openair0_device_t *device) +static int trx_brf_start(openair0_device_t *device) { - brf_state_t *brf = (brf_state_t*)device->priv; - int status; + brf_state_t *brf = device->priv; + int status; - brf->meta_tx.flags = 0; + brf->meta_tx.flags = 0; - if ((status = bladerf_sync_config(brf->dev, - BLADERF_MODULE_TX, - format, - brf->num_buffers, - brf->buffer_size, - brf->num_transfers, - 100/*brf->tx_timeout_ms*/)) != 0 ) { - fprintf(stderr,"Failed to configure TX sync interface: %s\n", bladerf_strerror(status)); - abort(); - } - if ((status = bladerf_sync_config(brf->dev, - BLADERF_MODULE_RX, - format, - brf->num_buffers, - brf->buffer_size, - brf->num_transfers, - 100/*brf->rx_timeout_ms*/)) != 0 ) { - fprintf(stderr,"Failed to configure RX sync interface: %s\n", bladerf_strerror(status)); - abort(); - } - if ((status=bladerf_enable_module(brf->dev, BLADERF_MODULE_TX, true)) != 0) { - fprintf(stderr,"Failed to enable TX module: %s\n", bladerf_strerror(status)); - abort(); - } - if ((status=bladerf_enable_module(brf->dev, BLADERF_MODULE_RX, true)) != 0) { - fprintf(stderr,"Failed to enable RX module: %s\n", bladerf_strerror(status)); - abort(); - } + /* Configure the device's TX module for use with the sync interface. + * SC16 Q11 samples *with* metadata are used. */ + status = bladerf_sync_config(brf->dev, BLADERF_MODULE_TX, format, + brf->num_buffers, brf->buffer_size, brf->num_transfers, brf->tx_timeout_ms); + BLADERF_CHECK(status == 0, + "Set TX sync interface num_buffers %u, buffer_size %u, num_transfer %u, tx_timeout_ms %u", + brf->num_buffers, brf->buffer_size, brf->num_transfers, brf->tx_timeout_ms); - return 0; + /* Configure the device's RX module for use with the sync interface. + * SC16 Q11 samples *with* metadata are used. */ + status = bladerf_sync_config(brf->dev, BLADERF_MODULE_RX, format, + brf->num_buffers, brf->buffer_size, brf->num_transfers, brf->rx_timeout_ms); + BLADERF_CHECK(status == 0, + "Set RX sync interface num_buffers %u, buffer_size %u, num_transfer %u, rx_timeout_ms %u", + brf->num_buffers, brf->buffer_size, brf->num_transfers, brf->rx_timeout_ms); + + /* We must always enable the TX module after calling bladerf_sync_config(), and + * before attempting to TX samples via bladerf_sync_tx(). */ + status = bladerf_enable_module(brf->dev, BLADERF_MODULE_TX, true); + BLADERF_CHECK(status == 0, "Enable TX module"); + + /* We must always enable the RX module after calling bladerf_sync_config(), and + * before attempting to RX samples via bladerf_sync_rx(). */ + status = bladerf_enable_module(brf->dev, BLADERF_MODULE_RX, true); + BLADERF_CHECK(status == 0, "Enable RX module"); + + return 0; } /*! \brief Called to send samples to the BladeRF RF target \param device pointer to the device structure specific to the RF hardware target - \param timestamp The timestamp at whicch the first sample MUST be sent + \param timestamp The timestamp at which the first sample MUST be sent \param buff Buffer which holds the samples \param nsamps number of samples to be sent \param cc index of the component carrier @@ -458,39 +463,6 @@ int device_init(openair0_device_t *device, openair0_config_t *openair0_cfg) printf("[BRF] set the TX gain to %d\n", (int)openair0_cfg->tx_gain[0]); - /* Configure the device's TX module for use with the sync interface. - * SC16 Q11 samples *with* metadata are used. */ - if ((status=bladerf_sync_config(brf->dev, BLADERF_MODULE_TX,BLADERF_FORMAT_SC16_Q11_META,brf->num_buffers,brf->buffer_size,brf->num_transfers,brf->tx_timeout_ms)) != 0 ) { - fprintf(stderr,"Failed to configure TX sync interface: %s\n", bladerf_strerror(status)); - brf_error(status); - } else - printf("[BRF] configured TX sync interface \n"); - - /* Configure the device's RX module for use with the sync interface. - * SC16 Q11 samples *with* metadata are used. */ - if ((status=bladerf_sync_config(brf->dev, BLADERF_MODULE_RX, BLADERF_FORMAT_SC16_Q11_META,brf->num_buffers,brf->buffer_size,brf->num_transfers,brf->rx_timeout_ms)) != 0 ) { - fprintf(stderr,"Failed to configure RX sync interface: %s\n", bladerf_strerror(status)); - brf_error(status); - } else - printf("[BRF] configured Rx sync interface \n"); - - - /* We must always enable the TX module after calling bladerf_sync_config(), and - * before attempting to TX samples via bladerf_sync_tx(). */ - if ((status=bladerf_enable_module(brf->dev, BLADERF_MODULE_TX, true)) != 0) { - fprintf(stderr,"Failed to enable TX module: %s\n", bladerf_strerror(status)); - brf_error(status); - } else - printf("[BRF] TX module enabled \n"); - - /* We must always enable the RX module after calling bladerf_sync_config(), and - * before attempting to RX samples via bladerf_sync_rx(). */ - if ((status=bladerf_enable_module(brf->dev, BLADERF_MODULE_RX, true)) != 0) { - fprintf(stderr,"Failed to enable RX module: %s\n", bladerf_strerror(status)); - brf_error(status); - } else - printf("[BRF] RX module enabled \n"); - /* set log to info, available log levels are: * - BLADERF_LOG_LEVEL_VERBOSE * - BLADERF_LOG_LEVEL_DEBUG From 792e1ed7d929d74a773ec3b3feefa915d9fb6899 Mon Sep 17 00:00:00 2001 From: Robert Schmidt Date: Wed, 4 Feb 2026 10:25:59 +0100 Subject: [PATCH 080/100] Rewrite BladeRF's device_init() - Add more sampling rates to support, e.g., 40MHz in 5G - Allocate memory only at end to avoid memory leaks in case of failure - Group recurring configuration functionality in configure_channel() to reduced code duplication and uniform output - Read maximum TX gain to set maximum automatically, and analogous to USRP radio lib - Check if BladeRF config (buffer size, num transfers, ...) fulfills requirements as specified in documentation [1] - Set BladeRF logging early to see BladeRF output [1] https://www.nuand.com/bladeRF-doc/libbladeRF/v2.6.0/group___f_n___s_t_r_e_a_m_i_n_g___a_s_y_n_c.html#ga72752f2a047b95544e7686596a409abd Co-authored-by: Brice Robert Closes: #845 --- radio/BLADERF/bladerf_lib.c | 336 ++++++++++++++++++++---------------- 1 file changed, 188 insertions(+), 148 deletions(-) diff --git a/radio/BLADERF/bladerf_lib.c b/radio/BLADERF/bladerf_lib.c index 8326d83084..e3effac03f 100644 --- a/radio/BLADERF/bladerf_lib.c +++ b/radio/BLADERF/bladerf_lib.c @@ -95,6 +95,23 @@ int brf_error(int status); const bladerf_format format = BLADERF_FORMAT_SC16_Q11_META; +static const char *ch_dir(bladerf_channel ch) +{ + switch (ch) { + case BLADERF_CHANNEL_RX(0): + return "RX0"; + case BLADERF_CHANNEL_RX(1): + return "RX1"; + case BLADERF_CHANNEL_TX(0): + return "TX0"; + case BLADERF_CHANNEL_TX(1): + return "TX1"; + default: + return "unknown"; + } + return "unknown"; +} + /*! \brief Start BladeRF * \param device the hardware to use * \returns 0 on success @@ -333,6 +350,40 @@ int trx_brf_write_init(openair0_device_t *device) return 0; } +typedef struct channel_config { + bladerf_channel channel; + unsigned int frequency; + unsigned int sample_rate; + unsigned int bandwidth; + int gain; +} channel_config_t; + + +static int configure_channel(struct bladerf *dev, channel_config_t *c) +{ + int status; + unsigned int actual_value = 0; + const char *dir = ch_dir(c->channel); + + status = bladerf_set_frequency(dev, c->channel, c->frequency); + BLADERF_CHECK(status == 0, "Set %s frequency %u", dir, c->frequency); + + status = bladerf_set_sample_rate(dev, c->channel, c->sample_rate, &actual_value); + BLADERF_CHECK(status == 0, "Set %s sample rate %u (is %u)", dir, c->sample_rate, actual_value); + + status = bladerf_set_bandwidth(dev, c->channel, c->bandwidth, &actual_value); + BLADERF_CHECK(status == 0, "Set %s bandwidth %u (is %u)", dir, c->bandwidth, actual_value); + + status = bladerf_set_gain(dev, c->channel, c->gain); + BLADERF_CHECK(status == 0, "Set %s gain %u", dir, c->gain); + + bladerf_gain g; + status = bladerf_get_gain(dev, c->channel, &g); + BLADERF_CHECK(status == 0, "Get %s gain: %u", dir, g); + + return 0; +} + /*! \brief Initialize Openair BLADERF target. It returns 0 if OK * \param device the hardware to use * \param openair0_cfg RF frontend parameters set by application @@ -340,167 +391,156 @@ int trx_brf_write_init(openair0_device_t *device) */ int device_init(openair0_device_t *device, openair0_config_t *openair0_cfg) { - int status; - brf_state_t *brf = (brf_state_t*)malloc(sizeof(brf_state_t)); - memset(brf, 0, sizeof(brf_state_t)); - /* device specific */ - //openair0_cfg->txlaunch_wait = 1;//manage when TX processing is triggered - //openair0_cfg->txlaunch_wait_slotcount = 1; //manage when TX processing is triggered + int status; + brf_state_t brf = {0}; + + switch ((long) openair0_cfg->sample_rate) { + case 46080000: + openair0_cfg->samples_per_packet = 8192; + openair0_cfg->tx_sample_advance = 0; + brf.num_buffers = 128; + brf.num_transfers = 32; + brf.rx_timeout_ms = 2000; + break; - // init required params - switch ((int)openair0_cfg->sample_rate) { case 30720000: - openair0_cfg->samples_per_packet = 2048; - openair0_cfg->tx_sample_advance = 0; - break; + openair0_cfg->samples_per_packet = 4096; + openair0_cfg->tx_sample_advance = 0; + brf.num_buffers = 128; + brf.num_transfers = 32; + brf.rx_timeout_ms = 2000; + break; + + case 23040000: + openair0_cfg->samples_per_packet = 2048; + openair0_cfg->tx_sample_advance = 0; + brf.num_buffers = 64; + brf.num_transfers = 16; + brf.rx_timeout_ms = 2000; + break; + case 15360000: - openair0_cfg->samples_per_packet = 2048; - openair0_cfg->tx_sample_advance = 0; - break; + openair0_cfg->samples_per_packet = 2048; + openair0_cfg->tx_sample_advance = 0; + + brf.num_buffers = 64; + brf.num_transfers = 16; + brf.rx_timeout_ms = 2000; + break; + case 11520000: + openair0_cfg->samples_per_packet = 2048; + openair0_cfg->tx_sample_advance = 0; + brf.num_buffers = 64; + brf.num_transfers = 16; + brf.rx_timeout_ms = 2000; + break; + case 7680000: - openair0_cfg->samples_per_packet = 1024; - openair0_cfg->tx_sample_advance = 0; - break; + openair0_cfg->samples_per_packet = 1024; + openair0_cfg->tx_sample_advance = 0; + brf.num_buffers = 32; + brf.num_transfers = 16; + brf.rx_timeout_ms = 2000; + break; + case 1920000: - openair0_cfg->samples_per_packet = 256; - openair0_cfg->tx_sample_advance = 50; - break; + openair0_cfg->samples_per_packet = 256; + openair0_cfg->tx_sample_advance = 50; + brf.num_buffers = 128; + brf.num_transfers = 16; + brf.rx_timeout_ms = 2000; + break; + default: - printf("Error: unknown sampling rate %f\n",openair0_cfg->sample_rate); - exit(-1); - break; + LOG_E(HW, "Error: unknown sampling rate %f\n", openair0_cfg->sample_rate); + return -1; + break; + } + + brf.tx_timeout_ms = 2000; + brf.buffer_size = (unsigned int)openair0_cfg->samples_per_packet; + brf.sample_rate = (unsigned int)openair0_cfg->sample_rate; + + float rxterm = brf.num_transfers * brf.buffer_size * 1000.0 / brf.rx_timeout_ms; + float txterm = brf.num_transfers * brf.buffer_size * 1000.0 / brf.tx_timeout_ms; + LOG_I(HW, + "sample rate %d trnsfers %d timeout rx %d tx %d buffer size %d (rx %f, tx %f)\n", + brf.sample_rate, + brf.num_transfers, + brf.rx_timeout_ms, + brf.tx_timeout_ms, + brf.buffer_size, + rxterm, + txterm); + DevAssert(brf.sample_rate > txterm * 1.25); // see doc on async interface + DevAssert(brf.sample_rate > rxterm * 1.25); + + memset(&brf.meta_rx, 0, sizeof(brf.meta_rx)); + memset(&brf.meta_tx, 0, sizeof(brf.meta_tx)); + + // set this to BLADERF_LOG_LEVEL_VERBOSE for more detailed BladeRF info + bladerf_log_set_verbosity(BLADERF_LOG_LEVEL_INFO); + + status = bladerf_open(&brf.dev, openair0_cfg->sdr_addrs); + BLADERF_CHECK(status == 0, "Init BladeRF device '%s'", openair0_cfg->sdr_addrs); + + if (bladerf_device_speed(brf.dev) != BLADERF_DEVICE_SPEED_SUPER) { + LOG_E(HW, "Device does not operate at max speed, change the USB port\n"); + return -1; } - // The number of buffers to use in the underlying data stream - brf->num_buffers = 128; - // the size of the underlying stream buffers, in samples - brf->buffer_size = (unsigned int) openair0_cfg->samples_per_packet;//*sizeof(int32_t); // buffer size = 4096 for sample_len of 1024 - brf->num_transfers = 16; - brf->rx_timeout_ms = 0; - brf->tx_timeout_ms = 0; - brf->sample_rate=(unsigned int)openair0_cfg->sample_rate; + /** [Forcing Tuning Mode to FPGA] */ + const bladerf_tuning_mode tMode = BLADERF_TUNING_MODE_FPGA; + status = bladerf_set_tuning_mode(brf.dev, tMode); + BLADERF_CHECK(status == 0, "Set tuning mode FPGA"); - memset(&brf->meta_rx, 0, sizeof(brf->meta_rx)); - memset(&brf->meta_tx, 0, sizeof(brf->meta_tx)); + status = bladerf_set_gain_mode(brf.dev, BLADERF_CHANNEL_RX(0), BLADERF_GAIN_MGC); + BLADERF_CHECK(status == 0, "Disable AGC"); - printf("\n[BRF] sampling_rate %u, num_buffers %u, buffer_size %u, num transfer %u, timeout_ms (rx %u, tx %u)\n", - brf->sample_rate, brf->num_buffers, brf->buffer_size,brf->num_transfers, brf->rx_timeout_ms, brf->tx_timeout_ms); + channel_config_t rx_chan = { + .channel = BLADERF_CHANNEL_RX(0), + .frequency = openair0_cfg->rx_freq[0], + .sample_rate = openair0_cfg->sample_rate, + .bandwidth = openair0_cfg->rx_bw, + .gain = openair0_cfg->rx_gain[0], + }; + configure_channel(brf.dev, &rx_chan); - if ((status=bladerf_open(&brf->dev, "")) != 0 ) { - fprintf(stderr,"Failed to open brf device: %s\n",bladerf_strerror(status)); - brf_error(status); - } - printf("[BRF] init dev %p\n", brf->dev); - switch(bladerf_device_speed(brf->dev)) { - case BLADERF_DEVICE_SPEED_SUPER: - printf("[BRF] Device operates at max speed\n"); - break; - default: - printf("[BRF] Device does not operates at max speed, change the USB port\n"); - brf_error(BLADERF_ERR_UNSUPPORTED); - } - // RX - // Example of CLI output: RX Frequency: 2539999999Hz + // This is a hack. The USRP library currently does max_gain-tx_gain (tx_gain + // is the attenuation, so completely illogical), but let's keep what users + // would expect by doing the same. Existing configs can then be reused. + // Note also that the doc says that get_gain_range() should be called after + // setting the frequency, but the gain should normally come from outside, so + // be set by user and independent of the exact frequency. + const struct bladerf_range *g = NULL; + status = bladerf_get_gain_range(brf.dev, BLADERF_CHANNEL_TX(0), &g); + BLADERF_CHECK(status == 0 && g != NULL, "Get TX0 gain range"); + channel_config_t tx_chan = { + .channel = BLADERF_CHANNEL_TX(0), + .frequency = openair0_cfg->tx_freq[0], + .sample_rate = openair0_cfg->sample_rate, + .bandwidth = openair0_cfg->tx_bw, + .gain = g->max - openair0_cfg->tx_gain[0], // see comment above + }; + configure_channel(brf.dev, &tx_chan); + LOG_I(HW, "BLADERF: Initializing device done\n"); + device->type = BLADERF_DEV; + device->trx_start_func = trx_brf_start; + device->trx_end_func = trx_brf_end; + device->trx_read_func = trx_brf_read; + device->trx_write_func = trx_brf_write; + device->trx_get_stats_func = trx_brf_get_stats; + device->trx_reset_stats_func = trx_brf_reset_stats; + device->trx_stop_func = trx_brf_stop; + device->trx_set_freq_func = trx_brf_set_freq; + device->trx_set_gains_func = trx_brf_set_gains; + device->trx_write_init = trx_brf_write_init; + device->openair0_cfg = openair0_cfg; + device->priv = malloc_or_fail(sizeof(brf_state_t)); + *(brf_state_t *)device->priv = brf; - if ((status=bladerf_set_gain_mode(brf->dev, BLADERF_MODULE_RX, BLADERF_GAIN_MGC))) { - fprintf(stderr, "[BRF] Failed to disable AGC\n"); - brf_error(status); - } - - if ((status=bladerf_set_frequency(brf->dev, BLADERF_MODULE_RX, (unsigned int) openair0_cfg->rx_freq[0])) != 0) { - fprintf(stderr,"Failed to set RX frequency: %s\n",bladerf_strerror(status)); - brf_error(status); - } else - printf("[BRF] set RX frequency to %u\n",(unsigned int)openair0_cfg->rx_freq[0]); - - - - unsigned int actual_value=0; - if ((status=bladerf_set_sample_rate(brf->dev, BLADERF_MODULE_RX, (unsigned int) openair0_cfg->sample_rate, &actual_value)) != 0) { - fprintf(stderr,"Failed to set RX sample rate: %s\n", bladerf_strerror(status)); - brf_error(status); - } else - printf("[BRF] set RX sample rate to %u, %u\n", (unsigned int) openair0_cfg->sample_rate, actual_value); - - - if ((status=bladerf_set_bandwidth(brf->dev, BLADERF_MODULE_RX, (unsigned int) openair0_cfg->rx_bw*2, &actual_value)) != 0) { - fprintf(stderr,"Failed to set RX bandwidth: %s\n", bladerf_strerror(status)); - brf_error(status); - } else - printf("[BRF] set RX bandwidth to %u, %u\n",(unsigned int)openair0_cfg->rx_bw*2, actual_value); - - if ((status=bladerf_set_gain(brf->dev, BLADERF_MODULE_RX, (int) openair0_cfg->rx_gain[0]-openair0_cfg[0].rx_gain_offset[0])) != 0) { - fprintf(stderr,"Failed to set RX gain: %s\n",bladerf_strerror(status)); - brf_error(status); - } else - printf("[BRF] set RX gain to %d (%d)\n",(int)(openair0_cfg->rx_gain[0]-openair0_cfg[0].rx_gain_offset[0]),(int)openair0_cfg[0].rx_gain_offset[0]); - - // TX - - if ((status=bladerf_set_frequency(brf->dev, BLADERF_MODULE_TX, (unsigned int) openair0_cfg->tx_freq[0])) != 0) { - fprintf(stderr,"Failed to set TX frequency: %s\n",bladerf_strerror(status)); - brf_error(status); - } else - printf("[BRF] set TX Frequency to %u\n", (unsigned int) openair0_cfg->tx_freq[0]); - - if ((status=bladerf_set_sample_rate(brf->dev, BLADERF_MODULE_TX, (unsigned int) openair0_cfg->sample_rate, NULL)) != 0) { - fprintf(stderr,"Failed to set TX sample rate: %s\n", bladerf_strerror(status)); - brf_error(status); - } else - printf("[BRF] set TX sampling rate to %u \n", (unsigned int) openair0_cfg->sample_rate); - - if ((status=bladerf_set_bandwidth(brf->dev, BLADERF_MODULE_TX,(unsigned int)openair0_cfg->tx_bw*2, NULL)) != 0) { - fprintf(stderr, "Failed to set TX bandwidth: %s\n", bladerf_strerror(status)); - brf_error(status); - } else - printf("[BRF] set TX bandwidth to %u \n", (unsigned int) openair0_cfg->tx_bw*2); - - if ((status=bladerf_set_gain(brf->dev, BLADERF_MODULE_TX, (int) openair0_cfg->tx_gain[0])) != 0) { - fprintf(stderr,"Failed to set TX gain: %s\n",bladerf_strerror(status)); - brf_error(status); - } else - printf("[BRF] set the TX gain to %d\n", (int)openair0_cfg->tx_gain[0]); - - - /* set log to info, available log levels are: - * - BLADERF_LOG_LEVEL_VERBOSE - * - BLADERF_LOG_LEVEL_DEBUG - * - BLADERF_LOG_LEVEL_INFO - * - BLADERF_LOG_LEVEL_WARNING - * - BLADERF_LOG_LEVEL_ERROR - * - BLADERF_LOG_LEVEL_CRITICAL - * - BLADERF_LOG_LEVEL_SILENT - */ - bladerf_log_set_verbosity(BLADERF_LOG_LEVEL_INFO); - - printf("BLADERF: Initializing openair0_device_t\n"); - device->type = BLADERF_DEV; - device->trx_start_func = trx_brf_start; - device->trx_end_func = trx_brf_end; - device->trx_read_func = trx_brf_read; - device->trx_write_func = trx_brf_write; - device->trx_get_stats_func = trx_brf_get_stats; - device->trx_reset_stats_func = trx_brf_reset_stats; - device->trx_stop_func = trx_brf_stop; - device->trx_set_freq_func = trx_brf_set_freq; - device->trx_set_gains_func = trx_brf_set_gains; - device->trx_write_init = trx_brf_write_init; - device->openair0_cfg = openair0_cfg; - device->priv = (void *)brf; - - // memcpy((void*)&device->openair0_cfg,(void*)&openair0_cfg[0],sizeof(openair0_config_t)); - - if ((status=bladerf_enable_module(brf->dev, BLADERF_MODULE_TX, false)) != 0) { - fprintf(stderr,"Failed to enable TX module: %s\n", bladerf_strerror(status)); - abort(); - } - if ((status=bladerf_enable_module(brf->dev, BLADERF_MODULE_RX, false)) != 0) { - fprintf(stderr,"Failed to enable RX module: %s\n", bladerf_strerror(status)); - abort(); - } - - return 0; + return 0; } From 80b5d4ef228316196078e424b80d4d5f461536fd Mon Sep 17 00:00:00 2001 From: Robert Schmidt Date: Wed, 4 Feb 2026 10:30:54 +0100 Subject: [PATCH 081/100] Make functions static, reformat, use logging module This commit does not introduce a functional change. --- radio/BLADERF/bladerf_lib.c | 221 +++++++++++++++--------------------- 1 file changed, 93 insertions(+), 128 deletions(-) diff --git a/radio/BLADERF/bladerf_lib.c b/radio/BLADERF/bladerf_lib.c index e3effac03f..437aef4c23 100644 --- a/radio/BLADERF/bladerf_lib.c +++ b/radio/BLADERF/bladerf_lib.c @@ -79,8 +79,6 @@ typedef struct { uint64_t rx_count; } brf_state_t; -int brf_error(int status); - /** @addtogroup _BLADERF_PHY_RF_INTERFACE_ * @{ */ @@ -125,7 +123,7 @@ static int trx_brf_start(openair0_device_t *device) /* Configure the device's TX module for use with the sync interface. * SC16 Q11 samples *with* metadata are used. */ - status = bladerf_sync_config(brf->dev, BLADERF_MODULE_TX, format, + status = bladerf_sync_config(brf->dev, BLADERF_CHANNEL_TX(0), format, brf->num_buffers, brf->buffer_size, brf->num_transfers, brf->tx_timeout_ms); BLADERF_CHECK(status == 0, "Set TX sync interface num_buffers %u, buffer_size %u, num_transfer %u, tx_timeout_ms %u", @@ -133,7 +131,7 @@ static int trx_brf_start(openair0_device_t *device) /* Configure the device's RX module for use with the sync interface. * SC16 Q11 samples *with* metadata are used. */ - status = bladerf_sync_config(brf->dev, BLADERF_MODULE_RX, format, + status = bladerf_sync_config(brf->dev, BLADERF_CHANNEL_RX(0), format, brf->num_buffers, brf->buffer_size, brf->num_transfers, brf->rx_timeout_ms); BLADERF_CHECK(status == 0, "Set RX sync interface num_buffers %u, buffer_size %u, num_transfer %u, rx_timeout_ms %u", @@ -141,14 +139,17 @@ static int trx_brf_start(openair0_device_t *device) /* We must always enable the TX module after calling bladerf_sync_config(), and * before attempting to TX samples via bladerf_sync_tx(). */ - status = bladerf_enable_module(brf->dev, BLADERF_MODULE_TX, true); - BLADERF_CHECK(status == 0, "Enable TX module"); + status = bladerf_enable_module(brf->dev, BLADERF_CHANNEL_TX(0), true); + BLADERF_CHECK(status == 0, "Enable TX0 module"); /* We must always enable the RX module after calling bladerf_sync_config(), and * before attempting to RX samples via bladerf_sync_rx(). */ - status = bladerf_enable_module(brf->dev, BLADERF_MODULE_RX, true); + status = bladerf_enable_module(brf->dev, BLADERF_CHANNEL_RX(0), true); BLADERF_CHECK(status == 0, "Enable RX module"); + // in case it was set to verbosity before, disable now because otherwise the + // terminal will be flooded + bladerf_log_set_verbosity(BLADERF_LOG_LEVEL_INFO); return 0; } @@ -164,46 +165,37 @@ static int trx_brf_start(openair0_device_t *device) */ static int trx_brf_write(openair0_device_t *device, openair0_timestamp_t ptimestamp, void **buff, int nsamps, int cc, int flags) { - int status; - brf_state_t *brf = (brf_state_t*)device->priv; - /* BRF has only 1 rx/tx chaine : is it correct? */ - int16_t *samples = (int16_t*)buff[0]; - ptimestamp -= device->openair0_cfg->command_line_sample_advance - device->openair0_cfg->tx_sample_advance; - //memset(&brf->meta_tx, 0, sizeof(brf->meta_tx)); - // When BLADERF_META_FLAG_TX_NOW is used the timestamp is not used, so one can't schedule a tx - if (brf->meta_tx.flags == 0 ) - brf->meta_tx.flags = (BLADERF_META_FLAG_TX_BURST_START);// | BLADERF_META_FLAG_TX_BURST_END);// | BLADERF_META_FLAG_TX_NOW); + brf_state_t *brf = device->priv; + /* BRF has only 1 rx/tx chaine : is it correct? TODO: now, handle also two! */ + int16_t *samples = (int16_t *)buff[0]; + ptimestamp -= device->openair0_cfg->command_line_sample_advance - device->openair0_cfg->tx_sample_advance; - brf->meta_tx.timestamp= (uint64_t) (ptimestamp); - status = bladerf_sync_tx(brf->dev, samples, (unsigned int) nsamps, &brf->meta_tx, 2*brf->tx_timeout_ms); + // When BLADERF_META_FLAG_TX_NOW is used the timestamp is not used, so one can't schedule a tx + if (brf->meta_tx.flags == 0) + brf->meta_tx.flags = BLADERF_META_FLAG_TX_BURST_START; + brf->meta_tx.timestamp = (uint64_t)(ptimestamp); - if (brf->meta_tx.flags == BLADERF_META_FLAG_TX_BURST_START) - brf->meta_tx.flags = BLADERF_META_FLAG_TX_UPDATE_TIMESTAMP; + int status = bladerf_sync_tx(brf->dev, samples, nsamps, &brf->meta_tx, 2 * brf->tx_timeout_ms); + if (status == BLADERF_ERR_TIME_PAST) { + brf->num_tx_errors++; + LOG_W(HW, "Timestamp error: Requested timestamp %ld is in the past.\n", ptimestamp); + } else if (status != 0) { + brf->num_tx_errors++; + LOG_W(HW, "Error during TX: %s\n", bladerf_strerror(status)); + } + if (brf->meta_tx.flags == BLADERF_META_FLAG_TX_BURST_START) + brf->meta_tx.flags = BLADERF_META_FLAG_TX_UPDATE_TIMESTAMP; - if (status != 0) { - //fprintf(stderr,"Failed to TX sample: %s\n", bladerf_strerror(status)); - brf->num_tx_errors++; - brf_error(status); - } else if (brf->meta_tx.status & BLADERF_META_STATUS_UNDERRUN) { - /* libbladeRF does not report this status. It is here for future use. */ - fprintf(stderr, "TX Underrun detected. %u valid samples were read.\n", brf->meta_tx.actual_count); - brf->num_underflows++; - } - //printf("Provided TX timestampe %u, meta timestame %u\n", ptimestamp,brf->meta_tx.timestamp); + brf->tx_current_ts = brf->meta_tx.timestamp; + brf->tx_actual_nsamps += brf->meta_tx.actual_count; + brf->tx_nsamps += nsamps; + brf->tx_count++; - // printf("tx status %d \n",brf->meta_tx.status); - brf->tx_current_ts=brf->meta_tx.timestamp; - brf->tx_actual_nsamps+=brf->meta_tx.actual_count; - brf->tx_nsamps+=nsamps; - brf->tx_count++; - - - return nsamps; //brf->meta_tx.actual_count; + return nsamps; // brf->meta_tx.actual_count; } - /*! \brief Receive samples from hardware. * Read \ref nsamps samples from each channel to buffers. buff[0] is the array for * the first channel. *ptimestamp is the time at which the first sample @@ -217,63 +209,56 @@ static int trx_brf_write(openair0_device_t *device, openair0_timestamp_t ptimest */ static int trx_brf_read(openair0_device_t *device, openair0_timestamp_t *ptimestamp, void **buff, int nsamps, int cc) { - int status=0; - brf_state_t *brf = (brf_state_t*)device->priv; + brf_state_t *brf = device->priv; - // BRF has only one rx/tx chain - int16_t *samples = (int16_t*)buff[0]; + // BRF has only one rx/tx chain + int16_t *samples = (int16_t *)buff[0]; - brf->meta_rx.actual_count = 0; - brf->meta_rx.flags = BLADERF_META_FLAG_RX_NOW; - status = bladerf_sync_rx(brf->dev, samples, (unsigned int) nsamps, &brf->meta_rx, 2*brf->rx_timeout_ms); + brf->meta_rx.actual_count = 0; + brf->meta_rx.flags = BLADERF_META_FLAG_RX_NOW; + int status = bladerf_sync_rx(brf->dev, samples, (unsigned int)nsamps, &brf->meta_rx, 2 * brf->rx_timeout_ms); - // printf("Current RX timestampe %u, nsamps %u, actual %u, cc %d\n", brf->meta_rx.timestamp, nsamps, brf->meta_rx.actual_count, cc); + if (status != 0) { + LOG_E(HW, "RX failed: %s\n", bladerf_strerror(status)); + brf->num_rx_errors++; + } else if (brf->meta_rx.status & BLADERF_META_STATUS_OVERRUN) { + brf->num_overflows++; + LOG_W(HW, + "%d RX overrun is detected. timestamp %ld. Requested %u but got %u samples\n", + brf->num_overflows, + brf->meta_rx.timestamp, + brf->meta_rx.actual_count, + nsamps); + } - if (status != 0) { - fprintf(stderr, "RX failed: %s\n", bladerf_strerror(status)); - // printf("RX failed: %s\n", bladerf_strerror(status)); - brf->num_rx_errors++; - } else if ( brf->meta_rx.status & BLADERF_META_STATUS_OVERRUN) { - brf->num_overflows++; - printf("RX overrun (%d) is detected. t=" "%" PRIu64 "Got %u samples. nsymps %d\n", - brf->num_overflows,brf->meta_rx.timestamp, brf->meta_rx.actual_count, nsamps); - } + if (brf->meta_rx.actual_count != nsamps) { + LOG_E(HW, "RX bad samples count, wanted %d, got %d\n", nsamps, brf->meta_rx.actual_count); + } - if (brf->meta_rx.actual_count != nsamps) { - printf("RX bad samples count, wanted %d, got %d\n", nsamps, brf->meta_rx.actual_count); - } + brf->rx_current_ts = brf->meta_rx.timestamp; + brf->rx_actual_nsamps += brf->meta_rx.actual_count; + brf->rx_nsamps += nsamps; + brf->rx_count++; - //printf("Current RX timestampe %u\n", brf->meta_rx.timestamp); - //printf("[BRF] (buff %p) ts=0x%"PRIu64" %s\n",samples, brf->meta_rx.timestamp,bladerf_strerror(status)); - brf->rx_current_ts=brf->meta_rx.timestamp; - brf->rx_actual_nsamps+=brf->meta_rx.actual_count; - brf->rx_nsamps+=nsamps; - brf->rx_count++; - - - *ptimestamp = brf->meta_rx.timestamp; - - return nsamps; //brf->meta_rx.actual_count; + *ptimestamp = brf->meta_rx.timestamp; + return nsamps; // brf->meta_rx.actual_count; } - /*! \brief Terminate operation of the BladeRF transceiver -- free all associated resources * \param device the hardware to use */ -void trx_brf_end(openair0_device_t *device) +static void trx_brf_end(openair0_device_t *device) { - int status; - brf_state_t *brf = (brf_state_t*)device->priv; - // Disable RX module, shutting down our underlying RX stream - if ((status=bladerf_enable_module(brf->dev, BLADERF_MODULE_RX, false)) != 0) { - fprintf(stderr, "Failed to disable RX module: %s\n", bladerf_strerror(status)); - } - if ((status=bladerf_enable_module(brf->dev, BLADERF_MODULE_TX, false)) != 0) { - fprintf(stderr, "Failed to disable TX module: %s\n", bladerf_strerror(status)); - } - bladerf_close(brf->dev); - exit(1); + brf_state_t *brf = device->priv; + // Disable RX module, shutting down our underlying RX stream + + int status; + if ((status = bladerf_enable_module(brf->dev, BLADERF_CHANNEL_RX(0), false)) != 0) + LOG_E(HW, "Failed: Disable RX module: %s\n", bladerf_strerror(status)); + if ((status = bladerf_enable_module(brf->dev, BLADERF_CHANNEL_TX(0), false)) != 0) + LOG_E(HW, "Failed: Disable TX module: %s\n", bladerf_strerror(status)); + bladerf_close(brf->dev); } @@ -281,58 +266,49 @@ void trx_brf_end(openair0_device_t *device) * \param device the hardware to use * \returns 0 on success */ -int trx_brf_get_stats(openair0_device_t *device) +static int trx_brf_get_stats(openair0_device_t *device) { - return(0); + return 0; } - /*! \brief Reset the BladeRF statistics -* \param device the hardware to use -* \returns 0 on success -*/ -int trx_brf_reset_stats(openair0_device_t *device) + * \param device the hardware to use + * \returns 0 on success + */ +static int trx_brf_reset_stats(openair0_device_t *device) { - return(0); + return 0; } - /*! \brief Stop BladeRF * \param card the hardware to use * \returns 0 in success */ -int trx_brf_stop(openair0_device_t *device) +static int trx_brf_stop(openair0_device_t *device) { - return(0); + return 0; } - /*! \brief Set frequencies (TX/RX) * \param device the hardware to use * \param openair0_cfg1 openair0 Config structure (ignored. It is there to comply with RF common API) * \returns 0 in success */ -int trx_brf_set_freq(openair0_device_t *device, openair0_config_t *openair0_cfg1) +static int trx_brf_set_freq(openair0_device_t *device, openair0_config_t *openair0_cfg1) { - int status; - brf_state_t *brf = (brf_state_t *)device->priv; - openair0_config_t *openair0_cfg = (openair0_config_t *)device->openair0_cfg; + int status; + brf_state_t *brf = device->priv; + openair0_config_t *openair0_cfg = (openair0_config_t *)device->openair0_cfg; + unsigned int tx_freq = (unsigned int)openair0_cfg->tx_freq[0]; + status = bladerf_set_frequency(brf->dev, BLADERF_CHANNEL_TX(0), tx_freq); + BLADERF_CHECK(status == 0, "Set TX frequency %u", tx_freq); - if ((status=bladerf_set_frequency(brf->dev, BLADERF_MODULE_TX, (unsigned int) openair0_cfg->tx_freq[0])) != 0) { - fprintf(stderr,"Failed to set TX frequency: %s\n",bladerf_strerror(status)); - brf_error(status); - } else - printf("[BRF] set TX Frequency to %u\n", (unsigned int) openair0_cfg->tx_freq[0]); - - if ((status=bladerf_set_frequency(brf->dev, BLADERF_MODULE_RX, (unsigned int) openair0_cfg->rx_freq[0])) != 0) { - fprintf(stderr,"Failed to set RX frequency: %s\n",bladerf_strerror(status)); - brf_error(status); - } else - printf("[BRF] set RX frequency to %u\n",(unsigned int)openair0_cfg->rx_freq[0]); - - return(0); + unsigned int rx_freq = (unsigned int)openair0_cfg->rx_freq[0]; + status = bladerf_set_frequency(brf->dev, BLADERF_CHANNEL_RX(0), rx_freq); + BLADERF_CHECK(status == 0, "Set RX frequency %u", rx_freq); + return 0; } @@ -341,13 +317,14 @@ int trx_brf_set_freq(openair0_device_t *device, openair0_config_t *openair0_cfg1 * \param openair0_cfg openair0 Config structure * \returns 0 in success */ -int trx_brf_set_gains(openair0_device_t *device, openair0_config_t *openair0_cfg) +static int trx_brf_set_gains(openair0_device_t *device, openair0_config_t *openair0_cfg) { - return(0); + return 0; } -int trx_brf_write_init(openair0_device_t *device) + +static int trx_brf_write_init(openair0_device_t *device) { - return 0; + return 0; } typedef struct channel_config { @@ -542,16 +519,4 @@ int device_init(openair0_device_t *device, openair0_config_t *openair0_cfg) return 0; } - - -/*! \brief bladeRF error report - * \param status - * \returns 0 on success - */ -int brf_error(int status) -{ - fprintf(stderr, "[BRF] brf_error: %s\n", bladerf_strerror(status)); - exit(-1); - return status; // or status error code -} /*@}*/ From f83b53d6d1f57b9abd06b1a2760a89992cabef16 Mon Sep 17 00:00:00 2001 From: Reem Bahsoun Date: Wed, 4 Feb 2026 09:33:31 +0100 Subject: [PATCH 082/100] Update BladeRF documentation --- doc/README.md | 2 +- radio/BLADERF/README | 138 ------------------------------------- radio/BLADERF/README.md | 148 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 149 insertions(+), 139 deletions(-) delete mode 100644 radio/BLADERF/README create mode 100644 radio/BLADERF/README.md diff --git a/doc/README.md b/doc/README.md index 022174a4e7..5f61489893 100644 --- a/doc/README.md +++ b/doc/README.md @@ -103,7 +103,7 @@ Some directories under `radio` contain READMEs: - [RFsimulator](../radio/rfsimulator/README.md) - [USRP](../radio/USRP/README.md) -- [BladeRF](../radio/BLADERF/README) +- [BladeRF](../radio/BLADERF/README.md) - [IQPlayer](../radio/iqplayer/DOC/iqrecordplayer_usage.md), and [general documentation](./iqrecordplayer_usage.md) - [fhi_72](../radio/fhi_72/README.md) - [vrtsim](../radio/vrtsim/README.md) diff --git a/radio/BLADERF/README b/radio/BLADERF/README deleted file mode 100644 index c93c938baf..0000000000 --- a/radio/BLADERF/README +++ /dev/null @@ -1,138 +0,0 @@ -BladeRF documentation -===================== - -As of 2018-11-06, the bladeRF support is not fully automatic and requires -some manual settings before use. This documentation is the ultimate source -of information. If something described in this file does not work or does -not correspond to the reality, then contact us so we can fix the problems -and update this documentation. - -1. Install bladeRF 2.0 libraries. - - As of now, it's better to install from source. - So, do not run: ./build_oai -I -w BLADERF - (That is: do not include '-w BLADERF'.) - - Instead, follow the instructions at: https://github.com/Nuand/bladeRF - - If you already had some bladeRF software installed using automatic - methods, first remove it by hand ('apt-get purge bladeRF' or something - similar, you can get the list of installed bladeRF packages by running - 'dpkg -l|grep -i blade', remove them all). - -2. Update the device. - - Download the latest FX3 firmware and FPGA images from Nuand's website. - As of writing, this is: - - https://github.com/Nuand/bladeRF/wiki - - That points to the following pages. - For FX3: - - http://www.nuand.com/fx3_images/ - - For FPGA: - - http://www.nuand.com/fpga_images/ - - Install FX3 firmware: - - sudo bladeRF-cli -f bladeRF_fw_latest.img - - Install FPGA image (this is for BladeRF x40): - - sudo bladeRF-cli -L hostedx40-latest.rbf - - Retrieve calibration information: - - sudo bladeRF-cli -i - info - - That outputs the serial number of your device. - - Go to: - - https://www.nuand.com/calibration - - And enter your serial number. - The website tells you to run something like: - - sudo bladeRF-cli -i - flash_init_cal 40 0x9271 - - Actual values depend on your device and serial number. - -3. Calibrate the bladeRF device. - - We will work with band 7 on 2.68GHz with a bandwidth of 5 MHz (25 RBs). - - Plug the bladeRF device, then run: - - sudo bladeRF-cli -i - set frequency tx 2680000000 - set frequency rx 2560000000 - set gain rx 60 - set gain tx 60 - set bandwidth 5000000 - set samplerate 7680000 - cal lms - cal lms - cal lms - cal dc rxtx - cal dc rxtx - cal dc rxtx - -4. Tune the RX gain using the enb tracer. - - Run the softmodem and the 'enb' tracer. For instructions, see: - - https://gitlab.eurecom.fr/oai/openairinterface5g/wikis/T/basic - - In the enb window, check the 'input signal'. You should see some blue - signal as seen at: - - https://gitlab.eurecom.fr/oai/openairinterface5g/wikis/T/enb - - (the 'Time signal power' plot). - - The level should be around 30. - - If it's not around 30 then edit your configuration file and modify - the value 'max_rxgain' in the section 'RUs'. - - The configuration file to use is: - - configuration/bladeRF/enb-band7-5mhz.conf - - In the configuration file, you also need to set the correct values for: - - - tracking_area_code - - plmn_list: mcc, mnc, mnc_length - - mme_ip_address: this is the IP address used by the computer running - the softmodem to connect to the EPC - - NETWORK_INTERFACES: all the ENB*ADDRESS* variables have to point - to the IP address of the EPC machine - -5. You're good to go. - - You can now connect a UE and pass some traffic. If everything is well - configured you can expect more than 16 Mb/s of throughput in the downlink - using iperf and more than 8 Mb/s in the uplink. Looking at the logs, you - should find lines containing 'PHR 40' and 'CQI 15'. If your values are - lower then your setup may need some adjustments. - -6. In case of problems. - - If the performance of the softmodem is very bad, you can stop it and - run the calibration again, without setting the parameters (frequencies, - gains, etc.). Just run: - - sudo bladeRF-cli -i - cal lms - cal dc rxtx - - That may help. - - Be sure to use proper radio equipment (duplexer, antennas, clean - environment without interferences). diff --git a/radio/BLADERF/README.md b/radio/BLADERF/README.md new file mode 100644 index 0000000000..d2ac6e1547 --- /dev/null +++ b/radio/BLADERF/README.md @@ -0,0 +1,148 @@ +# BladeRF 2.0 Micro Documentation + +[TOC] + +## Install BladeRF 2.0 Micro Libraries + +You can install the BladeRF library by using `build_oai -I -w BLADERF`, which +will install it from the package manager if the distribution supports it, or +falls back to a build from source otherwise. + +If you intentionally want to install BladeRF from source, you can follow +[Nuand's Getting Started +guide](https://github.com/Nuand/bladeRF/wiki/Getting-Started%3A-Linux#building-bladerf-libraries-and-tools-from-source) +to do so. + +## Configure the device + +### Image Flash + +Download the latest FX3 firmware and FPGA images from Nuand's website. There +are [fx3 images](http://www.nuand.com/fx3_images/) and [FPGA +images](http://www.nuand.com/fpga_images/). Download and save the corresponding +files to your computer, then install the FX3 firmware + + sudo bladeRF-cli -f bladeRF_fw_latest.img + +and install the FPGA image (this is x40): + + sudo bladeRF-cli -L hostedx40-latest.rbf + +### Use of the configuration file + +This configuration will help the bladeRF board to load the bitstream images +automatically, and can be used to set some options automatically that are not +configured by OAI. + +Create the bladeRF config file: + +```bash +$ cat ~/.config/Nuand/bladeRF/bladerf.conf +# Load the hostedx40.rbf FPGA image, set the trim DAC, and +# configure the frequency +fpga ~/.config/Nuand/bladeRF/image.rbf +# trimdac 592 +# frequency 2.4G +``` + +More information is available at [Nuand's +website](https://nuand.com/libbladeRF-doc/v2.5.0/configfile.html). + +### Useful commands for `bladerf-cli` + +`bladeRF-cli` can be used to query various device details. + +Show the version of bladeRF and device details + + bladeRF-cli --exec version + bladeRF-cli --exec info + +Probe for a specific device + + bladeRF-cli --probe + +Print some information on device capabilities and current configuration +(bandwidth, frequency, clocking and sample rate, various features) + + bladeRF-cli --exec print + +### Calibration + +Retrieve calibration serial number: + + bladerf-cli --exec info + +Go to the [Nuand calibration page](https://www.nuand.com/calibration) and enter +your serial number. The website tells you to run something like: + + sudo bladeRF-cli -i + flash_init_cal 40 0x9271 + +Actual values depend on your device and serial number. + +You can also rerun a calibration like so + + sudo bladeRF-cli -i + cal lms + cal dc rxtx + +## OAI + +### Build BladeRF driver + +You can use `build_oai` to build with BladeRF support, e.g., + + cd openairinterface5g/cmake_targets/ + ./build_oai --ninja --nrUE --gNB -w BLADERF + +or you can use cmake directly + + cd openairinterface5g/ + cmake -B build -G Ninja -DOAI_BLADERF=ON + cmake --build build -t oai_bladerfdevif # to build only the driver + cmake --build build # to build it all + +For more information about the build, please refer to [the build +documentation](../../doc/BUILD.md). + +### Adapt the OAI-gNB configuration file to your system + +You can start from a [sample BladeRF configuration +fiel](../../targets/PROJECTS/GENERIC-NR-5GC/CONF/gnb.sa.band78.fr1.51PRB.bladerf20xa0.conf). + +The only BladeRF-specific configuration resides in `RUs.[0].sdr_addrs`. This +string is passed to +[`bladerf_open()`](https://www.nuand.com/libbladeRF-doc/v2.6.0/group___f_n___i_n_i_t.html#gab341ac98615f393da9158ea59cdb6a24) +and can be used to select a specific BladeRF device. For instance, to open a +specific device with serial 730f5a36545849d9aa71ca6cf50e8ca0 on USB, you would +add `libusb: serial=730f5a36545849d9aa71ca6cf50e8ca0`. + +The TX gain is automatically chosen to be the maximum, and can be reduced with +`att_tx`. The RX gain is taken from `RUs.[0].max_rxgain`, and can be attenuated +with `att_rx`. + +The rest of the paramaters (PLMN, frequencies, IP addresses) is the same as in +any other configuration. Refer to the [general +tutorials](../..//doc/NR_SA_Tutorial_OAI_CN5G.md) for more information. + +### Run the setup + +Assuming you built from source above: + + cd ~/openairinterface5g/build/ + sudo ./nr-softmodem -O ../targets/PROJECTS/GENERIC-NR-5GC/CONF/gnb.sa.band78.fr1.51PRB.bladerf20xa0.conf -E + +### In case of problems + +Refer back to the calibration section above. If not done yet, consider +calibrating your device. + +You can get more logs from BladeRF. To do so, you have to edit the code and +modify this line inside [`bladerf_lib.c`](./bladerf_lib.c). + +```patch +- bladerf_log_set_verbosity(BLADERF_LOG_LEVEL_INFO); ++ bladerf_log_set_verbosity(BLADERF_LOG_LEVEL_VERBOSE); +``` + +in function `device_init()`. From 0a585c123589bcc6bb7b824c75593a60bbdc7fc0 Mon Sep 17 00:00:00 2001 From: Robert Schmidt Date: Wed, 4 Feb 2026 09:35:19 +0100 Subject: [PATCH 083/100] Add BladeRF configs --- .../gnb.sa.band78.106prb.bladerf2.0xa4.conf | 229 ++++++++++++++++++ .../gnb.sa.band78.51PRB.bladerf2.0xa4.conf | 229 ++++++++++++++++++ 2 files changed, 458 insertions(+) create mode 100644 targets/PROJECTS/GENERIC-NR-5GC/CONF/gnb.sa.band78.106prb.bladerf2.0xa4.conf create mode 100644 targets/PROJECTS/GENERIC-NR-5GC/CONF/gnb.sa.band78.51PRB.bladerf2.0xa4.conf diff --git a/targets/PROJECTS/GENERIC-NR-5GC/CONF/gnb.sa.band78.106prb.bladerf2.0xa4.conf b/targets/PROJECTS/GENERIC-NR-5GC/CONF/gnb.sa.band78.106prb.bladerf2.0xa4.conf new file mode 100644 index 0000000000..d53e3137fe --- /dev/null +++ b/targets/PROJECTS/GENERIC-NR-5GC/CONF/gnb.sa.band78.106prb.bladerf2.0xa4.conf @@ -0,0 +1,229 @@ +Active_gNBs = ( "gNB-OAI"); +# Asn1_verbosity, choice in: none, info, annoying +Asn1_verbosity = "none"; + +gNBs = +( + { + ////////// Identification parameters: + gNB_ID = 0xe00; + gNB_name = "gNB-OAI"; + + // 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 = 2; + + servingCellConfigCommon = ( + { + #spCellConfigCommon + + physCellId = 0; + +# downlinkConfigCommon + #frequencyInfoDL + # this is 3300.60 MHz + 53*12*30e-3 MHz = 3319.68 + absoluteFrequencySSB = 621312; + dl_frequencyBand = 78; + # this is 3300.60 MHz + dl_absoluteFrequencyPointA = 620040; + #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=0,L=106 (275*(L-1))+RBstart + initialDLBWPlocationAndBandwidth = 28875; +# subcarrierSpacing +# 0=kHz15, 1=kHz30, 2=kHz60, 3=kHz120 + initialDLBWPsubcarrierSpacing = 1; + #pdcch-ConfigCommon + initialDLBWPcontrolResourceSetZero = 11; + 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 = 12; + 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; +#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; +#one (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 = 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; + }; + + ////////// 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 + }; + + } +); + +MACRLCs = ( +{ + num_cc = 1; + tr_s_preference = "local_L1"; + tr_n_preference = "local_RRC"; + pusch_TargetSNRx10 = 200; + pucch_TargetSNRx10 = 200; +} +); + +L1s = ( +{ + num_cc = 1; + tr_n_preference = "local_mac"; + prach_dtx_threshold = 200; +} +); + +RUs = ( +{ + local_rf = "yes" + nb_tx = 1 + nb_rx = 1 + att_tx = 0; + att_rx = 0; + bands = [78]; + max_pdschReferenceSignalPower = -27; + max_rxgain = 75; + eNB_instances = [0]; + sdr_addrs = "libusb: serial=730f5a36545849d9aa71ca6cf50e8ca0" +} +); + +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"; + hw_log_level = "info"; + phy_log_level = "info"; + mac_log_level = "info"; + rlc_log_level = "info"; + pdcp_log_level = "info"; + rrc_log_level = "info"; +}; diff --git a/targets/PROJECTS/GENERIC-NR-5GC/CONF/gnb.sa.band78.51PRB.bladerf2.0xa4.conf b/targets/PROJECTS/GENERIC-NR-5GC/CONF/gnb.sa.band78.51PRB.bladerf2.0xa4.conf new file mode 100644 index 0000000000..b48977847b --- /dev/null +++ b/targets/PROJECTS/GENERIC-NR-5GC/CONF/gnb.sa.band78.51PRB.bladerf2.0xa4.conf @@ -0,0 +1,229 @@ +Active_gNBs = ( "gNB-OAI"); +# Asn1_verbosity, choice in: none, info, annoying +Asn1_verbosity = "none"; + +gNBs = +( + { + ////////// Identification parameters: + gNB_ID = 0xe00; + gNB_name = "gNB-OAI"; + + // 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 = 2; + + servingCellConfigCommon = ( + { + #spCellConfigCommon + + physCellId = 0; + +# downlinkConfigCommon + #frequencyInfoDL + # this is 3600 MHz + 43 PRBs@30kHz SCS (same as initial BWP) + absoluteFrequencySSB = 636672; + dl_frequencyBand = 78; + # this is 3600 MHz + dl_absoluteFrequencyPointA = 636060; + #scs-SpecificCarrierList + dl_offstToCarrier = 0; +# subcarrierSpacing +# 0=kHz15, 1=kHz30, 2=kHz60, 3=kHz120 + dl_subcarrierSpacing = 1; + dl_carrierBandwidth = 51; + #initialDownlinkBWP + #genericParameters + # this is RBstart=27,L=48 (275*(L-1))+RBstart + initialDLBWPlocationAndBandwidth = 13750; # 6366 12925 12956 28875 12952 +# subcarrierSpacing +# 0=kHz15, 1=kHz30, 2=kHz60, 3=kHz120 + initialDLBWPsubcarrierSpacing = 1; + #pdcch-ConfigCommon + initialDLBWPcontrolResourceSetZero = 11; + 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 = 51; + pMax = 20; + #initialUplinkBWP + #genericParameters + initialULBWPlocationAndBandwidth = 13750; +# 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; +#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; + }; + + + ////////// 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 + }; + + } +); + +MACRLCs = ( +{ + num_cc = 1; + tr_s_preference = "local_L1"; + tr_n_preference = "local_RRC"; + pusch_TargetSNRx10 = 200; + pucch_TargetSNRx10 = 200; +} +); + +L1s = ( +{ + num_cc = 1; + tr_n_preference = "local_mac"; + prach_dtx_threshold = 150; + pucch0_dtx_threshold = 100; + 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 = 75; + eNB_instances = [0]; + sdr_addrs = "libusb: serial=730f5a36545849d9aa71ca6cf50e8ca0" +} +); + +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"; + hw_log_level = "info"; + phy_log_level = "info"; + mac_log_level = "info"; + rlc_log_level = "info"; + pdcp_log_level = "info"; + rrc_log_level = "info"; +}; From 16c333b1a3850a79647ca2bf6387b1b14391b821 Mon Sep 17 00:00:00 2001 From: Robert Schmidt Date: Wed, 4 Feb 2026 17:01:51 +0100 Subject: [PATCH 084/100] Compile everything in Ubuntu builder Compile all targets in Ubuntu build to avoid breakage of software. Remove ldd in build dockerfile, as we don't need that (can also be inspected manually). --- docker/Dockerfile.build.ubuntu | 25 +++++++++---------------- 1 file changed, 9 insertions(+), 16 deletions(-) diff --git a/docker/Dockerfile.build.ubuntu b/docker/Dockerfile.build.ubuntu index 9ccd921043..50c31f1357 100644 --- a/docker/Dockerfile.build.ubuntu +++ b/docker/Dockerfile.build.ubuntu @@ -42,22 +42,15 @@ RUN \ # Mount CPM package cache --mount=type=cache,target=/root/.cache/cpm/ \ /bin/sh oaienv && \ - cd cmake_targets && \ - mkdir -p log && \ - ./build_oai -c --ninja \ - --eNB --gNB --RU --UE --nrUE \ - --build-lib "telnetsrv enbscope uescope nrscope" \ - -w USRP -t Ethernet \ - --build-e2 --cmake-opt -DKPM_VERSION=$KPM_VERSION --cmake-opt -DE2AP_VERSION=$E2AP_VERSION \ - --cmake-opt -DOAI_VRTSIM_TAPS_CLIENT=ON \ - --noavx512 \ - --build-tool-opt -k10 \ - --cmake-opt -DCMAKE_C_FLAGS="-Werror" --cmake-opt -DCMAKE_CXX_FLAGS="-Werror" $BUILD_OPTION && \ - # Mainly to see if the sanitize option was perfectly executed - echo "---- ldd on executables ----" && \ - ldd ran_build/build/*softmodem* ran_build/build/nr-cuup ran_build/build/oairu && \ - echo "---- ldd on shared libraries ----" && \ - ldd ran_build/build/*.so + mkdir -p cmake_targets/ran_build/build && \ + cd cmake_targets/ran_build/build && \ + cmake ../../../ -GNinja \ + -DENABLE_TELNETSRV=ON -DENABLE_ENBSCOPE=ON -DENABLE_UESCOPE=ON -DENABLE_NRSCOPE=ON \ + -DOAI_USRP=ON -DOAI_ETHERNET=ON -DOAI_VRTSIM_TAPS_CLIENT=ON \ + -DE2_AGENT=ON -DKPM_VERSION=KPM_V3_00 -DE2AP_VERSION=E2AP_V3 \ + -DAVX512=OFF && \ + # -DCMAKE_C_FLAGS=-Werror -DCMAKE_CXX_FLAGS=-Werror && \ + ninja ## Build FlexRIC for SM Models RUN \ From 054d0f55cd62f201f7d0e083926cf53305b31d2e Mon Sep 17 00:00:00 2001 From: Robert Schmidt Date: Thu, 5 Feb 2026 00:24:06 +0100 Subject: [PATCH 085/100] base&build: install with BladeRF --- docker/Dockerfile.base.ubuntu | 2 +- docker/Dockerfile.build.ubuntu | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docker/Dockerfile.base.ubuntu b/docker/Dockerfile.base.ubuntu index 3d7e162544..b0eb5c4d32 100644 --- a/docker/Dockerfile.base.ubuntu +++ b/docker/Dockerfile.base.ubuntu @@ -86,4 +86,4 @@ COPY oaienv . RUN /bin/sh oaienv && \ cd cmake_targets && \ mkdir -p log && \ - ./build_oai -I -w USRP --install-optional-packages + ./build_oai -I -w USRP -w BLADERF --install-optional-packages diff --git a/docker/Dockerfile.build.ubuntu b/docker/Dockerfile.build.ubuntu index 50c31f1357..bc2ed577a6 100644 --- a/docker/Dockerfile.build.ubuntu +++ b/docker/Dockerfile.build.ubuntu @@ -46,7 +46,7 @@ RUN \ cd cmake_targets/ran_build/build && \ cmake ../../../ -GNinja \ -DENABLE_TELNETSRV=ON -DENABLE_ENBSCOPE=ON -DENABLE_UESCOPE=ON -DENABLE_NRSCOPE=ON \ - -DOAI_USRP=ON -DOAI_ETHERNET=ON -DOAI_VRTSIM_TAPS_CLIENT=ON \ + -DOAI_USRP=ON -DOAI_BLADERF=ON -DOAI_ETHERNET=ON -DOAI_VRTSIM_TAPS_CLIENT=ON \ -DE2_AGENT=ON -DKPM_VERSION=KPM_V3_00 -DE2AP_VERSION=E2AP_V3 \ -DAVX512=OFF && \ # -DCMAKE_C_FLAGS=-Werror -DCMAKE_CXX_FLAGS=-Werror && \ From abe3ee96247306f3320b77eeb5f3aaf479778efe Mon Sep 17 00:00:00 2001 From: Robert Schmidt Date: Thu, 5 Feb 2026 02:05:38 +0100 Subject: [PATCH 086/100] Fixes bladerf install scripts - at least in CI, there is no bladerf, so don't try to upgrade a firmware (we don't do that either with USRPs) - the base distro for ubuntu is debian --- cmake_targets/tools/build_helper | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/cmake_targets/tools/build_helper b/cmake_targets/tools/build_helper index 4e832891eb..50615e42ea 100755 --- a/cmake_targets/tools/build_helper +++ b/cmake_targets/tools/build_helper @@ -346,19 +346,10 @@ install_bladerf_driver_from_source(){ make $SUDO make install $SUDO ldconfig - echo "Downloading FPGA and firmware images" - cd /tmp/bladeRF - #wget has been removed from default dependencies, so we must be sure that's installed - $SUDO $INSTALLER -y install wget - wget --tries=3 --retry-connrefused https://www.nuand.com/fx3/bladeRF_fw_latest.img - wget --tries=3 --retry-connrefused https://www.nuand.com/fpga/hostedx40-latest.rbf - sudo mkdir -p /usr/share/Nuand/bladeRF - sudo mv bladeRF_fw_latest.img /usr/share/Nuand/bladeRF/bladeRF_fw.img - sudo mv hostedx40-latest.rbf /usr/share/Nuand/bladeRF/hostedx40.rbf } check_install_bladerf_driver(){ - if [[ "$OS_DISTRO" == "ubuntu" ]]; then + if [[ "$OS_BASEDISTRO" == "debian" ]]; then $SUDO apt-get install -y bladerf libbladerf-dev $SUDO apt-get install -y bladerf-firmware-fx3 $SUDO apt-get install -y bladerf-fpga-hostedx40 From 7a7ffc18e2341a206ac6ca240b887c5ca2a8cb6c Mon Sep 17 00:00:00 2001 From: Robert Schmidt Date: Sat, 7 Feb 2026 13:20:04 +0100 Subject: [PATCH 087/100] Move reverse_bits*() to separate source file reversing bits is not NR specific. Think of it like this: LTE code might use reverse_bits*() as well, but in the old location, it would pull in a bunch of NR dependencies that are superfluous. --- common/utils/CMakeLists.txt | 2 +- common/utils/nr/nr_common.c | 66 ------------- common/utils/nr/nr_common.h | 3 - common/utils/reverse_bits.c | 92 +++++++++++++++++++ common/utils/reverse_bits.h | 31 +++++++ .../nrLDPC_coding_aal/nrLDPC_coding_aal.c | 1 + .../nrPolar_tools/nr_polar_procedures.c | 2 +- openair1/PHY/NR_TRANSPORT/nr_pbch.c | 1 + openair1/PHY/NR_TRANSPORT/pucch_rx.c | 1 + openair1/PHY/NR_UE_TRANSPORT/nr_pbch.c | 1 + openair1/PHY/NR_UE_TRANSPORT/pucch_nr.c | 1 + openair2/LAYER2/NR_MAC_UE/nr_ue_procedures.c | 1 + openair2/LAYER2/NR_MAC_gNB/nr_radio_config.c | 1 + openair2/NR_UE_PHY_INTERFACE/NR_IF_Module.c | 1 + 14 files changed, 133 insertions(+), 71 deletions(-) create mode 100644 common/utils/reverse_bits.c create mode 100644 common/utils/reverse_bits.h diff --git a/common/utils/CMakeLists.txt b/common/utils/CMakeLists.txt index 395139132d..84668852ae 100644 --- a/common/utils/CMakeLists.txt +++ b/common/utils/CMakeLists.txt @@ -14,7 +14,7 @@ add_subdirectory(nr) add_subdirectory(LOG) add_subdirectory(threadPool) add_subdirectory(time_manager) -add_library(utils utils.c system.c time_meas.c time_stat.c tuntap_if.c) +add_library(utils utils.c system.c time_meas.c time_stat.c tuntap_if.c reverse_bits.c) target_include_directories(utils PUBLIC .) target_link_libraries(utils PRIVATE ${T_LIB}) if (ENABLE_TESTS) diff --git a/common/utils/nr/nr_common.c b/common/utils/nr/nr_common.c index 96b8681cff..b91ea4e708 100644 --- a/common/utils/nr/nr_common.c +++ b/common/utils/nr/nr_common.c @@ -37,7 +37,6 @@ #include "nr_common.h" #include #include -#include #define C_SRS_NUMBER (64) #define B_SRS_NUMBER (4) @@ -112,46 +111,6 @@ static const unsigned short srs_bandwidth_config[C_SRS_NUMBER][B_SRS_NUMBER][2] /* 63 */ {{272, 1}, {16, 17}, {8, 2}, {4, 2}}, }; -static const uint8_t bit_reverse_table_256[] = { - 0x00, 0x80, 0x40, 0xC0, 0x20, 0xA0, 0x60, 0xE0, 0x10, 0x90, 0x50, 0xD0, 0x30, 0xB0, 0x70, 0xF0, 0x08, 0x88, 0x48, 0xC8, - 0x28, 0xA8, 0x68, 0xE8, 0x18, 0x98, 0x58, 0xD8, 0x38, 0xB8, 0x78, 0xF8, 0x04, 0x84, 0x44, 0xC4, 0x24, 0xA4, 0x64, 0xE4, - 0x14, 0x94, 0x54, 0xD4, 0x34, 0xB4, 0x74, 0xF4, 0x0C, 0x8C, 0x4C, 0xCC, 0x2C, 0xAC, 0x6C, 0xEC, 0x1C, 0x9C, 0x5C, 0xDC, - 0x3C, 0xBC, 0x7C, 0xFC, 0x02, 0x82, 0x42, 0xC2, 0x22, 0xA2, 0x62, 0xE2, 0x12, 0x92, 0x52, 0xD2, 0x32, 0xB2, 0x72, 0xF2, - 0x0A, 0x8A, 0x4A, 0xCA, 0x2A, 0xAA, 0x6A, 0xEA, 0x1A, 0x9A, 0x5A, 0xDA, 0x3A, 0xBA, 0x7A, 0xFA, 0x06, 0x86, 0x46, 0xC6, - 0x26, 0xA6, 0x66, 0xE6, 0x16, 0x96, 0x56, 0xD6, 0x36, 0xB6, 0x76, 0xF6, 0x0E, 0x8E, 0x4E, 0xCE, 0x2E, 0xAE, 0x6E, 0xEE, - 0x1E, 0x9E, 0x5E, 0xDE, 0x3E, 0xBE, 0x7E, 0xFE, 0x01, 0x81, 0x41, 0xC1, 0x21, 0xA1, 0x61, 0xE1, 0x11, 0x91, 0x51, 0xD1, - 0x31, 0xB1, 0x71, 0xF1, 0x09, 0x89, 0x49, 0xC9, 0x29, 0xA9, 0x69, 0xE9, 0x19, 0x99, 0x59, 0xD9, 0x39, 0xB9, 0x79, 0xF9, - 0x05, 0x85, 0x45, 0xC5, 0x25, 0xA5, 0x65, 0xE5, 0x15, 0x95, 0x55, 0xD5, 0x35, 0xB5, 0x75, 0xF5, 0x0D, 0x8D, 0x4D, 0xCD, - 0x2D, 0xAD, 0x6D, 0xED, 0x1D, 0x9D, 0x5D, 0xDD, 0x3D, 0xBD, 0x7D, 0xFD, 0x03, 0x83, 0x43, 0xC3, 0x23, 0xA3, 0x63, 0xE3, - 0x13, 0x93, 0x53, 0xD3, 0x33, 0xB3, 0x73, 0xF3, 0x0B, 0x8B, 0x4B, 0xCB, 0x2B, 0xAB, 0x6B, 0xEB, 0x1B, 0x9B, 0x5B, 0xDB, - 0x3B, 0xBB, 0x7B, 0xFB, 0x07, 0x87, 0x47, 0xC7, 0x27, 0xA7, 0x67, 0xE7, 0x17, 0x97, 0x57, 0xD7, 0x37, 0xB7, 0x77, 0xF7, - 0x0F, 0x8F, 0x4F, 0xCF, 0x2F, 0xAF, 0x6F, 0xEF, 0x1F, 0x9F, 0x5F, 0xDF, 0x3F, 0xBF, 0x7F, 0xFF}; - -void reverse_bits_u8(uint8_t const* in, size_t sz, uint8_t* out) -{ - DevAssert(in != NULL); - DevAssert(out != NULL); - -// Bit reversal implementation based on https://wunkolo.github.io/post/2020/11/gf2p8affineqb-bit-reversal/ -#if defined(__GFNI__) && defined(__AVX512F__) - int simde_sz = 64; - int i = 0; - int simde_bound = sz - simde_sz; - for (; i <= simde_bound; i += simde_sz) { - __m512i input = _mm512_loadu_epi8(&in[i]); - __m512i reversed = _mm512_gf2p8affine_epi64_epi8(input, _mm512_set1_epi64(0x8040201008040201), 0); - _mm512_storeu_epi8(&out[i], reversed); - } - - for (; i < sz; ++i) { - out[i] = bit_reverse_table_256[in[i]]; - } -#else - for(size_t i = 0; i < sz; ++i) - out[i] = bit_reverse_table_256[in[i]]; -#endif -} - /** @brief 3GPP TS 38.133 Table 10.1.6.1-1 mapping from dBm to RSRP index */ uint8_t get_rsrp_index(int rsrp_dBm) { @@ -163,31 +122,6 @@ uint8_t get_rsrp_index(int rsrp_dBm) return (uint8_t)index; } -// Reverse bits implementation based on http://graphics.stanford.edu/~seander/bithacks.html -uint64_t reverse_bits(uint64_t in, int n_bits) -{ - // Reverse n_bits in uint64_t variable, example: - // n_bits: 10 - // in: 10 0000 1111 - // return: 11 1100 0001 - - AssertFatal(n_bits <= 64, "Maximum bits to reverse is 64, impossible to reverse %d bits!\n", n_bits); - uint64_t rev_bits = 0; - uint8_t *p = (uint8_t *)∈ - uint8_t *q = (uint8_t *)&rev_bits; - int n_bytes = n_bits >> 3; - for (int n = 0; n < n_bytes; n++) { - q[n_bytes - 1 - n] = bit_reverse_table_256[p[n]]; - } - - // Reverse remaining bits (not aligned with 8-bit) - rev_bits = rev_bits << (n_bits % 8); - for (int i = n_bytes * 8; i < n_bits; i++) { - rev_bits |= ((in >> i) & 0x1) << (n_bits - i - 1); - } - return rev_bits; -} - #define NUM_BW_ENTRIES 15 static const int tables_5_3_2[5][NUM_BW_ENTRIES] = { diff --git a/common/utils/nr/nr_common.h b/common/utils/nr/nr_common.h index e62d66658c..0d15e83f9d 100644 --- a/common/utils/nr/nr_common.h +++ b/common/utils/nr/nr_common.h @@ -286,9 +286,6 @@ static inline void warn_higher_threequarter_fs(const int n_rb, const int mu) mu); } -uint64_t reverse_bits(uint64_t in, int n_bits); -void reverse_bits_u8(uint8_t const* in, size_t sz, uint8_t* out); - uint64_t from_nrarfcn(int nr_bandP, uint8_t scs_index, uint32_t dl_nrarfcn); uint32_t to_nrarfcn(int nr_bandP, uint64_t dl_CarrierFreq, uint8_t scs_index, uint32_t bw); diff --git a/common/utils/reverse_bits.c b/common/utils/reverse_bits.c new file mode 100644 index 0000000000..525929c557 --- /dev/null +++ b/common/utils/reverse_bits.c @@ -0,0 +1,92 @@ +/* + * 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 "reverse_bits.h" +// we avoid assertions.h as it necessitates othe dependencies (e.g., exit_function) +#include +#include + +static const uint8_t bit_reverse_table_256[] = { + 0x00, 0x80, 0x40, 0xC0, 0x20, 0xA0, 0x60, 0xE0, 0x10, 0x90, 0x50, 0xD0, 0x30, 0xB0, 0x70, 0xF0, 0x08, 0x88, 0x48, 0xC8, + 0x28, 0xA8, 0x68, 0xE8, 0x18, 0x98, 0x58, 0xD8, 0x38, 0xB8, 0x78, 0xF8, 0x04, 0x84, 0x44, 0xC4, 0x24, 0xA4, 0x64, 0xE4, + 0x14, 0x94, 0x54, 0xD4, 0x34, 0xB4, 0x74, 0xF4, 0x0C, 0x8C, 0x4C, 0xCC, 0x2C, 0xAC, 0x6C, 0xEC, 0x1C, 0x9C, 0x5C, 0xDC, + 0x3C, 0xBC, 0x7C, 0xFC, 0x02, 0x82, 0x42, 0xC2, 0x22, 0xA2, 0x62, 0xE2, 0x12, 0x92, 0x52, 0xD2, 0x32, 0xB2, 0x72, 0xF2, + 0x0A, 0x8A, 0x4A, 0xCA, 0x2A, 0xAA, 0x6A, 0xEA, 0x1A, 0x9A, 0x5A, 0xDA, 0x3A, 0xBA, 0x7A, 0xFA, 0x06, 0x86, 0x46, 0xC6, + 0x26, 0xA6, 0x66, 0xE6, 0x16, 0x96, 0x56, 0xD6, 0x36, 0xB6, 0x76, 0xF6, 0x0E, 0x8E, 0x4E, 0xCE, 0x2E, 0xAE, 0x6E, 0xEE, + 0x1E, 0x9E, 0x5E, 0xDE, 0x3E, 0xBE, 0x7E, 0xFE, 0x01, 0x81, 0x41, 0xC1, 0x21, 0xA1, 0x61, 0xE1, 0x11, 0x91, 0x51, 0xD1, + 0x31, 0xB1, 0x71, 0xF1, 0x09, 0x89, 0x49, 0xC9, 0x29, 0xA9, 0x69, 0xE9, 0x19, 0x99, 0x59, 0xD9, 0x39, 0xB9, 0x79, 0xF9, + 0x05, 0x85, 0x45, 0xC5, 0x25, 0xA5, 0x65, 0xE5, 0x15, 0x95, 0x55, 0xD5, 0x35, 0xB5, 0x75, 0xF5, 0x0D, 0x8D, 0x4D, 0xCD, + 0x2D, 0xAD, 0x6D, 0xED, 0x1D, 0x9D, 0x5D, 0xDD, 0x3D, 0xBD, 0x7D, 0xFD, 0x03, 0x83, 0x43, 0xC3, 0x23, 0xA3, 0x63, 0xE3, + 0x13, 0x93, 0x53, 0xD3, 0x33, 0xB3, 0x73, 0xF3, 0x0B, 0x8B, 0x4B, 0xCB, 0x2B, 0xAB, 0x6B, 0xEB, 0x1B, 0x9B, 0x5B, 0xDB, + 0x3B, 0xBB, 0x7B, 0xFB, 0x07, 0x87, 0x47, 0xC7, 0x27, 0xA7, 0x67, 0xE7, 0x17, 0x97, 0x57, 0xD7, 0x37, 0xB7, 0x77, 0xF7, + 0x0F, 0x8F, 0x4F, 0xCF, 0x2F, 0xAF, 0x6F, 0xEF, 0x1F, 0x9F, 0x5F, 0xDF, 0x3F, 0xBF, 0x7F, 0xFF}; + +void reverse_bits_u8(uint8_t const* in, size_t sz, uint8_t* out) +{ + assert(in != NULL); + assert(out != NULL); + +// Bit reversal implementation based on https://wunkolo.github.io/post/2020/11/gf2p8affineqb-bit-reversal/ +#if defined(__GFNI__) && defined(__AVX512F__) + int simde_sz = 64; + int i = 0; + int simde_bound = sz - simde_sz; + for (; i <= simde_bound; i += simde_sz) { + __m512i input = _mm512_loadu_epi8(&in[i]); + __m512i reversed = _mm512_gf2p8affine_epi64_epi8(input, _mm512_set1_epi64(0x8040201008040201), 0); + _mm512_storeu_epi8(&out[i], reversed); + } + + for (; i < sz; ++i) { + out[i] = bit_reverse_table_256[in[i]]; + } +#else + for(size_t i = 0; i < sz; ++i) + out[i] = bit_reverse_table_256[in[i]]; +#endif +} + + +// Reverse bits implementation based on http://graphics.stanford.edu/~seander/bithacks.html +uint64_t reverse_bits(uint64_t in, int n_bits) +{ + // Reverse n_bits in uint64_t variable, example: + // n_bits: 10 + // in: 10 0000 1111 + // return: 11 1100 0001 + + assert(n_bits <= 64); // Maximum bits to reverse is 64 + uint64_t rev_bits = 0; + uint8_t *p = (uint8_t *)∈ + uint8_t *q = (uint8_t *)&rev_bits; + int n_bytes = n_bits >> 3; + for (int n = 0; n < n_bytes; n++) { + q[n_bytes - 1 - n] = bit_reverse_table_256[p[n]]; + } + + // Reverse remaining bits (not aligned with 8-bit) + rev_bits = rev_bits << (n_bits % 8); + for (int i = n_bytes * 8; i < n_bits; i++) { + rev_bits |= ((in >> i) & 0x1) << (n_bits - i - 1); + } + return rev_bits; +} + diff --git a/common/utils/reverse_bits.h b/common/utils/reverse_bits.h new file mode 100644 index 0000000000..5627ba4b6d --- /dev/null +++ b/common/utils/reverse_bits.h @@ -0,0 +1,31 @@ +/* + * 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 REVERSE_BITS_H_ +#define REVERSE_BITS_H_ + +#include +#include + +uint64_t reverse_bits(uint64_t in, int n_bits); +void reverse_bits_u8(uint8_t const* in, size_t sz, uint8_t* out); + +#endif /* REVERSE_BITS_H_ */ diff --git a/openair1/PHY/CODING/nrLDPC_coding/nrLDPC_coding_aal/nrLDPC_coding_aal.c b/openair1/PHY/CODING/nrLDPC_coding/nrLDPC_coding_aal/nrLDPC_coding_aal.c index 31ca0b387b..89f16bb706 100644 --- a/openair1/PHY/CODING/nrLDPC_coding/nrLDPC_coding_aal/nrLDPC_coding_aal.c +++ b/openair1/PHY/CODING/nrLDPC_coding/nrLDPC_coding_aal/nrLDPC_coding_aal.c @@ -5,6 +5,7 @@ #include "../nrLDPC_coding_interface.h" #include "nrLDPC_coding_aal.h" #include "PHY/sse_intrin.h" +#include "reverse_bits.h" #include #define NR_LDPC_ENABLE_PARITY_CHECK diff --git a/openair1/PHY/CODING/nrPolar_tools/nr_polar_procedures.c b/openair1/PHY/CODING/nrPolar_tools/nr_polar_procedures.c index cf1df52ac0..506d185e89 100644 --- a/openair1/PHY/CODING/nrPolar_tools/nr_polar_procedures.c +++ b/openair1/PHY/CODING/nrPolar_tools/nr_polar_procedures.c @@ -30,8 +30,8 @@ * \warning */ -#include "common/utils/nr/nr_common.h" #include "PHY/CODING/nrPolar_tools/nr_polar_defs.h" +#include "reverse_bits.h" #include #include #include diff --git a/openair1/PHY/NR_TRANSPORT/nr_pbch.c b/openair1/PHY/NR_TRANSPORT/nr_pbch.c index c5edd4cfde..2aa2a113ad 100644 --- a/openair1/PHY/NR_TRANSPORT/nr_pbch.c +++ b/openair1/PHY/NR_TRANSPORT/nr_pbch.c @@ -35,6 +35,7 @@ #include "PHY/NR_TRANSPORT/nr_transport_proto.h" #include "PHY/sse_intrin.h" #include "executables/softmodem-common.h" +#include "reverse_bits.h" #include "openair1/PHY/NR_REFSIG/nr_refsig_common.h" #include "openair1/PHY/NR_REFSIG/nr_mod_table.h" #include "openair1/PHY/TOOLS/tools_defs.h" diff --git a/openair1/PHY/NR_TRANSPORT/pucch_rx.c b/openair1/PHY/NR_TRANSPORT/pucch_rx.c index 8e2076df2f..c3874fe93d 100644 --- a/openair1/PHY/NR_TRANSPORT/pucch_rx.c +++ b/openair1/PHY/NR_TRANSPORT/pucch_rx.c @@ -50,6 +50,7 @@ #include "nfapi/oai_integration/vendor_ext.h" #include "nfapi/oai_integration/vendor_ext.h" #include "SCHED_NR/sched_nr.h" +#include "reverse_bits.h" #include "T.h" #include "nr_phy_common.h" diff --git a/openair1/PHY/NR_UE_TRANSPORT/nr_pbch.c b/openair1/PHY/NR_UE_TRANSPORT/nr_pbch.c index 8d3ac6db21..613e70be0d 100644 --- a/openair1/PHY/NR_UE_TRANSPORT/nr_pbch.c +++ b/openair1/PHY/NR_UE_TRANSPORT/nr_pbch.c @@ -38,6 +38,7 @@ #include #include "openair1/PHY/NR_REFSIG/nr_refsig_common.h" #include "PHY/nr_phy_common/inc/nr_phy_common.h" +#include "reverse_bits.h" #include "instrumentation.h" //#define DEBUG_PBCH //#define DEBUG_PBCH_ENCODING diff --git a/openair1/PHY/NR_UE_TRANSPORT/pucch_nr.c b/openair1/PHY/NR_UE_TRANSPORT/pucch_nr.c index b6b20cefaf..73f9f8e061 100644 --- a/openair1/PHY/NR_UE_TRANSPORT/pucch_nr.c +++ b/openair1/PHY/NR_UE_TRANSPORT/pucch_nr.c @@ -40,6 +40,7 @@ #include #include "common/utils/LOG/log.h" #include "common/utils/LOG/vcd_signal_dumper.h" +#include "reverse_bits.h" #include "openair1/PHY/NR_REFSIG/nr_refsig.h" #include "T.h" diff --git a/openair2/LAYER2/NR_MAC_UE/nr_ue_procedures.c b/openair2/LAYER2/NR_MAC_UE/nr_ue_procedures.c index 672bc3c3a2..0d463f39e4 100644 --- a/openair2/LAYER2/NR_MAC_UE/nr_ue_procedures.c +++ b/openair2/LAYER2/NR_MAC_UE/nr_ue_procedures.c @@ -51,6 +51,7 @@ /* utils */ #include "assertions.h" +#include "reverse_bits.h" #include "oai_asn1.h" #include "common/utils/LOG/log.h" #include "common/utils/LOG/vcd_signal_dumper.h" diff --git a/openair2/LAYER2/NR_MAC_gNB/nr_radio_config.c b/openair2/LAYER2/NR_MAC_gNB/nr_radio_config.c index 71547f85e7..7549ab56a8 100644 --- a/openair2/LAYER2/NR_MAC_gNB/nr_radio_config.c +++ b/openair2/LAYER2/NR_MAC_gNB/nr_radio_config.c @@ -41,6 +41,7 @@ #include "openair2/LAYER2/NR_MAC_gNB/mac_proto.h" #include "openair3/UTILS/conversions.h" #include "LAYER2/nr_rlc/nr_rlc_asn1_utils.h" +#include "reverse_bits.h" #include "NR_MeasurementTimingConfiguration.h" #include "uper_decoder.h" diff --git a/openair2/NR_UE_PHY_INTERFACE/NR_IF_Module.c b/openair2/NR_UE_PHY_INTERFACE/NR_IF_Module.c index 42030133af..73845551af 100644 --- a/openair2/NR_UE_PHY_INTERFACE/NR_IF_Module.c +++ b/openair2/NR_UE_PHY_INTERFACE/NR_IF_Module.c @@ -35,6 +35,7 @@ #include "NR_MAC_UE/mac_proto.h" #include "assertions.h" #include "SCHED_NR_UE/fapi_nr_ue_l1.h" +#include "reverse_bits.h" #include "openair2/RRC/NR_UE/L2_interface_ue.h" #define MAX_IF_MODULES 100 From cc33a01cee54ff99a723c49f635cce9770605bed Mon Sep 17 00:00:00 2001 From: Robert Schmidt Date: Sat, 7 Feb 2026 12:04:28 +0100 Subject: [PATCH 088/100] Remove unused -lz from simulators We don't use zlib in the simulators, we don't need this. --- CMakeLists.txt | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index bae1c590bc..abfc8fa7c0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2016,7 +2016,7 @@ add_executable(nr_psbchsim ${PHY_INTERFACE_DIR}/queue_t.c ) target_link_libraries(nr_psbchsim PRIVATE - -Wl,--start-group UTIL SIMU PHY_COMMON PHY_NR_COMMON PHY_NR PHY_NR_UE SCHED_NR_LIB SCHED_NR_UE_LIB MAC_UE_NR MAC_NR_COMMON CONFIG_LIB L2_NR -lz -Wl,--end-group + -Wl,--start-group UTIL SIMU PHY_COMMON PHY_NR_COMMON PHY_NR PHY_NR_UE SCHED_NR_LIB SCHED_NR_UE_LIB MAC_UE_NR MAC_NR_COMMON CONFIG_LIB L2_NR -Wl,--end-group m pthread ${T_LIB} ITTI dl shlib_loader physim_common ) target_link_libraries(nr_psbchsim PRIVATE asn1_nr_rrc_hdrs asn1_lte_rrc_hdrs) @@ -2036,14 +2036,14 @@ add_executable(nr_dlsim ${PHY_INTERFACE_DIR}/queue_t.c ) target_link_libraries(nr_dlsim PRIVATE - -Wl,--start-group UTIL SIMU PHY_COMMON PHY_NR_COMMON PHY_NR PHY_NR_UE SCHED_NR_LIB SCHED_NR_UE_LIB MAC_UE_NR MAC_NR_COMMON nr_rrc CONFIG_LIB L2_NR HASHTABLE x2ap SECURITY ngap -lz -Wl,--end-group + -Wl,--start-group UTIL SIMU PHY_COMMON PHY_NR_COMMON PHY_NR PHY_NR_UE SCHED_NR_LIB SCHED_NR_UE_LIB MAC_UE_NR MAC_NR_COMMON nr_rrc CONFIG_LIB L2_NR HASHTABLE x2ap SECURITY ngap -Wl,--end-group m pthread ${T_LIB} ITTI dl shlib_loader nr_ue_phy_meas physim_common ) target_link_libraries(nr_dlsim PRIVATE asn1_nr_rrc_hdrs asn1_lte_rrc_hdrs) add_executable(nr_prachsim ${OPENAIR1_DIR}/SIMULATION/NR_PHY/prachsim.c) target_link_libraries(nr_prachsim PRIVATE - -Wl,--start-group UTIL SIMU PHY_COMMON PHY_NR_COMMON PHY_NR PHY_RU PHY_NR_UE MAC_NR_COMMON SCHED_NR_LIB CONFIG_LIB -lz -Wl,--end-group + -Wl,--start-group UTIL SIMU PHY_COMMON PHY_NR_COMMON PHY_NR PHY_RU PHY_NR_UE MAC_NR_COMMON SCHED_NR_LIB CONFIG_LIB -Wl,--end-group m pthread ${T_LIB} ITTI dl shlib_loader physim_common) target_link_libraries(nr_prachsim PRIVATE asn1_nr_rrc_hdrs asn1_lte_rrc_hdrs) @@ -2066,7 +2066,7 @@ add_executable(nr_ulsim ) target_link_libraries(nr_ulsim PRIVATE - -Wl,--start-group UTIL SIMU PHY_COMMON PHY_NR_COMMON PHY_NR PHY_NR_UE SCHED_NR_LIB SCHED_NR_UE_LIB MAC_UE_NR MAC_NR_COMMON nr_rrc CONFIG_LIB L2_NR HASHTABLE x2ap SECURITY ngap -lz -Wl,--end-group + -Wl,--start-group UTIL SIMU PHY_COMMON PHY_NR_COMMON PHY_NR PHY_NR_UE SCHED_NR_LIB SCHED_NR_UE_LIB MAC_UE_NR MAC_NR_COMMON nr_rrc CONFIG_LIB L2_NR HASHTABLE x2ap SECURITY ngap -Wl,--end-group m pthread ${T_LIB} ITTI dl shlib_loader nr_ue_phy_meas physim_common ) target_link_libraries(nr_ulsim PRIVATE asn1_nr_rrc_hdrs asn1_lte_rrc_hdrs) @@ -2080,7 +2080,7 @@ add_executable(nr_srssim ) target_link_libraries(nr_srssim PRIVATE - -Wl,--start-group UTIL SIMU PHY_COMMON PHY_NR_COMMON PHY_NR PHY_NR_UE SCHED_NR_LIB SCHED_NR_UE_LIB MAC_UE_NR MAC_NR_COMMON nr_rrc L2_NR -lz -Wl,--end-group + -Wl,--start-group UTIL SIMU PHY_COMMON PHY_NR_COMMON PHY_NR PHY_NR_UE SCHED_NR_LIB SCHED_NR_UE_LIB MAC_UE_NR MAC_NR_COMMON nr_rrc L2_NR -Wl,--end-group m pthread ITTI dl nr_ue_phy_meas physim_common ) From 76a5aad7c48d60c982f3158c632fdb7f099c59a4 Mon Sep 17 00:00:00 2001 From: Robert Schmidt Date: Sat, 7 Feb 2026 13:23:59 +0100 Subject: [PATCH 089/100] Move polar coding cmake code to subdirectory --- CMakeLists.txt | 20 ++----------------- openair1/PHY/CODING/CMakeLists.txt | 1 + .../PHY/CODING/nrPolar_tools/CMakeLists.txt | 15 ++++++++++++++ 3 files changed, 18 insertions(+), 18 deletions(-) create mode 100644 openair1/PHY/CODING/nrPolar_tools/CMakeLists.txt diff --git a/CMakeLists.txt b/CMakeLists.txt index abfc8fa7c0..2c7776c94a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -780,20 +780,6 @@ include_directories(${NFAPI_USER_DIR}) # Layer 1 ############################# -set(PHY_POLARSRC - ${OPENAIR1_DIR}/PHY/CODING/nrPolar_tools/nr_polar_init.c - ${OPENAIR1_DIR}/PHY/CODING/nrPolar_tools/nr_bitwise_operations.c - ${OPENAIR1_DIR}/PHY/CODING/nrPolar_tools/nr_crc_byte.c - ${OPENAIR1_DIR}/PHY/CODING/nrPolar_tools/nr_polar_decoder.c - ${OPENAIR1_DIR}/PHY/CODING/nrPolar_tools/nr_polar_decoding_tools.c - ${OPENAIR1_DIR}/PHY/CODING/nrPolar_tools/nr_polar_encoder.c - ${OPENAIR1_DIR}/PHY/CODING/nrPolar_tools/nr_polar_interleaving_pattern.c - ${OPENAIR1_DIR}/PHY/CODING/nrPolar_tools/nr_polar_kernal_operation.c - ${OPENAIR1_DIR}/PHY/CODING/nrPolar_tools/nr_polar_kronecker_power_matrices.c - ${OPENAIR1_DIR}/PHY/CODING/nrPolar_tools/nr_polar_matrix_and_array.c - ${OPENAIR1_DIR}/PHY/CODING/nrPolar_tools/nr_polar_procedures.c - ${OPENAIR1_DIR}/PHY/CODING/nrPolar_tools/nr_polar_sequence_pattern.c -) set(PHY_SMALLBLOCKSRC ${OPENAIR1_DIR}/PHY/CODING/nrSmallBlock/encodeSmallBlock.c ${OPENAIR1_DIR}/PHY/CODING/nrSmallBlock/decodeSmallBlock.c @@ -978,7 +964,6 @@ set(PHY_SRC_UE ${OPENAIR1_DIR}/PHY/TOOLS/dB_routines.c ${OPENAIR1_DIR}/PHY/TOOLS/sqrt.c ${OPENAIR1_DIR}/PHY/TOOLS/oai_arith_operations.c - ${PHY_POLARSRC} ${PHY_SMALLBLOCKSRC} ${PHY_NRLDPC_CODINGIF} ${OPENAIR1_DIR}/PHY/NR_TRANSPORT/pucch_rx.c @@ -1029,7 +1014,6 @@ set(PHY_SRC_UE ${OPENAIR1_DIR}/PHY/TOOLS/sqrt.c ${OPENAIR1_DIR}/PHY/TOOLS/oai_arith_operations.c ${OPENAIR1_DIR}/PHY/INIT/nr_init_ue.c - ${PHY_POLARSRC} ${PHY_SMALLBLOCKSRC} ${PHY_NRLDPC_CODINGIF} ) @@ -1064,14 +1048,14 @@ add_library(PHY_NR_COMMON ${PHY_NR_SRC_COMMON}) target_link_libraries(PHY_NR_COMMON PUBLIC UTIL) add_library(PHY_NR ${PHY_NR_SRC}) -target_link_libraries(PHY_NR nr_phy_common nr_common nr_fapi_p5) +target_link_libraries(PHY_NR nr_phy_common nr_common nr_fapi_p5 polar) add_library(PHY_NR_NO_AVX_256 ${PHY_NR_SRC}) target_link_libraries(PHY_NR_NO_AVX_256 nr_phy_common nr_common) target_compile_definitions(PHY_NR_NO_AVX_256 PUBLIC USE_128BIT) add_library(PHY_NR_UE ${PHY_NR_UE_SRC}) -target_link_libraries(PHY_NR_UE PRIVATE asn1_nr_rrc_hdrs nr_phy_common nr_common UTIL) +target_link_libraries(PHY_NR_UE PRIVATE asn1_nr_rrc_hdrs nr_phy_common nr_common UTIL polar) add_library(PHY_RU ${PHY_SRC_RU}) target_link_libraries(PHY_RU PRIVATE asn1_lte_rrc_hdrs UTIL) diff --git a/openair1/PHY/CODING/CMakeLists.txt b/openair1/PHY/CODING/CMakeLists.txt index f696c71213..bbc5184418 100644 --- a/openair1/PHY/CODING/CMakeLists.txt +++ b/openair1/PHY/CODING/CMakeLists.txt @@ -68,3 +68,4 @@ endif() add_subdirectory(nrLDPC_coding) add_subdirectory(nrLDPC_decoder) +add_subdirectory(nrPolar_tools) diff --git a/openair1/PHY/CODING/nrPolar_tools/CMakeLists.txt b/openair1/PHY/CODING/nrPolar_tools/CMakeLists.txt new file mode 100644 index 0000000000..14e3e816e1 --- /dev/null +++ b/openair1/PHY/CODING/nrPolar_tools/CMakeLists.txt @@ -0,0 +1,15 @@ +add_library(polar STATIC + nr_polar_init.c + nr_bitwise_operations.c + nr_crc_byte.c + nr_polar_decoder.c + nr_polar_decoding_tools.c + nr_polar_encoder.c + nr_polar_interleaving_pattern.c + nr_polar_kernal_operation.c + nr_polar_kronecker_power_matrices.c + nr_polar_matrix_and_array.c + nr_polar_procedures.c + nr_polar_sequence_pattern.c +) +target_link_libraries(polar PRIVATE utils) From f159d2249985970c364323d543b7d8afd4728f3d Mon Sep 17 00:00:00 2001 From: Robert Schmidt Date: Wed, 11 Feb 2026 07:53:59 +0100 Subject: [PATCH 090/100] Move crc_byte cmake code to subdirectory --- CMakeLists.txt | 2 -- openair1/PHY/CODING/CMakeLists.txt | 2 ++ 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 2c7776c94a..5ca8d55995 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -794,8 +794,6 @@ set(PHY_NRLDPC_CODINGIF add_library(dfts MODULE ${OPENAIR1_DIR}/PHY/TOOLS/oai_dfts.c ${OPENAIR1_DIR}/PHY/TOOLS/oai_dfts_neon.c) -add_library(crc_byte OBJECT ${OPENAIR1_DIR}/PHY/CODING/crc_byte.c) - set(PHY_SRC_COMMON ${OPENAIR1_DIR}/PHY/LTE_TRANSPORT/dci_tools_common.c ${OPENAIR1_DIR}/PHY/LTE_TRANSPORT/lte_mcs.c diff --git a/openair1/PHY/CODING/CMakeLists.txt b/openair1/PHY/CODING/CMakeLists.txt index bbc5184418..8c3108910b 100644 --- a/openair1/PHY/CODING/CMakeLists.txt +++ b/openair1/PHY/CODING/CMakeLists.txt @@ -66,6 +66,8 @@ if (ENABLE_LDPC_CUDA) add_dependencies(nr_dlschsim ldpc_cuda) endif() +add_library(crc_byte OBJECT crc_byte.c) + add_subdirectory(nrLDPC_coding) add_subdirectory(nrLDPC_decoder) add_subdirectory(nrPolar_tools) From 49eeba339a54611c6ffbb64e42fb570621e68660 Mon Sep 17 00:00:00 2001 From: Robert Schmidt Date: Wed, 11 Feb 2026 07:54:41 +0100 Subject: [PATCH 091/100] Move smallblock cmake code to subdirectory --- CMakeLists.txt | 10 ++-------- openair1/PHY/CODING/CMakeLists.txt | 1 + openair1/PHY/CODING/nrSmallBlock/CMakeLists.txt | 1 + 3 files changed, 4 insertions(+), 8 deletions(-) create mode 100644 openair1/PHY/CODING/nrSmallBlock/CMakeLists.txt diff --git a/CMakeLists.txt b/CMakeLists.txt index 5ca8d55995..8d3c9e5390 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -780,10 +780,6 @@ include_directories(${NFAPI_USER_DIR}) # Layer 1 ############################# -set(PHY_SMALLBLOCKSRC - ${OPENAIR1_DIR}/PHY/CODING/nrSmallBlock/encodeSmallBlock.c - ${OPENAIR1_DIR}/PHY/CODING/nrSmallBlock/decodeSmallBlock.c -) set(PHY_TURBOIF ${OPENAIR1_DIR}/PHY/CODING/coding_load.c ) @@ -962,7 +958,6 @@ set(PHY_SRC_UE ${OPENAIR1_DIR}/PHY/TOOLS/dB_routines.c ${OPENAIR1_DIR}/PHY/TOOLS/sqrt.c ${OPENAIR1_DIR}/PHY/TOOLS/oai_arith_operations.c - ${PHY_SMALLBLOCKSRC} ${PHY_NRLDPC_CODINGIF} ${OPENAIR1_DIR}/PHY/NR_TRANSPORT/pucch_rx.c ${OPENAIR1_DIR}/PHY/NR_TRANSPORT/srs_rx.c @@ -1012,7 +1007,6 @@ set(PHY_SRC_UE ${OPENAIR1_DIR}/PHY/TOOLS/sqrt.c ${OPENAIR1_DIR}/PHY/TOOLS/oai_arith_operations.c ${OPENAIR1_DIR}/PHY/INIT/nr_init_ue.c - ${PHY_SMALLBLOCKSRC} ${PHY_NRLDPC_CODINGIF} ) @@ -1046,14 +1040,14 @@ add_library(PHY_NR_COMMON ${PHY_NR_SRC_COMMON}) target_link_libraries(PHY_NR_COMMON PUBLIC UTIL) add_library(PHY_NR ${PHY_NR_SRC}) -target_link_libraries(PHY_NR nr_phy_common nr_common nr_fapi_p5 polar) +target_link_libraries(PHY_NR nr_phy_common nr_common nr_fapi_p5 polar smallblock) add_library(PHY_NR_NO_AVX_256 ${PHY_NR_SRC}) target_link_libraries(PHY_NR_NO_AVX_256 nr_phy_common nr_common) target_compile_definitions(PHY_NR_NO_AVX_256 PUBLIC USE_128BIT) add_library(PHY_NR_UE ${PHY_NR_UE_SRC}) -target_link_libraries(PHY_NR_UE PRIVATE asn1_nr_rrc_hdrs nr_phy_common nr_common UTIL polar) +target_link_libraries(PHY_NR_UE PRIVATE asn1_nr_rrc_hdrs nr_phy_common nr_common UTIL polar smallblock) add_library(PHY_RU ${PHY_SRC_RU}) target_link_libraries(PHY_RU PRIVATE asn1_lte_rrc_hdrs UTIL) diff --git a/openair1/PHY/CODING/CMakeLists.txt b/openair1/PHY/CODING/CMakeLists.txt index 8c3108910b..cdfbbf6f6f 100644 --- a/openair1/PHY/CODING/CMakeLists.txt +++ b/openair1/PHY/CODING/CMakeLists.txt @@ -71,3 +71,4 @@ add_library(crc_byte OBJECT crc_byte.c) add_subdirectory(nrLDPC_coding) add_subdirectory(nrLDPC_decoder) add_subdirectory(nrPolar_tools) +add_subdirectory(nrSmallBlock) diff --git a/openair1/PHY/CODING/nrSmallBlock/CMakeLists.txt b/openair1/PHY/CODING/nrSmallBlock/CMakeLists.txt new file mode 100644 index 0000000000..63f0361c74 --- /dev/null +++ b/openair1/PHY/CODING/nrSmallBlock/CMakeLists.txt @@ -0,0 +1 @@ +add_library(smallblock STATIC encodeSmallBlock.c decodeSmallBlock.c) From ee1f9dfedeafc8ce80af0825c716ede7a1656fbb Mon Sep 17 00:00:00 2001 From: Robert Schmidt Date: Sun, 8 Feb 2026 16:02:27 +0100 Subject: [PATCH 092/100] Move hashtable to ds/ folder Notably, remove the include_directories(), which is not necessary, as most files do not actually use this. --- CMakeLists.txt | 22 +++++++------------ common/utils/CMakeLists.txt | 3 --- common/utils/ds/CMakeLists.txt | 2 ++ common/utils/{hashtable => ds}/hashtable.c | 0 common/utils/{hashtable => ds}/hashtable.h | 0 .../utils/{hashtable => ds}/obj_hashtable.c | 0 .../utils/{hashtable => ds}/obj_hashtable.h | 0 common/utils/ds/tests/CMakeLists.txt | 6 +++++ .../tests/test_hashtable.cpp | 0 common/utils/hashtable/tests/CMakeLists.txt | 5 ----- openair2/F1AP/CMakeLists.txt | 2 +- openair2/F1AP/f1ap_ids.c | 2 +- openair2/LAYER2/PDCP_v10.1.0/pdcp.c | 2 +- openair2/LAYER2/RLC/rlc.h | 2 +- openair2/LAYER2/nr_pdcp/nr_pdcp_oai_api.c | 2 +- openair2/RRC/LTE/rrc_UE.c | 2 +- 16 files changed, 22 insertions(+), 28 deletions(-) rename common/utils/{hashtable => ds}/hashtable.c (100%) rename common/utils/{hashtable => ds}/hashtable.h (100%) rename common/utils/{hashtable => ds}/obj_hashtable.c (100%) rename common/utils/{hashtable => ds}/obj_hashtable.h (100%) rename common/utils/{hashtable => ds}/tests/test_hashtable.cpp (100%) delete mode 100644 common/utils/hashtable/tests/CMakeLists.txt diff --git a/CMakeLists.txt b/CMakeLists.txt index 8d3c9e5390..da1a10adb1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -477,7 +477,7 @@ add_library(f1ap ${F1AP_DIR}/f1ap_itti_messaging.c) target_include_directories(f1ap PUBLIC F1AP_DIR) target_link_libraries(f1ap PUBLIC asn1_f1ap GTPV1U) -target_link_libraries(f1ap PRIVATE ngap nr_rrc HASHTABLE f1ap_lib) +target_link_libraries(f1ap PRIVATE ngap nr_rrc ds f1ap_lib) target_include_directories(f1ap PRIVATE ${F1AP_DIR}/lib) if(E2_AGENT) target_compile_definitions(f1ap PRIVATE E2_AGENT) @@ -617,12 +617,6 @@ include_directories(${CMAKE_CURRENT_SOURCE_DIR}/common/utils/T # set the T_LIB name to T if the target exists, otherwise leave empty set(T_LIB $) -add_library(HASHTABLE - ${OPENAIR_DIR}/common/utils/hashtable/hashtable.c - ${OPENAIR_DIR}/common/utils/hashtable/obj_hashtable.c -) -include_directories(${OPENAIR_DIR}/common/utils/hashtable) - add_library(UTIL ${OPENAIR_DIR}/common/utils/LOG/vcd_signal_dumper.c ${OPENAIR2_DIR}/UTIL/OPT/probe.c @@ -1709,7 +1703,7 @@ add_dependencies(lte-softmodem oai_iqplayer) target_link_libraries(lte-softmodem PRIVATE -Wl,--start-group - lte_rrc nr_rrc s1ap m2ap x2ap m3ap GTPV1U SECURITY UTIL HASHTABLE SCTP_CLIENT MME_APP SCHED_LIB SCHED_RU_LIB + lte_rrc nr_rrc s1ap m2ap x2ap m3ap GTPV1U SECURITY UTIL SCTP_CLIENT MME_APP SCHED_LIB SCHED_RU_LIB PHY_COMMON PHY PHY_RU L2 L2_LTE NFAPI_LIB NFAPI_VNF_LIB NFAPI_PNF_LIB NFAPI_USER_LIB MISC_NFAPI_LTE_LIB ${NAS_UE_LIB} ITTI SIMU shlib_loader -Wl,--end-group z dl) @@ -1766,7 +1760,7 @@ add_executable(lte-uesoftmodem target_link_libraries(lte-uesoftmodem PRIVATE -Wl,--start-group lte_rrc nr_rrc s1ap x2ap m2ap m3ap - SECURITY UTIL HASHTABLE SCTP_CLIENT MME_APP SCHED_RU_LIB SCHED_UE_LIB PHY_COMMON + SECURITY UTIL SCTP_CLIENT MME_APP SCHED_RU_LIB SCHED_UE_LIB PHY_COMMON PHY_UE PHY_RU L2_UE L2_LTE SIMU NFAPI_LIB NFAPI_PNF_LIB NFAPI_USER_LIB MISC_NFAPI_LTE_LIB ${NAS_UE_LIB} ITTI shlib_loader -Wl,--end-group z dl) @@ -1819,7 +1813,7 @@ add_executable(nr-softmodem target_link_libraries(nr-softmodem PRIVATE -Wl,--start-group - UTIL HASHTABLE SCTP_CLIENT SCHED_LIB SCHED_RU_LIB SCHED_NR_LIB PHY_NR PHY PHY_COMMON PHY_NR_COMMON PHY_RU GTPV1U SECURITY + UTIL SCTP_CLIENT SCHED_LIB SCHED_RU_LIB SCHED_NR_LIB PHY_NR PHY PHY_COMMON PHY_NR_COMMON PHY_RU GTPV1U SECURITY ITTI ${NAS_UE_LIB} lte_rrc nr_rrc ngap s1ap L2_LTE_NR L2_NR MAC_NR_COMMON NFAPI_LIB NFAPI_VNF_LIB NFAPI_PNF_LIB NFAPI_USER_LIB SIMU x2ap f1ap m2ap m3ap e1ap shlib_loader @@ -1894,7 +1888,7 @@ add_executable(nr-uesoftmodem target_link_libraries(nr-uesoftmodem PRIVATE -Wl,--start-group - nr_rrc SECURITY UTIL HASHTABLE SCHED_RU_LIB SCHED_NR_UE_LIB + nr_rrc SECURITY UTIL SCHED_RU_LIB SCHED_NR_UE_LIB PHY_COMMON PHY_NR_COMMON PHY_NR_UE NR_L2_UE MAC_NR_COMMON NFAPI_LIB ITTI SIMU shlib_loader time_management @@ -2012,7 +2006,7 @@ add_executable(nr_dlsim ${PHY_INTERFACE_DIR}/queue_t.c ) target_link_libraries(nr_dlsim PRIVATE - -Wl,--start-group UTIL SIMU PHY_COMMON PHY_NR_COMMON PHY_NR PHY_NR_UE SCHED_NR_LIB SCHED_NR_UE_LIB MAC_UE_NR MAC_NR_COMMON nr_rrc CONFIG_LIB L2_NR HASHTABLE x2ap SECURITY ngap -Wl,--end-group + -Wl,--start-group UTIL SIMU PHY_COMMON PHY_NR_COMMON PHY_NR PHY_NR_UE SCHED_NR_LIB SCHED_NR_UE_LIB MAC_UE_NR MAC_NR_COMMON nr_rrc CONFIG_LIB L2_NR x2ap SECURITY ngap -Wl,--end-group m pthread ${T_LIB} ITTI dl shlib_loader nr_ue_phy_meas physim_common ) target_link_libraries(nr_dlsim PRIVATE asn1_nr_rrc_hdrs asn1_lte_rrc_hdrs) @@ -2042,7 +2036,7 @@ add_executable(nr_ulsim ) target_link_libraries(nr_ulsim PRIVATE - -Wl,--start-group UTIL SIMU PHY_COMMON PHY_NR_COMMON PHY_NR PHY_NR_UE SCHED_NR_LIB SCHED_NR_UE_LIB MAC_UE_NR MAC_NR_COMMON nr_rrc CONFIG_LIB L2_NR HASHTABLE x2ap SECURITY ngap -Wl,--end-group + -Wl,--start-group UTIL SIMU PHY_COMMON PHY_NR_COMMON PHY_NR PHY_NR_UE SCHED_NR_LIB SCHED_NR_UE_LIB MAC_UE_NR MAC_NR_COMMON nr_rrc CONFIG_LIB L2_NR x2ap SECURITY ngap -Wl,--end-group m pthread ${T_LIB} ITTI dl shlib_loader nr_ue_phy_meas physim_common ) target_link_libraries(nr_ulsim PRIVATE asn1_nr_rrc_hdrs asn1_lte_rrc_hdrs) @@ -2112,7 +2106,7 @@ if (${T_TRACER}) #all "add_library" definitions ITTI lte_rrc nr_rrc s1ap x2ap m2ap m3ap f1ap params_libconfig - oai_eth_transpro HASHTABLE UTIL + oai_eth_transpro UTIL SECURITY SCHED_LIB SCHED_NR_LIB SCHED_RU_LIB SCHED_UE_LIB SCHED_NR_UE_LIB NFAPI_LIB NFAPI_PNF_LIB NFAPI_VNF_LIB NFAPI_USER_LIB MISC_NFAPI_LTE_LIB diff --git a/common/utils/CMakeLists.txt b/common/utils/CMakeLists.txt index 84668852ae..f4611914d4 100644 --- a/common/utils/CMakeLists.txt +++ b/common/utils/CMakeLists.txt @@ -17,9 +17,6 @@ add_subdirectory(time_manager) add_library(utils utils.c system.c time_meas.c time_stat.c tuntap_if.c reverse_bits.c) target_include_directories(utils PUBLIC .) target_link_libraries(utils PRIVATE ${T_LIB}) -if (ENABLE_TESTS) - add_subdirectory(hashtable/tests) -endif() add_subdirectory(barrier) add_subdirectory(actor) add_subdirectory(shm_iq_channel) diff --git a/common/utils/ds/CMakeLists.txt b/common/utils/ds/CMakeLists.txt index b5f5efb47a..22dc7a3c63 100644 --- a/common/utils/ds/CMakeLists.txt +++ b/common/utils/ds/CMakeLists.txt @@ -2,6 +2,8 @@ add_library(ds OBJECT byte_array.c byte_array_producer.c seq_arr.c + hashtable.c + obj_hashtable.c ) target_include_directories(ds PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) diff --git a/common/utils/hashtable/hashtable.c b/common/utils/ds/hashtable.c similarity index 100% rename from common/utils/hashtable/hashtable.c rename to common/utils/ds/hashtable.c diff --git a/common/utils/hashtable/hashtable.h b/common/utils/ds/hashtable.h similarity index 100% rename from common/utils/hashtable/hashtable.h rename to common/utils/ds/hashtable.h diff --git a/common/utils/hashtable/obj_hashtable.c b/common/utils/ds/obj_hashtable.c similarity index 100% rename from common/utils/hashtable/obj_hashtable.c rename to common/utils/ds/obj_hashtable.c diff --git a/common/utils/hashtable/obj_hashtable.h b/common/utils/ds/obj_hashtable.h similarity index 100% rename from common/utils/hashtable/obj_hashtable.h rename to common/utils/ds/obj_hashtable.h diff --git a/common/utils/ds/tests/CMakeLists.txt b/common/utils/ds/tests/CMakeLists.txt index a3ef512f09..a38fba2dab 100644 --- a/common/utils/ds/tests/CMakeLists.txt +++ b/common/utils/ds/tests/CMakeLists.txt @@ -2,3 +2,9 @@ add_executable(test_seq_arr test_seq_array.c) target_link_libraries(test_seq_arr ds alg) add_dependencies(tests test_seq_arr) add_test(NAME test_seq_arr COMMAND test_seq_arr) # no options required + +add_executable(test_hashtable test_hashtable.cpp) +add_dependencies(tests test_hashtable) +target_link_libraries(test_hashtable PRIVATE ds GTest::gtest) +add_test(NAME test_hashtable + COMMAND ./test_hashtable) diff --git a/common/utils/hashtable/tests/test_hashtable.cpp b/common/utils/ds/tests/test_hashtable.cpp similarity index 100% rename from common/utils/hashtable/tests/test_hashtable.cpp rename to common/utils/ds/tests/test_hashtable.cpp diff --git a/common/utils/hashtable/tests/CMakeLists.txt b/common/utils/hashtable/tests/CMakeLists.txt deleted file mode 100644 index baf2461262..0000000000 --- a/common/utils/hashtable/tests/CMakeLists.txt +++ /dev/null @@ -1,5 +0,0 @@ -add_executable(test_hashtable test_hashtable.cpp) -add_dependencies(tests test_hashtable) -target_link_libraries(test_hashtable PRIVATE HASHTABLE GTest::gtest) -add_test(NAME test_hashtable - COMMAND ./test_hashtable) diff --git a/openair2/F1AP/CMakeLists.txt b/openair2/F1AP/CMakeLists.txt index 53ab2823ed..052964b3e1 100644 --- a/openair2/F1AP/CMakeLists.txt +++ b/openair2/F1AP/CMakeLists.txt @@ -3,7 +3,7 @@ add_subdirectory(MESSAGES) if(ENABLE_TESTS) add_executable(f1ap_ids_test f1ap_ids_test.c f1ap_ids.c) - target_link_libraries(f1ap_ids_test HASHTABLE minimal_lib) + target_link_libraries(f1ap_ids_test ds minimal_lib) add_dependencies(tests f1ap_ids_test) add_test(NAME F1AP_ID_test COMMAND f1ap_ids_test) diff --git a/openair2/F1AP/f1ap_ids.c b/openair2/F1AP/f1ap_ids.c index 5569d60c48..d222d9e35a 100644 --- a/openair2/F1AP/f1ap_ids.c +++ b/openair2/F1AP/f1ap_ids.c @@ -25,7 +25,7 @@ #include "f1ap_ids.h" #include -#include "common/utils/hashtable/hashtable.h" +#include "ds/hashtable.h" #include "common/utils/assertions.h" diff --git a/openair2/LAYER2/PDCP_v10.1.0/pdcp.c b/openair2/LAYER2/PDCP_v10.1.0/pdcp.c index 03b8ecc2d3..fdb5204081 100644 --- a/openair2/LAYER2/PDCP_v10.1.0/pdcp.c +++ b/openair2/LAYER2/PDCP_v10.1.0/pdcp.c @@ -32,7 +32,7 @@ #define MBMS_MULTICAST_OUT #include "assertions.h" -#include "hashtable.h" +#include "ds/hashtable.h" #include "pdcp.h" #include "pdcp_util.h" #include "pdcp_sequence_manager.h" diff --git a/openair2/LAYER2/RLC/rlc.h b/openair2/LAYER2/RLC/rlc.h index 4559eb3273..cd9f34b750 100644 --- a/openair2/LAYER2/RLC/rlc.h +++ b/openair2/LAYER2/RLC/rlc.h @@ -37,7 +37,7 @@ #include "common/platform_types.h" #include "common/platform_constants.h" -# include "hashtable.h" +#include "ds/hashtable.h" # include "LTE_asn_constant.h" # include "common/utils/LOG/log.h" #include "intertask_interface.h" diff --git a/openair2/LAYER2/nr_pdcp/nr_pdcp_oai_api.c b/openair2/LAYER2/nr_pdcp/nr_pdcp_oai_api.c index e588c2665b..aefe34eec3 100644 --- a/openair2/LAYER2/nr_pdcp/nr_pdcp_oai_api.c +++ b/openair2/LAYER2/nr_pdcp/nr_pdcp_oai_api.c @@ -53,7 +53,7 @@ #include "f1ap_messages_types.h" #include "gnb_config.h" #include "gtpv1_u_messages_types.h" -#include "hashtable.h" +#include "ds/hashtable.h" #include "intertask_interface.h" #include "common/utils/LOG/log.h" #include "nfapi/oai_integration/vendor_ext.h" diff --git a/openair2/RRC/LTE/rrc_UE.c b/openair2/RRC/LTE/rrc_UE.c index c8ea0fa878..6b327d92bc 100644 --- a/openair2/RRC/LTE/rrc_UE.c +++ b/openair2/RRC/LTE/rrc_UE.c @@ -35,7 +35,7 @@ #include #include "assertions.h" -#include "hashtable.h" +#include "ds/hashtable.h" #include "oai_asn1.h" #include "rrc_defs.h" #include "rrc_extern.h" From e1736b2fb592836ba3f56f509d653fca95eddce1 Mon Sep 17 00:00:00 2001 From: Robert Schmidt Date: Wed, 11 Feb 2026 08:03:10 +0100 Subject: [PATCH 093/100] Move softmodem-common.c to separate library This potentially reduces multiple compilation of the same file. --- CMakeLists.txt | 39 ++++++++++++++++---------------------- executables/CMakeLists.txt | 2 ++ 2 files changed, 18 insertions(+), 23 deletions(-) create mode 100644 executables/CMakeLists.txt diff --git a/CMakeLists.txt b/CMakeLists.txt index da1a10adb1..ec0b683fc8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1687,7 +1687,6 @@ add_executable(lte-softmodem ${OPENAIR_DIR}/executables/lte-ru.c ${OPENAIR_DIR}/executables/ru_control.c ${OPENAIR_DIR}/executables/lte-softmodem.c - ${OPENAIR_DIR}/executables/softmodem-common.c ${OPENAIR2_DIR}/ENB_APP/NB_IoT_interface.c ${OPENAIR_DIR}/executables/create_tasks.c ${OPENAIR_DIR}/executables/create_tasks_mbms.c @@ -1705,7 +1704,7 @@ target_link_libraries(lte-softmodem PRIVATE -Wl,--start-group lte_rrc nr_rrc s1ap m2ap x2ap m3ap GTPV1U SECURITY UTIL SCTP_CLIENT MME_APP SCHED_LIB SCHED_RU_LIB PHY_COMMON PHY PHY_RU L2 L2_LTE NFAPI_LIB NFAPI_VNF_LIB NFAPI_PNF_LIB NFAPI_USER_LIB MISC_NFAPI_LTE_LIB - ${NAS_UE_LIB} ITTI SIMU shlib_loader + ${NAS_UE_LIB} ITTI SIMU shlib_loader softmodem_common -Wl,--end-group z dl) target_link_libraries(lte-softmodem PRIVATE pthread m CONFIG_LIB rt) @@ -1723,14 +1722,13 @@ add_executable(oairu ${OPENAIR_DIR}/executables/ru_control.c ${OPENAIR_DIR}/radio/COMMON/common_lib.c ${OPENAIR_DIR}/radio/COMMON/record_player.c - ${OPENAIR_DIR}/executables/softmodem-common.c ${OPENAIR_DIR}/openair1/SCHED/phy_procedures_lte_common.c ${OPENAIR_DIR}/executables/main_ru.c ${OPENAIR_DIR}/common/utils/lte/prach_utils.c ) target_link_libraries(oairu PRIVATE -Wl,--start-group - SCHED_RU_LIB PHY_COMMON PHY_RU UTIL shlib_loader + SCHED_RU_LIB PHY_COMMON PHY_RU UTIL shlib_loader softmodem_common -Wl,--end-group z dl) target_link_libraries(oairu PRIVATE pthread m CONFIG_LIB rt ${T_LIB}) @@ -1747,7 +1745,6 @@ target_link_libraries(lte-softmodem PRIVATE add_executable(lte-uesoftmodem ${OPENAIR_DIR}/executables/lte-ue.c ${OPENAIR_DIR}/executables/lte-uesoftmodem.c - ${OPENAIR_DIR}/executables/softmodem-common.c ${OPENAIR_DIR}/executables/create_tasks_ue.c ${OPENAIR_DIR}/radio/COMMON/common_lib.c ${OPENAIR_DIR}/radio/COMMON/record_player.c @@ -1762,7 +1759,7 @@ target_link_libraries(lte-uesoftmodem PRIVATE lte_rrc nr_rrc s1ap x2ap m2ap m3ap SECURITY UTIL SCTP_CLIENT MME_APP SCHED_RU_LIB SCHED_UE_LIB PHY_COMMON PHY_UE PHY_RU L2_UE L2_LTE SIMU NFAPI_LIB NFAPI_PNF_LIB NFAPI_USER_LIB MISC_NFAPI_LTE_LIB - ${NAS_UE_LIB} ITTI shlib_loader + ${NAS_UE_LIB} ITTI shlib_loader softmodem_common -Wl,--end-group z dl) target_link_libraries(lte-uesoftmodem PRIVATE pthread m CONFIG_LIB rt) @@ -1783,11 +1780,11 @@ add_executable(nr-oru ${OPENAIR_DIR}/openair1/SCHED/phy_procedures_lte_common.c ${OPENAIR_DIR}/radio/COMMON/common_lib.c ${OPENAIR_DIR}/radio/COMMON/record_player.c - ${OPENAIR_DIR}/executables/softmodem-common.c ${OPENAIR_DIR}/executables/main_nr_ru.c ) target_link_libraries(nr-oru PRIVATE - UTIL SCHED_RU_LIB PHY_COMMON PHY_RU PHY_NR shlib_loader z dl) + UTIL SCHED_RU_LIB PHY_COMMON PHY_RU PHY_NR shlib_loader z dl + softmodem_common) target_link_libraries(nr-oru PRIVATE pthread m CONFIG_LIB rt ${T_LIB} utils barrier actor) target_link_libraries(nr-oru PRIVATE asn1_nr_rrc_hdrs asn1_lte_rrc_hdrs nr_phy_common time_management) @@ -1802,7 +1799,6 @@ add_executable(nr-softmodem ${OPENAIR_DIR}/executables/nr-gnb.c ${OPENAIR_DIR}/executables/nr-ru.c ${OPENAIR_DIR}/executables/nr-softmodem.c - ${OPENAIR_DIR}/executables/softmodem-common.c ${OPENAIR_DIR}/radio/COMMON/common_lib.c ${OPENAIR_DIR}/radio/COMMON/record_player.c ${OPENAIR_DIR}/common/utils/lte/ue_power.c @@ -1817,7 +1813,7 @@ target_link_libraries(nr-softmodem PRIVATE ITTI ${NAS_UE_LIB} lte_rrc nr_rrc ngap s1ap L2_LTE_NR L2_NR MAC_NR_COMMON NFAPI_LIB NFAPI_VNF_LIB NFAPI_PNF_LIB NFAPI_USER_LIB SIMU x2ap f1ap m2ap m3ap e1ap shlib_loader - time_management + time_management softmodem_common -Wl,--end-group z dl) target_link_libraries(nr-softmodem PRIVATE pthread m CONFIG_LIB rt) @@ -1847,7 +1843,6 @@ target_link_libraries(nr-softmodem PRIVATE add_executable(nr-cuup executables/nr-cuup.c - executables/softmodem-common.c ${NR_RRC_DIR}/rrc_gNB_UE_context.c ${OPENAIR2_DIR}/E1AP/e1ap_setup.c ${NR_PDCP_SRC} @@ -1857,7 +1852,7 @@ add_executable(nr-cuup target_link_libraries(nr-cuup PRIVATE CONFIG_LIB ITTI SCTP_CLIENT GTPV1U e1ap f1ap - time_management + time_management softmodem_common alg z dl pthread shlib_loader ${T_LIB}) target_link_libraries(nr-cuup PRIVATE asn1_lte_rrc_hdrs asn1_nr_rrc_hdrs) @@ -1876,7 +1871,6 @@ add_executable(nr-uesoftmodem ${OPENAIR_DIR}/executables/position_interface.c ${OPENAIR_DIR}/executables/nr-ue.c ${OPENAIR_DIR}/executables/nr-ue-ru.c - ${OPENAIR_DIR}/executables/softmodem-common.c ${OPENAIR_DIR}/radio/COMMON/common_lib.c ${OPENAIR_DIR}/radio/COMMON/record_player.c ${OPENAIR2_DIR}/LAYER2/NR_MAC_COMMON/nr_mac_common.c @@ -1891,7 +1885,7 @@ target_link_libraries(nr-uesoftmodem PRIVATE nr_rrc SECURITY UTIL SCHED_RU_LIB SCHED_NR_UE_LIB PHY_COMMON PHY_NR_COMMON PHY_NR_UE NR_L2_UE MAC_NR_COMMON NFAPI_LIB ITTI SIMU shlib_loader - time_management + time_management softmodem_common -Wl,--end-group z dl) target_link_libraries(nr-uesoftmodem PRIVATE pthread m CONFIG_LIB rt nr_ue_phy_meas) @@ -1925,9 +1919,10 @@ add_executable(rftest ${OPENAIR_DIR}/openair1/PHY/TOOLS/calibration_test.c ${OPENAIR_DIR}/openair1/PHY/TOOLS/calibration_scope.c ${OPENAIR_DIR}/radio/COMMON/common_lib.c - ${OPENAIR_DIR}/executables/softmodem-common.c ) -target_link_libraries(rftest PRIVATE minimal_lib PHY_NR_COMMON UTIL forms pthread dl m ${T_LIB} shlib_loader) +target_link_libraries(rftest PRIVATE + minimal_lib PHY_NR_COMMON UTIL forms pthread dl m ${T_LIB} + shlib_loader softmodem_common) target_link_libraries(rftest PRIVATE asn1_lte_rrc_hdrs asn1_nr_rrc_hdrs) add_executable(polartest @@ -1980,14 +1975,13 @@ target_link_libraries(nr_pbchsim PRIVATE asn1_nr_rrc_hdrs asn1_lte_rrc_hdrs) add_executable(nr_psbchsim ${OPENAIR1_DIR}/SIMULATION/NR_PHY/psbchsim.c - ${OPENAIR_DIR}/executables/softmodem-common.c ${NFAPI_USER_DIR}/nfapi.c ${NFAPI_USER_DIR}/gnb_ind_vars.c ${PHY_INTERFACE_DIR}/queue_t.c ) target_link_libraries(nr_psbchsim PRIVATE -Wl,--start-group UTIL SIMU PHY_COMMON PHY_NR_COMMON PHY_NR PHY_NR_UE SCHED_NR_LIB SCHED_NR_UE_LIB MAC_UE_NR MAC_NR_COMMON CONFIG_LIB L2_NR -Wl,--end-group - m pthread ${T_LIB} ITTI dl shlib_loader physim_common + m pthread ${T_LIB} ITTI dl shlib_loader physim_common softmodem_common ) target_link_libraries(nr_psbchsim PRIVATE asn1_nr_rrc_hdrs asn1_lte_rrc_hdrs) @@ -2000,7 +1994,6 @@ target_link_libraries(nr_pucchsim PRIVATE asn1_nr_rrc_hdrs asn1_lte_rrc_hdrs) add_executable(nr_dlsim ${OPENAIR1_DIR}/SIMULATION/NR_PHY/dlsim.c - ${OPENAIR_DIR}/executables/softmodem-common.c ${NFAPI_USER_DIR}/nfapi.c ${NFAPI_USER_DIR}/gnb_ind_vars.c ${PHY_INTERFACE_DIR}/queue_t.c @@ -2008,6 +2001,7 @@ add_executable(nr_dlsim target_link_libraries(nr_dlsim PRIVATE -Wl,--start-group UTIL SIMU PHY_COMMON PHY_NR_COMMON PHY_NR PHY_NR_UE SCHED_NR_LIB SCHED_NR_UE_LIB MAC_UE_NR MAC_NR_COMMON nr_rrc CONFIG_LIB L2_NR x2ap SECURITY ngap -Wl,--end-group m pthread ${T_LIB} ITTI dl shlib_loader nr_ue_phy_meas physim_common + softmodem_common ) target_link_libraries(nr_dlsim PRIVATE asn1_nr_rrc_hdrs asn1_lte_rrc_hdrs) @@ -2029,7 +2023,6 @@ target_link_libraries(nr_ulschsim PRIVATE asn1_nr_rrc_hdrs asn1_lte_rrc_hdrs) add_executable(nr_ulsim ${OPENAIR1_DIR}/SIMULATION/NR_PHY/ulsim.c - ${OPENAIR_DIR}/executables/softmodem-common.c ${NFAPI_USER_DIR}/nfapi.c ${NFAPI_USER_DIR}/gnb_ind_vars.c ${PHY_INTERFACE_DIR}/queue_t.c @@ -2037,13 +2030,12 @@ add_executable(nr_ulsim target_link_libraries(nr_ulsim PRIVATE -Wl,--start-group UTIL SIMU PHY_COMMON PHY_NR_COMMON PHY_NR PHY_NR_UE SCHED_NR_LIB SCHED_NR_UE_LIB MAC_UE_NR MAC_NR_COMMON nr_rrc CONFIG_LIB L2_NR x2ap SECURITY ngap -Wl,--end-group - m pthread ${T_LIB} ITTI dl shlib_loader nr_ue_phy_meas physim_common + m pthread ${T_LIB} ITTI dl shlib_loader nr_ue_phy_meas physim_common softmodem_common ) target_link_libraries(nr_ulsim PRIVATE asn1_nr_rrc_hdrs asn1_lte_rrc_hdrs) add_executable(nr_srssim ${OPENAIR1_DIR}/SIMULATION/NR_PHY/srssim.c - ${OPENAIR_DIR}/executables/softmodem-common.c ${NFAPI_USER_DIR}/nfapi.c ${NFAPI_USER_DIR}/gnb_ind_vars.c ${PHY_INTERFACE_DIR}/queue_t.c @@ -2051,7 +2043,7 @@ add_executable(nr_srssim target_link_libraries(nr_srssim PRIVATE -Wl,--start-group UTIL SIMU PHY_COMMON PHY_NR_COMMON PHY_NR PHY_NR_UE SCHED_NR_LIB SCHED_NR_UE_LIB MAC_UE_NR MAC_NR_COMMON nr_rrc L2_NR -Wl,--end-group - m pthread ITTI dl nr_ue_phy_meas physim_common + m pthread ITTI dl nr_ue_phy_meas physim_common softmodem_common ) # these simulators do not compile: @@ -2159,6 +2151,7 @@ if(ENABLE_TESTS) endif() add_subdirectory(common) +add_subdirectory(executables) add_subdirectory(doc) add_subdirectory(nfapi) add_subdirectory(openair1) diff --git a/executables/CMakeLists.txt b/executables/CMakeLists.txt new file mode 100644 index 0000000000..d3b925bb02 --- /dev/null +++ b/executables/CMakeLists.txt @@ -0,0 +1,2 @@ +add_library(softmodem_common STATIC softmodem-common.c) +target_link_libraries(softmodem_common PUBLIC shlib_loader) From 7da5d3889fbc828ec4ce8daf6bc8705410253196 Mon Sep 17 00:00:00 2001 From: Robert Schmidt Date: Wed, 11 Feb 2026 08:09:54 +0100 Subject: [PATCH 094/100] Split common_lib.c in separate compilation unit This avoids double compilation. --- CMakeLists.txt | 53 +++++++++++++------------------------ radio/CMakeLists.txt | 2 ++ radio/COMMON/CMakeLists.txt | 3 +++ radio/COMMON/common_lib.c | 1 - 4 files changed, 24 insertions(+), 35 deletions(-) create mode 100644 radio/COMMON/CMakeLists.txt diff --git a/CMakeLists.txt b/CMakeLists.txt index ec0b683fc8..a11137d1ef 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -515,8 +515,6 @@ target_link_libraries(params_libconfig PRIVATE config_internals ${libconfig_LIBR add_library(shlib_loader OBJECT common/utils/load_module_shlib.c) target_link_libraries(shlib_loader PRIVATE CONFIG_LIB) -include_directories ("${OPENAIR_DIR}/radio/COMMON") - ############################################################## # ???!!! TO BE DOCUMENTED OPTIONS !!!??? ############################################################## @@ -1690,8 +1688,6 @@ add_executable(lte-softmodem ${OPENAIR2_DIR}/ENB_APP/NB_IoT_interface.c ${OPENAIR_DIR}/executables/create_tasks.c ${OPENAIR_DIR}/executables/create_tasks_mbms.c - ${OPENAIR_DIR}/radio/COMMON/common_lib.c - ${OPENAIR_DIR}/radio/COMMON/record_player.c ${OPENAIR3_DIR}/NAS/UE/nas_ue_task.c ${PHY_INTERFACE_DIR}/queue_t.c ${OPENAIR_DIR}/common/utils/lte/ue_power.c @@ -1704,7 +1700,7 @@ target_link_libraries(lte-softmodem PRIVATE -Wl,--start-group lte_rrc nr_rrc s1ap m2ap x2ap m3ap GTPV1U SECURITY UTIL SCTP_CLIENT MME_APP SCHED_LIB SCHED_RU_LIB PHY_COMMON PHY PHY_RU L2 L2_LTE NFAPI_LIB NFAPI_VNF_LIB NFAPI_PNF_LIB NFAPI_USER_LIB MISC_NFAPI_LTE_LIB - ${NAS_UE_LIB} ITTI SIMU shlib_loader softmodem_common + ${NAS_UE_LIB} ITTI SIMU radio_common softmodem_common -Wl,--end-group z dl) target_link_libraries(lte-softmodem PRIVATE pthread m CONFIG_LIB rt) @@ -1720,15 +1716,13 @@ endif() add_executable(oairu ${OPENAIR_DIR}/executables/lte-ru.c ${OPENAIR_DIR}/executables/ru_control.c - ${OPENAIR_DIR}/radio/COMMON/common_lib.c - ${OPENAIR_DIR}/radio/COMMON/record_player.c ${OPENAIR_DIR}/openair1/SCHED/phy_procedures_lte_common.c ${OPENAIR_DIR}/executables/main_ru.c ${OPENAIR_DIR}/common/utils/lte/prach_utils.c ) target_link_libraries(oairu PRIVATE -Wl,--start-group - SCHED_RU_LIB PHY_COMMON PHY_RU UTIL shlib_loader softmodem_common + SCHED_RU_LIB PHY_COMMON PHY_RU UTIL radio_common softmodem_common -Wl,--end-group z dl) target_link_libraries(oairu PRIVATE pthread m CONFIG_LIB rt ${T_LIB}) @@ -1746,8 +1740,6 @@ add_executable(lte-uesoftmodem ${OPENAIR_DIR}/executables/lte-ue.c ${OPENAIR_DIR}/executables/lte-uesoftmodem.c ${OPENAIR_DIR}/executables/create_tasks_ue.c - ${OPENAIR_DIR}/radio/COMMON/common_lib.c - ${OPENAIR_DIR}/radio/COMMON/record_player.c ${OPENAIR3_DIR}/NAS/UE/nas_ue_task.c ${OPENAIR_DIR}/common/utils/lte/ue_power.c ${OPENAIR_DIR}/common/utils/lte/prach_utils.c @@ -1759,7 +1751,7 @@ target_link_libraries(lte-uesoftmodem PRIVATE lte_rrc nr_rrc s1ap x2ap m2ap m3ap SECURITY UTIL SCTP_CLIENT MME_APP SCHED_RU_LIB SCHED_UE_LIB PHY_COMMON PHY_UE PHY_RU L2_UE L2_LTE SIMU NFAPI_LIB NFAPI_PNF_LIB NFAPI_USER_LIB MISC_NFAPI_LTE_LIB - ${NAS_UE_LIB} ITTI shlib_loader softmodem_common + ${NAS_UE_LIB} ITTI radio_common softmodem_common -Wl,--end-group z dl) target_link_libraries(lte-uesoftmodem PRIVATE pthread m CONFIG_LIB rt) @@ -1778,13 +1770,11 @@ add_executable(nr-oru ${OPENAIR_DIR}/openair1/SCHED_NR/phy_frame_config_nr.c ${OPENAIR_DIR}/openair1/SCHED_NR/nr_prach_procedures.c ${OPENAIR_DIR}/openair1/SCHED/phy_procedures_lte_common.c - ${OPENAIR_DIR}/radio/COMMON/common_lib.c - ${OPENAIR_DIR}/radio/COMMON/record_player.c ${OPENAIR_DIR}/executables/main_nr_ru.c ) target_link_libraries(nr-oru PRIVATE UTIL SCHED_RU_LIB PHY_COMMON PHY_RU PHY_NR shlib_loader z dl - softmodem_common) + radio_common softmodem_common) target_link_libraries(nr-oru PRIVATE pthread m CONFIG_LIB rt ${T_LIB} utils barrier actor) target_link_libraries(nr-oru PRIVATE asn1_nr_rrc_hdrs asn1_lte_rrc_hdrs nr_phy_common time_management) @@ -1799,8 +1789,6 @@ add_executable(nr-softmodem ${OPENAIR_DIR}/executables/nr-gnb.c ${OPENAIR_DIR}/executables/nr-ru.c ${OPENAIR_DIR}/executables/nr-softmodem.c - ${OPENAIR_DIR}/radio/COMMON/common_lib.c - ${OPENAIR_DIR}/radio/COMMON/record_player.c ${OPENAIR_DIR}/common/utils/lte/ue_power.c ${OPENAIR_DIR}/common/utils/lte/prach_utils.c ${PHY_INTERFACE_DIR}/queue_t.c @@ -1812,7 +1800,7 @@ target_link_libraries(nr-softmodem PRIVATE UTIL SCTP_CLIENT SCHED_LIB SCHED_RU_LIB SCHED_NR_LIB PHY_NR PHY PHY_COMMON PHY_NR_COMMON PHY_RU GTPV1U SECURITY ITTI ${NAS_UE_LIB} lte_rrc nr_rrc ngap s1ap L2_LTE_NR L2_NR MAC_NR_COMMON NFAPI_LIB NFAPI_VNF_LIB NFAPI_PNF_LIB NFAPI_USER_LIB SIMU - x2ap f1ap m2ap m3ap e1ap shlib_loader + x2ap f1ap m2ap m3ap e1ap radio_common time_management softmodem_common -Wl,--end-group z dl) @@ -1854,7 +1842,7 @@ target_link_libraries(nr-cuup PRIVATE GTPV1U e1ap f1ap time_management softmodem_common alg - z dl pthread shlib_loader ${T_LIB}) + z dl pthread ${T_LIB}) target_link_libraries(nr-cuup PRIVATE asn1_lte_rrc_hdrs asn1_nr_rrc_hdrs) if(E2_AGENT) target_link_libraries(nr-cuup PRIVATE e2_agent e2_agent_arg e2_ran_func_cuup) @@ -1871,8 +1859,6 @@ add_executable(nr-uesoftmodem ${OPENAIR_DIR}/executables/position_interface.c ${OPENAIR_DIR}/executables/nr-ue.c ${OPENAIR_DIR}/executables/nr-ue-ru.c - ${OPENAIR_DIR}/radio/COMMON/common_lib.c - ${OPENAIR_DIR}/radio/COMMON/record_player.c ${OPENAIR2_DIR}/LAYER2/NR_MAC_COMMON/nr_mac_common.c ${OPENAIR2_DIR}/LAYER2/NR_MAC_COMMON/nr_mac_common_tdd.c ${OPENAIR1_DIR}/PHY/TOOLS/phy_scope_interface.c @@ -1884,7 +1870,7 @@ target_link_libraries(nr-uesoftmodem PRIVATE -Wl,--start-group nr_rrc SECURITY UTIL SCHED_RU_LIB SCHED_NR_UE_LIB PHY_COMMON PHY_NR_COMMON PHY_NR_UE NR_L2_UE MAC_NR_COMMON NFAPI_LIB - ITTI SIMU shlib_loader + ITTI SIMU radio_common time_management softmodem_common -Wl,--end-group z dl) @@ -1918,11 +1904,10 @@ target_link_libraries(nr-uesoftmodem PRIVATE add_executable(rftest ${OPENAIR_DIR}/openair1/PHY/TOOLS/calibration_test.c ${OPENAIR_DIR}/openair1/PHY/TOOLS/calibration_scope.c - ${OPENAIR_DIR}/radio/COMMON/common_lib.c ) target_link_libraries(rftest PRIVATE minimal_lib PHY_NR_COMMON UTIL forms pthread dl m ${T_LIB} - shlib_loader softmodem_common) + radio_common softmodem_common) target_link_libraries(rftest PRIVATE asn1_lte_rrc_hdrs asn1_nr_rrc_hdrs) add_executable(polartest @@ -1939,7 +1924,7 @@ add_executable(smallblocktest target_link_libraries(smallblocktest PRIVATE -Wl,--start-group UTIL SIMU PHY_NR PHY_COMMON PHY_NR_COMMON -Wl,--end-group - m pthread dl shlib_loader ${T_LIB} + m pthread dl ${T_LIB} ) @@ -1949,7 +1934,7 @@ add_executable(ldpctest ) target_link_libraries(ldpctest PRIVATE -Wl,--start-group UTIL SIMU PHY_COMMON PHY_NR_COMMON -Wl,--end-group - m pthread dl shlib_loader ${T_LIB} + m pthread dl ${T_LIB} # link 'check_crc' to make it resolved in the LDPC coding libraries # 'check_crc' is not used in ldpctest so it is not linked in the executable by default # --whole-archive links 'check_crc' in the executable even though it is note used, see 'man ld' @@ -1962,14 +1947,14 @@ target_link_libraries(physim_common PRIVATE UTIL) add_executable(nr_dlschsim ${OPENAIR1_DIR}/SIMULATION/NR_PHY/dlschsim.c) target_link_libraries(nr_dlschsim PRIVATE -Wl,--start-group UTIL SIMU PHY_COMMON PHY_NR_COMMON PHY_NR PHY_NR_UE SCHED_NR_LIB CONFIG_LIB MAC_NR_COMMON -Wl,--end-group - m pthread ${T_LIB} ITTI dl shlib_loader physim_common + m pthread ${T_LIB} ITTI dl physim_common ) target_link_libraries(nr_dlschsim PRIVATE asn1_nr_rrc_hdrs) add_executable(nr_pbchsim ${OPENAIR1_DIR}/SIMULATION/NR_PHY/pbchsim.c) target_link_libraries(nr_pbchsim PRIVATE -Wl,--start-group UTIL SIMU PHY_COMMON PHY_NR_COMMON PHY_NR PHY_NR_UE SCHED_NR_LIB CONFIG_LIB MAC_NR_COMMON -Wl,--end-group - m pthread ${T_LIB} ITTI dl shlib_loader physim_common + m pthread ${T_LIB} ITTI dl physim_common ) target_link_libraries(nr_pbchsim PRIVATE asn1_nr_rrc_hdrs asn1_lte_rrc_hdrs) @@ -1981,14 +1966,14 @@ add_executable(nr_psbchsim ) target_link_libraries(nr_psbchsim PRIVATE -Wl,--start-group UTIL SIMU PHY_COMMON PHY_NR_COMMON PHY_NR PHY_NR_UE SCHED_NR_LIB SCHED_NR_UE_LIB MAC_UE_NR MAC_NR_COMMON CONFIG_LIB L2_NR -Wl,--end-group - m pthread ${T_LIB} ITTI dl shlib_loader physim_common softmodem_common + m pthread ${T_LIB} ITTI dl physim_common softmodem_common ) target_link_libraries(nr_psbchsim PRIVATE asn1_nr_rrc_hdrs asn1_lte_rrc_hdrs) add_executable(nr_pucchsim ${OPENAIR1_DIR}/SIMULATION/NR_PHY/pucchsim.c) target_link_libraries(nr_pucchsim PRIVATE -Wl,--start-group UTIL SIMU PHY_COMMON PHY_NR_COMMON PHY_NR PHY_NR_UE SCHED_NR_LIB CONFIG_LIB MAC_NR_COMMON -Wl,--end-group - m pthread ${T_LIB} ITTI dl shlib_loader physim_common + m pthread ${T_LIB} ITTI dl physim_common ) target_link_libraries(nr_pucchsim PRIVATE asn1_nr_rrc_hdrs asn1_lte_rrc_hdrs) @@ -2000,7 +1985,7 @@ add_executable(nr_dlsim ) target_link_libraries(nr_dlsim PRIVATE -Wl,--start-group UTIL SIMU PHY_COMMON PHY_NR_COMMON PHY_NR PHY_NR_UE SCHED_NR_LIB SCHED_NR_UE_LIB MAC_UE_NR MAC_NR_COMMON nr_rrc CONFIG_LIB L2_NR x2ap SECURITY ngap -Wl,--end-group - m pthread ${T_LIB} ITTI dl shlib_loader nr_ue_phy_meas physim_common + m pthread ${T_LIB} ITTI dl nr_ue_phy_meas physim_common softmodem_common ) target_link_libraries(nr_dlsim PRIVATE asn1_nr_rrc_hdrs asn1_lte_rrc_hdrs) @@ -2008,7 +1993,7 @@ target_link_libraries(nr_dlsim PRIVATE asn1_nr_rrc_hdrs asn1_lte_rrc_hdrs) add_executable(nr_prachsim ${OPENAIR1_DIR}/SIMULATION/NR_PHY/prachsim.c) target_link_libraries(nr_prachsim PRIVATE -Wl,--start-group UTIL SIMU PHY_COMMON PHY_NR_COMMON PHY_NR PHY_RU PHY_NR_UE MAC_NR_COMMON SCHED_NR_LIB CONFIG_LIB -Wl,--end-group - m pthread ${T_LIB} ITTI dl shlib_loader physim_common) + m pthread ${T_LIB} ITTI dl physim_common) target_link_libraries(nr_prachsim PRIVATE asn1_nr_rrc_hdrs asn1_lte_rrc_hdrs) add_executable(nr_ulschsim @@ -2017,7 +2002,7 @@ add_executable(nr_ulschsim ) target_link_libraries(nr_ulschsim PRIVATE -Wl,--start-group UTIL SIMU PHY_COMMON PHY_NR_COMMON PHY_NR PHY_NR_UE SCHED_NR_LIB CONFIG_LIB MAC_NR_COMMON -Wl,--end-group - m pthread ${T_LIB} ITTI dl shlib_loader physim_common + m pthread ${T_LIB} ITTI dl physim_common ) target_link_libraries(nr_ulschsim PRIVATE asn1_nr_rrc_hdrs asn1_lte_rrc_hdrs) @@ -2030,7 +2015,7 @@ add_executable(nr_ulsim target_link_libraries(nr_ulsim PRIVATE -Wl,--start-group UTIL SIMU PHY_COMMON PHY_NR_COMMON PHY_NR PHY_NR_UE SCHED_NR_LIB SCHED_NR_UE_LIB MAC_UE_NR MAC_NR_COMMON nr_rrc CONFIG_LIB L2_NR x2ap SECURITY ngap -Wl,--end-group - m pthread ${T_LIB} ITTI dl shlib_loader nr_ue_phy_meas physim_common softmodem_common + m pthread ${T_LIB} ITTI dl nr_ue_phy_meas physim_common softmodem_common ) target_link_libraries(nr_ulsim PRIVATE asn1_nr_rrc_hdrs asn1_lte_rrc_hdrs) @@ -2059,7 +2044,7 @@ foreach(myExe dlsim ulsim) ) target_link_libraries (${myExe} PRIVATE -Wl,--start-group SIMU UTIL SCHED_LIB SCHED_RU_LIB SCHED_UE_LIB PHY_COMMON PHY_NR_COMMON PHY PHY_UE PHY_RU ITTI -Wl,--end-group - pthread m rt CONFIG_LIB ${T_LIB} dl shlib_loader + pthread m rt CONFIG_LIB ${T_LIB} dl ) target_link_libraries(${myExe} PRIVATE asn1_lte_rrc_hdrs) add_dependencies(${myExe} coding) diff --git a/radio/CMakeLists.txt b/radio/CMakeLists.txt index 1159416afa..c5da06ffde 100644 --- a/radio/CMakeLists.txt +++ b/radio/CMakeLists.txt @@ -1,3 +1,5 @@ +add_subdirectory(COMMON) + add_boolean_option(OAI_AW2SORI OFF "Activate OAI's AW2S driver" OFF) if(OAI_AW2SORI) add_subdirectory(AW2SORI) diff --git a/radio/COMMON/CMakeLists.txt b/radio/COMMON/CMakeLists.txt new file mode 100644 index 0000000000..af70ac4047 --- /dev/null +++ b/radio/COMMON/CMakeLists.txt @@ -0,0 +1,3 @@ +add_library(radio_common STATIC common_lib.c record_player.c) +target_include_directories(radio_common PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) +target_link_libraries(radio_common PUBLIC dl log_headers shlib_loader) diff --git a/radio/COMMON/common_lib.c b/radio/COMMON/common_lib.c index 7b70dc20fa..4720347f20 100644 --- a/radio/COMMON/common_lib.c +++ b/radio/COMMON/common_lib.c @@ -40,7 +40,6 @@ #include "assertions.h" #include "common/utils/load_module_shlib.h" #include "common/utils/LOG/log.h" -//#include "targets/RT/USER/lte-softmodem.h" #include "executables/softmodem-common.h" #define MAX_GAP 100ULL From b14127b3ffb2d3b507a6d47022809ba38e8b8980 Mon Sep 17 00:00:00 2001 From: Robert Schmidt Date: Sun, 8 Feb 2026 16:23:33 +0100 Subject: [PATCH 095/100] Remove double-declared C files Is already linked in through MAC_NR_COMMON. --- CMakeLists.txt | 2 -- 1 file changed, 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index a11137d1ef..441713eb6e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1859,8 +1859,6 @@ add_executable(nr-uesoftmodem ${OPENAIR_DIR}/executables/position_interface.c ${OPENAIR_DIR}/executables/nr-ue.c ${OPENAIR_DIR}/executables/nr-ue-ru.c - ${OPENAIR2_DIR}/LAYER2/NR_MAC_COMMON/nr_mac_common.c - ${OPENAIR2_DIR}/LAYER2/NR_MAC_COMMON/nr_mac_common_tdd.c ${OPENAIR1_DIR}/PHY/TOOLS/phy_scope_interface.c ${NFAPI_USER_DIR}/nfapi.c ${PHY_INTERFACE_DIR}/queue_t.c From d1aa6b1d7c0a7a4b08a70886bd85ed81de96e6f1 Mon Sep 17 00:00:00 2001 From: Robert Schmidt Date: Wed, 11 Feb 2026 08:25:35 +0100 Subject: [PATCH 096/100] Remove unused headers --- openair2/LAYER2/nr_pdcp/cucp_cuup_handler.c | 1 - openair2/LAYER2/nr_pdcp/nr_pdcp_oai_api.c | 5 ----- 2 files changed, 6 deletions(-) diff --git a/openair2/LAYER2/nr_pdcp/cucp_cuup_handler.c b/openair2/LAYER2/nr_pdcp/cucp_cuup_handler.c index ecdf3dfd72..3291a74719 100644 --- a/openair2/LAYER2/nr_pdcp/cucp_cuup_handler.c +++ b/openair2/LAYER2/nr_pdcp/cucp_cuup_handler.c @@ -37,7 +37,6 @@ #include "common/utils/oai_asn1.h" #include "constr_TYPE.h" #include "cuup_cucp_if.h" -#include "gtpv1_u_messages_types.h" #include "nr_pdcp/nr_pdcp_entity.h" #include "nr_pdcp_oai_api.h" #include "openair2/COMMON/e1ap_messages_types.h" diff --git a/openair2/LAYER2/nr_pdcp/nr_pdcp_oai_api.c b/openair2/LAYER2/nr_pdcp/nr_pdcp_oai_api.c index aefe34eec3..fdc0c57fe4 100644 --- a/openair2/LAYER2/nr_pdcp/nr_pdcp_oai_api.c +++ b/openair2/LAYER2/nr_pdcp/nr_pdcp_oai_api.c @@ -31,11 +31,6 @@ #include #include #include -#include "LAYER2/MAC/mac_extern.h" -#include "LTE_DRB-ToAddModList.h" -#include "LTE_DRB-ToReleaseList.h" -#include "LTE_PMCH-InfoList-r9.h" -#include "LTE_SRB-ToAddModList.h" #include "NR_DRB-ToAddMod.h" #include "NR_QFI.h" #include "NR_SDAP-Config.h" From 12e9ab1412d54e255829c7347e1eaec6475f5d69 Mon Sep 17 00:00:00 2001 From: Robert Schmidt Date: Wed, 11 Feb 2026 08:31:55 +0100 Subject: [PATCH 097/100] Remove unused type --- openair2/COMMON/rrc_messages_types.h | 6 ------ 1 file changed, 6 deletions(-) diff --git a/openair2/COMMON/rrc_messages_types.h b/openair2/COMMON/rrc_messages_types.h index cfc6914f4b..ee848f6f06 100644 --- a/openair2/COMMON/rrc_messages_types.h +++ b/openair2/COMMON/rrc_messages_types.h @@ -425,12 +425,6 @@ typedef struct NRRrcConfigurationReq_s { int drbs; } gNB_RrcConfigurationReq; -typedef struct NRDuDlReq_s { - rnti_t rnti; - uint8_t *buf; - uint64_t srb_id; -} NRDuDlReq_t; - // eNB: realtime -> RRC messages typedef struct rrc_subframe_process_s { protocol_ctxt_t ctxt; From 4d4d0d4dd4548ed15519212e55c93be030d1e0f4 Mon Sep 17 00:00:00 2001 From: Robert Schmidt Date: Wed, 11 Feb 2026 08:38:22 +0100 Subject: [PATCH 098/100] Move RRC config to RRC header, rename openair2/COMMON/rrc_messages_types.h has mostly 4G types. The only 5G (gNB) RRC type is gNB_RrcConfigurationReq, which we can move to a header in the corresponding directory (where it is used). This allows to separate 5G gNB RRC module from the 4G types in rrc_messages_types.h in the next commit. --- openair2/COMMON/rrc_messages_types.h | 11 ----------- openair2/GNB_APP/gnb_config.c | 2 +- openair2/RRC/NR/MESSAGES/asn1_msg.c | 1 - openair2/RRC/NR/MESSAGES/asn1_msg.h | 1 - openair2/RRC/NR/nr_rrc_defs.h | 12 +++++++++++- openair2/RRC/NR/nr_rrc_proto.h | 2 +- openair2/RRC/NR/rrc_gNB.c | 4 ++-- openair2/RRC/NR/rrc_gNB_NGAP.c | 2 +- openair2/RRC/NR/rrc_gNB_du.c | 2 +- 9 files changed, 17 insertions(+), 20 deletions(-) diff --git a/openair2/COMMON/rrc_messages_types.h b/openair2/COMMON/rrc_messages_types.h index ee848f6f06..d11e6573c7 100644 --- a/openair2/COMMON/rrc_messages_types.h +++ b/openair2/COMMON/rrc_messages_types.h @@ -414,17 +414,6 @@ typedef struct NbIoTRrcConfigurationReq_s { long ue_TimersAndConstants_n311_NB; } NbIoTRrcConfigurationReq; -// gNB: GNB_APP -> RRC messages -typedef struct NRRrcConfigurationReq_s { - uint32_t tac; - plmn_id_t plmn[PLMN_LIST_MAX_SIZE]; - uint8_t num_plmn; - - bool um_on_default_drb; - bool enable_sdap; - int drbs; -} gNB_RrcConfigurationReq; - // eNB: realtime -> RRC messages typedef struct rrc_subframe_process_s { protocol_ctxt_t ctxt; diff --git a/openair2/GNB_APP/gnb_config.c b/openair2/GNB_APP/gnb_config.c index 0b3845f5b9..8e1c4637b7 100644 --- a/openair2/GNB_APP/gnb_config.c +++ b/openair2/GNB_APP/gnb_config.c @@ -2068,7 +2068,7 @@ gNB_RRC_INST *RCconfig_NRRRC() // search if in active list - gNB_RrcConfigurationReq nrrrc_config = {0}; + nr_rrc_config_t nrrrc_config = {0}; for (k=0; k xids[xid] = RRC_SETUP; NR_SRB_ToAddModList_t *SRBs = createSRBlist(ue_p, false); - int size = do_RRCSetup(buf, sizeof(buf), xid, masterCellGroup, masterCellGroup_len, &rrc->configuration, SRBs); + int size = do_RRCSetup(buf, sizeof(buf), xid, masterCellGroup, masterCellGroup_len, SRBs); AssertFatal(size > 0, "do_RRCSetup failed\n"); AssertFatal(size <= 1024, "memory corruption\n"); diff --git a/openair2/RRC/NR/rrc_gNB_NGAP.c b/openair2/RRC/NR/rrc_gNB_NGAP.c index bf8a797a49..d9928e1abd 100644 --- a/openair2/RRC/NR/rrc_gNB_NGAP.c +++ b/openair2/RRC/NR/rrc_gNB_NGAP.c @@ -1590,7 +1590,7 @@ int rrc_gNB_process_PAGING_IND(MessageDef *msg_p, instance_t instance) p->mnc_digit_length, p->mnc, msg->tac[tai_size]); - gNB_RrcConfigurationReq *req = &RC.nrrrc[instance]->configuration; + nr_rrc_config_t *req = &RC.nrrrc[instance]->configuration; for (uint8_t j = 0; j < req->num_plmn; j++) { plmn_id_t *req_plmn = &req->plmn[j]; if (req_plmn->mcc == p->mcc && req_plmn->mnc == p->mnc && req->tac == msg->tac[tai_size]) { diff --git a/openair2/RRC/NR/rrc_gNB_du.c b/openair2/RRC/NR/rrc_gNB_du.c index 246df60626..b2aa18158f 100644 --- a/openair2/RRC/NR/rrc_gNB_du.c +++ b/openair2/RRC/NR/rrc_gNB_du.c @@ -98,7 +98,7 @@ RB_GENERATE/*_STATIC*/(rrc_du_tree, nr_rrc_du_container_t, entries, du_compare); static bool rrc_gNB_plmn_matches(const gNB_RRC_INST *rrc, const f1ap_served_cell_info_t *info) { - const gNB_RrcConfigurationReq *conf = &rrc->configuration; + const nr_rrc_config_t *conf = &rrc->configuration; return conf->num_plmn == 1 // F1 supports only one && conf->plmn[0].mcc == info->plmn.mcc && conf->plmn[0].mnc == info->plmn.mnc; } From 0464329eed8d52fca13f5f777685b30157dc2960 Mon Sep 17 00:00:00 2001 From: Robert Schmidt Date: Wed, 11 Feb 2026 08:40:44 +0100 Subject: [PATCH 099/100] Make 5G RRC not depend on LTE code --- openair2/RRC/NR/MESSAGES/asn1_msg.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/openair2/RRC/NR/MESSAGES/asn1_msg.h b/openair2/RRC/NR/MESSAGES/asn1_msg.h index e9257504cb..d6783211a6 100644 --- a/openair2/RRC/NR/MESSAGES/asn1_msg.h +++ b/openair2/RRC/NR/MESSAGES/asn1_msg.h @@ -32,6 +32,7 @@ #define __RRC_NR_MESSAGES_ASN1_MSG__H__ #include +#include "common/platform_constants.h" #include #include #include "NR_ARFCN-ValueNR.h" @@ -52,10 +53,10 @@ #include "NR_SecurityConfig.h" #include "NR_MeasurementReport.h" #include "NR_MeasurementTimingConfiguration.h" +#include "NR_UE-NR-Capability.h" #include "NR_UE-CapabilityRAT-ContainerList.h" #include "ds/seq_arr.h" #include "ds/byte_array.h" -#include "rrc_messages_types.h" #include "openair2/LAYER2/nr_pdcp/nr_pdcp_configuration.h" #include "common/utils/nr/nr_common.h" struct asn_TYPE_descriptor_s; From 144f0de222c09601ddb414ce02cfaa58b89c1599 Mon Sep 17 00:00:00 2001 From: Bartosz Podrygajlo Date: Thu, 12 Feb 2026 11:39:28 +0100 Subject: [PATCH 100/100] Fix and update tracy tag to v0.13.1 --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index bae1c590bc..19a2e328a4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -297,7 +297,7 @@ add_boolean_option(TRACY_ENABLE OFF "Enable tracy instrumentation" ON) if (TRACY_ENABLE) # the tracy version here should match the tracy server version # below is latest release as of this commit - CPMAddPackage("gh:wolfpld/tracy#0.12.2") + CPMAddPackage("gh:wolfpld/tracy#v0.13.1") endif() set (OCP_ITTI ${OPENAIR_DIR}/common/utils/ocp_itti)