From a59cfae18ca94ec853093de49cb76c2726b8f2b6 Mon Sep 17 00:00:00 2001 From: ndomingues Date: Wed, 2 Apr 2025 16:00:45 +0100 Subject: [PATCH 01/63] Prevent RedCap UEs to move to dedicated BWP --- openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_primitives.c | 2 +- openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_ulsch.c | 1 + openair2/LAYER2/NR_MAC_gNB/nr_mac_gNB.h | 1 + 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_primitives.c b/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_primitives.c index 3c7c9ebd39..7ad2ac5bb4 100644 --- a/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_primitives.c +++ b/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_primitives.c @@ -3322,7 +3322,7 @@ bool prepare_initial_ul_rrc_message(gNB_MAC_INST *mac, NR_UE_info_t *UE) int CC_id = 0; int srb_id = 1; const NR_ServingCellConfigCommon_t *scc = mac->common_channels[CC_id].ServingCellConfigCommon; - const NR_ServingCellConfig_t *sccd = mac->common_channels[CC_id].pre_ServingCellConfig; + const NR_ServingCellConfig_t *sccd = UE->is_redcap ? NULL : mac->common_channels[CC_id].pre_ServingCellConfig; NR_CellGroupConfig_t *cellGroupConfig = get_initial_cellGroupConfig(UE->uid, scc, sccd, &mac->radio_config); ASN_STRUCT_FREE(asn_DEF_NR_CellGroupConfig, UE->CellGroup); UE->CellGroup = cellGroupConfig; diff --git a/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_ulsch.c b/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_ulsch.c index e1a48d6002..edbb467b19 100644 --- a/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_ulsch.c +++ b/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_ulsch.c @@ -351,6 +351,7 @@ static int nr_process_mac_pdu(instance_t module_idP, if (lcid == UL_SCH_LCID_CCCH_48_BITS_REDCAP) { LOG_I(MAC, "UE with RNTI %04x is RedCap\n", UE->rnti); + UE->is_redcap = true; } if (prepare_initial_ul_rrc_message(RC.nrmac[module_idP], UE)) { diff --git a/openair2/LAYER2/NR_MAC_gNB/nr_mac_gNB.h b/openair2/LAYER2/NR_MAC_gNB/nr_mac_gNB.h index 65812ae0f5..628a23f770 100644 --- a/openair2/LAYER2/NR_MAC_gNB/nr_mac_gNB.h +++ b/openair2/LAYER2/NR_MAC_gNB/nr_mac_gNB.h @@ -783,6 +783,7 @@ typedef struct { float ul_thr_ue; float dl_thr_ue; long pdsch_HARQ_ACK_Codebook; + bool is_redcap; } NR_UE_info_t; typedef struct { From f24a576d8147b3f48265fa19528d10a491141db8 Mon Sep 17 00:00:00 2001 From: ndomingues Date: Thu, 3 Apr 2025 09:17:28 +0100 Subject: [PATCH 02/63] Update RedCap configuration file to have 20 MHz InitialBWP and 40 MHz DedicatedBWP --- ...nb.sa.band78.fr1.106PRB.usrpb210.redcap.yaml} | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) rename targets/PROJECTS/GENERIC-NR-5GC/CONF/{gnb.sa.band78.fr1.51PRB.usrpb210.redcap.yaml => gnb.sa.band78.fr1.106PRB.usrpb210.redcap.yaml} (93%) diff --git a/targets/PROJECTS/GENERIC-NR-5GC/CONF/gnb.sa.band78.fr1.51PRB.usrpb210.redcap.yaml b/targets/PROJECTS/GENERIC-NR-5GC/CONF/gnb.sa.band78.fr1.106PRB.usrpb210.redcap.yaml similarity index 93% rename from targets/PROJECTS/GENERIC-NR-5GC/CONF/gnb.sa.band78.fr1.51PRB.usrpb210.redcap.yaml rename to targets/PROJECTS/GENERIC-NR-5GC/CONF/gnb.sa.band78.fr1.106PRB.usrpb210.redcap.yaml index b3f43bfbd7..01c40ca3d7 100644 --- a/targets/PROJECTS/GENERIC-NR-5GC/CONF/gnb.sa.band78.fr1.51PRB.usrpb210.redcap.yaml +++ b/targets/PROJECTS/GENERIC-NR-5GC/CONF/gnb.sa.band78.fr1.106PRB.usrpb210.redcap.yaml @@ -35,7 +35,7 @@ gNBs: # subcarrierSpacing # 0=kHz15, 1=kHz30, 2=kHz60, 3=kHz120 dl_subcarrierSpacing: 1 - dl_carrierBandwidth: 51 + dl_carrierBandwidth: 106 #initialDownlinkBWP #genericParameters # this is RBstart=0,L=51 (275*(L-1))+RBstart @@ -54,7 +54,7 @@ gNBs: # subcarrierSpacing # 0=kHz15, 1=kHz30, 2=kHz60, 3=kHz120 ul_subcarrierSpacing: 1 - ul_carrierBandwidth: 51 + ul_carrierBandwidth: 106 pMax: 20 #initialUplinkBWP #genericParameters @@ -134,6 +134,18 @@ gNBs: nrofUplinkSymbols: 4 ssPBCH_BlockPower: -25 + + servingCellConfigDedicated: + - dl_bwp-Id_1: 1 + dl_bwp1_locationAndBandwidth: 28875 + dl_bwp1_subcarrierSpacing: 1 + firstActiveDownlinkBWP-Id: 1 + defaultDownlinkBWP-Id: 1 + ul_bwp-Id_1: 1 + ul_bwp1_locationAndBandwidth: 28875 + ul_bwp1_subcarrierSpacing: 1 + firstActiveUplinkBWP-Id: 1 + RedCap: cellBarredRedCap1Rx_r17: 1 cellBarredRedCap2Rx_r17: 1 From 6504c038b2f8c2a76cd06b2490b6f03d54f27ea4 Mon Sep 17 00:00:00 2001 From: Cedric Roux Date: Tue, 8 Apr 2025 09:40:07 +0200 Subject: [PATCH 03/63] nr pdcp: 'count' is 32 bits, use explicit type instead of generic 'int' --- openair2/LAYER2/nr_pdcp/nr_pdcp_entity.h | 4 ++-- openair2/LAYER2/nr_pdcp/nr_pdcp_integrity_nia1.c | 2 +- openair2/LAYER2/nr_pdcp/nr_pdcp_integrity_nia1.h | 2 +- openair2/LAYER2/nr_pdcp/nr_pdcp_integrity_nia2.c | 3 +-- openair2/LAYER2/nr_pdcp/nr_pdcp_integrity_nia2.h | 2 +- openair2/LAYER2/nr_pdcp/nr_pdcp_security_nea1.c | 2 +- openair2/LAYER2/nr_pdcp/nr_pdcp_security_nea1.h | 2 +- openair2/LAYER2/nr_pdcp/nr_pdcp_security_nea2.c | 3 +-- openair2/LAYER2/nr_pdcp/nr_pdcp_security_nea2.h | 2 +- 9 files changed, 10 insertions(+), 12 deletions(-) diff --git a/openair2/LAYER2/nr_pdcp/nr_pdcp_entity.h b/openair2/LAYER2/nr_pdcp/nr_pdcp_entity.h index a5ef870749..b5de4c2b44 100644 --- a/openair2/LAYER2/nr_pdcp/nr_pdcp_entity.h +++ b/openair2/LAYER2/nr_pdcp/nr_pdcp_entity.h @@ -155,13 +155,13 @@ typedef struct nr_pdcp_entity_t { stream_security_context_t *security_context; void (*cipher)(stream_security_context_t *security_context, unsigned char *buffer, int length, - int bearer, int count, int direction); + int bearer, uint32_t count, int direction); void (*free_security)(stream_security_context_t *security_context); stream_security_context_t *integrity_context; void (*integrity)(stream_security_context_t *integrity_context, unsigned char *out, unsigned char *buffer, int length, - int bearer, int count, int direction); + int bearer, uint32_t count, int direction); void (*free_integrity)(stream_security_context_t *integrity_context); /* security/integrity algorithms need to know uplink/downlink information * which is reverse for gnb and ue, so we need to know if this diff --git a/openair2/LAYER2/nr_pdcp/nr_pdcp_integrity_nia1.c b/openair2/LAYER2/nr_pdcp/nr_pdcp_integrity_nia1.c index d21c98610e..cbcad2b401 100644 --- a/openair2/LAYER2/nr_pdcp/nr_pdcp_integrity_nia1.c +++ b/openair2/LAYER2/nr_pdcp/nr_pdcp_integrity_nia1.c @@ -42,7 +42,7 @@ stream_security_context_t *nr_pdcp_integrity_nia1_init(unsigned char *integrity_ void nr_pdcp_integrity_nia1_integrity(stream_security_context_t *integrity_context, unsigned char *out, unsigned char *buffer, int length, - int bearer, int count, int direction) + int bearer, uint32_t count, int direction) { nas_stream_cipher_t *ctx = (nas_stream_cipher_t *)integrity_context; diff --git a/openair2/LAYER2/nr_pdcp/nr_pdcp_integrity_nia1.h b/openair2/LAYER2/nr_pdcp/nr_pdcp_integrity_nia1.h index 89f6d9156a..da6b7a835e 100644 --- a/openair2/LAYER2/nr_pdcp/nr_pdcp_integrity_nia1.h +++ b/openair2/LAYER2/nr_pdcp/nr_pdcp_integrity_nia1.h @@ -29,7 +29,7 @@ stream_security_context_t *nr_pdcp_integrity_nia1_init(unsigned char *integrity_ void nr_pdcp_integrity_nia1_integrity(stream_security_context_t *integrity_context, unsigned char *out, unsigned char *buffer, int length, - int bearer, int count, int direction); + int bearer, uint32_t count, int direction); void nr_pdcp_integrity_nia1_free_integrity(stream_security_context_t *integrity_context); diff --git a/openair2/LAYER2/nr_pdcp/nr_pdcp_integrity_nia2.c b/openair2/LAYER2/nr_pdcp/nr_pdcp_integrity_nia2.c index f6db1b10a6..f74353e8b9 100644 --- a/openair2/LAYER2/nr_pdcp/nr_pdcp_integrity_nia2.c +++ b/openair2/LAYER2/nr_pdcp/nr_pdcp_integrity_nia2.c @@ -39,14 +39,13 @@ stream_security_context_t *nr_pdcp_integrity_nia2_init(uint8_t integrity_key[16] return (stream_security_context_t *)ctx; } -void nr_pdcp_integrity_nia2_integrity(stream_security_context_t *integrity_context, unsigned char *out, unsigned char *buffer, int length, int bearer, int count, int direction) +void nr_pdcp_integrity_nia2_integrity(stream_security_context_t *integrity_context, unsigned char *out, unsigned char *buffer, int length, int bearer, uint32_t count, int direction) { DevAssert(integrity_context != NULL); DevAssert(out != NULL); DevAssert(buffer != NULL); DevAssert(length > -1); DevAssert(bearer > 0 && bearer < 33); - DevAssert(count > -1); cbc_cmac_ctx_t *ctx = (cbc_cmac_ctx_t *)integrity_context; diff --git a/openair2/LAYER2/nr_pdcp/nr_pdcp_integrity_nia2.h b/openair2/LAYER2/nr_pdcp/nr_pdcp_integrity_nia2.h index c9bd6cd881..7483b2c4b0 100644 --- a/openair2/LAYER2/nr_pdcp/nr_pdcp_integrity_nia2.h +++ b/openair2/LAYER2/nr_pdcp/nr_pdcp_integrity_nia2.h @@ -31,7 +31,7 @@ stream_security_context_t *nr_pdcp_integrity_nia2_init(uint8_t integrity_key[16] void nr_pdcp_integrity_nia2_integrity(stream_security_context_t *integrity_context, unsigned char *out, unsigned char *buffer, int length, - int bearer, int count, int direction); + int bearer, uint32_t count, int direction); void nr_pdcp_integrity_nia2_free_integrity(stream_security_context_t *integrity_context); diff --git a/openair2/LAYER2/nr_pdcp/nr_pdcp_security_nea1.c b/openair2/LAYER2/nr_pdcp/nr_pdcp_security_nea1.c index 1cb29b3a81..d6df244e0c 100644 --- a/openair2/LAYER2/nr_pdcp/nr_pdcp_security_nea1.c +++ b/openair2/LAYER2/nr_pdcp/nr_pdcp_security_nea1.c @@ -43,7 +43,7 @@ void nr_pdcp_security_nea1_cipher(stream_security_context_t *security_context, unsigned char *buffer, int length, int bearer, - int count, + uint32_t count, int direction) { nas_stream_cipher_t *ctx = (nas_stream_cipher_t *)security_context; diff --git a/openair2/LAYER2/nr_pdcp/nr_pdcp_security_nea1.h b/openair2/LAYER2/nr_pdcp/nr_pdcp_security_nea1.h index 1338739298..508b4430f6 100644 --- a/openair2/LAYER2/nr_pdcp/nr_pdcp_security_nea1.h +++ b/openair2/LAYER2/nr_pdcp/nr_pdcp_security_nea1.h @@ -30,7 +30,7 @@ void nr_pdcp_security_nea1_cipher(stream_security_context_t *security_context, unsigned char *buffer, int length, int bearer, - int count, + uint32_t count, int direction); void nr_pdcp_security_nea1_free_security(stream_security_context_t *security_context); diff --git a/openair2/LAYER2/nr_pdcp/nr_pdcp_security_nea2.c b/openair2/LAYER2/nr_pdcp/nr_pdcp_security_nea2.c index 617d5c3372..27027d5324 100644 --- a/openair2/LAYER2/nr_pdcp/nr_pdcp_security_nea2.c +++ b/openair2/LAYER2/nr_pdcp/nr_pdcp_security_nea2.c @@ -36,14 +36,13 @@ stream_security_context_t *nr_pdcp_security_nea2_init(unsigned char *ciphering_k return (stream_security_context_t *)ciphering_key; } -void nr_pdcp_security_nea2_cipher(stream_security_context_t *security_context, unsigned char *buffer, int length, int bearer, int count, int direction) +void nr_pdcp_security_nea2_cipher(stream_security_context_t *security_context, unsigned char *buffer, int length, int bearer, uint32_t count, int direction) { DevAssert(security_context != NULL); DevAssert(buffer != NULL); DevAssert(length > 0); DevAssert(bearer > -1 && bearer < 32); DevAssert(direction > -1 && direction < 2); - DevAssert(count > -1); aes_128_t p = {0}; const uint8_t *ciphering_key = (uint8_t const *)security_context; diff --git a/openair2/LAYER2/nr_pdcp/nr_pdcp_security_nea2.h b/openair2/LAYER2/nr_pdcp/nr_pdcp_security_nea2.h index 4550e7290d..cec7bc3bf1 100644 --- a/openair2/LAYER2/nr_pdcp/nr_pdcp_security_nea2.h +++ b/openair2/LAYER2/nr_pdcp/nr_pdcp_security_nea2.h @@ -28,7 +28,7 @@ stream_security_context_t *nr_pdcp_security_nea2_init(unsigned char *ciphering_k void nr_pdcp_security_nea2_cipher(stream_security_context_t *security_context, unsigned char *buffer, int length, - int bearer, int count, int direction); + int bearer, uint32_t count, int direction); void nr_pdcp_security_nea2_free_security(stream_security_context_t *security_context); From 0f9220b5ef958b79c9d3ad762749266a7351c7e8 Mon Sep 17 00:00:00 2001 From: Justin Garey Date: Tue, 8 Apr 2025 15:30:52 +0000 Subject: [PATCH 04/63] Fixed typo in gNB_schedyler_uci.c --- openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_uci.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_uci.c b/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_uci.c index 7cad2d883d..2b5cc97ffe 100644 --- a/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_uci.c +++ b/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_uci.c @@ -100,7 +100,7 @@ static void nr_fill_nfapi_pucch(gNB_MAC_INST *nrmac, frame_t frame, slot_t slot, #define MIN_RSRP_VALUE -141 #define MAX_RSRP_VALUE -43 -//Measured RSRP Values Table 10.1.16.1-1 from 36.133 +//Measured RSRP Values Table 10.1.16.1-1 from 38.133 //Stored all the upper limits[Max RSRP Value of corresponding index] //stored -1 for invalid values static const int L1_SSB_CSI_RSRP_measReport_mapping_38133_10_1_6_1_1[128] = { @@ -119,7 +119,7 @@ static const int L1_SSB_CSI_RSRP_measReport_mapping_38133_10_1_6_1_1[128] = { -1, -1, -1, -1, -1, -1, -1, -1 // 120 - 127 }; -//Differential RSRP values Table 10.1.6.1-2 from 36.133 +//Differential RSRP values Table 10.1.6.1-2 from 38.133 //Stored the upper limits[MAX RSRP Value] static const int diff_rsrp_ssb_csi_meas_10_1_6_1_2[16] = { 0, From bc27d1e6912f7b710e3c310f00c8d87cfea11d4d Mon Sep 17 00:00:00 2001 From: francescomani Date: Wed, 18 Dec 2024 15:57:55 +0100 Subject: [PATCH 05/63] improvements in SIB2 message (info taken from commercial gNB) --- openair2/RRC/NR/MESSAGES/asn1_msg.c | 79 ++++++++++++++++++----------- 1 file changed, 49 insertions(+), 30 deletions(-) diff --git a/openair2/RRC/NR/MESSAGES/asn1_msg.c b/openair2/RRC/NR/MESSAGES/asn1_msg.c index beb8de2e3a..d0224300e2 100644 --- a/openair2/RRC/NR/MESSAGES/asn1_msg.c +++ b/openair2/RRC/NR/MESSAGES/asn1_msg.c @@ -199,49 +199,68 @@ int xer_nr_sprint(char *string, size_t string_size, asn_TYPE_descriptor_t *td, v int do_SIB23_NR(rrc_gNB_carrier_data_t *carrier) { - asn_enc_rval_t enc_rval; - SystemInformation_IEs__sib_TypeAndInfo__Member *sib2 = NULL; - SystemInformation_IEs__sib_TypeAndInfo__Member *sib3 = NULL; - - NR_BCCH_DL_SCH_Message_t *sib_message = CALLOC(1,sizeof(NR_BCCH_DL_SCH_Message_t)); + NR_BCCH_DL_SCH_Message_t *sib_message = calloc(1,sizeof(NR_BCCH_DL_SCH_Message_t)); sib_message->message.present = NR_BCCH_DL_SCH_MessageType_PR_c1; - sib_message->message.choice.c1 = CALLOC(1,sizeof(struct NR_BCCH_DL_SCH_MessageType__c1)); + sib_message->message.choice.c1 = calloc(1,sizeof(struct NR_BCCH_DL_SCH_MessageType__c1)); sib_message->message.choice.c1->present = NR_BCCH_DL_SCH_MessageType__c1_PR_systemInformation; - sib_message->message.choice.c1->choice.systemInformation = CALLOC(1,sizeof(struct NR_SystemInformation)); + sib_message->message.choice.c1->choice.systemInformation = calloc(1,sizeof(struct NR_SystemInformation)); struct NR_SystemInformation *sib = sib_message->message.choice.c1->choice.systemInformation; sib->criticalExtensions.present = NR_SystemInformation__criticalExtensions_PR_systemInformation; - sib->criticalExtensions.choice.systemInformation = CALLOC(1, sizeof(struct NR_SystemInformation_IEs)); + sib->criticalExtensions.choice.systemInformation = calloc(1, sizeof(struct NR_SystemInformation_IEs)); struct NR_SystemInformation_IEs *ies = sib->criticalExtensions.choice.systemInformation; - sib2 = CALLOC(1, sizeof(SystemInformation_IEs__sib_TypeAndInfo__Member)); - sib2->present = NR_SystemInformation_IEs__sib_TypeAndInfo__Member_PR_sib2; - sib2->choice.sib2 = CALLOC(1, sizeof(struct NR_SIB2)); - sib2->choice.sib2->cellReselectionInfoCommon.q_Hyst = NR_SIB2__cellReselectionInfoCommon__q_Hyst_dB1; - sib2->choice.sib2->cellReselectionServingFreqInfo.threshServingLowP = 2; // INTEGER (0..31) - sib2->choice.sib2->cellReselectionServingFreqInfo.cellReselectionPriority = 2; // INTEGER (0..7) - sib2->choice.sib2->intraFreqCellReselectionInfo.q_RxLevMin = -50; // INTEGER (-70..-22) - sib2->choice.sib2->intraFreqCellReselectionInfo.s_IntraSearchP = 2; // INTEGER (0..31) - sib2->choice.sib2->intraFreqCellReselectionInfo.t_ReselectionNR = 2; // INTEGER (0..7) - sib2->choice.sib2->intraFreqCellReselectionInfo.deriveSSB_IndexFromCell = true; + SystemInformation_IEs__sib_TypeAndInfo__Member *sib2_ie = calloc(1, sizeof(*sib2_ie)); + sib2_ie->present = NR_SystemInformation_IEs__sib_TypeAndInfo__Member_PR_sib2; + NR_SIB2_t *sib2 = calloc(1, sizeof(*sib2)); + sib2->cellReselectionInfoCommon.q_Hyst = NR_SIB2__cellReselectionInfoCommon__q_Hyst_dB0; + struct NR_SIB2__cellReselectionInfoCommon__speedStateReselectionPars *speed = calloc(1, sizeof(*speed)); + NR_MobilityStateParameters_t mobilityStateParameters = {0}; + mobilityStateParameters.t_Evaluation = NR_MobilityStateParameters__t_Evaluation_s30; + mobilityStateParameters.t_HystNormal = NR_MobilityStateParameters__t_HystNormal_s30; + mobilityStateParameters.n_CellChangeMedium = 1; + mobilityStateParameters.n_CellChangeHigh = 2; + speed->mobilityStateParameters = mobilityStateParameters; + struct NR_SIB2__cellReselectionInfoCommon__speedStateReselectionPars__q_HystSF qhyst = {0}; + qhyst.sf_Medium = NR_SIB2__cellReselectionInfoCommon__speedStateReselectionPars__q_HystSF__sf_Medium_dB_4; + qhyst.sf_High = NR_SIB2__cellReselectionInfoCommon__speedStateReselectionPars__q_HystSF__sf_High_dB_6; + speed->q_HystSF = qhyst; + sib2->cellReselectionInfoCommon.speedStateReselectionPars = speed; + sib2->cellReselectionServingFreqInfo.cellReselectionPriority = 0; // INTEGER (0..7) + sib2->cellReselectionServingFreqInfo.threshServingLowP = 0; + NR_ReselectionThresholdQ_t *threshServingLowQ = calloc(1, sizeof(*threshServingLowQ)); + *threshServingLowQ = 0; + sib2->cellReselectionServingFreqInfo.threshServingLowQ = threshServingLowQ; + sib2->intraFreqCellReselectionInfo.q_RxLevMin = -56; // INTEGER (-70..-22) + sib2->intraFreqCellReselectionInfo.s_IntraSearchP = 22; // INTEGER (0..31) + sib2->intraFreqCellReselectionInfo.t_ReselectionNR = 1; // INTEGER (0..7) + sib2->intraFreqCellReselectionInfo.deriveSSB_IndexFromCell = true; + NR_SSB_MTC_t *ssbmtc = calloc(1, sizeof(*ssbmtc)); + ssbmtc->duration = NR_SSB_MTC__duration_sf5; + ssbmtc->periodicityAndOffset.present = NR_SSB_MTC__periodicityAndOffset_PR_sf20; + ssbmtc->periodicityAndOffset.choice.sf20 = 0; + sib2->intraFreqCellReselectionInfo.smtc = ssbmtc; + sib2_ie->choice.sib2 = sib2; asn1cSeqAdd(&ies->sib_TypeAndInfo.list, sib2); - sib3 = CALLOC(1, sizeof(SystemInformation_IEs__sib_TypeAndInfo__Member)); - sib3->present = NR_SystemInformation_IEs__sib_TypeAndInfo__Member_PR_sib3; - sib3->choice.sib3 = CALLOC(1, sizeof(struct NR_SIB3)); - asn1cSeqAdd(&ies->sib_TypeAndInfo.list, sib3); + SystemInformation_IEs__sib_TypeAndInfo__Member *sib3_ie = calloc(1, sizeof(*sib3_ie)); + sib3_ie->present = NR_SystemInformation_IEs__sib_TypeAndInfo__Member_PR_sib3; + sib3_ie->choice.sib3 = calloc(1, sizeof(*sib3_ie->choice.sib3)); + asn1cSeqAdd(&ies->sib_TypeAndInfo.list, sib3_ie); //encode SIB to data // carrier->SIB23 = (uint8_t *) malloc16(128); - enc_rval = uper_encode_to_buffer(&asn_DEF_NR_BCCH_DL_SCH_Message, - NULL, - (void *)sib_message, - carrier->SIB23, - 100); - AssertFatal (enc_rval.encoded > 0, "ASN1 message encoding failed (%s, %lu)!\n", - enc_rval.failed_type->name, enc_rval.encoded); + asn_enc_rval_t enc_rval = uper_encode_to_buffer(&asn_DEF_NR_BCCH_DL_SCH_Message, + NULL, + (void *)sib_message, + carrier->SIB23, + 100); + AssertFatal (enc_rval.encoded > 0, + "ASN1 message encoding failed (%s, %lu)!\n", + enc_rval.failed_type->name, + enc_rval.encoded); ASN_STRUCT_FREE(asn_DEF_NR_BCCH_DL_SCH_Message, sib_message); - return((enc_rval.encoded+7)/8); + return((enc_rval.encoded + 7) / 8); } int do_RRCReject(uint8_t *const buffer) From 8e253e3435536623be02eb026d93db02258340e3 Mon Sep 17 00:00:00 2001 From: Reem Bahsoun Date: Fri, 28 Mar 2025 13:00:41 +0100 Subject: [PATCH 06/63] Modify XML and gnb.conf - Update the N2 and N3 IP addresses to match the Grace Hopper's in the gnb.conf. - Remove the test XML file for pulling images in the ARM image builder. - Update the Aerial XML file to pull ARM images. - Decrease tested throughput (temporarily) due to problems with Foxconn RU --- .../gnb-vnf.sa.band78.273prb.aerial.conf | 4 +- .../xml_files/container_sa_aerial_quectel.xml | 9 ++-- .../container_sa_gh_aerial_quectel.xml | 49 ------------------- 3 files changed, 7 insertions(+), 55 deletions(-) delete mode 100644 ci-scripts/xml_files/container_sa_gh_aerial_quectel.xml diff --git a/ci-scripts/conf_files/gnb-vnf.sa.band78.273prb.aerial.conf b/ci-scripts/conf_files/gnb-vnf.sa.band78.273prb.aerial.conf index 2d2120b038..be2e0404e3 100644 --- a/ci-scripts/conf_files/gnb-vnf.sa.band78.273prb.aerial.conf +++ b/ci-scripts/conf_files/gnb-vnf.sa.band78.273prb.aerial.conf @@ -186,8 +186,8 @@ gNBs = NETWORK_INTERFACES : { - GNB_IPV4_ADDRESS_FOR_NG_AMF = "172.21.16.131"; - GNB_IPV4_ADDRESS_FOR_NGU = "172.21.16.131"; + GNB_IPV4_ADDRESS_FOR_NG_AMF = "172.21.16.202"; + GNB_IPV4_ADDRESS_FOR_NGU = "172.21.16.202"; GNB_PORT_FOR_S1U = 2152; # Spec 2152 }; diff --git a/ci-scripts/xml_files/container_sa_aerial_quectel.xml b/ci-scripts/xml_files/container_sa_aerial_quectel.xml index 07c0c095a2..6a3e7f8057 100644 --- a/ci-scripts/xml_files/container_sa_aerial_quectel.xml +++ b/ci-scripts/xml_files/container_sa_aerial_quectel.xml @@ -52,6 +52,7 @@ Pull Images from Local Registry 0 oai-gnb-aerial + arm_ @@ -96,8 +97,8 @@ Iperf - iperf (DL/UDP/600M)(30 sec)(single-ue profile) - -u -b 600M -t 30 -R + iperf (DL/UDP/500M)(30 sec)(single-ue profile) + -u -b 500M -t 30 -R oc-cn5g-20897-aerial2 up2-aerial 25 @@ -106,8 +107,8 @@ Iperf - iperf (UL/UDP/50M)(30 sec)(single-ue profile) - -u -b 50M -t 30 + iperf (UL/UDP/40M)(30 sec)(single-ue profile) + -u -b 40M -t 30 oc-cn5g-20897-aerial2 up2-aerial 25 diff --git a/ci-scripts/xml_files/container_sa_gh_aerial_quectel.xml b/ci-scripts/xml_files/container_sa_gh_aerial_quectel.xml deleted file mode 100644 index ea60cb2b6f..0000000000 --- a/ci-scripts/xml_files/container_sa_gh_aerial_quectel.xml +++ /dev/null @@ -1,49 +0,0 @@ - - - TEST-GH-AERIAL-SA - Gracehopper AERIAL 100 MHz TDD SA - tasks - - 111111 - 333333 - - - - - Pull_Local_Registry - Pull Images from Local Registry - 0 - oai-gnb-aerial - arm_ - - - - Clean_Test_Server_Images - true - Clean Test Images on Test Server - 0 - oai-gnb-aerial - - - From 52f0bbc165a3d5a4b91bba6507dc997a5db14b7e Mon Sep 17 00:00:00 2001 From: Reem Bahsoun Date: Fri, 28 Mar 2025 13:02:30 +0100 Subject: [PATCH 07/63] Modify files in `ci-scripts/yaml_files/sa_gnb_aerial` - Update the L1 (cuBB) version in the docker-compose.yaml - Change the argument for the L1 config file (cuphycontroller.yaml) in the aerial_l1_entrypoint.sh --- ci-scripts/yaml_files/sa_gnb_aerial/aerial_l1_entrypoint.sh | 2 +- ci-scripts/yaml_files/sa_gnb_aerial/docker-compose.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ci-scripts/yaml_files/sa_gnb_aerial/aerial_l1_entrypoint.sh b/ci-scripts/yaml_files/sa_gnb_aerial/aerial_l1_entrypoint.sh index 857f815290..e0fea22190 100755 --- a/ci-scripts/yaml_files/sa_gnb_aerial/aerial_l1_entrypoint.sh +++ b/ci-scripts/yaml_files/sa_gnb_aerial/aerial_l1_entrypoint.sh @@ -24,7 +24,7 @@ sudo -E echo start_server -uid 0 | sudo -E nvidia-cuda-mps-control # Check if an argument is provided if [ $# -eq 0 ]; then # No argument provided, use default value - argument="P5G_FXN" + argument="P5G_FXN_GH" else # Argument provided, use it argument="$1" diff --git a/ci-scripts/yaml_files/sa_gnb_aerial/docker-compose.yaml b/ci-scripts/yaml_files/sa_gnb_aerial/docker-compose.yaml index 1bc99ff4ba..d5677ac239 100644 --- a/ci-scripts/yaml_files/sa_gnb_aerial/docker-compose.yaml +++ b/ci-scripts/yaml_files/sa_gnb_aerial/docker-compose.yaml @@ -23,7 +23,7 @@ services: - ../../../cmake_targets/share:/opt/cuBB/share userns_mode: host ipc: "shareable" - image: cubb-build:24-1 + image: cubb-build:24-3 environment: - cuBB_SDK=/opt/nvidia/cuBB command: bash -c "sudo rm -rf /tmp/phy.log && sudo chmod +x /opt/nvidia/cuBB/aerial_l1_entrypoint.sh && /opt/nvidia/cuBB/aerial_l1_entrypoint.sh" From 683670ed912e998524efc194db63be6f2302d97f Mon Sep 17 00:00:00 2001 From: alexjiao Date: Wed, 9 Apr 2025 20:06:41 +0800 Subject: [PATCH 08/63] Fix PRACH generation with frequency resource id not 0 when msg1-FDM > 1 in SIB1 --- openair1/PHY/NR_UE_TRANSPORT/nr_prach.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openair1/PHY/NR_UE_TRANSPORT/nr_prach.c b/openair1/PHY/NR_UE_TRANSPORT/nr_prach.c index c2953dd196..f0ee943817 100644 --- a/openair1/PHY/NR_UE_TRANSPORT/nr_prach.c +++ b/openair1/PHY/NR_UE_TRANSPORT/nr_prach.c @@ -68,7 +68,7 @@ int32_t generate_nr_prach(PHY_VARS_NR_UE *ue, uint8_t gNB_id, int frame, uint8_t int Ncp = 0; int prach_start, prach_sequence_length, prach_len, dftlen, mu, n_ra_prb, k, prachStartSymbol, sample_offset_slot; - fd_occasion = 0; + fd_occasion = prach_pdu->num_ra; prach_len = 0; dftlen = 0; first_nonzero_root_idx = 0; From 05d393a399262c61462ff27a5e0a85e611232934 Mon Sep 17 00:00:00 2001 From: Robert Schmidt Date: Tue, 8 Apr 2025 18:56:41 +0200 Subject: [PATCH 09/63] Encapsulate cmake asn1c call, suppress useless output Encapsulate the asn1c call from cmake in a cmake function run_asn1c(). As a notable change to the previous manual add_custom_command() invocation, in suppresses the output of asn1c by default [1] and stores it in a log file. In case of asn1c4 error, the output will output on stdout. Use the new run_asn1c() in all occasions for code generation. [1] it is quite verbose, with many warnings that we cannot influence nor suppress, and lists all generated files which is verified by the build system automatically, anyway. --- cmake_targets/macros.cmake | 15 +++++++++++++++ openair2/E1AP/MESSAGES/CMakeLists.txt | 9 +++++---- openair2/F1AP/MESSAGES/CMakeLists.txt | 9 +++++---- openair2/M2AP/MESSAGES/CMakeLists.txt | 9 +++++---- openair2/RRC/LTE/MESSAGES/CMakeLists.txt | 9 +++++---- openair2/RRC/NR/MESSAGES/CMakeLists.txt | 9 +++++---- openair2/X2AP/MESSAGES/CMakeLists.txt | 9 +++++---- openair2/XNAP/MESSAGES/CMakeLists.txt | 11 ++++++----- openair3/LPP/MESSAGES/CMakeLists.txt | 9 +++++---- openair3/M3AP/MESSAGES/CMakeLists.txt | 9 +++++---- openair3/NGAP/MESSAGES/CMakeLists.txt | 9 +++++---- openair3/NRPPA/MESSAGES/CMakeLists.txt | 9 +++++---- openair3/S1AP/MESSAGES/CMakeLists.txt | 9 +++++---- 13 files changed, 76 insertions(+), 49 deletions(-) diff --git a/cmake_targets/macros.cmake b/cmake_targets/macros.cmake index 25ba5cb2b8..eafee3a0c1 100644 --- a/cmake_targets/macros.cmake +++ b/cmake_targets/macros.cmake @@ -107,3 +107,18 @@ or directly with ") endif() endfunction() + +function(run_asn1c ASN1C_GRAMMAR ASN1C_PREFIX) + set(options "") + set(oneValueArgs COMMENT) + set(multiValueArgs OUTPUT OPTIONS) + cmake_parse_arguments(ASN1C "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) + + get_filename_component(GRAMMAR_FILE ${ASN1C_GRAMMAR} NAME) + set(LOGFILE "${CMAKE_CURRENT_BINARY_DIR}/${GRAMMAR_FILE}.log") + add_custom_command(OUTPUT ${ASN1C_OUTPUT} + COMMAND ASN1C_PREFIX=${ASN1C_PREFIX} ${ASN1C_EXEC} ${ASN1C_OPTIONS} -D ${CMAKE_CURRENT_BINARY_DIR} ${ASN1C_GRAMMAR} > ${LOGFILE} 2>&1 || cat ${LOGFILE} + DEPENDS ${ASN1C_GRAMMAR} + COMMENT "Generating ${ASN1C_COMMENT} from ${GRAMMAR_FILE}" + ) +endfunction() diff --git a/openair2/E1AP/MESSAGES/CMakeLists.txt b/openair2/E1AP/MESSAGES/CMakeLists.txt index 0657740e93..5d79f8dc3d 100644 --- a/openair2/E1AP/MESSAGES/CMakeLists.txt +++ b/openair2/E1AP/MESSAGES/CMakeLists.txt @@ -9,10 +9,11 @@ else() message(FATAL_ERROR "unknown E1AP_RELEASE ${E1AP_RELEASE}") endif() -add_custom_command(OUTPUT ${e1ap_source} ${e1ap_headers} - COMMAND ASN1C_PREFIX=E1AP_ ${ASN1C_EXEC} -gen-APER -gen-UPER -no-gen-JER -no-gen-BER -no-gen-OER -fcompound-names -no-gen-example -findirect-choice -fno-include-deps -D ${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/${E1AP_GRAMMAR} - DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/${E1AP_GRAMMAR} - COMMENT "Generating E1AP source files from ${CMAKE_CURRENT_SOURCE_DIR}/${E1AP_GRAMMAR}" +run_asn1c(${CMAKE_CURRENT_SOURCE_DIR}/${E1AP_GRAMMAR} + "E1AP_" + OUTPUT ${e1ap_source} ${e1ap_headers} + OPTIONS -gen-APER -gen-UPER -no-gen-JER -no-gen-BER -no-gen-OER -fcompound-names -no-gen-example -findirect-choice -fno-include-deps + COMMENT "E1AP source files" ) add_library(asn1_e1ap ${e1ap_source}) diff --git a/openair2/F1AP/MESSAGES/CMakeLists.txt b/openair2/F1AP/MESSAGES/CMakeLists.txt index 2b17d09ddd..0274b3a6cc 100644 --- a/openair2/F1AP/MESSAGES/CMakeLists.txt +++ b/openair2/F1AP/MESSAGES/CMakeLists.txt @@ -8,10 +8,11 @@ else() message(FATAL_ERROR "unknown F1AP_RELEASE ${F1AP_RELEASE}") endif() -add_custom_command(OUTPUT ${f1ap_source} ${f1ap_headers} - COMMAND ASN1C_PREFIX=F1AP_ ${ASN1C_EXEC} -gen-APER -no-gen-BER -no-gen-JER -no-gen-OER -gen-UPER -fcompound-names -no-gen-example -findirect-choice -fno-include-deps -D ${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/${F1AP_GRAMMAR} - DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/${F1AP_GRAMMAR} - COMMENT "Generating F1AP source files from ${CMAKE_CURRENT_SOURCE_DIR}/${F1AP_GRAMMAR}" +run_asn1c(${CMAKE_CURRENT_SOURCE_DIR}/${F1AP_GRAMMAR} + "F1AP_" + OUTPUT ${f1ap_source} ${f1ap_headers} + OPTIONS -gen-APER -no-gen-BER -no-gen-JER -no-gen-OER -gen-UPER -fcompound-names -no-gen-example -findirect-choice -fno-include-deps + COMMENT "F1AP source files" ) add_library(asn1_f1ap ${f1ap_source}) diff --git a/openair2/M2AP/MESSAGES/CMakeLists.txt b/openair2/M2AP/MESSAGES/CMakeLists.txt index 2142655ac2..5a436a06e6 100644 --- a/openair2/M2AP/MESSAGES/CMakeLists.txt +++ b/openair2/M2AP/MESSAGES/CMakeLists.txt @@ -18,10 +18,11 @@ else() message(FATAL_ERROR "unknown M2AP_RELEASE ${M2AP_RELEASE}") endif() -add_custom_command(OUTPUT ${m2ap_source} ${m2ap_headers} - COMMAND ASN1C_PREFIX=M2AP_ ${ASN1C_EXEC} -gen-APER -no-gen-BER -no-gen-JER -no-gen-OER -gen-UPER -fcompound-names -no-gen-example -fno-include-deps -D ${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/${M2AP_GRAMMAR} - DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/${M2AP_GRAMMAR} - COMMENT "Generating M2AP source files from ${CMAKE_CURRENT_SOURCE_DIR}/${M2AP_GRAMMAR}" +run_asn1c(${CMAKE_CURRENT_SOURCE_DIR}/${M2AP_GRAMMAR} + "M2AP_" + OUTPUT ${m2ap_source} ${m2ap_headers} + OPTIONS -gen-APER -no-gen-BER -no-gen-JER -no-gen-OER -gen-UPER -fcompound-names -no-gen-example -fno-include-deps + COMMENT "M2AP source files" ) add_library(asn1_m2ap ${m2ap_source}) diff --git a/openair2/RRC/LTE/MESSAGES/CMakeLists.txt b/openair2/RRC/LTE/MESSAGES/CMakeLists.txt index 162463dc46..776983068a 100644 --- a/openair2/RRC/LTE/MESSAGES/CMakeLists.txt +++ b/openair2/RRC/LTE/MESSAGES/CMakeLists.txt @@ -10,10 +10,11 @@ else() message(FATAL_ERROR "unknown LTE_RRC_RELEASE ${LTE_RRC_RELEASE}") endif() -add_custom_command(OUTPUT ${lte_rrc_source} ${lte_rrc_headers} - COMMAND ASN1C_PREFIX=LTE_ ${ASN1C_EXEC} -pdu=all -fcompound-names -gen-UPER -no-gen-BER -no-gen-JER -no-gen-OER -gen-APER -no-gen-example -D ${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/${LTE_RRC_GRAMMAR} - DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/${LTE_RRC_GRAMMAR} - COMMENT "Generating LTE RRC source file from ${CMAKE_CURRENT_SOURCE_DIR}/${LTE_RRC_GRAMMAR}" +run_asn1c(${CMAKE_CURRENT_SOURCE_DIR}/${LTE_RRC_GRAMMAR} + "LTE_" + OUTPUT ${lte_rrc_source} ${lte_rrc_headers} + OPTIONS -pdu=all -fcompound-names -gen-UPER -no-gen-BER -no-gen-JER -no-gen-OER -gen-APER -no-gen-example + COMMENT "LTE RRC source files" ) # Many other targets include LTE RRC ASN.1 headers, without actually needing diff --git a/openair2/RRC/NR/MESSAGES/CMakeLists.txt b/openair2/RRC/NR/MESSAGES/CMakeLists.txt index f85c39bf73..8854801e1c 100644 --- a/openair2/RRC/NR/MESSAGES/CMakeLists.txt +++ b/openair2/RRC/NR/MESSAGES/CMakeLists.txt @@ -10,10 +10,11 @@ else() message(FATAL_ERROR "unknown NR_RRC_RELEASE ${NR_RRC_RELEASE}") endif() -add_custom_command(OUTPUT ${nr_rrc_source} ${nr_rrc_headers} - COMMAND ASN1C_PREFIX=NR_ ${ASN1C_EXEC} -pdu=all -fcompound-names -gen-UPER -no-gen-BER -no-gen-JER -no-gen-OER -gen-APER -no-gen-example -findirect-choice -D ${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/${NR_RRC_GRAMMAR} - DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/${NR_RRC_GRAMMAR} - COMMENT "Generating NR RRC source file from ${CMAKE_CURRENT_SOURCE_DIR}/${NR_RRC_GRAMMAR}" +run_asn1c(${CMAKE_CURRENT_SOURCE_DIR}/${NR_RRC_GRAMMAR} + "NR_" + OUTPUT ${nr_rrc_source} ${nr_rrc_headers} + OPTIONS -pdu=all -fcompound-names -gen-UPER -no-gen-BER -no-gen-JER -no-gen-OER -gen-APER -no-gen-example -findirect-choice + COMMENT "NR RRC source files" ) # Many other targets include NR RRC ASN.1 headers, without actually needing the diff --git a/openair2/X2AP/MESSAGES/CMakeLists.txt b/openair2/X2AP/MESSAGES/CMakeLists.txt index 679ab8aca2..755a13c7c1 100644 --- a/openair2/X2AP/MESSAGES/CMakeLists.txt +++ b/openair2/X2AP/MESSAGES/CMakeLists.txt @@ -9,10 +9,11 @@ else() message(FATAL_ERROR "unknown X2AP_RELEASE ${X2AP_RELEASE}") endif() -add_custom_command(OUTPUT ${x2ap_source} ${x2ap_headers} - COMMAND ASN1C_PREFIX=X2AP_ ${ASN1C_EXEC} -pdu=all -gen-APER -no-gen-BER -no-gen-JER -no-gen-OER -gen-UPER -fcompound-names -no-gen-example -fno-include-deps -D ${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/${X2AP_GRAMMAR} - DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/${X2AP_GRAMMAR} - COMMENT "Generating X2AP source files from ${CMAKE_CURRENT_SOURCE_DIR}/${X2AP_GRAMMAR}" +run_asn1c(${CMAKE_CURRENT_SOURCE_DIR}/${X2AP_GRAMMAR} + "X2AP_" + OUTPUT ${x2ap_source} ${x2ap_headers} + OPTIONS -pdu=all -gen-APER -no-gen-BER -no-gen-JER -no-gen-OER -gen-UPER -fcompound-names -no-gen-example -fno-include-deps + COMMENT "X2AP source files" ) add_library(asn1_x2ap ${x2ap_source}) diff --git a/openair2/XNAP/MESSAGES/CMakeLists.txt b/openair2/XNAP/MESSAGES/CMakeLists.txt index 078388e3c0..92528fc73f 100644 --- a/openair2/XNAP/MESSAGES/CMakeLists.txt +++ b/openair2/XNAP/MESSAGES/CMakeLists.txt @@ -9,11 +9,12 @@ else() message(FATAL_ERROR "unknown XNAP_RELEASE ${XNAP_RELEASE}") endif() -add_custom_command(OUTPUT ${xnap_source} ${xnap_headers} - COMMAND ASN1C_PREFIX=XNAP_ ${ASN1C_EXEC} -pdu=all -gen-APER -no-gen-BER -no-gen-JER -no-gen-OER -gen-UPER -fcompound-names -no-gen-example -fno-include-deps -findirect-choice -D ${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/${XNAP_GRAMMAR} - DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/${XNAP_GRAMMAR} - COMMENT "Generating XNAP source files from ${CMAKE_CURRENT_SOURCE_DIR}/${XNAP_GRAMMAR}" - ) +run_asn1c(${CMAKE_CURRENT_SOURCE_DIR}/${XNAP_GRAMMAR} + "XNAP_" + OUTPUT ${xnap_source} ${xnap_headers} + OPTIONS -pdu=all -gen-APER -no-gen-BER -no-gen-JER -no-gen-OER -gen-UPER -fcompound-names -no-gen-example -fno-include-deps -findirect-choice + COMMENT "XnAP source files" +) add_library(asn1_xnap ${xnap_source}) target_include_directories(asn1_xnap PUBLIC "${CMAKE_CURRENT_BINARY_DIR}") diff --git a/openair3/LPP/MESSAGES/CMakeLists.txt b/openair3/LPP/MESSAGES/CMakeLists.txt index 0ffdf87113..b340a6ad51 100644 --- a/openair3/LPP/MESSAGES/CMakeLists.txt +++ b/openair3/LPP/MESSAGES/CMakeLists.txt @@ -9,10 +9,11 @@ include(ASN1/37355-g60.cmake) # message(FATAL_ERROR "unknown LPP_RELEASE ${LPP_RELEASE}") #endif() -add_custom_command(OUTPUT ${lpp_source} ${lpp_headers} - COMMAND ASN1C_PREFIX=LPP_ ${ASN1C_EXEC} -gen-UPER -no-gen-BER -no-gen-JER -no-gen-OER -gen-APER -fcompound-names -no-gen-example -findirect-choice -fno-include-deps -D ${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/${LPP_GRAMMAR} - DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/${LPP_GRAMMAR} - COMMENT "Generating LPP source files from ${CMAKE_CURRENT_SOURCE_DIR}/${LPP_GRAMMAR}" +run_asn1c(${CMAKE_CURRENT_SOURCE_DIR}/${LPP_GRAMMAR} + "LPP_" + OUTPUT ${lpp_source} ${lpp_headers} + OPTIONS -gen-UPER -no-gen-BER -no-gen-JER -no-gen-OER -gen-APER -fcompound-names -no-gen-example -findirect-choice -fno-include-deps + COMMENT "LPP source files" ) add_library(asn1_lpp ${lpp_source}) diff --git a/openair3/M3AP/MESSAGES/CMakeLists.txt b/openair3/M3AP/MESSAGES/CMakeLists.txt index 8b5512919d..52007b629d 100644 --- a/openair3/M3AP/MESSAGES/CMakeLists.txt +++ b/openair3/M3AP/MESSAGES/CMakeLists.txt @@ -16,10 +16,11 @@ else() message(FATAL_ERROR "unknown M3AP_RELEASE ${M3AP_RELEASE}") endif() -add_custom_command(OUTPUT ${m3ap_source} ${m3ap_headers} - COMMAND ASN1C_PREFIX=M3AP_ ${ASN1C_EXEC} -gen-APER -no-gen-BER -no-gen-JER -no-gen-OER -gen-UPER -fcompound-names -no-gen-example -fno-include-deps -D ${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/${M3AP_GRAMMAR} - DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/${M3AP_GRAMMAR} - COMMENT "Generating M3AP source files from ${CMAKE_CURRENT_SOURCE_DIR}/${M3AP_GRAMMAR}" +run_asn1c(${CMAKE_CURRENT_SOURCE_DIR}/${M3AP_GRAMMAR} + "M3AP_" + OUTPUT ${m3ap_source} ${m3ap_headers} + OPTIONS -gen-APER -no-gen-BER -no-gen-JER -no-gen-OER -gen-UPER -fcompound-names -no-gen-example -fno-include-deps + COMMENT "M3AP source files" ) add_library(asn1_m3ap ${m3ap_source}) diff --git a/openair3/NGAP/MESSAGES/CMakeLists.txt b/openair3/NGAP/MESSAGES/CMakeLists.txt index 06148b6c5a..e5cfe07956 100644 --- a/openair3/NGAP/MESSAGES/CMakeLists.txt +++ b/openair3/NGAP/MESSAGES/CMakeLists.txt @@ -9,10 +9,11 @@ else() message(FATAL_ERROR "unknown NGAP_RELEASE ${NGAP_RELEASE}") endif() -add_custom_command(OUTPUT ${ngap_source} ${ngap_headers} - COMMAND ASN1C_PREFIX=NGAP_ ${ASN1C_EXEC} -pdu=all -fcompound-names -gen-APER -no-gen-BER -no-gen-JER -no-gen-OER -gen-UPER -no-gen-example -fno-include-deps -findirect-choice -D ${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/${NGAP_GRAMMAR} - DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/${NGAP_GRAMMAR} - COMMENT "Generating NGAP source files from ${CMAKE_CURRENT_SOURCE_DIR}/${NGAP_GRAMMAR}" +run_asn1c(${CMAKE_CURRENT_SOURCE_DIR}/${NGAP_GRAMMAR} + "NGAP_" + OUTPUT ${ngap_source} ${ngap_headers} + OPTIONS -pdu=all -fcompound-names -gen-APER -no-gen-BER -no-gen-JER -no-gen-OER -gen-UPER -no-gen-example -fno-include-deps -findirect-choice + COMMENT "NGAP source files" ) add_library(asn1_ngap ${ngap_source}) diff --git a/openair3/NRPPA/MESSAGES/CMakeLists.txt b/openair3/NRPPA/MESSAGES/CMakeLists.txt index 840553a62d..997fb70a9c 100644 --- a/openair3/NRPPA/MESSAGES/CMakeLists.txt +++ b/openair3/NRPPA/MESSAGES/CMakeLists.txt @@ -9,10 +9,11 @@ include(ASN1/38455.cmake) # message(FATAL_ERROR "unknown NRPPA_RELEASE ${NRPPA_RELEASE}") #endif() -add_custom_command(OUTPUT ${nrppa_source} ${nrppa_headers} - COMMAND ASN1C_PREFIX=NRPPA_ ${ASN1C_EXEC} -gen-APER -gen-UPER -no-gen-OER -fcompound-names -no-gen-example -findirect-choice -fno-include-deps -D ${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/${NRPPA_GRAMMAR} - DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/${NRPPA_GRAMMAR} - COMMENT "Generating NRPPA source files from ${CMAKE_CURRENT_SOURCE_DIR}/${NRPPA_GRAMMAR}" +run_asn1c(${CMAKE_CURRENT_SOURCE_DIR}/${NRPPA_GRAMMAR} + "NRPPA_" + OUTPUT ${nrppa_source} ${nrppa_headers} + OPTIONS -gen-APER -gen-UPER -no-gen-OER -fcompound-names -no-gen-example -findirect-choice -fno-include-deps + COMMENT "NRPPA source files" ) add_library(asn1_nrppa ${nrppa_source}) diff --git a/openair3/S1AP/MESSAGES/CMakeLists.txt b/openair3/S1AP/MESSAGES/CMakeLists.txt index bb1526b659..f38a3a93a6 100644 --- a/openair3/S1AP/MESSAGES/CMakeLists.txt +++ b/openair3/S1AP/MESSAGES/CMakeLists.txt @@ -9,10 +9,11 @@ else() message(FATAL_ERROR "unknown S1AP_RELEASE ${S1AP_RELEASE}") endif() -add_custom_command(OUTPUT ${s1ap_source} ${s1ap_headers} - COMMAND ASN1C_PREFIX=S1AP_ ${ASN1C_EXEC} -gen-APER -no-gen-BER -no-gen-JER -no-gen-OER -gen-UPER -fcompound-names -no-gen-example -fno-include-deps -D ${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/${S1AP_GRAMMAR} - DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/${S1AP_GRAMMAR} - COMMENT "Generating S1AP source files from ${CMAKE_CURRENT_SOURCE_DIR}/${S1AP_GRAMMAR}" +run_asn1c(${CMAKE_CURRENT_SOURCE_DIR}/${S1AP_GRAMMAR} + "S1AP_" + OUTPUT ${s1ap_source} ${s1ap_headers} + OPTIONS -gen-APER -no-gen-BER -no-gen-JER -no-gen-OER -gen-UPER -fcompound-names -no-gen-example -fno-include-deps + COMMENT "S1AP source files" ) add_library(asn1_s1ap ${s1ap_source}) From b9a6d93bfa87e8f8023476b31eb089d207fbda62 Mon Sep 17 00:00:00 2001 From: Robert Schmidt Date: Wed, 9 Apr 2025 16:47:32 +0200 Subject: [PATCH 10/63] Doc: add information on how to avoid a common asan bug --- doc/dev_tools/sanitizers.md | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/doc/dev_tools/sanitizers.md b/doc/dev_tools/sanitizers.md index 6e6376ee08..96fc5317f7 100644 --- a/doc/dev_tools/sanitizers.md +++ b/doc/dev_tools/sanitizers.md @@ -29,6 +29,25 @@ cd cmake_targets/ran_build/build sudo LD_LIBRARY_PATH=. ./nr-softmodem ... ``` +### Possible problems + +On some systems with specific ASan versions, executing a program compiled with +ASan might fail with an error `DEADLYSIGNAL`, or simply a segmentation fault. +Note that since OAI compiles and runs some programs _during compilation_ (for +code generation), typically already the build is impacted. + +The issue is related to Address Space Layout Randomization (ASLR) settings +in Linux, a security feature designed to make it more difficult for attackers +to predict the locations of processes in memory. In this case, it is possible +to avoid the bug by running + +``` +sudo sysctl vm.mmap_rnd_bits=30 +``` + +which reduces the number of bits for memory mappings used to randomize mmap +base addresses. _This reduces the security of the system._ + ## Undefined Behavior Sanitizer (UBSAN) [Undefined Behavior Sanitizer](https://clang.llvm.org/docs/UndefinedBehaviorSanitizer.html) (UBSAN) is a runtime undefined behavior checker. It uses compile-time instrumentation to catch undefined behavior by inserting code that performs specific checks before operations that may cause it. UBSAN can be activated with the `--sanitize-undefined` option or `-fsanitize=undefined`. From 96b0cae63e610864658221158a7ab7273a6ca2d1 Mon Sep 17 00:00:00 2001 From: luis_pereira87 Date: Thu, 27 Feb 2025 11:23:58 +0000 Subject: [PATCH 11/63] Drop 'min_grant_mcs' configuration parameter --- openair2/GNB_APP/MACRLC_nr_paramdef.h | 23 ++++++++----------- openair2/GNB_APP/gnb_config.c | 1 - .../LAYER2/NR_MAC_gNB/gNB_scheduler_ulsch.c | 1 - openair2/LAYER2/NR_MAC_gNB/nr_mac_gNB.h | 1 - .../gnb.sa.band41.fr1.106PRB.usrpb210.conf | 1 - 5 files changed, 9 insertions(+), 18 deletions(-) diff --git a/openair2/GNB_APP/MACRLC_nr_paramdef.h b/openair2/GNB_APP/MACRLC_nr_paramdef.h index a1d860c86c..9ee5338ddd 100644 --- a/openair2/GNB_APP/MACRLC_nr_paramdef.h +++ b/openair2/GNB_APP/MACRLC_nr_paramdef.h @@ -70,7 +70,6 @@ #define CONFIG_STRING_MACRLC_DL_HARQ_ROUND_MAX "dl_harq_round_max" #define CONFIG_STRING_MACRLC_UL_HARQ_ROUND_MAX "ul_harq_round_max" #define CONFIG_STRING_MACRLC_MIN_GRANT_PRB "min_grant_prb" -#define CONFIG_STRING_MACRLC_MIN_GRANT_MCS "min_grant_mcs" #define CONFIG_STRING_MACRLC_IDENTITY_PM "identity_precoding_matrix" #define CONFIG_STRING_MACRLC_ANALOG_BEAMFORMING "set_analog_beamforming" #define CONFIG_STRING_MACRLC_BEAM_DURATION "beam_duration" @@ -88,7 +87,6 @@ #define HLP_MACRLC_DL_HARQ_MAX "Maximum number of DL HARQ rounds" #define HLP_MACRLC_UL_HARQ_MAX "Maximum number of UL HARQ rounds" #define HLP_MACRLC_MIN_GRANT_PRB "Minimal Periodic ULSCH Grant PRBs" -#define HLP_MACRLC_MIN_GRANT_MCS "Minimal Periodic ULSCH Grant MCS" #define HLP_MACRLC_IDENTITY_PM "Flag to use only identity matrix in DL precoding" #define HLP_MACRLC_AB "Flag to enable analog beamforming" #define HLP_MACRLC_BEAM_DURATION "number of consecutive slots for a given set of beams" @@ -132,7 +130,6 @@ {CONFIG_STRING_MACRLC_DL_HARQ_ROUND_MAX, HLP_MACRLC_DL_HARQ_MAX, 0, .u8ptr=NULL, .defintval=4, TYPE_UINT8, 0}, \ {CONFIG_STRING_MACRLC_UL_HARQ_ROUND_MAX, HLP_MACRLC_UL_HARQ_MAX, 0, .u8ptr=NULL, .defintval=4, TYPE_UINT8, 0}, \ {CONFIG_STRING_MACRLC_MIN_GRANT_PRB, HLP_MACRLC_MIN_GRANT_PRB, 0, .u8ptr=NULL, .defintval=5, TYPE_UINT8, 0}, \ - {CONFIG_STRING_MACRLC_MIN_GRANT_MCS, HLP_MACRLC_MIN_GRANT_MCS, 0, .u8ptr=NULL, .defintval=9, TYPE_UINT8, 0}, \ {CONFIG_STRING_MACRLC_IDENTITY_PM, HLP_MACRLC_IDENTITY_PM, PARAMFLAG_BOOL, .u8ptr=NULL, .defintval=0, TYPE_UINT8, 0}, \ {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}, \ @@ -177,16 +174,15 @@ #define MACRLC_DL_HARQ_ROUND_MAX_IDX 27 #define MACRLC_UL_HARQ_ROUND_MAX_IDX 28 #define MACRLC_MIN_GRANT_PRB_IDX 29 -#define MACRLC_MIN_GRANT_MCS_IDX 30 -#define MACRLC_IDENTITY_PM_IDX 31 -#define MACRLC_LOCAL_N_ADDRESS_F1U_IDX 32 -#define MACRLC_TRANSPORT_S_SHM_PREFIX 33 -#define MACRLC_TRANSPORT_S_POLL_CORE 34 -#define MACRLC_ANALOG_BEAMFORMING_IDX 35 -#define MACRLC_ANALOG_BEAM_DURATION_IDX 36 -#define MACRLC_ANALOG_BEAMS_PERIOD_IDX 37 -#define MACRLC_PUSCH_RSSI_THRES_IDX 38 -#define MACRLC_PUCCH_RSSI_THRES_IDX 39 +#define MACRLC_IDENTITY_PM_IDX 30 +#define MACRLC_LOCAL_N_ADDRESS_F1U_IDX 31 +#define MACRLC_TRANSPORT_S_SHM_PREFIX 32 +#define MACRLC_TRANSPORT_S_POLL_CORE 33 +#define MACRLC_ANALOG_BEAMFORMING_IDX 34 +#define MACRLC_ANALOG_BEAM_DURATION_IDX 35 +#define MACRLC_ANALOG_BEAMS_PERIOD_IDX 36 +#define MACRLC_PUSCH_RSSI_THRES_IDX 37 +#define MACRLC_PUCCH_RSSI_THRES_IDX 38 #define MACRLCPARAMS_CHECK { \ { .s5 = { NULL } }, \ @@ -219,7 +215,6 @@ { .s2 = { config_check_intrange, {1, 8} } }, /* DL max HARQ rounds */ \ { .s2 = { config_check_intrange, {1, 8} } }, /* UL max HARQ rounds */ \ { .s5 = { NULL } }, \ - { .s5 = { NULL } }, \ { .s2 = { NULL } }, \ { .s5 = { NULL } }, \ { .s5 = { NULL } }, \ diff --git a/openair2/GNB_APP/gnb_config.c b/openair2/GNB_APP/gnb_config.c index deb8835157..bc5d4b06a3 100644 --- a/openair2/GNB_APP/gnb_config.c +++ b/openair2/GNB_APP/gnb_config.c @@ -1664,7 +1664,6 @@ void RCconfig_nr_macrlc(configmodule_interface_t *cfg) else ul_bler_options->harq_round_max = *(MacRLC_ParamList.paramarray[j][MACRLC_UL_HARQ_ROUND_MAX_IDX].u8ptr); RC.nrmac[j]->min_grant_prb = *(MacRLC_ParamList.paramarray[j][MACRLC_MIN_GRANT_PRB_IDX].u8ptr); - RC.nrmac[j]->min_grant_mcs = *(MacRLC_ParamList.paramarray[j][MACRLC_MIN_GRANT_MCS_IDX].u8ptr); RC.nrmac[j]->identity_pm = *(MacRLC_ParamList.paramarray[j][MACRLC_IDENTITY_PM_IDX].u8ptr); // PRB Blacklist uint16_t prbbl[MAX_BWP_SIZE] = {0}; diff --git a/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_ulsch.c b/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_ulsch.c index b6e86fa6b7..5fe91b86c2 100644 --- a/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_ulsch.c +++ b/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_ulsch.c @@ -2050,7 +2050,6 @@ static void pf_ul(module_id_t module_id, fill_pdcch_vrb_map(nrmac, CC_id, &sched_ctrl->sched_pdcch, CCEIndex, sched_ctrl->aggregation_level, dci_beam.idx); NR_sched_pusch_t *sched_pusch = &sched_ctrl->sched_pusch; - sched_pusch->mcs = max(nrmac->min_grant_mcs, sched_pusch->mcs); update_ul_ue_R_Qm(sched_pusch->mcs, current_BWP->mcs_table, current_BWP->pusch_Config, &sched_pusch->R, &sched_pusch->Qm); sched_pusch->rbStart = rbStart; sched_pusch->rbSize = min_rb; diff --git a/openair2/LAYER2/NR_MAC_gNB/nr_mac_gNB.h b/openair2/LAYER2/NR_MAC_gNB/nr_mac_gNB.h index 65812ae0f5..684e73405e 100644 --- a/openair2/LAYER2/NR_MAC_gNB/nr_mac_gNB.h +++ b/openair2/LAYER2/NR_MAC_gNB/nr_mac_gNB.h @@ -938,7 +938,6 @@ typedef struct gNB_MAC_INST_s { NR_bler_options_t dl_bler; NR_bler_options_t ul_bler; uint8_t min_grant_prb; - uint8_t min_grant_mcs; bool identity_pm; int precoding_matrix_size[NR_MAX_NB_LAYERS]; int fapi_beam_index[MAX_NUM_OF_SSB]; diff --git a/targets/PROJECTS/GENERIC-NR-5GC/CONF/gnb.sa.band41.fr1.106PRB.usrpb210.conf b/targets/PROJECTS/GENERIC-NR-5GC/CONF/gnb.sa.band41.fr1.106PRB.usrpb210.conf index f9a0dfc372..350d4a08a5 100644 --- a/targets/PROJECTS/GENERIC-NR-5GC/CONF/gnb.sa.band41.fr1.106PRB.usrpb210.conf +++ b/targets/PROJECTS/GENERIC-NR-5GC/CONF/gnb.sa.band41.fr1.106PRB.usrpb210.conf @@ -177,7 +177,6 @@ MACRLCs = ( pusch_TargetSNRx10 = 150; pucch_TargetSNRx10 = 200; min_grant_prb = 20; - min_grant_mcs = 16; dl_max_mcs = 10; } ); From 699afafc6ff7869cbc41aef2adbded8a7f3a0ba8 Mon Sep 17 00:00:00 2001 From: luis_pereira87 Date: Thu, 27 Feb 2025 11:45:23 +0000 Subject: [PATCH 12/63] Add support for 'dl_min_mcs' and 'ul_min_mcs' configuration parameters, they will enforce the gNB scheduler to select always a MCS equal or above its values --- openair2/GNB_APP/MACRLC_nr_paramdef.h | 46 +++++++++++-------- openair2/GNB_APP/gnb_config.c | 2 + .../NR_MAC_gNB/gNB_scheduler_primitives.c | 1 + openair2/LAYER2/NR_MAC_gNB/nr_mac_gNB.h | 1 + .../gnb.sa.band41.fr1.106PRB.usrpb210.conf | 1 + 5 files changed, 33 insertions(+), 18 deletions(-) diff --git a/openair2/GNB_APP/MACRLC_nr_paramdef.h b/openair2/GNB_APP/MACRLC_nr_paramdef.h index 9ee5338ddd..e4eaa52804 100644 --- a/openair2/GNB_APP/MACRLC_nr_paramdef.h +++ b/openair2/GNB_APP/MACRLC_nr_paramdef.h @@ -63,9 +63,11 @@ #define CONFIG_STRING_MACRLC_PUSCHFAILURETHRES "pusch_FailureThres" #define CONFIG_STRING_MACRLC_DL_BLER_TARGET_UPPER "dl_bler_target_upper" #define CONFIG_STRING_MACRLC_DL_BLER_TARGET_LOWER "dl_bler_target_lower" +#define CONFIG_STRING_MACRLC_DL_MIN_MCS "dl_min_mcs" #define CONFIG_STRING_MACRLC_DL_MAX_MCS "dl_max_mcs" #define CONFIG_STRING_MACRLC_UL_BLER_TARGET_UPPER "ul_bler_target_upper" #define CONFIG_STRING_MACRLC_UL_BLER_TARGET_LOWER "ul_bler_target_lower" +#define CONFIG_STRING_MACRLC_UL_MIN_MCS "ul_min_mcs" #define CONFIG_STRING_MACRLC_UL_MAX_MCS "ul_max_mcs" #define CONFIG_STRING_MACRLC_DL_HARQ_ROUND_MAX "dl_harq_round_max" #define CONFIG_STRING_MACRLC_UL_HARQ_ROUND_MAX "ul_harq_round_max" @@ -80,9 +82,11 @@ #define HLP_MACRLC_UL_PRBBLACK "SNR threshold to decide whether a PRB will be blacklisted or not" #define HLP_MACRLC_DL_BLER_UP "Upper threshold of BLER to decrease DL MCS" #define HLP_MACRLC_DL_BLER_LO "Lower threshold of BLER to increase DL MCS" +#define HLP_MACRLC_DL_MIN_MCS "Minimum DL MCS that should be used" #define HLP_MACRLC_DL_MAX_MCS "Maximum DL MCS that should be used" #define HLP_MACRLC_UL_BLER_UP "Upper threshold of BLER to decrease UL MCS" #define HLP_MACRLC_UL_BLER_LO "Lower threshold of BLER to increase UL MCS" +#define HLP_MACRLC_UL_MIN_MCS "Minimum UL MCS that should be used" #define HLP_MACRLC_UL_MAX_MCS "Maximum UL MCS that should be used" #define HLP_MACRLC_DL_HARQ_MAX "Maximum number of DL HARQ rounds" #define HLP_MACRLC_UL_HARQ_MAX "Maximum number of UL HARQ rounds" @@ -123,9 +127,11 @@ {CONFIG_STRING_MACRLC_PUSCHFAILURETHRES, NULL, 0, .iptr=NULL, .defintval=10, TYPE_INT, 0}, \ {CONFIG_STRING_MACRLC_DL_BLER_TARGET_UPPER, HLP_MACRLC_DL_BLER_UP, 0, .dblptr=NULL, .defdblval=0.15, TYPE_DOUBLE, 0}, \ {CONFIG_STRING_MACRLC_DL_BLER_TARGET_LOWER, HLP_MACRLC_DL_BLER_LO, 0, .dblptr=NULL, .defdblval=0.05, TYPE_DOUBLE, 0}, \ + {CONFIG_STRING_MACRLC_DL_MIN_MCS, HLP_MACRLC_DL_MIN_MCS, 0, .u8ptr=NULL, .defintval=0, TYPE_UINT8, 0}, \ {CONFIG_STRING_MACRLC_DL_MAX_MCS, HLP_MACRLC_DL_MAX_MCS, 0, .u8ptr=NULL, .defintval=28, TYPE_UINT8, 0}, \ {CONFIG_STRING_MACRLC_UL_BLER_TARGET_UPPER, HLP_MACRLC_UL_BLER_UP, 0, .dblptr=NULL, .defdblval=0.15, TYPE_DOUBLE, 0}, \ {CONFIG_STRING_MACRLC_UL_BLER_TARGET_LOWER, HLP_MACRLC_UL_BLER_LO, 0, .dblptr=NULL, .defdblval=0.05, TYPE_DOUBLE, 0}, \ + {CONFIG_STRING_MACRLC_UL_MIN_MCS, HLP_MACRLC_UL_MIN_MCS, 0, .u8ptr=NULL, .defintval=0, TYPE_UINT8, 0}, \ {CONFIG_STRING_MACRLC_UL_MAX_MCS, HLP_MACRLC_UL_MAX_MCS, 0, .u8ptr=NULL, .defintval=28, TYPE_UINT8, 0}, \ {CONFIG_STRING_MACRLC_DL_HARQ_ROUND_MAX, HLP_MACRLC_DL_HARQ_MAX, 0, .u8ptr=NULL, .defintval=4, TYPE_UINT8, 0}, \ {CONFIG_STRING_MACRLC_UL_HARQ_ROUND_MAX, HLP_MACRLC_UL_HARQ_MAX, 0, .u8ptr=NULL, .defintval=4, TYPE_UINT8, 0}, \ @@ -167,22 +173,24 @@ #define MACRLC_PUSCHFAILURETHRES_IDX 20 #define MACRLC_DL_BLER_TARGET_UPPER_IDX 21 #define MACRLC_DL_BLER_TARGET_LOWER_IDX 22 -#define MACRLC_DL_MAX_MCS_IDX 23 -#define MACRLC_UL_BLER_TARGET_UPPER_IDX 24 -#define MACRLC_UL_BLER_TARGET_LOWER_IDX 25 -#define MACRLC_UL_MAX_MCS_IDX 26 -#define MACRLC_DL_HARQ_ROUND_MAX_IDX 27 -#define MACRLC_UL_HARQ_ROUND_MAX_IDX 28 -#define MACRLC_MIN_GRANT_PRB_IDX 29 -#define MACRLC_IDENTITY_PM_IDX 30 -#define MACRLC_LOCAL_N_ADDRESS_F1U_IDX 31 -#define MACRLC_TRANSPORT_S_SHM_PREFIX 32 -#define MACRLC_TRANSPORT_S_POLL_CORE 33 -#define MACRLC_ANALOG_BEAMFORMING_IDX 34 -#define MACRLC_ANALOG_BEAM_DURATION_IDX 35 -#define MACRLC_ANALOG_BEAMS_PERIOD_IDX 36 -#define MACRLC_PUSCH_RSSI_THRES_IDX 37 -#define MACRLC_PUCCH_RSSI_THRES_IDX 38 +#define MACRLC_DL_MIN_MCS_IDX 23 +#define MACRLC_DL_MAX_MCS_IDX 24 +#define MACRLC_UL_BLER_TARGET_UPPER_IDX 25 +#define MACRLC_UL_BLER_TARGET_LOWER_IDX 26 +#define MACRLC_UL_MIN_MCS_IDX 27 +#define MACRLC_UL_MAX_MCS_IDX 28 +#define MACRLC_DL_HARQ_ROUND_MAX_IDX 29 +#define MACRLC_UL_HARQ_ROUND_MAX_IDX 30 +#define MACRLC_MIN_GRANT_PRB_IDX 31 +#define MACRLC_IDENTITY_PM_IDX 32 +#define MACRLC_LOCAL_N_ADDRESS_F1U_IDX 33 +#define MACRLC_TRANSPORT_S_SHM_PREFIX 34 +#define MACRLC_TRANSPORT_S_POLL_CORE 35 +#define MACRLC_ANALOG_BEAMFORMING_IDX 36 +#define MACRLC_ANALOG_BEAM_DURATION_IDX 37 +#define MACRLC_ANALOG_BEAMS_PERIOD_IDX 38 +#define MACRLC_PUSCH_RSSI_THRES_IDX 39 +#define MACRLC_PUCCH_RSSI_THRES_IDX 40 #define MACRLCPARAMS_CHECK { \ { .s5 = { NULL } }, \ @@ -208,10 +216,12 @@ { .s5 = { NULL } }, \ { .s5 = { NULL } }, \ { .s5 = { NULL } }, \ + { .s2 = { config_check_intrange, {0, 31} } }, /* DL min MCS */ \ + { .s2 = { config_check_intrange, {0, 31} } }, /* DL max MCS */ \ { .s5 = { NULL } }, \ { .s5 = { NULL } }, \ - { .s5 = { NULL } }, \ - { .s5 = { NULL } }, \ + { .s2 = { config_check_intrange, {0, 31} } }, /* UL min MCS */ \ + { .s2 = { config_check_intrange, {0, 31} } }, /* UL max MCS */ \ { .s2 = { config_check_intrange, {1, 8} } }, /* DL max HARQ rounds */ \ { .s2 = { config_check_intrange, {1, 8} } }, /* UL max HARQ rounds */ \ { .s5 = { NULL } }, \ diff --git a/openair2/GNB_APP/gnb_config.c b/openair2/GNB_APP/gnb_config.c index bc5d4b06a3..377a4a8be0 100644 --- a/openair2/GNB_APP/gnb_config.c +++ b/openair2/GNB_APP/gnb_config.c @@ -1650,6 +1650,7 @@ void RCconfig_nr_macrlc(configmodule_interface_t *cfg) NR_bler_options_t *dl_bler_options = &RC.nrmac[j]->dl_bler; dl_bler_options->upper = *(MacRLC_ParamList.paramarray[j][MACRLC_DL_BLER_TARGET_UPPER_IDX].dblptr); dl_bler_options->lower = *(MacRLC_ParamList.paramarray[j][MACRLC_DL_BLER_TARGET_LOWER_IDX].dblptr); + dl_bler_options->min_mcs = *(MacRLC_ParamList.paramarray[j][MACRLC_DL_MIN_MCS_IDX].u8ptr); dl_bler_options->max_mcs = *(MacRLC_ParamList.paramarray[j][MACRLC_DL_MAX_MCS_IDX].u8ptr); if (config.disable_harq) dl_bler_options->harq_round_max = 1; @@ -1658,6 +1659,7 @@ void RCconfig_nr_macrlc(configmodule_interface_t *cfg) NR_bler_options_t *ul_bler_options = &RC.nrmac[j]->ul_bler; ul_bler_options->upper = *(MacRLC_ParamList.paramarray[j][MACRLC_UL_BLER_TARGET_UPPER_IDX].dblptr); ul_bler_options->lower = *(MacRLC_ParamList.paramarray[j][MACRLC_UL_BLER_TARGET_LOWER_IDX].dblptr); + ul_bler_options->min_mcs = *(MacRLC_ParamList.paramarray[j][MACRLC_UL_MIN_MCS_IDX].u8ptr); ul_bler_options->max_mcs = *(MacRLC_ParamList.paramarray[j][MACRLC_UL_MAX_MCS_IDX].u8ptr); if (config.disable_harq) ul_bler_options->harq_round_max = 1; diff --git a/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_primitives.c b/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_primitives.c index 3c7c9ebd39..f775fcf7a5 100644 --- a/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_primitives.c +++ b/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_primitives.c @@ -761,6 +761,7 @@ int get_mcs_from_bler(const NR_bler_options_t *bler_options, new_mcs -= 1; // else we are within threshold boundaries + new_mcs = max(new_mcs, bler_options->min_mcs); bler_stats->last_frame = frame; bler_stats->mcs = new_mcs; memcpy(bler_stats->rounds, stats->rounds, sizeof(stats->rounds)); diff --git a/openair2/LAYER2/NR_MAC_gNB/nr_mac_gNB.h b/openair2/LAYER2/NR_MAC_gNB/nr_mac_gNB.h index 684e73405e..c5086e2c32 100644 --- a/openair2/LAYER2/NR_MAC_gNB/nr_mac_gNB.h +++ b/openair2/LAYER2/NR_MAC_gNB/nr_mac_gNB.h @@ -738,6 +738,7 @@ typedef struct NR_mac_stats { typedef struct NR_bler_options { double upper; double lower; + uint8_t min_mcs; uint8_t max_mcs; uint8_t harq_round_max; } NR_bler_options_t; diff --git a/targets/PROJECTS/GENERIC-NR-5GC/CONF/gnb.sa.band41.fr1.106PRB.usrpb210.conf b/targets/PROJECTS/GENERIC-NR-5GC/CONF/gnb.sa.band41.fr1.106PRB.usrpb210.conf index 350d4a08a5..a5e9841772 100644 --- a/targets/PROJECTS/GENERIC-NR-5GC/CONF/gnb.sa.band41.fr1.106PRB.usrpb210.conf +++ b/targets/PROJECTS/GENERIC-NR-5GC/CONF/gnb.sa.band41.fr1.106PRB.usrpb210.conf @@ -177,6 +177,7 @@ MACRLCs = ( pusch_TargetSNRx10 = 150; pucch_TargetSNRx10 = 200; min_grant_prb = 20; + ul_min_mcs = 16; dl_max_mcs = 10; } ); From ab3bb363578f9bffcdc3224347cf39d5b689dadc Mon Sep 17 00:00:00 2001 From: Guido Casati Date: Tue, 18 Feb 2025 11:15:00 +0100 Subject: [PATCH 13/63] Add eq_gprs_timer to common 5gmm_lib --- openair3/NAS/NR_UE/5GS/5GMM/MSG/fgmm_lib.c | 8 ++++++++ openair3/NAS/NR_UE/5GS/5GMM/MSG/fgmm_lib.h | 1 + 2 files changed, 9 insertions(+) diff --git a/openair3/NAS/NR_UE/5GS/5GMM/MSG/fgmm_lib.c b/openair3/NAS/NR_UE/5GS/5GMM/MSG/fgmm_lib.c index e21bc141c4..323cffe7e7 100644 --- a/openair3/NAS/NR_UE/5GS/5GMM/MSG/fgmm_lib.c +++ b/openair3/NAS/NR_UE/5GS/5GMM/MSG/fgmm_lib.c @@ -143,6 +143,14 @@ int decode_gprs_timer_ie(gprs_timer_t *timer, const byte_array_t *buffer) return decoded; } +/** @brief Equality check for GPRS Timer */ +bool eq_gprs_timer(const gprs_timer_t *a, const gprs_timer_t *b) +{ + _NAS_EQ_CHECK_INT(a->value, b->value); + _NAS_EQ_CHECK_INT(a->unit, b->unit); + return true; +} + /* Decode EAP Message (todo) */ int decode_eap_msg_ie(const byte_array_t *buffer) { diff --git a/openair3/NAS/NR_UE/5GS/5GMM/MSG/fgmm_lib.h b/openair3/NAS/NR_UE/5GS/5GMM/MSG/fgmm_lib.h index b09f19b828..4acf7368b5 100644 --- a/openair3/NAS/NR_UE/5GS/5GMM/MSG/fgmm_lib.h +++ b/openair3/NAS/NR_UE/5GS/5GMM/MSG/fgmm_lib.h @@ -161,6 +161,7 @@ int encode_pdu_session_ie(byte_array_t *buffer, nas_service_IEI_t iei, const uin int decode_pdu_session_ie(uint8_t *psi, const byte_array_t *buffer); int encode_gprs_timer_ie(byte_array_t *buffer, nas_service_IEI_t iei, const gprs_timer_t *timer); int decode_gprs_timer_ie(gprs_timer_t *timer, const byte_array_t *buffer); +bool eq_gprs_timer(const gprs_timer_t *a, const gprs_timer_t *b); int decode_eap_msg_ie(const byte_array_t *buffer); #endif /* FGS_LIB_H */ \ No newline at end of file From 25bc1bd89a18b30e7cda864546c9b31502ad6da0 Mon Sep 17 00:00:00 2001 From: Guido Casati Date: Fri, 4 Apr 2025 10:40:22 +0200 Subject: [PATCH 14/63] Adopt eq_gprs_timer in eq_service_accept --- openair3/NAS/NR_UE/5GS/5GMM/MSG/fgmm_service_accept.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openair3/NAS/NR_UE/5GS/5GMM/MSG/fgmm_service_accept.c b/openair3/NAS/NR_UE/5GS/5GMM/MSG/fgmm_service_accept.c index 7130586326..797deede84 100644 --- a/openair3/NAS/NR_UE/5GS/5GMM/MSG/fgmm_service_accept.c +++ b/openair3/NAS/NR_UE/5GS/5GMM/MSG/fgmm_service_accept.c @@ -172,8 +172,8 @@ bool eq_service_accept(const fgs_service_accept_msg_t *a, const fgs_service_acce PRINT_NAS_ERROR("t3448 equality check failed\n"); return false; } - _NAS_EQ_CHECK_INT(a->t3448->value, b->t3448->value); - _NAS_EQ_CHECK_INT(a->t3448->unit, b->t3448->unit); + if (!eq_gprs_timer(a->t3448, b->t3448)) + return false; } return true; } From b421d6eaa9d03f89964ede6f9fc046c6810c0d30 Mon Sep 17 00:00:00 2001 From: luis_pereira87 Date: Thu, 10 Apr 2025 09:15:31 +0100 Subject: [PATCH 15/63] Set 'min_mcs' in 'bler_stats' when initializing and when no activity is detected, instead of hardcode it to '9' --- openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_primitives.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_primitives.c b/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_primitives.c index f775fcf7a5..c99957cc29 100644 --- a/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_primitives.c +++ b/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_primitives.c @@ -735,7 +735,7 @@ int get_mcs_from_bler(const NR_bler_options_t *bler_options, /* first call: everything is zero. Initialize to sensible default */ if (bler_stats->last_frame == 0 && bler_stats->mcs == 0) { bler_stats->last_frame = frame; - bler_stats->mcs = 9; + bler_stats->mcs = bler_options->min_mcs; bler_stats->bler = (bler_options->lower + bler_options->upper) / 2.0f; } int diff = frame - bler_stats->last_frame; @@ -756,8 +756,7 @@ int get_mcs_from_bler(const NR_bler_options_t *bler_options, int new_mcs = old_mcs; if (bler_stats->bler < bler_options->lower && old_mcs < max_mcs && num_dl_sched > 3) new_mcs += 1; - else if ((bler_stats->bler > bler_options->upper && old_mcs > 6) // above threshold - || (num_dl_sched <= 3 && old_mcs > 9)) // no activity + else if (bler_stats->bler > bler_options->upper || num_dl_sched <= 3) // above threshold or no activity new_mcs -= 1; // else we are within threshold boundaries From 2d35fc90ace4b7e72b611f1b2a9c0618e67f1583 Mon Sep 17 00:00:00 2001 From: Bartosz Podrygajlo Date: Tue, 18 Mar 2025 10:08:04 +0100 Subject: [PATCH 16/63] NR UE: enable concurrent UL slot processing This commit introduces support for concurrent UL slot processing Key changes: - Increased number of UL actors from 1 to 2 - fix several issues related to writing to global variables inside processSlotTX - Ensure in-order execution of if_module calls by utilizing the preexisting dynamic_barrier structure. --- executables/nr-ue.c | 12 ++++++++---- executables/nr-uesoftmodem.c | 8 ++++++-- openair1/PHY/INIT/nr_init_ue.c | 6 ------ openair1/PHY/NR_UE_TRANSPORT/nr_transport_ue.h | 3 +-- openair1/PHY/NR_UE_TRANSPORT/nr_ulsch_ue.c | 9 +-------- openair1/PHY/NR_UE_TRANSPORT/srs_modulation_nr.c | 7 ++++--- openair1/PHY/NR_UE_TRANSPORT/srs_modulation_nr.h | 7 +++++-- openair1/PHY/defs_nr_UE.h | 7 ++++--- openair1/SCHED_NR_UE/fapi_nr_ue_l1.c | 6 +++--- openair1/SCHED_NR_UE/phy_procedures_nr_ue.c | 2 +- 10 files changed, 33 insertions(+), 34 deletions(-) diff --git a/executables/nr-ue.c b/executables/nr-ue.c index d34e710cec..3fd5f00873 100644 --- a/executables/nr-ue.c +++ b/executables/nr-ue.c @@ -104,7 +104,7 @@ static void *NRUE_phy_stub_standalone_pnf_task(void *arg); static void start_process_slot_tx(void* arg) { notifiedFIFO_elt_t *newTx = arg; nr_rxtx_thread_data_t *curMsgTx = NotifiedFifoData(newTx); - pushNotifiedFIFO(&curMsgTx->UE->ul_actor.fifo, newTx); + pushNotifiedFIFO(&curMsgTx->UE->ul_actors[curMsgTx->proc.nr_slot_tx % NUM_UL_ACTORS].fifo, newTx); } static size_t dump_L1_UE_meas_stats(PHY_VARS_NR_UE *ue, char *output, size_t max_len) @@ -585,6 +585,7 @@ void processSlotTX(void *arg) UE->if_inst->sl_indication(&sl_indication); stop_meas(&UE->ue_ul_indication_stats); } + dynamic_barrier_join(rxtxD->next_barrier); if (phy_data.sl_tx_action) { @@ -611,12 +612,14 @@ void processSlotTX(void *arg) UE->if_inst->ul_indication(&ul_indication); stop_meas(&UE->ue_ul_indication_stats); } + dynamic_barrier_join(rxtxD->next_barrier); phy_procedures_nrUE_TX(UE, proc, &phy_data); } + } else { + dynamic_barrier_join(rxtxD->next_barrier); } RU_write(rxtxD, sl_tx_action); - dynamic_barrier_join(rxtxD->next_barrier); TracyCZoneEnd(ctx); } @@ -656,7 +659,9 @@ static int handle_sync_req_from_mac(PHY_VARS_NR_UE *UE) for (int i = 0; i < NUM_DL_ACTORS; i++) { flush_actor(UE->dl_actors + i); } - flush_actor(&UE->ul_actor); + for (int i = 0; i < NUM_UL_ACTORS; i++) { + flush_actor(UE->ul_actors + i); + } clean_UE_harq(UE); UE->is_synchronized = 0; @@ -1186,7 +1191,6 @@ void *UE_thread(void *arg) curMsgTx->proc.timestamp_tx = writeTimestamp; curMsgTx->UE = UE; curMsgTx->proc.nr_slot_tx_offset = nr_slot_tx_offset; - int slot = curMsgTx->proc.nr_slot_tx; int slot_and_frame = slot + curMsgTx->proc.frame_tx * UE->frame_parms.slots_per_frame; int next_tx_slot_and_frame = absolute_slot + duration_rx_to_tx + 1; diff --git a/executables/nr-uesoftmodem.c b/executables/nr-uesoftmodem.c index c0142ce896..b898cdf7d2 100644 --- a/executables/nr-uesoftmodem.c +++ b/executables/nr-uesoftmodem.c @@ -508,7 +508,9 @@ int main(int argc, char **argv) for (int i = 0; i < NUM_DL_ACTORS; i++) { init_actor(&UE[CC_id]->dl_actors[i], "DL_", -1); } - init_actor(&UE[CC_id]->ul_actor, "UL_", -1); + for (int i = 0; i < NUM_UL_ACTORS; i++) { + init_actor(&UE[CC_id]->ul_actors[i], "UL_", -1); + } init_nr_ue_vars(UE[CC_id], inst); if (UE[CC_id]->sl_mode) { @@ -578,7 +580,9 @@ int main(int argc, char **argv) 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]; if (phy_vars) { - shutdown_actor(&phy_vars->ul_actor); + for (int i = 0; i < NUM_UL_ACTORS; i++) { + shutdown_actor(&phy_vars->ul_actors[i]); + } for (int i = 0; i < NUM_DL_ACTORS; i++) { shutdown_actor(&phy_vars->dl_actors[i]); } diff --git a/openair1/PHY/INIT/nr_init_ue.c b/openair1/PHY/INIT/nr_init_ue.c index 19816e215f..9f8a463ec7 100644 --- a/openair1/PHY/INIT/nr_init_ue.c +++ b/openair1/PHY/INIT/nr_init_ue.c @@ -171,7 +171,6 @@ int init_nr_ue_signal(PHY_VARS_NR_UE *ue, int nb_connected_gNB) NR_DL_FRAME_PARMS *const fp = &ue->frame_parms; NR_UE_COMMON *const common_vars = &ue->common_vars; NR_UE_PRACH **const prach_vars = ue->prach_vars; - NR_UE_SRS **const srs_vars = ue->srs_vars; LOG_I(PHY, "Initializing UE vars for gNB TXant %u, UE RXant %u\n", fp->nb_antennas_tx, fp->nb_antennas_rx); @@ -257,8 +256,6 @@ int init_nr_ue_signal(PHY_VARS_NR_UE *ue, int nb_connected_gNB) // DLSCH for (int gNB_id = 0; gNB_id < ue->n_connected_gNB; gNB_id++) { prach_vars[gNB_id] = malloc16_clear(sizeof(NR_UE_PRACH)); - srs_vars[gNB_id] = malloc16_clear(sizeof(NR_UE_SRS)); - srs_vars[gNB_id]->active = false; // ceil((NB_RB*8(max allocation per RB)*2(QPSK))/32) ue->nr_csi_info = malloc16_clear(sizeof(nr_csi_info_t)); @@ -318,8 +315,6 @@ void term_nr_ue_signal(PHY_VARS_NR_UE *ue, int nb_connected_gNB) free_and_zero(ue->nr_srs_info); - free_and_zero(ue->srs_vars[gNB_id]); - free_and_zero(ue->prach_vars[gNB_id]); } @@ -487,7 +482,6 @@ void clean_UE_harq(PHY_VARS_NR_UE *UE) for (int harq_pid = 0; harq_pid < NR_MAX_ULSCH_HARQ_PROCESSES; harq_pid++) { NR_UL_UE_HARQ_t *ul_harq_process = &UE->ul_harq_processes[harq_pid]; ul_harq_process->tx_status = NEW_TRANSMISSION_HARQ; - ul_harq_process->ULstatus = SCH_IDLE; ul_harq_process->round = 0; } } diff --git a/openair1/PHY/NR_UE_TRANSPORT/nr_transport_ue.h b/openair1/PHY/NR_UE_TRANSPORT/nr_transport_ue.h index 07917b9075..ce7f41d2b9 100644 --- a/openair1/PHY/NR_UE_TRANSPORT/nr_transport_ue.h +++ b/openair1/PHY/NR_UE_TRANSPORT/nr_transport_ue.h @@ -46,8 +46,6 @@ typedef enum { typedef struct { /// HARQ tx status harq_result_t tx_status; - /// Status Flag indicating for this ULSCH (idle,active,disabled) - SCH_status_t ULstatus; /// Last TPC command uint8_t TPC; /// Length of ACK information (bits) @@ -87,6 +85,7 @@ typedef struct { } NR_UL_UE_HARQ_t; typedef struct { + SCH_status_t status; /// NDAPI struct for UE nfapi_nr_ue_pusch_pdu_t pusch_pdu; // UL number of harq processes diff --git a/openair1/PHY/NR_UE_TRANSPORT/nr_ulsch_ue.c b/openair1/PHY/NR_UE_TRANSPORT/nr_ulsch_ue.c index bd4ea90df8..be7a8101db 100644 --- a/openair1/PHY/NR_UE_TRANSPORT/nr_ulsch_ue.c +++ b/openair1/PHY/NR_UE_TRANSPORT/nr_ulsch_ue.c @@ -471,7 +471,7 @@ void nr_ue_ulsch_procedures(PHY_VARS_NR_UE *UE, int harq_pid = phy_data->ulsch.pusch_pdu.pusch_data.harq_process_id; - if (UE->ul_harq_processes[harq_pid].ULstatus != ACTIVE) + if (phy_data->ulsch.status != ACTIVE) return; start_meas_nr_ue_phy(UE, PUSCH_PROC_STATS); @@ -542,8 +542,6 @@ void nr_ue_ulsch_procedures(PHY_VARS_NR_UE *UE, /////////////////////////ULSCH coding///////////////////////// if (nr_ulsch_encoding(UE, &phy_data->ulsch, frame, slot, G, 1, ULSCH_ids) == -1) { - NR_UL_UE_HARQ_t *harq_process_ulsch = &UE->ul_harq_processes[harq_pid]; - harq_process_ulsch->ULstatus = SCH_IDLE; stop_meas_nr_ue_phy(UE, PUSCH_PROC_STATS); return; } @@ -820,12 +818,7 @@ void nr_ue_ulsch_procedures(PHY_VARS_NR_UE *UE, } // symbol loop } // port loop - NR_UL_UE_HARQ_t *harq_process_ulsch = NULL; - harq_process_ulsch = &UE->ul_harq_processes[harq_pid]; - harq_process_ulsch->ULstatus = SCH_IDLE; - stop_meas_nr_ue_phy(UE, PUSCH_PROC_STATS); - } uint8_t nr_ue_pusch_common_procedures(PHY_VARS_NR_UE *UE, diff --git a/openair1/PHY/NR_UE_TRANSPORT/srs_modulation_nr.c b/openair1/PHY/NR_UE_TRANSPORT/srs_modulation_nr.c index 17dc69cd4f..44be6a578e 100644 --- a/openair1/PHY/NR_UE_TRANSPORT/srs_modulation_nr.c +++ b/openair1/PHY/NR_UE_TRANSPORT/srs_modulation_nr.c @@ -426,14 +426,15 @@ int generate_srs_nr(nfapi_nr_srs_pdu_t *srs_config_pdu, int ue_srs_procedures_nr(PHY_VARS_NR_UE *ue, const UE_nr_rxtx_proc_t *proc, c16_t **txdataF, + nr_phy_data_tx_t *phy_data, bool was_symbol_used[NR_NUMBER_OF_SYMBOLS_PER_SLOT]) { - if(!ue->srs_vars[0]->active) { + if(phy_data->srs_vars.active == false) { return -1; } - ue->srs_vars[0]->active = false; - nfapi_nr_srs_pdu_t *srs_config_pdu = (nfapi_nr_srs_pdu_t*)&ue->srs_vars[0]->srs_config_pdu; + // TODO: This is wrong: casting from UE fapi to gNB NFAPI srs pdu + nfapi_nr_srs_pdu_t *srs_config_pdu = (nfapi_nr_srs_pdu_t *)&phy_data->srs_vars.srs_config_pdu; int first_srs_symbol = ue->frame_parms.symbols_per_slot - 1 - srs_config_pdu->time_start_position; // Num consecutive SRS symbols according to 38.211 6.4.1.4.1 diff --git a/openair1/PHY/NR_UE_TRANSPORT/srs_modulation_nr.h b/openair1/PHY/NR_UE_TRANSPORT/srs_modulation_nr.h index fc785c7aec..1bbe3a27ef 100644 --- a/openair1/PHY/NR_UE_TRANSPORT/srs_modulation_nr.h +++ b/openair1/PHY/NR_UE_TRANSPORT/srs_modulation_nr.h @@ -78,7 +78,10 @@ int is_srs_period_nr(SRS_Resource_t *p_SRS_Resource, @param rxtx context @param current gNB_id identifier @returns 0 if srs is transmitted -1 otherwise */ - -int ue_srs_procedures_nr(PHY_VARS_NR_UE *ue, const UE_nr_rxtx_proc_t *proc, c16_t **txdataF, bool was_symbol_used[14]); +int ue_srs_procedures_nr(PHY_VARS_NR_UE *ue, + const UE_nr_rxtx_proc_t *proc, + c16_t **txdataF, + nr_phy_data_tx_t *phy_data, + bool was_symbol_used[NR_NUMBER_OF_SYMBOLS_PER_SLOT]); #endif /* SRS_MODULATION_NR_H */ diff --git a/openair1/PHY/defs_nr_UE.h b/openair1/PHY/defs_nr_UE.h index 2cb2b3d8e6..ba280c4f6b 100644 --- a/openair1/PHY/defs_nr_UE.h +++ b/openair1/PHY/defs_nr_UE.h @@ -82,6 +82,7 @@ /// suppress compiler warning for unused arguments #define UNUSED(x) (void)x; #define NUM_DL_ACTORS 4 +#define NUM_UL_ACTORS 2 // Set the number of barriers for processSlotTX to 512. This value has to be at least 483 for NTN where // DL-to-UL offset is up to 483. The selected value is also half of the frame range so that @@ -401,7 +402,6 @@ typedef struct PHY_VARS_NR_UE_s { nr_synch_request_t synch_request; NR_UE_PRACH *prach_vars[NUMBER_OF_CONNECTED_gNB_MAX]; - NR_UE_SRS *srs_vars[NUMBER_OF_CONNECTED_gNB_MAX]; NR_UE_PRS *prs_vars[NR_MAX_PRS_COMB_SIZE]; uint8_t prs_active_gNBs; NR_DL_UE_HARQ_t dl_harq_processes[2][NR_MAX_DLSCH_HARQ_PROCESSES]; @@ -514,7 +514,7 @@ typedef struct PHY_VARS_NR_UE_s { sl_nr_ue_phy_params_t SL_UE_PHY_PARAMS; Actor_t sync_actor; Actor_t dl_actors[NUM_DL_ACTORS]; - Actor_t ul_actor; + Actor_t ul_actors[NUM_UL_ACTORS]; ntn_config_message_t* ntn_config_message; pthread_t main_thread; pthread_t stat_thread; @@ -571,6 +571,7 @@ typedef struct { typedef struct nr_phy_data_tx_s { NR_UE_ULSCH_t ulsch; NR_UE_PUCCH pucch_vars; + NR_UE_SRS srs_vars; // Sidelink Rx action decided by MAC sl_nr_tx_config_type_enum_t sl_tx_action; @@ -597,7 +598,7 @@ typedef struct nr_rxtx_thread_data_s { PHY_VARS_NR_UE *UE; int writeBlockSize; nr_phy_data_t phy_data; - dynamic_barrier_t *next_barrier; + dynamic_barrier_t* next_barrier; } nr_rxtx_thread_data_t; typedef struct LDPCDecode_ue_s { diff --git a/openair1/SCHED_NR_UE/fapi_nr_ue_l1.c b/openair1/SCHED_NR_UE/fapi_nr_ue_l1.c index ba4ad4a0f7..6bb7ed3e4c 100644 --- a/openair1/SCHED_NR_UE/fapi_nr_ue_l1.c +++ b/openair1/SCHED_NR_UE/fapi_nr_ue_l1.c @@ -518,7 +518,7 @@ static void nr_ue_scheduled_response_ul(PHY_VARS_NR_UE *phy, fapi_nr_ul_config_r pdu->pusch_config_pdu.tx_request_body.pdu_length); } - harq_process_ul_ue->ULstatus = ACTIVE; + phy_data->ulsch.status = ACTIVE; pdu->pdu_type = FAPI_NR_UL_CONFIG_TYPE_DONE; // not handle it any more } break; @@ -549,8 +549,8 @@ static void nr_ue_scheduled_response_ul(PHY_VARS_NR_UE *phy, fapi_nr_ul_config_r case FAPI_NR_UL_CONFIG_TYPE_SRS: // srs config pdu - phy->srs_vars[0]->srs_config_pdu = pdu->srs_config_pdu; - phy->srs_vars[0]->active = true; + phy_data->srs_vars.srs_config_pdu = pdu->srs_config_pdu; + phy_data->srs_vars.active = true; pdu->pdu_type = FAPI_NR_UL_CONFIG_TYPE_DONE; // not handle it any more break; diff --git a/openair1/SCHED_NR_UE/phy_procedures_nr_ue.c b/openair1/SCHED_NR_UE/phy_procedures_nr_ue.c index 37593bb9a7..38e53be4ac 100644 --- a/openair1/SCHED_NR_UE/phy_procedures_nr_ue.c +++ b/openair1/SCHED_NR_UE/phy_procedures_nr_ue.c @@ -290,7 +290,7 @@ void phy_procedures_nrUE_TX(PHY_VARS_NR_UE *ue, const UE_nr_rxtx_proc_t *proc, n nr_ue_ulsch_procedures(ue, frame_tx, slot_tx, phy_data, (c16_t **)&txdataF, was_symbol_used); - ue_srs_procedures_nr(ue, proc, (c16_t **)&txdataF, was_symbol_used); + ue_srs_procedures_nr(ue, proc, (c16_t **)&txdataF, phy_data, was_symbol_used); pucch_procedures_ue_nr(ue, proc, phy_data, (c16_t **)&txdataF, was_symbol_used); From afd49e703c4c8ec7f83076132667198b4b25d722 Mon Sep 17 00:00:00 2001 From: Bartosz Podrygajlo Date: Thu, 10 Apr 2025 13:09:43 +0200 Subject: [PATCH 17/63] Workaround for issues with NTN and NR UE concurrent UL processing - Allow setting number of used UL actors in NR UE - Disable the second UL actor in NTN GEO testcase --- .../yaml_files/5g_rfsimulator_ntn_geo/docker-compose.yaml | 2 +- executables/nr-ue.c | 4 +++- executables/nr-uesoftmodem.h | 3 +++ 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/ci-scripts/yaml_files/5g_rfsimulator_ntn_geo/docker-compose.yaml b/ci-scripts/yaml_files/5g_rfsimulator_ntn_geo/docker-compose.yaml index 193b3a9064..0414910077 100644 --- a/ci-scripts/yaml_files/5g_rfsimulator_ntn_geo/docker-compose.yaml +++ b/ci-scripts/yaml_files/5g_rfsimulator_ntn_geo/docker-compose.yaml @@ -110,7 +110,7 @@ services: - NET_ADMIN # for interface bringup - NET_RAW # for ping environment: - USE_ADDITIONAL_OPTIONS: --band 254 -C 2488400000 --CO -873500000 -r 25 --numerology 0 --ssb 60 --rfsim --rfsimulator.prop_delay 238.74 --rfsimulator.serveraddr 192.168.71.140 --log_config.global_log_options level,nocolor,time + USE_ADDITIONAL_OPTIONS: --band 254 -C 2488400000 --CO -873500000 -r 25 --numerology 0 --ssb 60 --rfsim --rfsimulator.prop_delay 238.74 --rfsimulator.serveraddr 192.168.71.140 --num-ul-actors 1 --log_config.global_log_options level,nocolor,time depends_on: - oai-gnb networks: diff --git a/executables/nr-ue.c b/executables/nr-ue.c index 3fd5f00873..f68b49cd3d 100644 --- a/executables/nr-ue.c +++ b/executables/nr-ue.c @@ -104,7 +104,9 @@ static void *NRUE_phy_stub_standalone_pnf_task(void *arg); static void start_process_slot_tx(void* arg) { notifiedFIFO_elt_t *newTx = arg; nr_rxtx_thread_data_t *curMsgTx = NotifiedFifoData(newTx); - pushNotifiedFIFO(&curMsgTx->UE->ul_actors[curMsgTx->proc.nr_slot_tx % NUM_UL_ACTORS].fifo, newTx); + int num_ul_actors_to_use = + get_nrUE_params()->num_ul_actors == 0 ? NUM_UL_ACTORS : min(get_nrUE_params()->num_ul_actors, NUM_UL_ACTORS); + pushNotifiedFIFO(&curMsgTx->UE->ul_actors[curMsgTx->proc.nr_slot_tx % num_ul_actors_to_use].fifo, newTx); } static size_t dump_L1_UE_meas_stats(PHY_VARS_NR_UE *ue, char *output, size_t max_len) diff --git a/executables/nr-uesoftmodem.h b/executables/nr-uesoftmodem.h index f7e3b42a72..28de33770e 100644 --- a/executables/nr-uesoftmodem.h +++ b/executables/nr-uesoftmodem.h @@ -15,6 +15,7 @@ #define CONFIG_HLP_NTN_INIT_TIME_DRIFT "Initial NTN DL time drift (feeder link and service link), given in µs/s\n" #define CONFIG_HLP_AUTONOMOUS_TA "Autonomously update TA based on DL drift (useful if main contribution to DL drift is movement, e.g. LEO satellite)\n" #define CONFIG_HLP_AGC "Rx Gain control used for UE\n" +#define CONFIG_HLP_NUM_UL_ACTORS "Number of UL actors to use. Set to 0 to use the default value\n" /***************************************************************************************************************************************/ /* command line options definitions, CMDLINE_XXXX_DESC macros are used to initialize paramdef_t arrays which are then used as argument @@ -66,6 +67,7 @@ {"ntn-initial-time-drift", CONFIG_HLP_NTN_INIT_TIME_DRIFT, 0, .dblptr=&(nrUE_params.ntn_init_time_drift), .defdblval=0.0, TYPE_DOUBLE, 0}, \ {"autonomous-ta", CONFIG_HLP_AUTONOMOUS_TA, PARAMFLAG_BOOL, .iptr=&(nrUE_params.autonomous_ta), .defintval=0, TYPE_INT, 0}, \ {"agc", CONFIG_HLP_AGC, PARAMFLAG_BOOL, .iptr=&(nrUE_params.agc), .defintval=0, TYPE_INT, 0}, \ + {"num-ul-actors", CONFIG_HLP_NUM_UL_ACTORS, 0, .iptr=&nrUE_params.num_ul_actors, .defintval=0, TYPE_INT, 0}, \ } // clang-format on @@ -101,6 +103,7 @@ typedef struct { double rx_gain_off; int vcdflag; int tx_max_power; + int num_ul_actors; } nrUE_params_t; extern uint64_t get_nrUE_optmask(void); extern uint64_t set_nrUE_optmask(uint64_t bitmask); From f1811b593441bf7f1136b839d4fe707288d6f6fd Mon Sep 17 00:00:00 2001 From: luis_pereira87 Date: Thu, 10 Apr 2025 09:28:20 +0100 Subject: [PATCH 18/63] Move the initialization of bler_stats to 'add_new_nr_ue()' function --- .../NR_MAC_gNB/gNB_scheduler_primitives.c | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_primitives.c b/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_primitives.c index c99957cc29..a5a1c7ebdc 100644 --- a/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_primitives.c +++ b/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_primitives.c @@ -732,12 +732,6 @@ int get_mcs_from_bler(const NR_bler_options_t *bler_options, int max_mcs, frame_t frame) { - /* first call: everything is zero. Initialize to sensible default */ - if (bler_stats->last_frame == 0 && bler_stats->mcs == 0) { - bler_stats->last_frame = frame; - bler_stats->mcs = bler_options->min_mcs; - bler_stats->bler = (bler_options->lower + bler_options->upper) / 2.0f; - } int diff = frame - bler_stats->last_frame; if (diff < 0) // wrap around diff += 1024; @@ -2492,6 +2486,13 @@ void reset_srs_stats(NR_UE_info_t *UE) { } } +static void init_bler_stats(const NR_bler_options_t *bler_options, NR_bler_stats_t *bler_stats, frame_t frame) +{ + bler_stats->last_frame = frame; + bler_stats->mcs = bler_options->min_mcs; + bler_stats->bler = (float)(bler_options->lower + bler_options->upper) / 2.0f; +} + //------------------------------------------------------------------------------ NR_UE_info_t *add_new_nr_ue(gNB_MAC_INST *nr_mac, rnti_t rntiP, NR_CellGroupConfig_t *CellGroup) { @@ -2522,6 +2523,10 @@ NR_UE_info_t *add_new_nr_ue(gNB_MAC_INST *nr_mac, rnti_t rntiP, NR_CellGroupConf sched_ctrl->sched_srs.slot = -1; sched_ctrl->pdcch_cl_adjust = 0; + // Initialize bler_stats + init_bler_stats(&nr_mac->dl_bler, &sched_ctrl->dl_bler_stats, nr_mac->frame); + init_bler_stats(&nr_mac->ul_bler, &sched_ctrl->ul_bler_stats, nr_mac->frame); + // initialize LCID structure seq_arr_init(&sched_ctrl->lc_config, sizeof(nr_lc_config_t)); From 9e4df97d8409173b4c994e6780d94655f9ff65ed Mon Sep 17 00:00:00 2001 From: Thomas Schlichter Date: Wed, 21 Aug 2024 18:08:29 +0200 Subject: [PATCH 19/63] move function add_noise() to file openair1/SIMULATION/TOOLS/phase_noise.c --- openair1/SIMULATION/TOOLS/multipath_channel.c | 25 -------- openair1/SIMULATION/TOOLS/phase_noise.c | 35 +++++++++-- openair1/SIMULATION/TOOLS/sim.h | 62 +++++++++---------- 3 files changed, 60 insertions(+), 62 deletions(-) diff --git a/openair1/SIMULATION/TOOLS/multipath_channel.c b/openair1/SIMULATION/TOOLS/multipath_channel.c index de616bf86c..f17c2166a3 100644 --- a/openair1/SIMULATION/TOOLS/multipath_channel.c +++ b/openair1/SIMULATION/TOOLS/multipath_channel.c @@ -143,31 +143,6 @@ void __attribute__ ((no_sanitize_address)) multipath_channel(channel_desc_t *des #else -void add_noise(c16_t **rxdata, - const double **r_re, - const double **r_im, - const double sigma, - const int length, - const int slot_offset, - const double ts, - const int delay, - const uint16_t pdu_bit_map, - const uint16_t ptrs_bit_map, - const uint8_t nb_antennas_rx) -{ - 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 = r_re[ap][i] + sqrt(sigma / 2) * gaussZiggurat(0.0, 1.0); // convert to fixed point - rxd->i = r_im[ap][i] + sqrt(sigma / 2) * gaussZiggurat(0.0, 1.0); - /* Add phase noise if enabled */ - if (pdu_bit_map & ptrs_bit_map) { - phase_noise(ts, &rxdata[ap][slot_offset + i + delay].r, &rxdata[ap][slot_offset + i + delay].i); - } - } - } -} - 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], diff --git a/openair1/SIMULATION/TOOLS/phase_noise.c b/openair1/SIMULATION/TOOLS/phase_noise.c index 8c2b36dfec..fa406e7ff9 100644 --- a/openair1/SIMULATION/TOOLS/phase_noise.c +++ b/openair1/SIMULATION/TOOLS/phase_noise.c @@ -22,8 +22,14 @@ #include #include "sim.h" - +#define ResolSinCos 100 static uint16_t LUTSin[ResolSinCos+1]; + +/* Initialisation function for SIN table values */ +void InitSinLUT( void ) { + for ( int i=0; i<(ResolSinCos+1); i++ ) { + LUTSin[i] = sin((double)(M_PI*i)/(2*ResolSinCos)) * (1<<14); //Format: Q14 + /* linear phase noise model */ void phase_noise(double ts, int16_t * InRe, int16_t * InIm) { @@ -61,9 +67,28 @@ void phase_noise(double ts, int16_t * InRe, int16_t * InIm) InIm[0]= (int16_t)(y>>14); i++; } -/* Initialisation function for SIN table values */ -void InitSinLUT( void ) { - for ( int i=0; i<(ResolSinCos+1); i++ ) { - LUTSin[i] = sin((double)(M_PI*i)/(2*ResolSinCos)) * (1<<14); //Format: Q14 + +void add_noise(c16_t **rxdata, + const double **r_re, + const double **r_im, + const double sigma, + const int length, + const int slot_offset, + const double ts, + const int delay, + const uint16_t pdu_bit_map, + const uint16_t ptrs_bit_map, + const uint8_t nb_antennas_rx) +{ + 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 = r_re[ap][i] + sqrt(sigma / 2) * gaussZiggurat(0.0, 1.0); // convert to fixed point + rxd->i = r_im[ap][i] + sqrt(sigma / 2) * gaussZiggurat(0.0, 1.0); + /* Add phase noise if enabled */ + if (pdu_bit_map & ptrs_bit_map) { + phase_noise(ts, &rxdata[ap][slot_offset + i + delay].r, &rxdata[ap][slot_offset + i + delay].i); + } + } } } diff --git a/openair1/SIMULATION/TOOLS/sim.h b/openair1/SIMULATION/TOOLS/sim.h index 34cae4ba55..ffd5bef8ed 100644 --- a/openair1/SIMULATION/TOOLS/sim.h +++ b/openair1/SIMULATION/TOOLS/sim.h @@ -379,32 +379,6 @@ void get_cexp_doppler(struct complexd *cexp_doppler, channel_desc_t *chan_desc, */ int random_channel(channel_desc_t *desc, uint8_t abstraction_flag); -/** -\brief Add AWGN noise and phase noise if enabled -\param rxdata output data with noise -\param r_re real part of input data without noise -\param r_im imaginary part of input data without noise -\param sigma noise power -\param length number of samples to apply the noise -\param slot_offset slot offset to start applying the noise -\param ts sampling time -\param delay introduce delay in terms of number of samples -\param pdu_bit_map bitmap indicating presence of optional PDUs -\param ptrs_bit_map -\param nb_antennas_rx number of receive antennas -*/ -void add_noise(c16_t **rxdata, - const double **r_re, - const double **r_im, - const double sigma, - const int length, - const int slot_offset, - const double ts, - const int delay, - const uint16_t pdu_bit_map, - const uint16_t ptrs_bit_map, - const uint8_t nb_antennas_rx); - /**\fn void multipath_channel(channel_desc_t *desc, double tx_sig_re[NB_ANTENNAS_TX], double tx_sig_im[NB_ANTENANS_TX], @@ -569,17 +543,41 @@ int load_channellist(uint8_t nb_tx, uint8_t nb_rx, double sampling_rate, uint64_ double N_RB2sampling_rate(uint16_t N_RB); double N_RB2channel_bandwidth(uint16_t N_RB); +void InitSinLUT(void); + /* Linear phase noise model */ -/*! +/** \brief This function produce phase noise and add to input signal - \param ts Sampling time + \param ts Sampling time \param *Re *Im Real and Imag part of the signal */ -//look-up table for the sine (cosine) function -#define ResolSinCos 100 -void InitSinLUT( void ); -void phase_noise(double ts, int16_t * InRe, int16_t * InIm); +void phase_noise(double ts, int16_t *InRe, int16_t *InIm); +/** +\brief Add AWGN noise and phase noise if enabled +\param rxdata output data with noise +\param r_re real part of input data without noise +\param r_im imaginary part of input data without noise +\param sigma noise power +\param length number of samples to apply the noise +\param slot_offset slot offset to start applying the noise +\param ts sampling time +\param delay introduce delay in terms of number of samples +\param pdu_bit_map bitmap indicating presence of optional PDUs +\param ptrs_bit_map +\param nb_antennas_rx number of receive antennas +*/ +void add_noise(c16_t **rxdata, + const double **r_re, + const double **r_im, + const double sigma, + const int length, + const int slot_offset, + const double ts, + const int delay, + const uint16_t pdu_bit_map, + const uint16_t ptrs_bit_map, + const uint8_t nb_antennas_rx); void do_DL_sig(sim_t *sim, uint16_t subframe, From dd827c7bd8082b673466ae22eaed5a912af7ed58 Mon Sep 17 00:00:00 2001 From: Thomas Schlichter Date: Fri, 23 Aug 2024 10:01:37 +0200 Subject: [PATCH 20/63] refactor (co)sinus calculation out of phase_noise() into openair1/PHY/TOOLS/get_sin_cos.c --- CMakeLists.txt | 1 + openair1/PHY/TOOLS/get_sin_cos.c | 70 +++++++++++++++++++++++++ openair1/PHY/TOOLS/tools_defs.h | 7 +++ openair1/SIMULATION/TOOLS/phase_noise.c | 45 +++------------- openair1/SIMULATION/TOOLS/sim.h | 2 - 5 files changed, 85 insertions(+), 40 deletions(-) create mode 100644 openair1/PHY/TOOLS/get_sin_cos.c diff --git a/CMakeLists.txt b/CMakeLists.txt index b00ca296cd..25bc56b79a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -823,6 +823,7 @@ set(PHY_SRC_COMMON ${OPENAIR1_DIR}/PHY/TOOLS/sqrt.c ${OPENAIR1_DIR}/PHY/TOOLS/lut.c ${OPENAIR1_DIR}/PHY/TOOLS/simde_operations.c + ${OPENAIR1_DIR}/PHY/TOOLS/get_sin_cos.c ) set(PHY_SRC diff --git a/openair1/PHY/TOOLS/get_sin_cos.c b/openair1/PHY/TOOLS/get_sin_cos.c new file mode 100644 index 0000000000..1795b36554 --- /dev/null +++ b/openair1/PHY/TOOLS/get_sin_cos.c @@ -0,0 +1,70 @@ +/* + * 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 "tools_defs.h" + +#define LUT_SIN_RESOL 128 +// look-up table for the sine (cosine) function +static uint16_t LUTSin[LUT_SIN_RESOL + 1]; + +void InitSinLUT(void) +{ + for (int i = 0; i < (LUT_SIN_RESOL + 1); i++) { + LUTSin[i] = round(sin((M_PI * i) / (2 * LUT_SIN_RESOL)) * (1 << 14)); // Format: Q14 + } +} + +// fast calculation of e^{i*2*pi*phase} +// ret.r == cosinus << 14 +// ret.i == sinus << 14 +c16_t get_sin_cos(double phase) +{ + int index; + if (phase < 0) { + index = (int)(-phase * 4 * LUT_SIN_RESOL + 0.5) % (4 * LUT_SIN_RESOL); + index = 4 * LUT_SIN_RESOL - index; + } else { + index = (int)(phase * 4 * LUT_SIN_RESOL + 0.5) % (4 * LUT_SIN_RESOL); + } + + c16_t ret; + if (index < 2 * LUT_SIN_RESOL) { // check for 1st and 2nd Quadrant + if (index < LUT_SIN_RESOL) { // 1st Quadrant + ret.r = LUTSin[LUT_SIN_RESOL - index]; + ret.i = LUTSin[index]; + } else { // 2nd Quadrant + ret.r = -LUTSin[index - LUT_SIN_RESOL]; + ret.i = LUTSin[2 * LUT_SIN_RESOL - index]; + } + } else { // 3rd and 4th Quadrant + if (index < 3 * LUT_SIN_RESOL) { // 3rd Quadrant + ret.r = -LUTSin[3 * LUT_SIN_RESOL - index]; + ret.i = -LUTSin[index - 2 * LUT_SIN_RESOL]; + } else { // 4th Quadrant + ret.r = LUTSin[index - 3 * LUT_SIN_RESOL]; + ret.i = -LUTSin[4 * LUT_SIN_RESOL - index]; + } + } + + return ret; +} diff --git a/openair1/PHY/TOOLS/tools_defs.h b/openair1/PHY/TOOLS/tools_defs.h index 93b07a60de..34d7b73fc7 100644 --- a/openair1/PHY/TOOLS/tools_defs.h +++ b/openair1/PHY/TOOLS/tools_defs.h @@ -792,6 +792,13 @@ double interp(double x, double *xs, double *ys, int count); void simde_mm128_separate_real_imag_parts(simde__m128i *out_re, simde__m128i *out_im, simde__m128i in0, simde__m128i in1); void simde_mm256_separate_real_imag_parts(simde__m256i *out_re, simde__m256i *out_im, simde__m256i in0, simde__m256i in1); +void InitSinLUT(void); + +// fast calculation of e^{i*2*pi*phase} +// ret.r == cosinus << 14 +// ret.i == sinus << 14 +c16_t get_sin_cos(double phase); + #ifdef __cplusplus } #endif diff --git a/openair1/SIMULATION/TOOLS/phase_noise.c b/openair1/SIMULATION/TOOLS/phase_noise.c index fa406e7ff9..5bb6df3b5e 100644 --- a/openair1/SIMULATION/TOOLS/phase_noise.c +++ b/openair1/SIMULATION/TOOLS/phase_noise.c @@ -22,49 +22,18 @@ #include #include "sim.h" -#define ResolSinCos 100 -static uint16_t LUTSin[ResolSinCos+1]; - -/* Initialisation function for SIN table values */ -void InitSinLUT( void ) { - for ( int i=0; i<(ResolSinCos+1); i++ ) { - LUTSin[i] = sin((double)(M_PI*i)/(2*ResolSinCos)) * (1<<14); //Format: Q14 /* linear phase noise model */ -void phase_noise(double ts, int16_t * InRe, int16_t * InIm) +void phase_noise(double ts, int16_t *InRe, int16_t *InIm) { static uint64_t i=0; - int32_t x=0 ,y=0; double fd = 300;//0.01*30000 - int16_t SinValue = 0, CosValue= 0; - double IdxDouble = (double)(i*fd * ts * ResolSinCos * 4); - int16_t IdxModulo = ((int32_t)(IdxDouble>0 ? IdxDouble+0.5 : IdxDouble-0.5)) % (ResolSinCos*4); - IdxModulo = IdxModulo<0 ? IdxModulo+ResolSinCos*4 : IdxModulo; - - if(IdxModulo<2*ResolSinCos) {//< 2 check for 1st and 2nd - if(IdxModulo < ResolSinCos) {// 1st Quadrant - SinValue = LUTSin[IdxModulo]; - CosValue = LUTSin[ResolSinCos-IdxModulo]; - } - else {// 2nd Quadrant - SinValue = LUTSin[2*ResolSinCos-IdxModulo]; - CosValue = -LUTSin[IdxModulo-ResolSinCos]; - } - } - else {// 3rd and 4th Quadrant - if(IdxModulo < 3*ResolSinCos) {// 3rd Quadrant - SinValue = -LUTSin[IdxModulo-2*ResolSinCos]; - CosValue = -LUTSin[3*ResolSinCos-IdxModulo]; - } - else {//4th Quadrant - SinValue = -LUTSin[4*ResolSinCos-IdxModulo]; - CosValue = LUTSin[IdxModulo-3*ResolSinCos]; - } - } - x = ( ((int32_t)InRe[0] * CosValue) - ((int32_t)InIm[0] * SinValue )); - y = ( ((int32_t)InIm[0] * CosValue) + ((int32_t)InRe[0] * SinValue )); - InRe[0]= (int16_t)(x>>14); - InIm[0]= (int16_t)(y>>14); + double phase = (double)(i * fd * ts); + c16_t rot = get_sin_cos(phase); + c16_t val = {.r = *InRe, .i = *InIm}; + val = c16mulShift(val, rot, 14); + *InRe = val.r; + *InIm = val.i; i++; } diff --git a/openair1/SIMULATION/TOOLS/sim.h b/openair1/SIMULATION/TOOLS/sim.h index ffd5bef8ed..28194dcc25 100644 --- a/openair1/SIMULATION/TOOLS/sim.h +++ b/openair1/SIMULATION/TOOLS/sim.h @@ -543,8 +543,6 @@ int load_channellist(uint8_t nb_tx, uint8_t nb_rx, double sampling_rate, uint64_ double N_RB2sampling_rate(uint16_t N_RB); double N_RB2channel_bandwidth(uint16_t N_RB); -void InitSinLUT(void); - /* Linear phase noise model */ /** \brief This function produce phase noise and add to input signal From d16507055f620017dbfa3f9f0a14c1b18117eac9 Mon Sep 17 00:00:00 2001 From: Thomas Schlichter Date: Tue, 10 Sep 2024 17:13:55 +0200 Subject: [PATCH 21/63] NR UE: continuously estimate the frequency offset using PBCH DMRS --- openair1/PHY/NR_UE_TRANSPORT/nr_pbch.c | 18 ++++++++++++++++++ .../NR_UE_TRANSPORT/nr_transport_proto_ue.h | 4 ++++ openair1/SCHED_NR_UE/phy_procedures_nr_ue.c | 5 +++++ 3 files changed, 27 insertions(+) diff --git a/openair1/PHY/NR_UE_TRANSPORT/nr_pbch.c b/openair1/PHY/NR_UE_TRANSPORT/nr_pbch.c index 1de1996126..a570f0a433 100644 --- a/openair1/PHY/NR_UE_TRANSPORT/nr_pbch.c +++ b/openair1/PHY/NR_UE_TRANSPORT/nr_pbch.c @@ -525,3 +525,21 @@ int nr_rx_pbch(PHY_VARS_NR_UE *ue, TracyCZoneEnd(ctx); return 0; } + +double nr_ue_pbch_freq_offset(const NR_DL_FRAME_PARMS *frame_parms, + int estimateSz, + const c16_t dl_ch_estimates[][estimateSz]) +{ + const int i_ssb = frame_parms->ssb_index; + const int symbol_offset = nr_get_ssb_start_symbol(frame_parms, i_ssb) % frame_parms->symbols_per_slot; + const c16_t *dl_ch_est_symb1 = &dl_ch_estimates[0][(symbol_offset + 1) * frame_parms->ofdm_symbol_size]; + const c16_t *dl_ch_est_symb3 = &dl_ch_estimates[0][(symbol_offset + 3) * frame_parms->ofdm_symbol_size]; + const int nb_re = 240; + const c32_t dot_prod_res = dot_product(dl_ch_est_symb1, dl_ch_est_symb3, nb_re, 8); + const double res_phase = atan2(dot_prod_res.i, dot_prod_res.r); + const int samples_per_symbol = frame_parms->ofdm_symbol_size + frame_parms->nb_prefix_samples; + const double t_ofdm = samples_per_symbol / (frame_parms->samples_per_subframe * 1000.0); // symbol duration in sec + const double freq_offset = res_phase / (2 * M_PI * (3 - 1) * t_ofdm); + + return freq_offset; +} diff --git a/openair1/PHY/NR_UE_TRANSPORT/nr_transport_proto_ue.h b/openair1/PHY/NR_UE_TRANSPORT/nr_transport_proto_ue.h index dc075753ef..bf68a69492 100644 --- a/openair1/PHY/NR_UE_TRANSPORT/nr_transport_proto_ue.h +++ b/openair1/PHY/NR_UE_TRANSPORT/nr_transport_proto_ue.h @@ -204,6 +204,10 @@ int nr_rx_pbch(PHY_VARS_NR_UE *ue, int rxdataFSize, const struct complex16 rxdataF[][rxdataFSize]); +double nr_ue_pbch_freq_offset(const NR_DL_FRAME_PARMS *frame_parms, + int estimateSz, + const c16_t dl_ch_estimates[][estimateSz]); + #ifndef modOrder #define modOrder(I_MCS,I_TBS) ((I_MCS-I_TBS)*2+2) // Find modulation order from I_TBS and I_MCS #endif diff --git a/openair1/SCHED_NR_UE/phy_procedures_nr_ue.c b/openair1/SCHED_NR_UE/phy_procedures_nr_ue.c index 37593bb9a7..54788de4d1 100644 --- a/openair1/SCHED_NR_UE/phy_procedures_nr_ue.c +++ b/openair1/SCHED_NR_UE/phy_procedures_nr_ue.c @@ -929,6 +929,11 @@ int pbch_pdcch_processing(PHY_VARS_NR_UE *ue, const UE_nr_rxtx_proc_t *proc, nr_ sampleShift = nr_adjust_synch_ue(fp, ue, gNB_id, fp->ofdm_symbol_size, dl_ch_estimates_time, frame_rx, nr_slot_rx, 16384); } + + if (/*ue->continuous_freq_correction &&*/ pbchSuccess == 0) { + double freq_offset = nr_ue_pbch_freq_offset(fp, estimateSz, dl_ch_estimates); + LOG_I(PHY,"detected frequency offset = %.3f Hz\n", freq_offset); + } } LOG_D(PHY, "Doing N0 measurements in %s\n", __FUNCTION__); nr_ue_rrc_measurements(ue, proc, rxdataF); From 5af4b8920303eecc14f8a3877453858feb5eeeb2 Mon Sep 17 00:00:00 2001 From: Guido Casati Date: Fri, 15 Nov 2024 19:16:51 +0100 Subject: [PATCH 22/63] Add enc/dec function for NAS Service Reject library --- .../NAS/NR_UE/5GS/5GMM/MSG/CMakeLists.txt | 1 + .../NR_UE/5GS/5GMM/MSG/fgmm_service_reject.c | 154 ++++++++++++++++++ .../NR_UE/5GS/5GMM/MSG/fgmm_service_reject.h | 45 +++++ 3 files changed, 200 insertions(+) create mode 100644 openair3/NAS/NR_UE/5GS/5GMM/MSG/fgmm_service_reject.c create mode 100644 openair3/NAS/NR_UE/5GS/5GMM/MSG/fgmm_service_reject.h diff --git a/openair3/NAS/NR_UE/5GS/5GMM/MSG/CMakeLists.txt b/openair3/NAS/NR_UE/5GS/5GMM/MSG/CMakeLists.txt index 6947a3421c..c1a28a18f3 100644 --- a/openair3/NAS/NR_UE/5GS/5GMM/MSG/CMakeLists.txt +++ b/openair3/NAS/NR_UE/5GS/5GMM/MSG/CMakeLists.txt @@ -2,6 +2,7 @@ add_library(fgs_5gmm_lib OBJECT fgmm_lib.c fgs_service_request.c fgmm_service_accept.c + fgmm_service_reject.c RegistrationRequest.c RegistrationAccept.c FGSIdentityResponse.c diff --git a/openair3/NAS/NR_UE/5GS/5GMM/MSG/fgmm_service_reject.c b/openair3/NAS/NR_UE/5GS/5GMM/MSG/fgmm_service_reject.c new file mode 100644 index 0000000000..74175fda36 --- /dev/null +++ b/openair3/NAS/NR_UE/5GS/5GMM/MSG/fgmm_service_reject.c @@ -0,0 +1,154 @@ +/* + * 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 "fgmm_service_reject.h" +#include +#include // For htons and ntohs +#include // For malloc and free +#include "common/utils/ds/byte_array.h" +#include "common/utils/utils.h" +#include "fgmm_lib.h" + +#define SERVICE_REJECT_MIN_LEN 1 // 1 octet + +/** @brief Encode Service Reject (8.2.18 of 3GPP TS 24.501) */ +int encode_fgs_service_reject(byte_array_t *buffer, const fgs_service_reject_msg_t *msg) +{ + if (buffer->len < SERVICE_REJECT_MIN_LEN) { + PRINT_NAS_ERROR("Failed to encode Service Reject: missing Cause IE!\n"); + return -1; + } + + byte_array_t ba = *buffer; // Local copy of buffer + int encoded = 0; + + // 5GMM cause (Mandatory) + buffer->buf[encoded++] = msg->cause; + UPDATE_BYTE_ARRAY(ba, 1); + + // PDU Session Status (Optional) + if (msg->has_psi_status) { + ENCODE_NAS_IE(ba, encode_pdu_session_ie(&ba, IEI_PDU_SESSION_STATUS, msg->psi_status), encoded); + } + + // T3446 Timer (Optional) + if (msg->t3446) { + ENCODE_NAS_IE(ba, encode_gprs_timer_ie(&ba, IEI_T3446_VALUE, msg->t3446), encoded); + } + + // T3448 Timer (Optional) + if (msg->t3448) { + ENCODE_NAS_IE(ba, encode_gprs_timer_ie(&ba, IEI_T3448_VALUE, msg->t3448), encoded); + } + + return encoded; +} + +/** @brief Decode Service Reject (8.2.18 of 3GPP TS 24.501) */ +int decode_fgs_service_reject(fgs_service_reject_msg_t *msg, const byte_array_t *buffer) +{ + if (buffer->len < SERVICE_REJECT_MIN_LEN) { + PRINT_NAS_ERROR("Nothing to decode: missing Cause IE!\n"); + return -1; + } + + byte_array_t ba = *buffer; // Local copy of buffer to be used in the decoding sub-functions + uint32_t decoded = 0; + + // Cause (Mandatory) + msg->cause = buffer->buf[decoded++]; + UPDATE_BYTE_ARRAY(ba, 1); + + // Decode Optional IEs + while (decoded < buffer->len) { + uint8_t iei = buffer->buf[decoded++]; + UPDATE_BYTE_ARRAY(ba, 1); // Skip IEI + + switch (iei) { + case IEI_PDU_SESSION_STATUS: + msg->has_psi_status = true; + DECODE_NAS_IE(ba, decode_pdu_session_ie(msg->psi_status, &ba), decoded); + break; + + case IEI_T3448_VALUE: + msg->t3448 = malloc_or_fail(sizeof(*msg->t3448)); + DECODE_NAS_IE(ba, decode_gprs_timer_ie(msg->t3448, (const byte_array_t *)&ba), decoded); + break; + + case IEI_T3446_VALUE: + msg->t3446 = malloc_or_fail(sizeof(*msg->t3446)); + DECODE_NAS_IE(ba, decode_gprs_timer_ie(msg->t3446, (const byte_array_t *)&ba), decoded); + break; + + case IEI_EAPMSG: + DECODE_NAS_IE(ba, decode_eap_msg_ie((const byte_array_t *)&ba), decoded); + break; + + case IEI_CAG_INFO_LIST: { + int cag_list_len = (buffer->buf[decoded] << 8) | buffer->buf[decoded + 1]; + int cag_len = cag_list_len + 2; // encoded content + 2 octets for the length IE + decoded += cag_len; + UPDATE_BYTE_ARRAY(ba, cag_len); + PRINT_NAS_ERROR("CAG information list IE in NAS Service Reject is not handled\n"); + } break; + + default: + PRINT_NAS_ERROR("Uknown or invalid NAS Service Reject IEI %d\n", iei); + return -1; + } + } + + return decoded; +} + +/** @brief Equality check for Service Reject (enc/dec) */ +bool eq_service_reject(const fgs_service_reject_msg_t *a, const fgs_service_reject_msg_t *b) +{ + _NAS_EQ_CHECK_INT(a->cause, b->cause); + _NAS_EQ_CHECK_INT(a->has_psi_status, b->has_psi_status); + if (a->has_psi_status && b->has_psi_status) { + for (int i = 0; i < MAX_NUM_PSI; i++) + _NAS_EQ_CHECK_INT(a->psi_status[i], b->psi_status[i]); + } + if (a->t3448 != NULL || b->t3448 != NULL) { + if (a->t3448 == NULL || b->t3448 == NULL) { + PRINT_NAS_ERROR("t3448 equality check failed\n"); + return false; + } + if (!eq_gprs_timer(a->t3448, b->t3448)) + return false; + } + if (a->t3446 != NULL || b->t3446 != NULL) { + if (a->t3446 == NULL || b->t3446 == NULL) { + PRINT_NAS_ERROR("t3448 equality check failed\n"); + return false; + } + if (!eq_gprs_timer(a->t3446, b->t3446)) + return false; + } + return true; +} + +void free_fgs_service_reject(fgs_service_reject_msg_t *msg) +{ + free(msg->t3446); + free(msg->t3448); +} diff --git a/openair3/NAS/NR_UE/5GS/5GMM/MSG/fgmm_service_reject.h b/openair3/NAS/NR_UE/5GS/5GMM/MSG/fgmm_service_reject.h new file mode 100644 index 0000000000..61337d8525 --- /dev/null +++ b/openair3/NAS/NR_UE/5GS/5GMM/MSG/fgmm_service_reject.h @@ -0,0 +1,45 @@ +/* + * 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 FGS_service_reject_H +#define FGS_service_reject_H + +#include +#include "fgmm_lib.h" + +typedef struct { + // 5GMM cause (Mandatory) + cause_id_t cause; + // T3346 Value (Optional) + gprs_timer_t *t3446; + // PDU Session Status (Optional) + uint8_t psi_status[MAX_NUM_PSI]; + bool has_psi_status; + // T3448 Value (Optional) + gprs_timer_t *t3448; +} fgs_service_reject_msg_t; + +int decode_fgs_service_reject(fgs_service_reject_msg_t *msg, const byte_array_t *buffer); +int encode_fgs_service_reject(byte_array_t *buffer, const fgs_service_reject_msg_t *msg); +void free_fgs_service_reject(fgs_service_reject_msg_t *msg); +bool eq_service_reject(const fgs_service_reject_msg_t *a, const fgs_service_reject_msg_t *b); + +#endif /* FGS_service_reject_H */ From 2b9782caa1ebbc8136dae8e06ee13b01a4b3a80a Mon Sep 17 00:00:00 2001 From: Guido Casati Date: Fri, 15 Nov 2024 19:18:13 +0100 Subject: [PATCH 23/63] Add unit test for Service Reject --- openair3/NAS/NR_UE/5GS/tests/nas_lib_test.c | 44 +++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/openair3/NAS/NR_UE/5GS/tests/nas_lib_test.c b/openair3/NAS/NR_UE/5GS/tests/nas_lib_test.c index 12e69c1581..a73ee9b2be 100644 --- a/openair3/NAS/NR_UE/5GS/tests/nas_lib_test.c +++ b/openair3/NAS/NR_UE/5GS/tests/nas_lib_test.c @@ -5,6 +5,7 @@ #include "common/utils/ds/byte_array.h" #include "fgs_service_request.h" #include "fgmm_service_accept.h" +#include "fgmm_service_reject.h" #include "nr_nas_msg.h" void exit_function(const char *file, const char *function, const int line, const char *s, const int assert) @@ -127,9 +128,52 @@ static void test_service_accept(void) free_fgs_service_accept(&orig); } +/** @brief Test NAS Service Reject enc/dec */ +static void test_service_reject(void) +{ + // Dummy NAS Service Reject message + fgs_service_reject_msg_t orig = { + .cause = Illegal_UE, + .has_psi_status = true, + .t3446 = malloc_or_fail(sizeof(*orig.t3446)), + .t3448 = malloc_or_fail(sizeof(*orig.t3448)), + }; + orig.psi_status[1] = PDU_SESSION_INACTIVE; // PDU Session 1 + orig.psi_status[2] = PDU_SESSION_ACTIVE; // PDU Session 2 + orig.t3446->value = 10; + orig.t3446->unit = TWO_SECONDS; + orig.t3448->value = 1; + orig.t3448->unit = ONE_MINUTE; + + // Expected encoded data + uint8_t expected_enc[] = {0x03, 0x50, 0x02, 0x04, 0x00, 0x5F, 0x01, 0x0A, 0x6B, 0x01, 0x21}; + + // Buffer + uint8_t buf[64] = {0}; + byte_array_t buffer = {.buf = &buf[0], .len = sizeof(expected_enc)}; + + // Encode NAS Service Reject + int encoded_length = encode_fgs_service_reject(&buffer, &orig); + AssertFatal(encoded_length == sizeofArray(expected_enc), "encode_fgs_service_reject() failed: %d != %ld\n", encoded_length, sizeofArray(expected_enc)); + + // Compare the raw encoded buffer with expected encoded data + AssertFatal(memcmp(buffer.buf, expected_enc, buffer.len) == 0, "Encoding mismatch!\n"); + + // Decode NAS Service Reject + fgs_service_reject_msg_t dec = {0}; + int decoded_length = decode_fgs_service_reject(&dec, &buffer); + AssertFatal(decoded_length >= 0, "decode_fgs_service_reject() failed\n"); + + // Compare original and decoded messages + AssertFatal(eq_service_reject(&orig, &dec), "test_service_reject() failed: original and decoded messages do not match\n"); + free_fgs_service_reject(&dec); + free_fgs_service_reject(&orig); +} + int main() { test_service_request(); test_service_accept(); + test_service_reject(); return 0; } From 312f296b60caa6b9a645c5d62260477a8cc6d061 Mon Sep 17 00:00:00 2001 From: Guido Casati Date: Fri, 4 Apr 2025 11:11:36 +0200 Subject: [PATCH 24/63] Add NAS handler for Service Reject message --- openair3/NAS/NR_UE/5GS/5GMM/MSG/fgmm_lib.h | 2 ++ openair3/NAS/NR_UE/nr_nas_msg.c | 19 +++++++++++++++++++ openair3/NAS/NR_UE/nr_nas_msg.h | 2 ++ 3 files changed, 23 insertions(+) diff --git a/openair3/NAS/NR_UE/5GS/5GMM/MSG/fgmm_lib.h b/openair3/NAS/NR_UE/5GS/5GMM/MSG/fgmm_lib.h index 4acf7368b5..b757620506 100644 --- a/openair3/NAS/NR_UE/5GS/5GMM/MSG/fgmm_lib.h +++ b/openair3/NAS/NR_UE/5GS/5GMM/MSG/fgmm_lib.h @@ -151,6 +151,8 @@ static const text_info_t sa_iei_s[] = {FOREACH_SA_IEI(TO_TEXT)}; typedef enum { FOREACH_CAUSE(TO_ENUM) } cause_id_t; +static const text_info_t fgmm_cause_s[] = {FOREACH_CAUSE(TO_TEXT)}; + /* 10.5.7.3 3GPP TS 24.008 */ typedef struct { uint8_t value; diff --git a/openair3/NAS/NR_UE/nr_nas_msg.c b/openair3/NAS/NR_UE/nr_nas_msg.c index b9b90ab339..941e0d7cdc 100644 --- a/openair3/NAS/NR_UE/nr_nas_msg.c +++ b/openair3/NAS/NR_UE/nr_nas_msg.c @@ -66,6 +66,7 @@ #include "openair2/SDAP/nr_sdap/nr_sdap.h" #include "fgs_nas_utils.h" #include "fgmm_service_accept.h" +#include "fgmm_service_reject.h" #define MAX_NAS_UE 4 @@ -1531,6 +1532,17 @@ static void handle_service_accept(nr_ue_nas_t *nas, const byte_array_t *buffer) print_info(msg.cause->cause, cause_text_info, sizeofArray(cause_text_info))); } +static void handle_service_reject(nr_ue_nas_t *nas, const byte_array_t *buffer) +{ + fgs_service_reject_msg_t msg = {0}; + decode_fgs_service_reject(&msg, buffer); + // Extract timer t3448 in seconds (optional IE) + nas->t3448 = process_gprs_timer(msg.t3448); + // Extract timer t3446 in seconds (optional IE) + nas->t3446 = process_gprs_timer(msg.t3446); + LOG_E(NAS, "Received NAS Service Reject message with cause %s\n", fgmm_cause_s[msg.cause].text); +} + void *nas_nrue(void *args_p) { // Wait for a message or an event @@ -1737,6 +1749,13 @@ void *nas_nrue(void *args_p) handle_service_accept(nas, &buffer); break; } + + case FGS_SERVICE_REJECT: { + byte_array_t buffer = {.buf = pdu_buffer, .len = pdu_length}; + handle_service_reject(nas, &buffer); + break; + } + default: LOG_W(NR_RRC, "unknown message type %d\n", msg_type); break; diff --git a/openair3/NAS/NR_UE/nr_nas_msg.h b/openair3/NAS/NR_UE/nr_nas_msg.h index 3bba6b1cec..c4f1d04e28 100644 --- a/openair3/NAS/NR_UE/nr_nas_msg.h +++ b/openair3/NAS/NR_UE/nr_nas_msg.h @@ -100,6 +100,8 @@ typedef struct { int t3512; // Timer t3448 in seconds (-1 = disabled) int t3448; + // Timer t3446 in seconds (-1 = disabled) + int t3446; } nr_ue_nas_t; nr_ue_nas_t *get_ue_nas_info(module_id_t module_id); From 616568a79de278ccb712b061d0062b663c152e1c Mon Sep 17 00:00:00 2001 From: Thomas Schlichter Date: Tue, 14 Jan 2025 12:46:56 +0100 Subject: [PATCH 25/63] NR UE: add command line parameter --initial-fo to specify known initial frequency offset --- doc/RUNMODEM.md | 1 + executables/nr-uesoftmodem.c | 1 + executables/nr-uesoftmodem.h | 3 +++ openair1/PHY/NR_UE_TRANSPORT/nr_initial_sync.c | 6 +++++- openair1/PHY/defs_nr_UE.h | 2 ++ 5 files changed, 12 insertions(+), 1 deletion(-) diff --git a/doc/RUNMODEM.md b/doc/RUNMODEM.md index b24b4940e5..56359071a8 100644 --- a/doc/RUNMODEM.md +++ b/doc/RUNMODEM.md @@ -137,6 +137,7 @@ Here are some useful command line options for the NR UE: |--------------------------|---------------------------------------------------------------------------------------------------------------| | `--ue-scan-carrier` | Scan for cells in current bandwidth. This option can be used if the SSB position of the gNB is unknown. If multiple cells are detected, the UE will try to connect to the first cell. By default, this option is disabled and the UE attempts to only decode SSB given by `--ssb`. | | `--ue-fo-compensation` | Enables the frequency offset compensation at the UE. Useful when running over the air and/or without an external clock/time source. | +| `--initial-fo` | Sets the known initial frequency offset. Useful especially with large Doppler frequency, e.g. LEO satellite. | | `--usrp-args` | Equivalent to the `sdr_addrs` field in the gNB config file. Used to identify the USRP and set some basic parameters (like the clock source). | | `--clock-source` | Sets the clock source (internal or external). | | `--time-source` | Sets the time source (internal or external). | diff --git a/executables/nr-uesoftmodem.c b/executables/nr-uesoftmodem.c index c0142ce896..9f8b8d8563 100644 --- a/executables/nr-uesoftmodem.c +++ b/executables/nr-uesoftmodem.c @@ -224,6 +224,7 @@ void set_options(int CC_id, PHY_VARS_NR_UE *UE){ UE->chest_freq = nrUE_params.chest_freq; UE->chest_time = nrUE_params.chest_time; UE->no_timing_correction = nrUE_params.no_timing_correction; + UE->initial_fo = nrUE_params.initial_fo; LOG_I(PHY,"Set UE_fo_compensation %d, UE_scan_carrier %d, UE_no_timing_correction %d \n, chest-freq %d, chest-time %d\n", UE->UE_fo_compensation, UE->UE_scan_carrier, UE->no_timing_correction, UE->chest_freq, UE->chest_time); diff --git a/executables/nr-uesoftmodem.h b/executables/nr-uesoftmodem.h index f7e3b42a72..93741a3424 100644 --- a/executables/nr-uesoftmodem.h +++ b/executables/nr-uesoftmodem.h @@ -14,6 +14,7 @@ #define CONFIG_HLP_TIME_SYNC_I "coefficient for Integrating part of time sync PI controller\n" #define CONFIG_HLP_NTN_INIT_TIME_DRIFT "Initial NTN DL time drift (feeder link and service link), given in µs/s\n" #define CONFIG_HLP_AUTONOMOUS_TA "Autonomously update TA based on DL drift (useful if main contribution to DL drift is movement, e.g. LEO satellite)\n" +#define CONFIG_HLP_INITIAL_FO "Initially compensated DL frequency offset (e.g. known Doppler shift in NTN LEO scenario)\n" #define CONFIG_HLP_AGC "Rx Gain control used for UE\n" /***************************************************************************************************************************************/ @@ -65,6 +66,7 @@ {"time-sync-I", CONFIG_HLP_TIME_SYNC_I, 0, .dblptr=&(nrUE_params.time_sync_I), .defdblval=0.0, TYPE_DOUBLE, 0}, \ {"ntn-initial-time-drift", CONFIG_HLP_NTN_INIT_TIME_DRIFT, 0, .dblptr=&(nrUE_params.ntn_init_time_drift), .defdblval=0.0, TYPE_DOUBLE, 0}, \ {"autonomous-ta", CONFIG_HLP_AUTONOMOUS_TA, PARAMFLAG_BOOL, .iptr=&(nrUE_params.autonomous_ta), .defintval=0, TYPE_INT, 0}, \ + {"initial-fo", CONFIG_HLP_INITIAL_FO, 0, .dblptr=&(nrUE_params.initial_fo), .defdblval=0.0, TYPE_DOUBLE, 0}, \ {"agc", CONFIG_HLP_AGC, PARAMFLAG_BOOL, .iptr=&(nrUE_params.agc), .defintval=0, TYPE_INT, 0}, \ } // clang-format on @@ -89,6 +91,7 @@ typedef struct { double time_sync_I; double ntn_init_time_drift; int autonomous_ta; + double initial_fo; int agc; char *usrp_args; char *tx_subdev; diff --git a/openair1/PHY/NR_UE_TRANSPORT/nr_initial_sync.c b/openair1/PHY/NR_UE_TRANSPORT/nr_initial_sync.c index 3143333a3c..dc634584f6 100644 --- a/openair1/PHY/NR_UE_TRANSPORT/nr_initial_sync.c +++ b/openair1/PHY/NR_UE_TRANSPORT/nr_initial_sync.c @@ -199,6 +199,9 @@ void nr_scan_ssb(void *arg) // initial sync performed on two successive frames, if pbch passes on first frame, no need to process second frame // only one frame is used for simulation tools for (int frame_id = 0; frame_id < ssbInfo->nFrames && !ssbInfo->syncRes.cell_detected; frame_id++) { + if (ssbInfo->freqOffset) + compensate_freq_offset(rxdata, fp, ssbInfo->freqOffset, frame_id); + /* process pss search on received buffer */ ssbInfo->syncRes.frame_id = frame_id; int nid2; @@ -268,7 +271,7 @@ void nr_scan_ssb(void *arg) phase_tdd_ncp, ssbInfo->syncRes.rx_offset); #endif - ssbInfo->freqOffset = freq_offset_pss + freq_offset_sss; + ssbInfo->freqOffset += freq_offset_pss + freq_offset_sss; if (ssbInfo->syncRes.cell_detected) { // we got sss channel ssbInfo->syncRes.cell_detected = nr_pbch_detection(ssbInfo->proc, @@ -319,6 +322,7 @@ nr_initial_sync_t nr_initial_sync(UE_nr_rxtx_proc_t *proc, .syncRes.cell_detected = false, .nFrames = n_frames, .foFlag = ue->UE_fo_compensation, + .freqOffset = ue->initial_fo, .targetNidCell = ue->target_Nid_cell}; ssbInfo->rxdata = malloc16_clear(fp->nb_antennas_rx * sizeof(c16_t *)); for (int ant = 0; ant < fp->nb_antennas_rx; ant++) { diff --git a/openair1/PHY/defs_nr_UE.h b/openair1/PHY/defs_nr_UE.h index 2cb2b3d8e6..02fe0d6989 100644 --- a/openair1/PHY/defs_nr_UE.h +++ b/openair1/PHY/defs_nr_UE.h @@ -460,6 +460,8 @@ typedef struct PHY_VARS_NR_UE_s { int64_t max_pos_iir; /// Timing offset IIR filter int max_pos_acc; /// Timing offset accumuluated error for PI filter + double initial_fo; /// initial frequency offset provided by the user + /// Timing Advance updates variables /// Timing advance update computed from the TA command signalled from gNB int timing_advance; From aac4642910dfa3d81997cc040331829157151f92 Mon Sep 17 00:00:00 2001 From: Thomas Schlichter Date: Tue, 14 Jan 2025 13:05:59 +0100 Subject: [PATCH 26/63] NR UE: add support for continuous DL frequency offset compensation (--cont-fo-comp) --- executables/nr-ue.c | 13 +++++++++---- executables/nr-uesoftmodem.c | 1 + executables/nr-uesoftmodem.h | 3 +++ openair1/PHY/MODULATION/slot_fep_nr.c | 7 +++++++ openair1/PHY/defs_nr_UE.h | 2 ++ openair1/PHY/nr_phy_common/inc/nr_phy_common.h | 2 ++ openair1/PHY/nr_phy_common/inc/nr_ue_phy_meas.h | 1 + openair1/PHY/nr_phy_common/src/nr_phy_common.c | 13 +++++++++++++ openair1/SCHED_NR_UE/phy_procedures_nr_ue.c | 6 ++++-- 9 files changed, 42 insertions(+), 6 deletions(-) diff --git a/executables/nr-ue.c b/executables/nr-ue.c index d34e710cec..35977941e3 100644 --- a/executables/nr-ue.c +++ b/executables/nr-ue.c @@ -436,9 +436,13 @@ static void UE_synch(void *arg) { ((ret.rx_offset << 1) / fp->samples_per_subframe * fp->slots_per_subframe) + round((float)((ret.rx_offset << 1) % fp->samples_per_subframe) / fp->samples_per_slot0); - // rerun with new cell parameters and frequency-offset - // todo: the freq_offset computed on DL shall be scaled before being applied to UL - nr_rf_card_config_freq(cfg0, ul_carrier, dl_carrier, freq_offset); + if (get_nrUE_params()->cont_fo_comp) { + UE->freq_offset = freq_offset; + } else { + // rerun with new cell parameters and frequency-offset + nr_rf_card_config_freq(cfg0, ul_carrier, dl_carrier, freq_offset); + UE->rfdevice.trx_set_freq_func(&UE->rfdevice, cfg0); + } if (get_nrUE_params()->agc) { nr_ue_adjust_rx_gain(UE, cfg0, UE->adjust_rxgain); @@ -452,7 +456,6 @@ static void UE_synch(void *arg) { cfg0->rx_freq[0], cfg0->tx_freq[0]); - UE->rfdevice.trx_set_freq_func(&UE->rfdevice, cfg0); UE->is_synchronized = 1; } else { int gain_change = 0; @@ -913,6 +916,8 @@ void *UE_thread(void *arg) if (usrp_tx_thread == 1) UE->rfdevice.trx_write_init(&UE->rfdevice); + InitSinLUT(); + notifiedFIFO_t nf; initNotifiedFIFO(&nf); diff --git a/executables/nr-uesoftmodem.c b/executables/nr-uesoftmodem.c index 9f8b8d8563..485fcb8943 100644 --- a/executables/nr-uesoftmodem.c +++ b/executables/nr-uesoftmodem.c @@ -225,6 +225,7 @@ void set_options(int CC_id, PHY_VARS_NR_UE *UE){ UE->chest_time = nrUE_params.chest_time; UE->no_timing_correction = nrUE_params.no_timing_correction; UE->initial_fo = nrUE_params.initial_fo; + UE->cont_fo_comp = nrUE_params.cont_fo_comp; LOG_I(PHY,"Set UE_fo_compensation %d, UE_scan_carrier %d, UE_no_timing_correction %d \n, chest-freq %d, chest-time %d\n", UE->UE_fo_compensation, UE->UE_scan_carrier, UE->no_timing_correction, UE->chest_freq, UE->chest_time); diff --git a/executables/nr-uesoftmodem.h b/executables/nr-uesoftmodem.h index 93741a3424..5210db5362 100644 --- a/executables/nr-uesoftmodem.h +++ b/executables/nr-uesoftmodem.h @@ -15,6 +15,7 @@ #define CONFIG_HLP_NTN_INIT_TIME_DRIFT "Initial NTN DL time drift (feeder link and service link), given in µs/s\n" #define CONFIG_HLP_AUTONOMOUS_TA "Autonomously update TA based on DL drift (useful if main contribution to DL drift is movement, e.g. LEO satellite)\n" #define CONFIG_HLP_INITIAL_FO "Initially compensated DL frequency offset (e.g. known Doppler shift in NTN LEO scenario)\n" +#define CONFIG_HLP_CONT_FO_COMP "Enable continuous frequency offset estimation and compensation\n" #define CONFIG_HLP_AGC "Rx Gain control used for UE\n" /***************************************************************************************************************************************/ @@ -67,6 +68,7 @@ {"ntn-initial-time-drift", CONFIG_HLP_NTN_INIT_TIME_DRIFT, 0, .dblptr=&(nrUE_params.ntn_init_time_drift), .defdblval=0.0, TYPE_DOUBLE, 0}, \ {"autonomous-ta", CONFIG_HLP_AUTONOMOUS_TA, PARAMFLAG_BOOL, .iptr=&(nrUE_params.autonomous_ta), .defintval=0, TYPE_INT, 0}, \ {"initial-fo", CONFIG_HLP_INITIAL_FO, 0, .dblptr=&(nrUE_params.initial_fo), .defdblval=0.0, TYPE_DOUBLE, 0}, \ + {"cont-fo-comp", CONFIG_HLP_CONT_FO_COMP, PARAMFLAG_BOOL, .iptr=&(nrUE_params.cont_fo_comp), .defintval=0, TYPE_INT, 0}, \ {"agc", CONFIG_HLP_AGC, PARAMFLAG_BOOL, .iptr=&(nrUE_params.agc), .defintval=0, TYPE_INT, 0}, \ } // clang-format on @@ -92,6 +94,7 @@ typedef struct { double ntn_init_time_drift; int autonomous_ta; double initial_fo; + int cont_fo_comp; int agc; char *usrp_args; char *tx_subdev; diff --git a/openair1/PHY/MODULATION/slot_fep_nr.c b/openair1/PHY/MODULATION/slot_fep_nr.c index bd61d100f8..201101a39a 100644 --- a/openair1/PHY/MODULATION/slot_fep_nr.c +++ b/openair1/PHY/MODULATION/slot_fep_nr.c @@ -25,6 +25,7 @@ #include "nr_modulation.h" #include "PHY/LTE_ESTIMATION/lte_estimation.h" #include "PHY/NR_UE_ESTIMATION/nr_estimation.h" +#include "PHY/nr_phy_common/inc/nr_phy_common.h" #include //#define DEBUG_FEP @@ -108,6 +109,12 @@ int nr_slot_fep(PHY_VARS_NR_UE *ue, rxdata_ptr = (int16_t *)tmp_dft_in; } + if (ue && ue->cont_fo_comp) { + start_meas_nr_ue_phy(ue, RX_FO_COMPENSATION_STATS); + nr_fo_compensation(ue->freq_offset, frame_parms->samples_per_subframe, rx_offset, (c16_t *)rxdata_ptr, frame_parms->ofdm_symbol_size); + stop_meas_nr_ue_phy(ue, RX_FO_COMPENSATION_STATS); + } + if (ue) start_meas_nr_ue_phy(ue, RX_DFT_STATS); diff --git a/openair1/PHY/defs_nr_UE.h b/openair1/PHY/defs_nr_UE.h index 02fe0d6989..1b0bfe9f89 100644 --- a/openair1/PHY/defs_nr_UE.h +++ b/openair1/PHY/defs_nr_UE.h @@ -461,6 +461,8 @@ typedef struct PHY_VARS_NR_UE_s { int max_pos_acc; /// Timing offset accumuluated error for PI filter double initial_fo; /// initial frequency offset provided by the user + int cont_fo_comp; /// flag enabling the continuous frequency offset estimation and compensation + double freq_offset; /// currently compensated frequency offset /// Timing Advance updates variables /// Timing advance update computed from the TA command signalled from gNB diff --git a/openair1/PHY/nr_phy_common/inc/nr_phy_common.h b/openair1/PHY/nr_phy_common/inc/nr_phy_common.h index 9ae785d8ff..225388cb0d 100644 --- a/openair1/PHY/nr_phy_common/inc/nr_phy_common.h +++ b/openair1/PHY/nr_phy_common/inc/nr_phy_common.h @@ -80,4 +80,6 @@ void nr_256qam_llr(int32_t *rxdataF_comp, int32_t *ch_mag, int32_t *ch_mag2, int void freq2time(uint16_t ofdm_symbol_size, int16_t *freq_signal, int16_t *time_signal); void nr_est_delay(int ofdm_symbol_size, const c16_t *ls_est, c16_t *ch_estimates_time, delay_t *delay); unsigned int nr_get_tx_amp(int power_dBm, int power_max_dBm, int total_nb_rb, int nb_rb); +void nr_fo_compensation(double fo_Hz, int samples_per_ms, int sample_offset, c16_t *rxdata_ptr, int size); + #endif diff --git a/openair1/PHY/nr_phy_common/inc/nr_ue_phy_meas.h b/openair1/PHY/nr_phy_common/inc/nr_ue_phy_meas.h index 09e66e8adf..9f91274bae 100644 --- a/openair1/PHY/nr_phy_common/inc/nr_ue_phy_meas.h +++ b/openair1/PHY/nr_phy_common/inc/nr_ue_phy_meas.h @@ -28,6 +28,7 @@ #define FOREACH_NR_PHY_CPU_MEAS(FN) \ FN(RX_PDSCH_STATS),\ FN(DLSCH_RX_PDCCH_STATS),\ + FN(RX_FO_COMPENSATION_STATS),\ FN(RX_DFT_STATS),\ FN(DLSCH_CHANNEL_ESTIMATION_STATS),\ FN(DLSCH_DECODING_STATS),\ diff --git a/openair1/PHY/nr_phy_common/src/nr_phy_common.c b/openair1/PHY/nr_phy_common/src/nr_phy_common.c index e1580ca9f0..c363037850 100644 --- a/openair1/PHY/nr_phy_common/src/nr_phy_common.c +++ b/openair1/PHY/nr_phy_common/src/nr_phy_common.c @@ -453,3 +453,16 @@ unsigned int nr_get_tx_amp(int power_dBm, int power_max_dBm, int total_nb_rb, in } return (0); } + +void nr_fo_compensation(double fo_Hz, int samples_per_ms, int sample_offset, c16_t *rxdata_ptr, int size) +{ + const double phase_inc = -fo_Hz / (samples_per_ms * 1000); + double phase = sample_offset * phase_inc; + phase -= (int)phase; + for (int i = 0; i < size; i++) { + c16_t rot = get_sin_cos(phase); + *rxdata_ptr = c16mulShift(*rxdata_ptr, rot, 14); + rxdata_ptr++; + phase += phase_inc; + } +} diff --git a/openair1/SCHED_NR_UE/phy_procedures_nr_ue.c b/openair1/SCHED_NR_UE/phy_procedures_nr_ue.c index 54788de4d1..c7bfbb0f3b 100644 --- a/openair1/SCHED_NR_UE/phy_procedures_nr_ue.c +++ b/openair1/SCHED_NR_UE/phy_procedures_nr_ue.c @@ -930,9 +930,11 @@ int pbch_pdcch_processing(PHY_VARS_NR_UE *ue, const UE_nr_rxtx_proc_t *proc, nr_ nr_adjust_synch_ue(fp, ue, gNB_id, fp->ofdm_symbol_size, dl_ch_estimates_time, frame_rx, nr_slot_rx, 16384); } - if (/*ue->continuous_freq_correction &&*/ pbchSuccess == 0) { + if (get_nrUE_params()->cont_fo_comp && pbchSuccess == 0) { double freq_offset = nr_ue_pbch_freq_offset(fp, estimateSz, dl_ch_estimates); - LOG_I(PHY,"detected frequency offset = %.3f Hz\n", freq_offset); + LOG_D(PHY,"compensated frequency offset = %.3f Hz, detected residual frequency offset = %.3f Hz\n", ue->freq_offset, freq_offset); + + ue->freq_offset += freq_offset; } } LOG_D(PHY, "Doing N0 measurements in %s\n", __FUNCTION__); From 84712f5f923ff5db5645a716c3c8e36f85e3407f Mon Sep 17 00:00:00 2001 From: Thomas Schlichter Date: Tue, 14 Jan 2025 13:21:58 +0100 Subject: [PATCH 27/63] NR UE: add support for continuous UL frequency offset pre-compensation Depending on the main source of frequency offset, pre-compensate the UL frequency offset (FO) accordingly. Therefore modify the flag `--cont-fo-comp` to accept an integer value, where - `1` specifies, that the main FO contribution comes from the accuracy of the local oscillator (LO) - `2` specifies, that the main FO contribution comes from Doppler shift --- doc/RUNMODEM.md | 3 ++- executables/nr-ue.c | 10 ++++++++++ executables/nr-uesoftmodem.h | 4 ++-- 3 files changed, 14 insertions(+), 3 deletions(-) diff --git a/doc/RUNMODEM.md b/doc/RUNMODEM.md index 56359071a8..fc35146e45 100644 --- a/doc/RUNMODEM.md +++ b/doc/RUNMODEM.md @@ -136,7 +136,8 @@ Here are some useful command line options for the NR UE: | Parameter | Description | |--------------------------|---------------------------------------------------------------------------------------------------------------| | `--ue-scan-carrier` | Scan for cells in current bandwidth. This option can be used if the SSB position of the gNB is unknown. If multiple cells are detected, the UE will try to connect to the first cell. By default, this option is disabled and the UE attempts to only decode SSB given by `--ssb`. | -| `--ue-fo-compensation` | Enables the frequency offset compensation at the UE. Useful when running over the air and/or without an external clock/time source. | +| `--ue-fo-compensation` | Enables the initial frequency offset compensation at the UE. Useful when running over the air and/or without an external clock/time source. | +| `--cont-fo-comp` | Enables the continuous frequency offset (FO) estimation and compensation. Parameter value `1` specifies that the main FO contribution comes from the local oscillator's (LO) accuracy. Parameter value `2` specifies that the main FO contribution comes from Doppler shift. | | `--initial-fo` | Sets the known initial frequency offset. Useful especially with large Doppler frequency, e.g. LEO satellite. | | `--usrp-args` | Equivalent to the `sdr_addrs` field in the gNB config file. Used to identify the USRP and set some basic parameters (like the clock source). | | `--clock-source` | Sets the clock source (internal or external). | diff --git a/executables/nr-ue.c b/executables/nr-ue.c index 35977941e3..d172255c2f 100644 --- a/executables/nr-ue.c +++ b/executables/nr-ue.c @@ -41,6 +41,7 @@ #include "instrumentation.h" #include "common/utils/threadPool/notified_fifo.h" #include "position_interface.h" +#include "nr_phy_common.h" /* * NR SLOT PROCESSING SEQUENCE @@ -554,6 +555,15 @@ static void RU_write(nr_rxtx_thread_data_t *rxtxD, bool sl_tx_action) writeBlockSize -= dummyBlockSize; } + // pre-compensate UL frequency offset + if (flags != TX_BURST_INVALID && get_nrUE_params()->cont_fo_comp) { + double ul_freq_offset = -UE->freq_offset * ((double)fp->ul_CarrierFreq / (double)fp->dl_CarrierFreq); + if (get_nrUE_params()->cont_fo_comp == 2) // different from LO frequency error compensation, Doppler UL pre-compensation has to be negative + ul_freq_offset = -ul_freq_offset; + for (int i = 0; i < fp->nb_antennas_tx; i++) + nr_fo_compensation(ul_freq_offset, fp->samples_per_subframe, writeTimestamp, txp[i], writeBlockSize); + } + int tmp = openair0_write_reorder(&UE->rfdevice, writeTimestamp, txp, writeBlockSize, fp->nb_antennas_tx, flags); AssertFatal(tmp == writeBlockSize, ""); } diff --git a/executables/nr-uesoftmodem.h b/executables/nr-uesoftmodem.h index 5210db5362..cc134eaecd 100644 --- a/executables/nr-uesoftmodem.h +++ b/executables/nr-uesoftmodem.h @@ -15,7 +15,7 @@ #define CONFIG_HLP_NTN_INIT_TIME_DRIFT "Initial NTN DL time drift (feeder link and service link), given in µs/s\n" #define CONFIG_HLP_AUTONOMOUS_TA "Autonomously update TA based on DL drift (useful if main contribution to DL drift is movement, e.g. LEO satellite)\n" #define CONFIG_HLP_INITIAL_FO "Initially compensated DL frequency offset (e.g. known Doppler shift in NTN LEO scenario)\n" -#define CONFIG_HLP_CONT_FO_COMP "Enable continuous frequency offset estimation and compensation\n" +#define CONFIG_HLP_CONT_FO_COMP "Enable continuous frequency offset (FO) estimation and compensation and specify main FO source (1 = local oscillator, 2 = Doppler shift)\n" #define CONFIG_HLP_AGC "Rx Gain control used for UE\n" /***************************************************************************************************************************************/ @@ -68,7 +68,7 @@ {"ntn-initial-time-drift", CONFIG_HLP_NTN_INIT_TIME_DRIFT, 0, .dblptr=&(nrUE_params.ntn_init_time_drift), .defdblval=0.0, TYPE_DOUBLE, 0}, \ {"autonomous-ta", CONFIG_HLP_AUTONOMOUS_TA, PARAMFLAG_BOOL, .iptr=&(nrUE_params.autonomous_ta), .defintval=0, TYPE_INT, 0}, \ {"initial-fo", CONFIG_HLP_INITIAL_FO, 0, .dblptr=&(nrUE_params.initial_fo), .defdblval=0.0, TYPE_DOUBLE, 0}, \ - {"cont-fo-comp", CONFIG_HLP_CONT_FO_COMP, PARAMFLAG_BOOL, .iptr=&(nrUE_params.cont_fo_comp), .defintval=0, TYPE_INT, 0}, \ + {"cont-fo-comp", CONFIG_HLP_CONT_FO_COMP, 0, .iptr=&(nrUE_params.cont_fo_comp), .defintval=0, TYPE_INT, 0}, \ {"agc", CONFIG_HLP_AGC, PARAMFLAG_BOOL, .iptr=&(nrUE_params.agc), .defintval=0, TYPE_INT, 0}, \ } // clang-format on From 18469302d879688a0889706dbce0a57ac7befd8b Mon Sep 17 00:00:00 2001 From: Thomas Schlichter Date: Tue, 14 Jan 2025 13:29:29 +0100 Subject: [PATCH 28/63] NR UE: add PI controller to continuous frequency offset compensation --- doc/RUNMODEM.md | 2 ++ executables/nr-uesoftmodem.h | 6 ++++++ openair1/PHY/defs_nr_UE.h | 1 + openair1/SCHED_NR_UE/phy_procedures_nr_ue.c | 8 ++++++-- 4 files changed, 15 insertions(+), 2 deletions(-) diff --git a/doc/RUNMODEM.md b/doc/RUNMODEM.md index fc35146e45..eb47a8f753 100644 --- a/doc/RUNMODEM.md +++ b/doc/RUNMODEM.md @@ -139,6 +139,8 @@ Here are some useful command line options for the NR UE: | `--ue-fo-compensation` | Enables the initial frequency offset compensation at the UE. Useful when running over the air and/or without an external clock/time source. | | `--cont-fo-comp` | Enables the continuous frequency offset (FO) estimation and compensation. Parameter value `1` specifies that the main FO contribution comes from the local oscillator's (LO) accuracy. Parameter value `2` specifies that the main FO contribution comes from Doppler shift. | | `--initial-fo` | Sets the known initial frequency offset. Useful especially with large Doppler frequency, e.g. LEO satellite. | +| `--freq-sync-P` | Sets the coefficient for the Proportional part of the PI-controller for the continuous frequency offset compensation. Default value 0.01. | +| `--freq-sync-I` | Sets the coefficient for the Integrating part of the PI-controller for the continuous frequency offset compensation. Default value 0.001. | | `--usrp-args` | Equivalent to the `sdr_addrs` field in the gNB config file. Used to identify the USRP and set some basic parameters (like the clock source). | | `--clock-source` | Sets the clock source (internal or external). | | `--time-source` | Sets the time source (internal or external). | diff --git a/executables/nr-uesoftmodem.h b/executables/nr-uesoftmodem.h index cc134eaecd..2b2387915a 100644 --- a/executables/nr-uesoftmodem.h +++ b/executables/nr-uesoftmodem.h @@ -15,6 +15,8 @@ #define CONFIG_HLP_NTN_INIT_TIME_DRIFT "Initial NTN DL time drift (feeder link and service link), given in µs/s\n" #define CONFIG_HLP_AUTONOMOUS_TA "Autonomously update TA based on DL drift (useful if main contribution to DL drift is movement, e.g. LEO satellite)\n" #define CONFIG_HLP_INITIAL_FO "Initially compensated DL frequency offset (e.g. known Doppler shift in NTN LEO scenario)\n" +#define CONFIG_HLP_FREQ_SYNC_P "coefficient for Proportional part of continuous frequency offset compensation PI controller\n" +#define CONFIG_HLP_FREQ_SYNC_I "coefficient for Integrating part of continuous frequency offset compensation PI controller\n" #define CONFIG_HLP_CONT_FO_COMP "Enable continuous frequency offset (FO) estimation and compensation and specify main FO source (1 = local oscillator, 2 = Doppler shift)\n" #define CONFIG_HLP_AGC "Rx Gain control used for UE\n" @@ -68,6 +70,8 @@ {"ntn-initial-time-drift", CONFIG_HLP_NTN_INIT_TIME_DRIFT, 0, .dblptr=&(nrUE_params.ntn_init_time_drift), .defdblval=0.0, TYPE_DOUBLE, 0}, \ {"autonomous-ta", CONFIG_HLP_AUTONOMOUS_TA, PARAMFLAG_BOOL, .iptr=&(nrUE_params.autonomous_ta), .defintval=0, TYPE_INT, 0}, \ {"initial-fo", CONFIG_HLP_INITIAL_FO, 0, .dblptr=&(nrUE_params.initial_fo), .defdblval=0.0, TYPE_DOUBLE, 0}, \ + {"freq-sync-P", CONFIG_HLP_FREQ_SYNC_P, 0, .dblptr=&(nrUE_params.freq_sync_P), .defdblval=0.01, TYPE_DOUBLE, 0}, \ + {"freq-sync-I", CONFIG_HLP_FREQ_SYNC_I, 0, .dblptr=&(nrUE_params.freq_sync_I), .defdblval=0.001, TYPE_DOUBLE, 0}, \ {"cont-fo-comp", CONFIG_HLP_CONT_FO_COMP, 0, .iptr=&(nrUE_params.cont_fo_comp), .defintval=0, TYPE_INT, 0}, \ {"agc", CONFIG_HLP_AGC, PARAMFLAG_BOOL, .iptr=&(nrUE_params.agc), .defintval=0, TYPE_INT, 0}, \ } @@ -94,6 +98,8 @@ typedef struct { double ntn_init_time_drift; int autonomous_ta; double initial_fo; + double freq_sync_P; + double freq_sync_I; int cont_fo_comp; int agc; char *usrp_args; diff --git a/openair1/PHY/defs_nr_UE.h b/openair1/PHY/defs_nr_UE.h index 1b0bfe9f89..b1f75e8bb4 100644 --- a/openair1/PHY/defs_nr_UE.h +++ b/openair1/PHY/defs_nr_UE.h @@ -463,6 +463,7 @@ typedef struct PHY_VARS_NR_UE_s { double initial_fo; /// initial frequency offset provided by the user int cont_fo_comp; /// flag enabling the continuous frequency offset estimation and compensation double freq_offset; /// currently compensated frequency offset + double freq_off_acc; /// accumulated frequency error (for PI controller) /// Timing Advance updates variables /// Timing advance update computed from the TA command signalled from gNB diff --git a/openair1/SCHED_NR_UE/phy_procedures_nr_ue.c b/openair1/SCHED_NR_UE/phy_procedures_nr_ue.c index c7bfbb0f3b..936adb6d6c 100644 --- a/openair1/SCHED_NR_UE/phy_procedures_nr_ue.c +++ b/openair1/SCHED_NR_UE/phy_procedures_nr_ue.c @@ -932,9 +932,13 @@ int pbch_pdcch_processing(PHY_VARS_NR_UE *ue, const UE_nr_rxtx_proc_t *proc, nr_ if (get_nrUE_params()->cont_fo_comp && pbchSuccess == 0) { double freq_offset = nr_ue_pbch_freq_offset(fp, estimateSz, dl_ch_estimates); - LOG_D(PHY,"compensated frequency offset = %.3f Hz, detected residual frequency offset = %.3f Hz\n", ue->freq_offset, freq_offset); + LOG_D(PHY,"compensated frequency offset = %.3f Hz, detected residual frequency offset = %.3f Hz, accumulated frequency offset = %.3f Hz\n", ue->freq_offset, freq_offset, ue->freq_off_acc); - ue->freq_offset += freq_offset; + // PI controller + const double PID_P = get_nrUE_params()->freq_sync_P; + const double PID_I = get_nrUE_params()->freq_sync_I; + ue->freq_offset += freq_offset * PID_P + ue->freq_off_acc * PID_I; + ue->freq_off_acc += freq_offset; } } LOG_D(PHY, "Doing N0 measurements in %s\n", __FUNCTION__); From d6585f37502080de9fed069ccef2c0f4d9935ee4 Mon Sep 17 00:00:00 2001 From: Thomas Schlichter Date: Tue, 14 Jan 2025 13:31:21 +0100 Subject: [PATCH 29/63] rfsimulator: enable and correct NTN LEO doppler simulation --- openair1/SIMULATION/TOOLS/random_channel.c | 7 +- openair1/SIMULATION/TOOLS/sim.h | 10 ++ radio/rfsimulator/apply_channelmod.c | 110 ++++++++++++++------- radio/rfsimulator/simulator.c | 2 + 4 files changed, 93 insertions(+), 36 deletions(-) diff --git a/openair1/SIMULATION/TOOLS/random_channel.c b/openair1/SIMULATION/TOOLS/random_channel.c index b9ae9e3fff..d13cfd8921 100644 --- a/openair1/SIMULATION/TOOLS/random_channel.c +++ b/openair1/SIMULATION/TOOLS/random_channel.c @@ -1663,7 +1663,7 @@ channel_desc_t *new_channel_desc_scm(uint8_t nb_tx, maxDoppler = 0; chan_desc->sat_height = 600e3; chan_desc->enable_dynamic_delay = true; - chan_desc->enable_dynamic_Doppler = false; // TODO: requires UE to support continuous Doppler estimation, compensation and pre-compensation + chan_desc->enable_dynamic_Doppler = true; fill_channel_desc(chan_desc,nb_tx, nb_rx, nb_taps, @@ -1762,6 +1762,11 @@ void set_channeldesc_name(channel_desc_t *cdesc,char *modelname) { cdesc->model_name=strdup(modelname); } +void set_channeldesc_direction(channel_desc_t *cdesc, bool is_uplink) +{ + cdesc->is_uplink = is_uplink; +} + #ifdef DEBUG_CH_POWER double accumulated_ch_power = 0; int ch_power_count = 0; diff --git a/openair1/SIMULATION/TOOLS/sim.h b/openair1/SIMULATION/TOOLS/sim.h index 28194dcc25..98b4fb26c6 100644 --- a/openair1/SIMULATION/TOOLS/sim.h +++ b/openair1/SIMULATION/TOOLS/sim.h @@ -139,6 +139,8 @@ typedef struct { float Doppler_phase_inc; /// current Doppler phase of each RX antenna (for continuous phase from one block to the next) float *Doppler_phase_cur; + /// flag indicating if channel direction is UL or DL + bool is_uplink; } channel_desc_t; typedef struct { @@ -358,6 +360,7 @@ void free_channel_desc_scm(channel_desc_t *ch); \param module_id identifies the channel model. should be define as a macro in simu.h */ void set_channeldesc_owner(channel_desc_t *cdesc, channelmod_moduleid_t module_id); + /** \brief This function set a model name to a model descriptor, can be later used to identify a allocated channel model \param cdesc points to the model descriptor @@ -365,6 +368,13 @@ void set_channeldesc_owner(channel_desc_t *cdesc, channelmod_moduleid_t module_i */ void set_channeldesc_name(channel_desc_t *cdesc,char *modelname); +/** +\brief This function set a channel model direction to either uplink or downlink +\param cdesc points to the model descriptor +\param is_uplink indicates if this channel is applied in uplink (not downlink) direction +*/ +void set_channeldesc_direction(channel_desc_t *cdesc, bool is_uplink); + /** \fn void get_cexp_doppler(struct complexd *cexp_doppler, channel_desc_t *chan_desc, const uint32_t length) \brief This routine generates the complex exponential to apply the Doppler shift \param cexp_doppler Output with the complex exponential of Doppler shift diff --git a/radio/rfsimulator/apply_channelmod.c b/radio/rfsimulator/apply_channelmod.c index 60d3afbe83..efeb03b25c 100644 --- a/radio/rfsimulator/apply_channelmod.c +++ b/radio/rfsimulator/apply_channelmod.c @@ -50,6 +50,8 @@ void rxAddInput(const c16_t *input_sig, uint32_t CirSize, bool add_noise) { + static uint64_t last_TS = 0; + if ((channelDesc->sat_height > 0) && (channelDesc->enable_dynamic_delay || channelDesc->enable_dynamic_Doppler)) { // model for transparent satellite on circular orbit /* assumptions: - The Earth is spherical, the ground station is static, and that the Earth does not rotate. @@ -82,42 +84,76 @@ void rxAddInput(const c16_t *input_sig, const double pos_ue_y = 0; const double pos_ue_z = radius_earth; - const double dir_sat_ue_x = pos_ue_x - pos_sat_x; - const double dir_sat_ue_y = pos_ue_y - pos_sat_y; - const double dir_sat_ue_z = pos_ue_z - pos_sat_z; - - const double dist_sat_ue = sqrt(dir_sat_ue_x * dir_sat_ue_x + dir_sat_ue_y * dir_sat_ue_y + dir_sat_ue_z * dir_sat_ue_z); - const double vel_sat_ue = (vel_sat_x * dir_sat_ue_x + vel_sat_y * dir_sat_ue_y + vel_sat_z * dir_sat_ue_z) / dist_sat_ue; - - double dist_gnb_sat = 0; - double vel_gnb_sat = 0; - if (channelDesc->modelid == SAT_LEO_TRANS) { - const double pos_gnb_x = 0; - const double pos_gnb_y = 0; - const double pos_gnb_z = radius_earth; - - const double dir_gnb_sat_x = pos_sat_x - pos_gnb_x; - const double dir_gnb_sat_y = pos_sat_y - pos_gnb_y; - const double dir_gnb_sat_z = pos_sat_z - pos_gnb_z; - - dist_gnb_sat = sqrt(dir_gnb_sat_x * dir_gnb_sat_x + dir_gnb_sat_y * dir_gnb_sat_y + dir_gnb_sat_z * dir_gnb_sat_z); - vel_gnb_sat = (vel_sat_x * dir_gnb_sat_x + vel_sat_y * dir_gnb_sat_y + vel_sat_z * dir_gnb_sat_z) / dist_gnb_sat; - } - const double c = 299792458; // m/s - const double prop_delay = (dist_gnb_sat + dist_sat_ue) / c; - if (channelDesc->enable_dynamic_delay) - channelDesc->channel_offset = prop_delay * channelDesc->sampling_rate; - const double f_Doppler_shift_sat_ue = (vel_sat_ue / (c - vel_sat_ue)) * channelDesc->center_freq; - const double f_Doppler_shift_gnb_sat = (-vel_gnb_sat / c) * channelDesc->center_freq; - if (channelDesc->enable_dynamic_Doppler) - channelDesc->Doppler_phase_inc = 2 * M_PI * (f_Doppler_shift_gnb_sat + f_Doppler_shift_sat_ue) / channelDesc->sampling_rate; + if (channelDesc->is_uplink) { + const double dir_ue_sat_x = pos_sat_x - pos_ue_x; + const double dir_ue_sat_y = pos_sat_y - pos_ue_y; + const double dir_ue_sat_z = pos_sat_z - pos_ue_z; - static uint64_t last_TS = 0; - if(TS - last_TS >= channelDesc->sampling_rate) { - last_TS = TS; - LOG_I(HW, "Satellite orbit: time %f s, Doppler: gNB->SAT %f kHz, SAT->UE %f kHz, Delay %f ms\n", t, f_Doppler_shift_gnb_sat / 1000, f_Doppler_shift_sat_ue / 1000, prop_delay * 1000); + const double dist_ue_sat = sqrt(dir_ue_sat_x * dir_ue_sat_x + dir_ue_sat_y * dir_ue_sat_y + dir_ue_sat_z * dir_ue_sat_z); + const double vel_ue_sat = (vel_sat_x * dir_ue_sat_x + vel_sat_y * dir_ue_sat_y + vel_sat_z * dir_ue_sat_z) / dist_ue_sat; + + double dist_sat_gnb = 0; + if (channelDesc->modelid == SAT_LEO_TRANS) { + const double pos_gnb_x = 0; + const double pos_gnb_y = 0; + const double pos_gnb_z = radius_earth; + + const double dir_sat_gnb_x = pos_gnb_x - pos_sat_x; + const double dir_sat_gnb_y = pos_gnb_y - pos_sat_y; + const double dir_sat_gnb_z = pos_gnb_z - pos_sat_z; + + dist_sat_gnb = sqrt(dir_sat_gnb_x * dir_sat_gnb_x + dir_sat_gnb_y * dir_sat_gnb_y + dir_sat_gnb_z * dir_sat_gnb_z); + } + + const double prop_delay = (dist_ue_sat + dist_sat_gnb) / c; + if (channelDesc->enable_dynamic_delay) + channelDesc->channel_offset = prop_delay * channelDesc->sampling_rate; + + const double f_Doppler_shift_ue_sat = (-vel_ue_sat / c) * channelDesc->center_freq; + if (channelDesc->enable_dynamic_Doppler) + channelDesc->Doppler_phase_inc = 2 * M_PI * f_Doppler_shift_ue_sat / channelDesc->sampling_rate; + + if(TS - last_TS >= channelDesc->sampling_rate) { + last_TS = TS; + LOG_D(HW, "Satellite orbit: time %f s, Position = (%f, %f, %f), Velocity = (%f, %f, %f)\n", t, pos_sat_x, pos_sat_y, pos_sat_z, vel_sat_x, vel_sat_y, vel_sat_z); + LOG_D(HW, "Uplink delay %f ms, Doppler shift UE->SAT %f kHz\n", prop_delay * 1000, f_Doppler_shift_ue_sat / 1000); + } + } else { + const double dir_sat_ue_x = pos_ue_x - pos_sat_x; + const double dir_sat_ue_y = pos_ue_y - pos_sat_y; + const double dir_sat_ue_z = pos_ue_z - pos_sat_z; + + const double dist_sat_ue = sqrt(dir_sat_ue_x * dir_sat_ue_x + dir_sat_ue_y * dir_sat_ue_y + dir_sat_ue_z * dir_sat_ue_z); + const double vel_sat_ue = (vel_sat_x * dir_sat_ue_x + vel_sat_y * dir_sat_ue_y + vel_sat_z * dir_sat_ue_z) / dist_sat_ue; + + double dist_gnb_sat = 0; + if (channelDesc->modelid == SAT_LEO_TRANS) { + const double pos_gnb_x = 0; + const double pos_gnb_y = 0; + const double pos_gnb_z = radius_earth; + + const double dir_gnb_sat_x = pos_sat_x - pos_gnb_x; + const double dir_gnb_sat_y = pos_sat_y - pos_gnb_y; + const double dir_gnb_sat_z = pos_sat_z - pos_gnb_z; + + dist_gnb_sat = sqrt(dir_gnb_sat_x * dir_gnb_sat_x + dir_gnb_sat_y * dir_gnb_sat_y + dir_gnb_sat_z * dir_gnb_sat_z); + } + + const double prop_delay = (dist_gnb_sat + dist_sat_ue) / c; + if (channelDesc->enable_dynamic_delay) + channelDesc->channel_offset = prop_delay * channelDesc->sampling_rate; + + const double f_Doppler_shift_sat_ue = (vel_sat_ue / (c - vel_sat_ue)) * channelDesc->center_freq; + if (channelDesc->enable_dynamic_Doppler) + channelDesc->Doppler_phase_inc = 2 * M_PI * f_Doppler_shift_sat_ue / channelDesc->sampling_rate; + + if(TS - last_TS >= channelDesc->sampling_rate) { + last_TS = TS; + LOG_D(HW, "Satellite orbit: time %f s, Position = (%f, %f, %f), Velocity = (%f, %f, %f)\n", t, pos_sat_x, pos_sat_y, pos_sat_z, vel_sat_x, vel_sat_y, vel_sat_z); + LOG_D(HW, "Downlink delay %f ms, Doppler shift SAT->UE %f kHz\n", prop_delay * 1000, f_Doppler_shift_sat_ue / 1000); + } } } @@ -131,6 +167,8 @@ void rxAddInput(const c16_t *input_sig, const double noise_per_sample = add_noise ? pow(10,channelDesc->noise_power_dB/10.0) * 256 : 0; const uint64_t dd = channelDesc->channel_offset; const int nbTx=channelDesc->nb_tx; + double Doppler_phase_cur = channelDesc->Doppler_phase_cur[rxAnt]; + Doppler_phase_cur -= 2 * M_PI * round(Doppler_phase_cur / (2 * M_PI)); for (int i=0; iDoppler_phase_cur[rxAnt] * I); + double complex out = in * cexp(Doppler_phase_cur * I); rx_tmp.r = creal(out); rx_tmp.i = cimag(out); - channelDesc->Doppler_phase_cur[rxAnt] += channelDesc->Doppler_phase_inc; + Doppler_phase_cur += channelDesc->Doppler_phase_inc; } out_ptr->r += rx_tmp.r * pathLossLinear + noise_per_sample * gaussZiggurat(0.0, 1.0); @@ -172,6 +210,8 @@ void rxAddInput(const c16_t *input_sig, out_ptr++; } + channelDesc->Doppler_phase_cur[rxAnt] = Doppler_phase_cur; + if ( (TS*nbTx)%CirSize+nbSamples <= CirSize ) // Cast to a wrong type for compatibility ! LOG_D(HW,"Input power %f, output power: %f, channel path loss %f, noise coeff: %f \n", diff --git a/radio/rfsimulator/simulator.c b/radio/rfsimulator/simulator.c index ca01a1768c..8070bf837e 100644 --- a/radio/rfsimulator/simulator.c +++ b/radio/rfsimulator/simulator.c @@ -274,6 +274,7 @@ static int allocCirBuf(rfsimulator_state_t *bridge, int sock) } set_channeldesc_owner(ptr->channel_model, RFSIMU_MODULEID); + set_channeldesc_direction(ptr->channel_model, bridge->role == SIMU_ROLE_SERVER); random_channel(ptr->channel_model,false); LOG_I(HW, "Random channel %s in rfsimulator activated\n", modelname); } @@ -467,6 +468,7 @@ static int rfsimu_setchanmod_cmd(char *buff, int debug, telnet_printfunc_t prnt, t->chan_pathloss, 0); // noise_power set_channeldesc_owner(newmodel, RFSIMU_MODULEID); + set_channeldesc_direction(newmodel, t->role == SIMU_ROLE_SERVER); set_channeldesc_name(newmodel,modelname); random_channel(newmodel,false); channel_desc_t *oldmodel=b->channel_model; From 29ed144764f5f1248ee61b239581816422df517a Mon Sep 17 00:00:00 2001 From: Thomas Schlichter Date: Fri, 17 Jan 2025 09:02:52 +0000 Subject: [PATCH 30/63] use SIMD to speed up nr_fo_compensation() --- .../PHY/nr_phy_common/src/nr_phy_common.c | 32 ++++++++++++++++--- 1 file changed, 28 insertions(+), 4 deletions(-) diff --git a/openair1/PHY/nr_phy_common/src/nr_phy_common.c b/openair1/PHY/nr_phy_common/src/nr_phy_common.c index c363037850..700721e69f 100644 --- a/openair1/PHY/nr_phy_common/src/nr_phy_common.c +++ b/openair1/PHY/nr_phy_common/src/nr_phy_common.c @@ -459,10 +459,34 @@ void nr_fo_compensation(double fo_Hz, int samples_per_ms, int sample_offset, c16 const double phase_inc = -fo_Hz / (samples_per_ms * 1000); double phase = sample_offset * phase_inc; phase -= (int)phase; - for (int i = 0; i < size; i++) { - c16_t rot = get_sin_cos(phase); - *rxdata_ptr = c16mulShift(*rxdata_ptr, rot, 14); - rxdata_ptr++; +#if 1 + // The bottleneck is the calculation of the complex rotation values using get_sin_cos(). + // This code path does not compute these values for the complete OFDM symbol, but only for a smaller CHUNK size. + // After applying the rotation to a CHUNK size of the output, these rotation values are efficiently rotated further by `rot_vec`. + // Unfortunately, this propagates small errors from one chunk to the next. + // Therefore, there is a tradeoff between speed (better with small CHUNK sizes) and accuracy (better with large CHUNK sizes). +#define CHUNK 128 + c16_t rot[CHUNK] __attribute__((aligned(32))); + for (int i = 0; i < CHUNK; i++) { + rot[i] = get_sin_cos(phase); phase += phase_inc; } + const c16_t rot_vec = get_sin_cos(CHUNK * phase_inc); + while (size > CHUNK) { + mult_complex_vectors(rxdata_ptr, rot, rxdata_ptr, CHUNK, 14); + rxdata_ptr += CHUNK; + rotate_cpx_vector(rot, &rot_vec, rot, CHUNK, 14); + size -= CHUNK; + } + mult_complex_vectors(rxdata_ptr, rot, rxdata_ptr, size, 14); +#else + // This code path computes the complex rotation values for the complete OFDM symbol using get_sin_cos(). + // This is more accurate, but also slower than the code path above. + c16_t rot[size] __attribute__((aligned(32))); + for (int i = 0; i < size; i++) { + rot[i] = get_sin_cos(phase); + phase += phase_inc; + } + mult_complex_vectors(rxdata_ptr, rot, rxdata_ptr, size, 14); +#endif } From 4e3a7ad56e500e4a5a62f91cf1b59ccafd1afcda Mon Sep 17 00:00:00 2001 From: Thomas Schlichter Date: Mon, 17 Feb 2025 13:18:26 +0000 Subject: [PATCH 31/63] Extend NTN LEO example in RUNMODEM.md to include continuous Doppler compensation --- doc/RUNMODEM.md | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/doc/RUNMODEM.md b/doc/RUNMODEM.md index eb47a8f753..f2e71390c7 100644 --- a/doc/RUNMODEM.md +++ b/doc/RUNMODEM.md @@ -141,6 +141,10 @@ Here are some useful command line options for the NR UE: | `--initial-fo` | Sets the known initial frequency offset. Useful especially with large Doppler frequency, e.g. LEO satellite. | | `--freq-sync-P` | Sets the coefficient for the Proportional part of the PI-controller for the continuous frequency offset compensation. Default value 0.01. | | `--freq-sync-I` | Sets the coefficient for the Integrating part of the PI-controller for the continuous frequency offset compensation. Default value 0.001. | +| `--ntn-initial-time-drift` | Sets the initial NTN DL time drift (feeder link and service link), given in µs/s. | +| `--autonomous-ta` | Enables the autonomous TA update, based on DL drift (useful if main contribution to DL drift is movement, e.g. LEO satellite). | +| `--time-sync-P` | Sets the coefficient for the Proportional part of the PI-controller for the time synchronization. Default value 0.5. | +| `--time-sync-I` | Sets the coefficient for the Integrating part of the PI-controller for the time synchronization. Default value 0.0. | | `--usrp-args` | Equivalent to the `sdr_addrs` field in the gNB config file. Used to identify the USRP and set some basic parameters (like the clock source). | | `--clock-source` | Sets the clock source (internal or external). | | `--time-source` | Sets the time source (internal or external). | @@ -353,10 +357,16 @@ For LEO satellite scenarios, the parameter `--ntn-initial-time-drift` must be pr This parameter provides the drift rate of the complete DL timing (incl. feeder link and service link) in µs/s. Also, to perform an autonomous TA update based on the DL drift, the boolean parameter `--autonomous-ta` should be added in case of a LEO satellite scenario. +For LEO satellite scenario we assume the LO to be very accurate and the main FO contribution comes from Doppler shift. +Therefore, we use the command line parameter `--cont-fo-comp 2` to continuously compensate the DL Doppler and pre-compensate the UL Doppler. +The initial Doppler frequency offset must be provided via command line with the parameter `--initial-fo`. + +For other information on optional NR UE command line options, please refer [here](#optional-nr-ue-command-line-options). + So an example NR UE command for FDD, 5MHz BW, 15 kHz SCS, transparent LEO satellite 5G NR NTN is this: ``` cd cmake_targets -sudo ./ran_build/build/nr-uesoftmodem -O ../targets/PROJECTS/GENERIC-NR-5GC/CONF/ue.conf --band 254 -C 2488400000 --CO -873500000 -r 25 --numerology 0 --ssb 60 --rfsim --rfsimulator.prop_delay 20 --rfsimulator.options chanmod --time-sync-I 0.1 --ntn-initial-time-drift -46 --autonomous-ta +sudo ./ran_build/build/nr-uesoftmodem -O ../targets/PROJECTS/GENERIC-NR-5GC/CONF/ue.conf --band 254 -C 2488400000 --CO -873500000 -r 25 --numerology 0 --ssb 60 --rfsim --rfsimulator.prop_delay 20 --rfsimulator.options chanmod --time-sync-I 0.1 --ntn-initial-time-drift -46 --autonomous-ta --initial-fo 57340 --cont-fo-comp 2 ``` # Specific OAI modes From a9132a2f797554f990771cc4ed487e6e384dcada Mon Sep 17 00:00:00 2001 From: Thomas Schlichter Date: Thu, 27 Mar 2025 09:22:22 +0000 Subject: [PATCH 32/63] NR UE: in nr_adjust_synch_ue() extend the peak search to +/- cyclic prefix size esp. for LEO NTN scenarios the timing drifts during initial sync, which is mostly compensated with --ntn-initial-time-drift. But there might be a residual time offset, and by extending the peak search window we make this more tolerant. --- openair1/PHY/NR_UE_ESTIMATION/nr_adjust_synch_ue.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 5b91a068ed..7bcd8264f8 100644 --- a/openair1/PHY/NR_UE_ESTIMATION/nr_adjust_synch_ue.c +++ b/openair1/PHY/NR_UE_ESTIMATION/nr_adjust_synch_ue.c @@ -47,7 +47,7 @@ int nr_adjust_synch_ue(NR_DL_FRAME_PARMS *frame_parms, 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/2; i < frame_parms->nb_prefix_samples/2; i++) { + for (int i = -frame_parms->nb_prefix_samples; i < frame_parms->nb_prefix_samples; i++) { int temp = 0; int j = (i < 0) ? (i + frame_parms->ofdm_symbol_size) : i; From 997790f6562df1fba617209d5c65816803ede3ee Mon Sep 17 00:00:00 2001 From: Thomas Schlichter Date: Thu, 3 Apr 2025 07:31:24 +0000 Subject: [PATCH 33/63] NR UE: remove unused nr_slot_tx_offset `nr_slot_tx_offset` is unused since commit f3e60a141747ac12e2c9058e35e64fc5315d2911 --- executables/nr-ue.c | 4 ---- openair1/PHY/defs_nr_UE.h | 4 ---- 2 files changed, 8 deletions(-) diff --git a/executables/nr-ue.c b/executables/nr-ue.c index d172255c2f..d9266a3664 100644 --- a/executables/nr-ue.c +++ b/executables/nr-ue.c @@ -964,11 +964,9 @@ void *UE_thread(void *arg) int ntn_koffset = 0; int duration_rx_to_tx = NR_UE_CAPABILITY_SLOT_RX_TO_TX; - int nr_slot_tx_offset = 0; bool update_ntn_system_information = false; while (!oai_exit) { - nr_slot_tx_offset = 0; if (syncRunning) { notifiedFIFO_elt_t *res = pollNotifiedFIFO(&nf); @@ -1096,7 +1094,6 @@ void *UE_thread(void *arg) if (UE->ntn_config_message->update) { UE->ntn_config_message->update = false; update_ntn_system_information = true; - nr_slot_tx_offset = UE->ntn_config_message->ntn_config_params.cell_specific_k_offset << fp->numerology_index; } int slot_nr = absolute_slot % nb_slot_frame; @@ -1200,7 +1197,6 @@ void *UE_thread(void *arg) curMsgTx->writeBlockSize = writeBlockSize; curMsgTx->proc.timestamp_tx = writeTimestamp; curMsgTx->UE = UE; - curMsgTx->proc.nr_slot_tx_offset = nr_slot_tx_offset; int slot = curMsgTx->proc.nr_slot_tx; int slot_and_frame = slot + curMsgTx->proc.frame_tx * UE->frame_parms.slots_per_frame; diff --git a/openair1/PHY/defs_nr_UE.h b/openair1/PHY/defs_nr_UE.h index b1f75e8bb4..f9349f7cd0 100644 --- a/openair1/PHY/defs_nr_UE.h +++ b/openair1/PHY/defs_nr_UE.h @@ -530,10 +530,6 @@ typedef struct { int gNB_id; /// NR slot index within frame_tx [0 .. slots_per_frame - 1] to act upon for transmission int nr_slot_tx; - /// NR slot index tx offset to resume - /// in case of NTN, tx_offset can be changed dynamically via SIB19 - /// we need to notify the right tx thread slot based on TX offset change - int nr_slot_tx_offset; int rx_slot_type; /// NR slot index within frame_rx [0 .. slots_per_frame - 1] to act upon for transmission int nr_slot_rx; From 6e06c817f435b3ebe3d0be04973044d2d086c0f1 Mon Sep 17 00:00:00 2001 From: Thomas Schlichter Date: Thu, 3 Apr 2025 07:52:38 +0000 Subject: [PATCH 34/63] NR UE: remove now unnecessary local variable update_ntn_system_information directly use `UE->ntn_config_message->update` instead --- executables/nr-ue.c | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/executables/nr-ue.c b/executables/nr-ue.c index d9266a3664..ff6da29a0e 100644 --- a/executables/nr-ue.c +++ b/executables/nr-ue.c @@ -874,13 +874,12 @@ static inline int get_readBlockSize(uint16_t slot, NR_DL_FRAME_PARMS *fp) { static inline void apply_ntn_config(PHY_VARS_NR_UE *UE, NR_DL_FRAME_PARMS *fp, int slot_nr, - bool *update_ntn_system_information, int *duration_rx_to_tx, int *timing_advance, int *ntn_koffset) { - if (*update_ntn_system_information) { - *update_ntn_system_information = false; + if (UE->ntn_config_message->update) { + UE->ntn_config_message->update = false; double total_ta_ms = UE->ntn_config_message->ntn_config_params.ntn_total_time_advance_ms; UE->timing_advance = fp->samples_per_subframe * total_ta_ms; @@ -962,9 +961,7 @@ void *UE_thread(void *arg) double ntn_init_time_drift = get_nrUE_params()->ntn_init_time_drift; int ntn_koffset = 0; - int duration_rx_to_tx = NR_UE_CAPABILITY_SLOT_RX_TO_TX; - bool update_ntn_system_information = false; while (!oai_exit) { if (syncRunning) { @@ -1091,11 +1088,6 @@ void *UE_thread(void *arg) absolute_slot++; TracyCFrameMark; - if (UE->ntn_config_message->update) { - UE->ntn_config_message->update = false; - update_ntn_system_information = true; - } - int slot_nr = absolute_slot % nb_slot_frame; nr_rxtx_thread_data_t curMsg = {0}; curMsg.UE=UE; @@ -1185,8 +1177,8 @@ void *UE_thread(void *arg) shiftForNextFrame = ret; pushNotifiedFIFO(&UE->dl_actors[curMsg.proc.nr_slot_rx % NUM_DL_ACTORS].fifo, newRx); - // apply new duration next run to avoid thread dead lock - apply_ntn_config(UE, fp, slot_nr, &update_ntn_system_information, &duration_rx_to_tx, &timing_advance, &ntn_koffset); + // apply new NTN timing information + apply_ntn_config(UE, fp, slot_nr, &duration_rx_to_tx, &timing_advance, &ntn_koffset); // Start TX slot processing here. It runs in parallel with RX slot processing // in current code, DURATION_RX_TO_TX constant is the limit to get UL data to encode from a RX slot From 67482f5d85d5f18eb909bc8404150710895649be Mon Sep 17 00:00:00 2001 From: Thomas Schlichter Date: Thu, 10 Apr 2025 14:34:39 +0000 Subject: [PATCH 35/63] CI: add NR UE conf file including NTN LEO channel model --- ci-scripts/conf_files/nrue.uicc.ntn-leo.conf | 35 ++++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 ci-scripts/conf_files/nrue.uicc.ntn-leo.conf diff --git a/ci-scripts/conf_files/nrue.uicc.ntn-leo.conf b/ci-scripts/conf_files/nrue.uicc.ntn-leo.conf new file mode 100644 index 0000000000..d8ad297180 --- /dev/null +++ b/ci-scripts/conf_files/nrue.uicc.ntn-leo.conf @@ -0,0 +1,35 @@ +uicc0 = { + imsi = "208990100001100"; + key = "fec86ba6eb707ed08905757b1bb44b8f"; + opc= "C42449363BBAD02B66D16BC975D77CC1"; + dnn= "oai"; + nssai_sst=1; +} + +position0 = { + x = 0.0; + y = 0.0; + z = 6377900.0; +} + +thread-pool = "-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1" + +#/* configuration for channel modelisation */ +#/* To be included in main config file when */ +#/* channel modelisation is used (rfsimulator with chanmod options enabled) */ +channelmod = { + max_chan = 10; + modellist = "modellist_rfsimu_1"; + modellist_rfsimu_1 = ( + { # DL, modify on UE side + model_name = "rfsimu_channel_enB0" + type = "SAT_LEO_TRANS"; + noise_power_dB = -100; + }, + { # UL, modify on gNB side + model_name = "rfsimu_channel_ue0" + type = "SAT_LEO_TRANS"; + noise_power_dB = -100; + } + ); +}; From 2ab747eaa6569d6e230b5222a0030cb6dbca6690 Mon Sep 17 00:00:00 2001 From: Robert Schmidt Date: Fri, 11 Apr 2025 14:16:46 +0200 Subject: [PATCH 36/63] CI CoreNetwork: define function getName() Some functionality (e.g., in cls_oaicitest.py) uses cn.getName(). Define that function to avoid assertions. --- ci-scripts/cls_corenetwork.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/ci-scripts/cls_corenetwork.py b/ci-scripts/cls_corenetwork.py index 7bb508d86b..e4dc3bf11a 100644 --- a/ci-scripts/cls_corenetwork.py +++ b/ci-scripts/cls_corenetwork.py @@ -162,6 +162,9 @@ class CoreNetwork: def getCmdPrefix(self): return self._cmd_prefix or "" + def getName(self): + return self._cn_name + def getHost(self): return self._host From 4b23c7ea65859c244dd2b86ede4354bdf91b2fff Mon Sep 17 00:00:00 2001 From: francescomani Date: Wed, 18 Dec 2024 18:00:07 +0100 Subject: [PATCH 37/63] F1 transmission from CU to DU of SIB2 --- openair2/GNB_APP/gnb_config.c | 24 ++++++++++++ openair2/GNB_APP/gnb_paramdef.h | 5 +++ .../LAYER2/NR_MAC_gNB/mac_rrc_dl_handler.c | 4 +- openair2/RRC/NR/MESSAGES/asn1_msg.c | 39 +++---------------- openair2/RRC/NR/MESSAGES/asn1_msg.h | 2 +- openair2/RRC/NR/nr_rrc_defs.h | 17 ++++---- openair2/RRC/NR/rrc_gNB.c | 14 ++++--- openair2/RRC/NR/rrc_gNB_du.c | 11 ++++++ 8 files changed, 64 insertions(+), 52 deletions(-) diff --git a/openair2/GNB_APP/gnb_config.c b/openair2/GNB_APP/gnb_config.c index deb8835157..34fd7a7f2b 100644 --- a/openair2/GNB_APP/gnb_config.c +++ b/openair2/GNB_APP/gnb_config.c @@ -1846,6 +1846,28 @@ static int sort_neighbour_cell_config_by_cell_id(const void *a, const void *b) return 0; } +static seq_arr_t *fill_cu_sibs(paramdef_t *GNBparamarray) +{ + if (!config_isparamset(GNBparamarray, GNB_CU_SIBS_IDX)) + return NULL; + + seq_arr_t *SIBs = malloc(sizeof(seq_arr_t)); + seq_arr_init(SIBs, sizeof(rrc_SIBs_t)); + for (int i = 0; i < GNBparamarray[GNB_CU_SIBS_IDX].numelt; i++) { + int sib_value = GNBparamarray[GNB_CU_SIBS_IDX].iptr[i]; + // SIB10, SIB12, SIB13, SIB14, SIB15, SIB17, SIB18, SIB19 and SIB20 are generated by the DU + // all the rest in the CU + AssertFatal((sib_value > 1 && sib_value < 10) || sib_value == 11 || sib_value == 16, + "SIB%d is not a CU SIB, to be configured in DU section\n", + sib_value); + rrc_SIBs_t *cu_sib = calloc_or_fail(1, sizeof(*cu_sib)); + cu_sib->SIB_type = sib_value; + LOG_I(GNB_APP, "activate SIB%d at CU\n", sib_value); + seq_arr_push_back(SIBs, cu_sib, sizeof(rrc_SIBs_t)); + } + return SIBs; +} + static void fill_neighbour_cell_configuration(uint8_t gnb_idx, gNB_RRC_INST *rrc) { char gnbpath[MAX_OPTNAME_SIZE + 8]; @@ -2085,6 +2107,8 @@ gNB_RRC_INST *RCconfig_NRRRC() fill_measurement_configuration(k, rrc); + rrc->SIBs = fill_cu_sibs(GNBParamList.paramarray[i]); + paramdef_t PLMNParams[] = GNBPLMNPARAMS_DESC; paramlist_def_t PLMNParamList = {GNB_CONFIG_STRING_PLMN_LIST, NULL, 0}; diff --git a/openair2/GNB_APP/gnb_paramdef.h b/openair2/GNB_APP/gnb_paramdef.h index 7f7fccbd8f..e8d36c55ae 100644 --- a/openair2/GNB_APP/gnb_paramdef.h +++ b/openair2/GNB_APP/gnb_paramdef.h @@ -139,6 +139,7 @@ typedef enum { #define GNB_CONFIG_STRING_NUM_UL_HARQPROCESSES "num_ulharq" #define GNB_CONFIG_STRING_BEAM_WEIGHTS_LIST "beam_weights" #define GNB_CONFIG_STRING_UESS_AGG_LEVEL_LIST "uess_agg_levels" +#define GNB_CONFIG_STRING_CU_SIB_LIST "cu_sibs" #define GNB_CONFIG_HLP_STRING_ENABLE_SDAP "enable the SDAP layer\n" #define GNB_CONFIG_HLP_FORCE256QAMOFF "suppress activation of 256 QAM despite UE support" @@ -150,6 +151,7 @@ typedef enum { #define GNB_CONFIG_HLP_NUM_DL_HARQ "Set Num DL harq processes. Valid values 2,4,6,8,10,12,16,32. Default 16" #define GNB_CONFIG_HLP_NUM_UL_HARQ "Set Num UL harq processes. Valid values 16,32. Default 16" #define GNB_CONFIG_HLP_UESS_AGG_LEVEL_LIST "List of aggregation levels with number of candidates per level. Element 0 - aggregation level 1" +#define GNB_CONFIG_HLP_CU_SIBS "List of CU generated SIBs to be transmitted" /*-----------------------------------------------------------------------------------------------------------------------------------------*/ /* cell configuration parameters */ @@ -195,6 +197,7 @@ typedef enum { {GNB_CONFIG_STRING_BEAM_WEIGHTS_LIST, NULL, 0, .iptr=NULL, .defintarrayval=0, TYPE_INTARRAY, 0}, \ {GNB_CONFIG_STRING_UESS_AGG_LEVEL_LIST, \ GNB_CONFIG_HLP_UESS_AGG_LEVEL_LIST, 0, .iptr=NULL, .defintarrayval=NULL, TYPE_INTARRAY, 0}, \ +{GNB_CONFIG_STRING_CU_SIB_LIST, GNB_CONFIG_HLP_CU_SIBS, 0, .iptr=NULL, .defintarrayval=0, TYPE_INTARRAY, 0}, \ } // clang-format on @@ -236,6 +239,7 @@ typedef enum { #define GNB_NUM_UL_HARQ_IDX 34 #define GNB_BEAMWEIGHTS_IDX 35 #define GNB_UESS_AGG_LEVEL_LIST_IDX 36 +#define GNB_CU_SIBS_IDX 37 #define TRACKING_AREA_CODE_OKRANGE {0x0001,0xFFFD} #define NUM_DL_HARQ_OKVALUES {2,4,6,8,10,12,16,32} @@ -279,6 +283,7 @@ typedef enum { { .s1 = { config_check_intval, NUM_UL_HARQ_OKVALUES,2 } }, \ { .s5 = { NULL } }, \ { .s5 = { NULL } }, \ + { .s5 = { NULL } }, \ } /*-------------------------------------------------------------------------------------------------------------------------------------------------*/ diff --git a/openair2/LAYER2/NR_MAC_gNB/mac_rrc_dl_handler.c b/openair2/LAYER2/NR_MAC_gNB/mac_rrc_dl_handler.c index 25a182dddb..9d19fe1c65 100644 --- a/openair2/LAYER2/NR_MAC_gNB/mac_rrc_dl_handler.c +++ b/openair2/LAYER2/NR_MAC_gNB/mac_rrc_dl_handler.c @@ -165,7 +165,9 @@ void f1_setup_response(const f1ap_setup_resp_t *resp) AssertFatal(check_plmn_identity(&du_cell->plmn, &cu_cell->plmn), "PLMN mismatch\n"); AssertFatal(du_cell->nr_pci == cu_cell->nrpci, "PCI mismatch: DU %d vs CU %d\n", du_cell->nr_pci, cu_cell->nrpci); - AssertFatal(cu_cell->num_SI == 0, "handling of CU-provided SIBs: not implemented\n"); + for (int i = 0; i < cu_cell->num_SI; i++) { + // TODO implement handling reception of SIBs in DU + } mac->f1_config.setup_resp = malloc(sizeof(*mac->f1_config.setup_resp)); AssertFatal(mac->f1_config.setup_resp != NULL, "out of memory\n"); diff --git a/openair2/RRC/NR/MESSAGES/asn1_msg.c b/openair2/RRC/NR/MESSAGES/asn1_msg.c index d0224300e2..0b811b11ad 100644 --- a/openair2/RRC/NR/MESSAGES/asn1_msg.c +++ b/openair2/RRC/NR/MESSAGES/asn1_msg.c @@ -197,21 +197,8 @@ int xer_nr_sprint(char *string, size_t string_size, asn_TYPE_descriptor_t *td, v //------------------------------------------------------------------------------ -int do_SIB23_NR(rrc_gNB_carrier_data_t *carrier) +int do_SIB2_NR(uint8_t **msg_SIB2) { - NR_BCCH_DL_SCH_Message_t *sib_message = calloc(1,sizeof(NR_BCCH_DL_SCH_Message_t)); - sib_message->message.present = NR_BCCH_DL_SCH_MessageType_PR_c1; - sib_message->message.choice.c1 = calloc(1,sizeof(struct NR_BCCH_DL_SCH_MessageType__c1)); - sib_message->message.choice.c1->present = NR_BCCH_DL_SCH_MessageType__c1_PR_systemInformation; - sib_message->message.choice.c1->choice.systemInformation = calloc(1,sizeof(struct NR_SystemInformation)); - - struct NR_SystemInformation *sib = sib_message->message.choice.c1->choice.systemInformation; - sib->criticalExtensions.present = NR_SystemInformation__criticalExtensions_PR_systemInformation; - sib->criticalExtensions.choice.systemInformation = calloc(1, sizeof(struct NR_SystemInformation_IEs)); - - struct NR_SystemInformation_IEs *ies = sib->criticalExtensions.choice.systemInformation; - SystemInformation_IEs__sib_TypeAndInfo__Member *sib2_ie = calloc(1, sizeof(*sib2_ie)); - sib2_ie->present = NR_SystemInformation_IEs__sib_TypeAndInfo__Member_PR_sib2; NR_SIB2_t *sib2 = calloc(1, sizeof(*sib2)); sib2->cellReselectionInfoCommon.q_Hyst = NR_SIB2__cellReselectionInfoCommon__q_Hyst_dB0; struct NR_SIB2__cellReselectionInfoCommon__speedStateReselectionPars *speed = calloc(1, sizeof(*speed)); @@ -240,27 +227,11 @@ int do_SIB23_NR(rrc_gNB_carrier_data_t *carrier) ssbmtc->periodicityAndOffset.present = NR_SSB_MTC__periodicityAndOffset_PR_sf20; ssbmtc->periodicityAndOffset.choice.sf20 = 0; sib2->intraFreqCellReselectionInfo.smtc = ssbmtc; - sib2_ie->choice.sib2 = sib2; - asn1cSeqAdd(&ies->sib_TypeAndInfo.list, sib2); - SystemInformation_IEs__sib_TypeAndInfo__Member *sib3_ie = calloc(1, sizeof(*sib3_ie)); - sib3_ie->present = NR_SystemInformation_IEs__sib_TypeAndInfo__Member_PR_sib3; - sib3_ie->choice.sib3 = calloc(1, sizeof(*sib3_ie->choice.sib3)); - asn1cSeqAdd(&ies->sib_TypeAndInfo.list, sib3_ie); - - //encode SIB to data - // carrier->SIB23 = (uint8_t *) malloc16(128); - asn_enc_rval_t enc_rval = uper_encode_to_buffer(&asn_DEF_NR_BCCH_DL_SCH_Message, - NULL, - (void *)sib_message, - carrier->SIB23, - 100); - AssertFatal (enc_rval.encoded > 0, - "ASN1 message encoding failed (%s, %lu)!\n", - enc_rval.failed_type->name, - enc_rval.encoded); - ASN_STRUCT_FREE(asn_DEF_NR_BCCH_DL_SCH_Message, sib_message); - return((enc_rval.encoded + 7) / 8); + ssize_t size = uper_encode_to_new_buffer(&asn_DEF_NR_SIB2, NULL, (void *)sib2, (void **)msg_SIB2); + AssertFatal (size > 0, "ASN1 message encoding failed (encoded %lu bytes)!\n", size); + ASN_STRUCT_FREE(asn_DEF_NR_SIB2, sib2); + return size; } int do_RRCReject(uint8_t *const buffer) diff --git a/openair2/RRC/NR/MESSAGES/asn1_msg.h b/openair2/RRC/NR/MESSAGES/asn1_msg.h index a6c5e25aec..3916305171 100644 --- a/openair2/RRC/NR/MESSAGES/asn1_msg.h +++ b/openair2/RRC/NR/MESSAGES/asn1_msg.h @@ -66,7 +66,7 @@ struct asn_TYPE_descriptor_s; */ int xer_sprint_NR(char *string, size_t string_size, struct asn_TYPE_descriptor_s *td, void *sptr); -int do_SIB23_NR(rrc_gNB_carrier_data_t *carrier); +int do_SIB2_NR(uint8_t **msg_SIB2); int do_RRCReject(uint8_t *const buffer); diff --git a/openair2/RRC/NR/nr_rrc_defs.h b/openair2/RRC/NR/nr_rrc_defs.h index c30f8856bc..51f2d427e5 100644 --- a/openair2/RRC/NR/nr_rrc_defs.h +++ b/openair2/RRC/NR/nr_rrc_defs.h @@ -268,15 +268,6 @@ typedef struct rrc_gNB_ue_context_s { struct gNB_RRC_UE_s ue_context; } rrc_gNB_ue_context_t; -typedef struct { - - uint8_t *SIB23; - int sizeof_SIB23; - -} rrc_gNB_carrier_data_t; -//--------------------------------------------------- - - typedef struct { /* nea0 = 0, nea1 = 1, ... */ int ciphering_algorithms[4]; @@ -370,6 +361,12 @@ typedef struct nr_rrc_cuup_container_t { sctp_assoc_t assoc_id; } nr_rrc_cuup_container_t; +typedef struct { + uint8_t *SIB_buffer; + int SIB_size; + int SIB_type; +} rrc_SIBs_t; + //---NR---(completely change)--------------------- typedef struct gNB_RRC_INST_s { @@ -378,7 +375,6 @@ typedef struct gNB_RRC_INST_s { char *node_name; int module_id; eth_params_t eth_params_s; - rrc_gNB_carrier_data_t carrier; 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 @@ -386,6 +382,7 @@ typedef struct gNB_RRC_INST_s { // RRC configuration gNB_RrcConfigurationReq configuration; + seq_arr_t *SIBs; // gNB N3 GTPU instance instance_t e1_inst; diff --git a/openair2/RRC/NR/rrc_gNB.c b/openair2/RRC/NR/rrc_gNB.c index 7eef21c810..0acba1797c 100644 --- a/openair2/RRC/NR/rrc_gNB.c +++ b/openair2/RRC/NR/rrc_gNB.c @@ -221,13 +221,15 @@ static void nr_rrc_transfer_protected_rrc_message(const gNB_RRC_INST *rrc, static void init_NR_SI(gNB_RRC_INST *rrc) { if (!NODE_IS_DU(rrc->node_type)) { - rrc->carrier.SIB23 = (uint8_t *) malloc16(100); - AssertFatal(rrc->carrier.SIB23 != NULL, "cannot allocate memory for SIB"); - rrc->carrier.sizeof_SIB23 = do_SIB23_NR(&rrc->carrier); - LOG_I(NR_RRC, "do_SIB23_NR, size %d\n", rrc->carrier.sizeof_SIB23); - AssertFatal(rrc->carrier.sizeof_SIB23 != 255,"FATAL, RC.nrrrc[mod].carrier[CC_id].sizeof_SIB23 == 255"); + seq_arr_t *sibs = rrc->SIBs; + if (sibs) { + for (int i = 0; i < sibs->size; i++) { + rrc_SIBs_t *sib = (rrc_SIBs_t *)seq_arr_at(sibs, i); + AssertFatal(sib->SIB_type == 2, "Only SIB2 currently implemented\n"); + sib->SIB_size = do_SIB2_NR(&sib->SIB_buffer); + } + } } - if (get_softmodem_params()->phy_test > 0 || get_softmodem_params()->do_ra > 0) { AssertFatal(NODE_IS_MONOLITHIC(rrc->node_type), "phy_test and do_ra only work in monolithic\n"); rrc_gNB_ue_context_t *ue_context_p = rrc_gNB_allocate_new_ue_context(rrc); diff --git a/openair2/RRC/NR/rrc_gNB_du.c b/openair2/RRC/NR/rrc_gNB_du.c index 6c7c157374..10113c21aa 100644 --- a/openair2/RRC/NR/rrc_gNB_du.c +++ b/openair2/RRC/NR/rrc_gNB_du.c @@ -397,6 +397,17 @@ void rrc_gNB_process_f1_setup_req(f1ap_setup_req_t *req, sctp_assoc_t assoc_id) .num_SI = 0, }; + seq_arr_t *sibs = rrc->SIBs; + if (sibs) { + for (int i = 0; i < sibs->size; i++) { + rrc_SIBs_t *si = (rrc_SIBs_t *)seq_arr_at(sibs, i); + cell.SI_msg[cell.num_SI].SI_container = si->SIB_buffer; + cell.SI_msg[cell.num_SI].SI_container_length = si->SIB_size; + cell.SI_msg[cell.num_SI].SI_type = si->SIB_type; + cell.num_SI++; + } + } + if (du->mib != NULL && du->sib1 != NULL) label_intra_frequency_neighbours(rrc, du, cell_info); From 4adadedeb966f69c104aaf38449aefc124e53b2a Mon Sep 17 00:00:00 2001 From: Sakthivel Velumani Date: Fri, 28 Feb 2025 17:55:03 -0500 Subject: [PATCH 38/63] Fix logging to matlab/bin file Add Python script to plot logged buffers. Modified write_file_matlab() to add .bin extension if binary format is selected. --- common/utils/LOG/log.c | 11 +- .../NR_UE_TRANSPORT/nr_dlsch_demodulation.c | 6 +- openair1/SIMULATION/NR_PHY/dlsim.c | 51 +- openair1/SIMULATION/NR_PHY/ulsim.c | 57 +- tools/plots/README.md | 13 + tools/plots/ber_compare.svg | 1506 +++++++++++++++++ tools/plots/dl_graph.py | 68 + 7 files changed, 1666 insertions(+), 46 deletions(-) create mode 100644 tools/plots/ber_compare.svg create mode 100644 tools/plots/dl_graph.py diff --git a/common/utils/LOG/log.c b/common/utils/LOG/log.c index 148da232ae..ab71715aac 100644 --- a/common/utils/LOG/log.c +++ b/common/utils/LOG/log.c @@ -150,14 +150,19 @@ int write_file_matlab(const char *fname, const char *vname, void *data, int leng //printf("Writing %d elements of type %d to %s\n",length,format,fname); + const int fname_len = strlen(fname); + char write_file_name[fname_len + 10]; + const char *fn_format = (format & MATLAB_RAW) ? "%s.bin" : "%s"; + snprintf(write_file_name, sizeof(write_file_name), fn_format, fname); + if (format == 10 || format ==11 || format == 12 || format == 13 || format == 14 || multiVec) { - fp = fopen(fname,"a+"); + fp = fopen(write_file_name, "a+"); } else if (format != 10 && format !=11 && format != 12 && format != 13 && format != 14) { - fp = fopen(fname,"w+"); + fp = fopen(write_file_name, "w+"); } if (fp== NULL) { - printf("[OPENAIR][FILE OUTPUT] Cannot open file %s\n",fname); + printf("[OPENAIR][FILE OUTPUT] Cannot open file %s\n", write_file_name); return(-1); } diff --git a/openair1/PHY/NR_UE_TRANSPORT/nr_dlsch_demodulation.c b/openair1/PHY/NR_UE_TRANSPORT/nr_dlsch_demodulation.c index 503f58fb36..74543a250d 100644 --- a/openair1/PHY/NR_UE_TRANSPORT/nr_dlsch_demodulation.c +++ b/openair1/PHY/NR_UE_TRANSPORT/nr_dlsch_demodulation.c @@ -766,10 +766,12 @@ int nr_rx_pdsch(PHY_VARS_NR_UE *ue, for (int a = 0; a < nbRx; a++) { int offset = (void *)rxdataF_comp[0][a] - (void *)rxdataF_comp[0] + symbol * rx_size_symbol * sizeof(c16_t); memcpy(ue->phy_sim_pdsch_rxdataF_comp + offset, rxdataF_comp[0][a] + symbol * rx_size_symbol, sizeof(c16_t) * rx_size_symbol); - memcpy(ue->phy_sim_pdsch_dl_ch_estimates + offset, dl_ch_estimates, pdsch_est_size * sizeof(c16_t)); + memcpy((c16_t *)ue->phy_sim_pdsch_dl_ch_estimates + pdsch_est_size * a, dl_ch_estimates, pdsch_est_size * sizeof(c16_t)); } if (ue->phy_sim_pdsch_dl_ch_estimates_ext) - memcpy(ue->phy_sim_pdsch_dl_ch_estimates_ext + symbol * rx_size_symbol, dl_ch_estimates_ext, sizeof(dl_ch_estimates_ext)); + memcpy((c16_t *)ue->phy_sim_pdsch_dl_ch_estimates_ext + symbol * rx_size_symbol, + dl_ch_estimates_ext, + sizeof(dl_ch_estimates_ext)); return (0); } diff --git a/openair1/SIMULATION/NR_PHY/dlsim.c b/openair1/SIMULATION/NR_PHY/dlsim.c index 95c32bdb94..5f5e7a9b05 100644 --- a/openair1/SIMULATION/NR_PHY/dlsim.c +++ b/openair1/SIMULATION/NR_PHY/dlsim.c @@ -989,6 +989,9 @@ printf("%d\n", slot); n_false_positive = 0; if (n_trials== 1) num_rounds = 1; + NR_gNB_DLSCH_t *gNB_dlsch = &msgDataTx->dlsch[0][0]; + nfapi_nr_dl_tti_pdsch_pdu_rel15_t *rel15 = &gNB_dlsch->harq_process.pdsch_pdu.pdsch_pdu_rel15; + for (trial = 0; trial < n_trials && !stop; trial++) { errors_bit = 0; @@ -1004,9 +1007,6 @@ printf("%d\n", slot); int harq_pid = slot; NR_DL_UE_HARQ_t *UE_harq_process = &UE->dl_harq_processes[0][harq_pid]; - NR_gNB_DLSCH_t *gNB_dlsch = &msgDataTx->dlsch[0][0]; - nfapi_nr_dl_tti_pdsch_pdu_rel15_t *rel15 = &gNB_dlsch->harq_process.pdsch_pdu.pdsch_pdu_rel15; - UE_harq_process->decodeResult = false; round = 0; UE_harq_process->DLround = round; @@ -1105,10 +1105,10 @@ printf("%d\n", slot); sprintf(filename,"txsig%d.m", aa);//LOG_M LOG_M(filename,"txs", &txdata[aa][slot_offset +frame_parms->ofdm_symbol_size+frame_parms->nb_prefix_samples0],6*(frame_parms->ofdm_symbol_size+frame_parms->nb_prefix_samples),1,1); } - } - if (output_fd) { - printf("writing txdata to binary file\n"); - fwrite(txdata[0],sizeof(int32_t),frame_length_complex_samples,output_fd); + if (output_fd) { + printf("writing txdata to binary file\n"); + fwrite(txdata[0], sizeof(int32_t), frame_length_complex_samples, output_fd); + } } int txlev[n_tx]; @@ -1292,15 +1292,38 @@ printf("%d\n", slot); } if (n_trials == 1) { + unsigned int op_format = 1; + unsigned int dec = 1; + if (output_fd) { // Write in bin format + op_format |= MATLAB_RAW; + } - LOG_M("rxsig0.m","rxs0", UE->common_vars.rxdata[0], frame_length_complex_samples, 1, 1); + LOG_M("rxsig0.m", "rxs0", UE->common_vars.rxdata[0], frame_length_complex_samples, dec, op_format); if (UE->frame_parms.nb_antennas_rx>1) - LOG_M("rxsig1.m","rxs1", UE->common_vars.rxdata[1], frame_length_complex_samples, 1, 1); - LOG_M("rxF0.m","rxF0", UE->phy_sim_rxdataF, frame_parms->samples_per_slot_wCP, 1, 1); - LOG_M("rxF_ext.m","rxFe",UE->phy_sim_pdsch_rxdataF_ext,g_rbSize*12*14,1,1); - LOG_M("chestF0.m","chF0",UE->phy_sim_pdsch_dl_ch_estimates_ext,g_rbSize*12*14,1,1); - write_output("rxF_comp.m","rxFc",UE->phy_sim_pdsch_rxdataF_comp,N_RB_DL*12*14,1,1); - LOG_M("rxF_llr.m","rxFllr",UE->phy_sim_pdsch_llr,available_bits,1,0); + LOG_M("rxsig1.m", "rxs1", UE->common_vars.rxdata[1], frame_length_complex_samples, dec, op_format); + LOG_M("rxF0.m", "rxF0", UE->phy_sim_rxdataF, frame_parms->samples_per_slot_wCP, dec, op_format); + LOG_M("rxF_ext.m", "rxFe", UE->phy_sim_pdsch_rxdataF_ext, g_rbSize * 12 * 14, dec, op_format); + const uint32_t numReSym = (g_rbSize * 12 + 15) & (~15); + const uint32_t numValidReSym = g_rbSize * 12; + { + const int s = rel15->StartSymbolIndex; + const int n = rel15->NrOfSymbols; + for (int i = s; i < s + n; i++) { + char fName[50]; + snprintf(fName, sizeof(fName), "chestF0_ext_s%d.m", i); + LOG_M(fName, + "chF0_ext", + ((c16_t *)UE->phy_sim_pdsch_dl_ch_estimates_ext) + (i * numReSym), + numValidReSym, + dec, + op_format); + snprintf(fName, sizeof(fName), "rxF_comp_s%d.m", i); + LOG_M(fName, "rxFc", ((c16_t *)UE->phy_sim_pdsch_rxdataF_comp) + (i * numReSym), numValidReSym, dec, op_format); + } + } + LOG_M("chestF0.m", "chF0", UE->phy_sim_pdsch_dl_ch_estimates, frame_parms->ofdm_symbol_size * 14, dec, op_format); + op_format = (op_format & (~1)); + LOG_M("rxF_llr.m", "rxFllr", UE->phy_sim_pdsch_llr, available_bits, dec, op_format); break; } diff --git a/openair1/SIMULATION/NR_PHY/ulsim.c b/openair1/SIMULATION/NR_PHY/ulsim.c index 276b1f3500..9dd4931361 100644 --- a/openair1/SIMULATION/NR_PHY/ulsim.c +++ b/openair1/SIMULATION/NR_PHY/ulsim.c @@ -240,6 +240,7 @@ int main(int argc, char *argv[]) int threadCnt=0; int max_ldpc_iterations = 5; int num_antennas_per_thread = 1; + uint32_t log_format = 0; if ((uniqCfg = load_configmodule(argc, argv, CONFIG_ENABLECMDLINEONLY)) == 0) { exit_fun("[NR_ULSIM] Error, configuration module init failed\n"); } @@ -251,8 +252,7 @@ int main(int argc, char *argv[]) InitSinLUT(); int c; - while ((c = getopt(argc, argv, "--:O:a:b:c:d:ef:g:h:i:k:m:n:p:q:r:s:t:u:v:w:y:z:A:C:F:G:H:I:M:N:PR:S:T:U:L:ZW:E:X:")) != -1) { - + while ((c = getopt(argc, argv, "--:O:a:b:c:d:ef:g:h:i:jk:m:n:p:q:r:s:t:u:v:w:y:z:A:C:F:G:H:I:M:N:PR:S:T:U:L:ZW:E:X:")) != -1) { /* ignore long options starting with '--', option '-O' and their arguments that are handled by configmodule */ /* with this opstring getopt returns 1 for non-option arguments, refer to 'man 3 getopt' */ if (c == 1 || c == '-' || c == 'O') @@ -346,7 +346,11 @@ int main(int argc, char *argv[]) i+=2; } while (optarg[i-1] == ','); break; - + + case 'j': + log_format |= MATLAB_RAW; + break; + case 'k': printf("Setting threequarter_fs_flag\n"); openair0_cfg[0].threequarter_fs= 1; @@ -524,6 +528,7 @@ int main(int argc, char *argv[]) printf("-g Channel model configuration. Arguments list: Number of arguments = 3, {Channel model: [A] TDLA30, [B] TDLB100, [C] TDLC300}, {Correlation: [l] Low, [m] Medium, [h] High}, {Maximum Doppler shift} e.g. -g A,l,10\n"); printf("-h This message\n"); printf("-i Change channel estimation technique. Arguments list: Number of arguments=2, Frequency domain {0:Linear interpolation, 1:PRB based averaging}, Time domain {0:Estimates of last DMRS symbol, 1:Average of DMRS symbols}. e.g. -i 1,0\n"); + printf("-j Save signal buffers in binary format."); printf("-k 3/4 sampling\n"); printf("-m MCS value\n"); printf("-n Number of trials to simulate\n"); @@ -560,7 +565,7 @@ int main(int argc, char *argv[]) } } - + logInit(); set_glog(loglvl); @@ -1215,12 +1220,12 @@ int main(int argc, char *argv[]) phy_procedures_nrUE_TX(UE, &UE_proc, &phy_data); if (n_trials == 1) { - LOG_M("txsig0.m", "txs0", &UE->common_vars.txData[0][slot_offset], slot_length, 1, 1); + LOG_M("txsig0.m", "txs0", &UE->common_vars.txData[0][slot_offset], slot_length, 1, 1 | log_format); if (precod_nbr_layers > 1) { - LOG_M("txsig1.m", "txs1", &UE->common_vars.txData[1][slot_offset], slot_length, 1, 1); - if (precod_nbr_layers == 4) { - LOG_M("txsig2.m", "txs2", &UE->common_vars.txData[2][slot_offset], slot_length, 1, 1); - LOG_M("txsig3.m", "txs3", &UE->common_vars.txData[3][slot_offset], slot_length, 1, 1); + LOG_M("txsig1.m", "txs1", &UE->common_vars.txData[1][slot_offset], slot_length, 1, 1 | log_format); + if (precod_nbr_layers == 4) { + LOG_M("txsig2.m", "txs2", &UE->common_vars.txData[2][slot_offset], slot_length, 1, 1 | log_format); + LOG_M("txsig3.m", "txs3", &UE->common_vars.txData[3][slot_offset], slot_length, 1, 1 | log_format); } } } @@ -1294,16 +1299,16 @@ int main(int argc, char *argv[]) ul_proc_error = phy_procedures_gNB_uespec_RX(gNB, frame, slot, &UL_INFO); if (n_trials == 1 && round == 0) { - LOG_M("rxsig0.m", "rx0", &rxdata[0][slot_offset], slot_length, 1, 1); - LOG_M("rxsigF0.m", "rxsF0", gNB->common_vars.rxdataF[0][0], 14 * frame_parms->ofdm_symbol_size, 1, 1); + LOG_M("rxsig0.m", "rx0", &rxdata[0][slot_offset], slot_length, 1, 1 | log_format); + LOG_M("rxsigF0.m", "rxsF0", gNB->common_vars.rxdataF[0][0], 14 * frame_parms->ofdm_symbol_size, 1, 1 | log_format); if (precod_nbr_layers > 1) { LOG_M("rxsig1.m", "rx1", &rxdata[1][slot_offset], slot_length, 1, 1); - LOG_M("rxsigF1.m", "rxsF1", gNB->common_vars.rxdataF[0][1], 14 * frame_parms->ofdm_symbol_size, 1, 1); + LOG_M("rxsigF1.m", "rxsF1", gNB->common_vars.rxdataF[0][1], 14 * frame_parms->ofdm_symbol_size, 1, 1 | log_format); if (precod_nbr_layers == 4) { LOG_M("rxsig2.m", "rx2", &rxdata[2][slot_offset], slot_length, 1, 1); LOG_M("rxsig3.m", "rx3", &rxdata[3][slot_offset], slot_length, 1, 1); - LOG_M("rxsigF2.m", "rxsF2", gNB->common_vars.rxdataF[0][2], 14 * frame_parms->ofdm_symbol_size, 1, 1); - LOG_M("rxsigF3.m", "rxsF3", gNB->common_vars.rxdataF[0][3], 14 * frame_parms->ofdm_symbol_size, 1, 1); + LOG_M("rxsigF2.m", "rxsF2", gNB->common_vars.rxdataF[0][2], 14 * frame_parms->ofdm_symbol_size, 1, 1 | log_format); + LOG_M("rxsigF3.m", "rxsF3", gNB->common_vars.rxdataF[0][3], 14 * frame_parms->ofdm_symbol_size, 1, 1 | log_format); } } } @@ -1317,71 +1322,69 @@ int main(int argc, char *argv[]) &pusch_vars->ul_ch_estimates[0][start_symbol * frame_parms->ofdm_symbol_size], frame_parms->ofdm_symbol_size, 1, - 1); + 1 | log_format); LOG_M("rxsigF0_comp.m", "rxsF0_comp", &pusch_vars->rxdataF_comp[0][start_symbol * (off + (NR_NB_SC_PER_RB * pusch_pdu->rb_size))], nb_symb_sch * (off + (NR_NB_SC_PER_RB * pusch_pdu->rb_size)), 1, - 1); + 1 | log_format); if (precod_nbr_layers == 2) { - LOG_M("chestF3.m", "chF3", &pusch_vars->ul_ch_estimates[3][start_symbol * frame_parms->ofdm_symbol_size], frame_parms->ofdm_symbol_size, 1, - 1); + 1 | log_format); LOG_M("rxsigF2_comp.m", "rxsF2_comp", &pusch_vars->rxdataF_comp[2][start_symbol * (off + (NR_NB_SC_PER_RB * pusch_pdu->rb_size))], nb_symb_sch * (off + (NR_NB_SC_PER_RB * pusch_pdu->rb_size)), 1, - 1); + 1 | log_format); } if (precod_nbr_layers == 4) { - LOG_M("chestF5.m", "chF5", &pusch_vars->ul_ch_estimates[5][start_symbol * frame_parms->ofdm_symbol_size], frame_parms->ofdm_symbol_size, 1, - 1); + 1 | log_format); LOG_M("chestF10.m", "chF10", &pusch_vars->ul_ch_estimates[10][start_symbol * frame_parms->ofdm_symbol_size], frame_parms->ofdm_symbol_size, 1, - 1); + 1 | log_format); LOG_M("chestF15.m", "chF15", &pusch_vars->ul_ch_estimates[15][start_symbol * frame_parms->ofdm_symbol_size], frame_parms->ofdm_symbol_size, 1, - 1); + 1 | log_format); LOG_M("rxsigF4_comp.m", "rxsF4_comp", &pusch_vars->rxdataF_comp[4][start_symbol * (off + (NR_NB_SC_PER_RB * pusch_pdu->rb_size))], nb_symb_sch * (off + (NR_NB_SC_PER_RB * pusch_pdu->rb_size)), 1, - 1); + 1 | log_format); LOG_M("rxsigF8_comp.m", "rxsF8_comp", &pusch_vars->rxdataF_comp[8][start_symbol * (off + (NR_NB_SC_PER_RB * pusch_pdu->rb_size))], nb_symb_sch * (off + (NR_NB_SC_PER_RB * pusch_pdu->rb_size)), 1, - 1); + 1 | log_format); LOG_M("rxsigF12_comp.m", "rxsF12_comp", &pusch_vars->rxdataF_comp[12][start_symbol * (off + (NR_NB_SC_PER_RB * pusch_pdu->rb_size))], nb_symb_sch * (off + (NR_NB_SC_PER_RB * pusch_pdu->rb_size)), 1, - 1); + 1 | log_format); } LOG_M("rxsigF0_llr.m", @@ -1389,7 +1392,7 @@ int main(int argc, char *argv[]) &pusch_vars->llr[0], precod_nbr_layers * (nb_symb_sch - 1) * NR_NB_SC_PER_RB * pusch_pdu->rb_size * mod_order, 1, - 0); + 0 | log_format); } if ((ulsch_gNB->last_iteration_cnt >= ulsch_gNB->max_ldpc_iterations) || ul_proc_error == 1) { diff --git a/tools/plots/README.md b/tools/plots/README.md index 5d5175a882..39f9fab6f1 100644 --- a/tools/plots/README.md +++ b/tools/plots/README.md @@ -19,3 +19,16 @@ Same goes for any software modifications of `nr_ulsim` - the cache is not aware the data - remember to clear your cache manually if you are comparing different `nr_ulsim` versions. The script also allows to skip usage of the cache with `--rerun` option. + +## `dl_graph.py` + +This generates plots from data saved from nr_dlsim. It takes data in two formats: + +1. Plots frequency domain channel estimates, constellation diagram and LLRs from data read in binary format. The data is generated from `nr_dlsim -w` +2. Plots BER (other also possible) from CSV data generated by `nr_dlsim -Z`. + +The plots are saved in three formats: `.png`, `.pdf` and `.svg`. + +### Example graphs: +#### BER +![image](./ber_compare.svg) diff --git a/tools/plots/ber_compare.svg b/tools/plots/ber_compare.svg new file mode 100644 index 0000000000..90d6827aad --- /dev/null +++ b/tools/plots/ber_compare.svg @@ -0,0 +1,1506 @@ + + + + + + + + 2025-03-27T13:43:33.593313 + image/svg+xml + + + Matplotlib v3.5.1, https://matplotlib.org/ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tools/plots/dl_graph.py b/tools/plots/dl_graph.py new file mode 100644 index 0000000000..0fd1a8bc2b --- /dev/null +++ b/tools/plots/dl_graph.py @@ -0,0 +1,68 @@ +#!/usr/bin/env python +# coding: utf-8 +import numpy as np +import matplotlib.pyplot as plt +import argparse +import pandas + +def save_fig(fig:plt.figure, figName:str): + fig.savefig(figName + '.pdf') + fig.savefig(figName + '.png') + fig.savefig(figName + '.svg') + +# Channel Frequency Response +def channel_est(dataPath): + binfile = dataPath + 'chestF0_ext_s1.m.bin' + data = np.fromfile(binfile,dtype=np.int16) + dataCmp = data[0::2] + 1j*data[1::2] + fig = plt.gcf() + plt.plot(np.abs(dataCmp)) + save_fig(fig, 'channel_est') + plt.close(fig) + +# Constellation +def constellation(dataPath): + binfile = dataPath + 'rxF_comp_s1.m.bin' + data = np.fromfile(binfile,dtype=np.int16) + dataCmp = data[0::2] + 1j*data[1::2] + fig = plt.gcf() + plt.scatter(np.real(dataCmp),np.imag(dataCmp)) + save_fig(fig, 'constellation') + plt.close(fig) + +# LLR +def llr(dataPath): + binfile = dataPath + 'rxF_llr.m.bin' + data = np.fromfile(binfile,dtype=np.int16) + fig = plt.gcf() + plt.plot(data,'*') + save_fig(fig, 'llr') + plt.close(fig) + +# BER from data in CSV files generated by nr_dlsim +def ber(dataPath, csvFiles): + d0 = pandas.read_csv(dataPath + csvFiles[0] + '.csv') + ax = d0.plot(x='SNR',y='channel_ber_0',logy=True,label=csvFiles[0]) + plt.ylabel('BER') + for i in range(1,len(csvFiles)): + d = pandas.read_csv(dataPath + csvFiles[i] + '.csv') + d.plot(ax=ax,x='SNR',y='channel_ber_0',logy=True,label=csvFiles[i],grid=True) + + fig = ax.figure + save_fig(fig, 'ber_compare') + plt.close(fig) + + +if __name__ == '__main__': + parser = argparse.ArgumentParser(description="DL plot generator") + parser.add_argument('--data-path', help='Path to directory containing data', default='../../cmake_targets/ran_build/build/') + parser.add_argument('--csv-data', nargs='+' ,help='CSV file name without extension (generated from nr_dlsim with -Z option)') + parser.add_argument('--ber-compare', help='Compare BER from multiple CSV files', action='store_true') + + args = parser.parse_args() + + channel_est(args.data_path) + constellation(args.data_path) + llr(args.data_path) + if args.ber_compare: + ber(args.data_path, args.csv_data) From d334a75c8ab47ba42922fc4f79cf25e79e634736 Mon Sep 17 00:00:00 2001 From: Sakthivel Velumani Date: Fri, 28 Feb 2025 19:01:56 -0500 Subject: [PATCH 39/63] Fix memory leak and possible crash nr_dlsim allocates memory on every SNR iteration without freeing it. This can lead to memory exhaustion. Fix this by allocating only once and reuse memory. Also, free it before exit. --- openair1/SIMULATION/NR_PHY/dlsim.c | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/openair1/SIMULATION/NR_PHY/dlsim.c b/openair1/SIMULATION/NR_PHY/dlsim.c index 5f5e7a9b05..534d11b448 100644 --- a/openair1/SIMULATION/NR_PHY/dlsim.c +++ b/openair1/SIMULATION/NR_PHY/dlsim.c @@ -959,6 +959,13 @@ printf("%d\n", slot); fprintf(csv_file,"avg_round,eff_rate,eff_throughput,TBS\n"); } //--------------- + + Sched_INFO = malloc(sizeof(*Sched_INFO)); + if (Sched_INFO == NULL) { + LOG_E(PHY, "out of memory\n"); + exit(1); + } + for (SNR = snr0; SNR < snr1 && !stop; SNR += .2) { varArray_t *table_tx=initVarArray(1000,sizeof(double)); @@ -1012,11 +1019,6 @@ printf("%d\n", slot); UE_harq_process->DLround = round; UE_harq_process->first_rx = 1; - Sched_INFO = malloc(sizeof(*Sched_INFO)); - if (Sched_INFO == NULL) { - LOG_E(PHY, "out of memory\n"); - exit(1); - } memset(Sched_INFO, 0, sizeof(*Sched_INFO)); Sched_INFO->sched_response_id = -1; @@ -1335,6 +1337,8 @@ printf("%d\n", slot); n_errs = n_errors[0]; } // NSR + free(Sched_INFO); + free_channel_desc_scm(gNB2UE); for (i = 0; i < n_tx; i++) { From 135b522cf4e2a0c4b0815e1ea8278d59134ea051 Mon Sep 17 00:00:00 2001 From: Robert Schmidt Date: Fri, 11 Apr 2025 14:17:50 +0200 Subject: [PATCH 40/63] CoreNetwork: do not look up IP for CN name printing In various places, e.g., during initialization (before deployment), we use the name of the CN object, previously including the IP address. However, the IP might not be available (because the CN is not deployed), so we would get warnings at this stage. Avoid this by not looking up the IP. There is now one place where we have to look up the IP address separately. Also, the exception in initialization might be misleading if we used {self}, because we complain about no node being present, but the {self} would include a non-existing node name => do not use it. --- ci-scripts/cls_corenetwork.py | 4 ++-- ci-scripts/cls_oaicitest.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/ci-scripts/cls_corenetwork.py b/ci-scripts/cls_corenetwork.py index e4dc3bf11a..579bad585b 100644 --- a/ci-scripts/cls_corenetwork.py +++ b/ci-scripts/cls_corenetwork.py @@ -49,7 +49,7 @@ class CoreNetwork: self._host = c.get('Host').strip() if self._host == "%%current_host%%": if node is None: - raise Exception(f"core network {self} requires node, but none provided (cannot replace %%current_host%%)") + raise Exception(f"core network {cn_name} requires node, but none provided (cannot replace %%current_host%%)") self._host = node if d is not None: raise Exception("directory handling not implemented") @@ -66,7 +66,7 @@ class CoreNetwork: logging.info(f'initialized core {self} from {filename}') def __str__(self): - return f"{self._cn_name}@{self._host} [IP: {self.getIP()}]" + return f"{self._cn_name}@{self._host}" def __repr__(self): return self.__str__() diff --git a/ci-scripts/cls_oaicitest.py b/ci-scripts/cls_oaicitest.py index 51a7108875..1f286e9083 100644 --- a/ci-scripts/cls_oaicitest.py +++ b/ci-scripts/cls_oaicitest.py @@ -862,7 +862,7 @@ class OaiCiTest(): success, output = cn.deploy() logging.info(f"deployment core network {core_name} success {success}, output:\n{output}") if success: - msg = f"Started {cn}" + msg = f"Started {cn} [{cn.getIP()}]" HTML.CreateHtmlTestRowQueue(core_name, 'OK', [msg]) else: msg = f"deployment of core network {core_name} FAILED" From 556396e56edeafe1a4d35e1ee47c25de3c5464f7 Mon Sep 17 00:00:00 2001 From: Robert Schmidt Date: Fri, 11 Apr 2025 14:19:22 +0200 Subject: [PATCH 41/63] Ping: use cn name when checking for CN IP address --- ci-scripts/cls_oaicitest.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ci-scripts/cls_oaicitest.py b/ci-scripts/cls_oaicitest.py index 1f286e9083..cd40c807e1 100644 --- a/ci-scripts/cls_oaicitest.py +++ b/ci-scripts/cls_oaicitest.py @@ -464,7 +464,7 @@ class OaiCiTest(): return (False, f"UE {ue.getName()} has no IP address") svrIP = cn.getIP() if not svrIP: - return (False, f"Iperf server {ue.getName()} has no IP address") + return (False, f"Iperf server {cn.getName()} has no IP address") iperf_opt = self.iperf_args jsonReport = "--json" From 535d8e77657bac1b87e7cd7f5770abfb50fa5bbd Mon Sep 17 00:00:00 2001 From: francescomani Date: Thu, 19 Dec 2024 19:29:23 +0100 Subject: [PATCH 42/63] other SIBs scheduling configuration in SIB1 --- common/utils/nr/nr_common.h | 6 + executables/nr-softmodem.c | 13 +- openair2/GNB_APP/gnb_config.c | 56 ++++- openair2/GNB_APP/gnb_config.h | 8 +- openair2/GNB_APP/gnb_paramdef.h | 5 + openair2/LAYER2/NR_MAC_gNB/config.c | 102 ++++++++- openair2/LAYER2/NR_MAC_gNB/gNB_scheduler.c | 2 - openair2/LAYER2/NR_MAC_gNB/mac_proto.h | 7 +- .../LAYER2/NR_MAC_gNB/mac_rrc_dl_handler.c | 10 +- openair2/LAYER2/NR_MAC_gNB/nr_mac_gNB.h | 8 +- openair2/RRC/NR/MESSAGES/asn1_msg.c | 6 +- openair2/RRC/NR/MESSAGES/asn1_msg.h | 2 +- openair2/RRC/NR/nr_rrc_config.c | 208 ++++++++++-------- openair2/RRC/NR/nr_rrc_config.h | 10 +- openair2/RRC/NR/nr_rrc_defs.h | 6 - openair2/RRC/NR/rrc_gNB.c | 36 +-- openair2/RRC/NR/rrc_gNB_du.c | 28 ++- 17 files changed, 315 insertions(+), 198 deletions(-) diff --git a/common/utils/nr/nr_common.h b/common/utils/nr/nr_common.h index fdb3cc7ffc..d064b522b9 100644 --- a/common/utils/nr/nr_common.h +++ b/common/utils/nr/nr_common.h @@ -93,6 +93,12 @@ static inline const char *rnti_types(nr_rnti_type_t rr) // the total shift is 2 * 15, in dB scale thats 10log10(2^(15*2)) #define SQ15_SQUARED_NORM_FACTOR_DB 90.3089986992 +typedef struct { + uint8_t *SIB_buffer; + int SIB_size; + int SIB_type; +} nr_SIBs_t; + typedef struct nr_bandentry_s { int16_t band; uint64_t ul_min; diff --git a/executables/nr-softmodem.c b/executables/nr-softmodem.c index 482da9a1cf..4f9ae49c42 100644 --- a/executables/nr-softmodem.c +++ b/executables/nr-softmodem.c @@ -464,23 +464,14 @@ int start_L1L2(module_id_t gnb_id) NR_BCCH_BCH_Message_t *mib = mac->common_channels[0].mib; const NR_BCCH_DL_SCH_Message_t *sib1 = mac->common_channels[0].sib1; - - /* update existing config in F1 Setup request structures */ f1ap_setup_req_t *sr = mac->f1_config.setup_req; DevAssert(sr->num_cells_available == 1); f1ap_served_cell_info_t *info = &sr->cell[0].info; DevAssert(info->mode == F1AP_MODE_TDD); + /* update existing config in F1 Setup request structures */ DevAssert(scc->tdd_UL_DL_ConfigurationCommon != NULL); info->tdd = read_tdd_config(scc); /* updates radio config */ - /* send gNB-DU configuration update to RRC */ - f1ap_gnb_du_configuration_update_t update = { - .transaction_id = 1, - .num_cells_to_modify = 1, - }; - 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); - mac->mac_rrc.gnb_du_configuration_update(&update); + prepare_du_configuration_update(mac, info, mib, sib1); init_NR_RU(config_get_if(), NULL); diff --git a/openair2/GNB_APP/gnb_config.c b/openair2/GNB_APP/gnb_config.c index 34fd7a7f2b..5a58f23006 100644 --- a/openair2/GNB_APP/gnb_config.c +++ b/openair2/GNB_APP/gnb_config.c @@ -1312,7 +1312,7 @@ static f1ap_fdd_info_t read_fdd_config(const NR_ServingCellConfigCommon_t *scc) return fdd; } -f1ap_gnb_du_system_info_t *get_sys_info(NR_BCCH_BCH_Message_t *mib, const NR_BCCH_DL_SCH_Message_t *sib1) +f1ap_gnb_du_system_info_t *get_sys_info(NR_BCCH_BCH_Message_t *mib, const NR_BCCH_DL_SCH_Message_t *sib1, seq_arr_t *du_SIBs) { int buf_len = 3; f1ap_gnb_du_system_info_t *sys_info = calloc_or_fail(1, sizeof(*sys_info)); @@ -1329,15 +1329,24 @@ f1ap_gnb_du_system_info_t *get_sys_info(NR_BCCH_BCH_Message_t *mib, const NR_BCC AssertFatal(enc_rval.encoded > 0, "ASN1 message encoding failed (%s, %lu)!\n", enc_rval.failed_type->name, enc_rval.encoded); sys_info->sib1_length = (enc_rval.encoded + 7) / 8; + if (du_SIBs) { + for (int i = 0; i < du_SIBs->size; i++) { + nr_SIBs_t *si = (nr_SIBs_t *)seq_arr_at(du_SIBs, i); + // other SIB in gNB-DU System Information not implemented yet + // only DU SIB not included in this message is SIB19 + AssertFatal(si->SIB_type == 19, "Cannot handle SIB%d in gNB-DU System Information\n", si->SIB_type); + } + } return sys_info; } -f1ap_setup_req_t *RC_read_F1Setup(uint64_t id, +static f1ap_setup_req_t *RC_read_F1Setup(uint64_t id, const char *name, const f1ap_served_cell_info_t *info, const NR_ServingCellConfigCommon_t *scc, NR_BCCH_BCH_Message_t *mib, - const NR_BCCH_DL_SCH_Message_t *sib1) + const NR_BCCH_DL_SCH_Message_t *sib1, + seq_arr_t *du_SIBs) { f1ap_setup_req_t *req = calloc(1, sizeof(*req)); AssertFatal(req != NULL, "out of memory\n"); @@ -1381,7 +1390,7 @@ f1ap_setup_req_t *RC_read_F1Setup(uint64_t id, if (IS_SA_MODE(get_softmodem_params())) { // in NSA we don't transmit SIB1, so cannot fill DU system information // so cannot send MIB either - req->cell[0].sys_info = get_sys_info(mib, sib1); + req->cell[0].sys_info = get_sys_info(mib, sib1, du_SIBs); } int num = read_version(TO_STRING(NR_RRC_VERSION), &req->rrc_ver[0], &req->rrc_ver[1], &req->rrc_ver[2]); @@ -1421,6 +1430,28 @@ static nr_redcap_config_t *get_redcap_config(int gnb_idx) return rc; } +static seq_arr_t *fill_du_sibs(paramdef_t *GNBparamarray) +{ + if (!config_isparamset(GNBparamarray, GNB_DU_SIBS_IDX)) + return NULL; + + seq_arr_t *du_SIBs = malloc(sizeof(seq_arr_t)); + seq_arr_init(du_SIBs, sizeof(nr_SIBs_t)); + for (int i = 0; i < GNBparamarray[GNB_DU_SIBS_IDX].numelt; i++) { + int sib_value = GNBparamarray[GNB_DU_SIBS_IDX].iptr[i]; + // SIB10, SIB12, SIB13, SIB14, SIB15, SIB17, SIB18, SIB19 and SIB20 are generated by the DU + // all the rest in the DU + AssertFatal((sib_value > 9 && sib_value < 21) && sib_value != 11 && sib_value != 16, + "SIB%d is not a DU SIB, to be configured in DU section\n", + sib_value); + nr_SIBs_t *du_sib = calloc_or_fail(1, sizeof(nr_SIBs_t)); + du_sib->SIB_type = sib_value; + LOG_I(GNB_APP, "activate SIB%d at DU\n", sib_value); + seq_arr_push_back(du_SIBs, du_sib, sizeof(nr_SIBs_t)); + } + return du_SIBs; +} + void RCconfig_nr_macrlc(configmodule_interface_t *cfg) { int j = 0; @@ -1692,17 +1723,18 @@ void RCconfig_nr_macrlc(configmodule_interface_t *cfg) f1ap_served_cell_info_t info; read_du_cell_info(cfg, NODE_IS_DU(node_type), &gnb_id, &gnb_du_id, &name, &info, 1); - if (IS_SA_MODE(get_softmodem_params())) { + NR_COMMON_channels_t *cc = &RC.nrmac[0]->common_channels[0]; + cc->du_SIBs = fill_du_sibs(GNBParamList.paramarray[0]); + + if (IS_SA_MODE(get_softmodem_params())) nr_mac_configure_sib1(RC.nrmac[0], &info.plmn, info.nr_cellid, *info.tac); - if (scc->ext2 && scc->ext2->ntn_Config_r17) - nr_mac_configure_sib19(RC.nrmac[0]); - } // read F1 Setup information from config and generated MIB/SIB1 // and store it at MAC for sending later NR_BCCH_BCH_Message_t *mib = RC.nrmac[0]->common_channels[0].mib; const NR_BCCH_DL_SCH_Message_t *sib1 = RC.nrmac[0]->common_channels[0].sib1; - f1ap_setup_req_t *req = RC_read_F1Setup(gnb_du_id, name, &info, scc, mib, sib1); + 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"); RC.nrmac[0]->f1_config.setup_req = req; RC.nrmac[0]->f1_config.gnb_id = gnb_id; @@ -1852,7 +1884,7 @@ static seq_arr_t *fill_cu_sibs(paramdef_t *GNBparamarray) return NULL; seq_arr_t *SIBs = malloc(sizeof(seq_arr_t)); - seq_arr_init(SIBs, sizeof(rrc_SIBs_t)); + seq_arr_init(SIBs, sizeof(nr_SIBs_t)); for (int i = 0; i < GNBparamarray[GNB_CU_SIBS_IDX].numelt; i++) { int sib_value = GNBparamarray[GNB_CU_SIBS_IDX].iptr[i]; // SIB10, SIB12, SIB13, SIB14, SIB15, SIB17, SIB18, SIB19 and SIB20 are generated by the DU @@ -1860,10 +1892,10 @@ static seq_arr_t *fill_cu_sibs(paramdef_t *GNBparamarray) AssertFatal((sib_value > 1 && sib_value < 10) || sib_value == 11 || sib_value == 16, "SIB%d is not a CU SIB, to be configured in DU section\n", sib_value); - rrc_SIBs_t *cu_sib = calloc_or_fail(1, sizeof(*cu_sib)); + nr_SIBs_t *cu_sib = calloc_or_fail(1, sizeof(*cu_sib)); cu_sib->SIB_type = sib_value; LOG_I(GNB_APP, "activate SIB%d at CU\n", sib_value); - seq_arr_push_back(SIBs, cu_sib, sizeof(rrc_SIBs_t)); + seq_arr_push_back(SIBs, cu_sib, sizeof(nr_SIBs_t)); } return SIBs; } diff --git a/openair2/GNB_APP/gnb_config.h b/openair2/GNB_APP/gnb_config.h index baf4ad5d94..f9d23ccba4 100644 --- a/openair2/GNB_APP/gnb_config.h +++ b/openair2/GNB_APP/gnb_config.h @@ -95,14 +95,8 @@ gNB_RRC_INST *RCconfig_NRRRC(); int RCconfig_NR_NG(MessageDef *msg_p, uint32_t i); int RCconfig_NR_X2(MessageDef *msg_p, uint32_t i); void wait_f1_setup_response(void); -f1ap_setup_req_t *RC_read_F1Setup(uint64_t id, - const char *name, - const f1ap_served_cell_info_t *info, - const NR_ServingCellConfigCommon_t *scc, - NR_BCCH_BCH_Message_t *mib, - const NR_BCCH_DL_SCH_Message_t *sib1); f1ap_tdd_info_t read_tdd_config(const NR_ServingCellConfigCommon_t *scc); -f1ap_gnb_du_system_info_t *get_sys_info(NR_BCCH_BCH_Message_t *mib, const NR_BCCH_DL_SCH_Message_t *sib1); +f1ap_gnb_du_system_info_t *get_sys_info(NR_BCCH_BCH_Message_t *mib, const NR_BCCH_DL_SCH_Message_t *sib1, seq_arr_t *du_SIBs); int gNB_app_handle_f1ap_gnb_cu_configuration_update(f1ap_gnb_cu_configuration_update_t *gnb_cu_cfg_update); MessageDef *RCconfig_NR_CU_E1(const E1_t *entity); ngran_node_t get_node_type(void); diff --git a/openair2/GNB_APP/gnb_paramdef.h b/openair2/GNB_APP/gnb_paramdef.h index e8d36c55ae..c76b6bc143 100644 --- a/openair2/GNB_APP/gnb_paramdef.h +++ b/openair2/GNB_APP/gnb_paramdef.h @@ -140,6 +140,7 @@ typedef enum { #define GNB_CONFIG_STRING_BEAM_WEIGHTS_LIST "beam_weights" #define GNB_CONFIG_STRING_UESS_AGG_LEVEL_LIST "uess_agg_levels" #define GNB_CONFIG_STRING_CU_SIB_LIST "cu_sibs" +#define GNB_CONFIG_STRING_DU_SIB_LIST "du_sibs" #define GNB_CONFIG_HLP_STRING_ENABLE_SDAP "enable the SDAP layer\n" #define GNB_CONFIG_HLP_FORCE256QAMOFF "suppress activation of 256 QAM despite UE support" @@ -152,6 +153,7 @@ typedef enum { #define GNB_CONFIG_HLP_NUM_UL_HARQ "Set Num UL harq processes. Valid values 16,32. Default 16" #define GNB_CONFIG_HLP_UESS_AGG_LEVEL_LIST "List of aggregation levels with number of candidates per level. Element 0 - aggregation level 1" #define GNB_CONFIG_HLP_CU_SIBS "List of CU generated SIBs to be transmitted" +#define GNB_CONFIG_HLP_DU_SIBS "List of DU generated SIBs to be transmitted" /*-----------------------------------------------------------------------------------------------------------------------------------------*/ /* cell configuration parameters */ @@ -198,6 +200,7 @@ typedef enum { {GNB_CONFIG_STRING_UESS_AGG_LEVEL_LIST, \ GNB_CONFIG_HLP_UESS_AGG_LEVEL_LIST, 0, .iptr=NULL, .defintarrayval=NULL, TYPE_INTARRAY, 0}, \ {GNB_CONFIG_STRING_CU_SIB_LIST, GNB_CONFIG_HLP_CU_SIBS, 0, .iptr=NULL, .defintarrayval=0, TYPE_INTARRAY, 0}, \ +{GNB_CONFIG_STRING_DU_SIB_LIST, GNB_CONFIG_HLP_DU_SIBS, 0, .iptr=NULL, .defintarrayval=0, TYPE_INTARRAY, 0}, \ } // clang-format on @@ -240,6 +243,7 @@ typedef enum { #define GNB_BEAMWEIGHTS_IDX 35 #define GNB_UESS_AGG_LEVEL_LIST_IDX 36 #define GNB_CU_SIBS_IDX 37 +#define GNB_DU_SIBS_IDX 38 #define TRACKING_AREA_CODE_OKRANGE {0x0001,0xFFFD} #define NUM_DL_HARQ_OKVALUES {2,4,6,8,10,12,16,32} @@ -284,6 +288,7 @@ typedef enum { { .s5 = { NULL } }, \ { .s5 = { NULL } }, \ { .s5 = { NULL } }, \ + { .s5 = { NULL } }, \ } /*-------------------------------------------------------------------------------------------------------------------------------------------------*/ diff --git a/openair2/LAYER2/NR_MAC_gNB/config.c b/openair2/LAYER2/NR_MAC_gNB/config.c index 19efd1d401..c9a58a4e4a 100644 --- a/openair2/LAYER2/NR_MAC_gNB/config.c +++ b/openair2/LAYER2/NR_MAC_gNB/config.c @@ -38,6 +38,7 @@ #include "BIT_STRING.h" #include "LAYER2/NR_MAC_COMMON/nr_mac_common.h" #include "LAYER2/NR_MAC_gNB/mac_proto.h" +#include "GNB_APP/gnb_config.h" #include "NR_MIB.h" #include "NR_MAC_gNB/nr_mac_gNB.h" #include "NR_BCCH-BCH-Message.h" @@ -889,6 +890,94 @@ void nr_fill_sched_osi(gNB_MAC_INST *nrmac, const struct NR_SetupRelease_PDCCH_C } } +bool nr_mac_configure_other_sib(gNB_MAC_INST *nrmac, int num_cu_sib, const f1ap_sib_msg_t cu_sib[num_cu_sib]) +{ + NR_COMMON_channels_t *cc = &nrmac->common_channels[0]; + seq_arr_t *du_SIBs = nrmac->common_channels[0].du_SIBs; + int num_du_sib = 0; + if (du_SIBs) + num_du_sib = du_SIBs->size; + if (num_cu_sib + num_du_sib == 0) + return false; /* no updates */ + + int config_sibs[num_cu_sib + num_du_sib]; + NR_SystemInformation_IEs_t *sysInfo = calloc(1, sizeof(*sysInfo)); //for SIB up to 14 included + NR_SystemInformation_IEs_t *sysInfov17 = calloc(1, sizeof(*sysInfov17)); // for othersibs + for (int i = 0; i < num_cu_sib; i++) { + config_sibs[i] = cu_sib[i].SI_type; + switch (config_sibs[i]) { + case 2: { + struct NR_SystemInformation_IEs__sib_TypeAndInfo__Member *type = calloc(1, sizeof(*type)); + type->present = NR_SystemInformation_IEs__sib_TypeAndInfo__Member_PR_sib2; + // SIB2 coming from CU need to decode it + NR_SIB2_t *sib2 = NULL; + asn_dec_rval_t dec_rval = uper_decode(NULL, + &asn_DEF_NR_SIB2, + (void **)&sib2, + cu_sib[i].SI_container, + cu_sib[i].SI_container_length, + 0, + 0); + if (dec_rval.code != RC_OK) { + LOG_E(NR_MAC, "cannot decode SIB%d from CU\n", config_sibs[i]); + ASN_STRUCT_FREE(asn_DEF_NR_SIB2, cu_sib[i].SI_container); + } + type->choice.sib2 = sib2; + add_sib_to_systeminformation(sysInfo, type); + break; + } + default : + AssertFatal(false, "Invalid or not supported SIB%d\n", config_sibs[i]); + } + } + + for (int i = 0; i < num_du_sib; i++) { + nr_SIBs_t *si = (nr_SIBs_t *)seq_arr_at(du_SIBs, i); + int sib_idx = i + num_cu_sib; + config_sibs[sib_idx] = si->SIB_type; + switch (config_sibs[sib_idx]) { + case 19: { + struct NR_SystemInformation_IEs__sib_TypeAndInfo__Member *type_du = calloc(1, sizeof(*type_du)); + type_du->present = NR_SystemInformation_IEs__sib_TypeAndInfo__Member_PR_sib19_v1700; + NR_SIB19_r17_t *sib19 = get_SIB19_NR(cc->ServingCellConfigCommon); + type_du->choice.sib19_v1700 = sib19; + add_sib_to_systeminformation(sysInfov17, type_du); + break; + } + default : + AssertFatal(false, "Invalid or not supported SIB%d\n", sib_idx); + } + } + + update_SIB1_NR_SI(cc->sib1, num_cu_sib + num_du_sib, config_sibs); + cc->sib1_bcch_length = encode_SIB_NR(cc->sib1, cc->sib1_bcch_pdu, sizeof(cc->sib1_bcch_pdu)); + AssertFatal(cc->sib1_bcch_length > 0, "could not encode SIB1\n"); + + // generate otherSIB payloads + cc->other_sib_bcch_length[0] = encode_sysinfo_ie(sysInfo, cc->other_sib_bcch_pdu[0], sizeof(cc->other_sib_bcch_pdu[0])); + cc->other_sib_bcch_length[1] = encode_sysinfo_ie(sysInfov17, cc->other_sib_bcch_pdu[1], sizeof(cc->other_sib_bcch_pdu[1])); + + ASN_STRUCT_FREE(asn_DEF_NR_SystemInformation_IEs, sysInfo); + ASN_STRUCT_FREE(asn_DEF_NR_SystemInformation_IEs, sysInfov17); + return true; +} + +void prepare_du_configuration_update(gNB_MAC_INST *mac, + f1ap_served_cell_info_t *info, + NR_BCCH_BCH_Message_t *mib, + const NR_BCCH_DL_SCH_Message_t *sib1) +{ + /* send gNB-DU configuration update to RRC */ + f1ap_gnb_du_configuration_update_t update = { + .transaction_id = 1, + .num_cells_to_modify = 1, + }; + 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); + mac->mac_rrc.gnb_du_configuration_update(&update); +} + void nr_mac_configure_sib1(gNB_MAC_INST *nrmac, const f1ap_plmn_t *plmn, uint64_t cellID, int tac) { AssertFatal(IS_SA_MODE(get_softmodem_params()), "error: SIB1 only applicable for SA\n"); @@ -897,21 +986,10 @@ void nr_mac_configure_sib1(gNB_MAC_INST *nrmac, const f1ap_plmn_t *plmn, uint64_ NR_ServingCellConfigCommon_t *scc = cc->ServingCellConfigCommon; NR_BCCH_DL_SCH_Message_t *sib1 = get_SIB1_NR(scc, plmn, cellID, tac, &nrmac->radio_config); cc->sib1 = sib1; - cc->sib1_bcch_length = encode_SIB1_NR(sib1, cc->sib1_bcch_pdu, sizeof(cc->sib1_bcch_pdu)); + cc->sib1_bcch_length = encode_SIB_NR(sib1, cc->sib1_bcch_pdu, sizeof(cc->sib1_bcch_pdu)); AssertFatal(cc->sib1_bcch_length > 0, "could not encode SIB1\n"); } -void nr_mac_configure_sib19(gNB_MAC_INST *nrmac) -{ - AssertFatal(IS_SA_MODE(get_softmodem_params()), "error: SIB19 only applicable for SA\n"); - NR_COMMON_channels_t *cc = &nrmac->common_channels[0]; - NR_ServingCellConfigCommon_t *scc = cc->ServingCellConfigCommon; - NR_BCCH_DL_SCH_Message_t *sib19 = get_SIB19_NR(scc); - cc->sib19 = sib19; - cc->sib19_bcch_length = encode_SIB19_NR(sib19, cc->sib19_bcch_pdu, sizeof(cc->sib19_bcch_pdu)); - AssertFatal(cc->sib19_bcch_length > 0, "could not encode SIB19\n"); -} - bool nr_mac_add_test_ue(gNB_MAC_INST *nrmac, uint32_t rnti, NR_CellGroupConfig_t *CellGroup) { /* ideally, instead of this function, "users" of this function should call diff --git a/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler.c b/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler.c index 65928fbea3..f86812f893 100644 --- a/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler.c +++ b/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler.c @@ -219,8 +219,6 @@ void gNB_dlsch_ulsch_scheduler(module_id_t module_idP, frame_t frame, slot_t slo // SIB19 will be scheduled if ntn_Config_r17 is initialized if (IS_SA_MODE(get_softmodem_params())) { schedule_nr_sib1(module_idP, frame, slot, &sched_info->DL_req, &sched_info->TX_req); - if (cc->sib19) - schedule_nr_sib19(module_idP, frame, slot, &sched_info->DL_req, &sched_info->TX_req, cc->sib19_bcch_length, cc->sib19_bcch_pdu); } // This schedule PRACH if we are not in phy_test mode diff --git a/openair2/LAYER2/NR_MAC_gNB/mac_proto.h b/openair2/LAYER2/NR_MAC_gNB/mac_proto.h index 65c5aba9f1..2a6ee13a2b 100644 --- a/openair2/LAYER2/NR_MAC_gNB/mac_proto.h +++ b/openair2/LAYER2/NR_MAC_gNB/mac_proto.h @@ -56,8 +56,7 @@ void nr_mac_send_f1_setup_req(void); void nr_mac_config_scc(gNB_MAC_INST *nrmac, NR_ServingCellConfigCommon_t *scc, const nr_mac_config_t *mac_config); void nr_fill_sched_osi(gNB_MAC_INST *nrmac, const struct NR_SetupRelease_PDCCH_ConfigCommon *pdcch_ConfigCommon); void nr_mac_configure_sib1(gNB_MAC_INST *nrmac, const f1ap_plmn_t *plmn, uint64_t cellID, int tac); -void nr_mac_configure_sib19(gNB_MAC_INST *nrmac); - +bool nr_mac_configure_other_sib(gNB_MAC_INST *nrmac, int num_cu_sib, const f1ap_sib_msg_t cu_sib[num_cu_sib]); bool nr_mac_add_test_ue(gNB_MAC_INST *nrmac, uint32_t rnti, NR_CellGroupConfig_t *CellGroup); bool nr_mac_prepare_ra_ue(gNB_MAC_INST *nrmac, uint32_t rnti, NR_CellGroupConfig_t *CellGroup); @@ -474,4 +473,8 @@ bool nr_mac_remove_lcid(NR_UE_sched_ctrl_t *sched_ctrl, long lcid); bool nr_mac_get_new_rnti(NR_UEs_t *UEs, const NR_RA_t *ra_base, int ra_count, rnti_t *rnti); void nr_mac_update_pdcch_closed_loop_adjust(NR_UE_sched_ctrl_t *sched_ctrl, bool feedback_not_detected); +void prepare_du_configuration_update(gNB_MAC_INST *mac, + f1ap_served_cell_info_t *info, + NR_BCCH_BCH_Message_t *mib, + const NR_BCCH_DL_SCH_Message_t *sib1); #endif /*__LAYER2_NR_MAC_PROTO_H__*/ diff --git a/openair2/LAYER2/NR_MAC_gNB/mac_rrc_dl_handler.c b/openair2/LAYER2/NR_MAC_gNB/mac_rrc_dl_handler.c index 9d19fe1c65..ca07d1555a 100644 --- a/openair2/LAYER2/NR_MAC_gNB/mac_rrc_dl_handler.c +++ b/openair2/LAYER2/NR_MAC_gNB/mac_rrc_dl_handler.c @@ -165,8 +165,14 @@ void f1_setup_response(const f1ap_setup_resp_t *resp) AssertFatal(check_plmn_identity(&du_cell->plmn, &cu_cell->plmn), "PLMN mismatch\n"); AssertFatal(du_cell->nr_pci == cu_cell->nrpci, "PCI mismatch: DU %d vs CU %d\n", du_cell->nr_pci, cu_cell->nrpci); - for (int i = 0; i < cu_cell->num_SI; i++) { - // TODO implement handling reception of SIBs in DU + // we can configure other SIB only after having received the ones generated by CU + bool update = nr_mac_configure_other_sib(mac, cu_cell->num_SI, cu_cell->SI_msg); + if (update) { + f1ap_setup_req_t *sr = mac->f1_config.setup_req; + DevAssert(sr->num_cells_available == 1); + f1ap_served_cell_info_t *info = &sr->cell[0].info; + NR_COMMON_channels_t *cc = &mac->common_channels[0]; + prepare_du_configuration_update(mac, info, cc->mib, cc->sib1); } mac->f1_config.setup_resp = malloc(sizeof(*mac->f1_config.setup_resp)); diff --git a/openair2/LAYER2/NR_MAC_gNB/nr_mac_gNB.h b/openair2/LAYER2/NR_MAC_gNB/nr_mac_gNB.h index 65812ae0f5..fe7222b2c6 100644 --- a/openair2/LAYER2/NR_MAC_gNB/nr_mac_gNB.h +++ b/openair2/LAYER2/NR_MAC_gNB/nr_mac_gNB.h @@ -276,7 +276,7 @@ typedef struct { frame_type_t frame_type; NR_BCCH_BCH_Message_t *mib; NR_BCCH_DL_SCH_Message_t *sib1; - NR_BCCH_DL_SCH_Message_t *sib19; + seq_arr_t *du_SIBs; NR_ServingCellConfigCommon_t *ServingCellConfigCommon; /// pre-configured ServingCellConfig that is default for every UE NR_ServingCellConfig_t *pre_ServingCellConfig; @@ -285,9 +285,9 @@ typedef struct { /// Outgoing BCCH pdu for PHY uint8_t sib1_bcch_pdu[NR_MAX_SIB_LENGTH / 8]; int sib1_bcch_length; - /// used for sib19 data - uint8_t sib19_bcch_pdu[NR_MAX_SIB_LENGTH / 8]; - int sib19_bcch_length; + /// used for otherSIB data + uint8_t other_sib_bcch_pdu[2][NR_MAX_SIB_LENGTH / 8]; + int other_sib_bcch_length[2]; /// Template for RA computations NR_RA_t ra[NR_NB_RA_PROC_MAX]; /// VRB map for common channels diff --git a/openair2/RRC/NR/MESSAGES/asn1_msg.c b/openair2/RRC/NR/MESSAGES/asn1_msg.c index 0b811b11ad..07343488a2 100644 --- a/openair2/RRC/NR/MESSAGES/asn1_msg.c +++ b/openair2/RRC/NR/MESSAGES/asn1_msg.c @@ -197,7 +197,7 @@ int xer_nr_sprint(char *string, size_t string_size, asn_TYPE_descriptor_t *td, v //------------------------------------------------------------------------------ -int do_SIB2_NR(uint8_t **msg_SIB2) +int do_SIB2_NR(uint8_t **msg_SIB2, NR_SSB_MTC_t *ssbmtc) { NR_SIB2_t *sib2 = calloc(1, sizeof(*sib2)); sib2->cellReselectionInfoCommon.q_Hyst = NR_SIB2__cellReselectionInfoCommon__q_Hyst_dB0; @@ -222,10 +222,6 @@ int do_SIB2_NR(uint8_t **msg_SIB2) sib2->intraFreqCellReselectionInfo.s_IntraSearchP = 22; // INTEGER (0..31) sib2->intraFreqCellReselectionInfo.t_ReselectionNR = 1; // INTEGER (0..7) sib2->intraFreqCellReselectionInfo.deriveSSB_IndexFromCell = true; - NR_SSB_MTC_t *ssbmtc = calloc(1, sizeof(*ssbmtc)); - ssbmtc->duration = NR_SSB_MTC__duration_sf5; - ssbmtc->periodicityAndOffset.present = NR_SSB_MTC__periodicityAndOffset_PR_sf20; - ssbmtc->periodicityAndOffset.choice.sf20 = 0; sib2->intraFreqCellReselectionInfo.smtc = ssbmtc; ssize_t size = uper_encode_to_new_buffer(&asn_DEF_NR_SIB2, NULL, (void *)sib2, (void **)msg_SIB2); diff --git a/openair2/RRC/NR/MESSAGES/asn1_msg.h b/openair2/RRC/NR/MESSAGES/asn1_msg.h index 3916305171..2d703912e2 100644 --- a/openair2/RRC/NR/MESSAGES/asn1_msg.h +++ b/openair2/RRC/NR/MESSAGES/asn1_msg.h @@ -66,7 +66,7 @@ struct asn_TYPE_descriptor_s; */ int xer_sprint_NR(char *string, size_t string_size, struct asn_TYPE_descriptor_s *td, void *sptr); -int do_SIB2_NR(uint8_t **msg_SIB2); +int do_SIB2_NR(uint8_t **msg_SIB2, NR_SSB_MTC_t *ssbmtc); int do_RRCReject(uint8_t *const buffer); diff --git a/openair2/RRC/NR/nr_rrc_config.c b/openair2/RRC/NR/nr_rrc_config.c index e39dd3581f..5c2efdd3c8 100644 --- a/openair2/RRC/NR/nr_rrc_config.c +++ b/openair2/RRC/NR/nr_rrc_config.c @@ -2349,6 +2349,105 @@ static long get_NR_UE_TimersAndConstants_t319(const nr_mac_timers_t *timer_confi } } +void add_sib_to_systeminformation(NR_SystemInformation_IEs_t *si, struct NR_SystemInformation_IEs__sib_TypeAndInfo__Member *type) +{ + asn1cSeqAdd(&si->sib_TypeAndInfo.list, type); +} + +void update_SIB1_NR_SI(NR_BCCH_DL_SCH_Message_t *sib1_bcch, int num_sibs, int sibs[num_sibs]) +{ + NR_SIB1_t *sib1 = sib1_bcch->message.choice.c1->choice.systemInformationBlockType1; + //si-SchedulingInfo + NR_SI_SchedulingInfo_t *info = calloc(1, sizeof(*info)); + info->si_WindowLength = NR_SI_SchedulingInfo__si_WindowLength_s40; + NR_SchedulingInfo_t *schedulingInfo = calloc(1, sizeof(*schedulingInfo)); + schedulingInfo->si_BroadcastStatus = NR_SchedulingInfo__si_BroadcastStatus_broadcasting; + schedulingInfo->si_Periodicity = NR_SchedulingInfo__si_Periodicity_rf16; + bool reg_sib = false; + NR_SI_SchedulingInfo_v1700_t *si_schedulingInfo_v17 = NULL; + NR_SchedulingInfo2_r17_t *schedulingInfo2_r17 = NULL; + bool v17_sib = false; + for (int i = 0; i < num_sibs; i++) { + int sib_num = sibs[i]; + AssertFatal(sib_num > 1 && sib_num < 21, "other SIB invalid type\n"); + if (sib_num < 15) { + reg_sib = true; + NR_SIB_TypeInfo_t *mapping = calloc(1, sizeof(*mapping)); + // NR_SIB_TypeInfo__type_sibType2 = 0 + // NR_SIB_TypeInfo__type_sibType3 = 1 + // and so on + mapping->type = sib_num - 2; + // The field is mandatory present if the type is different from SIB6, SIB7 or SIB8. For SIB6, SIB7 and SIB8 it is absent. + if (sib_num != 6 && sib_num != 7 && sib_num != 8) { + mapping->valueTag = calloc(1, sizeof(*mapping->valueTag)); + *mapping->valueTag = 0; + } + asn1cSeqAdd(&schedulingInfo->sib_MappingInfo.list, mapping); + } else { + if (v17_sib == false) { + si_schedulingInfo_v17 = calloc(1, sizeof(*si_schedulingInfo_v17)); + schedulingInfo2_r17 = calloc(1, sizeof(*schedulingInfo2_r17)); + schedulingInfo2_r17->si_BroadcastStatus_r17 = NR_SchedulingInfo2_r17__si_BroadcastStatus_r17_broadcasting; + schedulingInfo2_r17->si_Periodicity_r17 = NR_SchedulingInfo2_r17__si_Periodicity_r17_rf16; + schedulingInfo2_r17->si_WindowPosition_r17 = 2; + } + NR_SIB_TypeInfo_v1700_t *mapping17 = calloc(1, sizeof(*mapping17)); + mapping17->sibType_r17.present = NR_SIB_TypeInfo_v1700__sibType_r17_PR_type1_r17; + // NR_SIB_TypeInfo_v1700__sibType_r17__type1_r17_sibType15 = 0 + // and so on + mapping17->sibType_r17.choice.type1_r17 = sib_num - 15; + mapping17->valueTag_r17 = calloc(1, sizeof(*mapping17->valueTag_r17)); + *mapping17->valueTag_r17 = 0; + asn1cSeqAdd(&schedulingInfo2_r17->sib_MappingInfo_r17.list, mapping17); + } + } + AssertFatal(reg_sib, "At least 1 SIB from SchedulingInfo (SIB2 to SIB14 included) needs to be present\n"); + asn1cSeqAdd(&info->schedulingInfoList.list, schedulingInfo); + sib1->si_SchedulingInfo = info; + if (si_schedulingInfo_v17) { + asn1cSeqAdd(&si_schedulingInfo_v17->schedulingInfoList2_r17.list, schedulingInfo2_r17); + NR_SIB1_v1610_IEs_t *sib1_v1610 = NULL; + if (sib1->nonCriticalExtension) + sib1_v1610 = sib1->nonCriticalExtension; + else { + sib1_v1610 = calloc_or_fail(1, sizeof(*sib1_v1610)); + sib1->nonCriticalExtension = sib1_v1610; + } + NR_SIB1_v1630_IEs_t *sib1_v1630 = NULL; + if (sib1_v1610->nonCriticalExtension) + sib1_v1630 = sib1_v1610->nonCriticalExtension; + else { + sib1_v1630 = calloc_or_fail(1, sizeof(*sib1_v1630)); + sib1_v1610->nonCriticalExtension = sib1_v1630; + } + NR_SIB1_v1700_IEs_t *sib1_v17 = NULL; + if (sib1_v1630->nonCriticalExtension) + sib1_v17 = sib1_v1630->nonCriticalExtension; + else { + sib1_v17 = calloc_or_fail(1, sizeof(*sib1_v17)); + sib1_v1630->nonCriticalExtension = sib1_v17; + } + sib1_v17->si_SchedulingInfo_v1700 = si_schedulingInfo_v17; + } + if (LOG_DEBUGFLAG(DEBUG_ASN1)) { + xer_fprint(stdout, &asn_DEF_NR_BCCH_DL_SCH_Message, sib1_bcch); + } +} + +int encode_sysinfo_ie(NR_SystemInformation_IEs_t *sysInfo, uint8_t *buf, int len) +{ + if (sysInfo->sib_TypeAndInfo.list.count == 0) + return 0; + NR_SystemInformation_t si = {.criticalExtensions.present = NR_SystemInformation__criticalExtensions_PR_systemInformation}; + si.criticalExtensions.choice.systemInformation = sysInfo; + struct NR_BCCH_DL_SCH_MessageType__c1 c1 = {.present = NR_BCCH_DL_SCH_MessageType__c1_PR_systemInformation}; + c1.choice.systemInformation = &si; + NR_BCCH_DL_SCH_MessageType_t message_type = {.present = NR_BCCH_DL_SCH_MessageType_PR_c1}; + message_type.choice.c1 = &c1; + NR_BCCH_DL_SCH_Message_t sib_message = {.message = message_type}; + return encode_SIB_NR(&sib_message, buf, len); +} + static bool is_ntn_band(int band) { // TS 3GPP 38.101-5 V1807 Section 5.2.2 @@ -2378,7 +2477,7 @@ NR_BCCH_DL_SCH_Message_t *get_SIB1_NR(const NR_ServingCellConfigCommon_t *scc, sib1_message->message.choice.c1->present = NR_BCCH_DL_SCH_MessageType__c1_PR_systemInformationBlockType1; sib1_message->message.choice.c1->choice.systemInformationBlockType1 = CALLOC(1,sizeof(struct NR_SIB1)); AssertFatal(sib1_message->message.choice.c1->choice.systemInformationBlockType1 != NULL, "out of memory\n"); - struct NR_SIB1 *sib1 = sib1_message->message.choice.c1->choice.systemInformationBlockType1; + NR_SIB1_t *sib1 = sib1_message->message.choice.c1->choice.systemInformationBlockType1; // cellSelectionInfo sib1->cellSelectionInfo = CALLOC(1,sizeof(*sib1->cellSelectionInfo)); @@ -2429,37 +2528,6 @@ NR_BCCH_DL_SCH_Message_t *get_SIB1_NR(const NR_ServingCellConfigCommon_t *scc, // connEstFailureControl // TODO: add connEstFailureControl - //si-SchedulingInfo - /*sib1->si_SchedulingInfo = CALLOC(1,sizeof(struct NR_SI_SchedulingInfo)); - asn_set_empty(&sib1->si_SchedulingInfo->schedulingInfoList.list); - sib1->si_SchedulingInfo->si_WindowLength = NR_SI_SchedulingInfo__si_WindowLength_s40; - struct NR_SchedulingInfo *schedulingInfo = CALLOC(1,sizeof(struct NR_SchedulingInfo)); - schedulingInfo->si_BroadcastStatus = NR_SchedulingInfo__si_BroadcastStatus_broadcasting; - schedulingInfo->si_Periodicity = NR_SchedulingInfo__si_Periodicity_rf8; - asn_set_empty(&schedulingInfo->sib_MappingInfo.list); - - NR_SIB_TypeInfo_t *sib_type3 = CALLOC(1,sizeof(e_NR_SIB_TypeInfo__type)); - sib_type3->type = NR_SIB_TypeInfo__type_sibType3; - sib_type3->valueTag = CALLOC(1,sizeof(sib_type3->valueTag)); - asn1cSeqAdd(&schedulingInfo->sib_MappingInfo.list,sib_type3); - - NR_SIB_TypeInfo_t *sib_type5 = CALLOC(1,sizeof(e_NR_SIB_TypeInfo__type)); - sib_type5->type = NR_SIB_TypeInfo__type_sibType5; - sib_type5->valueTag = CALLOC(1,sizeof(sib_type5->valueTag)); - asn1cSeqAdd(&schedulingInfo->sib_MappingInfo.list,sib_type5); - - NR_SIB_TypeInfo_t *sib_type4 = CALLOC(1,sizeof(e_NR_SIB_TypeInfo__type)); - sib_type4->type = NR_SIB_TypeInfo__type_sibType4; - sib_type4->valueTag = CALLOC(1,sizeof(sib_type4->valueTag)); - asn1cSeqAdd(&schedulingInfo->sib_MappingInfo.list,sib_type4); - - NR_SIB_TypeInfo_t *sib_type2 = CALLOC(1,sizeof(e_NR_SIB_TypeInfo__type)); - sib_type2->type = NR_SIB_TypeInfo__type_sibType2; - sib_type2->valueTag = CALLOC(1,sizeof(sib_type2->valueTag)); - asn1cSeqAdd(&schedulingInfo->sib_MappingInfo.list,sib_type2); - - asn1cSeqAdd(&sib1->si_SchedulingInfo->schedulingInfoList.list,schedulingInfo);*/ - // servingCellConfigCommon asn1cCalloc(sib1->servingCellConfigCommon, ServCellCom); NR_BWP_DownlinkCommon_t *initialDownlinkBWP = &ServCellCom->downlinkConfigCommon.initialDownlinkBWP; @@ -2685,29 +2753,9 @@ NR_BCCH_DL_SCH_Message_t *get_SIB1_NR(const NR_ServingCellConfigCommon_t *scc, *sib1_1700->intraFreqReselectionRedCap_r17 = redcap_config->intraFreqReselectionRedCap_r17; } - // sib19 scheduling info - // ensure ntn-config is initialized - if (scc->ext2 && scc->ext2->ntn_Config_r17) { - struct NR_SI_SchedulingInfo_v1700 *sib_v17_scheduling_info = CALLOC(1, sizeof(struct NR_SI_SchedulingInfo_v1700)); - - struct NR_SchedulingInfo2_r17 *si_schedulinginfo2_r17 = CALLOC(1, sizeof(struct NR_SchedulingInfo2_r17)); - si_schedulinginfo2_r17->si_BroadcastStatus_r17 = NR_SchedulingInfo2_r17__si_BroadcastStatus_r17_broadcasting; - si_schedulinginfo2_r17->si_WindowPosition_r17 = 2; - si_schedulinginfo2_r17->si_Periodicity_r17 = NR_SchedulingInfo2_r17__si_Periodicity_r17_rf8; - - struct NR_SIB_TypeInfo_v1700 *sib_type_info = CALLOC(1, sizeof(struct NR_SIB_TypeInfo_v1700)); - sib_type_info->sibType_r17.present = NR_SIB_TypeInfo_v1700__sibType_r17_PR_type1_r17; - sib_type_info->sibType_r17.choice.type1_r17 = NR_SIB_TypeInfo_v1700__sibType_r17__type1_r17_sibType19; - - asn1cSeqAdd(&si_schedulinginfo2_r17->sib_MappingInfo_r17.list, sib_type_info); - asn1cSeqAdd(&sib_v17_scheduling_info->schedulingInfoList2_r17.list, si_schedulinginfo2_r17); - sib1_1700->si_SchedulingInfo_v1700 = sib_v17_scheduling_info; - - if (is_ntn_band(band)) { - // If cell provides NTN access, set cellBarredNTN to notBarred. - sib1_1700->cellBarredNTN_r17 = CALLOC(1, sizeof(long)); - *sib1_1700->cellBarredNTN_r17 = NR_SIB1_v1700_IEs__cellBarredNTN_r17_notBarred; - } + if (is_ntn_band(band)) { + // If cell provides NTN access, set cellBarredNTN to notBarred. + asn1cCallocOne(sib1_1700->cellBarredNTN_r17, NR_SIB1_v1700_IEs__cellBarredNTN_r17_notBarred); } } @@ -2723,57 +2771,23 @@ void free_SIB1_NR(NR_BCCH_DL_SCH_Message_t *sib1) ASN_STRUCT_FREE(asn_DEF_NR_BCCH_DL_SCH_Message, sib1); } -int encode_SIB1_NR(NR_BCCH_DL_SCH_Message_t *sib1, uint8_t *buffer, int max_buffer_size) +int encode_SIB_NR(NR_BCCH_DL_SCH_Message_t *sib, uint8_t *buffer, int max_buffer_size) { AssertFatal(max_buffer_size <= NR_MAX_SIB_LENGTH / 8, - "%s(): maximum buffer size too large: 3GPP TS 38.331 section 5.2.1 - The physical layer imposes a limit to the " - "maximum size a SIB can take. The maximum SIB1 or SI message size is 2976 bits.\n", - __func__); - asn_enc_rval_t enc_rval = uper_encode_to_buffer(&asn_DEF_NR_BCCH_DL_SCH_Message, NULL, sib1, buffer, max_buffer_size); + "Maximum buffer size too large: 3GPP TS 38.331 section 5.2.1 - The physical layer imposes a limit to the " + "maximum size a SIB can take. The maximum SIB1 or SI message size is 2976 bits.\n"); + asn_enc_rval_t enc_rval = uper_encode_to_buffer(&asn_DEF_NR_BCCH_DL_SCH_Message, NULL, sib, buffer, max_buffer_size); AssertFatal(enc_rval.encoded > 0 && enc_rval.encoded <= max_buffer_size * 8, "ASN1 message encoding failed (%s, %lu)!\n", enc_rval.failed_type->name, enc_rval.encoded); return (enc_rval.encoded + 7) / 8; } -NR_BCCH_DL_SCH_Message_t *get_SIB19_NR(const NR_ServingCellConfigCommon_t *scc) +NR_SIB19_r17_t *get_SIB19_NR(const NR_ServingCellConfigCommon_t *scc) { - NR_BCCH_DL_SCH_Message_t *sib_message = CALLOC(1, sizeof(NR_BCCH_DL_SCH_Message_t)); - sib_message->message.present = NR_BCCH_DL_SCH_MessageType_PR_c1; - sib_message->message.choice.c1 = CALLOC(1,sizeof(struct NR_BCCH_DL_SCH_MessageType__c1)); - sib_message->message.choice.c1->present = NR_BCCH_DL_SCH_MessageType__c1_PR_systemInformation; - sib_message->message.choice.c1->choice.systemInformation = CALLOC(1,sizeof(struct NR_SystemInformation)); - - struct NR_SystemInformation *sib = sib_message->message.choice.c1->choice.systemInformation; - sib->criticalExtensions.present = NR_SystemInformation__criticalExtensions_PR_systemInformation; - sib->criticalExtensions.choice.systemInformation = CALLOC(1, sizeof(struct NR_SystemInformation_IEs)); - - struct NR_SystemInformation_IEs *ies = sib->criticalExtensions.choice.systemInformation; - - SystemInformation_IEs__sib_TypeAndInfo__Member *sib19 = NULL; - sib19 = CALLOC(1, sizeof(SystemInformation_IEs__sib_TypeAndInfo__Member)); - sib19->present = NR_SystemInformation_IEs__sib_TypeAndInfo__Member_PR_sib19_v1700; - sib19->choice.sib19_v1700 = CALLOC(1, sizeof(struct NR_SIB19_r17)); - + NR_SIB19_r17_t *sib19 = calloc(1, sizeof(*sib19)); // use ntn-config from NR_ServingCellConfigCommon_t - const int copy_result = asn_copy(&asn_DEF_NR_NTN_Config_r17, (void **) &sib19->choice.sib19_v1700->ntn_Config_r17, scc->ext2->ntn_Config_r17); + const int copy_result = asn_copy(&asn_DEF_NR_NTN_Config_r17, (void **) &sib19->ntn_Config_r17, scc->ext2->ntn_Config_r17); AssertFatal(copy_result == 0, "Was unable to copy ntn_Config_r17 from scc to SIB19 structure\n"); - - asn1cSeqAdd(&ies->sib_TypeAndInfo.list, sib19); - - if (LOG_DEBUGFLAG(DEBUG_ASN1)) { - xer_fprint(stdout, &asn_DEF_NR_BCCH_DL_SCH_Message, sib_message); - } - - return sib_message; -} - -int encode_SIB19_NR(NR_BCCH_DL_SCH_Message_t *sib19, uint8_t *buffer, int max_buffer_size) -{ - asn_enc_rval_t enc_rval; - - enc_rval = uper_encode_to_buffer(&asn_DEF_NR_BCCH_DL_SCH_Message, NULL, sib19, buffer, 150); - AssertFatal(enc_rval.encoded > 0, "ASN1 message encoding failed (%s, %lu)!\n", enc_rval.failed_type->name, enc_rval.encoded); - - return ((enc_rval.encoded + 7) / 8); + return sib19; } void free_SIB19_NR(NR_BCCH_DL_SCH_Message_t *sib19) diff --git a/openair2/RRC/NR/nr_rrc_config.h b/openair2/RRC/NR/nr_rrc_config.h index 707e46cd66..d56db4b0cf 100644 --- a/openair2/RRC/NR/nr_rrc_config.h +++ b/openair2/RRC/NR/nr_rrc_config.h @@ -83,12 +83,12 @@ NR_BCCH_DL_SCH_Message_t *get_SIB1_NR(const NR_ServingCellConfigCommon_t *scc, uint64_t cellID, int tac, const nr_mac_config_t *mac_config); +void update_SIB1_NR_SI(NR_BCCH_DL_SCH_Message_t *sib1, int num_sibs, int sibs[num_sibs]); +int encode_sysinfo_ie(NR_SystemInformation_IEs_t *sysInfo, uint8_t *buf, int len); void free_SIB1_NR(NR_BCCH_DL_SCH_Message_t *sib1); -int encode_SIB1_NR(NR_BCCH_DL_SCH_Message_t *sib1, uint8_t *buffer, int max_buffer_size); - -NR_BCCH_DL_SCH_Message_t *get_SIB19_NR(const NR_ServingCellConfigCommon_t *scc); -int encode_SIB19_NR(NR_BCCH_DL_SCH_Message_t *sib19, uint8_t *buffer, int max_buffer_size); -void free_SIB19_NR(NR_BCCH_DL_SCH_Message_t *sib19); +int encode_SIB_NR(NR_BCCH_DL_SCH_Message_t *sib, uint8_t *buffer, int max_buffer_size); +void add_sib_to_systeminformation(NR_SystemInformation_IEs_t *si, struct NR_SystemInformation_IEs__sib_TypeAndInfo__Member *type); +NR_SIB19_r17_t *get_SIB19_NR(const NR_ServingCellConfigCommon_t *scc); NR_CellGroupConfig_t *get_initial_cellGroupConfig(int uid, const NR_ServingCellConfigCommon_t *scc, diff --git a/openair2/RRC/NR/nr_rrc_defs.h b/openair2/RRC/NR/nr_rrc_defs.h index 51f2d427e5..f0dfc2ec02 100644 --- a/openair2/RRC/NR/nr_rrc_defs.h +++ b/openair2/RRC/NR/nr_rrc_defs.h @@ -361,12 +361,6 @@ typedef struct nr_rrc_cuup_container_t { sctp_assoc_t assoc_id; } nr_rrc_cuup_container_t; -typedef struct { - uint8_t *SIB_buffer; - int SIB_size; - int SIB_type; -} rrc_SIBs_t; - //---NR---(completely change)--------------------- typedef struct gNB_RRC_INST_s { diff --git a/openair2/RRC/NR/rrc_gNB.c b/openair2/RRC/NR/rrc_gNB.c index 0acba1797c..b93e08e79f 100644 --- a/openair2/RRC/NR/rrc_gNB.c +++ b/openair2/RRC/NR/rrc_gNB.c @@ -215,31 +215,6 @@ static void nr_rrc_transfer_protected_rrc_message(const gNB_RRC_INST *rrc, #endif } -///---------------------------------------------------------------------------------------------------------------/// -///---------------------------------------------------------------------------------------------------------------/// - -static void init_NR_SI(gNB_RRC_INST *rrc) -{ - if (!NODE_IS_DU(rrc->node_type)) { - seq_arr_t *sibs = rrc->SIBs; - if (sibs) { - for (int i = 0; i < sibs->size; i++) { - rrc_SIBs_t *sib = (rrc_SIBs_t *)seq_arr_at(sibs, i); - AssertFatal(sib->SIB_type == 2, "Only SIB2 currently implemented\n"); - sib->SIB_size = do_SIB2_NR(&sib->SIB_buffer); - } - } - } - if (get_softmodem_params()->phy_test > 0 || get_softmodem_params()->do_ra > 0) { - AssertFatal(NODE_IS_MONOLITHIC(rrc->node_type), "phy_test and do_ra only work in monolithic\n"); - rrc_gNB_ue_context_t *ue_context_p = rrc_gNB_allocate_new_ue_context(rrc); - LOG_I(NR_RRC,"Adding new user (%p)\n",ue_context_p); - if (!NODE_IS_CU(rrc->node_type)) { - rrc_add_nsa_user(rrc,ue_context_p,NULL); - } - } -} - static void rrc_gNB_CU_DU_init(gNB_RRC_INST *rrc) { switch (rrc->node_type) { @@ -279,8 +254,15 @@ void openair_rrc_gNB_configuration(gNB_RRC_INST *rrc, gNB_RrcConfigurationReq *c RB_INIT(&rrc->cuups); RB_INIT(&rrc->dus); rrc->configuration = *configuration; - /// System Information INIT - init_NR_SI(rrc); + + if (get_softmodem_params()->phy_test > 0 || get_softmodem_params()->do_ra > 0) { + AssertFatal(NODE_IS_MONOLITHIC(rrc->node_type), "phy_test and do_ra only work in monolithic\n"); + rrc_gNB_ue_context_t *ue_context_p = rrc_gNB_allocate_new_ue_context(rrc); + LOG_I(NR_RRC,"Adding new user (%p)\n",ue_context_p); + if (!NODE_IS_CU(rrc->node_type)) { + rrc_add_nsa_user(rrc,ue_context_p,NULL); + } + } } static void rrc_gNB_process_AdditionRequestInformation(const module_id_t gnb_mod_idP, x2ap_ENDC_sgnb_addition_req_t *m) diff --git a/openair2/RRC/NR/rrc_gNB_du.c b/openair2/RRC/NR/rrc_gNB_du.c index 10113c21aa..f34ca13e6c 100644 --- a/openair2/RRC/NR/rrc_gNB_du.c +++ b/openair2/RRC/NR/rrc_gNB_du.c @@ -61,6 +61,15 @@ 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; } +static NR_SSB_MTC_t *get_ssb_mtc(const NR_MeasurementTimingConfiguration_t *mtc) +{ + // TODO verify which element of the list to pick + NR_MeasTimingList_t *mtlist = mtc->criticalExtensions.choice.c1->choice.measTimingConf->measTiming; + NR_SSB_MTC_t *ssb_mtc = calloc(1, sizeof(*ssb_mtc)); + *ssb_mtc = mtlist->list.array[0]->frequencyAndTiming->ssb_MeasurementTimingConfiguration; + return ssb_mtc; +} + static int ssb_arfcn_mtc(const NR_MeasurementTimingConfiguration_t *mtc) { /* format has been verified when accepting MeasurementTimingConfiguration */ @@ -397,14 +406,23 @@ void rrc_gNB_process_f1_setup_req(f1ap_setup_req_t *req, sctp_assoc_t assoc_id) .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++) { - rrc_SIBs_t *si = (rrc_SIBs_t *)seq_arr_at(sibs, i); - cell.SI_msg[cell.num_SI].SI_container = si->SIB_buffer; - cell.SI_msg[cell.num_SI].SI_container_length = si->SIB_size; - cell.SI_msg[cell.num_SI].SI_type = si->SIB_type; - cell.num_SI++; + 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); + 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); + } } } From 04213f29815fb75ef6d9ac8e1909fe89b5d15339 Mon Sep 17 00:00:00 2001 From: francescomani Date: Thu, 26 Dec 2024 11:20:57 +0100 Subject: [PATCH 43/63] schedule SIBs other than 1 (including fixes for SIB19) --- openair2/LAYER2/NR_MAC_COMMON/nr_mac_common.c | 68 +++ openair2/LAYER2/NR_MAC_COMMON/nr_mac_common.h | 1 + .../NR_MAC_UE/nr_ue_dci_configuration.c | 70 --- openair2/LAYER2/NR_MAC_gNB/config.c | 20 - openair2/LAYER2/NR_MAC_gNB/gNB_scheduler.c | 1 + openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_RA.c | 78 +-- .../LAYER2/NR_MAC_gNB/gNB_scheduler_bch.c | 452 +++++++++--------- .../LAYER2/NR_MAC_gNB/gNB_scheduler_dlsch.c | 2 +- .../NR_MAC_gNB/gNB_scheduler_primitives.c | 26 +- .../LAYER2/NR_MAC_gNB/gNB_scheduler_ulsch.c | 2 +- openair2/LAYER2/NR_MAC_gNB/mac_proto.h | 18 +- openair2/LAYER2/NR_MAC_gNB/nr_mac_gNB.h | 16 +- openair2/RRC/NR/nr_rrc_config.c | 4 +- 13 files changed, 337 insertions(+), 421 deletions(-) diff --git a/openair2/LAYER2/NR_MAC_COMMON/nr_mac_common.c b/openair2/LAYER2/NR_MAC_COMMON/nr_mac_common.c index 38b65a8158..44c6673f2f 100644 --- a/openair2/LAYER2/NR_MAC_COMMON/nr_mac_common.c +++ b/openair2/LAYER2/NR_MAC_COMMON/nr_mac_common.c @@ -1505,6 +1505,74 @@ void find_aggregation_candidates(uint8_t *aggregation_level, } } +void get_monitoring_period_offset(const NR_SearchSpace_t *ss, int *period, int *offset) +{ + switch(ss->monitoringSlotPeriodicityAndOffset->present) { + case NR_SearchSpace__monitoringSlotPeriodicityAndOffset_PR_sl1: + *period = 1; + *offset = 0; + break; + case NR_SearchSpace__monitoringSlotPeriodicityAndOffset_PR_sl2: + *period = 2; + *offset = ss->monitoringSlotPeriodicityAndOffset->choice.sl2; + break; + case NR_SearchSpace__monitoringSlotPeriodicityAndOffset_PR_sl4: + *period = 4; + *offset = ss->monitoringSlotPeriodicityAndOffset->choice.sl4; + break; + case NR_SearchSpace__monitoringSlotPeriodicityAndOffset_PR_sl5: + *period = 5; + *offset = ss->monitoringSlotPeriodicityAndOffset->choice.sl5; + break; + case NR_SearchSpace__monitoringSlotPeriodicityAndOffset_PR_sl8: + *period = 8; + *offset = ss->monitoringSlotPeriodicityAndOffset->choice.sl8; + break; + case NR_SearchSpace__monitoringSlotPeriodicityAndOffset_PR_sl10: + *period = 10; + *offset = ss->monitoringSlotPeriodicityAndOffset->choice.sl10; + break; + case NR_SearchSpace__monitoringSlotPeriodicityAndOffset_PR_sl16: + *period = 16; + *offset = ss->monitoringSlotPeriodicityAndOffset->choice.sl16; + break; + case NR_SearchSpace__monitoringSlotPeriodicityAndOffset_PR_sl20: + *period = 20; + *offset = ss->monitoringSlotPeriodicityAndOffset->choice.sl20; + break; + case NR_SearchSpace__monitoringSlotPeriodicityAndOffset_PR_sl40: + *period = 40; + *offset = ss->monitoringSlotPeriodicityAndOffset->choice.sl40; + break; + case NR_SearchSpace__monitoringSlotPeriodicityAndOffset_PR_sl80: + *period = 80; + *offset = ss->monitoringSlotPeriodicityAndOffset->choice.sl80; + break; + case NR_SearchSpace__monitoringSlotPeriodicityAndOffset_PR_sl160: + *period = 160; + *offset = ss->monitoringSlotPeriodicityAndOffset->choice.sl160; + break; + case NR_SearchSpace__monitoringSlotPeriodicityAndOffset_PR_sl320: + *period = 320; + *offset = ss->monitoringSlotPeriodicityAndOffset->choice.sl320; + break; + case NR_SearchSpace__monitoringSlotPeriodicityAndOffset_PR_sl640: + *period = 640; + *offset = ss->monitoringSlotPeriodicityAndOffset->choice.sl640; + break; + case NR_SearchSpace__monitoringSlotPeriodicityAndOffset_PR_sl1280: + *period = 1280; + *offset = ss->monitoringSlotPeriodicityAndOffset->choice.sl1280; + break; + case NR_SearchSpace__monitoringSlotPeriodicityAndOffset_PR_sl2560: + *period = 2560; + *offset = ss->monitoringSlotPeriodicityAndOffset->choice.sl2560; + break; + default: + AssertFatal(1==0,"Invalid monitoring slot periodicity value\n"); + break; + } +} void set_monitoring_periodicity_offset(NR_SearchSpace_t *ss, uint16_t period, diff --git a/openair2/LAYER2/NR_MAC_COMMON/nr_mac_common.h b/openair2/LAYER2/NR_MAC_COMMON/nr_mac_common.h index 487e81ac40..8e0e57d3a6 100644 --- a/openair2/LAYER2/NR_MAC_COMMON/nr_mac_common.h +++ b/openair2/LAYER2/NR_MAC_COMMON/nr_mac_common.h @@ -213,6 +213,7 @@ uint8_t nr_get_Qm_ul(uint8_t Imcs, uint8_t table_idx); uint32_t nr_get_code_rate_ul(uint8_t Imcs, uint8_t table_idx); uint16_t get_nr_srs_offset(NR_SRS_PeriodicityAndOffset_t periodicityAndOffset); +void get_monitoring_period_offset(const NR_SearchSpace_t *ss, int *period, int *offset); uint32_t nr_compute_tbslbrm(uint16_t table, uint16_t nb_rb, diff --git a/openair2/LAYER2/NR_MAC_UE/nr_ue_dci_configuration.c b/openair2/LAYER2/NR_MAC_UE/nr_ue_dci_configuration.c index 8457cad135..f3a132669f 100644 --- a/openair2/LAYER2/NR_MAC_UE/nr_ue_dci_configuration.c +++ b/openair2/LAYER2/NR_MAC_UE/nr_ue_dci_configuration.c @@ -343,76 +343,6 @@ void config_dci_pdu(NR_UE_MAC_INST_t *mac, dl_config->number_pdus += 1; } - -void get_monitoring_period_offset(const NR_SearchSpace_t *ss, int *period, int *offset) -{ - switch(ss->monitoringSlotPeriodicityAndOffset->present) { - case NR_SearchSpace__monitoringSlotPeriodicityAndOffset_PR_sl1: - *period = 1; - *offset = 0; - break; - case NR_SearchSpace__monitoringSlotPeriodicityAndOffset_PR_sl2: - *period = 2; - *offset = ss->monitoringSlotPeriodicityAndOffset->choice.sl2; - break; - case NR_SearchSpace__monitoringSlotPeriodicityAndOffset_PR_sl4: - *period = 4; - *offset = ss->monitoringSlotPeriodicityAndOffset->choice.sl4; - break; - case NR_SearchSpace__monitoringSlotPeriodicityAndOffset_PR_sl5: - *period = 5; - *offset = ss->monitoringSlotPeriodicityAndOffset->choice.sl5; - break; - case NR_SearchSpace__monitoringSlotPeriodicityAndOffset_PR_sl8: - *period = 8; - *offset = ss->monitoringSlotPeriodicityAndOffset->choice.sl8; - break; - case NR_SearchSpace__monitoringSlotPeriodicityAndOffset_PR_sl10: - *period = 10; - *offset = ss->monitoringSlotPeriodicityAndOffset->choice.sl10; - break; - case NR_SearchSpace__monitoringSlotPeriodicityAndOffset_PR_sl16: - *period = 16; - *offset = ss->monitoringSlotPeriodicityAndOffset->choice.sl16; - break; - case NR_SearchSpace__monitoringSlotPeriodicityAndOffset_PR_sl20: - *period = 20; - *offset = ss->monitoringSlotPeriodicityAndOffset->choice.sl20; - break; - case NR_SearchSpace__monitoringSlotPeriodicityAndOffset_PR_sl40: - *period = 40; - *offset = ss->monitoringSlotPeriodicityAndOffset->choice.sl40; - break; - case NR_SearchSpace__monitoringSlotPeriodicityAndOffset_PR_sl80: - *period = 80; - *offset = ss->monitoringSlotPeriodicityAndOffset->choice.sl80; - break; - case NR_SearchSpace__monitoringSlotPeriodicityAndOffset_PR_sl160: - *period = 160; - *offset = ss->monitoringSlotPeriodicityAndOffset->choice.sl160; - break; - case NR_SearchSpace__monitoringSlotPeriodicityAndOffset_PR_sl320: - *period = 320; - *offset = ss->monitoringSlotPeriodicityAndOffset->choice.sl320; - break; - case NR_SearchSpace__monitoringSlotPeriodicityAndOffset_PR_sl640: - *period = 640; - *offset = ss->monitoringSlotPeriodicityAndOffset->choice.sl640; - break; - case NR_SearchSpace__monitoringSlotPeriodicityAndOffset_PR_sl1280: - *period = 1280; - *offset = ss->monitoringSlotPeriodicityAndOffset->choice.sl1280; - break; - case NR_SearchSpace__monitoringSlotPeriodicityAndOffset_PR_sl2560: - *period = 2560; - *offset = ss->monitoringSlotPeriodicityAndOffset->choice.sl2560; - break; - default: - AssertFatal(1==0,"Invalid monitoring slot periodicity value\n"); - break; - } -} - bool is_ss_monitor_occasion(const int frame, const int slot, const int slots_per_frame, const NR_SearchSpace_t *ss) { if (!ss) diff --git a/openair2/LAYER2/NR_MAC_gNB/config.c b/openair2/LAYER2/NR_MAC_gNB/config.c index c9a58a4e4a..26319a6049 100644 --- a/openair2/LAYER2/NR_MAC_gNB/config.c +++ b/openair2/LAYER2/NR_MAC_gNB/config.c @@ -864,32 +864,12 @@ void nr_mac_config_scc(gNB_MAC_INST *nrmac, NR_ServingCellConfigCommon_t *scc, c NR_RA_t *ra = &cc->ra[n]; nr_clear_ra_proc(ra); } - - nr_fill_sched_osi(nrmac, scc->downlinkConfigCommon->initialDownlinkBWP->pdcch_ConfigCommon); NR_SCHED_UNLOCK(&nrmac->sched_lock); if (IS_SA_MODE(get_softmodem_params())) config_sched_ctrlCommon(nrmac); } -void nr_fill_sched_osi(gNB_MAC_INST *nrmac, const struct NR_SetupRelease_PDCCH_ConfigCommon *pdcch_ConfigCommon) -{ - NR_SearchSpaceId_t *osi_SearchSpace = pdcch_ConfigCommon->choice.setup->searchSpaceOtherSystemInformation; - if (osi_SearchSpace) { - nrmac->sched_osi = CALLOC(1, sizeof(*nrmac->sched_osi)); - - struct NR_PDCCH_ConfigCommon__commonSearchSpaceList *commonSearchSpaceList = - pdcch_ConfigCommon->choice.setup->commonSearchSpaceList; - AssertFatal(commonSearchSpaceList->list.count > 0, "common SearchSpace list has 0 elements\n"); - for (int i = 0; i < commonSearchSpaceList->list.count; i++) { - NR_SearchSpace_t *ss = commonSearchSpaceList->list.array[i]; - if (ss->searchSpaceId == *osi_SearchSpace) - nrmac->sched_osi->search_space = ss; - } - AssertFatal(nrmac->sched_osi->search_space != NULL, "SearchSpace cannot be null for OSI\n"); - } -} - bool nr_mac_configure_other_sib(gNB_MAC_INST *nrmac, int num_cu_sib, const f1ap_sib_msg_t cu_sib[num_cu_sib]) { NR_COMMON_channels_t *cc = &nrmac->common_channels[0]; diff --git a/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler.c b/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler.c index f86812f893..e14004edc3 100644 --- a/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler.c +++ b/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler.c @@ -219,6 +219,7 @@ void gNB_dlsch_ulsch_scheduler(module_id_t module_idP, frame_t frame, slot_t slo // SIB19 will be scheduled if ntn_Config_r17 is initialized if (IS_SA_MODE(get_softmodem_params())) { schedule_nr_sib1(module_idP, frame, slot, &sched_info->DL_req, &sched_info->TX_req); + schedule_nr_other_sib(module_idP, frame, slot, &sched_info->DL_req, &sched_info->TX_req); } // This schedule PRACH if we are not in phy_test mode diff --git a/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_RA.c b/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_RA.c index 147b8abc6c..017dc3cd4f 100644 --- a/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_RA.c +++ b/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_RA.c @@ -878,7 +878,7 @@ static void nr_generate_Msg3_retransmission(module_id_t module_idP, ul_dci_request_pdu->PDUSize = (uint8_t)(2+sizeof(nfapi_nr_dl_tti_pdcch_pdu)); pdcch_pdu_rel15 = &ul_dci_request_pdu->pdcch_pdu.pdcch_pdu_rel15; ul_dci_req->numPdus += 1; - nr_configure_pdcch(pdcch_pdu_rel15, coreset, &ra->sched_pdcch, false); + nr_configure_pdcch(pdcch_pdu_rel15, coreset, &ra->sched_pdcch); nr_mac->pdcch_pdu_idx[CC_id][coresetid] = pdcch_pdu_rel15; } @@ -1250,81 +1250,11 @@ static void nr_add_msg3(module_id_t module_idP, int CC_id, frame_t frameP, slot_ nr_fill_rar(module_idP, ra, RAR_pdu, pusch_pdu); } - -static void find_monitoring_periodicity_offset_common(const NR_SearchSpace_t *ss, uint16_t *slot_period, uint16_t *offset) -{ - switch (ss->monitoringSlotPeriodicityAndOffset->present) { - case NR_SearchSpace__monitoringSlotPeriodicityAndOffset_PR_sl1: - *slot_period = 1; - *offset = 0; - break; - case NR_SearchSpace__monitoringSlotPeriodicityAndOffset_PR_sl2: - *slot_period = 2; - *offset = ss->monitoringSlotPeriodicityAndOffset->choice.sl2; - break; - case NR_SearchSpace__monitoringSlotPeriodicityAndOffset_PR_sl4: - *slot_period = 4; - *offset = ss->monitoringSlotPeriodicityAndOffset->choice.sl4; - break; - case NR_SearchSpace__monitoringSlotPeriodicityAndOffset_PR_sl5: - *slot_period = 5; - *offset = ss->monitoringSlotPeriodicityAndOffset->choice.sl5; - break; - case NR_SearchSpace__monitoringSlotPeriodicityAndOffset_PR_sl8: - *slot_period = 8; - *offset = ss->monitoringSlotPeriodicityAndOffset->choice.sl8; - break; - case NR_SearchSpace__monitoringSlotPeriodicityAndOffset_PR_sl10: - *slot_period = 10; - *offset = ss->monitoringSlotPeriodicityAndOffset->choice.sl10; - break; - case NR_SearchSpace__monitoringSlotPeriodicityAndOffset_PR_sl16: - *slot_period = 16; - *offset = ss->monitoringSlotPeriodicityAndOffset->choice.sl16; - break; - case NR_SearchSpace__monitoringSlotPeriodicityAndOffset_PR_sl20: - *slot_period = 20; - *offset = ss->monitoringSlotPeriodicityAndOffset->choice.sl20; - break; - case NR_SearchSpace__monitoringSlotPeriodicityAndOffset_PR_sl40: - *slot_period = 40; - *offset = ss->monitoringSlotPeriodicityAndOffset->choice.sl40; - break; - case NR_SearchSpace__monitoringSlotPeriodicityAndOffset_PR_sl80: - *slot_period = 80; - *offset = ss->monitoringSlotPeriodicityAndOffset->choice.sl80; - break; - case NR_SearchSpace__monitoringSlotPeriodicityAndOffset_PR_sl160: - *slot_period = 160; - *offset = ss->monitoringSlotPeriodicityAndOffset->choice.sl160; - break; - case NR_SearchSpace__monitoringSlotPeriodicityAndOffset_PR_sl320: - *slot_period = 320; - *offset = ss->monitoringSlotPeriodicityAndOffset->choice.sl320; - break; - case NR_SearchSpace__monitoringSlotPeriodicityAndOffset_PR_sl640: - *slot_period = 640; - *offset = ss->monitoringSlotPeriodicityAndOffset->choice.sl640; - break; - case NR_SearchSpace__monitoringSlotPeriodicityAndOffset_PR_sl1280: - *slot_period = 1280; - *offset = ss->monitoringSlotPeriodicityAndOffset->choice.sl1280; - break; - case NR_SearchSpace__monitoringSlotPeriodicityAndOffset_PR_sl2560: - *slot_period = 2560; - *offset = ss->monitoringSlotPeriodicityAndOffset->choice.sl2560; - break; - default: - AssertFatal(1 == 0, "Invalid monitoring slot periodicity and offset value\n"); - break; - } -} - static bool check_msg2_monitoring(const NR_RA_t *ra, int slots_per_frame, int current_frame, int current_slot) { // check if the slot is not among the PDCCH monitored ones (38.213 10.1) - uint16_t monitoring_slot_period, monitoring_offset; - find_monitoring_periodicity_offset_common(ra->ra_ss, &monitoring_slot_period, &monitoring_offset); + int monitoring_slot_period, monitoring_offset; + get_monitoring_period_offset(ra->ra_ss, &monitoring_slot_period, &monitoring_offset); if ((current_frame * slots_per_frame + current_slot - monitoring_offset) % monitoring_slot_period != 0) return false; return true; @@ -1432,7 +1362,7 @@ static void prepare_dl_pdus(gNB_MAC_INST *nr_mac, dl_tti_pdcch_pdu->PDUSize = (uint8_t)(2 + sizeof(nfapi_nr_dl_tti_pdcch_pdu)); dl_req->nPDUs += 1; pdcch_pdu_rel15 = &dl_tti_pdcch_pdu->pdcch_pdu.pdcch_pdu_rel15; - nr_configure_pdcch(pdcch_pdu_rel15, coreset, &ra->sched_pdcch, false); + nr_configure_pdcch(pdcch_pdu_rel15, coreset, &ra->sched_pdcch); nr_mac->pdcch_pdu_idx[CC_id][coresetid] = pdcch_pdu_rel15; } diff --git a/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_bch.c b/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_bch.c index 32647cbe65..ce3b8a7e13 100644 --- a/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_bch.c +++ b/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_bch.c @@ -397,92 +397,22 @@ static uint32_t schedule_control_sib1(module_id_t module_id, return TBS; } -static uint32_t schedule_control_other_si(module_id_t module_id, - int CC_id, - NR_Type0_PDCCH_CSS_config_t *type0_PDCCH_CSS_config, - int time_domain_allocation, - NR_pdsch_dmrs_t *dmrs_parms, - NR_tda_info_t *tda_info, - uint8_t candidate_idx, - int beam, - uint16_t num_total_bytes) +static void nr_fill_nfapi_dl_SIB_pdu(gNB_MAC_INST *gNB_mac, + NR_sched_pdsch_t *pdsch, + NR_sched_pdcch_t *pdcch, + NR_SearchSpace_t *search_space, + NR_ControlResourceSet_t *coreset, + int aggregation_level, + int cce_index, + nfapi_nr_dl_tti_request_body_t *dl_req, + int pdu_index, + NR_Type0_PDCCH_CSS_config_t *type0_PDCCH_CSS_config, + uint32_t TBS, + int StartSymbolIndex, + int NrOfSymbols, + bool is_sib1, + int beam_index) { - gNB_MAC_INST *gNB_mac = RC.nrmac[module_id]; - NR_COMMON_channels_t *cc = &gNB_mac->common_channels[CC_id]; - NR_ServingCellConfigCommon_t *scc = cc->ServingCellConfigCommon; - uint16_t *vrb_map = cc->vrb_map[beam]; - - if (!gNB_mac->sched_osi->coreset) { - gNB_mac->sched_osi->coreset = - get_coreset(gNB_mac, scc, NULL, gNB_mac->sched_osi->search_space, NR_SearchSpace__searchSpaceType_PR_common); - gNB_mac->sched_osi->sched_pdcch = - set_pdcch_structure(NULL, gNB_mac->sched_osi->search_space, gNB_mac->sched_osi->coreset, scc, NULL, type0_PDCCH_CSS_config); - } - - NR_sched_pdsch_t *pdsch = &gNB_mac->sched_osi->sched_pdsch; - pdsch->time_domain_allocation = time_domain_allocation; - pdsch->dmrs_parms = *dmrs_parms; - pdsch->tda_info = *tda_info; - pdsch->mcs = 0; // starting from mcs 0 - gNB_mac->sched_osi->num_total_bytes = num_total_bytes; - - uint8_t nr_of_candidates; - - for (int i = 0; i < 3; i++) { - find_aggregation_candidates(&gNB_mac->sched_osi->aggregation_level, - &nr_of_candidates, - gNB_mac->sched_ctrlCommon->search_space, - 4 << i); - if (nr_of_candidates > 0) - break; // choosing the lower value of aggregation level available - } - AssertFatal(nr_of_candidates > 0, "nr_of_candidates is 0\n"); - gNB_mac->sched_osi->cce_index = find_pdcch_candidate(gNB_mac, - CC_id, - gNB_mac->sched_osi->aggregation_level, - nr_of_candidates, - beam, - &gNB_mac->sched_osi->sched_pdcch, - gNB_mac->sched_osi->coreset, - 0); - - AssertFatal(gNB_mac->sched_osi->cce_index >= 0, "Could not find CCE for coreset0\n"); - - // Calculate number of PRB_DMRS - uint32_t TBS = get_tbs(type0_PDCCH_CSS_config, &gNB_mac->sched_osi->sched_pdsch, gNB_mac->sched_osi->num_total_bytes, vrb_map); - // Mark the corresponding RBs as used - fill_pdcch_vrb_map(gNB_mac, - CC_id, - &gNB_mac->sched_osi->sched_pdcch, - gNB_mac->sched_osi->cce_index, - gNB_mac->sched_osi->aggregation_level, - beam); - for (int rb = 0; rb < pdsch->rbSize; rb++) { - vrb_map[rb + type0_PDCCH_CSS_config->cset_start_rb] |= SL_to_bitmap(tda_info->startSymbolIndex, tda_info->nrOfSymbols); - } - return TBS; -} - -static void nr_fill_nfapi_dl_SIB_pdu(int Mod_idP, - NR_UE_sched_osi_ctrl_t *sched_osi, - NR_UE_sched_ctrl_t *sched_ctrlCommon, - nfapi_nr_dl_tti_request_body_t *dl_req, - int pdu_index, - NR_Type0_PDCCH_CSS_config_t *type0_PDCCH_CSS_config, - uint32_t TBS, - int StartSymbolIndex, - int NrOfSymbols, - int beam_index) -{ - // will set data according to SIB type (SIB1/OtherSI) - NR_sched_pdsch_t *pdsch = (sched_ctrlCommon ? &sched_ctrlCommon->sched_pdsch : &sched_osi->sched_pdsch); - NR_sched_pdcch_t *pdcch = (sched_ctrlCommon ? &sched_ctrlCommon->sched_pdcch : &sched_osi->sched_pdcch); - NR_SearchSpace_t *search_space = (sched_ctrlCommon ? sched_ctrlCommon->search_space : sched_osi->search_space); - NR_ControlResourceSet_t *coreset = (sched_ctrlCommon ? sched_ctrlCommon->coreset : sched_osi->coreset); - uint8_t aggregation_level = (sched_ctrlCommon ? sched_ctrlCommon->aggregation_level : sched_osi->aggregation_level); - int cce_index = (sched_ctrlCommon ? sched_ctrlCommon->cce_index : sched_osi->cce_index); - - gNB_MAC_INST *gNB_mac = RC.nrmac[Mod_idP]; NR_COMMON_channels_t *cc = gNB_mac->common_channels; NR_ServingCellConfigCommon_t *scc = cc->ServingCellConfigCommon; int mcsTableIdx = 0; @@ -493,10 +423,7 @@ static void nr_fill_nfapi_dl_SIB_pdu(int Mod_idP, dl_tti_pdcch_pdu->PDUSize = (uint16_t)(4+sizeof(nfapi_nr_dl_tti_pdcch_pdu)); dl_req->nPDUs += 1; nfapi_nr_dl_tti_pdcch_pdu_rel15_t *pdcch_pdu_rel15 = &dl_tti_pdcch_pdu->pdcch_pdu.pdcch_pdu_rel15; - nr_configure_pdcch(pdcch_pdu_rel15, - coreset, - pdcch, - sched_ctrlCommon ? false : true); + nr_configure_pdcch(pdcch_pdu_rel15, coreset, pdcch); nfapi_nr_dl_tti_request_pdu_t *dl_tti_pdsch_pdu = &dl_req->dl_tti_pdu_list[dl_req->nPDUs]; memset(dl_tti_pdsch_pdu, 0, sizeof(*dl_tti_pdcch_pdu)); @@ -598,7 +525,7 @@ static void nr_fill_nfapi_dl_SIB_pdu(int Mod_idP, dci_payload.pdsch_to_harq_feedback_timing_indicator.val = 0; dci_payload.antenna_ports.val = 0; dci_payload.dmrs_sequence_initialization.val = pdsch_pdu_rel15->SCID; - dci_payload.system_info_indicator = sched_ctrlCommon ? 0 : 1; + dci_payload.system_info_indicator = is_sib1 ? 0 : 1; int dci_format = NR_DL_DCI_FORMAT_1_0; int rnti_type = TYPE_SI_RNTI_; @@ -707,17 +634,21 @@ void schedule_nr_sib1(module_id_t module_idP, nfapi_nr_dl_tti_request_body_t *dl_req = &DL_req->dl_tti_request_body; int pdu_index = gNB_mac->pdu_index[0]++; - LOG_D(NR_MAC, "%s() %4d.%2d\n", __func__, frameP, slotP); - nr_fill_nfapi_dl_SIB_pdu(module_idP, - NULL, - gNB_mac->sched_ctrlCommon, - dl_req, - pdu_index, - type0_PDCCH_CSS_config, - TBS, - tda_info.startSymbolIndex, - tda_info.nrOfSymbols, - beam_index); + nr_fill_nfapi_dl_SIB_pdu(gNB_mac, + &gNB_mac->sched_ctrlCommon->sched_pdsch, + &gNB_mac->sched_ctrlCommon->sched_pdcch, + gNB_mac->sched_ctrlCommon->search_space, + gNB_mac->sched_ctrlCommon->coreset, + gNB_mac->sched_ctrlCommon->aggregation_level, + gNB_mac->sched_ctrlCommon->cce_index, + dl_req, + pdu_index, + type0_PDCCH_CSS_config, + TBS, + tda_info.startSymbolIndex, + tda_info.nrOfSymbols, + true, + beam_index); const int ntx_req = TX_req->Number_of_PDUs; nfapi_nr_pdu_t *tx_req = &TX_req->pdu_list[ntx_req]; @@ -762,133 +693,226 @@ struct NR_SchedulingInfo2_r17 *find_sib19_sched_info(const struct NR_SI_Scheduli return NULL; } -void schedule_nr_sib19(module_id_t module_idP, - frame_t frameP, - slot_t slotP, - nfapi_nr_dl_tti_request_t *DL_req, - nfapi_nr_tx_data_request_t *TX_req, - int sib19_bcch_length, - uint8_t *sib19_bcch_pdu) +static void other_sib_sched_control(module_id_t module_idP, + frame_t frame, + slot_t slot, + int beam_index, + NR_SearchSpace_t *ss, + nfapi_nr_dl_tti_request_t *DL_req, + nfapi_nr_tx_data_request_t *TX_req, + int payload_idx) { - /* already mutex protected: held in gNB_dlsch_ulsch_scheduler() */ - - const int CC_id = 0; - uint8_t candidate_idx = 0; - gNB_MAC_INST *gNB_mac = RC.nrmac[module_idP]; - NR_ServingCellConfigCommon_t *scc = gNB_mac->common_channels[CC_id].ServingCellConfigCommon; + NR_ServingCellConfigCommon_t *scc = gNB_mac->common_channels[0].ServingCellConfigCommon; + int n_slots_frame = gNB_mac->frame_structure.numb_slots_frame; + NR_beam_alloc_t beam = beam_allocation_procedure(&gNB_mac->beam_info, frame, slot, beam_index, n_slots_frame); + AssertFatal(beam.idx >= 0, "Cannot allocate otherSIB corresponding for SSB number %d in any available beam\n", beam_index); + LOG_D(NR_MAC, "(%d.%d) otherSIB transmission for ssb number %d\n", frame, slot, beam_index); + + NR_PDSCH_ConfigCommon_t *pdsch_ConfigCommon = scc->downlinkConfigCommon->initialDownlinkBWP->pdsch_ConfigCommon->choice.setup; + int time_domain_allocation = 1; + NR_tda_info_t tda_info = set_tda_info_from_list(pdsch_ConfigCommon->pdsch_TimeDomainAllocationList, time_domain_allocation); + LOG_D(NR_MAC,"tda_info.startSymbolIndex: %d, tda_info.nrOfSymbols: %d\n", tda_info.startSymbolIndex, tda_info.nrOfSymbols); + + NR_pdsch_dmrs_t dmrs_parms = get_dl_dmrs_params(scc, NULL, &tda_info, 1); + + uint8_t aggregation_level = 0; + uint8_t nr_of_candidates = 0; + for (int i = 0; i < 5; i++) { + find_aggregation_candidates(&aggregation_level, &nr_of_candidates, ss, 16 >> i); + if (nr_of_candidates > 0) + break; // choosing the higher value of aggregation level available + } + + AssertFatal(nr_of_candidates > 0, "nr_of_candidates is 0\n"); + NR_ControlResourceSet_t *coreset = get_coreset(gNB_mac, scc, NULL, ss, NR_SearchSpace__searchSpaceType_PR_common); NR_COMMON_channels_t *cc = &gNB_mac->common_channels[0]; + NR_Type0_PDCCH_CSS_config_t *type0_PDCCH_CSS_config = &gNB_mac->type0_PDCCH_CSS_config[cc->ssb_index[beam_index]]; + if (!gNB_mac->sched_pdcch_otherSI) { + gNB_mac->sched_pdcch_otherSI = calloc(1, sizeof(*gNB_mac->sched_pdcch_otherSI)); + *gNB_mac->sched_pdcch_otherSI = set_pdcch_structure(gNB_mac, ss, coreset, scc, NULL, type0_PDCCH_CSS_config); + } + int cce_index = find_pdcch_candidate(gNB_mac, + 0, + aggregation_level, + nr_of_candidates, + beam.idx, + gNB_mac->sched_pdcch_otherSI, + coreset, + 0); - // there is no reason to execute further if sched_osi not initialized - if (!gNB_mac->sched_osi) - return; + AssertFatal(cce_index >= 0, "Could not find CCE for otherSIB DCI\n"); - // get sib19 scheduling info from sib1 - struct NR_SI_SchedulingInfo_v1700 *si_schedulinginfo2_r17 = cc->sib1->message.choice.c1->choice.systemInformationBlockType1->nonCriticalExtension->nonCriticalExtension->nonCriticalExtension->si_SchedulingInfo_v1700; - if (!si_schedulinginfo2_r17) { - LOG_E(GNB_APP, "SIB1 does not contain si_SchedulingInfo_v1700\n"); - return; + // Mark the corresponding RBs as used + fill_pdcch_vrb_map(gNB_mac, 0, gNB_mac->sched_pdcch_otherSI, cce_index, aggregation_level, beam.idx); + + NR_sched_pdsch_t sched_pdsch_otherSI = {0}; + sched_pdsch_otherSI.time_domain_allocation = time_domain_allocation; + sched_pdsch_otherSI.dmrs_parms = dmrs_parms; + sched_pdsch_otherSI.tda_info = tda_info; + sched_pdsch_otherSI.mcs = 0; // starting from mcs 0 + + uint16_t *vrb_map = cc->vrb_map[beam.idx]; + uint8_t *sib_bcch_pdu = cc->other_sib_bcch_pdu[payload_idx]; + int num_total_bytes = cc->other_sib_bcch_length[payload_idx]; + uint32_t TBS = get_tbs(type0_PDCCH_CSS_config, &sched_pdsch_otherSI, num_total_bytes, vrb_map); + + for (int rb = 0; rb < sched_pdsch_otherSI.rbSize; rb++) { + vrb_map[rb + type0_PDCCH_CSS_config->cset_start_rb] |= SL_to_bitmap(tda_info.startSymbolIndex, tda_info.nrOfSymbols); } - struct NR_SchedulingInfo2_r17 *sib19_sched_info = find_sib19_sched_info(si_schedulinginfo2_r17); - if (!sib19_sched_info) { - LOG_D(GNB_APP, "SIB1 does not contain scheduling info for SIB19\n"); + int pdu_index = gNB_mac->pdu_index[0]++; + nfapi_nr_dl_tti_request_body_t *dl_req = &DL_req->dl_tti_request_body; + nr_fill_nfapi_dl_SIB_pdu(gNB_mac, + &sched_pdsch_otherSI, + gNB_mac->sched_pdcch_otherSI, + ss, + coreset, + aggregation_level, + cce_index, + dl_req, + pdu_index, + type0_PDCCH_CSS_config, + TBS, + tda_info.startSymbolIndex, + tda_info.nrOfSymbols, + false, + beam_index); + + const int ntx_req = TX_req->Number_of_PDUs; + nfapi_nr_pdu_t *tx_req = &TX_req->pdu_list[ntx_req]; + + // Data to be transmitted + memcpy(tx_req->TLVs[0].value.direct, sib_bcch_pdu, TBS); + + tx_req->PDU_length = TBS; + tx_req->PDU_index = pdu_index; + tx_req->num_TLV = 1; + tx_req->TLVs[0].length = TBS + 2; + TX_req->Number_of_PDUs++; + TX_req->SFN = frame; + TX_req->Slot = slot; + + T(T_GNB_MAC_DL_PDU_WITH_DATA, + T_INT(module_idP), + T_INT(0), + T_INT(SI_RNTI), + T_INT(frame), + T_INT(slot), + T_INT(0), // harq_pid + T_BUFFER(sib_bcch_pdu, num_total_bytes)); +} + +// This function verifies if we need to schedule otherSIB in frame and slot +static bool test_other_sib_sched_occasion(int window_pos, + int window_len, + int period, + int n_slots_frame, + int frame, + int slot, + int rel_frame, + int rel_slot) +{ + int x = window_pos * window_len; + int T = 8 << period; + int test_frame = (frame - rel_frame) % MAX_FRAME_NUMBER; + int si_slot = (x % n_slots_frame) + rel_slot; + bool res = ((test_frame % T) != (x / n_slots_frame)) || (si_slot != slot); + return res; +} + +void schedule_nr_other_sib(module_id_t module_idP, + frame_t frame, + slot_t slot, + nfapi_nr_dl_tti_request_t *DL_req, + nfapi_nr_tx_data_request_t *TX_req) +{ + gNB_MAC_INST *gNB_mac = RC.nrmac[module_idP]; + NR_COMMON_channels_t *cc = &gNB_mac->common_channels[0]; + NR_SIB1_t *sib1 = cc->sib1->message.choice.c1->choice.systemInformationBlockType1; + NR_SI_SchedulingInfo_t *schedInfo = sib1->si_SchedulingInfo; + if (!schedInfo) return; + + NR_ServingCellConfigCommon_t *scc = gNB_mac->common_channels[0].ServingCellConfigCommon; + NR_PDCCH_ConfigCommon_t *pdcch_common = scc->downlinkConfigCommon->initialDownlinkBWP->pdcch_ConfigCommon->choice.setup; + NR_SearchSpaceId_t *ss_id = pdcch_common->searchSpaceOtherSystemInformation; + AssertFatal(ss_id, "searchSpaceOtherSystemInformation not present\n"); + NR_SearchSpace_t *ss = NULL; + for (int i = 0; i < pdcch_common->commonSearchSpaceList->list.count; i++) { + if (pdcch_common->commonSearchSpaceList->list.array[i]->searchSpaceId == *ss_id) + ss = pdcch_common->commonSearchSpaceList->list.array[i]; + } + AssertFatal(ss, "searchSpaceOtherSystemInformation not found\n"); + + NR_SI_SchedulingInfo_v1700_t *schedInfo17 = NULL; + if (sib1->nonCriticalExtension) { + NR_SIB1_v1610_IEs_t *sib1_v1610 = sib1->nonCriticalExtension; + if (sib1_v1610 && sib1_v1610->nonCriticalExtension && sib1_v1610->nonCriticalExtension->nonCriticalExtension) { + NR_SIB1_v1700_IEs_t *sib1_v17 = sib1_v1610->nonCriticalExtension->nonCriticalExtension; + schedInfo17 = sib1_v17->si_SchedulingInfo_v1700; + } } - // convert periodicity enum type into actual frames number - int frame = 8 << sib19_sched_info->si_Periodicity_r17; - - int time_domain_allocation = 1; // row 0 of pdsch tda common table - int L_max; - switch (scc->ssb_PositionsInBurst->present) { - case 1: - L_max = 4; - break; - case 2: - L_max = 8; - break; - case 3: - L_max = 64; - break; - default: - AssertFatal(0,"SSB bitmap size value %d undefined (allowed values 1,2,3)\n", - scc->ssb_PositionsInBurst->present); + int n_slots_frame = gNB_mac->frame_structure.numb_slots_frame; + int window_length_sl = 5 << schedInfo->si_WindowLength; + int window_length_f = (window_length_sl / n_slots_frame) + ((window_length_sl % n_slots_frame) > 0); + int num_ssb = cc->num_active_ssb; + // number of PDCCH monitoring occasions in SI-window + int period, offset; + get_monitoring_period_offset(ss, &period, &offset); + // The UE assumes that, in the SI window, PDCCH for an SI message is transmitted + // in at least one PDCCH monitoring occasion corresponding to each transmitted SSB + // frame and slot relative to start of window + // The [x×N+K]th PDCCH monitoring occasion(s) for SI message in SI-window corresponds to the Kth transmitted SSB + // in our implmentation we transmit only once per SSB per window so N = 1 + // Section 5.2.2.3.2 of 38.331 + int temp_slot = offset % n_slots_frame; + int temp_frame = offset / n_slots_frame; + int rel_slot[num_ssb]; + int rel_frame[num_ssb]; + int ssb = 0; + while (ssb < num_ssb) { + AssertFatal(temp_frame < window_length_f, "Couldn't fit %d SSB in window length of %d slots\n", num_ssb, window_length_sl); + if (is_dl_slot(temp_slot, &gNB_mac->frame_structure)) { + rel_slot[ssb] = temp_slot; + rel_frame[ssb] = temp_frame; + ssb++; + } + temp_frame += (temp_slot + period) / n_slots_frame; + temp_slot = (temp_slot + period) % n_slots_frame; } - const int window_length = 10; - for (int i = 0; i < L_max; i++) { + for (int ssb = 0; ssb < num_ssb; ssb++) { + for (int i = 0; i < schedInfo->schedulingInfoList.list.count; i++) { + NR_SchedulingInfo_t *schedulingInfo = schedInfo->schedulingInfoList.list.array[i]; + if (test_other_sib_sched_occasion(i, + window_length_sl, + schedulingInfo->si_Periodicity, + n_slots_frame, + frame, + slot, + rel_frame[ssb], + rel_slot[ssb])) + continue; - NR_Type0_PDCCH_CSS_config_t *type0_PDCCH_CSS_config = &gNB_mac->type0_PDCCH_CSS_config[i]; - if(((frameP - 1) % frame == 0) && - (slotP == ((sib19_sched_info->si_WindowPosition_r17 % window_length) - 1)) && - (type0_PDCCH_CSS_config->num_rbs > 0)) { + other_sib_sched_control(module_idP, frame, slot, ssb, ss, DL_req, TX_req, 0); + } + if (!schedInfo17) + continue; + for (int i = 0; i < schedInfo17->schedulingInfoList2_r17.list.count; i++) { + NR_SchedulingInfo2_r17_t *schedulingInfo2_r17 = schedInfo17->schedulingInfoList2_r17.list.array[i]; + if (test_other_sib_sched_occasion(schedulingInfo2_r17->si_WindowPosition_r17, + window_length_sl, + schedulingInfo2_r17->si_Periodicity_r17, + n_slots_frame, + frame, + slot, + rel_frame[ssb], + rel_slot[ssb])) + continue; - const int n_slots_frame = gNB_mac->frame_structure.numb_slots_frame; - int beam_index = get_fapi_beamforming_index(gNB_mac, i); - NR_beam_alloc_t beam = beam_allocation_procedure(&gNB_mac->beam_info, frameP, slotP, beam_index, n_slots_frame); - AssertFatal(beam.idx >= 0, "Cannot allocate SIB19 corresponding to SSB %d in any available beam\n", i); - LOG_D(NR_MAC,"(%d.%d) SIB19 transmission: ssb_index %d\n", frameP, slotP, type0_PDCCH_CSS_config->ssb_index); - - NR_tda_info_t tda_info = set_tda_info_from_list(scc->downlinkConfigCommon->initialDownlinkBWP->pdsch_ConfigCommon->choice.setup->pdsch_TimeDomainAllocationList, time_domain_allocation); - LOG_D(NR_MAC,"tda_info.startSymbolIndex: %d, tda_info.nrOfSymbols: %d\n", tda_info.startSymbolIndex, tda_info.nrOfSymbols); - - AssertFatal((tda_info.startSymbolIndex + tda_info.nrOfSymbols) < 14, "SIB19 TDA %d would cause overlap with CSI-RS. Please select a different SIB1 TDA.\n", time_domain_allocation); - - NR_pdsch_dmrs_t dmrs_parms = get_dl_dmrs_params(scc, - NULL, - &tda_info, - 1); - - // Configure sched_osi for SIB19 - uint32_t TBS = schedule_control_other_si(module_idP, - CC_id, - type0_PDCCH_CSS_config, - time_domain_allocation, - &dmrs_parms, - &tda_info, - candidate_idx, - beam.idx, - sib19_bcch_length); - - nfapi_nr_dl_tti_request_body_t *dl_req = &DL_req->dl_tti_request_body; - int pdu_index = gNB_mac->pdu_index[0]++; - - nr_fill_nfapi_dl_SIB_pdu(module_idP, - gNB_mac->sched_osi, - NULL, - dl_req, - pdu_index, - type0_PDCCH_CSS_config, - TBS, - tda_info.startSymbolIndex, - tda_info.nrOfSymbols, - beam_index); - - const int ntx_req = TX_req->Number_of_PDUs; - nfapi_nr_pdu_t *tx_req = &TX_req->pdu_list[ntx_req]; - - // Data to be transmitted - memcpy(tx_req->TLVs[0].value.direct, sib19_bcch_pdu, TBS); - - tx_req->PDU_length = TBS; - tx_req->PDU_index = pdu_index; - tx_req->num_TLV = 1; - tx_req->TLVs[0].length = TBS + 2; - TX_req->Number_of_PDUs++; - TX_req->SFN = frameP; - TX_req->Slot = slotP; - - type0_PDCCH_CSS_config->active = false; - - T(T_GNB_MAC_DL_PDU_WITH_DATA, - T_INT(module_idP), - T_INT(CC_id), - T_INT(SI_RNTI), - T_INT(frameP), - T_INT(slotP), - T_INT(0 /* harq_pid */), - T_BUFFER(sib19_bcch_pdu, sib19_bcch_length)); + other_sib_sched_control(module_idP, frame, slot, ssb, ss, DL_req, TX_req, 1); } } } diff --git a/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_dlsch.c b/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_dlsch.c index 2a5b979f5a..b69490fbc9 100644 --- a/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_dlsch.c +++ b/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_dlsch.c @@ -1045,7 +1045,7 @@ void nr_schedule_ue_spec(module_id_t module_id, pdcch_pdu = &dl_tti_pdcch_pdu->pdcch_pdu.pdcch_pdu_rel15; LOG_D(NR_MAC,"Trying to configure DL pdcch for UE %04x, bwp %d, cs %d\n", UE->rnti, bwp_id, coresetid); NR_ControlResourceSet_t *coreset = sched_ctrl->coreset; - nr_configure_pdcch(pdcch_pdu, coreset, &sched_ctrl->sched_pdcch, false); + nr_configure_pdcch(pdcch_pdu, coreset, &sched_ctrl->sched_pdcch); gNB_mac->pdcch_pdu_idx[CC_id][coresetid] = pdcch_pdu; } diff --git a/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_primitives.c b/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_primitives.c index 3c7c9ebd39..4ad2514ab2 100644 --- a/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_primitives.c +++ b/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_primitives.c @@ -281,8 +281,8 @@ uint8_t get_mcs_from_cqi(int mcs_table, int cqi_table, int cqi_idx) NR_pdsch_dmrs_t get_dl_dmrs_params(const NR_ServingCellConfigCommon_t *scc, const NR_UE_DL_BWP_t *dl_bwp, const NR_tda_info_t *tda_info, - const int Layers) { - + const int Layers) +{ NR_pdsch_dmrs_t dmrs = {0}; int frontloaded_symb = 1; // default value nr_dci_format_t dci_format = dl_bwp ? dl_bwp->dci_format : NR_DL_DCI_FORMAT_1_0; @@ -865,12 +865,8 @@ int nr_get_default_pucch_res(int pucch_ResourceCommon) { return(default_pucch_csset[pucch_ResourceCommon]); } -void nr_configure_pdcch(nfapi_nr_dl_tti_pdcch_pdu_rel15_t *pdcch_pdu, - NR_ControlResourceSet_t *coreset, - NR_sched_pdcch_t *pdcch, - bool otherSI) { - - +void nr_configure_pdcch(nfapi_nr_dl_tti_pdcch_pdu_rel15_t *pdcch_pdu, NR_ControlResourceSet_t *coreset, NR_sched_pdcch_t *pdcch) +{ pdcch_pdu->BWPSize = pdcch->BWPSize; pdcch_pdu->BWPStart = pdcch->BWPStart; pdcch_pdu->SubcarrierSpacing = pdcch->SubcarrierSpacing; @@ -879,19 +875,23 @@ void nr_configure_pdcch(nfapi_nr_dl_tti_pdcch_pdu_rel15_t *pdcch_pdu, pdcch_pdu->DurationSymbols = coreset->duration; - for (int i=0;i<6;i++) + for (int i = 0; i < 6; i++) pdcch_pdu->FreqDomainResource[i] = coreset->frequencyDomainResources.buf[i]; - LOG_D(MAC,"Coreset : BWPstart %d, BWPsize %d, SCS %d, freq %x, , duration %d\n", - pdcch_pdu->BWPStart,pdcch_pdu->BWPSize,(int)pdcch_pdu->SubcarrierSpacing,(int)coreset->frequencyDomainResources.buf[0],(int)coreset->duration); + LOG_D(NR_MAC, + "Coreset : BWPstart %d, BWPsize %d, SCS %d, freq %x, , duration %ld\n", + pdcch_pdu->BWPStart, + pdcch_pdu->BWPSize, + pdcch_pdu->SubcarrierSpacing, + coreset->frequencyDomainResources.buf[0], + coreset->duration); pdcch_pdu->CceRegMappingType = pdcch->CceRegMappingType; pdcch_pdu->RegBundleSize = pdcch->RegBundleSize; pdcch_pdu->InterleaverSize = pdcch->InterleaverSize; pdcch_pdu->ShiftIndex = pdcch->ShiftIndex; - pdcch_pdu->CoreSetType = - (otherSI || coreset->controlResourceSetId != 0) ? NFAPI_NR_CSET_CONFIG_PDCCH_CONFIG : NFAPI_NR_CSET_CONFIG_MIB_SIB1; + pdcch_pdu->CoreSetType = coreset->controlResourceSetId != 0 ? NFAPI_NR_CSET_CONFIG_PDCCH_CONFIG : NFAPI_NR_CSET_CONFIG_MIB_SIB1; //precoderGranularity pdcch_pdu->precoderGranularity = coreset->precoderGranularity; diff --git a/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_ulsch.c b/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_ulsch.c index b6e86fa6b7..7bef14b365 100644 --- a/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_ulsch.c +++ b/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_ulsch.c @@ -2680,7 +2680,7 @@ void nr_schedule_ulsch(module_id_t module_id, frame_t frame, slot_t slot, nfapi_ ul_dci_request_pdu->PDUSize = (uint8_t)(4+sizeof(nfapi_nr_dl_tti_pdcch_pdu)); pdcch_pdu = &ul_dci_request_pdu->pdcch_pdu.pdcch_pdu_rel15; ul_dci_req->numPdus += 1; - nr_configure_pdcch(pdcch_pdu, coreset, &sched_ctrl->sched_pdcch, false); + nr_configure_pdcch(pdcch_pdu, coreset, &sched_ctrl->sched_pdcch); pdcch_pdu_coreset[coresetid] = pdcch_pdu; } diff --git a/openair2/LAYER2/NR_MAC_gNB/mac_proto.h b/openair2/LAYER2/NR_MAC_gNB/mac_proto.h index 2a6ee13a2b..c8b809a8cb 100644 --- a/openair2/LAYER2/NR_MAC_gNB/mac_proto.h +++ b/openair2/LAYER2/NR_MAC_gNB/mac_proto.h @@ -54,7 +54,6 @@ void mac_top_init_gNB(ngran_node_t node_type, void nr_mac_send_f1_setup_req(void); void nr_mac_config_scc(gNB_MAC_INST *nrmac, NR_ServingCellConfigCommon_t *scc, const nr_mac_config_t *mac_config); -void nr_fill_sched_osi(gNB_MAC_INST *nrmac, const struct NR_SetupRelease_PDCCH_ConfigCommon *pdcch_ConfigCommon); void nr_mac_configure_sib1(gNB_MAC_INST *nrmac, const f1ap_plmn_t *plmn, uint64_t cellID, int tac); bool nr_mac_configure_other_sib(gNB_MAC_INST *nrmac, int num_cu_sib, const f1ap_sib_msg_t cu_sib[num_cu_sib]); bool nr_mac_add_test_ue(gNB_MAC_INST *nrmac, uint32_t rnti, NR_CellGroupConfig_t *CellGroup); @@ -96,13 +95,11 @@ void schedule_nr_sib1(module_id_t module_idP, nfapi_nr_dl_tti_request_t *DL_req, nfapi_nr_tx_data_request_t *TX_req); -void schedule_nr_sib19(module_id_t module_idP, - frame_t frameP, - slot_t slotP, - nfapi_nr_dl_tti_request_t *DL_req, - nfapi_nr_tx_data_request_t *TX_req, - int sib19_bcch_length, - uint8_t *sib19_bcch_pdu); +void schedule_nr_other_sib(module_id_t module_idP, + frame_t frame, + slot_t slot, + nfapi_nr_dl_tti_request_t *DL_req, + nfapi_nr_tx_data_request_t *TX_req); struct NR_SchedulingInfo2_r17* find_sib19_sched_info(const struct NR_SI_SchedulingInfo_v1700*); @@ -214,10 +211,7 @@ void find_search_space(int ss_type, NR_BWP_Downlink_t *bwp, NR_SearchSpace_t *ss); -void nr_configure_pdcch(nfapi_nr_dl_tti_pdcch_pdu_rel15_t *pdcch_pdu, - NR_ControlResourceSet_t *coreset, - NR_sched_pdcch_t *pdcch, - bool otherSI); +void nr_configure_pdcch(nfapi_nr_dl_tti_pdcch_pdu_rel15_t *pdcch_pdu, NR_ControlResourceSet_t *coreset, NR_sched_pdcch_t *pdcch); NR_sched_pdcch_t set_pdcch_structure(gNB_MAC_INST *gNB_mac, NR_SearchSpace_t *ss, diff --git a/openair2/LAYER2/NR_MAC_gNB/nr_mac_gNB.h b/openair2/LAYER2/NR_MAC_gNB/nr_mac_gNB.h index fe7222b2c6..8365cba37b 100644 --- a/openair2/LAYER2/NR_MAC_gNB/nr_mac_gNB.h +++ b/openair2/LAYER2/NR_MAC_gNB/nr_mac_gNB.h @@ -691,19 +691,6 @@ typedef struct { float pdcch_cl_adjust; } NR_UE_sched_ctrl_t; -typedef struct { - NR_SearchSpace_t *search_space; - NR_ControlResourceSet_t *coreset; - - NR_sched_pdcch_t sched_pdcch; - NR_sched_pdsch_t sched_pdsch; - - uint32_t num_total_bytes; - - int cce_index; - uint8_t aggregation_level; -} NR_UE_sched_osi_ctrl_t; - typedef struct { uicc_t *uicc; } NRUEcontext_t; @@ -927,9 +914,8 @@ typedef struct gNB_MAC_INST_s { nr_mac_config_t radio_config; - NR_UE_sched_osi_ctrl_t *sched_osi; NR_UE_sched_ctrl_t *sched_ctrlCommon; - + NR_sched_pdcch_t *sched_pdcch_otherSI; uint16_t cset0_bwp_start; uint16_t cset0_bwp_size; NR_Type0_PDCCH_CSS_config_t type0_PDCCH_CSS_config[64]; diff --git a/openair2/RRC/NR/nr_rrc_config.c b/openair2/RRC/NR/nr_rrc_config.c index 5c2efdd3c8..2047f108c5 100644 --- a/openair2/RRC/NR/nr_rrc_config.c +++ b/openair2/RRC/NR/nr_rrc_config.c @@ -2359,7 +2359,9 @@ void update_SIB1_NR_SI(NR_BCCH_DL_SCH_Message_t *sib1_bcch, int num_sibs, int si NR_SIB1_t *sib1 = sib1_bcch->message.choice.c1->choice.systemInformationBlockType1; //si-SchedulingInfo NR_SI_SchedulingInfo_t *info = calloc(1, sizeof(*info)); - info->si_WindowLength = NR_SI_SchedulingInfo__si_WindowLength_s40; + // TODO need to compute optimal SI-windowlength automatically + // based on the number of SSBs, TDD configuration and SS configuration + info->si_WindowLength = NR_SI_SchedulingInfo__si_WindowLength_s10; NR_SchedulingInfo_t *schedulingInfo = calloc(1, sizeof(*schedulingInfo)); schedulingInfo->si_BroadcastStatus = NR_SchedulingInfo__si_BroadcastStatus_broadcasting; schedulingInfo->si_Periodicity = NR_SchedulingInfo__si_Periodicity_rf16; From 6af70bd4b9b031a0e1dc811647b8120cbf9d878b Mon Sep 17 00:00:00 2001 From: francescomani Date: Fri, 27 Dec 2024 16:45:35 +0100 Subject: [PATCH 44/63] fix buggy TBS function for SIB and set additional candidate for common search space --- .../LAYER2/NR_MAC_gNB/gNB_scheduler_bch.c | 110 +++++++++--------- openair2/RRC/NR/nr_rrc_config.c | 1 - 2 files changed, 56 insertions(+), 55 deletions(-) diff --git a/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_bch.c b/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_bch.c index ce3b8a7e13..a00db6585c 100644 --- a/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_bch.c +++ b/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_bch.c @@ -274,72 +274,71 @@ void schedule_nr_mib(module_id_t module_idP, frame_t frameP, slot_t slotP, nfapi } } -uint32_t get_tbs(NR_Type0_PDCCH_CSS_config_t *type0_PDCCH_CSS_config, - NR_sched_pdsch_t *pdsch, - uint32_t num_total_bytes, - uint16_t *vrb_map) +static uint32_t get_tbs_bch(NR_Type0_PDCCH_CSS_config_t *type0_PDCCH_CSS_config, + NR_sched_pdsch_t *pdsch, + uint32_t num_total_bytes, + uint16_t *vrb_map) { NR_tda_info_t *tda_info = &pdsch->tda_info; - const uint16_t bwpSize = type0_PDCCH_CSS_config->num_rbs; - int rbStart = type0_PDCCH_CSS_config->cset_start_rb; - // Calculate number of PRB_DMRS uint8_t N_PRB_DMRS = pdsch->dmrs_parms.N_PRB_DMRS; - uint16_t dmrs_length = pdsch->dmrs_parms.N_DMRS_SLOT; LOG_D(MAC, "dlDmrsSymbPos %x\n", pdsch->dmrs_parms.dl_dmrs_symb_pos); int mcsTableIdx = 0; - int rbSize = 0; uint32_t TBS = 0; - do { - if (rbSize < bwpSize && !(vrb_map[rbStart + rbSize] & SL_to_bitmap(tda_info->startSymbolIndex, tda_info->nrOfSymbols))) - rbSize++; + const uint16_t slbitmap = SL_to_bitmap(tda_info->startSymbolIndex, tda_info->nrOfSymbols); + int bwpSize = type0_PDCCH_CSS_config->num_rbs; + int bwpStart = type0_PDCCH_CSS_config->cset_start_rb; + int rbStop = bwpSize - 1; + int rbStart = 0; + uint16_t rbSize = 0; + while (rbStart < rbStop) { + if (vrb_map[rbStart + bwpStart] & slbitmap) + rbStart++; else { - if (pdsch->mcs < 10) - pdsch->mcs++; - else - break; - } - TBS = nr_compute_tbs(nr_get_Qm_dl(pdsch->mcs, mcsTableIdx), - nr_get_code_rate_dl(pdsch->mcs, mcsTableIdx), - rbSize, - tda_info->nrOfSymbols, - N_PRB_DMRS * dmrs_length, - 0, - 0, - 1) - >> 3; - } while (TBS < num_total_bytes); + int max_rbSize = 0; + while (rbStart + max_rbSize <= rbStop && !(vrb_map[rbStart + max_rbSize + bwpStart] & slbitmap)) + max_rbSize++; - if (TBS < num_total_bytes) { - for (int rb = 0; rb < bwpSize; rb++) - LOG_I(NR_MAC, "vrb_map[%d] %x\n", rbStart + rb, vrb_map[rbStart + rb]); + bool res = false; + while (res == false && pdsch->mcs < 10) { + res = nr_find_nb_rb(nr_get_Qm_dl(pdsch->mcs, mcsTableIdx), + nr_get_code_rate_dl(pdsch->mcs, mcsTableIdx), + 1, // no transform precoding for DL + 1, // single layer + tda_info->nrOfSymbols, + pdsch->dmrs_parms.N_PRB_DMRS * pdsch->dmrs_parms.N_DMRS_SLOT, + num_total_bytes, + 1, // min_rbSize + max_rbSize, + &TBS, + &rbSize); + if (!res) + pdsch->mcs++; + } + break; + } } - AssertFatal( - TBS >= num_total_bytes, - "Couldn't allocate enough resources for %d bytes in SIB PDSCH (rbStart %d, rbSize %d, bwpSize %d SLmask %x - [%d,%d])\n", - num_total_bytes, - rbStart, - rbSize, - bwpSize, - SL_to_bitmap(tda_info->startSymbolIndex, tda_info->nrOfSymbols), - tda_info->startSymbolIndex, - tda_info->nrOfSymbols); + AssertFatal(TBS >= num_total_bytes, + "Couldn't allocate enough resources for %d bytes in SIB PDSCH (rbStart %d, rbSize %d, bwpSize %d)\n", + num_total_bytes, + rbStart, + rbSize, + bwpSize); pdsch->rbSize = rbSize; - pdsch->rbStart = 0; + pdsch->rbStart = rbStart; - LOG_D( - NR_MAC, - "mcs=%i, startSymbolIndex = %i, nrOfSymbols = %i, rbSize = %i, TBS = %i, dmrs_length %d, N_PRB_DMRS = %d, mappingtype = %d\n", - pdsch->mcs, - tda_info->startSymbolIndex, - tda_info->nrOfSymbols, - pdsch->rbSize, - TBS, - dmrs_length, - N_PRB_DMRS, - tda_info->mapping_type); + LOG_D(NR_MAC, + "mcs=%i, startSymbolIndex = %i, nrOfSymbols = %i, rbSize = %i, TBS = %i, dmrs_length %d, N_PRB_DMRS = %d, mappingtype = %d\n", + pdsch->mcs, + tda_info->startSymbolIndex, + tda_info->nrOfSymbols, + rbSize, + TBS, + pdsch->dmrs_parms.N_DMRS_SLOT, + N_PRB_DMRS, + tda_info->mapping_type); return TBS; } @@ -382,7 +381,10 @@ static uint32_t schedule_control_sib1(module_id_t module_id, AssertFatal(gNB_mac->sched_ctrlCommon->cce_index >= 0, "Could not find CCE for coreset0\n"); - uint32_t TBS = get_tbs(type0_PDCCH_CSS_config, &gNB_mac->sched_ctrlCommon->sched_pdsch, gNB_mac->sched_ctrlCommon->num_total_bytes, vrb_map); + uint32_t TBS = get_tbs_bch(type0_PDCCH_CSS_config, + &gNB_mac->sched_ctrlCommon->sched_pdsch, + gNB_mac->sched_ctrlCommon->num_total_bytes, + vrb_map); // Mark the corresponding RBs as used fill_pdcch_vrb_map(gNB_mac, @@ -755,7 +757,7 @@ static void other_sib_sched_control(module_id_t module_idP, uint16_t *vrb_map = cc->vrb_map[beam.idx]; uint8_t *sib_bcch_pdu = cc->other_sib_bcch_pdu[payload_idx]; int num_total_bytes = cc->other_sib_bcch_length[payload_idx]; - uint32_t TBS = get_tbs(type0_PDCCH_CSS_config, &sched_pdsch_otherSI, num_total_bytes, vrb_map); + uint32_t TBS = get_tbs_bch(type0_PDCCH_CSS_config, &sched_pdsch_otherSI, num_total_bytes, vrb_map); for (int rb = 0; rb < sched_pdsch_otherSI.rbSize; rb++) { vrb_map[rb + type0_PDCCH_CSS_config->cset_start_rb] |= SL_to_bitmap(tda_info.startSymbolIndex, tda_info.nrOfSymbols); diff --git a/openair2/RRC/NR/nr_rrc_config.c b/openair2/RRC/NR/nr_rrc_config.c index 2047f108c5..d377b2aff3 100644 --- a/openair2/RRC/NR/nr_rrc_config.c +++ b/openair2/RRC/NR/nr_rrc_config.c @@ -277,7 +277,6 @@ NR_SearchSpace_t *rrc_searchspace_config(bool is_common, ss->nrofCandidates->aggregationLevel4 = num_agg_level_candidates[PDCCH_AGG_LEVEL4]; ss->nrofCandidates->aggregationLevel8 = num_agg_level_candidates[PDCCH_AGG_LEVEL8]; ss->nrofCandidates->aggregationLevel16 = num_agg_level_candidates[PDCCH_AGG_LEVEL16]; - ss->searchSpaceType = calloc(1,sizeof(*ss->searchSpaceType)); if (is_common) { ss->searchSpaceType->present = NR_SearchSpace__searchSpaceType_PR_common; From c633fa58550a1fab904fc27bd783a375fe24f308 Mon Sep 17 00:00:00 2001 From: francescomani Date: Fri, 11 Apr 2025 09:13:46 +0200 Subject: [PATCH 45/63] increase number of candidates for otherSIB --- openair2/GNB_APP/gnb_config.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openair2/GNB_APP/gnb_config.c b/openair2/GNB_APP/gnb_config.c index 5a58f23006..24b1fb0c8b 100644 --- a/openair2/GNB_APP/gnb_config.c +++ b/openair2/GNB_APP/gnb_config.c @@ -1141,7 +1141,7 @@ static NR_ServingCellConfigCommon_t *get_scc_config(configmodule_interface_t *cf css_num_agg_level_candidates[PDCCH_AGG_LEVEL8] = NR_SearchSpace__nrofCandidates__aggregationLevel8_n1; } else { css_num_agg_level_candidates[PDCCH_AGG_LEVEL2] = NR_SearchSpace__nrofCandidates__aggregationLevel2_n0; - css_num_agg_level_candidates[PDCCH_AGG_LEVEL4] = NR_SearchSpace__nrofCandidates__aggregationLevel4_n1; + css_num_agg_level_candidates[PDCCH_AGG_LEVEL4] = NR_SearchSpace__nrofCandidates__aggregationLevel4_n2; css_num_agg_level_candidates[PDCCH_AGG_LEVEL8] = NR_SearchSpace__nrofCandidates__aggregationLevel8_n0; } css_num_agg_level_candidates[PDCCH_AGG_LEVEL16] = NR_SearchSpace__nrofCandidates__aggregationLevel16_n0; From 5e8f64996343ffd379e147640cfd4f91f1abfbe0 Mon Sep 17 00:00:00 2001 From: francescomani Date: Sun, 29 Dec 2024 09:10:43 +0100 Subject: [PATCH 46/63] check multiple DCI candidates with same length if SIB RNTI --- openair1/PHY/NR_UE_TRANSPORT/dci_nr.c | 5 ++++- openair2/RRC/NR_UE/rrc_UE.c | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/openair1/PHY/NR_UE_TRANSPORT/dci_nr.c b/openair1/PHY/NR_UE_TRANSPORT/dci_nr.c index 36bd5a1ce7..bc87b3de7f 100644 --- a/openair1/PHY/NR_UE_TRANSPORT/dci_nr.c +++ b/openair1/PHY/NR_UE_TRANSPORT/dci_nr.c @@ -640,6 +640,9 @@ void nr_dci_decoding_procedure(PHY_VARS_NR_UE *ue, { int e_rx_cand_idx = 0; *dci_ind = (fapi_nr_dci_indication_t){.SFN = proc->frame_rx, .slot = proc->nr_slot_rx}; + // if DCI for SIB we don't break after finding 1st DCI with that RNTI + // there might be SIB1 and otherSIB in the same slot with the same length + bool is_SI = rel15->rnti == SI_RNTI; for (int j = 0; j < rel15->number_of_candidates; j++) { int CCEind = rel15->CCE[j]; @@ -653,7 +656,7 @@ void nr_dci_decoding_procedure(PHY_VARS_NR_UE *ue, int dci_length = rel15->dci_length_options[k]; int ind; for (ind = 0; ind < dci_ind->number_of_dcis; ind++) { - if (rel15->rnti == dci_ind->dci_list[ind].rnti && dci_length == dci_ind->dci_list[ind].payloadSize) { + if (!is_SI && rel15->rnti == dci_ind->dci_list[ind].rnti && dci_length == dci_ind->dci_list[ind].payloadSize) { break; } } diff --git a/openair2/RRC/NR_UE/rrc_UE.c b/openair2/RRC/NR_UE/rrc_UE.c index dc1cb72baa..e321c1dd5b 100644 --- a/openair2/RRC/NR_UE/rrc_UE.c +++ b/openair2/RRC/NR_UE/rrc_UE.c @@ -185,7 +185,7 @@ static void nr_decode_SI(NR_UE_RRC_SI_INFO *SI_info, NR_SystemInformation_t *si, for (int i = 0; i < si->criticalExtensions.choice.systemInformation->sib_TypeAndInfo.list.count; i++) { SystemInformation_IEs__sib_TypeAndInfo__Member *typeandinfo; typeandinfo = si->criticalExtensions.choice.systemInformation->sib_TypeAndInfo.list.array[i]; - LOG_I(NR_RRC, "Found SIB%d\n", typeandinfo->present + 1); + LOG_A(NR_RRC, "Found SIB%d\n", typeandinfo->present + 1); switch(typeandinfo->present) { case NR_SystemInformation_IEs__sib_TypeAndInfo__Member_PR_sib2: SI_info->sib2_validity = SIB_VALID; From 97ca739be22633b03c01c1d8cadcf029398b7f97 Mon Sep 17 00:00:00 2001 From: francescomani Date: Fri, 27 Dec 2024 18:30:08 +0100 Subject: [PATCH 47/63] remove hardcoded window size for otherSI in UE --- openair2/LAYER2/NR_MAC_UE/nr_ue_dci_configuration.c | 1 - 1 file changed, 1 deletion(-) diff --git a/openair2/LAYER2/NR_MAC_UE/nr_ue_dci_configuration.c b/openair2/LAYER2/NR_MAC_UE/nr_ue_dci_configuration.c index f3a132669f..1a816c64f6 100644 --- a/openair2/LAYER2/NR_MAC_UE/nr_ue_dci_configuration.c +++ b/openair2/LAYER2/NR_MAC_UE/nr_ue_dci_configuration.c @@ -429,7 +429,6 @@ bool monitor_dci_for_other_SI(NR_UE_MAC_INST_t *mac, if ((frame % T == floor(x / slots_per_frame)) && (slot == x % slots_per_frame)) mac->si_SchedInfo.si_window_start = abs_slot; } - window_slots = 10; // TODO window length hardcoded to 10 at gNB for NTN check_valid = is_window_valid(mac, window_slots, abs_slot); if (check_valid && (config->si_WindowPosition - 1) == slot) return search_space_monitoring_ocasion_other_si(mac, ss, abs_slot, frame, slot, slots_per_frame); From e4cb8be02342172fb86b29f9f7a67196c4ee04ed Mon Sep 17 00:00:00 2001 From: francescomani Date: Sun, 29 Dec 2024 08:51:17 +0100 Subject: [PATCH 48/63] do not schedule reception of DLSCH associated with DCI for SIB1 if no SIB1 is scheduled for reception in this scenario we proably were scheduling reception of otherSIB in the same slot as SIB1 was present --- openair2/LAYER2/NR_MAC_UE/nr_ue_procedures.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/openair2/LAYER2/NR_MAC_UE/nr_ue_procedures.c b/openair2/LAYER2/NR_MAC_UE/nr_ue_procedures.c index 20745a5e25..995d987304 100644 --- a/openair2/LAYER2/NR_MAC_UE/nr_ue_procedures.c +++ b/openair2/LAYER2/NR_MAC_UE/nr_ue_procedures.c @@ -2993,7 +2993,7 @@ static void extract_10_ra_rnti(dci_pdu_rel15_t *dci_pdu_rel15, const uint8_t *dc EXTRACT_DCI_ITEM(dci_pdu_rel15->tb_scaling, 2); } -static void extract_10_si_rnti(dci_pdu_rel15_t *dci_pdu_rel15, const uint8_t *dci_pdu, int pos, const int N_RB) +static uint8_t extract_10_si_rnti(dci_pdu_rel15_t *dci_pdu_rel15, const uint8_t *dci_pdu, int pos, const int N_RB) { LOG_D(NR_MAC_DCI, "Received dci 1_0 SI rnti\n"); @@ -3009,6 +3009,7 @@ static void extract_10_si_rnti(dci_pdu_rel15_t *dci_pdu_rel15, const uint8_t *dc EXTRACT_DCI_ITEM(dci_pdu_rel15->rv, 2); // System information indicator 1 bit EXTRACT_DCI_ITEM(dci_pdu_rel15->system_info_indicator, 1); + return dci_pdu_rel15->system_info_indicator; } static bool extract_10_c_rnti(NR_UE_MAC_INST_t *mac, @@ -3312,7 +3313,10 @@ static nr_dci_format_t nr_extract_dci_00_10(NR_UE_MAC_INST_t *mac, n_RB = get_nrb_for_dci(mac, format, ss_type); if (n_RB == 0) return NR_DCI_NONE; - extract_10_si_rnti(dci_pdu_rel15, dci_pdu, pos, n_RB); + uint8_t sys_info = extract_10_si_rnti(dci_pdu_rel15, dci_pdu, pos, n_RB); + // sys info = 0 for SIB1 and 1 for other SIB + if (mac->get_sib1 == 0 && sys_info == 0) + return NR_DCI_NONE; break; case TYPE_C_RNTI_ : // Identifier for DCI formats From 08da0bd2cc8716105d3a59a1eb717f81d600c156 Mon Sep 17 00:00:00 2001 From: francescomani Date: Mon, 30 Dec 2024 15:13:44 +0100 Subject: [PATCH 49/63] improvements at UE to receive multiple instances of otherSIB for example SIB2 and SIB19 which are scheduled in separate messages --- common/utils/nr/nr_common.h | 1 + openair2/LAYER2/NR_MAC_UE/config_ue.c | 9 +- openair2/LAYER2/NR_MAC_UE/mac_defs.h | 2 +- openair2/LAYER2/NR_MAC_UE/main_ue_nr.c | 3 +- .../NR_MAC_UE/nr_ue_dci_configuration.c | 71 ++++++++-------- openair2/LAYER2/NR_MAC_UE/nr_ue_procedures.c | 18 +++- .../LAYER2/NR_MAC_gNB/gNB_scheduler_bch.c | 13 +-- openair2/RRC/NR_UE/rrc_UE.c | 84 ++++++++++--------- openair2/RRC/NR_UE/rrc_defs.h | 3 +- 9 files changed, 108 insertions(+), 96 deletions(-) diff --git a/common/utils/nr/nr_common.h b/common/utils/nr/nr_common.h index d064b522b9..ef04daaad1 100644 --- a/common/utils/nr/nr_common.h +++ b/common/utils/nr/nr_common.h @@ -38,6 +38,7 @@ #include "assertions.h" #include "common/utils/utils.h" +#define MAX_SI_GROUPS 3 #define NR_MAX_PDSCH_TBS 3824 #define MAX_NUM_BEAM_PERIODS 4 #define MAX_BWP_SIZE 275 diff --git a/openair2/LAYER2/NR_MAC_UE/config_ue.c b/openair2/LAYER2/NR_MAC_UE/config_ue.c index 1b5518f0bc..46e126ed97 100644 --- a/openair2/LAYER2/NR_MAC_UE/config_ue.c +++ b/openair2/LAYER2/NR_MAC_UE/config_ue.c @@ -936,10 +936,7 @@ static void update_mib_conf(NR_MIB_t *target, NR_MIB_t *source) target->intraFreqReselection = source->intraFreqReselection; } -void nr_rrc_mac_config_req_mib(module_id_t module_id, - int cc_idP, - NR_MIB_t *mib, - int sched_sib) +void nr_rrc_mac_config_req_mib(module_id_t module_id, int cc_idP, NR_MIB_t *mib, int sched_sib) { NR_UE_MAC_INST_t *mac = get_mac_inst(module_id); int ret = pthread_mutex_lock(&mac->if_mutex); @@ -952,8 +949,8 @@ void nr_rrc_mac_config_req_mib(module_id_t module_id, mac->phy_config.CC_id = cc_idP; if (sched_sib == 1) mac->get_sib1 = true; - else if (sched_sib == 2) - mac->get_otherSI = true; + else if (sched_sib > 1) + mac->get_otherSI[sched_sib - 2] = true; nr_ue_decode_mib(mac, cc_idP); ret = pthread_mutex_unlock(&mac->if_mutex); AssertFatal(!ret, "mutex failed %d\n", ret); diff --git a/openair2/LAYER2/NR_MAC_UE/mac_defs.h b/openair2/LAYER2/NR_MAC_UE/mac_defs.h index 838d1989f0..3241235bd3 100644 --- a/openair2/LAYER2/NR_MAC_UE/mac_defs.h +++ b/openair2/LAYER2/NR_MAC_UE/mac_defs.h @@ -539,7 +539,7 @@ typedef struct NR_UE_MAC_INST_s { int servCellIndex; long physCellId; bool get_sib1; - bool get_otherSI; + bool get_otherSI[MAX_SI_GROUPS]; NR_MIB_t *mib; si_schedInfo_t si_SchedInfo; diff --git a/openair2/LAYER2/NR_MAC_UE/main_ue_nr.c b/openair2/LAYER2/NR_MAC_UE/main_ue_nr.c index 6c744e8275..1ca4168b6f 100644 --- a/openair2/LAYER2/NR_MAC_UE/main_ue_nr.c +++ b/openair2/LAYER2/NR_MAC_UE/main_ue_nr.c @@ -56,7 +56,8 @@ void nr_ue_init_mac(NR_UE_MAC_INST_t *mac) LOG_I(NR_MAC, "[UE%d] Initializing MAC\n", mac->ue_id); nr_ue_reset_sync_state(mac); mac->get_sib1 = false; - mac->get_otherSI = false; + for (int i = 0; i < MAX_SI_GROUPS; i++) + mac->get_otherSI[i] = false; memset(&mac->phy_config, 0, sizeof(mac->phy_config)); mac->si_SchedInfo.si_window_start = -1; mac->servCellIndex = 0; diff --git a/openair2/LAYER2/NR_MAC_UE/nr_ue_dci_configuration.c b/openair2/LAYER2/NR_MAC_UE/nr_ue_dci_configuration.c index 1a816c64f6..34ef533884 100644 --- a/openair2/LAYER2/NR_MAC_UE/nr_ue_dci_configuration.c +++ b/openair2/LAYER2/NR_MAC_UE/nr_ue_dci_configuration.c @@ -375,7 +375,6 @@ bool search_space_monitoring_ocasion_other_si(NR_UE_MAC_INST_t *mac, if (((frame * slots_per_frame + slot - offset - i) % period) == 0) { int N = mac->ssb_list.nb_tx_ssb; int K = mac->ssb_list.nb_ssb_per_index[mac->mib_ssb]; - // numbering current frame and slot in terms of monitoring occasions in window int rel_slot = abs_slot - mac->si_SchedInfo.si_window_start; int current_monitor_occasion = (rel_slot % period) + (duration * rel_slot / period); @@ -399,43 +398,41 @@ bool is_window_valid(NR_UE_MAC_INST_t *mac, int window_slots, int abs_slot) return true; } -bool monitor_dci_for_other_SI(NR_UE_MAC_INST_t *mac, - const NR_SearchSpace_t *ss, - const int slots_per_frame, - const int frame, - const int slot) +static bool monitor_dci_for_other_SI(NR_UE_MAC_INST_t *mac, + const NR_SearchSpace_t *ss, + const int si_idx, + const int slots_per_frame, + const int frame, + const int slot) { // according to 5.2.2.3.2 in 331 const int abs_slot = frame * slots_per_frame + slot; - for (int i = 0; i < mac->si_SchedInfo.si_SchedInfo_list.count; i++) { - si_schedinfo_config_t *config = mac->si_SchedInfo.si_SchedInfo_list.array[i]; - int window_slots = 5 << mac->si_SchedInfo.si_WindowLength; - int x = (config->si_WindowPosition - 1) * window_slots; - int T = 8 << config->si_Periodicity; // radio frame periodicity - bool check_valid; - switch (config->type) { - case NR_SI_INFO : - if (mac->si_SchedInfo.si_window_start == -1) { - if ((frame % T) == (x / slots_per_frame) && (x % slots_per_frame == 0)) - mac->si_SchedInfo.si_window_start = abs_slot; // in terms of absolute slot number - } - check_valid = is_window_valid(mac, window_slots, abs_slot); - if (check_valid && search_space_monitoring_ocasion_other_si(mac, ss, abs_slot, frame, slot, slots_per_frame)) - return true; - break; - case NR_SI_INFO_v1700 : - if (mac->si_SchedInfo.si_window_start == -1) { - if ((frame % T == floor(x / slots_per_frame)) && (slot == x % slots_per_frame)) - mac->si_SchedInfo.si_window_start = abs_slot; - } - check_valid = is_window_valid(mac, window_slots, abs_slot); - if (check_valid && (config->si_WindowPosition - 1) == slot) - return search_space_monitoring_ocasion_other_si(mac, ss, abs_slot, frame, slot, slots_per_frame); - break; - default : - AssertFatal(false, "Invalid SI-SchedulingInfo case\n"); - } + si_schedinfo_config_t *config = mac->si_SchedInfo.si_SchedInfo_list.array[si_idx]; + int window_slots = 5 << mac->si_SchedInfo.si_WindowLength; + int x = (config->si_WindowPosition - 1) * window_slots; + int T = 8 << config->si_Periodicity; // radio frame periodicity + bool check_valid; + switch (config->type) { + case NR_SI_INFO : + if (mac->si_SchedInfo.si_window_start == -1) { + if ((frame % T) == (x / slots_per_frame) && (x % slots_per_frame == 0)) + mac->si_SchedInfo.si_window_start = abs_slot; // in terms of absolute slot number + } + check_valid = is_window_valid(mac, window_slots, abs_slot); + if (check_valid && search_space_monitoring_ocasion_other_si(mac, ss, abs_slot, frame, slot, slots_per_frame)) + return true; + break; + case NR_SI_INFO_v1700 : + if (mac->si_SchedInfo.si_window_start == -1) { + if ((frame % T == floor(x / slots_per_frame)) && (slot == x % slots_per_frame)) + mac->si_SchedInfo.si_window_start = abs_slot; + } + if (is_window_valid(mac, window_slots, abs_slot)) + return search_space_monitoring_ocasion_other_si(mac, ss, abs_slot, frame, slot, slots_per_frame); + break; + default : + AssertFatal(false, "Invalid SI-SchedulingInfo case\n"); } return false; } @@ -478,13 +475,15 @@ void ue_dci_configuration(NR_UE_MAC_INST_t *mac, fapi_nr_dl_config_request_t *dl config_dci_pdu(mac, dl_config, TYPE_SI_RNTI_, slot, mac->search_space_zero); } } - if (mac->get_otherSI) { + for (int i = 0; i < MAX_SI_GROUPS; i++) { + if (!mac->get_otherSI[i]) + continue; // If searchSpaceOtherSystemInformation is set to zero, // PDCCH monitoring occasions for SI message reception in SI-window // are same as PDCCH monitoring occasions for SIB1 const NR_SearchSpace_t *ss = pdcch_config->otherSI_SS ? pdcch_config->otherSI_SS : mac->search_space_zero; // TODO configure SI-window - if (monitor_dci_for_other_SI(mac, ss, slots_per_frame, frame, slot)) { + if (monitor_dci_for_other_SI(mac, ss, i, slots_per_frame, frame, slot)) { LOG_D(NR_MAC_DCI, "Monitoring DCI for other SIs in frame %d slot %d\n", frame, slot); config_dci_pdu(mac, dl_config, TYPE_SI_RNTI_, slot, ss); } diff --git a/openair2/LAYER2/NR_MAC_UE/nr_ue_procedures.c b/openair2/LAYER2/NR_MAC_UE/nr_ue_procedures.c index 995d987304..9d2f702a16 100644 --- a/openair2/LAYER2/NR_MAC_UE/nr_ue_procedures.c +++ b/openair2/LAYER2/NR_MAC_UE/nr_ue_procedures.c @@ -310,8 +310,12 @@ int8_t nr_ue_decode_BCCH_DL_SCH(NR_UE_MAC_INST_t *mac, if(ack_nack) { LOG_D(NR_MAC, "Decoding NR-BCCH-DL-SCH-Message (SIB1 or SI)\n"); nr_mac_rrc_data_ind_ue(mac->ue_id, cc_id, gNB_index, 0, 0, 0, mac->physCellId, 0, NR_BCCH_DL_SCH, (uint8_t *) pduP, pdu_len); - mac->get_sib1 = false; - mac->get_otherSI = false; + if (mac->get_sib1) + mac->get_sib1 = false; + for (int i = 0; i < MAX_SI_GROUPS; i++) { + if (mac->get_otherSI[i]) + mac->get_otherSI[i] = false; + } T(T_NRUE_MAC_DL_PDU_WITH_DATA, T_INT(SI_RNTI), T_INT(-1 /* frame, unavailable here */), @@ -742,8 +746,16 @@ static int nr_ue_process_dci_dl_10(NR_UE_MAC_INST_t *mac, LOG_W(MAC, "[%d.%d] Invalid frequency_domain_assignment. Possibly due to false DCI. Ignoring DCI!\n", frame, slot); return -1; } + dlsch_pdu->rb_offset = dlsch_pdu->start_rb + dlsch_pdu->BWPStart; - if (mac->get_sib1 || mac->get_otherSI) + bool otherSI = false; + for (int i = 0; i < MAX_SI_GROUPS; i++) { + if (mac->get_otherSI[i]) { + otherSI = true; + break; + } + } + if (mac->get_sib1 || otherSI) dlsch_pdu->rb_offset -= dlsch_pdu->BWPStart; /* TIME_DOM_RESOURCE_ASSIGNMENT */ diff --git a/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_bch.c b/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_bch.c index a00db6585c..39cb665c21 100644 --- a/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_bch.c +++ b/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_bch.c @@ -342,7 +342,7 @@ static uint32_t get_tbs_bch(NR_Type0_PDCCH_CSS_config_t *type0_PDCCH_CSS_config, return TBS; } -static uint32_t schedule_control_sib1(module_id_t module_id, +static uint32_t schedule_control_sib1(gNB_MAC_INST *gNB_mac, int CC_id, NR_Type0_PDCCH_CSS_config_t *type0_PDCCH_CSS_config, int time_domain_allocation, @@ -352,7 +352,6 @@ static uint32_t schedule_control_sib1(module_id_t module_id, int beam, uint16_t num_total_bytes) { - gNB_MAC_INST *gNB_mac = RC.nrmac[module_id]; AssertFatal(gNB_mac->sched_ctrlCommon, "sched_ctrlCommon is NULL\n"); NR_COMMON_channels_t *cc = &gNB_mac->common_channels[CC_id]; uint16_t *vrb_map = cc->vrb_map[beam]; @@ -625,7 +624,8 @@ void schedule_nr_sib1(module_id_t module_idP, NR_COMMON_channels_t *cc = &gNB_mac->common_channels[0]; // Configure sched_ctrlCommon for SIB1 - uint32_t TBS = schedule_control_sib1(module_idP, CC_id, + uint32_t TBS = schedule_control_sib1(gNB_mac, + CC_id, type0_PDCCH_CSS_config, time_domain_allocation, &dmrs_parms, @@ -709,8 +709,11 @@ static void other_sib_sched_control(module_id_t module_idP, int n_slots_frame = gNB_mac->frame_structure.numb_slots_frame; NR_beam_alloc_t beam = beam_allocation_procedure(&gNB_mac->beam_info, frame, slot, beam_index, n_slots_frame); AssertFatal(beam.idx >= 0, "Cannot allocate otherSIB corresponding for SSB number %d in any available beam\n", beam_index); - LOG_D(NR_MAC, "(%d.%d) otherSIB transmission for ssb number %d\n", frame, slot, beam_index); + LOG_D(NR_MAC, "(%d.%d) otherSIB payload %d transmission for ssb number %d\n", frame, slot, payload_idx, beam_index); + NR_COMMON_channels_t *cc = &gNB_mac->common_channels[0]; + NR_Type0_PDCCH_CSS_config_t *type0_PDCCH_CSS_config = &gNB_mac->type0_PDCCH_CSS_config[cc->ssb_index[beam_index]]; + AssertFatal(gNB_mac->sched_ctrlCommon, "sched_ctrlCommon is NULL\n"); NR_PDSCH_ConfigCommon_t *pdsch_ConfigCommon = scc->downlinkConfigCommon->initialDownlinkBWP->pdsch_ConfigCommon->choice.setup; int time_domain_allocation = 1; NR_tda_info_t tda_info = set_tda_info_from_list(pdsch_ConfigCommon->pdsch_TimeDomainAllocationList, time_domain_allocation); @@ -728,8 +731,6 @@ static void other_sib_sched_control(module_id_t module_idP, AssertFatal(nr_of_candidates > 0, "nr_of_candidates is 0\n"); NR_ControlResourceSet_t *coreset = get_coreset(gNB_mac, scc, NULL, ss, NR_SearchSpace__searchSpaceType_PR_common); - NR_COMMON_channels_t *cc = &gNB_mac->common_channels[0]; - NR_Type0_PDCCH_CSS_config_t *type0_PDCCH_CSS_config = &gNB_mac->type0_PDCCH_CSS_config[cc->ssb_index[beam_index]]; if (!gNB_mac->sched_pdcch_otherSI) { gNB_mac->sched_pdcch_otherSI = calloc(1, sizeof(*gNB_mac->sched_pdcch_otherSI)); *gNB_mac->sched_pdcch_otherSI = set_pdcch_structure(gNB_mac, ss, coreset, scc, NULL, type0_PDCCH_CSS_config); diff --git a/openair2/RRC/NR_UE/rrc_UE.c b/openair2/RRC/NR_UE/rrc_UE.c index e321c1dd5b..30c0e548a7 100644 --- a/openair2/RRC/NR_UE/rrc_UE.c +++ b/openair2/RRC/NR_UE/rrc_UE.c @@ -282,28 +282,34 @@ static void nr_rrc_ue_prepare_RRCSetupRequest(NR_UE_RRC_INST_t *rrc) } static void nr_rrc_configure_default_SI(NR_UE_RRC_SI_INFO *SI_info, - struct NR_SI_SchedulingInfo *si_SchedulingInfo, - struct NR_SI_SchedulingInfo_v1700 *si_SchedulingInfo_v1700) + NR_SI_SchedulingInfo_t *si_SchedulingInfo, + NR_SI_SchedulingInfo_v1700_t *si_SchedulingInfo_v1700) { + for (int i = 0; i < MAX_SI_GROUPS; i++) + SI_info->default_otherSI_map[i] = 0; + int nb_groups = 0; if (si_SchedulingInfo) { - SI_info->default_otherSI_map = 0; - for (int i = 0; i < si_SchedulingInfo->schedulingInfoList.list.count; i++) { - struct NR_SchedulingInfo *schedulingInfo = si_SchedulingInfo->schedulingInfoList.list.array[i]; + nb_groups = si_SchedulingInfo->schedulingInfoList.list.count; + AssertFatal(nb_groups <= MAX_SI_GROUPS, "Exceeding max number of SI groups configured\n"); + for (int i = 0; i < nb_groups; i++) { + NR_SchedulingInfo_t *schedulingInfo = si_SchedulingInfo->schedulingInfoList.list.array[i]; for (int j = 0; j < schedulingInfo->sib_MappingInfo.list.count; j++) { - struct NR_SIB_TypeInfo *sib_Type = schedulingInfo->sib_MappingInfo.list.array[j]; - SI_info->default_otherSI_map |= 1 << sib_Type->type; + NR_SIB_TypeInfo_t *sib_Type = schedulingInfo->sib_MappingInfo.list.array[j]; + SI_info->default_otherSI_map[i] |= 1 << sib_Type->type; } } } if (si_SchedulingInfo_v1700) { - SI_info->SInfo_r17.default_otherSI_map_r17 = 0; + int start_idx = nb_groups; + nb_groups += si_SchedulingInfo_v1700->schedulingInfoList2_r17.list.count; + AssertFatal(nb_groups <= MAX_SI_GROUPS, "Exceeding max number of SI groups configured\n"); for (int i = 0; i < si_SchedulingInfo_v1700->schedulingInfoList2_r17.list.count; i++) { - struct NR_SchedulingInfo2_r17 *schedulingInfo2 = si_SchedulingInfo_v1700->schedulingInfoList2_r17.list.array[i]; + NR_SchedulingInfo2_r17_t *schedulingInfo2 = si_SchedulingInfo_v1700->schedulingInfoList2_r17.list.array[i]; for (int j = 0; j < schedulingInfo2->sib_MappingInfo_r17.list.count; j++) { - struct NR_SIB_TypeInfo_v1700 *sib_TypeInfo_v1700 = schedulingInfo2->sib_MappingInfo_r17.list.array[j]; + NR_SIB_TypeInfo_v1700_t *sib_TypeInfo_v1700 = schedulingInfo2->sib_MappingInfo_r17.list.array[j]; if (sib_TypeInfo_v1700->sibType_r17.present == NR_SIB_TypeInfo_v1700__sibType_r17_PR_type1_r17) { - SI_info->SInfo_r17.default_otherSI_map_r17 |= 1 << sib_TypeInfo_v1700->sibType_r17.choice.type1_r17; + SI_info->default_otherSI_map[start_idx + i] |= 1 << (sib_TypeInfo_v1700->sibType_r17.choice.type1_r17 + 13); } } } @@ -318,11 +324,14 @@ static bool verify_NTN_access(const NR_UE_RRC_SI_INFO *SI_info, const NR_SIB1_v1 && *sib1_v1700->cellBarredNTN_r17 == NR_SIB1_v1700_IEs__cellBarredNTN_r17_notBarred) ntn_access = true; - uint32_t sib19_mask = 1 << NR_SIB_TypeInfo_v1700__sibType_r17__type1_r17_sibType19; - int sib19_present = SI_info->SInfo_r17.default_otherSI_map_r17 & sib19_mask; - + uint32_t sib19_mask = 1 << (NR_SIB_TypeInfo_v1700__sibType_r17__type1_r17_sibType19 + 13); + int sib19_present = false; + for (int i = 0; i < MAX_SI_GROUPS; i++) { + sib19_present = SI_info->default_otherSI_map[i] & sib19_mask; + if (sib19_present) + break; + } AssertFatal(!ntn_access || sib19_present, "NTN cell, but SIB19 not configured.\n"); - return ntn_access && sib19_present; } @@ -783,7 +792,7 @@ bool check_si_validity_r17(NR_UE_RRC_SI_INFO_r17 *SI_info, int si_type) return true; } -int check_si_status(NR_UE_RRC_SI_INFO *SI_info) +static int check_si_status(NR_UE_RRC_SI_INFO *SI_info) { // schedule reception of SIB1 if RRC doesn't have it if (SI_info->sib1_validity == SIB_NOT_VALID) { @@ -791,31 +800,22 @@ int check_si_status(NR_UE_RRC_SI_INFO *SI_info) return 1; } else { - if (SI_info->default_otherSI_map) { + for (int j = 0; j < MAX_SI_GROUPS; j++) { + if (!SI_info->default_otherSI_map[j]) + continue; // Check if RRC has configured default SI - // from SIB2 to SIB14 as current ASN1 version // TODO can be used for on demand SI when (if) implemented - for (int i = 2; i < 15; i++) { - int si_index = i - 2; - if ((SI_info->default_otherSI_map >> si_index) & 0x01) { - // if RRC has no valid version of one of the default configured SI - // Then schedule reception of otherSI - if (!check_si_validity(SI_info, si_index)) - return 2; - } - } - } - - // Check if RRC has configured default SI - // from SIB15 to SIB21 as current r17 version - if (SI_info->SInfo_r17.default_otherSI_map_r17) { - for (int i = 15; i < 22; i++) { - int si_index = i - 15; - if ((SI_info->SInfo_r17.default_otherSI_map_r17 >> si_index) & 0x01) { - // if RRC has no valid version of one of the default configured SI - // Then schedule reception of otherSI - if (!check_si_validity_r17(&SI_info->SInfo_r17, si_index)) - return 2; + for (int i = 2; i < 22; i++) { + if (!((SI_info->default_otherSI_map[j] >> (i - 2)) & 0x01)) + continue; + // if RRC has no valid version of one of the default configured SI + // Then schedule reception of otherSI + if (i < 15) { + if (!check_si_validity(SI_info, i - 2)) + return 2 + j; + } else { + if (!check_si_validity_r17(&SI_info->SInfo_r17, i - 15)) + return 2 + j; } } } @@ -862,8 +862,10 @@ static void nr_rrc_ue_decode_NR_BCCH_BCH_Message(NR_UE_RRC_INST_t *rrc, } int get_sib = 0; - if (IS_SA_MODE(get_softmodem_params()) && bcch_message->message.present == NR_BCCH_BCH_MessageType_PR_mib - && bcch_message->message.choice.mib->cellBarred == NR_MIB__cellBarred_notBarred && rrc->nrRrcState != RRC_STATE_DETACH_NR) { + if (IS_SA_MODE(get_softmodem_params()) + && bcch_message->message.present == NR_BCCH_BCH_MessageType_PR_mib + && bcch_message->message.choice.mib->cellBarred == NR_MIB__cellBarred_notBarred + && rrc->nrRrcState != RRC_STATE_DETACH_NR) { NR_UE_RRC_SI_INFO *SI_info = &rrc->perNB[gNB_index].SInfo; // to schedule MAC to get SI if required get_sib = check_si_status(SI_info); diff --git a/openair2/RRC/NR_UE/rrc_defs.h b/openair2/RRC/NR_UE/rrc_defs.h index ee46a343d7..c9b14b3ae5 100644 --- a/openair2/RRC/NR_UE/rrc_defs.h +++ b/openair2/RRC/NR_UE/rrc_defs.h @@ -111,7 +111,6 @@ typedef enum { } SIB_validity_t; typedef struct UE_RRC_SI_INFO_NR_r17_s { - uint32_t default_otherSI_map_r17; SIB_validity_t sib15_validity; NR_timer_t sib15_timer; SIB_validity_t sib16_validity; @@ -129,7 +128,7 @@ typedef struct UE_RRC_SI_INFO_NR_r17_s { } NR_UE_RRC_SI_INFO_r17; typedef struct UE_RRC_SI_INFO_NR_s { - uint32_t default_otherSI_map; + uint32_t default_otherSI_map[MAX_SI_GROUPS]; SIB_validity_t sib1_validity; NR_timer_t sib1_timer; SIB_validity_t sib2_validity; From 2228c3966f7be42d9dfd181794d4299cf06018cb Mon Sep 17 00:00:00 2001 From: francescomani Date: Mon, 30 Dec 2024 15:29:23 +0100 Subject: [PATCH 50/63] enabling configuration of transmitted SIBs in NTN config file --- ci-scripts/conf_files/gnb.sa.band254.u0.25prb.rfsim.ntn.conf | 2 ++ .../GENERIC-NR-5GC/CONF/gnb.sa.band254.ntn.mu1.24prb.rfsim.conf | 2 ++ .../GENERIC-NR-5GC/CONF/gnb.sa.band255.ntn.mu0.25prb.rfsim.conf | 2 ++ .../GENERIC-NR-5GC/CONF/gnb.sa.band256.ntn.mu0.25prb.rfsim.conf | 2 ++ 4 files changed, 8 insertions(+) diff --git a/ci-scripts/conf_files/gnb.sa.band254.u0.25prb.rfsim.ntn.conf b/ci-scripts/conf_files/gnb.sa.band254.u0.25prb.rfsim.ntn.conf index 1d0bba606d..002d790e61 100644 --- a/ci-scripts/conf_files/gnb.sa.band254.u0.25prb.rfsim.ntn.conf +++ b/ci-scripts/conf_files/gnb.sa.band254.u0.25prb.rfsim.ntn.conf @@ -19,6 +19,8 @@ gNBs = sib1_tda = 5; min_rxtxtime = 6; disable_harq = 1; + cu_sibs = [2]; + du_sibs = [19]; servingCellConfigCommon = ( { diff --git a/targets/PROJECTS/GENERIC-NR-5GC/CONF/gnb.sa.band254.ntn.mu1.24prb.rfsim.conf b/targets/PROJECTS/GENERIC-NR-5GC/CONF/gnb.sa.band254.ntn.mu1.24prb.rfsim.conf index 67be3dbb44..df06f0483b 100644 --- a/targets/PROJECTS/GENERIC-NR-5GC/CONF/gnb.sa.band254.ntn.mu1.24prb.rfsim.conf +++ b/targets/PROJECTS/GENERIC-NR-5GC/CONF/gnb.sa.band254.ntn.mu1.24prb.rfsim.conf @@ -19,6 +19,8 @@ gNBs = sib1_tda = 5; min_rxtxtime = 6; disable_harq = 1; + cu_sibs = [2]; + du_sibs = [19]; servingCellConfigCommon = ( { diff --git a/targets/PROJECTS/GENERIC-NR-5GC/CONF/gnb.sa.band255.ntn.mu0.25prb.rfsim.conf b/targets/PROJECTS/GENERIC-NR-5GC/CONF/gnb.sa.band255.ntn.mu0.25prb.rfsim.conf index 5a14f4fae6..34c31460ee 100644 --- a/targets/PROJECTS/GENERIC-NR-5GC/CONF/gnb.sa.band255.ntn.mu0.25prb.rfsim.conf +++ b/targets/PROJECTS/GENERIC-NR-5GC/CONF/gnb.sa.band255.ntn.mu0.25prb.rfsim.conf @@ -19,6 +19,8 @@ gNBs = sib1_tda = 5; min_rxtxtime = 6; disable_harq = 1; + cu_sibs = [2]; + du_sibs = [19]; servingCellConfigCommon = ( { diff --git a/targets/PROJECTS/GENERIC-NR-5GC/CONF/gnb.sa.band256.ntn.mu0.25prb.rfsim.conf b/targets/PROJECTS/GENERIC-NR-5GC/CONF/gnb.sa.band256.ntn.mu0.25prb.rfsim.conf index c4213eb0d3..7b87bb3e98 100644 --- a/targets/PROJECTS/GENERIC-NR-5GC/CONF/gnb.sa.band256.ntn.mu0.25prb.rfsim.conf +++ b/targets/PROJECTS/GENERIC-NR-5GC/CONF/gnb.sa.band256.ntn.mu0.25prb.rfsim.conf @@ -19,6 +19,8 @@ gNBs = sib1_tda = 5; min_rxtxtime = 6; disable_harq = 1; + cu_sibs = [2]; + du_sibs = [19]; servingCellConfigCommon = ( { From 3b22c4a566542ebcaba50db195999f8baf91f1da Mon Sep 17 00:00:00 2001 From: francescomani Date: Wed, 29 Jan 2025 10:39:26 +0100 Subject: [PATCH 51/63] Revert tentative fix for double sib1 decoding because it doesn't behave as expected. Instead a new pending_sib variable is used for the same goal. This reverts commit 35e9d1e0c246b43cb2cad20123678d839e7f3bae. --- openair2/LAYER2/NR_MAC_UE/mac_proto.h | 12 +- openair2/LAYER2/NR_MAC_UE/nr_ue_procedures.c | 17 +- openair2/NR_UE_PHY_INTERFACE/NR_IF_Module.c | 3 +- openair2/RRC/NR_UE/L2_interface_ue.c | 37 ++-- openair2/RRC/NR_UE/rrc_UE.c | 185 +++++++----------- openair2/RRC/NR_UE/rrc_defs.h | 49 +++-- openair2/RRC/NR_UE/rrc_timers_and_constants.c | 100 +++++++--- 7 files changed, 207 insertions(+), 196 deletions(-) diff --git a/openair2/LAYER2/NR_MAC_UE/mac_proto.h b/openair2/LAYER2/NR_MAC_UE/mac_proto.h index a2b2e0f777..693ce4eb5a 100644 --- a/openair2/LAYER2/NR_MAC_UE/mac_proto.h +++ b/openair2/LAYER2/NR_MAC_UE/mac_proto.h @@ -68,12 +68,12 @@ void release_common_ss_cset(NR_BWP_PDCCH_t *pdcch); \param sibs_mask sibs mask \param pduP pointer to pdu \param pdu_length length of pdu */ -int8_t nr_ue_decode_BCCH_DL_SCH(NR_UE_MAC_INST_t *mac, - int cc_id, - unsigned int gNB_index, - uint8_t ack_nack, - uint8_t *pduP, - uint32_t pdu_len); +void nr_ue_decode_BCCH_DL_SCH(NR_UE_MAC_INST_t *mac, + int cc_id, + unsigned int gNB_index, + uint8_t ack_nack, + uint8_t *pduP, + uint32_t pdu_len); void release_dl_BWP(NR_UE_MAC_INST_t *mac, int index); void release_ul_BWP(NR_UE_MAC_INST_t *mac, int index); diff --git a/openair2/LAYER2/NR_MAC_UE/nr_ue_procedures.c b/openair2/LAYER2/NR_MAC_UE/nr_ue_procedures.c index 9d2f702a16..e282c380b0 100644 --- a/openair2/LAYER2/NR_MAC_UE/nr_ue_procedures.c +++ b/openair2/LAYER2/NR_MAC_UE/nr_ue_procedures.c @@ -300,12 +300,12 @@ static void configure_ratematching_csi(fapi_nr_dl_config_dlsch_pdu_rel15_t *dlsc } } -int8_t nr_ue_decode_BCCH_DL_SCH(NR_UE_MAC_INST_t *mac, - int cc_id, - unsigned int gNB_index, - uint8_t ack_nack, - uint8_t *pduP, - uint32_t pdu_len) +void nr_ue_decode_BCCH_DL_SCH(NR_UE_MAC_INST_t *mac, + int cc_id, + unsigned int gNB_index, + uint8_t ack_nack, + uint8_t *pduP, + uint32_t pdu_len) { if(ack_nack) { LOG_D(NR_MAC, "Decoding NR-BCCH-DL-SCH-Message (SIB1 or SI)\n"); @@ -323,9 +323,10 @@ int8_t nr_ue_decode_BCCH_DL_SCH(NR_UE_MAC_INST_t *mac, T_INT(0 /* harq_pid */), T_BUFFER(pduP, pdu_len)); } - else + else { LOG_E(NR_MAC, "Got NACK on NR-BCCH-DL-SCH-Message (%s)\n", mac->get_sib1 ? "SIB1" : "other SI"); - return 0; + nr_mac_rrc_data_ind_ue(mac->ue_id, cc_id, gNB_index, 0, 0, 0, mac->physCellId, 0, NR_BCCH_DL_SCH, NULL, 0); + } } /* diff --git a/openair2/NR_UE_PHY_INTERFACE/NR_IF_Module.c b/openair2/NR_UE_PHY_INTERFACE/NR_IF_Module.c index 14916062b2..dfb0a0d942 100644 --- a/openair2/NR_UE_PHY_INTERFACE/NR_IF_Module.c +++ b/openair2/NR_UE_PHY_INTERFACE/NR_IF_Module.c @@ -1108,7 +1108,8 @@ static int handle_bcch_dlsch(NR_UE_MAC_INST_t *mac, uint8_t *pduP, uint32_t pdu_len) { - return nr_ue_decode_BCCH_DL_SCH(mac, cc_id, gNB_index, ack_nack, pduP, pdu_len); + nr_ue_decode_BCCH_DL_SCH(mac, cc_id, gNB_index, ack_nack, pduP, pdu_len); + return 0; } // L2 Abstraction Layer diff --git a/openair2/RRC/NR_UE/L2_interface_ue.c b/openair2/RRC/NR_UE/L2_interface_ue.c index 3bfe1d34e0..7202943322 100644 --- a/openair2/RRC/NR_UE/L2_interface_ue.c +++ b/openair2/RRC/NR_UE/L2_interface_ue.c @@ -59,15 +59,19 @@ void nr_mac_rrc_data_ind_ue(const module_id_t module_id, const sdu_size_t pdu_len) { sdu_size_t sdu_size = 0; - + MessageDef *message_p; switch(channel) { case NR_BCCH_BCH: case NR_BCCH_DL_SCH: - if (pdu_len>0) { - LOG_T(NR_RRC, "[UE %d] Received SDU for NR-BCCH-DL-SCH on SRB %u from gNB %d\n", module_id, channel & RAB_OFFSET, + message_p = itti_alloc_new_message(TASK_MAC_UE, 0, NR_RRC_MAC_BCCH_DATA_IND); + memset(NR_RRC_MAC_BCCH_DATA_IND (message_p).sdu, 0, BCCH_SDU_SIZE); + if (pdu_len > 0) { + LOG_T(NR_RRC, + "[UE %d] Received SDU for NR-BCCH-DL-SCH on SRB %u from gNB %d\n", + module_id, + channel & RAB_OFFSET, gNB_index); - MessageDef *message_p; int msg_sdu_size = BCCH_SDU_SIZE; if (pdu_len > msg_sdu_size) { @@ -77,25 +81,23 @@ void nr_mac_rrc_data_ind_ue(const module_id_t module_id, sdu_size = pdu_len; } - message_p = itti_alloc_new_message(TASK_MAC_UE, 0, NR_RRC_MAC_BCCH_DATA_IND); - memset(NR_RRC_MAC_BCCH_DATA_IND (message_p).sdu, 0, BCCH_SDU_SIZE); memcpy(NR_RRC_MAC_BCCH_DATA_IND (message_p).sdu, pduP, sdu_size); - NR_RRC_MAC_BCCH_DATA_IND (message_p).frame = frame; //frameP - NR_RRC_MAC_BCCH_DATA_IND (message_p).slot = slot; NR_RRC_MAC_BCCH_DATA_IND (message_p).sdu_size = sdu_size; - NR_RRC_MAC_BCCH_DATA_IND (message_p).gnb_index = gNB_index; - NR_RRC_MAC_BCCH_DATA_IND (message_p).phycellid = cellid; - NR_RRC_MAC_BCCH_DATA_IND (message_p).ssb_arfcn = arfcn; - NR_RRC_MAC_BCCH_DATA_IND (message_p).is_bch = (channel == NR_BCCH_BCH); - itti_send_msg_to_task(TASK_RRC_NRUE, GNB_MODULE_ID_TO_INSTANCE(module_id), message_p); } + else { + NR_RRC_MAC_BCCH_DATA_IND (message_p).sdu_size = 0; + } + NR_RRC_MAC_BCCH_DATA_IND (message_p).frame = frame; //frameP + NR_RRC_MAC_BCCH_DATA_IND (message_p).slot = slot; + NR_RRC_MAC_BCCH_DATA_IND (message_p).gnb_index = gNB_index; + NR_RRC_MAC_BCCH_DATA_IND (message_p).phycellid = cellid; + NR_RRC_MAC_BCCH_DATA_IND (message_p).ssb_arfcn = arfcn; + NR_RRC_MAC_BCCH_DATA_IND (message_p).is_bch = (channel == NR_BCCH_BCH); + itti_send_msg_to_task(TASK_RRC_NRUE, GNB_MODULE_ID_TO_INSTANCE(module_id), message_p); break; - case NR_SBCCH_SL_BCH: if (pdu_len > 0) { LOG_T(NR_RRC, "[UE %d] Received SL-MIB for NR_SBCCH_SL_BCH.\n", module_id); - - MessageDef *message_p; int msg_sdu_size = BCCH_SDU_SIZE; if (pdu_len > msg_sdu_size) { @@ -115,9 +117,8 @@ void nr_mac_rrc_data_ind_ue(const module_id_t module_id, itti_send_msg_to_task(TASK_RRC_NRUE, GNB_MODULE_ID_TO_INSTANCE(module_id), message_p); } break; - default: - break; + AssertFatal(false, "Invalid channel in data indication\n"); } } diff --git a/openair2/RRC/NR_UE/rrc_UE.c b/openair2/RRC/NR_UE/rrc_UE.c index 30c0e548a7..57ba669aeb 100644 --- a/openair2/RRC/NR_UE/rrc_UE.c +++ b/openair2/RRC/NR_UE/rrc_UE.c @@ -188,59 +188,59 @@ static void nr_decode_SI(NR_UE_RRC_SI_INFO *SI_info, NR_SystemInformation_t *si, LOG_A(NR_RRC, "Found SIB%d\n", typeandinfo->present + 1); switch(typeandinfo->present) { case NR_SystemInformation_IEs__sib_TypeAndInfo__Member_PR_sib2: - SI_info->sib2_validity = SIB_VALID; + SI_info->sib2_validity = true; nr_timer_start(&SI_info->sib2_timer); break; case NR_SystemInformation_IEs__sib_TypeAndInfo__Member_PR_sib3: - SI_info->sib3_validity = SIB_VALID; + SI_info->sib3_validity = true; nr_timer_start(&SI_info->sib3_timer); break; case NR_SystemInformation_IEs__sib_TypeAndInfo__Member_PR_sib4: - SI_info->sib4_validity = SIB_VALID; + SI_info->sib4_validity = true; nr_timer_start(&SI_info->sib4_timer); break; case NR_SystemInformation_IEs__sib_TypeAndInfo__Member_PR_sib5: - SI_info->sib5_validity = SIB_VALID; + SI_info->sib5_validity = true; nr_timer_start(&SI_info->sib5_timer); break; case NR_SystemInformation_IEs__sib_TypeAndInfo__Member_PR_sib6: - SI_info->sib6_validity = SIB_VALID; + SI_info->sib6_validity = true; nr_timer_start(&SI_info->sib6_timer); break; case NR_SystemInformation_IEs__sib_TypeAndInfo__Member_PR_sib7: - SI_info->sib7_validity = SIB_VALID; + SI_info->sib7_validity = true; nr_timer_start(&SI_info->sib7_timer); break; case NR_SystemInformation_IEs__sib_TypeAndInfo__Member_PR_sib8: - SI_info->sib8_validity = SIB_VALID; + SI_info->sib8_validity = true; nr_timer_start(&SI_info->sib8_timer); break; case NR_SystemInformation_IEs__sib_TypeAndInfo__Member_PR_sib9: - SI_info->sib9_validity = SIB_VALID; + SI_info->sib9_validity = true; nr_timer_start(&SI_info->sib9_timer); break; case NR_SystemInformation_IEs__sib_TypeAndInfo__Member_PR_sib10_v1610: - SI_info->sib10_validity = SIB_VALID; + SI_info->sib10_validity = true; nr_timer_start(&SI_info->sib10_timer); break; case NR_SystemInformation_IEs__sib_TypeAndInfo__Member_PR_sib11_v1610: - SI_info->sib11_validity = SIB_VALID; + SI_info->sib11_validity = true; nr_timer_start(&SI_info->sib11_timer); break; case NR_SystemInformation_IEs__sib_TypeAndInfo__Member_PR_sib12_v1610: - SI_info->sib12_validity = SIB_VALID; + SI_info->sib12_validity = true; nr_timer_start(&SI_info->sib12_timer); break; case NR_SystemInformation_IEs__sib_TypeAndInfo__Member_PR_sib13_v1610: - SI_info->sib13_validity = SIB_VALID; + SI_info->sib13_validity = true; nr_timer_start(&SI_info->sib13_timer); break; case NR_SystemInformation_IEs__sib_TypeAndInfo__Member_PR_sib14_v1610: - SI_info->sib14_validity = SIB_VALID; + SI_info->sib14_validity = true; nr_timer_start(&SI_info->sib14_timer); break; case NR_SystemInformation_IEs__sib_TypeAndInfo__Member_PR_sib19_v1700: - SI_info->SInfo_r17.sib19_validity = SIB_VALID; + SI_info->SInfo_r17.sib19_validity = true; if (g_log->log_component[NR_RRC].level >= OAILOG_DEBUG) xer_fprint(stdout, &asn_DEF_NR_SIB19_r17, (const void *)typeandinfo->choice.sib19_v1700); sib19 = typeandinfo->choice.sib19_v1700; @@ -341,7 +341,7 @@ static void nr_rrc_process_sib1(NR_UE_RRC_INST_t *rrc, NR_UE_RRC_SI_INFO *SI_inf xer_fprint(stdout, &asn_DEF_NR_SIB1, (const void *) sib1); LOG_A(NR_RRC, "SIB1 decoded\n"); nr_timer_start(&SI_info->sib1_timer); - SI_info->sib1_validity = SIB_VALID; + SI_info->sib1_validity = true; if (rrc->nrRrcState == RRC_STATE_IDLE_NR) { rrc->ra_trigger = RRC_CONNECTION_SETUP; } @@ -658,82 +658,56 @@ bool check_si_validity(NR_UE_RRC_SI_INFO *SI_info, int si_type) { switch (si_type) { case NR_SIB_TypeInfo__type_sibType2: - if (SI_info->sib2_validity == SIB_NOT_VALID) { - SI_info->sib2_validity = SIB_REQUESTED; + if (!SI_info->sib2_validity) return false; - } break; case NR_SIB_TypeInfo__type_sibType3: - if (SI_info->sib3_validity == SIB_NOT_VALID) { - SI_info->sib3_validity = SIB_REQUESTED; + if (!SI_info->sib3_validity) return false; - } break; case NR_SIB_TypeInfo__type_sibType4: - if (SI_info->sib4_validity == SIB_NOT_VALID) { - SI_info->sib4_validity = SIB_REQUESTED; + if (!SI_info->sib4_validity) return false; - } break; case NR_SIB_TypeInfo__type_sibType5: - if (SI_info->sib5_validity == SIB_NOT_VALID) { - SI_info->sib5_validity = SIB_REQUESTED; + if (!SI_info->sib5_validity) return false; - } break; case NR_SIB_TypeInfo__type_sibType6: - if (SI_info->sib6_validity == SIB_NOT_VALID) { - SI_info->sib6_validity = SIB_REQUESTED; + if (!SI_info->sib6_validity) return false; - } break; case NR_SIB_TypeInfo__type_sibType7: - if (SI_info->sib7_validity == SIB_NOT_VALID) { - SI_info->sib7_validity = SIB_REQUESTED; + if (!SI_info->sib7_validity) return false; - } break; case NR_SIB_TypeInfo__type_sibType8: - if (SI_info->sib8_validity == SIB_NOT_VALID) { - SI_info->sib8_validity = SIB_REQUESTED; + if (!SI_info->sib8_validity) return false; - } break; case NR_SIB_TypeInfo__type_sibType9: - if (SI_info->sib9_validity == SIB_NOT_VALID) { - SI_info->sib9_validity = SIB_REQUESTED; + if (!SI_info->sib9_validity) return false; - } break; case NR_SIB_TypeInfo__type_sibType10_v1610: - if (SI_info->sib10_validity == SIB_NOT_VALID) { - SI_info->sib10_validity = SIB_REQUESTED; + if (!SI_info->sib10_validity) return false; - } break; case NR_SIB_TypeInfo__type_sibType11_v1610: - if (SI_info->sib11_validity == SIB_NOT_VALID) { - SI_info->sib11_validity = SIB_REQUESTED; + if (!SI_info->sib11_validity) return false; - } break; case NR_SIB_TypeInfo__type_sibType12_v1610: - if (SI_info->sib12_validity == SIB_NOT_VALID) { - SI_info->sib12_validity = SIB_REQUESTED; + if (!SI_info->sib12_validity) return false; - } break; case NR_SIB_TypeInfo__type_sibType13_v1610: - if (SI_info->sib13_validity == SIB_NOT_VALID) { - SI_info->sib13_validity = SIB_REQUESTED; + if (!SI_info->sib13_validity) return false; - } break; case NR_SIB_TypeInfo__type_sibType14_v1610: - if (SI_info->sib14_validity == SIB_NOT_VALID) { - SI_info->sib14_validity = SIB_REQUESTED; + if (!SI_info->sib14_validity) return false; - } break; default : AssertFatal(false, "Invalid SIB type %d\n", si_type); @@ -745,46 +719,32 @@ bool check_si_validity_r17(NR_UE_RRC_SI_INFO_r17 *SI_info, int si_type) { switch (si_type) { case NR_SIB_TypeInfo_v1700__sibType_r17__type1_r17_sibType15: - if (SI_info->sib15_validity == SIB_NOT_VALID) { - SI_info->sib15_validity = SIB_REQUESTED; + if (!SI_info->sib15_validity) return false; - } break; case NR_SIB_TypeInfo_v1700__sibType_r17__type1_r17_sibType16: - if (SI_info->sib16_validity == SIB_NOT_VALID) { - SI_info->sib16_validity = SIB_REQUESTED; + if (!SI_info->sib16_validity) return false; - } break; case NR_SIB_TypeInfo_v1700__sibType_r17__type1_r17_sibType17: - if (SI_info->sib17_validity == SIB_NOT_VALID) { - SI_info->sib17_validity = SIB_REQUESTED; + if (!SI_info->sib17_validity) return false; - } break; case NR_SIB_TypeInfo_v1700__sibType_r17__type1_r17_sibType18: - if (SI_info->sib18_validity == SIB_NOT_VALID) { - SI_info->sib18_validity = SIB_REQUESTED; + if (!SI_info->sib18_validity) return false; - } break; case NR_SIB_TypeInfo_v1700__sibType_r17__type1_r17_sibType19: - if (SI_info->sib19_validity == SIB_NOT_VALID) { - SI_info->sib19_validity = SIB_REQUESTED; + if (!SI_info->sib19_validity) return false; - } break; case NR_SIB_TypeInfo_v1700__sibType_r17__type1_r17_sibType20: - if (SI_info->sib20_validity == SIB_NOT_VALID) { - SI_info->sib20_validity = SIB_REQUESTED; + if (!SI_info->sib20_validity) return false; - } break; case NR_SIB_TypeInfo_v1700__sibType_r17__type1_r17_sibType21: - if (SI_info->sib21_validity == SIB_NOT_VALID) { - SI_info->sib21_validity = SIB_REQUESTED; + if (!SI_info->sib21_validity) return false; - } break; default : AssertFatal(false, "Invalid SIB r17 type %d\n", si_type); @@ -795,10 +755,8 @@ bool check_si_validity_r17(NR_UE_RRC_SI_INFO_r17 *SI_info, int si_type) static int check_si_status(NR_UE_RRC_SI_INFO *SI_info) { // schedule reception of SIB1 if RRC doesn't have it - if (SI_info->sib1_validity == SIB_NOT_VALID) { - SI_info->sib1_validity = SIB_REQUESTED; + if (!SI_info->sib1_validity) return 1; - } else { for (int j = 0; j < MAX_SI_GROUPS; j++) { if (!SI_info->default_otherSI_map[j]) @@ -861,14 +819,17 @@ static void nr_rrc_ue_decode_NR_BCCH_BCH_Message(NR_UE_RRC_INST_t *rrc, // not used } + NR_UE_RRC_SI_INFO *SI_info = &rrc->perNB[gNB_index].SInfo; int get_sib = 0; if (IS_SA_MODE(get_softmodem_params()) + && !SI_info->sib_pending && bcch_message->message.present == NR_BCCH_BCH_MessageType_PR_mib && bcch_message->message.choice.mib->cellBarred == NR_MIB__cellBarred_notBarred && rrc->nrRrcState != RRC_STATE_DETACH_NR) { - NR_UE_RRC_SI_INFO *SI_info = &rrc->perNB[gNB_index].SInfo; // to schedule MAC to get SI if required get_sib = check_si_status(SI_info); + if (get_sib) + SI_info->sib_pending = true; } if (bcch_message->message.present == NR_BCCH_BCH_MessageType_PR_mib) { MessageDef *msg = itti_alloc_new_message(TASK_RRC_NRUE, 0, NR_MAC_RRC_CONFIG_MIB); @@ -958,18 +919,20 @@ static void nr_rrc_handle_msg3_indication(NR_UE_RRC_INST_t *rrc, rnti_t rnti) } } -static int8_t nr_rrc_ue_decode_NR_BCCH_DL_SCH_Message(NR_UE_RRC_INST_t *rrc, - const uint8_t gNB_index, - uint8_t *const Sdu, - const uint8_t Sdu_len, - const uint8_t rsrq, - const uint8_t rsrp) +static void nr_rrc_ue_decode_NR_BCCH_DL_SCH_Message(NR_UE_RRC_INST_t *rrc, + const uint8_t gNB_index, + uint8_t *const Sdu, + const uint8_t Sdu_len, + const uint8_t rsrq, + const uint8_t rsrp) { - NR_BCCH_DL_SCH_Message_t *bcch_message = NULL; - NR_UE_RRC_SI_INFO *SI_info = &rrc->perNB[gNB_index].SInfo; - VCD_SIGNAL_DUMPER_DUMP_FUNCTION_BY_NAME(VCD_SIGNAL_DUMPER_FUNCTIONS_UE_DECODE_BCCH, VCD_FUNCTION_IN); + SI_info->sib_pending = false; + 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, (void **)&bcch_message, @@ -982,7 +945,7 @@ static int8_t nr_rrc_ue_decode_NR_BCCH_DL_SCH_Message(NR_UE_RRC_INST_t *rrc, // 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 -1; + return; } if (LOG_DEBUGFLAG(DEBUG_ASN1)) { @@ -1008,7 +971,6 @@ static int8_t 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 ); - return 0; } static void nr_rrc_signal_maxrtxindication(int ue_id) @@ -2619,27 +2581,28 @@ void nr_rrc_going_to_IDLE(NR_UE_RRC_INST_t *rrc, rrcPerNB_t *nb = &rrc->perNB[i]; NR_UE_RRC_SI_INFO *SI_info = &nb->SInfo; init_SI_timers(SI_info); - SI_info->sib1_validity = SIB_NOT_VALID; - SI_info->sib2_validity = SIB_NOT_VALID; - SI_info->sib3_validity = SIB_NOT_VALID; - SI_info->sib4_validity = SIB_NOT_VALID; - SI_info->sib5_validity = SIB_NOT_VALID; - SI_info->sib6_validity = SIB_NOT_VALID; - SI_info->sib7_validity = SIB_NOT_VALID; - SI_info->sib8_validity = SIB_NOT_VALID; - SI_info->sib9_validity = SIB_NOT_VALID; - SI_info->sib10_validity = SIB_NOT_VALID; - SI_info->sib11_validity = SIB_NOT_VALID; - SI_info->sib12_validity = SIB_NOT_VALID; - SI_info->sib13_validity = SIB_NOT_VALID; - SI_info->sib14_validity = SIB_NOT_VALID; - SI_info->SInfo_r17.sib15_validity = SIB_NOT_VALID; - SI_info->SInfo_r17.sib16_validity = SIB_NOT_VALID; - SI_info->SInfo_r17.sib17_validity = SIB_NOT_VALID; - SI_info->SInfo_r17.sib18_validity = SIB_NOT_VALID; - SI_info->SInfo_r17.sib19_validity = SIB_NOT_VALID; - SI_info->SInfo_r17.sib20_validity = SIB_NOT_VALID; - SI_info->SInfo_r17.sib21_validity = SIB_NOT_VALID; + SI_info->sib_pending = false; + SI_info->sib1_validity = false; + SI_info->sib2_validity = false; + SI_info->sib3_validity = false; + SI_info->sib4_validity = false; + SI_info->sib5_validity = false; + SI_info->sib6_validity = false; + SI_info->sib7_validity = false; + SI_info->sib8_validity = false; + SI_info->sib9_validity = false; + SI_info->sib10_validity = false; + SI_info->sib11_validity = false; + SI_info->sib12_validity = false; + SI_info->sib13_validity = false; + SI_info->sib14_validity = false; + SI_info->SInfo_r17.sib15_validity = false; + SI_info->SInfo_r17.sib16_validity = false; + SI_info->SInfo_r17.sib17_validity = false; + SI_info->SInfo_r17.sib18_validity = false; + SI_info->SInfo_r17.sib19_validity = false; + SI_info->SInfo_r17.sib20_validity = false; + SI_info->SInfo_r17.sib21_validity = false; } if (rrc->nrRrcState == RRC_STATE_DETACH_NR) { diff --git a/openair2/RRC/NR_UE/rrc_defs.h b/openair2/RRC/NR_UE/rrc_defs.h index c9b14b3ae5..d300d2a7b8 100644 --- a/openair2/RRC/NR_UE/rrc_defs.h +++ b/openair2/RRC/NR_UE/rrc_defs.h @@ -104,58 +104,53 @@ typedef enum RA_trigger_e { BEAM_FAILURE_RECOVERY, } RA_trigger_t; -typedef enum { - SIB_NOT_VALID, - SIB_VALID, - SIB_REQUESTED, -} SIB_validity_t; - typedef struct UE_RRC_SI_INFO_NR_r17_s { - SIB_validity_t sib15_validity; + bool sib15_validity; NR_timer_t sib15_timer; - SIB_validity_t sib16_validity; + bool sib16_validity; NR_timer_t sib16_timer; - SIB_validity_t sib17_validity; + bool sib17_validity; NR_timer_t sib17_timer; - SIB_validity_t sib18_validity; + bool sib18_validity; NR_timer_t sib18_timer; - SIB_validity_t sib19_validity; + bool sib19_validity; NR_timer_t sib19_timer; - SIB_validity_t sib20_validity; + bool sib20_validity; NR_timer_t sib20_timer; - SIB_validity_t sib21_validity; + bool sib21_validity; NR_timer_t sib21_timer; } NR_UE_RRC_SI_INFO_r17; typedef struct UE_RRC_SI_INFO_NR_s { + bool sib_pending; uint32_t default_otherSI_map[MAX_SI_GROUPS]; - SIB_validity_t sib1_validity; + bool sib1_validity; NR_timer_t sib1_timer; - SIB_validity_t sib2_validity; + bool sib2_validity; NR_timer_t sib2_timer; - SIB_validity_t sib3_validity; + bool sib3_validity; NR_timer_t sib3_timer; - SIB_validity_t sib4_validity; + bool sib4_validity; NR_timer_t sib4_timer; - SIB_validity_t sib5_validity; + bool sib5_validity; NR_timer_t sib5_timer; - SIB_validity_t sib6_validity; + bool sib6_validity; NR_timer_t sib6_timer; - SIB_validity_t sib7_validity; + bool sib7_validity; NR_timer_t sib7_timer; - SIB_validity_t sib8_validity; + bool sib8_validity; NR_timer_t sib8_timer; - SIB_validity_t sib9_validity; + bool sib9_validity; NR_timer_t sib9_timer; - SIB_validity_t sib10_validity; + bool sib10_validity; NR_timer_t sib10_timer; - SIB_validity_t sib11_validity; + bool sib11_validity; NR_timer_t sib11_timer; - SIB_validity_t sib12_validity; + bool sib12_validity; NR_timer_t sib12_timer; - SIB_validity_t sib13_validity; + bool sib13_validity; NR_timer_t sib13_timer; - SIB_validity_t sib14_validity; + bool sib14_validity; NR_timer_t sib14_timer; NR_UE_RRC_SI_INFO_r17 SInfo_r17; } NR_UE_RRC_SI_INFO; diff --git a/openair2/RRC/NR_UE/rrc_timers_and_constants.c b/openair2/RRC/NR_UE/rrc_timers_and_constants.c index 39e73fa5a4..6629055b3a 100644 --- a/openair2/RRC/NR_UE/rrc_timers_and_constants.c +++ b/openair2/RRC/NR_UE/rrc_timers_and_constants.c @@ -43,33 +43,83 @@ void init_SI_timers(NR_UE_RRC_SI_INFO *SInfo) nr_timer_setup(&SInfo->SInfo_r17.sib19_timer, 10800000, 10); } -static SIB_validity_t check_sib_timer_validity(SIB_validity_t sib_validity, NR_timer_t *sib_timer) -{ - if (sib_validity == SIB_VALID) { - bool sib_expired = nr_timer_tick(sib_timer); - if (sib_expired) - return SIB_NOT_VALID; - } - return sib_validity; -} - void nr_rrc_SI_timers(NR_UE_RRC_SI_INFO *SInfo) { - SInfo->sib1_validity = check_sib_timer_validity(SInfo->sib1_validity, &SInfo->sib1_timer); - SInfo->sib2_validity = check_sib_timer_validity(SInfo->sib2_validity, &SInfo->sib2_timer); - SInfo->sib3_validity = check_sib_timer_validity(SInfo->sib3_validity, &SInfo->sib3_timer); - SInfo->sib4_validity = check_sib_timer_validity(SInfo->sib4_validity, &SInfo->sib4_timer); - SInfo->sib5_validity = check_sib_timer_validity(SInfo->sib5_validity, &SInfo->sib5_timer); - SInfo->sib6_validity = check_sib_timer_validity(SInfo->sib6_validity, &SInfo->sib6_timer); - SInfo->sib7_validity = check_sib_timer_validity(SInfo->sib7_validity, &SInfo->sib7_timer); - SInfo->sib8_validity = check_sib_timer_validity(SInfo->sib8_validity, &SInfo->sib8_timer); - SInfo->sib9_validity = check_sib_timer_validity(SInfo->sib9_validity, &SInfo->sib9_timer); - SInfo->sib10_validity = check_sib_timer_validity(SInfo->sib10_validity, &SInfo->sib10_timer); - SInfo->sib11_validity = check_sib_timer_validity(SInfo->sib11_validity, &SInfo->sib11_timer); - SInfo->sib12_validity = check_sib_timer_validity(SInfo->sib12_validity, &SInfo->sib12_timer); - SInfo->sib13_validity = check_sib_timer_validity(SInfo->sib13_validity, &SInfo->sib13_timer); - SInfo->sib14_validity = check_sib_timer_validity(SInfo->sib14_validity, &SInfo->sib14_timer); - SInfo->SInfo_r17.sib19_validity = check_sib_timer_validity(SInfo->SInfo_r17.sib19_validity, &SInfo->SInfo_r17.sib19_timer); + if (SInfo->sib1_validity) { + bool sib1_expired = nr_timer_tick(&SInfo->sib1_timer); + if (sib1_expired) + SInfo->sib1_validity = false; + } + if (SInfo->sib2_validity) { + bool sib2_expired = nr_timer_tick(&SInfo->sib2_timer); + if (sib2_expired) + SInfo->sib2_validity = false; + } + if (SInfo->sib3_validity) { + bool sib3_expired = nr_timer_tick(&SInfo->sib3_timer); + if (sib3_expired) + SInfo->sib3_validity = false; + } + if (SInfo->sib4_validity) { + bool sib4_expired = nr_timer_tick(&SInfo->sib4_timer); + if (sib4_expired) + SInfo->sib4_validity = false; + } + if (SInfo->sib5_validity) { + bool sib5_expired = nr_timer_tick(&SInfo->sib5_timer); + if (sib5_expired) + SInfo->sib5_validity = false; + } + if (SInfo->sib6_validity) { + bool sib6_expired = nr_timer_tick(&SInfo->sib6_timer); + if (sib6_expired) + SInfo->sib6_validity = false; + } + if (SInfo->sib7_validity) { + bool sib7_expired = nr_timer_tick(&SInfo->sib7_timer); + if (sib7_expired) + SInfo->sib7_validity = false; + } + if (SInfo->sib8_validity) { + bool sib8_expired = nr_timer_tick(&SInfo->sib8_timer); + if (sib8_expired) + SInfo->sib8_validity = false; + } + if (SInfo->sib9_validity) { + bool sib9_expired = nr_timer_tick(&SInfo->sib9_timer); + if (sib9_expired) + SInfo->sib9_validity = false; + } + if (SInfo->sib10_validity) { + bool sib10_expired = nr_timer_tick(&SInfo->sib10_timer); + if (sib10_expired) + SInfo->sib10_validity = false; + } + if (SInfo->sib11_validity) { + bool sib11_expired = nr_timer_tick(&SInfo->sib11_timer); + if (sib11_expired) + SInfo->sib11_validity = false; + } + if (SInfo->sib12_validity) { + bool sib12_expired = nr_timer_tick(&SInfo->sib12_timer); + if (sib12_expired) + SInfo->sib12_validity = false; + } + if (SInfo->sib13_validity) { + bool sib13_expired = nr_timer_tick(&SInfo->sib13_timer); + if (sib13_expired) + SInfo->sib13_validity = false; + } + if (SInfo->sib14_validity) { + bool sib14_expired = nr_timer_tick(&SInfo->sib14_timer); + if (sib14_expired) + SInfo->sib14_validity = false; + } + if (SInfo->SInfo_r17.sib19_validity) { + bool sib19_expired = nr_timer_tick(&SInfo->SInfo_r17.sib19_timer); + if (sib19_expired) + SInfo->SInfo_r17.sib19_validity = false; + } } void nr_rrc_handle_timers(NR_UE_RRC_INST_t *rrc) From a9e9dfbd060f4271b109d28cbc4f558ff89ca8b5 Mon Sep 17 00:00:00 2001 From: francescomani Date: Wed, 9 Apr 2025 17:38:22 +0200 Subject: [PATCH 52/63] assertions for wrong node type SIB input --- openair2/GNB_APP/gnb_config.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/openair2/GNB_APP/gnb_config.c b/openair2/GNB_APP/gnb_config.c index 24b1fb0c8b..7a5ea5df93 100644 --- a/openair2/GNB_APP/gnb_config.c +++ b/openair2/GNB_APP/gnb_config.c @@ -1432,6 +1432,9 @@ static nr_redcap_config_t *get_redcap_config(int gnb_idx) static seq_arr_t *fill_du_sibs(paramdef_t *GNBparamarray) { + if (NODE_IS_DU(get_node_type())) + AssertFatal(GNBparamarray[GNB_CU_SIBS_IDX].numelt == 0, "This is DU, do not input CU SIBs\n"); + if (!config_isparamset(GNBparamarray, GNB_DU_SIBS_IDX)) return NULL; @@ -1880,6 +1883,9 @@ static int sort_neighbour_cell_config_by_cell_id(const void *a, const void *b) static seq_arr_t *fill_cu_sibs(paramdef_t *GNBparamarray) { + if (NODE_IS_CU(get_node_type())) + AssertFatal(GNBparamarray[GNB_DU_SIBS_IDX].numelt == 0, "This is CU, do not input DU SIBs\n"); + if (!config_isparamset(GNBparamarray, GNB_CU_SIBS_IDX)) return NULL; From 7edf52ca5bfa3e864afa60fbce4bd390b839b4a8 Mon Sep 17 00:00:00 2001 From: Robert Schmidt Date: Thu, 10 Apr 2025 16:35:36 +0200 Subject: [PATCH 53/63] Use cp_f1ap_() functions to properly copy F1 messages --- openair2/RRC/NR/mac_rrc_dl_f1ap.c | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/openair2/RRC/NR/mac_rrc_dl_f1ap.c b/openair2/RRC/NR/mac_rrc_dl_f1ap.c index 8a84a7f85d..ff89f61160 100644 --- a/openair2/RRC/NR/mac_rrc_dl_f1ap.c +++ b/openair2/RRC/NR/mac_rrc_dl_f1ap.c @@ -53,10 +53,7 @@ static void f1_setup_response_f1ap(sctp_assoc_t assoc_id, const f1ap_setup_resp_ MessageDef *msg = itti_alloc_new_message(TASK_RRC_GNB, 0, F1AP_SETUP_RESP); msg->ittiMsgHeader.originInstance = assoc_id; f1ap_setup_resp_t *f1ap_msg = &F1AP_SETUP_RESP(msg); - *f1ap_msg = *resp; - if (resp->gNB_CU_name != NULL) - f1ap_msg->gNB_CU_name = strdup(resp->gNB_CU_name); - free_f1ap_setup_response(resp); + *f1ap_msg = cp_f1ap_setup_response(resp); itti_send_msg_to_task(TASK_CU_F1, 0, msg); } @@ -65,7 +62,7 @@ static void f1_setup_failure_f1ap(sctp_assoc_t assoc_id, const f1ap_setup_failur MessageDef *msg = itti_alloc_new_message(TASK_RRC_GNB, 0, F1AP_SETUP_FAILURE); msg->ittiMsgHeader.originInstance = assoc_id; f1ap_setup_failure_t *f1ap_msg = &F1AP_SETUP_FAILURE(msg); - *f1ap_msg = *fail; + *f1ap_msg = cp_f1ap_setup_failure(fail); itti_send_msg_to_task(TASK_CU_F1, 0, msg); } @@ -74,7 +71,7 @@ static void gnb_du_configuration_update_ack_f1ap(sctp_assoc_t assoc_id, const f1 MessageDef *msg = itti_alloc_new_message(TASK_RRC_GNB, 0, F1AP_GNB_DU_CONFIGURATION_UPDATE_ACKNOWLEDGE); msg->ittiMsgHeader.originInstance = assoc_id; f1ap_gnb_du_configuration_update_acknowledge_t *f1ap_msg = &F1AP_GNB_DU_CONFIGURATION_UPDATE_ACKNOWLEDGE(msg); - *f1ap_msg = *ack; + *f1ap_msg = cp_f1ap_du_configuration_update_acknowledge(ack); itti_send_msg_to_task(TASK_CU_F1, 0, msg); } From 463942d90edc8f6e52fe1fed8f029adc04b73359 Mon Sep 17 00:00:00 2001 From: Robert Schmidt Date: Thu, 10 Apr 2025 16:46:23 +0200 Subject: [PATCH 54/63] F1AP: correctly encode SI containers - encode all SI containers the encoding function gets (it's not the encoding function's job to enforce semantics) - Fixes: 30628b820c3 ("Add encoding/decoding lib for F1AP Setup Response (Interface Management)") --- openair2/F1AP/lib/f1ap_interface_management.c | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/openair2/F1AP/lib/f1ap_interface_management.c b/openair2/F1AP/lib/f1ap_interface_management.c index 4d8c4fcccf..32a1f28b58 100644 --- a/openair2/F1AP/lib/f1ap_interface_management.c +++ b/openair2/F1AP/lib/f1ap_interface_management.c @@ -653,11 +653,9 @@ static void encode_cells_to_activate(const served_cells_to_activate_t *cell, F1A &cells_to_be_activated_itemExtIEs->extensionValue.choice.GNB_CUSystemInformation; const f1ap_sib_msg_t *SI_msg = &cell->SI_msg[n]; if (SI_msg->SI_container != NULL) { - if (SI_msg->SI_type > 6 && SI_msg->SI_type != 9) { - asn1cSequenceAdd(gNB_CUSystemInformation->sibtypetobeupdatedlist.list, F1AP_SibtypetobeupdatedListItem_t, sib_item); - sib_item->sIBtype = SI_msg->SI_type; - OCTET_STRING_fromBuf(&sib_item->sIBmessage, (const char *)SI_msg->SI_container, SI_msg->SI_container_length); - } + asn1cSequenceAdd(gNB_CUSystemInformation->sibtypetobeupdatedlist.list, F1AP_SibtypetobeupdatedListItem_t, sib_item); + sib_item->sIBtype = SI_msg->SI_type; + OCTET_STRING_fromBuf(&sib_item->sIBmessage, (const char *)SI_msg->SI_container, SI_msg->SI_container_length); } } } @@ -1221,7 +1219,7 @@ f1ap_setup_resp_t cp_f1ap_setup_response(const f1ap_setup_resp_t *msg) const f1ap_sib_msg_t *b_SI_msg = &msg_cell->SI_msg[j]; cp_SI_msg->SI_container_length = b_SI_msg->SI_container_length; cp_SI_msg->SI_container = malloc_or_fail(cp_SI_msg->SI_container_length); - *cp_SI_msg->SI_container = *b_SI_msg->SI_container; + memcpy(cp_SI_msg->SI_container, b_SI_msg->SI_container, b_SI_msg->SI_container_length); cp_SI_msg->SI_type = b_SI_msg->SI_type; } } From e73f1f6220e74814a0ce40acea2cf206a1de72c0 Mon Sep 17 00:00:00 2001 From: Robert Schmidt Date: Thu, 10 Apr 2025 16:50:43 +0200 Subject: [PATCH 55/63] F1AP: Correctly encode DU configuration update acknowledge SI container Fixes: b91e1b0a12e ("Add enc/dec lib for F1AP gNB-DU Configuration Update Acknowledge (Interface Management)") --- openair2/F1AP/lib/f1ap_interface_management.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/openair2/F1AP/lib/f1ap_interface_management.c b/openair2/F1AP/lib/f1ap_interface_management.c index 32a1f28b58..5767a06d95 100644 --- a/openair2/F1AP/lib/f1ap_interface_management.c +++ b/openair2/F1AP/lib/f1ap_interface_management.c @@ -2107,8 +2107,12 @@ f1ap_gnb_du_configuration_update_acknowledge_t cp_f1ap_du_configuration_update_a for (int i = 0; i < cp.num_cells_to_activate; 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].SI_container = calloc_or_fail(1, sizeof(*cp.cells_to_activate[i].SI_msg[s].SI_container)); - *cp.cells_to_activate[i].SI_msg[s].SI_container = *msg->cells_to_activate[i].SI_msg[s].SI_container; + f1ap_sib_msg_t *cp_sib = &cp.cells_to_activate[i].SI_msg[s]; + const f1ap_sib_msg_t *msg_sib = &msg->cells_to_activate[i].SI_msg[s]; + cp_sib->SI_type = msg_sib->SI_type; + cp_sib->SI_container_length = msg_sib->SI_container_length; + cp_sib->SI_container = calloc_or_fail(cp_sib->SI_container_length, sizeof(*cp_sib->SI_container)); + memcpy(cp_sib->SI_container, msg_sib->SI_container, cp_sib->SI_container_length); } } return cp; From f3724ec4513e5c845ff9ee7335771e82fd741cda Mon Sep 17 00:00:00 2001 From: Robert Schmidt Date: Fri, 11 Apr 2025 16:05:24 +0200 Subject: [PATCH 56/63] Add SIBs in configuration doc and feature set --- doc/FEATURE_SET.md | 5 +++-- doc/MAC/mac-usage.md | 6 +++++- doc/RRC/rrc-usage.md | 5 +++++ 3 files changed, 13 insertions(+), 3 deletions(-) diff --git a/doc/FEATURE_SET.md b/doc/FEATURE_SET.md index 5dd5d8b8b3..3c7490d937 100644 --- a/doc/FEATURE_SET.md +++ b/doc/FEATURE_SET.md @@ -105,7 +105,7 @@ These modes of operation are supported: - MAC -> PHY configuration using NR FAPI P5 interface - MAC <-> PHY data interface using FAPI P7 interface for BCH PDU, DCI PDU, PDSCH PDU -- Scheduler procedures for SIB1 +- Generation of and scheduler procedures for MIB/SIB1 - Scheduler procedures for RA - 4-Step RA - Contention Free RA procedure @@ -140,6 +140,7 @@ These modes of operation are supported: - MAC scheduling of SR reception - Intra-frequency handover - Initial support for RedCap +- Scheduling of SIBs (2, 19) ## gNB RLC @@ -174,7 +175,7 @@ These modes of operation are supported: - NR RRC (38.331) Rel 17 messages using new [asn1c](https://github.com/mouse07410/asn1c) - LTE RRC (36.331) also updated to Rel 15 -- Generation of MIB/SIB1 (received from DU) +- Generation of system information (SIB2) - RRC can configure PDCP, RLC, MAC - Interface with GTP-U (tunnel creation/handling for S1-U (NSA), N3 (SA), F1 interfaces) - Integration of RRC messages and procedures supporting UE 5G SA connection diff --git a/doc/MAC/mac-usage.md b/doc/MAC/mac-usage.md index 6804ca4543..e4d6ebc098 100644 --- a/doc/MAC/mac-usage.md +++ b/doc/MAC/mac-usage.md @@ -232,7 +232,9 @@ In the `MACRLCs` section of the gNB/DU configuration file: In the `gNBs` section of the gNB/DU configuration file: some of the parameters affect RRC configuration (CellGroupConfig) of a UE, and are therefore listed -here, *also they pertain to the DU*, i.e., the scheduler. +here, *also they pertain to the DU*, i.e., the scheduler. Note also that some +SIBs are configured at the DU and some at the CU; please consult the [RRC +configuration](../RRC/rrc-usage.md) as well for SIB configuration. * `pdsch_AntennaPorts_XP` (default 1): number of XP logical antenna ports in PDSCH (see also [`RUNMODEM.md`](../RUNMODEM.md)) @@ -267,6 +269,8 @@ here, *also they pertain to the DU*, i.e., the scheduler. options are 2, 4, 6, 8, 10, 12, 32; **32 is a Rel-17 features**) * `num_ulharq` (default 16): as `num_dlharq` for UL (other valid option is 32; **32 is i Rel-17 feature**) +- `du_sibs` (default `[]`): list of SIBs to transmit in the cell. Currently, + SIB19 (for NTN) is supported. ## ServingCellConfigCommon parameters diff --git a/doc/RRC/rrc-usage.md b/doc/RRC/rrc-usage.md index 668c106a0d..09c8952889 100644 --- a/doc/RRC/rrc-usage.md +++ b/doc/RRC/rrc-usage.md @@ -67,6 +67,9 @@ RRC-specific configuration ### cell-specific options +Note that some SIBS are configured at the CU and some at the DU; please consult +the [MAC configuration](../MAC/mac-usage.md) as well for SIB configuration. + - `gNB_ID` and `gNB_name`: ID and name of the gNB - `tracking_area_code`: the current tracking area code in the range `[0x0001, 0xfffd]` @@ -84,6 +87,8 @@ RRC-specific configuration - `enable_sdap` (default: false): enable the use of the SDAP layer. If deactivated, a transparent SDAP header is prepended to packets, but no further processing is being done. +- `cu_sibs` (default: `[]`) list of SIBs to give to the DU for transmission. + Currently, SIB2 is supported. ### UE-specific configuration From 9185c29e33c541876b97ee5eb043255cbc2ac46a Mon Sep 17 00:00:00 2001 From: francescomani Date: Sun, 13 Apr 2025 09:43:28 +0200 Subject: [PATCH 57/63] change ss0 configuration to schedule multiple sibs in ntn config files with small bw --- ci-scripts/conf_files/gnb.sa.band254.u0.25prb.rfsim.ntn.conf | 2 +- .../CONF/gnb.sa.band254.ntn.mu1.24prb.rfsim.conf | 4 ++-- .../CONF/gnb.sa.band255.ntn.mu0.25prb.rfsim.conf | 4 ++-- .../CONF/gnb.sa.band256.ntn.mu0.25prb.rfsim.conf | 4 ++-- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/ci-scripts/conf_files/gnb.sa.band254.u0.25prb.rfsim.ntn.conf b/ci-scripts/conf_files/gnb.sa.band254.u0.25prb.rfsim.ntn.conf index 002d790e61..198ac12833 100644 --- a/ci-scripts/conf_files/gnb.sa.band254.u0.25prb.rfsim.ntn.conf +++ b/ci-scripts/conf_files/gnb.sa.band254.u0.25prb.rfsim.ntn.conf @@ -50,7 +50,7 @@ gNBs = initialDLBWPsubcarrierSpacing = 0; #pdcch-ConfigCommon initialDLBWPcontrolResourceSetZero = 2; - initialDLBWPsearchSpaceZero = 0; + initialDLBWPsearchSpaceZero = 2; #uplinkConfigCommon #frequencyInfoUL diff --git a/targets/PROJECTS/GENERIC-NR-5GC/CONF/gnb.sa.band254.ntn.mu1.24prb.rfsim.conf b/targets/PROJECTS/GENERIC-NR-5GC/CONF/gnb.sa.band254.ntn.mu1.24prb.rfsim.conf index df06f0483b..dd3307d32c 100644 --- a/targets/PROJECTS/GENERIC-NR-5GC/CONF/gnb.sa.band254.ntn.mu1.24prb.rfsim.conf +++ b/targets/PROJECTS/GENERIC-NR-5GC/CONF/gnb.sa.band254.ntn.mu1.24prb.rfsim.conf @@ -49,8 +49,8 @@ gNBs = # 0=kHz15, 1=kHz30, 2=kHz60, 3=kHz120 initialDLBWPsubcarrierSpacing = 1; #pdcch-ConfigCommon - initialDLBWPcontrolResourceSetZero = 0; - initialDLBWPsearchSpaceZero = 0; + initialDLBWPcontrolResourceSetZero = 2; + initialDLBWPsearchSpaceZero = 2; #uplinkConfigCommon #frequencyInfoUL diff --git a/targets/PROJECTS/GENERIC-NR-5GC/CONF/gnb.sa.band255.ntn.mu0.25prb.rfsim.conf b/targets/PROJECTS/GENERIC-NR-5GC/CONF/gnb.sa.band255.ntn.mu0.25prb.rfsim.conf index 34c31460ee..7c2c3805ee 100644 --- a/targets/PROJECTS/GENERIC-NR-5GC/CONF/gnb.sa.band255.ntn.mu0.25prb.rfsim.conf +++ b/targets/PROJECTS/GENERIC-NR-5GC/CONF/gnb.sa.band255.ntn.mu0.25prb.rfsim.conf @@ -49,8 +49,8 @@ gNBs = # 0=kHz15, 1=kHz30, 2=kHz60, 3=kHz120 initialDLBWPsubcarrierSpacing = 0; #pdcch-ConfigCommon - initialDLBWPcontrolResourceSetZero = 0; - initialDLBWPsearchSpaceZero = 0; + initialDLBWPcontrolResourceSetZero = 2; + initialDLBWPsearchSpaceZero = 2; #uplinkConfigCommon #frequencyInfoUL diff --git a/targets/PROJECTS/GENERIC-NR-5GC/CONF/gnb.sa.band256.ntn.mu0.25prb.rfsim.conf b/targets/PROJECTS/GENERIC-NR-5GC/CONF/gnb.sa.band256.ntn.mu0.25prb.rfsim.conf index 7b87bb3e98..b720d6d720 100644 --- a/targets/PROJECTS/GENERIC-NR-5GC/CONF/gnb.sa.band256.ntn.mu0.25prb.rfsim.conf +++ b/targets/PROJECTS/GENERIC-NR-5GC/CONF/gnb.sa.band256.ntn.mu0.25prb.rfsim.conf @@ -49,8 +49,8 @@ gNBs = # 0=kHz15, 1=kHz30, 2=kHz60, 3=kHz120 initialDLBWPsubcarrierSpacing = 0; #pdcch-ConfigCommon - initialDLBWPcontrolResourceSetZero = 0; - initialDLBWPsearchSpaceZero = 0; + initialDLBWPcontrolResourceSetZero = 2; + initialDLBWPsearchSpaceZero = 2; #uplinkConfigCommon #frequencyInfoUL From 890a6c702e82bb15ba40af5e86d53f70dcff5643 Mon Sep 17 00:00:00 2001 From: Robert Schmidt Date: Mon, 14 Apr 2025 10:42:10 +0200 Subject: [PATCH 58/63] Remove unnecessary fields in NTN-LEO gNB config --- .../gnb.sa.band254.u0.25prb.rfsim.ntn-leo.conf | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/ci-scripts/conf_files/gnb.sa.band254.u0.25prb.rfsim.ntn-leo.conf b/ci-scripts/conf_files/gnb.sa.band254.u0.25prb.rfsim.ntn-leo.conf index 7a47d90975..729bb4d6f0 100644 --- a/ci-scripts/conf_files/gnb.sa.band254.u0.25prb.rfsim.ntn-leo.conf +++ b/ci-scripts/conf_files/gnb.sa.band254.u0.25prb.rfsim.ntn-leo.conf @@ -235,15 +235,6 @@ RUs = ( } ); -THREAD_STRUCT = ( - { - #three config for level of parallelism "PARALLEL_SINGLE_THREAD", "PARALLEL_RU_L1_SPLIT", or "PARALLEL_RU_L1_TRX_SPLIT" - parallel_config = "PARALLEL_RU_L1_TRX_SPLIT"; - #two option for worker "WORKER_DISABLE" or "WORKER_ENABLE" - worker_config = "WORKER_DISABLE"; - } -); - rfsimulator : { serveraddr = "server"; @@ -296,6 +287,4 @@ log_config : rlc_log_level ="info"; pdcp_log_level ="info"; rrc_log_level ="info"; - ngap_log_level ="debug"; - f1ap_log_level ="debug"; }; From 75b237fb90459f77361845ed4b4df81fe37ab434 Mon Sep 17 00:00:00 2001 From: Robert Schmidt Date: Mon, 14 Apr 2025 10:45:40 +0200 Subject: [PATCH 59/63] Add 5G RFsim NTN LEO CI Test --- .../xml_files/container_5g_rfsim_ntn_leo.xml | 118 ++++++++++++++ .../docker-compose.yaml | 151 ++++++++++++++++++ 2 files changed, 269 insertions(+) create mode 100644 ci-scripts/xml_files/container_5g_rfsim_ntn_leo.xml create mode 100644 ci-scripts/yaml_files/5g_rfsimulator_ntn_leo/docker-compose.yaml diff --git a/ci-scripts/xml_files/container_5g_rfsim_ntn_leo.xml b/ci-scripts/xml_files/container_5g_rfsim_ntn_leo.xml new file mode 100644 index 0000000000..2846fc016e --- /dev/null +++ b/ci-scripts/xml_files/container_5g_rfsim_ntn_leo.xml @@ -0,0 +1,118 @@ + + + rfsim-5gnr-ntn-Leo + Monolithic SA NTN LEO gNB + wrench + + 111111 + 100001 + 800813 + 000001 + 000002 + 000003 + 020001 + 020002 + 100001 + 222222 + + + + + Pull_Local_Registry + Pull Images from Local Registry + 0 + oai-gnb-asan oai-nr-ue-asan + + + + Create_Workspace + Create new Workspace + 0 + 0 + + + Deploy_Object + Deploy OAI 5G CoreNetwork + ci-scripts/yaml_files/5g_rfsimulator_ntn_leo + mysql oai-amf oai-smf oai-upf oai-ext-dn + 0 + 0 + + + + Deploy_Object + Deploy OAI 5G gNB+nrUE RF sim SA + ci-scripts/yaml_files/5g_rfsimulator_ntn_leo + oai-gnb oai-nr-ue + 0 + 0 + + + + Attach_UE + Attach OAI UE (Wait for IP) + rfsim5g_ue + cacofonix + + + + Ping + Ping ext-dn from NR-UE + rfsim5g_ue + cacofonix + rfsim5g_ext_dn + cacofonix + -c 20 -i 0.25 + 5 + + + + Ping + Ping NR-UE from ext-dn + rfsim5g_ext_dn + cacofonix + rfsim5g_ue + cacofonix + -c 20 -i 0.25 + 5 + + + + Undeploy_Object + true + Undeploy all OAI 5G stack + ci-scripts/yaml_files/5g_rfsimulator_ntn_leo + 0 + 0 + + + + Clean_Test_Server_Images + true + Clean Test Images on Test Server + 0 + oai-gnb-asan oai-nr-ue-asan + + + diff --git a/ci-scripts/yaml_files/5g_rfsimulator_ntn_leo/docker-compose.yaml b/ci-scripts/yaml_files/5g_rfsimulator_ntn_leo/docker-compose.yaml new file mode 100644 index 0000000000..0cb382710e --- /dev/null +++ b/ci-scripts/yaml_files/5g_rfsimulator_ntn_leo/docker-compose.yaml @@ -0,0 +1,151 @@ +services: + mysql: + container_name: "rfsim5g-mysql" + image: mysql:8.0 + 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 + retries: 30 + networks: + public_net: + ipv4_address: 192.168.71.131 + oai-amf: + container_name: "rfsim5g-oai-amf" + image: oaisoftwarealliance/oai-amf:v2.0.0 + 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.0.0 + 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.0.0 + 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 + traffic_net: + ipv4_address: 192.168.72.134 + oai-ext-dn: + privileged: true + container_name: rfsim5g-oai-ext-dn + image: oaisoftwarealliance/trf-gen-cn5g:focal + 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 + oai-gnb: + image: ${REGISTRY:-oaisoftwarealliance}/${GNB_IMG:-oai-gnb}:${TAG:-develop} + container_name: rfsim5g-oai-gnb + cap_drop: + - ALL + environment: + USE_ADDITIONAL_OPTIONS: --rfsim --log_config.global_log_options level,nocolor,time + ASAN_OPTIONS: detect_leaks=0 + depends_on: + - oai-ext-dn + networks: + public_net: + ipv4_address: 192.168.71.140 + volumes: + - ../../conf_files/gnb.sa.band254.u0.25prb.rfsim.ntn-leo.conf:/opt/oai-gnb/etc/gnb.conf + healthcheck: + test: /bin/bash -c "pgrep nr-softmodem" + interval: 10s + timeout: 5s + retries: 5 + + 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 # for interface bringup + - NET_RAW # for ping + environment: + USE_ADDITIONAL_OPTIONS: + --band 254 -C 2488400000 --CO -873500000 -r 25 --numerology 0 --ssb 60 + --rfsim --rfsimulator.prop_delay 20 --rfsimulator.options chanmod + --rfsimulator.serveraddr 192.168.71.140 + --time-sync-I 0.1 --ntn-initial-time-drift -46 + --autonomous-ta --initial-fo 57340 --cont-fo-comp 2 + --log_config.global_log_options level,nocolor,time + depends_on: + - oai-gnb + networks: + public_net: + ipv4_address: 192.168.71.150 + devices: + - /dev/net/tun:/dev/net/tun + volumes: + - ../../conf_files/nrue.uicc.ntn-leo.conf:/opt/oai-nr-ue/etc/nr-ue.conf + healthcheck: + test: /bin/bash -c "pgrep nr-uesoftmodem" + interval: 10s + timeout: 5s + retries: 5 + +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" From 7c4eabdb6eeb27cc8ce6a480e3bbb89b8b09bee2 Mon Sep 17 00:00:00 2001 From: francescomani Date: Mon, 14 Apr 2025 11:28:00 +0200 Subject: [PATCH 60/63] fix for DLSCH DMRS reference point (regression at the OAI UE introduced when testing with OAI gNB for a missing implementation in OAI gNB) --- openair1/PHY/NR_TRANSPORT/nr_dlsch.c | 2 +- openair2/LAYER2/NR_MAC_UE/nr_ue_procedures.c | 10 ++-------- openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_bch.c | 2 +- 3 files changed, 4 insertions(+), 10 deletions(-) diff --git a/openair1/PHY/NR_TRANSPORT/nr_dlsch.c b/openair1/PHY/NR_TRANSPORT/nr_dlsch.c index 30ce9cdc5e..aed99ff358 100644 --- a/openair1/PHY/NR_TRANSPORT/nr_dlsch.c +++ b/openair1/PHY/NR_TRANSPORT/nr_dlsch.c @@ -730,7 +730,7 @@ static int do_one_dlsch(unsigned char *input_ptr, PHY_VARS_gNB *gNB, NR_gNB_DLSC #endif } uint32_t dmrs_idx = rel15->rbStart; - if (rel15->rnti != SI_RNTI) + if (rel15->refPoint == 0) dmrs_idx += rel15->BWPStart; dmrs_idx *= dmrs_Type == NFAPI_NR_DMRS_TYPE1 ? 6 : 4; c16_t txdataF_precoding[rel15->nrOfLayers][symbol_sz] __attribute__((aligned(64))); diff --git a/openair2/LAYER2/NR_MAC_UE/nr_ue_procedures.c b/openair2/LAYER2/NR_MAC_UE/nr_ue_procedures.c index e282c380b0..572202d044 100644 --- a/openair2/LAYER2/NR_MAC_UE/nr_ue_procedures.c +++ b/openair2/LAYER2/NR_MAC_UE/nr_ue_procedures.c @@ -749,14 +749,8 @@ static int nr_ue_process_dci_dl_10(NR_UE_MAC_INST_t *mac, } dlsch_pdu->rb_offset = dlsch_pdu->start_rb + dlsch_pdu->BWPStart; - bool otherSI = false; - for (int i = 0; i < MAX_SI_GROUPS; i++) { - if (mac->get_otherSI[i]) { - otherSI = true; - break; - } - } - if (mac->get_sib1 || otherSI) + + if (mac->get_sib1) dlsch_pdu->rb_offset -= dlsch_pdu->BWPStart; /* TIME_DOM_RESOURCE_ASSIGNMENT */ diff --git a/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_bch.c b/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_bch.c index 39cb665c21..71ebc7d8a9 100644 --- a/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_bch.c +++ b/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_bch.c @@ -460,7 +460,7 @@ static void nr_fill_nfapi_dl_SIB_pdu(gNB_MAC_INST *gNB_mac, pdsch_pdu_rel15->dataScramblingId = *scc->physCellId; pdsch_pdu_rel15->nrOfLayers = 1; pdsch_pdu_rel15->transmissionScheme = 0; - pdsch_pdu_rel15->refPoint = 1; + pdsch_pdu_rel15->refPoint = is_sib1; pdsch_pdu_rel15->dmrsConfigType = 0; pdsch_pdu_rel15->dlDmrsScramblingId = *scc->physCellId; pdsch_pdu_rel15->SCID = 0; From b3a1e3d2e89026d9f6546945574c4c2ecd5bffde Mon Sep 17 00:00:00 2001 From: Reem Bahsoun Date: Tue, 1 Apr 2025 09:51:37 +0200 Subject: [PATCH 61/63] Doc update - Update the Aerial tutorial to include Grace Hopper server and add reference to Gigabyte server setup instructions. - Update the Aerial setup in the TESTBench.md --- doc/Aerial_FAPI_Split_Tutorial.md | 101 +++++++++--------- doc/TESTBenches.md | 8 +- .../5g-ota-bench.pdf | Bin 0 -> 217733 bytes .../5g-ota-bench.png | Bin 94044 -> 82211 bytes .../5g-ota-bench.tex | 52 +++++---- 5 files changed, 82 insertions(+), 79 deletions(-) create mode 100644 doc/testbenches_doc_resources/5g-ota-bench.pdf diff --git a/doc/Aerial_FAPI_Split_Tutorial.md b/doc/Aerial_FAPI_Split_Tutorial.md index 03e917ac06..8d4ea54c3d 100644 --- a/doc/Aerial_FAPI_Split_Tutorial.md +++ b/doc/Aerial_FAPI_Split_Tutorial.md @@ -24,8 +24,11 @@ The hardware on which we have tried this tutorial: |----------------------------------------------------------------------------|----------------------------------|--------------------------------------------------| | Gigabyte Edge E251-U70 (Intel Xeon Gold 6240R, 2.4GHz, 24C48T, 96GB DDR4) |Ubuntu 22.04.3 LTS (5.15.0-72-lowlatency)| NVIDIA ConnectX®-6 Dx 22.38.1002 | | Dell PowerEdge R750 (Dual Intel Xeon Gold 6336Y CPU @ 2.4G, 24C/48T (185W), 512GB RDIMM, 3200MT/s) |Ubuntu 22.04.3 LTS (5.15.0-72-lowlatency)| NVIDIA Converged Accelerator A100X (24.39.2048) | +| Supermicro Grace Hopper MGX ARS-111GL-NHR (Neoverse-V2, 3.4GHz, 72C/72T, 576GB LPDDR5) | Ubuntu 22.04.5 LTS (6.5.0-1019-nvidia-64k) |NVIDIA BlueField3 (32.41.1000)| -**NOTE**: These are not minimum hardware requirements. This is the configuration of our servers. The NIC card should support hardware PTP time stamping. +**NOTE**: +- These are not minimum hardware requirements. This is the configuration of our servers. The NIC card should support hardware PTP time stamping. +- Starting from tag [2025.w13](https://gitlab.eurecom.fr/oai/openairinterface5g/-/tree/2025.w13?ref_type=tags) of OAI, we are only testing with the Grace Hopper server. PTP enabled switches and grandmaster clock we have tested with: @@ -42,6 +45,7 @@ These are the radio units we've used for testing: |-------------|------------------| | Foxconn RPQN-7801E RU | 2.6.9r254 | | Foxconn RPQN-7801E RU | 3.1.15_0p4 | +| Foxconn RPQN-7801E RU | v3.2.0q.551.12.E.rc2.srs-AIO | The UEs that have been tested and confirmed working with Aerial are the following: @@ -54,35 +58,30 @@ The UEs that have been tested and confirmed working with Aerial are the followin | Apaltec | Tributo 5G-Dongle | | OnePlus | Nord (AC2003) | | Apple iPhone | 14 Pro (MQ0G3RX/A) (iOS 17.3) | +| Samsung | S23 Ultra | ## Configure your server -The first step is to obtain the NVIDIA Aerial SDK, you'll need to request access [here](https://catalog.ngc.nvidia.com/orgs/nvidia/containers/aerial-sdk). - -After obtaining access to the SDK, you can refer to [this instructions page](https://docs.nvidia.com/aerial/aerial-ran-colab-ota/current/index.html) to setup the L1 components using NVIDIAs' SDK manager, or to [this instructions page](https://docs.nvidia.com/aerial/cuda-accelerated-ran/index.html) in order to setup and install the components manually. - -The currently used Aerial Version is 24-1, which is also the one currently used by the SDK Manager. +To set up the L1 and install the components manually refer to this [instructions page](https://docs.nvidia.com/aerial/cuda-accelerated-ran/index.html). +**Note**: +- To configure the Gigabyte server please refer to these [instructions](https://gitlab.eurecom.fr/oai/openairinterface5g/-/blob/2025.w13/doc/Aerial_FAPI_Split_Tutorial.md) +- The last release to support the Gigabyte server is **Aerial CUDA-Accelerated RAN 24-1**. ### CPU allocation -Currently, the CPU isolation is setup the following: +| Server brand | Model | Nº of CPU Cores | Isolated CPUs | +|------------------|---------------|:---------------:|:--------------:| +| Grace Hopper MGX | ARS-111GL-NHR | 72 | 4-64 | -| Server brand | Model | Nº of CPU Cores | Isolated CPUs | -|--------------|-----------|:---------------:|:--------------:| -| Gigabyte | Edge E251-U70 | 24 | 2-10 | - - -**Gigabyte Edge E251-U70** +**Grace Hopper MGX ARS-111GL-NHR** | Applicative Threads | Allocated CPUs | |------------------------|----------------| -| Aerial L1 | 2,3,4,5,6,7,8 | -| PTP & PHC2SYS Services | 9 | +| PTP & PHC2SYS Services | 41 | | OAI `nr-softmodem` | 13-20 | - ## PTP configuration 1. You need to install the `linuxptp` debian package. It will install both ptp4l and phc2sys. @@ -99,14 +98,10 @@ Once installed you can use this configuration file for ptp4l (`/etc/ptp4l.conf`) domainNumber 24 slaveOnly 1 time_stamping hardware -tx_timestamp_timeout 1 -logging_level 6 -summary_interval 0 -#priority1 127 +tx_timestamp_timeout 30 [your_PTP_ENABLED_NIC] network_transport L2 -hybrid_e2e 0 ``` @@ -120,7 +115,7 @@ Documentation=man:ptp4l Restart=always RestartSec=5s Type=simple -ExecStart=taskset -c 9 /usr/sbin/ptp4l -f /etc/ptp.conf +ExecStart=taskset -c 41 /usr/sbin/ptp4l -f /etc/ptp.conf [Install] WantedBy=multi-user.target @@ -149,17 +144,16 @@ WantedBy=multi-user.target # Build OAI gNB -If installing with the Aerial SDK, you should already have the repository cloned in `~/openairinterface5g`, if -Clone OAI code base in a suitable repository, here we are cloning in `~/openairinterface5g` directory, - +If it's not already cloned, the first step is to clone OAI repository ```bash git clone https://gitlab.eurecom.fr/oai/openairinterface5g.git ~/openairinterface5g cd ~/openairinterface5g/ ``` ## Get nvIPC sources from the L1 container -The library used for communication between L1 and L2 components is called nvIPC, and was developed by NVIDIA, as such, it is not open-source and can't be freely distributed. -In order to be able to use this library to achieve communication, we need to obtain the nvIPC source files from the L1 container (cuBB) and place it in out gNB project directory `~/openairinterface5g`. + +The library used for communication between L1 and L2 components is called nvIPC, and is developed by NVIDIA. It is not open-source and can't be freely distributed. +In order to achieve this communication, we need to obtain the nvIPC source files from the L1 container (cuBB) and place it in the gNB project directory `~/openairinterface5g`. This allows us to build and install this library when building the L2 docker container. Check whether your L1 container is running: @@ -177,7 +171,7 @@ cuBB aerial@c_aerial_aerial:/opt/nvidia/cuBB# ``` -After logging into the container, we need to pack the nvIPC sources and copy them to the host ( the command creates a tar.gz file with the following name format: nvipc_src.YYYY.MM.DD.tar.gz) +After logging into the container, you need to pack the nvIPC sources and copy them to the host ( the command creates a `tar.gz` file with the following name format: `nvipc_src.YYYY.MM.DD.tar.gz`) ```bash ~$ docker exec -it cuBB bash aerial@c_aerial_aerial:/opt/nvidia/cuBB# cd cuPHY-CP/gt_common_libs @@ -216,7 +210,7 @@ With the nvIPC sources in the project directory, the docker image can be built. ## Building OAI gNB docker image -In order to build the final image, there is an intermediary image to be built (ran-base) +In order to build the target image (`oai-gnb-aerial`), first you should build a common shared image (`ran-base`) ```bash ~$ cd ~/openairinterface5g/ ~/openairinterface5g$ docker build . -f docker/Dockerfile.base.ubuntu22 --tag ran-base:latest @@ -224,21 +218,23 @@ In order to build the final image, there is an intermediary image to be built (r ``` -## Running the setup -In order to use Docker compose to automatically start and stop the setup, we need to first create a ready-made image of the L1, after compiling the L1 software and making the necessary adjustments to its configuration files. -The process of preparing the L1 is covered on NVIDIAs' documentation, and falls outside the scope of this document. +# Running the setup -### Prepare the L1 image -After preparing the L1 software, the container needs to be committed in order for an image in which the L1 is ready to be executed, and that can be referenced by a docker-compose.yaml file later: -*Note:* In preparing the L1 image, the default L1 configuration file is cuphycontroller_P5G_FXN.yaml, located in `/opt/nvidia/cuBB/cuPHY-CP/cuphycontroller/config/`. -This is the file where the RU MAC address needs to be applied before commiting the image +In order to use Docker Compose to automatically start and stop the setup, we first need to create a pre-built image of L1 after compiling the L1 software and making the necessary adjustments to its configuration files. +The process of preparing L1 is covered in NVIDIA's documentation and falls outside the scope of this document. + +## Prepare the L1 image +After preparing the L1 software, the container needs to be committed to create an image where L1 is ready for execution, which can later be referenced by a `docker-compose.yaml` file. + +*Note:* The default L1 configuration file is `cuphycontroller_P5G_FXN_GH.yaml`, located in `/opt/nvidia/cuBB/cuPHY-CP/cuphycontroller/config/`. +In this file the RU MAC address needs to be specified before commiting the image. ```bash -~$ docker commit nv-cubb cubb-build:24-1 +~$ docker commit nv-cubb cubb-build:24-3 ~$ docker image ls .. -cubb-build 24-1 824156e0334c 2 weeks ago 40.1GB -.. +cubb-build 24-3 824156e0334c 2 weeks ago 23.9GB +-.. ``` ## Adapt the OAI-gNB configuration file to your system/workspace @@ -255,14 +251,12 @@ The default amf_ip_address:ipv4 value is 192.168.70.132, when installing the CN5 Both 'GNB_IPV4_ADDRESS_FOR_NG_AMF' and 'GNB_IPV4_ADDRESS_FOR_NGU' need to be set to the IP address of the NIC referenced previously. -### Running docker compose -#### Aerial L1 entrypoint script -Before running docker-compose, we can check which L1 configuration file is to be used by cuphycontroller_scf. -This is set in the script [`aerial_l1_entrypoint.sh`](../ci-scripts/yaml_files/sa_gnb_aerial/aerial_l1_entrypoint.sh), which is used by the L1 container in order to start the L1 software. -The script begins by setting up some environment variables, restarting NVIDIA MPS, and finally running cuphycontroller_scf. -The L1 software is run with an argument that indicates which configuration file is to be used. -This argument may be changed by providing an argument to the [`aerial_l1_entrypoint.sh`](../ci-scripts/yaml_files/sa_gnb_aerial/aerial_l1_entrypoint.sh) call in [`docker-compose.yaml`](../ci-scripts/yaml_files/sa_gnb_aerial/docker-compose.yaml). -When no argument is provided (this is the default behaviour), it uses "P5G_FXN" as the cuphycontroller_scf argument. +## Running docker compose +### Aerial L1 entrypoint script +The `aerial_l1_entrypoint` script is used by the L1 container to start the L1 software and is called in the Docker Compose file. +It begins by setting up environment variables, restarting NVIDIA MPS, and finally running `cuphycontroller_scf`. + +The L1 software is executed with an argument that specifies which configuration file to use. If not modified, the default argument is set to `P5G_FXN_GH`. After building the gNB image, and preparing the configuration file, the setup can be run with the following command: @@ -271,17 +265,18 @@ cd ci-scripts/yaml_files/sa_gnb_aerial/ docker compose up -d ``` -This will start both containers, beginning with 'nv-cubb' and only after it being ready it starts 'oai-gnb-aerial'. +This will start both containers, beginning with `nv-cubb`, and `oai-gnb-aerial` will start only after it is ready. -The gNB logs can be followed with: +The logs can be followed using these commands: ```bash docker logs -f oai-gnb-aerial +docker logs -f nv-cubb ``` -#### Running with multiple L2s +### Running with multiple L2s One L1 instance can support multiple L2 instances. See also the [aerial documentation](https://developer.nvidia.com/docs/gputelecom/aerial-sdk/text/cubb_quickstart/running_cubb-end-to-end.html#run-multiple-l2-instances-with-single-l1-instance) for more details. -In OAI the share memory prefix must be configured in the configuration file. +In OAI the shared memory prefix must be configured in the configuration file. ```bash tr_s_preference = "aerial"; @@ -289,9 +284,9 @@ In OAI the share memory prefix must be configured in the configuration file. ``` -### Stopping the setup +## Stopping the setup -Running the following command, will stop both containers, leaving the system ready to be run later: +Run the following command to stop and remove both containers, leaving the system ready to be restarted later: ```bash cd ci-scripts/yaml_files/sa_gnb_aerial/ docker compose down diff --git a/doc/TESTBenches.md b/doc/TESTBenches.md index 3a7a1f8f67..53d54e45ec 100644 --- a/doc/TESTBenches.md +++ b/doc/TESTBenches.md @@ -19,10 +19,10 @@ | starsky | Starsky | eNB (B40) | b200mini (30A3E3C) | | carabe | Carabe | UE (B7UE) | B200mini (30AE8C9) | | avra | Avra | gNB (n78) | AW2S Jaguar (192.168.80.239) | -| aerial2 | Aerial2 | gNB (PNF/Nvidia CUBB + VNF) | Foxconn RU, _Nvidia Aerial SDK integrated_ | | cacofonix | Cacofonix | gNB (n78, FHI7.2) | -- | | matix | Matix | gNB (n77) | N310 | -| gracehopper1-oai | Gracehopper1 | build, gNB/Aerial | _Nvidia Aerial SDK integrated_ | +| gracehopper1-oai | Gracehopper1 | gNB (n78, PNF/Nvidia CUBB + VNF) | Foxconn RU, _Nvidia Aerial integration_ | +| gracehopper3-oai | Gracehopper3 | build | -- | Note: The available resources, and their current usage, is indicated here: - [Lockable resources of jenkins-oai](https://jenkins-oai.eurecom.fr/lockable-resources/): @@ -127,7 +127,7 @@ information on how the images are built. - build unit tests from `ci-scripts/docker/Dockerfile.unittest.ubuntu22`, and run them - [RAN-Ubuntu-ARM-Image-Builder](https://jenkins-oai.eurecom.fr/job/RAN-Ubuntu-ARM-Image-Builder/) ~BUILD-ONLY ~4G-LTE ~5G-NR - - gracehopper1-oai: ARM Ubuntu 22 image build using docker + - gracehopper3-oai: ARM Ubuntu 22 image build using docker - base image from `Dockerfile.base.ubuntu22` - build image from `Dockerfile.build.ubuntu22`, followed by - target image from `Dockerfile.gNB.ubuntu22` @@ -199,7 +199,7 @@ information on how the images are built. - OpenShift cluster for CN deployment and container images for gNB and UE deployment - [RAN-SA-AERIAL-CN5G](https://jenkins-oai.eurecom.fr/job/RAN-SA-AERIAL-CN5G/) ~5G-NR - - 5G-NR SA test setup: OAI VNF + PNF/NVIDIA CUBB on Aerial2 + Foxconn RU, up2 + COTS UE (Quectel RM520N), OAI CN5G + - 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/) ~5G-NR diff --git a/doc/testbenches_doc_resources/5g-ota-bench.pdf b/doc/testbenches_doc_resources/5g-ota-bench.pdf new file mode 100644 index 0000000000000000000000000000000000000000..ff5b9ab88d2e6634635f15ee123f0e6f4a5c1f58 GIT binary patch literal 217733 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

_#790Vv8GlZ-E^#z9%YiXXM(|i(e1! zqndl&S|#`UyERN(w(rD+zm6P9*qxzm0pB*~R_dHB+wm*8`*-Zb?|}Di^Gi)S*x!Ji z!6;D+-!7A?su>eDQsIqqjLA$lyUGVJTyJSIUtY%jD`+LPpVxi(yFL zk+g+}aaXw-1HK~W#_sZoU2;BW_)>gtxmL#_$fkMzDE9^)j9OyFHsjQj8ZXX_taCjn ziTS43#`mP~e=YmX)$B+SwVH$Bdf?7||3z59Tgpw+qg=9@qMaNz?F zK7j7P3-jlL?Z^H2)4T7v3y%zVr9SHIaIsJTk0EF{;f{_df)_V-h=0C&&jSzMk7b4E z5Ipz%bN!Vn>?yJT2p*LeUvlxT-Fqt4dH~Oz-7xpYue9v_&>p7&v);}4iPwb zBY*{IVCORKgpWGT4q|@i?%kkU@#ukqE}OjmWZgR4h6fhbb07mFhvRXBC*8a6zK8w` zele_D!oLF|9KQhWU;D|Y`0L{thch1PL{i8U=a1Uei-{*Sc=DPiG8D-$8p(!ILx%4S z_wz%cv)4Qd^l0?@UaD^`l|#TIfrkUq-=hG=^1r>{u$NEqZvy*bH#6d5{x8SRAI*N) z-EW?J+-cuymgk>#c;4|geje63umbFrW09%VhaP$meJE@ryY{+kU|o*?+`I0+6Rzg3 zzxn3s57%OQ1>$PKf(3vj&p-bh+~ehmfZYo3EnkU6jrdnWs3YcIe&uDcS%B^NEw|o; zuLA%B6fIo1aKA&v+Yo!JKl~7OXLzh&nalRiKbKl{@BRZYkVhjJ;yp3`#nJ&R!ho}} z-+$Bvm*ZP+y9MAwfA=7~2RhPQZn+8n`@%h9Tg@}mpDvXvsQVD)cxqsC&^t?)2B$ux`o)Xx zz3=X_f6%aFq~P{s_)aj7O3=Ft-lyscYMAJA%Wj{_MEwwPQ7^9vSo-p{Ce0zj`9Hy8g~p| zcmZ!*_U?NUJa|Uje*0~Bj-a;1H-~NhbADg#TT7SXjJ)9%1G)iUqNhISUwkJx$RA!(YyVa*rS|BN1{>VX^@GPk4 z{rYuW-ll#`YS<%c%nt8p;PsKr!)^^4vqL-&yD6QA&G7s>AM>YvZvs63vFpRiUb#*c z`WjIwt5Ln>#Xa|+mV6#s;_W*=$Nx3r6ETlnnW#D;Du$Unszby)s!r@Mf-^k613rE5 zp@*<`2ag8)*8nUbmfm{%Ew>Hu#h0c}e+J)e=CjY@r@NS6yy(@}{($ENfC2VHVgC^z zG=Abe_ubp-S9xL?10D^CC?F90J`X(j;IwH^V!a4*I6eg_bjxkGV9Cyd4?cju5n}Z% zSmM9^Hs}>VcWh0A3~`Ey`wZ@cXl>^ebsxWv_8 z#kyzI(fAQ?00J$8H{N>Nt!vhPhy|&DZCgLvzGC(2haP?qjOyaWOP+q_8BkIJ`H zPl~4oR*mZjs2ga7E%=Uu*p9>ChyPm=`>M1b%+!MA+s~Lyy*@@wYi*%=Z|2S4JZYHiuw`gciwYgMPBOa?S1igFWzy-?MRYP@~m3@A(qx{-L~B?yj})o((N;P zaS8A^!W#>$Q4}Lw@f-y-0(@GxaRa!-4?bLj{|oFV!NWq$!La4($!SkOBZt@TuUP)f z445&`UAkZ|*|K*5coaEfg9#p9AXGbh`>@jrRXG42=o*mu zC_u0&8^7&quf2{pVx$1i2mmv;({uj;hV#7Ds+Fqbe1Y+D563*B-BCBOhRyMO zGk$(Yfvc36k!_7?*v$dk1mAeQvQ4{=Z6|(QWZfQv6YUaIjpdx5YtD+=Hnxn3KzYJ+?*TjreBn zAFAfcJrHHM0sJohsH?k#XC(eT{ZGU4qQO3=QXTTfEIs(%_+P*md;LbpUaxEFakmXg z^2FQ%9;O!8FwE1!pZ^2K2{1x8{VTqrCuSD7$d%&vLkuA|U zFgOBRI(%o`gP-EIGJ%yFJ%}G9e|Ict!mog1h+h@&E!XR~Q_?$kh}hjXd%+~6Di}4l zD>z{WvMca2A=Ud&9q|~H0RBHnIrf;bBMGU30Uxd$$-UmSLt#v)Ug;@-{|yatAu1*8 zy_DGFdGIz1|BCalv>$fYn?FO$H?3i3OU$={xM+=SsbmYJUApkJv$dUdSzYA%7-jA4 zVYhEw+MX!8f|q+7N#Eq&@&0Bx2+`R4YeD>NDhbb@ zXu6|$9J*nGV!aBNn zD^pSKm?+^pgSsXNcjL%LD;ZFWLPlq?6ZtMpB(V~#Jua+i@8(t40iv+yfKoAW^rJUM ze!r%7o>vOgdRCUk#WcYvsV^DaU@E>l=6UI#${+EpQ`Usao(LTCfov>=)~XTj zfOoZRza5SIbvzW(gq8Jz(3q8(pP#fxYq2MF@JM=_u?GD7gxF?w!1L{Xt;YUVHNl`Q zOLTclF&oN=;xU=mutiI{&cLF#-K<(HhB^PI!%Ct{R}rJ0lpqG{2*!-;gU3r}WE@p; zHK+7;^0L`lx3Cry#|`HW zl_>%bHS#Cx*F%ki7l>Qf>40WOZwMM2ycgdARVS!>{9FiH5PSGjd<9)0Y&S+)hSCfS zkGp^M2O!?@4e_Hv<8R-w3wCX8s^vf%upBCA{2B-$)Y@Pv@e82Yy3IEf@N1xM1)~q~ zgXosQYk}LuM>GWx+hAt!+pcs=KldV{z=+~63;aysZ9$&(eKgc<@|0b#({T)@Vr)jk zKMNSoC~2u^KSM!bd@t%`lFC$~x)I;bH^>2z^p2?J10wP7E`LwJ^S*{HSE`BjU@|pq zK|eo{kJ-jXTHK|Uc z_PVRzdgmRy@!D&zhVW6|g`)u)1f6;u2auTvA&6wa_yICuZ!C6g&z>^}-F$3goWB6H z_Cp|iz;%F`$CCwgJ#=5MzV;gS(L<>7${$_>H3TsOm>j$p!HlN=i!Z$dP7*p9RJS04 z&;VV(aU)u&5}A+?fCvST1l^ijLH_8Y?=N4D6Y<28P6YwN^AX4SzW{=$uhhTnz4sy7 zfvOa=Kgb*$3Vd_$NKO~=_+y{%+JhJPE8}#7eZ|-DuK|(@y=G{Bz{w+dKK2BLKIfy1 zE#mf&s8h0~Vazk@&V3?{{5ZH&$`myQ{=@i2Maxym?V|NHMlrr-5>hHq;~`6BMehlQ zk8&E#?Go?~#(N~MHSCDT^DfUXTJ)FF5+9we@<=fcWefWAEy7Z^cJj!QLHG7ff zV@8l8wz0F=(cmjr-f_omEza4p18-lncrn5f`Ys$UI8A{+$Js&e0Dp>K>9NNi!=h$1 zixIR)@3;Tzjz7HiDx?%3UGcmClZkWs_S^4Z12`-SaRYk9Z@=>n=ul9UcwXSQy6L7H zpfbXdL7x9$&03r?bQ7?8-U;*~<_Tj*5&5WNDuWsBM(N&FZbijU6$|xu#35PIMjBYk zj!92?X`UB#0k|?;sx)vPwKGe&T>*a|3Cy%2$TXG)fhtuT|ER01{AcYM+5P5;$DPtN z=AVAn^`!P9E-@N7JS6^AQ zXd&*0{Osvxo4+2f9S`q#z!av-avA z{_x;~_v4`e2mz4eT2AxkA?n_E^DUrpfFd+u(FI15cZ6qG4*<-KH(rm(M@f!PA&*Dr z6q>iCUWb671#^z3EshNMd?5BUAANkq6_?MN{VcjmPPc%m-h~U%3q!V-Vs`n8m3SDz z!wM1t31%-FzEjQ=?w{$uKaPCA-xuH5$U0ga@Ca&WY*EQ2jx?#0OkeZ|_P9OSUlZn) zqIjS`GL|KMp?IdJFEZ*;TA<;|F&}Q|>u1V6ye1({C~u23EQfuy1}b>C81wTcw-dRD z=eI^(<jxiv2nbnX z-U$YlufX>~Dn>j*D&xvYU)2H(BSN5ukx!wPzjW#)OP4NHmhum;zjo!7mm{_XP$8<( z5=Gxf%_5=v@9)H^x$&m!H~Wyr zJ?Nc1^UPC#STbh^@Q+!5MT-`7#WCvDYu4O;JEZSxB;-()Am-8S@?+O7=1~GbiU~L* zLHpVpZ{jbw6QY{5)5ON_UFk}W;4MbL2C&4MQ#=5o4+T{wP)D^VPN8c2OV;fUXsV>c zO`7Br(xz+_jpz+D&W*!iZa)@aW^P9G8ZH=4^9t6M=?u(nZ z3L67$`JH$E%7NtFd-0XUFTQx?l~({-02m-G@7%K&;d}7$IaOZ{(#B{4*X-TL zlCYS54Z2fQONa{h67YPQwB-snl#P~Qgs%XeD_N&88}NAkkAmm_6Z~yPj}-GyKRXLL z8SpmfNwqodG2bsK1B3Vo0SvG!?u3*JdR@c})CKoH@W9(km!WJ#Fd)xMipJ>xv_{K* z?!4z7bs7Mu)A#)P&R+q*BPNk2@q=!>@dhY!0G2jv*npF@$^#GF0CWL~&g78C8gyNyF^u71+Ji?EF)*TT7 zJ_pzd(uf()&RDc~3GTo5zWV^waW39?<4t5^j3we1cyFjL`KQ;peclfd(z zHHrDc&V_@8oeP%dovw1?JUmch(br#peaqI*P+%gqk(?1HtJZw*=G$*0hVeZC_#u6nGjASZyQ{Zv?(@&% z&W_G5)V7c#;syjBFbT3fYEMvz;BXLiU@51$5_Jj64#Y5O0jDur`VP|dZMWS9y-uPs$7_@f6OghH&< zQ87w7GNVB9?yho8RD% zko1g@=}Yurx)>#K9A>YJn-dtV&cjy6gaK{Ymr>ibQa)fvO!P6XVsl!1{{d+^+EPJ4Uqxf@=+wEctY1~%O? z`q`}qx8FLx{g(0VH;sRG9$A0Z$htF!*PSuEZp!edrwxB{ z%Frh#4}N^oj@N$rgOmTMam@ex?CH=|evBISfXE4CO{&Taf@~o6i9p$bql2gt(p(@A zND3D&UV<0MhY(NV+#t?heCc;EdV1;izsIl!@-zMjWiKRTNYwZam?FdsAo#;~-+Leb z<9K#pb`|r0NX|G!04*z4twKou;kDO6b>jX_Ter=gi|s#~o|!%!WDaok%2lh8v5|y7 z+x|HcBc3wgFjs%@0el4!PB@tJ7cRtBIQ$emTyO*G3lKG+jKKN$liLbO|Km?Qfs~H_ zM2N?ppFbZ{R5&2uGZA%2`1s~f1;4X&8R~lc!=QA<-xq&D{PpoWqo~0*!~wwzgx~s& zo8jixTe6W=rp>igl+E|`i0Cm&eC6g?Y&MwbQ|T{*7Jgx}0g^$oJSGD$Nz7Mkf@CzL zT`7uqjw~g@J6yH8Vwgb7Ys}Z`UPxSrCTetSJYbuJi@ta9c~@L={B1X!^x)lRJoDIj z&&{}a{;W$D&c0;9?28x7x(L_8*%vLEec__nzg#%`f`zkioxgC_c?)NqyKvSy3!cUG ziv=@(zF_9r3ud0RVCI<%W}dNN#_9f=vS7w(3#OksfBGr&pE-H{GbhcTcH(nS{$$>h zKc4gWj~~DP$N%Gm?|uE0lfQoI$yv;Q`{!prHFFmJuR+bVwuy&ikPReFSn^F$4%3zt zM9Ypgc0Hx-kXk7i+aJ5MMW^ew%rQ^BI7q9bwlLbaHKRz~5lOyD?WC&_^Nnbl6h$>X zeVI8J6D-QWWHWt<(=Qt<@p=L>=+G%|txWl72#o2Cr-SiNmtdHZv5vhZwowW%P!|-L z8GRx&P)ia^&zL`Z5M))V2lk0Lq2xKe&NFZe$Fqx{*V9gH~;#*Z~f@F z@0|FPzdiM&znyZ*f17gZe{&!Gb^4L6DX0AHl#?fQop#cHjn_9$J?We0{^A=`PHPbJ z&(3oEyziN|`E7b?Tgn@hf;nDC63o0BTSP3ul8)@`lE99_FlU89i%r#bCfNndW~bd| zf7edi1cd!&olLUux)8d|1d6MM7#Yd#NFK5)&MW1{)U*gVarEW6R7&rHVLGFmf%Z)M zyqqA&eFKqFW^3!76uMY_N7MtOVo)fW2$GAVuRj_!&41>P$rbCF_P<{{Zy^X zS1KON_-L<|tM)jMJU^m&e$k@8JMY~8{kR`|?WZRo{Jwe4&%bryFTZ`sMSqR!(u==6 z^^$L#F{K#u&>no`V*Wrot*+)=?Ovm{jhnAqJG#}O*X$Wpz7@mLR(-MM7c0Ri$UA6z zwU|+-D}Z~&#gvy;tG>ORCXdWOiCi4BMZlJ&z42p^%Ji19osWr;Z`?|tshg8CJGZCM zC0EEOZkPK--!5QAeuM^m4^8B8WD0<>d5v-i%S13h=R$(q1UBSdpp3c@k9Jj!kkK}i z^j`ZTo>3&@0grJw_#zNdZE%Qqn?-+j{<;6<_~X8Q(usd_=IP%&|J-k1a`Cq>yYxGk zPnGMde*KLzPH!0Vpq;^0cDR^~Zq=q*jQ+j;HTsVr-T}ffPq)v{M+->^P8;Hu{jF{5 zHg5dj!w>N(R4ynyKm7P(xA4WQ!qCQrBMAOO;dBJC3XT|>M0g9r7{9>gty@8c05H3e z>fL)k-LL`fXU-)Co=9E2__l!P7|3$+&z?T~8kiFYC;5p-)NpWejs)F3u4{lUGA2Rb z!tmJ+^pbFoC!<_v0V*AA1$FoD+mAatx_h9y!C}W&Q1d#W-ToH*T}01@JMpbtw{Cw6 z0uu6@O`Ep>veq&I52w!AJZZG0BVH5Z?#dA$&u82i%GK zLwb+m0Y&Mo^;O)?4NyfF67Whhh8tp5Hvw3=YN~kFz(@4X^{Gqaa-C zZT%Q>j`3hue10?xh!I}4^& zt6)lo&eZ3-_M!;|t0>6KmM>p{cY#O*llj^kuY;dN!2u&OG)9-NTmfpd&6g6l-+nuU zWOL@sdHLm+kxRiE;auP=P{H{f5STZA1T%mj;UO46nSRF|cc6s?HquY?m7vK4#|{PB z%{SlF*5PUv@Hx;(coELaII5tA&;`Q{h*-ySe*6i36DUaWcXwWg-MuhIfU5}(8crEL z1+kCL1w=TQQU~LNzazZe&?9*1cQ4`4iJfW5VQe)@s%GegI3y5&)a-C ziJ1S210JuN#=M^a$1GB-m{@zvfB$^} z?cf+82}3slW+3nw21f#rlg@;p#c5^`z%Y6e%?zGR_k#=M&Ox|} zt{60?>rB1+3jVwCJD?ej!ElH|;gf)|Ltu4KnrP2~IK{sq*hr{=km_Ni1Kk?AwTFcT zTz;CkF{1ITEagH~H+pcDs`xy7BJ!FNP)(FmYPOg275cDI zOMHYp{}o@2v5n(xK86{7Lz4fMU(zM|1fD@qUI{XHCbaG*mfZs%_S{TmY(SV1Aw*K)e z!KMTEOAG1sH{XI!+K$gZ7dA$46#fe0;)M$}g>yn!xk{oGzBkyP^;rmD2hL%|fPalX zx!=2--EqgQt3LR^A@jcemi7+lju7)QL(OKV601FAbE>qVxg?d!WVu^cRC1(^f zmOgZs3ez77_~~*COxB0WE{$-B`2cL{104CLB`!CQ`OBt;>+-2z>FXvjFEwl{Z;A=? zMCk%YyjE-jZ44aq@P7@dEg(0(j;Mf+1`7XfzDP1i$6EUJ|c*M0;YKAt|i_wK_DYd`)JPJ?hlL!R&TrfN{~ z-{SK&{-^L2{8J#wBe6rwDK#dL2_hCPvkrd-(T&0p0GP+5H~tjmJiyFjk39-(j}?)4 z3wp9Rws>%$rbbZVL4Xb#ekgt-%g$B4l9it-gVB_0l9(8cDpuF5E7S^?oCaWGr77>_ zs#6oS$0rV!R0jrQ0(Z9w>~YY=zX(9c-wATY-Au3L9+2}Gw;vrl_{Tr}3DTP<7ccrB z7o7Kh{`7?Z>+~sqf8M$OF!j=Zy85c`UU$tuUw4iBc>T5CyYc#ey7Bsdyy5D9yymL^ z@9HbVb=8&s;I1oh{lB=byj-sT>94=P;xfA~zwG~U*Hm2pH?GSrmFw>=zVO@Uo%8LB zFZ{;oQyS-a^kF@3!^?SXeTJ?Z@G*~k{)&(J7Zxmlg(J+45Ze;-=wJfRul;15u#U@D zBIYl{KW1BG>|x}Ko-7QPHf-967>1J=GBCg}N?q6nfagI&AKg-*aTEnen;4-2Fz{A6 zcb6)4+z+3PH{X5>g5GY=jRWYQ+Ql#P=38&#{uQe}m^pJMq&^5*3@$7|!u%$R{63gyG*E!zODP-5UOB3^$GnBmi>U9I{es1=+tsCocY@Oz?2 zfk7Eui(h-=jhmg1pCcr31c1J9BB1j@b%W>B6Hhz_KP%MKsA)QTdhuGs3@D0a!bnGo zSi8uXIn19DbT=X;qf_ERu-mF84B$#r0^2ATIv`S|daOQZCxRNU67wPceVi+d4@GM{ z8XEIs;|IapRDf+3FZ$l4zr6g4Gj6^8mk&I2>F@BJL~$H&t5z8*=uIbyk_Q%t7go& zYWj>Tr_Z>2`t-}EKXchLPfx}5^wXC<{nW*{o_gw{r>0#v?a2$LJ#oP!k4^piGfo6K z|Bcg5X&Cbu--GtRS#t62NZlhwd-o7*%U7)e-oSq}vN}pvP%(hY_&Vl009j#<2b;=Q zUWFK7jgR@M;0ZC51jzxGhClMi!%%ZQ_xuY8An5 zoDV+y2xTwM4vGcDw%6D^fcu|-W@a8_1Lfa5@(9eMz?VMxO4JQGjGesa(yT9oga1 zXNNvY5c!Dtkx|!^QoYUdkDT_-ymL3cHnn5TbwisWv$+*AvqL)|HoN)Ij++kdxZ&XT z>koc*-NDbU8Q*sG__nLYw_Z8E_44tpmyK_kI=1D~u`L&mZN6x1^MzxZE*RZ(-sq-t zM>n1`y73pI8-6~z;jEGMXO65tV`Tm5BkN8ZS$FElr>6{mdh-6|zx>J1f80=SwOPzsaCki^mCms;^IuJXc1tvab&zX&{LokDd z2r#_EcLq6$42j4G!H%at;vZ!%P6}{^I65MMBOd|-gH*&r0UpRg|P7u z|7#E@Lz9DV1r5*o4V#e9*KdGkW)AQP_)n*?X@Rfcizv^b%)uZnej>_zjD6vE0DFU< z2zN4sH25wYNklmQp-|)CpYioK-@p+-M4_Po=QNO7_=%vT@D%_;5Upw*(?`oCZ5FkZ zG*S}*08#Xrnlb2pN#D!uR2J1}c$Zl!6R!lc*QheB)(ksEw#T4Oe61{yv$t`1e!|(n zuh`$FWyJ+QIr~Qw#5_Rn9-rrfr&D`JvYsh=6zK9wl@nOUN)2l0(Ll>MH?6g$Cw0twMv8j_v)Mq~xzJ2MKZ%%M<72+fPbbAlwpC^BV<%U&F`@|+J?$@3=$^Sm+8jrvniGs^ zidLi)OH^ZcvUHuaWh+ThtbHzlG+G6hqdq}>%o^wU)R*}15q->#$=~L{^7H@g?BkCR z^LzK(`bb)Pt>Ru?fhvQ0dK$wWQ+qWHCW9c3o19WoOtU84if1-=3frD|HurCjE$?b&eu>VZ(u<#8(D7806a= zliJbfOjATMIU!$YND0{<_;!*0x<9R`T0rAp!<*B!3WJNxyxVeTyFE|xKo%hx$oNsxqESgo`pJ@qZXHU@W8dG zd?SpBlh-kxds1)7qEUQ!7W5l3$@J}kuIW#pi{|!lE<8D-UIezeMmw{G0qq2b1mR6J z!1ex0DQewg{9E3XpE2*$uxGq$>>_AaxQd+nvW4hUL52P)0BiC^Sh``j}x7 zRzLEjo_3dl?$3PMo)_Mxz0(qPGP2a!U>3>$X;^!0+uI3^M6_?qtS!Px+1}BW zY>f=FOD2XdWxLwhF;cO*&WU7;BXvNjo3^9s+G{c#>u3Ls#P2R{L{+#Rpm@B&|A3zUObv4)RTaZxn2I$2mFw0=GMH%Y~1nlm4-3DrHOZ6bFUBHts^l%p6waoy@3+* z?<3}qI})}zpbUv2mUOsJN1Otx@z6p+4pEL)u1aoa9!BvtKM2CUZ13yaPIO8K`(u+! zSXfM!@{pN7WLCX0n91=;^n}-;q&VDUG-Ee4j~JOa3}1jTMf9#UZ*M7Ce~`I^F}qlt z(bJ)iTubbOTyeW9XX0FTEn(~nJY+-AZbJH8&rnOsRo9;#5I_IWpY<~%ejXNm*rUfb z-mh{3kJfSGZN@h>;%(MB--G?{pF2U!qYt}A^L&?y%G$Fjr%1Dls5s@!!-n^EQYi?p z*b!a4)c87Ie(gD#He(U{Vp-}EatD!z*<_!*-D=XlWbHM3IqD`3Hw@L6uGLFN@OqPg zSCavgGG`6gybJSerUt(A05n_JU><@h( z7X3uh&l5kXe(YW3+-_rlv8fbIuyx>Cnm!uy%stvnUxe$tS**B&h8-Q zew=J2Xruyo3*#8KMeUi&2DJC;l)zp^L#4nz4k~Z#5`WRPkq39{k+C}wL#4P zReDnU-#-U2|Mlj1-sx?eem-vE2#LCGr0H2c)`CHtuG zBU?Aai)lD?)eS~=`-<6-JS=oiL0B9a%Gh!!Z)pmTXY}$-kuh~Mx;BUQ81_lCQmfHy zF-P1omarchu_Os=IGSg}O3`E>(@dJI-3Py>UwNS#;Jh*|)Tffhr6$$aAY|%(HB!xo zw}icY^gjiEG#E5yj;pkopVv6g7k6bI&UR%AZ$q9=8uPX*b3?u>bN_qi{OF8{u+4{F zKQDPcB9lPZ=vs|py4HOh*4dBsrX31N_`2{q_B8FzXjNw;)r7+>pHrOs0<*$X*z&G) zS!5|L6ti+wrck@X9x+=DFH7FlE=hsCExax_u*Fbrut?4@-_9Bs#xNP2PDmSay{2)z zn(8M*jp_5P9uqN&Eb|;64RMG_SjAulU1<74P6tgtNn#JDMq^u3x_r@JgAwYOmN<|3 zquCqS*sqeY&88z^oA-Y4qthph`L)j9rbCP`q>v;-7&F%;-vDmz8`$nCDJvd4sJoM9 zXh#Tg_pu|gG0c{TVO`cOG8gyW{@g1mt`#e;B=fJ!Vw$9z$SlZ9ww}NTE#-Nc+Uu{_ zg1ju$3o>{H;Yk29{HUg1C!U05@TI7Jk$YmnSPzwHJOz~~=+c&`7y@h6$uTdwUk>;% z&F3Rrp8_t1k;4av1ieVia~conos{$PMalgBat{J$Z!>=Ik5Nl}RM_T_?hVX(Qd=7F zHqCb~?DH|-4BJebITJN3l!u;F=?f+ss(b0`$s9RMLZFO&iq-9jU}TFO6FnE%4r`?% z6C>-iyOcS%^A>|F<~ww$6Rm2J1Jy%@y}GABL6mldl6Gopoy1d4T%Ts`)S_p5vT=-H zk4@px=m&di#K%E3t2fnoBR3Yyiql%sLgo6@&PA+Jt$@wQ`FbdC5%X5tdN890MBqK} zJo($?ewD*pVxHd_`moSd9*(}m&{eva-)6c>*)sxN2YUVQ>y>eN?w@1K0B~tFydM*+!D+wvI zOg%)n)eAL!D1@U?Q%|R3=t(u>Dk;y86F=WfW47;I7xNAGNijd)p@d;9LVXQuSzUWf z9R+i+j6%0u-P6bICyi&uE!n5WsM9`5t62Mrd)~#`*PNFk@?j-dX!=WphxNXZnKT_& zf9yA<(4D82RAJcc>MMG@m-DT``dmn6F;ul{f|(i*0|u!KO*L z>|%o9Yk`f4VNWo=mXyHKW~~S0<8e+slnLQ=dOzWp7MV~@kILd*0jnG?vFZd-e*;T;u^;UiJ;A$nD;9zLa`k!|SU zxHYIRty1z9>(*700m<|Uuc z!*!4C2en*PoL^C*UXI3qo0A&V*}z4Mn(oTnX}rzhbPsq#z0E<1dD#QLDdlZ82Hr-n z4R{;G{P7dSyw_Fk_AwvvOmCE_n>;Lv!EzHkj`lF?9p}fxrc^@U3T8So!*<%E%F)i` zjMzUadEUitd$AAYFc0}XI>a9QuKU$1$@X-VlAQ%?ufeth;S|uA9&a>Y-ZJp#1u9-+ zSiYhXbZU>rXb;lIL=2!=J)tfs&&&7JWX|BnWHSP`2RNi{(3M8K16Qw|rV#dqYQk%# zK)E8urvt;_D#!nr#r)*+{AWk#NvZ$*!HrG(u=|#sef;S^Xdd&@huz;5Tlo_6VIy#N zs7864fTidt!L@E_cfJ&li_>&LSV<+3>g&a}?Yte5DMWQ>&o<}B!oF;l#- zST|dBnsxjeXaHjLvR#tt%%ff_@y8&4ROQ%hQm;~ry2mGHM^lC^<^tK>eE9u3FOql7(pNc1*O7I$SHsfN{< z4>jzzBiFFwv4$OAAF$0{AM;;3u^DgU@x0%)8rn=g>=lFu-D5;DtWg(9SdhC=)>SoQ z03>mV9v>FIpHE7*J3CwZ-VQ1C2>k`5d4i?AKg`U zXMww%iH#CXUMuC8XS1|IfpxSiAbbz+16+zpr+NG2Y6nnP>VEb^6LF`;i9UkWZX zYOhD!qb2`(U2=8gX9nmRWEw{?jP-fr6`vhL!Rv$b{Jh3oCGdPhuF~~kw~?+g=6T?G z&GQ=b30Jw!>F4(@J8PntpZP4zOdVI*6=wQuUP-kvnKOK6keNvzR@XtAV3>}Eja1V& zOjHV)622#!zb*y7Jz;gyc2#nfY=e@2-QAb+>ZNj`knFcrV^{Y{5rL z%NOa*bR}7(p9C}3x{5WfJk`kg41}0(%+DVqp5M|$W7cG!)RAI-mh7pA#i+WhNAYb4Es;%9uZOnr zW^1W+EiYm3p1Aq0s2Py=Dwp14TQ=O)E8A!MJ#g3%!}c&Vs=JoTRefWhD?%$-*3C@U zHTKxoMddCb(^9TxK?^SEFNh(t{TNpCC z4ug2^fY6eH%o8SQmDVyw-9+ihIBI<$LmFNWIY5onx&jgjU6#rS!B|NE$pAAmp|vD$ zjHq7Gdwp<-Dpornt`_#Ez}2v#J*YO|Z6?)+Eocvpq7NHtSj7ClPEf-F+h7J9*oF~^ z>diWA-$xG%CUr3rY0RvW6(gNws`3gE*ODzQK!2}__$Z@ywlCslQnN$oNc7vKI$ zHqgOqA`R6L{m6#E+;8RiYkr!fuJ08V07oBql3E`q zCj?F{NBH!e6Zjn&OUdwRtQ~MmY<^i49Pgpip`5=^KtuX4Qt!S$%531`MMu3WGws7> zy2^%q*s+cI9`L&*)u^Kkp4?E_44^(fncj+0& zPx;rcH`m)>6^`4@?I)Xb=t(rdI>%vy*s>!pp(}e?Ib7sq8=s^uH$=WFSFH&vyzG}xWQ`)$DqxZTT4)wK#DD5ekS){;^LjC> zcg9*+V7yN7dGuc5DJPY+8zC9di%#D0{Hf-1lR*}>1TosJMHOI7Sy{@4x`Kwgnc^U7 zMRj6D29h_?=o@*0spyr>0i5HLTAiIy<)I+(TPt54)>AQZkh%2)?-o`WN)&-y{lJocz>AY=m~Qi0lma5ZB30-|n2qB_*sKlQ5Y_cO_a?C1$5J-;WXB;A zJK9$wwr3zpWrkD?S&cHtEcs_jg9_o9_!a^%p}nOot-PKc7#f)%=9|{AI|?=Ir2DYN z8g>t2eoE7re^Tiy%c|h+rnnyIIuT|x>XuS-~)CaOlJFtOo9~h+EKEU789t089m!pczy)j}KZNFAy z4@(8}!dupb$szU?1kPBT87cOp{)7DdIB}KMlPdIK_q=mDV*YE*Vt&R5cPCX1j38YaPUkLe-`QwBXKDso22>kq?bG^;Peb^oUp+2nD zuznu)h%xVYo4xx(Pm1(l{F;X^H0JfWU~&SZ=);xCBXtlpA{Lt3P|DpWa~nie6PTFZ zGgoSrL}uJOk0UXA6QnY!Y%DWA$}H@3^L6IdUgk}aslL5ix( zp~q=JuS~E+BanYY5QEk2?IMFGSduG*18xsP-@{RgCS%}3JPfGJzslA8IBbmh#m6!; za>P6zX22WbdCK$6uuaq+?C+#m4+{7a+a+a}8incDwXAoSU=AKKB##NrR(&F*ibu8D zP`;7Lc0t++i&!?3b{@n?yuz|glsfUzXd^JYer2`;QTJG3cLL+pwVjk{eX~YVeL$V= z;{6G=(jewZ@xrK^b9<@;8{(dqYRRf`#Dkv&HPh7$vT=vL(4H){XbrvlJU`6sUHRy? zfIE#@6RvV{*yhk=Vt!+jm|uT{n0GZS)@{3(?@>qJtf|C%-gQ7Po4M}PtH(TK%vdRW z2Qgr;bP+itz$UPZByq`RQMsdJ-qb`na?z?|AjJs|9_2{PA##d821=BnIR_R z>5PP5DaQPWDjC8@f4HKS_)pbFF+WUR+ztA$m>Jm~=V3ofYuK%!hAsLYG^k0c(XdPS0a=18Dw0pS~aNeQ6crI5hMyfLvbjU}Y!d2(p1 zbDt>jb&NEN+G{9Qw`rp$9C#%Yu18j|Xad6)8P?y7;hpDO;scrDs~B@Q5DjF86)+Hx z4q6w{)kv5P#1gXM%>v`=@m(x&&&&!Cr2(1Xdk&5`SUx1+d`S5A_=7@X#~%&ITkuE2 zBl4+S_=kw|i`#8CK{lq84>4tK?Ws9lnfCOBo_;8V2Mke%A=9B`5Q)Hy3CZe%+@;MP z2MUccd9djxm9(L%S&f?r^AG8OiTqY_I^2ON zyQX^VLKa=+!9#~p!yZGPpP+_4*idiN$kPe>`Q2}yHgU|a`S2s`$^_W%a}iio5u<+E z>too5aW%+#`>Q=(11z6Lp|4XL_PGkNCie$%Z+uPd`>OFIx;0gOT?&I0#SsZLg4POA zjp>+qW&&IxtKMKfbv{lifuI}WI!@_6thL zRKrg+x;}-D2+{~YYj`Bdu_4nA#d{>Mhvm0I`VG;G$aKLOE_`Lk$0{z+jCd`V{4D=P zd^(2Y?e6z)bhnL;$@jqv_u79e+yl!47x7;pPwkQ&9n~ZL0FUnWIFOXFof}BS%+-=$$a&Jj?ro!uy zc!8WB_0Z;vKOxW8(ma1`TH@&Gbm*x1uoLEahvz@=y2{R;Krr^?yJOgbLjivejqEYC zF72e~*=}WT4~g4F0L*k^!%a-dj8Zw5@3KFbosnuS8{*FWtIba%OezIEB*|UmqaLD$ z&z%NOpPIcWPP0}FNLyX3QpxbdiKGqb1O_voCAF{N!EioV_ys~QHBzH&*2fdv%IBw# zHlp++bhpP@d^}}^bGY1*8TCD>!%ex$t&{S%Ih5u3hW<89YFLkLPW|C&-)~yOq9+w& zUPC$x$%1B}xWAygS9T4Q8x81{w5PAqmu!g=+gaO64r)SWqDN~vWyd64nV0Pm<4viw zQ>oHV0UwFPdRBXuDv4l#&15p&qZ%k#*C`gK8sS_WG{gCo_|&N8S28Na4=mM#RU(~O zwR*Om(Dk7{H3BD93k(sNV^5IC{0xUs_qkok`6S?nLf8*@EHv)${C{8`_A~3lBF`VW z4=dPaz1PqCKJ4zd5cAEs${8-t@7l9h%_MaPO<{3r2>YHmXHncx!0S1Zm@ll}h{LKS zlg-dPHOF}z2JG+CZB3;zFVQPH48$E_74>Y7i+~j-CEnKxjj|my$pl9n#N&6FS6&LA zDms5n4-Pq8ss{_~Itw6E1HDM34=}sJv)9#J5{9qGw7IwaO!@yE?2jh?W&L z7!}Kwj1=WEB#@lL&iA0H#_TBjum{r`wz00V;7jcDd{b-#?ZL{`A3#6v^fpl^tjcyJ zDot1BTT7CwbBlP|tdz+Yi}Dy7#AZi=BAzLm5{x&IHAMIXd(`Q=8pQ2B=2;uezK*ti zOii#g;m2JDS!;>hIR;qMZgSkzG*TVOlq9V1b0Kg(Y@lbVb!Gdrd2K!;j7$>xQ78Kn z2OF!h{hT8?2T}hlfpL867Oi_^>I|%gTg%Q?zc`k?zHbW z#q%>}to&fjuDyHPUCftR^cw(q>`Y}nDMjyWDZ_w-KUFgsPW}_@JPiU!wVSkLSs@i%3hBNFJi?LnHsP6%8`VVBZ zIXAp2%D2l^OH>DXSqU7A*#S=(WreK?X*5y$7>+4jl6>Hrq_9OiYww9EN`3p#2)IhZ zHj9sb9yaU4PQsU1@x0csKIXso^`?DTuQA)VPx`Pb@f8lIH!kns`D-ESS?gj!TQ64HYywW8EzErRoSgY{31kl{}Q zQ#z`=O?d5tJiPGi<&Y;WLy~ulrDH2yAJ+3WN4-yKf;_)@ax)__&o``Lzj@Y~TEljc zhaN9lk_gWj$Yc!%A(P%xIWyKsFZzoF)9rs{VqtM-EG-A5WSo)CCqZB6}8cj`oL%{Q*8713;I=!Cg z9IPE62D6GE{ak?bkCWjl|%N%v6JgBU#%Swo%*aY}7PPRY(nJ(0yJsgb##ds|Y;i@L_A7 zGOr?kRF9NSL&MV2qP}dH|oP4&CJN5$?O@~*mMRw z#QX$#e)Sr}JfICL7?Yi1sjqJzEs^9ksbtBRD+jm~RhhcHU^!y$dQ?dUsMD#^nvPmC zp2W5u=e0k+-opIO;Q)( z`imI?R~C`#P0jNPOA^7ru+u;)2+?W2!3bmIqYwMhD6NV59MT)m7emYsjsyh`GQ7h! z<082EQxfy@(i!k3TxHmmxy^QE9?EuQZf;z|CVCsxuy0Nj^G}ADM-AI+lhh)H)b%u} zAzr5PO*r<%(W_cqKw$k=Ixh_nc$WW7FjGQf|waHvQBE!A?~`G9zX$-o{_<1nnRC%(qoJ`cOz_KfWIc>Zh6u+7Yw ztJbW6mKYMVUfL;Ua^FgoVM&pVvDrP!=hph~udqCrw@ik&R1fxc9Ad}E(XqiahE-IoV#}eZr3~Us0q~<^L;9&` zi6fq$cC`NU+jCvzvH8#Me)FUWYFL-&F&pUijPyo@Jb7zq85oYrlZX~R29yedf^PR21Jwy~uK z3DaIPVpEsvDcNpm-${91O+m z5UDHej0~k7hK1NKq^cwb`C&u9Gnq-)s2$Lw!oyv@&Co^KZOz9;4Kd|w|=mFAPA zWP+s2!mR7$_iOOZYaX&Tb0q5fXJ#7N;9h8<#$i2;bD0=5=Z#vORW6CUOQ9c{(1R3> zW#wep^{AFGih-XQ$l6kaY40i7&7|u*Xo>b9HJB2WesQvIz{Kz)`VdLDN1xc0hYTgB zBy(+*l4dwc_hUPCC`2#a>Ar~su%vY5=ZB(BUan!u^MYqQjMtKO$Z?eiotC%>KR?`z ztK8n$yH9g||DSEW-@_BwDY~%cG_QKLhm5Mg9S5CYf8MJ4ur6Ls>W$g{r@1_SU zS2GJon$gqxv{tFriyc+2!oNm3AjC;6y@jRjNzD{lR^!9IP&w0PWX%N0Y9?d z#_y9lQl6g?^)|hk!+-`bFA-*&Q>88yt{d8^*77ZrGZN9(U?LH|K3;xIFK;%DyDv<8pRZLMk#*4H%nmBxogNFpv!!Dw!#e3b)YE_<`$DG17nZ7T53>G zqt#C432Z1`H`my^(ik4j#FMEgM1x-Wq4q1Wu2q*I?9lPZtV z0M3j!m;<*LEX|4|LW!xJZ8#0fIMj4k!q3EGHCx-keINGzGQQpJu^&AU+o^O7Yn-b# zrXJ}O(!h*F--DspR~pF_HH<@)PS?=YhE)8Drf3Ox4eU{7V}{i6z#;a7_98Mlfbl-^ zeawIH#h?HD@44^6OFb{XQvyf~=D%a{62xm-^Z+oxSv*p)C0I&69@WCTCoBL3_)i-mVNvW04fudDn zgP8`bM&fyXBBa8DK_%qnu#iE`P^8%h^MSho>qeQZ-O!GS2ZXT<`T^A##14@)o2XW0 zDkgxLL<1kb!s$Iafh5;3ohC{3xS%9A zHsdNiwsA3!$vz!TvboV}jhPwq8A$DXb0vJT^H(QNtGxNTQmYsD3+OmMXL#){B{X1M zSdbGk*;N~0($YjV+14alG!VX++INImC-$#apAB>EF!oXpmv)FU-#m6$N?bTN+SzA` z)Q_x?F<=uTnY}pbCa{U792DUi=Lx*0HdAP@W^|?Zx&ccm>KeI610H!E^n7D}{Rtw*k513#bQ`5&C}-DY_HiRm+3p5L>t2QeQ2DHe!WFJpn@O1DvQ7Xf`Twi!%4@#4A`i_HaYN)`?u=hqI~G#A7N>QAO%f)CeJ% zvk4KEpAF^bVnhWw06F6g=cN8ZHEhy{-PE*({lUrq)Qqc?nBTQ~FWha4j**$PrdY1> z4pv$^V+Jx|gK(r@iWaOgf2~7*rWvTkrHye|DYN5{H)>Bx)Hu|-rFBb%363f&!HluA z-INA_$jTt~pGjzTUFyVqd$o|;8_Zvf+(u_N=(+ zzy~+CuDPjo&5f;VZfIG3eaq@=TUKAwvg+!VRaYHYdF6qXmmgSh*?|>P53IO!|MH9X zFTZI2`xowi|CjsUJAdDM=k9y&oPF>9V&Af}u>p4PvNN$aYVSK!_Plf2o_9{&^Uf)I z-adKHTR+?V)`_1iKH+;OAvi_k zXPEu3)zGz)>M@@uN!a&yV(~Z{c^LAU`DT=E?d*wD$#8{O+Mf4LX%t2~SvR%8C}&OPUU`Y0dfz+m=>sc41-V~=q!b+Ev< zEAbd=($R5Ii^U)x47Lj+m$bUMMd&vtGoKpmLA!iT%!lwyWB???e0?vc!+JjbhgC*UaR{&-zet49k1_P zKJ_bo-7w~#oEbD`;3_k>E^b`fR9)f*Q&(ML#TbkM<{rzbTbCV;h-VTMO=n8mql~~U zjYj~ZA`{h+^NJ(t|orJv1`P zVl6}CqhoQbgZrW6o2}7qAB`UMLI7qoJtKOK0*r}SU4YH#!4UIu z9N^HZG2b}PUq01e`r@m4-7w}IZ?kH(!#0TdC}n2@ncUQu_X1Zk{YhpYCSB_qdoZ zapKLTiPY%nH%Zt)){@(*{Q;hj<8@8GH zZNnj7$?L{>e)^0Rt5$=b?}nFC9AA>jyRZndYDgncF;PfnyF6-*c}tqnNLJyfZzLO~ zR;aBRji&SQ3Oh=vet_qdY|}?AsU6G&l^Ub;6}Af-;_gx=YiGNp-0w6!kIKf!UXnFW z)i;14%AiVrhcdoBErdrxdx*!eMLaeXhq481C^IiHFH3NAh;=A!rxvhEI5&qG&fgAY~RGM0=fDV?ZI8J-GuAvAKxg~RX@7ns^f3C64!UHy7a$ZaPGG* z`sFuHZ^X|}H-5fSuP1$cCFg7Dgk4mvtG-=(siJHz*Bbc@#8l52+Z+IB;4Wd>2L_8o zB?Fjbq;w#+0(P(;-ePqf*+^>4;@^~li9 zM}~L(wzlgxwH^1@w%%Laa(8vhUDYjjRyO^*vgucqO}Cdf-d5gtOL^nXn_?Pwo5Y(!P%_>05hI@7fD{Km29Sn)7$PciE55 z`{_5PoZ7Gt>wU~VaQu8*I)_urkxI^ZgG8*ta-Y&2dLg*(AiA*eG1N!K z6Mo)*dKdvO7ry8wMjTh^^88avjx+<_aOXm!JtLFfCk20-mKDGJ$=N?@s<)XrQ}8?_ zW|dk4{xDm6o%vVuSE{MmMbYnz-SF5Y#uk`H63?$p%!Dlsi)J*)rk2$-95c#6j?Rf8F1{p8iti%JomxAF1mfmCN=Wi1g6yG zh%M@BSoD}o-bJ+x|6Nq~HPGZ{R`yxCT#ipP-5 zNg?A1b_Vdn=65XzVW=>;BXWD0ih!0$79AjFoEC zfi>SG+D$w_>sEsmu>U?$OJEy^CHE?Hm#_?vgHfW*HGylyri>!J9+x+o`o+JHl9R#A zun~_zgurRsqJ8|JLiCvJg{AbJ*71tkJr`H5!0f#>(em`_Y#1~M6>j9sfMyO|@A#Miux;u58p%t^RQ z(a8PEWSP-`B~lvM9?F)yn*(Hlkm1!}9P@d)PSoZji4tsRBs0n=I`l^2%_y&18W>L6 zM&>6Fn3+Jcu%aec*Qk&na`K#hGY+T7v1`vrg$Ks8c&M+@PU-z%cAFraP|QXGM-G&J z2LeKegu3w#ts&;olbT>Q@C%v^3^U+`U6}_bvnw;^`SJC^>*K)k^Z)Ja4i;u7)zaP3K8IABaYF8HKKZ+>wgW-CcuxFya=L0dR|dh{o}hW7MoZoPv|# zRP-C~H&_x<^w`0XG{Z;O6Ow$8jd75tIG>wS8t@YHc%#!*hCDxs8n)p+sR{k(Wd{79 zpNBoTG4bxx8F0k>kI(#J)7ijhrmtAJO89w}wf9$RnZa*ywp8iFh7~St=osLxYlXes z(@7=U!IA1)G9Ns~HtJ@{3N$5-l((#>sxxT>u&ajZ1T^@Aiz#pG$QA*|J8N-y0gHs? zi#$dhHyRA9a%_<7eAygK;xICw<`NdnI%2yvo83sEexNP~W7unvo6_?39R#QkkC~8c z*zkO!%q&&`?g8O(4(=I0oN$##@T)u$Z__O1#~a1`So9@E%>U@jA5MVh|Nr*BgFUM2 zTKmf_$^D!g+p)0|``*M(Vkb9l^xk{#O#*?0=paB6qDVq?L@~YD#`NAh>JsXm>JA8% z>7zz+zaQ|ebN1P5uf5NikpzxSp2s}<86S;CI~|v@FbW9oHi-DG{&QL7w~x5pD0}7XPvE+*|4UQ zF&{QX*!j})BRURj_hNUyS|6G&@psf#Q>9Zy!djH|Wl0&LzAJHQ#2guAxCSR5(wTSW zc^^_is!~xdRdS3%eHW#t70mNYyJ4Rd7?;zrVq^vlYWb>;5Unc_B-4s24^hQf$tpRc zf`uVUo|ig19HOEsiwsDu9GI37`FS}5V<&Dr1+eJ*K-XP+fyc>XVTCHp3Q{a$~>oPpplQa5&~kIBx#Y%?YH zZF+9UNXTHf2yGG}-6EtEi)C3iGwfPI9?{P2KiGN7pI2g7!=LvX!+J>!%P{Z0DKuJL zM5C4F=joi0(sRT8U>^MW^A|7W7ZzzcW)I7h*&NaoY&xf@jB*SliFi7F<{@eY$VDrL zTPbzJd-TeP{1GiJQ7Sv_qM}krND@;5TV?t%mA0dj7mr2vhn;%$)a{}EjA)*c1bYR4 z3|l$yc3|W3N~G(`PC`^C*09j!poB?t*1d`|2Si;}<_QpL4Ket9aaUiE4W!&VWt17^ zCzhPSYs4r;88)Bi=ULw7v7Wc-YU$hf_UE-Z;FkV;Cdu2dT%|luiq0AFk9o*df}PJV zGoImO0K0o!8tn>32!)89! z9PmtahMJqyVNrh`^Yf+WhH3u1Pv2(4Mcuc7RHfN<7Y%xk5p}|u5i^f?8pKSeQIt+G zx3836Dfi~FN~D+CJ)|0zA@E!ISRJ>-Cvg~Pw93hgaqv~VnaGZ=1?}Sy$`~@WrqUFR zviu~kpGwkp74o1-l#wyI_K3w+l)*R?(urA0Ota7)Z6%i?8+;cJHr>LQlM<@%+Z9*e zh*G@5pjcuib%A`8P~Sk%EotBY3$Wm?kl!HQeLw2u(L(%5S*b1Ejh z%Q+bwB2z)Y&-IH8%Umvvn>5B-)65KI|Du2l<^~m+Q*73*B-|LvX%`_aNxx7s07`Qh z4c#u6$`Fu1&ItyDMa=T&)KPcJ6;<{>BBtIzHIH6Jy;WddM73U4jbaB7!xGGY(zN_M z)1S}s!7(E<)ZD&is4+W#c4)``?fhu>u^1L~4``#4h);3Iok+PDo-Zj6(AAZ|OSNAe1kkx`_9 zszas;Rgiv|Ocu`A?c!hxHl5`sUl1=YadM^S|abDZsqu&r^0Hkq9%o zIgC~#o8q*W$#Y@M9=PJ?BY&)1YFw(q_9!C%Zj-cYWWh#c1=E~4rV}4?xl!uU7*uwf zm~hQN%lsLdHmNiPk46F1#AB>b#lb|^5>=aFs$Zfnm9WBOXGQ_PAckq!wszVD`<*VN zgt79d-Q<%9ni=nS<^G9o2}hg_x3pZ`wG7kgZRE{a*j(cC#g<;AFs2Gz309FCGXm(uchrBu?%2uQ`L z{UBy9=W#nEXr1J!e%(zknm7UC>G2DJr0|?D4qC<{uPlY@2PAJ z6r~DST`ZK=>!t*F<9Wh_WL;5)lY{LfBsniGACpECyZR9A4wswukI*~}d5uRT@Qn+{ zxUA6O_pq82*!c>JT%|?Nh~IfqZa;jRbWxL1{P~hIL;UzQ6!Qvqh0I~MiBWpIuJnjW z=W)>|%ve%F<|AGnk`;ZKCUBKJ_FWp@ifne$R8pxV9?Qw-fya37K)Gq7vro`^5#*S4 zR5VVNo(`nhR8Zf>CK_PPRaXl23aVU9)FUwiNfkCkSxSbt&7>7+%D1~|YMr)IDfAcu zc!Qb116U7U4lL6S7TZM_!cI}W@`%<~BoCA94U1uonv^+)^)(;1DRV|7zKt?Zip&ix zJ~O04|2DqtJmZ)F=JhgcIoFhybA!A#WE^umGrwJ#bBf8CMzNxwp=Pk1tR?Cj>;6$6BH>~#g|a*%NYjML5uX|_s|rKIk>@_209RP%J}D_2H$r?aJWnDpai@*&CZk&S1}=iOMKiS$U>KFdL=rJVAz$`0Z7wm`AIk zcBNSf4ZAc=j4Pm$zC7N4&HWQ0b|*8z$brblE8YmEZYWBLdylHuF2uZ@P-2w$RDr$0 zkjK}vOe6QzF^zBJDhV+j<5_g?8RjvPH4>FNkr*)l#8{r~PBz@NJCzyoAh3l+>V#kw|(hudGsXk49#`LYikyZyXq?mQ_e<9*W!2Oqj&$f=a5&nRX|#DCGpZ;j3eR zE9X7BDc8$Ll@qlA?B+0>Y8~WQkfJF^hp7jOrXNvE)u2t1E+-nFy7ZXWA()d~(7+?o zIP7-7?hCv<$~g8IX8vys^UoN=dK%Fi=%l@Yjl=u}jc-$|YkgRZGr-ATkBIi8LA)s| zH@AmB)JI?o4Q(NeQz7~MN~2x^(Viclyy6-VkTG0&r<&k)v`aqD5Wor?9Dc{msg z6L|#QBl9*k$*>rh`mRU`R+y9R zn2HhnF@kwI7uBpSR!#)rtu$wab4k z&l_A~$lIJ6*uH;j%bpPy!=Ar*v7opZ?EGVC?5Vy;Tc2PWlvgT~$R6_^SeAsOXjh7` z<}?l}hA9=kdO;a6wFt#kIAr|0Nk}V{XY3}TuX34CJu&RpR`Tsq`q9mcP(+l; z#j`P%*9g{U8SGugWx_5TJyFT(u$o?_HbbpTQ+r1I_6Dka8y|U_#`rcxB_-eLm?s2- zN}O0~2w(HMzfn#lZU(v~>~AZ$WtQiENpfW6qK(Lo=)z<;cH8mXxKtaGrrO!08DUi< z2sMwf7WqaUijvd9pEsNndHAp3h7r0Wwu@RwC&`T%7&$UBfFhTKup|{CY_&tr#AwH$ z{*0VLchf8)0+p6fg*7((79F?TCt;7mW6`5gbZ=NH(o<8by+(gAgFRSv_MRTy?8D-1 zQcrZf$~{esVF~x(DfDdw=1&dm(7*Ld-TnAB7a?y0m@k(n2+&C!bWkY~@2ylg>SAz! zIFK4q6(t#MRg&gw(v(4FCpjoRhL(OPM;$dwjZ}=oiNr`s12eMdIB|*fA~~gDVWM$^ z2w7LrX-Zt7*J=`n(*Fm2*ceG-q58la&f+G=K&3BOB1xG~O#Cn{aSSD{`y{-GuK zT`c{g!6EZbg?p+5qUNUa^M|SKn{CUQ=vA}$Txk`mg?2&nu4D*?%H8uVlpYkmh&8ZiiY*y!d!>Z2Hb?O>CyU1ohSe6i5kqn2Nx`wGPX zmrZV5Qt+=hGc5Oxe~6k(G}A&Z4KIPc%$Z?63};1Z4m_!-;@=9wq|4(XP^UKw=?cTW8+8xTP22;5c|SF&a~CecOk}{kF=PbQA6KH5-)K$_)*4NwNP25D(|?kYfBr}8ht)CPzMmh=uLsQM6&8K- z?E~|;5S;l=am(2?<%(nt%VBa>WQ9zatl${73-Kjc)=BPQs=_g>XyGYmUKS;=nbnzS zvYpQVt>8Qv$wwk-cJz@X!T<>nlrVwPkIkuaI`0Mrep19WDTP?yB8Ps$YErV+h?9^~5) z%$vCff^S1&SSRKxQ+gJi^oRKsYva$J*Dwz~;ONXe62fJo6Hw9Urs9^0_BCw=ROT8Z zdxzc&Bp<25#7yARa0DBf$iib?iOam(X?0`mrK%@1crTlQ@{G!{^teCoGQkKI(jGT1drJgepB>+0A{WHaU)(4Wsy^=;fr z4>*rued^B>%=?OAm#hZ|R54q!=L$W(O6l)V+M|%W zC|b6w1Wyv39I7f~;luE>d?j(hsdNAp5LSlW;K&;>SzgSAgg3FvM0XeAm`htP*gtQRB_1B38Chl0<21w6d(jhs&|KT_nZzChjEAr z!g!2U+KyOtxHGCV+{?RYnFw3`5Et zlkIMvqG!LQ`5Cn5aKQ5L(wp#S^Z>R_FG=Y!>>l5B*nGVXtLV>TKP>TWEOm+9R{gNd z&O0-eYR(|;NEQUqQ<7{6;Sg%SVba9CcLz|t>J2Q)evDa6SSpyZqqjpg3Yi!mQ zT^oeWD5AwGq3es>VClL*klP(nvy#Xq(g{swkUQy+Cg!^Ebc))wq_!O)2bc)?QUyrb zvSgRFtT8c2rZ}m#m*@;AMOClC9B@;3yU{aYq(@q8<%xH%o_MduCzWg@MKZq%;Got+ zN>^Bl5{Y_X+tk&ArDt;kRe#=W=X0N?4(o5W4#&LD{QNr2pU*FV81_4=8;MP<$P>mM zKt9)|oK0~;*&Phqj$k|17d!$|`nHwxCiAiB;6`kTFdR?7V z>_`!iq$62*WtOX?l1w1>s1ikAVcyBuKBpxUo;}aU@VNk$Kx)5GngCNV4pmajgP9q; z9k6PY!Vr*UR0&&v69yRO%{~mf3h)IK|NK}gODR``lleAvnnMqmf0`IJN3KaVl}pV0 z`4c+kyZg%9tUG(|LT-M+mtTD&iKC)qjY!>K+aP8vE0vB{DhNiCIYl!Z3>EvJ}KJPClBvHnI_&LSSg9- zQU6Dtos9#_Fr>xNHjky6sf~=-<^ZOH!Pve{VO&WB zQE4}m+`ekJk)bv;*;qwAd0811TR(6ZH_9BaBA!8Hsn1sfigJ{A?9d>jWhs;{XPCd2rT6Ccb_$e`|$#oa;DH325iZ41I z9Igg`%*FX*lmljtIz0v}*x=QG$Ib#(9*47X#ad z%>iGvXK$1EHU@v*k4EcFpBk-Bnz!+b`4vX4Qo}rsMx{j}vx+q$T+~Am>XnAD3=R&a z?Hg1gtb#X;kd9P`jZep>qu%MLf(j|Y?!c0ejCm)o{<|!rRCzWB!8{*lQastr~VkXnadJ_Ua2dM!=z$a+O<~qTScfoDsKDhfS4y8|ahL?0jE; zm_K{|yr#wd?RRuSEA84=b%41aUPfdJ=(IAF3sVMvVC9G;bC4v1Lp|(N8I_935YNXZ zNi$Ijfg}rTm-{|Y7T7@rXN+bLK`M4ow}gsvIHXI%==)$j9h5U8H-LztHa0rfKx$(3 z@UB=73jd-~6Vtw?L;4&d)k72s^e~oMd#9;u)vAOlaz4nGp%)0f9gzErL9=9pOm>NB zIUIJG0QW$PVe87u%ATnX+jvbUs`a7Au!YC_0OtMljDVfj^liTTuH2+iK=rGI*gJLL z#S{-RKHg@R#0QAWEftAuc3GytCR&_qhAsg%{m~ecW+2s6B!@Ti(kV9vDP&Z64|$L1 z*eE?6XseCiy2y9}?$sy^8JROIbLK^b)GbRB7~T}JyikSA7+d45c9CSesQBwp#7=E< z$f=+mb1&@n!7RDq3wfl!@)`!bMs|t}kh7d5-cStYJYZgjJ&R!@{*)vc`bDx5mt(#}J@K&(A+9E0Zv<^uzk;4a}B$1F1%JVakk(~#*To`zrpg38IWyzJiob)zwu*qN-D(~O_F}W@l1mlsUL)aTnT&8)KgDh`O#y-vmUWZ%gwDi_4e71aLn>HI_AO7 zH`FsC_kerQ&Rh13D0~~=THJv7c70n~_DM0!pE-9PFwbT#Q0+28jIk*s8TJ~LEg+Z% zy!<40q*jt15IXV$)9hG>!m9)OQt=q44_j1`>{MITfqL@DY&YZFG+$CQu8KZZ^md%$ z9$_byCYu=5=OH{9sS|`!f>EG=B_2_Q&ywg!#+HdTL#0IPkXez6$Bs)##Cxm*TVyY| zMZ%{>w(TsjqjpOn;8_@}@ohAJUdH^9r-@wMMo_1h59?Jv@O)xd(2-(qRXhk*|LO^aIJa*=9Wl zoW-zGZy@JYHohNL=pKj|7BJu9MIUxvi($`e{ji|*frk3AUf-!u%bLj46;kcknuoZh zUJ4HzRWTVfyfnMQt_a0l$6_VHlG~TAuZ`#AZG`+;P3_G#ps9%kQ8A%Py)R9U_ zxpp!6UDEYc2aqaVD8g!(hg@Zo?R<`*Z{sId=^`;KwewEZ&bRPs z=k+=)#IS(*$7Iq0N?4`U*tv&$q&Rkn-f8>LH$kacZ7erae|L=D!PJj8ET4kU+P|Iv%rh>rO>Zs$8s9%+qv z-Wyn*Cwc?5nG3u(P|-2N-at3uRVo^-*c(`QtXI1}elV|d%mDMC^+DVijAuy;l0(pg zO>&Wx-)=N3$vOrM=U98i5EW;k@~M$dr3;(RAXB81_>eLwO4a3MtfvOs>97xaU2x%3 zyNM4bYrnf}5~&zz14`micqzyY#t8)uS@lk#O`;;D)1(_iv8{OT)OBa*<>^PSIaZ|t zWs>W{YfSVmfMF@#Ah1MBagMKnM}vEy{#XqfkJ z=k;7AaEX!MP76sQe~9D8OdY0G1iEz8FEiITP!ByHWgZiqDQ2*<>`t`X=17xDRihpw zCS*|i)HLedE!d1?5)B=+B#lEg%eHg>hvm$vN*FR5iQ_GB$fc8rXp$`Is|1p6;bSY+ zQVYQWQa@!@x|R_Mu+f85muji$cJqun;|v(T(xFR@ykz#7Kf_+5BOKrj3jxoLYZ>1L z?ELQC3g#Q^hgJCV68E5Sb=Zb6&w2w33L$TUJxWr$2T7rGjt$mK)wE0%Wv#=pLvAS* z^hKLeLSP<)?P%2H7U?U#6ZAWXbAzy>No}V{k-YSOml3b;S+j)w4a}4V?_ruZ{ zR`+dQY=m!P?piVrd?ym_YR{b_wIm0#Hd)A;4&9>-9fTVokDF9pFOn@ZuVPZP!z&0m z1C$;^hhm%j89Jwcmq)pd!yjoRLhS>15Woahh|L z$9nqp=i`}<8OYDG-X$smNePn`Qg?=!|41`1>JU{shN2E^mqrzrDq*w*CYk3ix0gx> zgc~3=E-6$2q=AV>43`^w^c=j8$rAAB2swEtwbL#98I#j47IS7Cf)Z0Livw6||0;1M z>S=gnB|Y|6)K|f5Ic0@mc?dDg-(UAz^rcjhtO3u>KK0wturnWqE_j9mp5<*=O^Rc_ zNgA!$sz$535gM(uXQV*K{EL1tf8iqN5`XuojLry?dP}*vwmFD?5o%o6RSAnqGB`%= zKXnnyEOd%6VM0A{qFL%fSD)co)M(ru)U}|+O+#mZjM8(6pwi_do#@3;swjxz;CVu{ zz%)fk+VQHHbHWtxlvX=pqN`CckMn1$6+#PbJ5@SQDY@53Cu#?E)Jh{J`Jc;KM}Q?a z$VhZY#6nWNnmaA*pr-x_bM@KF7^=~SM^BzV^MoHiVO-)eiurB6wYbTASTEt2HL5pI zj$v`0RDpjxzcwD~u;ANh^m%!#m^2#LX0H?%f}&jQRQpo73vEmp;J&ooDTSz1@)Si~ zsL`%6_`oh4vC5mTo#y#>w1LisYLL8;WKOo52EOz3Bdd+l)Cvwil{+lhMS()rE|b!i zGE>rUypbw{T77`AeaNyhOnxK`zq#E5#t%+z++m5 zhOXq^QG=N04wZ4cVVH*+mf_nxf?TD)7`9QGPFYXW4_k1oN4wrWF%Ozf%+7!N@DXaF z5+O%n@D;)K3cb1{_2`kVDC`xbTE0X955)wWinOl+crgN7ZYiZ|?UXY{b=pny2;Am5 zGB}2eR#DC4fJN?w=g&!9$+!i$grtB(Zo#w+t*&CF-c(4fRwPfcN6%x`?nE)q;K%%S zA)2i>WwU3f7lhdx3{es4M(UE^9TZcqING%Z=3S#4;L`298T0mMo0Zu}eVanS ze4pliFb{bf@aG?tl~qb(uZcJk9|t2yj7}kNWRu;JD$=!ty%;u*d10$bjztU|vy)y7 zr<({(OZtrpH-?VAmQ<{*Cc@B(1Bgk=U{UzGf@;gvz)%?sJrfs!D|V6^i9rg{>KZ93 zG`Qfq&bea1Kt3eklYL(!f~c|vnxsG60ZnDcK`QK?f+F^^#;bzMC2wLraeB`w3iOoCPj zRO=DvbP>!y(J)`<1M~j5#5hAusY!9n`UlM6gr~H zPUp6o^_CiargAFA#9(yFdM_mLqC3@A^Ef;_Vd%krYD)%-V&qZ#g1aJK|FY7UbrcD3 zS`QU|wW@A}$wQjZrt)7F!6wPc5Z0EIZxr^Kr)vzyTZE7jfW15dt;RYjx8z>_TXXIO zla18EOCF1kf4mClGt9!XRsS~~^N%dJ2aTCC;%#DYpvyEXvj8yPySZ=vyoUM0;^GI7 z%7oN8&W=GDXIbfsBi2pAMk8QE{eLrw5qss3Qfs8XN&e7bg;isisau+qBn&kv9Xjy5Z2g<{nUz19rsCO%WNzIs5D7lBa-I|?9JH{#d)UnO$JB};RL$W z@Jg^}7F)+;wRy=YM+LoFj{V8ssJnuYruavY(_aDLpZxvrwFdWKSJP@zISurHt9+X@ ztD2N<=iB!F$d_*uw|dQ)bLaAT-Ui#K=?D+OK;UYVND{`jJ1Q@!@XZLF*j7PQ-z7~X zX(+10%x|}IE)Bc&pfWJ!5>SG%s;Va@4lDkWQ*pQW_+S^hMpQ!vuPK~#MrD36-(u!T z%CdXZaWZ##Scn4~oB<-rAys3Eyrh|&gDtDZl`H4``Mrk4ztwoFQm!?R;Xa8_LJ1J? zKBe((>gz$j@8QEoD|YQtYf_n}eptCr%EM=6daUNdvVK^Rt4uS^P}BQi3yyYg;~Vpu zKhN?u4<1%fMPI61ZD7yEhdK#ohOpd4UXe0n1PzQa!CY=Con%E{bW=f8A`#$Kqyw<@aOgXyv(b7>N>0+ zjaHJY)G*KcVKvMj(=hKlZ?jg6S+ZHZm~wKE1L?4mSCTmfgzGDY^m!Fw0W2iR^(n(~ zVtN`pD3Izz2V9wI+6i zwqk~_i5a>&dg!X9Lsl&vv~mfofh(5`Sh2YOipBkwFY3EIs!v>0uegOhmo4nEED~0? zWf5ItBf7+fcZmt_9J8QP%z}G+@m{1sA9a7_xk(oC@&VFRxH6{AWf=JTD z0m&@OS$BopF6K*eQ46QsWJG204AX7m(wJM8JTmoNW`)&qihGTT09mWM=w2Zsqow7w z;FN^Mv|q0d#b=N|BZ^8UwgB~i@EL}AidYI=8s5@Bq%IAHz4d?lJBwj;_Xcv60`qST z9`y3N@4Wi{d#|-@{@ZqK-ss%n%`P3^?AnQ~H@fJLxVqr#NLB~7e%o1Io!b9~u8!?q z@7VVB4%&LHL)#bIwS9SD|Cjsrw!}Q>m_e@c!NW(2+%ByXA&ZPu{D}>hQjwCR1`*v= zCkSJj2=hO@?k@yh%&xdd2k|N@7D=(_X{D0krFt3+=SKUIx0>|dRTF_PFP$L9LOf#R zy_BqLHrL!ZOK~F2-k6EIS_zf6pz zHWj3ts7LMKV>qNdj7t*rl_f7+C?I3>D`75$P!6bI7d9ffC<~noh&T03dr3>3(Q4I~^F|4k`!F_HzsRN* zRb4o@9l0^mkUNqCSNwJ%OwIf9_+Up189Jmg8u(-!0IshtaLWTv_A6Rr;@xwCzAI!! ztEF~n%1fmDcFPtd#Vt`Xiq)DzwaSD?^N!aA^0*jcrRF=cttPA(^lI>B=yFiI%@T|{ zCjVx(iZmx5RuizL-J&#~u5`{5h>R5bCg1g!(8v zb^>^b!OQEg0{=p-+9}NxpsX-5cgAiWhewoTNWR5mbm~bQ7}1WTlzxcDyVn@(?iQ+F za&fkr0A6b7a1xm#E2S?#zKt|xOws!?Xk*xRO7be&uS_g5bloY@(Mu?2J`yYv*PX?} z=Ies~NF5jj`HC4w*qhBhFV{X{+ykv9<-@mW2C&ra@IU(4+P8^kxk`;otW1-kqDdCC z8I&Y7an$9mQkNu@;p8e|lJK2{qAyYru+h(98XqPTa-4KnBzKTJmOAZR!nB$v*o6a6 zF>g{Bu*lLV8jnaeqk>C#H3^nNjq31d_XXe8C3=li8{G98b2bJ^>@=x}&Jw!ucy%Zp zPxRWcwEX5f1-+r5Gq3scby^*E`7R%Jo_HYt&|@F|JYfEU&M|vbWfvo`<+0axDyD?D z1LbnCo75oDt7js>RPWJ@c`3rBHv+;~Q!WanmWpLr7PLBLo+Q=UC8Z^!Gn%t^q<#&j zNDYSFksLR~>BkXx{E%z>!~5K}tn3$avZ4WR@)F{^V( zluL4Vpl%X%@|A#BaiWk*taA?@F5k8D_m=+r54HT*c`aA@sLVz@d75dbU^cp3<&gwZ z`ca+{Ch3F84N8hi7&2REzp9JK$k5y)N;smJ7_-kv8=z{vlBlD%nmIg%Y&#*9Q5w7{ z+3uCIF*Rm`Pe|{zijs_1pTWui@8#)4a|^b}>d49)O8%Vk&1glHv~Scaz6wpnP%m9c zBDV3e`WaKUnx5D@pF|0_xR8y9V5_k%^By7hc94)J`R1gCg zAyetgR=juQ0App7B+@yLR~%TB^{YvUioF}=gdrMXC9gvLcIL*=SXDtRl4pEn9hvDy z7^CaLo_zB6|78b=e0YXm0?fD3@;1wN?`%GP#DvNHW`&Jd95Z3fhG|=N%-Oy>Y{%|- z+jr01v3K5%Pv>v_By7u`nOkQ_Lvu^$9b?Zm1iyyUi-H0{of|`#R{K~K) zFZb)?NB2OBVF?F@+F=J7t6?{(I8u_Zm?>adlA@hU@k}U&t5NC2%xotu)n4omb$P30 z!mw9XsG}WB+*^>fUu4f*>Fi*dV3KT|nd?HbH)5KYhXR{-WGK8B<c#IwkT5<%uaxG9hkE>RXVV7$hvk{H&8EG2d#@DROl%BUQ zjP5$T%S*j_T5=CGO()&AVg5W(65tZkGPdaf6;_gl=polprg#a@#O1^zpF$fp{=<_p2-FtsAUQ$L61UNE~N(c?|T6|-L< z7JnuDFDzN{2!4ibX4uUMbF}1U8_rBLg}QOT;Z0wUFIKJ&H1gz%;P#)zu)BAvn9po@ zZlEXY88EN&=Wm}M)n#zUhBR7>SxpLuoLZB(cz`K4Xp@}0rIL)?>x2>%#69ZMAPiby+9+3=^#f>Msan}$V2kK?#bWTqwddZm}mZc z=Sd@e_6rJ!pFe%(Y+in$rekKaEBSdMFX;@~KRs z`G^b!ljamiGN+zuY3Pi6JMa}9kTGFYD-ZcL7K>1EjB>5nUsB|(mEXF#q>S6t=xt^~ z(hga(Bg7j9rLvUL#7*-y)bgAE%1oz`i@8z`hrs^tI_96~E>^wHpRfJy;lsFHJ337o z(Wrjd-0wl(#>Mq*Zk-?5X;24Wb4Eay`1ILxFi#4&2UW6k43!8I^M4&I1LGAvN-QU% z`y_TBdNM@U2!U$R^r}{lJYtTF@ms+P@N!8pw9-bN5NVJ~7M)6j(McQ%0gU<;)RRHF z7qhe&)e11niAOa7PGx(U(bmnonTQSor%JMsw7b^(A_-syo~C8c4l89N^YbFLF1zkD zIHf-_ldk*o|H~E$W;MQzhWQ5%;&$zPx)|2ucg_g*2D*IO`H`Ilw)fK;2$%=o=3!a6 zLuN3_Nfl{ase|Vd4c}WHOD*UDM03U^Pd>8hAtL>oRE*2Y(h?Pd8mVJ4=Bsarb{kbwV+#j>F&|k(%(`IYi0Hy!U8&BM^7BE_o2 zis8S)ObaF((R){ZtG@t##a{Wp`U|wB{OF4fD&<&NtW_NbP)9lQmkM zvW^*GzT?35eter#r_bc%=E1B?)OAGRNA!?sm`N){=6)g<6|(c7Ywc0w9F-7Nq|Hem z98UOB<$1ZLG}9#@G{qXrtfML3G^hez5pvd0nmV!VlIDiwdvkiZq6VXs?~y!KE9(!K z^5OGh3aN=|(%BVXb>$o+nj$eniKC?R@GUtXXsX#L0}ztS`R$ z3N#I<{tPB$jF}p`@kb+18%v#1_~{WXOR~RhN@(er=p(ww1ko>%HQ^4^A4l6|(dOIK;rb z491^1MP7n@T509SH5znl%}RRM@nM;M*YIFs5g_Jk>z>FIFPZ42k{-nYo71N$Y}D5? z2D{Oe%74X=jf46p1`~`NFhBfni@qFqBqbHph^+tJ^BANBr2~`AQHcvFQsEP(;JQCaWA* zA&JiJq>48-St{J&!iHRzIq&3P_d&z)^SIo`(ywl1;kBtp;@Yu?U5v&hsrg+sP>%u+ z0hXt_TE9-&(yu=45;In{ekJNld%n6xEU$5(|F#y=hjq1l@C|*e)h9sFM}1I8Jzv^u z>d2*3$0h^TvqvprWtUQ|HcS)Dg%^xGG(F$Bdw0o}%^ib=OIqBS4QX-ba66wR#jtKN zXGF(mrQG2|54-Rg_x_eJs z>{wg(?%x06&b|BEx~m=Ey?6hv_T#;KUwlESE^);+-;PGp)4BveF)i-eb4TM7avGxoce*^2g2j4z;pdT2=u)fn5^xA*- z;NgP@kF>{!#-p(wY3t#mGU&F@7T6PgJ<=?RSel8zRj#eaU_&Y@%B5AwRz)SQs!CW@ zRd5UkRhq4Ue>UARXg~60XOBQFv}unHKrbvjaKP;wjDYrEb{}`@&uBl|HN&F4TSY}B zIP+QA*$dXK?J#lJ&L6xcReEliFMs~mg9b###H?Jkddt>rdp`N}^8*JD9X)#V#EIi4 zPo6k+>cpwjCt;mFed5GPcw`F#-jk=#oH}#%_O*REZ^aaCK_VZrhB>o=}l zhlhmg2?^}++6{O{TkzwJgv1+(iSXo&#H56zB=|8Q5mr)SlC~1`m6)8Il$@+TlWZKr zV@gU&YAUQ`wvx4)6!B>wUwHdt{tbPfjQBS)6>#3^aTg$=@}XNx|wk^Gb7_> z8XVt**mc&vA`DTD-%|hu>ee#vMEs6dJ2gH*1AO(qjMMxB2>?!~ZuqCH2&)(~}l0Y7;Pc z=dylHnt%M(M}ekMOt#)*7}JuUQkdT0drwYu0K{u3fui-P*-#;b6^@ zwQIE>ajjdobR8V5<3Fxl3eUvHlcoHqt(f@uXtH9~uh-Uw4SezAjm9&vur_Xt-LNrM zKVG(xJ)=FjY~v<6S^UKLvv3c{#&6`*7wQ`H` z^k=rL+`4t;)-Br7j<>AZx^?xot?;P7U<>>?e59Gt(I1T)`_b5e zyEM#?XaMs$emQ2DP1CC^IWx3le{0|7*TaUqGH}3a{rkSwr&qJyuzEIn_4-Gz*Pfg8 zdY|4ut){Nu^y~9l|9-z7IN+o4gLW_PkC+!RY`%32i!&D*uS+a2k1_07z{R59;gqcy8xjaHZJ+Zc6NmbWo{o3@s|jn*L^ zIKElHIBks|OV^~><@-K6%+|`yJGD+L`32AC>K`8Q$tMTd$6mc1}vKvv|@Td+_d3KLOX>`{!?(^Yo_n5N$brCf%qlE>NS7CKgE~)Zb)$K zrfuvq@BQT9gqUR(FJqtz9N#*0^uE=D&h0QT|CIi`FaErbyiLiOA%OXpd|=*J^X%m1 zs}3AE&erPfd)}Bd@rP{Ped^VJVbo`zA7LMR-S)k$X3u_#H+g+x;M-Fsc3BXzblbd~ z#JKYOHC2Uc>_uxGu!^-uIIb#KQ$iZof5ZYC;QC%K0i7!Zl#|Ww+S7yZ%rdG?>kq?>^$X|G0eMp-bTvLbIf<> z-^Qn%N1-$MxbwXEVe2-|i`UlJn7G%iUh^Ctx12p|=9+l+u>)e4|0#4zQ{U%z!4pQu zPTPMp@^RjZy4!1D)$7a8F}(7VLlMJcrULSt^#A%z(1aQM)55p?-t=j&{WE;ezYCr; za%l|v*t6HJ?=pYBpV#Y{ABdQ5xDJ~mbBQypV^}w>!=~{%EU!tGoF424^I*U`&YwRl zcG+zgM#}nT0DR69{AWx59|+Ev!-R}bpEkJ`$a8+kKKFG zqBkc8`FhVbb0@FeGxzJPn7Uibo3v_gEx(@;z4^0wAI_X)`Obd~oiZdw``&s+M6!!#}F>g6f3NZivteJ;y+$j3ui=r>?FJHH9^thRUlfrf#Jf5CcaN^{}{rip| zKYl(nH~*6(rzVF+z;B~hY%08eA6}nYRP>J)Uh{MOR%pn^Gp7r`xLHPyX#KWhB50I3T3!i7q_?Zi%*IvJwdHH(s z!2>5@!NK*LSqo#$zvp2qSEN7{>WeQDvvOw6jfM{vI&;zG zw2Xwb%)^Jz?En1u#Vd*Ms}=Fv$4!_sF?jyIqo>(6R~$OX-h}x!XBEurcD|a#u+@Z1 z>?LzXJd{g(Q=T)DCUMMgh8pB;iW%m8*!dP=b5n|nY#z7W>)O3zf9QmHhYy}gznQyW zX7tnv^QQ!a!81|w;t~?mj~qHZeNyJJY8gPECf-Z1cD- zUc3=JZqBClyYdT5mq)LkHqrRl^Jhk%J$v;=LfVwTu=v&6zJB-+USIs}*L`E7mG=a| zkByyjH!Y&NXi;skv}%eMxe6AQ=R~;*wcjeQhj++J2p-Gv3_c+o?ApFR<7Q6m;&t!>co}Cww5;f; zHSo1h4V)VmGH+_&95{~A&r$TkHCs0A1(?C>b<79w75D=mBj#ZN=I2gblvh}?YS||E zs8a&wz{j3GVJ;lQ^VxYtYgTMtzk0ijdGVeA_`!=OKge8AQxu`B$Jyc6&d=Srcly*7 z69+FC-D%pewo`_5o<6kWw9x_4K^ynZNW2(UmKARP!_wsWqho^UX9mo(@9mq1kERDL zICkXhv7=|FPg)Q%ZqDpUVY4TPg^ZuWpFamcf|s$^>zM!1zGoftj}6R6uHN_QQLug? z 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 literal 0 HcmV?d00001 diff --git a/doc/testbenches_doc_resources/5g-ota-bench.png b/doc/testbenches_doc_resources/5g-ota-bench.png index e72263629dcd691145a0f0d8d46cf252d28d1afb..e2997f63a1bb081dc02ad7c9873be64bb0659af0 100644 GIT binary patch literal 82211 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�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 Date: Fri, 11 Apr 2025 12:54:05 +0200 Subject: [PATCH 62/63] Modify `ci_infra.yaml` and XML to start CN from Grace Hopper --- ci-scripts/ci_infra.yaml | 4 ++-- ci-scripts/xml_files/container_sa_aerial_quectel.xml | 10 +++++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/ci-scripts/ci_infra.yaml b/ci-scripts/ci_infra.yaml index 4d1a87ddba..64432f6188 100644 --- a/ci-scripts/ci_infra.yaml +++ b/ci-scripts/ci_infra.yaml @@ -77,8 +77,8 @@ oc-cn5g-20897: Undeploy: "! scripts/oc-cn5g-undeploy.sh /opt/oai-cn5g-fed-develop-2025-jan oaicicd-core-for-fhi72" LogCollect: "! scripts/oc-cn5g-logcollect.sh /opt/oai-cn5g-fed-develop-2025-jan oaicicd-core-for-fhi72 %%log_dir%%" -oc-cn5g-20897-aerial2: - Host: aerial2 +oc-cn5g-20897-aerial: + Host: gracehopper1-oai NetworkScript: echo "inet 172.21.6.105" RunIperf3Server: False Deploy: "! scripts/oc-cn5g-deploy.sh /opt/oai-cn5g-fed-develop-2025-jan oaicicd-core-for-nvidia-aerial" diff --git a/ci-scripts/xml_files/container_sa_aerial_quectel.xml b/ci-scripts/xml_files/container_sa_aerial_quectel.xml index 6a3e7f8057..ccb9500a91 100644 --- a/ci-scripts/xml_files/container_sa_aerial_quectel.xml +++ b/ci-scripts/xml_files/container_sa_aerial_quectel.xml @@ -44,7 +44,7 @@ DeployCoreNetwork Initialize 5G Core - oc-cn5g-20897-aerial2 + oc-cn5g-20897-aerial @@ -90,7 +90,7 @@ Ping Ping: 100pings in 20sec up2-aerial - oc-cn5g-20897-aerial2 + oc-cn5g-20897-aerial -c 100 -i 0.2 5 @@ -99,7 +99,7 @@ Iperf iperf (DL/UDP/500M)(30 sec)(single-ue profile) -u -b 500M -t 30 -R - oc-cn5g-20897-aerial2 + oc-cn5g-20897-aerial up2-aerial 25 80 @@ -109,7 +109,7 @@ Iperf iperf (UL/UDP/40M)(30 sec)(single-ue profile) -u -b 40M -t 30 - oc-cn5g-20897-aerial2 + oc-cn5g-20897-aerial up2-aerial 25 80 @@ -145,7 +145,7 @@ UndeployCoreNetwork true Terminate 5G Core - oc-cn5g-20897-aerial2 + oc-cn5g-20897-aerial From b790fb235d6c0c9aba682815ea7356f39b41f8e1 Mon Sep 17 00:00:00 2001 From: Robert Schmidt Date: Mon, 14 Apr 2025 21:29:34 +0200 Subject: [PATCH 63/63] Add SIB2 and SIB19 to NTN-LEO config --- .../conf_files/gnb.sa.band254.u0.25prb.rfsim.ntn-leo.conf | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ci-scripts/conf_files/gnb.sa.band254.u0.25prb.rfsim.ntn-leo.conf b/ci-scripts/conf_files/gnb.sa.band254.u0.25prb.rfsim.ntn-leo.conf index 729bb4d6f0..fc591cd464 100644 --- a/ci-scripts/conf_files/gnb.sa.band254.u0.25prb.rfsim.ntn-leo.conf +++ b/ci-scripts/conf_files/gnb.sa.band254.u0.25prb.rfsim.ntn-leo.conf @@ -20,6 +20,8 @@ gNBs = min_rxtxtime = 6; num_dlharq = 32; num_ulharq = 32; + cu_sibs = [2]; + du_sibs = [19]; servingCellConfigCommon = ( {