From a4718d4a16db44de9ebf7af00d849dfc46863c07 Mon Sep 17 00:00:00 2001 From: Cedric Roux Date: Fri, 17 Mar 2023 16:39:06 +0100 Subject: [PATCH 01/89] bugfix: LDPC decoding in the gNB: clear d properly The d array has to be cleared at the first usage, which may not necessarily be when new_rx is true. For example, in case of DTX detected for a first transmission, the next transmission won't have new_rx true, so d won't be cleared, containing data from a previous transmission. Or when a LDPC decoding is cancelled for a segment when new_rx is true, a decoding for a retransmission will not clear d because then new_rx will not be true. (Yes, the logic of cancellation will be changed in the future, but the changes will be orthogonal to the ones of this commit.) new_rx is removed. --- openair1/PHY/NR_TRANSPORT/nr_ulsch.c | 6 +++-- openair1/PHY/NR_TRANSPORT/nr_ulsch_decoding.c | 23 +++++++++++++++---- openair1/PHY/defs_gNB.h | 8 ++++++- 3 files changed, 30 insertions(+), 7 deletions(-) diff --git a/openair1/PHY/NR_TRANSPORT/nr_ulsch.c b/openair1/PHY/NR_TRANSPORT/nr_ulsch.c index 9fb95ac273..d7098b4c8e 100644 --- a/openair1/PHY/NR_TRANSPORT/nr_ulsch.c +++ b/openair1/PHY/NR_TRANSPORT/nr_ulsch.c @@ -74,9 +74,11 @@ void nr_fill_ulsch(PHY_VARS_gNB *gNB, harq->slot=slot; harq->handled = 0; harq->status= NR_ACTIVE; - harq->new_rx = ulsch_pdu->pusch_data.new_data_indicator; + if (ulsch_pdu->pusch_data.new_data_indicator) + harq->harq_to_be_cleared = true; LOG_D(PHY,"ULSCH ID %d RNTI %x HARQ PID %d new data indicator %d\n",ulsch_id, ulsch_pdu->rnti, harq_pid, ulsch_pdu->pusch_data.new_data_indicator); - if (harq->new_rx) + + if (ulsch_pdu->pusch_data.new_data_indicator) harq->round = 0; else harq->round++; diff --git a/openair1/PHY/NR_TRANSPORT/nr_ulsch_decoding.c b/openair1/PHY/NR_TRANSPORT/nr_ulsch_decoding.c index 0bb872852b..3c6a16418e 100644 --- a/openair1/PHY/NR_TRANSPORT/nr_ulsch_decoding.c +++ b/openair1/PHY/NR_TRANSPORT/nr_ulsch_decoding.c @@ -77,6 +77,7 @@ void free_gNB_ulsch(NR_gNB_ULSCH_t **ulschptr, uint16_t N_RB_UL) } free_and_zero(ulsch->harq_processes[i]->c); free_and_zero(ulsch->harq_processes[i]->d); + free(ulsch->harq_processes[i]->d_to_be_cleared); free_and_zero(ulsch->harq_processes[i]); ulsch->harq_processes[i] = NULL; } @@ -111,6 +112,8 @@ NR_gNB_ULSCH_t *new_gNB_ulsch(uint8_t max_ldpc_iterations, uint16_t N_RB_UL) ulsch->harq_processes[i]->c[r] = (uint8_t*)malloc16_clear(8448*sizeof(uint8_t)); ulsch->harq_processes[i]->d[r] = (int16_t*)malloc16_clear((68*384)*sizeof(int16_t)); } + ulsch->harq_processes[i]->d_to_be_cleared = calloc(a_segments, sizeof(bool)); + AssertFatal(ulsch->harq_processes[i]->d_to_be_cleared != NULL, "out of memory\n"); } return(ulsch); @@ -151,6 +154,7 @@ void clean_gNB_ulsch(NR_gNB_ULSCH_t *ulsch) /// code blocks after bit selection in rate matching for LDPC code (38.212 V15.4.0 section 5.4.2.1) //int16_t e[MAX_NUM_NR_ULSCH_SEGMENTS][3*8448]; ulsch->harq_processes[i]->E=0; + ulsch->harq_processes[i]->harq_to_be_cleared=true; } } } @@ -237,7 +241,7 @@ void nr_processULSegment(void* arg) { harq_e, ulsch_harq->C, rv_index, - ulsch_harq->new_rx, + ulsch_harq->d_to_be_cleared[r], E, ulsch_harq->F, Kr-ulsch_harq->F-2*(p_decoderParms->Z))==-1) { @@ -251,6 +255,8 @@ void nr_processULSegment(void* arg) { stop_meas(&phy_vars_gNB->ulsch_rate_unmatching_stats); } + ulsch_harq->d_to_be_cleared[r] = false; + memset(ulsch_harq->c[r],0,Kr_bytes); if (ulsch_harq->C == 1) { @@ -369,7 +375,7 @@ uint32_t nr_ulsch_decoding(PHY_VARS_gNB *phy_vars_gNB, float Coderate = (float) pusch_pdu->target_code_rate / 10240.0f; LOG_D(PHY,"ULSCH Decoding, harq_pid %d rnti %x TBS %d G %d mcs %d Nl %d nb_rb %d, Qm %d, Coderate %f RV %d round %d new RX %d\n", - harq_pid, ulsch->rnti, A, G, mcs, n_layers, nb_rb, Qm, Coderate, pusch_pdu->pusch_data.rv_index, harq_process->round, harq_process->new_rx); + harq_pid, ulsch->rnti, A, G, mcs, n_layers, nb_rb, Qm, Coderate, pusch_pdu->pusch_data.rv_index, harq_process->round, harq_process->harq_to_be_cleared); t_nrLDPC_dec_params decParams = {0}; decParams.BG = pusch_pdu->maintenance_parms_v3.ldpcBaseGraph; int kc; @@ -399,7 +405,7 @@ uint32_t nr_ulsch_decoding(PHY_VARS_gNB *phy_vars_gNB, stats->power[aarx] = dB_fixed_x10(pusch->ulsch_power[aarx]); stats->noise_power[aarx] = dB_fixed_x10(pusch->ulsch_noise_power[aarx]); } - if (harq_process->new_rx == 0) { + if (!harq_process->harq_to_be_cleared) { stats->current_Qm = Qm; stats->current_RI = n_layers; stats->total_bytes_tx += harq_process->TBS; @@ -452,6 +458,13 @@ uint32_t nr_ulsch_decoding(PHY_VARS_gNB *phy_vars_gNB, #ifdef DEBUG_ULSCH_DECODING printf("Segmentation: C %d, K %d\n",harq_process->C,harq_process->K); #endif + + if (harq_process->harq_to_be_cleared) { + for (int r = 0; r < harq_process->C; r++) + harq_process->d_to_be_cleared[r] = true; + harq_process->harq_to_be_cleared = false; + } + Kr = harq_process->K; Kr_bytes = Kr >> 3; @@ -523,7 +536,7 @@ uint32_t nr_ulsch_decoding(PHY_VARS_gNB *phy_vars_gNB, harq_e, harq_process->C, pusch_pdu->pusch_data.rv_index, - harq_process->new_rx, + harq_process->d_to_be_cleared[r], E, harq_process->F, Kr - harq_process->F - 2 * (decParams.Z)) @@ -533,6 +546,8 @@ uint32_t nr_ulsch_decoding(PHY_VARS_gNB *phy_vars_gNB, return 1; } + harq_process->d_to_be_cleared[r] = false; + // set first 2*Z_c bits to zeros memset(&z[0], 0, 2 * harq_process->Z * sizeof(int16_t)); // set Filler bits diff --git a/openair1/PHY/defs_gNB.h b/openair1/PHY/defs_gNB.h index 9c338b5d3f..8d8981c069 100644 --- a/openair1/PHY/defs_gNB.h +++ b/openair1/PHY/defs_gNB.h @@ -191,7 +191,9 @@ typedef struct { uint32_t slot; /// Index of current HARQ round for this DLSCH uint8_t round; - bool new_rx; + /// flag used to clear d properly (together with d_to_be_cleared below) + /// set to true in nr_fill_ulsch() when new_data_indicator is received + bool harq_to_be_cleared; /// Status Flag indicating for this ULSCH (idle,active,disabled) NR_SCH_status_t status; /// Flag to indicate that the UL configuration has been handled. Used to remove a stale ULSCH when frame wraps around @@ -213,6 +215,10 @@ typedef struct { uint32_t C; /// Pointers to code blocks after LDPC coding (38.212 V15.4.0 section 5.3.2) int16_t **d; + /// flag used to clear d properly (together with harq_to_be_cleared above) + /// set to true in nr_ulsch_decoding() when harq_to_be_cleared is true + /// when true, clear d in the next call to function nr_rate_matching_ldpc_rx() + bool *d_to_be_cleared; /// LDPC lifting size (38.212 V15.4.0 table 5.3.2-1) uint32_t Z; /// Number of bits in each code block after rate matching for LDPC code (38.212 V15.4.0 section 5.4.2.1) From dcdf057dc89ffa62b50c1cc2ae192cccce9568ee Mon Sep 17 00:00:00 2001 From: Roberto Louro Magueta Date: Tue, 18 Oct 2022 19:10:11 +0100 Subject: [PATCH 02/89] Enable T304 after receiving the reconfigurationWithSync --- openair2/RRC/NR_UE/rrc_UE.c | 36 ++++++++++++++++++++++++++++++++---- 1 file changed, 32 insertions(+), 4 deletions(-) diff --git a/openair2/RRC/NR_UE/rrc_UE.c b/openair2/RRC/NR_UE/rrc_UE.c index ecbaeea17e..9f47cf7640 100644 --- a/openair2/RRC/NR_UE/rrc_UE.c +++ b/openair2/RRC/NR_UE/rrc_UE.c @@ -1286,10 +1286,38 @@ nr_rrc_ue_process_masterCellGroup( xer_fprint(stdout, &asn_DEF_NR_CellGroupConfig, (const void *) cellGroupConfig); } - if( cellGroupConfig->spCellConfig != NULL && cellGroupConfig->spCellConfig->reconfigurationWithSync != NULL){ - //TODO (perform Reconfiguration with sync according to 5.3.5.5.2) - //TODO (resume all suspended radio bearers and resume SCG transmission for all radio bearers, if suspended) - // NSA procedures + // TS 38.331 - Section 5.3.5.5.2 Reconfiguration with sync + if (cellGroupConfig->spCellConfig != NULL && cellGroupConfig->spCellConfig->reconfigurationWithSync != NULL) { + + LOG_A(NR_RRC, "Received the reconfigurationWithSync in %s\n", __FUNCTION__); + + NR_ReconfigurationWithSync_t *reconfigurationWithSync = cellGroupConfig->spCellConfig->reconfigurationWithSync; + NR_UE_rrc_inst[ctxt_pP->module_id].Info[gNB_index].T304_active = 1; + switch (reconfigurationWithSync->t304) { + case NR_ReconfigurationWithSync__t304_ms100: + NR_UE_rrc_inst[ctxt_pP->module_id].Info[gNB_index].T304_cnt = 100; + break; + case NR_ReconfigurationWithSync__t304_ms150: + NR_UE_rrc_inst[ctxt_pP->module_id].Info[gNB_index].T304_cnt = 150; + break; + case NR_ReconfigurationWithSync__t304_ms200: + NR_UE_rrc_inst[ctxt_pP->module_id].Info[gNB_index].T304_cnt = 200; + break; + case NR_ReconfigurationWithSync__t304_ms500: + NR_UE_rrc_inst[ctxt_pP->module_id].Info[gNB_index].T304_cnt = 500; + break; + case NR_ReconfigurationWithSync__t304_ms1000: + NR_UE_rrc_inst[ctxt_pP->module_id].Info[gNB_index].T304_cnt = 1000; + break; + case NR_ReconfigurationWithSync__t304_ms2000: + NR_UE_rrc_inst[ctxt_pP->module_id].Info[gNB_index].T304_cnt = 2000; + break; + case NR_ReconfigurationWithSync__t304_ms10000: + NR_UE_rrc_inst[ctxt_pP->module_id].Info[gNB_index].T304_cnt = 10000; + break; + default: + NR_UE_rrc_inst[ctxt_pP->module_id].Info[gNB_index].T304_cnt = 50; + } } if(NR_UE_rrc_inst[ctxt_pP->module_id].cell_group_config == NULL){ From b58ad4baf0b44f17e7e1799bccb9707e95b0446c Mon Sep 17 00:00:00 2001 From: Dhanuja Elizabeth Thomas Date: Wed, 22 Mar 2023 15:18:30 +0100 Subject: [PATCH 03/89] Introducing new pipeline for LTE TDD 2x2 in container Following changes are made: 1. Removed XMLs used in old pipeline lte-OAIEPC 2. Added TM1 and TM2 config, XML and YAML 3. Added pipeline details in TestBenches.md 4. Modification in Jenkins file for the pipeline 5. Added ping/iperf test cases in XML 6. Minor modification in cls_containerize.py and cls_oaicitest.py --- ci-scripts/Jenkinsfile-GitLab-Container | 21 +++ ci-scripts/cls_containerize.py | 2 +- ci-scripts/cls_oaicitest.py | 2 +- .../enb.band38.lte_2x2.100PRB.usrpn310.conf | 8 +- ...nb.band38.lte_2x2_tm2.100PRB.usrpn310.conf | 8 +- .../container_lte_n3xx_tdd_2x2_tm1.xml | 160 +++++++++++++++++ .../container_lte_n3xx_tdd_2x2_tm2.xml | 160 +++++++++++++++++ ci-scripts/xml_files/lte_2x2_tm1_quectel.xml | 164 ------------------ ci-scripts/xml_files/lte_2x2_tm2_quectel.xml | 164 ------------------ .../lte_n3xx_tdd_2x2_tm1/docker-compose.yml | 18 ++ .../lte_n3xx_tdd_2x2_tm2/docker-compose.yml | 18 ++ doc/TESTBenches.md | 3 + 12 files changed, 390 insertions(+), 338 deletions(-) create mode 100644 ci-scripts/xml_files/container_lte_n3xx_tdd_2x2_tm1.xml create mode 100644 ci-scripts/xml_files/container_lte_n3xx_tdd_2x2_tm2.xml delete mode 100644 ci-scripts/xml_files/lte_2x2_tm1_quectel.xml delete mode 100644 ci-scripts/xml_files/lte_2x2_tm2_quectel.xml create mode 100644 ci-scripts/yaml_files/lte_n3xx_tdd_2x2_tm1/docker-compose.yml create mode 100644 ci-scripts/yaml_files/lte_n3xx_tdd_2x2_tm2/docker-compose.yml diff --git a/ci-scripts/Jenkinsfile-GitLab-Container b/ci-scripts/Jenkinsfile-GitLab-Container index 31ea662e3c..0a033cf760 100644 --- a/ci-scripts/Jenkinsfile-GitLab-Container +++ b/ci-scripts/Jenkinsfile-GitLab-Container @@ -385,6 +385,27 @@ pipeline { } } } + stage ("LTE-TDD-2x2-Container") { + when { expression {do4Gtest} } + steps { + script { + triggerSlaveJob ('RAN-LTE-TDD-2x2-Container', 'LTE-TDD-2x2-Container') + } + } + post { + always { + script { + finalizeSlaveJob('RAN-LTE-TDD-2x2-Container') + } + } + failure { + script { + currentBuild.result = 'FAILURE' + } + } + } + } + //avra is offline, re-enable once it is available //stage ("T1-Offload-Test") { // when { expression {do5Gtest} } diff --git a/ci-scripts/cls_containerize.py b/ci-scripts/cls_containerize.py index 9a00ec3dfe..9aa493b3cf 100644 --- a/ci-scripts/cls_containerize.py +++ b/ci-scripts/cls_containerize.py @@ -844,7 +844,7 @@ class Containerize(): CreateWorkspace(mySSH, lSourcePath, self.ranRepository, self.ranCommitID, self.ranTargetBranch, self.ranAllowMerge) mySSH.command('cd ' + lSourcePath + '/' + self.yamlPath[self.eNB_instance], '\$', 5) - mySSH.command('cp docker-compose.yml ci-docker-compose.yml', '\$', 5) + mySSH.command('cp docker-compose.y*ml ci-docker-compose.yml', '\$', 5) imagesList = ['oai-enb', 'oai-gnb', 'oai-nr-cuup'] for image in imagesList: imageToUse = self.ImageTagToUse(image) diff --git a/ci-scripts/cls_oaicitest.py b/ci-scripts/cls_oaicitest.py index 9fa1dcbb71..2bd8ba4263 100644 --- a/ci-scripts/cls_oaicitest.py +++ b/ci-scripts/cls_oaicitest.py @@ -1227,7 +1227,7 @@ class OaiCiTest(): raise Exception("could not corver prod-trf-gen IP address") cn_target_ip = result.group('trf_ip_addr') SSH.close() - cn_iperf_prefix = "docker exec -it prod-trf-gen" # -w /iperf-2.0.13 necessary? + cn_iperf_prefix = "docker exec prod-trf-gen" # -w /iperf-2.0.13 necessary? else: # ltebox, sabox cn_target_ip = "192.172.0.1" cn_iperf_prefix = "" diff --git a/ci-scripts/conf_files/enb.band38.lte_2x2.100PRB.usrpn310.conf b/ci-scripts/conf_files/enb.band38.lte_2x2.100PRB.usrpn310.conf index f1d30c4b42..2042768933 100644 --- a/ci-scripts/conf_files/enb.band38.lte_2x2.100PRB.usrpn310.conf +++ b/ci-scripts/conf_files/enb.band38.lte_2x2.100PRB.usrpn310.conf @@ -172,7 +172,7 @@ eNBs = ////////// MME parameters: - mme_ip_address = ( { ipv4 = "CI_MME_IP_ADDR"; + mme_ip_address = ( { ipv4 = "192.168.61.195"; ipv6 = "192:168:30::17"; active = "yes"; preference = "ipv4"; @@ -191,11 +191,11 @@ eNBs = NETWORK_INTERFACES : { ENB_INTERFACE_NAME_FOR_S1_MME = "eno1"; - ENB_IPV4_ADDRESS_FOR_S1_MME = "CI_ENB_IP_ADDR"; + ENB_IPV4_ADDRESS_FOR_S1_MME = "172.21.16.128"; ENB_INTERFACE_NAME_FOR_S1U = "eno1"; - ENB_IPV4_ADDRESS_FOR_S1U = "CI_ENB_IP_ADDR"; + ENB_IPV4_ADDRESS_FOR_S1U = "172.21.16.128"; ENB_PORT_FOR_S1U = 2152; # Spec 2152 - ENB_IPV4_ADDRESS_FOR_X2C = "CI_ENB_IP_ADDR"; + ENB_IPV4_ADDRESS_FOR_X2C = "172.21.16.128"; ENB_PORT_FOR_X2C = 36422; # Spec 36422 }; } diff --git a/ci-scripts/conf_files/enb.band38.lte_2x2_tm2.100PRB.usrpn310.conf b/ci-scripts/conf_files/enb.band38.lte_2x2_tm2.100PRB.usrpn310.conf index c3d9b7c893..e50c1acfe8 100644 --- a/ci-scripts/conf_files/enb.band38.lte_2x2_tm2.100PRB.usrpn310.conf +++ b/ci-scripts/conf_files/enb.band38.lte_2x2_tm2.100PRB.usrpn310.conf @@ -172,7 +172,7 @@ eNBs = ////////// MME parameters: - mme_ip_address = ( { ipv4 = "CI_MME_IP_ADDR"; + mme_ip_address = ( { ipv4 = "192.168.61.195"; ipv6 = "192:168:30::17"; active = "yes"; preference = "ipv4"; @@ -191,11 +191,11 @@ eNBs = NETWORK_INTERFACES : { ENB_INTERFACE_NAME_FOR_S1_MME = "eno1"; - ENB_IPV4_ADDRESS_FOR_S1_MME = "CI_ENB_IP_ADDR"; + ENB_IPV4_ADDRESS_FOR_S1_MME = "172.21.16.128"; ENB_INTERFACE_NAME_FOR_S1U = "eno1"; - ENB_IPV4_ADDRESS_FOR_S1U = "CI_ENB_IP_ADDR"; + ENB_IPV4_ADDRESS_FOR_S1U = "172.21.16.128"; ENB_PORT_FOR_S1U = 2152; # Spec 2152 - ENB_IPV4_ADDRESS_FOR_X2C = "CI_ENB_IP_ADDR"; + ENB_IPV4_ADDRESS_FOR_X2C = "172.21.16.128"; ENB_PORT_FOR_X2C = 36422; # Spec 36422 }; } diff --git a/ci-scripts/xml_files/container_lte_n3xx_tdd_2x2_tm1.xml b/ci-scripts/xml_files/container_lte_n3xx_tdd_2x2_tm1.xml new file mode 100644 index 0000000000..480825bede --- /dev/null +++ b/ci-scripts/xml_files/container_lte_n3xx_tdd_2x2_tm1.xml @@ -0,0 +1,160 @@ + + + + TEST-LTE-2x2-N3XX-TM1 + 2.6 Ghz TDD LTE 2x2 TM1 + tasks + 1 + + +111111 +100000 +010000 +030101 +010001 +000001 +050000 +050001 +070000 +070001 +010010 +010002 +030201 + + + + + + Pull_Local_Registry + Pull Images from Local Registry + 0 + oai-enb + + + + Custom_Command + Disable Sleep States + obelix + sudo cpupower idle-set -D 0 + + + + Custom_Command + Enable Sleep States + obelix + sudo cpupower idle-set -E + + + + Deploy_Object + Deploy eNB (TDD/Band38/2.6Ghz/N3XX) in a container + ci-scripts/yaml_files/lte_n3xx_tdd_2x2_tm1 + 0 + 0 + + + + IdleSleep + Sleep + 5 + + + + Initialize_UE + Initialize Quectel + nrmodule2_quectel + + + + Terminate_UE + Terminate Quectel + nrmodule2_quectel + + + + Attach_UE + Attach UE + nrmodule2_quectel + + + + Ping + Ping: 20 pings + nrmodule2_quectel + -c 20 + 1 + 60 + + + + Ping + Ping: 100 pings, size 1024 + nrmodule2_quectel + -c 100 -s 1024 -i 0,2 + 1 + 60 + + + + Iperf + iperf (DL/26Mbps/UDP)(60 sec)(single-ue profile) + -u -b 26M -t 60 + DL + nrmodule2_quectel + 5 + 95 + single-ue + + + + Iperf + iperf (UL/7Mbps/UDP)(60 sec)(single-ue profile) + -u -b 7M -t 60 + UL + nrmodule2_quectel + 5 + 95 + single-ue + + + + Detach_UE + Detach UE + nrmodule2_quectel + + + + Undeploy_Object + Undeploy gNB + ci-scripts/yaml_files/lte_n3xx_tdd_2x2_tm1 + 0 + 0 + + + + + diff --git a/ci-scripts/xml_files/container_lte_n3xx_tdd_2x2_tm2.xml b/ci-scripts/xml_files/container_lte_n3xx_tdd_2x2_tm2.xml new file mode 100644 index 0000000000..b3dc7efab7 --- /dev/null +++ b/ci-scripts/xml_files/container_lte_n3xx_tdd_2x2_tm2.xml @@ -0,0 +1,160 @@ + + + + TEST-LTE-2x2-N3XXi-TM2 + 2.6 Ghz TDD LTE 2x2 TM2 + tasks + 1 + + +111111 +100000 +010000 +030101 +010001 +000001 +050000 +050001 +070000 +070001 +010010 +010002 +030201 + + + + + + Pull_Local_Registry + Pull Images from Local Registry + 0 + oai-enb + + + + Custom_Command + Disable Sleep States + obelix + sudo cpupower idle-set -D 0 + + + + Custom_Command + Enable Sleep States + obelix + sudo cpupower idle-set -E + + + + Deploy_Object + Deploy eNB (TDD/Band38/2.6Ghz/N3XX) in a container + ci-scripts/yaml_files/lte_n3xx_tdd_2x2_tm2 + 0 + 0 + + + + IdleSleep + Sleep + 5 + + + + Initialize_UE + Initialize Quectel + nrmodule2_quectel + + + + Terminate_UE + Terminate Quectel + nrmodule2_quectel + + + + Attach_UE + Attach UE + nrmodule2_quectel + + + + Ping + Ping: 20 pings + nrmodule2_quectel + -c 20 + 1 + 60 + + + + Ping + Ping: 100 pings, size 1024 + nrmodule2_quectel + -c 100 -s 1024 -i 0,2 + 1 + 60 + + + + Iperf + iperf (DL/26Mbps/UDP)(60 sec)(single-ue profile) + -u -b 1M -t 60 + DL + nrmodule2_quectel + 5 + 95 + single-ue + + + + Iperf + iperf (UL/7Mbps/UDP)(60 sec)(single-ue profile) + -u -b 7M -t 60 + UL + nrmodule2_quectel + 5 + 95 + single-ue + + + + Detach_UE + Detach UE + nrmodule2_quectel + + + + Undeploy_Object + Undeploy gNB + ci-scripts/yaml_files/lte_n3xx_tdd_2x2_tm2 + 0 + 0 + + + + + diff --git a/ci-scripts/xml_files/lte_2x2_tm1_quectel.xml b/ci-scripts/xml_files/lte_2x2_tm1_quectel.xml deleted file mode 100644 index 4270510842..0000000000 --- a/ci-scripts/xml_files/lte_2x2_tm1_quectel.xml +++ /dev/null @@ -1,164 +0,0 @@ - - - TEST-LTE-TM1-Tab1 - LTE 2x2 TM1 Ping DL UL with QUECTEL - tasks - 1 - - 100000 - 010000 - 030000 - 010001 - 050000 - 050001 - 070000 - 070001 - 010011 - 010002 - 080000 - 200000 - - - - - Custom_Command - Disable Sleep States - obelix - sudo cpupower idle-set -D 0 - - - Custom_Command - Enable Sleep States - obelix - sudo cpupower idle-set -E - - - - Initialize_UE - Initialize Quectel - nrmodule2_quectel - - - Terminate_UE - Terminate Quectel - nrmodule2_quectel - - - Attach_UE - Attach Quectel - nrmodule2_quectel - - - Detach_UE - Detach Quectel - nrmodule2_quectel - - - - Initialize_eNB - Initialize eNB - -O ci-scripts/conf_files/enb.band38.lte_2x2.100PRB.usrpn310.conf --usrp-tx-thread-config 1 --thread-pool 0,2,4,6 --log_config.global_log_options level,nocolor,time - 0 - 0 - lte - yes - yes - 172.21.19.13 - - - - - IdleSleep - Sleep - 5 - - - - IdleSleep - Sleep - 20 - - - - - Ping - Ping: 20 pings - nrmodule2_quectel - -c 20 - 1 - 60 - - - Ping - Ping: 100 pings, size 1024 - nrmodule2_quectel - -c 100 -s 1024 -i 0,2 - 1 - 60 - - - - - Iperf - iperf (DL/26Mbps/UDP)(60 sec)(single-ue profile) - -u -b 26M -t 60 - DL - nrmodule2_quectel - 5 - 95 - single-ue - - - - Iperf - iperf (UL/7Mbps/UDP)(60 sec)(single-ue profile) - -u -b 7M -t 60 - UL - nrmodule2_quectel - 5 - 95 - single-ue - - - - - - - Terminate_eNB - Terminate eNB - 0 - 0 - lte - - - - Terminate_eNB - Terminate gNB - 1 - 1 - nr - - - - diff --git a/ci-scripts/xml_files/lte_2x2_tm2_quectel.xml b/ci-scripts/xml_files/lte_2x2_tm2_quectel.xml deleted file mode 100644 index d02749700d..0000000000 --- a/ci-scripts/xml_files/lte_2x2_tm2_quectel.xml +++ /dev/null @@ -1,164 +0,0 @@ - - - TEST-LTE-TM2-Tab2 - LTE 2x2 TM2 Ping DL UL with QUECTEL - tasks - 1 - - 100000 - 010000 - 030001 - 010001 - 050002 - 050003 - 070002 - 070003 - 010011 - 010002 - 080000 - 200000 - - - - - Custom_Command - Disable Sleep States - obelix - sudo cpupower idle-set -D 0 - - - Custom_Command - Enable Sleep States - obelix - sudo cpupower idle-set -E - - - - Initialize_UE - Initialize Quectel - nrmodule2_quectel - - - Terminate_UE - Terminate Quectel - nrmodule2_quectel - - - Attach_UE - Attach Quectel - nrmodule2_quectel - - - Detach_UE - Detach Quectel - nrmodule2_quectel - - - - Initialize_eNB - Initialize eNB - -O ci-scripts/conf_files/enb.band38.lte_2x2_tm2.100PRB.usrpn310.conf --usrp-tx-thread-config 1 --thread-pool 0,2,4,6 --log_config.global_log_options level,nocolor,time - 0 - 0 - lte - yes - yes - 172.21.19.13 - - - - - IdleSleep - Sleep - 5 - - - - IdleSleep - Sleep - 20 - - - - - Ping - Ping: 20 pings - nrmodule2_quectel - -c 20 - 1 - 60 - - - Ping - Ping: 100 pings, size 1024 - nrmodule2_quectel - -c 100 -s 1024 -i 0,2 - 1 - 60 - - - - - Iperf - iperf (DL/1Mbps/UDP)(60 sec)(single-ue profile) - -u -b 1M -t 60 - DL - nrmodule2_quectel - 5 - 95 - single-ue - - - - Iperf - iperf (UL/7Mbps/UDP)(60 sec)(single-ue profile) - -u -b 7M -t 60 - UL - nrmodule2_quectel - 5 - 95 - single-ue - - - - - - - Terminate_eNB - Terminate eNB - 0 - 0 - lte - - - - Terminate_eNB - Terminate gNB - 1 - 1 - nr - - - - diff --git a/ci-scripts/yaml_files/lte_n3xx_tdd_2x2_tm1/docker-compose.yml b/ci-scripts/yaml_files/lte_n3xx_tdd_2x2_tm1/docker-compose.yml new file mode 100644 index 0000000000..abd2c7d40e --- /dev/null +++ b/ci-scripts/yaml_files/lte_n3xx_tdd_2x2_tm1/docker-compose.yml @@ -0,0 +1,18 @@ +version: '3.8' +services: + oai-enb: + image: oai-enb:latest + privileged: true + container_name: oai-enb + environment: + TZ: Europe/Paris + USE_VOLUMED_CONF: 'yes' + USE_ADDITIONAL_OPTIONS: --usrp-tx-thread-config 1 --thread-pool 0,2,4,6 + volumes: + - ../../conf_files/enb.band38.lte_2x2.100PRB.usrpn310.conf:/opt/oai-enb/etc/mounted.conf + network_mode: "host" + healthcheck: + test: /bin/bash -c "pgrep lte-softmodem" + interval: 10s + timeout: 5s + retries: 5 diff --git a/ci-scripts/yaml_files/lte_n3xx_tdd_2x2_tm2/docker-compose.yml b/ci-scripts/yaml_files/lte_n3xx_tdd_2x2_tm2/docker-compose.yml new file mode 100644 index 0000000000..1679b632b6 --- /dev/null +++ b/ci-scripts/yaml_files/lte_n3xx_tdd_2x2_tm2/docker-compose.yml @@ -0,0 +1,18 @@ +version: '3.8' +services: + oai-enb: + image: oai-enb:latest + privileged: true + container_name: oai-enb + environment: + TZ: Europe/Paris + USE_VOLUMED_CONF: 'yes' + USE_ADDITIONAL_OPTIONS: --usrp-tx-thread-config 1 --thread-pool 0,2,4,6 + volumes: + - ../../conf_files/enb.band38.lte_2x2_tm2.100PRB.usrpn310.conf:/opt/oai-enb/etc/mounted.conf + network_mode: "host" + healthcheck: + test: /bin/bash -c "pgrep lte-softmodem" + interval: 10s + timeout: 5s + retries: 5 diff --git a/doc/TESTBenches.md b/doc/TESTBenches.md index 20d8f79ff0..33e3ede1eb 100644 --- a/doc/TESTBenches.md +++ b/doc/TESTBenches.md @@ -108,6 +108,9 @@ Webhook build using the OpenShift Cluster - [RAN-Ubuntu18-Image-Builder](https://jenkins-oai.eurecom.fr/job/RAN-Ubuntu18-Image-Builder/) - obelix: Ubuntu 18 image build using docker +- [RAN-LTE-TDD-2x2-Container](https://jenkins-oai.eurecom.fr/view/RAN/job/RAN-LTE-TDD-2x2-Container/) + - obelix + N310, porcepix, quectel + - TM1 and TM2 test ### [RAN-CI-NSA-Trigger](https://jenkins-oai.eurecom.fr/view/RAN/job/RAN-CI-NSA-Trigger/) From 7c9b5013106226b5d014e1f50fd29ef2023b835c Mon Sep 17 00:00:00 2001 From: francescomani Date: Thu, 19 Jan 2023 18:51:16 +0100 Subject: [PATCH 04/89] removing call from MAC to PHY to clear structures (now automatic clearing) --- common/openairinterface5g_limits.h | 2 + executables/nr-gnb.c | 47 +-- openair1/PHY/INIT/nr_init.c | 22 ++ .../PHY/NR_ESTIMATION/nr_measurements_gNB.c | 15 +- .../NR_ESTIMATION/nr_ul_channel_estimation.c | 19 +- openair1/PHY/NR_ESTIMATION/nr_ul_estimation.h | 2 +- openair1/PHY/NR_TRANSPORT/nr_dlsch.c | 25 +- openair1/PHY/NR_TRANSPORT/nr_dlsch.h | 2 - openair1/PHY/NR_TRANSPORT/nr_dlsch_coding.c | 33 +- openair1/PHY/NR_TRANSPORT/nr_dlsch_tools.c | 5 +- .../NR_TRANSPORT/nr_transport_common_proto.h | 1 - .../PHY/NR_TRANSPORT/nr_transport_proto.h | 11 +- openair1/PHY/NR_TRANSPORT/nr_ulsch.c | 154 +++++---- openair1/PHY/NR_TRANSPORT/nr_ulsch.h | 3 +- openair1/PHY/NR_TRANSPORT/nr_ulsch_decoding.c | 139 +++----- .../PHY/NR_TRANSPORT/nr_ulsch_demodulation.c | 2 +- openair1/PHY/NR_TRANSPORT/pucch_rx.c | 165 ++++------ openair1/PHY/NR_TRANSPORT/srs_rx.c | 48 +-- openair1/PHY/defs_gNB.h | 62 ++-- openair1/SCHED_NR/fapi_nr_l1.c | 13 +- openair1/SCHED_NR/phy_procedures_nr_gNB.c | 299 +++++++++--------- .../NR_MAC_gNB/gNB_scheduler_primitives.c | 30 +- .../LAYER2/NR_MAC_gNB/gNB_scheduler_ulsch.c | 7 - 23 files changed, 465 insertions(+), 641 deletions(-) diff --git a/common/openairinterface5g_limits.h b/common/openairinterface5g_limits.h index b86f01df6a..534c125226 100644 --- a/common/openairinterface5g_limits.h +++ b/common/openairinterface5g_limits.h @@ -15,6 +15,8 @@ # define NUMBER_OF_SCH_STATS_MAX 16 # define NUMBER_OF_NR_SCH_STATS_MAX MAX_MOBILES_PER_GNB +# define NUMBER_FRAMES_PHY_UE_INACTIVE 10 + #define MAX_MANAGED_ENB_PER_MOBILE 2 #define MAX_MANAGED_GNB_PER_MOBILE 2 diff --git a/executables/nr-gnb.c b/executables/nr-gnb.c index 37f29ce1ea..b2f88aca39 100644 --- a/executables/nr-gnb.c +++ b/executables/nr-gnb.c @@ -128,7 +128,9 @@ void tx_func(void *param) { } -void rx_func(void *param) { +void rx_func(void *param) +{ + processingData_L1_t *info = (processingData_L1_t *) param; PHY_VARS_gNB *gNB = info->gNB; int frame_rx = info->frame_rx; @@ -173,46 +175,15 @@ void rx_func(void *param) { T(T_GNB_PHY_DL_TICK, T_INT(gNB->Mod_id), T_INT(frame_tx), T_INT(slot_tx)); - /* hack to remove UEs */ - extern int rnti_to_remove[10]; - extern volatile int rnti_to_remove_count; - extern pthread_mutex_t rnti_to_remove_mutex; - if (pthread_mutex_lock(&rnti_to_remove_mutex)) exit(1); - int up_removed = 0; - int down_removed = 0; - int pucch_removed = 0; - for (int i = 0; i < rnti_to_remove_count; i++) { - LOG_W(NR_PHY, "to remove rnti 0x%04x\n", rnti_to_remove[i]); - void clean_gNB_ulsch(NR_gNB_ULSCH_t *ulsch); - void clean_gNB_dlsch(NR_gNB_DLSCH_t *dlsch); - int j; - for (j = 0; j < NUMBER_OF_NR_ULSCH_MAX; j++) - if (gNB->ulsch[j]->rnti == rnti_to_remove[i]) { - gNB->ulsch[j]->rnti = 0; - gNB->ulsch[j]->harq_mask = 0; - int h; - for (h = 0; h < NR_MAX_ULSCH_HARQ_PROCESSES; h++) { - gNB->ulsch[j]->harq_processes[h]->status = NR_SCH_IDLE; - gNB->ulsch[j]->harq_processes[h]->round = 0; - gNB->ulsch[j]->harq_processes[h]->handled = 0; - } - up_removed++; - } - - for (j = 0; j < gNB->max_nb_pucch; j++) - if (gNB->pucch[j]->active > 0 && - gNB->pucch[j]->pucch_pdu.rnti == rnti_to_remove[i]) { - gNB->pucch[j]->active = 0; - gNB->pucch[j]->pucch_pdu.rnti = 0; - pucch_removed++; - } + // disactivate PHY stats if UE is inactive for more than 10 frames + for (int i = 0; i < MAX_MOBILES_PER_GNB; i++) { + NR_gNB_PHY_STATS_t *stats = &gNB->phy_stats[i]; + if(stats->active && (frame_rx > (stats->frame + NUMBER_FRAMES_PHY_UE_INACTIVE) % 1024)) + stats->active = false; } - if (rnti_to_remove_count) LOG_W(NR_PHY, "to remove rnti_to_remove_count=%d, up_removed=%d down_removed=%d pucch_removed=%d\n", rnti_to_remove_count, up_removed, down_removed, pucch_removed); - rnti_to_remove_count = 0; - if (pthread_mutex_unlock(&rnti_to_remove_mutex)) exit(1); // RX processing - int rx_slot_type = nr_slot_select(cfg,frame_rx,slot_rx); + int rx_slot_type = nr_slot_select(cfg,frame_rx,slot_rx); if (rx_slot_type == NR_UPLINK_SLOT || rx_slot_type == NR_MIXED_SLOT) { // UE-specific RX processing for subframe n // TODO: check if this is correct for PARALLEL_RU_L1_TRX_SPLIT diff --git a/openair1/PHY/INIT/nr_init.c b/openair1/PHY/INIT/nr_init.c index b02c2e0397..9a74f093b1 100644 --- a/openair1/PHY/INIT/nr_init.c +++ b/openair1/PHY/INIT/nr_init.c @@ -77,6 +77,28 @@ void init_ul_delay_table(NR_DL_FRAME_PARMS *fp) } } +NR_gNB_PHY_STATS_t *get_phy_stats(PHY_VARS_gNB *gNB, uint16_t rnti) +{ + NR_gNB_PHY_STATS_t *stats; + int first_free = -1; + for (int i = 0; i < MAX_MOBILES_PER_GNB; i++) { + stats = &gNB->phy_stats[i]; + if (stats->active && stats->rnti == rnti) + return stats; + else if (!stats->active && first_free == -1) + first_free = i; + } + // new stats + AssertFatal(first_free >= 0, "PHY statistics list is full\n"); + stats = &gNB->phy_stats[first_free]; + stats->active = true; + stats->rnti = rnti; + memset((void*)&stats->dlsch_stats, 0, sizeof(stats->dlsch_stats)); + memset((void*)&stats->ulsch_stats, 0, sizeof(stats->ulsch_stats)); + memset((void*)&stats->uci_stats, 0, sizeof(stats->uci_stats)); + return(stats); +} + int init_codebook_gNB(PHY_VARS_gNB *gNB) { if(gNB->frame_parms.nb_antennas_tx>1){ diff --git a/openair1/PHY/NR_ESTIMATION/nr_measurements_gNB.c b/openair1/PHY/NR_ESTIMATION/nr_measurements_gNB.c index ddd5d33a86..96b832ceab 100644 --- a/openair1/PHY/NR_ESTIMATION/nr_measurements_gNB.c +++ b/openair1/PHY/NR_ESTIMATION/nr_measurements_gNB.c @@ -38,7 +38,7 @@ extern openair0_config_t openair0_cfg[MAX_CARDS]; -void nr_est_timing_advance_pusch(const NR_DL_FRAME_PARMS *frame_parms, const int32_t *ul_ch_estimates_time, struct delay_s *delay) +void nr_est_timing_advance_pusch(const NR_DL_FRAME_PARMS *frame_parms, const int32_t *ul_ch_estimates_time, NR_ULSCH_delay_t *delay) { int max_pos = delay->pusch_delay_max_pos; int max_val = delay->pusch_delay_max_val; @@ -208,19 +208,20 @@ void gNB_I0_measurements(PHY_VARS_gNB *gNB,int slot, int first_symb,int num_symb // // Todo: // - averaging IIR filter for RX power and noise -void nr_gnb_measurements(PHY_VARS_gNB *gNB, uint8_t ulsch_id, unsigned char harq_pid, unsigned char symbol, uint8_t nrOfLayers){ +void nr_gnb_measurements(PHY_VARS_gNB *gNB, uint8_t ulsch_id, unsigned char harq_pid, unsigned char symbol, uint8_t nrOfLayers) +{ int rx_power_tot[NUMBER_OF_NR_ULSCH_MAX]; int rx_power[NUMBER_OF_NR_ULSCH_MAX][NB_ANTENNAS_RX]; unsigned short rx_power_avg_dB[NUMBER_OF_NR_ULSCH_MAX]; unsigned short rx_power_tot_dB[NUMBER_OF_NR_ULSCH_MAX]; - double rx_gain = openair0_cfg[0].rx_gain[0]; - double rx_gain_offset = openair0_cfg[0].rx_gain_offset[0]; + double rx_gain = openair0_cfg[0].rx_gain[0]; + double rx_gain_offset = openair0_cfg[0].rx_gain_offset[0]; PHY_MEASUREMENTS_gNB *meas = &gNB->measurements; - NR_DL_FRAME_PARMS *fp = &gNB->frame_parms; - int ch_offset = fp->ofdm_symbol_size * symbol; - int N_RB_UL = gNB->ulsch[ulsch_id]->harq_processes[harq_pid]->ulsch_pdu.rb_size; + NR_DL_FRAME_PARMS *fp = &gNB->frame_parms; + int ch_offset = fp->ofdm_symbol_size * symbol; + int N_RB_UL = gNB->ulsch[ulsch_id]->harq_process->ulsch_pdu.rb_size; rx_power_tot[ulsch_id] = 0; diff --git a/openair1/PHY/NR_ESTIMATION/nr_ul_channel_estimation.c b/openair1/PHY/NR_ESTIMATION/nr_ul_channel_estimation.c index fd2ed4f675..1ce5ed577f 100644 --- a/openair1/PHY/NR_ESTIMATION/nr_ul_channel_estimation.c +++ b/openair1/PHY/NR_ESTIMATION/nr_ul_channel_estimation.c @@ -187,7 +187,8 @@ int nr_pusch_channel_estimation(PHY_VARS_gNB *gNB, c16_t ul_ls_est[symbolSize] __attribute__((aligned(32))); memset(ul_ls_est, 0, sizeof(c16_t) * symbolSize); - memset(&gNB->measurements.delay[ul_id], 0, sizeof(gNB->measurements.delay[ul_id])); + NR_ULSCH_delay_t *delay = &gNB->ulsch[ul_id]->delay; + memset(&delay, 0, sizeof(delay)); for (int aarx=0; aarxframe_parms.nb_antennas_rx; aarx++) { c16_t *rxdataF = (c16_t *)&gNB->common_vars.rxdataF[aarx][symbol_offset]; @@ -232,13 +233,13 @@ int nr_pusch_channel_estimation(PHY_VARS_gNB *gNB, (int16_t *) ul_ls_est, (int16_t *) gNB->pusch_vars[ul_id]->ul_ch_estimates_time[aarx]); - nr_est_timing_advance_pusch(&gNB->frame_parms, gNB->pusch_vars[ul_id]->ul_ch_estimates_time[aarx], &gNB->measurements.delay[ul_id]); - int delay = gNB->measurements.delay[ul_id].pusch_est_delay; - int delay_idx = get_delay_idx(delay); + nr_est_timing_advance_pusch(&gNB->frame_parms, gNB->pusch_vars[ul_id]->ul_ch_estimates_time[aarx], delay); + int pusch_delay = delay->pusch_est_delay; + int delay_idx = get_delay_idx(pusch_delay); c16_t *ul_delay_table = gNB->frame_parms.ul_delay_table[delay_idx]; #ifdef DEBUG_PUSCH - printf("Estimated delay = %i\n", delay >> 1); + printf("Estimated delay = %i\n", pusch_delay >> 1); #endif pilot_cnt = 0; @@ -277,7 +278,7 @@ int nr_pusch_channel_estimation(PHY_VARS_gNB *gNB, // Revert delay pilot_cnt = 0; ul_ch = &ul_ch_estimates[p * gNB->frame_parms.nb_antennas_rx + aarx][ch_offset]; - int inv_delay_idx = get_delay_idx(-delay); + int inv_delay_idx = get_delay_idx(-pusch_delay); c16_t *ul_inv_delay_table = gNB->frame_parms.ul_delay_table[inv_delay_idx]; for (int n = 0; n < 3 * nb_rb_pusch; n++) { for (int k_line = 0; k_line <= 1; k_line++) { @@ -311,9 +312,9 @@ int nr_pusch_channel_estimation(PHY_VARS_gNB *gNB, // Delay compensation freq2time(symbolSize, (int16_t *)ul_ls_est, (int16_t *)gNB->pusch_vars[ul_id]->ul_ch_estimates_time[aarx]); - nr_est_timing_advance_pusch(&gNB->frame_parms, gNB->pusch_vars[ul_id]->ul_ch_estimates_time[aarx], &gNB->measurements.delay[ul_id]); - int delay = gNB->measurements.delay[ul_id].pusch_est_delay; - int delay_idx = get_delay_idx(-delay); + nr_est_timing_advance_pusch(&gNB->frame_parms, gNB->pusch_vars[ul_id]->ul_ch_estimates_time[aarx], delay); + int pusch_delay = delay->pusch_est_delay; + int delay_idx = get_delay_idx(-pusch_delay); c16_t *ul_delay_table = gNB->frame_parms.ul_delay_table[delay_idx]; for (int n = 0; n < nb_rb_pusch * NR_NB_SC_PER_RB; n++) { ul_ch[n] = c16mulShift(ul_ls_est[n], ul_delay_table[n % 6], 8); diff --git a/openair1/PHY/NR_ESTIMATION/nr_ul_estimation.h b/openair1/PHY/NR_ESTIMATION/nr_ul_estimation.h index fb43e83827..defd62fd8b 100644 --- a/openair1/PHY/NR_ESTIMATION/nr_ul_estimation.h +++ b/openair1/PHY/NR_ESTIMATION/nr_ul_estimation.h @@ -55,7 +55,7 @@ void gNB_I0_measurements(PHY_VARS_gNB *gNB,int slot,int first_symb,int num_symb) void nr_gnb_measurements(PHY_VARS_gNB *gNB, uint8_t ulsch_id, unsigned char harq_pid, unsigned char symbol, uint8_t nrOfLayers); -void nr_est_timing_advance_pusch(const NR_DL_FRAME_PARMS *frame_parms, const int32_t *ul_ch_estimates_time, struct delay_s *delay); +void nr_est_timing_advance_pusch(const NR_DL_FRAME_PARMS *frame_parms, const int32_t *ul_ch_estimates_time, NR_ULSCH_delay_t *delay); int nr_est_timing_advance_srs(const NR_DL_FRAME_PARMS *frame_parms, const int32_t srs_estimated_channel_time[][frame_parms->ofdm_symbol_size]); diff --git a/openair1/PHY/NR_TRANSPORT/nr_dlsch.c b/openair1/PHY/NR_TRANSPORT/nr_dlsch.c index 47ef721bef..17882077a8 100644 --- a/openair1/PHY/NR_TRANSPORT/nr_dlsch.c +++ b/openair1/PHY/NR_TRANSPORT/nr_dlsch.c @@ -568,20 +568,21 @@ void nr_generate_pdsch(processingData_L1tx_t *msgTx, void dump_pdsch_stats(FILE *fd,PHY_VARS_gNB *gNB) { - for (int i=0;idlsch_stats[i].rnti > 0 && gNB->dlsch_stats[i].frame != gNB->dlsch_stats[i].dump_frame) { - gNB->dlsch_stats[i].dump_frame = gNB->dlsch_stats[i].frame; + for (int i = 0; i < MAX_MOBILES_PER_GNB; i++) { + NR_gNB_PHY_STATS_t *stats = &gNB->phy_stats[i]; + if (stats->active && stats->frame != stats->dlsch_stats.dump_frame) { + stats->dlsch_stats.dump_frame = stats->frame; fprintf(fd,"DLSCH RNTI %x: current_Qm %d, current_RI %d, total_bytes TX %d\n", - gNB->dlsch_stats[i].rnti, - gNB->dlsch_stats[i].current_Qm, - gNB->dlsch_stats[i].current_RI, - gNB->dlsch_stats[i].total_bytes_tx); + stats->rnti, + stats->dlsch_stats.current_Qm, + stats->dlsch_stats.current_RI, + stats->dlsch_stats.total_bytes_tx); } - + } } -void clear_pdsch_stats(PHY_VARS_gNB *gNB) { - - for (int i = 0;i < NUMBER_OF_NR_SCH_STATS_MAX; i++) - memset((void*)&gNB->dlsch_stats[i],0,sizeof(gNB->dlsch_stats[i])); +void clear_pdsch_stats(PHY_VARS_gNB *gNB) +{ + for (int i = 0; i < MAX_MOBILES_PER_GNB;i++) + memset((void*)&gNB->phy_stats[i].dlsch_stats,0,sizeof(gNB->phy_stats[i].dlsch_stats)); } diff --git a/openair1/PHY/NR_TRANSPORT/nr_dlsch.h b/openair1/PHY/NR_TRANSPORT/nr_dlsch.h index 974e74f30e..6e2210aab0 100644 --- a/openair1/PHY/NR_TRANSPORT/nr_dlsch.h +++ b/openair1/PHY/NR_TRANSPORT/nr_dlsch.h @@ -52,8 +52,6 @@ void nr_generate_pdsch(processingData_L1tx_t *msgTx, void clean_gNB_dlsch(NR_gNB_DLSCH_t *dlsch); -void clean_gNB_ulsch(NR_gNB_ULSCH_t *ulsch); - int16_t find_nr_dlsch(uint16_t rnti, PHY_VARS_gNB *gNB,find_type_t type); NR_gNB_SCH_STATS_t *find_nr_dlsch_stats(uint16_t rnti, PHY_VARS_gNB *gNB,find_type_t type); diff --git a/openair1/PHY/NR_TRANSPORT/nr_dlsch_coding.c b/openair1/PHY/NR_TRANSPORT/nr_dlsch_coding.c index 63fbf20d0d..81b3ca686a 100644 --- a/openair1/PHY/NR_TRANSPORT/nr_dlsch_coding.c +++ b/openair1/PHY/NR_TRANSPORT/nr_dlsch_coding.c @@ -167,7 +167,6 @@ NR_gNB_DLSCH_t *new_gNB_dlsch(NR_DL_FRAME_PARMS *frame_parms, void clean_gNB_dlsch(NR_gNB_DLSCH_t *dlsch) { AssertFatal(dlsch!=NULL,"dlsch is null\n"); - dlsch->rnti = 0; dlsch->active = 0; } @@ -280,7 +279,8 @@ int nr_dlsch_encoding(PHY_VARS_gNB *gNB, unsigned char * output, time_stats_t *tinput,time_stats_t *tprep,time_stats_t *tparity,time_stats_t *toutput, time_stats_t *dlsch_rate_matching_stats,time_stats_t *dlsch_interleaving_stats, - time_stats_t *dlsch_segmentation_stats) { + time_stats_t *dlsch_segmentation_stats) +{ encoder_implemparams_t impp; impp.output=output; unsigned int crc=1; @@ -289,29 +289,18 @@ int nr_dlsch_encoding(PHY_VARS_gNB *gNB, VCD_SIGNAL_DUMPER_DUMP_FUNCTION_BY_NAME(VCD_SIGNAL_DUMPER_FUNCTIONS_gNB_DLSCH_ENCODING, VCD_FUNCTION_IN); uint32_t A = rel15->TBSize[0]<<3; unsigned char *a=harq->pdu; - if ( rel15->rnti != SI_RNTI) + if (rel15->rnti != SI_RNTI) trace_NRpdu(DIRECTION_DOWNLINK, a, rel15->TBSize[0], WS_C_RNTI, rel15->rnti, frame, slot,0, 0); - NR_gNB_SCH_STATS_t *stats=NULL; - int first_free=-1; + NR_gNB_PHY_STATS_t *phy_stats = NULL; + if (rel15->rnti != 0xFFFF) + phy_stats = get_phy_stats(gNB, rel15->rnti); - for (int i=0; idlsch_stats[i].rnti == 0 && first_free == -1) { - first_free = i; - stats=&gNB->dlsch_stats[i]; - } - - if (gNB->dlsch_stats[i].rnti == rel15->rnti) { - stats=&gNB->dlsch_stats[i]; - break; - } - } - - if (stats) { - stats->rnti = rel15->rnti; - stats->total_bytes_tx += rel15->TBSize[0]; - stats->current_RI = rel15->nrOfLayers; - stats->current_Qm = rel15->qamModOrder[0]; + if (phy_stats) { + phy_stats->frame = frame; + phy_stats->dlsch_stats.total_bytes_tx += rel15->TBSize[0]; + phy_stats->dlsch_stats.current_RI = rel15->nrOfLayers; + phy_stats->dlsch_stats.current_Qm = rel15->qamModOrder[0]; } int max_bytes = MAX_NUM_NR_DLSCH_SEGMENTS_PER_LAYER*rel15->nrOfLayers*1056; diff --git a/openair1/PHY/NR_TRANSPORT/nr_dlsch_tools.c b/openair1/PHY/NR_TRANSPORT/nr_dlsch_tools.c index 2f8087ea0e..b2be4af8c0 100644 --- a/openair1/PHY/NR_TRANSPORT/nr_dlsch_tools.c +++ b/openair1/PHY/NR_TRANSPORT/nr_dlsch_tools.c @@ -46,14 +46,15 @@ void nr_emulate_dlsch_payload(uint8_t* pdu, uint16_t size) { void nr_fill_dlsch(processingData_L1tx_t *msgTx, nfapi_nr_dl_tti_pdsch_pdu *pdsch_pdu, - uint8_t *sdu) { + uint8_t *sdu) +{ NR_gNB_DLSCH_t *dlsch = msgTx->dlsch[msgTx->num_pdsch_slot][0]; NR_DL_gNB_HARQ_t *harq = &dlsch->harq_process; /// DLSCH struct memcpy((void*)&harq->pdsch_pdu, (void*)pdsch_pdu, sizeof(nfapi_nr_dl_tti_pdsch_pdu)); msgTx->num_pdsch_slot++; - AssertFatal(sdu!=NULL,"sdu is null\n"); + AssertFatal(sdu != NULL,"sdu is null\n"); harq->pdu = sdu; } diff --git a/openair1/PHY/NR_TRANSPORT/nr_transport_common_proto.h b/openair1/PHY/NR_TRANSPORT/nr_transport_common_proto.h index 74d19bf6f6..f45c23980b 100644 --- a/openair1/PHY/NR_TRANSPORT/nr_transport_common_proto.h +++ b/openair1/PHY/NR_TRANSPORT/nr_transport_common_proto.h @@ -40,7 +40,6 @@ #include "PHY/defs_nr_common.h" - void nr_group_sequence_hopping(pucch_GroupHopping_t PUCCH_GroupHopping, uint32_t n_id, uint8_t n_hop, diff --git a/openair1/PHY/NR_TRANSPORT/nr_transport_proto.h b/openair1/PHY/NR_TRANSPORT/nr_transport_proto.h index 2f0e46a13f..b0a575a0ac 100644 --- a/openair1/PHY/NR_TRANSPORT/nr_transport_proto.h +++ b/openair1/PHY/NR_TRANSPORT/nr_transport_proto.h @@ -38,6 +38,7 @@ #define NR_PBCH_PDU_BITS 24 +NR_gNB_PHY_STATS_t *get_phy_stats(PHY_VARS_gNB *gNB, uint16_t rnti); int nr_generate_prs(uint32_t **nr_gold_prs, c16_t *txdataF, @@ -312,19 +313,9 @@ void nr_fill_pucch(PHY_VARS_gNB *gNB, int slot, nfapi_nr_pucch_pdu_t *pucch_pdu); -int nr_find_pucch(uint16_t rnti, - int frame, - int slot, - PHY_VARS_gNB *gNB); - NR_gNB_SRS_t *new_gNB_srs(void); void free_gNB_srs(NR_gNB_SRS_t *srs); -int nr_find_srs(rnti_t rnti, - frame_t frame, - slot_t slot, - PHY_VARS_gNB *gNB); - void nr_fill_srs(PHY_VARS_gNB *gNB, frame_t frame, slot_t slot, diff --git a/openair1/PHY/NR_TRANSPORT/nr_ulsch.c b/openair1/PHY/NR_TRANSPORT/nr_ulsch.c index 9fb95ac273..aae638e4ad 100644 --- a/openair1/PHY/NR_TRANSPORT/nr_ulsch.c +++ b/openair1/PHY/NR_TRANSPORT/nr_ulsch.c @@ -34,57 +34,63 @@ #include "PHY/NR_TRANSPORT/nr_transport_common_proto.h" #include "PHY/NR_TRANSPORT/nr_ulsch.h" -int16_t find_nr_ulsch(uint16_t rnti, PHY_VARS_gNB *gNB,find_type_t type) { +int16_t find_nr_ulsch(PHY_VARS_gNB *gNB, uint16_t rnti, int pid, int frame) +{ - uint16_t i; - int16_t first_free_index=-1; + int16_t first_free_index = -1; + AssertFatal(gNB != NULL,"gNB is null\n"); - AssertFatal(gNB!=NULL,"gNB is null\n"); - for (i = 0; i < NUMBER_OF_NR_ULSCH_MAX; i++) { - AssertFatal(gNB->ulsch[i]!=NULL,"gNB->ulsch[%d] is null\n",i); - LOG_D(PHY,"searching for rnti %x : ulsch_index %d=> harq_mask %x, rnti %x, first_free_index %d\n", rnti,i,gNB->ulsch[i]->harq_mask,gNB->ulsch[i]->rnti,first_free_index); - if ((gNB->ulsch[i]->harq_mask >0) && - (gNB->ulsch[i]->rnti==rnti)) return i; - else if ((gNB->ulsch[i]->harq_mask == 0) && (first_free_index==-1)) first_free_index=i; + for (int i = 0; i < NUMBER_OF_NR_ULSCH_MAX; i++) { + NR_gNB_ULSCH_t *ulsch = gNB->ulsch[i]; + AssertFatal(ulsch != NULL, "gNB->ulsch[%d] is null\n", i); + if(!ulsch->active) { + if (first_free_index == -1) + first_free_index = i; + } + else { + // if there is already an active ULSCH for this RNTI and HARQ_PID + if ((ulsch->harq_pid == pid) && (ulsch->rnti == rnti)) + return i; + // remove inactive ULSCH (from disconnected UEs) + else if ((frame > (ulsch->harq_process->frame + NUMBER_FRAMES_PHY_UE_INACTIVE) % 1024)) + ulsch->active = false; + } } - if (type == SEARCH_EXIST) return -1; - if (first_free_index != -1) - gNB->ulsch[first_free_index]->rnti = 0; return first_free_index; } void nr_fill_ulsch(PHY_VARS_gNB *gNB, int frame, int slot, - nfapi_nr_pusch_pdu_t *ulsch_pdu) { - + nfapi_nr_pusch_pdu_t *ulsch_pdu) +{ - int ulsch_id = find_nr_ulsch(ulsch_pdu->rnti,gNB,SEARCH_EXIST_OR_FREE); + int harq_pid = ulsch_pdu->pusch_data.harq_process_id; + int ulsch_id = find_nr_ulsch(gNB, ulsch_pdu->rnti, harq_pid, frame); AssertFatal((ulsch_id >= 0) && (ulsch_id < NUMBER_OF_NR_ULSCH_MAX), "illegal or no ulsch_id found!!! rnti %04x ulsch_id %d\n",ulsch_pdu->rnti,ulsch_id); NR_gNB_ULSCH_t *ulsch = gNB->ulsch[ulsch_id]; - int harq_pid = ulsch_pdu->pusch_data.harq_process_id; - ulsch->rnti = ulsch_pdu->rnti; - //ulsch->rnti_type; - ulsch->harq_mask |= 1<harq_processes[harq_pid]; - harq->frame=frame; - harq->slot=slot; - harq->handled = 0; - harq->status= NR_ACTIVE; + ulsch->rnti = ulsch_pdu->rnti; + ulsch->harq_pid = harq_pid; + ulsch->handled = 0; + ulsch->active = true; + + NR_UL_gNB_HARQ_t *harq = ulsch->harq_process; + harq->frame = frame; + harq->slot = slot; harq->new_rx = ulsch_pdu->pusch_data.new_data_indicator; - LOG_D(PHY,"ULSCH ID %d RNTI %x HARQ PID %d new data indicator %d\n",ulsch_id, ulsch_pdu->rnti, harq_pid, ulsch_pdu->pusch_data.new_data_indicator); + LOG_D(PHY,"%d.%d ULSCH ID %d RNTI %x HARQ PID %d new data indicator %d\n", + frame, slot, ulsch_id, ulsch_pdu->rnti, harq_pid, ulsch_pdu->pusch_data.new_data_indicator); if (harq->new_rx) harq->round = 0; else harq->round++; - memcpy((void*)&ulsch->harq_processes[harq_pid]->ulsch_pdu, (void*)ulsch_pdu, sizeof(nfapi_nr_pusch_pdu_t)); - - LOG_D(PHY,"Initializing nFAPI for ULSCH, UE %d, harq_pid %d\n",ulsch_id,harq_pid); + memcpy((void*)&ulsch->harq_process->ulsch_pdu, (void*)ulsch_pdu, sizeof(nfapi_nr_pusch_pdu_t)); + LOG_D(PHY,"Initializing nFAPI for ULSCH, UE %d, harq_pid %d\n", ulsch_id, harq_pid); } void nr_ulsch_unscrambling(int16_t* llr, uint32_t size, uint32_t Nid, uint32_t n_RNTI) @@ -93,10 +99,10 @@ void nr_ulsch_unscrambling(int16_t* llr, uint32_t size, uint32_t Nid, uint32_t n } void nr_ulsch_layer_demapping(int16_t *llr_cw, - uint8_t Nl, - uint8_t mod_order, - uint32_t length, - int16_t **llr_layers) + uint8_t Nl, + uint8_t mod_order, + uint32_t length, + int16_t **llr_layers) { switch (Nl) { @@ -115,63 +121,51 @@ void nr_ulsch_layer_demapping(int16_t *llr_cw, } break; default: - AssertFatal(0, "Not supported number of layers %d\n", Nl); + AssertFatal(0, "Not supported number of layers %d\n", Nl); } } -void dump_pusch_stats(FILE *fd,PHY_VARS_gNB *gNB) { +void dump_pusch_stats(FILE *fd, PHY_VARS_gNB *gNB) +{ - for (int i = 0;i < NUMBER_OF_NR_SCH_STATS_MAX;i++) { - - if (gNB->ulsch_stats[i].rnti>0 && gNB->ulsch_stats[i].frame != gNB->ulsch_stats[i].dump_frame) { - gNB->ulsch_stats[i].dump_frame = gNB->ulsch_stats[i].frame; - for (int aa=0;aaframe_parms.nb_antennas_rx;aa++) - if (aa==0) fprintf(fd,"ULSCH RNTI %4x, %d.%d: ulsch_power[%d] %d,%d ulsch_noise_power[%d] %d.%d, sync_pos %d\n", - gNB->ulsch_stats[i].rnti,gNB->ulsch_stats[i].frame,gNB->ulsch_stats[i].dump_frame, - aa,gNB->ulsch_stats[i].power[aa]/10,gNB->ulsch_stats[i].power[aa]%10, - aa,gNB->ulsch_stats[i].noise_power[aa]/10,gNB->ulsch_stats[i].noise_power[aa]%10, - gNB->ulsch_stats[i].sync_pos); - else fprintf(fd," ulsch_power[%d] %d.%d, ulsch_noise_power[%d] %d.%d\n", - aa,gNB->ulsch_stats[i].power[aa]/10,gNB->ulsch_stats[i].power[aa]%10, - aa,gNB->ulsch_stats[i].noise_power[aa]/10,gNB->ulsch_stats[i].noise_power[aa]%10); + for (int i = 0;i < MAX_MOBILES_PER_GNB; i++) { + NR_gNB_PHY_STATS_t *stats = &gNB->phy_stats[i]; + if (stats->active && stats->frame != stats->ulsch_stats.dump_frame) { + stats->ulsch_stats.dump_frame = stats->frame; + for (int aa = 0; aa < gNB->frame_parms.nb_antennas_rx; aa++) + if (aa==0) + fprintf(fd,"ULSCH RNTI %4x, %d: ulsch_power[%d] %d,%d ulsch_noise_power[%d] %d.%d, sync_pos %d\n", + stats->rnti, stats->frame, + aa, stats->ulsch_stats.power[aa]/10, stats->ulsch_stats.power[aa]%10, + aa, stats->ulsch_stats.noise_power[aa]/10, stats->ulsch_stats.noise_power[aa]%10, + stats->ulsch_stats.sync_pos); + else + fprintf(fd," ulsch_power[%d] %d.%d, ulsch_noise_power[%d] %d.%d\n", + aa, stats->ulsch_stats.power[aa]/10, stats->ulsch_stats.power[aa]%10, + aa, stats->ulsch_stats.noise_power[aa]/10, stats->ulsch_stats.noise_power[aa]%10); fprintf(fd," round_trials %d(%1.1e):%d(%1.1e):%d(%1.1e):%d, DTX %d, current_Qm %d, current_RI %d, total_bytes RX/SCHED %d/%d\n", - gNB->ulsch_stats[i].round_trials[0], - (double)gNB->ulsch_stats[i].round_trials[1]/gNB->ulsch_stats[i].round_trials[0], - gNB->ulsch_stats[i].round_trials[1], - (double)gNB->ulsch_stats[i].round_trials[2]/gNB->ulsch_stats[i].round_trials[0], - gNB->ulsch_stats[i].round_trials[2], - (double)gNB->ulsch_stats[i].round_trials[3]/gNB->ulsch_stats[i].round_trials[0], - gNB->ulsch_stats[i].round_trials[3], - gNB->ulsch_stats[i].DTX, - gNB->ulsch_stats[i].current_Qm, - gNB->ulsch_stats[i].current_RI, - gNB->ulsch_stats[i].total_bytes_rx, - gNB->ulsch_stats[i].total_bytes_tx); + stats->ulsch_stats.round_trials[0], + (double)stats->ulsch_stats.round_trials[1]/stats->ulsch_stats.round_trials[0], + stats->ulsch_stats.round_trials[1], + (double)stats->ulsch_stats.round_trials[2]/stats->ulsch_stats.round_trials[0], + stats->ulsch_stats.round_trials[2], + (double)stats->ulsch_stats.round_trials[3]/stats->ulsch_stats.round_trials[0], + stats->ulsch_stats.round_trials[3], + stats->ulsch_stats.DTX, + stats->ulsch_stats.current_Qm, + stats->ulsch_stats.current_RI, + stats->ulsch_stats.total_bytes_rx, + stats->ulsch_stats.total_bytes_tx); } - } + } } -void clear_pusch_stats(PHY_VARS_gNB *gNB) { - - for (int i = 0; i < NUMBER_OF_NR_ULSCH_MAX; i++) - memset((void*)&gNB->ulsch_stats[i],0,sizeof(gNB->ulsch_stats[i])); +void clear_pusch_stats(PHY_VARS_gNB *gNB) +{ + for (int i = 0; i < MAX_MOBILES_PER_GNB; i++) + memset((void*)&gNB->phy_stats[i].ulsch_stats,0,sizeof(gNB->phy_stats[i].ulsch_stats)); } -NR_gNB_SCH_STATS_t *get_ulsch_stats(PHY_VARS_gNB *gNB,NR_gNB_ULSCH_t *ulsch) { - NR_gNB_SCH_STATS_t *stats=NULL; - int first_free=-1; - for (int i = 0; i < NUMBER_OF_NR_ULSCH_MAX; i++) { - if (gNB->ulsch_stats[i].rnti == 0 && first_free == -1) { - first_free = i; - stats=&gNB->ulsch_stats[i]; - } - if (gNB->ulsch_stats[i].rnti == ulsch->rnti) { - stats=&gNB->ulsch_stats[i]; - break; - } - } - return(stats); -} diff --git a/openair1/PHY/NR_TRANSPORT/nr_ulsch.h b/openair1/PHY/NR_TRANSPORT/nr_ulsch.h index c7229bc5d0..d3b8599aca 100644 --- a/openair1/PHY/NR_TRANSPORT/nr_ulsch.h +++ b/openair1/PHY/NR_TRANSPORT/nr_ulsch.h @@ -82,7 +82,8 @@ void nr_ulsch_procedures(PHY_VARS_gNB *gNB, int slot_rx, int UE_id, uint8_t harq_pid); -int16_t find_nr_ulsch(uint16_t rnti, PHY_VARS_gNB *gNB,find_type_t type); + +int16_t find_nr_ulsch(PHY_VARS_gNB *gNB, uint16_t rnti, int pid, int frame); void dump_pusch_stats(FILE *fd,PHY_VARS_gNB *gNB); diff --git a/openair1/PHY/NR_TRANSPORT/nr_ulsch_decoding.c b/openair1/PHY/NR_TRANSPORT/nr_ulsch_decoding.c index 0bb872852b..2c05ac1488 100644 --- a/openair1/PHY/NR_TRANSPORT/nr_ulsch_decoding.c +++ b/openair1/PHY/NR_TRANSPORT/nr_ulsch_decoding.c @@ -38,6 +38,7 @@ #include "PHY/CODING/lte_interleaver_inline.h" #include "PHY/CODING/nrLDPC_extern.h" #include "PHY/NR_TRANSPORT/nr_transport_common_proto.h" +#include "PHY/NR_TRANSPORT/nr_transport_proto.h" #include "PHY/NR_TRANSPORT/nr_ulsch.h" #include "PHY/NR_TRANSPORT/nr_dlsch.h" #include "SCHED_NR/sched_nr.h" @@ -65,21 +66,19 @@ void free_gNB_ulsch(NR_gNB_ULSCH_t **ulschptr, uint16_t N_RB_UL) a_segments = a_segments/273 +1; } - for (int i=0; iharq_processes[i]) { - if (ulsch->harq_processes[i]->b) { - free_and_zero(ulsch->harq_processes[i]->b); - ulsch->harq_processes[i]->b = NULL; - } - for (int r=0; rharq_processes[i]->c[r]); - free_and_zero(ulsch->harq_processes[i]->d[r]); - } - free_and_zero(ulsch->harq_processes[i]->c); - free_and_zero(ulsch->harq_processes[i]->d); - free_and_zero(ulsch->harq_processes[i]); - ulsch->harq_processes[i] = NULL; + if (ulsch->harq_process) { + if (ulsch->harq_process->b) { + free_and_zero(ulsch->harq_process->b); + ulsch->harq_process->b = NULL; } + for (int r = 0; r < a_segments; r++) { + free_and_zero(ulsch->harq_process->c[r]); + free_and_zero(ulsch->harq_process->d[r]); + } + free_and_zero(ulsch->harq_process->c); + free_and_zero(ulsch->harq_process->d); + free_and_zero(ulsch->harq_process); + ulsch->harq_process = NULL; } free_and_zero(*ulschptr); } @@ -88,8 +87,6 @@ void free_gNB_ulsch(NR_gNB_ULSCH_t **ulschptr, uint16_t N_RB_UL) NR_gNB_ULSCH_t *new_gNB_ulsch(uint8_t max_ldpc_iterations, uint16_t N_RB_UL) { - NR_gNB_ULSCH_t *ulsch; - uint8_t i,r; uint16_t a_segments = MAX_NUM_NR_ULSCH_SEGMENTS_PER_LAYER*NR_MAX_NB_LAYERS; //number of segments to be allocated if (N_RB_UL != 273) { @@ -97,70 +94,31 @@ NR_gNB_ULSCH_t *new_gNB_ulsch(uint8_t max_ldpc_iterations, uint16_t N_RB_UL) a_segments = a_segments/273 +1; } - uint32_t ulsch_bytes = a_segments*1056; // allocated bytes per segment - ulsch = (NR_gNB_ULSCH_t *)malloc16_clear(sizeof(NR_gNB_ULSCH_t)); + uint32_t ulsch_bytes = a_segments * 1056; // allocated bytes per segment + NR_gNB_ULSCH_t *ulsch = (NR_gNB_ULSCH_t *)malloc16_clear(sizeof(NR_gNB_ULSCH_t)); ulsch->max_ldpc_iterations = max_ldpc_iterations; + ulsch->harq_pid = -1; + ulsch->active = false; - for (i=0; iharq_processes[i] = (NR_UL_gNB_HARQ_t *)malloc16_clear(sizeof(NR_UL_gNB_HARQ_t)); - ulsch->harq_processes[i]->b = (uint8_t*)malloc16_clear(ulsch_bytes); - ulsch->harq_processes[i]->c = (uint8_t**)malloc16_clear(a_segments*sizeof(uint8_t *)); - ulsch->harq_processes[i]->d = (int16_t**)malloc16_clear(a_segments*sizeof(int16_t *)); - for (r=0; rharq_processes[i]->c[r] = (uint8_t*)malloc16_clear(8448*sizeof(uint8_t)); - ulsch->harq_processes[i]->d[r] = (int16_t*)malloc16_clear((68*384)*sizeof(int16_t)); - } + ulsch->harq_process = (NR_UL_gNB_HARQ_t *)malloc16_clear(sizeof(NR_UL_gNB_HARQ_t)); + ulsch->harq_process->b = (uint8_t*)malloc16_clear(ulsch_bytes); + ulsch->harq_process->c = (uint8_t**)malloc16_clear(a_segments*sizeof(uint8_t *)); + ulsch->harq_process->d = (int16_t**)malloc16_clear(a_segments*sizeof(int16_t *)); + for (int r = 0; r < a_segments; r++) { + ulsch->harq_process->c[r] = (uint8_t*)malloc16_clear(8448*sizeof(uint8_t)); + ulsch->harq_process->d[r] = (int16_t*)malloc16_clear((68*384)*sizeof(int16_t)); } - return(ulsch); } -void clean_gNB_ulsch(NR_gNB_ULSCH_t *ulsch) -{ - - - if (ulsch) { - ulsch->harq_mask = 0; - ulsch->rnti = 0; - ulsch->rnti_type = 0; - ulsch->max_ldpc_iterations = 0; - ulsch->last_iteration_cnt = 0; - - for (int i=0; iharq_processes[i]){ - /// Nfapi ULSCH PDU - //nfapi_nr_ul_config_ulsch_pdu ulsch_pdu; - ulsch->harq_processes[i]->frame=0; - ulsch->harq_processes[i]->slot=0; - ulsch->harq_processes[i]->round=0; - ulsch->harq_processes[i]->status=NR_SCH_IDLE; - ulsch->harq_processes[i]->handled=0; - ulsch->harq_processes[i]->TBS=0; - /// Pointer to the payload (38.212 V15.4.0 section 5.1) - //uint8_t *b; - ulsch->harq_processes[i]->B=0; - /// Pointers to code blocks after code block segmentation and CRC attachment (38.212 V15.4.0 section 5.2.2) - //uint8_t *c[MAX_NUM_NR_ULSCH_SEGMENTS]; - ulsch->harq_processes[i]->K=0; - ulsch->harq_processes[i]->F=0; - ulsch->harq_processes[i]->C=0; - /// Pointers to code blocks after LDPC coding (38.212 V15.4.0 section 5.3.2) - //int16_t *d[MAX_NUM_NR_ULSCH_SEGMENTS]; - ulsch->harq_processes[i]->Z=0; - /// code blocks after bit selection in rate matching for LDPC code (38.212 V15.4.0 section 5.4.2.1) - //int16_t e[MAX_NUM_NR_ULSCH_SEGMENTS][3*8448]; - ulsch->harq_processes[i]->E=0; - } - } - } -} - #ifdef PRINT_CRC_CHECK static uint32_t prnt_crc_cnt = 0; #endif -void nr_processULSegment(void* arg) { +void nr_processULSegment(void* arg) +{ + ldpcDecode_t *rdata = (ldpcDecode_t*) arg; PHY_VARS_gNB *phy_vars_gNB = rdata->gNB; NR_UL_gNB_HARQ_t *ulsch_harq = rdata->ulsch_harq; @@ -325,14 +283,14 @@ uint32_t nr_ulsch_decoding(PHY_VARS_gNB *phy_vars_gNB, uint8_t harq_pid, uint32_t G) { + #ifdef PRINT_CRC_CHECK prnt_crc_cnt++; #endif - - NR_gNB_ULSCH_t *ulsch = phy_vars_gNB->ulsch[ULSCH_id]; - NR_gNB_PUSCH *pusch = phy_vars_gNB->pusch_vars[ULSCH_id]; - NR_UL_gNB_HARQ_t *harq_process = ulsch->harq_processes[harq_pid]; + NR_gNB_ULSCH_t *ulsch = phy_vars_gNB->ulsch[ULSCH_id]; + NR_gNB_PUSCH *pusch = phy_vars_gNB->pusch_vars[ULSCH_id]; + NR_UL_gNB_HARQ_t *harq_process = ulsch->harq_process; if (!harq_process) { LOG_E(PHY,"ulsch_decoding.c: NULL harq_process pointer\n"); @@ -379,30 +337,18 @@ uint32_t nr_ulsch_decoding(PHY_VARS_gNB *phy_vars_gNB, kc = 68; } - NR_gNB_SCH_STATS_t *stats=NULL; - int first_free=-1; - for (int i=0;iulsch_stats[i].rnti == 0 && first_free == -1) { - first_free = i; - stats=&phy_vars_gNB->ulsch_stats[i]; - } - if (phy_vars_gNB->ulsch_stats[i].rnti == ulsch->rnti) { - stats=&phy_vars_gNB->ulsch_stats[i]; - break; - } - } + NR_gNB_PHY_STATS_t *stats = get_phy_stats(phy_vars_gNB, ulsch->rnti); if (stats) { stats->frame = frame; - stats->rnti = ulsch->rnti; - stats->round_trials[harq_process->round]++; - for (int aarx=0;aarxnb_antennas_rx;aarx++) { - stats->power[aarx] = dB_fixed_x10(pusch->ulsch_power[aarx]); - stats->noise_power[aarx] = dB_fixed_x10(pusch->ulsch_noise_power[aarx]); + stats->ulsch_stats.round_trials[harq_process->round]++; + for (int aarx = 0; aarx < frame_parms->nb_antennas_rx; aarx++) { + stats->ulsch_stats.power[aarx] = dB_fixed_x10(pusch->ulsch_power[aarx]); + stats->ulsch_stats.noise_power[aarx] = dB_fixed_x10(pusch->ulsch_noise_power[aarx]); } if (harq_process->new_rx == 0) { - stats->current_Qm = Qm; - stats->current_RI = n_layers; - stats->total_bytes_tx += harq_process->TBS; + stats->ulsch_stats.current_Qm = Qm; + stats->ulsch_stats.current_RI = n_layers; + stats->ulsch_stats.total_bytes_tx += harq_process->TBS; } } if (A > 3824) @@ -594,9 +540,8 @@ uint32_t nr_ulsch_decoding(PHY_VARS_gNB *phy_vars_gNB, if (harq_process->processedSegments == (harq_process->C)) { LOG_D(PHY, "[gNB %d] ULSCH: Setting ACK for slot %d TBS %d\n", phy_vars_gNB->Mod_id, harq_process->slot, harq_process->TBS); - harq_process->status = NR_SCH_IDLE; + ulsch->active = false; harq_process->round = 0; - ulsch->harq_mask &= ~(1 << harq_pid); LOG_D(PHY, "ULSCH received ok \n"); nr_fill_indication(phy_vars_gNB, harq_process->frame, harq_process->slot, ULSCH_id, harq_pid, 0, 0); @@ -608,10 +553,10 @@ uint32_t nr_ulsch_decoding(PHY_VARS_gNB *phy_vars_gNB, harq_process->frame, harq_process->slot, harq_pid, - harq_process->status, + ulsch->active, harq_process->round, harq_process->TBS); - harq_process->handled = 1; + ulsch->handled = 1; no_iteration_ldpc = ulsch->max_ldpc_iterations + 1; LOG_D(PHY, "ULSCH %d in error\n", ULSCH_id); nr_fill_indication(phy_vars_gNB, harq_process->frame, harq_process->slot, ULSCH_id, harq_pid, 1, 0); diff --git a/openair1/PHY/NR_TRANSPORT/nr_ulsch_demodulation.c b/openair1/PHY/NR_TRANSPORT/nr_ulsch_demodulation.c index 47d778903e..67f33cbd6a 100644 --- a/openair1/PHY/NR_TRANSPORT/nr_ulsch_demodulation.c +++ b/openair1/PHY/NR_TRANSPORT/nr_ulsch_demodulation.c @@ -1847,7 +1847,7 @@ void nr_rx_pusch(PHY_VARS_gNB *gNB, int avgs = 0; NR_DL_FRAME_PARMS *frame_parms = &gNB->frame_parms; - nfapi_nr_pusch_pdu_t *rel15_ul = &gNB->ulsch[ulsch_id]->harq_processes[harq_pid]->ulsch_pdu; + nfapi_nr_pusch_pdu_t *rel15_ul = &gNB->ulsch[ulsch_id]->harq_process->ulsch_pdu; int avg[frame_parms->nb_antennas_rx*rel15_ul->nrOfLayers]; gNB->pusch_vars[ulsch_id]->dmrs_symbol = INVALID_VALUE; diff --git a/openair1/PHY/NR_TRANSPORT/pucch_rx.c b/openair1/PHY/NR_TRANSPORT/pucch_rx.c index 99a3439420..6324b9f60b 100644 --- a/openair1/PHY/NR_TRANSPORT/pucch_rx.c +++ b/openair1/PHY/NR_TRANSPORT/pucch_rx.c @@ -44,22 +44,23 @@ #include "PHY/NR_UE_TRANSPORT/pucch_nr.h" #include #include "PHY/NR_TRANSPORT/nr_transport_common_proto.h" +#include "PHY/NR_TRANSPORT/nr_transport_proto.h" #include "PHY/NR_REFSIG/nr_refsig.h" #include "common/utils/LOG/log.h" #include "common/utils/LOG/vcd_signal_dumper.h" #include "nfapi/oai_integration/vendor_ext.h" - #include "nfapi/oai_integration/vendor_ext.h" #include "T.h" //#define DEBUG_NR_PUCCH_RX 1 -NR_gNB_PUCCH_t *new_gNB_pucch(void){ - NR_gNB_PUCCH_t *pucch; - pucch = (NR_gNB_PUCCH_t *)malloc16(sizeof(NR_gNB_PUCCH_t)); - pucch->active = 0; - return (pucch); +NR_gNB_PUCCH_t *new_gNB_pucch(void) +{ + NR_gNB_PUCCH_t *pucch; + pucch = (NR_gNB_PUCCH_t *)malloc16(sizeof(NR_gNB_PUCCH_t)); + pucch->active = 0; + return (pucch); } void free_gNB_pucch(NR_gNB_PUCCH_t *pucch) @@ -67,32 +68,6 @@ void free_gNB_pucch(NR_gNB_PUCCH_t *pucch) free_and_zero(pucch); } -int nr_find_pucch(uint16_t rnti, - int frame, - int slot, - PHY_VARS_gNB *gNB) -{ - - AssertFatal(gNB!=NULL,"gNB is null\n"); - int index = -1; - - for (int i = 0; i < gNB->max_nb_pucch; i++) { - AssertFatal(gNB->pucch[i] != NULL,"gNB->pucch[%d] is null\n",i); - if ((gNB->pucch[i]->active > 0) && - (gNB->pucch[i]->pucch_pdu.rnti == rnti) && - (gNB->pucch[i]->frame == frame) && - (gNB->pucch[i]->slot == slot)) - return i; - else if ((gNB->pucch[i]->active == 0) && (index == -1)) - index = i; - } - - if (index==-1) - LOG_E(MAC,"PUCCH list is full\n"); - - return(index); -} - void nr_fill_pucch(PHY_VARS_gNB *gNB, int frame, int slot, @@ -101,18 +76,22 @@ void nr_fill_pucch(PHY_VARS_gNB *gNB, if (NFAPI_MODE == NFAPI_MODE_PNF) gNB->pucch[0]->active = 0; //check if ture in monolithic mode - int id = nr_find_pucch(pucch_pdu->rnti,frame,slot,gNB); - AssertFatal((id >= 0) && (id < gNB->max_nb_pucch), - "invalid id found for pucch !!! rnti %04x id %d\n",pucch_pdu->rnti,id); - NR_gNB_PUCCH_t *pucch = gNB->pucch[id]; - pucch->frame = frame; - pucch->slot = slot; - pucch->active = 1; - if (pucch->pucch_pdu.format_type > 0) - LOG_D(PHY,"Programming PUCCH[%d] for %d.%d, format %d, nb_harq %d, nb_sr %d, nb_csi %d\n",id, - pucch->frame,pucch->slot,pucch->pucch_pdu.format_type,pucch->pucch_pdu.bit_len_harq,pucch->pucch_pdu.sr_flag,pucch->pucch_pdu.bit_len_csi_part1); - memcpy((void*)&pucch->pucch_pdu, (void*)pucch_pdu, sizeof(nfapi_nr_pucch_pdu_t)); + bool found = false; + for (int i = 0; i < gNB->max_nb_pucch; i++) { + if (gNB->pucch[i]->active == 0) { + NR_gNB_PUCCH_t *pucch = gNB->pucch[i]; + pucch->frame = frame; + pucch->slot = slot; + pucch->active = 1; + memcpy((void*)&pucch->pucch_pdu, (void*)pucch_pdu, sizeof(nfapi_nr_pucch_pdu_t)); + LOG_D(PHY,"Programming PUCCH[%d] for %d.%d, format %d, nb_harq %d, nb_sr %d, nb_csi %d\n", + i, pucch->frame, pucch->slot, pucch->pucch_pdu.format_type, pucch->pucch_pdu.bit_len_harq, pucch->pucch_pdu.sr_flag, pucch->pucch_pdu.bit_len_csi_part1); + found = true; + break; + } + } + AssertFatal(found, "PUCCH list is full\n"); } @@ -176,38 +155,28 @@ int16_t idft12_im[12][12] = { {0,-11585,-20066,-23170,-20066,-11585,0,11585,20066,23170,20066,11585} }; - void nr_decode_pucch0(PHY_VARS_gNB *gNB, int frame, int slot, nfapi_nr_uci_pucch_pdu_format_0_1_t* uci_pdu, - nfapi_nr_pucch_pdu_t* pucch_pdu) { + nfapi_nr_pucch_pdu_t* pucch_pdu) +{ c16_t **rxdataF = gNB->common_vars.rxdataF; NR_DL_FRAME_PARMS *frame_parms = &gNB->frame_parms; - int soffset=(slot&3)*frame_parms->symbols_per_slot*frame_parms->ofdm_symbol_size; - int nr_sequences; - const uint8_t *mcs; - - pucch_GroupHopping_t pucch_GroupHopping = pucch_pdu->group_hop_flag + (pucch_pdu->sequence_hop_flag<<1); + int soffset = (slot & 3) * frame_parms->symbols_per_slot * frame_parms->ofdm_symbol_size; AssertFatal(pucch_pdu->bit_len_harq > 0 || pucch_pdu->sr_flag > 0, "Either bit_len_harq (%d) or sr_flag (%d) must be > 0\n", pucch_pdu->bit_len_harq,pucch_pdu->sr_flag); - NR_gNB_UCI_STATS_t *uci_stats=NULL; - NR_gNB_UCI_STATS_t *first_uci_stats = gNB->uci_stats; - for (int i=0;iuci_stats[i].rnti == pucch_pdu->rnti) { - uci_stats = &gNB->uci_stats[i]; - break; - } else if (first_uci_stats == NULL && gNB->uci_stats[i].rnti == 0) first_uci_stats = &gNB->uci_stats[i]; - - if (uci_stats == NULL) { uci_stats=first_uci_stats; uci_stats->rnti = pucch_pdu->rnti;} - - AssertFatal(uci_stats!=NULL,"No stat index found\n"); - uci_stats->frame = frame; + NR_gNB_PHY_STATS_t *phy_stats = get_phy_stats(gNB, pucch_pdu->rnti); + AssertFatal(phy_stats != NULL,"No stat index found\n"); + phy_stats->frame = frame; + NR_gNB_UCI_STATS_t *uci_stats = &phy_stats->uci_stats; + int nr_sequences; + const uint8_t *mcs; if(pucch_pdu->bit_len_harq==0){ mcs=table1_mcs; nr_sequences=1; @@ -258,20 +227,18 @@ void nr_decode_pucch0(PHY_VARS_gNB *gNB, int n,i; int prb_offset[2] = {pucch_pdu->bwp_start+pucch_pdu->prb_start, pucch_pdu->bwp_start+pucch_pdu->prb_start}; - nr_group_sequence_hopping(pucch_GroupHopping,pucch_pdu->hopping_id,0,slot,&u[0],&v[0]); // calculating u and v value first hop + pucch_GroupHopping_t pucch_GroupHopping = pucch_pdu->group_hop_flag + (pucch_pdu->sequence_hop_flag << 1); + nr_group_sequence_hopping(pucch_GroupHopping, pucch_pdu->hopping_id, 0, slot, &u[0], &v[0]); // calculating u and v value first hop LOG_D(PHY,"pucch0: u %d, v %d\n",u[0],v[0]); - if (pucch_pdu->freq_hop_flag == 1) { - nr_group_sequence_hopping(pucch_GroupHopping,pucch_pdu->hopping_id,1,slot,&u[1],&v[1]); // calculating u and v value second hop + nr_group_sequence_hopping(pucch_GroupHopping, pucch_pdu->hopping_id, 1, slot, &u[1], &v[1]); // calculating u and v value second hop LOG_D(PHY,"pucch0 second hop: u %d, v %d\n",u[1],v[1]); - prb_offset[1] = pucch_pdu->bwp_start+pucch_pdu->second_hop_prb; + prb_offset[1] = pucch_pdu->bwp_start + pucch_pdu->second_hop_prb; } - AssertFatal(pucch_pdu->nr_of_symbols < 3,"nr_of_symbols %d not allowed\n",pucch_pdu->nr_of_symbols); - uint32_t re_offset[2]={0}; - uint8_t l2; + uint32_t re_offset[2] = {0}; const int16_t *x_re[2],*x_im[2]; x_re[0] = table_5_2_2_2_2_Re[u[0]]; @@ -288,7 +255,7 @@ void nr_decode_pucch0(PHY_VARS_gNB *gNB, int32_t rp[frame_parms->nb_antennas_rx][pucch_pdu->nr_of_symbols][nb_re_pucch],*tmp_rp; for (int l=0; lnr_of_symbols; l++) { - l2 = l+pucch_pdu->start_symbol_index; + uint8_t l2 = l+pucch_pdu->start_symbol_index; re_offset[l] = (12*prb_offset[l]) + frame_parms->first_carrier_offset; if (re_offset[l]>= frame_parms->ofdm_symbol_size) @@ -454,10 +421,10 @@ void nr_decode_pucch0(PHY_VARS_gNB *gNB, uci_pdu->harq->harq_confidence_level = no_conf; uci_pdu->harq->harq_list = (nfapi_nr_harq_t*)malloc(sizeof *uci_pdu->harq->harq_list); uci_pdu->harq->harq_list[0].harq_value = !(index&0x01); - LOG_D(PHY, "[DLSCH/PDSCH/PUCCH] %d.%d HARQ %s with confidence level %s xrt_mag %d xrt_mag_next %d n0 %d (%d,%d) pucch0_thres %d, cqi %d, SNRtimes10 %d, energy %f, sync_pos %d\n", + LOG_D(PHY, "[DLSCH/PDSCH/PUCCH] %d.%d HARQ %s with confidence level %s xrt_mag %d xrt_mag_next %d n0 %d (%d,%d) pucch0_thres %d, cqi %d, SNRtimes10 %d, energy %f\n", frame,slot,uci_pdu->harq->harq_list[0].harq_value==0?"ACK":"NACK", uci_pdu->harq->harq_confidence_level==0?"good":"bad", - xrtmag_dBtimes10,xrtmag_next_dBtimes10,max_n0,uci_stats->pucch0_n00,uci_stats->pucch0_n01,uci_stats->pucch0_thres,cqi,SNRtimes10,10*log10((double)sigenergy),gNB->ulsch_stats[0].sync_pos); + xrtmag_dBtimes10,xrtmag_next_dBtimes10,max_n0,uci_stats->pucch0_n00,uci_stats->pucch0_n01,uci_stats->pucch0_thres,cqi,SNRtimes10,10*log10((double)sigenergy)); if (pucch_pdu->sr_flag == 1) { uci_pdu->sr = calloc(1,sizeof(*uci_pdu->sr)); @@ -478,14 +445,13 @@ void nr_decode_pucch0(PHY_VARS_gNB *gNB, uci_pdu->harq->harq_list[1].harq_value = !(index&0x01); uci_pdu->harq->harq_list[0].harq_value = !((index>>1)&0x01); - LOG_D(PHY, "[DLSCH/PDSCH/PUCCH] %d.%d HARQ values (%s, %s) with confidence level %s, xrt_mag %d xrt_mag_next %d n0 %d (%d,%d) pucch0_thres %d, cqi %d, SNRtimes10 %d,sync_pos %d\n", + LOG_D(PHY, "[DLSCH/PDSCH/PUCCH] %d.%d HARQ values (%s, %s) with confidence level %s, xrt_mag %d xrt_mag_next %d n0 %d (%d,%d) pucch0_thres %d, cqi %d, SNRtimes10 %d\n", frame,slot, uci_pdu->harq->harq_list[1].harq_value == 0 ? "ACK" : "NACK", uci_pdu->harq->harq_list[0].harq_value == 0 ? "ACK" : "NACK", uci_pdu->harq->harq_confidence_level == 0 ? "good" : "bad", xrtmag_dBtimes10,xrtmag_next_dBtimes10,max_n0, - uci_stats->pucch0_n00,uci_stats->pucch0_n01,uci_stats->pucch0_thres,cqi, - SNRtimes10,gNB->ulsch_stats[0].sync_pos); + uci_stats->pucch0_n00,uci_stats->pucch0_n01,uci_stats->pucch0_thres,cqi,SNRtimes10); if (pucch_pdu->sr_flag == 1) { uci_pdu->sr = calloc(1,sizeof(*uci_pdu->sr)); uci_pdu->sr->sr_indication = (index>3) ? 1 : 0; @@ -1209,8 +1175,8 @@ void nr_decode_pucch2(PHY_VARS_gNB *gNB, } } } - LOG_D(PHY,"Decoding pucch2 for %d symbols, %d PRB, nb_harq %d, nb_sr %d, nb_csi %d/%d\n", - pucch_pdu->nr_of_symbols,pucch_pdu->prb_size, + LOG_D(PHY,"%d.%d Decoding pucch2 for %d symbols, %d PRB, nb_harq %d, nb_sr %d, nb_csi %d/%d\n", + frame, slot, pucch_pdu->nr_of_symbols,pucch_pdu->prb_size, pucch_pdu->bit_len_harq,pucch_pdu->sr_flag,pucch_pdu->bit_len_csi_part1,pucch_pdu->bit_len_csi_part2); int nc_group_size=1; // 2 PRB @@ -1790,30 +1756,31 @@ void nr_decode_pucch2(PHY_VARS_gNB *gNB, void nr_dump_uci_stats(FILE *fd,PHY_VARS_gNB *gNB,int frame) { - int strpos=0; - char output[16384]; + int strpos=0; + char output[16384]; - for (int i=0;iuci_stats[i].rnti>0) { - NR_gNB_UCI_STATS_t *uci_stats = &gNB->uci_stats[i]; - if (uci_stats->pucch0_sr_trials > 0) - strpos+=sprintf(output+strpos,"UCI %d RNTI %x: pucch0_sr_trials %d, pucch0_n00 %d dB, pucch0_n01 %d dB, pucch0_sr_thres %d dB, current pucch1_stat0 %d dB, current pucch1_stat1 %d dB, positive SR count %d\n", - i,uci_stats->rnti,uci_stats->pucch0_sr_trials,uci_stats->pucch0_n00,uci_stats->pucch0_n01,uci_stats->pucch0_sr_thres,dB_fixed(uci_stats->current_pucch0_sr_stat0),dB_fixed(uci_stats->current_pucch0_sr_stat1),uci_stats->pucch0_positive_SR); - if (uci_stats->pucch01_trials > 0) - strpos+=sprintf(output+strpos,"UCI %d RNTI %x: pucch01_trials %d, pucch0_n00 %d dB, pucch0_n01 %d dB, pucch0_thres %d dB, current pucch0_stat0 %d dB, current pucch1_stat1 %d dB, pucch01_DTX %d\n", - i,uci_stats->rnti,uci_stats->pucch01_trials,uci_stats->pucch0_n01,uci_stats->pucch0_n01,uci_stats->pucch0_thres,dB_fixed(uci_stats->current_pucch0_stat0),dB_fixed(uci_stats->current_pucch0_stat1),uci_stats->pucch01_DTX); + for (int i = 0; i < MAX_MOBILES_PER_GNB; i++) { + NR_gNB_PHY_STATS_t *stats = &gNB->phy_stats[i]; + if (stats->active) { + NR_gNB_UCI_STATS_t *uci_stats = &stats->uci_stats; + if (uci_stats->pucch0_sr_trials > 0) + strpos+=sprintf(output+strpos,"UCI %d RNTI %x: pucch0_sr_trials %d, pucch0_n00 %d dB, pucch0_n01 %d dB, pucch0_sr_thres %d dB, current pucch1_stat0 %d dB, current pucch1_stat1 %d dB, positive SR count %d\n", + i,stats->rnti,uci_stats->pucch0_sr_trials,uci_stats->pucch0_n00,uci_stats->pucch0_n01,uci_stats->pucch0_sr_thres,dB_fixed(uci_stats->current_pucch0_sr_stat0),dB_fixed(uci_stats->current_pucch0_sr_stat1),uci_stats->pucch0_positive_SR); + if (uci_stats->pucch01_trials > 0) + strpos+=sprintf(output+strpos,"UCI %d RNTI %x: pucch01_trials %d, pucch0_n00 %d dB, pucch0_n01 %d dB, pucch0_thres %d dB, current pucch0_stat0 %d dB, current pucch1_stat1 %d dB, pucch01_DTX %d\n", + i,stats->rnti,uci_stats->pucch01_trials,uci_stats->pucch0_n01,uci_stats->pucch0_n01,uci_stats->pucch0_thres,dB_fixed(uci_stats->current_pucch0_stat0),dB_fixed(uci_stats->current_pucch0_stat1),uci_stats->pucch01_DTX); - if (uci_stats->pucch02_trials > 0) - strpos+=sprintf(output+strpos,"UCI %d RNTI %x: pucch01_trials %d, pucch0_n00 %d dB, pucch0_n01 %d dB, pucch0_thres %d dB, current pucch0_stat0 %d dB, current pucch0_stat1 %d dB, pucch01_DTX %d\n", - i,uci_stats->rnti,uci_stats->pucch02_trials,uci_stats->pucch0_n00,uci_stats->pucch0_n01,uci_stats->pucch0_thres,dB_fixed(uci_stats->current_pucch0_stat0),dB_fixed(uci_stats->current_pucch0_stat1),uci_stats->pucch02_DTX); + if (uci_stats->pucch02_trials > 0) + strpos+=sprintf(output+strpos,"UCI %d RNTI %x: pucch01_trials %d, pucch0_n00 %d dB, pucch0_n01 %d dB, pucch0_thres %d dB, current pucch0_stat0 %d dB, current pucch0_stat1 %d dB, pucch01_DTX %d\n", + i,stats->rnti,uci_stats->pucch02_trials,uci_stats->pucch0_n00,uci_stats->pucch0_n01,uci_stats->pucch0_thres,dB_fixed(uci_stats->current_pucch0_stat0),dB_fixed(uci_stats->current_pucch0_stat1),uci_stats->pucch02_DTX); - if (uci_stats->pucch2_trials > 0) - strpos+=sprintf(output+strpos,"UCI %d RNTI %x: pucch2_trials %d, pucch2_DTX %d\n", - i,uci_stats->rnti, - uci_stats->pucch2_trials, - uci_stats->pucch2_DTX); - } + if (uci_stats->pucch2_trials > 0) + strpos+=sprintf(output+strpos,"UCI %d RNTI %x: pucch2_trials %d, pucch2_DTX %d\n", + i,stats->rnti, + uci_stats->pucch2_trials, + uci_stats->pucch2_DTX); } - if (fd) fprintf(fd,"%s",output); - else printf("%s",output); + } + if (fd) fprintf(fd,"%s",output); + else printf("%s",output); } diff --git a/openair1/PHY/NR_TRANSPORT/srs_rx.c b/openair1/PHY/NR_TRANSPORT/srs_rx.c index 5521619a8f..310a63d2c3 100644 --- a/openair1/PHY/NR_TRANSPORT/srs_rx.c +++ b/openair1/PHY/NR_TRANSPORT/srs_rx.c @@ -56,43 +56,25 @@ void free_gNB_srs(NR_gNB_SRS_t *srs) free_and_zero(srs); } -int nr_find_srs(rnti_t rnti, - frame_t frame, - slot_t slot, - PHY_VARS_gNB *gNB) { - - AssertFatal(gNB!=NULL,"gNB is null\n"); - int index = -1; - - for (int i = 0; i < gNB->max_nb_srs; i++) { - AssertFatal(gNB->srs[i]!=NULL,"gNB->srs[%d] is null\n",i); - if ((gNB->srs[i]->active>0) && - (gNB->srs[i]->srs_pdu.rnti==rnti) && - (gNB->srs[i]->frame==frame) && - (gNB->srs[i]->slot==slot)) return(i); - else if ((gNB->srs[i]->active == 0) && (index==-1)) index=i; - } - - if (index==-1) - LOG_E(PHY,"SRS list is full\n"); - - return(index); -} - void nr_fill_srs(PHY_VARS_gNB *gNB, frame_t frame, slot_t slot, - nfapi_nr_srs_pdu_t *srs_pdu) { + nfapi_nr_srs_pdu_t *srs_pdu) +{ - int id = nr_find_srs(srs_pdu->rnti,frame,slot,gNB); - AssertFatal((id >= 0) && (id < gNB->max_nb_srs), - "invalid id found for srs !!! rnti %04x id %d\n",srs_pdu->rnti,id); - - NR_gNB_SRS_t *srs = gNB->srs[id]; - srs->frame = frame; - srs->slot = slot; - srs->active = 1; - memcpy((void*)&srs->srs_pdu, (void*)srs_pdu, sizeof(nfapi_nr_srs_pdu_t)); + bool found = false; + for (int i = 0; i < gNB->max_nb_srs; i++) { + if (gNB->srs[i]->active == 0) { + found = true; + NR_gNB_SRS_t *srs = gNB->srs[i]; + srs->frame = frame; + srs->slot = slot; + srs->active = 1; + memcpy((void*)&srs->srs_pdu, (void*)srs_pdu, sizeof(nfapi_nr_srs_pdu_t)); + break; + } + } + AssertFatal(found, "SRS list is full\n"); } int nr_get_srs_signal(PHY_VARS_gNB *gNB, diff --git a/openair1/PHY/defs_gNB.h b/openair1/PHY/defs_gNB.h index 2badbd0868..9a59214877 100644 --- a/openair1/PHY/defs_gNB.h +++ b/openair1/PHY/defs_gNB.h @@ -97,9 +97,7 @@ typedef struct { } NR_gNB_CSIRS_t; typedef struct { - int frame; int dump_frame; - uint16_t rnti; int round_trials[8]; int total_bytes_tx; int total_bytes_rx; @@ -112,8 +110,6 @@ typedef struct { } NR_gNB_SCH_STATS_t; typedef struct { - int frame; - uint16_t rnti; int pucch0_sr_trials; int pucch0_sr_thres; int current_pucch0_sr_stat0; @@ -132,6 +128,17 @@ typedef struct { int pucch2_DTX; } NR_gNB_UCI_STATS_t; +typedef struct { + int frame; + uint16_t rnti; + bool active; + /// statistics for DLSCH measurement collection + NR_gNB_SCH_STATS_t dlsch_stats; + /// statistics for ULSCH measurement collection + NR_gNB_SCH_STATS_t ulsch_stats; + NR_gNB_UCI_STATS_t uci_stats; +} NR_gNB_PHY_STATS_t; + typedef struct { /// Pointers to variables related to DLSCH harq process NR_DL_gNB_HARQ_t harq_process; @@ -141,12 +148,8 @@ typedef struct { int32_t **mod_symbs; /// beamforming weights for UE-spec transmission (antenna ports 5 or 7..14), for each codeword, maximum 4 layers? int32_t ***ue_spec_bf_weights; - /// Allocated RNTI (0 means DLSCH_t is not currently used) - uint16_t rnti; /// Active flag for baseband transmitter processing uint8_t active; - /// HARQ process mask, indicates which processes are currently active - uint16_t harq_mask; /// Number of soft channel bits uint32_t G; } NR_gNB_DLSCH_t; @@ -192,11 +195,7 @@ typedef struct { /// Index of current HARQ round for this DLSCH uint8_t round; bool new_rx; - /// Status Flag indicating for this ULSCH (idle,active,disabled) - NR_SCH_status_t status; - /// Flag to indicate that the UL configuration has been handled. Used to remove a stale ULSCH when frame wraps around - uint8_t handled; - /////////////////////// ulsch decoding /////////////////////// + /////////////////////// ulsch decoding /////////////////////// /// Transport block size (This is A from 38.212 V15.4.0 section 5.1) uint32_t TBS; /// Pointer to the payload (38.212 V15.4.0 section 5.1) @@ -226,19 +225,31 @@ typedef struct { } NR_UL_gNB_HARQ_t; +typedef struct { + /// Time shift in number of samples estimated based on DMRS-PUSCH + int pusch_est_delay; + /// Max position in OFDM symbol related to time shift estimation based on DMRS-PUSCH + int pusch_delay_max_pos; + /// Max value related to time shift estimation based on DMRS-PUSCH + int pusch_delay_max_val; +} NR_ULSCH_delay_t; + typedef struct { /// Pointers to 16 HARQ processes for the ULSCH - NR_UL_gNB_HARQ_t *harq_processes[NR_MAX_ULSCH_HARQ_PROCESSES]; + NR_UL_gNB_HARQ_t *harq_process; /// HARQ process mask, indicates which processes are currently active - uint16_t harq_mask; + int harq_pid; /// Allocated RNTI for this ULSCH uint16_t rnti; - /// RNTI type - uint8_t rnti_type; /// Maximum number of LDPC iterations uint8_t max_ldpc_iterations; /// number of iterations used in last LDPC decoding - uint8_t last_iteration_cnt; + uint8_t last_iteration_cnt; + /// Status Flag indicating for this ULSCH + bool active; + /// Flag to indicate that the UL configuration has been handled. Used to remove a stale ULSCH when frame wraps around + uint8_t handled; + NR_ULSCH_delay_t delay; } NR_gNB_ULSCH_t; typedef struct { @@ -546,15 +557,6 @@ typedef struct { /// PRACH background noise level int prach_I0; - struct delay_s { - /// Time shift in number of samples estimated based on DMRS-PUSCH - int pusch_est_delay; - /// Max position in OFDM symbol related to time shift estimation based on DMRS-PUSCH - int pusch_delay_max_pos; - /// Max value related to time shift estimation based on DMRS-PUSCH - int pusch_delay_max_val; - } delay[NUMBER_OF_NR_ULSCH_MAX]; - } PHY_MEASUREMENTS_gNB; @@ -615,11 +617,7 @@ typedef struct PHY_VARS_gNB_s { NR_gNB_PUCCH_t **pucch; NR_gNB_SRS_t **srs; NR_gNB_ULSCH_t *ulsch[NUMBER_OF_NR_ULSCH_MAX]; // [Nusers times] - /// statistics for DLSCH measurement collection - NR_gNB_SCH_STATS_t dlsch_stats[NUMBER_OF_NR_SCH_STATS_MAX]; - /// statistics for ULSCH measurement collection - NR_gNB_SCH_STATS_t ulsch_stats[NUMBER_OF_NR_SCH_STATS_MAX]; - NR_gNB_UCI_STATS_t uci_stats[NUMBER_OF_NR_UCI_STATS_MAX]; + NR_gNB_PHY_STATS_t phy_stats[MAX_MOBILES_PER_GNB]; t_nrPolar_params **polarParams; /// SRS variables diff --git a/openair1/SCHED_NR/fapi_nr_l1.c b/openair1/SCHED_NR/fapi_nr_l1.c index b9f0d8e681..798b85f1e8 100644 --- a/openair1/SCHED_NR/fapi_nr_l1.c +++ b/openair1/SCHED_NR/fapi_nr_l1.c @@ -103,7 +103,8 @@ void handle_nr_nfapi_ssb_pdu(processingData_L1tx_t *msgTx,int frame,int slot, void handle_nfapi_nr_csirs_pdu(processingData_L1tx_t *msgTx, int frame,int slot, - nfapi_nr_dl_tti_csi_rs_pdu *csirs_pdu) { + nfapi_nr_dl_tti_csi_rs_pdu *csirs_pdu) +{ int found = 0; @@ -127,12 +128,12 @@ void handle_nr_nfapi_pdsch_pdu(processingData_L1tx_t *msgTx, uint8_t *sdu) { - nr_fill_dlsch(msgTx,pdsch_pdu,sdu); } -void nr_schedule_response(NR_Sched_Rsp_t *Sched_INFO){ +void nr_schedule_response(NR_Sched_Rsp_t *Sched_INFO) +{ PHY_VARS_gNB *gNB; // copy data from L2 interface into L1 structures @@ -170,9 +171,9 @@ void nr_schedule_response(NR_Sched_Rsp_t *Sched_INFO){ const time_stats_t ts = exec_time_stats_NotifiedFIFO(res); merge_meas(&gNB->phy_proc_tx, &ts); - msgTx->num_pdsch_slot=0; - msgTx->num_dl_pdcch=0; - msgTx->num_ul_pdcch=number_ul_dci_pdu; + msgTx->num_pdsch_slot = 0; + msgTx->num_dl_pdcch = 0; + msgTx->num_ul_pdcch = number_ul_dci_pdu; msgTx->slot = slot; msgTx->frame = frame; diff --git a/openair1/SCHED_NR/phy_procedures_nr_gNB.c b/openair1/SCHED_NR/phy_procedures_nr_gNB.c index fc36ad3f79..a9471dd6cc 100644 --- a/openair1/SCHED_NR/phy_procedures_nr_gNB.c +++ b/openair1/SCHED_NR/phy_procedures_nr_gNB.c @@ -207,12 +207,14 @@ void phy_procedures_gNB_TX(processingData_L1tx_t *msgTx, VCD_SIGNAL_DUMPER_DUMP_FUNCTION_BY_NAME(VCD_SIGNAL_DUMPER_FUNCTIONS_PHY_PROCEDURES_gNB_TX+offset,0); } -void nr_postDecode(PHY_VARS_gNB *gNB, notifiedFIFO_elt_t *req) { +void nr_postDecode(PHY_VARS_gNB *gNB, notifiedFIFO_elt_t *req) +{ + ldpcDecode_t *rdata = (ldpcDecode_t*) NotifiedFifoData(req); NR_UL_gNB_HARQ_t *ulsch_harq = rdata->ulsch_harq; NR_gNB_ULSCH_t *ulsch = rdata->ulsch; int r = rdata->segment_r; - nfapi_nr_pusch_pdu_t *pusch_pdu = &gNB->ulsch[rdata->ulsch_id]->harq_processes[rdata->harq_pid]->ulsch_pdu; + nfapi_nr_pusch_pdu_t *pusch_pdu = &gNB->ulsch[rdata->ulsch_id]->harq_process->ulsch_pdu; bool decodeSuccess = (rdata->decodeIterations <= rdata->decoderParms.numMaxIter); ulsch_harq->processedSegments++; LOG_D(PHY, "processing result of segment: %d, processed %d/%d\n", @@ -241,27 +243,25 @@ void nr_postDecode(PHY_VARS_gNB *gNB, notifiedFIFO_elt_t *req) { // if all segments are done if (rdata->nbSegments == ulsch_harq->processedSegments) { if (decodeSuccess && !gNB->pusch_vars[rdata->ulsch_id]->DTX) { - LOG_D(PHY,"[gNB %d] ULSCH: Setting ACK for SFN/SF %d.%d (pid %d, ndi %d, status %d, round %d, TBS %d, Max interation (all seg) %d)\n", - gNB->Mod_id,ulsch_harq->frame,ulsch_harq->slot,rdata->harq_pid,pusch_pdu->pusch_data.new_data_indicator,ulsch_harq->status,ulsch_harq->round,ulsch_harq->TBS,rdata->decodeIterations); - ulsch_harq->status = NR_SCH_IDLE; - ulsch_harq->round = 0; - ulsch->harq_mask &= ~(1 << rdata->harq_pid); - + LOG_D(PHY,"[gNB %d] ULSCH: Setting ACK for SFN/SF %d.%d (rnti %x, pid %d, ndi %d, status %d, round %d, TBS %d, Max interation (all seg) %d)\n", + gNB->Mod_id, ulsch_harq->frame, ulsch_harq->slot, ulsch->rnti, rdata->harq_pid, pusch_pdu->pusch_data.new_data_indicator, + ulsch->active, ulsch_harq->round, ulsch_harq->TBS, rdata->decodeIterations); + ulsch->active = false; + ulsch_harq->round = 0; LOG_D(PHY, "ULSCH received ok \n"); - nr_fill_indication(gNB,ulsch_harq->frame, ulsch_harq->slot, rdata->ulsch_id, rdata->harq_pid, 0,0); + nr_fill_indication(gNB, ulsch_harq->frame, ulsch_harq->slot, rdata->ulsch_id, rdata->harq_pid, 0,0); //dumpsig=1; } else { LOG_D(PHY,"[gNB %d] ULSCH: Setting NAK for SFN/SF %d/%d (pid %d, ndi %d, status %d, round %d, RV %d, prb_start %d, prb_size %d, TBS %d) r %d\n", gNB->Mod_id, ulsch_harq->frame, ulsch_harq->slot, - rdata->harq_pid, pusch_pdu->pusch_data.new_data_indicator, ulsch_harq->status, - ulsch_harq->round, + rdata->harq_pid, pusch_pdu->pusch_data.new_data_indicator, ulsch->active, + ulsch_harq->round, ulsch_harq->ulsch_pdu.pusch_data.rv_index, - ulsch_harq->ulsch_pdu.rb_start, - ulsch_harq->ulsch_pdu.rb_size, - ulsch_harq->TBS, - r); - ulsch_harq->handled = 1; - + ulsch_harq->ulsch_pdu.rb_start, + ulsch_harq->ulsch_pdu.rb_size, + ulsch_harq->TBS, + r); + ulsch->handled = 1; LOG_D(PHY, "ULSCH %d in error\n",rdata->ulsch_id); nr_fill_indication(gNB,ulsch_harq->frame, ulsch_harq->slot, rdata->ulsch_id, rdata->harq_pid, 1,0); // dumpsig=1; @@ -306,17 +306,17 @@ void nr_postDecode(PHY_VARS_gNB *gNB, notifiedFIFO_elt_t *req) { void nr_ulsch_procedures(PHY_VARS_gNB *gNB, int frame_rx, int slot_rx, int ULSCH_id, uint8_t harq_pid) { - NR_DL_FRAME_PARMS *frame_parms = &gNB->frame_parms; - nfapi_nr_pusch_pdu_t *pusch_pdu = &gNB->ulsch[ULSCH_id]->harq_processes[harq_pid]->ulsch_pdu; - - uint8_t l, number_dmrs_symbols = 0; - uint32_t G; - uint16_t start_symbol, number_symbols, nb_re_dmrs; - uint8_t enable_ldpc_offload = gNB->ldpc_offload_flag; - start_symbol = pusch_pdu->start_symbol_index; - number_symbols = pusch_pdu->nr_of_symbols; - for (l = start_symbol; l < start_symbol + number_symbols; l++) + NR_DL_FRAME_PARMS *frame_parms = &gNB->frame_parms; + nfapi_nr_pusch_pdu_t *pusch_pdu = &gNB->ulsch[ULSCH_id]->harq_process->ulsch_pdu; + + uint16_t nb_re_dmrs; + uint8_t enable_ldpc_offload = gNB->ldpc_offload_flag; + uint16_t start_symbol = pusch_pdu->start_symbol_index; + uint16_t number_symbols = pusch_pdu->nr_of_symbols; + + uint8_t number_dmrs_symbols = 0; + for (int l = start_symbol; l < start_symbol + number_symbols; l++) number_dmrs_symbols += ((pusch_pdu->ul_dmrs_symb_pos)>>l)&0x01; if (pusch_pdu->dmrs_config_type==pusch_dmrs_type1) @@ -324,12 +324,12 @@ void nr_ulsch_procedures(PHY_VARS_gNB *gNB, int frame_rx, int slot_rx, int ULSCH else nb_re_dmrs = 4*pusch_pdu->num_dmrs_cdm_grps_no_data; - G = nr_get_G(pusch_pdu->rb_size, - number_symbols, - nb_re_dmrs, - number_dmrs_symbols, // number of dmrs symbols irrespective of single or double symbol dmrs - pusch_pdu->qam_mod_order, - pusch_pdu->nrOfLayers); + uint32_t G = nr_get_G(pusch_pdu->rb_size, + number_symbols, + nb_re_dmrs, + number_dmrs_symbols, // number of dmrs symbols irrespective of single or double symbol dmrs + pusch_pdu->qam_mod_order, + pusch_pdu->nrOfLayers); AssertFatal(G>0,"G is 0 : rb_size %u, number_symbols %d, nb_re_dmrs %d, number_dmrs_symbols %d, qam_mod_order %u, nrOfLayer %u\n", pusch_pdu->rb_size, @@ -377,7 +377,7 @@ void nr_ulsch_procedures(PHY_VARS_gNB *gNB, int frame_rx, int slot_rx, int ULSCH slot_rx, harq_pid, G); - if (enable_ldpc_offload ==0) { + if (enable_ldpc_offload == 0) { while (gNB->nbDecode > 0) { notifiedFIFO_elt_t *req = pullTpool(&gNB->respDecode, &gNB->threadPool); if (req == NULL) @@ -390,18 +390,19 @@ void nr_ulsch_procedures(PHY_VARS_gNB *gNB, int frame_rx, int slot_rx, int ULSCH } -void nr_fill_indication(PHY_VARS_gNB *gNB, int frame, int slot_rx, int ULSCH_id, uint8_t harq_pid, uint8_t crc_flag, int dtx_flag) { +void nr_fill_indication(PHY_VARS_gNB *gNB, int frame, int slot_rx, int ULSCH_id, uint8_t harq_pid, uint8_t crc_flag, int dtx_flag) +{ pthread_mutex_lock(&gNB->UL_INFO_mutex); - NR_gNB_ULSCH_t *ulsch = gNB->ulsch[ULSCH_id]; - NR_UL_gNB_HARQ_t *harq_process = ulsch->harq_processes[harq_pid]; - NR_gNB_SCH_STATS_t *stats=get_ulsch_stats(gNB,ulsch); + NR_gNB_ULSCH_t *ulsch = gNB->ulsch[ULSCH_id]; + NR_UL_gNB_HARQ_t *harq_process = ulsch->harq_process; + NR_gNB_PHY_STATS_t *stats = get_phy_stats(gNB, ulsch->rnti); nfapi_nr_pusch_pdu_t *pusch_pdu = &harq_process->ulsch_pdu; // Get estimated timing advance for MAC - int sync_pos = gNB->measurements.delay[ULSCH_id].pusch_est_delay; + int sync_pos = ulsch->delay.pusch_est_delay; // scale the 16 factor in N_TA calculation in 38.213 section 4.2 according to the used FFT size uint16_t bw_scaling = 16 * gNB->frame_parms.ofdm_symbol_size / 2048; @@ -412,7 +413,7 @@ void nr_fill_indication(PHY_VARS_gNB *gNB, int frame, int slot_rx, int ULSCH_id, sync_pos_rounded = sync_pos + (bw_scaling / 2) - 1; else sync_pos_rounded = sync_pos - (bw_scaling / 2) + 1; - if (stats) stats->sync_pos = sync_pos; + if (stats) stats->ulsch_stats.sync_pos = sync_pos; int timing_advance_update = sync_pos_rounded / bw_scaling; @@ -554,27 +555,21 @@ void fill_ul_rb_mask(PHY_VARS_gNB *gNB, int frame_rx, int slot_rx) { for (int ULSCH_id = 0; ULSCH_id < NUMBER_OF_NR_ULSCH_MAX; ULSCH_id++) { NR_gNB_ULSCH_t *ulsch = gNB->ulsch[ULSCH_id]; int harq_pid; - NR_UL_gNB_HARQ_t *ulsch_harq; - if ((ulsch) && - (ulsch->rnti > 0)) { - for (harq_pid=0;harq_pidharq_processes[harq_pid]; - AssertFatal(ulsch_harq!=NULL,"harq_pid %d is not allocated\n",harq_pid); - if ((ulsch_harq->status == NR_ACTIVE) && - (ulsch_harq->frame == frame_rx) && - (ulsch_harq->slot == slot_rx) && - (ulsch_harq->handled == 0)){ - uint8_t symbol_start = ulsch_harq->ulsch_pdu.start_symbol_index; - uint8_t symbol_end = symbol_start + ulsch_harq->ulsch_pdu.nr_of_symbols; - for (int symbol=symbol_start ; symbolframe_parms.frame_type == FDD || - (gNB->frame_parms.frame_type == TDD && gNB->gNB_config.tdd_table.max_tdd_periodicity_list[slot_rx].max_num_of_symbol_per_slot_list[symbol].slot_config.value==1)) { - LOG_D(PHY,"symbol %d Filling rb_mask_ul rb_size %d\n",symbol,ulsch_harq->ulsch_pdu.rb_size); - for (rb=0; rbulsch_pdu.rb_size; rb++) { - rb2 = rb+ulsch_harq->ulsch_pdu.rb_start+ulsch_harq->ulsch_pdu.bwp_start; - gNB->rb_mask_ul[symbol][rb2 >> 5] |= 1U << (rb2 & 31); - } - } + NR_UL_gNB_HARQ_t *ulsch_harq = ulsch_harq = ulsch->harq_process; + AssertFatal(ulsch_harq != NULL,"harq_pid %d is not allocated\n",harq_pid); + if ((ulsch->active == true) && + (ulsch_harq->frame == frame_rx) && + (ulsch_harq->slot == slot_rx) && + (ulsch->handled == 0)) { + uint8_t symbol_start = ulsch_harq->ulsch_pdu.start_symbol_index; + uint8_t symbol_end = symbol_start + ulsch_harq->ulsch_pdu.nr_of_symbols; + for (int symbol = symbol_start ; symbol < symbol_end ; symbol++) { + if(gNB->frame_parms.frame_type == FDD || + (gNB->frame_parms.frame_type == TDD && gNB->gNB_config.tdd_table.max_tdd_periodicity_list[slot_rx].max_num_of_symbol_per_slot_list[symbol].slot_config.value==1)) { + LOG_D(PHY,"symbol %d Filling rb_mask_ul rb_size %d\n",symbol,ulsch_harq->ulsch_pdu.rb_size); + for (rb=0; rbulsch_pdu.rb_size; rb++) { + rb2 = rb+ulsch_harq->ulsch_pdu.rb_start+ulsch_harq->ulsch_pdu.bwp_start; + gNB->rb_mask_ul[symbol][rb2 >> 5] |= 1U << (rb2 & 31); } } } @@ -683,7 +678,9 @@ int check_srs_pdu(const nfapi_nr_srs_pdu_t *srs_pdu, nfapi_nr_srs_pdu_t *saved_s return 0; } -int phy_procedures_gNB_uespec_RX(PHY_VARS_gNB *gNB, int frame_rx, int slot_rx) { +int phy_procedures_gNB_uespec_RX(PHY_VARS_gNB *gNB, int frame_rx, int slot_rx) +{ + /* those variables to log T_GNB_PHY_PUCCH_PUSCH_IQ only when we try to decode */ int pucch_decode_done = 0; int pusch_decode_done = 0; @@ -702,17 +699,18 @@ int phy_procedures_gNB_uespec_RX(PHY_VARS_gNB *gNB, int frame_rx, int slot_rx) { num_symb++; } } - else num_symb=NR_NUMBER_OF_SYMBOLS_PER_SLOT; + else + num_symb=NR_NUMBER_OF_SYMBOLS_PER_SLOT; gNB_I0_measurements(gNB,slot_rx,first_symb,num_symb); - const int soffset = (slot_rx&3) * gNB->frame_parms.symbols_per_slot * gNB->frame_parms.ofdm_symbol_size; - int offset = 10*gNB->frame_parms.ofdm_symbol_size + gNB->frame_parms.first_carrier_offset; - int power_rxF = signal_energy_nodc((int32_t *)&gNB->common_vars.rxdataF[0][soffset+offset+(47*12)],12*18); + const int soffset = (slot_rx & 3) * gNB->frame_parms.symbols_per_slot * gNB->frame_parms.ofdm_symbol_size; + int offset = 10 * gNB->frame_parms.ofdm_symbol_size + gNB->frame_parms.first_carrier_offset; + int power_rxF = signal_energy_nodc((int32_t *)&gNB->common_vars.rxdataF[0][soffset + offset + (47 * 12)], 12 * 18); LOG_D(PHY,"frame %d, slot %d: UL signal energy %d\n",frame_rx,slot_rx,power_rxF); start_meas(&gNB->phy_proc_rx); - for (int i = 0; i < gNB->max_nb_pucch; i++){ + for (int i = 0; i < gNB->max_nb_pucch; i++) { NR_gNB_PUCCH_t *pucch = gNB->pucch[i]; if (pucch) { if (NFAPI_MODE == NFAPI_MODE_PNF) @@ -736,8 +734,8 @@ int phy_procedures_gNB_uespec_RX(PHY_VARS_gNB *gNB, int frame_rx, int slot_rx) { nfapi_nr_uci_pucch_pdu_format_0_1_t *uci_pdu_format0 = &gNB->uci_pdu_list[num_ucis].pucch_pdu_format_0_1; offset = pucch_pdu->start_symbol_index*gNB->frame_parms.ofdm_symbol_size + (gNB->frame_parms.first_carrier_offset+pucch_pdu->prb_start*12); - power_rxF = signal_energy_nodc((int32_t *)&gNB->common_vars.rxdataF[0][soffset+offset],12); - LOG_D(PHY,"frame %d, slot %d: PUCCH signal energy %d\n",frame_rx,slot_rx,power_rxF); + power_rxF = signal_energy_nodc((int32_t *)&gNB->common_vars.rxdataF[0][soffset+offset], 12); + LOG_D(PHY,"frame %d, slot %d: PUCCH signal energy %d\n", frame_rx, slot_rx, power_rxF); nr_decode_pucch0(gNB, frame_rx, @@ -747,7 +745,7 @@ int phy_procedures_gNB_uespec_RX(PHY_VARS_gNB *gNB, int frame_rx, int slot_rx) { gNB->UL_INFO.uci_ind.num_ucis += 1; pucch->active = 0; - break; + break; case 2: num_ucis = gNB->UL_INFO.uci_ind.num_ucis; gNB->UL_INFO.uci_ind.uci_list = &gNB->uci_pdu_list[0]; @@ -757,7 +755,7 @@ int phy_procedures_gNB_uespec_RX(PHY_VARS_gNB *gNB, int frame_rx, int slot_rx) { gNB->uci_pdu_list[num_ucis].pdu_size = sizeof(nfapi_nr_uci_pucch_pdu_format_2_3_4_t); nfapi_nr_uci_pucch_pdu_format_2_3_4_t *uci_pdu_format2 = &gNB->uci_pdu_list[num_ucis].pucch_pdu_format_2_3_4; - LOG_D(PHY,"%d.%d Calling nr_decode_pucch2\n",frame_rx,slot_rx); + LOG_D(PHY,"%d.%d Calling nr_decode_pucch2\n", frame_rx, slot_rx); nr_decode_pucch2(gNB, frame_rx, slot_rx, @@ -768,104 +766,97 @@ int phy_procedures_gNB_uespec_RX(PHY_VARS_gNB *gNB, int frame_rx, int slot_rx) { pucch->active = 0; break; default: - AssertFatal(1==0,"Only PUCCH formats 0 and 2 are currently supported\n"); + AssertFatal(1==0,"Only PUCCH formats 0 and 2 are currently supported\n"); } } } } for (int ULSCH_id = 0; ULSCH_id < NUMBER_OF_NR_ULSCH_MAX; ULSCH_id++) { + NR_gNB_ULSCH_t *ulsch = gNB->ulsch[ULSCH_id]; - NR_UL_gNB_HARQ_t *ulsch_harq; + NR_UL_gNB_HARQ_t *ulsch_harq = ulsch->harq_process; + AssertFatal(ulsch_harq != NULL, "harq_pid %d is not allocated\n", ulsch->harq_pid); - if ((ulsch) && - (ulsch->rnti > 0)) { - // for for an active HARQ process - for (int harq_pid=0;harq_pidharq_processes[harq_pid]; - AssertFatal(ulsch_harq!=NULL,"harq_pid %d is not allocated\n",harq_pid); - if ((ulsch_harq->status == NR_ACTIVE) && - (ulsch_harq->frame == frame_rx) && - (ulsch_harq->slot == slot_rx) && - (ulsch_harq->handled == 0)){ + if ((ulsch->active == true) && + (ulsch_harq->frame == frame_rx) && + (ulsch_harq->slot == slot_rx) && + (ulsch->handled == 0)) { - LOG_D(PHY, "PUSCH detection started in frame %d slot %d\n", - frame_rx,slot_rx); - int num_dmrs=0; - for (int s=0;sulsch_pdu.ul_dmrs_symb_pos>>s)&1; + LOG_D(PHY, "PUSCH ID %d with RNTI %x detection started in frame %d slot %d\n", + ULSCH_id, ulsch->rnti, frame_rx, slot_rx); + int num_dmrs = 0; + for (int s = 0; s < NR_NUMBER_OF_SYMBOLS_PER_SLOT; s++) + num_dmrs += (ulsch_harq->ulsch_pdu.ul_dmrs_symb_pos >> s) & 1; #ifdef DEBUG_RXDATA - NR_DL_FRAME_PARMS *frame_parms = &gNB->frame_parms; - RU_t *ru = gNB->RU_list[0]; - int slot_offset = frame_parms->get_samples_slot_timestamp(slot_rx,frame_parms,0); - slot_offset -= ru->N_TA_offset; - ((int16_t*)&gNB->common_vars.debugBuff[gNB->common_vars.debugBuff_sample_offset])[0]=(int16_t)ulsch->rnti; - ((int16_t*)&gNB->common_vars.debugBuff[gNB->common_vars.debugBuff_sample_offset])[1]=(int16_t)ulsch_harq->ulsch_pdu.rb_size; - ((int16_t*)&gNB->common_vars.debugBuff[gNB->common_vars.debugBuff_sample_offset])[2]=(int16_t)ulsch_harq->ulsch_pdu.rb_start; - ((int16_t*)&gNB->common_vars.debugBuff[gNB->common_vars.debugBuff_sample_offset])[3]=(int16_t)ulsch_harq->ulsch_pdu.nr_of_symbols; - ((int16_t*)&gNB->common_vars.debugBuff[gNB->common_vars.debugBuff_sample_offset])[4]=(int16_t)ulsch_harq->ulsch_pdu.start_symbol_index; - ((int16_t*)&gNB->common_vars.debugBuff[gNB->common_vars.debugBuff_sample_offset])[5]=(int16_t)ulsch_harq->ulsch_pdu.mcs_index; - ((int16_t*)&gNB->common_vars.debugBuff[gNB->common_vars.debugBuff_sample_offset])[6]=(int16_t)ulsch_harq->ulsch_pdu.pusch_data.rv_index; - ((int16_t*)&gNB->common_vars.debugBuff[gNB->common_vars.debugBuff_sample_offset])[7]=(int16_t)harq_pid; - memcpy(&gNB->common_vars.debugBuff[gNB->common_vars.debugBuff_sample_offset+4],&ru->common.rxdata[0][slot_offset],frame_parms->get_samples_per_slot(slot_rx,frame_parms)*sizeof(int32_t)); - gNB->common_vars.debugBuff_sample_offset+=(frame_parms->get_samples_per_slot(slot_rx,frame_parms)+1000+4); - if(gNB->common_vars.debugBuff_sample_offset>((frame_parms->get_samples_per_slot(slot_rx,frame_parms)+1000+2)*20)) { - FILE *f; - f = fopen("rxdata_buff.raw", "w"); if (f == NULL) exit(1); - fwrite((int16_t*)gNB->common_vars.debugBuff,2,(frame_parms->get_samples_per_slot(slot_rx,frame_parms)+1000+4)*20*2, f); - fclose(f); - exit(-1); - } + NR_DL_FRAME_PARMS *frame_parms = &gNB->frame_parms; + RU_t *ru = gNB->RU_list[0]; + int slot_offset = frame_parms->get_samples_slot_timestamp(slot_rx,frame_parms,0); + slot_offset -= ru->N_TA_offset; + ((int16_t*)&gNB->common_vars.debugBuff[gNB->common_vars.debugBuff_sample_offset])[0]=(int16_t)ulsch->rnti; + ((int16_t*)&gNB->common_vars.debugBuff[gNB->common_vars.debugBuff_sample_offset])[1]=(int16_t)ulsch_harq->ulsch_pdu.rb_size; + ((int16_t*)&gNB->common_vars.debugBuff[gNB->common_vars.debugBuff_sample_offset])[2]=(int16_t)ulsch_harq->ulsch_pdu.rb_start; + ((int16_t*)&gNB->common_vars.debugBuff[gNB->common_vars.debugBuff_sample_offset])[3]=(int16_t)ulsch_harq->ulsch_pdu.nr_of_symbols; + ((int16_t*)&gNB->common_vars.debugBuff[gNB->common_vars.debugBuff_sample_offset])[4]=(int16_t)ulsch_harq->ulsch_pdu.start_symbol_index; + ((int16_t*)&gNB->common_vars.debugBuff[gNB->common_vars.debugBuff_sample_offset])[5]=(int16_t)ulsch_harq->ulsch_pdu.mcs_index; + ((int16_t*)&gNB->common_vars.debugBuff[gNB->common_vars.debugBuff_sample_offset])[6]=(int16_t)ulsch_harq->ulsch_pdu.pusch_data.rv_index; + ((int16_t*)&gNB->common_vars.debugBuff[gNB->common_vars.debugBuff_sample_offset])[7]=(int16_t)ulsch->harq_pid; + memcpy(&gNB->common_vars.debugBuff[gNB->common_vars.debugBuff_sample_offset+4],&ru->common.rxdata[0][slot_offset],frame_parms->get_samples_per_slot(slot_rx,frame_parms)*sizeof(int32_t)); + gNB->common_vars.debugBuff_sample_offset+=(frame_parms->get_samples_per_slot(slot_rx,frame_parms)+1000+4); + if(gNB->common_vars.debugBuff_sample_offset>((frame_parms->get_samples_per_slot(slot_rx,frame_parms)+1000+2)*20)) { + FILE *f; + f = fopen("rxdata_buff.raw", "w"); if (f == NULL) exit(1); + fwrite((int16_t*)gNB->common_vars.debugBuff,2,(frame_parms->get_samples_per_slot(slot_rx,frame_parms)+1000+4)*20*2, f); + fclose(f); + exit(-1); + } #endif - pusch_decode_done = 1; + pusch_decode_done = 1; - VCD_SIGNAL_DUMPER_DUMP_FUNCTION_BY_NAME(VCD_SIGNAL_DUMPER_FUNCTIONS_NR_RX_PUSCH,1); - start_meas(&gNB->rx_pusch_stats); - nr_rx_pusch(gNB, ULSCH_id, frame_rx, slot_rx, harq_pid); - gNB->pusch_vars[ULSCH_id]->ulsch_power_tot=0; - gNB->pusch_vars[ULSCH_id]->ulsch_noise_power_tot=0; - for (int aarx=0;aarxframe_parms.nb_antennas_rx;aarx++) { - gNB->pusch_vars[ULSCH_id]->ulsch_power[aarx]/=num_dmrs; - gNB->pusch_vars[ULSCH_id]->ulsch_power_tot += gNB->pusch_vars[ULSCH_id]->ulsch_power[aarx]; - gNB->pusch_vars[ULSCH_id]->ulsch_noise_power[aarx]/=num_dmrs; - gNB->pusch_vars[ULSCH_id]->ulsch_noise_power_tot += gNB->pusch_vars[ULSCH_id]->ulsch_noise_power[aarx]; - } - if (dB_fixed_x10(gNB->pusch_vars[ULSCH_id]->ulsch_power_tot) < - dB_fixed_x10(gNB->pusch_vars[ULSCH_id]->ulsch_noise_power_tot) + gNB->pusch_thres) { - NR_gNB_SCH_STATS_t *stats=get_ulsch_stats(gNB,ulsch); - - LOG_D(PHY, "PUSCH not detected in %d.%d (%d,%d,%d)\n",frame_rx,slot_rx, - dB_fixed_x10(gNB->pusch_vars[ULSCH_id]->ulsch_power_tot), - dB_fixed_x10(gNB->pusch_vars[ULSCH_id]->ulsch_noise_power_tot),gNB->pusch_thres); - gNB->pusch_vars[ULSCH_id]->ulsch_power_tot = gNB->pusch_vars[ULSCH_id]->ulsch_noise_power_tot; - gNB->pusch_vars[ULSCH_id]->DTX=1; - if (stats) stats->DTX++; - if (!get_softmodem_params()->phy_test) { - /* in case of phy_test mode, we still want to decode to measure execution time. - Therefore, we don't yet call nr_fill_indication, it will be called later */ - nr_fill_indication(gNB,frame_rx, slot_rx, ULSCH_id, harq_pid, 1,1); - pusch_DTX++; - continue; - } - } else { - LOG_D(PHY, "PUSCH detected in %d.%d (%d,%d,%d)\n",frame_rx,slot_rx, - dB_fixed_x10(gNB->pusch_vars[ULSCH_id]->ulsch_power_tot), - dB_fixed_x10(gNB->pusch_vars[ULSCH_id]->ulsch_noise_power_tot),gNB->pusch_thres); - - gNB->pusch_vars[ULSCH_id]->DTX=0; - } - stop_meas(&gNB->rx_pusch_stats); - VCD_SIGNAL_DUMPER_DUMP_FUNCTION_BY_NAME(VCD_SIGNAL_DUMPER_FUNCTIONS_NR_RX_PUSCH,0); - //LOG_M("rxdataF_comp.m","rxF_comp",gNB->pusch_vars[0]->rxdataF_comp[0],6900,1,1); - //LOG_M("rxdataF_ext.m","rxF_ext",gNB->pusch_vars[0]->rxdataF_ext[0],6900,1,1); - VCD_SIGNAL_DUMPER_DUMP_FUNCTION_BY_NAME(VCD_SIGNAL_DUMPER_FUNCTIONS_NR_ULSCH_PROCEDURES_RX,1); - nr_ulsch_procedures(gNB, frame_rx, slot_rx, ULSCH_id, harq_pid); - VCD_SIGNAL_DUMPER_DUMP_FUNCTION_BY_NAME(VCD_SIGNAL_DUMPER_FUNCTIONS_NR_ULSCH_PROCEDURES_RX,0); - break; - } + VCD_SIGNAL_DUMPER_DUMP_FUNCTION_BY_NAME(VCD_SIGNAL_DUMPER_FUNCTIONS_NR_RX_PUSCH,1); + start_meas(&gNB->rx_pusch_stats); + nr_rx_pusch(gNB, ULSCH_id, frame_rx, slot_rx, ulsch->harq_pid); + gNB->pusch_vars[ULSCH_id]->ulsch_power_tot = 0; + gNB->pusch_vars[ULSCH_id]->ulsch_noise_power_tot = 0; + for (int aarx=0;aarxframe_parms.nb_antennas_rx;aarx++) { + gNB->pusch_vars[ULSCH_id]->ulsch_power[aarx] /= num_dmrs; + gNB->pusch_vars[ULSCH_id]->ulsch_power_tot += gNB->pusch_vars[ULSCH_id]->ulsch_power[aarx]; + gNB->pusch_vars[ULSCH_id]->ulsch_noise_power[aarx] /= num_dmrs; + gNB->pusch_vars[ULSCH_id]->ulsch_noise_power_tot += gNB->pusch_vars[ULSCH_id]->ulsch_noise_power[aarx]; } + if (dB_fixed_x10(gNB->pusch_vars[ULSCH_id]->ulsch_power_tot) < + dB_fixed_x10(gNB->pusch_vars[ULSCH_id]->ulsch_noise_power_tot) + gNB->pusch_thres) { + NR_gNB_PHY_STATS_t *stats = get_phy_stats(gNB, ulsch->rnti); + + LOG_D(PHY, "PUSCH not detected in %d.%d (%d,%d,%d)\n",frame_rx,slot_rx, + dB_fixed_x10(gNB->pusch_vars[ULSCH_id]->ulsch_power_tot), + dB_fixed_x10(gNB->pusch_vars[ULSCH_id]->ulsch_noise_power_tot),gNB->pusch_thres); + gNB->pusch_vars[ULSCH_id]->ulsch_power_tot = gNB->pusch_vars[ULSCH_id]->ulsch_noise_power_tot; + gNB->pusch_vars[ULSCH_id]->DTX = 1; + if (stats) stats->ulsch_stats.DTX++; + if (!get_softmodem_params()->phy_test) { + /* in case of phy_test mode, we still want to decode to measure execution time. + Therefore, we don't yet call nr_fill_indication, it will be called later */ + nr_fill_indication(gNB, frame_rx, slot_rx, ULSCH_id, ulsch->harq_pid, 1, 1); + pusch_DTX++; + continue; + } + } else { + LOG_D(PHY, "PUSCH detected in %d.%d (%d,%d,%d)\n",frame_rx,slot_rx, + dB_fixed_x10(gNB->pusch_vars[ULSCH_id]->ulsch_power_tot), + dB_fixed_x10(gNB->pusch_vars[ULSCH_id]->ulsch_noise_power_tot),gNB->pusch_thres); + + gNB->pusch_vars[ULSCH_id]->DTX=0; + } + stop_meas(&gNB->rx_pusch_stats); + VCD_SIGNAL_DUMPER_DUMP_FUNCTION_BY_NAME(VCD_SIGNAL_DUMPER_FUNCTIONS_NR_RX_PUSCH,0); + //LOG_M("rxdataF_comp.m","rxF_comp",gNB->pusch_vars[0]->rxdataF_comp[0],6900,1,1); + //LOG_M("rxdataF_ext.m","rxF_ext",gNB->pusch_vars[0]->rxdataF_ext[0],6900,1,1); + VCD_SIGNAL_DUMPER_DUMP_FUNCTION_BY_NAME(VCD_SIGNAL_DUMPER_FUNCTIONS_NR_ULSCH_PROCEDURES_RX,1); + nr_ulsch_procedures(gNB, frame_rx, slot_rx, ULSCH_id, ulsch->harq_pid); + VCD_SIGNAL_DUMPER_DUMP_FUNCTION_BY_NAME(VCD_SIGNAL_DUMPER_FUNCTIONS_NR_ULSCH_PROCEDURES_RX,0); } } diff --git a/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_primitives.c b/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_primitives.c index 7981db7522..1f86cf38c5 100644 --- a/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_primitives.c +++ b/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_primitives.c @@ -1979,13 +1979,9 @@ int get_nrofHARQ_ProcessesForPDSCH(e_NR_PDSCH_ServingCellConfig__nrofHARQ_Proces } } -/* hack data to remove UE in the phy */ -int rnti_to_remove[10]; -volatile int rnti_to_remove_count; -pthread_mutex_t rnti_to_remove_mutex = PTHREAD_MUTEX_INITIALIZER; - void delete_nr_ue_data(NR_UE_info_t *UE, NR_COMMON_channels_t *ccPtr, uid_allocator_t *uia) { + NR_UE_sched_ctrl_t *sched_ctrl = &UE->UE_sched_ctrl; destroy_nr_list(&sched_ctrl->available_dl_harq); destroy_nr_list(&sched_ctrl->feedback_dl_harq); @@ -1997,16 +1993,6 @@ void delete_nr_ue_data(NR_UE_info_t *UE, NR_COMMON_channels_t *ccPtr, uid_alloca LOG_I(NR_MAC, "Remove NR rnti 0x%04x\n", UE->rnti); const rnti_t rnti = UE->rnti; free(UE); - /* hack to remove UE in the phy */ - if (pthread_mutex_lock(&rnti_to_remove_mutex)) - exit(1); - if (rnti_to_remove_count == 10) - exit(1); - rnti_to_remove[rnti_to_remove_count] = rnti; - LOG_W(NR_MAC, "to remove in mac rnti_to_remove[%d] = 0x%04x\n", rnti_to_remove_count, rnti); - rnti_to_remove_count++; - if (pthread_mutex_unlock(&rnti_to_remove_mutex)) - exit(1); /* clear RA process(es?) associated to the UE */ for (int cc_id = 0; cc_id < NFAPI_CC_MAX; cc_id++) { @@ -2015,8 +2001,8 @@ void delete_nr_ue_data(NR_UE_info_t *UE, NR_COMMON_channels_t *ccPtr, uid_alloca if (cc->ra[i].rnti == rnti) { LOG_D(NR_MAC, "free RA process %d for rnti %04x\n", i, rnti); /* is it enough? */ - cc->ra[i].cfra = false; - cc->ra[i].rnti = 0; + cc->ra[i].cfra = false; + cc->ra[i].rnti = 0; cc->ra[i].crnti = 0; } } @@ -2524,16 +2510,6 @@ void mac_remove_nr_ue(gNB_MAC_INST *nr_mac, rnti_t rnti) delete_nr_ue_data(UE, nr_mac->common_channels, &UE_info->uid_allocator); } -void nr_mac_remove_ra_rnti(module_id_t mod_id, rnti_t rnti) { - // Hack to remove UE in the phy (following the same procedure as in function mac_remove_nr_ue) - if (pthread_mutex_lock(&rnti_to_remove_mutex)) exit(1); - if (rnti_to_remove_count == 10) exit(1); - rnti_to_remove[rnti_to_remove_count] = rnti; - LOG_W(NR_MAC, "to remove in mac rnti_to_remove[%d] = 0x%04x\n", rnti_to_remove_count, rnti); - rnti_to_remove_count++; - if (pthread_mutex_unlock(&rnti_to_remove_mutex)) exit(1); -} - uint8_t nr_get_tpc(int target, uint8_t cqi, int incr) { // al values passed to this function are x10 int snrx10 = (cqi*5) - 640; diff --git a/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_ulsch.c b/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_ulsch.c index bc86fded1b..6ded61a220 100644 --- a/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_ulsch.c +++ b/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_ulsch.c @@ -680,7 +680,6 @@ void nr_rx_sdu(const module_id_t gnb_mod_idP, if(no_sig) { LOG_D(NR_MAC, "Random Access %i failed at state %i (no signal)\n", i, ra->state); - nr_mac_remove_ra_rnti(gnb_mod_idP, ra->rnti); nr_clear_ra_proc(gnb_mod_idP, CC_idP, frameP, ra); } else { @@ -693,7 +692,6 @@ void nr_rx_sdu(const module_id_t gnb_mod_idP, if( (frameP==ra->Msg3_frame) && (slotP==ra->Msg3_slot) ) { LOG_D(NR_MAC, "Random Access %i failed at state %i (TC_RNTI %04x RNTI %04x)\n", i, ra->state,ra->rnti,current_rnti); - nr_mac_remove_ra_rnti(gnb_mod_idP, ra->rnti); nr_clear_ra_proc(gnb_mod_idP, CC_idP, frameP, ra); } @@ -703,7 +701,6 @@ void nr_rx_sdu(const module_id_t gnb_mod_idP, NR_UE_info_t *UE = add_new_nr_ue(gNB_mac, ra->rnti, ra->CellGroup); if (!UE) { LOG_W(NR_MAC, "Random Access %i discarded at state %i (TC_RNTI %04x RNTI %04x): max number of users achieved!\n", i, ra->state,ra->rnti,current_rnti); - nr_mac_remove_ra_rnti(gnb_mod_idP, ra->rnti); nr_clear_ra_proc(gnb_mod_idP, CC_idP, frameP, ra); return; } @@ -737,7 +734,6 @@ void nr_rx_sdu(const module_id_t gnb_mod_idP, LOG_A(NR_MAC, "(rnti 0x%04x) CFRA procedure succeeded!\n", ra->rnti); UE->ra_timer = 0; - nr_mac_remove_ra_rnti(gnb_mod_idP, ra->rnti); nr_clear_ra_proc(gnb_mod_idP, CC_idP, frameP, ra); process_CellGroup(ra->CellGroup, UE_scheduling_control); @@ -785,7 +781,6 @@ void nr_rx_sdu(const module_id_t gnb_mod_idP, } } else { - nr_mac_remove_ra_rnti(gnb_mod_idP, ra->rnti); nr_clear_ra_proc(gnb_mod_idP, CC_idP, frameP, ra); } } @@ -804,14 +799,12 @@ void nr_rx_sdu(const module_id_t gnb_mod_idP, // for CFRA (NSA) do not schedule retransmission of msg3 if (ra->cfra) { LOG_D(NR_MAC, "Random Access %i failed at state %i (NSA msg3 reception failed)\n", i, ra->state); - nr_mac_remove_ra_rnti(gnb_mod_idP, ra->rnti); nr_clear_ra_proc(gnb_mod_idP, CC_idP, frameP, ra); return; } if (ra->msg3_round >= gNB_mac->ul_bler.harq_round_max - 1) { LOG_W(NR_MAC, "Random Access %i failed at state %i (Reached msg3 max harq rounds)\n", i, ra->state); - nr_mac_remove_ra_rnti(gnb_mod_idP, ra->rnti); nr_clear_ra_proc(gnb_mod_idP, CC_idP, frameP, ra); return; } From 48d19391e2fdb74cc4d6625d22648011d75ec4a9 Mon Sep 17 00:00:00 2001 From: francescomani Date: Thu, 26 Jan 2023 11:22:32 +0100 Subject: [PATCH 05/89] fix for PHY structure disactivation --- executables/nr-gnb.c | 10 ++++++++-- openair1/PHY/NR_TRANSPORT/nr_ulsch.c | 3 --- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/executables/nr-gnb.c b/executables/nr-gnb.c index b2f88aca39..83f0a05e69 100644 --- a/executables/nr-gnb.c +++ b/executables/nr-gnb.c @@ -175,12 +175,18 @@ void rx_func(void *param) T(T_GNB_PHY_DL_TICK, T_INT(gNB->Mod_id), T_INT(frame_tx), T_INT(slot_tx)); - // disactivate PHY stats if UE is inactive for more than 10 frames + // disactivate PHY stats if UE is inactive for a given number of frames for (int i = 0; i < MAX_MOBILES_PER_GNB; i++) { NR_gNB_PHY_STATS_t *stats = &gNB->phy_stats[i]; - if(stats->active && (frame_rx > (stats->frame + NUMBER_FRAMES_PHY_UE_INACTIVE) % 1024)) + if(stats->active && (((frame_rx - stats->frame + 1024) % 1024) > NUMBER_FRAMES_PHY_UE_INACTIVE)) stats->active = false; } + // disactivate ULSCH structure if it is inactive for a given number of frames + for (int i = 0; i < NUMBER_OF_NR_ULSCH_MAX; i++) { + NR_gNB_ULSCH_t *ulsch = gNB->ulsch[i]; + if (ulsch->active && (((frame_rx - ulsch->harq_process->frame + 1024) % 1024) > NUMBER_FRAMES_PHY_UE_INACTIVE)) + ulsch->active = false; + } // RX processing int rx_slot_type = nr_slot_select(cfg,frame_rx,slot_rx); diff --git a/openair1/PHY/NR_TRANSPORT/nr_ulsch.c b/openair1/PHY/NR_TRANSPORT/nr_ulsch.c index aae638e4ad..2430b6e2fa 100644 --- a/openair1/PHY/NR_TRANSPORT/nr_ulsch.c +++ b/openair1/PHY/NR_TRANSPORT/nr_ulsch.c @@ -51,9 +51,6 @@ int16_t find_nr_ulsch(PHY_VARS_gNB *gNB, uint16_t rnti, int pid, int frame) // if there is already an active ULSCH for this RNTI and HARQ_PID if ((ulsch->harq_pid == pid) && (ulsch->rnti == rnti)) return i; - // remove inactive ULSCH (from disconnected UEs) - else if ((frame > (ulsch->harq_process->frame + NUMBER_FRAMES_PHY_UE_INACTIVE) % 1024)) - ulsch->active = false; } } return first_free_index; From f3126586f3f6d325ec6b50a99d6b97ec84937f36 Mon Sep 17 00:00:00 2001 From: francescomani Date: Tue, 7 Feb 2023 11:35:55 +0100 Subject: [PATCH 06/89] removing constant max number of xlsch --- common/openairinterface5g_limits.h | 3 - executables/nr-gnb.c | 2 +- nfapi/oai_integration/nfapi_pnf.c | 4 +- openair1/PHY/INIT/nr_init.c | 44 ++++++++----- .../PHY/NR_ESTIMATION/nr_measurements_gNB.c | 42 ++++++------- openair1/PHY/NR_TRANSPORT/nr_ulsch.c | 4 +- .../PHY/NR_TRANSPORT/nr_ulsch_demodulation.c | 2 +- openair1/PHY/defs_gNB.h | 61 ++++++++++--------- openair1/SCHED_NR/fapi_nr_l1.c | 7 +-- openair1/SCHED_NR/phy_procedures_nr_gNB.c | 4 +- 10 files changed, 92 insertions(+), 81 deletions(-) diff --git a/common/openairinterface5g_limits.h b/common/openairinterface5g_limits.h index 534c125226..b3ae99d047 100644 --- a/common/openairinterface5g_limits.h +++ b/common/openairinterface5g_limits.h @@ -10,10 +10,7 @@ # define NUMBER_OF_ULSCH_MAX 8 # define NUMBER_OF_DLSCH_MAX 8 # define NUMBER_OF_SRS_MAX 16 -# define NUMBER_OF_NR_ULSCH_MAX MAX_MOBILES_PER_GNB -# define NUMBER_OF_NR_DLSCH_MAX MAX_MOBILES_PER_GNB # define NUMBER_OF_SCH_STATS_MAX 16 -# define NUMBER_OF_NR_SCH_STATS_MAX MAX_MOBILES_PER_GNB # define NUMBER_FRAMES_PHY_UE_INACTIVE 10 diff --git a/executables/nr-gnb.c b/executables/nr-gnb.c index 83f0a05e69..93462620ac 100644 --- a/executables/nr-gnb.c +++ b/executables/nr-gnb.c @@ -182,7 +182,7 @@ void rx_func(void *param) stats->active = false; } // disactivate ULSCH structure if it is inactive for a given number of frames - for (int i = 0; i < NUMBER_OF_NR_ULSCH_MAX; i++) { + for (int i = 0; i < gNB->max_nb_pusch; i++) { NR_gNB_ULSCH_t *ulsch = gNB->ulsch[i]; if (ulsch->active && (((frame_rx - ulsch->harq_process->frame + 1024) % 1024) > NUMBER_FRAMES_PHY_UE_INACTIVE)) ulsch->active = false; diff --git a/nfapi/oai_integration/nfapi_pnf.c b/nfapi/oai_integration/nfapi_pnf.c index 0ba6df03b9..275a42c736 100644 --- a/nfapi/oai_integration/nfapi_pnf.c +++ b/nfapi/oai_integration/nfapi_pnf.c @@ -1220,8 +1220,8 @@ int pnf_phy_dl_tti_req(gNB_L1_rxtx_proc_t *proc, nfapi_pnf_p7_config_t *pnf_p7, if (tx_data != NULL) { uint8_t *dlsch_sdu = (uint8_t *)tx_data->TLVs[0].value.direct; //NFAPI_TRACE(NFAPI_TRACE_INFO, "%s() DLSCH:pdu_index:%d handle_nfapi_dlsch_pdu(eNB, proc_rxtx, dlsch_pdu, transport_blocks:%d sdu:%p) eNB->pdcch_vars[proc->subframe_tx & 1].num_pdcch_symbols:%d\n", __FUNCTION__, rel8_pdu->pdu_index, rel8_pdu->transport_blocks, dlsch_sdu, eNB->pdcch_vars[proc->subframe_tx & 1].num_pdcch_symbols); - AssertFatal(msgTx->num_pdsch_slot < NUMBER_OF_NR_DLSCH_MAX,"Number of PDSCH PDUs %d exceeded the limit %d\n", - msgTx->num_pdsch_slot, NUMBER_OF_NR_DLSCH_MAX); + AssertFatal(msgTx->num_pdsch_slot < gNB->max_nb_pdsch,"Number of PDSCH PDUs %d exceeded the limit %d\n", + msgTx->num_pdsch_slot, gNB->max_nb_pdsch); handle_nr_nfapi_pdsch_pdu(msgTx, pdsch_pdu, dlsch_sdu); } else { diff --git a/openair1/PHY/INIT/nr_init.c b/openair1/PHY/INIT/nr_init.c index 9a74f093b1..861f8ac56f 100644 --- a/openair1/PHY/INIT/nr_init.c +++ b/openair1/PHY/INIT/nr_init.c @@ -508,7 +508,6 @@ int phy_init_nr_gNB(PHY_VARS_gNB *gNB) nfapi_nr_config_request_scf_t *cfg = &gNB->gNB_config; NR_gNB_COMMON *const common_vars = &gNB->common_vars; NR_gNB_PRACH *const prach_vars = &gNB->prach_vars; - NR_gNB_PUSCH **const pusch_vars = gNB->pusch_vars; int i; int Ptx=cfg->carrier_config.num_tx_ant.value; @@ -532,6 +531,8 @@ int phy_init_nr_gNB(PHY_VARS_gNB *gNB) if (gNB->ldpc_offload_flag) load_nrLDPClib_offload(); + gNB->max_nb_pdsch = MAX_MOBILES_PER_GNB; + init_codebook_gNB(gNB); init_ul_delay_table(fp); @@ -684,7 +685,8 @@ int phy_init_nr_gNB(PHY_VARS_gNB *gNB) int nb_re_pusch = N_RB_UL * NR_NB_SC_PER_RB; int nb_re_pusch2 = nb_re_pusch + (nb_re_pusch&7); - for (int ULSCH_id = 0; ULSCH_id < NUMBER_OF_NR_ULSCH_MAX; ULSCH_id++) { + NR_gNB_PUSCH **pusch_vars = (NR_gNB_PUSCH **)malloc16_clear(gNB->max_nb_pusch * sizeof(NR_gNB_PUSCH*)); + for (int ULSCH_id = 0; ULSCH_id < gNB->max_nb_pusch; ULSCH_id++) { pusch_vars[ULSCH_id] = (NR_gNB_PUSCH *)malloc16_clear( sizeof(NR_gNB_PUSCH) ); pusch_vars[ULSCH_id]->rxdataF_ext = (int32_t **)malloc16(Prx*sizeof(int32_t *) ); pusch_vars[ULSCH_id]->ul_ch_estimates = (int32_t **)malloc16(n_buf*sizeof(int32_t *) ); @@ -836,7 +838,7 @@ void phy_free_nr_gNB(PHY_VARS_gNB *gNB) free_and_zero(prach_vars->prach_ifft); NR_gNB_PUSCH** pusch_vars = gNB->pusch_vars; - for (int ULSCH_id = 0; ULSCH_id < NUMBER_OF_NR_ULSCH_MAX; ULSCH_id++) { + for (int ULSCH_id = 0; ULSCH_id < gNB->max_nb_pusch; ULSCH_id++) { for (int i=0; i< max_ul_mimo_layers; i++) free_and_zero(pusch_vars[ULSCH_id]->llr_layers[i]); for (int i = 0; i < Prx; i++) { @@ -955,7 +957,8 @@ void nr_phy_config_request_sim(PHY_VARS_gNB *gNB, } -void nr_phy_config_request(NR_PHY_Config_t *phy_config) { +void nr_phy_config_request(NR_PHY_Config_t *phy_config) +{ uint8_t Mod_id = phy_config->Mod_id; uint8_t short_sequence, num_sequences, rootSequenceIndex, fd_occasion; NR_DL_FRAME_PARMS *fp = &RC.gNB[Mod_id]->frame_parms; @@ -1007,16 +1010,19 @@ void nr_phy_config_request(NR_PHY_Config_t *phy_config) { LOG_I(PHY,"gNB %d configured\n",Mod_id); } -void init_DLSCH_struct(PHY_VARS_gNB *gNB, processingData_L1tx_t *msg) { +void init_DLSCH_struct(PHY_VARS_gNB *gNB, processingData_L1tx_t *msg) +{ NR_DL_FRAME_PARMS *fp = &gNB->frame_parms; nfapi_nr_config_request_scf_t *cfg = &gNB->gNB_config; uint16_t grid_size = cfg->carrier_config.dl_grid_size[fp->numerology_index].value; msg->num_pdsch_slot = 0; + msg->dlsch = (NR_gNB_DLSCH_t ***)malloc16(gNB->max_nb_pdsch * sizeof(NR_gNB_DLSCH_t **)); int num_cw = NR_MAX_NB_LAYERS > 4? 2:1; - for (int i = 0; i < NUMBER_OF_NR_DLSCH_MAX; i++) { - LOG_D(PHY,"Allocating Transport Channel Buffers for DLSCH %d/%d\n", i, NUMBER_OF_NR_DLSCH_MAX); - for (int j=0; jmax_nb_pdsch; i++) { + LOG_I(PHY,"Allocating Transport Channel Buffers for DLSCH %d/%d\n", i, gNB->max_nb_pdsch); + msg->dlsch[i] = (NR_gNB_DLSCH_t **)malloc16(num_cw * sizeof(NR_gNB_DLSCH_t *)); + for (int j = 0; j < num_cw; j++) { msg->dlsch[i][j] = new_gNB_dlsch(fp, grid_size); AssertFatal(msg->dlsch[i][j]!=NULL,"Can't initialize dlsch %d \n", i); } @@ -1029,9 +1035,13 @@ void reset_DLSCH_struct(const PHY_VARS_gNB *gNB, processingData_L1tx_t *msg) const nfapi_nr_config_request_scf_t *cfg = &gNB->gNB_config; const uint16_t grid_size = cfg->carrier_config.dl_grid_size[fp->numerology_index].value; int num_cw = NR_MAX_NB_LAYERS > 4? 2:1; - for (int i = 0; i < NUMBER_OF_NR_DLSCH_MAX; i++) - for (int j=0; jmax_nb_pdsch; i++) { + for (int j=0; jdlsch[i][j], grid_size, fp); + free(msg->dlsch[i][j]); + } + free(msg->dlsch[i]); + } } void init_nr_transport(PHY_VARS_gNB *gNB) @@ -1066,8 +1076,11 @@ void init_nr_transport(PHY_VARS_gNB *gNB) buffer_ul_slots = (nb_ul_slots_period < slot_ahead) ? nb_ul_slots_period : slot_ahead; gNB->max_nb_pucch = MAX_MOBILES_PER_GNB * buffer_ul_slots; + gNB->max_nb_pusch = MAX_MOBILES_PER_GNB * buffer_ul_slots; gNB->max_nb_srs = buffer_ul_slots << 1; // assuming at most 2 SRS per slot + gNB->measurements.ulsch_measurements = (ULSCH_MEASUREMENTS_gNB *) malloc16(gNB->max_nb_pusch * sizeof(ULSCH_MEASUREMENTS_gNB)); + gNB->pucch = (NR_gNB_PUCCH_t **) malloc16(gNB->max_nb_pucch * sizeof(NR_gNB_PUCCH_t*)); for (int i = 0; i < gNB->max_nb_pucch; i++) { LOG_D(PHY,"Allocating Transport Channel Buffers for PUCCH %d/%d\n", i, gNB->max_nb_pucch); @@ -1082,12 +1095,10 @@ void init_nr_transport(PHY_VARS_gNB *gNB) AssertFatal(gNB->srs[i]!=NULL,"Can't initialize srs %d \n", i); } - for (int i = 0; i < NUMBER_OF_NR_ULSCH_MAX; i++) { - - LOG_D(PHY,"Allocating Transport Channel Buffers for ULSCH %d/%d\n", i, NUMBER_OF_NR_ULSCH_MAX); - + gNB->ulsch = (NR_gNB_ULSCH_t **) malloc16(gNB->max_nb_pusch * sizeof(NR_gNB_ULSCH_t*)); + for (int i = 0; i < gNB->max_nb_pusch; i++) { + LOG_I(PHY,"Allocating Transport Channel Buffers for ULSCH %d/%d\n", i, gNB->max_nb_pusch); gNB->ulsch[i] = new_gNB_ulsch(gNB->max_ldpc_iterations, fp->N_RB_UL); - if (!gNB->ulsch[i]) { LOG_E(PHY,"Can't get gNB ulsch structures\n"); exit(-1); @@ -1111,6 +1122,7 @@ void reset_nr_transport(PHY_VARS_gNB *gNB) free_gNB_srs(gNB->srs[i]); free(gNB->srs); - for (int i = 0; i < NUMBER_OF_NR_ULSCH_MAX; i++) + for (int i = 0; i < gNB->max_nb_pusch; i++) free_gNB_ulsch(&gNB->ulsch[i], fp->N_RB_UL); + free(gNB->ulsch); } diff --git a/openair1/PHY/NR_ESTIMATION/nr_measurements_gNB.c b/openair1/PHY/NR_ESTIMATION/nr_measurements_gNB.c index 96b832ceab..293442721a 100644 --- a/openair1/PHY/NR_ESTIMATION/nr_measurements_gNB.c +++ b/openair1/PHY/NR_ESTIMATION/nr_measurements_gNB.c @@ -211,10 +211,10 @@ void gNB_I0_measurements(PHY_VARS_gNB *gNB,int slot, int first_symb,int num_symb void nr_gnb_measurements(PHY_VARS_gNB *gNB, uint8_t ulsch_id, unsigned char harq_pid, unsigned char symbol, uint8_t nrOfLayers) { - int rx_power_tot[NUMBER_OF_NR_ULSCH_MAX]; - int rx_power[NUMBER_OF_NR_ULSCH_MAX][NB_ANTENNAS_RX]; - unsigned short rx_power_avg_dB[NUMBER_OF_NR_ULSCH_MAX]; - unsigned short rx_power_tot_dB[NUMBER_OF_NR_ULSCH_MAX]; + int rx_power_tot = 0; + int rx_power[NB_ANTENNAS_RX]; + unsigned short rx_power_avg_dB; + unsigned short rx_power_tot_dB; double rx_gain = openair0_cfg[0].rx_gain[0]; double rx_gain_offset = openair0_cfg[0].rx_gain_offset[0]; @@ -223,43 +223,41 @@ void nr_gnb_measurements(PHY_VARS_gNB *gNB, uint8_t ulsch_id, unsigned char harq int ch_offset = fp->ofdm_symbol_size * symbol; int N_RB_UL = gNB->ulsch[ulsch_id]->harq_process->ulsch_pdu.rb_size; - rx_power_tot[ulsch_id] = 0; - for (int aarx = 0; aarx < fp->nb_antennas_rx; aarx++){ - rx_power[ulsch_id][aarx] = 0; + rx_power[aarx] = 0; for (int aatx = 0; aatx < nrOfLayers; aatx++){ - meas->rx_spatial_power[ulsch_id][aatx][aarx] = (signal_energy_nodc(&gNB->pusch_vars[ulsch_id]->ul_ch_estimates[aatx*fp->nb_antennas_rx+aarx][ch_offset], N_RB_UL * NR_NB_SC_PER_RB)); + meas->ulsch_measurements[ulsch_id].rx_spatial_power[aatx][aarx] = (signal_energy_nodc(&gNB->pusch_vars[ulsch_id]->ul_ch_estimates[aatx*fp->nb_antennas_rx+aarx][ch_offset], N_RB_UL * NR_NB_SC_PER_RB)); - if (meas->rx_spatial_power[ulsch_id][aatx][aarx] < 0) { - meas->rx_spatial_power[ulsch_id][aatx][aarx] = 0; + if (meas->ulsch_measurements[ulsch_id].rx_spatial_power[aatx][aarx] < 0) { + meas->ulsch_measurements[ulsch_id].rx_spatial_power[aatx][aarx] = 0; } - meas->rx_spatial_power_dB[ulsch_id][aatx][aarx] = (unsigned short) dB_fixed(meas->rx_spatial_power[ulsch_id][aatx][aarx]); - rx_power[ulsch_id][aarx] += meas->rx_spatial_power[ulsch_id][aatx][aarx]; + meas->ulsch_measurements[ulsch_id].rx_spatial_power_dB[aatx][aarx] = (unsigned short) dB_fixed(meas->ulsch_measurements[ulsch_id].rx_spatial_power[aatx][aarx]); + rx_power[aarx] += meas->ulsch_measurements[ulsch_id].rx_spatial_power[aatx][aarx]; } - LOG_D(PHY, "[ULSCH ID %d] RX power in antenna %d = %d\n", ulsch_id, aarx, rx_power[ulsch_id][aarx]); + LOG_D(PHY, "[ULSCH ID %d] RX power in antenna %d = %d\n", ulsch_id, aarx, rx_power[aarx]); - rx_power_tot[ulsch_id] += rx_power[ulsch_id][aarx]; + rx_power_tot += rx_power[aarx]; } - rx_power_tot_dB[ulsch_id] = (unsigned short) dB_fixed(rx_power_tot[ulsch_id]); - rx_power_avg_dB[ulsch_id] = rx_power_tot_dB[ulsch_id]; + rx_power_tot_dB = (unsigned short) dB_fixed(rx_power_tot); + rx_power_avg_dB = rx_power_tot_dB; - meas->wideband_cqi_tot[ulsch_id] = dB_fixed2(rx_power_tot[ulsch_id], meas->n0_power_tot); - meas->rx_rssi_dBm[ulsch_id] = rx_power_avg_dB[ulsch_id] + 30 - 10 * log10(pow(2, 30)) - (rx_gain - rx_gain_offset) - dB_fixed(fp->ofdm_symbol_size); + meas->ulsch_measurements[ulsch_id].wideband_cqi_tot = dB_fixed2(rx_power_tot, meas->n0_power_tot); + meas->ulsch_measurements[ulsch_id].rx_rssi_dBm = rx_power_avg_dB + 30 - 10 * log10(pow(2, 30)) - (rx_gain - rx_gain_offset) - dB_fixed(fp->ofdm_symbol_size); LOG_D(PHY, "[ULSCH %d] RSSI %d dBm/RE, RSSI (digital) %d dB (N_RB_UL %d), WBand CQI tot %d dB, N0 Power tot %d, RX Power tot %d\n", ulsch_id, - meas->rx_rssi_dBm[ulsch_id], - rx_power_avg_dB[ulsch_id], + meas->ulsch_measurements[ulsch_id].rx_rssi_dBm, + rx_power_avg_dB, N_RB_UL, - meas->wideband_cqi_tot[ulsch_id], + meas->ulsch_measurements[ulsch_id].wideband_cqi_tot, meas->n0_power_tot, - rx_power_tot[ulsch_id]); + rx_power_tot); } diff --git a/openair1/PHY/NR_TRANSPORT/nr_ulsch.c b/openair1/PHY/NR_TRANSPORT/nr_ulsch.c index 2430b6e2fa..c7c35e6865 100644 --- a/openair1/PHY/NR_TRANSPORT/nr_ulsch.c +++ b/openair1/PHY/NR_TRANSPORT/nr_ulsch.c @@ -40,7 +40,7 @@ int16_t find_nr_ulsch(PHY_VARS_gNB *gNB, uint16_t rnti, int pid, int frame) int16_t first_free_index = -1; AssertFatal(gNB != NULL,"gNB is null\n"); - for (int i = 0; i < NUMBER_OF_NR_ULSCH_MAX; i++) { + for (int i = 0; i < gNB->max_nb_pusch; i++) { NR_gNB_ULSCH_t *ulsch = gNB->ulsch[i]; AssertFatal(ulsch != NULL, "gNB->ulsch[%d] is null\n", i); if(!ulsch->active) { @@ -64,7 +64,7 @@ void nr_fill_ulsch(PHY_VARS_gNB *gNB, int harq_pid = ulsch_pdu->pusch_data.harq_process_id; int ulsch_id = find_nr_ulsch(gNB, ulsch_pdu->rnti, harq_pid, frame); - AssertFatal((ulsch_id >= 0) && (ulsch_id < NUMBER_OF_NR_ULSCH_MAX), + AssertFatal((ulsch_id >= 0) && (ulsch_id < gNB->max_nb_pusch), "illegal or no ulsch_id found!!! rnti %04x ulsch_id %d\n",ulsch_pdu->rnti,ulsch_id); NR_gNB_ULSCH_t *ulsch = gNB->ulsch[ulsch_id]; diff --git a/openair1/PHY/NR_TRANSPORT/nr_ulsch_demodulation.c b/openair1/PHY/NR_TRANSPORT/nr_ulsch_demodulation.c index 67f33cbd6a..c96fc64866 100644 --- a/openair1/PHY/NR_TRANSPORT/nr_ulsch_demodulation.c +++ b/openair1/PHY/NR_TRANSPORT/nr_ulsch_demodulation.c @@ -1885,7 +1885,7 @@ void nr_rx_pusch(PHY_VARS_gNB *gNB, &max_ch); } - nr_gnb_measurements(gNB, ulsch_id, harq_pid, symbol,rel15_ul->nrOfLayers); + nr_gnb_measurements(gNB, ulsch_id, harq_pid, symbol, rel15_ul->nrOfLayers); for (aarx = 0; aarx < frame_parms->nb_antennas_rx; aarx++) { if (symbol == rel15_ul->start_symbol_index) { diff --git a/openair1/PHY/defs_gNB.h b/openair1/PHY/defs_gNB.h index 9a59214877..8f9148fc05 100644 --- a/openair1/PHY/defs_gNB.h +++ b/openair1/PHY/defs_gNB.h @@ -503,7 +503,32 @@ typedef struct gNB_L1_proc_t_s { gNB_L1_rxtx_proc_t L1_proc, L1_proc_tx; } gNB_L1_proc_t; - +typedef struct { + //! estimated received spatial signal power (linear) + unsigned int rx_spatial_power[NB_ANTENNAS_TX][NB_ANTENNAS_RX]; + //! estimated received spatial signal power (dB) + unsigned int rx_spatial_power_dB[NB_ANTENNAS_TX][NB_ANTENNAS_RX]; + //! estimated rssi (dBm) + int rx_rssi_dBm; + //! estimated correlation (wideband linear) between spatial channels (computed in dlsch_demodulation) + int rx_correlation[2]; + //! estimated correlation (wideband dB) between spatial channels (computed in dlsch_demodulation) + int rx_correlation_dB[2]; + /// Wideband CQI (= SINR) + int wideband_cqi[MAX_NUM_RU_PER_gNB]; + /// Wideband CQI in dB (= SINR dB) + int wideband_cqi_dB[MAX_NUM_RU_PER_gNB]; + /// Wideband CQI (sum of all RX antennas, in dB) + char wideband_cqi_tot; + /// Subband CQI per RX antenna and RB (= SINR) + int subband_cqi[MAX_NUM_RU_PER_gNB][275]; + /// Total Subband CQI and RB (= SINR) + int subband_cqi_tot[275]; + /// Subband CQI in dB and RB (= SINR dB) + int subband_cqi_dB[MAX_NUM_RU_PER_gNB][275]; + /// Total Subband CQI and RB + int subband_cqi_tot_dB[275]; +} ULSCH_MEASUREMENTS_gNB; typedef struct { // common measurements @@ -529,31 +554,8 @@ typedef struct { int n0_subband_power_tot_dBm[275]; // gNB measurements (per user) - //! estimated received spatial signal power (linear) - unsigned int rx_spatial_power[NUMBER_OF_NR_ULSCH_MAX][NB_ANTENNAS_TX][NB_ANTENNAS_RX]; - //! estimated received spatial signal power (dB) - unsigned int rx_spatial_power_dB[NUMBER_OF_NR_ULSCH_MAX][NB_ANTENNAS_TX][NB_ANTENNAS_RX]; - //! estimated rssi (dBm) - int rx_rssi_dBm[NUMBER_OF_NR_ULSCH_MAX]; - //! estimated correlation (wideband linear) between spatial channels (computed in dlsch_demodulation) - int rx_correlation[NUMBER_OF_NR_ULSCH_MAX][2]; - //! estimated correlation (wideband dB) between spatial channels (computed in dlsch_demodulation) - int rx_correlation_dB[NUMBER_OF_NR_ULSCH_MAX][2]; + ULSCH_MEASUREMENTS_gNB *ulsch_measurements; - /// Wideband CQI (= SINR) - int wideband_cqi[NUMBER_OF_NR_ULSCH_MAX][MAX_NUM_RU_PER_gNB]; - /// Wideband CQI in dB (= SINR dB) - int wideband_cqi_dB[NUMBER_OF_NR_ULSCH_MAX][MAX_NUM_RU_PER_gNB]; - /// Wideband CQI (sum of all RX antennas, in dB) - char wideband_cqi_tot[NUMBER_OF_NR_ULSCH_MAX]; - /// Subband CQI per RX antenna and RB (= SINR) - int subband_cqi[NUMBER_OF_NR_ULSCH_MAX][MAX_NUM_RU_PER_gNB][275]; - /// Total Subband CQI and RB (= SINR) - int subband_cqi_tot[NUMBER_OF_NR_ULSCH_MAX][275]; - /// Subband CQI in dB and RB (= SINR dB) - int subband_cqi_dB[NUMBER_OF_NR_ULSCH_MAX][MAX_NUM_RU_PER_gNB][275]; - /// Total Subband CQI and RB - int subband_cqi_tot_dB[NUMBER_OF_NR_ULSCH_MAX][275]; /// PRACH background noise level int prach_I0; @@ -609,14 +611,17 @@ typedef struct PHY_VARS_gNB_s { int max_nb_pucch; int max_nb_srs; + int max_nb_pdsch; + int max_nb_pusch; + NR_gNB_PBCH pbch; NR_gNB_COMMON common_vars; NR_gNB_PRACH prach_vars; NR_gNB_PRS prs_vars; - NR_gNB_PUSCH *pusch_vars[NUMBER_OF_NR_ULSCH_MAX]; + NR_gNB_PUSCH **pusch_vars; NR_gNB_PUCCH_t **pucch; NR_gNB_SRS_t **srs; - NR_gNB_ULSCH_t *ulsch[NUMBER_OF_NR_ULSCH_MAX]; // [Nusers times] + NR_gNB_ULSCH_t **ulsch; NR_gNB_PHY_STATS_t phy_stats[MAX_MOBILES_PER_GNB]; t_nrPolar_params **polarParams; @@ -811,7 +816,7 @@ typedef struct processingData_L1tx { nfapi_nr_dl_tti_pdcch_pdu pdcch_pdu[NFAPI_NR_MAX_NB_CORESETS]; nfapi_nr_ul_dci_request_pdus_t ul_pdcch_pdu[NFAPI_NR_MAX_NB_CORESETS]; NR_gNB_CSIRS_t csirs_pdu[NR_SYMBOLS_PER_SLOT]; - NR_gNB_DLSCH_t *dlsch[NUMBER_OF_NR_DLSCH_MAX][2]; + NR_gNB_DLSCH_t ***dlsch; NR_gNB_SSB_t ssb[64]; uint16_t num_pdsch_slot; int num_dl_pdcch; diff --git a/openair1/SCHED_NR/fapi_nr_l1.c b/openair1/SCHED_NR/fapi_nr_l1.c index 798b85f1e8..9f0e50e583 100644 --- a/openair1/SCHED_NR/fapi_nr_l1.c +++ b/openair1/SCHED_NR/fapi_nr_l1.c @@ -135,7 +135,6 @@ void handle_nr_nfapi_pdsch_pdu(processingData_L1tx_t *msgTx, void nr_schedule_response(NR_Sched_Rsp_t *Sched_INFO) { - PHY_VARS_gNB *gNB; // copy data from L2 interface into L1 structures module_id_t Mod_id = Sched_INFO->module_id; nfapi_nr_dl_tti_request_t *DL_req = Sched_INFO->DL_req; @@ -148,7 +147,7 @@ void nr_schedule_response(NR_Sched_Rsp_t *Sched_INFO) AssertFatal(RC.gNB!=NULL,"RC.gNB is null\n"); AssertFatal(RC.gNB[Mod_id]!=NULL,"RC.gNB[%d] is null\n",Mod_id); - gNB = RC.gNB[Mod_id]; + PHY_VARS_gNB *gNB = RC.gNB[Mod_id]; start_meas(&gNB->schedule_response_stats); nfapi_nr_config_request_scf_t *cfg = &gNB->gNB_config; @@ -204,8 +203,8 @@ void nr_schedule_response(NR_Sched_Rsp_t *Sched_INFO) AssertFatal(TX_req->pdu_list[pduIndex].num_TLV == 1, "TX_req->pdu_list[%d].num_TLV %d != 1\n", pduIndex,TX_req->pdu_list[pduIndex].num_TLV); uint8_t *sdu = (uint8_t *)TX_req->pdu_list[pduIndex].TLVs[0].value.direct; - AssertFatal(msgTx->num_pdsch_slot < NUMBER_OF_NR_DLSCH_MAX,"Number of PDSCH PDUs %d exceeded the limit %d\n", - msgTx->num_pdsch_slot, NUMBER_OF_NR_DLSCH_MAX); + AssertFatal(msgTx->num_pdsch_slot < gNB->max_nb_pdsch,"Number of PDSCH PDUs %d exceeded the limit %d\n", + msgTx->num_pdsch_slot, gNB->max_nb_pdsch); handle_nr_nfapi_pdsch_pdu(msgTx,&dl_tti_pdu->pdsch_pdu, sdu); } } diff --git a/openair1/SCHED_NR/phy_procedures_nr_gNB.c b/openair1/SCHED_NR/phy_procedures_nr_gNB.c index a9471dd6cc..7ec28872f7 100644 --- a/openair1/SCHED_NR/phy_procedures_nr_gNB.c +++ b/openair1/SCHED_NR/phy_procedures_nr_gNB.c @@ -552,7 +552,7 @@ void fill_ul_rb_mask(PHY_VARS_gNB *gNB, int frame_rx, int slot_rx) { } } - for (int ULSCH_id = 0; ULSCH_id < NUMBER_OF_NR_ULSCH_MAX; ULSCH_id++) { + for (int ULSCH_id = 0; ULSCH_id < gNB->max_nb_pusch; ULSCH_id++) { NR_gNB_ULSCH_t *ulsch = gNB->ulsch[ULSCH_id]; int harq_pid; NR_UL_gNB_HARQ_t *ulsch_harq = ulsch_harq = ulsch->harq_process; @@ -772,7 +772,7 @@ int phy_procedures_gNB_uespec_RX(PHY_VARS_gNB *gNB, int frame_rx, int slot_rx) } } - for (int ULSCH_id = 0; ULSCH_id < NUMBER_OF_NR_ULSCH_MAX; ULSCH_id++) { + for (int ULSCH_id = 0; ULSCH_id < gNB->max_nb_pusch; ULSCH_id++) { NR_gNB_ULSCH_t *ulsch = gNB->ulsch[ULSCH_id]; NR_UL_gNB_HARQ_t *ulsch_harq = ulsch->harq_process; From 779748579c50c9800d62e0ebf393fbd93d13e352 Mon Sep 17 00:00:00 2001 From: francescomani Date: Tue, 14 Feb 2023 10:59:05 +0100 Subject: [PATCH 07/89] fixes for physical simulators --- executables/nr-gnb.c | 4 ++-- openair1/PHY/INIT/nr_init.c | 14 ++++++++------ openair1/PHY/NR_ESTIMATION/nr_measurements_gNB.c | 2 +- openair1/SIMULATION/NR_PHY/dlschsim.c | 1 - openair1/SIMULATION/NR_PHY/pucchsim.c | 2 +- openair1/SIMULATION/NR_PHY/ulschsim.c | 4 ++-- openair1/SIMULATION/NR_PHY/ulsim.c | 10 +++++----- 7 files changed, 19 insertions(+), 18 deletions(-) diff --git a/executables/nr-gnb.c b/executables/nr-gnb.c index 93462620ac..1220fae924 100644 --- a/executables/nr-gnb.c +++ b/executables/nr-gnb.c @@ -178,13 +178,13 @@ void rx_func(void *param) // disactivate PHY stats if UE is inactive for a given number of frames for (int i = 0; i < MAX_MOBILES_PER_GNB; i++) { NR_gNB_PHY_STATS_t *stats = &gNB->phy_stats[i]; - if(stats->active && (((frame_rx - stats->frame + 1024) % 1024) > NUMBER_FRAMES_PHY_UE_INACTIVE)) + if(stats->active && (((frame_tx - stats->frame + 1024) % 1024) > NUMBER_FRAMES_PHY_UE_INACTIVE)) stats->active = false; } // disactivate ULSCH structure if it is inactive for a given number of frames for (int i = 0; i < gNB->max_nb_pusch; i++) { NR_gNB_ULSCH_t *ulsch = gNB->ulsch[i]; - if (ulsch->active && (((frame_rx - ulsch->harq_process->frame + 1024) % 1024) > NUMBER_FRAMES_PHY_UE_INACTIVE)) + if (ulsch->active && (((frame_tx - ulsch->harq_process->frame + 1024) % 1024) > NUMBER_FRAMES_PHY_UE_INACTIVE)) ulsch->active = false; } diff --git a/openair1/PHY/INIT/nr_init.c b/openair1/PHY/INIT/nr_init.c index 861f8ac56f..14aee3fd7f 100644 --- a/openair1/PHY/INIT/nr_init.c +++ b/openair1/PHY/INIT/nr_init.c @@ -729,10 +729,8 @@ int phy_init_nr_gNB(PHY_VARS_gNB *gNB) pusch_vars[ULSCH_id]->llr = (int16_t *)malloc16_clear( (8*((3*8*6144)+12))*sizeof(int16_t) ); // [hna] 6144 is LTE and (8*((3*8*6144)+12)) is not clear pusch_vars[ULSCH_id]->ul_valid_re_per_slot = (int16_t *)malloc16_clear( sizeof(int16_t)*fp->symbols_per_slot); } //ulsch_id -/* - for (ulsch_id=0; ulsch_idUE_stats_ptr[ulsch_id] = &gNB->UE_stats[ulsch_id]; -*/ + + gNB->pusch_vars = pusch_vars; return (0); } @@ -877,6 +875,7 @@ void phy_free_nr_gNB(PHY_VARS_gNB *gNB) free_and_zero(pusch_vars[ULSCH_id]->llr); free_and_zero(pusch_vars[ULSCH_id]); } //ULSCH_id + free(gNB->pusch_vars); } //Adding nr_schedule_handler @@ -1042,6 +1041,7 @@ void reset_DLSCH_struct(const PHY_VARS_gNB *gNB, processingData_L1tx_t *msg) } free(msg->dlsch[i]); } + free(msg->dlsch); } void init_nr_transport(PHY_VARS_gNB *gNB) @@ -1075,8 +1075,8 @@ void init_nr_transport(PHY_VARS_gNB *gNB) else buffer_ul_slots = (nb_ul_slots_period < slot_ahead) ? nb_ul_slots_period : slot_ahead; - gNB->max_nb_pucch = MAX_MOBILES_PER_GNB * buffer_ul_slots; - gNB->max_nb_pusch = MAX_MOBILES_PER_GNB * buffer_ul_slots; + gNB->max_nb_pucch = buffer_ul_slots ? MAX_MOBILES_PER_GNB * buffer_ul_slots : 1; + gNB->max_nb_pusch = buffer_ul_slots ? MAX_MOBILES_PER_GNB * buffer_ul_slots : 1; gNB->max_nb_srs = buffer_ul_slots << 1; // assuming at most 2 SRS per slot gNB->measurements.ulsch_measurements = (ULSCH_MEASUREMENTS_gNB *) malloc16(gNB->max_nb_pusch * sizeof(ULSCH_MEASUREMENTS_gNB)); @@ -1125,4 +1125,6 @@ void reset_nr_transport(PHY_VARS_gNB *gNB) for (int i = 0; i < gNB->max_nb_pusch; i++) free_gNB_ulsch(&gNB->ulsch[i], fp->N_RB_UL); free(gNB->ulsch); + + free(gNB->measurements.ulsch_measurements); } diff --git a/openair1/PHY/NR_ESTIMATION/nr_measurements_gNB.c b/openair1/PHY/NR_ESTIMATION/nr_measurements_gNB.c index 293442721a..f76b9cbf4c 100644 --- a/openair1/PHY/NR_ESTIMATION/nr_measurements_gNB.c +++ b/openair1/PHY/NR_ESTIMATION/nr_measurements_gNB.c @@ -212,7 +212,6 @@ void nr_gnb_measurements(PHY_VARS_gNB *gNB, uint8_t ulsch_id, unsigned char harq { int rx_power_tot = 0; - int rx_power[NB_ANTENNAS_RX]; unsigned short rx_power_avg_dB; unsigned short rx_power_tot_dB; @@ -223,6 +222,7 @@ void nr_gnb_measurements(PHY_VARS_gNB *gNB, uint8_t ulsch_id, unsigned char harq int ch_offset = fp->ofdm_symbol_size * symbol; int N_RB_UL = gNB->ulsch[ulsch_id]->harq_process->ulsch_pdu.rb_size; + int rx_power[fp->nb_antennas_rx]; for (int aarx = 0; aarx < fp->nb_antennas_rx; aarx++){ rx_power[aarx] = 0; diff --git a/openair1/SIMULATION/NR_PHY/dlschsim.c b/openair1/SIMULATION/NR_PHY/dlschsim.c index 42a5fc2dcc..3907f44d4a 100644 --- a/openair1/SIMULATION/NR_PHY/dlschsim.c +++ b/openair1/SIMULATION/NR_PHY/dlschsim.c @@ -430,7 +430,6 @@ int main(int argc, char **argv) uint16_t length_dmrs = 1; uint8_t Nl = 1; uint8_t rvidx = 0; - dlsch->rnti = 1; /*dlsch->harq_processes[0]->mcs = Imcs; dlsch->harq_processes[0]->rvidx = rvidx;*/ //printf("dlschsim harqid %d nb_rb %d, mscs %d\n",dlsch->harq_ids[subframe], diff --git a/openair1/SIMULATION/NR_PHY/pucchsim.c b/openair1/SIMULATION/NR_PHY/pucchsim.c index 68ba96ab88..00441db30b 100644 --- a/openair1/SIMULATION/NR_PHY/pucchsim.c +++ b/openair1/SIMULATION/NR_PHY/pucchsim.c @@ -623,7 +623,7 @@ int main(int argc, char **argv) if(format==0){ nfapi_nr_uci_pucch_pdu_format_0_1_t uci_pdu; nfapi_nr_pucch_pdu_t pucch_pdu; - gNB->uci_stats[0].rnti = 0x1234; + gNB->phy_stats[0].rnti = 0x1234; pucch_pdu.rnti = 0x1234; pucch_pdu.subcarrier_spacing = 1; pucch_pdu.group_hop_flag = PUCCH_GroupHopping&1; diff --git a/openair1/SIMULATION/NR_PHY/ulschsim.c b/openair1/SIMULATION/NR_PHY/ulschsim.c index 325611d68d..d94cfc778f 100644 --- a/openair1/SIMULATION/NR_PHY/ulschsim.c +++ b/openair1/SIMULATION/NR_PHY/ulschsim.c @@ -456,7 +456,7 @@ int main(int argc, char **argv) uint8_t UE_id = 0; NR_gNB_ULSCH_t *ulsch_gNB = gNB->ulsch[UE_id]; - NR_UL_gNB_HARQ_t *harq_process_gNB = ulsch_gNB->harq_processes[harq_pid]; + NR_UL_gNB_HARQ_t *harq_process_gNB = ulsch_gNB->harq_process; nfapi_nr_pusch_pdu_t *rel15_ul = &harq_process_gNB->ulsch_pdu; nr_phy_data_tx_t phy_data = {0}; @@ -614,7 +614,7 @@ int main(int argc, char **argv) errors_bit = 0; for (i = 0; i < TBS; i++) { - estimated_output_bit[i] = (ulsch_gNB->harq_processes[harq_pid]->b[i/8] & (1 << (i & 7))) >> (i & 7); + estimated_output_bit[i] = (ulsch_gNB->harq_process->b[i/8] & (1 << (i & 7))) >> (i & 7); test_input_bit[i] = (test_input[i / 8] & (1 << (i & 7))) >> (i & 7); // Further correct for multiple segments if (estimated_output_bit[i] != test_input_bit[i]) { diff --git a/openair1/SIMULATION/NR_PHY/ulsim.c b/openair1/SIMULATION/NR_PHY/ulsim.c index 4ec8fc3cca..1a02308e12 100644 --- a/openair1/SIMULATION/NR_PHY/ulsim.c +++ b/openair1/SIMULATION/NR_PHY/ulsim.c @@ -754,7 +754,7 @@ int main(int argc, char **argv) NR_gNB_ULSCH_t *ulsch_gNB = gNB->ulsch[UE_id]; - //nfapi_nr_ul_config_ulsch_pdu *rel15_ul = &ulsch_gNB->harq_processes[harq_pid]->ulsch_pdu; + //nfapi_nr_ul_config_ulsch_pdu *rel15_ul = &ulsch_gNB->harq_process->ulsch_pdu; nfapi_nr_ul_tti_request_t *UL_tti_req = malloc(sizeof(*UL_tti_req)); NR_Sched_Rsp_t *Sched_INFO = malloc(sizeof(*Sched_INFO)); memset((void*)Sched_INFO,0,sizeof(*Sched_INFO)); @@ -1477,7 +1477,7 @@ int main(int argc, char **argv) for (i = 0; i < TBS; i++) { - estimated_output_bit[i] = (ulsch_gNB->harq_processes[harq_pid]->b[i / 8] & (1 << (i & 7))) >> (i & 7); + estimated_output_bit[i] = (ulsch_gNB->harq_process->b[i / 8] & (1 << (i & 7))) >> (i & 7); test_input_bit[i] = (UE->ul_harq_processes[harq_pid].b[i / 8] & (1 << (i & 7))) >> (i & 7); if (estimated_output_bit[i] != test_input_bit[i]) { @@ -1489,12 +1489,12 @@ int main(int argc, char **argv) if (n_trials == 1) { for (int r = 0; r < UE->ul_harq_processes[harq_pid].C; r++) for (int i = 0; i < UE->ul_harq_processes[harq_pid].K >> 3; i++) { - if ((UE->ul_harq_processes[harq_pid].c[r][i] ^ ulsch_gNB->harq_processes[harq_pid]->c[r][i]) != 0) + if ((UE->ul_harq_processes[harq_pid].c[r][i] ^ ulsch_gNB->harq_process->c[r][i]) != 0) printf("************"); /*printf("r %d: in[%d] %x, out[%d] %x (%x)\n",r, i,UE->ul_harq_processes[harq_pid].c[r][i], - i,ulsch_gNB->harq_processes[harq_pid]->c[r][i], - UE->ul_harq_processes[harq_pid].c[r][i]^ulsch_gNB->harq_processes[harq_pid]->c[r][i]);*/ + i,ulsch_gNB->harq_process->c[r][i], + UE->ul_harq_processes[harq_pid].c[r][i]^ulsch_gNB->harq_process->c[r][i]);*/ } } if (errors_decoding > 0 && error_flag == 0) { From 6e93619896b7f8fea58b2e5c48f297818c429d01 Mon Sep 17 00:00:00 2001 From: Robert Schmidt Date: Fri, 17 Feb 2023 09:49:59 +0000 Subject: [PATCH 08/89] review comments --- openair1/PHY/INIT/nr_init.c | 16 ++---- .../PHY/NR_ESTIMATION/nr_measurements_gNB.c | 19 ++++--- openair1/PHY/NR_TRANSPORT/nr_ulsch.c | 18 +++--- openair1/PHY/NR_TRANSPORT/nr_ulsch_decoding.c | 14 ++--- openair1/PHY/NR_TRANSPORT/pucch_rx.c | 7 +-- openair1/PHY/defs_gNB.h | 57 +++++++++---------- openair1/SCHED_NR/fapi_nr_l1.c | 1 - openair1/SCHED_NR/phy_procedures_nr_gNB.c | 52 +++++++++-------- 8 files changed, 90 insertions(+), 94 deletions(-) diff --git a/openair1/PHY/INIT/nr_init.c b/openair1/PHY/INIT/nr_init.c index 14aee3fd7f..465618a9f4 100644 --- a/openair1/PHY/INIT/nr_init.c +++ b/openair1/PHY/INIT/nr_init.c @@ -93,9 +93,9 @@ NR_gNB_PHY_STATS_t *get_phy_stats(PHY_VARS_gNB *gNB, uint16_t rnti) stats = &gNB->phy_stats[first_free]; stats->active = true; stats->rnti = rnti; - memset((void*)&stats->dlsch_stats, 0, sizeof(stats->dlsch_stats)); - memset((void*)&stats->ulsch_stats, 0, sizeof(stats->ulsch_stats)); - memset((void*)&stats->uci_stats, 0, sizeof(stats->uci_stats)); + memset(&stats->dlsch_stats, 0, sizeof(stats->dlsch_stats)); + memset(&stats->ulsch_stats, 0, sizeof(stats->ulsch_stats)); + memset(&stats->uci_stats, 0, sizeof(stats->uci_stats)); return(stats); } @@ -1016,10 +1016,10 @@ void init_DLSCH_struct(PHY_VARS_gNB *gNB, processingData_L1tx_t *msg) uint16_t grid_size = cfg->carrier_config.dl_grid_size[fp->numerology_index].value; msg->num_pdsch_slot = 0; - msg->dlsch = (NR_gNB_DLSCH_t ***)malloc16(gNB->max_nb_pdsch * sizeof(NR_gNB_DLSCH_t **)); + msg->dlsch = malloc16(gNB->max_nb_pdsch * sizeof(NR_gNB_DLSCH_t **)); int num_cw = NR_MAX_NB_LAYERS > 4? 2:1; for (int i = 0; i < gNB->max_nb_pdsch; i++) { - LOG_I(PHY,"Allocating Transport Channel Buffers for DLSCH %d/%d\n", i, gNB->max_nb_pdsch); + LOG_D(PHY, "Allocating Transport Channel Buffers for DLSCH %d/%d\n", i, gNB->max_nb_pdsch); msg->dlsch[i] = (NR_gNB_DLSCH_t **)malloc16(num_cw * sizeof(NR_gNB_DLSCH_t *)); for (int j = 0; j < num_cw; j++) { msg->dlsch[i][j] = new_gNB_dlsch(fp, grid_size); @@ -1079,8 +1079,6 @@ void init_nr_transport(PHY_VARS_gNB *gNB) gNB->max_nb_pusch = buffer_ul_slots ? MAX_MOBILES_PER_GNB * buffer_ul_slots : 1; gNB->max_nb_srs = buffer_ul_slots << 1; // assuming at most 2 SRS per slot - gNB->measurements.ulsch_measurements = (ULSCH_MEASUREMENTS_gNB *) malloc16(gNB->max_nb_pusch * sizeof(ULSCH_MEASUREMENTS_gNB)); - gNB->pucch = (NR_gNB_PUCCH_t **) malloc16(gNB->max_nb_pucch * sizeof(NR_gNB_PUCCH_t*)); for (int i = 0; i < gNB->max_nb_pucch; i++) { LOG_D(PHY,"Allocating Transport Channel Buffers for PUCCH %d/%d\n", i, gNB->max_nb_pucch); @@ -1097,7 +1095,7 @@ void init_nr_transport(PHY_VARS_gNB *gNB) gNB->ulsch = (NR_gNB_ULSCH_t **) malloc16(gNB->max_nb_pusch * sizeof(NR_gNB_ULSCH_t*)); for (int i = 0; i < gNB->max_nb_pusch; i++) { - LOG_I(PHY,"Allocating Transport Channel Buffers for ULSCH %d/%d\n", i, gNB->max_nb_pusch); + LOG_D(PHY, "Allocating Transport Channel Buffers for ULSCH %d/%d\n", i, gNB->max_nb_pusch); gNB->ulsch[i] = new_gNB_ulsch(gNB->max_ldpc_iterations, fp->N_RB_UL); if (!gNB->ulsch[i]) { LOG_E(PHY,"Can't get gNB ulsch structures\n"); @@ -1125,6 +1123,4 @@ void reset_nr_transport(PHY_VARS_gNB *gNB) for (int i = 0; i < gNB->max_nb_pusch; i++) free_gNB_ulsch(&gNB->ulsch[i], fp->N_RB_UL); free(gNB->ulsch); - - free(gNB->measurements.ulsch_measurements); } diff --git a/openair1/PHY/NR_ESTIMATION/nr_measurements_gNB.c b/openair1/PHY/NR_ESTIMATION/nr_measurements_gNB.c index f76b9cbf4c..ff90ca54ff 100644 --- a/openair1/PHY/NR_ESTIMATION/nr_measurements_gNB.c +++ b/openair1/PHY/NR_ESTIMATION/nr_measurements_gNB.c @@ -221,6 +221,7 @@ void nr_gnb_measurements(PHY_VARS_gNB *gNB, uint8_t ulsch_id, unsigned char harq NR_DL_FRAME_PARMS *fp = &gNB->frame_parms; int ch_offset = fp->ofdm_symbol_size * symbol; int N_RB_UL = gNB->ulsch[ulsch_id]->harq_process->ulsch_pdu.rb_size; + ulsch_measurements_gNB *ulsch_measurements = &gNB->ulsch[ulsch_id]->ulsch_measurements; int rx_power[fp->nb_antennas_rx]; for (int aarx = 0; aarx < fp->nb_antennas_rx; aarx++){ @@ -229,14 +230,14 @@ void nr_gnb_measurements(PHY_VARS_gNB *gNB, uint8_t ulsch_id, unsigned char harq for (int aatx = 0; aatx < nrOfLayers; aatx++){ - meas->ulsch_measurements[ulsch_id].rx_spatial_power[aatx][aarx] = (signal_energy_nodc(&gNB->pusch_vars[ulsch_id]->ul_ch_estimates[aatx*fp->nb_antennas_rx+aarx][ch_offset], N_RB_UL * NR_NB_SC_PER_RB)); + ulsch_measurements->rx_spatial_power[aatx][aarx] = (signal_energy_nodc(&gNB->pusch_vars[ulsch_id]->ul_ch_estimates[aatx*fp->nb_antennas_rx+aarx][ch_offset], N_RB_UL * NR_NB_SC_PER_RB)); - if (meas->ulsch_measurements[ulsch_id].rx_spatial_power[aatx][aarx] < 0) { - meas->ulsch_measurements[ulsch_id].rx_spatial_power[aatx][aarx] = 0; + if (ulsch_measurements->rx_spatial_power[aatx][aarx] < 0) { + ulsch_measurements->rx_spatial_power[aatx][aarx] = 0; } - meas->ulsch_measurements[ulsch_id].rx_spatial_power_dB[aatx][aarx] = (unsigned short) dB_fixed(meas->ulsch_measurements[ulsch_id].rx_spatial_power[aatx][aarx]); - rx_power[aarx] += meas->ulsch_measurements[ulsch_id].rx_spatial_power[aatx][aarx]; + ulsch_measurements->rx_spatial_power_dB[aatx][aarx] = (unsigned short) dB_fixed(ulsch_measurements->rx_spatial_power[aatx][aarx]); + rx_power[aarx] += ulsch_measurements->rx_spatial_power[aatx][aarx]; } LOG_D(PHY, "[ULSCH ID %d] RX power in antenna %d = %d\n", ulsch_id, aarx, rx_power[aarx]); @@ -248,15 +249,15 @@ void nr_gnb_measurements(PHY_VARS_gNB *gNB, uint8_t ulsch_id, unsigned char harq rx_power_tot_dB = (unsigned short) dB_fixed(rx_power_tot); rx_power_avg_dB = rx_power_tot_dB; - meas->ulsch_measurements[ulsch_id].wideband_cqi_tot = dB_fixed2(rx_power_tot, meas->n0_power_tot); - meas->ulsch_measurements[ulsch_id].rx_rssi_dBm = rx_power_avg_dB + 30 - 10 * log10(pow(2, 30)) - (rx_gain - rx_gain_offset) - dB_fixed(fp->ofdm_symbol_size); + ulsch_measurements->wideband_cqi_tot = dB_fixed2(rx_power_tot, meas->n0_power_tot); + ulsch_measurements->rx_rssi_dBm = rx_power_avg_dB + 30 - 10 * log10(pow(2, 30)) - (rx_gain - rx_gain_offset) - dB_fixed(fp->ofdm_symbol_size); LOG_D(PHY, "[ULSCH %d] RSSI %d dBm/RE, RSSI (digital) %d dB (N_RB_UL %d), WBand CQI tot %d dB, N0 Power tot %d, RX Power tot %d\n", ulsch_id, - meas->ulsch_measurements[ulsch_id].rx_rssi_dBm, + ulsch_measurements->rx_rssi_dBm, rx_power_avg_dB, N_RB_UL, - meas->ulsch_measurements[ulsch_id].wideband_cqi_tot, + ulsch_measurements->wideband_cqi_tot, meas->n0_power_tot, rx_power_tot); diff --git a/openair1/PHY/NR_TRANSPORT/nr_ulsch.c b/openair1/PHY/NR_TRANSPORT/nr_ulsch.c index c7c35e6865..8aca638bfa 100644 --- a/openair1/PHY/NR_TRANSPORT/nr_ulsch.c +++ b/openair1/PHY/NR_TRANSPORT/nr_ulsch.c @@ -85,7 +85,7 @@ void nr_fill_ulsch(PHY_VARS_gNB *gNB, else harq->round++; - memcpy((void*)&ulsch->harq_process->ulsch_pdu, (void*)ulsch_pdu, sizeof(nfapi_nr_pusch_pdu_t)); + memcpy(&ulsch->harq_process->ulsch_pdu, ulsch_pdu, sizeof(ulsch->harq_process->ulsch_pdu)); LOG_D(PHY,"Initializing nFAPI for ULSCH, UE %d, harq_pid %d\n", ulsch_id, harq_pid); } @@ -141,15 +141,15 @@ void dump_pusch_stats(FILE *fd, PHY_VARS_gNB *gNB) aa, stats->ulsch_stats.power[aa]/10, stats->ulsch_stats.power[aa]%10, aa, stats->ulsch_stats.noise_power[aa]/10, stats->ulsch_stats.noise_power[aa]%10); - + int *rt = stats->ulsch_stats.round_trials; fprintf(fd," round_trials %d(%1.1e):%d(%1.1e):%d(%1.1e):%d, DTX %d, current_Qm %d, current_RI %d, total_bytes RX/SCHED %d/%d\n", - stats->ulsch_stats.round_trials[0], - (double)stats->ulsch_stats.round_trials[1]/stats->ulsch_stats.round_trials[0], - stats->ulsch_stats.round_trials[1], - (double)stats->ulsch_stats.round_trials[2]/stats->ulsch_stats.round_trials[0], - stats->ulsch_stats.round_trials[2], - (double)stats->ulsch_stats.round_trials[3]/stats->ulsch_stats.round_trials[0], - stats->ulsch_stats.round_trials[3], + rt[0], + (double)rt[1]/rt[0], + rt[1], + (double)rt[2]/rt[0], + rt[2], + (double)rt[3]/rt[0], + rt[3], stats->ulsch_stats.DTX, stats->ulsch_stats.current_Qm, stats->ulsch_stats.current_RI, diff --git a/openair1/PHY/NR_TRANSPORT/nr_ulsch_decoding.c b/openair1/PHY/NR_TRANSPORT/nr_ulsch_decoding.c index 2c05ac1488..60902a3783 100644 --- a/openair1/PHY/NR_TRANSPORT/nr_ulsch_decoding.c +++ b/openair1/PHY/NR_TRANSPORT/nr_ulsch_decoding.c @@ -95,19 +95,19 @@ NR_gNB_ULSCH_t *new_gNB_ulsch(uint8_t max_ldpc_iterations, uint16_t N_RB_UL) } uint32_t ulsch_bytes = a_segments * 1056; // allocated bytes per segment - NR_gNB_ULSCH_t *ulsch = (NR_gNB_ULSCH_t *)malloc16_clear(sizeof(NR_gNB_ULSCH_t)); + NR_gNB_ULSCH_t *ulsch = malloc16_clear(sizeof(*ulsch)); ulsch->max_ldpc_iterations = max_ldpc_iterations; ulsch->harq_pid = -1; ulsch->active = false; - ulsch->harq_process = (NR_UL_gNB_HARQ_t *)malloc16_clear(sizeof(NR_UL_gNB_HARQ_t)); - ulsch->harq_process->b = (uint8_t*)malloc16_clear(ulsch_bytes); - ulsch->harq_process->c = (uint8_t**)malloc16_clear(a_segments*sizeof(uint8_t *)); - ulsch->harq_process->d = (int16_t**)malloc16_clear(a_segments*sizeof(int16_t *)); + ulsch->harq_process = malloc16_clear(sizeof(ulsch->harq_process)); + ulsch->harq_process->b = malloc16_clear(ulsch_bytes); + ulsch->harq_process->c = malloc16_clear(a_segments*sizeof(uint8_t *)); + ulsch->harq_process->d = malloc16_clear(a_segments*sizeof(int16_t *)); for (int r = 0; r < a_segments; r++) { - ulsch->harq_process->c[r] = (uint8_t*)malloc16_clear(8448*sizeof(uint8_t)); - ulsch->harq_process->d[r] = (int16_t*)malloc16_clear((68*384)*sizeof(int16_t)); + ulsch->harq_process->c[r] = malloc16_clear(8448*sizeof(uint8_t)); + ulsch->harq_process->d[r] = malloc16_clear((68*384)*sizeof(int16_t)); } return(ulsch); } diff --git a/openair1/PHY/NR_TRANSPORT/pucch_rx.c b/openair1/PHY/NR_TRANSPORT/pucch_rx.c index 6324b9f60b..814aceb8ed 100644 --- a/openair1/PHY/NR_TRANSPORT/pucch_rx.c +++ b/openair1/PHY/NR_TRANSPORT/pucch_rx.c @@ -57,10 +57,9 @@ NR_gNB_PUCCH_t *new_gNB_pucch(void) { - NR_gNB_PUCCH_t *pucch; - pucch = (NR_gNB_PUCCH_t *)malloc16(sizeof(NR_gNB_PUCCH_t)); - pucch->active = 0; - return (pucch); + NR_gNB_PUCCH_t *pucch = malloc16(sizeof(*pucch)); + memset(pucch, 0, sizeof(*pucch)); + return pucch; } void free_gNB_pucch(NR_gNB_PUCCH_t *pucch) diff --git a/openair1/PHY/defs_gNB.h b/openair1/PHY/defs_gNB.h index 8f9148fc05..cb8990de9c 100644 --- a/openair1/PHY/defs_gNB.h +++ b/openair1/PHY/defs_gNB.h @@ -224,6 +224,32 @@ typedef struct { ////////////////////////////////////////////////////////////// } NR_UL_gNB_HARQ_t; +typedef struct { + //! estimated received spatial signal power (linear) + unsigned int rx_spatial_power[NB_ANTENNAS_TX][NB_ANTENNAS_RX]; + //! estimated received spatial signal power (dB) + unsigned int rx_spatial_power_dB[NB_ANTENNAS_TX][NB_ANTENNAS_RX]; + //! estimated rssi (dBm) + int rx_rssi_dBm; + //! estimated correlation (wideband linear) between spatial channels (computed in dlsch_demodulation) + int rx_correlation[2]; + //! estimated correlation (wideband dB) between spatial channels (computed in dlsch_demodulation) + int rx_correlation_dB[2]; + /// Wideband CQI (= SINR) + int wideband_cqi[MAX_NUM_RU_PER_gNB]; + /// Wideband CQI in dB (= SINR dB) + int wideband_cqi_dB[MAX_NUM_RU_PER_gNB]; + /// Wideband CQI (sum of all RX antennas, in dB) + char wideband_cqi_tot; + /// Subband CQI per RX antenna and RB (= SINR) + int subband_cqi[MAX_NUM_RU_PER_gNB][275]; + /// Total Subband CQI and RB (= SINR) + int subband_cqi_tot[275]; + /// Subband CQI in dB and RB (= SINR dB) + int subband_cqi_dB[MAX_NUM_RU_PER_gNB][275]; + /// Total Subband CQI and RB + int subband_cqi_tot_dB[275]; +} ulsch_measurements_gNB; typedef struct { /// Time shift in number of samples estimated based on DMRS-PUSCH @@ -250,6 +276,7 @@ typedef struct { /// Flag to indicate that the UL configuration has been handled. Used to remove a stale ULSCH when frame wraps around uint8_t handled; NR_ULSCH_delay_t delay; + ulsch_measurements_gNB ulsch_measurements; } NR_gNB_ULSCH_t; typedef struct { @@ -503,33 +530,6 @@ typedef struct gNB_L1_proc_t_s { gNB_L1_rxtx_proc_t L1_proc, L1_proc_tx; } gNB_L1_proc_t; -typedef struct { - //! estimated received spatial signal power (linear) - unsigned int rx_spatial_power[NB_ANTENNAS_TX][NB_ANTENNAS_RX]; - //! estimated received spatial signal power (dB) - unsigned int rx_spatial_power_dB[NB_ANTENNAS_TX][NB_ANTENNAS_RX]; - //! estimated rssi (dBm) - int rx_rssi_dBm; - //! estimated correlation (wideband linear) between spatial channels (computed in dlsch_demodulation) - int rx_correlation[2]; - //! estimated correlation (wideband dB) between spatial channels (computed in dlsch_demodulation) - int rx_correlation_dB[2]; - /// Wideband CQI (= SINR) - int wideband_cqi[MAX_NUM_RU_PER_gNB]; - /// Wideband CQI in dB (= SINR dB) - int wideband_cqi_dB[MAX_NUM_RU_PER_gNB]; - /// Wideband CQI (sum of all RX antennas, in dB) - char wideband_cqi_tot; - /// Subband CQI per RX antenna and RB (= SINR) - int subband_cqi[MAX_NUM_RU_PER_gNB][275]; - /// Total Subband CQI and RB (= SINR) - int subband_cqi_tot[275]; - /// Subband CQI in dB and RB (= SINR dB) - int subband_cqi_dB[MAX_NUM_RU_PER_gNB][275]; - /// Total Subband CQI and RB - int subband_cqi_tot_dB[275]; -} ULSCH_MEASUREMENTS_gNB; - typedef struct { // common measurements //! estimated noise power (linear) @@ -553,9 +553,6 @@ typedef struct { //! estimated avg noise power per RB (dBm) int n0_subband_power_tot_dBm[275]; - // gNB measurements (per user) - ULSCH_MEASUREMENTS_gNB *ulsch_measurements; - /// PRACH background noise level int prach_I0; diff --git a/openair1/SCHED_NR/fapi_nr_l1.c b/openair1/SCHED_NR/fapi_nr_l1.c index 9f0e50e583..94725eeeea 100644 --- a/openair1/SCHED_NR/fapi_nr_l1.c +++ b/openair1/SCHED_NR/fapi_nr_l1.c @@ -105,7 +105,6 @@ void handle_nfapi_nr_csirs_pdu(processingData_L1tx_t *msgTx, int frame,int slot, nfapi_nr_dl_tti_csi_rs_pdu *csirs_pdu) { - int found = 0; for (int id = 0; id < NR_SYMBOLS_PER_SLOT; id++) { diff --git a/openair1/SCHED_NR/phy_procedures_nr_gNB.c b/openair1/SCHED_NR/phy_procedures_nr_gNB.c index 7ec28872f7..5388bccb2b 100644 --- a/openair1/SCHED_NR/phy_procedures_nr_gNB.c +++ b/openair1/SCHED_NR/phy_procedures_nr_gNB.c @@ -555,7 +555,7 @@ void fill_ul_rb_mask(PHY_VARS_gNB *gNB, int frame_rx, int slot_rx) { for (int ULSCH_id = 0; ULSCH_id < gNB->max_nb_pusch; ULSCH_id++) { NR_gNB_ULSCH_t *ulsch = gNB->ulsch[ULSCH_id]; int harq_pid; - NR_UL_gNB_HARQ_t *ulsch_harq = ulsch_harq = ulsch->harq_process; + NR_UL_gNB_HARQ_t *ulsch_harq = ulsch->harq_process; AssertFatal(ulsch_harq != NULL,"harq_pid %d is not allocated\n",harq_pid); if ((ulsch->active == true) && (ulsch_harq->frame == frame_rx) && @@ -794,14 +794,17 @@ int phy_procedures_gNB_uespec_RX(PHY_VARS_gNB *gNB, int frame_rx, int slot_rx) RU_t *ru = gNB->RU_list[0]; int slot_offset = frame_parms->get_samples_slot_timestamp(slot_rx,frame_parms,0); slot_offset -= ru->N_TA_offset; - ((int16_t*)&gNB->common_vars.debugBuff[gNB->common_vars.debugBuff_sample_offset])[0]=(int16_t)ulsch->rnti; - ((int16_t*)&gNB->common_vars.debugBuff[gNB->common_vars.debugBuff_sample_offset])[1]=(int16_t)ulsch_harq->ulsch_pdu.rb_size; - ((int16_t*)&gNB->common_vars.debugBuff[gNB->common_vars.debugBuff_sample_offset])[2]=(int16_t)ulsch_harq->ulsch_pdu.rb_start; - ((int16_t*)&gNB->common_vars.debugBuff[gNB->common_vars.debugBuff_sample_offset])[3]=(int16_t)ulsch_harq->ulsch_pdu.nr_of_symbols; - ((int16_t*)&gNB->common_vars.debugBuff[gNB->common_vars.debugBuff_sample_offset])[4]=(int16_t)ulsch_harq->ulsch_pdu.start_symbol_index; - ((int16_t*)&gNB->common_vars.debugBuff[gNB->common_vars.debugBuff_sample_offset])[5]=(int16_t)ulsch_harq->ulsch_pdu.mcs_index; - ((int16_t*)&gNB->common_vars.debugBuff[gNB->common_vars.debugBuff_sample_offset])[6]=(int16_t)ulsch_harq->ulsch_pdu.pusch_data.rv_index; - ((int16_t*)&gNB->common_vars.debugBuff[gNB->common_vars.debugBuff_sample_offset])[7]=(int16_t)ulsch->harq_pid; + int32_t sample_offset = gNB->common_vars.debugBuff_sample_offset; + nfapi_nr_pusch_pdu_t *pdu = &ulsch_harq->ulsch_pdu; + int16_t *buf = (int16_t*)&gNB->common_vars.debugBuff[offset]; + buf[0]=(int16_t)ulsch->rnti; + buf[1]=(int16_t)pdu->rb_size; + buf[2]=(int16_t)pdu->rb_start; + buf[3]=(int16_t)pdu->nr_of_symbols; + buf[4]=(int16_t)pdu->start_symbol_index; + buf[5]=(int16_t)pdu->mcs_index; + buf[6]=(int16_t)pdu->pusch_data.rv_index; + buf[7]=(int16_t)ulsch->harq_pid; memcpy(&gNB->common_vars.debugBuff[gNB->common_vars.debugBuff_sample_offset+4],&ru->common.rxdata[0][slot_offset],frame_parms->get_samples_per_slot(slot_rx,frame_parms)*sizeof(int32_t)); gNB->common_vars.debugBuff_sample_offset+=(frame_parms->get_samples_per_slot(slot_rx,frame_parms)+1000+4); if(gNB->common_vars.debugBuff_sample_offset>((frame_parms->get_samples_per_slot(slot_rx,frame_parms)+1000+2)*20)) { @@ -818,23 +821,24 @@ int phy_procedures_gNB_uespec_RX(PHY_VARS_gNB *gNB, int frame_rx, int slot_rx) VCD_SIGNAL_DUMPER_DUMP_FUNCTION_BY_NAME(VCD_SIGNAL_DUMPER_FUNCTIONS_NR_RX_PUSCH,1); start_meas(&gNB->rx_pusch_stats); nr_rx_pusch(gNB, ULSCH_id, frame_rx, slot_rx, ulsch->harq_pid); - gNB->pusch_vars[ULSCH_id]->ulsch_power_tot = 0; - gNB->pusch_vars[ULSCH_id]->ulsch_noise_power_tot = 0; + NR_gNB_PUSCH *pusch_vars = gNB->pusch_vars[ULSCH_id]; + pusch_vars->ulsch_power_tot = 0; + pusch_vars->ulsch_noise_power_tot = 0; for (int aarx=0;aarxframe_parms.nb_antennas_rx;aarx++) { - gNB->pusch_vars[ULSCH_id]->ulsch_power[aarx] /= num_dmrs; - gNB->pusch_vars[ULSCH_id]->ulsch_power_tot += gNB->pusch_vars[ULSCH_id]->ulsch_power[aarx]; - gNB->pusch_vars[ULSCH_id]->ulsch_noise_power[aarx] /= num_dmrs; - gNB->pusch_vars[ULSCH_id]->ulsch_noise_power_tot += gNB->pusch_vars[ULSCH_id]->ulsch_noise_power[aarx]; + pusch_vars->ulsch_power[aarx] /= num_dmrs; + pusch_vars->ulsch_power_tot += pusch_vars->ulsch_power[aarx]; + pusch_vars->ulsch_noise_power[aarx] /= num_dmrs; + pusch_vars->ulsch_noise_power_tot += pusch_vars->ulsch_noise_power[aarx]; } - if (dB_fixed_x10(gNB->pusch_vars[ULSCH_id]->ulsch_power_tot) < - dB_fixed_x10(gNB->pusch_vars[ULSCH_id]->ulsch_noise_power_tot) + gNB->pusch_thres) { + if (dB_fixed_x10(pusch_vars->ulsch_power_tot) < + dB_fixed_x10(pusch_vars->ulsch_noise_power_tot) + gNB->pusch_thres) { NR_gNB_PHY_STATS_t *stats = get_phy_stats(gNB, ulsch->rnti); LOG_D(PHY, "PUSCH not detected in %d.%d (%d,%d,%d)\n",frame_rx,slot_rx, - dB_fixed_x10(gNB->pusch_vars[ULSCH_id]->ulsch_power_tot), - dB_fixed_x10(gNB->pusch_vars[ULSCH_id]->ulsch_noise_power_tot),gNB->pusch_thres); - gNB->pusch_vars[ULSCH_id]->ulsch_power_tot = gNB->pusch_vars[ULSCH_id]->ulsch_noise_power_tot; - gNB->pusch_vars[ULSCH_id]->DTX = 1; + dB_fixed_x10(pusch_vars->ulsch_power_tot), + dB_fixed_x10(pusch_vars->ulsch_noise_power_tot),gNB->pusch_thres); + pusch_vars->ulsch_power_tot = pusch_vars->ulsch_noise_power_tot; + pusch_vars->DTX = 1; if (stats) stats->ulsch_stats.DTX++; if (!get_softmodem_params()->phy_test) { /* in case of phy_test mode, we still want to decode to measure execution time. @@ -845,10 +849,10 @@ int phy_procedures_gNB_uespec_RX(PHY_VARS_gNB *gNB, int frame_rx, int slot_rx) } } else { LOG_D(PHY, "PUSCH detected in %d.%d (%d,%d,%d)\n",frame_rx,slot_rx, - dB_fixed_x10(gNB->pusch_vars[ULSCH_id]->ulsch_power_tot), - dB_fixed_x10(gNB->pusch_vars[ULSCH_id]->ulsch_noise_power_tot),gNB->pusch_thres); + dB_fixed_x10(pusch_vars->ulsch_power_tot), + dB_fixed_x10(pusch_vars->ulsch_noise_power_tot),gNB->pusch_thres); - gNB->pusch_vars[ULSCH_id]->DTX=0; + pusch_vars->DTX=0; } stop_meas(&gNB->rx_pusch_stats); VCD_SIGNAL_DUMPER_DUMP_FUNCTION_BY_NAME(VCD_SIGNAL_DUMPER_FUNCTIONS_NR_RX_PUSCH,0); From 12ccca72b6ce85d0fba84ee8f6d08789694773fc Mon Sep 17 00:00:00 2001 From: francescomani Date: Mon, 20 Feb 2023 11:09:32 +0100 Subject: [PATCH 09/89] further improvements --- executables/nr-gnb.c | 14 +----- openair1/PHY/INIT/nr_init.c | 10 ++++ .../PHY/NR_ESTIMATION/nr_measurements_gNB.c | 15 +++--- openair1/PHY/NR_ESTIMATION/nr_ul_estimation.h | 2 +- openair1/PHY/NR_TRANSPORT/nr_dlsch.c | 6 --- openair1/PHY/NR_TRANSPORT/nr_dlsch.h | 2 - .../PHY/NR_TRANSPORT/nr_transport_proto.h | 3 ++ openair1/PHY/NR_TRANSPORT/nr_ulsch.c | 47 +++++++++++-------- openair1/PHY/NR_TRANSPORT/nr_ulsch.h | 3 +- openair1/PHY/NR_TRANSPORT/nr_ulsch_decoding.c | 25 +++++----- .../PHY/NR_TRANSPORT/nr_ulsch_demodulation.c | 2 +- openair1/PHY/defs_gNB.h | 6 +-- openair1/SCHED_NR/phy_procedures_nr_gNB.c | 16 +++---- openair1/SIMULATION/NR_PHY/dlsim.c | 2 - openair1/SIMULATION/NR_PHY/ulsim.c | 8 ++-- 15 files changed, 78 insertions(+), 83 deletions(-) diff --git a/executables/nr-gnb.c b/executables/nr-gnb.c index 1220fae924..6df583d704 100644 --- a/executables/nr-gnb.c +++ b/executables/nr-gnb.c @@ -175,18 +175,8 @@ void rx_func(void *param) T(T_GNB_PHY_DL_TICK, T_INT(gNB->Mod_id), T_INT(frame_tx), T_INT(slot_tx)); - // disactivate PHY stats if UE is inactive for a given number of frames - for (int i = 0; i < MAX_MOBILES_PER_GNB; i++) { - NR_gNB_PHY_STATS_t *stats = &gNB->phy_stats[i]; - if(stats->active && (((frame_tx - stats->frame + 1024) % 1024) > NUMBER_FRAMES_PHY_UE_INACTIVE)) - stats->active = false; - } - // disactivate ULSCH structure if it is inactive for a given number of frames - for (int i = 0; i < gNB->max_nb_pusch; i++) { - NR_gNB_ULSCH_t *ulsch = gNB->ulsch[i]; - if (ulsch->active && (((frame_tx - ulsch->harq_process->frame + 1024) % 1024) > NUMBER_FRAMES_PHY_UE_INACTIVE)) - ulsch->active = false; - } + reset_active_stats(gNB, frame_tx); + reset_active_ulsch(gNB, frame_tx); // RX processing int rx_slot_type = nr_slot_select(cfg,frame_rx,slot_rx); diff --git a/openair1/PHY/INIT/nr_init.c b/openair1/PHY/INIT/nr_init.c index 465618a9f4..f3cbc03065 100644 --- a/openair1/PHY/INIT/nr_init.c +++ b/openair1/PHY/INIT/nr_init.c @@ -99,6 +99,16 @@ NR_gNB_PHY_STATS_t *get_phy_stats(PHY_VARS_gNB *gNB, uint16_t rnti) return(stats); } +void reset_active_stats(PHY_VARS_gNB *gNB, int frame) +{ + // disactivate PHY stats if UE is inactive for a given number of frames + for (int i = 0; i < MAX_MOBILES_PER_GNB; i++) { + NR_gNB_PHY_STATS_t *stats = &gNB->phy_stats[i]; + if(stats->active && (((frame - stats->frame + 1024) % 1024) > NUMBER_FRAMES_PHY_UE_INACTIVE)) + stats->active = false; + } +} + int init_codebook_gNB(PHY_VARS_gNB *gNB) { if(gNB->frame_parms.nb_antennas_tx>1){ diff --git a/openair1/PHY/NR_ESTIMATION/nr_measurements_gNB.c b/openair1/PHY/NR_ESTIMATION/nr_measurements_gNB.c index ff90ca54ff..efd0493bb5 100644 --- a/openair1/PHY/NR_ESTIMATION/nr_measurements_gNB.c +++ b/openair1/PHY/NR_ESTIMATION/nr_measurements_gNB.c @@ -208,7 +208,7 @@ void gNB_I0_measurements(PHY_VARS_gNB *gNB,int slot, int first_symb,int num_symb // // Todo: // - averaging IIR filter for RX power and noise -void nr_gnb_measurements(PHY_VARS_gNB *gNB, uint8_t ulsch_id, unsigned char harq_pid, unsigned char symbol, uint8_t nrOfLayers) +void nr_gnb_measurements(PHY_VARS_gNB *gNB, NR_gNB_ULSCH_t *ulsch, NR_gNB_PUSCH *pusch_vars, unsigned char symbol, uint8_t nrOfLayers) { int rx_power_tot = 0; @@ -220,8 +220,8 @@ void nr_gnb_measurements(PHY_VARS_gNB *gNB, uint8_t ulsch_id, unsigned char harq PHY_MEASUREMENTS_gNB *meas = &gNB->measurements; NR_DL_FRAME_PARMS *fp = &gNB->frame_parms; int ch_offset = fp->ofdm_symbol_size * symbol; - int N_RB_UL = gNB->ulsch[ulsch_id]->harq_process->ulsch_pdu.rb_size; - ulsch_measurements_gNB *ulsch_measurements = &gNB->ulsch[ulsch_id]->ulsch_measurements; + int N_RB_UL = ulsch->harq_process->ulsch_pdu.rb_size; + ulsch_measurements_gNB *ulsch_measurements = &ulsch->ulsch_measurements; int rx_power[fp->nb_antennas_rx]; for (int aarx = 0; aarx < fp->nb_antennas_rx; aarx++){ @@ -230,7 +230,7 @@ void nr_gnb_measurements(PHY_VARS_gNB *gNB, uint8_t ulsch_id, unsigned char harq for (int aatx = 0; aatx < nrOfLayers; aatx++){ - ulsch_measurements->rx_spatial_power[aatx][aarx] = (signal_energy_nodc(&gNB->pusch_vars[ulsch_id]->ul_ch_estimates[aatx*fp->nb_antennas_rx+aarx][ch_offset], N_RB_UL * NR_NB_SC_PER_RB)); + ulsch_measurements->rx_spatial_power[aatx][aarx] = (signal_energy_nodc(&pusch_vars->ul_ch_estimates[aatx*fp->nb_antennas_rx+aarx][ch_offset], N_RB_UL * NR_NB_SC_PER_RB)); if (ulsch_measurements->rx_spatial_power[aatx][aarx] < 0) { ulsch_measurements->rx_spatial_power[aatx][aarx] = 0; @@ -240,7 +240,7 @@ void nr_gnb_measurements(PHY_VARS_gNB *gNB, uint8_t ulsch_id, unsigned char harq rx_power[aarx] += ulsch_measurements->rx_spatial_power[aatx][aarx]; } - LOG_D(PHY, "[ULSCH ID %d] RX power in antenna %d = %d\n", ulsch_id, aarx, rx_power[aarx]); + LOG_D(PHY, "[RNTI %04x] RX power in antenna %d = %d\n", ulsch->rnti, aarx, rx_power[aarx]); rx_power_tot += rx_power[aarx]; @@ -252,13 +252,12 @@ void nr_gnb_measurements(PHY_VARS_gNB *gNB, uint8_t ulsch_id, unsigned char harq ulsch_measurements->wideband_cqi_tot = dB_fixed2(rx_power_tot, meas->n0_power_tot); ulsch_measurements->rx_rssi_dBm = rx_power_avg_dB + 30 - 10 * log10(pow(2, 30)) - (rx_gain - rx_gain_offset) - dB_fixed(fp->ofdm_symbol_size); - LOG_D(PHY, "[ULSCH %d] RSSI %d dBm/RE, RSSI (digital) %d dB (N_RB_UL %d), WBand CQI tot %d dB, N0 Power tot %d, RX Power tot %d\n", - ulsch_id, + LOG_D(PHY, "[RNTI %04x] RSSI %d dBm/RE, RSSI (digital) %d dB (N_RB_UL %d), WBand CQI tot %d dB, N0 Power tot %d, RX Power tot %d\n", + ulsch->rnti, ulsch_measurements->rx_rssi_dBm, rx_power_avg_dB, N_RB_UL, ulsch_measurements->wideband_cqi_tot, meas->n0_power_tot, rx_power_tot); - } diff --git a/openair1/PHY/NR_ESTIMATION/nr_ul_estimation.h b/openair1/PHY/NR_ESTIMATION/nr_ul_estimation.h index defd62fd8b..ce369ef9ac 100644 --- a/openair1/PHY/NR_ESTIMATION/nr_ul_estimation.h +++ b/openair1/PHY/NR_ESTIMATION/nr_ul_estimation.h @@ -53,7 +53,7 @@ void dump_nr_I0_stats(FILE *fd,PHY_VARS_gNB *gNB); void gNB_I0_measurements(PHY_VARS_gNB *gNB,int slot,int first_symb,int num_symb); -void nr_gnb_measurements(PHY_VARS_gNB *gNB, uint8_t ulsch_id, unsigned char harq_pid, unsigned char symbol, uint8_t nrOfLayers); +void nr_gnb_measurements(PHY_VARS_gNB *gNB, NR_gNB_ULSCH_t *ulsch, NR_gNB_PUSCH *pusch_vars, unsigned char symbol, uint8_t nrOfLayers); void nr_est_timing_advance_pusch(const NR_DL_FRAME_PARMS *frame_parms, const int32_t *ul_ch_estimates_time, NR_ULSCH_delay_t *delay); diff --git a/openair1/PHY/NR_TRANSPORT/nr_dlsch.c b/openair1/PHY/NR_TRANSPORT/nr_dlsch.c index 17882077a8..983d51d66e 100644 --- a/openair1/PHY/NR_TRANSPORT/nr_dlsch.c +++ b/openair1/PHY/NR_TRANSPORT/nr_dlsch.c @@ -580,9 +580,3 @@ void dump_pdsch_stats(FILE *fd,PHY_VARS_gNB *gNB) { } } } - -void clear_pdsch_stats(PHY_VARS_gNB *gNB) -{ - for (int i = 0; i < MAX_MOBILES_PER_GNB;i++) - memset((void*)&gNB->phy_stats[i].dlsch_stats,0,sizeof(gNB->phy_stats[i].dlsch_stats)); -} diff --git a/openair1/PHY/NR_TRANSPORT/nr_dlsch.h b/openair1/PHY/NR_TRANSPORT/nr_dlsch.h index 6e2210aab0..8ee2e1171f 100644 --- a/openair1/PHY/NR_TRANSPORT/nr_dlsch.h +++ b/openair1/PHY/NR_TRANSPORT/nr_dlsch.h @@ -75,6 +75,4 @@ void nr_emulate_dlsch_payload(uint8_t* payload, uint16_t size); void dump_pdsch_stats(FILE *fd,PHY_VARS_gNB *gNB); -void clear_pdsch_stats(PHY_VARS_gNB *gNB); - #endif diff --git a/openair1/PHY/NR_TRANSPORT/nr_transport_proto.h b/openair1/PHY/NR_TRANSPORT/nr_transport_proto.h index b0a575a0ac..b458028173 100644 --- a/openair1/PHY/NR_TRANSPORT/nr_transport_proto.h +++ b/openair1/PHY/NR_TRANSPORT/nr_transport_proto.h @@ -270,6 +270,9 @@ void nr_ulsch_compute_llr(int32_t *rxdataF_comp, uint8_t symbol, uint8_t mod_order); +void reset_active_stats(PHY_VARS_gNB *gNB, int frame); +void reset_active_ulsch(PHY_VARS_gNB *gNB, int frame); + void nr_fill_ulsch(PHY_VARS_gNB *gNB, int frame, int slot, diff --git a/openair1/PHY/NR_TRANSPORT/nr_ulsch.c b/openair1/PHY/NR_TRANSPORT/nr_ulsch.c index 8aca638bfa..5d02718db9 100644 --- a/openair1/PHY/NR_TRANSPORT/nr_ulsch.c +++ b/openair1/PHY/NR_TRANSPORT/nr_ulsch.c @@ -34,14 +34,15 @@ #include "PHY/NR_TRANSPORT/nr_transport_common_proto.h" #include "PHY/NR_TRANSPORT/nr_ulsch.h" -int16_t find_nr_ulsch(PHY_VARS_gNB *gNB, uint16_t rnti, int pid, int frame) +NR_gNB_ULSCH_t *find_nr_ulsch(PHY_VARS_gNB *gNB, uint16_t rnti, int pid) { int16_t first_free_index = -1; AssertFatal(gNB != NULL,"gNB is null\n"); + NR_gNB_ULSCH_t *ulsch = NULL; for (int i = 0; i < gNB->max_nb_pusch; i++) { - NR_gNB_ULSCH_t *ulsch = gNB->ulsch[i]; + ulsch = gNB->ulsch[i]; AssertFatal(ulsch != NULL, "gNB->ulsch[%d] is null\n", i); if(!ulsch->active) { if (first_free_index == -1) @@ -50,10 +51,13 @@ int16_t find_nr_ulsch(PHY_VARS_gNB *gNB, uint16_t rnti, int pid, int frame) else { // if there is already an active ULSCH for this RNTI and HARQ_PID if ((ulsch->harq_pid == pid) && (ulsch->rnti == rnti)) - return i; + return ulsch; } } - return first_free_index; + if (first_free_index != -1) + ulsch = gNB->ulsch[first_free_index]; + + return ulsch; } void nr_fill_ulsch(PHY_VARS_gNB *gNB, @@ -63,23 +67,20 @@ void nr_fill_ulsch(PHY_VARS_gNB *gNB, { int harq_pid = ulsch_pdu->pusch_data.harq_process_id; - int ulsch_id = find_nr_ulsch(gNB, ulsch_pdu->rnti, harq_pid, frame); - AssertFatal((ulsch_id >= 0) && (ulsch_id < gNB->max_nb_pusch), - "illegal or no ulsch_id found!!! rnti %04x ulsch_id %d\n",ulsch_pdu->rnti,ulsch_id); - - NR_gNB_ULSCH_t *ulsch = gNB->ulsch[ulsch_id]; + NR_gNB_ULSCH_t *ulsch = find_nr_ulsch(gNB, ulsch_pdu->rnti, harq_pid); + AssertFatal(ulsch, "No ulsch_id found for rnti %04x\n", ulsch_pdu->rnti); ulsch->rnti = ulsch_pdu->rnti; ulsch->harq_pid = harq_pid; ulsch->handled = 0; ulsch->active = true; + ulsch->frame = frame; + ulsch->slot = slot; NR_UL_gNB_HARQ_t *harq = ulsch->harq_process; - harq->frame = frame; - harq->slot = slot; harq->new_rx = ulsch_pdu->pusch_data.new_data_indicator; - LOG_D(PHY,"%d.%d ULSCH ID %d RNTI %x HARQ PID %d new data indicator %d\n", - frame, slot, ulsch_id, ulsch_pdu->rnti, harq_pid, ulsch_pdu->pusch_data.new_data_indicator); + LOG_D(PHY,"%d.%d RNTI %x HARQ PID %d new data indicator %d\n", + frame, slot, ulsch_pdu->rnti, harq_pid, ulsch_pdu->pusch_data.new_data_indicator); if (harq->new_rx) harq->round = 0; else @@ -87,7 +88,19 @@ void nr_fill_ulsch(PHY_VARS_gNB *gNB, memcpy(&ulsch->harq_process->ulsch_pdu, ulsch_pdu, sizeof(ulsch->harq_process->ulsch_pdu)); - LOG_D(PHY,"Initializing nFAPI for ULSCH, UE %d, harq_pid %d\n", ulsch_id, harq_pid); + LOG_D(PHY,"Initializing nFAPI for ULSCH, harq_pid %d\n", harq_pid); +} + +void reset_active_ulsch(PHY_VARS_gNB *gNB, int frame) +{ + // disactivate ULSCH structure after a given number of frames + // no activity on this structure for NUMBER_FRAMES_PHY_UE_INACTIVE + // assuming UE disconnected or some other error occurred + for (int i = 0; i < gNB->max_nb_pusch; i++) { + NR_gNB_ULSCH_t *ulsch = gNB->ulsch[i]; + if (ulsch->active && (((frame - ulsch->frame + 1024) % 1024) > NUMBER_FRAMES_PHY_UE_INACTIVE)) + ulsch->active = false; + } } void nr_ulsch_unscrambling(int16_t* llr, uint32_t size, uint32_t Nid, uint32_t n_RNTI) @@ -159,10 +172,4 @@ void dump_pusch_stats(FILE *fd, PHY_VARS_gNB *gNB) } } -void clear_pusch_stats(PHY_VARS_gNB *gNB) -{ - for (int i = 0; i < MAX_MOBILES_PER_GNB; i++) - memset((void*)&gNB->phy_stats[i].ulsch_stats,0,sizeof(gNB->phy_stats[i].ulsch_stats)); -} - diff --git a/openair1/PHY/NR_TRANSPORT/nr_ulsch.h b/openair1/PHY/NR_TRANSPORT/nr_ulsch.h index d3b8599aca..2393830881 100644 --- a/openair1/PHY/NR_TRANSPORT/nr_ulsch.h +++ b/openair1/PHY/NR_TRANSPORT/nr_ulsch.h @@ -83,11 +83,10 @@ void nr_ulsch_procedures(PHY_VARS_gNB *gNB, int UE_id, uint8_t harq_pid); -int16_t find_nr_ulsch(PHY_VARS_gNB *gNB, uint16_t rnti, int pid, int frame); +NR_gNB_ULSCH_t *find_nr_ulsch(PHY_VARS_gNB *gNB, uint16_t rnti, int pid); void dump_pusch_stats(FILE *fd,PHY_VARS_gNB *gNB); void dump_nr_I0_stats(FILE *fd,PHY_VARS_gNB *gNB); -void clear_pusch_stats(PHY_VARS_gNB *gNB); NR_gNB_SCH_STATS_t *get_ulsch_stats(PHY_VARS_gNB *gNB,NR_gNB_ULSCH_t *ulsch); diff --git a/openair1/PHY/NR_TRANSPORT/nr_ulsch_decoding.c b/openair1/PHY/NR_TRANSPORT/nr_ulsch_decoding.c index 60902a3783..6d7f197357 100644 --- a/openair1/PHY/NR_TRANSPORT/nr_ulsch_decoding.c +++ b/openair1/PHY/NR_TRANSPORT/nr_ulsch_decoding.c @@ -101,13 +101,14 @@ NR_gNB_ULSCH_t *new_gNB_ulsch(uint8_t max_ldpc_iterations, uint16_t N_RB_UL) ulsch->harq_pid = -1; ulsch->active = false; - ulsch->harq_process = malloc16_clear(sizeof(ulsch->harq_process)); - ulsch->harq_process->b = malloc16_clear(ulsch_bytes); - ulsch->harq_process->c = malloc16_clear(a_segments*sizeof(uint8_t *)); - ulsch->harq_process->d = malloc16_clear(a_segments*sizeof(int16_t *)); + NR_UL_gNB_HARQ_t *harq = malloc16_clear(sizeof(harq)); + ulsch->harq_process = harq; + harq->b = malloc16_clear(ulsch_bytes * sizeof(*harq->b)); + harq->c = malloc16_clear(a_segments * sizeof(*harq->c)); + harq->d = malloc16_clear(a_segments * sizeof(*harq->d)); for (int r = 0; r < a_segments; r++) { - ulsch->harq_process->c[r] = malloc16_clear(8448*sizeof(uint8_t)); - ulsch->harq_process->d[r] = malloc16_clear((68*384)*sizeof(int16_t)); + harq->c[r] = malloc16_clear(8448 * sizeof(*harq->c[r])); + harq->d[r] = malloc16_clear(68 * 384 * sizeof(*harq->d[r])); } return(ulsch); } @@ -538,20 +539,20 @@ uint32_t nr_ulsch_decoding(PHY_VARS_gNB *phy_vars_gNB, VCD_SIGNAL_DUMPER_DUMP_FUNCTION_BY_NAME(VCD_SIGNAL_DUMPER_FUNCTIONS_PHY_gNB_ULSCH_DECODING, 0); - if (harq_process->processedSegments == (harq_process->C)) { - LOG_D(PHY, "[gNB %d] ULSCH: Setting ACK for slot %d TBS %d\n", phy_vars_gNB->Mod_id, harq_process->slot, harq_process->TBS); + if (harq_process->processedSegments == harq_process->C) { + LOG_D(PHY, "[gNB %d] ULSCH: Setting ACK for slot %d TBS %d\n", phy_vars_gNB->Mod_id, ulsch->slot, harq_process->TBS); ulsch->active = false; harq_process->round = 0; LOG_D(PHY, "ULSCH received ok \n"); - nr_fill_indication(phy_vars_gNB, harq_process->frame, harq_process->slot, ULSCH_id, harq_pid, 0, 0); + nr_fill_indication(phy_vars_gNB, ulsch->frame, ulsch->slot, ULSCH_id, harq_pid, 0, 0); } else { LOG_D(PHY, "[gNB %d] ULSCH: Setting NAK for SFN/SF %d/%d (pid %d, status %d, round %d, TBS %d)\n", phy_vars_gNB->Mod_id, - harq_process->frame, - harq_process->slot, + ulsch->frame, + ulsch->slot, harq_pid, ulsch->active, harq_process->round, @@ -559,7 +560,7 @@ uint32_t nr_ulsch_decoding(PHY_VARS_gNB *phy_vars_gNB, ulsch->handled = 1; no_iteration_ldpc = ulsch->max_ldpc_iterations + 1; LOG_D(PHY, "ULSCH %d in error\n", ULSCH_id); - nr_fill_indication(phy_vars_gNB, harq_process->frame, harq_process->slot, ULSCH_id, harq_pid, 1, 0); + nr_fill_indication(phy_vars_gNB, ulsch->frame, ulsch->slot, ULSCH_id, harq_pid, 1, 0); } ulsch->last_iteration_cnt = no_iteration_ldpc; } diff --git a/openair1/PHY/NR_TRANSPORT/nr_ulsch_demodulation.c b/openair1/PHY/NR_TRANSPORT/nr_ulsch_demodulation.c index c96fc64866..370ec4a9c9 100644 --- a/openair1/PHY/NR_TRANSPORT/nr_ulsch_demodulation.c +++ b/openair1/PHY/NR_TRANSPORT/nr_ulsch_demodulation.c @@ -1885,7 +1885,7 @@ void nr_rx_pusch(PHY_VARS_gNB *gNB, &max_ch); } - nr_gnb_measurements(gNB, ulsch_id, harq_pid, symbol, rel15_ul->nrOfLayers); + nr_gnb_measurements(gNB, gNB->ulsch[ulsch_id], gNB->pusch_vars[ulsch_id], symbol, rel15_ul->nrOfLayers); for (aarx = 0; aarx < frame_parms->nb_antennas_rx; aarx++) { if (symbol == rel15_ul->start_symbol_index) { diff --git a/openair1/PHY/defs_gNB.h b/openair1/PHY/defs_gNB.h index cb8990de9c..f52fe32d7f 100644 --- a/openair1/PHY/defs_gNB.h +++ b/openair1/PHY/defs_gNB.h @@ -188,10 +188,6 @@ typedef struct { typedef struct { /// Nfapi ULSCH PDU nfapi_nr_pusch_pdu_t ulsch_pdu; - /// Frame where current HARQ round was sent - uint32_t frame; - /// Slot where current HARQ round was sent - uint32_t slot; /// Index of current HARQ round for this DLSCH uint8_t round; bool new_rx; @@ -261,6 +257,8 @@ typedef struct { } NR_ULSCH_delay_t; typedef struct { + uint32_t frame; + uint32_t slot; /// Pointers to 16 HARQ processes for the ULSCH NR_UL_gNB_HARQ_t *harq_process; /// HARQ process mask, indicates which processes are currently active diff --git a/openair1/SCHED_NR/phy_procedures_nr_gNB.c b/openair1/SCHED_NR/phy_procedures_nr_gNB.c index 5388bccb2b..89fdf4918b 100644 --- a/openair1/SCHED_NR/phy_procedures_nr_gNB.c +++ b/openair1/SCHED_NR/phy_procedures_nr_gNB.c @@ -244,16 +244,16 @@ void nr_postDecode(PHY_VARS_gNB *gNB, notifiedFIFO_elt_t *req) if (rdata->nbSegments == ulsch_harq->processedSegments) { if (decodeSuccess && !gNB->pusch_vars[rdata->ulsch_id]->DTX) { LOG_D(PHY,"[gNB %d] ULSCH: Setting ACK for SFN/SF %d.%d (rnti %x, pid %d, ndi %d, status %d, round %d, TBS %d, Max interation (all seg) %d)\n", - gNB->Mod_id, ulsch_harq->frame, ulsch_harq->slot, ulsch->rnti, rdata->harq_pid, pusch_pdu->pusch_data.new_data_indicator, + gNB->Mod_id, ulsch->frame, ulsch->slot, ulsch->rnti, rdata->harq_pid, pusch_pdu->pusch_data.new_data_indicator, ulsch->active, ulsch_harq->round, ulsch_harq->TBS, rdata->decodeIterations); ulsch->active = false; ulsch_harq->round = 0; LOG_D(PHY, "ULSCH received ok \n"); - nr_fill_indication(gNB, ulsch_harq->frame, ulsch_harq->slot, rdata->ulsch_id, rdata->harq_pid, 0,0); + nr_fill_indication(gNB, ulsch->frame, ulsch->slot, rdata->ulsch_id, rdata->harq_pid, 0,0); //dumpsig=1; } else { LOG_D(PHY,"[gNB %d] ULSCH: Setting NAK for SFN/SF %d/%d (pid %d, ndi %d, status %d, round %d, RV %d, prb_start %d, prb_size %d, TBS %d) r %d\n", - gNB->Mod_id, ulsch_harq->frame, ulsch_harq->slot, + gNB->Mod_id, ulsch->frame, ulsch->slot, rdata->harq_pid, pusch_pdu->pusch_data.new_data_indicator, ulsch->active, ulsch_harq->round, ulsch_harq->ulsch_pdu.pusch_data.rv_index, @@ -263,7 +263,7 @@ void nr_postDecode(PHY_VARS_gNB *gNB, notifiedFIFO_elt_t *req) r); ulsch->handled = 1; LOG_D(PHY, "ULSCH %d in error\n",rdata->ulsch_id); - nr_fill_indication(gNB,ulsch_harq->frame, ulsch_harq->slot, rdata->ulsch_id, rdata->harq_pid, 1,0); + nr_fill_indication(gNB, ulsch->frame, ulsch->slot, rdata->ulsch_id, rdata->harq_pid, 1,0); // dumpsig=1; } /* @@ -558,8 +558,8 @@ void fill_ul_rb_mask(PHY_VARS_gNB *gNB, int frame_rx, int slot_rx) { NR_UL_gNB_HARQ_t *ulsch_harq = ulsch->harq_process; AssertFatal(ulsch_harq != NULL,"harq_pid %d is not allocated\n",harq_pid); if ((ulsch->active == true) && - (ulsch_harq->frame == frame_rx) && - (ulsch_harq->slot == slot_rx) && + (ulsch->frame == frame_rx) && + (ulsch->slot == slot_rx) && (ulsch->handled == 0)) { uint8_t symbol_start = ulsch_harq->ulsch_pdu.start_symbol_index; uint8_t symbol_end = symbol_start + ulsch_harq->ulsch_pdu.nr_of_symbols; @@ -779,8 +779,8 @@ int phy_procedures_gNB_uespec_RX(PHY_VARS_gNB *gNB, int frame_rx, int slot_rx) AssertFatal(ulsch_harq != NULL, "harq_pid %d is not allocated\n", ulsch->harq_pid); if ((ulsch->active == true) && - (ulsch_harq->frame == frame_rx) && - (ulsch_harq->slot == slot_rx) && + (ulsch->frame == frame_rx) && + (ulsch->slot == slot_rx) && (ulsch->handled == 0)) { LOG_D(PHY, "PUSCH ID %d with RNTI %x detection started in frame %d slot %d\n", diff --git a/openair1/SIMULATION/NR_PHY/dlsim.c b/openair1/SIMULATION/NR_PHY/dlsim.c index 80ff799ab0..13528aeb6f 100644 --- a/openair1/SIMULATION/NR_PHY/dlsim.c +++ b/openair1/SIMULATION/NR_PHY/dlsim.c @@ -951,8 +951,6 @@ int main(int argc, char **argv) reset_meas(&gNB->tparity); reset_meas(&gNB->toutput); - clear_pdsch_stats(gNB); - uint32_t errors_scrambling[16] = {0}; int n_errors[16] = {0}; int round_trials[16] = {0}; diff --git a/openair1/SIMULATION/NR_PHY/ulsim.c b/openair1/SIMULATION/NR_PHY/ulsim.c index 1a02308e12..805352e3b9 100644 --- a/openair1/SIMULATION/NR_PHY/ulsim.c +++ b/openair1/SIMULATION/NR_PHY/ulsim.c @@ -986,8 +986,6 @@ int main(int argc, char **argv) double blerStats[16] = {0}; double berStats[16] = {0}; - clear_pusch_stats(gNB); - uint64_t sum_pusch_delay = 0; int min_pusch_delay = INT_MAX; int max_pusch_delay = INT_MIN; @@ -1506,9 +1504,9 @@ int main(int argc, char **argv) if (!crc_status) effRate += ((double)TBS) / (double)round; - sum_pusch_delay += gNB->measurements.delay[UE_id].pusch_est_delay; - min_pusch_delay = gNB->measurements.delay[UE_id].pusch_est_delay < min_pusch_delay ? gNB->measurements.delay[UE_id].pusch_est_delay : min_pusch_delay; - max_pusch_delay = gNB->measurements.delay[UE_id].pusch_est_delay > max_pusch_delay ? gNB->measurements.delay[UE_id].pusch_est_delay : max_pusch_delay; + sum_pusch_delay += ulsch_gNB->delay.pusch_est_delay; + min_pusch_delay = ulsch_gNB->delay.pusch_est_delay < min_pusch_delay ? ulsch_gNB->delay.pusch_est_delay : min_pusch_delay; + max_pusch_delay = ulsch_gNB->delay.pusch_est_delay > max_pusch_delay ? ulsch_gNB->delay.pusch_est_delay : max_pusch_delay; delay_pusch_est_count++; } // trial loop From 0d664103176eef28a7c839999f01c7e3c1806f42 Mon Sep 17 00:00:00 2001 From: francescomani Date: Fri, 17 Mar 2023 18:04:25 +0100 Subject: [PATCH 10/89] another round of review --- common/openairinterface5g_limits.h | 2 +- executables/nr-gnb.c | 3 +- nfapi/oai_integration/nfapi_pnf.c | 6 +- openair1/PHY/INIT/nr_init.c | 200 ++++----- .../PHY/NR_ESTIMATION/nr_measurements_gNB.c | 40 +- .../NR_ESTIMATION/nr_ul_channel_estimation.c | 50 ++- openair1/PHY/NR_ESTIMATION/nr_ul_estimation.h | 10 +- openair1/PHY/NR_TRANSPORT/nr_dlsch.c | 6 +- openair1/PHY/NR_TRANSPORT/nr_dlsch_coding.c | 49 +-- openair1/PHY/NR_TRANSPORT/nr_dlsch_tools.c | 12 +- .../PHY/NR_TRANSPORT/nr_transport_proto.h | 11 +- openair1/PHY/NR_TRANSPORT/nr_ulsch.c | 83 ++-- openair1/PHY/NR_TRANSPORT/nr_ulsch.h | 5 +- openair1/PHY/NR_TRANSPORT/nr_ulsch_decoding.c | 283 ++++++------- .../PHY/NR_TRANSPORT/nr_ulsch_demodulation.c | 145 +++---- openair1/PHY/NR_TRANSPORT/pucch_rx.c | 208 +++++---- openair1/PHY/NR_TRANSPORT/srs_rx.c | 24 +- openair1/PHY/defs_gNB.h | 12 +- openair1/SCHED_NR/fapi_nr_l1.c | 13 +- openair1/SCHED_NR/phy_procedures_nr_gNB.c | 395 ++++++++++-------- openair1/SIMULATION/NR_PHY/dlschsim.c | 4 +- openair1/SIMULATION/NR_PHY/dlsim.c | 8 +- openair1/SIMULATION/NR_PHY/pucchsim.c | 2 +- openair1/SIMULATION/NR_PHY/ulschsim.c | 4 +- openair1/SIMULATION/NR_PHY/ulsim.c | 127 ++++-- .../NR_MAC_gNB/gNB_scheduler_primitives.c | 1 - 26 files changed, 916 insertions(+), 787 deletions(-) diff --git a/common/openairinterface5g_limits.h b/common/openairinterface5g_limits.h index b3ae99d047..d9e49e26a7 100644 --- a/common/openairinterface5g_limits.h +++ b/common/openairinterface5g_limits.h @@ -12,7 +12,7 @@ # define NUMBER_OF_SRS_MAX 16 # define NUMBER_OF_SCH_STATS_MAX 16 -# define NUMBER_FRAMES_PHY_UE_INACTIVE 10 +#define NUMBER_FRAMES_PHY_UE_INACTIVE 10 #define MAX_MANAGED_ENB_PER_MOBILE 2 #define MAX_MANAGED_GNB_PER_MOBILE 2 diff --git a/executables/nr-gnb.c b/executables/nr-gnb.c index 6df583d704..05516e01b4 100644 --- a/executables/nr-gnb.c +++ b/executables/nr-gnb.c @@ -130,7 +130,6 @@ void tx_func(void *param) { void rx_func(void *param) { - processingData_L1_t *info = (processingData_L1_t *) param; PHY_VARS_gNB *gNB = info->gNB; int frame_rx = info->frame_rx; @@ -179,7 +178,7 @@ void rx_func(void *param) reset_active_ulsch(gNB, frame_tx); // RX processing - int rx_slot_type = nr_slot_select(cfg,frame_rx,slot_rx); + int rx_slot_type = nr_slot_select(cfg, frame_rx, slot_rx); if (rx_slot_type == NR_UPLINK_SLOT || rx_slot_type == NR_MIXED_SLOT) { // UE-specific RX processing for subframe n // TODO: check if this is correct for PARALLEL_RU_L1_TRX_SPLIT diff --git a/nfapi/oai_integration/nfapi_pnf.c b/nfapi/oai_integration/nfapi_pnf.c index 275a42c736..b431c9b37a 100644 --- a/nfapi/oai_integration/nfapi_pnf.c +++ b/nfapi/oai_integration/nfapi_pnf.c @@ -1220,8 +1220,10 @@ int pnf_phy_dl_tti_req(gNB_L1_rxtx_proc_t *proc, nfapi_pnf_p7_config_t *pnf_p7, if (tx_data != NULL) { uint8_t *dlsch_sdu = (uint8_t *)tx_data->TLVs[0].value.direct; //NFAPI_TRACE(NFAPI_TRACE_INFO, "%s() DLSCH:pdu_index:%d handle_nfapi_dlsch_pdu(eNB, proc_rxtx, dlsch_pdu, transport_blocks:%d sdu:%p) eNB->pdcch_vars[proc->subframe_tx & 1].num_pdcch_symbols:%d\n", __FUNCTION__, rel8_pdu->pdu_index, rel8_pdu->transport_blocks, dlsch_sdu, eNB->pdcch_vars[proc->subframe_tx & 1].num_pdcch_symbols); - AssertFatal(msgTx->num_pdsch_slot < gNB->max_nb_pdsch,"Number of PDSCH PDUs %d exceeded the limit %d\n", - msgTx->num_pdsch_slot, gNB->max_nb_pdsch); + AssertFatal(msgTx->num_pdsch_slot < gNB->max_nb_pdsch, + "Number of PDSCH PDUs %d exceeded the limit %d\n", + msgTx->num_pdsch_slot, + gNB->max_nb_pdsch); handle_nr_nfapi_pdsch_pdu(msgTx, pdsch_pdu, dlsch_sdu); } else { diff --git a/openair1/PHY/INIT/nr_init.c b/openair1/PHY/INIT/nr_init.c index f3cbc03065..1fa946b476 100644 --- a/openair1/PHY/INIT/nr_init.c +++ b/openair1/PHY/INIT/nr_init.c @@ -79,24 +79,24 @@ void init_ul_delay_table(NR_DL_FRAME_PARMS *fp) NR_gNB_PHY_STATS_t *get_phy_stats(PHY_VARS_gNB *gNB, uint16_t rnti) { - NR_gNB_PHY_STATS_t *stats; - int first_free = -1; - for (int i = 0; i < MAX_MOBILES_PER_GNB; i++) { - stats = &gNB->phy_stats[i]; - if (stats->active && stats->rnti == rnti) - return stats; - else if (!stats->active && first_free == -1) - first_free = i; - } - // new stats - AssertFatal(first_free >= 0, "PHY statistics list is full\n"); - stats = &gNB->phy_stats[first_free]; - stats->active = true; - stats->rnti = rnti; - memset(&stats->dlsch_stats, 0, sizeof(stats->dlsch_stats)); - memset(&stats->ulsch_stats, 0, sizeof(stats->ulsch_stats)); - memset(&stats->uci_stats, 0, sizeof(stats->uci_stats)); - return(stats); + NR_gNB_PHY_STATS_t *stats; + int first_free = -1; + for (int i = 0; i < MAX_MOBILES_PER_GNB; i++) { + stats = &gNB->phy_stats[i]; + if (stats->active && stats->rnti == rnti) + return stats; + else if (!stats->active && first_free == -1) + first_free = i; + } + // new stats + AssertFatal(first_free >= 0, "PHY statistics list is full\n"); + stats = &gNB->phy_stats[first_free]; + stats->active = true; + stats->rnti = rnti; + memset(&stats->dlsch_stats, 0, sizeof(stats->dlsch_stats)); + memset(&stats->ulsch_stats, 0, sizeof(stats->ulsch_stats)); + memset(&stats->uci_stats, 0, sizeof(stats->uci_stats)); + return stats; } void reset_active_stats(PHY_VARS_gNB *gNB, int frame) @@ -104,7 +104,7 @@ void reset_active_stats(PHY_VARS_gNB *gNB, int frame) // disactivate PHY stats if UE is inactive for a given number of frames for (int i = 0; i < MAX_MOBILES_PER_GNB; i++) { NR_gNB_PHY_STATS_t *stats = &gNB->phy_stats[i]; - if(stats->active && (((frame - stats->frame + 1024) % 1024) > NUMBER_FRAMES_PHY_UE_INACTIVE)) + if (stats->active && (((frame - stats->frame + 1024) % 1024) > NUMBER_FRAMES_PHY_UE_INACTIVE)) stats->active = false; } } @@ -539,7 +539,7 @@ int phy_init_nr_gNB(PHY_VARS_gNB *gNB) load_nrLDPClib(NULL); if (gNB->ldpc_offload_flag) - load_nrLDPClib_offload(); + load_nrLDPClib_offload(); gNB->max_nb_pdsch = MAX_MOBILES_PER_GNB; @@ -695,52 +695,54 @@ int phy_init_nr_gNB(PHY_VARS_gNB *gNB) int nb_re_pusch = N_RB_UL * NR_NB_SC_PER_RB; int nb_re_pusch2 = nb_re_pusch + (nb_re_pusch&7); - NR_gNB_PUSCH **pusch_vars = (NR_gNB_PUSCH **)malloc16_clear(gNB->max_nb_pusch * sizeof(NR_gNB_PUSCH*)); + gNB->pusch_vars = (NR_gNB_PUSCH *)malloc16_clear(gNB->max_nb_pusch * sizeof(NR_gNB_PUSCH)); for (int ULSCH_id = 0; ULSCH_id < gNB->max_nb_pusch; ULSCH_id++) { - pusch_vars[ULSCH_id] = (NR_gNB_PUSCH *)malloc16_clear( sizeof(NR_gNB_PUSCH) ); - pusch_vars[ULSCH_id]->rxdataF_ext = (int32_t **)malloc16(Prx*sizeof(int32_t *) ); - pusch_vars[ULSCH_id]->ul_ch_estimates = (int32_t **)malloc16(n_buf*sizeof(int32_t *) ); - pusch_vars[ULSCH_id]->ul_ch_estimates_ext = (int32_t **)malloc16(n_buf*sizeof(int32_t *) ); - pusch_vars[ULSCH_id]->ptrs_phase_per_slot = (int32_t **)malloc16(n_buf*sizeof(int32_t *) ); - pusch_vars[ULSCH_id]->ul_ch_estimates_time = (int32_t **)malloc16(n_buf*sizeof(int32_t *) ); - pusch_vars[ULSCH_id]->rxdataF_comp = (int32_t **)malloc16(n_buf*sizeof(int32_t *) ); - pusch_vars[ULSCH_id]->ul_ch_mag0 = (int32_t **)malloc16(n_buf*sizeof(int32_t *) ); - pusch_vars[ULSCH_id]->ul_ch_magb0 = (int32_t **)malloc16(n_buf*sizeof(int32_t *) ); - pusch_vars[ULSCH_id]->ul_ch_mag = (int32_t **)malloc16(n_buf*sizeof(int32_t *) ); - pusch_vars[ULSCH_id]->ul_ch_magb = (int32_t **)malloc16(n_buf*sizeof(int32_t *) ); - pusch_vars[ULSCH_id]->rho = (int32_t ***)malloc16(Prx*sizeof(int32_t **) ); - pusch_vars[ULSCH_id]->llr_layers = (int16_t **)malloc16(max_ul_mimo_layers*sizeof(int32_t *) ); + NR_gNB_PUSCH *pusch = &gNB->pusch_vars[ULSCH_id]; + pusch->rxdataF_ext = (int32_t **)malloc16(Prx * sizeof(int32_t *)); + pusch->ul_ch_estimates = (int32_t **)malloc16(n_buf * sizeof(int32_t *)); + pusch->ul_ch_estimates_ext = (int32_t **)malloc16(n_buf * sizeof(int32_t *)); + pusch->ptrs_phase_per_slot = (int32_t **)malloc16(n_buf * sizeof(int32_t *)); + pusch->ul_ch_estimates_time = (int32_t **)malloc16(n_buf * sizeof(int32_t *)); + pusch->rxdataF_comp = (int32_t **)malloc16(n_buf * sizeof(int32_t *)); + pusch->ul_ch_mag0 = (int32_t **)malloc16(n_buf * sizeof(int32_t *)); + pusch->ul_ch_magb0 = (int32_t **)malloc16(n_buf * sizeof(int32_t *)); + pusch->ul_ch_mag = (int32_t **)malloc16(n_buf * sizeof(int32_t *)); + pusch->ul_ch_magb = (int32_t **)malloc16(n_buf * sizeof(int32_t *)); + pusch->rho = (int32_t ***)malloc16(Prx * sizeof(int32_t **)); + pusch->llr_layers = (int16_t **)malloc16(max_ul_mimo_layers * sizeof(int32_t *)); for (i=0; irxdataF_ext[i] = (int32_t *)malloc16_clear( sizeof(int32_t)*nb_re_pusch2*fp->symbols_per_slot ); - pusch_vars[ULSCH_id]->rho[i] = (int32_t **)malloc16_clear(NR_MAX_NB_LAYERS*NR_MAX_NB_LAYERS*sizeof(int32_t*)); + pusch->rxdataF_ext[i] = (int32_t *)malloc16_clear(sizeof(int32_t) * nb_re_pusch2 * fp->symbols_per_slot); + pusch->rho[i] = (int32_t **)malloc16_clear(NR_MAX_NB_LAYERS * NR_MAX_NB_LAYERS * sizeof(int32_t *)); for (int j=0; j< max_ul_mimo_layers; j++) { for (int k=0; krho[i][j*max_ul_mimo_layers+k]=(int32_t *)malloc16_clear( sizeof(int32_t)*nb_re_pusch2*fp->symbols_per_slot ); + pusch->rho[i][j * max_ul_mimo_layers + k] = + (int32_t *)malloc16_clear(sizeof(int32_t) * nb_re_pusch2 * fp->symbols_per_slot); } } } for (i=0; iul_ch_estimates[i] = (int32_t *)malloc16_clear( sizeof(int32_t)*fp->ofdm_symbol_size*fp->symbols_per_slot ); - pusch_vars[ULSCH_id]->ul_ch_estimates_ext[i] = (int32_t *)malloc16_clear( sizeof(int32_t)*nb_re_pusch2*fp->symbols_per_slot ); - pusch_vars[ULSCH_id]->ul_ch_estimates_time[i] = (int32_t *)malloc16_clear( sizeof(int32_t)*fp->ofdm_symbol_size ); - pusch_vars[ULSCH_id]->ptrs_phase_per_slot[i] = (int32_t *)malloc16_clear( sizeof(int32_t)*fp->symbols_per_slot); // symbols per slot - pusch_vars[ULSCH_id]->rxdataF_comp[i] = (int32_t *)malloc16_clear( sizeof(int32_t)*nb_re_pusch2*fp->symbols_per_slot ); - pusch_vars[ULSCH_id]->ul_ch_mag0[i] = (int32_t *)malloc16_clear( sizeof(int32_t)*nb_re_pusch2*fp->symbols_per_slot ); - pusch_vars[ULSCH_id]->ul_ch_magb0[i] = (int32_t *)malloc16_clear( sizeof(int32_t)*nb_re_pusch2*fp->symbols_per_slot ); - pusch_vars[ULSCH_id]->ul_ch_mag[i] = (int32_t *)malloc16_clear( sizeof(int32_t)*nb_re_pusch2*fp->symbols_per_slot ); - pusch_vars[ULSCH_id]->ul_ch_magb[i] = (int32_t *)malloc16_clear( sizeof(int32_t)*nb_re_pusch2*fp->symbols_per_slot ); + pusch->ul_ch_estimates[i] = (int32_t *)malloc16_clear(sizeof(int32_t) * fp->ofdm_symbol_size * fp->symbols_per_slot); + pusch->ul_ch_estimates_ext[i] = (int32_t *)malloc16_clear(sizeof(int32_t) * nb_re_pusch2 * fp->symbols_per_slot); + pusch->ul_ch_estimates_time[i] = (int32_t *)malloc16_clear(sizeof(int32_t) * fp->ofdm_symbol_size); + pusch->ptrs_phase_per_slot[i] = (int32_t *)malloc16_clear(sizeof(int32_t) * fp->symbols_per_slot); // symbols per slot + pusch->rxdataF_comp[i] = (int32_t *)malloc16_clear(sizeof(int32_t) * nb_re_pusch2 * fp->symbols_per_slot); + pusch->ul_ch_mag0[i] = (int32_t *)malloc16_clear(sizeof(int32_t) * nb_re_pusch2 * fp->symbols_per_slot); + pusch->ul_ch_magb0[i] = (int32_t *)malloc16_clear(sizeof(int32_t) * nb_re_pusch2 * fp->symbols_per_slot); + pusch->ul_ch_mag[i] = (int32_t *)malloc16_clear(sizeof(int32_t) * nb_re_pusch2 * fp->symbols_per_slot); + pusch->ul_ch_magb[i] = (int32_t *)malloc16_clear(sizeof(int32_t) * nb_re_pusch2 * fp->symbols_per_slot); } for (i=0; i< max_ul_mimo_layers; i++) { - pusch_vars[ULSCH_id]->llr_layers[i] = (int16_t *)malloc16_clear( (8*((3*8*6144)+12))*sizeof(int16_t) ); // [hna] 6144 is LTE and (8*((3*8*6144)+12)) is not clear + pusch->llr_layers[i] = (int16_t *)malloc16_clear((8 * ((3 * 8 * 6144) + 12)) + * sizeof(int16_t)); // [hna] 6144 is LTE and (8*((3*8*6144)+12)) is not clear } - pusch_vars[ULSCH_id]->llr = (int16_t *)malloc16_clear( (8*((3*8*6144)+12))*sizeof(int16_t) ); // [hna] 6144 is LTE and (8*((3*8*6144)+12)) is not clear - pusch_vars[ULSCH_id]->ul_valid_re_per_slot = (int16_t *)malloc16_clear( sizeof(int16_t)*fp->symbols_per_slot); - } //ulsch_id + pusch->llr = (int16_t *)malloc16_clear((8 * ((3 * 8 * 6144) + 12)) + * sizeof(int16_t)); // [hna] 6144 is LTE and (8*((3*8*6144)+12)) is not clear + pusch->ul_valid_re_per_slot = (int16_t *)malloc16_clear(sizeof(int16_t) * fp->symbols_per_slot); + } // ulsch_id - gNB->pusch_vars = pusch_vars; return (0); } @@ -845,46 +847,46 @@ void phy_free_nr_gNB(PHY_VARS_gNB *gNB) free_and_zero(prach_vars->rxsigF); free_and_zero(prach_vars->prach_ifft); - NR_gNB_PUSCH** pusch_vars = gNB->pusch_vars; for (int ULSCH_id = 0; ULSCH_id < gNB->max_nb_pusch; ULSCH_id++) { + NR_gNB_PUSCH *pusch_vars = &gNB->pusch_vars[ULSCH_id]; for (int i=0; i< max_ul_mimo_layers; i++) - free_and_zero(pusch_vars[ULSCH_id]->llr_layers[i]); + free_and_zero(pusch_vars->llr_layers[i]); for (int i = 0; i < Prx; i++) { - free_and_zero(pusch_vars[ULSCH_id]->rxdataF_ext[i]); + free_and_zero(pusch_vars->rxdataF_ext[i]); for (int j=0; j< max_ul_mimo_layers; j++) { for (int k=0; krho[i][j*max_ul_mimo_layers+k]); + free_and_zero(pusch_vars->rho[i][j * max_ul_mimo_layers + k]); } - free_and_zero(pusch_vars[ULSCH_id]->rho[i]); + free_and_zero(pusch_vars->rho[i]); } for (int i = 0; i < n_buf; i++) { - free_and_zero(pusch_vars[ULSCH_id]->ul_ch_estimates[i]); - free_and_zero(pusch_vars[ULSCH_id]->ul_ch_estimates_ext[i]); - free_and_zero(pusch_vars[ULSCH_id]->ul_ch_estimates_time[i]); - free_and_zero(pusch_vars[ULSCH_id]->ptrs_phase_per_slot[i]); - free_and_zero(pusch_vars[ULSCH_id]->rxdataF_comp[i]); - free_and_zero(pusch_vars[ULSCH_id]->ul_ch_mag0[i]); - free_and_zero(pusch_vars[ULSCH_id]->ul_ch_magb0[i]); - free_and_zero(pusch_vars[ULSCH_id]->ul_ch_mag[i]); - free_and_zero(pusch_vars[ULSCH_id]->ul_ch_magb[i]); + free_and_zero(pusch_vars->ul_ch_estimates[i]); + free_and_zero(pusch_vars->ul_ch_estimates_ext[i]); + free_and_zero(pusch_vars->ul_ch_estimates_time[i]); + free_and_zero(pusch_vars->ptrs_phase_per_slot[i]); + free_and_zero(pusch_vars->rxdataF_comp[i]); + free_and_zero(pusch_vars->ul_ch_mag0[i]); + free_and_zero(pusch_vars->ul_ch_magb0[i]); + free_and_zero(pusch_vars->ul_ch_mag[i]); + free_and_zero(pusch_vars->ul_ch_magb[i]); } - free_and_zero(pusch_vars[ULSCH_id]->llr_layers); - free_and_zero(pusch_vars[ULSCH_id]->rxdataF_ext); - free_and_zero(pusch_vars[ULSCH_id]->ul_ch_estimates); - free_and_zero(pusch_vars[ULSCH_id]->ul_ch_estimates_ext); - free_and_zero(pusch_vars[ULSCH_id]->ptrs_phase_per_slot); - free_and_zero(pusch_vars[ULSCH_id]->ul_ch_estimates_time); - free_and_zero(pusch_vars[ULSCH_id]->ul_valid_re_per_slot); - free_and_zero(pusch_vars[ULSCH_id]->rxdataF_comp); - free_and_zero(pusch_vars[ULSCH_id]->ul_ch_mag0); - free_and_zero(pusch_vars[ULSCH_id]->ul_ch_magb0); - free_and_zero(pusch_vars[ULSCH_id]->ul_ch_mag); - free_and_zero(pusch_vars[ULSCH_id]->ul_ch_magb); - free_and_zero(pusch_vars[ULSCH_id]->rho); + free_and_zero(pusch_vars->llr_layers); + free_and_zero(pusch_vars->rxdataF_ext); + free_and_zero(pusch_vars->ul_ch_estimates); + free_and_zero(pusch_vars->ul_ch_estimates_ext); + free_and_zero(pusch_vars->ptrs_phase_per_slot); + free_and_zero(pusch_vars->ul_ch_estimates_time); + free_and_zero(pusch_vars->ul_valid_re_per_slot); + free_and_zero(pusch_vars->rxdataF_comp); + free_and_zero(pusch_vars->ul_ch_mag0); + free_and_zero(pusch_vars->ul_ch_magb0); + free_and_zero(pusch_vars->ul_ch_mag); + free_and_zero(pusch_vars->ul_ch_magb); + free_and_zero(pusch_vars->rho); - free_and_zero(pusch_vars[ULSCH_id]->llr); - free_and_zero(pusch_vars[ULSCH_id]); - } //ULSCH_id + free_and_zero(pusch_vars->llr); + free_and_zero(pusch_vars); + } // ULSCH_id free(gNB->pusch_vars); } @@ -965,7 +967,6 @@ void nr_phy_config_request_sim(PHY_VARS_gNB *gNB, LOG_I(PHY,"gNB configured\n"); } - void nr_phy_config_request(NR_PHY_Config_t *phy_config) { uint8_t Mod_id = phy_config->Mod_id; @@ -1026,14 +1027,13 @@ void init_DLSCH_struct(PHY_VARS_gNB *gNB, processingData_L1tx_t *msg) uint16_t grid_size = cfg->carrier_config.dl_grid_size[fp->numerology_index].value; msg->num_pdsch_slot = 0; - msg->dlsch = malloc16(gNB->max_nb_pdsch * sizeof(NR_gNB_DLSCH_t **)); + msg->dlsch = malloc16(gNB->max_nb_pdsch * sizeof(NR_gNB_DLSCH_t *)); int num_cw = NR_MAX_NB_LAYERS > 4? 2:1; for (int i = 0; i < gNB->max_nb_pdsch; i++) { LOG_D(PHY, "Allocating Transport Channel Buffers for DLSCH %d/%d\n", i, gNB->max_nb_pdsch); - msg->dlsch[i] = (NR_gNB_DLSCH_t **)malloc16(num_cw * sizeof(NR_gNB_DLSCH_t *)); + msg->dlsch[i] = (NR_gNB_DLSCH_t *)malloc16(num_cw * sizeof(NR_gNB_DLSCH_t)); for (int j = 0; j < num_cw; j++) { msg->dlsch[i][j] = new_gNB_dlsch(fp, grid_size); - AssertFatal(msg->dlsch[i][j]!=NULL,"Can't initialize dlsch %d \n", i); } } } @@ -1045,9 +1045,8 @@ void reset_DLSCH_struct(const PHY_VARS_gNB *gNB, processingData_L1tx_t *msg) const uint16_t grid_size = cfg->carrier_config.dl_grid_size[fp->numerology_index].value; int num_cw = NR_MAX_NB_LAYERS > 4? 2:1; for (int i = 0; i < gNB->max_nb_pdsch; i++) { - for (int j=0; jdlsch[i][j], grid_size, fp); - free(msg->dlsch[i][j]); } free(msg->dlsch[i]); } @@ -1089,28 +1088,19 @@ void init_nr_transport(PHY_VARS_gNB *gNB) gNB->max_nb_pusch = buffer_ul_slots ? MAX_MOBILES_PER_GNB * buffer_ul_slots : 1; gNB->max_nb_srs = buffer_ul_slots << 1; // assuming at most 2 SRS per slot - gNB->pucch = (NR_gNB_PUCCH_t **) malloc16(gNB->max_nb_pucch * sizeof(NR_gNB_PUCCH_t*)); + gNB->pucch = (NR_gNB_PUCCH_t *)malloc16(gNB->max_nb_pucch * sizeof(NR_gNB_PUCCH_t)); for (int i = 0; i < gNB->max_nb_pucch; i++) { - LOG_D(PHY,"Allocating Transport Channel Buffers for PUCCH %d/%d\n", i, gNB->max_nb_pucch); - gNB->pucch[i] = new_gNB_pucch(); - AssertFatal(gNB->pucch[i] != NULL,"Can't initialize pucch %d \n", i); + memset(&gNB->pucch[i], 0, sizeof(gNB->pucch[i])); } - gNB->srs = (NR_gNB_SRS_t **) malloc16(gNB->max_nb_srs * sizeof(NR_gNB_SRS_t*)); - for (int i = 0; i < gNB->max_nb_srs; i++) { - LOG_D(PHY,"Allocating Transport Channel Buffers for SRS %d/%d\n", i, gNB->max_nb_srs); - gNB->srs[i] = new_gNB_srs(); - AssertFatal(gNB->srs[i]!=NULL,"Can't initialize srs %d \n", i); - } + gNB->srs = (NR_gNB_SRS_t *)malloc16(gNB->max_nb_srs * sizeof(NR_gNB_SRS_t)); + for (int i = 0; i < gNB->max_nb_srs; i++) + gNB->srs[i].active = 0; - gNB->ulsch = (NR_gNB_ULSCH_t **) malloc16(gNB->max_nb_pusch * sizeof(NR_gNB_ULSCH_t*)); + gNB->ulsch = (NR_gNB_ULSCH_t *)malloc16(gNB->max_nb_pusch * sizeof(NR_gNB_ULSCH_t)); for (int i = 0; i < gNB->max_nb_pusch; i++) { LOG_D(PHY, "Allocating Transport Channel Buffers for ULSCH %d/%d\n", i, gNB->max_nb_pusch); gNB->ulsch[i] = new_gNB_ulsch(gNB->max_ldpc_iterations, fp->N_RB_UL); - if (!gNB->ulsch[i]) { - LOG_E(PHY,"Can't get gNB ulsch structures\n"); - exit(-1); - } } gNB->rx_total_gain_dB=130; @@ -1122,15 +1112,9 @@ void reset_nr_transport(PHY_VARS_gNB *gNB) { const NR_DL_FRAME_PARMS *fp = &gNB->frame_parms; - for (int i = 0; i < gNB->max_nb_pucch; i++) - free_gNB_pucch(gNB->pucch[i]); free(gNB->pucch); - - for (int i = 0; i < gNB->max_nb_srs; i++) - free_gNB_srs(gNB->srs[i]); free(gNB->srs); for (int i = 0; i < gNB->max_nb_pusch; i++) free_gNB_ulsch(&gNB->ulsch[i], fp->N_RB_UL); - free(gNB->ulsch); } diff --git a/openair1/PHY/NR_ESTIMATION/nr_measurements_gNB.c b/openair1/PHY/NR_ESTIMATION/nr_measurements_gNB.c index efd0493bb5..bf2466d2f2 100644 --- a/openair1/PHY/NR_ESTIMATION/nr_measurements_gNB.c +++ b/openair1/PHY/NR_ESTIMATION/nr_measurements_gNB.c @@ -208,9 +208,12 @@ void gNB_I0_measurements(PHY_VARS_gNB *gNB,int slot, int first_symb,int num_symb // // Todo: // - averaging IIR filter for RX power and noise -void nr_gnb_measurements(PHY_VARS_gNB *gNB, NR_gNB_ULSCH_t *ulsch, NR_gNB_PUSCH *pusch_vars, unsigned char symbol, uint8_t nrOfLayers) +void nr_gnb_measurements(PHY_VARS_gNB *gNB, + NR_gNB_ULSCH_t *ulsch, + NR_gNB_PUSCH *pusch_vars, + unsigned char symbol, + uint8_t nrOfLayers) { - int rx_power_tot = 0; unsigned short rx_power_avg_dB; unsigned short rx_power_tot_dB; @@ -225,39 +228,40 @@ void nr_gnb_measurements(PHY_VARS_gNB *gNB, NR_gNB_ULSCH_t *ulsch, NR_gNB_PUSCH int rx_power[fp->nb_antennas_rx]; for (int aarx = 0; aarx < fp->nb_antennas_rx; aarx++){ - rx_power[aarx] = 0; for (int aatx = 0; aatx < nrOfLayers; aatx++){ - - ulsch_measurements->rx_spatial_power[aatx][aarx] = (signal_energy_nodc(&pusch_vars->ul_ch_estimates[aatx*fp->nb_antennas_rx+aarx][ch_offset], N_RB_UL * NR_NB_SC_PER_RB)); + ulsch_measurements->rx_spatial_power[aatx][aarx] = + (signal_energy_nodc(&pusch_vars->ul_ch_estimates[aatx * fp->nb_antennas_rx + aarx][ch_offset], + N_RB_UL * NR_NB_SC_PER_RB)); if (ulsch_measurements->rx_spatial_power[aatx][aarx] < 0) { ulsch_measurements->rx_spatial_power[aatx][aarx] = 0; } - ulsch_measurements->rx_spatial_power_dB[aatx][aarx] = (unsigned short) dB_fixed(ulsch_measurements->rx_spatial_power[aatx][aarx]); + ulsch_measurements->rx_spatial_power_dB[aatx][aarx] = + (unsigned short)dB_fixed(ulsch_measurements->rx_spatial_power[aatx][aarx]); rx_power[aarx] += ulsch_measurements->rx_spatial_power[aatx][aarx]; - } LOG_D(PHY, "[RNTI %04x] RX power in antenna %d = %d\n", ulsch->rnti, aarx, rx_power[aarx]); rx_power_tot += rx_power[aarx]; - } - rx_power_tot_dB = (unsigned short) dB_fixed(rx_power_tot); + rx_power_tot_dB = (unsigned short)dB_fixed(rx_power_tot); rx_power_avg_dB = rx_power_tot_dB; ulsch_measurements->wideband_cqi_tot = dB_fixed2(rx_power_tot, meas->n0_power_tot); - ulsch_measurements->rx_rssi_dBm = rx_power_avg_dB + 30 - 10 * log10(pow(2, 30)) - (rx_gain - rx_gain_offset) - dB_fixed(fp->ofdm_symbol_size); + ulsch_measurements->rx_rssi_dBm = + rx_power_avg_dB + 30 - 10 * log10(pow(2, 30)) - (rx_gain - rx_gain_offset) - dB_fixed(fp->ofdm_symbol_size); - LOG_D(PHY, "[RNTI %04x] RSSI %d dBm/RE, RSSI (digital) %d dB (N_RB_UL %d), WBand CQI tot %d dB, N0 Power tot %d, RX Power tot %d\n", - ulsch->rnti, - ulsch_measurements->rx_rssi_dBm, - rx_power_avg_dB, - N_RB_UL, - ulsch_measurements->wideband_cqi_tot, - meas->n0_power_tot, - rx_power_tot); + LOG_D(PHY, + "[RNTI %04x] RSSI %d dBm/RE, RSSI (digital) %d dB (N_RB_UL %d), WBand CQI tot %d dB, N0 Power tot %d, RX Power tot %d\n", + ulsch->rnti, + ulsch_measurements->rx_rssi_dBm, + rx_power_avg_dB, + N_RB_UL, + ulsch_measurements->wideband_cqi_tot, + meas->n0_power_tot, + rx_power_tot); } diff --git a/openair1/PHY/NR_ESTIMATION/nr_ul_channel_estimation.c b/openair1/PHY/NR_ESTIMATION/nr_ul_channel_estimation.c index 1ce5ed577f..e953d8a4a0 100644 --- a/openair1/PHY/NR_ESTIMATION/nr_ul_channel_estimation.c +++ b/openair1/PHY/NR_ESTIMATION/nr_ul_channel_estimation.c @@ -130,7 +130,8 @@ int nr_pusch_channel_estimation(PHY_VARS_gNB *gNB, debug_ch_est = fopen("debug_ch_est.txt","w"); #endif //uint16_t Nid_cell = (eNB_offset == 0) ? gNB->frame_parms.Nid_cell : gNB->measurements.adj_cell_id[eNB_offset-1]; - c16_t **ul_ch_estimates = (c16_t **) gNB->pusch_vars[ul_id]->ul_ch_estimates; + NR_gNB_PUSCH *pusch_vars = &gNB->pusch_vars[ul_id]; + c16_t **ul_ch_estimates = (c16_t **)pusch_vars->ul_ch_estimates; const int symbolSize = gNB->frame_parms.ofdm_symbol_size; const int soffset = (Ns&3)*gNB->frame_parms.symbols_per_slot*symbolSize; const int nushift = (p>>1)&1; @@ -187,8 +188,8 @@ int nr_pusch_channel_estimation(PHY_VARS_gNB *gNB, c16_t ul_ls_est[symbolSize] __attribute__((aligned(32))); memset(ul_ls_est, 0, sizeof(c16_t) * symbolSize); - NR_ULSCH_delay_t *delay = &gNB->ulsch[ul_id]->delay; - memset(&delay, 0, sizeof(delay)); + NR_ULSCH_delay_t *delay = &gNB->ulsch[ul_id].delay; + memset(delay, 0, sizeof(*delay)); for (int aarx=0; aarxframe_parms.nb_antennas_rx; aarx++) { c16_t *rxdataF = (c16_t *)&gNB->common_vars.rxdataF[aarx][symbol_offset]; @@ -229,11 +230,9 @@ int nr_pusch_channel_estimation(PHY_VARS_gNB *gNB, pilot_cnt += 2; } - freq2time(symbolSize, - (int16_t *) ul_ls_est, - (int16_t *) gNB->pusch_vars[ul_id]->ul_ch_estimates_time[aarx]); + freq2time(symbolSize, (int16_t *)ul_ls_est, (int16_t *)pusch_vars->ul_ch_estimates_time[aarx]); - nr_est_timing_advance_pusch(&gNB->frame_parms, gNB->pusch_vars[ul_id]->ul_ch_estimates_time[aarx], delay); + nr_est_timing_advance_pusch(&gNB->frame_parms, pusch_vars->ul_ch_estimates_time[aarx], delay); int pusch_delay = delay->pusch_est_delay; int delay_idx = get_delay_idx(pusch_delay); c16_t *ul_delay_table = gNB->frame_parms.ul_delay_table[delay_idx]; @@ -311,8 +310,8 @@ int nr_pusch_channel_estimation(PHY_VARS_gNB *gNB, } // Delay compensation - freq2time(symbolSize, (int16_t *)ul_ls_est, (int16_t *)gNB->pusch_vars[ul_id]->ul_ch_estimates_time[aarx]); - nr_est_timing_advance_pusch(&gNB->frame_parms, gNB->pusch_vars[ul_id]->ul_ch_estimates_time[aarx], delay); + freq2time(symbolSize, (int16_t *)ul_ls_est, (int16_t *)pusch_vars->ul_ch_estimates_time[aarx]); + nr_est_timing_advance_pusch(&gNB->frame_parms, pusch_vars->ul_ch_estimates_time[aarx], delay); int pusch_delay = delay->pusch_est_delay; int delay_idx = get_delay_idx(-pusch_delay); c16_t *ul_delay_table = gNB->frame_parms.ul_delay_table[delay_idx]; @@ -533,7 +532,9 @@ void nr_pusch_ptrs_processing(PHY_VARS_gNB *gNB, uint8_t ulsch_id, uint8_t nr_tti_rx, unsigned char symbol, - uint32_t nb_re_pusch) { + uint32_t nb_re_pusch) +{ + NR_gNB_PUSCH *pusch_vars = &gNB->pusch_vars[ulsch_id]; //#define DEBUG_UL_PTRS 1 int32_t *ptrs_re_symbol = NULL; int8_t ret = 0; @@ -543,16 +544,16 @@ void nr_pusch_ptrs_processing(PHY_VARS_gNB *gNB, uint8_t *L_ptrs = &rel15_ul->pusch_ptrs.ptrs_time_density; uint8_t *K_ptrs = &rel15_ul->pusch_ptrs.ptrs_freq_density; uint16_t *dmrsSymbPos = &rel15_ul->ul_dmrs_symb_pos; - uint16_t *ptrsSymbPos = &gNB->pusch_vars[ulsch_id]->ptrs_symbols; - uint8_t *ptrsSymbIdx = &gNB->pusch_vars[ulsch_id]->ptrs_symbol_index; + uint16_t *ptrsSymbPos = &pusch_vars->ptrs_symbols; + uint8_t *ptrsSymbIdx = &pusch_vars->ptrs_symbol_index; uint8_t *dmrsConfigType = &rel15_ul->dmrs_config_type; uint16_t *nb_rb = &rel15_ul->rb_size; uint8_t *ptrsReOffset = &rel15_ul->pusch_ptrs.ptrs_ports_list[0].ptrs_re_offset; /* loop over antennas */ for (int aarx=0; aarxnb_antennas_rx; aarx++) { - c16_t *phase_per_symbol = (c16_t*)gNB->pusch_vars[ulsch_id]->ptrs_phase_per_slot[aarx]; - ptrs_re_symbol = &gNB->pusch_vars[ulsch_id]->ptrs_re_per_slot; + c16_t *phase_per_symbol = (c16_t *)pusch_vars->ptrs_phase_per_slot[aarx]; + ptrs_re_symbol = &pusch_vars->ptrs_re_per_slot; *ptrs_re_symbol = 0; phase_per_symbol[symbol].i = 0; /* set DMRS estimates to 0 angle with magnitude 1 */ @@ -585,13 +586,17 @@ void nr_pusch_ptrs_processing(PHY_VARS_gNB *gNB, /*------------------------------------------------------------------------------------------------------- */ /* 1) Estimate common phase error per PTRS symbol */ /*------------------------------------------------------------------------------------------------------- */ - nr_ptrs_cpe_estimation(*K_ptrs,*ptrsReOffset,*dmrsConfigType,*nb_rb, + nr_ptrs_cpe_estimation(*K_ptrs, + *ptrsReOffset, + *dmrsConfigType, + *nb_rb, rel15_ul->rnti, nr_tti_rx, - symbol,frame_parms->ofdm_symbol_size, - (int16_t *)&gNB->pusch_vars[ulsch_id]->rxdataF_comp[aarx][(symbol * nb_re_pusch)], + symbol, + frame_parms->ofdm_symbol_size, + (int16_t *)&pusch_vars->rxdataF_comp[aarx][(symbol * nb_re_pusch)], gNB->nr_gold_pusch_dmrs[rel15_ul->scid][nr_tti_rx][symbol], - (int16_t*)&phase_per_symbol[symbol], + (int16_t *)&phase_per_symbol[symbol], ptrs_re_symbol); } @@ -609,7 +614,7 @@ void nr_pusch_ptrs_processing(PHY_VARS_gNB *gNB, } #ifdef DEBUG_UL_PTRS - LOG_M("ptrsEstUl.m","est",gNB->pusch_vars[ulsch_id]->ptrs_phase_per_slot[aarx],frame_parms->symbols_per_slot,1,1 ); + LOG_M("ptrsEstUl.m", "est", pusch_vars->ptrs_phase_per_slot[aarx], frame_parms->symbols_per_slot, 1, 1); LOG_M("rxdataF_bf_ptrs_comp_ul.m","bf_ptrs_cmp", &gNB->pusch_vars[0]->rxdataF_comp[aarx][rel15_ul->start_symbol_index * NR_NB_SC_PER_RB * rel15_ul->rb_size], rel15_ul->nr_of_symbols * NR_NB_SC_PER_RB * rel15_ul->rb_size,1,1); @@ -625,10 +630,11 @@ void nr_pusch_ptrs_processing(PHY_VARS_gNB *gNB, #ifdef DEBUG_UL_PTRS printf("[PHY][UL][PTRS]: Rotate Symbol %2d with %d + j* %d\n", i, phase_per_symbol[i].r,phase_per_symbol[i].i); #endif - rotate_cpx_vector((c16_t*)&gNB->pusch_vars[ulsch_id]->rxdataF_comp[aarx][(i * rel15_ul->rb_size * NR_NB_SC_PER_RB)], + rotate_cpx_vector((c16_t *)&pusch_vars->rxdataF_comp[aarx][(i * rel15_ul->rb_size * NR_NB_SC_PER_RB)], &phase_per_symbol[i], - (c16_t*)&gNB->pusch_vars[ulsch_id]->rxdataF_comp[aarx][(i * rel15_ul->rb_size * NR_NB_SC_PER_RB)], - ((*nb_rb) * NR_NB_SC_PER_RB), 15); + (c16_t *)&pusch_vars->rxdataF_comp[aarx][(i * rel15_ul->rb_size * NR_NB_SC_PER_RB)], + ((*nb_rb) * NR_NB_SC_PER_RB), + 15); }// if not DMRS Symbol }// symbol loop }// last symbol check diff --git a/openair1/PHY/NR_ESTIMATION/nr_ul_estimation.h b/openair1/PHY/NR_ESTIMATION/nr_ul_estimation.h index ce369ef9ac..41f841a975 100644 --- a/openair1/PHY/NR_ESTIMATION/nr_ul_estimation.h +++ b/openair1/PHY/NR_ESTIMATION/nr_ul_estimation.h @@ -53,9 +53,15 @@ void dump_nr_I0_stats(FILE *fd,PHY_VARS_gNB *gNB); void gNB_I0_measurements(PHY_VARS_gNB *gNB,int slot,int first_symb,int num_symb); -void nr_gnb_measurements(PHY_VARS_gNB *gNB, NR_gNB_ULSCH_t *ulsch, NR_gNB_PUSCH *pusch_vars, unsigned char symbol, uint8_t nrOfLayers); +void nr_gnb_measurements(PHY_VARS_gNB *gNB, + NR_gNB_ULSCH_t *ulsch, + NR_gNB_PUSCH *pusch_vars, + unsigned char symbol, + uint8_t nrOfLayers); -void nr_est_timing_advance_pusch(const NR_DL_FRAME_PARMS *frame_parms, const int32_t *ul_ch_estimates_time, NR_ULSCH_delay_t *delay); +void nr_est_timing_advance_pusch(const NR_DL_FRAME_PARMS *frame_parms, + const int32_t *ul_ch_estimates_time, + NR_ULSCH_delay_t *delay); int nr_est_timing_advance_srs(const NR_DL_FRAME_PARMS *frame_parms, const int32_t srs_estimated_channel_time[][frame_parms->ofdm_symbol_size]); diff --git a/openair1/PHY/NR_TRANSPORT/nr_dlsch.c b/openair1/PHY/NR_TRANSPORT/nr_dlsch.c index 983d51d66e..8cde42d940 100644 --- a/openair1/PHY/NR_TRANSPORT/nr_dlsch.c +++ b/openair1/PHY/NR_TRANSPORT/nr_dlsch.c @@ -75,7 +75,7 @@ void nr_generate_pdsch(processingData_L1tx_t *msgTx, time_stats_t *dlsch_segmentation_stats=&gNB->dlsch_segmentation_stats; for (int dlsch_id=0; dlsch_idnum_pdsch_slot; dlsch_id++) { - dlsch = msgTx->dlsch[dlsch_id][0]; + dlsch = &msgTx->dlsch[dlsch_id][0]; NR_DL_gNB_HARQ_t *harq = &dlsch->harq_process; nfapi_nr_dl_tti_pdsch_pdu_rel15_t *rel15 = &harq->pdsch_pdu.pdsch_pdu_rel15; @@ -567,12 +567,12 @@ void nr_generate_pdsch(processingData_L1tx_t *msgTx, } void dump_pdsch_stats(FILE *fd,PHY_VARS_gNB *gNB) { - for (int i = 0; i < MAX_MOBILES_PER_GNB; i++) { NR_gNB_PHY_STATS_t *stats = &gNB->phy_stats[i]; if (stats->active && stats->frame != stats->dlsch_stats.dump_frame) { stats->dlsch_stats.dump_frame = stats->frame; - fprintf(fd,"DLSCH RNTI %x: current_Qm %d, current_RI %d, total_bytes TX %d\n", + fprintf(fd, + "DLSCH RNTI %x: current_Qm %d, current_RI %d, total_bytes TX %d\n", stats->rnti, stats->dlsch_stats.current_Qm, stats->dlsch_stats.current_RI, diff --git a/openair1/PHY/NR_TRANSPORT/nr_dlsch_coding.c b/openair1/PHY/NR_TRANSPORT/nr_dlsch_coding.c index 81b3ca686a..d244881c32 100644 --- a/openair1/PHY/NR_TRANSPORT/nr_dlsch_coding.c +++ b/openair1/PHY/NR_TRANSPORT/nr_dlsch_coding.c @@ -48,13 +48,8 @@ //#define DEBUG_DLSCH_CODING //#define DEBUG_DLSCH_FREE 1 - -void free_gNB_dlsch(NR_gNB_DLSCH_t **dlschptr, - uint16_t N_RB, - const NR_DL_FRAME_PARMS* frame_parms) { - - NR_gNB_DLSCH_t *dlsch = *dlschptr; - +void free_gNB_dlsch(NR_gNB_DLSCH_t *dlsch, uint16_t N_RB, const NR_DL_FRAME_PARMS *frame_parms) +{ int max_layers = (frame_parms->nb_antennas_txnb_antennas_tx : NR_MAX_NB_LAYERS; uint16_t a_segments = MAX_NUM_NR_DLSCH_SEGMENTS_PER_LAYER*max_layers; @@ -92,14 +87,10 @@ void free_gNB_dlsch(NR_gNB_DLSCH_t **dlschptr, } free(dlsch->txdataF); free(dlsch->ue_spec_bf_weights); - - free(dlsch); - *dlschptr = NULL; } -NR_gNB_DLSCH_t *new_gNB_dlsch(NR_DL_FRAME_PARMS *frame_parms, - uint16_t N_RB) { - +NR_gNB_DLSCH_t new_gNB_dlsch(NR_DL_FRAME_PARMS *frame_parms, uint16_t N_RB) +{ int max_layers = (frame_parms->nb_antennas_txnb_antennas_tx : NR_MAX_NB_LAYERS; uint16_t a_segments = MAX_NUM_NR_DLSCH_SEGMENTS_PER_LAYER*max_layers; //number of segments to be allocated @@ -110,34 +101,32 @@ NR_gNB_DLSCH_t *new_gNB_dlsch(NR_DL_FRAME_PARMS *frame_parms, LOG_D(PHY,"Allocating %d segments (MAX %d, N_PRB %d)\n",a_segments,MAX_NUM_NR_DLSCH_SEGMENTS_PER_LAYER,N_RB); uint32_t dlsch_bytes = a_segments*1056; // allocated bytes per segment - NR_gNB_DLSCH_t *dlsch = malloc16(sizeof(NR_gNB_DLSCH_t)); - AssertFatal(dlsch, "cannot allocate dlsch\n"); - bzero(dlsch,sizeof(NR_gNB_DLSCH_t)); + NR_gNB_DLSCH_t dlsch; int txdataf_size = frame_parms->N_RB_DL*NR_SYMBOLS_PER_SLOT*NR_NB_SC_PER_RB*8; // max pdsch encoded length for each layer - dlsch->txdataF = (int32_t **)malloc16(max_layers*sizeof(int32_t *)); + dlsch.txdataF = (int32_t **)malloc16(max_layers * sizeof(int32_t *)); - dlsch->ue_spec_bf_weights = (int32_t ***)malloc16(max_layers*sizeof(int32_t **)); + dlsch.ue_spec_bf_weights = (int32_t ***)malloc16(max_layers * sizeof(int32_t **)); for (int layer=0; layerue_spec_bf_weights[layer] = (int32_t **)malloc16(64*sizeof(int32_t *)); + dlsch.ue_spec_bf_weights[layer] = (int32_t **)malloc16(64 * sizeof(int32_t *)); for (int aa=0; aa<64; aa++) { - dlsch->ue_spec_bf_weights[layer][aa] = (int32_t *)malloc16(OFDM_SYMBOL_SIZE_COMPLEX_SAMPLES*sizeof(int32_t)); + dlsch.ue_spec_bf_weights[layer][aa] = (int32_t *)malloc16(OFDM_SYMBOL_SIZE_COMPLEX_SAMPLES * sizeof(int32_t)); for (int re=0; reue_spec_bf_weights[layer][aa][re] = 0x00007fff; + dlsch.ue_spec_bf_weights[layer][aa][re] = 0x00007fff; } } - dlsch->txdataF[layer] = (int32_t *)malloc16((txdataf_size)*sizeof(int32_t)); + dlsch.txdataF[layer] = (int32_t *)malloc16((txdataf_size) * sizeof(int32_t)); } int nb_codewords = NR_MAX_NB_LAYERS > 4 ? 2 : 1; - dlsch->mod_symbs = (int32_t **)malloc16(nb_codewords*sizeof(int32_t *)); + dlsch.mod_symbs = (int32_t **)malloc16(nb_codewords * sizeof(int32_t *)); for (int q=0; qmod_symbs[q] = (int32_t *)malloc16(txdataf_size*max_layers*sizeof(int32_t)); + dlsch.mod_symbs[q] = (int32_t *)malloc16(txdataf_size * max_layers * sizeof(int32_t)); - NR_DL_gNB_HARQ_t *harq = &dlsch->harq_process; + NR_DL_gNB_HARQ_t *harq = &dlsch.harq_process; bzero(harq, sizeof(NR_DL_gNB_HARQ_t)); harq->b = malloc16(dlsch_bytes); AssertFatal(harq->b, "cannot allocate memory for harq->b\n"); @@ -276,9 +265,13 @@ int nr_dlsch_encoding(PHY_VARS_gNB *gNB, uint8_t slot, NR_DL_gNB_HARQ_t *harq, NR_DL_FRAME_PARMS *frame_parms, - unsigned char * output, - time_stats_t *tinput,time_stats_t *tprep,time_stats_t *tparity,time_stats_t *toutput, - time_stats_t *dlsch_rate_matching_stats,time_stats_t *dlsch_interleaving_stats, + unsigned char *output, + time_stats_t *tinput, + time_stats_t *tprep, + time_stats_t *tparity, + time_stats_t *toutput, + time_stats_t *dlsch_rate_matching_stats, + time_stats_t *dlsch_interleaving_stats, time_stats_t *dlsch_segmentation_stats) { encoder_implemparams_t impp; diff --git a/openair1/PHY/NR_TRANSPORT/nr_dlsch_tools.c b/openair1/PHY/NR_TRANSPORT/nr_dlsch_tools.c index b2be4af8c0..c868334a3c 100644 --- a/openair1/PHY/NR_TRANSPORT/nr_dlsch_tools.c +++ b/openair1/PHY/NR_TRANSPORT/nr_dlsch_tools.c @@ -44,17 +44,13 @@ void nr_emulate_dlsch_payload(uint8_t* pdu, uint16_t size) { *(pdu+i) = (uint8_t)rand(); } -void nr_fill_dlsch(processingData_L1tx_t *msgTx, - nfapi_nr_dl_tti_pdsch_pdu *pdsch_pdu, - uint8_t *sdu) +void nr_fill_dlsch(processingData_L1tx_t *msgTx, nfapi_nr_dl_tti_pdsch_pdu *pdsch_pdu, uint8_t *sdu) { - - NR_gNB_DLSCH_t *dlsch = msgTx->dlsch[msgTx->num_pdsch_slot][0]; - NR_DL_gNB_HARQ_t *harq = &dlsch->harq_process; + NR_gNB_DLSCH_t *dlsch = &msgTx->dlsch[msgTx->num_pdsch_slot][0]; + NR_DL_gNB_HARQ_t *harq = &dlsch->harq_process; /// DLSCH struct memcpy((void*)&harq->pdsch_pdu, (void*)pdsch_pdu, sizeof(nfapi_nr_dl_tti_pdsch_pdu)); msgTx->num_pdsch_slot++; - AssertFatal(sdu != NULL,"sdu is null\n"); + AssertFatal(sdu != NULL, "sdu is null\n"); harq->pdu = sdu; } - diff --git a/openair1/PHY/NR_TRANSPORT/nr_transport_proto.h b/openair1/PHY/NR_TRANSPORT/nr_transport_proto.h index b458028173..cb7cd35516 100644 --- a/openair1/PHY/NR_TRANSPORT/nr_transport_proto.h +++ b/openair1/PHY/NR_TRANSPORT/nr_transport_proto.h @@ -108,10 +108,9 @@ int nr_generate_pbch(nfapi_nr_dl_tti_ssb_pdu *ssb_pdu, */ void nr_init_pbch_interleaver(uint8_t *interleaver); -NR_gNB_DLSCH_t *new_gNB_dlsch(NR_DL_FRAME_PARMS *frame_parms, - uint16_t N_RB); +NR_gNB_DLSCH_t new_gNB_dlsch(NR_DL_FRAME_PARMS *frame_parms, uint16_t N_RB); -void free_gNB_dlsch(NR_gNB_DLSCH_t **dlschptr, uint16_t N_RB, const NR_DL_FRAME_PARMS* frame_parms); +void free_gNB_dlsch(NR_gNB_DLSCH_t *dlsch, uint16_t N_RB, const NR_DL_FRAME_PARMS *frame_parms); /** \brief This function is the top-level entry point to PUSCH demodulation, after frequency-domain transformation and channel estimation. It performs - RB extraction (signal and channel estimates) @@ -308,17 +307,11 @@ void nr_fill_prach_ru(RU_t *ru, int16_t find_nr_prach(PHY_VARS_gNB *gNB,int frame,int slot, find_type_t type); int16_t find_nr_prach_ru(RU_t *ru,int frame,int slot, find_type_t type); -NR_gNB_PUCCH_t *new_gNB_pucch(void); -void free_gNB_pucch(NR_gNB_PUCCH_t *pucch); - void nr_fill_pucch(PHY_VARS_gNB *gNB, int frame, int slot, nfapi_nr_pucch_pdu_t *pucch_pdu); -NR_gNB_SRS_t *new_gNB_srs(void); -void free_gNB_srs(NR_gNB_SRS_t *srs); - void nr_fill_srs(PHY_VARS_gNB *gNB, frame_t frame, slot_t slot, diff --git a/openair1/PHY/NR_TRANSPORT/nr_ulsch.c b/openair1/PHY/NR_TRANSPORT/nr_ulsch.c index 5d02718db9..1e763ece15 100644 --- a/openair1/PHY/NR_TRANSPORT/nr_ulsch.c +++ b/openair1/PHY/NR_TRANSPORT/nr_ulsch.c @@ -36,36 +36,30 @@ NR_gNB_ULSCH_t *find_nr_ulsch(PHY_VARS_gNB *gNB, uint16_t rnti, int pid) { - int16_t first_free_index = -1; - AssertFatal(gNB != NULL,"gNB is null\n"); - NR_gNB_ULSCH_t *ulsch = NULL; + AssertFatal(gNB != NULL, "gNB is null\n"); + NR_gNB_ULSCH_t *ulsch = NULL; for (int i = 0; i < gNB->max_nb_pusch; i++) { - ulsch = gNB->ulsch[i]; + ulsch = &gNB->ulsch[i]; AssertFatal(ulsch != NULL, "gNB->ulsch[%d] is null\n", i); - if(!ulsch->active) { + if (!ulsch->active) { if (first_free_index == -1) first_free_index = i; - } - else { + } else { // if there is already an active ULSCH for this RNTI and HARQ_PID if ((ulsch->harq_pid == pid) && (ulsch->rnti == rnti)) return ulsch; } } if (first_free_index != -1) - ulsch = gNB->ulsch[first_free_index]; + ulsch = &gNB->ulsch[first_free_index]; return ulsch; } -void nr_fill_ulsch(PHY_VARS_gNB *gNB, - int frame, - int slot, - nfapi_nr_pusch_pdu_t *ulsch_pdu) +void nr_fill_ulsch(PHY_VARS_gNB *gNB, int frame, int slot, nfapi_nr_pusch_pdu_t *ulsch_pdu) { - int harq_pid = ulsch_pdu->pusch_data.harq_process_id; NR_gNB_ULSCH_t *ulsch = find_nr_ulsch(gNB, ulsch_pdu->rnti, harq_pid); AssertFatal(ulsch, "No ulsch_id found for rnti %04x\n", ulsch_pdu->rnti); @@ -79,8 +73,13 @@ void nr_fill_ulsch(PHY_VARS_gNB *gNB, NR_UL_gNB_HARQ_t *harq = ulsch->harq_process; harq->new_rx = ulsch_pdu->pusch_data.new_data_indicator; - LOG_D(PHY,"%d.%d RNTI %x HARQ PID %d new data indicator %d\n", - frame, slot, ulsch_pdu->rnti, harq_pid, ulsch_pdu->pusch_data.new_data_indicator); + LOG_D(PHY, + "%d.%d RNTI %x HARQ PID %d new data indicator %d\n", + frame, + slot, + ulsch_pdu->rnti, + harq_pid, + ulsch_pdu->pusch_data.new_data_indicator); if (harq->new_rx) harq->round = 0; else @@ -88,16 +87,16 @@ void nr_fill_ulsch(PHY_VARS_gNB *gNB, memcpy(&ulsch->harq_process->ulsch_pdu, ulsch_pdu, sizeof(ulsch->harq_process->ulsch_pdu)); - LOG_D(PHY,"Initializing nFAPI for ULSCH, harq_pid %d\n", harq_pid); + LOG_D(PHY, "Initializing nFAPI for ULSCH, harq_pid %d\n", harq_pid); } void reset_active_ulsch(PHY_VARS_gNB *gNB, int frame) { - // disactivate ULSCH structure after a given number of frames + // deactivate ULSCH structure after a given number of frames // no activity on this structure for NUMBER_FRAMES_PHY_UE_INACTIVE // assuming UE disconnected or some other error occurred for (int i = 0; i < gNB->max_nb_pusch; i++) { - NR_gNB_ULSCH_t *ulsch = gNB->ulsch[i]; + NR_gNB_ULSCH_t *ulsch = &gNB->ulsch[i]; if (ulsch->active && (((frame - ulsch->frame + 1024) % 1024) > NUMBER_FRAMES_PHY_UE_INACTIVE)) ulsch->active = false; } @@ -108,11 +107,7 @@ void nr_ulsch_unscrambling(int16_t* llr, uint32_t size, uint32_t Nid, uint32_t n nr_codeword_unscrambling(llr, size, 0, Nid, n_RNTI); } -void nr_ulsch_layer_demapping(int16_t *llr_cw, - uint8_t Nl, - uint8_t mod_order, - uint32_t length, - int16_t **llr_layers) +void nr_ulsch_layer_demapping(int16_t *llr_cw, uint8_t Nl, uint8_t mod_order, uint32_t length, int16_t **llr_layers) { switch (Nl) { @@ -137,31 +132,43 @@ void nr_ulsch_layer_demapping(int16_t *llr_cw, void dump_pusch_stats(FILE *fd, PHY_VARS_gNB *gNB) { - - for (int i = 0;i < MAX_MOBILES_PER_GNB; i++) { + for (int i = 0; i < MAX_MOBILES_PER_GNB; i++) { NR_gNB_PHY_STATS_t *stats = &gNB->phy_stats[i]; if (stats->active && stats->frame != stats->ulsch_stats.dump_frame) { stats->ulsch_stats.dump_frame = stats->frame; for (int aa = 0; aa < gNB->frame_parms.nb_antennas_rx; aa++) - if (aa==0) - fprintf(fd,"ULSCH RNTI %4x, %d: ulsch_power[%d] %d,%d ulsch_noise_power[%d] %d.%d, sync_pos %d\n", - stats->rnti, stats->frame, - aa, stats->ulsch_stats.power[aa]/10, stats->ulsch_stats.power[aa]%10, - aa, stats->ulsch_stats.noise_power[aa]/10, stats->ulsch_stats.noise_power[aa]%10, + if (aa == 0) + fprintf(fd, + "ULSCH RNTI %4x, %d: ulsch_power[%d] %d,%d ulsch_noise_power[%d] %d.%d, sync_pos %d\n", + stats->rnti, + stats->frame, + aa, + stats->ulsch_stats.power[aa] / 10, + stats->ulsch_stats.power[aa] % 10, + aa, + stats->ulsch_stats.noise_power[aa] / 10, + stats->ulsch_stats.noise_power[aa] % 10, stats->ulsch_stats.sync_pos); else - fprintf(fd," ulsch_power[%d] %d.%d, ulsch_noise_power[%d] %d.%d\n", - aa, stats->ulsch_stats.power[aa]/10, stats->ulsch_stats.power[aa]%10, - aa, stats->ulsch_stats.noise_power[aa]/10, stats->ulsch_stats.noise_power[aa]%10); + fprintf(fd, + " ulsch_power[%d] %d.%d, ulsch_noise_power[%d] %d.%d\n", + aa, + stats->ulsch_stats.power[aa] / 10, + stats->ulsch_stats.power[aa] % 10, + aa, + stats->ulsch_stats.noise_power[aa] / 10, + stats->ulsch_stats.noise_power[aa] % 10); int *rt = stats->ulsch_stats.round_trials; - fprintf(fd," round_trials %d(%1.1e):%d(%1.1e):%d(%1.1e):%d, DTX %d, current_Qm %d, current_RI %d, total_bytes RX/SCHED %d/%d\n", + fprintf(fd, + " round_trials %d(%1.1e):%d(%1.1e):%d(%1.1e):%d, DTX %d, current_Qm %d, current_RI %d, total_bytes " + "RX/SCHED %d/%d\n", rt[0], - (double)rt[1]/rt[0], + (double)rt[1] / rt[0], rt[1], - (double)rt[2]/rt[0], + (double)rt[2] / rt[0], rt[2], - (double)rt[3]/rt[0], + (double)rt[3] / rt[0], rt[3], stats->ulsch_stats.DTX, stats->ulsch_stats.current_Qm, @@ -171,5 +178,3 @@ void dump_pusch_stats(FILE *fd, PHY_VARS_gNB *gNB) } } } - - diff --git a/openair1/PHY/NR_TRANSPORT/nr_ulsch.h b/openair1/PHY/NR_TRANSPORT/nr_ulsch.h index 2393830881..f2028e6591 100644 --- a/openair1/PHY/NR_TRANSPORT/nr_ulsch.h +++ b/openair1/PHY/NR_TRANSPORT/nr_ulsch.h @@ -33,10 +33,9 @@ #include "PHY/defs_gNB.h" #include "common/utils/threadPool/thread-pool.h" -void free_gNB_ulsch(NR_gNB_ULSCH_t **ulsch, uint16_t N_RB_UL); - -NR_gNB_ULSCH_t *new_gNB_ulsch(uint8_t max_ldpc_iterations, uint16_t N_RB_UL); +void free_gNB_ulsch(NR_gNB_ULSCH_t *ulsch, uint16_t N_RB_UL); +NR_gNB_ULSCH_t new_gNB_ulsch(uint8_t max_ldpc_iterations, uint16_t N_RB_UL); /*! \brief Perform PUSCH decoding. TS 38.212 V15.4.0 subclause 6.2 @param phy_vars_gNB, Pointer to PHY data structure for gNB diff --git a/openair1/PHY/NR_TRANSPORT/nr_ulsch_decoding.c b/openair1/PHY/NR_TRANSPORT/nr_ulsch_decoding.c index 6d7f197357..99db753058 100644 --- a/openair1/PHY/NR_TRANSPORT/nr_ulsch_decoding.c +++ b/openair1/PHY/NR_TRANSPORT/nr_ulsch_decoding.c @@ -55,11 +55,10 @@ //extern double cpuf; -void free_gNB_ulsch(NR_gNB_ULSCH_t **ulschptr, uint16_t N_RB_UL) +void free_gNB_ulsch(NR_gNB_ULSCH_t *ulsch, uint16_t N_RB_UL) { uint16_t a_segments = MAX_NUM_NR_ULSCH_SEGMENTS_PER_LAYER*NR_MAX_NB_LAYERS; //number of segments to be allocated - NR_gNB_ULSCH_t *ulsch = *ulschptr; if (N_RB_UL != 273) { a_segments = a_segments*N_RB_UL; @@ -80,11 +79,10 @@ void free_gNB_ulsch(NR_gNB_ULSCH_t **ulschptr, uint16_t N_RB_UL) free_and_zero(ulsch->harq_process); ulsch->harq_process = NULL; } - free_and_zero(*ulschptr); + free_and_zero(ulsch); } - -NR_gNB_ULSCH_t *new_gNB_ulsch(uint8_t max_ldpc_iterations, uint16_t N_RB_UL) +NR_gNB_ULSCH_t new_gNB_ulsch(uint8_t max_ldpc_iterations, uint16_t N_RB_UL) { uint16_t a_segments = MAX_NUM_NR_ULSCH_SEGMENTS_PER_LAYER*NR_MAX_NB_LAYERS; //number of segments to be allocated @@ -94,15 +92,15 @@ NR_gNB_ULSCH_t *new_gNB_ulsch(uint8_t max_ldpc_iterations, uint16_t N_RB_UL) a_segments = a_segments/273 +1; } - uint32_t ulsch_bytes = a_segments * 1056; // allocated bytes per segment - NR_gNB_ULSCH_t *ulsch = malloc16_clear(sizeof(*ulsch)); + uint32_t ulsch_bytes = a_segments * 1056; // allocated bytes per segment + NR_gNB_ULSCH_t ulsch; - ulsch->max_ldpc_iterations = max_ldpc_iterations; - ulsch->harq_pid = -1; - ulsch->active = false; + ulsch.max_ldpc_iterations = max_ldpc_iterations; + ulsch.harq_pid = -1; + ulsch.active = false; - NR_UL_gNB_HARQ_t *harq = malloc16_clear(sizeof(harq)); - ulsch->harq_process = harq; + NR_UL_gNB_HARQ_t *harq = malloc16_clear(sizeof(*harq)); + ulsch.harq_process = harq; harq->b = malloc16_clear(ulsch_bytes * sizeof(*harq->b)); harq->c = malloc16_clear(a_segments * sizeof(*harq->c)); harq->d = malloc16_clear(a_segments * sizeof(*harq->d)); @@ -117,144 +115,132 @@ NR_gNB_ULSCH_t *new_gNB_ulsch(uint8_t max_ldpc_iterations, uint16_t N_RB_UL) static uint32_t prnt_crc_cnt = 0; #endif -void nr_processULSegment(void* arg) -{ - - ldpcDecode_t *rdata = (ldpcDecode_t*) arg; - PHY_VARS_gNB *phy_vars_gNB = rdata->gNB; - NR_UL_gNB_HARQ_t *ulsch_harq = rdata->ulsch_harq; - t_nrLDPC_dec_params *p_decoderParms = &rdata->decoderParms; - int length_dec; - int no_iteration_ldpc; - int Kr; - int Kr_bytes; - int K_bits_F; - uint8_t crc_type; - int i; - int j; - int r = rdata->segment_r; - int A = rdata->A; - int E = rdata->E; - int Qm = rdata->Qm; - int rv_index = rdata->rv_index; - int r_offset = rdata->r_offset; - uint8_t kc = rdata->Kc; - short* ulsch_llr = rdata->ulsch_llr; - int max_ldpc_iterations = p_decoderParms->numMaxIter; - int8_t llrProcBuf[OAI_UL_LDPC_MAX_NUM_LLR] __attribute__((aligned(32))); - - int16_t z [68*384 + 16] __attribute__ ((aligned(16))); - int8_t l [68*384 + 16] __attribute__ ((aligned(16))); - - __m128i *pv = (__m128i*)&z; - __m128i *pl = (__m128i*)&l; - - Kr = ulsch_harq->K; - Kr_bytes = Kr>>3; - K_bits_F = Kr-ulsch_harq->F; - - t_nrLDPC_time_stats procTime = {0}; - t_nrLDPC_time_stats* p_procTime = &procTime ; - - //start_meas(&phy_vars_gNB->ulsch_deinterleaving_stats); - - //////////////////////////////////////////////////////////////////////////////////////////// - ///////////////////////////////// nr_deinterleaving_ldpc /////////////////////////////////// - ////////////////////////////////////////////////////////////////////////////////////////// - - //////////////////////////// ulsch_llr =====> ulsch_harq->e ////////////////////////////// - - /// code blocks after bit selection in rate matching for LDPC code (38.212 V15.4.0 section 5.4.2.1) - int16_t harq_e[E]; - - nr_deinterleaving_ldpc(E, - Qm, - harq_e, - ulsch_llr+r_offset); - - //for (int i =0; i<16; i++) - // printf("rx output deinterleaving w[%d]= %d r_offset %d\n", i,ulsch_harq->w[r][i], r_offset); - - stop_meas(&phy_vars_gNB->ulsch_deinterleaving_stats); - - - ////////////////////////////////////////////////////////////////////////////////////////// - - - ////////////////////////////////////////////////////////////////////////////////////////// - //////////////////////////////// nr_rate_matching_ldpc_rx //////////////////////////////// - ////////////////////////////////////////////////////////////////////////////////////////// - - ///////////////////////// ulsch_harq->e =====> ulsch_harq->d ///////////////////////// - - //start_meas(&phy_vars_gNB->ulsch_rate_unmatching_stats); - - if (nr_rate_matching_ldpc_rx(rdata->tbslbrm, - p_decoderParms->BG, - p_decoderParms->Z, - ulsch_harq->d[r], - harq_e, - ulsch_harq->C, - rv_index, - ulsch_harq->new_rx, - E, - ulsch_harq->F, - Kr-ulsch_harq->F-2*(p_decoderParms->Z))==-1) { - - stop_meas(&phy_vars_gNB->ulsch_rate_unmatching_stats); - - LOG_E(PHY,"ulsch_decoding.c: Problem in rate_matching\n"); - rdata->decodeIterations = max_ldpc_iterations + 1; - return; - } else { - stop_meas(&phy_vars_gNB->ulsch_rate_unmatching_stats); - } - - memset(ulsch_harq->c[r],0,Kr_bytes); - - if (ulsch_harq->C == 1) { - if (A > 3824) - crc_type = CRC24_A; - else - crc_type = CRC16; - - length_dec = ulsch_harq->B; - } - else { - crc_type = CRC24_B; - length_dec = (ulsch_harq->B+24*ulsch_harq->C)/ulsch_harq->C; - } - - //start_meas(&phy_vars_gNB->ulsch_ldpc_decoding_stats); - - //set first 2*Z_c bits to zeros - memset(&z[0],0,2*ulsch_harq->Z*sizeof(int16_t)); - //set Filler bits - memset((&z[0]+K_bits_F),127,ulsch_harq->F*sizeof(int16_t)); - //Move coded bits before filler bits - memcpy((&z[0]+2*ulsch_harq->Z),ulsch_harq->d[r],(K_bits_F-2*ulsch_harq->Z)*sizeof(int16_t)); - //skip filler bits - memcpy((&z[0]+Kr),ulsch_harq->d[r]+(Kr-2*ulsch_harq->Z),(kc*ulsch_harq->Z-Kr)*sizeof(int16_t)); - //Saturate coded bits before decoding into 8 bits values - for (i=0, j=0; j < ((kc*ulsch_harq->Z)>>4)+1; i+=2, j++) + void nr_processULSegment(void *arg) { - pl[j] = _mm_packs_epi16(pv[i],pv[i+1]); - } - ////////////////////////////////////////////////////////////////////////////////////////// + ldpcDecode_t *rdata = (ldpcDecode_t *)arg; + PHY_VARS_gNB *phy_vars_gNB = rdata->gNB; + NR_UL_gNB_HARQ_t *ulsch_harq = rdata->ulsch_harq; + t_nrLDPC_dec_params *p_decoderParms = &rdata->decoderParms; + int length_dec; + int no_iteration_ldpc; + int Kr; + int Kr_bytes; + int K_bits_F; + uint8_t crc_type; + int i; + int j; + int r = rdata->segment_r; + int A = rdata->A; + int E = rdata->E; + int Qm = rdata->Qm; + int rv_index = rdata->rv_index; + int r_offset = rdata->r_offset; + uint8_t kc = rdata->Kc; + short *ulsch_llr = rdata->ulsch_llr; + int max_ldpc_iterations = p_decoderParms->numMaxIter; + int8_t llrProcBuf[OAI_UL_LDPC_MAX_NUM_LLR] __attribute__((aligned(32))); + int16_t z[68 * 384 + 16] __attribute__((aligned(16))); + int8_t l[68 * 384 + 16] __attribute__((aligned(16))); - ////////////////////////////////////////////////////////////////////////////////////////// - ///////////////////////////////////// nrLDPC_decoder ///////////////////////////////////// - ////////////////////////////////////////////////////////////////////////////////////////// + __m128i *pv = (__m128i *)&z; + __m128i *pl = (__m128i *)&l; - ////////////////////////////////// pl =====> llrProcBuf ////////////////////////////////// - p_decoderParms->block_length = length_dec; - no_iteration_ldpc = nrLDPC_decoder(p_decoderParms, - (int8_t*)&pl[0], - llrProcBuf, - p_procTime); + Kr = ulsch_harq->K; + Kr_bytes = Kr >> 3; + K_bits_F = Kr - ulsch_harq->F; - if (check_crc((uint8_t*)llrProcBuf,length_dec,ulsch_harq->F,crc_type)) { + t_nrLDPC_time_stats procTime = {0}; + t_nrLDPC_time_stats *p_procTime = &procTime; + + // start_meas(&phy_vars_gNB->ulsch_deinterleaving_stats); + + //////////////////////////////////////////////////////////////////////////////////////////// + ///////////////////////////////// nr_deinterleaving_ldpc /////////////////////////////////// + ////////////////////////////////////////////////////////////////////////////////////////// + + //////////////////////////// ulsch_llr =====> ulsch_harq->e ////////////////////////////// + + /// code blocks after bit selection in rate matching for LDPC code (38.212 V15.4.0 section 5.4.2.1) + int16_t harq_e[E]; + + nr_deinterleaving_ldpc(E, Qm, harq_e, ulsch_llr + r_offset); + + // for (int i =0; i<16; i++) + // printf("rx output deinterleaving w[%d]= %d r_offset %d\n", i,ulsch_harq->w[r][i], r_offset); + + stop_meas(&phy_vars_gNB->ulsch_deinterleaving_stats); + + ////////////////////////////////////////////////////////////////////////////////////////// + + ////////////////////////////////////////////////////////////////////////////////////////// + //////////////////////////////// nr_rate_matching_ldpc_rx //////////////////////////////// + ////////////////////////////////////////////////////////////////////////////////////////// + + ///////////////////////// ulsch_harq->e =====> ulsch_harq->d ///////////////////////// + + // start_meas(&phy_vars_gNB->ulsch_rate_unmatching_stats); + + if (nr_rate_matching_ldpc_rx(rdata->tbslbrm, + p_decoderParms->BG, + p_decoderParms->Z, + ulsch_harq->d[r], + harq_e, + ulsch_harq->C, + rv_index, + ulsch_harq->new_rx, + E, + ulsch_harq->F, + Kr - ulsch_harq->F - 2 * (p_decoderParms->Z)) + == -1) { + stop_meas(&phy_vars_gNB->ulsch_rate_unmatching_stats); + + LOG_E(PHY, "ulsch_decoding.c: Problem in rate_matching\n"); + rdata->decodeIterations = max_ldpc_iterations + 1; + return; + } else { + stop_meas(&phy_vars_gNB->ulsch_rate_unmatching_stats); + } + + memset(ulsch_harq->c[r], 0, Kr_bytes); + + if (ulsch_harq->C == 1) { + if (A > 3824) + crc_type = CRC24_A; + else + crc_type = CRC16; + + length_dec = ulsch_harq->B; + } else { + crc_type = CRC24_B; + length_dec = (ulsch_harq->B + 24 * ulsch_harq->C) / ulsch_harq->C; + } + + // start_meas(&phy_vars_gNB->ulsch_ldpc_decoding_stats); + + // set first 2*Z_c bits to zeros + memset(&z[0], 0, 2 * ulsch_harq->Z * sizeof(int16_t)); + // set Filler bits + memset((&z[0] + K_bits_F), 127, ulsch_harq->F * sizeof(int16_t)); + // Move coded bits before filler bits + memcpy((&z[0] + 2 * ulsch_harq->Z), ulsch_harq->d[r], (K_bits_F - 2 * ulsch_harq->Z) * sizeof(int16_t)); + // skip filler bits + memcpy((&z[0] + Kr), ulsch_harq->d[r] + (Kr - 2 * ulsch_harq->Z), (kc * ulsch_harq->Z - Kr) * sizeof(int16_t)); + // Saturate coded bits before decoding into 8 bits values + for (i = 0, j = 0; j < ((kc * ulsch_harq->Z) >> 4) + 1; i += 2, j++) { + pl[j] = _mm_packs_epi16(pv[i], pv[i + 1]); + } + ////////////////////////////////////////////////////////////////////////////////////////// + + ////////////////////////////////////////////////////////////////////////////////////////// + ///////////////////////////////////// nrLDPC_decoder ///////////////////////////////////// + ////////////////////////////////////////////////////////////////////////////////////////// + + ////////////////////////////////// pl =====> llrProcBuf ////////////////////////////////// + p_decoderParms->block_length = length_dec; + no_iteration_ldpc = nrLDPC_decoder(p_decoderParms, (int8_t *)&pl[0], llrProcBuf, p_procTime); + + if (check_crc((uint8_t *)llrProcBuf, length_dec, ulsch_harq->F, crc_type)) { #ifdef PRINT_CRC_CHECK LOG_I(PHY,"Segment %d CRC OK, iterations %d/%d\n",r,no_iteration_ldpc,max_ldpc_iterations); #endif @@ -272,7 +258,7 @@ void nr_processULSegment(void* arg) } //stop_meas(&phy_vars_gNB->ulsch_ldpc_decoding_stats); -} + } uint32_t nr_ulsch_decoding(PHY_VARS_gNB *phy_vars_gNB, uint8_t ULSCH_id, @@ -284,13 +270,12 @@ uint32_t nr_ulsch_decoding(PHY_VARS_gNB *phy_vars_gNB, uint8_t harq_pid, uint32_t G) { - #ifdef PRINT_CRC_CHECK prnt_crc_cnt++; #endif - NR_gNB_ULSCH_t *ulsch = phy_vars_gNB->ulsch[ULSCH_id]; - NR_gNB_PUSCH *pusch = phy_vars_gNB->pusch_vars[ULSCH_id]; + NR_gNB_ULSCH_t *ulsch = &phy_vars_gNB->ulsch[ULSCH_id]; + NR_gNB_PUSCH *pusch = &phy_vars_gNB->pusch_vars[ULSCH_id]; NR_UL_gNB_HARQ_t *harq_process = ulsch->harq_process; if (!harq_process) { diff --git a/openair1/PHY/NR_TRANSPORT/nr_ulsch_demodulation.c b/openair1/PHY/NR_TRANSPORT/nr_ulsch_demodulation.c index 370ec4a9c9..f0a52b9c8e 100644 --- a/openair1/PHY/NR_TRANSPORT/nr_ulsch_demodulation.c +++ b/openair1/PHY/NR_TRANSPORT/nr_ulsch_demodulation.c @@ -1847,11 +1847,13 @@ void nr_rx_pusch(PHY_VARS_gNB *gNB, int avgs = 0; NR_DL_FRAME_PARMS *frame_parms = &gNB->frame_parms; - nfapi_nr_pusch_pdu_t *rel15_ul = &gNB->ulsch[ulsch_id]->harq_process->ulsch_pdu; + NR_gNB_ULSCH_t *ulsch = &gNB->ulsch[ulsch_id]; + nfapi_nr_pusch_pdu_t *rel15_ul = &ulsch->harq_process->ulsch_pdu; int avg[frame_parms->nb_antennas_rx*rel15_ul->nrOfLayers]; - gNB->pusch_vars[ulsch_id]->dmrs_symbol = INVALID_VALUE; - gNB->pusch_vars[ulsch_id]->cl_done = 0; + NR_gNB_PUSCH *pusch_vars = &gNB->pusch_vars[ulsch_id]; + pusch_vars->dmrs_symbol = INVALID_VALUE; + pusch_vars->cl_done = 0; bwp_start_subcarrier = ((rel15_ul->rb_start + rel15_ul->bwp_start)*NR_NB_SC_PER_RB + frame_parms->first_carrier_offset) % frame_parms->ofdm_symbol_size; LOG_D(PHY,"pusch %d.%d : bwp_start_subcarrier %d, rb_start %d, first_carrier_offset %d\n", frame,slot,bwp_start_subcarrier, rel15_ul->rb_start, frame_parms->first_carrier_offset); @@ -1870,8 +1872,8 @@ void nr_rx_pusch(PHY_VARS_gNB *gNB, LOG_D(PHY, "symbol %d, dmrs_symbol_flag :%d\n", symbol, dmrs_symbol_flag); if (dmrs_symbol_flag == 1) { - if (gNB->pusch_vars[ulsch_id]->dmrs_symbol == INVALID_VALUE) - gNB->pusch_vars[ulsch_id]->dmrs_symbol = symbol; + if (pusch_vars->dmrs_symbol == INVALID_VALUE) + pusch_vars->dmrs_symbol = symbol; for (int nl=0; nlnrOfLayers; nl++) { @@ -1885,40 +1887,44 @@ void nr_rx_pusch(PHY_VARS_gNB *gNB, &max_ch); } - nr_gnb_measurements(gNB, gNB->ulsch[ulsch_id], gNB->pusch_vars[ulsch_id], symbol, rel15_ul->nrOfLayers); + nr_gnb_measurements(gNB, ulsch, pusch_vars, symbol, rel15_ul->nrOfLayers); for (aarx = 0; aarx < frame_parms->nb_antennas_rx; aarx++) { if (symbol == rel15_ul->start_symbol_index) { - gNB->pusch_vars[ulsch_id]->ulsch_power[aarx] = 0; - gNB->pusch_vars[ulsch_id]->ulsch_noise_power[aarx] = 0; + pusch_vars->ulsch_power[aarx] = 0; + pusch_vars->ulsch_noise_power[aarx] = 0; } for (aatx = 0; aatx < rel15_ul->nrOfLayers; aatx++) { - gNB->pusch_vars[ulsch_id]->ulsch_power[aarx] += signal_energy_nodc( - &gNB->pusch_vars[ulsch_id]->ul_ch_estimates[aatx*gNB->frame_parms.nb_antennas_rx+aarx][symbol * frame_parms->ofdm_symbol_size], - rel15_ul->rb_size * 12); + pusch_vars->ulsch_power[aarx] += signal_energy_nodc( + &pusch_vars->ul_ch_estimates[aatx * gNB->frame_parms.nb_antennas_rx + aarx][symbol * frame_parms->ofdm_symbol_size], + rel15_ul->rb_size * 12); } for (int rb = 0; rb < rel15_ul->rb_size; rb++) { - gNB->pusch_vars[ulsch_id]->ulsch_noise_power[aarx] += - gNB->measurements.n0_subband_power[aarx][rel15_ul->bwp_start + rel15_ul->rb_start + rb] / - rel15_ul->rb_size; + pusch_vars->ulsch_noise_power[aarx] += + gNB->measurements.n0_subband_power[aarx][rel15_ul->bwp_start + rel15_ul->rb_start + rb] / rel15_ul->rb_size; } - LOG_D(PHY,"aa %d, bwp_start%d, rb_start %d, rb_size %d: ulsch_power %d, ulsch_noise_power %d\n",aarx, - rel15_ul->bwp_start,rel15_ul->rb_start,rel15_ul->rb_size, - gNB->pusch_vars[ulsch_id]->ulsch_power[aarx], - gNB->pusch_vars[ulsch_id]->ulsch_noise_power[aarx]); + LOG_D(PHY, + "aa %d, bwp_start%d, rb_start %d, rb_size %d: ulsch_power %d, ulsch_noise_power %d\n", + aarx, + rel15_ul->bwp_start, + rel15_ul->rb_start, + rel15_ul->rb_size, + pusch_vars->ulsch_power[aarx], + pusch_vars->ulsch_noise_power[aarx]); } } } if (gNB->chest_time == 1) { // averaging time domain channel estimates nr_chest_time_domain_avg(frame_parms, - gNB->pusch_vars[ulsch_id]->ul_ch_estimates, + pusch_vars->ul_ch_estimates, rel15_ul->nr_of_symbols, rel15_ul->start_symbol_index, rel15_ul->ul_dmrs_symb_pos, rel15_ul->rb_size); - gNB->pusch_vars[ulsch_id]->dmrs_symbol = get_next_dmrs_symbol_in_slot(rel15_ul->ul_dmrs_symb_pos, rel15_ul->start_symbol_index, rel15_ul->nr_of_symbols); + pusch_vars->dmrs_symbol = + get_next_dmrs_symbol_in_slot(rel15_ul->ul_dmrs_symb_pos, rel15_ul->start_symbol_index, rel15_ul->nr_of_symbols); } stop_meas(&gNB->ulsch_channel_estimation_stats); @@ -1940,7 +1946,7 @@ void nr_rx_pusch(PHY_VARS_gNB *gNB, AssertFatal(1==0,"Double DMRS configuration is not yet supported\n"); if (gNB->chest_time == 0) // Non averaging time domain channel estimates - gNB->pusch_vars[ulsch_id]->dmrs_symbol = symbol; + pusch_vars->dmrs_symbol = symbol; if (rel15_ul->dmrs_config_type == 0) { // if no data in dmrs cdm group is 1 only even REs have no data @@ -1955,29 +1961,23 @@ void nr_rx_pusch(PHY_VARS_gNB *gNB, nb_re_pusch = rel15_ul->rb_size * NR_NB_SC_PER_RB; } - gNB->pusch_vars[ulsch_id]->ul_valid_re_per_slot[symbol] = nb_re_pusch; - LOG_D(PHY,"symbol %d: nb_re_pusch %d, DMRS symbl used for Chest :%d \n", symbol, nb_re_pusch, gNB->pusch_vars[ulsch_id]->dmrs_symbol); + pusch_vars->ul_valid_re_per_slot[symbol] = nb_re_pusch; + LOG_D(PHY, "symbol %d: nb_re_pusch %d, DMRS symbl used for Chest :%d \n", symbol, nb_re_pusch, pusch_vars->dmrs_symbol); //---------------------------------------------------------- //--------------------- RBs extraction --------------------- //---------------------------------------------------------- if (nb_re_pusch > 0) { start_meas(&gNB->ulsch_rbs_extraction_stats); - nr_ulsch_extract_rbs(gNB->common_vars.rxdataF, - gNB->pusch_vars[ulsch_id], - slot, - symbol, - dmrs_symbol_flag, - rel15_ul, - frame_parms); + nr_ulsch_extract_rbs(gNB->common_vars.rxdataF, pusch_vars, slot, symbol, dmrs_symbol_flag, rel15_ul, frame_parms); stop_meas(&gNB->ulsch_rbs_extraction_stats); //---------------------------------------------------------- //--------------------- Channel Scaling -------------------- //---------------------------------------------------------- - nr_ulsch_scale_channel(gNB->pusch_vars[ulsch_id]->ul_ch_estimates_ext, + nr_ulsch_scale_channel(pusch_vars->ul_ch_estimates_ext, frame_parms, - gNB->ulsch[ulsch_id], + ulsch, symbol, dmrs_symbol_flag, nb_re_pusch, @@ -1985,14 +1985,14 @@ void nr_rx_pusch(PHY_VARS_gNB *gNB, rel15_ul->rb_size, shift_ch_ext); - if (gNB->pusch_vars[ulsch_id]->cl_done==0) { - nr_ulsch_channel_level(gNB->pusch_vars[ulsch_id]->ul_ch_estimates_ext, - frame_parms, - avg, - symbol, - nb_re_pusch, - rel15_ul->nrOfLayers, - rel15_ul->rb_size); + if (pusch_vars->cl_done == 0) { + nr_ulsch_channel_level(pusch_vars->ul_ch_estimates_ext, + frame_parms, + avg, + symbol, + nb_re_pusch, + rel15_ul->nrOfLayers, + rel15_ul->rb_size); avgs = 0; @@ -2000,24 +2000,24 @@ void nr_rx_pusch(PHY_VARS_gNB *gNB, for (aarx=0;aarxnb_antennas_rx;aarx++) avgs = cmax(avgs,avg[aatx*frame_parms->nb_antennas_rx+aarx]); - gNB->pusch_vars[ulsch_id]->log2_maxh = (log2_approx(avgs) >> 1) + ad_shift; - if (gNB->pusch_vars[ulsch_id]->log2_maxh < 0) { - gNB->pusch_vars[ulsch_id]->log2_maxh = 0; + pusch_vars->log2_maxh = (log2_approx(avgs) >> 1) + ad_shift; + if (pusch_vars->log2_maxh < 0) { + pusch_vars->log2_maxh = 0; } - gNB->pusch_vars[ulsch_id]->cl_done = 1; + pusch_vars->cl_done = 1; } //---------------------------------------------------------- //--------------------- Channel Compensation --------------- //---------------------------------------------------------- start_meas(&gNB->ulsch_channel_compensation_stats); - LOG_D(PHY,"Doing channel compensations log2_maxh %d, avgs %d (%d,%d)\n",gNB->pusch_vars[ulsch_id]->log2_maxh,avgs,avg[0],avg[1]); - nr_ulsch_channel_compensation(gNB->pusch_vars[ulsch_id]->rxdataF_ext, - gNB->pusch_vars[ulsch_id]->ul_ch_estimates_ext, - gNB->pusch_vars[ulsch_id]->ul_ch_mag0, - gNB->pusch_vars[ulsch_id]->ul_ch_magb0, - gNB->pusch_vars[ulsch_id]->rxdataF_comp, - (rel15_ul->nrOfLayers>1) ? gNB->pusch_vars[ulsch_id]->rho : NULL, + LOG_D(PHY, "Doing channel compensations log2_maxh %d, avgs %d (%d,%d)\n", pusch_vars->log2_maxh, avgs, avg[0], avg[1]); + nr_ulsch_channel_compensation(pusch_vars->rxdataF_ext, + pusch_vars->ul_ch_estimates_ext, + pusch_vars->ul_ch_mag0, + pusch_vars->ul_ch_magb0, + pusch_vars->rxdataF_comp, + (rel15_ul->nrOfLayers > 1) ? pusch_vars->rho : NULL, frame_parms, symbol, nb_re_pusch, @@ -2025,15 +2025,15 @@ void nr_rx_pusch(PHY_VARS_gNB *gNB, rel15_ul->qam_mod_order, rel15_ul->nrOfLayers, rel15_ul->rb_size, - gNB->pusch_vars[ulsch_id]->log2_maxh); + pusch_vars->log2_maxh); stop_meas(&gNB->ulsch_channel_compensation_stats); start_meas(&gNB->ulsch_mrc_stats); nr_ulsch_detection_mrc(frame_parms, - gNB->pusch_vars[ulsch_id]->rxdataF_comp, - gNB->pusch_vars[ulsch_id]->ul_ch_mag0, - gNB->pusch_vars[ulsch_id]->ul_ch_magb0, - (rel15_ul->nrOfLayers>1) ? gNB->pusch_vars[ulsch_id]->rho : NULL, + pusch_vars->rxdataF_comp, + pusch_vars->ul_ch_mag0, + pusch_vars->ul_ch_magb0, + (rel15_ul->nrOfLayers > 1) ? pusch_vars->rho : NULL, rel15_ul->nrOfLayers, symbol, rel15_ul->rb_size, @@ -2042,14 +2042,14 @@ void nr_rx_pusch(PHY_VARS_gNB *gNB, //Apply zero forcing for 2 Tx layers if (rel15_ul->nrOfLayers == 2) { nr_ulsch_zero_forcing_rx_2layers(frame_parms, - gNB->pusch_vars[ulsch_id]->rxdataF_comp, - gNB->pusch_vars[ulsch_id]->ul_ch_mag0, - gNB->pusch_vars[ulsch_id]->ul_ch_magb0, - gNB->pusch_vars[ulsch_id]->ul_ch_estimates_ext, + pusch_vars->rxdataF_comp, + pusch_vars->ul_ch_mag0, + pusch_vars->ul_ch_magb0, + pusch_vars->ul_ch_estimates_ext, rel15_ul->rb_size, frame_parms->nb_antennas_rx, rel15_ul->qam_mod_order, - gNB->pusch_vars[ulsch_id]->log2_maxh, + pusch_vars->log2_maxh, symbol, nb_re_pusch); } @@ -2061,7 +2061,7 @@ void nr_rx_pusch(PHY_VARS_gNB *gNB, int nb_re_pusch2 = nb_re_pusch + (nb_re_pusch&7); // perform IDFT operation on the compensated rxdata if transform precoding is enabled - nr_idft(&gNB->pusch_vars[ulsch_id]->rxdataF_comp[0][symbol * nb_re_pusch2], nb_re_pusch); + nr_idft(&pusch_vars->rxdataF_comp[0][symbol * nb_re_pusch2], nb_re_pusch); LOG_D(PHY,"Transform precoding being done on data- symbol: %d, nb_re_pusch: %d\n", symbol, nb_re_pusch); } @@ -2082,7 +2082,7 @@ void nr_rx_pusch(PHY_VARS_gNB *gNB, stop_meas(&gNB->ulsch_ptrs_processing_stats); /* Subtract total PTRS RE's in the symbol from PUSCH RE's */ - gNB->pusch_vars[ulsch_id]->ul_valid_re_per_slot[symbol] -= gNB->pusch_vars[ulsch_id]->ptrs_re_per_slot; + pusch_vars->ul_valid_re_per_slot[symbol] -= pusch_vars->ptrs_re_per_slot; } /*---------------------------------------------------------------------------------------------------- */ @@ -2090,17 +2090,18 @@ void nr_rx_pusch(PHY_VARS_gNB *gNB, /*-----------------------------------------------------------------------------------------------------*/ start_meas(&gNB->ulsch_llr_stats); for (aatx=0; aatx < rel15_ul->nrOfLayers; aatx++) { - nr_ulsch_compute_llr(&gNB->pusch_vars[ulsch_id]->rxdataF_comp[aatx*frame_parms->nb_antennas_rx][symbol * (off + rel15_ul->rb_size * NR_NB_SC_PER_RB)], - gNB->pusch_vars[ulsch_id]->ul_ch_mag0[aatx*frame_parms->nb_antennas_rx], - gNB->pusch_vars[ulsch_id]->ul_ch_magb0[aatx*frame_parms->nb_antennas_rx], - &gNB->pusch_vars[ulsch_id]->llr_layers[aatx][rxdataF_ext_offset * rel15_ul->qam_mod_order], - rel15_ul->rb_size, - gNB->pusch_vars[ulsch_id]->ul_valid_re_per_slot[symbol], - symbol, - rel15_ul->qam_mod_order); + nr_ulsch_compute_llr( + &pusch_vars->rxdataF_comp[aatx * frame_parms->nb_antennas_rx][symbol * (off + rel15_ul->rb_size * NR_NB_SC_PER_RB)], + pusch_vars->ul_ch_mag0[aatx * frame_parms->nb_antennas_rx], + pusch_vars->ul_ch_magb0[aatx * frame_parms->nb_antennas_rx], + &pusch_vars->llr_layers[aatx][rxdataF_ext_offset * rel15_ul->qam_mod_order], + rel15_ul->rb_size, + pusch_vars->ul_valid_re_per_slot[symbol], + symbol, + rel15_ul->qam_mod_order); } stop_meas(&gNB->ulsch_llr_stats); - rxdataF_ext_offset += gNB->pusch_vars[ulsch_id]->ul_valid_re_per_slot[symbol]; + rxdataF_ext_offset += pusch_vars->ul_valid_re_per_slot[symbol]; } } // symbol loop } diff --git a/openair1/PHY/NR_TRANSPORT/pucch_rx.c b/openair1/PHY/NR_TRANSPORT/pucch_rx.c index 814aceb8ed..71db65a14e 100644 --- a/openair1/PHY/NR_TRANSPORT/pucch_rx.c +++ b/openair1/PHY/NR_TRANSPORT/pucch_rx.c @@ -55,18 +55,6 @@ //#define DEBUG_NR_PUCCH_RX 1 -NR_gNB_PUCCH_t *new_gNB_pucch(void) -{ - NR_gNB_PUCCH_t *pucch = malloc16(sizeof(*pucch)); - memset(pucch, 0, sizeof(*pucch)); - return pucch; -} - -void free_gNB_pucch(NR_gNB_PUCCH_t *pucch) -{ - free_and_zero(pucch); -} - void nr_fill_pucch(PHY_VARS_gNB *gNB, int frame, int slot, @@ -74,18 +62,25 @@ void nr_fill_pucch(PHY_VARS_gNB *gNB, { if (NFAPI_MODE == NFAPI_MODE_PNF) - gNB->pucch[0]->active = 0; //check if ture in monolithic mode + gNB->pucch[0].active = 0; // check if ture in monolithic mode bool found = false; for (int i = 0; i < gNB->max_nb_pucch; i++) { - if (gNB->pucch[i]->active == 0) { - NR_gNB_PUCCH_t *pucch = gNB->pucch[i]; + NR_gNB_PUCCH_t *pucch = &gNB->pucch[i]; + if (pucch->active == 0) { pucch->frame = frame; pucch->slot = slot; pucch->active = 1; - memcpy((void*)&pucch->pucch_pdu, (void*)pucch_pdu, sizeof(nfapi_nr_pucch_pdu_t)); - LOG_D(PHY,"Programming PUCCH[%d] for %d.%d, format %d, nb_harq %d, nb_sr %d, nb_csi %d\n", - i, pucch->frame, pucch->slot, pucch->pucch_pdu.format_type, pucch->pucch_pdu.bit_len_harq, pucch->pucch_pdu.sr_flag, pucch->pucch_pdu.bit_len_csi_part1); + memcpy((void *)&pucch->pucch_pdu, (void *)pucch_pdu, sizeof(nfapi_nr_pucch_pdu_t)); + LOG_D(PHY, + "Programming PUCCH[%d] for %d.%d, format %d, nb_harq %d, nb_sr %d, nb_csi %d\n", + i, + pucch->frame, + pucch->slot, + pucch->pucch_pdu.format_type, + pucch->pucch_pdu.bit_len_harq, + pucch->pucch_pdu.sr_flag, + pucch->pucch_pdu.bit_len_csi_part1); found = true; break; } @@ -157,10 +152,9 @@ int16_t idft12_im[12][12] = { void nr_decode_pucch0(PHY_VARS_gNB *gNB, int frame, int slot, - nfapi_nr_uci_pucch_pdu_format_0_1_t* uci_pdu, - nfapi_nr_pucch_pdu_t* pucch_pdu) + nfapi_nr_uci_pucch_pdu_format_0_1_t *uci_pdu, + nfapi_nr_pucch_pdu_t *pucch_pdu) { - c16_t **rxdataF = gNB->common_vars.rxdataF; NR_DL_FRAME_PARMS *frame_parms = &gNB->frame_parms; int soffset = (slot & 3) * frame_parms->symbols_per_slot * frame_parms->ofdm_symbol_size; @@ -170,7 +164,7 @@ void nr_decode_pucch0(PHY_VARS_gNB *gNB, pucch_pdu->bit_len_harq,pucch_pdu->sr_flag); NR_gNB_PHY_STATS_t *phy_stats = get_phy_stats(gNB, pucch_pdu->rnti); - AssertFatal(phy_stats != NULL,"No stat index found\n"); + AssertFatal(phy_stats != NULL, "phy_stats shouldn't be NULL\n"); phy_stats->frame = frame; NR_gNB_UCI_STATS_t *uci_stats = &phy_stats->uci_stats; @@ -227,11 +221,21 @@ void nr_decode_pucch0(PHY_VARS_gNB *gNB, int prb_offset[2] = {pucch_pdu->bwp_start+pucch_pdu->prb_start, pucch_pdu->bwp_start+pucch_pdu->prb_start}; pucch_GroupHopping_t pucch_GroupHopping = pucch_pdu->group_hop_flag + (pucch_pdu->sequence_hop_flag << 1); - nr_group_sequence_hopping(pucch_GroupHopping, pucch_pdu->hopping_id, 0, slot, &u[0], &v[0]); // calculating u and v value first hop + nr_group_sequence_hopping(pucch_GroupHopping, + pucch_pdu->hopping_id, + 0, + slot, + &u[0], + &v[0]); // calculating u and v value first hop LOG_D(PHY,"pucch0: u %d, v %d\n",u[0],v[0]); if (pucch_pdu->freq_hop_flag == 1) { - nr_group_sequence_hopping(pucch_GroupHopping, pucch_pdu->hopping_id, 1, slot, &u[1], &v[1]); // calculating u and v value second hop + nr_group_sequence_hopping(pucch_GroupHopping, + pucch_pdu->hopping_id, + 1, + slot, + &u[1], + &v[1]); // calculating u and v value second hop LOG_D(PHY,"pucch0 second hop: u %d, v %d\n",u[1],v[1]); prb_offset[1] = pucch_pdu->bwp_start + pucch_pdu->second_hop_prb; } @@ -254,7 +258,7 @@ void nr_decode_pucch0(PHY_VARS_gNB *gNB, int32_t rp[frame_parms->nb_antennas_rx][pucch_pdu->nr_of_symbols][nb_re_pucch],*tmp_rp; for (int l=0; lnr_of_symbols; l++) { - uint8_t l2 = l+pucch_pdu->start_symbol_index; + uint8_t l2 = l + pucch_pdu->start_symbol_index; re_offset[l] = (12*prb_offset[l]) + frame_parms->first_carrier_offset; if (re_offset[l]>= frame_parms->ofdm_symbol_size) @@ -420,10 +424,22 @@ void nr_decode_pucch0(PHY_VARS_gNB *gNB, uci_pdu->harq->harq_confidence_level = no_conf; uci_pdu->harq->harq_list = (nfapi_nr_harq_t*)malloc(sizeof *uci_pdu->harq->harq_list); uci_pdu->harq->harq_list[0].harq_value = !(index&0x01); - LOG_D(PHY, "[DLSCH/PDSCH/PUCCH] %d.%d HARQ %s with confidence level %s xrt_mag %d xrt_mag_next %d n0 %d (%d,%d) pucch0_thres %d, cqi %d, SNRtimes10 %d, energy %f\n", - frame,slot,uci_pdu->harq->harq_list[0].harq_value==0?"ACK":"NACK", - uci_pdu->harq->harq_confidence_level==0?"good":"bad", - xrtmag_dBtimes10,xrtmag_next_dBtimes10,max_n0,uci_stats->pucch0_n00,uci_stats->pucch0_n01,uci_stats->pucch0_thres,cqi,SNRtimes10,10*log10((double)sigenergy)); + LOG_D(PHY, + "[DLSCH/PDSCH/PUCCH] %d.%d HARQ %s with confidence level %s xrt_mag %d xrt_mag_next %d n0 %d (%d,%d) pucch0_thres %d, " + "cqi %d, SNRtimes10 %d, energy %f\n", + frame, + slot, + uci_pdu->harq->harq_list[0].harq_value == 0 ? "ACK" : "NACK", + uci_pdu->harq->harq_confidence_level == 0 ? "good" : "bad", + xrtmag_dBtimes10, + xrtmag_next_dBtimes10, + max_n0, + uci_stats->pucch0_n00, + uci_stats->pucch0_n01, + uci_stats->pucch0_thres, + cqi, + SNRtimes10, + 10 * log10((double)sigenergy)); if (pucch_pdu->sr_flag == 1) { uci_pdu->sr = calloc(1,sizeof(*uci_pdu->sr)); @@ -444,13 +460,22 @@ void nr_decode_pucch0(PHY_VARS_gNB *gNB, uci_pdu->harq->harq_list[1].harq_value = !(index&0x01); uci_pdu->harq->harq_list[0].harq_value = !((index>>1)&0x01); - LOG_D(PHY, "[DLSCH/PDSCH/PUCCH] %d.%d HARQ values (%s, %s) with confidence level %s, xrt_mag %d xrt_mag_next %d n0 %d (%d,%d) pucch0_thres %d, cqi %d, SNRtimes10 %d\n", - frame,slot, + LOG_D(PHY, + "[DLSCH/PDSCH/PUCCH] %d.%d HARQ values (%s, %s) with confidence level %s, xrt_mag %d xrt_mag_next %d n0 %d (%d,%d) " + "pucch0_thres %d, cqi %d, SNRtimes10 %d\n", + frame, + slot, uci_pdu->harq->harq_list[1].harq_value == 0 ? "ACK" : "NACK", uci_pdu->harq->harq_list[0].harq_value == 0 ? "ACK" : "NACK", uci_pdu->harq->harq_confidence_level == 0 ? "good" : "bad", - xrtmag_dBtimes10,xrtmag_next_dBtimes10,max_n0, - uci_stats->pucch0_n00,uci_stats->pucch0_n01,uci_stats->pucch0_thres,cqi,SNRtimes10); + xrtmag_dBtimes10, + xrtmag_next_dBtimes10, + max_n0, + uci_stats->pucch0_n00, + uci_stats->pucch0_n01, + uci_stats->pucch0_thres, + cqi, + SNRtimes10); if (pucch_pdu->sr_flag == 1) { uci_pdu->sr = calloc(1,sizeof(*uci_pdu->sr)); uci_pdu->sr->sr_indication = (index>3) ? 1 : 0; @@ -463,24 +488,21 @@ void nr_decode_pucch0(PHY_VARS_gNB *gNB, } } - - - - -void nr_decode_pucch1( c16_t **rxdataF, - pucch_GroupHopping_t pucch_GroupHopping, - uint32_t n_id, // hoppingID higher layer parameter - uint64_t *payload, - NR_DL_FRAME_PARMS *frame_parms, - int16_t amp, - int nr_tti_tx, - uint8_t m0, - uint8_t nrofSymbols, - uint8_t startingSymbolIndex, - uint16_t startingPRB, - uint16_t startingPRB_intraSlotHopping, - uint8_t timeDomainOCC, - uint8_t nr_bit) { +void nr_decode_pucch1(c16_t **rxdataF, + pucch_GroupHopping_t pucch_GroupHopping, + uint32_t n_id, // hoppingID higher layer parameter + uint64_t *payload, + NR_DL_FRAME_PARMS *frame_parms, + int16_t amp, + int nr_tti_tx, + uint8_t m0, + uint8_t nrofSymbols, + uint8_t startingSymbolIndex, + uint16_t startingPRB, + uint16_t startingPRB_intraSlotHopping, + uint8_t timeDomainOCC, + uint8_t nr_bit) +{ #ifdef DEBUG_NR_PUCCH_RX printf("\t [nr_generate_pucch1] start function at slot(nr_tti_tx)=%d payload=%lp m0=%d nrofSymbols=%d startingSymbolIndex=%d startingPRB=%d startingPRB_intraSlotHopping=%d timeDomainOCC=%d nr_bit=%d\n", nr_tti_tx,payload,m0,nrofSymbols,startingSymbolIndex,startingPRB,startingPRB_intraSlotHopping,timeDomainOCC,nr_bit); @@ -1174,9 +1196,16 @@ void nr_decode_pucch2(PHY_VARS_gNB *gNB, } } } - LOG_D(PHY,"%d.%d Decoding pucch2 for %d symbols, %d PRB, nb_harq %d, nb_sr %d, nb_csi %d/%d\n", - frame, slot, pucch_pdu->nr_of_symbols,pucch_pdu->prb_size, - pucch_pdu->bit_len_harq,pucch_pdu->sr_flag,pucch_pdu->bit_len_csi_part1,pucch_pdu->bit_len_csi_part2); + LOG_D(PHY, + "%d.%d Decoding pucch2 for %d symbols, %d PRB, nb_harq %d, nb_sr %d, nb_csi %d/%d\n", + frame, + slot, + pucch_pdu->nr_of_symbols, + pucch_pdu->prb_size, + pucch_pdu->bit_len_harq, + pucch_pdu->sr_flag, + pucch_pdu->bit_len_csi_part1, + pucch_pdu->bit_len_csi_part2); int nc_group_size=1; // 2 PRB int ngroup = prb_size_ext/nc_group_size/2; @@ -1754,32 +1783,65 @@ void nr_decode_pucch2(PHY_VARS_gNB *gNB, } void nr_dump_uci_stats(FILE *fd,PHY_VARS_gNB *gNB,int frame) { - - int strpos=0; + int strpos = 0; char output[16384]; for (int i = 0; i < MAX_MOBILES_PER_GNB; i++) { NR_gNB_PHY_STATS_t *stats = &gNB->phy_stats[i]; - if (stats->active) { - NR_gNB_UCI_STATS_t *uci_stats = &stats->uci_stats; - if (uci_stats->pucch0_sr_trials > 0) - strpos+=sprintf(output+strpos,"UCI %d RNTI %x: pucch0_sr_trials %d, pucch0_n00 %d dB, pucch0_n01 %d dB, pucch0_sr_thres %d dB, current pucch1_stat0 %d dB, current pucch1_stat1 %d dB, positive SR count %d\n", - i,stats->rnti,uci_stats->pucch0_sr_trials,uci_stats->pucch0_n00,uci_stats->pucch0_n01,uci_stats->pucch0_sr_thres,dB_fixed(uci_stats->current_pucch0_sr_stat0),dB_fixed(uci_stats->current_pucch0_sr_stat1),uci_stats->pucch0_positive_SR); - if (uci_stats->pucch01_trials > 0) - strpos+=sprintf(output+strpos,"UCI %d RNTI %x: pucch01_trials %d, pucch0_n00 %d dB, pucch0_n01 %d dB, pucch0_thres %d dB, current pucch0_stat0 %d dB, current pucch1_stat1 %d dB, pucch01_DTX %d\n", - i,stats->rnti,uci_stats->pucch01_trials,uci_stats->pucch0_n01,uci_stats->pucch0_n01,uci_stats->pucch0_thres,dB_fixed(uci_stats->current_pucch0_stat0),dB_fixed(uci_stats->current_pucch0_stat1),uci_stats->pucch01_DTX); + if (!stats->active) + return; + NR_gNB_UCI_STATS_t *uci_stats = &stats->uci_stats; + if (uci_stats->pucch0_sr_trials > 0) + strpos += sprintf(output + strpos, + "UCI %d RNTI %x: pucch0_sr_trials %d, pucch0_n00 %d dB, pucch0_n01 %d dB, pucch0_sr_thres %d dB, current " + "pucch1_stat0 %d dB, current pucch1_stat1 %d dB, positive SR count %d\n", + i, + stats->rnti, + uci_stats->pucch0_sr_trials, + uci_stats->pucch0_n00, + uci_stats->pucch0_n01, + uci_stats->pucch0_sr_thres, + dB_fixed(uci_stats->current_pucch0_sr_stat0), + dB_fixed(uci_stats->current_pucch0_sr_stat1), + uci_stats->pucch0_positive_SR); + if (uci_stats->pucch01_trials > 0) + strpos += sprintf(output + strpos, + "UCI %d RNTI %x: pucch01_trials %d, pucch0_n00 %d dB, pucch0_n01 %d dB, pucch0_thres %d dB, current " + "pucch0_stat0 %d dB, current pucch1_stat1 %d dB, pucch01_DTX %d\n", + i, + stats->rnti, + uci_stats->pucch01_trials, + uci_stats->pucch0_n01, + uci_stats->pucch0_n01, + uci_stats->pucch0_thres, + dB_fixed(uci_stats->current_pucch0_stat0), + dB_fixed(uci_stats->current_pucch0_stat1), + uci_stats->pucch01_DTX); - if (uci_stats->pucch02_trials > 0) - strpos+=sprintf(output+strpos,"UCI %d RNTI %x: pucch01_trials %d, pucch0_n00 %d dB, pucch0_n01 %d dB, pucch0_thres %d dB, current pucch0_stat0 %d dB, current pucch0_stat1 %d dB, pucch01_DTX %d\n", - i,stats->rnti,uci_stats->pucch02_trials,uci_stats->pucch0_n00,uci_stats->pucch0_n01,uci_stats->pucch0_thres,dB_fixed(uci_stats->current_pucch0_stat0),dB_fixed(uci_stats->current_pucch0_stat1),uci_stats->pucch02_DTX); + if (uci_stats->pucch02_trials > 0) + strpos += sprintf(output + strpos, + "UCI %d RNTI %x: pucch01_trials %d, pucch0_n00 %d dB, pucch0_n01 %d dB, pucch0_thres %d dB, current " + "pucch0_stat0 %d dB, current pucch0_stat1 %d dB, pucch01_DTX %d\n", + i, + stats->rnti, + uci_stats->pucch02_trials, + uci_stats->pucch0_n00, + uci_stats->pucch0_n01, + uci_stats->pucch0_thres, + dB_fixed(uci_stats->current_pucch0_stat0), + dB_fixed(uci_stats->current_pucch0_stat1), + uci_stats->pucch02_DTX); - if (uci_stats->pucch2_trials > 0) - strpos+=sprintf(output+strpos,"UCI %d RNTI %x: pucch2_trials %d, pucch2_DTX %d\n", - i,stats->rnti, + if (uci_stats->pucch2_trials > 0) + strpos += sprintf(output + strpos, + "UCI %d RNTI %x: pucch2_trials %d, pucch2_DTX %d\n", + i, + stats->rnti, uci_stats->pucch2_trials, uci_stats->pucch2_DTX); - } } - if (fd) fprintf(fd,"%s",output); - else printf("%s",output); + if (fd) + fprintf(fd, "%s", output); + else + printf("%s", output); } diff --git a/openair1/PHY/NR_TRANSPORT/srs_rx.c b/openair1/PHY/NR_TRANSPORT/srs_rx.c index 310a63d2c3..989c5cf044 100644 --- a/openair1/PHY/NR_TRANSPORT/srs_rx.c +++ b/openair1/PHY/NR_TRANSPORT/srs_rx.c @@ -44,33 +44,17 @@ //#define SRS_DEBUG -NR_gNB_SRS_t *new_gNB_srs(void){ - NR_gNB_SRS_t *srs; - srs = (NR_gNB_SRS_t *)malloc16(sizeof(NR_gNB_SRS_t)); - srs->active = 0; - return (srs); -} - -void free_gNB_srs(NR_gNB_SRS_t *srs) +void nr_fill_srs(PHY_VARS_gNB *gNB, frame_t frame, slot_t slot, nfapi_nr_srs_pdu_t *srs_pdu) { - free_and_zero(srs); -} - -void nr_fill_srs(PHY_VARS_gNB *gNB, - frame_t frame, - slot_t slot, - nfapi_nr_srs_pdu_t *srs_pdu) -{ - bool found = false; for (int i = 0; i < gNB->max_nb_srs; i++) { - if (gNB->srs[i]->active == 0) { + NR_gNB_SRS_t *srs = &gNB->srs[i]; + if (srs->active == 0) { found = true; - NR_gNB_SRS_t *srs = gNB->srs[i]; srs->frame = frame; srs->slot = slot; srs->active = 1; - memcpy((void*)&srs->srs_pdu, (void*)srs_pdu, sizeof(nfapi_nr_srs_pdu_t)); + memcpy((void *)&srs->srs_pdu, (void *)srs_pdu, sizeof(nfapi_nr_srs_pdu_t)); break; } } diff --git a/openair1/PHY/defs_gNB.h b/openair1/PHY/defs_gNB.h index f52fe32d7f..1fa59ed20b 100644 --- a/openair1/PHY/defs_gNB.h +++ b/openair1/PHY/defs_gNB.h @@ -222,7 +222,7 @@ typedef struct { typedef struct { //! estimated received spatial signal power (linear) - unsigned int rx_spatial_power[NB_ANTENNAS_TX][NB_ANTENNAS_RX]; + unsigned int rx_spatial_power[NB_ANTENNAS_TX][NB_ANTENNAS_RX]; //! estimated received spatial signal power (dB) unsigned int rx_spatial_power_dB[NB_ANTENNAS_TX][NB_ANTENNAS_RX]; //! estimated rssi (dBm) @@ -613,10 +613,10 @@ typedef struct PHY_VARS_gNB_s { NR_gNB_COMMON common_vars; NR_gNB_PRACH prach_vars; NR_gNB_PRS prs_vars; - NR_gNB_PUSCH **pusch_vars; - NR_gNB_PUCCH_t **pucch; - NR_gNB_SRS_t **srs; - NR_gNB_ULSCH_t **ulsch; + NR_gNB_PUSCH *pusch_vars; + NR_gNB_PUCCH_t *pucch; + NR_gNB_SRS_t *srs; + NR_gNB_ULSCH_t *ulsch; NR_gNB_PHY_STATS_t phy_stats[MAX_MOBILES_PER_GNB]; t_nrPolar_params **polarParams; @@ -811,7 +811,7 @@ typedef struct processingData_L1tx { nfapi_nr_dl_tti_pdcch_pdu pdcch_pdu[NFAPI_NR_MAX_NB_CORESETS]; nfapi_nr_ul_dci_request_pdus_t ul_pdcch_pdu[NFAPI_NR_MAX_NB_CORESETS]; NR_gNB_CSIRS_t csirs_pdu[NR_SYMBOLS_PER_SLOT]; - NR_gNB_DLSCH_t ***dlsch; + NR_gNB_DLSCH_t **dlsch; NR_gNB_SSB_t ssb[64]; uint16_t num_pdsch_slot; int num_dl_pdcch; diff --git a/openair1/SCHED_NR/fapi_nr_l1.c b/openair1/SCHED_NR/fapi_nr_l1.c index 94725eeeea..43223906d5 100644 --- a/openair1/SCHED_NR/fapi_nr_l1.c +++ b/openair1/SCHED_NR/fapi_nr_l1.c @@ -100,10 +100,7 @@ void handle_nr_nfapi_ssb_pdu(processingData_L1tx_t *msgTx,int frame,int slot, }*/ - -void handle_nfapi_nr_csirs_pdu(processingData_L1tx_t *msgTx, - int frame,int slot, - nfapi_nr_dl_tti_csi_rs_pdu *csirs_pdu) +void handle_nfapi_nr_csirs_pdu(processingData_L1tx_t *msgTx, int frame, int slot, nfapi_nr_dl_tti_csi_rs_pdu *csirs_pdu) { int found = 0; @@ -121,7 +118,6 @@ void handle_nfapi_nr_csirs_pdu(processingData_L1tx_t *msgTx, LOG_E(MAC,"CSI-RS list is full\n"); } - void handle_nr_nfapi_pdsch_pdu(processingData_L1tx_t *msgTx, nfapi_nr_dl_tti_pdsch_pdu *pdsch_pdu, uint8_t *sdu) @@ -133,7 +129,6 @@ void handle_nr_nfapi_pdsch_pdu(processingData_L1tx_t *msgTx, void nr_schedule_response(NR_Sched_Rsp_t *Sched_INFO) { - // copy data from L2 interface into L1 structures module_id_t Mod_id = Sched_INFO->module_id; nfapi_nr_dl_tti_request_t *DL_req = Sched_INFO->DL_req; @@ -202,8 +197,10 @@ void nr_schedule_response(NR_Sched_Rsp_t *Sched_INFO) AssertFatal(TX_req->pdu_list[pduIndex].num_TLV == 1, "TX_req->pdu_list[%d].num_TLV %d != 1\n", pduIndex,TX_req->pdu_list[pduIndex].num_TLV); uint8_t *sdu = (uint8_t *)TX_req->pdu_list[pduIndex].TLVs[0].value.direct; - AssertFatal(msgTx->num_pdsch_slot < gNB->max_nb_pdsch,"Number of PDSCH PDUs %d exceeded the limit %d\n", - msgTx->num_pdsch_slot, gNB->max_nb_pdsch); + AssertFatal(msgTx->num_pdsch_slot < gNB->max_nb_pdsch, + "Number of PDSCH PDUs %d exceeded the limit %d\n", + msgTx->num_pdsch_slot, + gNB->max_nb_pdsch); handle_nr_nfapi_pdsch_pdu(msgTx,&dl_tti_pdu->pdsch_pdu, sdu); } } diff --git a/openair1/SCHED_NR/phy_procedures_nr_gNB.c b/openair1/SCHED_NR/phy_procedures_nr_gNB.c index 89fdf4918b..7060d13e51 100644 --- a/openair1/SCHED_NR/phy_procedures_nr_gNB.c +++ b/openair1/SCHED_NR/phy_procedures_nr_gNB.c @@ -209,12 +209,11 @@ void phy_procedures_gNB_TX(processingData_L1tx_t *msgTx, void nr_postDecode(PHY_VARS_gNB *gNB, notifiedFIFO_elt_t *req) { - ldpcDecode_t *rdata = (ldpcDecode_t*) NotifiedFifoData(req); NR_UL_gNB_HARQ_t *ulsch_harq = rdata->ulsch_harq; NR_gNB_ULSCH_t *ulsch = rdata->ulsch; int r = rdata->segment_r; - nfapi_nr_pusch_pdu_t *pusch_pdu = &gNB->ulsch[rdata->ulsch_id]->harq_process->ulsch_pdu; + nfapi_nr_pusch_pdu_t *pusch_pdu = &gNB->ulsch[rdata->ulsch_id].harq_process->ulsch_pdu; bool decodeSuccess = (rdata->decodeIterations <= rdata->decoderParms.numMaxIter); ulsch_harq->processedSegments++; LOG_D(PHY, "processing result of segment: %d, processed %d/%d\n", @@ -242,19 +241,35 @@ void nr_postDecode(PHY_VARS_gNB *gNB, notifiedFIFO_elt_t *req) //int dumpsig=0; // if all segments are done if (rdata->nbSegments == ulsch_harq->processedSegments) { - if (decodeSuccess && !gNB->pusch_vars[rdata->ulsch_id]->DTX) { - LOG_D(PHY,"[gNB %d] ULSCH: Setting ACK for SFN/SF %d.%d (rnti %x, pid %d, ndi %d, status %d, round %d, TBS %d, Max interation (all seg) %d)\n", - gNB->Mod_id, ulsch->frame, ulsch->slot, ulsch->rnti, rdata->harq_pid, pusch_pdu->pusch_data.new_data_indicator, - ulsch->active, ulsch_harq->round, ulsch_harq->TBS, rdata->decodeIterations); + if (decodeSuccess && !gNB->pusch_vars[rdata->ulsch_id].DTX) { + LOG_D(PHY, + "[gNB %d] ULSCH: Setting ACK for SFN/SF %d.%d (rnti %x, pid %d, ndi %d, status %d, round %d, TBS %d, Max interation " + "(all seg) %d)\n", + gNB->Mod_id, + ulsch->frame, + ulsch->slot, + ulsch->rnti, + rdata->harq_pid, + pusch_pdu->pusch_data.new_data_indicator, + ulsch->active, + ulsch_harq->round, + ulsch_harq->TBS, + rdata->decodeIterations); ulsch->active = false; ulsch_harq->round = 0; LOG_D(PHY, "ULSCH received ok \n"); - nr_fill_indication(gNB, ulsch->frame, ulsch->slot, rdata->ulsch_id, rdata->harq_pid, 0,0); + nr_fill_indication(gNB, ulsch->frame, ulsch->slot, rdata->ulsch_id, rdata->harq_pid, 0, 0); //dumpsig=1; } else { - LOG_D(PHY,"[gNB %d] ULSCH: Setting NAK for SFN/SF %d/%d (pid %d, ndi %d, status %d, round %d, RV %d, prb_start %d, prb_size %d, TBS %d) r %d\n", - gNB->Mod_id, ulsch->frame, ulsch->slot, - rdata->harq_pid, pusch_pdu->pusch_data.new_data_indicator, ulsch->active, + LOG_D(PHY, + "[gNB %d] ULSCH: Setting NAK for SFN/SF %d/%d (pid %d, ndi %d, status %d, round %d, RV %d, prb_start %d, prb_size %d, " + "TBS %d) r %d\n", + gNB->Mod_id, + ulsch->frame, + ulsch->slot, + rdata->harq_pid, + pusch_pdu->pusch_data.new_data_indicator, + ulsch->active, ulsch_harq->round, ulsch_harq->ulsch_pdu.pusch_data.rv_index, ulsch_harq->ulsch_pdu.rb_start, @@ -263,53 +278,56 @@ void nr_postDecode(PHY_VARS_gNB *gNB, notifiedFIFO_elt_t *req) r); ulsch->handled = 1; LOG_D(PHY, "ULSCH %d in error\n",rdata->ulsch_id); - nr_fill_indication(gNB, ulsch->frame, ulsch->slot, rdata->ulsch_id, rdata->harq_pid, 1,0); -// dumpsig=1; + nr_fill_indication(gNB, ulsch->frame, ulsch->slot, rdata->ulsch_id, rdata->harq_pid, 1, 0); + // dumpsig=1; } -/* - if (ulsch_harq->ulsch_pdu.mcs_index == 0 && dumpsig==1) { - int off = ((ulsch_harq->ulsch_pdu.rb_size&1) == 1)? 4:0; + /* + if (ulsch_harq->ulsch_pdu.mcs_index == 0 && dumpsig==1) { + int off = ((ulsch_harq->ulsch_pdu.rb_size&1) == 1)? 4:0; - LOG_M("rxsigF0.m","rxsF0",&gNB->common_vars.rxdataF[0][(ulsch_harq->slot&3)*gNB->frame_parms.ofdm_symbol_size*gNB->frame_parms.symbols_per_slot],gNB->frame_parms.ofdm_symbol_size*gNB->frame_parms.symbols_per_slot,1,1); - LOG_M("rxsigF0_ext.m","rxsF0_ext", - &gNB->pusch_vars[0]->rxdataF_ext[0][ulsch_harq->ulsch_pdu.start_symbol_index*NR_NB_SC_PER_RB * ulsch_harq->ulsch_pdu.rb_size],ulsch_harq->ulsch_pdu.nr_of_symbols*(off+(NR_NB_SC_PER_RB * ulsch_harq->ulsch_pdu.rb_size)),1,1); - LOG_M("chestF0.m","chF0", - &gNB->pusch_vars[0]->ul_ch_estimates[0][ulsch_harq->ulsch_pdu.start_symbol_index*gNB->frame_parms.ofdm_symbol_size],gNB->frame_parms.ofdm_symbol_size,1,1); - LOG_M("chestF0_ext.m","chF0_ext", - &gNB->pusch_vars[0]->ul_ch_estimates_ext[0][(ulsch_harq->ulsch_pdu.start_symbol_index+1)*(off+(NR_NB_SC_PER_RB * ulsch_harq->ulsch_pdu.rb_size))], - (ulsch_harq->ulsch_pdu.nr_of_symbols-1)*(off+(NR_NB_SC_PER_RB * ulsch_harq->ulsch_pdu.rb_size)),1,1); - LOG_M("rxsigF0_comp.m","rxsF0_comp", - &gNB->pusch_vars[0]->rxdataF_comp[0][ulsch_harq->ulsch_pdu.start_symbol_index*(off+(NR_NB_SC_PER_RB * ulsch_harq->ulsch_pdu.rb_size))],ulsch_harq->ulsch_pdu.nr_of_symbols*(off+(NR_NB_SC_PER_RB * ulsch_harq->ulsch_pdu.rb_size)),1,1); - LOG_M("rxsigF0_llr.m","rxsF0_llr", - &gNB->pusch_vars[0]->llr[0],(ulsch_harq->ulsch_pdu.nr_of_symbols-1)*NR_NB_SC_PER_RB * ulsch_harq->ulsch_pdu.rb_size * ulsch_harq->ulsch_pdu.qam_mod_order,1,0); - if (gNB->frame_parms.nb_antennas_rx > 1) { + LOG_M("rxsigF0.m","rxsF0",&gNB->common_vars.rxdataF[0][(ulsch_harq->slot&3)*gNB->frame_parms.ofdm_symbol_size*gNB->frame_parms.symbols_per_slot],gNB->frame_parms.ofdm_symbol_size*gNB->frame_parms.symbols_per_slot,1,1); + LOG_M("rxsigF0_ext.m","rxsF0_ext", + &gNB->pusch_vars[0].rxdataF_ext[0][ulsch_harq->ulsch_pdu.start_symbol_index*NR_NB_SC_PER_RB * + ulsch_harq->ulsch_pdu.rb_size],ulsch_harq->ulsch_pdu.nr_of_symbols*(off+(NR_NB_SC_PER_RB * + ulsch_harq->ulsch_pdu.rb_size)),1,1); LOG_M("chestF0.m","chF0", + &gNB->pusch_vars[0].ul_ch_estimates[0][ulsch_harq->ulsch_pdu.start_symbol_index*gNB->frame_parms.ofdm_symbol_size],gNB->frame_parms.ofdm_symbol_size,1,1); + LOG_M("chestF0_ext.m","chF0_ext", + &gNB->pusch_vars[0]->ul_ch_estimates_ext[0][(ulsch_harq->ulsch_pdu.start_symbol_index+1)*(off+(NR_NB_SC_PER_RB * + ulsch_harq->ulsch_pdu.rb_size))], (ulsch_harq->ulsch_pdu.nr_of_symbols-1)*(off+(NR_NB_SC_PER_RB * + ulsch_harq->ulsch_pdu.rb_size)),1,1); LOG_M("rxsigF0_comp.m","rxsF0_comp", + &gNB->pusch_vars[0].rxdataF_comp[0][ulsch_harq->ulsch_pdu.start_symbol_index*(off+(NR_NB_SC_PER_RB * + ulsch_harq->ulsch_pdu.rb_size))],ulsch_harq->ulsch_pdu.nr_of_symbols*(off+(NR_NB_SC_PER_RB * + ulsch_harq->ulsch_pdu.rb_size)),1,1); LOG_M("rxsigF0_llr.m","rxsF0_llr", + &gNB->pusch_vars[0].llr[0],(ulsch_harq->ulsch_pdu.nr_of_symbols-1)*NR_NB_SC_PER_RB * ulsch_harq->ulsch_pdu.rb_size * + ulsch_harq->ulsch_pdu.qam_mod_order,1,0); if (gNB->frame_parms.nb_antennas_rx > 1) { - LOG_M("rxsigF1_ext.m","rxsF0_ext", - &gNB->pusch_vars[0]->rxdataF_ext[1][ulsch_harq->ulsch_pdu.start_symbol_index*NR_NB_SC_PER_RB * ulsch_harq->ulsch_pdu.rb_size],ulsch_harq->ulsch_pdu.nr_of_symbols*(off+(NR_NB_SC_PER_RB * ulsch_harq->ulsch_pdu.rb_size)),1,1); - LOG_M("chestF1.m","chF1", - &gNB->pusch_vars[0]->ul_ch_estimates[1][ulsch_harq->ulsch_pdu.start_symbol_index*gNB->frame_parms.ofdm_symbol_size],gNB->frame_parms.ofdm_symbol_size,1,1); - LOG_M("chestF1_ext.m","chF1_ext", - &gNB->pusch_vars[0]->ul_ch_estimates_ext[1][(ulsch_harq->ulsch_pdu.start_symbol_index+1)*(off+(NR_NB_SC_PER_RB * ulsch_harq->ulsch_pdu.rb_size))], - (ulsch_harq->ulsch_pdu.nr_of_symbols-1)*(off+(NR_NB_SC_PER_RB * ulsch_harq->ulsch_pdu.rb_size)),1,1); - LOG_M("rxsigF1_comp.m","rxsF1_comp", - &gNB->pusch_vars[0]->rxdataF_comp[1][ulsch_harq->ulsch_pdu.start_symbol_index*(off+(NR_NB_SC_PER_RB * ulsch_harq->ulsch_pdu.rb_size))],ulsch_harq->ulsch_pdu.nr_of_symbols*(off+(NR_NB_SC_PER_RB * ulsch_harq->ulsch_pdu.rb_size)),1,1); - } - exit(-1); + LOG_M("rxsigF1_ext.m","rxsF0_ext", + &gNB->pusch_vars[0].rxdataF_ext[1][ulsch_harq->ulsch_pdu.start_symbol_index*NR_NB_SC_PER_RB * + ulsch_harq->ulsch_pdu.rb_size],ulsch_harq->ulsch_pdu.nr_of_symbols*(off+(NR_NB_SC_PER_RB * + ulsch_harq->ulsch_pdu.rb_size)),1,1); LOG_M("chestF1.m","chF1", + &gNB->pusch_vars[0].ul_ch_estimates[1][ulsch_harq->ulsch_pdu.start_symbol_index*gNB->frame_parms.ofdm_symbol_size],gNB->frame_parms.ofdm_symbol_size,1,1); + LOG_M("chestF1_ext.m","chF1_ext", + &gNB->pusch_vars[0].ul_ch_estimates_ext[1][(ulsch_harq->ulsch_pdu.start_symbol_index+1)*(off+(NR_NB_SC_PER_RB * + ulsch_harq->ulsch_pdu.rb_size))], (ulsch_harq->ulsch_pdu.nr_of_symbols-1)*(off+(NR_NB_SC_PER_RB * + ulsch_harq->ulsch_pdu.rb_size)),1,1); LOG_M("rxsigF1_comp.m","rxsF1_comp", + &gNB->pusch_vars[0].rxdataF_comp[1][ulsch_harq->ulsch_pdu.start_symbol_index*(off+(NR_NB_SC_PER_RB * + ulsch_harq->ulsch_pdu.rb_size))],ulsch_harq->ulsch_pdu.nr_of_symbols*(off+(NR_NB_SC_PER_RB * + ulsch_harq->ulsch_pdu.rb_size)),1,1); + } + exit(-1); - } -*/ + } + */ ulsch->last_iteration_cnt = rdata->decodeIterations; VCD_SIGNAL_DUMPER_DUMP_FUNCTION_BY_NAME(VCD_SIGNAL_DUMPER_FUNCTIONS_PHY_gNB_ULSCH_DECODING,0); } } - void nr_ulsch_procedures(PHY_VARS_gNB *gNB, int frame_rx, int slot_rx, int ULSCH_id, uint8_t harq_pid) { - NR_DL_FRAME_PARMS *frame_parms = &gNB->frame_parms; - nfapi_nr_pusch_pdu_t *pusch_pdu = &gNB->ulsch[ULSCH_id]->harq_process->ulsch_pdu; - + nfapi_nr_pusch_pdu_t *pusch_pdu = &gNB->ulsch[ULSCH_id].harq_process->ulsch_pdu; + uint16_t nb_re_dmrs; uint8_t enable_ldpc_offload = gNB->ldpc_offload_flag; uint16_t start_symbol = pusch_pdu->start_symbol_index; @@ -330,7 +348,7 @@ void nr_ulsch_procedures(PHY_VARS_gNB *gNB, int frame_rx, int slot_rx, int ULSCH number_dmrs_symbols, // number of dmrs symbols irrespective of single or double symbol dmrs pusch_pdu->qam_mod_order, pusch_pdu->nrOfLayers); - + AssertFatal(G>0,"G is 0 : rb_size %u, number_symbols %d, nb_re_dmrs %d, number_dmrs_symbols %d, qam_mod_order %u, nrOfLayer %u\n", pusch_pdu->rb_size, number_symbols, @@ -347,36 +365,24 @@ void nr_ulsch_procedures(PHY_VARS_gNB *gNB, int frame_rx, int slot_rx, int ULSCH pusch_pdu->qam_mod_order, pusch_pdu->nrOfLayers); - - nr_ulsch_layer_demapping(gNB->pusch_vars[ULSCH_id]->llr, + nr_ulsch_layer_demapping(gNB->pusch_vars[ULSCH_id].llr, pusch_pdu->nrOfLayers, pusch_pdu->qam_mod_order, G, - gNB->pusch_vars[ULSCH_id]->llr_layers); - + gNB->pusch_vars[ULSCH_id].llr_layers); + //---------------------------------------------------------- //------------------- ULSCH unscrambling ------------------- //---------------------------------------------------------- start_meas(&gNB->ulsch_unscrambling_stats); - nr_ulsch_unscrambling(gNB->pusch_vars[ULSCH_id]->llr, - G, - pusch_pdu->data_scrambling_id, - pusch_pdu->rnti); + nr_ulsch_unscrambling(gNB->pusch_vars[ULSCH_id].llr, G, pusch_pdu->data_scrambling_id, pusch_pdu->rnti); stop_meas(&gNB->ulsch_unscrambling_stats); //---------------------------------------------------------- //--------------------- ULSCH decoding --------------------- //---------------------------------------------------------- start_meas(&gNB->ulsch_decoding_stats); - nr_ulsch_decoding(gNB, - ULSCH_id, - gNB->pusch_vars[ULSCH_id]->llr, - frame_parms, - pusch_pdu, - frame_rx, - slot_rx, - harq_pid, - G); + nr_ulsch_decoding(gNB, ULSCH_id, gNB->pusch_vars[ULSCH_id].llr, frame_parms, pusch_pdu, frame_rx, slot_rx, harq_pid, G); if (enable_ldpc_offload == 0) { while (gNB->nbDecode > 0) { notifiedFIFO_elt_t *req = pullTpool(&gNB->respDecode, &gNB->threadPool); @@ -385,17 +391,15 @@ void nr_ulsch_procedures(PHY_VARS_gNB *gNB, int frame_rx, int slot_rx, int ULSCH nr_postDecode(gNB, req); delNotifiedFIFO_elt(req); } - } + } stop_meas(&gNB->ulsch_decoding_stats); } - void nr_fill_indication(PHY_VARS_gNB *gNB, int frame, int slot_rx, int ULSCH_id, uint8_t harq_pid, uint8_t crc_flag, int dtx_flag) { - pthread_mutex_lock(&gNB->UL_INFO_mutex); - NR_gNB_ULSCH_t *ulsch = gNB->ulsch[ULSCH_id]; + NR_gNB_ULSCH_t *ulsch = &gNB->ulsch[ULSCH_id]; NR_UL_gNB_HARQ_t *harq_process = ulsch->harq_process; NR_gNB_PHY_STATS_t *stats = get_phy_stats(gNB, ulsch->rnti); @@ -413,7 +417,8 @@ void nr_fill_indication(PHY_VARS_gNB *gNB, int frame, int slot_rx, int ULSCH_id, sync_pos_rounded = sync_pos + (bw_scaling / 2) - 1; else sync_pos_rounded = sync_pos - (bw_scaling / 2) + 1; - if (stats) stats->ulsch_stats.sync_pos = sync_pos; + if (stats) + stats->ulsch_stats.sync_pos = sync_pos; int timing_advance_update = sync_pos_rounded / bw_scaling; @@ -426,10 +431,17 @@ void nr_fill_indication(PHY_VARS_gNB *gNB, int frame, int slot_rx, int ULSCH_id, if (crc_flag == 0) LOG_D(PHY, "%d.%d : Received PUSCH : Estimated timing advance PUSCH is = %d, timing_advance_update is %d \n", frame,slot_rx,sync_pos,timing_advance_update); // estimate UL_CQI for MAC - int SNRtimes10 = dB_fixed_x10(gNB->pusch_vars[ULSCH_id]->ulsch_power_tot) - - dB_fixed_x10(gNB->pusch_vars[ULSCH_id]->ulsch_noise_power_tot); + int SNRtimes10 = + dB_fixed_x10(gNB->pusch_vars[ULSCH_id].ulsch_power_tot) - dB_fixed_x10(gNB->pusch_vars[ULSCH_id].ulsch_noise_power_tot); - LOG_D(PHY, "%d.%d: Estimated SNR for PUSCH is = %f dB (ulsch_power %f, noise %f) delay %d\n", frame, slot_rx, SNRtimes10/10.0,dB_fixed_x10(gNB->pusch_vars[ULSCH_id]->ulsch_power_tot)/10.0,dB_fixed_x10(gNB->pusch_vars[ULSCH_id]->ulsch_noise_power_tot)/10.0,sync_pos); + LOG_D(PHY, + "%d.%d: Estimated SNR for PUSCH is = %f dB (ulsch_power %f, noise %f) delay %d\n", + frame, + slot_rx, + SNRtimes10 / 10.0, + dB_fixed_x10(gNB->pusch_vars[ULSCH_id].ulsch_power_tot) / 10.0, + dB_fixed_x10(gNB->pusch_vars[ULSCH_id].ulsch_noise_power_tot) / 10.0, + sync_pos); int cqi; if (SNRtimes10 < -640) cqi=0; @@ -442,28 +454,64 @@ void nr_fill_indication(PHY_VARS_gNB *gNB, int frame, int slot_rx, int ULSCH_id, int off = ((pusch_pdu->rb_size&1) == 1)? 4:0; LOG_M("rxsigF0.m","rxsF0",&gNB->common_vars.rxdataF[0][(slot_rx&3)*gNB->frame_parms.ofdm_symbol_size*gNB->frame_parms.symbols_per_slot],gNB->frame_parms.ofdm_symbol_size*gNB->frame_parms.symbols_per_slot,1,1); - LOG_M("rxsigF0_ext.m","rxsF0_ext", - &gNB->pusch_vars[0]->rxdataF_ext[0][pusch_pdu->start_symbol_index*NR_NB_SC_PER_RB * pusch_pdu->rb_size],pusch_pdu->nr_of_symbols*(off+(NR_NB_SC_PER_RB * pusch_pdu->rb_size)),1,1); - LOG_M("chestF0.m","chF0", - &gNB->pusch_vars[0]->ul_ch_estimates[0][pusch_pdu->start_symbol_index*gNB->frame_parms.ofdm_symbol_size],gNB->frame_parms.ofdm_symbol_size,1,1); - LOG_M("chestF0_ext.m","chF0_ext", - &gNB->pusch_vars[0]->ul_ch_estimates_ext[0][(pusch_pdu->start_symbol_index+1)*(off+(NR_NB_SC_PER_RB * pusch_pdu->rb_size))], - (pusch_pdu->nr_of_symbols-1)*(off+(NR_NB_SC_PER_RB * pusch_pdu->rb_size)),1,1); - LOG_M("rxsigF0_comp.m","rxsF0_comp", - &gNB->pusch_vars[0]->rxdataF_comp[0][pusch_pdu->start_symbol_index*(off+(NR_NB_SC_PER_RB * pusch_pdu->rb_size))],pusch_pdu->nr_of_symbols*(off+(NR_NB_SC_PER_RB * pusch_pdu->rb_size)),1,1); - LOG_M("rxsigF0_llr.m","rxsF0_llr", - &gNB->pusch_vars[0]->llr[0],(pusch_pdu->nr_of_symbols-1)*NR_NB_SC_PER_RB *pusch_pdu->rb_size * pusch_pdu->qam_mod_order,1,0); + LOG_M("rxsigF0_ext.m", + "rxsF0_ext", + &gNB->pusch_vars[0].rxdataF_ext[0][pusch_pdu->start_symbol_index * NR_NB_SC_PER_RB * pusch_pdu->rb_size], + pusch_pdu->nr_of_symbols * (off + (NR_NB_SC_PER_RB * pusch_pdu->rb_size)), + 1, + 1); + LOG_M("chestF0.m", + "chF0", + &gNB->pusch_vars[0].ul_ch_estimates[0][pusch_pdu->start_symbol_index * gNB->frame_parms.ofdm_symbol_size], + gNB->frame_parms.ofdm_symbol_size, + 1, + 1); + LOG_M("chestF0_ext.m", + "chF0_ext", + &gNB->pusch_vars[0] + .ul_ch_estimates_ext[0][(pusch_pdu->start_symbol_index + 1) * (off + (NR_NB_SC_PER_RB * pusch_pdu->rb_size))], + (pusch_pdu->nr_of_symbols - 1) * (off + (NR_NB_SC_PER_RB * pusch_pdu->rb_size)), + 1, + 1); + LOG_M("rxsigF0_comp.m", + "rxsF0_comp", + &gNB->pusch_vars[0].rxdataF_comp[0][pusch_pdu->start_symbol_index * (off + (NR_NB_SC_PER_RB * pusch_pdu->rb_size))], + pusch_pdu->nr_of_symbols * (off + (NR_NB_SC_PER_RB * pusch_pdu->rb_size)), + 1, + 1); + LOG_M("rxsigF0_llr.m", + "rxsF0_llr", + &gNB->pusch_vars[0].llr[0], + (pusch_pdu->nr_of_symbols - 1) * NR_NB_SC_PER_RB * pusch_pdu->rb_size * pusch_pdu->qam_mod_order, + 1, + 0); if (gNB->frame_parms.nb_antennas_rx > 1) { LOG_M("rxsigF1.m","rxsF1",&gNB->common_vars.rxdataF[1][(slot_rx&3)*gNB->frame_parms.ofdm_symbol_size*gNB->frame_parms.symbols_per_slot],gNB->frame_parms.ofdm_symbol_size*gNB->frame_parms.symbols_per_slot,1,1); - LOG_M("rxsigF1_ext.m","rxsF1_ext", - &gNB->pusch_vars[0]->rxdataF_ext[1][pusch_pdu->start_symbol_index*NR_NB_SC_PER_RB * pusch_pdu->rb_size],pusch_pdu->nr_of_symbols*(off+(NR_NB_SC_PER_RB * pusch_pdu->rb_size)),1,1); - LOG_M("chestF1.m","chF1", - &gNB->pusch_vars[0]->ul_ch_estimates[1][pusch_pdu->start_symbol_index*gNB->frame_parms.ofdm_symbol_size],gNB->frame_parms.ofdm_symbol_size,1,1); - LOG_M("chestF1_ext.m","chF1_ext", - &gNB->pusch_vars[0]->ul_ch_estimates_ext[1][(pusch_pdu->start_symbol_index+1)*(off+(NR_NB_SC_PER_RB * pusch_pdu->rb_size))], - (pusch_pdu->nr_of_symbols-1)*(off+(NR_NB_SC_PER_RB * pusch_pdu->rb_size)),1,1); - LOG_M("rxsigF1_comp.m","rxsF1_comp", - &gNB->pusch_vars[0]->rxdataF_comp[1][pusch_pdu->start_symbol_index*(off+(NR_NB_SC_PER_RB * pusch_pdu->rb_size))],pusch_pdu->nr_of_symbols*(off+(NR_NB_SC_PER_RB * pusch_pdu->rb_size)),1,1); + LOG_M("rxsigF1_ext.m", + "rxsF1_ext", + &gNB->pusch_vars[0].rxdataF_ext[1][pusch_pdu->start_symbol_index * NR_NB_SC_PER_RB * pusch_pdu->rb_size], + pusch_pdu->nr_of_symbols * (off + (NR_NB_SC_PER_RB * pusch_pdu->rb_size)), + 1, + 1); + LOG_M("chestF1.m", + "chF1", + &gNB->pusch_vars[0].ul_ch_estimates[1][pusch_pdu->start_symbol_index * gNB->frame_parms.ofdm_symbol_size], + gNB->frame_parms.ofdm_symbol_size, + 1, + 1); + LOG_M("chestF1_ext.m", + "chF1_ext", + &gNB->pusch_vars[0] + .ul_ch_estimates_ext[1][(pusch_pdu->start_symbol_index + 1) * (off + (NR_NB_SC_PER_RB * pusch_pdu->rb_size))], + (pusch_pdu->nr_of_symbols - 1) * (off + (NR_NB_SC_PER_RB * pusch_pdu->rb_size)), + 1, + 1); + LOG_M("rxsigF1_comp.m", + "rxsF1_comp", + &gNB->pusch_vars[0].rxdataF_comp[1][pusch_pdu->start_symbol_index * (off + (NR_NB_SC_PER_RB * pusch_pdu->rb_size))], + pusch_pdu->nr_of_symbols * (off + (NR_NB_SC_PER_RB * pusch_pdu->rb_size)), + 1, + 1); } exit(-1); @@ -483,7 +531,8 @@ void nr_fill_indication(PHY_VARS_gNB *gNB, int frame, int slot_rx, int ULSCH_id, gNB->crc_pdu_list[num_crc].ul_cqi = cqi; gNB->crc_pdu_list[num_crc].timing_advance = timing_advance_update; // in terms of dBFS range -128 to 0 with 0.1 step - gNB->crc_pdu_list[num_crc].rssi = (dtx_flag==0) ? 1280 - (10*dB_fixed(32767*32767)-dB_fixed_times10(gNB->pusch_vars[ULSCH_id]->ulsch_power[0])) : 0; + gNB->crc_pdu_list[num_crc].rssi = + (dtx_flag == 0) ? 1280 - (10 * dB_fixed(32767 * 32767) - dB_fixed_times10(gNB->pusch_vars[ULSCH_id].ulsch_power[0])) : 0; gNB->UL_INFO.crc_ind.number_crcs++; @@ -508,7 +557,6 @@ void nr_fill_indication(PHY_VARS_gNB *gNB, int frame, int slot_rx, int ULSCH_id, gNB->UL_INFO.rx_ind.number_of_pdus++; pthread_mutex_unlock(&gNB->UL_INFO_mutex); - } // Function to fill UL RB mask to be used for N0 measurements @@ -530,7 +578,7 @@ void fill_ul_rb_mask(PHY_VARS_gNB *gNB, int frame_rx, int slot_rx) { } for (int i = 0; i < gNB->max_nb_pucch; i++){ - NR_gNB_PUCCH_t *pucch = gNB->pucch[i]; + NR_gNB_PUCCH_t *pucch = &gNB->pucch[i]; if (pucch) { if ((pucch->active == 1) && (pucch->frame == frame_rx) && @@ -553,22 +601,23 @@ void fill_ul_rb_mask(PHY_VARS_gNB *gNB, int frame_rx, int slot_rx) { } for (int ULSCH_id = 0; ULSCH_id < gNB->max_nb_pusch; ULSCH_id++) { - NR_gNB_ULSCH_t *ulsch = gNB->ulsch[ULSCH_id]; + NR_gNB_ULSCH_t *ulsch = &gNB->ulsch[ULSCH_id]; int harq_pid; NR_UL_gNB_HARQ_t *ulsch_harq = ulsch->harq_process; - AssertFatal(ulsch_harq != NULL,"harq_pid %d is not allocated\n",harq_pid); - if ((ulsch->active == true) && - (ulsch->frame == frame_rx) && - (ulsch->slot == slot_rx) && - (ulsch->handled == 0)) { + AssertFatal(ulsch_harq != NULL, "harq_pid %d is not allocated\n", harq_pid); + if ((ulsch->active == true) && (ulsch->frame == frame_rx) && (ulsch->slot == slot_rx) && (ulsch->handled == 0)) { uint8_t symbol_start = ulsch_harq->ulsch_pdu.start_symbol_index; uint8_t symbol_end = symbol_start + ulsch_harq->ulsch_pdu.nr_of_symbols; - for (int symbol = symbol_start ; symbol < symbol_end ; symbol++) { - if(gNB->frame_parms.frame_type == FDD || - (gNB->frame_parms.frame_type == TDD && gNB->gNB_config.tdd_table.max_tdd_periodicity_list[slot_rx].max_num_of_symbol_per_slot_list[symbol].slot_config.value==1)) { - LOG_D(PHY,"symbol %d Filling rb_mask_ul rb_size %d\n",symbol,ulsch_harq->ulsch_pdu.rb_size); - for (rb=0; rbulsch_pdu.rb_size; rb++) { - rb2 = rb+ulsch_harq->ulsch_pdu.rb_start+ulsch_harq->ulsch_pdu.bwp_start; + for (int symbol = symbol_start; symbol < symbol_end; symbol++) { + if (gNB->frame_parms.frame_type == FDD + || (gNB->frame_parms.frame_type == TDD + && gNB->gNB_config.tdd_table.max_tdd_periodicity_list[slot_rx] + .max_num_of_symbol_per_slot_list[symbol] + .slot_config.value + == 1)) { + LOG_D(PHY, "symbol %d Filling rb_mask_ul rb_size %d\n", symbol, ulsch_harq->ulsch_pdu.rb_size); + for (rb = 0; rb < ulsch_harq->ulsch_pdu.rb_size; rb++) { + rb2 = rb + ulsch_harq->ulsch_pdu.rb_start + ulsch_harq->ulsch_pdu.bwp_start; gNB->rb_mask_ul[symbol][rb2 >> 5] |= 1U << (rb2 & 31); } } @@ -577,7 +626,7 @@ void fill_ul_rb_mask(PHY_VARS_gNB *gNB, int frame_rx, int slot_rx) { } for (int i = 0; i < gNB->max_nb_srs; i++) { - NR_gNB_SRS_t *srs = gNB->srs[i]; + NR_gNB_SRS_t *srs = &gNB->srs[i]; if (srs) { if ((srs->active == 1) && (srs->frame == frame_rx) && (srs->slot == slot_rx)) { nfapi_nr_srs_pdu_t *srs_pdu = &srs->srs_pdu; @@ -680,7 +729,6 @@ int check_srs_pdu(const nfapi_nr_srs_pdu_t *srs_pdu, nfapi_nr_srs_pdu_t *saved_s int phy_procedures_gNB_uespec_RX(PHY_VARS_gNB *gNB, int frame_rx, int slot_rx) { - /* those variables to log T_GNB_PHY_PUCCH_PUSCH_IQ only when we try to decode */ int pucch_decode_done = 0; int pusch_decode_done = 0; @@ -700,7 +748,7 @@ int phy_procedures_gNB_uespec_RX(PHY_VARS_gNB *gNB, int frame_rx, int slot_rx) } } else - num_symb=NR_NUMBER_OF_SYMBOLS_PER_SLOT; + num_symb = NR_NUMBER_OF_SYMBOLS_PER_SLOT; gNB_I0_measurements(gNB,slot_rx,first_symb,num_symb); const int soffset = (slot_rx & 3) * gNB->frame_parms.symbols_per_slot * gNB->frame_parms.ofdm_symbol_size; @@ -711,7 +759,7 @@ int phy_procedures_gNB_uespec_RX(PHY_VARS_gNB *gNB, int frame_rx, int slot_rx) start_meas(&gNB->phy_proc_rx); for (int i = 0; i < gNB->max_nb_pucch; i++) { - NR_gNB_PUCCH_t *pucch = gNB->pucch[i]; + NR_gNB_PUCCH_t *pucch = &gNB->pucch[i]; if (pucch) { if (NFAPI_MODE == NFAPI_MODE_PNF) pucch->frame = frame_rx; @@ -755,7 +803,7 @@ int phy_procedures_gNB_uespec_RX(PHY_VARS_gNB *gNB, int frame_rx, int slot_rx) gNB->uci_pdu_list[num_ucis].pdu_size = sizeof(nfapi_nr_uci_pucch_pdu_format_2_3_4_t); nfapi_nr_uci_pucch_pdu_format_2_3_4_t *uci_pdu_format2 = &gNB->uci_pdu_list[num_ucis].pucch_pdu_format_2_3_4; - LOG_D(PHY,"%d.%d Calling nr_decode_pucch2\n", frame_rx, slot_rx); + LOG_D(PHY, "%d.%d Calling nr_decode_pucch2\n", frame_rx, slot_rx); nr_decode_pucch2(gNB, frame_rx, slot_rx, @@ -766,25 +814,19 @@ int phy_procedures_gNB_uespec_RX(PHY_VARS_gNB *gNB, int frame_rx, int slot_rx) pucch->active = 0; break; default: - AssertFatal(1==0,"Only PUCCH formats 0 and 2 are currently supported\n"); + AssertFatal(1 == 0, "Only PUCCH formats 0 and 2 are currently supported\n"); } } } } for (int ULSCH_id = 0; ULSCH_id < gNB->max_nb_pusch; ULSCH_id++) { - - NR_gNB_ULSCH_t *ulsch = gNB->ulsch[ULSCH_id]; + NR_gNB_ULSCH_t *ulsch = &gNB->ulsch[ULSCH_id]; NR_UL_gNB_HARQ_t *ulsch_harq = ulsch->harq_process; AssertFatal(ulsch_harq != NULL, "harq_pid %d is not allocated\n", ulsch->harq_pid); - if ((ulsch->active == true) && - (ulsch->frame == frame_rx) && - (ulsch->slot == slot_rx) && - (ulsch->handled == 0)) { - - LOG_D(PHY, "PUSCH ID %d with RNTI %x detection started in frame %d slot %d\n", - ULSCH_id, ulsch->rnti, frame_rx, slot_rx); + if ((ulsch->active == true) && (ulsch->frame == frame_rx) && (ulsch->slot == slot_rx) && (ulsch->handled == 0)) { + LOG_D(PHY, "PUSCH ID %d with RNTI %x detection started in frame %d slot %d\n", ULSCH_id, ulsch->rnti, frame_rx, slot_rx); int num_dmrs = 0; for (int s = 0; s < NR_NUMBER_OF_SYMBOLS_PER_SLOT; s++) num_dmrs += (ulsch_harq->ulsch_pdu.ul_dmrs_symb_pos >> s) & 1; @@ -792,25 +834,32 @@ int phy_procedures_gNB_uespec_RX(PHY_VARS_gNB *gNB, int frame_rx, int slot_rx) #ifdef DEBUG_RXDATA NR_DL_FRAME_PARMS *frame_parms = &gNB->frame_parms; RU_t *ru = gNB->RU_list[0]; - int slot_offset = frame_parms->get_samples_slot_timestamp(slot_rx,frame_parms,0); + int slot_offset = frame_parms->get_samples_slot_timestamp(slot_rx, frame_parms, 0); slot_offset -= ru->N_TA_offset; int32_t sample_offset = gNB->common_vars.debugBuff_sample_offset; nfapi_nr_pusch_pdu_t *pdu = &ulsch_harq->ulsch_pdu; - int16_t *buf = (int16_t*)&gNB->common_vars.debugBuff[offset]; - buf[0]=(int16_t)ulsch->rnti; - buf[1]=(int16_t)pdu->rb_size; - buf[2]=(int16_t)pdu->rb_start; - buf[3]=(int16_t)pdu->nr_of_symbols; - buf[4]=(int16_t)pdu->start_symbol_index; - buf[5]=(int16_t)pdu->mcs_index; - buf[6]=(int16_t)pdu->pusch_data.rv_index; - buf[7]=(int16_t)ulsch->harq_pid; - memcpy(&gNB->common_vars.debugBuff[gNB->common_vars.debugBuff_sample_offset+4],&ru->common.rxdata[0][slot_offset],frame_parms->get_samples_per_slot(slot_rx,frame_parms)*sizeof(int32_t)); - gNB->common_vars.debugBuff_sample_offset+=(frame_parms->get_samples_per_slot(slot_rx,frame_parms)+1000+4); - if(gNB->common_vars.debugBuff_sample_offset>((frame_parms->get_samples_per_slot(slot_rx,frame_parms)+1000+2)*20)) { + int16_t *buf = (int16_t *)&gNB->common_vars.debugBuff[offset]; + buf[0] = (int16_t)ulsch->rnti; + buf[1] = (int16_t)pdu->rb_size; + buf[2] = (int16_t)pdu->rb_start; + buf[3] = (int16_t)pdu->nr_of_symbols; + buf[4] = (int16_t)pdu->start_symbol_index; + buf[5] = (int16_t)pdu->mcs_index; + buf[6] = (int16_t)pdu->pusch_data.rv_index; + buf[7] = (int16_t)ulsch->harq_pid; + memcpy(&gNB->common_vars.debugBuff[gNB->common_vars.debugBuff_sample_offset + 4], + &ru->common.rxdata[0][slot_offset], + frame_parms->get_samples_per_slot(slot_rx, frame_parms) * sizeof(int32_t)); + gNB->common_vars.debugBuff_sample_offset += (frame_parms->get_samples_per_slot(slot_rx, frame_parms) + 1000 + 4); + if (gNB->common_vars.debugBuff_sample_offset > ((frame_parms->get_samples_per_slot(slot_rx, frame_parms) + 1000 + 2) * 20)) { FILE *f; - f = fopen("rxdata_buff.raw", "w"); if (f == NULL) exit(1); - fwrite((int16_t*)gNB->common_vars.debugBuff,2,(frame_parms->get_samples_per_slot(slot_rx,frame_parms)+1000+4)*20*2, f); + f = fopen("rxdata_buff.raw", "w"); + if (f == NULL) + exit(1); + fwrite((int16_t *)gNB->common_vars.debugBuff, + 2, + (frame_parms->get_samples_per_slot(slot_rx, frame_parms) + 1000 + 4) * 20 * 2, + f); fclose(f); exit(-1); } @@ -818,54 +867,62 @@ int phy_procedures_gNB_uespec_RX(PHY_VARS_gNB *gNB, int frame_rx, int slot_rx) pusch_decode_done = 1; - VCD_SIGNAL_DUMPER_DUMP_FUNCTION_BY_NAME(VCD_SIGNAL_DUMPER_FUNCTIONS_NR_RX_PUSCH,1); + VCD_SIGNAL_DUMPER_DUMP_FUNCTION_BY_NAME(VCD_SIGNAL_DUMPER_FUNCTIONS_NR_RX_PUSCH, 1); start_meas(&gNB->rx_pusch_stats); nr_rx_pusch(gNB, ULSCH_id, frame_rx, slot_rx, ulsch->harq_pid); - NR_gNB_PUSCH *pusch_vars = gNB->pusch_vars[ULSCH_id]; + NR_gNB_PUSCH *pusch_vars = &gNB->pusch_vars[ULSCH_id]; pusch_vars->ulsch_power_tot = 0; pusch_vars->ulsch_noise_power_tot = 0; - for (int aarx=0;aarxframe_parms.nb_antennas_rx;aarx++) { - pusch_vars->ulsch_power[aarx] /= num_dmrs; - pusch_vars->ulsch_power_tot += pusch_vars->ulsch_power[aarx]; - pusch_vars->ulsch_noise_power[aarx] /= num_dmrs; - pusch_vars->ulsch_noise_power_tot += pusch_vars->ulsch_noise_power[aarx]; + for (int aarx = 0; aarx < gNB->frame_parms.nb_antennas_rx; aarx++) { + pusch_vars->ulsch_power[aarx] /= num_dmrs; + pusch_vars->ulsch_power_tot += pusch_vars->ulsch_power[aarx]; + pusch_vars->ulsch_noise_power[aarx] /= num_dmrs; + pusch_vars->ulsch_noise_power_tot += pusch_vars->ulsch_noise_power[aarx]; } - if (dB_fixed_x10(pusch_vars->ulsch_power_tot) < - dB_fixed_x10(pusch_vars->ulsch_noise_power_tot) + gNB->pusch_thres) { - NR_gNB_PHY_STATS_t *stats = get_phy_stats(gNB, ulsch->rnti); + if (dB_fixed_x10(pusch_vars->ulsch_power_tot) < dB_fixed_x10(pusch_vars->ulsch_noise_power_tot) + gNB->pusch_thres) { + NR_gNB_PHY_STATS_t *stats = get_phy_stats(gNB, ulsch->rnti); - LOG_D(PHY, "PUSCH not detected in %d.%d (%d,%d,%d)\n",frame_rx,slot_rx, - dB_fixed_x10(pusch_vars->ulsch_power_tot), - dB_fixed_x10(pusch_vars->ulsch_noise_power_tot),gNB->pusch_thres); - pusch_vars->ulsch_power_tot = pusch_vars->ulsch_noise_power_tot; - pusch_vars->DTX = 1; - if (stats) stats->ulsch_stats.DTX++; - if (!get_softmodem_params()->phy_test) { - /* in case of phy_test mode, we still want to decode to measure execution time. - Therefore, we don't yet call nr_fill_indication, it will be called later */ - nr_fill_indication(gNB, frame_rx, slot_rx, ULSCH_id, ulsch->harq_pid, 1, 1); - pusch_DTX++; - continue; - } - } else { - LOG_D(PHY, "PUSCH detected in %d.%d (%d,%d,%d)\n",frame_rx,slot_rx, + LOG_D(PHY, + "PUSCH not detected in %d.%d (%d,%d,%d)\n", + frame_rx, + slot_rx, dB_fixed_x10(pusch_vars->ulsch_power_tot), - dB_fixed_x10(pusch_vars->ulsch_noise_power_tot),gNB->pusch_thres); + dB_fixed_x10(pusch_vars->ulsch_noise_power_tot), + gNB->pusch_thres); + pusch_vars->ulsch_power_tot = pusch_vars->ulsch_noise_power_tot; + pusch_vars->DTX = 1; + if (stats) + stats->ulsch_stats.DTX++; + if (!get_softmodem_params()->phy_test) { + /* in case of phy_test mode, we still want to decode to measure execution time. + Therefore, we don't yet call nr_fill_indication, it will be called later */ + nr_fill_indication(gNB, frame_rx, slot_rx, ULSCH_id, ulsch->harq_pid, 1, 1); + pusch_DTX++; + continue; + } + } else { + LOG_D(PHY, + "PUSCH detected in %d.%d (%d,%d,%d)\n", + frame_rx, + slot_rx, + dB_fixed_x10(pusch_vars->ulsch_power_tot), + dB_fixed_x10(pusch_vars->ulsch_noise_power_tot), + gNB->pusch_thres); - pusch_vars->DTX=0; + pusch_vars->DTX = 0; } stop_meas(&gNB->rx_pusch_stats); - VCD_SIGNAL_DUMPER_DUMP_FUNCTION_BY_NAME(VCD_SIGNAL_DUMPER_FUNCTIONS_NR_RX_PUSCH,0); - //LOG_M("rxdataF_comp.m","rxF_comp",gNB->pusch_vars[0]->rxdataF_comp[0],6900,1,1); - //LOG_M("rxdataF_ext.m","rxF_ext",gNB->pusch_vars[0]->rxdataF_ext[0],6900,1,1); - VCD_SIGNAL_DUMPER_DUMP_FUNCTION_BY_NAME(VCD_SIGNAL_DUMPER_FUNCTIONS_NR_ULSCH_PROCEDURES_RX,1); + VCD_SIGNAL_DUMPER_DUMP_FUNCTION_BY_NAME(VCD_SIGNAL_DUMPER_FUNCTIONS_NR_RX_PUSCH, 0); + // LOG_M("rxdataF_comp.m","rxF_comp",gNB->pusch_vars[0]->rxdataF_comp[0],6900,1,1); + // LOG_M("rxdataF_ext.m","rxF_ext",gNB->pusch_vars[0]->rxdataF_ext[0],6900,1,1); + VCD_SIGNAL_DUMPER_DUMP_FUNCTION_BY_NAME(VCD_SIGNAL_DUMPER_FUNCTIONS_NR_ULSCH_PROCEDURES_RX, 1); nr_ulsch_procedures(gNB, frame_rx, slot_rx, ULSCH_id, ulsch->harq_pid); - VCD_SIGNAL_DUMPER_DUMP_FUNCTION_BY_NAME(VCD_SIGNAL_DUMPER_FUNCTIONS_NR_ULSCH_PROCEDURES_RX,0); + VCD_SIGNAL_DUMPER_DUMP_FUNCTION_BY_NAME(VCD_SIGNAL_DUMPER_FUNCTIONS_NR_ULSCH_PROCEDURES_RX, 0); } } for (int i = 0; i < gNB->max_nb_srs; i++) { - NR_gNB_SRS_t *srs = gNB->srs[i]; + NR_gNB_SRS_t *srs = &gNB->srs[i]; if (srs) { if ((srs->active == 1) && (srs->frame == frame_rx) && (srs->slot == slot_rx)) { LOG_D(NR_PHY, "(%d.%d) gNB is waiting for SRS, id = %i\n", frame_rx, slot_rx, i); diff --git a/openair1/SIMULATION/NR_PHY/dlschsim.c b/openair1/SIMULATION/NR_PHY/dlschsim.c index 3907f44d4a..cdbe21465f 100644 --- a/openair1/SIMULATION/NR_PHY/dlschsim.c +++ b/openair1/SIMULATION/NR_PHY/dlschsim.c @@ -422,8 +422,8 @@ int main(int argc, char **argv) unsigned char harq_pid = 0; //dlsch->harq_ids[subframe]; processingData_L1tx_t msgDataTx; init_DLSCH_struct(gNB, &msgDataTx); - NR_gNB_DLSCH_t *dlsch = msgDataTx.dlsch[0][0]; - nfapi_nr_dl_tti_pdsch_pdu_rel15_t *rel15 = &dlsch->harq_process.pdsch_pdu.pdsch_pdu_rel15; + NR_gNB_DLSCH_t *dlsch = &msgDataTx.dlsch[0][0]; + nfapi_nr_dl_tti_pdsch_pdu_rel15_t *rel15 = &dlsch->harq_process.pdsch_pdu.pdsch_pdu_rel15; //time_stats_t *rm_stats, *te_stats, *i_stats; unsigned int TBS = 8424; uint8_t nb_re_dmrs = 6; // No data in dmrs symbol diff --git a/openair1/SIMULATION/NR_PHY/dlsim.c b/openair1/SIMULATION/NR_PHY/dlsim.c index 13528aeb6f..26ec99b80e 100644 --- a/openair1/SIMULATION/NR_PHY/dlsim.c +++ b/openair1/SIMULATION/NR_PHY/dlsim.c @@ -983,7 +983,7 @@ int main(int argc, char **argv) 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]; + 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->ack = 0; @@ -1245,8 +1245,10 @@ int main(int argc, char **argv) if (print_perf==1) { printf("\ngNB TX function statistics (per %d us slot, NPRB %d, mcs %d, block %d)\n", - 1000>>*scc->ssbSubcarrierSpacing, g_rbSize, g_mcsIndex, - msgDataTx->dlsch[0][0]->harq_process.pdsch_pdu.pdsch_pdu_rel15.TBSize[0]<<3); + 1000 >> *scc->ssbSubcarrierSpacing, + g_rbSize, + g_mcsIndex, + msgDataTx->dlsch[0][0].harq_process.pdsch_pdu.pdsch_pdu_rel15.TBSize[0] << 3); printDistribution(&gNB->phy_proc_tx,table_tx,"PHY proc tx"); printStatIndent2(&gNB->dlsch_encoding_stats,"DLSCH encoding time"); printStatIndent3(&gNB->dlsch_segmentation_stats,"DLSCH segmentation time"); diff --git a/openair1/SIMULATION/NR_PHY/pucchsim.c b/openair1/SIMULATION/NR_PHY/pucchsim.c index 00441db30b..01eec03c4d 100644 --- a/openair1/SIMULATION/NR_PHY/pucchsim.c +++ b/openair1/SIMULATION/NR_PHY/pucchsim.c @@ -623,7 +623,7 @@ int main(int argc, char **argv) if(format==0){ nfapi_nr_uci_pucch_pdu_format_0_1_t uci_pdu; nfapi_nr_pucch_pdu_t pucch_pdu; - gNB->phy_stats[0].rnti = 0x1234; + gNB->phy_stats[0].rnti = 0x1234; pucch_pdu.rnti = 0x1234; pucch_pdu.subcarrier_spacing = 1; pucch_pdu.group_hop_flag = PUCCH_GroupHopping&1; diff --git a/openair1/SIMULATION/NR_PHY/ulschsim.c b/openair1/SIMULATION/NR_PHY/ulschsim.c index d94cfc778f..344f3f3310 100644 --- a/openair1/SIMULATION/NR_PHY/ulschsim.c +++ b/openair1/SIMULATION/NR_PHY/ulschsim.c @@ -455,7 +455,7 @@ int main(int argc, char **argv) uint8_t rvidx = 0; uint8_t UE_id = 0; - NR_gNB_ULSCH_t *ulsch_gNB = gNB->ulsch[UE_id]; + NR_gNB_ULSCH_t *ulsch_gNB = &gNB->ulsch[UE_id]; NR_UL_gNB_HARQ_t *harq_process_gNB = ulsch_gNB->harq_process; nfapi_nr_pusch_pdu_t *rel15_ul = &harq_process_gNB->ulsch_pdu; @@ -614,7 +614,7 @@ int main(int argc, char **argv) errors_bit = 0; for (i = 0; i < TBS; i++) { - estimated_output_bit[i] = (ulsch_gNB->harq_process->b[i/8] & (1 << (i & 7))) >> (i & 7); + estimated_output_bit[i] = (ulsch_gNB->harq_process->b[i / 8] & (1 << (i & 7))) >> (i & 7); test_input_bit[i] = (test_input[i / 8] & (1 << (i & 7))) >> (i & 7); // Further correct for multiple segments if (estimated_output_bit[i] != test_input_bit[i]) { diff --git a/openair1/SIMULATION/NR_PHY/ulsim.c b/openair1/SIMULATION/NR_PHY/ulsim.c index 805352e3b9..60b8eaca89 100644 --- a/openair1/SIMULATION/NR_PHY/ulsim.c +++ b/openair1/SIMULATION/NR_PHY/ulsim.c @@ -752,9 +752,9 @@ int main(int argc, char **argv) unsigned char harq_pid = 0; - NR_gNB_ULSCH_t *ulsch_gNB = gNB->ulsch[UE_id]; + NR_gNB_ULSCH_t *ulsch_gNB = &gNB->ulsch[UE_id]; - //nfapi_nr_ul_config_ulsch_pdu *rel15_ul = &ulsch_gNB->harq_process->ulsch_pdu; + // nfapi_nr_ul_config_ulsch_pdu *rel15_ul = &ulsch_gNB->harq_process->ulsch_pdu; nfapi_nr_ul_tti_request_t *UL_tti_req = malloc(sizeof(*UL_tti_req)); NR_Sched_Rsp_t *Sched_INFO = malloc(sizeof(*Sched_INFO)); memset((void*)Sched_INFO,0,sizeof(*Sched_INFO)); @@ -1302,140 +1302,196 @@ int main(int argc, char **argv) } } + NR_gNB_PUSCH *pusch_vars = &gNB->pusch_vars[UE_id]; if (n_trials == 1 && round == 0) { __attribute__((unused)) int off = ((nb_rb & 1) == 1) ? 4 : 0; LOG_M("rxsigF0_ext.m", "rxsF0_ext", - &gNB->pusch_vars[0]->rxdataF_ext[0][start_symbol * NR_NB_SC_PER_RB * pusch_pdu->rb_size], + &pusch_vars->rxdataF_ext[0][start_symbol * NR_NB_SC_PER_RB * pusch_pdu->rb_size], nb_symb_sch * (off + (NR_NB_SC_PER_RB * pusch_pdu->rb_size)), 1, 1); - LOG_M("chestF0.m", "chF0", &gNB->pusch_vars[0]->ul_ch_estimates[0][start_symbol * frame_parms->ofdm_symbol_size], frame_parms->ofdm_symbol_size, 1, 1); - LOG_M("chestT0.m", "chT0", &gNB->pusch_vars[0]->ul_ch_estimates_time[0][0], frame_parms->ofdm_symbol_size, 1, 1); + LOG_M("chestF0.m", + "chF0", + &pusch_vars->ul_ch_estimates[0][start_symbol * frame_parms->ofdm_symbol_size], + frame_parms->ofdm_symbol_size, + 1, + 1); + LOG_M("chestT0.m", "chT0", &pusch_vars->ul_ch_estimates_time[0][0], frame_parms->ofdm_symbol_size, 1, 1); LOG_M("chestF0_ext.m", "chF0_ext", - &gNB->pusch_vars[0]->ul_ch_estimates_ext[0][(start_symbol + 1) * (off + (NR_NB_SC_PER_RB * pusch_pdu->rb_size))], + &pusch_vars->ul_ch_estimates_ext[0][(start_symbol + 1) * (off + (NR_NB_SC_PER_RB * pusch_pdu->rb_size))], (nb_symb_sch - 1) * (off + (NR_NB_SC_PER_RB * pusch_pdu->rb_size)), 1, 1); LOG_M("rxsigF0_comp.m", "rxsF0_comp", - &gNB->pusch_vars[0]->rxdataF_comp[0][start_symbol * (off + (NR_NB_SC_PER_RB * pusch_pdu->rb_size))], + &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); LOG_M("chmagF0.m", "chmF0", - &gNB->pusch_vars[0]->ul_ch_mag[0][start_symbol * (off + (NR_NB_SC_PER_RB * pusch_pdu->rb_size))], + &pusch_vars->ul_ch_mag[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); LOG_M("chmagbF0.m", "chmbF0", - &gNB->pusch_vars[0]->ul_ch_magb[0][start_symbol * (off + (NR_NB_SC_PER_RB * pusch_pdu->rb_size))], + &pusch_vars->ul_ch_magb[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); - LOG_M("rxsigF0_llrlayers0.m", "rxsF0_llrlayers0", &gNB->pusch_vars[0]->llr_layers[0][0], (nb_symb_sch - 1) * NR_NB_SC_PER_RB * pusch_pdu->rb_size * mod_order, 1, 0); + LOG_M("rxsigF0_llrlayers0.m", + "rxsF0_llrlayers0", + &pusch_vars->llr_layers[0][0], + (nb_symb_sch - 1) * NR_NB_SC_PER_RB * pusch_pdu->rb_size * mod_order, + 1, + 0); if (precod_nbr_layers == 2) { LOG_M("rxsigF1_ext.m", "rxsF1_ext", - &gNB->pusch_vars[0]->rxdataF_ext[1][start_symbol * NR_NB_SC_PER_RB * pusch_pdu->rb_size], + &pusch_vars->rxdataF_ext[1][start_symbol * NR_NB_SC_PER_RB * pusch_pdu->rb_size], nb_symb_sch * (off + (NR_NB_SC_PER_RB * pusch_pdu->rb_size)), 1, 1); - LOG_M("chestF3.m", "chF3", &gNB->pusch_vars[0]->ul_ch_estimates[3][start_symbol * frame_parms->ofdm_symbol_size], frame_parms->ofdm_symbol_size, 1, 1); + 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); LOG_M("chestF3_ext.m", "chF3_ext", - &gNB->pusch_vars[0]->ul_ch_estimates_ext[3][(start_symbol + 1) * (off + (NR_NB_SC_PER_RB * pusch_pdu->rb_size))], + &pusch_vars->ul_ch_estimates_ext[3][(start_symbol + 1) * (off + (NR_NB_SC_PER_RB * pusch_pdu->rb_size))], (nb_symb_sch - 1) * (off + (NR_NB_SC_PER_RB * pusch_pdu->rb_size)), 1, 1); LOG_M("rxsigF2_comp.m", "rxsF2_comp", - &gNB->pusch_vars[0]->rxdataF_comp[2][start_symbol * (off + (NR_NB_SC_PER_RB * pusch_pdu->rb_size))], + &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); - LOG_M("rxsigF0_llrlayers1.m", "rxsF0_llrlayers1", &gNB->pusch_vars[0]->llr_layers[1][0], (nb_symb_sch - 1) * NR_NB_SC_PER_RB * pusch_pdu->rb_size * mod_order, 1, 0); + LOG_M("rxsigF0_llrlayers1.m", + "rxsF0_llrlayers1", + &pusch_vars->llr_layers[1][0], + (nb_symb_sch - 1) * NR_NB_SC_PER_RB * pusch_pdu->rb_size * mod_order, + 1, + 0); } if (precod_nbr_layers == 4) { LOG_M("rxsigF1_ext.m", "rxsF1_ext", - &gNB->pusch_vars[0]->rxdataF_ext[1][start_symbol * NR_NB_SC_PER_RB * pusch_pdu->rb_size], + &pusch_vars->rxdataF_ext[1][start_symbol * NR_NB_SC_PER_RB * pusch_pdu->rb_size], nb_symb_sch * (off + (NR_NB_SC_PER_RB * pusch_pdu->rb_size)), 1, 1); LOG_M("rxsigF2_ext.m", "rxsF2_ext", - &gNB->pusch_vars[0]->rxdataF_ext[2][start_symbol * NR_NB_SC_PER_RB * pusch_pdu->rb_size], + &pusch_vars->rxdataF_ext[2][start_symbol * NR_NB_SC_PER_RB * pusch_pdu->rb_size], nb_symb_sch * (off + (NR_NB_SC_PER_RB * pusch_pdu->rb_size)), 1, 1); LOG_M("rxsigF3_ext.m", "rxsF3_ext", - &gNB->pusch_vars[0]->rxdataF_ext[3][start_symbol * NR_NB_SC_PER_RB * pusch_pdu->rb_size], + &pusch_vars->rxdataF_ext[3][start_symbol * NR_NB_SC_PER_RB * pusch_pdu->rb_size], nb_symb_sch * (off + (NR_NB_SC_PER_RB * pusch_pdu->rb_size)), 1, 1); - LOG_M("chestF5.m", "chF5", &gNB->pusch_vars[0]->ul_ch_estimates[5][start_symbol * frame_parms->ofdm_symbol_size], frame_parms->ofdm_symbol_size, 1, 1); - LOG_M("chestF10.m", "chF10", &gNB->pusch_vars[0]->ul_ch_estimates[10][start_symbol * frame_parms->ofdm_symbol_size], frame_parms->ofdm_symbol_size, 1, 1); - LOG_M("chestF15.m", "chF15", &gNB->pusch_vars[0]->ul_ch_estimates[15][start_symbol * frame_parms->ofdm_symbol_size], frame_parms->ofdm_symbol_size, 1, 1); + 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); + 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); + 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); LOG_M("chestF5_ext.m", "chF5_ext", - &gNB->pusch_vars[0]->ul_ch_estimates_ext[5][(start_symbol + 1) * (off + (NR_NB_SC_PER_RB * pusch_pdu->rb_size))], + &pusch_vars->ul_ch_estimates_ext[5][(start_symbol + 1) * (off + (NR_NB_SC_PER_RB * pusch_pdu->rb_size))], (nb_symb_sch - 1) * (off + (NR_NB_SC_PER_RB * pusch_pdu->rb_size)), 1, 1); LOG_M("chestF10_ext.m", "chF10_ext", - &gNB->pusch_vars[0]->ul_ch_estimates_ext[10][(start_symbol + 1) * (off + (NR_NB_SC_PER_RB * pusch_pdu->rb_size))], + &pusch_vars->ul_ch_estimates_ext[10][(start_symbol + 1) * (off + (NR_NB_SC_PER_RB * pusch_pdu->rb_size))], (nb_symb_sch - 1) * (off + (NR_NB_SC_PER_RB * pusch_pdu->rb_size)), 1, 1); LOG_M("chestF15_ext.m", "chF15_ext", - &gNB->pusch_vars[0]->ul_ch_estimates_ext[15][(start_symbol + 1) * (off + (NR_NB_SC_PER_RB * pusch_pdu->rb_size))], + &pusch_vars->ul_ch_estimates_ext[15][(start_symbol + 1) * (off + (NR_NB_SC_PER_RB * pusch_pdu->rb_size))], (nb_symb_sch - 1) * (off + (NR_NB_SC_PER_RB * pusch_pdu->rb_size)), 1, 1); LOG_M("rxsigF4_comp.m", "rxsF4_comp", - &gNB->pusch_vars[0]->rxdataF_comp[4][start_symbol * (off + (NR_NB_SC_PER_RB * pusch_pdu->rb_size))], + &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); LOG_M("rxsigF8_comp.m", "rxsF8_comp", - &gNB->pusch_vars[0]->rxdataF_comp[8][start_symbol * (off + (NR_NB_SC_PER_RB * pusch_pdu->rb_size))], + &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); LOG_M("rxsigF12_comp.m", "rxsF12_comp", - &gNB->pusch_vars[0]->rxdataF_comp[12][start_symbol * (off + (NR_NB_SC_PER_RB * pusch_pdu->rb_size))], + &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); - LOG_M("rxsigF0_llrlayers1.m", "rxsF0_llrlayers1", &gNB->pusch_vars[0]->llr_layers[1][0], (nb_symb_sch - 1) * NR_NB_SC_PER_RB * pusch_pdu->rb_size * mod_order, 1, 0); - LOG_M("rxsigF0_llrlayers2.m", "rxsF0_llrlayers2", &gNB->pusch_vars[0]->llr_layers[2][0], (nb_symb_sch - 1) * NR_NB_SC_PER_RB * pusch_pdu->rb_size * mod_order, 1, 0); - LOG_M("rxsigF0_llrlayers3.m", "rxsF0_llrlayers3", &gNB->pusch_vars[0]->llr_layers[3][0], (nb_symb_sch - 1) * NR_NB_SC_PER_RB * pusch_pdu->rb_size * mod_order, 1, 0); + LOG_M("rxsigF0_llrlayers1.m", + "rxsF0_llrlayers1", + &pusch_vars->llr_layers[1][0], + (nb_symb_sch - 1) * NR_NB_SC_PER_RB * pusch_pdu->rb_size * mod_order, + 1, + 0); + LOG_M("rxsigF0_llrlayers2.m", + "rxsF0_llrlayers2", + &pusch_vars->llr_layers[2][0], + (nb_symb_sch - 1) * NR_NB_SC_PER_RB * pusch_pdu->rb_size * mod_order, + 1, + 0); + LOG_M("rxsigF0_llrlayers3.m", + "rxsF0_llrlayers3", + &pusch_vars->llr_layers[3][0], + (nb_symb_sch - 1) * NR_NB_SC_PER_RB * pusch_pdu->rb_size * mod_order, + 1, + 0); } - LOG_M("rxsigF0_llr.m", "rxsF0_llr", &gNB->pusch_vars[0]->llr[0], precod_nbr_layers * (nb_symb_sch - 1) * NR_NB_SC_PER_RB * pusch_pdu->rb_size * mod_order, 1, 0); + LOG_M("rxsigF0_llr.m", + "rxsF0_llr", + &pusch_vars->llr[0], + precod_nbr_layers * (nb_symb_sch - 1) * NR_NB_SC_PER_RB * pusch_pdu->rb_size * mod_order, + 1, + 0); } - if ((gNB->ulsch[0]->last_iteration_cnt >= gNB->ulsch[0]->max_ldpc_iterations + 1) || ul_proc_error == 1) { + if ((ulsch_gNB->last_iteration_cnt >= ulsch_gNB->max_ldpc_iterations + 1) || ul_proc_error == 1) { error_flag = 1; n_errors[round]++; crc_status = 1; @@ -1451,7 +1507,7 @@ int main(int argc, char **argv) if ((pusch_pdu->pdu_bit_map & PUSCH_PDU_BITMAP_PUSCH_PTRS) && (SNR == snr0) && (trial == 0) && (round == 0)) { ptrs_symbols = 0; for (int i = pusch_pdu->start_symbol_index; i < pusch_pdu->start_symbol_index + pusch_pdu->nr_of_symbols; i++) - ptrs_symbols += ((gNB->pusch_vars[UE_id]->ptrs_symbols) >> i) & 1; + ptrs_symbols += ((pusch_vars->ptrs_symbols) >> i) & 1; /* 2*5*(50/2), for RB = 50,K = 2 for 5 OFDM PTRS symbols */ available_bits -= 2 * ptrs_symbols * ((nb_rb + ptrs_freq_density - 1) / ptrs_freq_density); @@ -1459,8 +1515,8 @@ int main(int argc, char **argv) } for (i = 0; i < available_bits; i++) { - if (((UE->ul_harq_processes[harq_pid].f[i] == 0) && (gNB->pusch_vars[UE_id]->llr[i] <= 0)) - || ((UE->ul_harq_processes[harq_pid].f[i] == 1) && (gNB->pusch_vars[UE_id]->llr[i] >= 0))) { + if (((UE->ul_harq_processes[harq_pid].f[i] == 0) && (pusch_vars->llr[i] <= 0)) + || ((UE->ul_harq_processes[harq_pid].f[i] == 1) && (pusch_vars->llr[i] >= 0))) { /*if(errors_scrambling == 0) printf("\x1B[34m" "[frame %d][trial %d]\t1st bit in error in unscrambling = %d\n" "\x1B[0m", frame, trial, i);*/ errors_scrambling[round]++; @@ -1474,7 +1530,6 @@ int main(int argc, char **argv) } for (i = 0; i < TBS; i++) { - estimated_output_bit[i] = (ulsch_gNB->harq_process->b[i / 8] & (1 << (i & 7))) >> (i & 7); test_input_bit[i] = (UE->ul_harq_processes[harq_pid].b[i / 8] & (1 << (i & 7))) >> (i & 7); diff --git a/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_primitives.c b/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_primitives.c index 1f86cf38c5..09d4d95458 100644 --- a/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_primitives.c +++ b/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_primitives.c @@ -1981,7 +1981,6 @@ int get_nrofHARQ_ProcessesForPDSCH(e_NR_PDSCH_ServingCellConfig__nrofHARQ_Proces void delete_nr_ue_data(NR_UE_info_t *UE, NR_COMMON_channels_t *ccPtr, uid_allocator_t *uia) { - NR_UE_sched_ctrl_t *sched_ctrl = &UE->UE_sched_ctrl; destroy_nr_list(&sched_ctrl->available_dl_harq); destroy_nr_list(&sched_ctrl->feedback_dl_harq); From d664190f9dbe79d654d676d6748e3c5ea1210919 Mon Sep 17 00:00:00 2001 From: francescomani Date: Mon, 20 Mar 2023 11:15:57 +0100 Subject: [PATCH 11/89] scope compilation fixes --- openair1/PHY/TOOLS/nr_phy_qt_scope.cpp | 6 +++--- openair1/PHY/TOOLS/nr_phy_scope.c | 24 +++++++++++------------- 2 files changed, 14 insertions(+), 16 deletions(-) diff --git a/openair1/PHY/TOOLS/nr_phy_qt_scope.cpp b/openair1/PHY/TOOLS/nr_phy_qt_scope.cpp index 04e42873ea..fecfefd131 100644 --- a/openair1/PHY/TOOLS/nr_phy_qt_scope.cpp +++ b/openair1/PHY/TOOLS/nr_phy_qt_scope.cpp @@ -697,7 +697,7 @@ void PainterWidgetGnb::makeConnections(int type) break; } case PlotTypeGnb::CIR: { - newChart = new CIRPlot((complex16 *)p->gNB->pusch_vars[0]->ul_ch_estimates_time[0], frame_parms->ofdm_symbol_size); + newChart = new CIRPlot((complex16 *)p->gNB->pusch_vars[0].ul_ch_estimates_time[0], frame_parms->ofdm_symbol_size); break; } @@ -705,12 +705,12 @@ void PainterWidgetGnb::makeConnections(int type) int num_re = frame_parms->N_RB_UL * 12 * frame_parms->symbols_per_slot; int Qm = 2; int coded_bits_per_codeword = num_re * Qm; - newChart = new LLRPlot((int16_t *)p->gNB->pusch_vars[0]->llr, coded_bits_per_codeword); + newChart = new LLRPlot((int16_t *)p->gNB->pusch_vars[0].llr, coded_bits_per_codeword); break; } case PlotTypeGnb::puschIQ: { int num_re = frame_parms->N_RB_UL * 12 * frame_parms->symbols_per_slot; - newChart = new IQPlot((complex16 *)p->gNB->pusch_vars[0]->rxdataF_comp[0], num_re); + newChart = new IQPlot((complex16 *)p->gNB->pusch_vars[0].rxdataF_comp[0], num_re); break; } case PlotTypeGnb::puschSNR: { diff --git a/openair1/PHY/TOOLS/nr_phy_scope.c b/openair1/PHY/TOOLS/nr_phy_scope.c index 5a4562cb58..206ed490e5 100644 --- a/openair1/PHY/TOOLS/nr_phy_scope.c +++ b/openair1/PHY/TOOLS/nr_phy_scope.c @@ -473,10 +473,10 @@ static void timeResponse (OAIgraph_t *graph, scopeData_t *p, int nb_UEs) { int uestart = 0; // xforms scope designed to display nb_UEs signals #endif for (int ue = uestart; ue < nb_UEs; ue++) { - if ( p->gNB->pusch_vars && p->gNB->pusch_vars[ue] && - p->gNB->pusch_vars[ue]->ul_ch_estimates_time && - p->gNB->pusch_vars[ue]->ul_ch_estimates_time[ant] ) { - scopeSample_t *data= (scopeSample_t *)p->gNB->pusch_vars[ue]->ul_ch_estimates_time[ant]; + if (p->gNB->pusch_vars && + p->gNB->pusch_vars[ue].ul_ch_estimates_time && + p->gNB->pusch_vars[ue].ul_ch_estimates_time[ant] ) { + scopeSample_t *data= (scopeSample_t *)p->gNB->pusch_vars[ue].ul_ch_estimates_time[ant]; if (data != NULL) { for (int i=0; igNB->pusch_vars && - p->gNB->pusch_vars[ue] && - p->gNB->pusch_vars[ue]->llr ) { - int16_t *pusch_llr = (int16_t *)p->gNB->pusch_vars[ue]->llr; + if (p->gNB->pusch_vars && + p->gNB->pusch_vars[ue].llr ) { + int16_t *pusch_llr = (int16_t *)p->gNB->pusch_vars[ue].llr; float *llr=NULL, *bit=NULL; int nx = coded_bits_per_codeword; #ifdef WEBSRVSCOPE @@ -557,11 +556,10 @@ static void puschIQ (OAIgraph_t *graph, scopeData_t *p, int nb_UEs) { int uestart = 0; // xforms scope designed to display nb_UEs signals #endif for (int ue = uestart; ue < nb_UEs; ue++) { - if ( p->gNB->pusch_vars && - p->gNB->pusch_vars[ue] && - p->gNB->pusch_vars[ue]->rxdataF_comp && - p->gNB->pusch_vars[ue]->rxdataF_comp[0] ) { - scopeSample_t *pusch_comp = (scopeSample_t *)p->gNB->pusch_vars[ue]->rxdataF_comp[0]; + if (p->gNB->pusch_vars && + p->gNB->pusch_vars[ue].rxdataF_comp && + p->gNB->pusch_vars[ue].rxdataF_comp[0] ) { + scopeSample_t *pusch_comp = (scopeSample_t *)p->gNB->pusch_vars[ue].rxdataF_comp[0]; float *I=NULL, *Q=NULL; #ifdef WEBSRVSCOPE newsz = websrv_cpiqbuff_tomsg(graph, pusch_comp, sz, 0, 0); From 47ea8d79a202be294feec757289b14f495a2c4cd Mon Sep 17 00:00:00 2001 From: Robert Schmidt Date: Fri, 24 Mar 2023 15:48:46 +0100 Subject: [PATCH 12/89] Free memory correctly --- openair1/PHY/INIT/nr_init.c | 2 +- openair1/PHY/NR_TRANSPORT/nr_ulsch_decoding.c | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/openair1/PHY/INIT/nr_init.c b/openair1/PHY/INIT/nr_init.c index 1fa946b476..36561b110c 100644 --- a/openair1/PHY/INIT/nr_init.c +++ b/openair1/PHY/INIT/nr_init.c @@ -885,7 +885,6 @@ void phy_free_nr_gNB(PHY_VARS_gNB *gNB) free_and_zero(pusch_vars->rho); free_and_zero(pusch_vars->llr); - free_and_zero(pusch_vars); } // ULSCH_id free(gNB->pusch_vars); } @@ -1117,4 +1116,5 @@ void reset_nr_transport(PHY_VARS_gNB *gNB) for (int i = 0; i < gNB->max_nb_pusch; i++) free_gNB_ulsch(&gNB->ulsch[i], fp->N_RB_UL); + free(gNB->ulsch); } diff --git a/openair1/PHY/NR_TRANSPORT/nr_ulsch_decoding.c b/openair1/PHY/NR_TRANSPORT/nr_ulsch_decoding.c index 99db753058..be7de7baea 100644 --- a/openair1/PHY/NR_TRANSPORT/nr_ulsch_decoding.c +++ b/openair1/PHY/NR_TRANSPORT/nr_ulsch_decoding.c @@ -79,7 +79,6 @@ void free_gNB_ulsch(NR_gNB_ULSCH_t *ulsch, uint16_t N_RB_UL) free_and_zero(ulsch->harq_process); ulsch->harq_process = NULL; } - free_and_zero(ulsch); } NR_gNB_ULSCH_t new_gNB_ulsch(uint8_t max_ldpc_iterations, uint16_t N_RB_UL) From a458a251a029340f1b8a167fed28a4e1e7202df7 Mon Sep 17 00:00:00 2001 From: Robert Schmidt Date: Mon, 6 Mar 2023 10:59:35 +0100 Subject: [PATCH 13/89] PDCP: harmonize DL RRC Message transfer --- openair2/LAYER2/nr_pdcp/nr_pdcp_oai_api.c | 41 ++++++++--------------- 1 file changed, 14 insertions(+), 27 deletions(-) diff --git a/openair2/LAYER2/nr_pdcp/nr_pdcp_oai_api.c b/openair2/LAYER2/nr_pdcp/nr_pdcp_oai_api.c index e4bb7bb4f5..702aabd528 100644 --- a/openair2/LAYER2/nr_pdcp/nr_pdcp_oai_api.c +++ b/openair2/LAYER2/nr_pdcp/nr_pdcp_oai_api.c @@ -772,33 +772,20 @@ srb_found: LOG_D(PDCP, "%s(): (srb %d) calling rlc_data_req size %d\n", __func__, srb_id, size); //for (i = 0; i < size; i++) printf(" %2.2x", (unsigned char)memblock->data[i]); //printf("\n"); - if ((RC.nrrrc == NULL) || (!NODE_IS_CU(node_type))) { - if (entity->is_gnb) { - f1ap_dl_rrc_message_t dl_rrc = {.old_gNB_DU_ue_id = 0xFFFFFF, .rrc_container = (uint8_t *)buf, .rrc_container_length = size, .rnti = ue->rntiMaybeUEid, .srb_id = DCCH}; - gNB_RRC_INST *rrc = RC.nrrrc[0]; - rrc->mac_rrc.dl_rrc_message_transfer(0, &dl_rrc); - } else { // UE - mem_block_t *memblock; - protocol_ctxt_t ctxt = {.module_id = 0, .enb_flag = 1, .instance = 0, .frame = 0, .subframe = 0, .eNB_index = 0, .brOption = 0, .rntiMaybeUEid = ue->rntiMaybeUEid}; - memblock = get_free_mem_block(size, __FUNCTION__); - memcpy(memblock->data, buf, size); - enqueue_rlc_data_req(&ctxt, 1, MBMS_FLAG_NO, srb_id, sdu_id, 0, size, memblock); - } - } else { - MessageDef *message_p = itti_alloc_new_message (TASK_RRC_GNB, 0, F1AP_DL_RRC_MESSAGE); - uint8_t *message_buffer = itti_malloc (TASK_RRC_GNB, TASK_CU_F1, size); - memcpy (message_buffer, buf, size); - F1AP_DL_RRC_MESSAGE (message_p).rrc_container = message_buffer; - F1AP_DL_RRC_MESSAGE (message_p).rrc_container_length = size; - F1AP_DL_RRC_MESSAGE (message_p).gNB_CU_ue_id = 0; - F1AP_DL_RRC_MESSAGE (message_p).gNB_DU_ue_id = 0; - F1AP_DL_RRC_MESSAGE (message_p).old_gNB_DU_ue_id = 0xFFFFFFFF; // unknown - F1AP_DL_RRC_MESSAGE(message_p).rnti = ue->rntiMaybeUEid; - F1AP_DL_RRC_MESSAGE (message_p).srb_id = srb_id; - F1AP_DL_RRC_MESSAGE (message_p).execute_duplication = 1; - F1AP_DL_RRC_MESSAGE (message_p).RAT_frequency_priority_information.en_dc = 0; - itti_send_msg_to_task (TASK_CU_F1, 0, message_p); - LOG_D(PDCP, "Send F1AP_DL_RRC_MESSAGE with ITTI\n"); + if (entity->is_gnb) { + f1ap_dl_rrc_message_t dl_rrc = {.old_gNB_DU_ue_id = 0xFFFFFF, + .rrc_container = (uint8_t *)buf, + .rrc_container_length = size, + .rnti = ue->rntiMaybeUEid, + .srb_id = srb_id}; + gNB_RRC_INST *rrc = RC.nrrrc[0]; + rrc->mac_rrc.dl_rrc_message_transfer(0, &dl_rrc); + } else { // UE + mem_block_t *memblock; + protocol_ctxt_t ctxt = {.module_id = 0, .enb_flag = 1, .instance = 0, .frame = 0, .subframe = 0, .eNB_index = 0, .brOption = 0, .rntiMaybeUEid = ue->rntiMaybeUEid}; + memblock = get_free_mem_block(size, __FUNCTION__); + memcpy(memblock->data, buf, size); + enqueue_rlc_data_req(&ctxt, 1, MBMS_FLAG_NO, srb_id, sdu_id, 0, size, memblock); } } From 2ac2a7f0745f59dafe695dd215a9fa3939168f4e Mon Sep 17 00:00:00 2001 From: Robert Schmidt Date: Sat, 18 Mar 2023 16:50:32 +0100 Subject: [PATCH 14/89] Make separate header for nr_pdcp instead of using 4G pdcp Creates function that have nr_pdcp_ prepended to their name, and change the signature of some functions for the 5G PDCP: - nr_pdcp_layer_init() - nr_pdcp_data_req() - nr_pdcp_remove_UE() - nr_pdcp_config_set_security() - nr_pdcp_remove_UE() - Make separate functions for nr_pdcp_data_req_srb/drb() --- doc/Doxyfile | 1 + doc/SW_archi.md | 19 +-- executables/lte-softmodem.c | 15 +++ executables/nr-cuup.c | 4 +- executables/nr-softmodem.c | 11 +- executables/nr-ue.c | 6 +- executables/nr-uesoftmodem.c | 4 +- openair2/F1AP/f1ap_cu_rrc_message_transfer.c | 2 +- openair2/F1AP/f1ap_cu_ue_context_management.c | 4 +- openair2/GNB_APP/gnb_app.c | 2 +- openair2/GNB_APP/gnb_config.c | 4 +- openair2/LAYER2/MAC/eNB_scheduler.c | 2 +- openair2/LAYER2/NR_MAC_UE/main_ue_nr.c | 4 +- openair2/LAYER2/NR_MAC_gNB/gNB_scheduler.c | 5 +- .../LAYER2/NR_MAC_gNB/gNB_scheduler_bch.c | 2 - openair2/LAYER2/NR_MAC_gNB/main.c | 5 +- openair2/LAYER2/PDCP_v10.1.0/pdcp.h | 2 - openair2/LAYER2/nr_pdcp/asn1_utils.c | 2 +- openair2/LAYER2/nr_pdcp/nr_pdcp_e1_api.h | 2 +- openair2/LAYER2/nr_pdcp/nr_pdcp_oai_api.c | 127 +++++++++--------- openair2/LAYER2/nr_pdcp/nr_pdcp_oai_api.h | 103 ++++++++++++++ openair2/LAYER2/nr_rlc/nr_rlc_oai_api.c | 2 +- openair2/LAYER2/openair2_proc.c | 2 +- openair2/RRC/NR/L2_nr_interface.c | 4 +- openair2/RRC/NR/nr_rrc_common.c | 1 - openair2/RRC/NR/rrc_gNB.c | 32 ++--- openair2/RRC/NR/rrc_gNB_NGAP.c | 13 +- openair2/RRC/NR/rrc_gNB_nsa.c | 4 +- openair2/RRC/NR_UE/rrc_UE.c | 20 +-- openair2/SDAP/nr_sdap/nr_sdap_entity.c | 44 +++--- openair3/ocp-gtpu/gtp_itf.cpp | 3 +- 31 files changed, 269 insertions(+), 182 deletions(-) create mode 100644 openair2/LAYER2/nr_pdcp/nr_pdcp_oai_api.h diff --git a/doc/Doxyfile b/doc/Doxyfile index 6a4dd850a3..afb89b43ef 100644 --- a/doc/Doxyfile +++ b/doc/Doxyfile @@ -1340,6 +1340,7 @@ INPUT = \ @CMAKE_CURRENT_SOURCE_DIR@/../openair2/LAYER2/NR_MAC_UE/nr_ue_dci_configuration.c \ @CMAKE_CURRENT_SOURCE_DIR@/../openair2/LAYER2/NR_MAC_UE/mac_extern.h \ @CMAKE_CURRENT_SOURCE_DIR@/../openair2/LAYER2/nr_pdcp/nr_pdcp_oai_api.c \ +@CMAKE_CURRENT_SOURCE_DIR@/../openair2/LAYER2/nr_pdcp/nr_pdcp_oai_api.h \ @CMAKE_CURRENT_SOURCE_DIR@/../openair2/LAYER2/nr_pdcp/nr_pdcp_entity.h \ @CMAKE_CURRENT_SOURCE_DIR@/../openair2/LAYER2/nr_pdcp/nr_pdcp_timer_thread.h \ @CMAKE_CURRENT_SOURCE_DIR@/../openair2/LAYER2/nr_pdcp/nr_pdcp_integrity_nia2.h \ diff --git a/doc/SW_archi.md b/doc/SW_archi.md index 7f16edab11..1e8e5cd011 100644 --- a/doc/SW_archi.md +++ b/doc/SW_archi.md @@ -327,25 +327,26 @@ successful_delivery() and max_retx_reached(): in ??? trigger, the RLC sends a it The PDCP implementation is also protected through a general mutex. The design is very similar to rlc layer. The pdcp data is isolated and encapsulated. -pdcp_layer_init(): same as rlc init +nr_pdcp_layer_init(): same as rlc init we have to call a second init function: pdcp_module_init() -At Tx side (DL in gNB), pdcp_data_req() is the entry function that the upper layer calls. +At Tx side (DL in gNB), `pdcp_data_req_drb()` and `pdcp_data_req_srb()` are the entry functions that the upper layer calls. The upper layer can be GTP or a PDCP internal thread enb_tun_read_thread() that read directly from Linux socket in case we skip 3GPP core implementation. -PDCP internals for pdcp_data_req() is thread safe: inside pdcp_data_req_drb(), the pdcp manager protects with the mutex the access to the SDU receiving function of PDCP (recv_sdu() callback, corresponding to nr_pdcp_entity_drb_am_recv_sdu() for DRBs). When it needs, the pdcp layer push this data to rlc by calling : rlc_data_req() +PDCP internals for nr_pdcp_data_req_srb()/nr_pdcp_data_req_drb() are thread safe: inside them, the pdcp manager protects with the mutex the access to the SDU receiving function of PDCP (recv_sdu() callback, corresponding to nr_pdcp_entity_drb_am_recv_sdu() for DRBs). When it needs, the pdcp layer push this data to rlc by calling : rlc_data_req() -Also, incoming downlink sdu can comme from internal RRC: in this case, pdcp_run() reads a itti queue, for message RRC_DCCH_DATA_REQ, to0 only call 'pdcp_data_req()' +Also, incoming downlink sdu can comme from internal RRC: in this case, nr +nr_pdcp_run() reads a itti queue, for message RRC_DCCH_DATA_REQ, to0 only call 'nr_pdcp_data_req_srb()' At Rx side, pdcp_data_ind() is the entry point that receives the data from RLC. - Inside pdcp_data_ind(), the pdcp manager mutex protects the access to the PDU receiving function of PDCP (recv_pdu() callback corresponding to nr_pdcp_entity_drb_am_recv_pdu() for DRBs) - Then deliver_sdu_drb() function sends the received data to GTP thread through an ITTI message (GTPV1U_TUNNEL_DATA_REQ). -pdcp_config_set_security(): not yet developped +nr_pdcp_config_set_security(): sets the keys for AS security of a UE -nr_DRB_preconfiguration(): the mac layer calls this for ??? +nr_DRB_preconfiguration(): the mac layer calls this for configuration in phy-test/do-ra mode -nr_pdcp_add_srbs() adds UE SRBs in pdcp, pdcp_remove_UE() removes it -nr_pdcp_add_drbs() adds UE DRBs in pdcp, pdcp_remove_UE() removes it +nr_pdcp_add_srbs() adds UE SRBs in pdcp, nr_pdcp_remove_UE() removes it +nr_pdcp_add_drbs() adds UE DRBs in pdcp, nr_pdcp_remove_UE() removes it # GTP Gtp + UDP are two twin threads performing the data plane interface to the core network @@ -357,7 +358,7 @@ PDCP layer push to the GTP queue (outside UDP thread that do almost nothing and ## GTP thread running code from other layers -gtp thread calls directly pdcp_data_req(), so it runs inside it's context internal pdcp structures updates +gtp thread calls directly nr_pdcp_data_req_drb(), so it runs inside it's context internal pdcp structures updates ## inside other threads gtpv1u_create_s1u_tunnel(), delete tunnel, ... functions are called inside the other threads, without mutex. diff --git a/executables/lte-softmodem.c b/executables/lte-softmodem.c index 6d75f0acbd..0c04456e27 100644 --- a/executables/lte-softmodem.c +++ b/executables/lte-softmodem.c @@ -181,6 +181,21 @@ bool sdap_data_req(protocol_ctxt_t *ctxt_p, abort(); } +/* hack: gtp_itf.cpp requires this empty function to be defined here */ +bool nr_pdcp_data_req_drb(protocol_ctxt_t *ctxt_pP, + const srb_flag_t srb_flagP, + const rb_id_t rb_id, + const mui_t muiP, + const confirm_t confirmP, + const sdu_size_t sdu_buffer_size, + unsigned char *const sdu_buffer, + const pdcp_transmission_mode_t mode, + const uint32_t *const sourceL2Id, + const uint32_t *const destinationL2Id) +{ + abort(); +} + /* forward declarations */ void set_default_frame_parms(LTE_DL_FRAME_PARMS *frame_parms[MAX_NUM_CCs]); diff --git a/executables/nr-cuup.c b/executables/nr-cuup.c index 25ebe6df13..34eae85810 100644 --- a/executables/nr-cuup.c +++ b/executables/nr-cuup.c @@ -28,7 +28,7 @@ #include "nfapi/oai_integration/vendor_ext.h" #include "openair2/F1AP/f1ap_common.h" #include "openair2/GNB_APP/gnb_config.h" -#include "pdcp.h" +#include "nr_pdcp/nr_pdcp_oai_api.h" RAN_CONTEXT_t RC; THREAD_STRUCT thread_struct; @@ -147,7 +147,7 @@ int main(int argc, char **argv) AssertFatal(rc >= 0, "Create task for GTPV1U failed\n"); rc = itti_create_task(TASK_CUUP_E1, E1AP_CUUP_task, NULL); AssertFatal(rc >= 0, "Create task for CUUP E1 failed\n"); - pdcp_layer_init(); + nr_pdcp_layer_init(); MessageDef *msg = RCconfig_NR_CU_E1(true); AssertFatal(msg != NULL, "Send init to task for E1AP UP failed\n"); itti_send_msg_to_task(TASK_CUUP_E1, 0, msg); diff --git a/executables/nr-softmodem.c b/executables/nr-softmodem.c index 120a7c0e65..455bf83904 100644 --- a/executables/nr-softmodem.c +++ b/executables/nr-softmodem.c @@ -59,7 +59,7 @@ unsigned short config_frames[4] = {2,9,11,13}; #include "common/utils/LOG/log.h" #include "common/utils/LOG/vcd_signal_dumper.h" #include "UTIL/OPT/opt.h" -#include "pdcp.h" +#include "LAYER2/nr_pdcp/nr_pdcp_oai_api.h" #include "intertask_interface.h" @@ -556,13 +556,8 @@ void init_pdcp(void) { PDCP_USE_NETLINK_BIT | LINK_ENB_PDCP_TO_IP_DRIVER_BIT | ENB_NAS_USE_TUN_BIT | SOFTMODEM_NOKRNMOD_BIT: LINK_ENB_PDCP_TO_GTPV1U_BIT; - if (!get_softmodem_params()->nsa) { - if (!NODE_IS_DU(get_node_type())) { - pdcp_layer_init(); - nr_pdcp_module_init(pdcp_initmask, 0); - } - } else { - pdcp_layer_init(); + if (!NODE_IS_DU(get_node_type())) { + nr_pdcp_layer_init(); nr_pdcp_module_init(pdcp_initmask, 0); } } diff --git a/executables/nr-ue.c b/executables/nr-ue.c index 075789e12b..a1ec29a42a 100644 --- a/executables/nr-ue.c +++ b/executables/nr-ue.c @@ -33,7 +33,7 @@ #include "executables/softmodem-common.h" #include "PHY/NR_REFSIG/refsig_defs_ue.h" #include "radio/COMMON/common_lib.h" -#include "pdcp.h" +#include "LAYER2/nr_pdcp/nr_pdcp_oai_api.h" /* * NR SLOT PROCESSING SEQUENCE @@ -359,7 +359,7 @@ static void *NRUE_phy_stub_standalone_pnf_task(void *arg) NR_UE_MAC_INST_t *mac = get_mac_inst(0); protocol_ctxt_t ctxt; PROTOCOL_CTXT_SET_BY_MODULE_ID(&ctxt, 0, ENB_FLAG_NO, mac->crnti, frame, slot, 0); - pdcp_run(&ctxt); + nr_pdcp_run(&ctxt); } process_queued_nr_nfapi_msgs(mac, sfn_slot); } @@ -627,7 +627,7 @@ void UE_processing(nr_rxtx_thread_data_t *rxtxD) { NR_UE_MAC_INST_t *mac = get_mac_inst(0); protocol_ctxt_t ctxt; PROTOCOL_CTXT_SET_BY_MODULE_ID(&ctxt, UE->Mod_id, ENB_FLAG_NO, mac->crnti, proc->frame_rx, proc->nr_slot_rx, 0); - pdcp_run(&ctxt); + nr_pdcp_run(&ctxt); } } diff --git a/executables/nr-uesoftmodem.c b/executables/nr-uesoftmodem.c index eb6e160ec3..7169881ed0 100644 --- a/executables/nr-uesoftmodem.c +++ b/executables/nr-uesoftmodem.c @@ -59,7 +59,7 @@ unsigned short config_frames[4] = {2,9,11,13}; #include "UTIL/OPT/opt.h" #include "enb_config.h" -#include "pdcp.h" +#include "LAYER2/nr_pdcp/nr_pdcp_oai_api.h" #include "intertask_interface.h" @@ -386,7 +386,7 @@ static void init_pdcp(int ue_id) { if (get_softmodem_params()->nsa && rlc_module_init(0) != 0) { LOG_I(RLC, "Problem at RLC initiation \n"); } - pdcp_layer_init(); + nr_pdcp_layer_init(); nr_pdcp_module_init(pdcp_initmask, ue_id); pdcp_set_rlc_data_req_func((send_rlc_data_req_func_t) rlc_data_req); pdcp_set_pdcp_data_ind_func((pdcp_data_ind_func_t) pdcp_data_ind); diff --git a/openair2/F1AP/f1ap_cu_rrc_message_transfer.c b/openair2/F1AP/f1ap_cu_rrc_message_transfer.c index f1e0a7ade1..7fc30967de 100644 --- a/openair2/F1AP/f1ap_cu_rrc_message_transfer.c +++ b/openair2/F1AP/f1ap_cu_rrc_message_transfer.c @@ -37,7 +37,7 @@ #include "f1ap_cu_rrc_message_transfer.h" #include "common/ran_context.h" #include "openair3/UTILS/conversions.h" -#include "pdcp.h" +#include "LAYER2/nr_pdcp/nr_pdcp_oai_api.h" /* Initial UL RRC Message Transfer diff --git a/openair2/F1AP/f1ap_cu_ue_context_management.c b/openair2/F1AP/f1ap_cu_ue_context_management.c index 0cb476b2d7..bfa48a4f88 100644 --- a/openair2/F1AP/f1ap_cu_ue_context_management.c +++ b/openair2/F1AP/f1ap_cu_ue_context_management.c @@ -40,7 +40,7 @@ #include "rrc_extern.h" #include "openair2/RRC/NR/rrc_gNB_NGAP.h" #include -#include "pdcp.h" +#include "LAYER2/nr_pdcp/nr_pdcp_oai_api.h" static void setQos(F1AP_NonDynamic5QIDescriptor_t *toFill) { asn1cCalloc(toFill, tmp); @@ -958,7 +958,7 @@ int CU_handle_UE_CONTEXT_RELEASE_COMPLETE(instance_t instance, LOG_E(F1AP, "could not find ue_context of UE RNTI %x\n", rnti); } - pdcp_remove_UE(&ctxt); + nr_pdcp_remove_UE(ctxt.rntiMaybeUEid); LOG_I(F1AP, "Received UE CONTEXT RELEASE COMPLETE: Removing CU UE entry for RNTI %x\n", rnti); f1ap_remove_ue(CUtype, instance, rnti); diff --git a/openair2/GNB_APP/gnb_app.c b/openair2/GNB_APP/gnb_app.c index 6404b26418..84cbf0d424 100644 --- a/openair2/GNB_APP/gnb_app.c +++ b/openair2/GNB_APP/gnb_app.c @@ -49,7 +49,7 @@ #include "f1ap_du_task.h" #include "nfapi/oai_integration/vendor_ext.h" #include -#include "openair2/LAYER2/PDCP_v10.1.0/pdcp.h" +#include "openair2/LAYER2/nr_pdcp/nr_pdcp_oai_api.h" #include "openair2/E1AP/e1ap.h" #include "gnb_config.h" extern unsigned char NB_gNB_INST; diff --git a/openair2/GNB_APP/gnb_config.c b/openair2/GNB_APP/gnb_config.c index 46044ca6df..97eae39842 100644 --- a/openair2/GNB_APP/gnb_config.c +++ b/openair2/GNB_APP/gnb_config.c @@ -54,7 +54,7 @@ #include "radio/ETHERNET/USERSPACE/LIB/ethernet_lib.h" #include "nfapi_vnf.h" #include "nfapi_pnf.h" -#include "pdcp.h" +#include "nr_pdcp/nr_pdcp_oai_api.h" //#include "L1_paramdef.h" #include "prs_nr_paramdef.h" @@ -2244,6 +2244,6 @@ void nr_read_config_and_init(void) { } if (NODE_IS_CU(RC.nrrrc[0]->node_type) && RC.nrrrc[0]->node_type != ngran_gNB_CUCP) { - pdcp_layer_init(); + nr_pdcp_layer_init(); } } diff --git a/openair2/LAYER2/MAC/eNB_scheduler.c b/openair2/LAYER2/MAC/eNB_scheduler.c index badcd36cc6..e56b3d4f6f 100644 --- a/openair2/LAYER2/MAC/eNB_scheduler.c +++ b/openair2/LAYER2/MAC/eNB_scheduler.c @@ -958,7 +958,7 @@ eNB_dlsch_ulsch_scheduler(module_id_t module_idP, void rlc_tick(int, int); PROTOCOL_CTXT_SET_BY_MODULE_ID(&ctxt, module_idP, ENB_FLAG_YES, NOT_A_RNTI, frameP, subframeP, module_idP); rlc_tick(frameP, subframeP); - pdcp_run(&ctxt); + //pdcp_run(&ctxt); pdcp_mbms_run(&ctxt); rrc_rx_tx(&ctxt, CC_id); #endif diff --git a/openair2/LAYER2/NR_MAC_UE/main_ue_nr.c b/openair2/LAYER2/NR_MAC_UE/main_ue_nr.c index 7fb2295f2d..2babe56ec8 100644 --- a/openair2/LAYER2/NR_MAC_UE/main_ue_nr.c +++ b/openair2/LAYER2/NR_MAC_UE/main_ue_nr.c @@ -38,7 +38,7 @@ #include "openair2/LAYER2/nr_pdcp/nr_pdcp_entity.h" #include "executables/softmodem-common.h" #include "openair2/LAYER2/nr_pdcp/nr_pdcp.h" -#include "openair2/LAYER2/PDCP_v10.1.0/pdcp.h" +#include "openair2/LAYER2/nr_pdcp/nr_pdcp_oai_api.h" #include static NR_UE_MAC_INST_t *nr_ue_mac_inst; @@ -65,7 +65,7 @@ NR_UE_MAC_INST_t * nr_l2_init_ue(NR_UE_RRC_INST_t* rrc_inst) { nr_rrc_mac_config_req_ue(0,0,0,NULL,NULL,NULL,rrc_inst->scell_group_config); AssertFatal(rlc_module_init(0) == 0, "%s: Could not initialize RLC layer\n", __FUNCTION__); if (IS_SOFTMODEM_NOS1){ - pdcp_layer_init(); + nr_pdcp_layer_init(); nr_DRB_preconfiguration(nr_ue_mac_inst->crnti); nr_ue_mac_inst->logicalChannelBearer_exist[0] = true; } diff --git a/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler.c b/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler.c index c641bf4d15..4a5b4cc626 100644 --- a/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler.c +++ b/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler.c @@ -42,8 +42,7 @@ #include "RRC/NR/nr_rrc_extern.h" -//#include "LAYER2/MAC/pre_processor.c" -#include "pdcp.h" +#include "nr_pdcp/nr_pdcp_oai_api.h" #include "intertask_interface.h" @@ -137,7 +136,7 @@ void gNB_dlsch_ulsch_scheduler(module_id_t module_idP, start_meas(&gNB->eNB_scheduler); VCD_SIGNAL_DUMPER_DUMP_FUNCTION_BY_NAME(VCD_SIGNAL_DUMPER_FUNCTIONS_gNB_DLSCH_ULSCH_SCHEDULER,VCD_FUNCTION_IN); - pdcp_run(&ctxt); + nr_pdcp_run(&ctxt); /* send tick to RLC and RRC every ms */ if ((slot & ((1 << *scc->ssbSubcarrierSpacing) - 1)) == 0) { void nr_rlc_tick(int frame, int subframe); diff --git a/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_bch.c b/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_bch.c index 3224223631..121ad179cc 100644 --- a/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_bch.c +++ b/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_bch.c @@ -42,8 +42,6 @@ #include "common/utils/nr/nr_common.h" -#include "pdcp.h" - #define ENABLE_MAC_PAYLOAD_DEBUG #define DEBUG_eNB_SCHEDULER 1 diff --git a/openair2/LAYER2/NR_MAC_gNB/main.c b/openair2/LAYER2/NR_MAC_gNB/main.c index f78e7f224f..5dcaffce94 100644 --- a/openair2/LAYER2/NR_MAC_gNB/main.c +++ b/openair2/LAYER2/NR_MAC_gNB/main.c @@ -33,9 +33,8 @@ #include "NR_MAC_gNB/mac_proto.h" #include "NR_MAC_COMMON/nr_mac_extern.h" #include "assertions.h" -#include "pdcp.h" +#include "nr_pdcp/nr_pdcp_oai_api.h" -#include "LAYER2/nr_pdcp/nr_pdcp_entity.h" #include "RRC/NR/nr_rrc_defs.h" #include "common/utils/LOG/log.h" //#include "RRC/L2_INTERFACE/openair_rrc_L2_interface.h" @@ -250,7 +249,7 @@ void mac_top_init_gNB(ngran_node_t node_type) AssertFatal(rlc_module_init(1) == 0,"Could not initialize RLC layer\n"); // These should be out of here later - if (get_softmodem_params()->usim_test == 0 ) pdcp_layer_init(); + if (get_softmodem_params()->usim_test == 0 ) nr_pdcp_layer_init(); if(IS_SOFTMODEM_NOS1 && get_softmodem_params()->phy_test) nr_DRB_preconfiguration(0x1234); diff --git a/openair2/LAYER2/PDCP_v10.1.0/pdcp.h b/openair2/LAYER2/PDCP_v10.1.0/pdcp.h index b4303d1fdd..043ac7fb4a 100644 --- a/openair2/LAYER2/PDCP_v10.1.0/pdcp.h +++ b/openair2/LAYER2/PDCP_v10.1.0/pdcp.h @@ -402,9 +402,7 @@ void pdcp_mbms_run ( void pdcp_run ( const protocol_ctxt_t *const ctxt_pP); uint64_t pdcp_module_init (uint64_t pdcp_optmask, int ue_id); -uint64_t nr_pdcp_module_init(uint64_t _pdcp_optmask, int id); void pdcp_module_cleanup (void); -void nr_ip_over_LTE_DRB_preconfiguration (void); void pdcp_layer_init (void); void pdcp_layer_cleanup (void); #define PDCP2NW_DRIVER_FIFO 21 diff --git a/openair2/LAYER2/nr_pdcp/asn1_utils.c b/openair2/LAYER2/nr_pdcp/asn1_utils.c index 264a2815bb..ab0cdf48fd 100644 --- a/openair2/LAYER2/nr_pdcp/asn1_utils.c +++ b/openair2/LAYER2/nr_pdcp/asn1_utils.c @@ -19,7 +19,7 @@ * contact@openairinterface.org */ -#include "pdcp.h" +#include "common/utils/LOG/log.h" int decode_t_reordering(int v) { diff --git a/openair2/LAYER2/nr_pdcp/nr_pdcp_e1_api.h b/openair2/LAYER2/nr_pdcp/nr_pdcp_e1_api.h index 68b146efeb..f45f3f0458 100644 --- a/openair2/LAYER2/nr_pdcp/nr_pdcp_e1_api.h +++ b/openair2/LAYER2/nr_pdcp/nr_pdcp_e1_api.h @@ -25,7 +25,7 @@ #include "common/utils/oai_asn1.h" #include "NR_RadioBearerConfig.h" -#include "pdcp.h" +#include "nr_pdcp_oai_api.h" #include "LAYER2/nr_rlc/nr_rlc_oai_api.h" #include #include "openair2/SDAP/nr_sdap/nr_sdap.h" diff --git a/openair2/LAYER2/nr_pdcp/nr_pdcp_oai_api.c b/openair2/LAYER2/nr_pdcp/nr_pdcp_oai_api.c index 702aabd528..3b198ff17c 100644 --- a/openair2/LAYER2/nr_pdcp/nr_pdcp_oai_api.c +++ b/openair2/LAYER2/nr_pdcp/nr_pdcp_oai_api.c @@ -34,7 +34,7 @@ /* from OAI */ #include "oai_asn1.h" -#include "pdcp.h" +#include "nr_pdcp_oai_api.h" #include "LAYER2/nr_rlc/nr_rlc_oai_api.h" #include #include "openair2/SDAP/nr_sdap/nr_sdap.h" @@ -73,7 +73,7 @@ static ngran_node_t node_type; /* NR PDCP and RLC both use "big locks". In some cases a thread may do * lock(rlc) followed by lock(pdcp) (typically when running 'rx_sdu'). * Another thread may first do lock(pdcp) and then lock(rlc) (typically - * the GTP module calls 'pdcp_data_req' that, in a previous implementation + * the GTP module calls 'nr_pdcp_data_req' that, in a previous implementation * was indirectly calling 'rlc_data_req' which does lock(rlc)). * To avoid the resulting deadlock it is enough to ensure that a call * to lock(pdcp) will never be followed by a call to lock(rlc). So, @@ -531,7 +531,13 @@ static void set_node_type() { node_type = get_node_type(); } +/* hack: dummy function needed due to LTE dependencies */ void pdcp_layer_init(void) +{ + abort(); +} + +void nr_pdcp_layer_init(void) { /* hack: be sure to initialize only once */ static pthread_mutex_t m = PTHREAD_MUTEX_INITIALIZER; @@ -789,7 +795,7 @@ srb_found: } } -void pdcp_run(const protocol_ctxt_t *const ctxt_pP) +void nr_pdcp_run(const protocol_ctxt_t *const ctxt_pP) { MessageDef *msg_p; int result; @@ -811,15 +817,11 @@ void pdcp_run(const protocol_ctxt_t *const ctxt_pP) RRC_DCCH_DATA_REQ(msg_p).frame, 0, RRC_DCCH_DATA_REQ(msg_p).eNB_index); - result = pdcp_data_req(&ctxt, - SRB_FLAG_YES, - RRC_DCCH_DATA_REQ(msg_p).rb_id, - RRC_DCCH_DATA_REQ(msg_p).muip, - RRC_DCCH_DATA_REQ(msg_p).confirmp, - RRC_DCCH_DATA_REQ(msg_p).sdu_size, - RRC_DCCH_DATA_REQ(msg_p).sdu_p, - RRC_DCCH_DATA_REQ(msg_p).mode, - NULL, NULL); + result = nr_pdcp_data_req_srb(ctxt.rntiMaybeUEid, + RRC_DCCH_DATA_REQ(msg_p).rb_id, + RRC_DCCH_DATA_REQ(msg_p).muip, + RRC_DCCH_DATA_REQ(msg_p).sdu_size, + RRC_DCCH_DATA_REQ(msg_p).sdu_p); if (result != true) LOG_E(PDCP, "PDCP data request failed!\n"); @@ -1118,30 +1120,43 @@ uint64_t get_pdcp_optmask(void) return pdcp_optmask; } +/* hack: dummy function needed due to LTE dependencies */ bool pdcp_remove_UE(const protocol_ctxt_t *const ctxt_pP) { - ue_id_t rntiMaybeUEid = ctxt_pP->rntiMaybeUEid; + abort(); +} +bool nr_pdcp_remove_UE(ue_id_t ue_id) +{ nr_pdcp_manager_lock(nr_pdcp_ue_manager); - nr_pdcp_manager_remove_ue(nr_pdcp_ue_manager, rntiMaybeUEid); + nr_pdcp_manager_remove_ue(nr_pdcp_ue_manager, ue_id); nr_pdcp_manager_unlock(nr_pdcp_ue_manager); return 1; } -void pdcp_config_set_security( - const protocol_ctxt_t* const ctxt_pP, - pdcp_t *const pdcp_pP, - const rb_id_t rb_id, - const uint16_t lc_idP, - const uint8_t security_modeP, - uint8_t *const kRRCenc_pP, - uint8_t *const kRRCint_pP, - uint8_t *const kUPenc_pP) +/* hack: dummy function needed due to LTE dependencies */ +void pdcp_config_set_security(const protocol_ctxt_t *const ctxt_pP, + pdcp_t *const pdcp_pP, + const rb_id_t rb_id, + const uint16_t lc_idP, + const uint8_t security_modeP, + uint8_t *const kRRCenc_pP, + uint8_t *const kRRCint_pP, + uint8_t *const kUPenc_pP) +{ + abort(); +} + +void nr_pdcp_config_set_security(ue_id_t ue_id, + const rb_id_t rb_id, + const uint8_t security_modeP, + uint8_t *const kRRCenc_pP, + uint8_t *const kRRCint_pP, + uint8_t *const kUPenc_pP) { nr_pdcp_ue_t *ue; nr_pdcp_entity_t *rb; - ue_id_t ue_id = ctxt_pP->rntiMaybeUEid; int integrity_algorithm; int ciphering_algorithm; @@ -1173,27 +1188,15 @@ void pdcp_config_set_security( nr_pdcp_manager_unlock(nr_pdcp_ue_manager); } -static bool pdcp_data_req_srb(protocol_ctxt_t *ctxt_pP, - const rb_id_t rb_id, - const mui_t muiP, - const confirm_t confirmP, - const sdu_size_t sdu_buffer_size, - unsigned char *const sdu_buffer) +bool nr_pdcp_data_req_srb(ue_id_t ue_id, + const rb_id_t rb_id, + const mui_t muiP, + const sdu_size_t sdu_buffer_size, + unsigned char *const sdu_buffer) { LOG_D(PDCP, "%s() called, size %d\n", __func__, sdu_buffer_size); nr_pdcp_ue_t *ue; nr_pdcp_entity_t *rb; - ue_id_t ue_id = ctxt_pP->rntiMaybeUEid; - - if (ctxt_pP->module_id != 0 || - //ctxt_pP->enb_flag != 1 || - ctxt_pP->instance != 0 || - ctxt_pP->eNB_index != 0 /*|| - ctxt_pP->configured != 1 || - ctxt_pP->brOption != 0*/) { - LOG_E(PDCP, "%s:%d:%s: fatal\n", __FILE__, __LINE__, __FUNCTION__); - exit(1); - } nr_pdcp_manager_lock(nr_pdcp_ue_manager); @@ -1217,14 +1220,19 @@ static bool pdcp_data_req_srb(protocol_ctxt_t *ctxt_pP, return 1; } - -static bool pdcp_data_req_drb(protocol_ctxt_t *ctxt_pP, - const rb_id_t rb_id, - const mui_t muiP, - const confirm_t confirmP, - const sdu_size_t sdu_buffer_size, - unsigned char *const sdu_buffer) +bool nr_pdcp_data_req_drb(protocol_ctxt_t *ctxt_pP, + const srb_flag_t srb_flagP, + const rb_id_t rb_id, + const mui_t muiP, + const confirm_t confirmP, + const sdu_size_t sdu_buffer_size, + unsigned char *const sdu_buffer, + const pdcp_transmission_mode_t mode, + const uint32_t *const sourceL2Id, + const uint32_t *const destinationL2Id) { + DevAssert(srb_flagP == SRB_FLAG_NO); + LOG_D(PDCP, "%s() called, size %d\n", __func__, sdu_buffer_size); nr_pdcp_ue_t *ue; nr_pdcp_entity_t *rb; @@ -1288,23 +1296,20 @@ bool cu_f1u_data_req(protocol_ctxt_t *ctxt_pP, return ret; } +/* hack: dummy function needed due to LTE dependencies */ bool pdcp_data_req(protocol_ctxt_t *ctxt_pP, - const srb_flag_t srb_flagP, - const rb_id_t rb_id, - const mui_t muiP, - const confirm_t confirmP, - const sdu_size_t sdu_buffer_size, - unsigned char *const sdu_buffer, - const pdcp_transmission_mode_t mode, + const srb_flag_t srb_flagP, + const rb_id_t rb_idP, + const mui_t muiP, + const confirm_t confirmP, + const sdu_size_t sdu_buffer_sizeP, + unsigned char *const sdu_buffer_pP, + const pdcp_transmission_mode_t modeP, const uint32_t *const sourceL2Id, const uint32_t *const destinationL2Id) { - if (srb_flagP) { - return pdcp_data_req_srb(ctxt_pP, rb_id, muiP, confirmP, sdu_buffer_size, sdu_buffer); - } - else{ - return pdcp_data_req_drb(ctxt_pP, rb_id, muiP, confirmP, sdu_buffer_size, sdu_buffer); - } + abort(); + return false; } void pdcp_set_pdcp_data_ind_func(pdcp_data_ind_func_t pdcp_data_ind) diff --git a/openair2/LAYER2/nr_pdcp/nr_pdcp_oai_api.h b/openair2/LAYER2/nr_pdcp/nr_pdcp_oai_api.h new file mode 100644 index 0000000000..56462297c1 --- /dev/null +++ b/openair2/LAYER2/nr_pdcp/nr_pdcp_oai_api.h @@ -0,0 +1,103 @@ +/* + * 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 NR_PDCP_OAI_API_H +#define NR_PDCP_OAI_API_H + +#include "pdcp.h" +#include "nr_pdcp_ue_manager.h" + +void nr_pdcp_layer_init(void); +uint64_t nr_pdcp_module_init(uint64_t _pdcp_optmask, int id); + +void du_rlc_data_req(const protocol_ctxt_t *const ctxt_pP, + const srb_flag_t srb_flagP, + const MBMS_flag_t MBMS_flagP, + const rb_id_t rb_idP, + const mui_t muiP, + confirm_t confirmP, + sdu_size_t sdu_sizeP, + mem_block_t *sdu_pP); + +bool pdcp_data_ind(const protocol_ctxt_t *const ctxt_pP, + const srb_flag_t srb_flagP, + const MBMS_flag_t MBMS_flagP, + const rb_id_t rb_id, + const sdu_size_t sdu_buffer_size, + mem_block_t *const sdu_buffer, + const uint32_t *const srcID, + const uint32_t *const dstID); + +void nr_pdcp_add_drbs(eNB_flag_t enb_flag, + ue_id_t rntiMaybeUEid, + ue_id_t reestablish_ue_id, + NR_DRB_ToAddModList_t *const drb2add_list, + const uint8_t security_modeP, + uint8_t *const kUPenc, + uint8_t *const kUPint, + struct NR_CellGroupConfig__rlc_BearerToAddModList *rlc_bearer2add_list); + +void nr_DRB_preconfiguration(ue_id_t crntiMaybeUEid); + +bool nr_pdcp_remove_UE(ue_id_t ue_id); + +void nr_pdcp_config_set_security(ue_id_t ue_id, + const rb_id_t rb_id, + const uint8_t security_modeP, + uint8_t *const kRRCenc_pP, + uint8_t *const kRRCint_pP, + uint8_t *const kUPenc_pP); + +bool cu_f1u_data_req(protocol_ctxt_t *ctxt_pP, + const srb_flag_t srb_flagP, + const rb_id_t rb_id, + const mui_t muiP, + const confirm_t confirmP, + const sdu_size_t sdu_buffer_size, + unsigned char *const sdu_buffer, + const pdcp_transmission_mode_t mode, + const uint32_t *const sourceL2Id, + const uint32_t *const destinationL2Id); + +bool nr_pdcp_data_req_srb(ue_id_t ue_id, + const rb_id_t rb_id, + const mui_t muiP, + const sdu_size_t sdu_buffer_size, + unsigned char *const sdu_buffer); +bool nr_pdcp_data_req_drb(protocol_ctxt_t *ctxt_pP, + const srb_flag_t srb_flagP, + const rb_id_t rb_id, + const mui_t muiP, + const confirm_t confirmP, + const sdu_size_t sdu_buffer_size, + unsigned char *const sdu_buffer, + const pdcp_transmission_mode_t mode, + const uint32_t *const sourceL2Id, + const uint32_t *const destinationL2Id); + +void nr_pdcp_run(const protocol_ctxt_t *const ctxt_pP); +void nr_pdcp_tick(int frame, int subframe); + +nr_pdcp_ue_manager_t *nr_pdcp_sdap_get_ue_manager(); + +const bool nr_pdcp_get_statistics(ue_id_t ue_id, int srb_flag, int rb_id, nr_pdcp_statistics_t *out); + +#endif /* NR_PDCP_OAI_API_H */ diff --git a/openair2/LAYER2/nr_rlc/nr_rlc_oai_api.c b/openair2/LAYER2/nr_rlc/nr_rlc_oai_api.c index dab6f9fe58..103044f1bc 100644 --- a/openair2/LAYER2/nr_rlc/nr_rlc_oai_api.c +++ b/openair2/LAYER2/nr_rlc/nr_rlc_oai_api.c @@ -21,7 +21,7 @@ /* from openair */ #include "rlc.h" -#include "pdcp.h" +#include "LAYER2/nr_pdcp/nr_pdcp_oai_api.h" /* from nr rlc module */ #include "nr_rlc_asn1_utils.h" diff --git a/openair2/LAYER2/openair2_proc.c b/openair2/LAYER2/openair2_proc.c index 6ef447df5f..d68232b2e2 100644 --- a/openair2/LAYER2/openair2_proc.c +++ b/openair2/LAYER2/openair2_proc.c @@ -34,7 +34,7 @@ #include "LAYER2/RLC/rlc.h" #include "LAYER2/MAC/mac.h" #include "LAYER2/MAC/mac_extern.h" -#include "LAYER2/PDCP_v10.1.0/pdcp.h" +#include "LAYER2/nr_pdcp/nr_pdcp_oai_api.h" #include "common/utils/LOG/log.h" #include "common/ran_context.h" diff --git a/openair2/RRC/NR/L2_nr_interface.c b/openair2/RRC/NR/L2_nr_interface.c index 4a88b153bd..3554cb3715 100644 --- a/openair2/RRC/NR/L2_nr_interface.c +++ b/openair2/RRC/NR/L2_nr_interface.c @@ -32,7 +32,6 @@ #include "nr_rrc_defs.h" #include "nr_rrc_extern.h" #include "common/utils/LOG/log.h" -#include "pdcp.h" #include "common/ran_context.h" #include "LAYER2/NR_MAC_COMMON/nr_mac_common.h" #include "LAYER2/NR_MAC_COMMON/nr_mac_extern.h" @@ -43,6 +42,7 @@ #include "NR_BCCH-BCH-Message.h" #include "rrc_gNB_UE_context.h" #include +#include "nr_pdcp/nr_pdcp_oai_api.h" extern RAN_CONTEXT_t RC; @@ -100,7 +100,7 @@ nr_rrc_data_req( * Ideally, PDCP would not neet to be triggered like this but react to ITTI * messages automatically */ if (ctxt_pP->enb_flag) - pdcp_run(ctxt_pP); + nr_pdcp_run(ctxt_pP); return true; // TODO should be changed to a CNF message later, currently RRC lite does not used the returned value anyway. } diff --git a/openair2/RRC/NR/nr_rrc_common.c b/openair2/RRC/NR/nr_rrc_common.c index 1d231f20bf..c6f27c7325 100644 --- a/openair2/RRC/NR/nr_rrc_common.c +++ b/openair2/RRC/NR/nr_rrc_common.c @@ -37,7 +37,6 @@ #include "COMMON/mac_rrc_primitives.h" #include "common/utils/LOG/log.h" #include "asn1_msg.h" -#include "pdcp.h" #include "common/utils/LOG/vcd_signal_dumper.h" #include "common/ran_context.h" diff --git a/openair2/RRC/NR/rrc_gNB.c b/openair2/RRC/NR/rrc_gNB.c index a4f57b0fae..c3afed5f6b 100644 --- a/openair2/RRC/NR/rrc_gNB.c +++ b/openair2/RRC/NR/rrc_gNB.c @@ -82,7 +82,7 @@ #include "rrc_gNB_GTPV1U.h" #include "nr_pdcp/nr_pdcp_entity.h" -#include "pdcp.h" +#include "nr_pdcp/nr_pdcp_oai_api.h" #include "intertask_interface.h" #include "SIMULATION/TOOLS/sim.h" // for taus @@ -105,15 +105,6 @@ extern RAN_CONTEXT_t RC; -extern void pdcp_config_set_security(const protocol_ctxt_t *const ctxt_pP, - pdcp_t *const pdcp_pP, - const rb_id_t rb_idP, - const uint16_t lc_idP, - const uint8_t security_modeP, - uint8_t *const kRRCenc, - uint8_t *const kRRCint, - uint8_t *const kUPenc); - static inline uint64_t bitStr_to_uint64(BIT_STRING_t *asn); mui_t rrc_gNB_mui = 0; @@ -1289,14 +1280,15 @@ void rrc_gNB_generate_RRCReestablishment(const protocol_ctxt_t *ctxt_pP, } // if (*SRB_configList != NULL) LOG_I(NR_RRC, "Set PDCP security RNTI %04lx nca %ld nia %d in RRCReestablishment\n", ctxt_pP->rntiMaybeUEid, ue_p->ciphering_algorithm, ue_p->integrity_algorithm); - pdcp_config_set_security(ctxt_pP, - NULL, /* pdcp_pP not used anymore in NR */ - DCCH, - DCCH + 2, - enable_ciphering ? ue_p->ciphering_algorithm | (ue_p->integrity_algorithm << 4) : 0 | (ue_p->integrity_algorithm << 4), - kRRCenc, - kRRCint, - kUPenc); + uint8_t security_mode = + enable_ciphering ? ue_p->ciphering_algorithm | (ue_p->integrity_algorithm << 4) : 0 | (ue_p->integrity_algorithm << 4); + + nr_pdcp_config_set_security(ctxt_pP->rntiMaybeUEid, + DCCH, + security_mode, + kRRCenc, + kRRCint, + kUPenc); if (!NODE_IS_CU(rrc->node_type)) { apply_macrlc_config_reest(rrc, ue_context_pP, ctxt_pP, ctxt_pP->rntiMaybeUEid); @@ -3225,7 +3217,7 @@ void nr_rrc_subframe_process(protocol_ctxt_t *const ctxt_pP, const int CC_id) { gNB_MAC_INST *nrmac = RC.nrmac[ctxt_pP->module_id]; // WHAT A BEAUTIFULL RACE CONDITION !!! mac_remove_nr_ue(nrmac, UE->rnti); rrc_rlc_remove_ue(ctxt_pP); - pdcp_remove_UE(ctxt_pP); + nr_pdcp_remove_UE(ctxt_pP->rntiMaybeUEid); /* remove RRC UE Context */ ue_context_p = rrc_gNB_get_ue_context_by_rnti(rrc, UE->rnti); @@ -3262,7 +3254,7 @@ void nr_rrc_subframe_process(protocol_ctxt_t *const ctxt_pP, const int CC_id) { mac_remove_nr_ue(nrmac, UE->rnti); } rrc_rlc_remove_ue(ctxt_pP); - pdcp_remove_UE(ctxt_pP); + nr_pdcp_remove_UE(ctxt_pP->rntiMaybeUEid); newGtpuDeleteAllTunnels(ctxt_pP->instance, UE->rnti); /* remove RRC UE Context */ diff --git a/openair2/RRC/NR/rrc_gNB_NGAP.c b/openair2/RRC/NR/rrc_gNB_NGAP.c index d3efb4b497..f9d7a492f5 100644 --- a/openair2/RRC/NR/rrc_gNB_NGAP.c +++ b/openair2/RRC/NR/rrc_gNB_NGAP.c @@ -36,7 +36,7 @@ #include "oai_asn1.h" #include "intertask_interface.h" -#include "pdcp.h" +#include "nr_pdcp/nr_pdcp_oai_api.h" #include "pdcp_primitives.h" #include "openair3/ocp-gtpu/gtp_itf.h" @@ -153,14 +153,9 @@ nr_rrc_pdcp_config_security( } } - pdcp_config_set_security(ctxt_pP, - NULL, /* pdcp_pP not used anymore in NR */ - DCCH, - DCCH + 2, - enable_ciphering ? UE->ciphering_algorithm | (UE->integrity_algorithm << 4) : 0 | (UE->integrity_algorithm << 4), - kRRCenc, - kRRCint, - kUPenc); + uint8_t security_mode = + enable_ciphering ? UE->ciphering_algorithm | (UE->integrity_algorithm << 4) : 0 | (UE->integrity_algorithm << 4); + nr_pdcp_config_set_security(ctxt_pP->rntiMaybeUEid, DCCH, security_mode, kRRCenc, kRRCint, kUPenc); } //------------------------------------------------------------------------------ diff --git a/openair2/RRC/NR/rrc_gNB_nsa.c b/openair2/RRC/NR/rrc_gNB_nsa.c index 0a6eb050e5..989c754c20 100644 --- a/openair2/RRC/NR/rrc_gNB_nsa.c +++ b/openair2/RRC/NR/rrc_gNB_nsa.c @@ -45,7 +45,7 @@ #include #include "UTIL/OSA/osa_defs.h" #include -#include "pdcp.h" +#include "nr_pdcp/nr_pdcp_oai_api.h" void rrc_parse_ue_capabilities(gNB_RRC_INST *rrc, NR_UE_CapabilityRAT_ContainerList_t *UE_CapabilityRAT_ContainerList, x2ap_ENDC_sgnb_addition_req_t *m, NR_CG_ConfigInfo_IEs_t *cg_config_info) { @@ -391,7 +391,7 @@ void rrc_remove_nsa_user(gNB_RRC_INST *rrc, int rnti) { return; } - pdcp_remove_UE(&ctxt); + nr_pdcp_remove_UE(ctxt.rntiMaybeUEid); rrc_rlc_remove_ue(&ctxt); diff --git a/openair2/RRC/NR_UE/rrc_UE.c b/openair2/RRC/NR_UE/rrc_UE.c index ecbaeea17e..3cff4e8c17 100644 --- a/openair2/RRC/NR_UE/rrc_UE.c +++ b/openair2/RRC/NR_UE/rrc_UE.c @@ -58,7 +58,7 @@ #include "nr-uesoftmodem.h" #include "executables/softmodem-common.h" #include "plmn_data.h" -#include "pdcp.h" +#include "nr_pdcp/nr_pdcp_oai_api.h" #include "UTIL/OSA/osa_defs.h" #include "common/utils/LOG/log.h" #include "common/utils/LOG/vcd_signal_dumper.h" @@ -90,17 +90,6 @@ static const char nr_nas_attach_req_imsi[] = { 0x01, 0x27, 0x11, }; -extern void pdcp_config_set_security( - const protocol_ctxt_t *const ctxt_pP, - pdcp_t *const pdcp_pP, - const rb_id_t rb_idP, - const uint16_t lc_idP, - const uint8_t security_modeP, - uint8_t *const kRRCenc, - uint8_t *const kRRCint, - uint8_t *const kUPenc -); - void nr_rrc_ue_process_ueCapabilityEnquiry( const protocol_ctxt_t *const ctxt_pP, @@ -1675,10 +1664,9 @@ int8_t nr_rrc_ue_decode_ccch( const protocol_ctxt_t *const ctxt_pP, const NR_SRB NR_UE_rrc_inst[ctxt_pP->module_id].kgnb[28], NR_UE_rrc_inst[ctxt_pP->module_id].kgnb[29], NR_UE_rrc_inst[ctxt_pP->module_id].kgnb[30], NR_UE_rrc_inst[ctxt_pP->module_id].kgnb[31]); if (securityMode != 0xff) { - pdcp_config_set_security(ctxt_pP, NULL, DCCH, DCCH+2, - NR_UE_rrc_inst[ctxt_pP->module_id].cipheringAlgorithm - | (NR_UE_rrc_inst[ctxt_pP->module_id].integrityProtAlgorithm << 4), - kRRCenc, kRRCint, kUPenc); + uint8_t security_mode = + NR_UE_rrc_inst[ctxt_pP->module_id].cipheringAlgorithm | (NR_UE_rrc_inst[ctxt_pP->module_id].integrityProtAlgorithm << 4); + nr_pdcp_config_set_security(ctxt_pP->rntiMaybeUEid, DCCH, security_mode, kRRCenc, kRRCint, kUPenc); } else { LOG_I(NR_RRC, "skipped pdcp_config_set_security() as securityMode == 0x%02x", securityMode); } diff --git a/openair2/SDAP/nr_sdap/nr_sdap_entity.c b/openair2/SDAP/nr_sdap/nr_sdap_entity.c index 280663e2b0..357bec24ad 100644 --- a/openair2/SDAP/nr_sdap/nr_sdap_entity.c +++ b/openair2/SDAP/nr_sdap/nr_sdap_entity.c @@ -21,7 +21,7 @@ #include "nr_sdap_entity.h" #include "common/utils/LOG/log.h" -#include +#include #include #include "openair2/LAYER2/nr_pdcp/nr_pdcp_ue_manager.h" @@ -37,8 +37,6 @@ static nr_sdap_entity_info sdap_info; instance_t *N3GTPUInst = NULL; -nr_pdcp_ue_manager_t *nr_pdcp_sdap_get_ue_manager(void); - void nr_pdcp_submit_sdap_ctrl_pdu(ue_id_t ue_id, rb_id_t sdap_ctrl_pdu_drb, nr_sdap_ul_hdr_t ctrl_pdu) { nr_pdcp_ue_t *ue; @@ -87,16 +85,16 @@ static bool nr_sdap_tx_entity(nr_sdap_entity_t *entity, if(!pdcp_ent_has_sdap){ LOG_D(SDAP, "TX - DRB ID: %ld does not have SDAP\n", entity->qfi2drb_table[qfi].drb_id); - ret = pdcp_data_req(ctxt_p, - srb_flag, - sdap_drb_id, - mui, - confirm, - sdu_buffer_size, - sdu_buffer, - pt_mode, - sourceL2Id, - destinationL2Id); + ret = nr_pdcp_data_req_drb(ctxt_p, + srb_flag, + sdap_drb_id, + mui, + confirm, + sdu_buffer_size, + sdu_buffer, + pt_mode, + sourceL2Id, + destinationL2Id); if(!ret) LOG_E(SDAP, "%s:%d:%s: PDCP refused PDU\n", __FILE__, __LINE__, __FUNCTION__); @@ -155,16 +153,16 @@ static bool nr_sdap_tx_entity(nr_sdap_entity_t *entity, * * Downlink gNB side */ - ret = pdcp_data_req(ctxt_p, - srb_flag, - sdap_drb_id, - mui, - confirm, - sdu_buffer_size+offset, - sdap_buf, - pt_mode, - sourceL2Id, - destinationL2Id); + ret = nr_pdcp_data_req_drb(ctxt_p, + srb_flag, + sdap_drb_id, + mui, + confirm, + sdu_buffer_size + offset, + sdap_buf, + pt_mode, + sourceL2Id, + destinationL2Id); if(!ret) LOG_E(SDAP, "%s:%d:%s: PDCP refused PDU\n", __FILE__, __LINE__, __FUNCTION__); diff --git a/openair3/ocp-gtpu/gtp_itf.cpp b/openair3/ocp-gtpu/gtp_itf.cpp index 2f60d5238c..f6255fb056 100644 --- a/openair3/ocp-gtpu/gtp_itf.cpp +++ b/openair3/ocp-gtpu/gtp_itf.cpp @@ -17,6 +17,7 @@ extern "C" { #include #include #include +#include #include #include "openair2/SDAP/nr_sdap/nr_sdap.h" #include "sim.h" @@ -748,7 +749,7 @@ int gtpv1u_create_ngu_tunnel( const instance_t instance, create_tunnel_req->outgoing_qfi[i], create_tunnel_req->dst_addr[i], dstport, - pdcp_data_req, + nr_pdcp_data_req_drb, sdap_data_req); create_tunnel_resp->status=0; create_tunnel_resp->ue_id=create_tunnel_req->ue_id; From c405ad16a826b143c5a9fdc81573d3db179fcbff Mon Sep 17 00:00:00 2001 From: Robert Schmidt Date: Sun, 19 Mar 2023 14:50:34 +0100 Subject: [PATCH 15/89] Remove nr_rrc_data_req()/rrc_data_req_nr_ue() --- doc/SW_archi.md | 3 -- executables/nr-softmodem.c | 6 +++ executables/nr-ue.c | 13 ------ openair2/LAYER2/MAC/eNB_scheduler.c | 2 +- openair2/LAYER2/NR_MAC_gNB/gNB_scheduler.c | 1 - openair2/LAYER2/nr_pdcp/nr_pdcp_oai_api.c | 40 ----------------- openair2/LAYER2/nr_pdcp/nr_pdcp_oai_api.h | 1 - openair2/RRC/NR/L2_nr_interface.c | 50 --------------------- openair2/RRC/NR/nr_rrc_proto.h | 11 ----- openair2/RRC/NR/rrc_gNB.c | 48 +++++--------------- openair2/RRC/NR/rrc_gNB_NGAP.c | 2 +- openair2/RRC/NR_UE/L2_interface_ue.c | 37 --------------- openair2/RRC/NR_UE/rrc_UE.c | 52 +++------------------- openair2/RRC/NR_UE/rrc_proto.h | 11 ----- 14 files changed, 25 insertions(+), 252 deletions(-) diff --git a/doc/SW_archi.md b/doc/SW_archi.md index 1e8e5cd011..6b509cc627 100644 --- a/doc/SW_archi.md +++ b/doc/SW_archi.md @@ -334,9 +334,6 @@ At Tx side (DL in gNB), `pdcp_data_req_drb()` and `pdcp_data_req_srb()` are the The upper layer can be GTP or a PDCP internal thread enb_tun_read_thread() that read directly from Linux socket in case we skip 3GPP core implementation. PDCP internals for nr_pdcp_data_req_srb()/nr_pdcp_data_req_drb() are thread safe: inside them, the pdcp manager protects with the mutex the access to the SDU receiving function of PDCP (recv_sdu() callback, corresponding to nr_pdcp_entity_drb_am_recv_sdu() for DRBs). When it needs, the pdcp layer push this data to rlc by calling : rlc_data_req() -Also, incoming downlink sdu can comme from internal RRC: in this case, nr -nr_pdcp_run() reads a itti queue, for message RRC_DCCH_DATA_REQ, to0 only call 'nr_pdcp_data_req_srb()' - At Rx side, pdcp_data_ind() is the entry point that receives the data from RLC. - Inside pdcp_data_ind(), the pdcp manager mutex protects the access to the PDU receiving function of PDCP (recv_pdu() callback corresponding to nr_pdcp_entity_drb_am_recv_pdu() for DRBs) - Then deliver_sdu_drb() function sends the received data to GTP thread through an ITTI message (GTPV1U_TUNNEL_DATA_REQ). diff --git a/executables/nr-softmodem.c b/executables/nr-softmodem.c index 455bf83904..643d3a9cc7 100644 --- a/executables/nr-softmodem.c +++ b/executables/nr-softmodem.c @@ -176,6 +176,12 @@ openair0_config_t openair0_cfg[MAX_CARDS]; double cpuf; +/* hack: pdcp_run() is required by 4G scheduler which is compiled into + * nr-softmodem because of linker issues */ +void pdcp_run(const protocol_ctxt_t *const ctxt_pP) +{ + abort(); +} /* see file openair2/LAYER2/MAC/main.c for why abstraction_flag is needed * this is very hackish - find a proper solution diff --git a/executables/nr-ue.c b/executables/nr-ue.c index a1ec29a42a..d5b7866505 100644 --- a/executables/nr-ue.c +++ b/executables/nr-ue.c @@ -355,12 +355,6 @@ static void *NRUE_phy_stub_standalone_pnf_task(void *arg) nr_ue_ul_scheduler(&ul_info); nr_ue_pucch_scheduler(mod_id, ul_info.frame_tx, ul_info.slot_tx, NULL); } - if (!IS_SOFTMODEM_NOS1 && get_softmodem_params()->sa) { - NR_UE_MAC_INST_t *mac = get_mac_inst(0); - protocol_ctxt_t ctxt; - PROTOCOL_CTXT_SET_BY_MODULE_ID(&ctxt, 0, ENB_FLAG_NO, mac->crnti, frame, slot, 0); - nr_pdcp_run(&ctxt); - } process_queued_nr_nfapi_msgs(mac, sfn_slot); } return NULL; @@ -622,13 +616,6 @@ void UE_processing(nr_rxtx_thread_data_t *rxtxD) { phy_procedures_nrUE_RX(UE, proc, &phy_data); LOG_D(PHY, "In %s: slot %d, time %llu\n", __FUNCTION__, proc->nr_slot_rx, (rdtsc_oai()-a)/3500); #endif - - if(IS_SOFTMODEM_NOS1 || get_softmodem_params()->sa){ - NR_UE_MAC_INST_t *mac = get_mac_inst(0); - protocol_ctxt_t ctxt; - PROTOCOL_CTXT_SET_BY_MODULE_ID(&ctxt, UE->Mod_id, ENB_FLAG_NO, mac->crnti, proc->frame_rx, proc->nr_slot_rx, 0); - nr_pdcp_run(&ctxt); - } } ue_ta_procedures(UE, proc->nr_slot_tx, proc->frame_tx); diff --git a/openair2/LAYER2/MAC/eNB_scheduler.c b/openair2/LAYER2/MAC/eNB_scheduler.c index e56b3d4f6f..badcd36cc6 100644 --- a/openair2/LAYER2/MAC/eNB_scheduler.c +++ b/openair2/LAYER2/MAC/eNB_scheduler.c @@ -958,7 +958,7 @@ eNB_dlsch_ulsch_scheduler(module_id_t module_idP, void rlc_tick(int, int); PROTOCOL_CTXT_SET_BY_MODULE_ID(&ctxt, module_idP, ENB_FLAG_YES, NOT_A_RNTI, frameP, subframeP, module_idP); rlc_tick(frameP, subframeP); - //pdcp_run(&ctxt); + pdcp_run(&ctxt); pdcp_mbms_run(&ctxt); rrc_rx_tx(&ctxt, CC_id); #endif diff --git a/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler.c b/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler.c index 4a5b4cc626..41a2153cf8 100644 --- a/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler.c +++ b/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler.c @@ -136,7 +136,6 @@ void gNB_dlsch_ulsch_scheduler(module_id_t module_idP, start_meas(&gNB->eNB_scheduler); VCD_SIGNAL_DUMPER_DUMP_FUNCTION_BY_NAME(VCD_SIGNAL_DUMPER_FUNCTIONS_gNB_DLSCH_ULSCH_SCHEDULER,VCD_FUNCTION_IN); - nr_pdcp_run(&ctxt); /* send tick to RLC and RRC every ms */ if ((slot & ((1 << *scc->ssbSubcarrierSpacing) - 1)) == 0) { void nr_rlc_tick(int frame, int subframe); diff --git a/openair2/LAYER2/nr_pdcp/nr_pdcp_oai_api.c b/openair2/LAYER2/nr_pdcp/nr_pdcp_oai_api.c index 3b198ff17c..a91f4e883f 100644 --- a/openair2/LAYER2/nr_pdcp/nr_pdcp_oai_api.c +++ b/openair2/LAYER2/nr_pdcp/nr_pdcp_oai_api.c @@ -795,46 +795,6 @@ srb_found: } } -void nr_pdcp_run(const protocol_ctxt_t *const ctxt_pP) -{ - MessageDef *msg_p; - int result; - protocol_ctxt_t ctxt = {.module_id = 0, .enb_flag = 1, .instance = 0, .rntiMaybeUEid = 0, .frame = -1, .subframe = -1, .eNB_index = 0, .brOption = false}; - - while (1) { - itti_poll_msg(ctxt_pP->enb_flag ? TASK_PDCP_ENB : TASK_PDCP_UE, &msg_p); - if (msg_p == NULL){ - break; - } - switch (ITTI_MSG_ID(msg_p)) { - case RRC_DCCH_DATA_REQ: - LOG_D(PDCP, "Received RRC_DCCH_DATA_REQ type at PDCP task \n"); - PROTOCOL_CTXT_SET_BY_MODULE_ID( - &ctxt, - RRC_DCCH_DATA_REQ(msg_p).module_id, - RRC_DCCH_DATA_REQ(msg_p).enb_flag, - RRC_DCCH_DATA_REQ(msg_p).rnti, - RRC_DCCH_DATA_REQ(msg_p).frame, - 0, - RRC_DCCH_DATA_REQ(msg_p).eNB_index); - result = nr_pdcp_data_req_srb(ctxt.rntiMaybeUEid, - RRC_DCCH_DATA_REQ(msg_p).rb_id, - RRC_DCCH_DATA_REQ(msg_p).muip, - RRC_DCCH_DATA_REQ(msg_p).sdu_size, - RRC_DCCH_DATA_REQ(msg_p).sdu_p); - - if (result != true) - LOG_E(PDCP, "PDCP data request failed!\n"); - result = itti_free(ITTI_MSG_ORIGIN_ID(msg_p), RRC_DCCH_DATA_REQ(msg_p).sdu_p); - AssertFatal(result == EXIT_SUCCESS, "Failed to free memory (%d)!\n", result); - break; - default: - LOG_E(PDCP, "Received unexpected message %s\n", ITTI_MSG_NAME(msg_p)); - break; - } - } -} - static void add_srb(int is_gnb, ue_id_t rntiMaybeUEid, struct NR_SRB_ToAddMod *s, int ciphering_algorithm, int integrity_algorithm, unsigned char *ciphering_key, unsigned char *integrity_key) { nr_pdcp_entity_t *pdcp_srb; diff --git a/openair2/LAYER2/nr_pdcp/nr_pdcp_oai_api.h b/openair2/LAYER2/nr_pdcp/nr_pdcp_oai_api.h index 56462297c1..1b69b9d2e7 100644 --- a/openair2/LAYER2/nr_pdcp/nr_pdcp_oai_api.h +++ b/openair2/LAYER2/nr_pdcp/nr_pdcp_oai_api.h @@ -93,7 +93,6 @@ bool nr_pdcp_data_req_drb(protocol_ctxt_t *ctxt_pP, const uint32_t *const sourceL2Id, const uint32_t *const destinationL2Id); -void nr_pdcp_run(const protocol_ctxt_t *const ctxt_pP); void nr_pdcp_tick(int frame, int subframe); nr_pdcp_ue_manager_t *nr_pdcp_sdap_get_ue_manager(); diff --git a/openair2/RRC/NR/L2_nr_interface.c b/openair2/RRC/NR/L2_nr_interface.c index 3554cb3715..d8d43e490c 100644 --- a/openair2/RRC/NR/L2_nr_interface.c +++ b/openair2/RRC/NR/L2_nr_interface.c @@ -55,56 +55,6 @@ nr_rrc_mac_remove_ue(module_id_t mod_idP, return 0; } -//------------------------------------------------------------------------------ -uint8_t -nr_rrc_data_req( - const protocol_ctxt_t *const ctxt_pP, - const rb_id_t rb_idP, - const mui_t muiP, - const confirm_t confirmP, - const sdu_size_t sdu_sizeP, - uint8_t *const buffer_pP, - const pdcp_transmission_mode_t modeP -) -//------------------------------------------------------------------------------ -{ - - MessageDef *message_p; - // Uses a new buffer to avoid issue with PDCP buffer content that could be changed by PDCP (asynchronous message handling). - uint8_t *message_buffer; - message_buffer = itti_malloc ( - ctxt_pP->enb_flag ? TASK_RRC_GNB : TASK_RRC_UE, - ctxt_pP->enb_flag ? TASK_PDCP_ENB : TASK_PDCP_UE, - sdu_sizeP); - memcpy (message_buffer, buffer_pP, sdu_sizeP); - message_p = itti_alloc_new_message (ctxt_pP->enb_flag ? TASK_RRC_GNB : TASK_RRC_UE, 0, RRC_DCCH_DATA_REQ); - RRC_DCCH_DATA_REQ (message_p).frame = ctxt_pP->frame; - RRC_DCCH_DATA_REQ (message_p).enb_flag = ctxt_pP->enb_flag; - RRC_DCCH_DATA_REQ (message_p).rb_id = rb_idP; - RRC_DCCH_DATA_REQ (message_p).muip = muiP; - RRC_DCCH_DATA_REQ (message_p).confirmp = confirmP; - RRC_DCCH_DATA_REQ (message_p).sdu_size = sdu_sizeP; - RRC_DCCH_DATA_REQ (message_p).sdu_p = message_buffer; - //memcpy (NR_RRC_DCCH_DATA_REQ (message_p).sdu_p, buffer_pP, sdu_sizeP); - RRC_DCCH_DATA_REQ (message_p).mode = modeP; - RRC_DCCH_DATA_REQ (message_p).module_id = ctxt_pP->module_id; - RRC_DCCH_DATA_REQ(message_p).rnti = ctxt_pP->rntiMaybeUEid; - RRC_DCCH_DATA_REQ (message_p).eNB_index = ctxt_pP->eNB_index; - itti_send_msg_to_task ( - ctxt_pP->enb_flag ? TASK_PDCP_ENB : TASK_PDCP_UE, - ctxt_pP->instance, - message_p); - LOG_I(NR_RRC,"send RRC_DCCH_DATA_REQ to PDCP\n"); - - /* Hack: only trigger PDCP if in CU, otherwise it is triggered by RU threads - * Ideally, PDCP would not neet to be triggered like this but react to ITTI - * messages automatically */ - if (ctxt_pP->enb_flag) - nr_pdcp_run(ctxt_pP); - - return true; // TODO should be changed to a CNF message later, currently RRC lite does not used the returned value anyway. -} - uint16_t mac_rrc_nr_data_req(const module_id_t Mod_idP, const int CC_id, const frame_t frameP, diff --git a/openair2/RRC/NR/nr_rrc_proto.h b/openair2/RRC/NR/nr_rrc_proto.h index 5ad9a71e39..b54165b13f 100644 --- a/openair2/RRC/NR/nr_rrc_proto.h +++ b/openair2/RRC/NR/nr_rrc_proto.h @@ -136,17 +136,6 @@ void nr_rrc_trigger(protocol_ctxt_t *ctxt, int CC_id, int frame, int subframe); \ *reOffset Pointer to RE Offset Value */ void rrc_config_dl_ptrs_params(NR_BWP_Downlink_t *bwp, int *ptrsNrb, int *ptrsMcs, int *epre_Ratio, int * reOffset); -uint8_t -nr_rrc_data_req( - const protocol_ctxt_t *const ctxt_pP, - const rb_id_t rb_idP, - const mui_t muiP, - const confirm_t confirmP, - const sdu_size_t sdu_size, - uint8_t *const buffer_pP, - const pdcp_transmission_mode_t modeP -); - int nr_rrc_mac_remove_ue(module_id_t mod_idP, rnti_t rntiP); diff --git a/openair2/RRC/NR/rrc_gNB.c b/openair2/RRC/NR/rrc_gNB.c index c3afed5f6b..b51d4563da 100644 --- a/openair2/RRC/NR/rrc_gNB.c +++ b/openair2/RRC/NR/rrc_gNB.c @@ -573,7 +573,7 @@ static void rrc_gNB_generate_defaultRRCReconfiguration(const protocol_ctxt_t *co ue_context_pP->ue_context.rnti); AssertFatal(!NODE_IS_DU(rrc->node_type), "illegal node type DU!\n"); - nr_rrc_data_req(ctxt_pP, DCCH, rrc_gNB_mui++, SDU_CONFIRM_NO, size, buffer, PDCP_TRANSMISSION_MODE_CONTROL); + nr_pdcp_data_req_srb(ctxt_pP->rntiMaybeUEid, DCCH, rrc_gNB_mui++, size, buffer); if (NODE_IS_DU(rrc->node_type) || NODE_IS_MONOLITHIC(rrc->node_type)) { gNB_RRC_UE_t *ue_p = &ue_context_pP->ue_context; @@ -762,14 +762,7 @@ rrc_gNB_generate_dedicatedRRCReconfiguration( ctxt_pP->module_id, DCCH); - nr_rrc_data_req( - ctxt_pP, - DCCH, - rrc_gNB_mui++, - SDU_CONFIRM_NO, - size, - buffer, - PDCP_TRANSMISSION_MODE_CONTROL); + nr_pdcp_data_req_srb(ctxt_pP->rntiMaybeUEid, DCCH, rrc_gNB_mui++, size, buffer); if (NODE_IS_DU(rrc->node_type) || NODE_IS_MONOLITHIC(rrc->node_type)) { nr_mac_update_cellgroup(RC.nrmac[rrc->module_id], ue_context_pP->ue_context.rnti, ue_context_pP->ue_context.masterCellGroup); @@ -936,14 +929,7 @@ rrc_gNB_modify_dedicatedRRCReconfiguration( ctxt_pP->module_id, DCCH); - nr_rrc_data_req( - ctxt_pP, - DCCH, - rrc_gNB_mui++, - SDU_CONFIRM_NO, - size, - buffer, - PDCP_TRANSMISSION_MODE_CONTROL); + nr_pdcp_data_req_srb(ctxt_pP->rntiMaybeUEid, DCCH, rrc_gNB_mui++, size, buffer); if (NODE_IS_DU(RC.nrrrc[ctxt_pP->module_id]->node_type) || NODE_IS_MONOLITHIC(RC.nrrrc[ctxt_pP->module_id]->node_type)) { uint32_t delay_ms = ue_p->masterCellGroup && ue_p->masterCellGroup->spCellConfig && ue_p->masterCellGroup->spCellConfig->spCellConfigDedicated @@ -1026,14 +1012,8 @@ rrc_gNB_generate_dedicatedRRCReconfiguration_release( rrc_gNB_mui, ctxt_pP->module_id, DCCH); - nr_rrc_data_req( - ctxt_pP, - DCCH, - rrc_gNB_mui++, - SDU_CONFIRM_NO, - size, - buffer, - PDCP_TRANSMISSION_MODE_CONTROL); + + nr_pdcp_data_req_srb(ctxt_pP->rntiMaybeUEid, DCCH, rrc_gNB_mui++, size, buffer); if (NODE_IS_DU(RC.nrrrc[ctxt_pP->module_id]->node_type) || NODE_IS_MONOLITHIC(RC.nrrrc[ctxt_pP->module_id]->node_type)) { uint32_t delay_ms = ue_p->masterCellGroup && ue_p->masterCellGroup->spCellConfig && ue_p->masterCellGroup->spCellConfig->spCellConfigDedicated @@ -1294,7 +1274,7 @@ void rrc_gNB_generate_RRCReestablishment(const protocol_ctxt_t *ctxt_pP, apply_macrlc_config_reest(rrc, ue_context_pP, ctxt_pP, ctxt_pP->rntiMaybeUEid); } - nr_rrc_data_req(ctxt_pP, DCCH, rrc_gNB_mui++, SDU_CONFIRM_NO, size, buffer, PDCP_TRANSMISSION_MODE_CONTROL); + nr_pdcp_data_req_srb(ctxt_pP->rntiMaybeUEid, DCCH, rrc_gNB_mui++, size, buffer); } //----------------------------------------------------------------------------- @@ -1507,7 +1487,7 @@ cellGroupConfig->physicalCellGroupConfig = ue_p->masterCellGroup->physicalCellGr DCCH); nr_mac_update_cellgroup(RC.nrmac[rrc->module_id], ue_context_pP->ue_context.rnti, cellGroupConfig); - nr_rrc_data_req(ctxt_pP, DCCH, rrc_gNB_mui++, SDU_CONFIRM_NO, size, buffer, PDCP_TRANSMISSION_MODE_CONTROL); + nr_pdcp_data_req_srb(ctxt_pP->rntiMaybeUEid, DCCH, rrc_gNB_mui++, size, buffer); } if (NODE_IS_DU(RC.nrrrc[ctxt_pP->module_id]->node_type) || NODE_IS_MONOLITHIC(RC.nrrrc[ctxt_pP->module_id]->node_type)) { @@ -1558,13 +1538,7 @@ int nr_rrc_reconfiguration_req(rrc_gNB_ue_context_t *const ue_context_pP nr_mac_update_cellgroup(RC.nrmac[ctxt_pP->module_id], ue_context_pP->ue_context.rnti, masterCellGroup); - nr_rrc_data_req(ctxt_pP, - DCCH, - rrc_gNB_mui++, - SDU_CONFIRM_NO, - size, - buffer, - PDCP_TRANSMISSION_MODE_CONTROL); + nr_pdcp_data_req_srb(ctxt_pP->rntiMaybeUEid, DCCH, rrc_gNB_mui++, size, buffer); if (NODE_IS_DU(RC.nrrrc[ctxt_pP->module_id]->node_type) || NODE_IS_MONOLITHIC(RC.nrrrc[ctxt_pP->module_id]->node_type)) { uint32_t delay_ms = ue_p->masterCellGroup && ue_p->masterCellGroup->spCellConfig && ue_p->masterCellGroup->spCellConfig->spCellConfigDedicated @@ -3615,7 +3589,7 @@ rrc_gNB_generate_SecurityModeCommand( gNB_RRC_INST *rrc = RC.nrrrc[ctxt_pP->module_id]; AssertFatal(!NODE_IS_DU(rrc->node_type), "illegal node type DU!\n"); - nr_rrc_data_req(ctxt_pP, DCCH, rrc_gNB_mui++, SDU_CONFIRM_NO, size, buffer, PDCP_TRANSMISSION_MODE_CONTROL); + nr_pdcp_data_req_srb(ctxt_pP->rntiMaybeUEid, DCCH, rrc_gNB_mui++, size, buffer); } void @@ -3641,7 +3615,7 @@ rrc_gNB_generate_UECapabilityEnquiry( gNB_RRC_INST *rrc = RC.nrrrc[ctxt_pP->module_id]; AssertFatal(!NODE_IS_DU(rrc->node_type), "illegal node type DU!\n"); - nr_rrc_data_req(ctxt_pP, DCCH, rrc_gNB_mui++, SDU_CONFIRM_NO, size, buffer, PDCP_TRANSMISSION_MODE_CONTROL); + nr_pdcp_data_req_srb(ctxt_pP->rntiMaybeUEid, DCCH, rrc_gNB_mui++, size, buffer); } //----------------------------------------------------------------------------- @@ -3675,7 +3649,7 @@ rrc_gNB_generate_RRCRelease( rrc_gNB_mui, DCCH); - nr_rrc_data_req(ctxt_pP, DCCH, rrc_gNB_mui++, SDU_CONFIRM_NO, size, buffer, PDCP_TRANSMISSION_MODE_CONTROL); + nr_pdcp_data_req_srb(ctxt_pP->rntiMaybeUEid, DCCH, rrc_gNB_mui++, size, buffer); rrc_gNB_send_NGAP_UE_CONTEXT_RELEASE_COMPLETE(ctxt_pP->instance, ue_context_pP->ue_context.gNB_ue_ngap_id); ue_context_pP->ue_context.ue_release_timer_rrc = 1; diff --git a/openair2/RRC/NR/rrc_gNB_NGAP.c b/openair2/RRC/NR/rrc_gNB_NGAP.c index f9d7a492f5..2120d25920 100644 --- a/openair2/RRC/NR/rrc_gNB_NGAP.c +++ b/openair2/RRC/NR/rrc_gNB_NGAP.c @@ -620,7 +620,7 @@ int rrc_gNB_process_NGAP_DOWNLINK_NAS(MessageDef *msg_p, instance_t instance, mu AssertFatal(!NODE_IS_DU(RC.nrrrc[ctxt.module_id]->node_type), "illegal node type DU: receiving NGAP messages at this node\n"); /* Transfer data to PDCP */ rb_id_t srb_id = UE->Srb[2].Active ? DCCH1 : DCCH; - nr_rrc_data_req(&ctxt, srb_id, (*rrc_gNB_mui)++, SDU_CONFIRM_NO, length, buffer, PDCP_TRANSMISSION_MODE_CONTROL); + nr_pdcp_data_req_srb(ctxt.rntiMaybeUEid, srb_id, (*rrc_gNB_mui)++, length, buffer); return 0; } diff --git a/openair2/RRC/NR_UE/L2_interface_ue.c b/openair2/RRC/NR_UE/L2_interface_ue.c index c67069e975..9efb8e5012 100644 --- a/openair2/RRC/NR_UE/L2_interface_ue.c +++ b/openair2/RRC/NR_UE/L2_interface_ue.c @@ -148,40 +148,3 @@ int8_t nr_mac_rrc_data_req_ue(const module_id_t Mod_idP, return 0; } - -uint8_t -rrc_data_req_nr_ue( - const protocol_ctxt_t *const ctxt_pP, - const rb_id_t rb_idP, - const mui_t muiP, - const confirm_t confirmP, - const sdu_size_t sdu_sizeP, - uint8_t *const buffer_pP, - const pdcp_transmission_mode_t modeP -) -{ - MessageDef *message_p; - // Uses a new buffer to avoid issue with PDCP buffer content that could be changed by PDCP (asynchronous message handling). - uint8_t *message_buffer; - message_buffer = itti_malloc (TASK_RRC_UE, TASK_PDCP_UE, sdu_sizeP); - LOG_D(RRC,"Sending RRC message for SRB %ld, sdu_size %d\n",rb_idP, sdu_sizeP); - memcpy (message_buffer, buffer_pP, sdu_sizeP); - message_p = itti_alloc_new_message ( TASK_RRC_UE, 0, RRC_DCCH_DATA_REQ); - RRC_DCCH_DATA_REQ (message_p).frame = ctxt_pP->frame; - RRC_DCCH_DATA_REQ (message_p).enb_flag = ctxt_pP->enb_flag; - RRC_DCCH_DATA_REQ (message_p).rb_id = rb_idP; - RRC_DCCH_DATA_REQ (message_p).muip = muiP; - RRC_DCCH_DATA_REQ (message_p).confirmp = confirmP; - RRC_DCCH_DATA_REQ (message_p).sdu_size = sdu_sizeP; - RRC_DCCH_DATA_REQ (message_p).sdu_p = message_buffer; - RRC_DCCH_DATA_REQ (message_p).mode = modeP; - RRC_DCCH_DATA_REQ (message_p).module_id = ctxt_pP->module_id; - RRC_DCCH_DATA_REQ(message_p).rnti = ctxt_pP->rntiMaybeUEid; - RRC_DCCH_DATA_REQ (message_p).eNB_index = ctxt_pP->eNB_index; - itti_send_msg_to_task ( - TASK_PDCP_UE, - ctxt_pP->instance, - message_p); - return true; // TODO should be changed to a CNF message later, currently RRC lite does not used the returned value anyway. - -} diff --git a/openair2/RRC/NR_UE/rrc_UE.c b/openair2/RRC/NR_UE/rrc_UE.c index 3cff4e8c17..27390a1b51 100644 --- a/openair2/RRC/NR_UE/rrc_UE.c +++ b/openair2/RRC/NR_UE/rrc_UE.c @@ -1386,14 +1386,7 @@ static void rrc_ue_generate_RRCSetupComplete( //for (int i=0;irnti; - rrc_data_req_nr_ue(ctxt_pP, - DCCH, - nr_rrc_mui++, - SDU_CONFIRM_NO, - size, - buffer, - PDCP_TRANSMISSION_MODE_CONTROL); + nr_pdcp_data_req_srb(ctxt_pP->rntiMaybeUEid, DCCH, nr_rrc_mui++, size, buffer); } int8_t nr_rrc_ue_decode_ccch( const protocol_ctxt_t *const ctxt_pP, const NR_SRB_INFO *const Srb_info, const uint8_t gNB_index ){ @@ -1698,13 +1691,7 @@ int8_t nr_rrc_ue_decode_ccch( const protocol_ctxt_t *const ctxt_pP, const NR_SRB } LOG_T(NR_RRC, "\n"); - rrc_data_req_nr_ue (ctxt_pP, - DCCH, - nr_rrc_mui++, - SDU_CONFIRM_NO, - (enc_rval.encoded + 7) / 8, - buffer, - PDCP_TRANSMISSION_MODE_CONTROL); + nr_pdcp_data_req_srb(ctxt_pP->rntiMaybeUEid, DCCH, nr_rrc_mui++, (enc_rval.encoded + 7) / 8, buffer); } else LOG_W(NR_RRC,"securityModeCommand->criticalExtensions.present (%d) != NR_SecurityModeCommand__criticalExtensions_PR_securityModeCommand\n", securityModeCommand->criticalExtensions.present); @@ -2190,14 +2177,7 @@ nr_rrc_ue_establish_srb2( nr_rrc_mui, UE_MODULE_ID_TO_INSTANCE(ctxt_pP->module_id), DCCH); - rrc_data_req_nr_ue ( - ctxt_pP, - DCCH, - nr_rrc_mui++, - SDU_CONFIRM_NO, - size, - buffer, - PDCP_TRANSMISSION_MODE_CONTROL); + nr_pdcp_data_req_srb(ctxt_pP->rntiMaybeUEid, DCCH, nr_rrc_mui++, size, buffer); } // from NR SRB1 @@ -2446,21 +2426,8 @@ nr_rrc_ue_establish_srb2( /* Transfer data to PDCP */ PROTOCOL_CTXT_SET_BY_MODULE_ID(&ctxt, ue_mod_id, GNB_FLAG_NO, NR_UE_rrc_inst[ue_mod_id].Info[0].rnti, 0, 0,0); // check if SRB2 is created, if yes request data_req on DCCH1 (SRB2) - if(NR_UE_rrc_inst[ue_mod_id].SRB2_config[0] == NULL) { - rrc_data_req_nr_ue (&ctxt, - DCCH, - nr_rrc_mui++, - SDU_CONFIRM_NO, - length, buffer, - PDCP_TRANSMISSION_MODE_CONTROL); - } else { - rrc_data_req_nr_ue (&ctxt, - DCCH1, - nr_rrc_mui++, - SDU_CONFIRM_NO, - length, buffer, - PDCP_TRANSMISSION_MODE_CONTROL); - } + rb_id_t srb_id = NR_UE_rrc_inst[ue_mod_id].SRB2_config[0] == NULL ? DCCH : DCCH1; + nr_pdcp_data_req_srb(ctxt.rntiMaybeUEid, srb_id, nr_rrc_mui++, length, buffer); break; } @@ -2604,14 +2571,7 @@ nr_rrc_ue_process_ueCapabilityEnquiry( } LOG_I(NR_RRC, "UECapabilityInformation Encoded %zd bits (%zd bytes)\n",enc_rval.encoded,(enc_rval.encoded+7)/8); - rrc_data_req_nr_ue ( - ctxt_pP, - DCCH, - nr_rrc_mui++, - SDU_CONFIRM_NO, - (enc_rval.encoded + 7) / 8, - buffer, - PDCP_TRANSMISSION_MODE_CONTROL); + nr_pdcp_data_req_srb(ctxt_pP->rntiMaybeUEid, DCCH, nr_rrc_mui++, (enc_rval.encoded + 7) / 8, buffer); } } } diff --git a/openair2/RRC/NR_UE/rrc_proto.h b/openair2/RRC/NR_UE/rrc_proto.h index 510fbff230..2b2bab13e0 100644 --- a/openair2/RRC/NR_UE/rrc_proto.h +++ b/openair2/RRC/NR_UE/rrc_proto.h @@ -145,17 +145,6 @@ int8_t nr_mac_rrc_data_req_ue(const module_id_t Mod_idP, const rb_id_t Srb_id, uint8_t *buffer_pP); -uint8_t -rrc_data_req_nr_ue( - const protocol_ctxt_t *const ctxt_pP, - const rb_id_t rb_idP, - const mui_t muiP, - const confirm_t confirmP, - const sdu_size_t sdu_sizeP, - uint8_t *const buffer_pP, - const pdcp_transmission_mode_t modeP -); - /**\brief RRC UE task. \param void *args_p Pointer on arguments to start the task. */ void *rrc_nrue_task(void *args_p); From 0f43c2c713e9b9f29a509e5ac69d5841b804806c Mon Sep 17 00:00:00 2001 From: Robert Schmidt Date: Mon, 20 Mar 2023 11:24:08 +0100 Subject: [PATCH 16/89] PDCP DL handling: unlock PDCP before forwarding PDU nr_pdcp_entity_recv_sdu() processes PDCP SDUs, and forwards the PDU at the end of the function, while having the PDCP lock. This can lead to deadlocks, e.g., when another thread goes through RLC and tries to access the PDCP while the PDCP tries to lock the RLC. This commit changes the logic: instead of having recv_sdu() forward the PDU directly, it fills a buffer with the PDU (thus the name change to process_sdu()), and returns. We then unlock the PDCP, before forwarding the PDU, thus eliminating potential deadlocks. --- openair2/LAYER2/nr_pdcp/nr_pdcp_entity.c | 17 ++++++++++------- openair2/LAYER2/nr_pdcp/nr_pdcp_entity.h | 4 ++-- openair2/LAYER2/nr_pdcp/nr_pdcp_oai_api.c | 14 ++++++++++++-- openair2/LAYER2/nr_pdcp/nr_pdcp_oai_api.h | 2 ++ openair2/SDAP/nr_sdap/nr_sdap_entity.c | 17 ++++++++++++----- 5 files changed, 38 insertions(+), 16 deletions(-) diff --git a/openair2/LAYER2/nr_pdcp/nr_pdcp_entity.c b/openair2/LAYER2/nr_pdcp/nr_pdcp_entity.c index e632fb2024..326ed49686 100644 --- a/openair2/LAYER2/nr_pdcp/nr_pdcp_entity.c +++ b/openair2/LAYER2/nr_pdcp/nr_pdcp_entity.c @@ -176,14 +176,19 @@ static void nr_pdcp_entity_recv_pdu(nr_pdcp_entity_t *entity, } } -static void nr_pdcp_entity_recv_sdu(nr_pdcp_entity_t *entity, - char *buffer, int size, int sdu_id) +static int nr_pdcp_entity_process_sdu(nr_pdcp_entity_t *entity, + char *buffer, + int size, + int sdu_id, + char *pdu_buffer, + int pdu_max_size) { uint32_t count; int sn; int header_size; int integrity_size; - char buf[size + 3 + 4]; + char *buf = pdu_buffer; + DevAssert(size + 3 + 4 <= pdu_max_size); int dc_bit; entity->stats.rxsdu_pkts++; entity->stats.rxsdu_bytes += size; @@ -243,13 +248,11 @@ static void nr_pdcp_entity_recv_sdu(nr_pdcp_entity_t *entity, entity->tx_next++; - entity->deliver_pdu(entity->deliver_pdu_data, entity, buf, - header_size + size + integrity_size, sdu_id); entity->stats.txpdu_pkts++; entity->stats.txpdu_bytes += header_size + size + integrity_size; entity->stats.txpdu_sn = sn; - + return header_size + size + integrity_size; } /* may be called several times, take care to clean previous settings */ @@ -420,7 +423,7 @@ nr_pdcp_entity_t *new_nr_pdcp_entity( ret->type = type; ret->recv_pdu = nr_pdcp_entity_recv_pdu; - ret->recv_sdu = nr_pdcp_entity_recv_sdu; + ret->process_sdu = nr_pdcp_entity_process_sdu; ret->set_security = nr_pdcp_entity_set_security; ret->set_time = nr_pdcp_entity_set_time; diff --git a/openair2/LAYER2/nr_pdcp/nr_pdcp_entity.h b/openair2/LAYER2/nr_pdcp/nr_pdcp_entity.h index 4ad2508e4a..c693ed81d4 100644 --- a/openair2/LAYER2/nr_pdcp/nr_pdcp_entity.h +++ b/openair2/LAYER2/nr_pdcp/nr_pdcp_entity.h @@ -73,8 +73,8 @@ typedef struct nr_pdcp_entity_t { /* functions provided by the PDCP module */ void (*recv_pdu)(struct nr_pdcp_entity_t *entity, char *buffer, int size); - void (*recv_sdu)(struct nr_pdcp_entity_t *entity, char *buffer, int size, - int sdu_id); + int (*process_sdu)(struct nr_pdcp_entity_t *entity, char *buffer, int size, + int sdu_id, char *pdu_buffer, int pdu_max_size); void (*delete_entity)(struct nr_pdcp_entity_t *entity); void (*get_stats)(struct nr_pdcp_entity_t *entity, nr_pdcp_statistics_t *out); diff --git a/openair2/LAYER2/nr_pdcp/nr_pdcp_oai_api.c b/openair2/LAYER2/nr_pdcp/nr_pdcp_oai_api.c index a91f4e883f..412b348302 100644 --- a/openair2/LAYER2/nr_pdcp/nr_pdcp_oai_api.c +++ b/openair2/LAYER2/nr_pdcp/nr_pdcp_oai_api.c @@ -1173,10 +1173,15 @@ bool nr_pdcp_data_req_srb(ue_id_t ue_id, return 0; } - rb->recv_sdu(rb, (char *)sdu_buffer, sdu_buffer_size, muiP); + int max_size = sdu_buffer_size + 3 + 4; // 3: max header, 4: max integrity + char pdu_buf[max_size]; + int pdu_size = rb->process_sdu(rb, (char *)sdu_buffer, sdu_buffer_size, muiP, pdu_buf, max_size); + deliver_pdu deliver_pdu_cb = rb->deliver_pdu; nr_pdcp_manager_unlock(nr_pdcp_ue_manager); + deliver_pdu_cb(rb->deliver_pdu_data, rb, pdu_buf, pdu_size, muiP); + return 1; } @@ -1223,10 +1228,15 @@ bool nr_pdcp_data_req_drb(protocol_ctxt_t *ctxt_pP, return 0; } - rb->recv_sdu(rb, (char *)sdu_buffer, sdu_buffer_size, muiP); + int max_size = sdu_buffer_size + 3 + 4; // 3: max header, 4: max integrity + char pdu_buf[max_size]; + int pdu_size = rb->process_sdu(rb, (char *)sdu_buffer, sdu_buffer_size, muiP, pdu_buf, max_size); + deliver_pdu deliver_pdu_cb = rb->deliver_pdu; nr_pdcp_manager_unlock(nr_pdcp_ue_manager); + deliver_pdu_cb(rb->deliver_pdu_data, rb, pdu_buf, pdu_size, muiP); + return 1; } diff --git a/openair2/LAYER2/nr_pdcp/nr_pdcp_oai_api.h b/openair2/LAYER2/nr_pdcp/nr_pdcp_oai_api.h index 1b69b9d2e7..84b918529f 100644 --- a/openair2/LAYER2/nr_pdcp/nr_pdcp_oai_api.h +++ b/openair2/LAYER2/nr_pdcp/nr_pdcp_oai_api.h @@ -77,6 +77,8 @@ bool cu_f1u_data_req(protocol_ctxt_t *ctxt_pP, const uint32_t *const sourceL2Id, const uint32_t *const destinationL2Id); +typedef void (*deliver_pdu)(void *data, nr_pdcp_entity_t *entity, + char *buf, int size, int sdu_id); bool nr_pdcp_data_req_srb(ue_id_t ue_id, const rb_id_t rb_id, const mui_t muiP, diff --git a/openair2/SDAP/nr_sdap/nr_sdap_entity.c b/openair2/SDAP/nr_sdap/nr_sdap_entity.c index 357bec24ad..e81490630c 100644 --- a/openair2/SDAP/nr_sdap/nr_sdap_entity.c +++ b/openair2/SDAP/nr_sdap/nr_sdap_entity.c @@ -39,11 +39,18 @@ instance_t *N3GTPUInst = NULL; void nr_pdcp_submit_sdap_ctrl_pdu(ue_id_t ue_id, rb_id_t sdap_ctrl_pdu_drb, nr_sdap_ul_hdr_t ctrl_pdu) { - nr_pdcp_ue_t *ue; - nr_pdcp_ue_manager_t *nr_pdcp_ue_manager; - nr_pdcp_ue_manager = nr_pdcp_sdap_get_ue_manager(); - ue = nr_pdcp_manager_get_ue(nr_pdcp_ue_manager, ue_id); - ue->drb[sdap_ctrl_pdu_drb-1]->recv_sdu(ue->drb[sdap_ctrl_pdu_drb-1], (char*)&ctrl_pdu, SDAP_HDR_LENGTH, RLC_MUI_UNDEFINED); + + protocol_ctxt_t ctxt = { .rntiMaybeUEid = ue_id }; + nr_pdcp_data_req_drb(&ctxt, + SRB_FLAG_NO, + sdap_ctrl_pdu_drb, + RLC_MUI_UNDEFINED, + SDU_CONFIRM_NO, + SDAP_HDR_LENGTH, + (unsigned char *)&ctrl_pdu, + PDCP_TRANSMISSION_MODE_UNKNOWN, + NULL, + NULL); LOG_D(SDAP, "Control PDU - Submitting Control PDU to DRB ID: %ld\n", sdap_ctrl_pdu_drb); LOG_D(SDAP, "QFI: %u\n R: %u\n D/C: %u\n", ctrl_pdu.QFI, ctrl_pdu.R, ctrl_pdu.DC); return; From ffebe2498d1877be324375cd3ab1c995a0d9583f Mon Sep 17 00:00:00 2001 From: Robert Schmidt Date: Mon, 20 Mar 2023 11:44:15 +0100 Subject: [PATCH 17/89] Simplify callback for deliver_pdu --- openair2/LAYER2/nr_pdcp/nr_pdcp_entity.c | 2 +- openair2/LAYER2/nr_pdcp/nr_pdcp_entity.h | 4 +- openair2/LAYER2/nr_pdcp/nr_pdcp_oai_api.c | 65 +++++------------------ openair2/LAYER2/nr_pdcp/nr_pdcp_oai_api.h | 2 +- 4 files changed, 16 insertions(+), 57 deletions(-) diff --git a/openair2/LAYER2/nr_pdcp/nr_pdcp_entity.c b/openair2/LAYER2/nr_pdcp/nr_pdcp_entity.c index 326ed49686..b503fcc010 100644 --- a/openair2/LAYER2/nr_pdcp/nr_pdcp_entity.c +++ b/openair2/LAYER2/nr_pdcp/nr_pdcp_entity.c @@ -401,7 +401,7 @@ nr_pdcp_entity_t *new_nr_pdcp_entity( void (*deliver_sdu)(void *deliver_sdu_data, struct nr_pdcp_entity_t *entity, char *buf, int size), void *deliver_sdu_data, - void (*deliver_pdu)(void *deliver_pdu_data, struct nr_pdcp_entity_t *entity, + void (*deliver_pdu)(void *deliver_pdu_data, ue_id_t ue_id, int rb_id, char *buf, int size, int sdu_id), void *deliver_pdu_data, int sn_size, diff --git a/openair2/LAYER2/nr_pdcp/nr_pdcp_entity.h b/openair2/LAYER2/nr_pdcp/nr_pdcp_entity.h index c693ed81d4..762c91c747 100644 --- a/openair2/LAYER2/nr_pdcp/nr_pdcp_entity.h +++ b/openair2/LAYER2/nr_pdcp/nr_pdcp_entity.h @@ -95,7 +95,7 @@ typedef struct nr_pdcp_entity_t { void (*deliver_sdu)(void *deliver_sdu_data, struct nr_pdcp_entity_t *entity, char *buf, int size); void *deliver_sdu_data; - void (*deliver_pdu)(void *deliver_pdu_data, struct nr_pdcp_entity_t *entity, + void (*deliver_pdu)(void *deliver_pdu_data, ue_id_t ue_id, int rb_id, char *buf, int size, int sdu_id); void *deliver_pdu_data; @@ -176,7 +176,7 @@ nr_pdcp_entity_t *new_nr_pdcp_entity( void (*deliver_sdu)(void *deliver_sdu_data, struct nr_pdcp_entity_t *entity, char *buf, int size), void *deliver_sdu_data, - void (*deliver_pdu)(void *deliver_pdu_data, struct nr_pdcp_entity_t *entity, + void (*deliver_pdu)(void *deliver_pdu_data, ue_id_t ue_id, int rb_id, char *buf, int size, int sdu_id), void *deliver_pdu_data, int sn_size, diff --git a/openair2/LAYER2/nr_pdcp/nr_pdcp_oai_api.c b/openair2/LAYER2/nr_pdcp/nr_pdcp_oai_api.c index 412b348302..e27fb2b25a 100644 --- a/openair2/LAYER2/nr_pdcp/nr_pdcp_oai_api.c +++ b/openair2/LAYER2/nr_pdcp/nr_pdcp_oai_api.c @@ -653,35 +653,12 @@ static void deliver_sdu_drb(void *_ue, nr_pdcp_entity_t *entity, } } -static void deliver_pdu_drb(void *_ue, nr_pdcp_entity_t *entity, +static void deliver_pdu_drb(void *deliver_pdu_data, ue_id_t ue_id, int rb_id, char *buf, int size, int sdu_id) { - nr_pdcp_ue_t *ue = _ue; - int rb_id; - protocol_ctxt_t ctxt; - int i; - mem_block_t *memblock; + DevAssert(deliver_pdu_data == NULL); + protocol_ctxt_t ctxt = { .enb_flag = 1, .rntiMaybeUEid = ue_id }; - for (i = 0; i < MAX_DRBS_PER_UE; i++) { - if (entity == ue->drb[i]) { - rb_id = i+1; - goto rb_found; - } - } - - LOG_E(PDCP, "%s:%d:%s: fatal, no RB found for UE ID/RNTI %ld\n", __FILE__, __LINE__, __FUNCTION__, ue->rntiMaybeUEid); - exit(1); - -rb_found: - ctxt.module_id = 0; - ctxt.enb_flag = 1; - ctxt.instance = 0; - ctxt.frame = 0; - ctxt.subframe = 0; - ctxt.eNB_index = 0; - ctxt.brOption = 0; - - ctxt.rntiMaybeUEid = ue->rntiMaybeUEid; if (NODE_IS_CU(node_type)) { MessageDef *message_p = itti_alloc_new_message_sized(TASK_PDCP_ENB, 0, GTPV1U_TUNNEL_DATA_REQ, @@ -696,14 +673,14 @@ rb_found: req->buffer = gtpu_buffer_p; req->length = size; req->offset = GTPU_HEADER_OVERHEAD_MAX; - req->ue_id = ue->rntiMaybeUEid; + req->ue_id = ue_id; req->bearer_id = rb_id; LOG_I(PDCP, "%s() (drb %d) sending message to gtp size %d\n", __func__, rb_id, size); extern instance_t CUuniqInstance; itti_send_msg_to_task(TASK_GTPV1_U, CUuniqInstance, message_p); } else { - memblock = get_free_mem_block(size, __FUNCTION__); + mem_block_t *memblock = get_free_mem_block(size, __FUNCTION__); memcpy(memblock->data, buf, size); LOG_D(PDCP, "%s(): (drb %d) calling rlc_data_req size %d\n", __func__, rb_id, size); //for (i = 0; i < size; i++) printf(" %2.2x", (unsigned char)memblock->data[i]); @@ -755,40 +732,22 @@ srb_found: } } -static void deliver_pdu_srb(void *_ue, nr_pdcp_entity_t *entity, +static void deliver_pdu_srb(void *deliver_pdu_data, ue_id_t ue_id, int srb_id, char *buf, int size, int sdu_id) { - nr_pdcp_ue_t *ue = _ue; - int srb_id; - int i; - - for (i = 0; i < sizeofArray(ue->srb) ; i++) { - if (entity == ue->srb[i]) { - srb_id = i+1; - goto srb_found; - } - } - - LOG_E(PDCP, "%s:%d:%s: fatal, no SRB found for UE ID/RNTI %ld\n", __FILE__, __LINE__, __FUNCTION__, ue->rntiMaybeUEid); - exit(1); - -srb_found: - - + bool is_gnb = *(bool *) deliver_pdu_data; LOG_D(PDCP, "%s(): (srb %d) calling rlc_data_req size %d\n", __func__, srb_id, size); - //for (i = 0; i < size; i++) printf(" %2.2x", (unsigned char)memblock->data[i]); - //printf("\n"); - if (entity->is_gnb) { + if (is_gnb) { f1ap_dl_rrc_message_t dl_rrc = {.old_gNB_DU_ue_id = 0xFFFFFF, .rrc_container = (uint8_t *)buf, .rrc_container_length = size, - .rnti = ue->rntiMaybeUEid, + .rnti = ue_id, .srb_id = srb_id}; gNB_RRC_INST *rrc = RC.nrrrc[0]; rrc->mac_rrc.dl_rrc_message_transfer(0, &dl_rrc); } else { // UE mem_block_t *memblock; - protocol_ctxt_t ctxt = {.module_id = 0, .enb_flag = 1, .instance = 0, .frame = 0, .subframe = 0, .eNB_index = 0, .brOption = 0, .rntiMaybeUEid = ue->rntiMaybeUEid}; + protocol_ctxt_t ctxt = { .enb_flag = 1, .rntiMaybeUEid = ue_id }; memblock = get_free_mem_block(size, __FUNCTION__); memcpy(memblock->data, buf, size); enqueue_rlc_data_req(&ctxt, 1, MBMS_FLAG_NO, srb_id, sdu_id, 0, size, memblock); @@ -1180,7 +1139,7 @@ bool nr_pdcp_data_req_srb(ue_id_t ue_id, nr_pdcp_manager_unlock(nr_pdcp_ue_manager); - deliver_pdu_cb(rb->deliver_pdu_data, rb, pdu_buf, pdu_size, muiP); + deliver_pdu_cb(&rb->is_gnb, ue_id, rb_id, pdu_buf, pdu_size, muiP); return 1; } @@ -1235,7 +1194,7 @@ bool nr_pdcp_data_req_drb(protocol_ctxt_t *ctxt_pP, nr_pdcp_manager_unlock(nr_pdcp_ue_manager); - deliver_pdu_cb(rb->deliver_pdu_data, rb, pdu_buf, pdu_size, muiP); + deliver_pdu_cb(NULL, ue_id, rb_id, pdu_buf, pdu_size, muiP); return 1; } diff --git a/openair2/LAYER2/nr_pdcp/nr_pdcp_oai_api.h b/openair2/LAYER2/nr_pdcp/nr_pdcp_oai_api.h index 84b918529f..51c3e95cf5 100644 --- a/openair2/LAYER2/nr_pdcp/nr_pdcp_oai_api.h +++ b/openair2/LAYER2/nr_pdcp/nr_pdcp_oai_api.h @@ -77,7 +77,7 @@ bool cu_f1u_data_req(protocol_ctxt_t *ctxt_pP, const uint32_t *const sourceL2Id, const uint32_t *const destinationL2Id); -typedef void (*deliver_pdu)(void *data, nr_pdcp_entity_t *entity, +typedef void (*deliver_pdu)(void *data, ue_id_t ue_id, int srb_id, char *buf, int size, int sdu_id); bool nr_pdcp_data_req_srb(ue_id_t ue_id, const rb_id_t rb_id, From 6529d875d8ab45a6d901891e80b28ffc2c6f2398 Mon Sep 17 00:00:00 2001 From: Robert Schmidt Date: Wed, 22 Mar 2023 16:44:51 +0100 Subject: [PATCH 18/89] nr_pdcp_data_req_srb(): take callback to forward data --- openair2/LAYER2/nr_pdcp/nr_pdcp_oai_api.c | 48 ++++++++++++----------- openair2/LAYER2/nr_pdcp/nr_pdcp_oai_api.h | 9 ++++- openair2/RRC/NR/rrc_gNB.c | 38 ++++++++++-------- openair2/RRC/NR/rrc_gNB_NGAP.c | 2 +- openair2/RRC/NR_UE/rrc_UE.c | 10 ++--- 5 files changed, 60 insertions(+), 47 deletions(-) diff --git a/openair2/LAYER2/nr_pdcp/nr_pdcp_oai_api.c b/openair2/LAYER2/nr_pdcp/nr_pdcp_oai_api.c index e27fb2b25a..efbb638a7e 100644 --- a/openair2/LAYER2/nr_pdcp/nr_pdcp_oai_api.c +++ b/openair2/LAYER2/nr_pdcp/nr_pdcp_oai_api.c @@ -732,26 +732,26 @@ srb_found: } } -static void deliver_pdu_srb(void *deliver_pdu_data, ue_id_t ue_id, int srb_id, - char *buf, int size, int sdu_id) +void deliver_pdu_srb_rlc(void *deliver_pdu_data, ue_id_t ue_id, int srb_id, + char *buf, int size, int sdu_id) { - bool is_gnb = *(bool *) deliver_pdu_data; - LOG_D(PDCP, "%s(): (srb %d) calling rlc_data_req size %d\n", __func__, srb_id, size); - if (is_gnb) { - f1ap_dl_rrc_message_t dl_rrc = {.old_gNB_DU_ue_id = 0xFFFFFF, - .rrc_container = (uint8_t *)buf, - .rrc_container_length = size, - .rnti = ue_id, - .srb_id = srb_id}; - gNB_RRC_INST *rrc = RC.nrrrc[0]; - rrc->mac_rrc.dl_rrc_message_transfer(0, &dl_rrc); - } else { // UE - mem_block_t *memblock; - protocol_ctxt_t ctxt = { .enb_flag = 1, .rntiMaybeUEid = ue_id }; - memblock = get_free_mem_block(size, __FUNCTION__); - memcpy(memblock->data, buf, size); - enqueue_rlc_data_req(&ctxt, 1, MBMS_FLAG_NO, srb_id, sdu_id, 0, size, memblock); - } + protocol_ctxt_t ctxt = { .enb_flag = 1, .rntiMaybeUEid = ue_id }; + mem_block_t *memblock = get_free_mem_block(size, __FUNCTION__); + memcpy(memblock->data, buf, size); + enqueue_rlc_data_req(&ctxt, 1, MBMS_FLAG_NO, srb_id, sdu_id, 0, size, memblock); +} + +void deliver_pdu_srb_f1(void *deliver_pdu_data, ue_id_t ue_id, int srb_id, + char *buf, int size, int sdu_id) +{ + DevAssert(deliver_pdu_data != NULL); + gNB_RRC_INST *rrc = deliver_pdu_data; + f1ap_dl_rrc_message_t dl_rrc = {.old_gNB_DU_ue_id = 0xFFFFFF, + .rrc_container = (uint8_t *)buf, + .rrc_container_length = size, + .rnti = ue_id, + .srb_id = srb_id}; + rrc->mac_rrc.dl_rrc_message_transfer(0, &dl_rrc); } static void add_srb(int is_gnb, ue_id_t rntiMaybeUEid, struct NR_SRB_ToAddMod *s, int ciphering_algorithm, int integrity_algorithm, unsigned char *ciphering_key, unsigned char *integrity_key) @@ -772,7 +772,7 @@ static void add_srb(int is_gnb, ue_id_t rntiMaybeUEid, struct NR_SRB_ToAddMod *s } else { pdcp_srb = new_nr_pdcp_entity(NR_PDCP_SRB, is_gnb, srb_id, 0, false, false, // sdap parameters - deliver_sdu_srb, ue, deliver_pdu_srb, ue, + deliver_sdu_srb, ue, NULL, ue, 12, t_Reordering, -1, ciphering_algorithm, integrity_algorithm, @@ -1111,7 +1111,9 @@ bool nr_pdcp_data_req_srb(ue_id_t ue_id, const rb_id_t rb_id, const mui_t muiP, const sdu_size_t sdu_buffer_size, - unsigned char *const sdu_buffer) + unsigned char *const sdu_buffer, + deliver_pdu deliver_pdu_cb, + void *data) { LOG_D(PDCP, "%s() called, size %d\n", __func__, sdu_buffer_size); nr_pdcp_ue_t *ue; @@ -1135,11 +1137,11 @@ bool nr_pdcp_data_req_srb(ue_id_t ue_id, int max_size = sdu_buffer_size + 3 + 4; // 3: max header, 4: max integrity char pdu_buf[max_size]; int pdu_size = rb->process_sdu(rb, (char *)sdu_buffer, sdu_buffer_size, muiP, pdu_buf, max_size); - deliver_pdu deliver_pdu_cb = rb->deliver_pdu; + AssertFatal(rb->deliver_pdu == NULL, "SRB callback should be NULL, to be provided on every invocation\n"); nr_pdcp_manager_unlock(nr_pdcp_ue_manager); - deliver_pdu_cb(&rb->is_gnb, ue_id, rb_id, pdu_buf, pdu_size, muiP); + deliver_pdu_cb(data, ue_id, rb_id, pdu_buf, pdu_size, muiP); return 1; } diff --git a/openair2/LAYER2/nr_pdcp/nr_pdcp_oai_api.h b/openair2/LAYER2/nr_pdcp/nr_pdcp_oai_api.h index 51c3e95cf5..0fd23d2f6b 100644 --- a/openair2/LAYER2/nr_pdcp/nr_pdcp_oai_api.h +++ b/openair2/LAYER2/nr_pdcp/nr_pdcp_oai_api.h @@ -79,11 +79,18 @@ bool cu_f1u_data_req(protocol_ctxt_t *ctxt_pP, typedef void (*deliver_pdu)(void *data, ue_id_t ue_id, int srb_id, char *buf, int size, int sdu_id); +/* default implementation of deliver_pdu */ +void deliver_pdu_srb_rlc(void *data, ue_id_t ue_id, int srb_id, char *buf, + int size, int sdu_id); +void deliver_pdu_srb_f1(void *data, ue_id_t ue_id, int srb_id, char *buf, + int size, int sdu_id); bool nr_pdcp_data_req_srb(ue_id_t ue_id, const rb_id_t rb_id, const mui_t muiP, const sdu_size_t sdu_buffer_size, - unsigned char *const sdu_buffer); + unsigned char *const sdu_buffer, + deliver_pdu deliver_cb, + void *data); bool nr_pdcp_data_req_drb(protocol_ctxt_t *ctxt_pP, const srb_flag_t srb_flagP, const rb_id_t rb_id, diff --git a/openair2/RRC/NR/rrc_gNB.c b/openair2/RRC/NR/rrc_gNB.c index b51d4563da..2af5e620f6 100644 --- a/openair2/RRC/NR/rrc_gNB.c +++ b/openair2/RRC/NR/rrc_gNB.c @@ -573,7 +573,7 @@ static void rrc_gNB_generate_defaultRRCReconfiguration(const protocol_ctxt_t *co ue_context_pP->ue_context.rnti); AssertFatal(!NODE_IS_DU(rrc->node_type), "illegal node type DU!\n"); - nr_pdcp_data_req_srb(ctxt_pP->rntiMaybeUEid, DCCH, rrc_gNB_mui++, size, buffer); + nr_pdcp_data_req_srb(ctxt_pP->rntiMaybeUEid, DCCH, rrc_gNB_mui++, size, buffer, deliver_pdu_srb_f1, rrc); if (NODE_IS_DU(rrc->node_type) || NODE_IS_MONOLITHIC(rrc->node_type)) { gNB_RRC_UE_t *ue_p = &ue_context_pP->ue_context; @@ -762,7 +762,7 @@ rrc_gNB_generate_dedicatedRRCReconfiguration( ctxt_pP->module_id, DCCH); - nr_pdcp_data_req_srb(ctxt_pP->rntiMaybeUEid, DCCH, rrc_gNB_mui++, size, buffer); + nr_pdcp_data_req_srb(ctxt_pP->rntiMaybeUEid, DCCH, rrc_gNB_mui++, size, buffer, deliver_pdu_srb_f1, rrc); if (NODE_IS_DU(rrc->node_type) || NODE_IS_MONOLITHIC(rrc->node_type)) { nr_mac_update_cellgroup(RC.nrmac[rrc->module_id], ue_context_pP->ue_context.rnti, ue_context_pP->ue_context.masterCellGroup); @@ -929,15 +929,16 @@ rrc_gNB_modify_dedicatedRRCReconfiguration( ctxt_pP->module_id, DCCH); - nr_pdcp_data_req_srb(ctxt_pP->rntiMaybeUEid, DCCH, rrc_gNB_mui++, size, buffer); + gNB_RRC_INST *rrc = RC.nrrrc[ctxt_pP->module_id]; + nr_pdcp_data_req_srb(ctxt_pP->rntiMaybeUEid, DCCH, rrc_gNB_mui++, size, buffer, deliver_pdu_srb_f1, rrc); - if (NODE_IS_DU(RC.nrrrc[ctxt_pP->module_id]->node_type) || NODE_IS_MONOLITHIC(RC.nrrrc[ctxt_pP->module_id]->node_type)) { + if (NODE_IS_DU(rrc->node_type) || NODE_IS_MONOLITHIC(rrc->node_type)) { uint32_t delay_ms = ue_p->masterCellGroup && ue_p->masterCellGroup->spCellConfig && ue_p->masterCellGroup->spCellConfig->spCellConfigDedicated && ue_p->masterCellGroup->spCellConfig->spCellConfigDedicated->downlinkBWP_ToAddModList ? NR_RRC_RECONFIGURATION_DELAY_MS + NR_RRC_BWP_SWITCHING_DELAY_MS : NR_RRC_RECONFIGURATION_DELAY_MS; - nr_mac_enable_ue_rrc_processing_timer(ctxt_pP->module_id, ue_p->rnti, *RC.nrrrc[ctxt_pP->module_id]->carrier.servingcellconfigcommon->ssbSubcarrierSpacing, delay_ms); + nr_mac_enable_ue_rrc_processing_timer(ctxt_pP->module_id, ue_p->rnti, *rrc->carrier.servingcellconfigcommon->ssbSubcarrierSpacing, delay_ms); } } @@ -1013,15 +1014,16 @@ rrc_gNB_generate_dedicatedRRCReconfiguration_release( ctxt_pP->module_id, DCCH); - nr_pdcp_data_req_srb(ctxt_pP->rntiMaybeUEid, DCCH, rrc_gNB_mui++, size, buffer); + gNB_RRC_INST *rrc = RC.nrrrc[ctxt_pP->module_id]; + nr_pdcp_data_req_srb(ctxt_pP->rntiMaybeUEid, DCCH, rrc_gNB_mui++, size, buffer, deliver_pdu_srb_f1, rrc); - if (NODE_IS_DU(RC.nrrrc[ctxt_pP->module_id]->node_type) || NODE_IS_MONOLITHIC(RC.nrrrc[ctxt_pP->module_id]->node_type)) { + if (NODE_IS_DU(rrc->node_type) || NODE_IS_MONOLITHIC(rrc->node_type)) { uint32_t delay_ms = ue_p->masterCellGroup && ue_p->masterCellGroup->spCellConfig && ue_p->masterCellGroup->spCellConfig->spCellConfigDedicated && ue_p->masterCellGroup->spCellConfig->spCellConfigDedicated->downlinkBWP_ToAddModList ? NR_RRC_RECONFIGURATION_DELAY_MS + NR_RRC_BWP_SWITCHING_DELAY_MS : NR_RRC_RECONFIGURATION_DELAY_MS; - nr_mac_enable_ue_rrc_processing_timer(ctxt_pP->module_id, ue_p->rnti, *RC.nrrrc[ctxt_pP->module_id]->carrier.servingcellconfigcommon->ssbSubcarrierSpacing, delay_ms); + nr_mac_enable_ue_rrc_processing_timer(ctxt_pP->module_id, ue_p->rnti, *rrc->carrier.servingcellconfigcommon->ssbSubcarrierSpacing, delay_ms); } } @@ -1274,7 +1276,7 @@ void rrc_gNB_generate_RRCReestablishment(const protocol_ctxt_t *ctxt_pP, apply_macrlc_config_reest(rrc, ue_context_pP, ctxt_pP, ctxt_pP->rntiMaybeUEid); } - nr_pdcp_data_req_srb(ctxt_pP->rntiMaybeUEid, DCCH, rrc_gNB_mui++, size, buffer); + nr_pdcp_data_req_srb(ctxt_pP->rntiMaybeUEid, DCCH, rrc_gNB_mui++, size, buffer, deliver_pdu_srb_f1, rrc); } //----------------------------------------------------------------------------- @@ -1487,7 +1489,7 @@ cellGroupConfig->physicalCellGroupConfig = ue_p->masterCellGroup->physicalCellGr DCCH); nr_mac_update_cellgroup(RC.nrmac[rrc->module_id], ue_context_pP->ue_context.rnti, cellGroupConfig); - nr_pdcp_data_req_srb(ctxt_pP->rntiMaybeUEid, DCCH, rrc_gNB_mui++, size, buffer); + nr_pdcp_data_req_srb(ctxt_pP->rntiMaybeUEid, DCCH, rrc_gNB_mui++, size, buffer, deliver_pdu_srb_f1, rrc); } if (NODE_IS_DU(RC.nrrrc[ctxt_pP->module_id]->node_type) || NODE_IS_MONOLITHIC(RC.nrrrc[ctxt_pP->module_id]->node_type)) { @@ -1538,15 +1540,16 @@ int nr_rrc_reconfiguration_req(rrc_gNB_ue_context_t *const ue_context_pP nr_mac_update_cellgroup(RC.nrmac[ctxt_pP->module_id], ue_context_pP->ue_context.rnti, masterCellGroup); - nr_pdcp_data_req_srb(ctxt_pP->rntiMaybeUEid, DCCH, rrc_gNB_mui++, size, buffer); + gNB_RRC_INST *rrc = RC.nrrrc[ctxt_pP->module_id]; + nr_pdcp_data_req_srb(ctxt_pP->rntiMaybeUEid, DCCH, rrc_gNB_mui++, size, buffer, deliver_pdu_srb_f1, rrc); - if (NODE_IS_DU(RC.nrrrc[ctxt_pP->module_id]->node_type) || NODE_IS_MONOLITHIC(RC.nrrrc[ctxt_pP->module_id]->node_type)) { + if (NODE_IS_DU(rrc->node_type) || NODE_IS_MONOLITHIC(rrc->node_type)) { uint32_t delay_ms = ue_p->masterCellGroup && ue_p->masterCellGroup->spCellConfig && ue_p->masterCellGroup->spCellConfig->spCellConfigDedicated && ue_p->masterCellGroup->spCellConfig->spCellConfigDedicated->downlinkBWP_ToAddModList ? NR_RRC_RECONFIGURATION_DELAY_MS + NR_RRC_BWP_SWITCHING_DELAY_MS : NR_RRC_RECONFIGURATION_DELAY_MS; - nr_mac_enable_ue_rrc_processing_timer(ctxt_pP->module_id, ue_p->rnti, *RC.nrrrc[ctxt_pP->module_id]->carrier.servingcellconfigcommon->ssbSubcarrierSpacing, delay_ms); + nr_mac_enable_ue_rrc_processing_timer(ctxt_pP->module_id, ue_p->rnti, *rrc->carrier.servingcellconfigcommon->ssbSubcarrierSpacing, delay_ms); } return 0; @@ -3589,7 +3592,7 @@ rrc_gNB_generate_SecurityModeCommand( gNB_RRC_INST *rrc = RC.nrrrc[ctxt_pP->module_id]; AssertFatal(!NODE_IS_DU(rrc->node_type), "illegal node type DU!\n"); - nr_pdcp_data_req_srb(ctxt_pP->rntiMaybeUEid, DCCH, rrc_gNB_mui++, size, buffer); + nr_pdcp_data_req_srb(ctxt_pP->rntiMaybeUEid, DCCH, rrc_gNB_mui++, size, buffer, deliver_pdu_srb_f1, rrc); } void @@ -3615,7 +3618,7 @@ rrc_gNB_generate_UECapabilityEnquiry( gNB_RRC_INST *rrc = RC.nrrrc[ctxt_pP->module_id]; AssertFatal(!NODE_IS_DU(rrc->node_type), "illegal node type DU!\n"); - nr_pdcp_data_req_srb(ctxt_pP->rntiMaybeUEid, DCCH, rrc_gNB_mui++, size, buffer); + nr_pdcp_data_req_srb(ctxt_pP->rntiMaybeUEid, DCCH, rrc_gNB_mui++, size, buffer, deliver_pdu_srb_f1, rrc); } //----------------------------------------------------------------------------- @@ -3649,12 +3652,13 @@ rrc_gNB_generate_RRCRelease( rrc_gNB_mui, DCCH); - nr_pdcp_data_req_srb(ctxt_pP->rntiMaybeUEid, DCCH, rrc_gNB_mui++, size, buffer); + gNB_RRC_INST *rrc = RC.nrrrc[ctxt_pP->module_id]; + nr_pdcp_data_req_srb(ctxt_pP->rntiMaybeUEid, DCCH, rrc_gNB_mui++, size, buffer, deliver_pdu_srb_f1, rrc); rrc_gNB_send_NGAP_UE_CONTEXT_RELEASE_COMPLETE(ctxt_pP->instance, ue_context_pP->ue_context.gNB_ue_ngap_id); ue_context_pP->ue_context.ue_release_timer_rrc = 1; - if (NODE_IS_CU(RC.nrrrc[ctxt_pP->module_id]->node_type)) { + if (NODE_IS_CU(rrc->node_type)) { uint8_t *message_buffer = itti_malloc (TASK_RRC_GNB, TASK_CU_F1, size); memcpy (message_buffer, buffer, size); MessageDef *m = itti_alloc_new_message(TASK_RRC_GNB, 0, F1AP_UE_CONTEXT_RELEASE_CMD); diff --git a/openair2/RRC/NR/rrc_gNB_NGAP.c b/openair2/RRC/NR/rrc_gNB_NGAP.c index 2120d25920..d38846e9ee 100644 --- a/openair2/RRC/NR/rrc_gNB_NGAP.c +++ b/openair2/RRC/NR/rrc_gNB_NGAP.c @@ -620,7 +620,7 @@ int rrc_gNB_process_NGAP_DOWNLINK_NAS(MessageDef *msg_p, instance_t instance, mu AssertFatal(!NODE_IS_DU(RC.nrrrc[ctxt.module_id]->node_type), "illegal node type DU: receiving NGAP messages at this node\n"); /* Transfer data to PDCP */ rb_id_t srb_id = UE->Srb[2].Active ? DCCH1 : DCCH; - nr_pdcp_data_req_srb(ctxt.rntiMaybeUEid, srb_id, (*rrc_gNB_mui)++, length, buffer); + nr_pdcp_data_req_srb(ctxt.rntiMaybeUEid, srb_id, (*rrc_gNB_mui)++, length, buffer, deliver_pdu_srb_f1, RC.nrrrc[instance]); return 0; } diff --git a/openair2/RRC/NR_UE/rrc_UE.c b/openair2/RRC/NR_UE/rrc_UE.c index 27390a1b51..87f5426b94 100644 --- a/openair2/RRC/NR_UE/rrc_UE.c +++ b/openair2/RRC/NR_UE/rrc_UE.c @@ -1386,7 +1386,7 @@ static void rrc_ue_generate_RRCSetupComplete( //for (int i=0;irntiMaybeUEid, DCCH, nr_rrc_mui++, size, buffer); + nr_pdcp_data_req_srb(ctxt_pP->rntiMaybeUEid, DCCH, nr_rrc_mui++, size, buffer, deliver_pdu_srb_rlc, NULL); } int8_t nr_rrc_ue_decode_ccch( const protocol_ctxt_t *const ctxt_pP, const NR_SRB_INFO *const Srb_info, const uint8_t gNB_index ){ @@ -1691,7 +1691,7 @@ int8_t nr_rrc_ue_decode_ccch( const protocol_ctxt_t *const ctxt_pP, const NR_SRB } LOG_T(NR_RRC, "\n"); - nr_pdcp_data_req_srb(ctxt_pP->rntiMaybeUEid, DCCH, nr_rrc_mui++, (enc_rval.encoded + 7) / 8, buffer); + nr_pdcp_data_req_srb(ctxt_pP->rntiMaybeUEid, DCCH, nr_rrc_mui++, (enc_rval.encoded + 7) / 8, buffer, deliver_pdu_srb_rlc, NULL); } else LOG_W(NR_RRC,"securityModeCommand->criticalExtensions.present (%d) != NR_SecurityModeCommand__criticalExtensions_PR_securityModeCommand\n", securityModeCommand->criticalExtensions.present); @@ -2177,7 +2177,7 @@ nr_rrc_ue_establish_srb2( nr_rrc_mui, UE_MODULE_ID_TO_INSTANCE(ctxt_pP->module_id), DCCH); - nr_pdcp_data_req_srb(ctxt_pP->rntiMaybeUEid, DCCH, nr_rrc_mui++, size, buffer); + nr_pdcp_data_req_srb(ctxt_pP->rntiMaybeUEid, DCCH, nr_rrc_mui++, size, buffer, deliver_pdu_srb_rlc, NULL); } // from NR SRB1 @@ -2427,7 +2427,7 @@ nr_rrc_ue_establish_srb2( PROTOCOL_CTXT_SET_BY_MODULE_ID(&ctxt, ue_mod_id, GNB_FLAG_NO, NR_UE_rrc_inst[ue_mod_id].Info[0].rnti, 0, 0,0); // check if SRB2 is created, if yes request data_req on DCCH1 (SRB2) rb_id_t srb_id = NR_UE_rrc_inst[ue_mod_id].SRB2_config[0] == NULL ? DCCH : DCCH1; - nr_pdcp_data_req_srb(ctxt.rntiMaybeUEid, srb_id, nr_rrc_mui++, length, buffer); + nr_pdcp_data_req_srb(ctxt.rntiMaybeUEid, srb_id, nr_rrc_mui++, length, buffer, deliver_pdu_srb_rlc, NULL); break; } @@ -2571,7 +2571,7 @@ nr_rrc_ue_process_ueCapabilityEnquiry( } LOG_I(NR_RRC, "UECapabilityInformation Encoded %zd bits (%zd bytes)\n",enc_rval.encoded,(enc_rval.encoded+7)/8); - nr_pdcp_data_req_srb(ctxt_pP->rntiMaybeUEid, DCCH, nr_rrc_mui++, (enc_rval.encoded + 7) / 8, buffer); + nr_pdcp_data_req_srb(ctxt_pP->rntiMaybeUEid, DCCH, nr_rrc_mui++, (enc_rval.encoded + 7) / 8, buffer, deliver_pdu_srb_rlc, NULL); } } } From 0de51bcd93cf18bea2da83cea3a65c78d4de4333 Mon Sep 17 00:00:00 2001 From: francescomani Date: Thu, 16 Mar 2023 18:03:09 +0100 Subject: [PATCH 19/89] scheduling aperiodic SRS at UE --- .../nfapi/public_inc/fapi_nr_ue_interface.h | 4 - openair2/LAYER2/NR_MAC_UE/mac_proto.h | 2 + openair2/LAYER2/NR_MAC_UE/nr_ue_procedures.c | 10 +- openair2/LAYER2/NR_MAC_UE/nr_ue_scheduler.c | 197 ++++++++++++------ 4 files changed, 145 insertions(+), 68 deletions(-) diff --git a/nfapi/open-nFAPI/nfapi/public_inc/fapi_nr_ue_interface.h b/nfapi/open-nFAPI/nfapi/public_inc/fapi_nr_ue_interface.h index 94bf1b02dc..d4fc713c19 100644 --- a/nfapi/open-nFAPI/nfapi/public_inc/fapi_nr_ue_interface.h +++ b/nfapi/open-nFAPI/nfapi/public_inc/fapi_nr_ue_interface.h @@ -423,9 +423,6 @@ typedef struct { typedef struct { fapi_nr_dl_config_dci_dl_pdu_rel15_t dci_config_rel15; } fapi_nr_dl_config_dci_pdu; -typedef struct{ - uint8_t aperiodicSRS_ResourceTrigger; -} fapi_nr_dl_srs_config_t; typedef enum{vrb_to_prb_mapping_non_interleaved = 0, vrb_to_prb_mapping_interleaved = 1} vrb_to_prb_mapping_t; @@ -462,7 +459,6 @@ typedef struct { uint16_t dmrs_ports; uint8_t n_front_load_symb; uint8_t tci_state; - fapi_nr_dl_srs_config_t srs_config; uint8_t cbgti; uint8_t codeBlockGroupFlushIndicator; // to be check the fields needed to L1 with NR_DL_UE_HARQ_t and NR_UE_DLSCH_t diff --git a/openair2/LAYER2/NR_MAC_UE/mac_proto.h b/openair2/LAYER2/NR_MAC_UE/mac_proto.h index eb773672d4..a402498eb0 100644 --- a/openair2/LAYER2/NR_MAC_UE/mac_proto.h +++ b/openair2/LAYER2/NR_MAC_UE/mac_proto.h @@ -189,6 +189,8 @@ int8_t nr_ue_process_csirs_measurements(module_id_t module_id, frame_t frame, in uint32_t get_ssb_frame(uint32_t test); +void nr_ue_aperiodic_srs_scheduling(NR_UE_MAC_INST_t *mac, long resource_trigger, int frame, int slot); + bool trigger_periodic_scheduling_request(NR_UE_MAC_INST_t *mac, PUCCH_sched_t *pucch, frame_t frame, diff --git a/openair2/LAYER2/NR_MAC_UE/nr_ue_procedures.c b/openair2/LAYER2/NR_MAC_UE/nr_ue_procedures.c index f9bc506102..3ffba2081d 100644 --- a/openair2/LAYER2/NR_MAC_UE/nr_ue_procedures.c +++ b/openair2/LAYER2/NR_MAC_UE/nr_ue_procedures.c @@ -536,6 +536,11 @@ int8_t nr_ue_process_dci(module_id_t module_id, int cc_id, uint8_t gNB_index, fr // - SECOND_DAI // - SRS_RESOURCE_IND + /* SRS_REQUEST */ + AssertFatal(dci->srs_request.nbits == 2, "If SUL is supported in the cell, there is an additional bit in SRS request field\n"); + if(dci->srs_request.val > 0) + nr_ue_aperiodic_srs_scheduling(mac, dci->srs_request.val, frame, slot); + // Schedule PUSCH tda_info = get_ul_tda_info(current_UL_BWP, coreset_type, dci_ind->ss_type, get_rnti_type(mac, rnti), dci->time_domain_assignment.val); if (tda_info.nrOfSymbols == 0) @@ -1072,8 +1077,9 @@ int8_t nr_ue_process_dci(module_id_t module_id, int cc_id, uint8_t gNB_index, fr dlsch_config_pdu_1_1->tci_state = dci->transmission_configuration_indication.val; } /* SRS_REQUEST */ - // if SUL is supported in the cell, there is an additional bit in this field and the value of this bit represents table 7.1.1.1-1 TS 38.212 FIXME!!! - dlsch_config_pdu_1_1->srs_config.aperiodicSRS_ResourceTrigger = (dci->srs_request.val & 0x11); // as per Table 7.3.1.1.2-24 TS 38.212 + AssertFatal(dci->srs_request.nbits == 2, "If SUL is supported in the cell, there is an additional bit in SRS request field\n"); + if(dci->srs_request.val > 0 ) + nr_ue_aperiodic_srs_scheduling(mac, dci->srs_request.val, frame, slot); /* CBGTI */ dlsch_config_pdu_1_1->cbgti = dci->cbgti.val; /* CBGFI */ diff --git a/openair2/LAYER2/NR_MAC_UE/nr_ue_scheduler.c b/openair2/LAYER2/NR_MAC_UE/nr_ue_scheduler.c index 5c68295739..3360cf5a34 100644 --- a/openair2/LAYER2/NR_MAC_UE/nr_ue_scheduler.c +++ b/openair2/LAYER2/NR_MAC_UE/nr_ue_scheduler.c @@ -789,6 +789,140 @@ int nr_config_pusch_pdu(NR_UE_MAC_INST_t *mac, NR_tda_info_t *tda_info, nfapi_nr } +void configure_srs_pdu(NR_UE_MAC_INST_t *mac, + NR_SRS_Resource_t *srs_resource, + fapi_nr_ul_config_srs_pdu *srs_config_pdu, + int period, int offset) +{ + NR_UE_UL_BWP_t *current_UL_BWP = &mac->current_UL_BWP; + + srs_config_pdu->rnti = mac->crnti; + srs_config_pdu->handle = 0; + srs_config_pdu->bwp_size = current_UL_BWP->BWPSize; + srs_config_pdu->bwp_start = current_UL_BWP->BWPStart; + srs_config_pdu->subcarrier_spacing = current_UL_BWP->scs; + srs_config_pdu->cyclic_prefix = 0; + srs_config_pdu->num_ant_ports = srs_resource->nrofSRS_Ports; + srs_config_pdu->num_symbols = srs_resource->resourceMapping.nrofSymbols; + srs_config_pdu->num_repetitions = srs_resource->resourceMapping.repetitionFactor; + srs_config_pdu->time_start_position = srs_resource->resourceMapping.startPosition; + srs_config_pdu->config_index = srs_resource->freqHopping.c_SRS; + srs_config_pdu->sequence_id = srs_resource->sequenceId; + srs_config_pdu->bandwidth_index = srs_resource->freqHopping.b_SRS; + srs_config_pdu->comb_size = srs_resource->transmissionComb.present - 1; + + switch(srs_resource->transmissionComb.present) { + case NR_SRS_Resource__transmissionComb_PR_n2: + srs_config_pdu->comb_offset = srs_resource->transmissionComb.choice.n2->combOffset_n2; + srs_config_pdu->cyclic_shift = srs_resource->transmissionComb.choice.n2->cyclicShift_n2; + break; + case NR_SRS_Resource__transmissionComb_PR_n4: + srs_config_pdu->comb_offset = srs_resource->transmissionComb.choice.n4->combOffset_n4; + srs_config_pdu->cyclic_shift = srs_resource->transmissionComb.choice.n4->cyclicShift_n4; + break; + default: + LOG_W(NR_MAC, "Invalid or not implemented comb_size!\n"); + } + + srs_config_pdu->frequency_position = srs_resource->freqDomainPosition; + srs_config_pdu->frequency_shift = srs_resource->freqDomainShift; + srs_config_pdu->frequency_hopping = srs_resource->freqHopping.b_hop; + srs_config_pdu->group_or_sequence_hopping = srs_resource->groupOrSequenceHopping; + srs_config_pdu->resource_type = srs_resource->resourceType.present - 1; + if(srs_config_pdu->resource_type > 0) { // not aperiodic + srs_config_pdu->t_srs = period; + srs_config_pdu->t_offset = offset; + } + +#ifdef SRS_DEBUG + LOG_I(NR_MAC,"Frame = %i, slot = %i\n", frame, slot); + LOG_I(NR_MAC,"srs_config_pdu->rnti = 0x%04x\n", srs_config_pdu->rnti); + LOG_I(NR_MAC,"srs_config_pdu->handle = %u\n", srs_config_pdu->handle); + LOG_I(NR_MAC,"srs_config_pdu->bwp_size = %u\n", srs_config_pdu->bwp_size); + LOG_I(NR_MAC,"srs_config_pdu->bwp_start = %u\n", srs_config_pdu->bwp_start); + LOG_I(NR_MAC,"srs_config_pdu->subcarrier_spacing = %u\n", srs_config_pdu->subcarrier_spacing); + LOG_I(NR_MAC,"srs_config_pdu->cyclic_prefix = %u (0: Normal; 1: Extended)\n", srs_config_pdu->cyclic_prefix); + LOG_I(NR_MAC,"srs_config_pdu->num_ant_ports = %u (0 = 1 port, 1 = 2 ports, 2 = 4 ports)\n", srs_config_pdu->num_ant_ports); + LOG_I(NR_MAC,"srs_config_pdu->num_symbols = %u (0 = 1 symbol, 1 = 2 symbols, 2 = 4 symbols)\n", srs_config_pdu->num_symbols); + LOG_I(NR_MAC,"srs_config_pdu->num_repetitions = %u (0 = 1, 1 = 2, 2 = 4)\n", srs_config_pdu->num_repetitions); + LOG_I(NR_MAC,"srs_config_pdu->time_start_position = %u\n", srs_config_pdu->time_start_position); + LOG_I(NR_MAC,"srs_config_pdu->config_index = %u\n", srs_config_pdu->config_index); + LOG_I(NR_MAC,"srs_config_pdu->sequence_id = %u\n", srs_config_pdu->sequence_id); + LOG_I(NR_MAC,"srs_config_pdu->bandwidth_index = %u\n", srs_config_pdu->bandwidth_index); + LOG_I(NR_MAC,"srs_config_pdu->comb_size = %u (0 = comb size 2, 1 = comb size 4, 2 = comb size 8)\n", srs_config_pdu->comb_size); + LOG_I(NR_MAC,"srs_config_pdu->comb_offset = %u\n", srs_config_pdu->comb_offset); + LOG_I(NR_MAC,"srs_config_pdu->cyclic_shift = %u\n", srs_config_pdu->cyclic_shift); + LOG_I(NR_MAC,"srs_config_pdu->frequency_position = %u\n", srs_config_pdu->frequency_position); + LOG_I(NR_MAC,"srs_config_pdu->frequency_shift = %u\n", srs_config_pdu->frequency_shift); + LOG_I(NR_MAC,"srs_config_pdu->frequency_hopping = %u\n", srs_config_pdu->frequency_hopping); + LOG_I(NR_MAC,"srs_config_pdu->group_or_sequence_hopping = %u (0 = No hopping, 1 = Group hopping groupOrSequenceHopping, 2 = Sequence hopping)\n", srs_config_pdu->group_or_sequence_hopping); + LOG_I(NR_MAC,"srs_config_pdu->resource_type = %u (0: aperiodic, 1: semi-persistent, 2: periodic)\n", srs_config_pdu->resource_type); + LOG_I(NR_MAC,"srs_config_pdu->t_srs = %u\n", srs_config_pdu->t_srs); + LOG_I(NR_MAC,"srs_config_pdu->t_offset = %u\n", srs_config_pdu->t_offset); +#endif +} + +// Aperiodic SRS scheduling +void nr_ue_aperiodic_srs_scheduling(NR_UE_MAC_INST_t *mac, long resource_trigger, int frame, int slot) +{ + NR_UE_UL_BWP_t *current_UL_BWP = &mac->current_UL_BWP; + NR_SRS_Config_t *srs_config = current_UL_BWP->srs_Config; + + if (!srs_config) { + LOG_E(NR_MAC, "DCI is triggering aperiodic SRS but there is no SRS configuration\n"); + return; + } + + int slot_offset = 0; + NR_SRS_Resource_t *srs_resource = NULL; + for(int rs = 0; rs < srs_config->srs_ResourceSetToAddModList->list.count; rs++) { + + // Find aperiodic resource set + NR_SRS_ResourceSet_t *srs_resource_set = srs_config->srs_ResourceSetToAddModList->list.array[rs]; + if(srs_resource_set->resourceType.present != NR_SRS_ResourceSet__resourceType_PR_aperiodic) + continue; + // the resource trigger need to match the DCI one + if(srs_resource_set->resourceType.choice.aperiodic->aperiodicSRS_ResourceTrigger != resource_trigger) + continue; + // if slotOffset is null -> offset = 0 + if(srs_resource_set->resourceType.choice.aperiodic->slotOffset) + slot_offset = *srs_resource_set->resourceType.choice.aperiodic->slotOffset; + + // Find the corresponding srs resource + for(int r1 = 0; r1 < srs_resource_set->srs_ResourceIdList->list.count; r1++) { + for (int r2 = 0; r2 < srs_config->srs_ResourceToAddModList->list.count; r2++) { + if ((*srs_resource_set->srs_ResourceIdList->list.array[r1] == srs_config->srs_ResourceToAddModList->list.array[r2]->srs_ResourceId) && + (srs_config->srs_ResourceToAddModList->list.array[r2]->resourceType.present == NR_SRS_Resource__resourceType_PR_aperiodic)) { + srs_resource = srs_config->srs_ResourceToAddModList->list.array[r2]; + break; + } + } + } + } + + if(srs_resource == NULL) { + LOG_E(NR_MAC, "Couldn't find SRS aperiodic resource with trigger %ld\n", resource_trigger); + return; + } + + AssertFatal(slot_offset >= DURATION_RX_TO_TX,"Slot offset between DCI and aperiodic SRS (%d) cannot be less than DURATION_RX_TO_TX (%d)\n", + slot_offset, DURATION_RX_TO_TX); + int n_slots_frame = nr_slots_per_frame[current_UL_BWP->scs]; + int sched_slot = (slot + slot_offset) % n_slots_frame; + NR_TDD_UL_DL_ConfigCommon_t *tdd_config = mac->scc==NULL ? mac->scc_SIB->tdd_UL_DL_ConfigurationCommon : mac->scc->tdd_UL_DL_ConfigurationCommon; + if (!is_nr_UL_slot(tdd_config, sched_slot, mac->frame_type)) { + LOG_E(NR_MAC, "Slot for scheduling aperiodic SRS %d is not an UL slot\n", sched_slot); + return; + } + int sched_frame = frame + (slot + slot_offset >= n_slots_frame) % 1024; + + fapi_nr_ul_config_request_t *ul_config = get_ul_config_request(mac, sched_slot); + fapi_nr_ul_config_srs_pdu *srs_config_pdu = &ul_config->ul_config_list[ul_config->number_pdus].srs_config_pdu; + configure_srs_pdu(mac, srs_resource, srs_config_pdu, 0, 0); + fill_ul_config(ul_config, sched_frame, sched_slot, FAPI_NR_UL_CONFIG_TYPE_SRS); +} + + // Periodic SRS scheduling bool nr_ue_periodic_srs_scheduling(module_id_t mod_id, frame_t frame, slot_t slot) { @@ -838,68 +972,7 @@ bool nr_ue_periodic_srs_scheduling(module_id_t mod_id, frame_t frame, slot_t slo fapi_nr_ul_config_request_t *ul_config = get_ul_config_request(mac, slot); fapi_nr_ul_config_srs_pdu *srs_config_pdu = &ul_config->ul_config_list[ul_config->number_pdus].srs_config_pdu; - srs_config_pdu->rnti = mac->crnti; - srs_config_pdu->handle = 0; - srs_config_pdu->bwp_size = current_UL_BWP->BWPSize; - srs_config_pdu->bwp_start = current_UL_BWP->BWPStart; - srs_config_pdu->subcarrier_spacing = current_UL_BWP->scs; - srs_config_pdu->cyclic_prefix = 0; - srs_config_pdu->num_ant_ports = srs_resource->nrofSRS_Ports; - srs_config_pdu->num_symbols = srs_resource->resourceMapping.nrofSymbols; - srs_config_pdu->num_repetitions = srs_resource->resourceMapping.repetitionFactor; - srs_config_pdu->time_start_position = srs_resource->resourceMapping.startPosition; - srs_config_pdu->config_index = srs_resource->freqHopping.c_SRS; - srs_config_pdu->sequence_id = srs_resource->sequenceId; - srs_config_pdu->bandwidth_index = srs_resource->freqHopping.b_SRS; - srs_config_pdu->comb_size = srs_resource->transmissionComb.present - 1; - - switch(srs_resource->transmissionComb.present) { - case NR_SRS_Resource__transmissionComb_PR_n2: - srs_config_pdu->comb_offset = srs_resource->transmissionComb.choice.n2->combOffset_n2; - srs_config_pdu->cyclic_shift = srs_resource->transmissionComb.choice.n2->cyclicShift_n2; - break; - case NR_SRS_Resource__transmissionComb_PR_n4: - srs_config_pdu->comb_offset = srs_resource->transmissionComb.choice.n4->combOffset_n4; - srs_config_pdu->cyclic_shift = srs_resource->transmissionComb.choice.n4->cyclicShift_n4; - break; - default: - LOG_W(NR_MAC, "Invalid or not implemented comb_size!\n"); - } - - srs_config_pdu->frequency_position = srs_resource->freqDomainPosition; - srs_config_pdu->frequency_shift = srs_resource->freqDomainShift; - srs_config_pdu->frequency_hopping = srs_resource->freqHopping.b_hop; - srs_config_pdu->group_or_sequence_hopping = srs_resource->groupOrSequenceHopping; - srs_config_pdu->resource_type = srs_resource->resourceType.present - 1; - srs_config_pdu->t_srs = period; - srs_config_pdu->t_offset = offset; - -#ifdef SRS_DEBUG - LOG_I(NR_MAC,"Frame = %i, slot = %i\n", frame, slot); - LOG_I(NR_MAC,"srs_config_pdu->rnti = 0x%04x\n", srs_config_pdu->rnti); - LOG_I(NR_MAC,"srs_config_pdu->handle = %u\n", srs_config_pdu->handle); - LOG_I(NR_MAC,"srs_config_pdu->bwp_size = %u\n", srs_config_pdu->bwp_size); - LOG_I(NR_MAC,"srs_config_pdu->bwp_start = %u\n", srs_config_pdu->bwp_start); - LOG_I(NR_MAC,"srs_config_pdu->subcarrier_spacing = %u\n", srs_config_pdu->subcarrier_spacing); - LOG_I(NR_MAC,"srs_config_pdu->cyclic_prefix = %u (0: Normal; 1: Extended)\n", srs_config_pdu->cyclic_prefix); - LOG_I(NR_MAC,"srs_config_pdu->num_ant_ports = %u (0 = 1 port, 1 = 2 ports, 2 = 4 ports)\n", srs_config_pdu->num_ant_ports); - LOG_I(NR_MAC,"srs_config_pdu->num_symbols = %u (0 = 1 symbol, 1 = 2 symbols, 2 = 4 symbols)\n", srs_config_pdu->num_symbols); - LOG_I(NR_MAC,"srs_config_pdu->num_repetitions = %u (0 = 1, 1 = 2, 2 = 4)\n", srs_config_pdu->num_repetitions); - LOG_I(NR_MAC,"srs_config_pdu->time_start_position = %u\n", srs_config_pdu->time_start_position); - LOG_I(NR_MAC,"srs_config_pdu->config_index = %u\n", srs_config_pdu->config_index); - LOG_I(NR_MAC,"srs_config_pdu->sequence_id = %u\n", srs_config_pdu->sequence_id); - LOG_I(NR_MAC,"srs_config_pdu->bandwidth_index = %u\n", srs_config_pdu->bandwidth_index); - LOG_I(NR_MAC,"srs_config_pdu->comb_size = %u (0 = comb size 2, 1 = comb size 4, 2 = comb size 8)\n", srs_config_pdu->comb_size); - LOG_I(NR_MAC,"srs_config_pdu->comb_offset = %u\n", srs_config_pdu->comb_offset); - LOG_I(NR_MAC,"srs_config_pdu->cyclic_shift = %u\n", srs_config_pdu->cyclic_shift); - LOG_I(NR_MAC,"srs_config_pdu->frequency_position = %u\n", srs_config_pdu->frequency_position); - LOG_I(NR_MAC,"srs_config_pdu->frequency_shift = %u\n", srs_config_pdu->frequency_shift); - LOG_I(NR_MAC,"srs_config_pdu->frequency_hopping = %u\n", srs_config_pdu->frequency_hopping); - LOG_I(NR_MAC,"srs_config_pdu->group_or_sequence_hopping = %u (0 = No hopping, 1 = Group hopping groupOrSequenceHopping, 2 = Sequence hopping)\n", srs_config_pdu->group_or_sequence_hopping); - LOG_I(NR_MAC,"srs_config_pdu->resource_type = %u (0: aperiodic, 1: semi-persistent, 2: periodic)\n", srs_config_pdu->resource_type); - LOG_I(NR_MAC,"srs_config_pdu->t_srs = %u\n", srs_config_pdu->t_srs); - LOG_I(NR_MAC,"srs_config_pdu->t_offset = %u\n", srs_config_pdu->t_offset); -#endif + configure_srs_pdu(mac, srs_resource, srs_config_pdu, period, offset); fill_ul_config(ul_config, frame, slot, FAPI_NR_UL_CONFIG_TYPE_SRS); srs_scheduled = true; From d4da44f1d321c3e3f32e4d7a5f46ea523fd61213 Mon Sep 17 00:00:00 2001 From: Robert Schmidt Date: Thu, 16 Mar 2023 15:40:43 +0100 Subject: [PATCH 20/89] CI: Remove unused routes --- ci-scripts/cls_containerize.py | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/ci-scripts/cls_containerize.py b/ci-scripts/cls_containerize.py index 9a00ec3dfe..bf3d7b7226 100644 --- a/ci-scripts/cls_containerize.py +++ b/ci-scripts/cls_containerize.py @@ -1559,17 +1559,6 @@ class Containerize(): mySSH.close() if svrName == 'nepes': mySSH.open(ipAddr, userName, password) - # Check if route to porcepix epc exists: not necessary, is on same host - #mySSH.command('ip route | grep --colour=never "192.168.61.192/26"', '\$', 10) - #result = re.search('172.21.16.136', mySSH.getBefore()) - #if result is None: - # mySSH.command('echo ' + password + ' | sudo -S ip route add 192.168.61.192/26 via 172.21.16.136 dev enp0s31f6', '\$', 10) - # Check if X2 route to obelix enb exists: not necessary, gnb is on ofqot - #mySSH.command('ip route | grep --colour=never "192.168.68.128/26"', '\$', 10) - #result = re.search('172.21.16.128', mySSH.getBefore()) - #if result is None: - # mySSH.command('echo ' + password + ' | sudo -S ip route add 192.168.68.128/26 via 172.21.16.128 dev enp0s31f6', '\$', 10) - # Check if forwarding is enabled # Check if route to ofqot gnb exists mySSH.command('ip route | grep --colour=never "192.168.68.192/26"', '\$', 10) result = re.search('172.21.16.109', mySSH.getBefore()) From 528025e9a6390fafcb78f5493528bd5a6356f973 Mon Sep 17 00:00:00 2001 From: Robert Schmidt Date: Mon, 13 Mar 2023 14:24:08 +0100 Subject: [PATCH 21/89] Fix decoding of Mobile identity --- openair3/NAS/COMMON/IES/FGSMobileIdentity.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/openair3/NAS/COMMON/IES/FGSMobileIdentity.c b/openair3/NAS/COMMON/IES/FGSMobileIdentity.c index 3bdddc3619..872fc8e53c 100644 --- a/openair3/NAS/COMMON/IES/FGSMobileIdentity.c +++ b/openair3/NAS/COMMON/IES/FGSMobileIdentity.c @@ -54,8 +54,8 @@ int decode_5gs_mobile_identity(FGSMobileIdentity *fgsmobileidentity, uint8_t iei decoded++; } - ielen = *(buffer + decoded); - decoded++; + ielen = *(uint16_t*)(buffer + decoded); /* length is two bytes */ + decoded += 2; CHECK_LENGTH_DECODER(len - decoded, ielen); uint8_t typeofidentity = *(buffer + decoded) & 0x7; @@ -63,6 +63,8 @@ int decode_5gs_mobile_identity(FGSMobileIdentity *fgsmobileidentity, uint8_t iei if (typeofidentity == FGS_MOBILE_IDENTITY_5G_GUTI) { decoded_rc = decode_guti_5gs_mobile_identity(&fgsmobileidentity->guti, buffer + decoded); + } else { + AssertFatal(false, "Mobile Identity encoding of type %d not implemented\n", typeofidentity); } if (decoded_rc < 0) { From 223fd28bb261505db87319e3c440deddce9094d3 Mon Sep 17 00:00:00 2001 From: Robert Schmidt Date: Sat, 11 Mar 2023 12:19:58 +0100 Subject: [PATCH 22/89] Refactor NAS: separate code into functions --- openair3/NAS/NR_UE/nr_nas_msg_sim.c | 206 +++++++++++++--------------- 1 file changed, 95 insertions(+), 111 deletions(-) diff --git a/openair3/NAS/NR_UE/nr_nas_msg_sim.c b/openair3/NAS/NR_UE/nr_nas_msg_sim.c index 9d3eb6db76..fdf52619a4 100644 --- a/openair3/NAS/NR_UE/nr_nas_msg_sim.c +++ b/openair3/NAS/NR_UE/nr_nas_msg_sim.c @@ -104,6 +104,63 @@ static int _nas_mm_msg_encode_header(const mm_msg_header_t *header, return (size); } +static int fill_suci(FGSMobileIdentity *mi, const uicc_t *uicc) +{ + mi->suci.typeofidentity = FGS_MOBILE_IDENTITY_SUCI; + mi->suci.mncdigit1 = uicc->nmc_size == 2 ? uicc->imsiStr[3] - '0' : uicc->imsiStr[4] - '0'; + mi->suci.mncdigit2 = uicc->nmc_size == 2 ? uicc->imsiStr[4] - '0' : uicc->imsiStr[5] - '0'; + mi->suci.mncdigit3 = uicc->nmc_size == 2 ? 0xF : uicc->imsiStr[3] - '0'; + mi->suci.mccdigit1 = uicc->imsiStr[0] - '0'; + mi->suci.mccdigit2 = uicc->imsiStr[1] - '0'; + mi->suci.mccdigit3 = uicc->imsiStr[2] - '0'; + memcpy(mi->suci.schemeoutput, uicc->imsiStr + 3 + uicc->nmc_size, strlen(uicc->imsiStr) - (3 + uicc->nmc_size)); + return sizeof(Suci5GSMobileIdentity_t); +} + +static int fill_guti(FGSMobileIdentity *mi, const uicc_t *uicc) +{ + AssertFatal(false, "Need to add AMF data in function\n"); + mi->guti.typeofidentity = FGS_MOBILE_IDENTITY_5G_GUTI; + mi->guti.amfregionid = 0xca; + mi->guti.amfpointer = 0; + mi->guti.amfsetid = 1016; + mi->guti.tmsi = 10; + mi->guti.mncdigit1 = + uicc->nmc_size==2 ? uicc->imsiStr[3]-'0' : uicc->imsiStr[4]-'0'; + mi->guti.mncdigit2 = + uicc->nmc_size==2 ? uicc->imsiStr[4]-'0' : uicc->imsiStr[5]-'0'; + mi->guti.mncdigit3 = + uicc->nmc_size==2 ? 0xf : uicc->imsiStr[3]-'0'; + mi->guti.mccdigit1 = uicc->imsiStr[0]-'0'; + mi->guti.mccdigit2 = uicc->imsiStr[1]-'0'; + mi->guti.mccdigit3 = uicc->imsiStr[2]-'0'; + return 13; +} + +static int fill_imeisv(FGSMobileIdentity *mi, int Mod_id) +{ + int i=0; + mi->imeisv.typeofidentity = FGS_MOBILE_IDENTITY_IMEISV; + mi->imeisv.digittac01 = getImeisvDigit(Mod_id,i++); + mi->imeisv.digittac02 = getImeisvDigit(Mod_id,i++); + mi->imeisv.digittac03 = getImeisvDigit(Mod_id,i++); + mi->imeisv.digittac04 = getImeisvDigit(Mod_id,i++); + mi->imeisv.digittac05 = getImeisvDigit(Mod_id,i++); + mi->imeisv.digittac06 = getImeisvDigit(Mod_id,i++); + mi->imeisv.digittac07 = getImeisvDigit(Mod_id,i++); + mi->imeisv.digittac08 = getImeisvDigit(Mod_id,i++); + mi->imeisv.digit09 = getImeisvDigit(Mod_id,i++); + mi->imeisv.digit10 = getImeisvDigit(Mod_id,i++); + mi->imeisv.digit11 = getImeisvDigit(Mod_id,i++); + mi->imeisv.digit12 = getImeisvDigit(Mod_id,i++); + mi->imeisv.digit13 = getImeisvDigit(Mod_id,i++); + mi->imeisv.digit14 = getImeisvDigit(Mod_id,i++); + mi->imeisv.digitsv1 = getImeisvDigit(Mod_id,i++); + mi->imeisv.digitsv2 = getImeisvDigit(Mod_id,i++); + mi->imeisv.spare = 0x0f; + mi->imeisv.oddeven = 1; + return 19; +} int mm_msg_encode(MM_msg *mm_msg, uint8_t *buffer, uint32_t len) { LOG_FUNC_IN; @@ -394,36 +451,9 @@ void generateRegistrationRequest(as_nas_info_t *initialNasMsg, int Mod_id) { mm_msg->registration_request.naskeysetidentifier.naskeysetidentifier = 1; size += 1; if(0){ - mm_msg->registration_request.fgsmobileidentity.guti.typeofidentity = FGS_MOBILE_IDENTITY_5G_GUTI; - mm_msg->registration_request.fgsmobileidentity.guti.amfregionid = 0xca; - mm_msg->registration_request.fgsmobileidentity.guti.amfpointer = 0; - mm_msg->registration_request.fgsmobileidentity.guti.amfsetid = 1016; - mm_msg->registration_request.fgsmobileidentity.guti.tmsi = 10; - mm_msg->registration_request.fgsmobileidentity.guti.mncdigit1 = - uicc->nmc_size==2 ? uicc->imsiStr[3]-'0' : uicc->imsiStr[4]-'0'; - mm_msg->registration_request.fgsmobileidentity.guti.mncdigit2 = - uicc->nmc_size==2 ? uicc->imsiStr[4]-'0' : uicc->imsiStr[5]-'0'; - mm_msg->registration_request.fgsmobileidentity.guti.mncdigit3 = - uicc->nmc_size==2 ? 0xf : uicc->imsiStr[3]-'0'; - mm_msg->registration_request.fgsmobileidentity.guti.mccdigit1 = uicc->imsiStr[0]-'0'; - mm_msg->registration_request.fgsmobileidentity.guti.mccdigit2 = uicc->imsiStr[1]-'0'; - mm_msg->registration_request.fgsmobileidentity.guti.mccdigit3 = uicc->imsiStr[2]-'0'; - - size += 13; - + size += fill_guti(&mm_msg->registration_request.fgsmobileidentity, uicc); } else { - mm_msg->registration_request.fgsmobileidentity.suci.typeofidentity = FGS_MOBILE_IDENTITY_SUCI; - mm_msg->registration_request.fgsmobileidentity.suci.mncdigit1 = - uicc->nmc_size==2 ? uicc->imsiStr[3]-'0' : uicc->imsiStr[4]-'0'; - mm_msg->registration_request.fgsmobileidentity.suci.mncdigit2 = - uicc->nmc_size==2 ? uicc->imsiStr[4]-'0' : uicc->imsiStr[5]-'0'; - mm_msg->registration_request.fgsmobileidentity.suci.mncdigit3 = - uicc->nmc_size==2 ? 0xf : uicc->imsiStr[3]-'0'; - mm_msg->registration_request.fgsmobileidentity.suci.mccdigit1 = uicc->imsiStr[0]-'0'; - mm_msg->registration_request.fgsmobileidentity.suci.mccdigit2 = uicc->imsiStr[1]-'0'; - mm_msg->registration_request.fgsmobileidentity.suci.mccdigit3 = uicc->imsiStr[2]-'0'; - memcpy(mm_msg->registration_request.fgsmobileidentity.suci.schemeoutput, uicc->imsiStr+3+uicc->nmc_size, strlen(uicc->imsiStr) - (3+uicc->nmc_size)); - size += sizeof(Suci5GSMobileIdentity_t); + size += fill_suci(&mm_msg->registration_request.fgsmobileidentity, uicc); } mm_msg->registration_request.presencemask |= REGISTRATION_REQUEST_5GMM_CAPABILITY_PRESENT; @@ -471,18 +501,7 @@ void generateIdentityResponse(as_nas_info_t *initialNasMsg, uint8_t identitytype mm_msg->fgs_identity_response.messagetype = FGS_IDENTITY_RESPONSE; size += 1; if(identitytype == FGS_MOBILE_IDENTITY_SUCI){ - mm_msg->fgs_identity_response.fgsmobileidentity.suci.typeofidentity = FGS_MOBILE_IDENTITY_SUCI; - mm_msg->fgs_identity_response.fgsmobileidentity.suci.mncdigit1 = - uicc->nmc_size==2 ? uicc->imsiStr[3]-'0' : uicc->imsiStr[4]-'0'; - mm_msg->fgs_identity_response.fgsmobileidentity.suci.mncdigit2 = - uicc->nmc_size==2 ? uicc->imsiStr[4]-'0' : uicc->imsiStr[5]-'0'; - mm_msg->fgs_identity_response.fgsmobileidentity.suci.mncdigit3 = - uicc->nmc_size==2? 0xF : uicc->imsiStr[3]-'0'; - mm_msg->fgs_identity_response.fgsmobileidentity.suci.mccdigit1 = uicc->imsiStr[0]-'0'; - mm_msg->fgs_identity_response.fgsmobileidentity.suci.mccdigit2 = uicc->imsiStr[1]-'0'; - mm_msg->fgs_identity_response.fgsmobileidentity.suci.mccdigit3 = uicc->imsiStr[2]-'0'; - memcpy(mm_msg->registration_request.fgsmobileidentity.suci.schemeoutput, uicc->imsiStr+3+uicc->nmc_size, strlen(uicc->imsiStr) - (3+uicc->nmc_size)); - size += sizeof(Suci5GSMobileIdentity_t); + size += fill_suci(&mm_msg->fgs_identity_response.fgsmobileidentity, uicc); } // encode the message @@ -535,31 +554,6 @@ int nas_itti_kgnb_refresh_req(const uint8_t kgnb[32], int instance) { return itti_send_msg_to_task(TASK_RRC_NRUE, instance, message_p); } -static int addImeisv(int Mod_id,MM_msg *mm_msg) -{ - int i=0; - mm_msg->fgs_security_mode_complete.fgsmobileidentity.imeisv.typeofidentity = FGS_MOBILE_IDENTITY_IMEISV; - mm_msg->fgs_security_mode_complete.fgsmobileidentity.imeisv.digittac01 = getImeisvDigit(Mod_id,i++); - mm_msg->fgs_security_mode_complete.fgsmobileidentity.imeisv.digittac02 = getImeisvDigit(Mod_id,i++); - mm_msg->fgs_security_mode_complete.fgsmobileidentity.imeisv.digittac03 = getImeisvDigit(Mod_id,i++); - mm_msg->fgs_security_mode_complete.fgsmobileidentity.imeisv.digittac04 = getImeisvDigit(Mod_id,i++); - mm_msg->fgs_security_mode_complete.fgsmobileidentity.imeisv.digittac05 = getImeisvDigit(Mod_id,i++); - mm_msg->fgs_security_mode_complete.fgsmobileidentity.imeisv.digittac06 = getImeisvDigit(Mod_id,i++); - mm_msg->fgs_security_mode_complete.fgsmobileidentity.imeisv.digittac07 = getImeisvDigit(Mod_id,i++); - mm_msg->fgs_security_mode_complete.fgsmobileidentity.imeisv.digittac08 = getImeisvDigit(Mod_id,i++); - mm_msg->fgs_security_mode_complete.fgsmobileidentity.imeisv.digit09 = getImeisvDigit(Mod_id,i++); - mm_msg->fgs_security_mode_complete.fgsmobileidentity.imeisv.digit10 = getImeisvDigit(Mod_id,i++); - mm_msg->fgs_security_mode_complete.fgsmobileidentity.imeisv.digit11 = getImeisvDigit(Mod_id,i++); - mm_msg->fgs_security_mode_complete.fgsmobileidentity.imeisv.digit12 = getImeisvDigit(Mod_id,i++); - mm_msg->fgs_security_mode_complete.fgsmobileidentity.imeisv.digit13 = getImeisvDigit(Mod_id,i++); - mm_msg->fgs_security_mode_complete.fgsmobileidentity.imeisv.digit14 = getImeisvDigit(Mod_id,i++); - mm_msg->fgs_security_mode_complete.fgsmobileidentity.imeisv.digitsv1 = getImeisvDigit(Mod_id,i++); - mm_msg->fgs_security_mode_complete.fgsmobileidentity.imeisv.digitsv2 = getImeisvDigit(Mod_id,i++); - mm_msg->fgs_security_mode_complete.fgsmobileidentity.imeisv.spare = 0x0f; - mm_msg->fgs_security_mode_complete.fgsmobileidentity.imeisv.oddeven = 1; - return 19; -} - static void generateSecurityModeComplete(int Mod_id,as_nas_info_t *initialNasMsg) { int size = sizeof(mm_msg_header_t); @@ -589,7 +583,7 @@ static void generateSecurityModeComplete(int Mod_id,as_nas_info_t *initialNasMsg mm_msg->fgs_security_mode_complete.messagetype = FGS_SECURITY_MODE_COMPLETE; size += 1; - size += addImeisv(Mod_id, mm_msg); + size += fill_imeisv(&mm_msg->fgs_security_mode_complete.fgsmobileidentity, Mod_id); mm_msg->fgs_security_mode_complete.fgsnasmessagecontainer.nasmessagecontainercontents.value = registration_request_buf; mm_msg->fgs_security_mode_complete.fgsnasmessagecontainer.nasmessagecontainercontents.length = registration_request_len; @@ -838,11 +832,20 @@ uint8_t get_msg_type(uint8_t *pdu_buffer, uint32_t length) { return msg_type; } +static void send_nas_uplink_data_req(instance_t instance, const as_nas_info_t *initial_nas_msg) +{ + MessageDef *msg = itti_alloc_new_message(TASK_NAS_NRUE, 0, NAS_UPLINK_DATA_REQ); + ul_info_transfer_req_t *req = &NAS_UPLINK_DATA_REQ(msg); + req->UEid = instance; + req->nasMsg.data = (uint8_t *) initial_nas_msg->data; + req->nasMsg.length = initial_nas_msg->length; + itti_send_msg_to_task(TASK_RRC_NRUE, instance, msg); +} + void *nas_nrue_task(void *args_p) { MessageDef *msg_p; instance_t instance; - unsigned int Mod_id; int result; uint8_t msg_type = 0; uint8_t *pdu_buffer = NULL; @@ -857,9 +860,7 @@ void *nas_nrue_task(void *args_p) if (msg_p != NULL) { instance = msg_p->ittiMsgHeader.originInstance; - Mod_id = instance ; - uicc_t *uicc=checkUicc(Mod_id); - LOG_I(NAS, "[UE %d] Received %s\n", Mod_id, ITTI_MSG_NAME(msg_p)); + uicc_t *uicc=checkUicc(instance); if (instance == INSTANCE_DEFAULT) { printf("%s:%d: FATAL: instance is INSTANCE_DEFAULT, should not happen.\n", @@ -881,8 +882,8 @@ void *nas_nrue_task(void *args_p) case NAS_CELL_SELECTION_CNF: LOG_I(NAS, - "[UE %d] Received %s: errCode %u, cellID %u, tac %u\n", - Mod_id, + "[UE %ld] Received %s: errCode %u, cellID %u, tac %u\n", + instance, ITTI_MSG_NAME(msg_p), NAS_CELL_SELECTION_CNF(msg_p).errCode, NAS_CELL_SELECTION_CNF(msg_p).cellID, @@ -895,19 +896,19 @@ void *nas_nrue_task(void *args_p) break; case NAS_CELL_SELECTION_IND: - LOG_I(NAS, "[UE %d] Received %s: cellID %u, tac %u\n", Mod_id, ITTI_MSG_NAME(msg_p), NAS_CELL_SELECTION_IND(msg_p).cellID, NAS_CELL_SELECTION_IND(msg_p).tac); + LOG_I(NAS, "[UE %ld] Received %s: cellID %u, tac %u\n", instance, ITTI_MSG_NAME(msg_p), NAS_CELL_SELECTION_IND(msg_p).cellID, NAS_CELL_SELECTION_IND(msg_p).tac); /* TODO not processed by NAS currently */ break; case NAS_PAGING_IND: - LOG_I(NAS, "[UE %d] Received %s: cause %u\n", Mod_id, ITTI_MSG_NAME(msg_p), NAS_PAGING_IND(msg_p).cause); + LOG_I(NAS, "[UE %ld] Received %s: cause %u\n", instance, ITTI_MSG_NAME(msg_p), NAS_PAGING_IND(msg_p).cause); /* TODO not processed by NAS currently */ break; case NAS_CONN_ESTABLI_CNF: { - LOG_I(NAS, "[UE %d] Received %s: errCode %u, length %u\n", Mod_id, ITTI_MSG_NAME(msg_p), NAS_CONN_ESTABLI_CNF(msg_p).errCode, NAS_CONN_ESTABLI_CNF(msg_p).nasMsg.length); + LOG_I(NAS, "[UE %ld] Received %s: errCode %u, length %u\n", instance, ITTI_MSG_NAME(msg_p), NAS_CONN_ESTABLI_CNF(msg_p).errCode, NAS_CONN_ESTABLI_CNF(msg_p).nasMsg.length); pdu_buffer = NAS_CONN_ESTABLI_CNF(msg_p).nasMsg.data; msg_type = get_msg_type(pdu_buffer, NAS_CONN_ESTABLI_CNF(msg_p).nasMsg.length); @@ -917,27 +918,17 @@ void *nas_nrue_task(void *args_p) as_nas_info_t initialNasMsg; memset(&initialNasMsg, 0, sizeof(as_nas_info_t)); - generateRegistrationComplete(Mod_id, &initialNasMsg, NULL); + generateRegistrationComplete(instance, &initialNasMsg, NULL); if (initialNasMsg.length > 0) { - MessageDef *message_p; - message_p = itti_alloc_new_message(TASK_NAS_NRUE, 0, NAS_UPLINK_DATA_REQ); - NAS_UPLINK_DATA_REQ(message_p).UEid = Mod_id; - NAS_UPLINK_DATA_REQ(message_p).nasMsg.data = (uint8_t *)initialNasMsg.data; - NAS_UPLINK_DATA_REQ(message_p).nasMsg.length = initialNasMsg.length; - itti_send_msg_to_task(TASK_RRC_NRUE, instance, message_p); + send_nas_uplink_data_req(instance, &initialNasMsg); LOG_I(NAS, "Send NAS_UPLINK_DATA_REQ message(RegistrationComplete)\n"); } as_nas_info_t pduEstablishMsg; memset(&pduEstablishMsg, 0, sizeof(as_nas_info_t)); - generatePduSessionEstablishRequest(Mod_id, uicc, &pduEstablishMsg); + generatePduSessionEstablishRequest(instance, uicc, &pduEstablishMsg); if (pduEstablishMsg.length > 0) { - MessageDef *message_p; - message_p = itti_alloc_new_message(TASK_NAS_NRUE, 0, NAS_UPLINK_DATA_REQ); - NAS_UPLINK_DATA_REQ(message_p).UEid = Mod_id; - NAS_UPLINK_DATA_REQ(message_p).nasMsg.data = (uint8_t *)pduEstablishMsg.data; - NAS_UPLINK_DATA_REQ(message_p).nasMsg.length = pduEstablishMsg.length; - itti_send_msg_to_task(TASK_RRC_NRUE, instance, message_p); + send_nas_uplink_data_req(instance, &pduEstablishMsg); LOG_I(NAS, "Send NAS_UPLINK_DATA_REQ message(PduSessionEstablishRequest)\n"); } } else if (msg_type == FGS_PDU_SESSION_ESTABLISHMENT_ACC) { @@ -948,24 +939,25 @@ void *nas_nrue_task(void *args_p) } case NAS_CONN_RELEASE_IND: - LOG_I(NAS, "[UE %d] Received %s: cause %u\n", Mod_id, ITTI_MSG_NAME (msg_p), + LOG_I(NAS, "[UE %ld] Received %s: cause %u\n", instance, ITTI_MSG_NAME (msg_p), NAS_CONN_RELEASE_IND (msg_p).cause); break; case NAS_UPLINK_DATA_CNF: - LOG_I(NAS, "[UE %d] Received %s: UEid %u, errCode %u\n", Mod_id, ITTI_MSG_NAME (msg_p), + LOG_I(NAS, "[UE %ld] Received %s: UEid %u, errCode %u\n", instance, ITTI_MSG_NAME (msg_p), NAS_UPLINK_DATA_CNF (msg_p).UEid, NAS_UPLINK_DATA_CNF (msg_p).errCode); break; case NAS_DOWNLINK_DATA_IND: { - LOG_I(NAS, "[UE %d] Received %s: UEid %u, length %u , buffer %p\n", Mod_id, - ITTI_MSG_NAME (msg_p), - Mod_id, - NAS_DOWNLINK_DATA_IND(msg_p).nasMsg.length, - NAS_DOWNLINK_DATA_IND(msg_p).nasMsg.data); + LOG_I(NAS, + "[UE %ld] Received %s: length %u , buffer %p\n", + instance, + ITTI_MSG_NAME(msg_p), + NAS_DOWNLINK_DATA_IND(msg_p).nasMsg.length, + NAS_DOWNLINK_DATA_IND(msg_p).nasMsg.data); as_nas_info_t initialNasMsg={0}; pdu_buffer = NAS_DOWNLINK_DATA_IND(msg_p).nasMsg.data; @@ -977,11 +969,11 @@ void *nas_nrue_task(void *args_p) generateIdentityResponse(&initialNasMsg,*(pdu_buffer+3), uicc); break; case FGS_AUTHENTICATION_REQUEST: - generateAuthenticationResp(Mod_id,&initialNasMsg, pdu_buffer, uicc); + generateAuthenticationResp(instance,&initialNasMsg, pdu_buffer, uicc); break; case FGS_SECURITY_MODE_COMMAND: - nas_itti_kgnb_refresh_req(ue_security_key[Mod_id]->kgnb, instance); - generateSecurityModeComplete(Mod_id,&initialNasMsg); + nas_itti_kgnb_refresh_req(ue_security_key[instance]->kgnb, instance); + generateSecurityModeComplete(instance,&initialNasMsg); break; case FGS_DOWNLINK_NAS_TRANSPORT: decodeDownlinkNASTransport(&initialNasMsg, pdu_buffer); @@ -1021,21 +1013,13 @@ void *nas_nrue_task(void *args_p) break; } - if(initialNasMsg.length > 0){ - MessageDef *message_p; - message_p = itti_alloc_new_message(TASK_NAS_NRUE, 0, NAS_UPLINK_DATA_REQ); - NAS_UPLINK_DATA_REQ(message_p).UEid = Mod_id; - NAS_UPLINK_DATA_REQ(message_p).nasMsg.data = (uint8_t *)initialNasMsg.data; - - NAS_UPLINK_DATA_REQ(message_p).nasMsg.length = initialNasMsg.length; - itti_send_msg_to_task(TASK_RRC_NRUE, instance, message_p); - LOG_I(NAS, "Send NAS_UPLINK_DATA_REQ message\n"); + if (initialNasMsg.length > 0) + send_nas_uplink_data_req(instance, &initialNasMsg); } - } break; default: - LOG_E(NAS, "[UE %d] Received unexpected message %s\n", Mod_id, ITTI_MSG_NAME (msg_p)); + LOG_E(NAS, "[UE %ld] Received unexpected message %s\n", instance, ITTI_MSG_NAME (msg_p)); break; } From e35401efde027e72157a6a0f1a2e8741d832fa6f Mon Sep 17 00:00:00 2001 From: Robert Schmidt Date: Mon, 13 Mar 2023 12:30:52 +0100 Subject: [PATCH 23/89] NAS refactor: no moduleID, group UICC and security in one context --- openair2/RRC/NR_UE/rrc_UE.c | 3 +- openair3/NAS/COMMON/NR_NAS_defs.h | 2 +- openair3/NAS/COMMON/nr_common.c | 2 +- openair3/NAS/NR_UE/nr_nas_msg_sim.c | 164 ++++++++++++++-------------- openair3/NAS/NR_UE/nr_nas_msg_sim.h | 9 +- openair3/UICC/usim_interface.c | 3 +- openair3/UICC/usim_interface.h | 3 +- 7 files changed, 93 insertions(+), 93 deletions(-) diff --git a/openair2/RRC/NR_UE/rrc_UE.c b/openair2/RRC/NR_UE/rrc_UE.c index ecbaeea17e..4bc1724fc7 100644 --- a/openair2/RRC/NR_UE/rrc_UE.c +++ b/openair2/RRC/NR_UE/rrc_UE.c @@ -1372,7 +1372,8 @@ static void rrc_ue_generate_RRCSetupComplete( if (get_softmodem_params()->sa) { as_nas_info_t initialNasMsg; - generateRegistrationRequest(&initialNasMsg, ctxt_pP->module_id); + nr_ue_nas_t *nas = get_ue_nas_info(ctxt_pP->module_id); + generateRegistrationRequest(&initialNasMsg, nas); nas_msg = (char*)initialNasMsg.data; nas_msg_length = initialNasMsg.length; } else { diff --git a/openair3/NAS/COMMON/NR_NAS_defs.h b/openair3/NAS/COMMON/NR_NAS_defs.h index 54ac8c037e..4137d395d2 100644 --- a/openair3/NAS/COMMON/NR_NAS_defs.h +++ b/openair3/NAS/COMMON/NR_NAS_defs.h @@ -371,7 +371,7 @@ typedef struct { #define STATIC_ASSERT(test_for_true) _Static_assert((test_for_true), "(" #test_for_true ") failed") #define myCalloc(var, type) type * var=(type*)calloc(sizeof(type),1); #define arrayCpy(tO, FroM) STATIC_ASSERT(sizeof(tO) == sizeof(FroM)) ; memcpy(tO, FroM, sizeof(tO)) -int resToresStar(uint8_t *msg, uicc_t* uicc); +int resToresStar(uint8_t *msg, const uicc_t* uicc); int identityResponse(void **msg, nr_user_nas_t *UE); int authenticationResponse(void **msg, nr_user_nas_t *UE); diff --git a/openair3/NAS/COMMON/nr_common.c b/openair3/NAS/COMMON/nr_common.c index 590ccf65ae..0a7aae2a17 100644 --- a/openair3/NAS/COMMON/nr_common.c +++ b/openair3/NAS/COMMON/nr_common.c @@ -39,7 +39,7 @@ void servingNetworkName(uint8_t *msg, char * imsiStr, int nmc_size) { memcpy(msg+13, imsiStr, 3); } -int resToresStar(uint8_t *msg, uicc_t* uicc) { +int resToresStar(uint8_t *msg, const uicc_t* uicc) { // TS 33.220 annex B.2 => FC=0x6B in TS 33.501 annex A.4 //input S to KDF uint8_t S[128]= {0}; diff --git a/openair3/NAS/NR_UE/nr_nas_msg_sim.c b/openair3/NAS/NR_UE/nr_nas_msg_sim.c index fdf52619a4..d415242324 100644 --- a/openair3/NAS/NR_UE/nr_nas_msg_sim.c +++ b/openair3/NAS/NR_UE/nr_nas_msg_sim.c @@ -44,7 +44,6 @@ #include "PduSessionEstablishmentAccept.h" #include "intertask_interface.h" #include "openair2/RRC/NAS/nas_config.h" -#include #include #include #include @@ -54,7 +53,7 @@ uint8_t *registration_request_buf; uint32_t registration_request_len; extern char *baseNetAddress; extern uint16_t NB_UE_INST; -static ue_sa_security_key_t ** ue_security_key; +static nr_ue_nas_t nr_ue_nas; static int nas_protected_security_header_encode( char *buffer, @@ -117,46 +116,44 @@ static int fill_suci(FGSMobileIdentity *mi, const uicc_t *uicc) return sizeof(Suci5GSMobileIdentity_t); } -static int fill_guti(FGSMobileIdentity *mi, const uicc_t *uicc) +static int fill_guti(FGSMobileIdentity *mi, const nr_ue_nas_t *nas) { + uicc_t *uicc = nas->uicc; AssertFatal(false, "Need to add AMF data in function\n"); mi->guti.typeofidentity = FGS_MOBILE_IDENTITY_5G_GUTI; mi->guti.amfregionid = 0xca; mi->guti.amfpointer = 0; mi->guti.amfsetid = 1016; mi->guti.tmsi = 10; - mi->guti.mncdigit1 = - uicc->nmc_size==2 ? uicc->imsiStr[3]-'0' : uicc->imsiStr[4]-'0'; - mi->guti.mncdigit2 = - uicc->nmc_size==2 ? uicc->imsiStr[4]-'0' : uicc->imsiStr[5]-'0'; - mi->guti.mncdigit3 = - uicc->nmc_size==2 ? 0xf : uicc->imsiStr[3]-'0'; + mi->guti.mncdigit1 = uicc->nmc_size == 2 ? uicc->imsiStr[3] - '0' : uicc->imsiStr[4] - '0'; + mi->guti.mncdigit2 = uicc->nmc_size == 2 ? uicc->imsiStr[4] - '0' : uicc->imsiStr[5] - '0'; + mi->guti.mncdigit3 = uicc->nmc_size == 2 ? 0xf : uicc->imsiStr[3] - '0'; mi->guti.mccdigit1 = uicc->imsiStr[0]-'0'; mi->guti.mccdigit2 = uicc->imsiStr[1]-'0'; mi->guti.mccdigit3 = uicc->imsiStr[2]-'0'; return 13; } -static int fill_imeisv(FGSMobileIdentity *mi, int Mod_id) +static int fill_imeisv(FGSMobileIdentity *mi, const uicc_t *uicc) { int i=0; mi->imeisv.typeofidentity = FGS_MOBILE_IDENTITY_IMEISV; - mi->imeisv.digittac01 = getImeisvDigit(Mod_id,i++); - mi->imeisv.digittac02 = getImeisvDigit(Mod_id,i++); - mi->imeisv.digittac03 = getImeisvDigit(Mod_id,i++); - mi->imeisv.digittac04 = getImeisvDigit(Mod_id,i++); - mi->imeisv.digittac05 = getImeisvDigit(Mod_id,i++); - mi->imeisv.digittac06 = getImeisvDigit(Mod_id,i++); - mi->imeisv.digittac07 = getImeisvDigit(Mod_id,i++); - mi->imeisv.digittac08 = getImeisvDigit(Mod_id,i++); - mi->imeisv.digit09 = getImeisvDigit(Mod_id,i++); - mi->imeisv.digit10 = getImeisvDigit(Mod_id,i++); - mi->imeisv.digit11 = getImeisvDigit(Mod_id,i++); - mi->imeisv.digit12 = getImeisvDigit(Mod_id,i++); - mi->imeisv.digit13 = getImeisvDigit(Mod_id,i++); - mi->imeisv.digit14 = getImeisvDigit(Mod_id,i++); - mi->imeisv.digitsv1 = getImeisvDigit(Mod_id,i++); - mi->imeisv.digitsv2 = getImeisvDigit(Mod_id,i++); + mi->imeisv.digittac01 = getImeisvDigit(uicc, i++); + mi->imeisv.digittac02 = getImeisvDigit(uicc, i++); + mi->imeisv.digittac03 = getImeisvDigit(uicc, i++); + mi->imeisv.digittac04 = getImeisvDigit(uicc, i++); + mi->imeisv.digittac05 = getImeisvDigit(uicc, i++); + mi->imeisv.digittac06 = getImeisvDigit(uicc, i++); + mi->imeisv.digittac07 = getImeisvDigit(uicc, i++); + mi->imeisv.digittac08 = getImeisvDigit(uicc, i++); + mi->imeisv.digit09 = getImeisvDigit(uicc, i++); + mi->imeisv.digit10 = getImeisvDigit(uicc, i++); + mi->imeisv.digit11 = getImeisvDigit(uicc, i++); + mi->imeisv.digit12 = getImeisvDigit(uicc, i++); + mi->imeisv.digit13 = getImeisvDigit(uicc, i++); + mi->imeisv.digit14 = getImeisvDigit(uicc, i++); + mi->imeisv.digitsv1 = getImeisvDigit(uicc, i++); + mi->imeisv.digitsv2 = getImeisvDigit(uicc, i++); mi->imeisv.spare = 0x0f; mi->imeisv.oddeven = 1; return 19; @@ -360,25 +357,18 @@ void derive_kgnb(uint8_t kamf[32], uint32_t count, uint8_t *kgnb){ printf("\n"); } -void derive_ue_keys(int Mod_id, uint8_t *buf, uicc_t *uicc) { +void derive_ue_keys(uint8_t *buf, nr_ue_nas_t *nas) { uint8_t ak[6]; uint8_t sqn[6]; - AssertFatal (Mod_id < NB_UE_INST, "Failed, Mod_id %d is over NB_UE_INST!\n", Mod_id); - if(ue_security_key[Mod_id]){ - // clear old key - memset(ue_security_key[Mod_id],0,sizeof(ue_sa_security_key_t)); - }else{ - // Allocate new memory - ue_security_key[Mod_id]=(ue_sa_security_key_t *)calloc(1,sizeof(ue_sa_security_key_t)); - } - uint8_t *kausf = ue_security_key[Mod_id]->kausf; - uint8_t *kseaf = ue_security_key[Mod_id]->kseaf; - uint8_t *kamf = ue_security_key[Mod_id]->kamf; - uint8_t *knas_int = ue_security_key[Mod_id]->knas_int; - uint8_t *output = ue_security_key[Mod_id]->res; - uint8_t *rand = ue_security_key[Mod_id]->rand; - uint8_t *kgnb = ue_security_key[Mod_id]->kgnb; + DevAssert(nas != NULL); + uint8_t *kausf = nas->security.kausf; + uint8_t *kseaf = nas->security.kseaf; + uint8_t *kamf = nas->security.kamf; + uint8_t *knas_int = nas->security.knas_int; + uint8_t *output = nas->security.res; + uint8_t *rand = nas->security.rand; + uint8_t *kgnb = nas->security.kgnb; // get RAND for authentication request for(int index = 0; index < 16;index++){ @@ -387,17 +377,17 @@ void derive_ue_keys(int Mod_id, uint8_t *buf, uicc_t *uicc) { uint8_t resTemp[16]; uint8_t ck[16], ik[16]; - f2345(uicc->key, rand, resTemp, ck, ik, ak, uicc->opc); + f2345(nas->uicc->key, rand, resTemp, ck, ik, ak, nas->uicc->opc); - transferRES(ck, ik, resTemp, rand, output, uicc); + transferRES(ck, ik, resTemp, rand, output, nas->uicc); for(int index = 0; index < 6; index++){ sqn[index] = buf[26+index]; } - derive_kausf(ck, ik, sqn, kausf, uicc); - derive_kseaf(kausf, kseaf, uicc); - derive_kamf(kseaf, kamf, 0x0000, uicc); + derive_kausf(ck, ik, sqn, kausf, nas->uicc); + derive_kseaf(kausf, kseaf, nas->uicc); + derive_kamf(kseaf, kamf, 0x0000, nas->uicc); derive_knas(0x02, 2, kamf, knas_int); derive_kgnb(kamf,0,kgnb); @@ -427,11 +417,17 @@ void derive_ue_keys(int Mod_id, uint8_t *buf, uicc_t *uicc) { printf("\n"); } -void generateRegistrationRequest(as_nas_info_t *initialNasMsg, int Mod_id) { +nr_ue_nas_t *get_ue_nas_info(module_id_t module_id) +{ + DevAssert(module_id == 0); + return &nr_ue_nas; +} + +void generateRegistrationRequest(as_nas_info_t *initialNasMsg, nr_ue_nas_t *nas) +{ int size = sizeof(mm_msg_header_t); fgs_nas_message_t nas_msg={0}; MM_msg *mm_msg; - uicc_t * uicc=checkUicc(Mod_id); mm_msg = &nas_msg.plain.mm_msg; // set header @@ -451,9 +447,9 @@ void generateRegistrationRequest(as_nas_info_t *initialNasMsg, int Mod_id) { mm_msg->registration_request.naskeysetidentifier.naskeysetidentifier = 1; size += 1; if(0){ - size += fill_guti(&mm_msg->registration_request.fgsmobileidentity, uicc); + size += fill_guti(&mm_msg->registration_request.fgsmobileidentity, nas); } else { - size += fill_suci(&mm_msg->registration_request.fgsmobileidentity, uicc); + size += fill_suci(&mm_msg->registration_request.fgsmobileidentity, nas->uicc); } mm_msg->registration_request.presencemask |= REGISTRATION_REQUEST_5GMM_CAPABILITY_PRESENT; @@ -511,13 +507,13 @@ void generateIdentityResponse(as_nas_info_t *initialNasMsg, uint8_t identitytype } -static void generateAuthenticationResp(int Mod_id,as_nas_info_t *initialNasMsg, uint8_t *buf, uicc_t *uicc){ - - derive_ue_keys(Mod_id,buf,uicc); +static void generateAuthenticationResp(nr_ue_nas_t *nas, as_nas_info_t *initialNasMsg, uint8_t *buf) +{ + derive_ue_keys(buf, nas); OctetString res; res.length = 16; res.value = calloc(1,16); - memcpy(res.value,ue_security_key[Mod_id]->res,16); + memcpy(res.value, nas->security.res, 16); int size = sizeof(mm_msg_header_t); fgs_nas_message_t nas_msg; @@ -547,14 +543,15 @@ static void generateAuthenticationResp(int Mod_id,as_nas_info_t *initialNasMsg, initialNasMsg->length = mm_msg_encode(mm_msg, (uint8_t*)(initialNasMsg->data), size); } -int nas_itti_kgnb_refresh_req(const uint8_t kgnb[32], int instance) { +int nas_itti_kgnb_refresh_req(const uint8_t kgnb[32]) +{ MessageDef *message_p; message_p = itti_alloc_new_message(TASK_NAS_NRUE, 0, NAS_KENB_REFRESH_REQ); memcpy(NAS_KENB_REFRESH_REQ(message_p).kenb, kgnb, sizeof(NAS_KENB_REFRESH_REQ(message_p).kenb)); - return itti_send_msg_to_task(TASK_RRC_NRUE, instance, message_p); + return itti_send_msg_to_task(TASK_RRC_NRUE, 0, message_p); } -static void generateSecurityModeComplete(int Mod_id,as_nas_info_t *initialNasMsg) +static void generateSecurityModeComplete(nr_ue_nas_t *nas, as_nas_info_t *initialNasMsg) { int size = sizeof(mm_msg_header_t); fgs_nas_message_t nas_msg; @@ -583,7 +580,7 @@ static void generateSecurityModeComplete(int Mod_id,as_nas_info_t *initialNasMsg mm_msg->fgs_security_mode_complete.messagetype = FGS_SECURITY_MODE_COMPLETE; size += 1; - size += fill_imeisv(&mm_msg->fgs_security_mode_complete.fgsmobileidentity, Mod_id); + size += fill_imeisv(&mm_msg->fgs_security_mode_complete.fgsmobileidentity, nas->uicc); mm_msg->fgs_security_mode_complete.fgsnasmessagecontainer.nasmessagecontainercontents.value = registration_request_buf; mm_msg->fgs_security_mode_complete.fgsnasmessagecontainer.nasmessagecontainercontents.length = registration_request_len; @@ -596,7 +593,7 @@ static void generateSecurityModeComplete(int Mod_id,as_nas_info_t *initialNasMsg initialNasMsg->length = security_header_len + mm_msg_encode(mm_msg, (uint8_t*)(initialNasMsg->data+security_header_len), size-security_header_len); - stream_cipher.key = ue_security_key[Mod_id]->knas_int; + stream_cipher.key = nas->security.knas_int; stream_cipher.key_length = 16; stream_cipher.count = 0; stream_cipher.bearer = 1; @@ -616,7 +613,8 @@ static void generateSecurityModeComplete(int Mod_id,as_nas_info_t *initialNasMsg } } -static void generateRegistrationComplete(int Mod_id, as_nas_info_t *initialNasMsg, SORTransparentContainer *sortransparentcontainer) { +static void generateRegistrationComplete(nr_ue_nas_t *nas, as_nas_info_t *initialNasMsg, SORTransparentContainer *sortransparentcontainer) +{ //wait send RRCReconfigurationComplete and InitialContextSetupResponse sleep(1); int length = 0; @@ -676,7 +674,7 @@ static void generateRegistrationComplete(int Mod_id, as_nas_info_t *initialNasMs } initialNasMsg->length = length; - stream_cipher.key = ue_security_key[Mod_id]->knas_int; + stream_cipher.key = nas->security.knas_int; stream_cipher.key_length = 16; stream_cipher.count = 1; stream_cipher.bearer = 1; @@ -709,7 +707,8 @@ void decodeDownlinkNASTransport(as_nas_info_t *initialNasMsg, uint8_t * pdu_buff } } -static void generatePduSessionEstablishRequest(int Mod_id, uicc_t * uicc, as_nas_info_t *initialNasMsg){ +static void generatePduSessionEstablishRequest(nr_ue_nas_t *nas, as_nas_info_t *initialNasMsg) +{ //wait send RegistrationComplete usleep(100*150); int size = 0; @@ -760,25 +759,25 @@ static void generatePduSessionEstablishRequest(int Mod_id, uicc_t * uicc, as_nas mm_msg->uplink_nas_transport.pdusessionid = 10; mm_msg->uplink_nas_transport.requesttype = 1; size += 3; - const bool has_nssai_sd = uicc->nssai_sd != 0xffffff; // 0xffffff means "no SD", TS 23.003 + const bool has_nssai_sd = nas->uicc->nssai_sd != 0xffffff; // 0xffffff means "no SD", TS 23.003 const size_t nssai_len = has_nssai_sd ? 4 : 1; mm_msg->uplink_nas_transport.snssai.length = nssai_len; //Fixme: it seems there are a lot of memory errors in this: this value was on the stack, // but pushed in a itti message to another thread // this kind of error seems in many places in 5G NAS mm_msg->uplink_nas_transport.snssai.value = calloc(1, nssai_len); - mm_msg->uplink_nas_transport.snssai.value[0] = uicc->nssai_sst; + mm_msg->uplink_nas_transport.snssai.value[0] = nas->uicc->nssai_sst; if (has_nssai_sd) { - mm_msg->uplink_nas_transport.snssai.value[1] = (uicc->nssai_sd >> 16) & 0xFF; - mm_msg->uplink_nas_transport.snssai.value[2] = (uicc->nssai_sd >> 8) & 0xFF; - mm_msg->uplink_nas_transport.snssai.value[3] = (uicc->nssai_sd) & 0xFF; + mm_msg->uplink_nas_transport.snssai.value[1] = (nas->uicc->nssai_sd >> 16) & 0xFF; + mm_msg->uplink_nas_transport.snssai.value[2] = (nas->uicc->nssai_sd >> 8) & 0xFF; + mm_msg->uplink_nas_transport.snssai.value[3] = (nas->uicc->nssai_sd) & 0xFF; } size += 1 + 1 + nssai_len; - int dnnSize=strlen(uicc->dnnStr); + int dnnSize=strlen(nas->uicc->dnnStr); mm_msg->uplink_nas_transport.dnn.value=calloc(1,dnnSize+1); mm_msg->uplink_nas_transport.dnn.length = dnnSize + 1; mm_msg->uplink_nas_transport.dnn.value[0] = dnnSize; - memcpy(mm_msg->uplink_nas_transport.dnn.value+1,uicc->dnnStr, dnnSize); + memcpy(mm_msg->uplink_nas_transport.dnn.value + 1, nas->uicc->dnnStr, dnnSize); size += (1+1+dnnSize+1); // encode the message @@ -787,7 +786,7 @@ static void generatePduSessionEstablishRequest(int Mod_id, uicc_t * uicc, as_nas initialNasMsg->length = security_header_len + mm_msg_encode(mm_msg, (uint8_t*)(initialNasMsg->data+security_header_len), size-security_header_len); - stream_cipher.key = ue_security_key[Mod_id]->knas_int; + stream_cipher.key = nas->security.knas_int; stream_cipher.key_length = 16; stream_cipher.count = 0; stream_cipher.bearer = 1; @@ -851,7 +850,8 @@ void *nas_nrue_task(void *args_p) uint8_t *pdu_buffer = NULL; - ue_security_key=(ue_sa_security_key_t **)calloc(1,sizeof(ue_sa_security_key_t*)*NB_UE_INST); + nr_ue_nas.uicc = checkUicc(0); + nr_ue_nas_t *nas = get_ue_nas_info(0); itti_mark_task_ready (TASK_NAS_NRUE); while(1) { @@ -860,13 +860,7 @@ void *nas_nrue_task(void *args_p) if (msg_p != NULL) { instance = msg_p->ittiMsgHeader.originInstance; - uicc_t *uicc=checkUicc(instance); - - if (instance == INSTANCE_DEFAULT) { - printf("%s:%d: FATAL: instance is INSTANCE_DEFAULT, should not happen.\n", - __FILE__, __LINE__); - exit_fun("exit... \n"); - } + AssertFatal(instance == 0, "cannot handle more than one UE!\n"); switch (ITTI_MSG_ID(msg_p)) { case INITIALIZE_MESSAGE: @@ -918,7 +912,7 @@ void *nas_nrue_task(void *args_p) as_nas_info_t initialNasMsg; memset(&initialNasMsg, 0, sizeof(as_nas_info_t)); - generateRegistrationComplete(instance, &initialNasMsg, NULL); + generateRegistrationComplete(nas, &initialNasMsg, NULL); if (initialNasMsg.length > 0) { send_nas_uplink_data_req(instance, &initialNasMsg); LOG_I(NAS, "Send NAS_UPLINK_DATA_REQ message(RegistrationComplete)\n"); @@ -926,7 +920,7 @@ void *nas_nrue_task(void *args_p) as_nas_info_t pduEstablishMsg; memset(&pduEstablishMsg, 0, sizeof(as_nas_info_t)); - generatePduSessionEstablishRequest(instance, uicc, &pduEstablishMsg); + generatePduSessionEstablishRequest(nas, &pduEstablishMsg); if (pduEstablishMsg.length > 0) { send_nas_uplink_data_req(instance, &pduEstablishMsg); LOG_I(NAS, "Send NAS_UPLINK_DATA_REQ message(PduSessionEstablishRequest)\n"); @@ -966,14 +960,14 @@ void *nas_nrue_task(void *args_p) switch(msg_type){ case FGS_IDENTITY_REQUEST: - generateIdentityResponse(&initialNasMsg,*(pdu_buffer+3), uicc); + generateIdentityResponse(&initialNasMsg, *(pdu_buffer + 3), nas->uicc); break; case FGS_AUTHENTICATION_REQUEST: - generateAuthenticationResp(instance,&initialNasMsg, pdu_buffer, uicc); + generateAuthenticationResp(nas, &initialNasMsg, pdu_buffer); break; case FGS_SECURITY_MODE_COMMAND: - nas_itti_kgnb_refresh_req(ue_security_key[instance]->kgnb, instance); - generateSecurityModeComplete(instance,&initialNasMsg); + nas_itti_kgnb_refresh_req(nas->security.kgnb); + generateSecurityModeComplete(nas, &initialNasMsg); break; case FGS_DOWNLINK_NAS_TRANSPORT: decodeDownlinkNASTransport(&initialNasMsg, pdu_buffer); diff --git a/openair3/NAS/NR_UE/nr_nas_msg_sim.h b/openair3/NAS/NR_UE/nr_nas_msg_sim.h index abdc5b2e75..8ca655ad9d 100644 --- a/openair3/NAS/NR_UE/nr_nas_msg_sim.h +++ b/openair3/NAS/NR_UE/nr_nas_msg_sim.h @@ -39,6 +39,7 @@ #include "RegistrationComplete.h" #include "as_message.h" #include "FGSUplinkNasTransport.h" +#include #define PLAIN_5GS_MSG 0b0000 #define INTEGRITY_PROTECTED 0b0001 @@ -80,6 +81,11 @@ typedef struct { uint8_t kgnb[32]; } ue_sa_security_key_t; +typedef struct { + uicc_t *uicc; + ue_sa_security_key_t security; +} nr_ue_nas_t; + typedef enum fgs_protocol_discriminator_e { /* Protocol discriminator identifier for 5GS Mobility Management */ FGS_MOBILITY_MANAGEMENT_MESSAGE = 0x7E, @@ -158,7 +164,8 @@ typedef struct { fgs_sm_nas_msg_header_t sm_nas_msg_header; } dl_nas_transport_t; -void generateRegistrationRequest(as_nas_info_t *initialNasMsg, int Mod_id); +nr_ue_nas_t *get_ue_nas_info(module_id_t module_id); +void generateRegistrationRequest(as_nas_info_t *initialNasMsg, nr_ue_nas_t *nas); void *nas_nrue_task(void *args_p); #endif /* __NR_NAS_MSG_SIM_H__*/ diff --git a/openair3/UICC/usim_interface.c b/openair3/UICC/usim_interface.c index 51a50fc8b8..fe91ff7d8d 100644 --- a/openair3/UICC/usim_interface.c +++ b/openair3/UICC/usim_interface.c @@ -116,9 +116,8 @@ uicc_t * checkUicc(int Mod_id) { return (uicc_t*) uiccArray[Mod_id]; } -uint8_t getImeisvDigit(int Mod_id,uint8_t i) +uint8_t getImeisvDigit(const uicc_t *uicc, uint8_t i) { - uicc_t * uicc=checkUicc(Mod_id); uint8_t r = 0; uint8_t l = strlen(uicc->imeisvStr); if (l > IMEISV_STR_MAX_LENGTH) { diff --git a/openair3/UICC/usim_interface.h b/openair3/UICC/usim_interface.h index 4c779f5a7f..1a275db887 100644 --- a/openair3/UICC/usim_interface.h +++ b/openair3/UICC/usim_interface.h @@ -73,6 +73,5 @@ typedef struct { uicc_t *checkUicc(int Mod_id); uicc_t *init_uicc(char *sectionName); void uicc_milenage_generate(uint8_t * autn, uicc_t *uicc); -uicc_t * checkUicc(int Mod_id); -uint8_t getImeisvDigit(int Mod_id,uint8_t i); +uint8_t getImeisvDigit(const uicc_t *uicc, uint8_t i); #endif From 6e01a628384a450beaad4d1e65275dd5fd78f4f4 Mon Sep 17 00:00:00 2001 From: Robert Schmidt Date: Mon, 13 Mar 2023 12:30:59 +0100 Subject: [PATCH 24/89] Decode Registration Accept to receive GUTI --- .../NAS/COMMON/EMM/MSG/RegistrationAccept.c | 17 ++++++++++++ .../NAS/COMMON/EMM/MSG/RegistrationAccept.h | 4 +++ openair3/NAS/NR_UE/nr_nas_msg_sim.c | 26 +++++++++++++++++++ openair3/NAS/NR_UE/nr_nas_msg_sim.h | 1 + 4 files changed, 48 insertions(+) diff --git a/openair3/NAS/COMMON/EMM/MSG/RegistrationAccept.c b/openair3/NAS/COMMON/EMM/MSG/RegistrationAccept.c index d8fde7e2cc..c34d777bd5 100644 --- a/openair3/NAS/COMMON/EMM/MSG/RegistrationAccept.c +++ b/openair3/NAS/COMMON/EMM/MSG/RegistrationAccept.c @@ -50,6 +50,16 @@ int decode_registration_accept(registration_accept_msg *registration_accept, uin decoded += decoded_result; + if (decoded < len && buffer[decoded] == 0x77) { + registration_accept->guti = calloc(1, sizeof(*registration_accept->guti)); + if (!registration_accept->guti) + return -1; + int mi_dec = decode_5gs_mobile_identity(registration_accept->guti, 0x77, buffer + decoded, len - decoded); + if (mi_dec < 0) + return -1; + decoded += mi_dec; + } + // todo ,Decoding optional fields return decoded; } @@ -64,6 +74,13 @@ int encode_registration_accept(registration_accept_msg *registration_accept, uin *(buffer + encoded) = encode_fgs_registration_result(®istration_accept->fgsregistrationresult); encoded = encoded + 2; + if (registration_accept->guti) { + int mi_enc = encode_5gs_mobile_identity(registration_accept->guti, 0x77, buffer + encoded, len - encoded); + if (mi_enc < 0) + return mi_enc; + encoded += mi_enc; + } + // todo ,Encoding optional fields LOG_FUNC_RETURN(encoded); } diff --git a/openair3/NAS/COMMON/EMM/MSG/RegistrationAccept.h b/openair3/NAS/COMMON/EMM/MSG/RegistrationAccept.h index 08daec2646..e0d3ade23a 100644 --- a/openair3/NAS/COMMON/EMM/MSG/RegistrationAccept.h +++ b/openair3/NAS/COMMON/EMM/MSG/RegistrationAccept.h @@ -38,6 +38,7 @@ #include "SpareHalfOctet.h" #include "MessageType.h" #include "FGSRegistrationResult.h" +#include "FGSMobileIdentity.h" #ifndef REGISTRATION_ACCEPT_H_ #define REGISTRATION_ACCEPT_H_ @@ -56,6 +57,9 @@ typedef struct registration_accept_msg_tag { SpareHalfOctet sparehalfoctet:4; MessageType messagetype; FGSRegistrationResult fgsregistrationresult; + + /* Optional fields */ + FGSMobileIdentity *guti; } registration_accept_msg; int decode_registration_accept(registration_accept_msg *registrationaccept, uint8_t *buffer, uint32_t len); diff --git a/openair3/NAS/NR_UE/nr_nas_msg_sim.c b/openair3/NAS/NR_UE/nr_nas_msg_sim.c index d415242324..443fb8d06c 100644 --- a/openair3/NAS/NR_UE/nr_nas_msg_sim.c +++ b/openair3/NAS/NR_UE/nr_nas_msg_sim.c @@ -42,6 +42,7 @@ #include "kdf.h" #include "PduSessionEstablishRequest.h" #include "PduSessionEstablishmentAccept.h" +#include "RegistrationAccept.h" #include "intertask_interface.h" #include "openair2/RRC/NAS/nas_config.h" #include @@ -613,6 +614,30 @@ static void generateSecurityModeComplete(nr_ue_nas_t *nas, as_nas_info_t *initia } } +static void decodeRegistrationAccept(uint8_t *buf, int len, nr_ue_nas_t *nas) +{ + registration_accept_msg reg_acc = {0}; + /* it seems there is no 5G corresponding emm_msg_decode() function, so here + * we just jump to the right decision */ + buf += 7; /* skip security header */ + buf += 2; /* skip prot discriminator, security header, half octet */ + AssertFatal(*buf == 0x42, "this is not a NAS Registration Accept\n"); + buf++; + int decoded = decode_registration_accept(®_acc, buf, len); + AssertFatal(decoded > 0, "could not decode registration accept\n"); + if (reg_acc.guti) { + AssertFatal(reg_acc.guti->guti.typeofidentity == FGS_MOBILE_IDENTITY_5G_GUTI, + "registration accept 5GS Mobile Identity is not GUTI, but %d\n", + reg_acc.guti->guti.typeofidentity); + nas->guti = malloc(sizeof(nas->guti)); + AssertFatal(nas->guti, "out of memory\n"); + *nas->guti = reg_acc.guti->guti; + free(reg_acc.guti); /* no proper memory management for NAS decoded messages */ + } else { + LOG_W(NAS, "no GUTI in registration accept\n"); + } +} + static void generateRegistrationComplete(nr_ue_nas_t *nas, as_nas_info_t *initialNasMsg, SORTransparentContainer *sortransparentcontainer) { //wait send RRCReconfigurationComplete and InitialContextSetupResponse @@ -909,6 +934,7 @@ void *nas_nrue_task(void *args_p) if (msg_type == REGISTRATION_ACCEPT) { LOG_I(NAS, "[UE] Received REGISTRATION ACCEPT message\n"); + decodeRegistrationAccept(pdu_buffer, NAS_CONN_ESTABLI_CNF(msg_p).nasMsg.length, nas); as_nas_info_t initialNasMsg; memset(&initialNasMsg, 0, sizeof(as_nas_info_t)); diff --git a/openair3/NAS/NR_UE/nr_nas_msg_sim.h b/openair3/NAS/NR_UE/nr_nas_msg_sim.h index 8ca655ad9d..86cc4f856d 100644 --- a/openair3/NAS/NR_UE/nr_nas_msg_sim.h +++ b/openair3/NAS/NR_UE/nr_nas_msg_sim.h @@ -84,6 +84,7 @@ typedef struct { typedef struct { uicc_t *uicc; ue_sa_security_key_t security; + Guti5GSMobileIdentity_t *guti; } nr_ue_nas_t; typedef enum fgs_protocol_discriminator_e { From 4642a7c0f501efc093ff4cd657bcd38e063d43b4 Mon Sep 17 00:00:00 2001 From: Robert Schmidt Date: Mon, 13 Mar 2023 15:04:50 +0100 Subject: [PATCH 25/89] Correctly fill GUTI in NAS if available --- openair3/NAS/NR_UE/nr_nas_msg_sim.c | 21 +++++---------------- 1 file changed, 5 insertions(+), 16 deletions(-) diff --git a/openair3/NAS/NR_UE/nr_nas_msg_sim.c b/openair3/NAS/NR_UE/nr_nas_msg_sim.c index 443fb8d06c..4e16b64566 100644 --- a/openair3/NAS/NR_UE/nr_nas_msg_sim.c +++ b/openair3/NAS/NR_UE/nr_nas_msg_sim.c @@ -117,21 +117,10 @@ static int fill_suci(FGSMobileIdentity *mi, const uicc_t *uicc) return sizeof(Suci5GSMobileIdentity_t); } -static int fill_guti(FGSMobileIdentity *mi, const nr_ue_nas_t *nas) +static int fill_guti(FGSMobileIdentity *mi, const Guti5GSMobileIdentity_t *guti) { - uicc_t *uicc = nas->uicc; - AssertFatal(false, "Need to add AMF data in function\n"); - mi->guti.typeofidentity = FGS_MOBILE_IDENTITY_5G_GUTI; - mi->guti.amfregionid = 0xca; - mi->guti.amfpointer = 0; - mi->guti.amfsetid = 1016; - mi->guti.tmsi = 10; - mi->guti.mncdigit1 = uicc->nmc_size == 2 ? uicc->imsiStr[3] - '0' : uicc->imsiStr[4] - '0'; - mi->guti.mncdigit2 = uicc->nmc_size == 2 ? uicc->imsiStr[4] - '0' : uicc->imsiStr[5] - '0'; - mi->guti.mncdigit3 = uicc->nmc_size == 2 ? 0xf : uicc->imsiStr[3] - '0'; - mi->guti.mccdigit1 = uicc->imsiStr[0]-'0'; - mi->guti.mccdigit2 = uicc->imsiStr[1]-'0'; - mi->guti.mccdigit3 = uicc->imsiStr[2]-'0'; + AssertFatal(guti != NULL, "UE has no GUTI\n"); + mi->guti = *guti; return 13; } @@ -447,8 +436,8 @@ void generateRegistrationRequest(as_nas_info_t *initialNasMsg, nr_ue_nas_t *nas) mm_msg->registration_request.fgsregistrationtype = INITIAL_REGISTRATION; mm_msg->registration_request.naskeysetidentifier.naskeysetidentifier = 1; size += 1; - if(0){ - size += fill_guti(&mm_msg->registration_request.fgsmobileidentity, nas); + if(nas->guti){ + size += fill_guti(&mm_msg->registration_request.fgsmobileidentity, nas->guti); } else { size += fill_suci(&mm_msg->registration_request.fgsmobileidentity, nas->uicc); } From 77e61425e0e93934d1d0e88fad4e09d215b14249 Mon Sep 17 00:00:00 2001 From: Robert Schmidt Date: Mon, 13 Mar 2023 15:08:44 +0100 Subject: [PATCH 26/89] UE NAS: Add counter for NAS messages --- openair3/NAS/NR_UE/nr_nas_msg_sim.c | 6 +++--- openair3/NAS/NR_UE/nr_nas_msg_sim.h | 2 ++ 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/openair3/NAS/NR_UE/nr_nas_msg_sim.c b/openair3/NAS/NR_UE/nr_nas_msg_sim.c index 4e16b64566..95a4bf6782 100644 --- a/openair3/NAS/NR_UE/nr_nas_msg_sim.c +++ b/openair3/NAS/NR_UE/nr_nas_msg_sim.c @@ -585,7 +585,7 @@ static void generateSecurityModeComplete(nr_ue_nas_t *nas, as_nas_info_t *initia stream_cipher.key = nas->security.knas_int; stream_cipher.key_length = 16; - stream_cipher.count = 0; + stream_cipher.count = nas->security.mm_counter++; stream_cipher.bearer = 1; stream_cipher.direction = 0; stream_cipher.message = (unsigned char *)(initialNasMsg->data + 6); @@ -690,7 +690,7 @@ static void generateRegistrationComplete(nr_ue_nas_t *nas, as_nas_info_t *initia initialNasMsg->length = length; stream_cipher.key = nas->security.knas_int; stream_cipher.key_length = 16; - stream_cipher.count = 1; + stream_cipher.count = nas->security.mm_counter++; stream_cipher.bearer = 1; stream_cipher.direction = 0; stream_cipher.message = (unsigned char *)(initialNasMsg->data + 6); @@ -802,7 +802,7 @@ static void generatePduSessionEstablishRequest(nr_ue_nas_t *nas, as_nas_info_t * stream_cipher.key = nas->security.knas_int; stream_cipher.key_length = 16; - stream_cipher.count = 0; + stream_cipher.count = nas->security.sm_counter++; stream_cipher.bearer = 1; stream_cipher.direction = 0; stream_cipher.message = (unsigned char *)(initialNasMsg->data + 6); diff --git a/openair3/NAS/NR_UE/nr_nas_msg_sim.h b/openair3/NAS/NR_UE/nr_nas_msg_sim.h index 86cc4f856d..009b14b2ec 100644 --- a/openair3/NAS/NR_UE/nr_nas_msg_sim.h +++ b/openair3/NAS/NR_UE/nr_nas_msg_sim.h @@ -79,6 +79,8 @@ typedef struct { uint8_t res[16]; uint8_t rand[16]; uint8_t kgnb[32]; + uint32_t mm_counter; + uint32_t sm_counter; } ue_sa_security_key_t; typedef struct { From 09ea970b34cca487916f260a23b31ab1c2cb64be Mon Sep 17 00:00:00 2001 From: Robert Schmidt Date: Sat, 11 Mar 2023 14:43:29 +0100 Subject: [PATCH 27/89] Stop UE with ^C --- executables/nr-uesoftmodem.c | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/executables/nr-uesoftmodem.c b/executables/nr-uesoftmodem.c index eb6e160ec3..cd83252bc2 100644 --- a/executables/nr-uesoftmodem.c +++ b/executables/nr-uesoftmodem.c @@ -426,7 +426,7 @@ int main( int argc, char **argv ) { if ( load_configmodule(argc,argv,CONFIG_ENABLECMDLINEONLY) == NULL) { exit_fun("[SOFTMODEM] Error, configuration module init failed\n"); } - set_softmodem_sighandler(); + //set_softmodem_sighandler(); CONFIG_SETRTFLAG(CONFIG_NOEXITONHELP); memset(openair0_cfg,0,sizeof(openair0_config_t)*MAX_CARDS); memset(tx_max_power,0,sizeof(int)*MAX_NUM_CCs); @@ -550,15 +550,28 @@ int main( int argc, char **argv ) { // Sleep a while before checking all parameters have been used // Some are used directly in external threads, asynchronously - sleep(20); + sleep(2); config_check_unknown_cmdlineopt(CONFIG_CHECKALLSECTIONS); - while(true) - sleep(3600); + // wait for end of program + printf("Entering ITTI signals handler\n"); + printf("TYPE TO TERMINATE\n"); + itti_wait_tasks_end(); + printf("Returned from ITTI signal handler\n"); + oai_exit=1; + printf("oai_exit=%d\n",oai_exit); if (ouput_vcd) vcd_signal_dumper_close(); + if (PHY_vars_UE_g && PHY_vars_UE_g[0]) { + for (int CC_id = 0; CC_id < MAX_NUM_CCs; CC_id++) { + PHY_VARS_NR_UE *phy_vars = PHY_vars_UE_g[0][CC_id]; + if (phy_vars && phy_vars->rfdevice.trx_end_func) + phy_vars->rfdevice.trx_end_func(&phy_vars->rfdevice); + } + } + return 0; } From a0318fb413ddf6e8f12f910aa6f9a90d5a24c314 Mon Sep 17 00:00:00 2001 From: Robert Schmidt Date: Sat, 11 Mar 2023 03:07:57 +0100 Subject: [PATCH 28/89] Implement NAS Deregistration Type message --- CMakeLists.txt | 1 + .../FGSDeregistrationRequestUEOriginating.c | 59 ++++++++++++++++++ .../FGSDeregistrationRequestUEOriginating.h | 60 +++++++++++++++++++ .../NAS/COMMON/IES/FGSDeregistrationType.h | 46 ++++++++++++++ 4 files changed, 166 insertions(+) create mode 100644 openair3/NAS/COMMON/EMM/MSG/FGSDeregistrationRequestUEOriginating.c create mode 100644 openair3/NAS/COMMON/EMM/MSG/FGSDeregistrationRequestUEOriginating.h create mode 100644 openair3/NAS/COMMON/IES/FGSDeregistrationType.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 2e14f7fe10..691cb4aaa8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1895,6 +1895,7 @@ set(libnrnas_emm_msg_OBJS ${NAS_SRC}COMMON/EMM/MSG/FGSUplinkNasTransport.c ${NAS_SRC}COMMON/ESM/MSG/PduSessionEstablishRequest.c ${NAS_SRC}COMMON/ESM/MSG/PduSessionEstablishmentAccept.c + ${NAS_SRC}COMMON/EMM/MSG/FGSDeregistrationRequestUEOriginating.c ) set(libnrnas_ies_OBJS diff --git a/openair3/NAS/COMMON/EMM/MSG/FGSDeregistrationRequestUEOriginating.c b/openair3/NAS/COMMON/EMM/MSG/FGSDeregistrationRequestUEOriginating.c new file mode 100644 index 0000000000..0d9a4bbac2 --- /dev/null +++ b/openair3/NAS/COMMON/EMM/MSG/FGSDeregistrationRequestUEOriginating.c @@ -0,0 +1,59 @@ +/* + * 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 + */ + +/*! \file RegistrationRequest.c + * \brief registration request procedures for gNB + * \author Yoshio INOUE, Masayuki HARADA + * \email yoshio.inoue@fujitsu.com,masayuki.harada@fujitsu.com + * \date 2020 + * \version 0.1 + */ + +#include +#include +#include +#include + +#include "FGSDeregistrationRequestUEOriginating.h" + +int encode_fgs_deregistration_request_ue_originating(fgs_deregistration_request_ue_originating_msg *drr, + uint8_t *buffer, + uint32_t len) +{ + int encoded = 0; + FGSDeregistrationType *dt = &drr->deregistrationtype; + *(buffer + encoded) = ((dt->switchoff & 0x1) << 7) + | ((dt->reregistration_required & 0x1) << 6) + | ((dt->access_type & 0x3) << 4); + + int encode_result; + if ((encode_result = encode_nas_key_set_identifier(&drr->naskeysetidentifier, 0, buffer + encoded, len - encoded)) < 0) + return encode_result; + + encoded++; + + if ((encode_result = encode_5gs_mobile_identity(&drr->fgsmobileidentity, 0, buffer + encoded, len - encoded)) < 0) + return encode_result; + else + encoded += encode_result; + + return encoded; +} diff --git a/openair3/NAS/COMMON/EMM/MSG/FGSDeregistrationRequestUEOriginating.h b/openair3/NAS/COMMON/EMM/MSG/FGSDeregistrationRequestUEOriginating.h new file mode 100644 index 0000000000..2bccd8a5bd --- /dev/null +++ b/openair3/NAS/COMMON/EMM/MSG/FGSDeregistrationRequestUEOriginating.h @@ -0,0 +1,60 @@ +/* + * Licensed to the OpenAirInterface (OAI) Software Alliance under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The OpenAirInterface Software Alliance licenses this file to You under + * the OAI Public License, Version 1.1 (the "License"); you may not use this file + * except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.openairinterface.org/?page_id=698 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *------------------------------------------------------------------------------- + * For more information about the OpenAirInterface (OAI) Software Alliance: + * contact@openairinterface.org + */ + +#ifndef FGS_DEREGISTRATION_REQUEST_UE_ORIGINATING_H_ +#define FGS_DEREGISTRATION_REQUEST_UE_ORIGINATING_H_ + +#include +#include +#include + +#include "ExtendedProtocolDiscriminator.h" +#include "SecurityHeaderType.h" +#include "SpareHalfOctet.h" +#include "MessageType.h" +#include "FGSDeregistrationType.h" +#include "NasKeySetIdentifier.h" +#include "FGSMobileIdentity.h" + +/* + * Message name: De-registration request (UE originating de-registration) + * Description: This message is sent by the UE to the AMF. See TS24.501 table 8.2.12.1.1. + * Significance: dual + * Direction: UE to network + */ + +typedef struct fgs_deregistration_request_ue_originating_msg_tag { + /* Mandatory fields */ + ExtendedProtocolDiscriminator protocoldiscriminator; + SecurityHeaderType securityheadertype: 4; + SpareHalfOctet sparehalfoctet: 4; + MessageType messagetype; + FGSDeregistrationType deregistrationtype; + NasKeySetIdentifier naskeysetidentifier; + FGSMobileIdentity fgsmobileidentity; +} fgs_deregistration_request_ue_originating_msg; + +int encode_fgs_deregistration_request_ue_originating(fgs_deregistration_request_ue_originating_msg *registrationrequest, + uint8_t *buffer, + uint32_t len); + +#endif /* ! defined(REGISTRATION_REQUEST_H_) */ + diff --git a/openair3/NAS/COMMON/IES/FGSDeregistrationType.h b/openair3/NAS/COMMON/IES/FGSDeregistrationType.h new file mode 100644 index 0000000000..84a09657d4 --- /dev/null +++ b/openair3/NAS/COMMON/IES/FGSDeregistrationType.h @@ -0,0 +1,46 @@ +/* + * 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_DEREGISTRATION_TYPE_H_ +#define FGS_DEREGISTRATION_TYPE_H_ + +#include +#include "OctetString.h" + +#define FGS_DEREGISTRATION_TYPE_MINIMUM_LENGTH 1 +#define FGS_DEREGISTRATION_TYPE_MAXIMUM_LENGTH 1 + +typedef struct FGSDeregistrationType_tag { +#define NORMAL_DEREGISTRATION 0 +#define SWITCH_OFF 1 + uint8_t switchoff: 1; +#define REREGISTRATION_NOT_REQUIRED 0 +#define REREGISTRATION_REQUIRED 1 + uint8_t reregistration_required: 1; +#define TGPP_ACCESS 1 +#define NON_TGPP_ACCESS 2 +#define TGPP_AND_NON_TGPP_ACCESS 3 + uint8_t access_type: 2; +} FGSDeregistrationType; + +int encode_fgs_deregistration_type(FGSDeregistrationType *dt, uint8_t iei, uint8_t *buffer, uint32_t len); + +#endif /* FGS_DEREGISTRATION_TYPE_H_ */ From 4700dde2aecadebf7aa7b1c63d31a47fdc683fbc Mon Sep 17 00:00:00 2001 From: Robert Schmidt Date: Sat, 11 Mar 2023 14:44:16 +0100 Subject: [PATCH 29/89] Trigger deregistration --- executables/nr-uesoftmodem.c | 7 ++++ openair2/COMMON/as_message.h | 7 ++++ openair2/COMMON/rrc_messages_def.h | 1 + openair2/COMMON/rrc_messages_types.h | 2 + openair3/NAS/COMMON/NR_NAS_defs.h | 6 +++ openair3/NAS/NR_UE/nr_nas_msg_sim.c | 60 ++++++++++++++++++++++++++++ openair3/NAS/NR_UE/nr_nas_msg_sim.h | 3 ++ 7 files changed, 86 insertions(+) diff --git a/executables/nr-uesoftmodem.c b/executables/nr-uesoftmodem.c index cd83252bc2..bb439ff4f0 100644 --- a/executables/nr-uesoftmodem.c +++ b/executables/nr-uesoftmodem.c @@ -397,6 +397,12 @@ void *rrc_enb_process_msg(void *notUsed) { return NULL; } +static void trigger_deregistration(int signal) +{ + MessageDef *msg = itti_alloc_new_message(TASK_RRC_UE_SIM, 0, NAS_DEREGISTRATION_REQ); + itti_send_msg_to_task(TASK_NAS_NRUE, 0, msg); +} + static void get_channel_model_mode() { paramdef_t GNBParams[] = GNBPARAMS_DESC; config_get(GNBParams, sizeof(GNBParams)/sizeof(paramdef_t), NULL); @@ -553,6 +559,7 @@ int main( int argc, char **argv ) { sleep(2); config_check_unknown_cmdlineopt(CONFIG_CHECKALLSECTIONS); + signal(SIGUSR2, trigger_deregistration); // wait for end of program printf("Entering ITTI signals handler\n"); printf("TYPE TO TERMINATE\n"); diff --git a/openair2/COMMON/as_message.h b/openair2/COMMON/as_message.h index b0ca06daec..ef7314697f 100644 --- a/openair2/COMMON/as_message.h +++ b/openair2/COMMON/as_message.h @@ -327,6 +327,13 @@ typedef struct nas_establish_req_s { as_nas_info_t initialNasMsg; /* Initial NAS message to transfer */ } nas_establish_req_t; +/* + * fill me + */ +typedef struct nas_deregistration_req_s { + // fill me +} nas_deregistration_req_t; + /* * AS->NAS - NAS signalling connection establishment indication * AS transfers the initial NAS message to the NAS. diff --git a/openair2/COMMON/rrc_messages_def.h b/openair2/COMMON/rrc_messages_def.h index d93f07d8f8..f70c8bdd0e 100644 --- a/openair2/COMMON/rrc_messages_def.h +++ b/openair2/COMMON/rrc_messages_def.h @@ -62,6 +62,7 @@ MESSAGE_DEF(NAS_KENB_REFRESH_REQ, MESSAGE_PRIORITY_MED, NasKenbRefre MESSAGE_DEF(NAS_CELL_SELECTION_REQ, MESSAGE_PRIORITY_MED, NasCellSelectionReq, nas_cell_selection_req) MESSAGE_DEF(NAS_CONN_ESTABLI_REQ, MESSAGE_PRIORITY_MED, NasConnEstabliReq, nas_conn_establi_req) MESSAGE_DEF(NAS_UPLINK_DATA_REQ, MESSAGE_PRIORITY_MED, NasUlDataReq, nas_ul_data_req) +MESSAGE_DEF(NAS_DEREGISTRATION_REQ, MESSAGE_PRIORITY_MED, NasDeregistrationReq, nas_deregistration_req) MESSAGE_DEF(NAS_RAB_ESTABLI_RSP, MESSAGE_PRIORITY_MED, NasRabEstRsp, nas_rab_est_rsp) diff --git a/openair2/COMMON/rrc_messages_types.h b/openair2/COMMON/rrc_messages_types.h index 64f3b68bfb..359720f92f 100644 --- a/openair2/COMMON/rrc_messages_types.h +++ b/openair2/COMMON/rrc_messages_types.h @@ -75,6 +75,7 @@ #define NAS_CELL_SELECTION_REQ(mSGpTR) (mSGpTR)->ittiMsg.nas_cell_selection_req #define NAS_CONN_ESTABLI_REQ(mSGpTR) (mSGpTR)->ittiMsg.nas_conn_establi_req #define NAS_UPLINK_DATA_REQ(mSGpTR) (mSGpTR)->ittiMsg.nas_ul_data_req +#define NAS_DEREGISTRATION_REQ(mSGpTR) (mSGpTR)->ittiMsg.nas_deregistration_req #define NAS_RAB_ESTABLI_RSP(mSGpTR) (mSGpTR)->ittiMsg.nas_rab_est_rsp @@ -441,6 +442,7 @@ typedef kenb_refresh_req_t NasKenbRefreshReq; typedef cell_info_req_t NasCellSelectionReq; typedef nas_establish_req_t NasConnEstabliReq; typedef ul_info_transfer_req_t NasUlDataReq; +typedef nas_deregistration_req_t NasDeregistrationReq; typedef rab_establish_rsp_t NasRabEstRsp; diff --git a/openair3/NAS/COMMON/NR_NAS_defs.h b/openair3/NAS/COMMON/NR_NAS_defs.h index 4137d395d2..b6c0b715fc 100644 --- a/openair3/NAS/COMMON/NR_NAS_defs.h +++ b/openair3/NAS/COMMON/NR_NAS_defs.h @@ -364,6 +364,12 @@ typedef struct __attribute__((packed)) { } securityModeCommand_t; +typedef struct __attribute__((packed)) { + Extendedprotocoldiscriminator_t epd: 8; + Security_header_t sh: 8; + SGSmobilitymanagementmessages_t mt: 8; +} deregistrationRequestUEOriginating_t; + typedef struct { uicc_t *uicc; } nr_user_nas_t; diff --git a/openair3/NAS/NR_UE/nr_nas_msg_sim.c b/openair3/NAS/NR_UE/nr_nas_msg_sim.c index 95a4bf6782..13642fa339 100644 --- a/openair3/NAS/NR_UE/nr_nas_msg_sim.c +++ b/openair3/NAS/NR_UE/nr_nas_msg_sim.c @@ -43,6 +43,7 @@ #include "PduSessionEstablishRequest.h" #include "PduSessionEstablishmentAccept.h" #include "RegistrationAccept.h" +#include "FGSDeregistrationRequestUEOriginating.h" #include "intertask_interface.h" #include "openair2/RRC/NAS/nas_config.h" #include @@ -184,6 +185,9 @@ int mm_msg_encode(MM_msg *mm_msg, uint8_t *buffer, uint32_t len) { case FGS_UPLINK_NAS_TRANSPORT: encode_result = encode_fgs_uplink_nas_transport(&mm_msg->uplink_nas_transport, buffer, len); break; + case FGS_DEREGISTRATION_REQUEST_UE_ORIGINATING: + encode_result = encode_fgs_deregistration_request_ue_originating(&mm_msg->fgs_deregistration_request_ue_originating, buffer, len); + break; default: LOG_TRACE(ERROR, "EMM-MSG - Unexpected message type: 0x%x", mm_msg->header.message_type); @@ -721,6 +725,53 @@ void decodeDownlinkNASTransport(as_nas_info_t *initialNasMsg, uint8_t * pdu_buff } } +static void generateDeregistrationRequest(nr_ue_nas_t *nas, as_nas_info_t *initialNasMsg, const nas_deregistration_req_t *req) +{ + fgs_nas_message_t nas_msg = {0}; + fgs_nas_message_security_protected_t *sp_msg; + sp_msg = &nas_msg.security_protected; + sp_msg->header.protocol_discriminator = FGS_MOBILITY_MANAGEMENT_MESSAGE; + sp_msg->header.security_header_type = INTEGRITY_PROTECTED_AND_CIPHERED; + sp_msg->header.message_authentication_code = 0; + sp_msg->header.sequence_number = 2; + int size = sizeof(fgs_nas_message_security_header_t); + + fgs_deregistration_request_ue_originating_msg *dereg_req = &sp_msg->plain.mm_msg.fgs_deregistration_request_ue_originating; + dereg_req->protocoldiscriminator = FGS_MOBILITY_MANAGEMENT_MESSAGE; + size += 1; + dereg_req->securityheadertype = INTEGRITY_PROTECTED_AND_CIPHERED_WITH_NEW_SECU_CTX; + size += 1; + dereg_req->messagetype = FGS_DEREGISTRATION_REQUEST_UE_ORIGINATING; + size += 1; + dereg_req->deregistrationtype.switchoff = NORMAL_DEREGISTRATION; + dereg_req->deregistrationtype.reregistration_required = REREGISTRATION_NOT_REQUIRED; + dereg_req->deregistrationtype.access_type = TGPP_ACCESS; + dereg_req->naskeysetidentifier.naskeysetidentifier = 1; + size += 1; + size += fill_guti(&dereg_req->fgsmobileidentity, nas->guti); + + // encode the message + initialNasMsg->data = calloc(size, sizeof(Byte_t)); + int security_header_len = nas_protected_security_header_encode((char *)(initialNasMsg->data), &nas_msg.header, size); + + initialNasMsg->length = security_header_len + mm_msg_encode(&sp_msg->plain.mm_msg, (uint8_t *)(initialNasMsg->data + security_header_len), size - security_header_len); + + nas_stream_cipher_t stream_cipher = { + .key = nas->security.knas_int, + .key_length = 16, + .count = nas->security.mm_counter++, + .bearer = 1, + .direction = 0, + .message = (unsigned char *)(initialNasMsg->data + 6), + .blength = (initialNasMsg->length - 6) << 3, /* length in bits */ + }; + uint8_t mac[4]; + nas_stream_encrypt_eia2(&stream_cipher, mac); + + for(int i = 0; i < 4; i++) + initialNasMsg->data[2 + i] = mac[i]; +} + static void generatePduSessionEstablishRequest(nr_ue_nas_t *nas, as_nas_info_t *initialNasMsg) { //wait send RegistrationComplete @@ -959,6 +1010,15 @@ void *nas_nrue_task(void *args_p) break; + case NAS_DEREGISTRATION_REQ: { + LOG_I(NAS, "[UE %ld] Received %s\n", instance, ITTI_MSG_NAME(msg_p)); + nas_deregistration_req_t *req = &NAS_DEREGISTRATION_REQ(msg_p); + as_nas_info_t initialNasMsg = {0}; + generateDeregistrationRequest(nas, &initialNasMsg, req); + send_nas_uplink_data_req(instance, &initialNasMsg); + } + break; + case NAS_DOWNLINK_DATA_IND: { LOG_I(NAS, diff --git a/openair3/NAS/NR_UE/nr_nas_msg_sim.h b/openair3/NAS/NR_UE/nr_nas_msg_sim.h index 009b14b2ec..fbf071562d 100644 --- a/openair3/NAS/NR_UE/nr_nas_msg_sim.h +++ b/openair3/NAS/NR_UE/nr_nas_msg_sim.h @@ -36,6 +36,7 @@ #include "FGSIdentityResponse.h" #include "FGSAuthenticationResponse.h" #include "FGSNASSecurityModeComplete.h" +#include "FGSDeregistrationRequestUEOriginating.h" #include "RegistrationComplete.h" #include "as_message.h" #include "FGSUplinkNasTransport.h" @@ -50,6 +51,7 @@ #define REGISTRATION_REQUEST 0b01000001 /* 65 = 0x41 */ #define REGISTRATION_ACCEPT 0b01000010 /* 66 = 0x42 */ #define REGISTRATION_COMPLETE 0b01000011 /* 67 = 0x43 */ +#define FGS_DEREGISTRATION_REQUEST_UE_ORIGINATING 0b01000101 #define FGS_AUTHENTICATION_REQUEST 0b01010110 /* 86 = 0x56 */ #define FGS_AUTHENTICATION_RESPONSE 0b01010111 /* 87 = 0x57 */ #define FGS_IDENTITY_REQUEST 0b01011011 /* 91 = 0x5b */ @@ -117,6 +119,7 @@ typedef union { registration_request_msg registration_request; fgs_identiy_response_msg fgs_identity_response; fgs_authentication_response_msg fgs_auth_response; + fgs_deregistration_request_ue_originating_msg fgs_deregistration_request_ue_originating; fgs_security_mode_complete_msg fgs_security_mode_complete; registration_complete_msg registration_complete; fgs_uplink_nas_transport_msg uplink_nas_transport; From a6486909ca2d20094fdbd60e9045901a77d67833 Mon Sep 17 00:00:00 2001 From: Robert Schmidt Date: Mon, 13 Mar 2023 17:31:35 +0100 Subject: [PATCH 30/89] Reimplement itti_wait_tasks_end() to allow custom wait handlers In the UE, we wish to handle ^C in the UE to send a message before terminating, so we allow custom wait handlers to implement such functionality. Use itti_wait_tasks_unblock() to unblock itti_wait_tasks_end(). Also, implement itti_wait_tasks_end() using semaphores, as signals are not async-signal-safe (and therefore should not be used in a signal handler). --- common/utils/ocp_itti/intertask_interface.cpp | 27 ++++++++++++------- common/utils/ocp_itti/intertask_interface.h | 9 ++++--- executables/lte-softmodem.c | 2 +- executables/lte-uesoftmodem.c | 2 +- executables/nr-cuup.c | 2 +- executables/nr-softmodem.c | 2 +- executables/nr-uesoftmodem.c | 2 +- 7 files changed, 29 insertions(+), 17 deletions(-) diff --git a/common/utils/ocp_itti/intertask_interface.cpp b/common/utils/ocp_itti/intertask_interface.cpp index fe43dde319..897e6757af 100644 --- a/common/utils/ocp_itti/intertask_interface.cpp +++ b/common/utils/ocp_itti/intertask_interface.cpp @@ -24,6 +24,7 @@ #include #include #include +#include extern "C" { @@ -434,24 +435,32 @@ extern "C" { void itti_send_terminate_message(task_id_t task_id) { } - pthread_mutex_t signal_mutex; + sem_t itti_sem_block; + void itti_wait_tasks_unblock() + { + int rc = sem_post(&itti_sem_block); + AssertFatal(rc == 0, "error in sem_post(): %d %s\n", errno, strerror(errno)); + } static void catch_sigterm(int) { static const char msg[] = "\n** Caught SIGTERM, shutting down\n"; __attribute__((unused)) int unused = write(STDOUT_FILENO, msg, sizeof(msg) - 1); - pthread_mutex_unlock(&signal_mutex); + itti_wait_tasks_unblock(); } - void itti_wait_tasks_end(void) { + void itti_wait_tasks_end(void (*handler)(int)) + { + int rc = sem_init(&itti_sem_block, 0, 0); + AssertFatal(rc == 0, "error in sem_init(): %d %s\n", errno, strerror(errno)); - pthread_mutex_init(&signal_mutex, NULL); - pthread_mutex_lock(&signal_mutex); + if (handler == NULL) /* no handler given: install default */ + handler = catch_sigterm; + signal(SIGTERM, handler); + signal(SIGINT, handler); - signal(SIGTERM, catch_sigterm); - signal(SIGINT, catch_sigterm); - - pthread_mutex_lock(&signal_mutex); + rc = sem_wait(&itti_sem_block); + AssertFatal(rc == 0, "error in sem_wait(): %d %s\n", errno, strerror(errno)); } void itti_update_lte_time(uint32_t frame, uint8_t slot) {} diff --git a/common/utils/ocp_itti/intertask_interface.h b/common/utils/ocp_itti/intertask_interface.h index 3874d52829..19b0ba32a9 100644 --- a/common/utils/ocp_itti/intertask_interface.h +++ b/common/utils/ocp_itti/intertask_interface.h @@ -550,10 +550,13 @@ MessageDef *itti_alloc_new_message_sized( MessagesIds message_id, MessageHeaderSize size); -/** \brief handle signals and wait for all threads to join when the process complete. - This function should be called from the main thread after having created all ITTI tasks. +/** \brief Wait for SIGINT/SIGTERM signals to unblock ITTI. + This function should be called from the main thread after having created all ITTI tasks. If handler is NULL, a default handler is installed. + \param handler a custom signal handler. To unblock, it should call itti_wait_tasks_unblock() **/ -void itti_wait_tasks_end(void); +void itti_wait_tasks_end(void (*handler)(int)); +/** \brif unblocks ITTI waiting in itti_wait_tasks_end(). **/ +void itti_wait_tasks_unblock(void); void itti_set_task_real_time(task_id_t task_id); /** \brief Send a termination message to all tasks. diff --git a/executables/lte-softmodem.c b/executables/lte-softmodem.c index 6d75f0acbd..f4f62f9a08 100644 --- a/executables/lte-softmodem.c +++ b/executables/lte-softmodem.c @@ -614,7 +614,7 @@ int main ( int argc, char **argv ) //getchar(); if(IS_SOFTMODEM_DOSCOPE) load_softscope("enb",NULL); - itti_wait_tasks_end(); + itti_wait_tasks_end(NULL); #if USING_GPROF // Save the gprof data now (rather than via atexit) in case we crash while shutting down diff --git a/executables/lte-uesoftmodem.c b/executables/lte-uesoftmodem.c index 4fe94f0b80..8e0161c1b6 100644 --- a/executables/lte-uesoftmodem.c +++ b/executables/lte-uesoftmodem.c @@ -699,7 +699,7 @@ int main( int argc, char **argv ) { printf("TYPE TO TERMINATE\n"); //getchar(); printf("Entering ITTI signals handler\n"); - itti_wait_tasks_end(); + itti_wait_tasks_end(NULL); printf("Returned from ITTI signal handler\n"); oai_exit=1; printf("oai_exit=%d\n",oai_exit); diff --git a/executables/nr-cuup.c b/executables/nr-cuup.c index 25ebe6df13..72430b5c3a 100644 --- a/executables/nr-cuup.c +++ b/executables/nr-cuup.c @@ -153,7 +153,7 @@ int main(int argc, char **argv) itti_send_msg_to_task(TASK_CUUP_E1, 0, msg); printf("TYPE TO TERMINATE\n"); - itti_wait_tasks_end(); + itti_wait_tasks_end(NULL); logClean(); printf("Bye.\n"); diff --git a/executables/nr-softmodem.c b/executables/nr-softmodem.c index 120a7c0e65..820173dc97 100644 --- a/executables/nr-softmodem.c +++ b/executables/nr-softmodem.c @@ -733,7 +733,7 @@ int main( int argc, char **argv ) { // wait for end of program printf("Entering ITTI signals handler\n"); printf("TYPE TO TERMINATE\n"); - itti_wait_tasks_end(); + itti_wait_tasks_end(NULL); printf("Returned from ITTI signal handler\n"); oai_exit=1; printf("oai_exit=%d\n",oai_exit); diff --git a/executables/nr-uesoftmodem.c b/executables/nr-uesoftmodem.c index bb439ff4f0..9611325edd 100644 --- a/executables/nr-uesoftmodem.c +++ b/executables/nr-uesoftmodem.c @@ -563,7 +563,7 @@ int main( int argc, char **argv ) { // wait for end of program printf("Entering ITTI signals handler\n"); printf("TYPE TO TERMINATE\n"); - itti_wait_tasks_end(); + itti_wait_tasks_end(NULL); printf("Returned from ITTI signal handler\n"); oai_exit=1; printf("oai_exit=%d\n",oai_exit); From 40c06fa8d5929222626ec8fee324959809c69ff1 Mon Sep 17 00:00:00 2001 From: Robert Schmidt Date: Mon, 13 Mar 2023 18:08:48 +0100 Subject: [PATCH 31/89] Custom handler in NRUE to first send deregistration before stopping --- executables/nr-uesoftmodem.c | 26 +++++++++++++++++++++----- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/executables/nr-uesoftmodem.c b/executables/nr-uesoftmodem.c index 9611325edd..110842d87c 100644 --- a/executables/nr-uesoftmodem.c +++ b/executables/nr-uesoftmodem.c @@ -22,6 +22,8 @@ #define _GNU_SOURCE /* See feature_test_macros(7) */ #include +#include +#include #include "T.h" #include "assertions.h" @@ -397,10 +399,25 @@ void *rrc_enb_process_msg(void *notUsed) { return NULL; } -static void trigger_deregistration(int signal) +static bool stop_immediately = false; +static void trigger_stop(int sig) { - MessageDef *msg = itti_alloc_new_message(TASK_RRC_UE_SIM, 0, NAS_DEREGISTRATION_REQ); - itti_send_msg_to_task(TASK_NAS_NRUE, 0, msg); + if (!oai_exit) + itti_wait_tasks_unblock(); +} +static void trigger_deregistration(int sig) +{ + if (!stop_immediately) { + MessageDef *msg = itti_alloc_new_message(TASK_RRC_UE_SIM, 0, NAS_DEREGISTRATION_REQ); + itti_send_msg_to_task(TASK_NAS_NRUE, 0, msg); + stop_immediately = true; + static const char m[] = "Press ^C again to trigger immediate shutdown\n"; + __attribute__((unused)) int unused = write(STDOUT_FILENO, m, sizeof(m) - 1); + signal(SIGALRM, trigger_stop); + alarm(5); + } else { + itti_wait_tasks_unblock(); + } } static void get_channel_model_mode() { @@ -559,11 +576,10 @@ int main( int argc, char **argv ) { sleep(2); config_check_unknown_cmdlineopt(CONFIG_CHECKALLSECTIONS); - signal(SIGUSR2, trigger_deregistration); // wait for end of program printf("Entering ITTI signals handler\n"); printf("TYPE TO TERMINATE\n"); - itti_wait_tasks_end(NULL); + itti_wait_tasks_end(trigger_deregistration); printf("Returned from ITTI signal handler\n"); oai_exit=1; printf("oai_exit=%d\n",oai_exit); From 1880ede5475aecc6d63693b8cffcbca6c2ddb95b Mon Sep 17 00:00:00 2001 From: Robert Schmidt Date: Mon, 13 Mar 2023 18:08:56 +0100 Subject: [PATCH 32/89] Check for GUTI --- openair3/NAS/NR_UE/nr_nas_msg_sim.c | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/openair3/NAS/NR_UE/nr_nas_msg_sim.c b/openair3/NAS/NR_UE/nr_nas_msg_sim.c index 13642fa339..6380713a19 100644 --- a/openair3/NAS/NR_UE/nr_nas_msg_sim.c +++ b/openair3/NAS/NR_UE/nr_nas_msg_sim.c @@ -1012,10 +1012,14 @@ void *nas_nrue_task(void *args_p) case NAS_DEREGISTRATION_REQ: { LOG_I(NAS, "[UE %ld] Received %s\n", instance, ITTI_MSG_NAME(msg_p)); - nas_deregistration_req_t *req = &NAS_DEREGISTRATION_REQ(msg_p); - as_nas_info_t initialNasMsg = {0}; - generateDeregistrationRequest(nas, &initialNasMsg, req); - send_nas_uplink_data_req(instance, &initialNasMsg); + if (nas->guti) { + nas_deregistration_req_t *req = &NAS_DEREGISTRATION_REQ(msg_p); + as_nas_info_t initialNasMsg = {0}; + generateDeregistrationRequest(nas, &initialNasMsg, req); + send_nas_uplink_data_req(instance, &initialNasMsg); + } else { + LOG_E(NAS, "no GUTI, cannot trigger deregistration request\n"); + } } break; From 889a50072fa19e3623c79f40ce136f9456b6b7b8 Mon Sep 17 00:00:00 2001 From: Roberto Louro Magueta Date: Thu, 20 Oct 2022 11:24:39 +0100 Subject: [PATCH 33/89] Implementation of resynchronization procedures for a gNB with a given Nid_cell --- executables/nr-ue.c | 28 +++++++--- .../nfapi/public_inc/fapi_nr_ue_interface.h | 4 ++ openair1/PHY/NR_REFSIG/pss_nr.h | 7 +-- .../PHY/NR_UE_TRANSPORT/nr_initial_sync.c | 8 ++- .../NR_UE_TRANSPORT/nr_transport_proto_ue.h | 4 +- openair1/PHY/NR_UE_TRANSPORT/nr_ulsch_ue.c | 12 +++++ openair1/PHY/NR_UE_TRANSPORT/pss_nr.c | 41 +++++++-------- openair1/PHY/NR_UE_TRANSPORT/sss_nr.c | 11 ++-- openair1/PHY/defs_nr_UE.h | 3 ++ openair1/SCHED_NR_UE/fapi_nr_ue_l1.c | 8 +++ openair1/SCHED_NR_UE/fapi_nr_ue_l1.h | 4 ++ openair1/SIMULATION/NR_PHY/dlsim.c | 4 ++ openair1/SIMULATION/NR_PHY/prachsim.c | 4 ++ openair1/SIMULATION/NR_PHY/ulsim.c | 4 ++ openair2/LAYER2/NR_MAC_UE/config_ue.c | 50 ++++++++++++------ openair2/LAYER2/NR_MAC_UE/mac_defs.h | 1 + openair2/LAYER2/NR_MAC_UE/nr_ra_procedures.c | 7 +-- .../NR_MAC_UE/nr_ue_dci_configuration.c | 2 +- openair2/LAYER2/NR_MAC_UE/nr_ue_procedures.c | 3 +- openair2/LAYER2/NR_MAC_UE/nr_ue_scheduler.c | 4 +- openair2/LAYER2/NR_MAC_gNB/config.c | 51 +++++++++++++++++++ openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_RA.c | 12 ++++- openair2/LAYER2/NR_MAC_gNB/mac_proto.h | 1 + openair2/NR_UE_PHY_INTERFACE/NR_IF_Module.c | 1 + openair2/NR_UE_PHY_INTERFACE/NR_IF_Module.h | 17 +++++++ 25 files changed, 229 insertions(+), 62 deletions(-) diff --git a/executables/nr-ue.c b/executables/nr-ue.c index 075789e12b..38d0026c17 100644 --- a/executables/nr-ue.c +++ b/executables/nr-ue.c @@ -157,6 +157,7 @@ void init_nr_ue_vars(PHY_VARS_NR_UE *ue, ue->mac_enabled = 1; ue->if_inst = nr_ue_if_module_init(0); ue->dci_thres = 0; + ue->target_Nid_cell = -1; // initialize all signal buffers init_nr_ue_signal(ue, nb_connected_gNB); @@ -413,7 +414,11 @@ static void UE_synch(void *arg) { */ } - LOG_W(PHY, "Starting sync detection\n"); + if (UE->target_Nid_cell != -1) { + LOG_W(NR_PHY, "Starting re-sync detection for target Nid_cell %i\n", UE->target_Nid_cell); + } else { + LOG_W(NR_PHY, "Starting sync detection\n"); + } switch (sync_mode) { /* @@ -597,6 +602,17 @@ void UE_processing(nr_rxtx_thread_data_t *rxtxD) { nr_phy_data_t phy_data = {0}; if (IS_SOFTMODEM_NOS1 || get_softmodem_params()->sa) { + + // Start synchronization with a target gNB + if (UE->synch_request.received_synch_request == 1 && UE->target_Nid_cell == -1) { + UE->is_synchronized = 0; + UE->target_Nid_cell = UE->synch_request.synch_req.target_Nid_cell; + clean_UE_ulsch(UE, proc->gNB_id); + } else if (UE->synch_request.received_synch_request == 1 && UE->target_Nid_cell != -1) { + UE->synch_request.received_synch_request = 0; + UE->target_Nid_cell = -1; + } + /* send tick to RLC and PDCP every ms */ if (proc->nr_slot_rx % UE->frame_parms.slots_per_subframe == 0) { void nr_rlc_tick(int frame, int subframe); @@ -815,13 +831,13 @@ void *UE_thread(void *arg) { if (!UE->is_synchronized) { readFrame(UE, ×tamp, false); - notifiedFIFO_elt_t *Msg=newNotifiedFIFO_elt(sizeof(syncData_t),0,&nf,UE_synch); - syncData_t *syncMsg=(syncData_t *)NotifiedFifoData(Msg); - syncMsg->UE=UE; + notifiedFIFO_elt_t *Msg = newNotifiedFIFO_elt(sizeof(syncData_t), 0, &nf, UE_synch); + syncData_t *syncMsg = (syncData_t *)NotifiedFifoData(Msg); + syncMsg->UE = UE; memset(&syncMsg->proc, 0, sizeof(syncMsg->proc)); pushTpool(&(get_nrUE_params()->Tpool), Msg); - trashed_frames=0; - syncRunning=true; + trashed_frames = 0; + syncRunning = true; continue; } diff --git a/nfapi/open-nFAPI/nfapi/public_inc/fapi_nr_ue_interface.h b/nfapi/open-nFAPI/nfapi/public_inc/fapi_nr_ue_interface.h index 94bf1b02dc..d429bf43ba 100644 --- a/nfapi/open-nFAPI/nfapi/public_inc/fapi_nr_ue_interface.h +++ b/nfapi/open-nFAPI/nfapi/public_inc/fapi_nr_ue_interface.h @@ -654,6 +654,10 @@ typedef struct } fapi_nr_prach_config_t; +typedef struct { + uint16_t target_Nid_cell; +} fapi_nr_synch_request_t; + typedef struct { uint32_t config_mask; diff --git a/openair1/PHY/NR_REFSIG/pss_nr.h b/openair1/PHY/NR_REFSIG/pss_nr.h index 3b0e87109e..c5bc4f880e 100644 --- a/openair1/PHY/NR_REFSIG/pss_nr.h +++ b/openair1/PHY/NR_REFSIG/pss_nr.h @@ -126,12 +126,7 @@ void init_context_pss_nr(NR_DL_FRAME_PARMS *frame_parms_ue); void free_context_pss_nr(void); int set_pss_nr(int ofdm_symbol_size); int pss_synchro_nr(PHY_VARS_NR_UE *PHY_vars_UE, int is, int rate_change); -int pss_search_time_nr(c16_t **rxdata, ///rx data in time domain - NR_DL_FRAME_PARMS *frame_parms, - int fo_flag, - int is, - int *eNB_id, - int *f_off); +int pss_search_time_nr(c16_t **rxdata, PHY_VARS_NR_UE *ue, int fo_flag, int is); #endif #undef EXTERN diff --git a/openair1/PHY/NR_UE_TRANSPORT/nr_initial_sync.c b/openair1/PHY/NR_UE_TRANSPORT/nr_initial_sync.c index 1904bed457..061eede6dd 100644 --- a/openair1/PHY/NR_UE_TRANSPORT/nr_initial_sync.c +++ b/openair1/PHY/NR_UE_TRANSPORT/nr_initial_sync.c @@ -300,11 +300,11 @@ int nr_initial_sync(UE_nr_rxtx_proc_t *proc, /* time samples in buffer rxdata are used as input of FFT -> FFT results are stored in the frequency buffer rxdataF */ /* rxdataF stores SS/PBCH from beginning of buffers in the same symbol order as in time domain */ - for(int i=0; i<4;i++) + for (int i = 0; i < NR_N_SYMBOLS_SSB; i++) nr_slot_fep_init_sync(ue, proc, i, - is*fp->samples_per_frame+ue->ssb_offset, + is * fp->samples_per_frame + ue->ssb_offset, false, rxdataF); @@ -522,6 +522,10 @@ int nr_initial_sync(UE_nr_rxtx_proc_t *proc, } + if (ue->target_Nid_cell != -1) { + return ret; + } + // if stand alone and sync on ssb do sib1 detection as part of initial sync if (sa==1 && ret==0) { nr_ue_dlsch_init(phy_data.dlsch, 1, ue->max_ldpc_iterations); 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 7432c82aac..0d6c334c45 100644 --- a/openair1/PHY/NR_UE_TRANSPORT/nr_transport_proto_ue.h +++ b/openair1/PHY/NR_UE_TRANSPORT/nr_transport_proto_ue.h @@ -251,6 +251,8 @@ uint8_t nr_ue_pusch_common_procedures(PHY_VARS_NR_UE *UE, NR_DL_FRAME_PARMS *frame_parms, uint8_t Nl); +int8_t clean_UE_ulsch(PHY_VARS_NR_UE *UE, uint8_t gNB_id); + void nr_dlsch_unscrambling(int16_t* llr, uint32_t size, uint8_t q, @@ -313,7 +315,7 @@ int dump_ue_stats(PHY_VARS_NR_UE *phy_vars_ue, UE_nr_rxtx_proc_t *proc, char* bu @param mode current running mode */ int nr_initial_sync(UE_nr_rxtx_proc_t *proc, - PHY_VARS_NR_UE *phy_vars_ue, + PHY_VARS_NR_UE *phy_vars_ue, int n_frames, int sa); diff --git a/openair1/PHY/NR_UE_TRANSPORT/nr_ulsch_ue.c b/openair1/PHY/NR_UE_TRANSPORT/nr_ulsch_ue.c index 9d0982689b..fc22a954fe 100644 --- a/openair1/PHY/NR_UE_TRANSPORT/nr_ulsch_ue.c +++ b/openair1/PHY/NR_UE_TRANSPORT/nr_ulsch_ue.c @@ -662,3 +662,15 @@ uint8_t nr_ue_pusch_common_procedures(PHY_VARS_NR_UE *UE, //////////////////////////////////////////////////// return 0; } + +int8_t clean_UE_ulsch(PHY_VARS_NR_UE *UE, uint8_t gNB_id) +{ + 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->status = SCH_IDLE; + ul_harq_process->round = 0; + ul_harq_process->first_tx = 1; + } + return 0; +} diff --git a/openair1/PHY/NR_UE_TRANSPORT/pss_nr.c b/openair1/PHY/NR_UE_TRANSPORT/pss_nr.c index fbb4a8a625..50f6fa2f34 100644 --- a/openair1/PHY/NR_UE_TRANSPORT/pss_nr.c +++ b/openair1/PHY/NR_UE_TRANSPORT/pss_nr.c @@ -555,12 +555,8 @@ int pss_synchro_nr(PHY_VARS_NR_UE *PHY_vars_UE, int is, int rate_change) #endif VCD_SIGNAL_DUMPER_DUMP_FUNCTION_BY_NAME(VCD_SIGNAL_DUMPER_FUNCTIONS_PSS_SEARCH_TIME_NR, VCD_FUNCTION_IN); - synchro_position = pss_search_time_nr(rxdata, - frame_parms, - fo_flag, - is, - (int *)&PHY_vars_UE->common_vars.eNb_id, - (int *)&PHY_vars_UE->common_vars.freq_offset); + + synchro_position = pss_search_time_nr(rxdata, PHY_vars_UE, fo_flag, is); VCD_SIGNAL_DUMPER_DUMP_FUNCTION_BY_NAME(VCD_SIGNAL_DUMPER_FUNCTIONS_PSS_SEARCH_TIME_NR, VCD_FUNCTION_OUT); @@ -605,7 +601,7 @@ int pss_synchro_nr(PHY_VARS_NR_UE *PHY_vars_UE, int is, int rate_change) * * NAME : pss_search_time_nr * -* PARAMETERS : received buffer +* PARAMETERS : received buffer in time domain * frame parameters * * RETURN : position of detected pss @@ -651,17 +647,15 @@ int pss_synchro_nr(PHY_VARS_NR_UE *PHY_vars_UE, int is, int rate_change) * *********************************************************************/ -int pss_search_time_nr(c16_t **rxdata, ///rx data in time domain - NR_DL_FRAME_PARMS *frame_parms, - int fo_flag, - int is, - int *eNB_id, - int *f_off) +int pss_search_time_nr(c16_t **rxdata, PHY_VARS_NR_UE *ue, int fo_flag, int is) { + NR_DL_FRAME_PARMS *frame_parms = &ue->frame_parms; + int *eNB_id = (int *)&ue->common_vars.eNb_id; + int *f_off = (int *)&ue->common_vars.freq_offset; unsigned int n, ar, peak_position, pss_source; int64_t peak_value; - int64_t avg[NUMBER_PSS_SEQUENCE]={0}; - double ffo_est=0; + int64_t avg[NUMBER_PSS_SEQUENCE] = {0}; + double ffo_est = 0; // performing the correlation on a frame length plus one symbol for the first of the two frame // to take into account the possibility of PSS in between the two frames @@ -692,7 +686,14 @@ int pss_search_time_nr(c16_t **rxdata, ///rx data in time domain /* This is required by SIMD (single instruction Multiple Data) Extensions of Intel processors. */ /* Correlation computation is based on a a dot product which is realized thank to SIMS extensions */ - for (int pss_index = 0; pss_index < NUMBER_PSS_SEQUENCE; pss_index++) { + uint16_t pss_index_start = 0; + uint16_t pss_index_end = NUMBER_PSS_SEQUENCE; + if (ue->target_Nid_cell != -1) { + pss_index_start = GET_NID2(ue->target_Nid_cell); + pss_index_end = pss_index_start + 1; + } + + for (int pss_index = pss_index_start; pss_index < pss_index_end; pss_index++) { for (n = 0; n < length; n += 8) { // int64_t pss_corr_ue=0; @@ -748,10 +749,10 @@ int pss_search_time_nr(c16_t **rxdata, ///rx data in time domain } // computing absolute value of frequency offset - *f_off = ffo_est*frame_parms->subcarrier_spacing; + *f_off = ffo_est*frame_parms->subcarrier_spacing; - for (int pss_index = 0; pss_index < NUMBER_PSS_SEQUENCE; pss_index++) - avg[pss_index]/=(length/4); + for (int pss_index = pss_index_start; pss_index < pss_index_end; pss_index++) + avg[pss_index] /= (length / 4); *eNB_id = pss_source; @@ -776,6 +777,6 @@ int pss_search_time_nr(c16_t **rxdata, ///rx data in time domain #endif - return(peak_position); + return peak_position; } diff --git a/openair1/PHY/NR_UE_TRANSPORT/sss_nr.c b/openair1/PHY/NR_UE_TRANSPORT/sss_nr.c index 3056315999..cdb45fc9c1 100644 --- a/openair1/PHY/NR_UE_TRANSPORT/sss_nr.c +++ b/openair1/PHY/NR_UE_TRANSPORT/sss_nr.c @@ -511,9 +511,15 @@ int rx_sss_nr(PHY_VARS_NR_UE *ue, UE_nr_rxtx_proc_t *proc, int32_t *tot_metric, /* cosinus cos(x + y) = cos(x)cos(y) - sin(x)sin(y) */ /* sinus sin(x + y) = sin(x)cos(y) + cos(x)sin(y) */ - for (Nid1 = 0 ; Nid1 < N_ID_1_NUMBER; Nid1++) { // all possible Nid1 values - for (phase=0; phase < PHASE_HYPOTHESIS_NUMBER; phase++) { // phase offset between PSS and SSS + uint16_t Nid1_start = 0; + uint16_t Nid1_end = N_ID_1_NUMBER; + if (ue->target_Nid_cell != -1) { + Nid1_start = GET_NID1(ue->target_Nid_cell); + Nid1_end = Nid1_start + 1; + } + for (Nid1 = Nid1_start; Nid1 < Nid1_end; Nid1++) { // all possible Nid1 values + for (phase = 0; phase < PHASE_HYPOTHESIS_NUMBER; phase++) { // phase offset between PSS and SSS metric = 0; metric_re = 0; @@ -523,7 +529,6 @@ int rx_sss_nr(PHY_VARS_NR_UE *ue, UE_nr_rxtx_proc_t *proc, int32_t *tot_metric, // This is the inner product using one particular value of each unknown parameter for (i=0; i < LENGTH_SSS_NR; i++) { - metric_re += d[i]*(((phase_re_nr[phase]*sss[2*i])>>SCALING_METRIC_SSS_NR) - ((phase_im_nr[phase]*sss[2*i+1])>>SCALING_METRIC_SSS_NR)); #if 0 diff --git a/openair1/PHY/defs_nr_UE.h b/openair1/PHY/defs_nr_UE.h index 79e75ee325..9ba7233770 100644 --- a/openair1/PHY/defs_nr_UE.h +++ b/openair1/PHY/defs_nr_UE.h @@ -400,6 +400,8 @@ typedef struct { int if_freq_off; /// \brief Indicator that UE is synchronized to a gNB int is_synchronized; + /// \brief Target gNB Nid_cell when UE is resynchronizing + int target_Nid_cell; /// \brief Indicator that UE lost frame synchronization int lost_sync; /// Data structure for UE process scheduling @@ -440,6 +442,7 @@ typedef struct { nr_ue_if_module_t *if_inst; fapi_nr_config_request_t nrUE_config; + nr_synch_request_t synch_request; NR_UE_PBCH *pbch_vars[NUMBER_OF_CONNECTED_gNB_MAX]; NR_UE_PRACH *prach_vars[NUMBER_OF_CONNECTED_gNB_MAX]; diff --git a/openair1/SCHED_NR_UE/fapi_nr_ue_l1.c b/openair1/SCHED_NR_UE/fapi_nr_ue_l1.c index b64be34f6e..255ecf17ca 100644 --- a/openair1/SCHED_NR_UE/fapi_nr_ue_l1.c +++ b/openair1/SCHED_NR_UE/fapi_nr_ue_l1.c @@ -548,3 +548,11 @@ int8_t nr_ue_phy_config_request(nr_phy_config_t *phy_config) memcpy(nrUE_config,&phy_config->config_req,sizeof(fapi_nr_config_request_t)); return 0; } + +void nr_ue_synch_request(nr_synch_request_t *synch_request) +{ + fapi_nr_synch_request_t *synch_req = &PHY_vars_UE_g[synch_request->Mod_id][synch_request->CC_id]->synch_request.synch_req; + memcpy(synch_req, &synch_request->synch_req, sizeof(fapi_nr_synch_request_t)); + PHY_vars_UE_g[synch_request->Mod_id][synch_request->CC_id]->synch_request.received_synch_request = 1; +} + diff --git a/openair1/SCHED_NR_UE/fapi_nr_ue_l1.h b/openair1/SCHED_NR_UE/fapi_nr_ue_l1.h index a0b0b805af..d83c014fa6 100644 --- a/openair1/SCHED_NR_UE/fapi_nr_ue_l1.h +++ b/openair1/SCHED_NR_UE/fapi_nr_ue_l1.h @@ -47,6 +47,10 @@ int8_t nr_ue_scheduled_response_stub(nr_scheduled_response_t *scheduled_response \param scheduled_response including transmission config(dl_config, ul_config) and data transmission (tx_req)*/ int8_t nr_ue_phy_config_request(nr_phy_config_t *phy_config); +/**\brief NR UE FAPI message to schedule a synchronization with target gNB + \param synch_request including target_Nid_cell*/ +void nr_ue_synch_request(nr_synch_request_t *synch_request); + void update_harq_status(module_id_t module_id, uint8_t harq_pid, uint8_t ack_nack); #endif diff --git a/openair1/SIMULATION/NR_PHY/dlsim.c b/openair1/SIMULATION/NR_PHY/dlsim.c index 80ff799ab0..4bc329e2e3 100644 --- a/openair1/SIMULATION/NR_PHY/dlsim.c +++ b/openair1/SIMULATION/NR_PHY/dlsim.c @@ -120,6 +120,10 @@ int8_t nr_mac_rrc_data_req_ue(const module_id_t Mod_idP, return 0; } +int8_t nr_rrc_RA_succeeded(const module_id_t mod_id, const uint8_t gNB_index) { + return 0; +} + int nr_derive_key(int alg_type, uint8_t alg_id, const uint8_t key[32], uint8_t **out) { diff --git a/openair1/SIMULATION/NR_PHY/prachsim.c b/openair1/SIMULATION/NR_PHY/prachsim.c index d72bf2ed4f..629b7316b0 100644 --- a/openair1/SIMULATION/NR_PHY/prachsim.c +++ b/openair1/SIMULATION/NR_PHY/prachsim.c @@ -106,6 +106,10 @@ int8_t nr_mac_rrc_data_req_ue(const module_id_t Mod_idP, return 0; } +int8_t nr_rrc_RA_succeeded(const module_id_t mod_id, const uint8_t gNB_index) { + return 0; +} + int nr_derive_key(int alg_type, uint8_t alg_id, const uint8_t key[32], uint8_t **out) { diff --git a/openair1/SIMULATION/NR_PHY/ulsim.c b/openair1/SIMULATION/NR_PHY/ulsim.c index 4ec8fc3cca..c9d32b6b8f 100644 --- a/openair1/SIMULATION/NR_PHY/ulsim.c +++ b/openair1/SIMULATION/NR_PHY/ulsim.c @@ -116,6 +116,10 @@ int8_t nr_mac_rrc_data_req_ue(const module_id_t Mod_idP, return 0; } +int8_t nr_rrc_RA_succeeded(const module_id_t mod_id, const uint8_t gNB_index) { + return 0; +} + int DU_send_INITIAL_UL_RRC_MESSAGE_TRANSFER(module_id_t module_idP, int CC_idP, int UE_id, diff --git a/openair2/LAYER2/NR_MAC_UE/config_ue.c b/openair2/LAYER2/NR_MAC_UE/config_ue.c index 8ab321b9e1..bd08153aff 100644 --- a/openair2/LAYER2/NR_MAC_UE/config_ue.c +++ b/openair2/LAYER2/NR_MAC_UE/config_ue.c @@ -752,8 +752,8 @@ int nr_rrc_mac_config_req_ue(module_id_t module_id, config_control_ue(mac); // Setup the SSB to Rach Occasions mapping according to the config build_ssb_to_ro_map(mac); - } - else if (cell_group_config != NULL ){ + + } else if (cell_group_config != NULL) { LOG_I(MAC,"Applying CellGroupConfig from gNodeB\n"); mac->cg = cell_group_config; if (cell_group_config->spCellConfig) @@ -772,20 +772,38 @@ int nr_rrc_mac_config_req_ue(module_id_t module_id, configure_current_BWP(mac, NULL, cell_group_config); config_control_ue(mac); - if (get_softmodem_params()->nsa) { - if (cell_group_config->spCellConfig && cell_group_config->spCellConfig->reconfigurationWithSync) { - if (cell_group_config->spCellConfig->reconfigurationWithSync->rach_ConfigDedicated) { - ra->rach_ConfigDedicated = cell_group_config->spCellConfig->reconfigurationWithSync->rach_ConfigDedicated->choice.uplink; - } - mac->scc = cell_group_config->spCellConfig->reconfigurationWithSync->spCellConfigCommon; - int num_slots = mac->scc->tdd_UL_DL_ConfigurationCommon->pattern1.nrofUplinkSlots; - if (mac->scc->tdd_UL_DL_ConfigurationCommon->pattern1.nrofUplinkSymbols > 0) { - num_slots++; - } - mac->ul_config_request = calloc(num_slots, sizeof(*mac->ul_config_request)); - config_common_ue(mac,module_id,cc_idP); - mac->crnti = cell_group_config->spCellConfig->reconfigurationWithSync->newUE_Identity; - LOG_I(MAC,"Configuring CRNTI %x\n",mac->crnti); + if (cell_group_config->spCellConfig && cell_group_config->spCellConfig->reconfigurationWithSync) { + LOG_A(NR_MAC, "Received the reconfigurationWithSync in %s\n", __FUNCTION__); + if (cell_group_config->spCellConfig->reconfigurationWithSync->rach_ConfigDedicated) { + ra->rach_ConfigDedicated = cell_group_config->spCellConfig->reconfigurationWithSync->rach_ConfigDedicated->choice.uplink; + } + mac->scc = cell_group_config->spCellConfig->reconfigurationWithSync->spCellConfigCommon; + if (mac->scc_SIB) { + free(mac->scc_SIB); + mac->scc_SIB = NULL; + } + int num_slots = mac->scc->tdd_UL_DL_ConfigurationCommon->pattern1.nrofUplinkSlots; + if (mac->scc->tdd_UL_DL_ConfigurationCommon->pattern1.nrofUplinkSymbols > 0) { + num_slots++; + } + mac->state = UE_NOT_SYNC; + mac->ra.ra_state = RA_UE_IDLE; + mac->physCellId = *mac->scc->physCellId; + if (!get_softmodem_params()->emulate_l1) { + mac->synch_request.Mod_id = module_id; + mac->synch_request.CC_id = cc_idP; + mac->synch_request.synch_req.target_Nid_cell = mac->physCellId; + mac->if_module->synch_request(&mac->synch_request); + } + mac->ul_config_request = calloc(num_slots, sizeof(*mac->ul_config_request)); + config_common_ue(mac, module_id, cc_idP); + mac->crnti = cell_group_config->spCellConfig->reconfigurationWithSync->newUE_Identity; + LOG_I(MAC, "Configuring CRNTI %x\n", mac->crnti); + + nr_ue_init_mac(module_id); + if (!get_softmodem_params()->emulate_l1) { + mac->if_module->phy_config_request(&mac->phy_config); + mac->phy_config_request_sent = true; } // Setup the SSB to Rach Occasions mapping according to the config diff --git a/openair2/LAYER2/NR_MAC_UE/mac_defs.h b/openair2/LAYER2/NR_MAC_UE/mac_defs.h index e3f4247bd6..b1af5393a1 100644 --- a/openair2/LAYER2/NR_MAC_UE/mac_defs.h +++ b/openair2/LAYER2/NR_MAC_UE/mac_defs.h @@ -446,6 +446,7 @@ typedef struct { /// Interface module instances nr_ue_if_module_t *if_module; nr_phy_config_t phy_config; + nr_synch_request_t synch_request; /// BSR report flag management uint8_t BSR_reporting_active; diff --git a/openair2/LAYER2/NR_MAC_UE/nr_ra_procedures.c b/openair2/LAYER2/NR_MAC_UE/nr_ra_procedures.c index c5800cfe14..ec9179e636 100644 --- a/openair2/LAYER2/NR_MAC_UE/nr_ra_procedures.c +++ b/openair2/LAYER2/NR_MAC_UE/nr_ra_procedures.c @@ -886,7 +886,7 @@ uint8_t nr_ue_get_rach(module_id_t mod_id, if(ra->cfra) { // Reset RA_active flag: it disables Msg3 retransmission (8.3 of TS 38.213) - nr_ra_succeeded(mod_id, frame, nr_slot_tx); + nr_ra_succeeded(mod_id, gNB_id, frame, nr_slot_tx); } } else if (ra->RA_window_cnt == 0 && !ra->RA_RAPID_found) { @@ -1001,13 +1001,14 @@ void nr_ue_contention_resolution(module_id_t module_id, int cc_id, frame_t frame // according to section 5 of 3GPP TS 38.321 version 16.2.1 Release 16 // todo: // - complete handling of received contention-based RA preamble -void nr_ra_succeeded(module_id_t mod_id, frame_t frame, int slot){ - +void nr_ra_succeeded(const module_id_t mod_id, const uint8_t gNB_index, const frame_t frame, const int slot) +{ NR_UE_MAC_INST_t *mac = get_mac_inst(mod_id); RA_config_t *ra = &mac->ra; if (ra->cfra) { LOG_I(MAC, "[UE %d][%d.%d][RAPROC] RA procedure succeeded. CF-RA: RAR successfully received.\n", mod_id, frame, slot); + nr_rrc_RA_succeeded(mod_id, gNB_index); mac->state = UE_CONNECTED; ra->RA_window_cnt = -1; } else { 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 e5b246f5c1..45b6a56978 100644 --- a/openair2/LAYER2/NR_MAC_UE/nr_ue_dci_configuration.c +++ b/openair2/LAYER2/NR_MAC_UE/nr_ue_dci_configuration.c @@ -269,7 +269,7 @@ void ue_dci_configuration(NR_UE_MAC_INST_t *mac, fapi_nr_dl_config_request_t *dl uint8_t bwp_id = mac->current_DL_BWP.bwp_id; //NR_ServingCellConfig_t *scd = mac->scg->spCellConfig->spCellConfigDedicated; NR_BWP_DownlinkDedicated_t *bwpd = (bwp_id>0) ? mac->DLbwp[bwp_id-1]->bwp_Dedicated : (mac->cg ? mac->cg->spCellConfig->spCellConfigDedicated->initialDownlinkBWP : NULL); - NR_BWP_DownlinkCommon_t *bwp_Common = (bwp_id>0) ? mac->DLbwp[bwp_id-1]->bwp_Common : &mac->scc_SIB->downlinkConfigCommon.initialDownlinkBWP; + NR_BWP_DownlinkCommon_t *bwp_Common = get_bwp_downlink_common(mac, bwp_id); LOG_D(NR_MAC, "[DCI_CONFIG] ra_rnti %p (%x) crnti %p (%x) t_crnti %p (%x)\n", &ra->ra_rnti, ra->ra_rnti, &mac->crnti, mac->crnti, &ra->t_crnti, ra->t_crnti); diff --git a/openair2/LAYER2/NR_MAC_UE/nr_ue_procedures.c b/openair2/LAYER2/NR_MAC_UE/nr_ue_procedures.c index 4190331a3e..80d0db469d 100644 --- a/openair2/LAYER2/NR_MAC_UE/nr_ue_procedures.c +++ b/openair2/LAYER2/NR_MAC_UE/nr_ue_procedures.c @@ -686,7 +686,7 @@ int8_t nr_ue_process_dci(module_id_t module_id, int cc_id, uint8_t gNB_index, fr /* dmrs symbol positions*/ dlsch_config_pdu_1_0->dlDmrsSymbPos = fill_dmrs_mask(pdsch_config, NR_DL_DCI_FORMAT_1_0, - (get_softmodem_params()->nsa) ? mac->scc->dmrs_TypeA_Position : mac->mib->dmrs_TypeA_Position, + mac->scc ? mac->scc->dmrs_TypeA_Position : mac->mib->dmrs_TypeA_Position, dlsch_config_pdu_1_0->number_symbols, dlsch_config_pdu_1_0->start_symbol, tda_info.mapping_type, @@ -2421,6 +2421,7 @@ static uint8_t nr_extract_dci_info(NR_UE_MAC_INST_t *mac, switch(dci_format) { case NR_DL_DCI_FORMAT_1_0: + switch(rnti_type) { case NR_RNTI_RA: // Freq domain assignment diff --git a/openair2/LAYER2/NR_MAC_UE/nr_ue_scheduler.c b/openair2/LAYER2/NR_MAC_UE/nr_ue_scheduler.c index deeb4a181a..b0d181f3da 100644 --- a/openair2/LAYER2/NR_MAC_UE/nr_ue_scheduler.c +++ b/openair2/LAYER2/NR_MAC_UE/nr_ue_scheduler.c @@ -495,7 +495,7 @@ int nr_config_pusch_pdu(NR_UE_MAC_INST_t *mac, #ifdef DEBUG_MSG3 LOG_D(NR_MAC, "In %s BWP assignment (BWP (start %d, size %d) \n", __FUNCTION__, pusch_config_pdu->bwp_start, pusch_config_pdu->bwp_size); - #endif +#endif // MCS pusch_config_pdu->mcs_index = rar_grant->mcs; @@ -1399,6 +1399,8 @@ int nr_ue_pusch_scheduler(NR_UE_MAC_INST_t *mac, uint8_t is_Msg3, frame_t curren case 3: delta = 6; break; + default: + AssertFatal(1 == 0, "Invalid numerology %i\n", mu); } AssertFatal((k2 + delta) >= DURATION_RX_TO_TX, diff --git a/openair2/LAYER2/NR_MAC_gNB/config.c b/openair2/LAYER2/NR_MAC_gNB/config.c index 38ae19c72a..2f2a3fd577 100644 --- a/openair2/LAYER2/NR_MAC_gNB/config.c +++ b/openair2/LAYER2/NR_MAC_gNB/config.c @@ -650,7 +650,58 @@ bool nr_mac_update_cellgroup(gNB_MAC_INST *nrmac, uint32_t rnti, NR_CellGroupCon exit(1); } + nr_mac_update_RA(nrmac, rnti, CellGroup); process_CellGroup(CellGroup, &UE->UE_sched_ctrl); return true; } + +bool nr_mac_update_RA(gNB_MAC_INST *nrmac, uint32_t rnti, NR_CellGroupConfig_t *CellGroup) +{ + // Checking for free RA process + NR_COMMON_channels_t *cc = &nrmac->common_channels[0]; + uint8_t ra_index = 0; + for (; ra_index < NR_NB_RA_PROC_MAX; ra_index++) { + if ((cc->ra[ra_index].state == RA_IDLE) && (!cc->ra[ra_index].cfra)) + break; + } + if (ra_index == NR_NB_RA_PROC_MAX) { + LOG_E(NR_MAC, "%s() %s:%d RA processes are not available for CFRA RNTI :%x\n", __FUNCTION__, __FILE__, __LINE__, rnti); + return -1; + } + + NR_RA_t *ra = &cc->ra[ra_index]; + if (CellGroup->spCellConfig && CellGroup->spCellConfig->reconfigurationWithSync + && CellGroup->spCellConfig->reconfigurationWithSync->rach_ConfigDedicated != NULL) { + if (CellGroup->spCellConfig->reconfigurationWithSync->rach_ConfigDedicated->choice.uplink->cfra != NULL) { + ra->cfra = true; + ra->rnti = rnti; + ra->CellGroup = CellGroup; + struct NR_CFRA *cfra = CellGroup->spCellConfig->reconfigurationWithSync->rach_ConfigDedicated->choice.uplink->cfra; + uint8_t num_preamble = cfra->resources.choice.ssb->ssb_ResourceList.list.count; + ra->preambles.num_preambles = num_preamble; + ra->preambles.preamble_list = (uint8_t *)malloc(num_preamble * sizeof(uint8_t)); + for (int i = 0; i < cc->num_active_ssb; i++) { + for (int j = 0; j < num_preamble; j++) { + if (cc->ssb_index[i] == cfra->resources.choice.ssb->ssb_ResourceList.list.array[j]->ssb) { + // One dedicated preamble for each beam + ra->preambles.preamble_list[i] = cfra->resources.choice.ssb->ssb_ResourceList.list.array[j]->ra_PreambleIndex; + break; + } + } + } + } + } else { + ra->cfra = false; + ra->rnti = 0; + if (ra->preambles.preamble_list == NULL) { + ra->preambles.num_preambles = MAX_NUM_NR_PRACH_PREAMBLES; + ra->preambles.preamble_list = (uint8_t *)malloc(MAX_NUM_NR_PRACH_PREAMBLES * sizeof(uint8_t)); + for (int i = 0; i < MAX_NUM_NR_PRACH_PREAMBLES; i++) + ra->preambles.preamble_list[i] = i; + } + } + LOG_I(NR_MAC, "Added new %s process for UE RNTI %04x with initial CellGroup\n", ra->cfra ? "CFRA" : "CBRA", rnti); + + return true; +} diff --git a/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_RA.c b/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_RA.c index 2729ccc00a..587575241f 100644 --- a/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_RA.c +++ b/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_RA.c @@ -1375,10 +1375,18 @@ void nr_generate_Msg2(module_id_t module_idP, int CC_id, frame_t frameP, sub_fra nr_get_Msg3alloc(module_idP, CC_id, scc, slotP, frameP, ra, nr_mac->tdd_beam_association); nr_add_msg3(module_idP, CC_id, frameP, slotP, ra, (uint8_t *) &tx_req->TLVs[0].value.direct[0]); - if(ra->cfra) { + if (ra->cfra) { + NR_UE_info_t *UE = find_nr_UE(&RC.nrmac[module_idP]->UE_info, ra->rnti); + if (UE) { + const NR_ServingCellConfig_t *servingCellConfig = UE->CellGroup ? UE->CellGroup->spCellConfig->spCellConfigDedicated : NULL; + uint32_t delay_ms = servingCellConfig && servingCellConfig->downlinkBWP_ToAddModList ? NR_RRC_SETUP_DELAY_MS + NR_RRC_BWP_SWITCHING_DELAY_MS : NR_RRC_SETUP_DELAY_MS; + NR_UE_sched_ctrl_t *sched_ctrl = &UE->UE_sched_ctrl; + sched_ctrl->rrc_processing_timer = (delay_ms << ra->DL_BWP.scs); + } LOG_D(NR_MAC, "Frame %d, Subframe %d: Setting RA-Msg3 reception (CFRA) for SFN.Slot %d.%d\n", frameP, slotP, ra->Msg3_frame, ra->Msg3_slot); + } else { + LOG_D(NR_MAC, "Frame %d, Subframe %d: Setting RA-Msg3 reception (CBRA) for SFN.Slot %d.%d\n", frameP, slotP, ra->Msg3_frame, ra->Msg3_slot); } - else LOG_D(NR_MAC, "Frame %d, Subframe %d: Setting RA-Msg3 reception (CBRA) for SFN.Slot %d.%d\n", frameP, slotP, ra->Msg3_frame, ra->Msg3_slot); T(T_GNB_MAC_DL_RAR_PDU_WITH_DATA, T_INT(module_idP), T_INT(CC_id), T_INT(ra->RA_rnti), T_INT(frameP), T_INT(slotP), T_INT(0), T_BUFFER(&tx_req->TLVs[0].value.direct[0], tx_req->TLVs[0].length)); diff --git a/openair2/LAYER2/NR_MAC_gNB/mac_proto.h b/openair2/LAYER2/NR_MAC_gNB/mac_proto.h index fe880785aa..3ad1597213 100644 --- a/openair2/LAYER2/NR_MAC_gNB/mac_proto.h +++ b/openair2/LAYER2/NR_MAC_gNB/mac_proto.h @@ -58,6 +58,7 @@ void nr_mac_config_sib1(gNB_MAC_INST *nrmac, NR_BCCH_DL_SCH_Message_t *sib1); bool nr_mac_add_test_ue(gNB_MAC_INST *nrmac, uint32_t rnti, NR_CellGroupConfig_t *CellGroup); bool nr_mac_prepare_ra_nsa_ue(gNB_MAC_INST *nrmac, uint32_t rnti, NR_CellGroupConfig_t *CellGroup); bool nr_mac_update_cellgroup(gNB_MAC_INST *nrmac, uint32_t rnti, NR_CellGroupConfig_t *CellGroup); +bool nr_mac_update_RA(gNB_MAC_INST *nrmac, uint32_t rnti, NR_CellGroupConfig_t *CellGroup); void clear_nr_nfapi_information(gNB_MAC_INST * gNB, int CC_idP, diff --git a/openair2/NR_UE_PHY_INTERFACE/NR_IF_Module.c b/openair2/NR_UE_PHY_INTERFACE/NR_IF_Module.c index 87d8527eb9..5ddd59fcc1 100644 --- a/openair2/NR_UE_PHY_INTERFACE/NR_IF_Module.c +++ b/openair2/NR_UE_PHY_INTERFACE/NR_IF_Module.c @@ -1239,6 +1239,7 @@ nr_ue_if_module_t *nr_ue_if_module_init(uint32_t module_id){ nr_ue_if_module_inst[module_id]->current_frame = 0; nr_ue_if_module_inst[module_id]->current_slot = 0; nr_ue_if_module_inst[module_id]->phy_config_request = nr_ue_phy_config_request; + nr_ue_if_module_inst[module_id]->synch_request = nr_ue_synch_request; if (get_softmodem_params()->emulate_l1) nr_ue_if_module_inst[module_id]->scheduled_response = nr_ue_scheduled_response_stub; else diff --git a/openair2/NR_UE_PHY_INTERFACE/NR_IF_Module.h b/openair2/NR_UE_PHY_INTERFACE/NR_IF_Module.h index e3adfe7d53..91775794ed 100644 --- a/openair2/NR_UE_PHY_INTERFACE/NR_IF_Module.h +++ b/openair2/NR_UE_PHY_INTERFACE/NR_IF_Module.h @@ -151,6 +151,16 @@ typedef struct { } nr_phy_config_t; +typedef struct { + /// module id + uint8_t Mod_id; + /// component carrier id + uint8_t CC_id; + /// Flag signaling that synch_request was received + uint8_t received_synch_request; + /// NR UE FAPI message + fapi_nr_synch_request_t synch_req; +} nr_synch_request_t; /* * Generic type of an application-defined callback to return various @@ -171,6 +181,12 @@ typedef int8_t (nr_ue_scheduled_response_f)(nr_scheduled_response_t *scheduled_r */ typedef int8_t (nr_ue_phy_config_request_f)(nr_phy_config_t *phy_config); +/* + * Generic type of an application-defined callback to return various + * types of data to the application. + */ +typedef void (nr_ue_synch_request_f)(nr_synch_request_t *synch_request); + /* * Generic type of an application-defined callback to return various @@ -196,6 +212,7 @@ typedef int (nr_ue_dcireq_f)(nr_dcireq_t *ul_info); typedef struct nr_ue_if_module_s { nr_ue_scheduled_response_f *scheduled_response; nr_ue_phy_config_request_f *phy_config_request; + nr_ue_synch_request_f *synch_request; nr_ue_dl_indication_f *dl_indication; nr_ue_ul_indication_f *ul_indication; //nr_ue_dcireq_f *dcireq; From 81d44c64228a7bf04b33f73b3628225ef90db016 Mon Sep 17 00:00:00 2001 From: Roberto Louro Magueta Date: Mon, 24 Oct 2022 20:20:43 +0100 Subject: [PATCH 34/89] Stop timer T304 when RA is succeeded --- openair2/LAYER2/NR_MAC_UE/mac_proto.h | 2 +- openair2/LAYER2/NR_MAC_UE/nr_ue_procedures.c | 2 +- openair2/RRC/NR_UE/L2_interface_ue.c | 10 ++++++++++ openair2/RRC/NR_UE/rrc_proto.h | 2 ++ 4 files changed, 14 insertions(+), 2 deletions(-) diff --git a/openair2/LAYER2/NR_MAC_UE/mac_proto.h b/openair2/LAYER2/NR_MAC_UE/mac_proto.h index 6015fe27bc..9776003e3f 100644 --- a/openair2/LAYER2/NR_MAC_UE/mac_proto.h +++ b/openair2/LAYER2/NR_MAC_UE/mac_proto.h @@ -371,7 +371,7 @@ void nr_ue_contention_resolution(module_id_t module_id, int cc_id, frame_t frame void nr_ra_failed(uint8_t mod_id, uint8_t CC_id, NR_PRACH_RESOURCES_t *prach_resources, frame_t frame, int slot); -void nr_ra_succeeded(module_id_t mod_id, frame_t frame, int slot); +void nr_ra_succeeded(const module_id_t mod_id, const uint8_t gNB_index, const frame_t frame, const int slot); void nr_get_RA_window(NR_UE_MAC_INST_t *mac); diff --git a/openair2/LAYER2/NR_MAC_UE/nr_ue_procedures.c b/openair2/LAYER2/NR_MAC_UE/nr_ue_procedures.c index 80d0db469d..af1faf19eb 100644 --- a/openair2/LAYER2/NR_MAC_UE/nr_ue_procedures.c +++ b/openair2/LAYER2/NR_MAC_UE/nr_ue_procedures.c @@ -3257,7 +3257,7 @@ void nr_ue_process_mac_pdu(nr_downlink_indication_t *dl_info, } if ( (ra->RA_active == 1) && ra_success) { - nr_ra_succeeded(module_idP, frameP, slot); + nr_ra_succeeded(module_idP, gNB_index, frameP, slot); } else if (!ra_success){ // TODO: Handle failure of RA procedure @ MAC layer // nr_ra_failed(module_idP, CC_id, prach_resources, frameP, slot); // prach_resources is a PHY structure diff --git a/openair2/RRC/NR_UE/L2_interface_ue.c b/openair2/RRC/NR_UE/L2_interface_ue.c index c67069e975..f3997fcdbe 100644 --- a/openair2/RRC/NR_UE/L2_interface_ue.c +++ b/openair2/RRC/NR_UE/L2_interface_ue.c @@ -185,3 +185,13 @@ rrc_data_req_nr_ue( return true; // TODO should be changed to a CNF message later, currently RRC lite does not used the returned value anyway. } + +int8_t nr_rrc_RA_succeeded(const module_id_t mod_id, const uint8_t gNB_index) +{ + if (NR_UE_rrc_inst[mod_id].Info[gNB_index].T304_active == 1) { + LOG_W(NR_RRC, "T304 was stoped with value %i\n", NR_UE_rrc_inst[mod_id].Info[gNB_index].T304_cnt); + NR_UE_rrc_inst[mod_id].Info[gNB_index].T304_active = 0; + NR_UE_rrc_inst[mod_id].Info[gNB_index].T304_cnt = 0; + } + return 0; +} \ No newline at end of file diff --git a/openair2/RRC/NR_UE/rrc_proto.h b/openair2/RRC/NR_UE/rrc_proto.h index 510fbff230..48b8510de5 100644 --- a/openair2/RRC/NR_UE/rrc_proto.h +++ b/openair2/RRC/NR_UE/rrc_proto.h @@ -156,6 +156,8 @@ rrc_data_req_nr_ue( const pdcp_transmission_mode_t modeP ); +int8_t nr_rrc_RA_succeeded(const module_id_t mod_id, const uint8_t gNB_index); + /**\brief RRC UE task. \param void *args_p Pointer on arguments to start the task. */ void *rrc_nrue_task(void *args_p); From 670fcc04596d1cc3d84cd8f4d95dd6b1be138199 Mon Sep 17 00:00:00 2001 From: Roberto Louro Magueta Date: Tue, 25 Oct 2022 19:48:07 +0100 Subject: [PATCH 35/89] Fix handle_nr_ul_harq() for CFRA in SA --- .../LAYER2/NR_MAC_gNB/gNB_scheduler_ulsch.c | 57 +++++++++++++------ 1 file changed, 40 insertions(+), 17 deletions(-) diff --git a/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_ulsch.c b/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_ulsch.c index bc86fded1b..4a1a3aa398 100644 --- a/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_ulsch.c +++ b/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_ulsch.c @@ -478,14 +478,29 @@ void abort_nr_ul_harq(NR_UE_info_t *UE, int8_t harq_pid) sched_ctrl->sched_ul_bytes = 0; } +bool get_UE_waiting_CFRA_msg3(const gNB_MAC_INST *gNB_mac, const int CC_id, const frame_t frame, const sub_frame_t slot) +{ + bool UE_waiting_CFRA_msg3 = false; + for (int i = 0; i < NR_NB_RA_PROC_MAX; i++) { + const NR_RA_t *ra = &gNB_mac->common_channels[CC_id].ra[i]; + if (ra->cfra == true && ra->state == WAIT_Msg3 && frame == ra->Msg3_frame && slot == ra->Msg3_slot) { + UE_waiting_CFRA_msg3 = true; + break; + } + } + return UE_waiting_CFRA_msg3; +} + void handle_nr_ul_harq(const int CC_idP, module_id_t mod_id, frame_t frame, sub_frame_t slot, const nfapi_nr_crc_t *crc_pdu) { - NR_UE_info_t* UE = find_nr_UE(&RC.nrmac[mod_id]->UE_info, crc_pdu->rnti); - if (!UE) { + NR_UE_info_t *UE = find_nr_UE(&RC.nrmac[mod_id]->UE_info, crc_pdu->rnti); + bool UE_waiting_CFRA_msg3 = get_UE_waiting_CFRA_msg3(RC.nrmac[mod_id], CC_idP, frame, slot); + + if (!UE || UE_waiting_CFRA_msg3 == true) { LOG_W(NR_MAC, "handle harq for rnti %04x, in RA process\n", crc_pdu->rnti); for (int i = 0; i < NR_NB_RA_PROC_MAX; ++i) { NR_RA_t *ra = &RC.nrmac[mod_id]->common_channels[CC_idP].ra[i]; @@ -568,9 +583,12 @@ void nr_rx_sdu(const module_id_t gnb_mod_idP, LOG_D(NR_MAC, "rx_sdu for rnti %04x\n", current_rnti); const int target_snrx10 = gNB_mac->pusch_target_snrx10; const int pusch_failure_thres = gNB_mac->pusch_failure_thres; - - NR_UE_info_t* UE = find_nr_UE(&gNB_mac->UE_info, current_rnti); - if (UE) { + + NR_UE_info_t *UE = find_nr_UE(&gNB_mac->UE_info, current_rnti); + bool UE_waiting_CFRA_msg3 = get_UE_waiting_CFRA_msg3(gNB_mac, CC_idP, frameP, slotP); + + if (UE && UE_waiting_CFRA_msg3 == false) { + NR_UE_sched_ctrl_t *UE_scheduling_control = &UE->UE_sched_ctrl; const int8_t harq_pid = UE_scheduling_control->feedback_ul_harq.head; @@ -700,18 +718,19 @@ void nr_rx_sdu(const module_id_t gnb_mod_idP, continue; } - NR_UE_info_t *UE = add_new_nr_ue(gNB_mac, ra->rnti, ra->CellGroup); - if (!UE) { - LOG_W(NR_MAC, "Random Access %i discarded at state %i (TC_RNTI %04x RNTI %04x): max number of users achieved!\n", i, ra->state,ra->rnti,current_rnti); + NR_UE_info_t *UE_msg3_stage = UE ? UE : add_new_nr_ue(gNB_mac, ra->rnti, ra->CellGroup); + if (!UE_msg3_stage) { + LOG_W(NR_MAC, "Random Access %i discarded at state %i (TC_RNTI %04x RNTI %04x): max number of users achieved!\n", i, ra->state, ra->rnti, current_rnti); + nr_mac_remove_ra_rnti(gnb_mod_idP, ra->rnti); nr_clear_ra_proc(gnb_mod_idP, CC_idP, frameP, ra); return; } - UE->UE_beam_index = ra->beam_id; + UE_msg3_stage->UE_beam_index = ra->beam_id; // re-initialize ta update variables after RA procedure completion - UE->UE_sched_ctrl.ta_frame = frameP; + UE_msg3_stage->UE_sched_ctrl.ta_frame = frameP; LOG_D(NR_MAC, "reset RA state information for RA-RNTI 0x%04x/index %d\n", @@ -725,18 +744,22 @@ void nr_rx_sdu(const module_id_t gnb_mod_idP, current_rnti, ra->rnti); - NR_UE_sched_ctrl_t *UE_scheduling_control = &UE->UE_sched_ctrl; + NR_UE_sched_ctrl_t *UE_scheduling_control = &UE_msg3_stage->UE_sched_ctrl; - UE_scheduling_control->tpc0 = nr_get_tpc(target_snrx10,ul_cqi,30); + UE_scheduling_control->tpc0 = nr_get_tpc(target_snrx10, ul_cqi, 30); if (timing_advance != 0xffff) UE_scheduling_control->ta_update = timing_advance; UE_scheduling_control->raw_rssi = rssi; UE_scheduling_control->pusch_snrx10 = ul_cqi * 5 - 640; - LOG_D(NR_MAC, "[UE %04x] PUSCH TPC %d and TA %d\n",UE->rnti,UE_scheduling_control->tpc0,UE_scheduling_control->ta_update); - if(ra->cfra) { - + LOG_D(NR_MAC, "[UE %04x] PUSCH TPC %d and TA %d\n", UE_msg3_stage->rnti, UE_scheduling_control->tpc0, UE_scheduling_control->ta_update); + if (ra->cfra) { LOG_A(NR_MAC, "(rnti 0x%04x) CFRA procedure succeeded!\n", ra->rnti); - UE->ra_timer = 0; + nr_mac_gNB_rrc_ul_failure_reset(gnb_mod_idP, frameP, slotP, ra->rnti); + reset_dl_harq_list(UE_scheduling_control); + reset_ul_harq_list(UE_scheduling_control); + UE_scheduling_control->pusch_consecutive_dtx_cnt = 0; + UE_scheduling_control->ul_failure = 0; + UE_msg3_stage->ra_timer = 0; nr_mac_remove_ra_rnti(gnb_mod_idP, ra->rnti); nr_clear_ra_proc(gnb_mod_idP, CC_idP, frameP, ra); process_CellGroup(ra->CellGroup, UE_scheduling_control); @@ -755,7 +778,7 @@ void nr_rx_sdu(const module_id_t gnb_mod_idP, memcpy(ra->cont_res_id, &sduP[1], sizeof(uint8_t) * 6); // harq_pid set a non valid value because it is not used in this call // the function is only called to decode the contention resolution sub-header - if (nr_process_mac_pdu(gnb_mod_idP, UE, CC_idP, frameP, slotP, sduP, sdu_lenP, -1) == 0) { + if (nr_process_mac_pdu(gnb_mod_idP, UE_msg3_stage, CC_idP, frameP, slotP, sduP, sdu_lenP, -1) == 0) { if (ra->state == Msg3_dcch_dtch) { // Check if the UE identified by C-RNTI still exists at the gNB From cf8dff2093f0a65c3a90285290263b8bdc756aa5 Mon Sep 17 00:00:00 2001 From: Roberto Louro Magueta Date: Mon, 13 Mar 2023 19:45:31 +0000 Subject: [PATCH 36/89] Implementation to update the RRC timers at UE --- executables/nr-uesoftmodem.c | 5 +++++ openair2/RRC/NR_UE/rrc_UE.c | 26 ++++++++++++++++++++++++++ openair2/RRC/NR_UE/rrc_proto.h | 3 +++ 3 files changed, 34 insertions(+) diff --git a/executables/nr-uesoftmodem.c b/executables/nr-uesoftmodem.c index eb6e160ec3..5cef04f195 100644 --- a/executables/nr-uesoftmodem.c +++ b/executables/nr-uesoftmodem.c @@ -211,6 +211,11 @@ int create_tasks_nrue(uint32_t ue_nb) { } itti_wait_ready(0); + + // Thread to update the RRC timers (in msec) at UE + pthread_t timers_update; + threadCreate(&timers_update, nr_rrc_timers_update, NULL, "nr_rrc_timer_update", -1, OAI_PRIORITY_RT_LOW); + return 0; } diff --git a/openair2/RRC/NR_UE/rrc_UE.c b/openair2/RRC/NR_UE/rrc_UE.c index 9f47cf7640..6ee8c762fc 100644 --- a/openair2/RRC/NR_UE/rrc_UE.c +++ b/openair2/RRC/NR_UE/rrc_UE.c @@ -2890,3 +2890,29 @@ void process_lte_nsa_msg(nsa_msg_t *msg, int msg_len) LOG_E(NR_RRC, "No NSA Message Found\n"); } } + +void *nr_rrc_timers_update() { + + while (1) { + + for (int mod_id = 0; mod_id < NB_NR_UE_INST; mod_id++) { + for (int i = 0; i < NB_SIG_CNX_UE; i++) { + NR_UE_RRC_INFO *timers = &NR_UE_rrc_inst[mod_id].Info[i]; + + // T304 + if (timers->T304_active == 1) { + if ((timers->T304_cnt % 100) == 0) { + LOG_W(NR_RRC, "T304: %u\n", timers->T304_cnt); + } + if (timers->T304_cnt == 1) { + timers->T304_active = 0; + } + timers->T304_cnt--; + } + + } + } + + usleep(1000); + } +} diff --git a/openair2/RRC/NR_UE/rrc_proto.h b/openair2/RRC/NR_UE/rrc_proto.h index 48b8510de5..2739c7ccf8 100644 --- a/openair2/RRC/NR_UE/rrc_proto.h +++ b/openair2/RRC/NR_UE/rrc_proto.h @@ -162,6 +162,9 @@ int8_t nr_rrc_RA_succeeded(const module_id_t mod_id, const uint8_t gNB_index); \param void *args_p Pointer on arguments to start the task. */ void *rrc_nrue_task(void *args_p); +/**\brief RRC timers update at UE. */ +void *nr_rrc_timers_update(); + /**\brief RRC NSA UE task. \param void *args_p Pointer on arguments to start the task. */ void *recv_msgs_from_lte_ue(void *args_p); From 15adb12b71457c75d9b892d48c680733f06da1c2 Mon Sep 17 00:00:00 2001 From: Dhanuja Elizabeth Thomas Date: Wed, 29 Mar 2023 17:44:37 +0200 Subject: [PATCH 37/89] Adding test-case in XML to reset USRP in LTE-TDD pipeline --- ci-scripts/xml_files/container_lte_b200_tdd_05Mhz_tm1.xml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/ci-scripts/xml_files/container_lte_b200_tdd_05Mhz_tm1.xml b/ci-scripts/xml_files/container_lte_b200_tdd_05Mhz_tm1.xml index 3f12ed8330..eb11412ef5 100644 --- a/ci-scripts/xml_files/container_lte_b200_tdd_05Mhz_tm1.xml +++ b/ci-scripts/xml_files/container_lte_b200_tdd_05Mhz_tm1.xml @@ -27,6 +27,7 @@ Replaces xml_files/enb_usrp210_band40_test_05mhz_tm1.xml Test-TDD-05MHz-TM1 tasks +300000 100000 111110 040101 @@ -57,6 +58,13 @@ Replaces xml_files/enb_usrp210_band40_test_05mhz_tm1.xml sudo cpupower idle-set -E + + Custom_Command + Reset USRP + starsky + sudo -S b2xx_fx3_utils --reset-device + + Deploy_Object Deploy eNB (TDD/Band40/5MHz/B200) in a container From 50bf5713476bb3f3b776389da2f3b1cbf8c6d3f0 Mon Sep 17 00:00:00 2001 From: Cedric Roux Date: Thu, 30 Mar 2023 12:57:10 +0200 Subject: [PATCH 38/89] hack: see "Bye." message when quitting gnb running with aw2s For some (unclear) reason, nr-softmodem with aw2s device does not print "Bye." when stopping it with control+c, which is problematic in the CI. This little hack solves the problem. (Maybe not the right solution, to be changed if needed.) --- executables/nr-ru.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/executables/nr-ru.c b/executables/nr-ru.c index 6c527defca..6a2b6e7e9f 100644 --- a/executables/nr-ru.c +++ b/executables/nr-ru.c @@ -328,7 +328,9 @@ void fh_if5_south_in(RU_t *ru, if (proc->first_rx == 0) { if (proc->tti_rx != *tti) { LOG_E(PHY,"Received Timestamp doesn't correspond to the time we think it is (proc->tti_rx %d, subframe %d)\n",proc->tti_rx,*tti); - exit_fun("Exiting"); + if (!oai_exit) + exit_fun("Exiting"); + return; } if (proc->frame_rx != *frame) { From 99d5e50b6aee33b09e93aef0dbb603871cc2acf2 Mon Sep 17 00:00:00 2001 From: Robert Schmidt Date: Thu, 30 Mar 2023 19:58:55 +0200 Subject: [PATCH 39/89] fix merge --- openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_ulsch.c | 2 -- openair2/LAYER2/NR_MAC_gNB/mac_proto.h | 2 -- 2 files changed, 4 deletions(-) diff --git a/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_ulsch.c b/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_ulsch.c index a51ebf7149..09aad3d9cb 100644 --- a/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_ulsch.c +++ b/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_ulsch.c @@ -720,7 +720,6 @@ void nr_rx_sdu(const module_id_t gnb_mod_idP, if (!UE_msg3_stage) { LOG_W(NR_MAC, "Random Access %i discarded at state %i (TC_RNTI %04x RNTI %04x): max number of users achieved!\n", i, ra->state, ra->rnti, current_rnti); - nr_mac_remove_ra_rnti(gnb_mod_idP, ra->rnti); nr_clear_ra_proc(gnb_mod_idP, CC_idP, frameP, ra); return; } @@ -758,7 +757,6 @@ void nr_rx_sdu(const module_id_t gnb_mod_idP, UE_scheduling_control->pusch_consecutive_dtx_cnt = 0; UE_scheduling_control->ul_failure = 0; UE_msg3_stage->ra_timer = 0; - nr_mac_remove_ra_rnti(gnb_mod_idP, ra->rnti); nr_clear_ra_proc(gnb_mod_idP, CC_idP, frameP, ra); process_CellGroup(ra->CellGroup, UE_scheduling_control); diff --git a/openair2/LAYER2/NR_MAC_gNB/mac_proto.h b/openair2/LAYER2/NR_MAC_gNB/mac_proto.h index 3ad1597213..b62984943c 100644 --- a/openair2/LAYER2/NR_MAC_gNB/mac_proto.h +++ b/openair2/LAYER2/NR_MAC_gNB/mac_proto.h @@ -351,8 +351,6 @@ NR_UE_info_t* add_new_nr_ue(gNB_MAC_INST *nr_mac, rnti_t rntiP, NR_CellGroupConf void mac_remove_nr_ue(gNB_MAC_INST *nr_mac, rnti_t rnti); -void nr_mac_remove_ra_rnti(module_id_t mod_id, rnti_t rnti); - int nr_get_default_pucch_res(int pucch_ResourceCommon); int get_dlscs(nfapi_nr_config_request_t *cfg); From ed1d009381e54a1bdce7af2d2479888289aa4fa7 Mon Sep 17 00:00:00 2001 From: Robert Schmidt Date: Thu, 30 Mar 2023 19:59:48 +0200 Subject: [PATCH 40/89] fix warning --- openair1/SIMULATION/NR_PHY/ulsim.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openair1/SIMULATION/NR_PHY/ulsim.c b/openair1/SIMULATION/NR_PHY/ulsim.c index a13b8f37fa..109b4faf20 100644 --- a/openair1/SIMULATION/NR_PHY/ulsim.c +++ b/openair1/SIMULATION/NR_PHY/ulsim.c @@ -1225,7 +1225,7 @@ int main(int argc, char **argv) tx_offset = frame_parms->get_samples_slot_timestamp(slot, frame_parms, 0); txlev_sum = 0; for (int aa = 0; aa < UE->frame_parms.nb_antennas_tx; aa++) { - atxlev[aa] = signal_energy(&UE->common_vars.txdata[aa][tx_offset + 5 * frame_parms->ofdm_symbol_size + 4 * frame_parms->nb_prefix_samples + frame_parms->nb_prefix_samples0], + atxlev[aa] = signal_energy((int32_t*)&UE->common_vars.txdata[aa][tx_offset + 5 * frame_parms->ofdm_symbol_size + 4 * frame_parms->nb_prefix_samples + frame_parms->nb_prefix_samples0], frame_parms->ofdm_symbol_size + frame_parms->nb_prefix_samples); txlev_sum += atxlev[aa]; From d1fc95a8a14b9cd09968872037f9c0cd3941b204 Mon Sep 17 00:00:00 2001 From: Robert Schmidt Date: Fri, 31 Mar 2023 11:53:50 +0200 Subject: [PATCH 41/89] GTP: when creating a tunnel, tell which functions to use The GTP module is common for 4G and 5G. It hardcodes the functions to call when creating S1U and NGU tunnels. The S1U case is both applicable for LTE and NR (NSA), which until now was not a problem as the PDCP data forwarding functions where called pdcp_data_req. The issue is that in this branch, the NR PDCP functions have changed to nr_pdcp_data_req_drb(). When setting up a GTP tunnel, we now pass the PDCP data forwarding functions into GTP, since the caller knows whether it is in 4G or 5G, but GTP itself does not. --- openair2/E1AP/e1ap_api.c | 4 +--- openair2/RRC/LTE/rrc_eNB_S1AP.c | 20 ++++---------------- openair2/RRC/NR/cucp_cuup_direct.c | 6 +++++- openair2/RRC/NR/rrc_gNB_NGAP.c | 3 ++- openair2/RRC/NR/rrc_gNB_nsa.c | 5 +---- openair3/ocp-gtpu/gtp_itf.cpp | 25 +++++++++++++++---------- openair3/ocp-gtpu/gtp_itf.h | 18 ++++++++++-------- 7 files changed, 38 insertions(+), 43 deletions(-) diff --git a/openair2/E1AP/e1ap_api.c b/openair2/E1AP/e1ap_api.c index 23a9fcd1ea..b67525acca 100644 --- a/openair2/E1AP/e1ap_api.c +++ b/openair2/E1AP/e1ap_api.c @@ -112,9 +112,7 @@ static int drb_config_N3gtpu_create(e1ap_bearer_setup_req_t * const req, create_tunnel_req.ue_id = (req->gNB_cu_cp_ue_id & 0xFFFF); // Create N3 tunnel - int ret = gtpv1u_create_ngu_tunnel(instance, - &create_tunnel_req, - create_tunnel_resp); + int ret = gtpv1u_create_ngu_tunnel(instance, &create_tunnel_req, create_tunnel_resp, nr_pdcp_data_req_drb, sdap_data_req); if (ret != 0) { LOG_E(NR_RRC,"rrc_gNB_process_NGAP_PDUSESSION_SETUP_REQ : gtpv1u_create_ngu_tunnel failed,start to release UE id %ld\n", create_tunnel_req.ue_id); diff --git a/openair2/RRC/LTE/rrc_eNB_S1AP.c b/openair2/RRC/LTE/rrc_eNB_S1AP.c index fbc19d0a47..bc4fc6d8cd 100644 --- a/openair2/RRC/LTE/rrc_eNB_S1AP.c +++ b/openair2/RRC/LTE/rrc_eNB_S1AP.c @@ -921,10 +921,7 @@ int rrc_eNB_process_S1AP_INITIAL_CONTEXT_SETUP_REQ(MessageDef *msg_p, const char create_tunnel_req.rnti = ue_context_p->ue_context.rnti; // warning put zero above // create_tunnel_req.num_tunnels = i; - ret = gtpv1u_create_s1u_tunnel( - instance, - &create_tunnel_req, - &create_tunnel_resp); + ret = gtpv1u_create_s1u_tunnel(instance, &create_tunnel_req, &create_tunnel_resp, pdcp_data_req); if ( ret != 0 ) { LOG_E(RRC,"rrc_eNB_process_S1AP_INITIAL_CONTEXT_SETUP_REQ : gtpv1u_create_s1u_tunnel failed,start to release UE %x\n",ue_context_p->ue_context.rnti); @@ -1234,10 +1231,7 @@ int rrc_eNB_process_S1AP_E_RAB_SETUP_REQ(MessageDef *msg_p, const char *msg_name create_tunnel_req.rnti = ue_context_p->ue_context.rnti; // warning put zero above create_tunnel_req.num_tunnels = e_rab_done; // NN: not sure if we should create a new tunnel: need to check teid, etc. - ret = gtpv1u_create_s1u_tunnel( - instance, - &create_tunnel_req, - &create_tunnel_resp); + ret = gtpv1u_create_s1u_tunnel(instance, &create_tunnel_req, &create_tunnel_resp, pdcp_data_req); if ( ret != 0 ) { LOG_E(RRC,"rrc_eNB_process_S1AP_E_RAB_SETUP_REQ : gtpv1u_create_s1u_tunnel failed,start to release UE %x\n",ue_context_p->ue_context.rnti); @@ -1908,14 +1902,8 @@ int rrc_eNB_send_PATH_SWITCH_REQ(const protocol_ctxt_t *const ctxt_pP, S1AP_PATH_SWITCH_REQ (msg_p).nb_of_e_rabs = e_rabs_done; create_tunnel_req.rnti = ue_context_pP->ue_context.rnti; create_tunnel_req.num_tunnels = e_rabs_done; - gtpv1u_create_s1u_tunnel( - ctxt_pP->instance, - &create_tunnel_req, - &create_tunnel_resp); - rrc_eNB_process_GTPV1U_CREATE_TUNNEL_RESP( - ctxt_pP, - &create_tunnel_resp, - &inde_list[0]); + gtpv1u_create_s1u_tunnel(ctxt_pP->instance, &create_tunnel_req, &create_tunnel_resp, pdcp_data_req); + rrc_eNB_process_GTPV1U_CREATE_TUNNEL_RESP(ctxt_pP, &create_tunnel_resp, &inde_list[0]); for (e_rab = 0; e_rab < e_rabs_done; e_rab++) { S1AP_PATH_SWITCH_REQ (msg_p).e_rabs_tobeswitched[e_rab].e_rab_id = create_tunnel_resp.eps_bearer_id[e_rab]; diff --git a/openair2/RRC/NR/cucp_cuup_direct.c b/openair2/RRC/NR/cucp_cuup_direct.c index d2c35e2c03..05ec9a837b 100644 --- a/openair2/RRC/NR/cucp_cuup_direct.c +++ b/openair2/RRC/NR/cucp_cuup_direct.c @@ -120,7 +120,11 @@ static int drb_config_gtpu_create(const protocol_ctxt_t *const ctxt_p, } create_tunnel_req.num_tunnels = UE->nb_of_pdusessions; create_tunnel_req.ue_id = UE->rnti; - int ret = gtpv1u_create_ngu_tunnel(getCxtE1(instance)->gtpInstN3, &create_tunnel_req, &create_tunnel_resp); + int ret = gtpv1u_create_ngu_tunnel(getCxtE1(instance)->gtpInstN3, + &create_tunnel_req, + &create_tunnel_resp, + nr_pdcp_data_req_drb, + sdap_data_req); if (ret != 0) { LOG_E(NR_RRC,"rrc_gNB_process_NGAP_PDUSESSION_SETUP_REQ : gtpv1u_create_ngu_tunnel failed,start to release UE rnti %ld\n", diff --git a/openair2/RRC/NR/rrc_gNB_NGAP.c b/openair2/RRC/NR/rrc_gNB_NGAP.c index d38846e9ee..1f37210b9c 100644 --- a/openair2/RRC/NR/rrc_gNB_NGAP.c +++ b/openair2/RRC/NR/rrc_gNB_NGAP.c @@ -38,6 +38,7 @@ #include "intertask_interface.h" #include "nr_pdcp/nr_pdcp_oai_api.h" #include "pdcp_primitives.h" +#include "SDAP/nr_sdap/nr_sdap.h" #include "openair3/ocp-gtpu/gtp_itf.h" #include @@ -391,7 +392,7 @@ int rrc_gNB_process_NGAP_INITIAL_CONTEXT_SETUP_REQ(MessageDef *msg_p, instance_t } create_tunnel_req.ue_id = UE->rnti; gtpv1u_gnb_create_tunnel_resp_t create_tunnel_resp = {0}; - int ret = gtpv1u_create_ngu_tunnel(instance, &create_tunnel_req, &create_tunnel_resp); + int ret = gtpv1u_create_ngu_tunnel(instance, &create_tunnel_req, &create_tunnel_resp, nr_pdcp_data_req_drb, sdap_data_req); if (ret != 0) { LOG_E(NR_RRC, "rrc_gNB_process_NGAP_INITIAL_CONTEXT_SETUP_REQ : gtpv1u_create_ngu_tunnel failed,start to release UE %x\n", UE->rnti); UE->ue_release_timer_ng = 1; diff --git a/openair2/RRC/NR/rrc_gNB_nsa.c b/openair2/RRC/NR/rrc_gNB_nsa.c index 989c754c20..4b4e06fc0f 100644 --- a/openair2/RRC/NR/rrc_gNB_nsa.c +++ b/openair2/RRC/NR/rrc_gNB_nsa.c @@ -282,10 +282,7 @@ void rrc_add_nsa_user(gNB_RRC_INST *rrc, rrc_gNB_ue_context_t *ue_context_p, x2a memset(&create_tunnel_resp, 0, sizeof(create_tunnel_resp)); if (!IS_SOFTMODEM_NOS1) { LOG_D(RRC, "Calling gtpv1u_create_s1u_tunnel()\n"); - gtpv1u_create_s1u_tunnel( - ctxt.instance, - &create_tunnel_req, - &create_tunnel_resp); + gtpv1u_create_s1u_tunnel(ctxt.instance, &create_tunnel_req, &create_tunnel_resp, nr_pdcp_data_req_drb); rrc_gNB_process_GTPV1U_CREATE_TUNNEL_RESP( &ctxt, &create_tunnel_resp, diff --git a/openair3/ocp-gtpu/gtp_itf.cpp b/openair3/ocp-gtpu/gtp_itf.cpp index f6255fb056..d8e1dc2314 100644 --- a/openair3/ocp-gtpu/gtp_itf.cpp +++ b/openair3/ocp-gtpu/gtp_itf.cpp @@ -648,7 +648,9 @@ teid_t newGtpuCreateTunnel(instance_t instance, int gtpv1u_create_s1u_tunnel(instance_t instance, const gtpv1u_enb_create_tunnel_req_t *create_tunnel_req, - gtpv1u_enb_create_tunnel_resp_t *create_tunnel_resp) { + gtpv1u_enb_create_tunnel_resp_t *create_tunnel_resp, + gtpCallback callBack) +{ LOG_D(GTPU, "[%ld] Start create tunnels for UE ID %u, num_tunnels %d, sgw_S1u_teid %x\n", instance, create_tunnel_req->rnti, @@ -675,7 +677,7 @@ int gtpv1u_create_s1u_tunnel(instance_t instance, -1, // no pdu session in 4G create_tunnel_req->sgw_addr[i], dstport, - pdcp_data_req, + callBack, NULL); create_tunnel_resp->status=0; create_tunnel_resp->rnti=create_tunnel_req->rnti; @@ -712,10 +714,10 @@ int gtpv1u_update_s1u_tunnel( auto it=inst->ue2te_mapping.find(prior_rnti); if ( it != inst->ue2te_mapping.end() ) { - LOG_W(GTPU,"[%ld] Update a not existing tunnel, start create the new one (new ue id %u, old ue id %u)\n", instance, create_tunnel_req->rnti, prior_rnti); pthread_mutex_unlock(&globGtp.gtp_lock); - gtpv1u_enb_create_tunnel_resp_t tmp; - (void)gtpv1u_create_s1u_tunnel(instance, create_tunnel_req, &tmp); + AssertFatal(false, "logic bug: update of non-existing tunnel (new ue id %u, old ue id %u)\n", create_tunnel_req->rnti, prior_rnti); + /* we don't know if we need 4G or 5G PDCP and can therefore not create a + * new tunnel */ return 0; } @@ -725,9 +727,12 @@ int gtpv1u_update_s1u_tunnel( return 0; } -int gtpv1u_create_ngu_tunnel( const instance_t instance, - const gtpv1u_gnb_create_tunnel_req_t *const create_tunnel_req, - gtpv1u_gnb_create_tunnel_resp_t *const create_tunnel_resp) { +int gtpv1u_create_ngu_tunnel(const instance_t instance, + const gtpv1u_gnb_create_tunnel_req_t *const create_tunnel_req, + gtpv1u_gnb_create_tunnel_resp_t *const create_tunnel_resp, + gtpCallback callBack, + gtpCallbackSDAP callBackSDAP) +{ LOG_D(GTPU, "[%ld] Start create tunnels for ue id %lu, num_tunnels %d, sgw_S1u_teid %x\n", instance, create_tunnel_req->ue_id, @@ -749,8 +754,8 @@ int gtpv1u_create_ngu_tunnel( const instance_t instance, create_tunnel_req->outgoing_qfi[i], create_tunnel_req->dst_addr[i], dstport, - nr_pdcp_data_req_drb, - sdap_data_req); + callBack, + callBackSDAP); create_tunnel_resp->status=0; create_tunnel_resp->ue_id=create_tunnel_req->ue_id; create_tunnel_resp->num_tunnels=create_tunnel_req->num_tunnels; diff --git a/openair3/ocp-gtpu/gtp_itf.h b/openair3/ocp-gtpu/gtp_itf.h index 8729be9fa5..9592070ca9 100644 --- a/openair3/ocp-gtpu/gtp_itf.h +++ b/openair3/ocp-gtpu/gtp_itf.h @@ -51,8 +51,10 @@ extern "C" { // the parameter originInstance will be sent back in each message from gtp to the creator void gtpv1uReceiver(int h); void gtpv1uProcessTimeout(int handle,void *arg); - int gtpv1u_create_s1u_tunnel(const instance_t instance, const gtpv1u_enb_create_tunnel_req_t *create_tunnel_req, - gtpv1u_enb_create_tunnel_resp_t *create_tunnel_resp); + int gtpv1u_create_s1u_tunnel(const instance_t instance, + const gtpv1u_enb_create_tunnel_req_t *create_tunnel_req, + gtpv1u_enb_create_tunnel_resp_t *create_tunnel_resp, + gtpCallback callBack); int gtpv1u_update_s1u_tunnel(const instance_t instanceP, const gtpv1u_enb_create_tunnel_req_t *create_tunnel_req_pP, const rnti_t prior_rnti @@ -66,12 +68,12 @@ extern "C" { int gtpv1u_delete_x2u_tunnel( const instance_t instanceP, const gtpv1u_enb_delete_tunnel_req_t *const req_pP); - int - gtpv1u_create_ngu_tunnel( - const instance_t instanceP, - const gtpv1u_gnb_create_tunnel_req_t *const create_tunnel_req_pP, - gtpv1u_gnb_create_tunnel_resp_t *const create_tunnel_resp_pP); - + int gtpv1u_create_ngu_tunnel(const instance_t instanceP, + const gtpv1u_gnb_create_tunnel_req_t *const create_tunnel_req_pP, + gtpv1u_gnb_create_tunnel_resp_t *const create_tunnel_resp_pP, + gtpCallback callBack, + gtpCallbackSDAP callBackSDAP); + int gtpv1u_delete_ngu_tunnel( const instance_t instance, gtpv1u_gnb_delete_tunnel_req_t *req); From b69fb5bd74ef2d64fcdea38cc932bdb1ea3959fa Mon Sep 17 00:00:00 2001 From: Roberto Louro Magueta Date: Wed, 29 Mar 2023 19:33:42 +0100 Subject: [PATCH 42/89] Do not send TA command when rrc_processing_timer is active --- openair2/LAYER2/NR_MAC_gNB/config.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/openair2/LAYER2/NR_MAC_gNB/config.c b/openair2/LAYER2/NR_MAC_gNB/config.c index 38ae19c72a..87789e8c8a 100644 --- a/openair2/LAYER2/NR_MAC_gNB/config.c +++ b/openair2/LAYER2/NR_MAC_gNB/config.c @@ -455,6 +455,12 @@ int nr_mac_enable_ue_rrc_processing_timer(module_id_t Mod_idP, rnti_t rnti, NR_S sched_ctrl->rrc_processing_timer = (rrc_reconfiguration_delay<rnti, rrc_reconfiguration_delay); + // it might happen that timing advance command should be sent during the RRC + // processing timer. To prevent this, set a variable as if we would have just + // sent it. This way, another TA command will for sure be sent in some + // frames, after RRC processing timer. + sched_ctrl->ta_frame = (RC.nrmac[Mod_idP]->frame - 1 + 1024) % 1024; + return 0; } From e4143ded39cfaa1adc8c647b42fd4a71d4ef2ce0 Mon Sep 17 00:00:00 2001 From: Robert Schmidt Date: Thu, 16 Mar 2023 15:38:11 +0100 Subject: [PATCH 43/89] Update main README and overview of documentation --- README.md | 72 +++++++++++------------------------ doc/README.md | 102 ++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 124 insertions(+), 50 deletions(-) create mode 100644 doc/README.md diff --git a/README.md b/README.md index c82eff8f36..70614fd72d 100644 --- a/README.md +++ b/README.md @@ -4,8 +4,12 @@

License - Supported OS - Supported OS + Supported OS Ubuntu 18 + Supported OS Ubuntu 20 + Supported OS Ubuntu 22 + Supported OS RHEL8 + Supported OS RELH9 + Supported OS Fedora 37

@@ -13,10 +17,10 @@

- Docker Pulls - Docker Pulls - Docker Pulls - Docker Pulls + Docker Pulls + Docker Pulls + Docker Pulls + Docker Pulls

# OpenAirInterface License # @@ -32,12 +36,12 @@ Please see [NOTICE](NOTICE.md) file for third party software that is included in # Where to Start # + * [General overview of documentation](./doc/README.md) * [The implemented features](./doc/FEATURE_SET.md) * [How to build](./doc/BUILD.md) * [How to run the modems](./doc/RUNMODEM.md) -More information and documentation can be found in the [doc folder](./doc). -Unfortunately, not all information is available there, and information for +Not all information is available in a central place, and information for specific sub-systems might be available in the corresponding sub-directories. To find all READMEs, this command might be handy: @@ -51,52 +55,20 @@ The OpenAirInterface (OAI) software is composed of the following parts: ``` openairinterface5g +├── charts ├── ci-scripts : Meta-scripts used by the OSA CI process. Contains also configuration files used day-to-day by CI. +├── CMakeLists.txt : Top-level CMakeLists.txt for building ├── cmake_targets : Build utilities to compile (simulation, emulation and real-time platforms), and generated build files. -├── common : Some common OAI utilities, other tools can be found at openair2/UTILS. -├── doc : Contains an up-to-date feature set list and starting tutorials. -├── executables : Top-level executable source files. -├── LICENSE : License file. +├── common : Some common OAI utilities, some other tools can be found at openair2/UTILS. +├── doc : Documentation +├── docker : Dockerfiles to build for Ubuntu and RHEL +├── executables : Top-level executable source files (gNB, eNB, ...) ├── maketags : Script to generate emacs tags. -├── nfapi : Contains the NFAPI code. A local Readme file provides more details. +├── nfapi : (n)FAPI code for MAC-PHY interface ├── openair1 : 3GPP LTE Rel-10/12 PHY layer / 3GPP NR Rel-15 layer. A local Readme file provides more details. -│   ├── PHY -│   ├── SCHED -│   ├── SCHED_NBIOT -│   ├── SCHED_NR -│   ├── SCHED_NR_UE -│   ├── SCHED_UE -│   └── SIMULATION : PHY RF simulation. ├── openair2 : 3GPP LTE Rel-10 RLC/MAC/PDCP/RRC/X2AP + LTE Rel-14 M2AP implementation. Also 3GPP NR Rel-15 RLC/MAC/PDCP/RRC/X2AP. -│   ├── COMMON -│   ├── DOCS -│   ├── ENB_APP -│   ├── F1AP -│   ├── GNB_APP -│   ├── LAYER2/RLC/ : with the following subdirectories: UM_v9.3.0, TM_v9.3.0, and AM_v9.3.0. -│   ├── LAYER2/PDCP/PDCP_v10.1.0 -│   ├── M2AP -│   ├── MCE_APP -│   ├── NETWORK_DRIVER -│   ├── NR_PHY_INTERFACE -│   ├── NR_UE_PHY_INTERFACE -│   ├── PHY_INTERFACE -│   ├── RRC -│   ├── UTIL -│   └── X2AP ├── openair3 : 3GPP LTE Rel10 for S1AP, NAS GTPV1-U for both ENB and UE. -│   ├── COMMON -│   ├── DOCS -│   ├── GTPV1-U -│   ├── M3AP -│   ├── MME_APP -│   ├── NAS -│   ├── S1AP -│   ├── SCTP -│   ├── SECU -│   ├── TEST -│   ├── UDP -│   └── UTILS -├── radio : drivers for various radios such as USRP, AW2S, RFsim, ... -└── targets : Top-level wrappers for unitary simulation for PHY channels, system-level emulation (eNB-UE with and without S1), and realtime eNB and UE and RRH GW. +├── openshift : OpenShift helm charts for some deployment options of OAI +├── radio : Drivers for various radios such as USRP, AW2S, RFsim, ... +└── targets : Some configuration files; only historical relevance, and might be deleted in the future ``` diff --git a/doc/README.md b/doc/README.md new file mode 100644 index 0000000000..8612665a7c --- /dev/null +++ b/doc/README.md @@ -0,0 +1,102 @@ + + + + + +
+ + + + + + OpenAirInterface documentation overview +
+ +[[_TOC_]] + +# General + +- [FEATURE_SET.md](./FEATURE_SET.md): lists supported features +- [GET_SOURCES.md](./GET_SOURCES.md): how to download the sources +- [BUILD.md](./BUILD.md): how to build the sources +- [clang-format.md](./clang-format.md): how to format the code + +There is some general information in the [OpenAirInterface Gitlab Wiki](https://gitlab.eurecom.fr/oai/openairinterface5g/-/wikis/home) + +# Tutorials + +- Step-by-step tutorials to set up 5G: + * [OAI 5GC](./NR_SA_Tutorial_OAI_CN5G.md) + * [OAI gNB with COTS UE](./NR_SA_Tutorial_COTS_UE.md) + * [OAI NR-UE](./NR_SA_Tutorial_OAI_nrUE.md) +- [RUNMODEM.md](./RUNMODEM.md): Generic information on how to + * Run simulators + * Run with hardware + * Specific OAI modes (phy-test, do-ra, noS1) + * (5G) Using SDAP and custom DRBs + * IF setups and arbitrary frequencies + * MIMO +- [How to run a 5G-SA setup](./TESTING_5GSA_setup.md) +- [How to run a 5G-NSA setup](./TESTING_GNB_W_COTS_UE.md) +- [How to run a 4G setup using L1 simulator](./L1SIM.md) _Note: we recommend the RFsimulator_ +- [How to use the L2 simulator](./L2NFAPI.md) +- [How to use multiple BWPs](./RUN_NR_multiple_BWPs.md) +- [How to run OAI-VNF and OAI-PNF](./RUN_NR_NFAPI.md) _Note: does not work currently_ +- [How to use the positioning reference signal (PRS)](./RUN_NR_PRS.md) +- [How to use device-to-device communication (D2D, 4G)](./d2d_emulator_setup.txt) + +Legacy unmaintained files: +- `L2NFAPI_NOS1.md`, `L2NFAPI_S1.md`: old L2simulator, not valid anymore +- `SystemX-tutorial-design.md` +- `UL_MIMO.txt` + +# Designs + +- General software architecture notes: [SW_archi.md](./SW_archi.md) +- [Information on E1](./E1-design.md) +- [Information on F1](./F1-design.md) +- [Information on how NR nFAPI works](./NR_NFAPI_archi.md) +- [Flow graph of the L1 in gNB](SW-archi-graph.md) +- [L1 threads in NR-UE](./nr-ue-design.md) + +Legacy unmaintained files: +- 5Gnas.md + +# Building and running from images + +- [How to build images](../docker/README.md) +- [How to run 5G with the RFsimulator from images](../ci-scripts/yaml_files/5g_rfsimulator/README.md) +- [How to run 4G with the RFsimulator from images](../ci-scripts/yaml_files/4g_rfsimulator_fdd_05MHz/README.md) +- [How to run 5G with the L2simulator from images](../ci-scripts/yaml_files/5g_l2sim_tdd/README.md) +- [How to run images in OpenShift](../openshift/README.md) + +# Libraries + +## General + +- The [T tracer](https://gitlab.eurecom.fr/oai/openairinterface5g/-/wikis/T/): a generic tracing tool (VCD, Wireshark, GUI, to save for later, ...) +- [OPT](../openair2/UTIL/OPT/README.txt): how to trace to wireshark +- The [configuration module](../common/config/DOC/config.md) +- The [logging module](../common/utils/LOG/DOC/log.md) +- The [shared object loader](../common/utils/DOC/loader.md) +- The [threadpool](../common/utils/threadPool/thread-pool.md) used in L1 + +## SDRs + +Some directories under `radio` contain READMEs: + +- [RFsimulator](../radio/rfsimulator/README.md) +- [USRP](../radio/USRP/README.md) +- [BladeRF](../radio/BLADERF/README) +- [IQPlayer](../radio/iqplayer/DOC/iqrecordplayer_usage.md) + +The other SDRs (AW2S, LimeSDR, ...) have no READMEs. + +## Special-purpose libraries + +- OAI comes with an integrated [telnet server](../common/utils/telnetsrv/DOC/telnethelp.md) to monitor and control +- OAI comes with an integrated [web server](../common/utils/websrv/DOC/websrv.md) + +# CI + +- [TESTBenches.md](./TESTBenches.md) lists the CI setup and links to pipelines From a29e59b602ced4ede406358c192ba7c5697f08ec Mon Sep 17 00:00:00 2001 From: Robert Schmidt Date: Thu, 16 Mar 2023 14:03:22 +0100 Subject: [PATCH 44/89] Update GET_SOURCES.md and BUILD.md --- doc/BUILD.md | 133 ++++++++++++++++++++++++++++----------------- doc/GET_SOURCES.md | 21 +++---- 2 files changed, 91 insertions(+), 63 deletions(-) diff --git a/doc/BUILD.md b/doc/BUILD.md index fd2af4703b..a2c9d0d5a4 100644 --- a/doc/BUILD.md +++ b/doc/BUILD.md @@ -14,13 +14,13 @@ This page is valid on tags starting from **`2019.w09`**. -# Soft Modem Build Script +# Overview -The OAI EPC is developed in a distinct project with it's own [documentation](https://github.com/OPENAIRINTERFACE/openair-epc-fed/wiki) , it is not described here. +The [OAI EPC](https://github.com/OPENAIRINTERFACE/openair-epc-fed/blob/master/docs/DEPLOY_HOME_MAGMA_MME.md) and [OAI 5GC](https://gitlab.eurecom.fr/oai/cn5g/oai-cn5g-fed/-/blob/master/docs/DEPLOY_HOME.md) are developed in distinct projects with their own documentation and are not further described here. OAI softmodem sources, which aim to implement 3GPP compliant UEs, eNodeB and gNodeB can be downloaded from the Eurecom [gitlab repository](./GET_SOURCES.md). -Sources come with a build script [build_oai](../cmake_targets/build_oai) located at the root of the `openairinterface5g/cmake_targets` directory. This script is developed to build the oai binaries (executables,shared libraries) for different hardware platforms, and use cases. +Sources come with a build script [build_oai](../cmake_targets/build_oai) located at the root of the `openairinterface5g/cmake_targets` directory. This script is developed to build the oai binaries (executables,shared libraries) for different hardware platforms, and use cases. The main oai binaries, which are tested by the Continuous Integration process are: @@ -28,36 +28,57 @@ The main oai binaries, which are tested by the Continuous Integration process ar - The 5G UE: `nr-uesoftmodem` - The LTE eNodeB: `lte-softmodem` - The 5G gNodeB: `nr-softmodem` +- The 5G CU-UP: `nr-cuup` - The LTE PHY simulators: `dlsim` and `ulsim` -- The 5G PHY simulators: `nr_dlschsim` `nr_dlsim` `nr_pbchsim` `nr_pucchsim` `nr_ulschsim` `nr_ulsim` `polartest` `smallblocktest` - `ulsim` `ldpctest` +- The 5G PHY simulators: `nr_dlschsim`, `nr_dlsim`, `nr_pbchsim`, `nr_pucchsim`, `nr_ulschsim`, `nr_ulsim`, `polartest`, `smallblocktest`, `nr _ulsim`, `ldpctest` Running the [build_oai](../cmake_targets/build_oai) script also generates some utilities required to build and/or run the oai softmodem binaries: -- `conf2uedata`: a binary used to build the UE data from a configuration file. The created file emulates the sim card of a 3GPP compliant phone. -- `nvram`: a binary used to build UE (IMEI...) and EMM (IMSI, registered PLMN) non volatile data. -- `rb_tool`: radio bearer utility -- `genids` T Tracer utility, used at build time to generate T_IDs.h include file. This binary is located in the [T Tracer source file directory](../common/utils/T) . +- `conf2uedata`: a binary used to build the (4G) UE data from a configuration file. The created file emulates the sim card of a 3GPP compliant phone. +- `nvram`: a binary used to build (4G) UE (IMEI...) and EMM (IMSI, registered PLMN) non volatile data. +- `rb_tool`: radio bearer utility for (4G) UE +- `genids` T Tracer utility, used at build time to generate `T_IDs.h` include file. This binary is located in the [T Tracer source file directory](../common/utils/T) . -The build system for OAI uses [cmake](https://cmake.org/) which is a tool to generate makefiles. The `build_oai` script is a wrapper using cmake, make and standard linux shell commands to ease the oai build and use . The file describing how to build the executables from source files is the [CMakeLists.txt](../CMakeLists.txt), it is used as input by cmake to generate the makefiles. +The build system for OAI uses [cmake](https://cmake.org/) which is a tool to generate makefiles. The `build_oai` script is a wrapper using `cmake` and `make`/`ninja` to ease the oai build and use. It logs the `cmake` and `ninja`/`make` commands it executes. The file describing how to build the executables from source files is the [CMakeLists.txt](../CMakeLists.txt), it is used as input by cmake to generate the makefiles. The oai softmodem supports many use cases, and new ones are regularly added. Most of them are accessible using the configuration file or the command line options and continuous effort is done to avoid introducing build options as it makes tests and usage more complicated than run-time options. The following functionalities, originally requiring a specific build are now accessible by configuration or command line options: - s1, noS1 - all simulators as the rfsimulator, the L2 simulator, with exception of PHY simulators, which are distinct executables. +# Running `build_oai` -Calling the `build_oai` script with the -h option gives the list of all available options, but a process to simplify and check the requirements of all these options is on-going. Check the [table](BUILD.md "# `build_oai` options") At the end of this page to know the status of `buid_oai` options which are not described hereafter. +## List of options -# Building PHY Simulators +Calling the `build_oai` script with the `-h` option gives the list of all available options. A number of important ones: + +- The `-I` option is to install pre-requisites, you only need it the first time you build the softmodem or when some oai dependencies have changed. +- The `-w` option is to select the radio head support you want to include in your build. Radio head support is provided via a shared library, which is called the "oai device" The build script creates a soft link from `liboai_device.so` to the true device which will be used at run-time (here the USRP one, `liboai_usrpdevif.so`). The RF simulator[RF simulator](../radio/rfsimulator/README.md) is implemented as a specific device replacing RF hardware, it can be specifically built using `-w SIMU` option, but is also built during any softmodem build. +- `--eNB` is to build the `lte-softmodem` executable and all required shared libraries +- `--gNB` is to build the `nr-softmodem` and `nr-cuup` executables and all required shared libraries +- `--UE` is to build the `lte-uesoftmodem` executable and all required shared libraries +- `--nrUE` is to build the `nr-uesoftmodem` executable and all required shared libraries +- `--ninja` is to use the `ninja` build tool, which speeds up compilation. +- `-c` is to clean the workspace and force a complete rebuild. + +## Installing dependencies + +Install all dependencies by issuing the `-I` option. To install furthermore libraries for optional libraries, use the `--install-optional-packages` option. The `-I` option will also install dependencies for an SDR when paired with `-w`. For instance, in order to install all dependencies and the ones for USRP, run: + +```bash +cd openairinterface5g/cmake_targets/ +./build_oai -I --install-optional-packages -w USRP +``` + +Note the section on installing UHD further down for more information. + +## Building PHY Simulators The PHY layer simulators (LTE and NR) can be built as follows: -``` -cd /openairinterface5g/ -source oaienv -cd cmake_targets/ -./build_oai -I --phy_simulators +```bash +cd openairinterface5g/cmake_targets/ +./build_oai --phy_simulators ``` After completing the build, the binaries are available in the `cmake_targets/ran_build/build` directory. @@ -68,19 +89,10 @@ Detailed information about these simulators can be found [in this dedicated page After downloading the source files, a single build command can be used to get the binaries supporting all the oai softmodem use cases (UE and [eg]NodeB): +```bash +cd openairinterface5g/cmake_targets/ +./build_oai -w USRP --eNB --UE --nrUE --gNB ``` -cd /openairinterface5g/ -source oaienv -cd cmake_targets/ -./build_oai -I -w USRP --eNB --UE --nrUE --gNB -``` - -- The `-I` option is to install pre-requisites, you only need it the first time you build the softmodem or when some oai dependencies have changed. -- The `-w` option is to select the radio head support you want to include in your build. Radio head support is provided via a shared library, which is called the "oai device" The build script creates a soft link from `liboai_device.so` to the true device which will be used at run-time (here the USRP one,`liboai_usrpdevif.so` . USRP is the only hardware tested today in the Continuous Integration process. The RF simulator[RF simulator](../radio/rfsimulator/README.md) is implemented as a specific device replacing RF hardware, it can be specifically built using `-w SIMU` option, but is also built during any softmodem build. -- `--eNB` is to build the `lte-softmodem` executable and all required shared libraries -- `--gNB` is to build the `nr-softmodem` executable and all required shared libraries -- `--UE` is to build the `lte-uesoftmodem` executable and all required shared libraries -- `--nrUE` is to build the `nr-uesoftmodem` executable and all required shared libraries You can build any oai softmodem executable separately, you may not need all of them depending on your oai usage. @@ -118,35 +130,56 @@ See: * `cmake_targets/tools/uhd-4.x-tdd-patch.diff` * `cmake_targets/tools/build_helper` --> function `install_usrp_uhd_driver_from_source` -# Building Optional Binaries +## Building Optional Binaries -## Telnet Server +There are a number of optional libraries that can be built in support of the +RAN, such as telnet, scopes, offloading libraries, etc. -The telnet server can be built with the `--build-lib telnetsrv` option, after building the softmodem or while building it. +Using the help option of the build script you can get the list of available optional libraries. -`./build_oai -I -w USRP --eNB --UE --build-lib telnetsrv` +```bash +./build_oai --build-lib all # build all +./build_oai --build-lib telnet # build only telnet +./build_oai --build-lib "telnet enbscope uescope nrscope nrqtscope" +./build_oai --build-lib telnet --build-lib nrqtscope +``` -or +The following libraries are build in CI and should always work: `telnet`, +`enbscope`, `uescope`, `nrscope`, `nrqtscope`. -`./build_oai --build-lib telnetsrv` +Some libraries have further dependencies and might not build on every system: +- `enbscope`, `uescope`, `nrscope`: libforms/X +- `nrqtscope`: Qt5 +- `ldpc_cuda`: CUDA +- `ldpc_t1`: DPDK and VVDN T1 +- `websrv`: npm and others -You can get documentation about the telnet server [here](common/utils/telnetsrv/DOC/telnetsrv.md) +# Running `cmake` directly +`build_oai` is a wrapper on top of `cmake`. It is therefore possible to run `cmake` directly. An example using `ninja`: to build all "main targets" for 5G, excluding additional libraries: +``` +cd openairinterface5g +mkdir build && cd build +cmake .. -GNinja && ninja nr-softmodem nr-uesoftmodem nr-cuup params_libconfig coding rfsimulator ldpc +``` +To build additional libraries, e.g., telnet, do the following: +```bash +cmake .. -GNinja -DENABLE_TELNETSRV=ON && ninja telnetsrv +``` -## Other optional libraries +A list of all libraries can be seen using `ccmake ..` or `cmake-gui ..`. -Using the help option of the build script you can get the list of available optional libraries. Those which haven't been mentioned above are known to need more tests and documentation. - -`./build_oai --build-lib all` will build all available optional libraries. - -# `build_oai` options - -Please run `./build_oai -h` to get a list of available options. - -[oai wiki home](https://gitlab.eurecom.fr/oai/openairinterface5g/wikis/home) - -[oai softmodem features](FEATURE_SET.md) - -[running the oai softmodem ](RUNMODEM.md) +It is currently not possible to build all targets in the form of `cmake .. +-GNinja && ninja`: currently, SDRs are always exposed, even if you don't have +the dependencies, and some targets are simply broken. Again, `build_oai` list +all targets that it builds, and you can use them with `ninja` +The default target directory of `build_oai` is the following, for historical reasons: +```bash +cd openairinterface5g/cmake_targets/ran_build/build +cmake ../../.. -GNinja +ccmake ../../.. +cmake-gui ../../.. +``` +You can of course use all standard cmake/ninja/make commands in this directory. diff --git a/doc/GET_SOURCES.md b/doc/GET_SOURCES.md index f594ba1747..98fd933882 100644 --- a/doc/GET_SOURCES.md +++ b/doc/GET_SOURCES.md @@ -25,7 +25,7 @@ git config --global user.email "Your email address" ## In order to clone the Git repository (for OAI Users without login to gitlab server) -Cloning RAN repository (eNB RAN + UE RAN): +Cloning RAN repository: ```shell git clone https://gitlab.eurecom.fr/oai/openairinterface5g.git @@ -35,22 +35,18 @@ git clone https://gitlab.eurecom.fr/oai/openairinterface5g.git Please send email to [contact@openairinterface.org](mailto:contact@openairinterface.org) to be added to the repository as a developer (only important for users who want to commit code to the repository). If -you do not have account on gitlab.eurecom.fr, please register yourself to gitlab.eurecom.fr and provide the identifiant in the email. +you do not have account on gitlab.eurecom.fr, please register yourself to gitlab.eurecom.fr and provide the login name in the email. + +More information can be found in [a separate page](../CONTRIBUTING.md). # Which branch to checkout? -On the RAN side: +`develop`: contains recent commits that are tested on our CI test bench. The update frequency is about once a week. 5G is only in this branch. It is the recommended and default branch. +`master`: contains a stable version of 4G, and will be updated in the future with 5G. -* **master**: This branch is targeted for the user community. Since January 2019, it is also subject to a Continuous Integration process. The update frequency is about once every 2-3 months. We are also performing bug fixes on this branch. -* **develop**: This branch contains recent commits that are tested on our CI test bench. The update frequency is about once a week. +Please see the work flow and policies page: https://gitlab.eurecom.fr/oai/openairinterface5g/wikis/oai-policies-home -Please see the work flow and policies page: - -https://gitlab.eurecom.fr/oai/openairinterface5g/wikis/oai-policies-home - -you can find the latest stable tag release here: - -https://gitlab.eurecom.fr/oai/openairinterface5g/tags +You can find the latest stable tag release here: https://gitlab.eurecom.fr/oai/openairinterface5g/tags The tag naming conventions are: @@ -60,4 +56,3 @@ The tag naming conventions are: - On `develop` branch **`yyyy`.w`xx`** * `yyyy` is the calendar year * `xx` the week number within the year - From 8ca640337a7a6fc6f407016304495a60af764980 Mon Sep 17 00:00:00 2001 From: Robert Schmidt Date: Thu, 16 Mar 2023 15:38:29 +0100 Subject: [PATCH 45/89] Reorganize RUNMODEM.md --- doc/RUNMODEM.md | 226 +++++++++++++++++++++++------------------------- 1 file changed, 109 insertions(+), 117 deletions(-) diff --git a/doc/RUNMODEM.md b/doc/RUNMODEM.md index 988f3364be..d32758b1a2 100644 --- a/doc/RUNMODEM.md +++ b/doc/RUNMODEM.md @@ -7,68 +7,123 @@ - Running OAI Softmodem + Running OAI Softmodems After you have [built the softmodem executables](BUILD.md) you can set your default directory to the build directory `cmake_targets/ran_build/build/` and start testing some use cases. Below, the description of the different oai functionalities should help you choose the oai configuration that suits your need. -# RF Simulator +[[_TOC_]] -The rf simulator is a oai device replacing the radio heads (for example the USRP device). It allows connecting the oai UE (LTE or 5G) and respectively the oai eNodeB or gNodeB through a network interface carrying the time-domain samples, getting rid of over the air unpredictable perturbations. This is the ideal tool to check signal processing algorithms and protocols implementation. The rf simulator has some preliminary support for channel modeling. +# Simulators + +## RFsimulator + +The RF simulator is an OAI device replacing the radio heads (for example the USRP device). It allows connecting the oai UE (LTE or 5G) and respectively the oai eNodeB or gNodeB through a network interface carrying the time-domain samples, getting rid of over the air unpredictable perturbations. This is the ideal tool to check signal processing algorithms and protocols implementation. The rf simulator has some preliminary support for channel modeling. It is planned to enhance this simulator with the following functionalities: -- Support for multiple UE connections,each UE being a `lte-uesoftmodem` or `nr_uesoftmodem` instance. - Support for multiple eNodeB's or gNodeB's for hand-over tests - This is an easy use-case to setup and test, as no specific hardware is required. The [rfsimulator page](../radio/rfsimulator/README.md ) contains the detailed documentation. +This is an easy use-case to setup and test, as no specific hardware is required. The [rfsimulator page](../radio/rfsimulator/README.md ) contains the detailed documentation. -# L2 nFAPI Simulator +## L2 nFAPI Simulator This simulator connects a eNodeB and UEs through a nfapi interface, short-cutting the L1 layer. The objective of this simulator is to allow multi UEs simulation, with a large number of UEs (ideally up to 255 ) .Here to ease the platform setup, UEs are simulated via a single `lte-uesoftmodem` instance. Today the CI tests just with one UE and architecture has to be reviewed to allow a number of UE above about 16. This work is on-going. As for the rf simulator, no specific hardware is required. The [L2 nfapi simulator page](L2NFAPI.md) contains the detailed documentation. -# L1 Simulator +## L1 Simulator + +**This information might be outdated. We recommend to use the RFsimulator as +shown above.** The L1 simulator is using the ethernet fronthaul protocol, as used to connect a RRU and a RAU to connect UEs and a eNodeB. UEs are simulated in a single `lte-uesoftmodem` process, as for the nfapi simulator. The [L1 simulator page](L1SIM.md) contains the detailed documentation. -## noS1 mode - -The noS1 mode is now available via the `--noS1`command line option. It can be used with simulators, described above, or when using oai with true RF boards. Only the oai UE can be connected to the oai eNodeB in noS1 mode. - -By default the noS1 mode is using linux tun interfaces to send or receive ip packets to/from the linux ip stack. using the `--nokrnmod 0`option you can enforce kernel modules instead of tun. - -noS1 code has been revisited, it has been tested with the rf simulator, and tun interfaces. More tests are on going and CI will soon include noS1 tests. - # Running with a true radio head -oai supports [number of deployment](FEATURE_SET.md) model, the following are tested in the CI: +OAI supports different radio heads, the following are tested in the CI: + +1. [Monolithic eNodeB](https://gitlab.eurecom.fr/oai/openairinterface5g/wikis/HowToConnectCOTSUEwithOAIeNBNew) where the whole signal processing is performed in a single process +2. IF4P5 mode, where frequency domain samples are carried over ethernet, from the RRU which implement part of L1(FFT,IFFT,part of PRACH), to a RAU +3. Monolithic gNodeB: see next section, or the [standalone tutorial](NR_SA_Tutorial_COTS_UE.md) -1. [Monolithic eNodeB](https://gitlab.eurecom.fr/oai/openairinterface5g/wikis/HowToConnectCOTSUEwithOAIeNBNew) where the whole signal processing is performed in a single process -2. if4p5 mode, where frequency domain samples are carried over ethernet, from the RRU which implement part of L1(FFT,IFFT,part of PRACH), to a RAU # 5G NR -As of February 2020, all 5G NR development is part of the develop branch (the branch develop-nr is no longer maintained). This also means that all new development will be merged into there once it passes all the CI. - ## NSA setup with COTS UE This setup requires an EPC, an OAI eNB and gNB, and a COTS Phone. A dedicated page describe the setup can be found [here](https://gitlab.eurecom.fr/oai/openairinterface5g/wikis/home/gNB-COTS-UE-testing). -### Launch gNB - -```bash sudo ./nr-softmodem -O ../../../targets/PROJECTS/GENERIC-LTE-EPC/CONF/gnb.band78.tm1.106PRB.usrpn300.conf``` - ### Launch eNB -```bash sudo ./lte-softmodem -O ../../../targets/PROJECTS/GENERIC-LTE-EPC/CONF/enb.band7.tm1.50PRB.usrpb210.conf``` +```bash +sudo ./lte-softmodem -O ../../../targets/PROJECTS/GENERIC-LTE-EPC/CONF/enb.band7.tm1.50PRB.usrpb210.conf +``` + +### Launch gNB + +```bash +sudo ./nr-softmodem -O ../../../targets/PROJECTS/GENERIC-LTE-EPC/CONF/gnb.band78.tm1.106PRB.usrpn300.conf +``` + +You should see the X2 messages in Wireshark and at the eNB. + +## SA setup with OAI NR-UE + +The sa flag is used to run gNB in standalone mode. + +In order to run gNB and UE in standalone mode, the `--sa` flag is needed. + +At the gNB the `--sa` flag does the following: +- The RRC encodes SIB1 according to the configuration file and transmits it through NR-BCCH-DL-SCH. + +At the UE the --sa flag will: +- Decode SIB1 and starts the 5G NR Initial Access Procedure for SA: + 1) 5G-NR RRC Connection Setup + 2) NAS Authentication and Security + 3) 5G-NR AS Security Procedure + 4) 5G-NR RRC Reconfiguration + 5) Start Downlink and Uplink Data Transfer + +Command line parameters for UE in --sa mode: +- `-C` : downlink carrier frequency in Hz (default value 0) +- `--CO` : uplink frequency offset for FDD in Hz (default value 0) +- `--numerology` : numerology index (default value 1) +- `-r` : bandwidth in terms of RBs (default value 106) +- `--band` : NR band number (default value 78) +- `--ssb` : SSB start subcarrier (default value 512) + +You can run this, using USRPs, on two separate machines: + +``` +sudo ./nr-softmodem -O ../../../targets/PROJECTS/GENERIC-NR-5GC/CONF/gnb.sa.band78.fr1.106PRB.usrpb210.conf --gNBs.[0].min_rxtxtime 6 --sa +sudo ./nr-uesoftmodem -r 106 --numerology 1 --band 78 -C 3619200000 --ssb 516 --sa +``` + +With the RF simulator (on the same machine): + +```bash +sudo ./nr-softmodem -O ../../../targets/PROJECTS/GENERIC-NR-5GC/CONF/gnb.sa.band78.fr1.106PRB.usrpb210.conf --gNBs.[0].min_rxtxtime 6 --rfsim --sa +sudo ./nr-uesoftmodem -r 106 --numerology 1 --band 78 -C 3619200000 --ssb 516 --rfsim --sa +``` + +Additionally, at UE side `--uecap_file` option can be used to pass the UE Capabilities input file (path location + filename), e.g. `--uecap_file ../../../targets/PROJECTS/GENERIC-NR-5GC/CONF/uecap_ports1.xml` + +## Some useful NR-UE parameters + +Some other useful paramters of the UE are + + - --ue-fo-compensation: enables the frequency offset compenstation at the UE. This is useful when running over the air and/or without an external clock/time source + - --usrp-args: this is the equivalend paramter of sdr_addrs field in the gNB config file and can be used to identify the USRP and set some basic paramters (like the clock source) + - --clock-source: sets the clock-source (internal or external). + - --time-source: sets the time-source (internal or external). +# Specific OAI modes ## phy-test setup with OAI UE @@ -81,22 +136,18 @@ At the UE the --phy-test flag will - read the binary files rbconfig.raw and reconfig.raw from the current directory (a different directory can be specified with the flag --rrc_config_path) and process them. -### Launch gNB +```bash +sudo ./nr-softmodem -O ../../../targets/PROJECTS/GENERIC-LTE-EPC/CONF/gnb.band78.tm1.106PRB.usrpn300.conf --phy-test +``` -```bash sudo ./nr-softmodem -O ../../../targets/PROJECTS/GENERIC-LTE-EPC/CONF/gnb.band78.tm1.106PRB.usrpn300.conf --phy-test``` +In phy-test mode it is possible to mimic the reception of UE Capabilities at gNB by passing through the command line parameter `--uecap_file` the location and file name of the input UE Capability file, e.g. `--uecap_file ../../../targets/PROJECTS/GENERIC-NR-5GC/CONF/uecap_ports1.xml` (1 layer) or `--uecap_file ../../../targets/PROJECTS/GENERIC-NR-5GC/CONF/uecap_ports2.xml` (2 layers). -In phy-test mode it is possible to mimic the reception of UE Capabilities at gNB by passing through the command line parameter `--uecap_file` the location and file name of the input UE Capability file, e.g. `--uecap_file ../../../targets/PROJECTS/GENERIC-NR-5GC/CONF/uecap_ports1.xml` +```bash +sudo ./nr-uesoftmodem --phy-test [--rrc_config_path ../../../ci-scripts/rrc-files] +``` -### Launch UE in another window - -```bash sudo ./nr-uesoftmodem --phy-test [--rrc_config_path ../../../ci-scripts/rrc-files]``` - -Some other useful paramters of the UE are - - - --ue-fo-compensation: enables the frequency offset compenstation at the UE. This is useful when running over the air and/or without an external clock/time source - - --usrp-args: this is the equivalend paramter of sdr_addrs field in the gNB config file and can be used to identify the USRP and set some basic paramters (like the clock source) - - --clock-source: sets the clock-source (internal or external). - - --time-source: sets the time-source (internal or external). +You need to provide `--rrc_config_path` if you don't start the UE after the gNB +in the same directory. ## noS1 setup with OAI UE @@ -108,78 +159,29 @@ This setup is described in the [rfsimulator page](../radio/rfsimulator/README.md The do-ra flag is used to ran the NR Random Access procedures in contention-free mode. Currently OAI implements the RACH process from Msg1 to Msg3. -In order to run the RA, the following flag is needed for both the gNB and the UE: - -`--do-ra` - -### Run OAI in do-ra mode - -From the `cmake_targets/ran_build/build` folder: - -gNB on machine 1: - -`sudo ./nr-softmodem -O ../../../targets/PROJECTS/GENERIC-LTE-EPC/CONF/gnb.band78.tm1.106PRB.usrpn300.conf --do-ra` +In order to run the RA, the `--do-ra` flag is needed for both the gNB and the UE. In do-ra mode it is possible to mimic the reception of UE Capabilities at gNB by passing through the command line parameter `--uecap_file` the location and file name of the input UE Capability file, e.g. `--uecap_file ../../../targets/PROJECTS/GENERIC-NR-5GC/CONF/uecap_ports1.xml` -UE on machine 2: +To run using the RFsimulator: -`sudo ./nr-uesoftmodem --do-ra` +```bash +sudo RFSIMULATOR=gnb ./nr-softmodem -O ../../../targets/PROJECTS/GENERIC-LTE-EPC/CONF/gnb.band78.tm1.106PRB.usrpn300.conf --do-ra --rfsim --parallel-config PARALLEL_SINGLE_THREAD +sudo RFSIMULATOR=127.0.0.1 ./nr-uesoftmodem --do-ra --rfsim --parallel-config PARALLEL_SINGLE_THREAD +``` -With the RF simulator (on the same machine): +Using USRPs: -`sudo RFSIMULATOR=gnb ./nr-softmodem -O ../../../targets/PROJECTS/GENERIC-LTE-EPC/CONF/gnb.band78.tm1.106PRB.usrpn300.conf --do-ra --rfsim --parallel-config PARALLEL_SINGLE_THREAD` +```bash +sudo ./nr-softmodem -O ../../../targets/PROJECTS/GENERIC-LTE-EPC/CONF/gnb.band78.tm1.106PRB.usrpn300.conf --do-ra +``` -`sudo RFSIMULATOR=127.0.0.1 ./nr-uesoftmodem --do-ra --rfsim --parallel-config PARALLEL_SINGLE_THREAD` +On a separate machine: -## SA setup with OAI +```bash +sudo ./nr-uesoftmodem --do-ra +``` -The sa flag is used to run gNB in standalone mode. - -In order to run gNB and UE in standalone mode, the following flag is needed: - -`--sa` - -At the gNB the --sa flag does the following: -- The RRC encodes SIB1 according to the configuration file and transmits it through NR-BCCH-DL-SCH. - -At the UE the --sa flag will: -- Decode SIB1 and starts the 5G NR Initial Access Procedure for SA: - 1) 5G-NR RRC Connection Setup - 2) NAS Authentication and Security - 3) 5G-NR AS Security Procedure - 4) 5G-NR RRC Reconfiguration - 5) Start Downlink and Uplink Data Transfer - -Command line parameters for UE in --sa mode: -- `C` : downlink carrier frequency in Hz (default value 0) -- `CO` : uplink frequency offset for FDD in Hz (default value 0) -- `numerology` : numerology index (default value 1) -- `r` : bandwidth in terms of RBs (default value 106) -- `band` : NR band number (default value 78) -- `s` : SSB start subcarrier (default value 512) - -### Run OAI in SA mode - -From the `cmake_targets/ran_build/build` folder: - -gNB on machine 1: - -`sudo ./nr-softmodem -O ../../../targets/PROJECTS/GENERIC-NR-5GC/CONF/gnb.sa.band78.fr1.106PRB.usrpb210.conf --gNBs.[0].min_rxtxtime 6 --sa` - -UE on machine 2: - -`sudo ./nr-uesoftmodem -r 106 --numerology 1 --band 78 -C 3619200000 --ssb 516 --sa` - -With the RF simulator (on the same machine): - -`sudo ./nr-softmodem -O ../../../targets/PROJECTS/GENERIC-NR-5GC/CONF/gnb.sa.band78.fr1.106PRB.usrpb210.conf --gNBs.[0].min_rxtxtime 6 --rfsim --sa` - -`sudo ./nr-uesoftmodem -r 106 --numerology 1 --band 78 -C 3619200000 --ssb 516 --rfsim --sa` - -where `-r` sets the transmission bandwidth configuration in terms of RBs, `-C` sets the downlink carrier frequency and `--ssb` sets the SSB start subcarrier. - -Additionally, at UE side `--uecap_file` option can be used to pass the UE Capabilities input file (path location + filename), e.g. `--uecap_file ../../../targets/PROJECTS/GENERIC-NR-5GC/CONF/uecap_ports1.xml` ### Run OAI with SDAP & Custom DRBs @@ -208,17 +210,14 @@ Accordingly, the following parameters must be configured in the RUs section of t The following example uses DL frequency 2169.080 MHz and UL frequency offset -400 MHz, with a configuration file for band 66 (FDD) at gNB side. -From the `cmake_targets/ran_build/build` folder: +On two separate machines with USRPs, run: -gNB on machine 1: +``` +sudo ./nr-softmodem -O ../../../targets/PROJECTS/GENERIC-LTE-EPC/CONF/gnb.band66.tm1.106PRB.usrpx300.conf +sudo ./nr-uesoftmodem --if_freq 2169080000 --if_freq_off -400000000 +``` -`sudo ./nr-softmodem -O ../../../targets/PROJECTS/GENERIC-LTE-EPC/CONF/gnb.band66.tm1.106PRB.usrpx300.conf` - -UE on machine 2: - -`sudo ./nr-uesoftmodem --if_freq 2169080000 --if_freq_off -400000000` - -# MIMO configuration +# 5G gNB MIMO configuration In order to enable DL-MIMO in OAI 5G softmodem, the prerequisite is to have `do_CSIRS = 1` in the configuration file. This allows the gNB to schedule CSI reference signal and to acquire from the UE CSI measurements to be able to schedule DLSCH with MIMO. @@ -246,10 +245,3 @@ Finally the number of TX physical antenna in the RU part of the configuration fi # Additional links [Selecting an alternative ldpc implementation at run time](../openair1/PHY/CODING/DOC/LDPCImplementation.md) - -[oai wiki home](https://gitlab.eurecom.fr/oai/openairinterface5g/wikis/home) - -[oai softmodem features](FEATURE_SET.md) - -[oai softmodem build procedure](BUILD.md) - From ca3a7c612b6491c20a762a6998aefaf52da45148 Mon Sep 17 00:00:00 2001 From: Robert Schmidt Date: Thu, 16 Mar 2023 16:07:52 +0100 Subject: [PATCH 46/89] Remove outdated CI section --- doc/TESTING_GNB_W_COTS_UE.md | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/doc/TESTING_GNB_W_COTS_UE.md b/doc/TESTING_GNB_W_COTS_UE.md index fa24d74d5d..a152d54075 100644 --- a/doc/TESTING_GNB_W_COTS_UE.md +++ b/doc/TESTING_GNB_W_COTS_UE.md @@ -395,18 +395,3 @@ The following parts have been validated with FR1 COTS UE: DL traffic : 3Mbps UL traffic : 1Mbps some packet losses might still occur even in ideal channel conditions - - -## CI integration -The automation scripts are available on ILIADE. -The end-to-end test is integrated in the CI flow in a semi-automated manner, comprising 3 steps: -- update a YAML file comprising the IT resources definition, branch and commit number the test has to run on -- run the python script that generates the test from the YAML file -``` -python3 obj_build_from_yaml.py py_params_template.yaml fr1.sh -``` -- run the test (fr1.sh) - -At the date of writing, the test comprises the deployment of the components (epc, eNB, gNB, cots ue) and the execution of 2 pings procedures (20 pings in 20sec, then 5 pings in 1sec) - -This automation is run for every integration branch to be merged into develop. From 03862de437ff9602538f1e5a107bd3bd5ca318a2 Mon Sep 17 00:00:00 2001 From: Robert Schmidt Date: Thu, 16 Mar 2023 13:49:02 +0100 Subject: [PATCH 47/89] Update TESTBenches.md to reflect current stetup --- doc/TESTBenches.md | 116 ++++++++---------- .../4g-faraday-bench.pdf | Bin 53079 -> 53079 bytes .../4g-faraday-bench.png | Bin 92377 -> 92377 bytes .../5g-nsa-faraday-bench.pdf | Bin 337509 -> 337573 bytes .../5g-nsa-faraday-bench.png | Bin 89413 -> 79393 bytes .../5g-nsa-faraday-bench.tex | 19 ++- .../5g-ota-bench.pdf | Bin 232400 -> 232308 bytes .../5g-ota-bench.png | Bin 96734 -> 91775 bytes .../5g-ota-bench.tex | 11 +- 9 files changed, 64 insertions(+), 82 deletions(-) diff --git a/doc/TESTBenches.md b/doc/TESTBenches.md index 20d8f79ff0..e17fbe2e80 100644 --- a/doc/TESTBenches.md +++ b/doc/TESTBenches.md @@ -6,25 +6,25 @@ | Machine | IP address | Lockable Resource | Function | Connected devices | | ------------- | --------------- | --------------------- | ------------------ | ----------------------------------------------------- | -| asterix | 172.21.16.127 | CI-Asterix-Usage | gNB (n78) | 173.21.19.14 | -| obelix | 172.21.16.128 | CI-Obelix-Usage | eNB (n40, n78), nrUE | 172.21.19.13, X300 (192.168.60.2), B200mini (30C51EB) | -| porcepix | 172.21.16.136 | CI-NSA-MiniBench | Executor, EPC, 5GC | -- | -| nrmodule2 | 172.21.16.139 | CI-NSA-MiniBench | Quectel | Quectel module | -| nepes | 172.21.16.137 | CI-NSA-MiniBench | gNB (n78) | B200mini (30C51D4) | +| asterix | 172.21.16.127 | CI-Asterix-Usage | gNB (n78) | 172.21.19.14 | +| obelix | 172.21.16.128 | CI-Obelix-Usage | eNB (n40, n78), nrUE | 172.21.19.13, X300 (192.168.60.2) | +| porcepix | 172.21.16.136 | CI-Porcepix | Executor, EPC, 5GC | -- | +| nrmodule2 | 172.21.16.139 | CI-NrModule2 | Quectel | Quectel module | +| nepes | 172.21.16.137 | CI-Nepes | gNB (n78), EPC/5GC | B200mini (30C51EB) | +| ofqot | 172.21.16.109 | CI-Ofqot | gNB (n78) | B200mini (30C51D4) | +| idefix | 172.21.16.135 | CI-Idefix | Quectel | Quectel module | | caracal | 172.21.16.132 | CI-Caracal | gNB/phytest | N300 (192.168.10.2) | -| idefix | 172.21.16.135 | CI-NSA-MiniBench | Quectel | Quectel module | | amariue | 172.21.16.144 | CI-Amarisoft-UE-Usage | nrUE | Amarisoft UE simulator | -| bellatrix | 192.168.117.115 | CI-RAN-VM-Deployment | Executor | -- | -| nano | 172.21.18.48 | CI-Bench-1-Phones | EPC, adb | 2x COTS (adb) | -| hutch | 172.21.18.46 | CI-Bench-1-Phones | eNB (B7) | B200mini (30C5239) | -| starsky | 172.21.18.45 | CI-Bench-1-Phones | eNB (B40) | b200mini (30A3E3C) | -| carabe | 172.21.18.47 | CI-Bench-2-OAI-Phone | UE (B7UE) | B200mini (30AE8C9) | +| manarola | 172.21.8.116 | _None_ | gNB | AW2S | +| nano | 172.21.18.48 | CI-Nano-Legacy-EPC | Executor, EPC, adb | 2x COTS (adb) | +| hutch | 172.21.18.46 | CI-Hutch-Legacy-FDD-eNB | eNB (B7) | B200mini (30C5239) | +| starsky | 172.21.18.45 | CI-Starsky-Legacy-TDD-eNB | eNB (B40) | b200mini (30A3E3C) | +| carabe | 172.21.18.47 | CI-Carabe-Legacy-FDD-OAI-LTE-UE | UE (B7UE) | B200mini (30AE8C9) | +| nokiabox | 172.21.19.39 | _None_ | gNB (Nokia), 5GC | _Nokia RF integrated_ | Note: The available resources, and their current usage, is indicated here: - [Lockable resources of jenkins-oai](https://jenkins-oai.eurecom.fr/lockable-resources/): "New" Jenkins, i.e., with RAN-Container-Parent -- [Lockable resources of open5glab jenkins](https://open5glab.eurecom.fr:8083/jenkins/lockable-resources/): - "Old" Jenkins, i.e., with RAN-CI-Develop ## Networked devices @@ -35,37 +35,39 @@ Note: The available resources, and their current usage, is indicated here: ## Testbenches -### OTA Testbench +### 5G OTA Testbench -**Purpose**: Over-the-air 4G/5G (NSA/SA) tests - -Note: obelix and porcepix are both used in the OTA testbench and the 5G -NSA/Faraday Cage testbench! +**Purpose**: Over-the-air 4G/5G (NSA/SA) tests, performance tests ![5G OTA Testbench](testbenches_doc_resources/5g-ota-bench.png) [PDF version](testbenches_doc_resources/5g-ota-bench.pdf) | [LaTeX/TikZ version](testbenches_doc_resources/5g-ota-bench.tex) if you want to modify to reflect your setup + ### 5G NSA/Faraday Cage Testbench -**Purpose**: Faraday cage 5G tests - -Note: obelix and porcepix are both used in the OTA testbench and the 5G -NSA/Faraday Cage testbench! +**Purpose**: Faraday cage 5G tests, functional tests ![5G NSA/Faraday Cage Testbench](testbenches_doc_resources/5g-nsa-faraday-bench.png) [PDF version](testbenches_doc_resources/5g-nsa-faraday-bench.pdf) | [LaTeX/TikZ version](testbenches_doc_resources/5g-nsa-faraday-bench.tex) if you want to modify to reflect your setup +### 5G AW2S Testbench + +**Purpose**: AW2S tests with Amarisoft + +Picture: TBD + + ### 4G Testbench(es) **Purpose**: 4G/LTE testbenches - ![4G Faraday Cage Testbench](testbenches_doc_resources/4g-faraday-bench.png) [PDF version](testbenches_doc_resources/4g-faraday-bench.pdf) | [LaTeX/TikZ version](testbenches_doc_resources/4g-faraday-bench.tex) if you want to modify to reflect your setup + ## Pipelines ### [RAN-Container-Parent](https://jenkins-oai.eurecom.fr/job/RAN-Container-Parent/) @@ -76,50 +78,48 @@ Webhook - [RAN-cppcheck](https://jenkins-oai.eurecom.fr/job/RAN-cppcheck/1664/) - obelix - performs static code analysis, currently not actively enforced -- [RAN-gNB-nrUE-MONO-TDD-Band78-N300](https://jenkins-oai.eurecom.fr/job/RAN-gNB-nrUE-MONO-TDD-Band78-N300/) +- [RAN-gNB-N300-Timing-Phytest-LDPC](https://jenkins-oai.eurecom.fr/view/RAN/job/RAN-gNB-N300-Timing-Phytest-LDPC/) - caracal + N310 - - pure performance test through phy-test scheduler, see command line for more - details + - pure performance test through phy-test scheduler, see command line for more details - [RAN-L2-Sim-Test-5G](https://jenkins-oai.eurecom.fr/job/RAN-L2-Sim-Test-4G/) - obelix (eNB, 1x UE, OAI EPC) - L2simulator: skips physical layer and uses proxy between eNB and UE - [RAN-L2-Sim-Test-5G](https://jenkins-oai.eurecom.fr/job/RAN-L2-Sim-Test-5G/) - obelix (gNB, 1x UE, OAI 5GC) - - L2simulator: skips physical layer and uses proxy between gNB and UE, - currently only ping + - L2simulator: skips physical layer and uses proxy between gNB and UE, currently only ping +- [RAN-LTE-FDD-LTEBOX-Container](https://jenkins-oai.eurecom.fr/job/RAN-LTE-FDD-LTEBOX-Container/) + - hutch + B210, nano w/ ltebox + 2x UE + - tests RRC inactivity timers, different bandwidths +- [RAN-LTE-TDD-LTEBOX-Container](https://jenkins-oai.eurecom.fr/job/RAN-LTE-TDD-LTEBOX-Container/) + - starsky + B210, nano w/ ltebox + 2x UE + - TM1 over bandwidths 5, 10, 20 MHz in Band 40, default scheduler for 20 MHz - [RAN-NSA-B200-Module-LTEBOX-Container](https://jenkins-oai.eurecom.fr/job/RAN-NSA-B200-Module-LTEBOX-Container/) - - obelix + B200, nepes + B200, idefix + Quectel, porcepix w/ ltebox + - nepes + B200 (eNB), ofqot + B200 (gNB), idefix + Quectel, nepes w/ ltebox - basic NSA test -- [RAN-SA-B200-Module-SABOX-Container](https://jenkins-oai.eurecom.fr/job/RAN-SA-B200-Module-SABOX-Container/) - - obelix + B200, nepes + B200, idefix + Quectel, porcepix w/ sabox - - basic SA test (40 MHz TDD) - [RAN-PhySim-Cluster](https://jenkins-oai.eurecom.fr/job/RAN-PhySim-Cluster/) - - poseidon (jumphost, `Asterix-OC-oaicicd-session` resource), tests in - OpenShift Cluster + - cluster (`Asterix-OC-oaicicd-session` resource), tests in OpenShift Cluster - unitary simulators (`nr_dlsim`, etc.) - [RAN-RF-Sim-Test-4G](https://jenkins-oai.eurecom.fr/job/RAN-RF-Sim-Test-4G/) - - obelix (eNB, 1x UE, OAI EPC) + - obelix (eNB, lteUE, OAI EPC) - uses RFsimulator, for FDD 5, 10, 20MHz with core, 5MHz noS1 - [RAN-RF-Sim-Test-5G](https://jenkins-oai.eurecom.fr/job/RAN-RF-Sim-Test-5G/) - - obelix (gNB, 2x UE, OAI 5GC) + - obelix (gNB, nrUE, OAI 5GC) - uses RFsimulator, TDD 40MHz, FDD 40MHz, F1 split - [RAN-RHEL8-Cluster-Image-Builder](https://jenkins-oai.eurecom.fr/job/RAN-RHEL8-Cluster-Image-Builder/) - - poseidon (jumphost, `Asterix-OC-oaicicd-session` resource): RHEL 8 image - build using the OpenShift Cluster -- [RAN-Ubuntu18-Image-Builder](https://jenkins-oai.eurecom.fr/job/RAN-Ubuntu18-Image-Builder/) - - obelix: Ubuntu 18 image build using docker + - cluster (`Asterix-OC-oaicicd-session` resource): RHEL 8 image build using the OpenShift Cluster +- [RAN-SA-B200-Module-SABOX-Container](https://jenkins-oai.eurecom.fr/job/RAN-SA-B200-Module-SABOX-Container/) + - ofqot + B200, idefix + Quectel, nepes w/ sabox + - basic SA test (20 MHz TDD), F1, reestablishment, ... +- [RAN-Ubuntu20-Image-Builder](https://jenkins-oai.eurecom.fr/job/RAN-Ubuntu18-Image-Builder/) + - obelix: Ubuntu 20 image build using docker (Note: builds U20 images while pipeline is named U18!) -### [RAN-CI-NSA-Trigger](https://jenkins-oai.eurecom.fr/view/RAN/job/RAN-CI-NSA-Trigger/) +### RAN-CI-NSA-Trigger -**Purpose**: longer-running over-the-air LTE, NSA, and SA tests, triggered -through cron job at midnight +***DEFUNCT***: longer-running over-the-air LTE, NSA, and SA tests. To be integrated into RAN-Container-Parent. - [RAN-LTE-2x2-Module-OAIEPC](https://jenkins-oai.eurecom.fr/job/RAN-LTE-2x2-Module-OAIEPC/) - obelix + N310, nrmodule2 + Quectel, porcepix w/ Magma EPC - LTE 2x2 test with TM1 and TM2 -- [RAN-NSA-B200-Module-LTEBOX](https://jenkins-oai.eurecom.fr/job/RAN-NSA-B200-Module-LTEBOX/) - - obelix + B200 (eNB), nepes + B200 (gNB), idefix + Quectel, porcepix w/ ltebox - - Note: like [RAN-NSA-B200-Module-LTEBOX-Container](https://jenkins-oai.eurecom.fr/job/RAN-NSA-B200-Module-LTEBOX-Container/) above, but compiled/run from source - [RAN-NSA-2x2-Module-OAIEPC](https://jenkins-oai.eurecom.fr/job/RAN-NSA-2x2-Module-OAIEPC/) - obelix + N310 (eNB), asterix + N310 (gNB), nrmodule2 + Quectel, porcepix w/ Magma EPC - LTE 2x2 and NR 2x2 (non-standalone) @@ -133,32 +133,22 @@ through cron job at midnight - asterix + N310, amariue (1x UE), porcepix w/ OAI 5GC - Amarisoft UE simulator: expected to be increased to more UEs -### [RAN-CI-develop](https://open5glab.eurecom.fr:8083/jenkins/job/RAN-CI-develop/) +### RAN-CI-develop -**Purpose**: automatically triggered tests, mostly 4G, to be phased out and +***DEFUNCT***: automatically triggered tests, mostly 4G, to be phased out and integrated into RAN-Container-Parent -runs tests: -- bellatrix: runs 4G/5G simulators directly (eNB + 1x UE + (opt.) OAI EPC, gNB + 1x UE in "noS1") - -triggers pipelines: -- [eNB-CI-FDD-Band7-B210](https://open5glab.eurecom.fr:8083/jenkins/job/eNB-CI-FDD-Band7-B210/) - - hutch + B210, nano w/ ltebox + 2x UE - - tests T tracer, RRC inactivity timers, different bandwidths -- [eNB-CI-IF4p5-FDD-Band7-B210](https://open5glab.eurecom.fr:8083/jenkins/job/eNB-CI-IF4p5-FDD-Band7-B210/) +The following defunct pipelines remain to be integrated into RAN-Container-Parent: +- eNB-CI-IF4p5-FDD-Band7-B210 - hutch + B210, nano w/ ltebox + 2x UE - tests IF4.5 split over bandwidths 5, 10, 20 MHz in Band 7 -- [eNB-CI-IF4p5-TDD-Band40-B210](https://open5glab.eurecom.fr:8083/jenkins/job/eNB-CI-IF4p5-TDD-Band40-B210/) +- eNB-CI-IF4p5-TDD-Band40-B210 - starsky + B210, nano w/ ltebox + 2x UE - tests IF4.5 split over bandwidths 5, 10, 20 MHz in Band 40 -- [eNB-CI-TDD-Band40-B210](https://open5glab.eurecom.fr:8083/jenkins/job/eNB-CI-TDD-Band40-B210/) - - starsky + B210, nano w/ ltebox + 2x UE - - T tracer, TM1 over bandwidths 5, 10, 20 MHz in Band 40, default scheduler - for 20 MHz -- [eNB-UE-CI-MONO-FDD-Band7-B200](https://open5glab.eurecom.fr:8083/jenkins/job/eNB-UE-CI-MONO-FDD-Band7-B200/) +- eNB-UE-CI-MONO-FDD-Band7-B200 - hutch + B210 (eNB), carabe + B210 (4G UE), nano w/ ltebox - tests OAI 4G for 5 MHz/TM1 with both CN and in noS1 model, MBMS; known to be unstable -- [UE-CI-FDD-Band20-B200](https://open5glab.eurecom.fr:8083/jenkins/job/UE-CI-FDD-Band20-B200/) +- UE-CI-FDD-Band20-B200 - starsky + B210 (sniffer) - Sniff MIB + SIB1 of Orange, SFR diff --git a/doc/testbenches_doc_resources/4g-faraday-bench.pdf b/doc/testbenches_doc_resources/4g-faraday-bench.pdf index f6f7b078e67c61044004c70a92c729b14079cafc..ad92b8410a26bad71f6bcd69fb04f0cb9f0d3621 100644 GIT binary patch delta 119 zcmcaUkNNsM<_(9A^BEf$8=4s!o0uCKX&V?$zHnR=O=$Dy#n!YB3Q5OJ{Cm@yp delta 109 zcmcb4k@e<9)(z`9xt_5}@Q8DWr0ME6@8sORlap~}oR^WVftjw6Nr<6=m65TPfrYk# uk(Ge~$9maH1_lPz64!{5l*E!$tK9sQ%(O}dBLgGs8Y(|6oxUc6Q5OIWF(0G= diff --git a/doc/testbenches_doc_resources/5g-nsa-faraday-bench.pdf b/doc/testbenches_doc_resources/5g-nsa-faraday-bench.pdf index 1b921e64370954673e45ed70ff1643462f4774ed..dfa35a3dfeead92a94073a55cc2a02b244c9e497 100644 GIT binary patch delta 9469 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 delta 9408 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%0A@0iC*}1sbiA^m}X6vp_ z2eGBu7c%MR{{c??B2ykhJT&Iy8JYCGE%7Zef*QDT0CY{(mS(RF#4c)b>e3>HmlU?s zWKx7HZfA2Mhrs95O2+1kZxRx^IOeDnSNrM^dCSc7I~SO6Zl0!J#-|P=%i@E0^w^OO zt=x3n%QyT((*Bug3lVHDQco|xJhHwr42a3Ue@mtx7MzB(w|uS0>4?KPMKP$~X`6(3 z?QFyPY=V5b4p&7sx(al;AV&v*O=mIe$vc=DH(tFU~bAk@((tCN+0q?ceifL(X*O{ppYv|d3{Aj}r z{fO}bxiyD@dogs9*l2L;)>WPpT>ksy_PACRK@XeL*OQY0S#%$_zzSn8=+~-N-y`SD z{{=RVx><(v<(#Xh<_s&`h|`3h6zS02=T;Mg5PCHCZZ;=GJtO@pbkkm+6F$Ts7S%N3 z2#wmZvb8lpNw~Y~@R$`C5_Lr(g9en5iCMul z{QVOv(Gg*O96g%zU0@SyO5K2nZ}tlD^TK%#uV2&8tCeZx0>z}hjtTace~xw;yxVq| z^9U?D=}B5i$E=CakN9BEeG}K^`x-|BEiH$pso4Ow{qdyY?{tIoiR<*h^#uC$8iXRt9{ z8$A`(wP`v}OXl2UafanQ{fP$K_#Wv3$ui#NSJcbSY4jG)+AX}lw~vW9@f5N7*B*(` z5=qBZm&weJzqR+zE4}Xdah_gQeNlR7cKd(@X9QzQKN|7~jwVX1;!pxK8!K5VULGNp zp1R4Lhm4SW3bJc0!!H3-fB&FE=eUlJUz3Fdhz*cPh=vMq@MnK--CBVVYX9_m-&Whe z0f1bdbeo~j_!tt|^tmYqHc*ianEHemz?u+hBLke#5MG}YF_XHWdB`=w{PXI)=&Qg ztj4WdN=olzN+= z=J)TikIZP=cbUVm7{+?VrtMHs%Zo#%`%x@^P%hN!2ALZbU<<>Y#Dud5)}b4+P#9sSd{b%q&n-b8 zJ}(FCo_*^6S-N66^{1LLMZTNT32SG?=06o7-QxBbh=|?=lZ|>mAdnd?wo_Lk$86^o z7u!zCb&eFioOR#ppRt`AU+OffF1Px3Kg!T7cA*n5g@3;_(=R9}Dk_M@p+RQZ7s+&-;NEs*dUIQMeA9FI)4GYaJU- z;beL`0++AJE#G}x-=j9P>RR}xx+}imofm8J&>}K&a5@L-Qd>2~b2b!kL{FH!ny%#- zd<7l7->>eFtA$l+zai1)(W08_$5{Ug4FCv3Jl$cYMzUNCI3h=zL{(*8+XQa#Pr{t;QD%UR{!IX!nEJ?Z2yY@MQ_7BjbVizWg z*B80+?%q_b{rt?Y>c{u*L&)DwSDX5B^2=BioVI2A(^Fv6#UhA9&WkIt^d@K!s>o~i z?gxJvAK1Uno_7F8z|<(+jeh7A!O-H1S`Ct5iOL8!rb=i&oc_s7CgZ|6tMk?Q!X(Eb}7rXifVtnZl#Eha9^=t!;}LKvPBaav4`v zkr=a+;z)X^p?G+};aP-H-gBHm|3LGLHz*B)Ob66WrqrQ27|^RW6YhOs645>XPrAvt zpNbEQlsE8@R?Yikr=ge`UA9Ech7pGq@rNTunr1)*(sB$=@OV*dvvo8lF|D5N89;=> zmZogG*1fdO(_Co+>)9}qlh2=(&c$Pc(!XFanjJrQbLQ1-%>zH=hQM8EBcysQ4n>*z zET+0#Pb`ZriiI`H+0i-5BKWe_@|EgTZwhphl1gw2P6{>ZRYUZ)ww?tW8WqMW$fh-Q zG~y+ud_dr`V9gK?j#O|=kqqLn>I9?&O#ZEiG6>cr2he?RF8!jFv%7p=dNMmK;ehc^6SS%$~lCO1Al~@A=y^ur^;%zRGzRq%4jc zq4Po1xO~?PJiW!XYb76Pdb#O7k;8CTKKG`ptXElYZ~3S8sHAizNS;uFoXus62g6*X z$KAT`j;~!qW$4&Yv9!P6Sr!_iRLxmmrk*E=WWe?XY~dYkPx3EqBNmVu+mB~l2L%>j z<~j5^cNoKFD376GQQ@NLYC(c91>}7Q#AoYF;qAie($k-|sY@rVDlG#=0r~j;$kPmu zwCu7#_i)|p-G*uzL6(?j_NU6a+$!v&2PDd`dj`-!WV<4X*UKpo+XOvl;=d@a6FU)0-*lH0>A8wJ0ln)chYwgBNGyI zbmfdI+*9lW8pv^m#xIlCTvna<6hoDQEyVMSs7T?n)8tB8${n)yUsW8D7eyDQGBr&w zOTw56!0mmI1oS07eD;7W{hVzIQ!7^Hd#WLlh;CA=;)9Q7%5mKb3IMFh`lnq58m`9{X0PYUalTl)|zMdhw`8Oi)>M_Px+zpZI4eR z*aJ^~2YVw5T<*qU8m?Rpn1(Dp?2|@?j;W{0Ijlyo7AH;9ZUird&Ujl6f)o>~t4HxO z_PC=aKElqUq=wrw!r{bx&Oci3rpYsRo=sOcVEy6?3eHH{wV}q0Xf%Ut&;Nbk*$0H@ z*2Vc+4Qf$0y{6L9^F1Q(U61yBS;jxfsw_>`z5&o6ejeR=d~Dm)SgGDC4nKoI8*5F8Ez z90A$uKA2v0L*m1!jHKSk$i0ZT2@rUzNQl-ODx~KtJB7*(Lgggc0j*_dBm;>S5vBF~ z9$>NSz6K#}<5QtGlNwu?Wb>tBjKxH5%ER`YGb)}(k4jHo4`|Q2iGKT#HV>t-OV*(% zg#6PJI6BLC=MZJyqx!g~*oH>Pe!gT^d41Ed`#aB*apw-4g|%yHscMW@eDirwRe1Jt zt~Rp23wwTB&+O!*K9pJLz1JYKz`wCG2%&&2LiYVgU0y()K-`fvrMIx`yPKqPVFA_I zeBVHiUH^pkx}q{q1+_8G_3LXNSAyv90R%?MCi;DHp)1le_bRMQI^0Op($kNEGwy~% z1IQbD$FKb~vl-z#T$+BT9#PS~EM#lj-A$%cVCrGanB1ZR!-ig_>EH1>G&B5PDZywE z5QQ%!$KU0S1BEU`RK~f$1lt4MN=InP9Z%#xg>IF*xROEX-_)};f4>cjy2HNYx++wk z^))-9mJGQ5e|+p#iC26*%xaUP|FrmYkD9!7tGZ!^C=7zkpdWf17HxOKHp}bK?XrW6 zCA*lWCK>|G$_ciqSpo z*iDi&soVA{c*L;)(^|J$pWxi!hl_38m%N>P%bQgiMloqfo{=`B_%HRtbVN1-HwqQJ z!<3eXdpq7EYAjE7SSl2RnKU*({%JHS8voJt{^X_sx=-hBzJ7a^ICRlL6&72d1$y{0 z(@KB&^3uC2fPU{tFDMrVR^l#Wuf}j0|Kd=_QBs-Dr5LPg!up_bg=OrNJkzKHo=q<9+C&Xa2mrlP=B90otpePynR{y6ZD=$;>q>0@e=OKwlTep-H&cPPZ{) zLBGcsUI8UKLCa z4L@`Md5*5#{DgFwGbYw#B97hMea`arPSCC3hv7e_b5;(*-i-Z50h z{|-G?Xlktzm3?+yP#RVb_yM);Of5`@0!C5Ii`ILlxjtvt(4Yx=LC${d<6Z@#w!qk> zL22xav);prj9tN00ACTY*!x`JyYQAs-QC!~5Ym1_vHgu{y!QFJ*;^t!O^$~7%JXV= zV}{;^JthX_jO~)n4i+bkkSoVqgVA6;e~+WN!;gEW6EGK=t(ThoV6>66*3r<%WW1pJ zWpMqvx9DWcMpkilcJt3C>Y}H7 zM*r9DpBn_D<)MrUy;xJq*G2)Liq=VDC19~>NRD#k{+=Y$(95)2r4#vzJheqeA{FiP z#c9KmE*DKaA%#t5zak=p)hC8TdQB0rRAIlB?N@(CsZv2G>NT7(Uz@U1e-N0DMK06o zgp=>_Zb1U{4$vZj@F2A8JfXis^BO)vW~MB1;ceMILZo;L_Aq?bY7$0t46>v&n~rLS z{qgZ}4ILBBv(7pbS!=yIJN8(cQ0Fnn)8)9HddlrM>N-(0h{+Cdt?_sAVuY^??=~$S0kOVhAX{-7yMMy0`tPrmd#n_>_b?VFhP>{FSU$LPLONYvt zz5V$rqH!saa5B>DNygsha$l<={A4+%LA}WWyMy&55(j2$mP^xU+{T-KowvC{_sB;i z(6f;5zS;;wbj)v8W#+Cd%d<-grB+hT$^pAUM_Zid!5zN5cG}FXv&X;0_>|wuJL$e+ z6uMs3AKzVOqQv>?DgF?rqE$@09+(&m07!`oNZ=qTDf7zqeZ7*vxWlFTCgRDIV$ zC}m~pBNsy<8h)tu+@1PE1?G!NPHVpco-{ar~|#wxwn{4|8ImD=9_7|0xiX!)Mc`R|0p@_vq; zay~1d@aT`~AcV@b2S|8N8ZaBe2I9N-5~u2>67O|H9U>ls89pHr!z=0;^Po>;2*kVdOi+?8n_wjR%h126ZL&q_Z3(#l}H&vhgLMt z-1nC3Vw}|<{k_J@b8-ss3KoU^8c6s&6E4OYO~ar4#UBP~%AjH6psLA<`xxM{i)iuAN#~wqQw4S>&P0s1=!e`3mpKTyq#JhgIXn zdwz-q=URH!&$}z}1~4|x$iCFQ(0op%jJK_optxSgT{v~1?)i>W?*v|z=YAP}=(V}# z!BLq)(*Z)p+nW!Z_B{7BI~&XjJNfvi=SQljy_MmkpJG!l%na8Y!UvxO*S|Ir>i&S{ zLKNJh*XY0`mJZ(T!zYY5CX$?TZ4(dEWvzIQitTJFpoaOSrL_yaGhV8_=uOgQeMnO% z_$ZlL@}FbP$2mXNgSOo_OBWm0)` zHOJn3?7^UEq%%KwH97x$22`qSSDK0qU?e(b9}Qg4XuW(^jO z&dp8;P~AdYSJV(W{%B-nz;tQBz}s8)h48a=>i^N|wl3-J znQ^<~PiB-^iWFKh`8Lzy)@A2zI?9k7Gs18SwpJ_mrN`7+B93%Kh4iS*u{cfD{Z)Bd z2Pw@gbmT_Zf~m2$Z^oD||D5kl)cS|_8RI9~p6&04_io)Pd0Ue6wlH00ndC!R31?+N ziDK)TyZOX(_Zekoq+Ki1e72O$5~tb(%kL4*Tnb9qX$ucZawqBoyUXf5xK%n@_x&<5 ze7zAs?V_~x#TXl=Qm2cMEm)QQi z=e%JqWzBYy#?yDFc=fzsZ@j&OQP0q8+ac#GV@(A+pZ9-Q%G7Qg6V0?P3QMvVPE0uN zcQASBKlD28K03O0`QHYt1$6jUEp^LM>!CHwp~1vK&SLAe)Sk=fnL%2@2KRKE49rzW_T@!}fhSN879!lO93DBR)=w@h-{hXv4y zDP1rTl0<@M1Up43gITCa0vWfV_q`btS7~N8+-Q!t#`7X?i3r-}^30!?ji@ccnm~5I zUq`|p$_r$}Vn#dV@$SsQ2$lH}UAz~XM7*dDAGzV!QS{Tu&{-wS7PE+{B+23;2KDpR zOppBuu^|-;ymu~@UMvYW`6|g&so8|!$N@>-7diae-u&GfO{z!r-m3)UsO0_kL;Yy( zWEchb2zUc9ZQ?+J5vg8T3h#$G+Zg%oQTnPeeV*Q&s_jz1XY(;>hv?+n=g)rknNi6Lm3B_ z*csk=zuU9ak#ulr2Uw8pa@w{PJ=v`!9MSMY4ToBWzYdC^%`{e^uA==Cm>nTn^0~&8 zikM((0%Nlx+DzVr;l5ZfRZ{B~tp7Ltby+y$@iQctFg>08&#wFjMxGyOsyd*M%$Rwn zr&)CXy)j>AQ&R8S089!Z7&?IH04Y(%9xZ^sp1tSJO|E_5mMPJ$ZoY2r<4T2>eD&qe z0|_h>^LimcU&RZ?sP~j0>8HPAP;L`ylMpA4RQ-8o3ge;_YwF$c++oSB#$_C0QBHf3 zXZPwY{bGnzj)3CsPoLK;u$H_|Otqm32znqq3z4aN79CCwziJ4VFWSH*qKp)~Vot#1 zBU1l&1oV>Gr^~#5JUSHb_Jehtm)-ID7ew|i|3t!}(7J(d8#99pCSmN(TKzn`dC4y| zP3URxKte;wx{YX_@TNAFVG8jNadN1}u~&z2%<#pRyG<+~oiEVauQENJ?Ni^E{N5Zq zb>Lus_yaQpVFZ~02!BK;e#!yw(9WW-+4U1tZ8ozwV4eb&bkW)bRddO$Z8C*s2YBjd z)L4?%s{$BLI#z;kID{s9G8bA>)4|W6W?)R&nLUQ16kbj@) zdAlp6SySZnql~pLLJwzTBX*oSF4MDMk4p0BuiBD~D#GI%COnFfZ*^@IY=4kK=JlLM z2pa9o;$#9*H(O`b_*^b&iraAIVI2kJqk?kcE#0yV?S+JkOf zt!7SG2lNgzVVH2&U?3t=De67ic`fF}wlV2h`WHSTcxkJ#jfBV_dwjFj=J%gLV&^|I zIkdBuC(oX>akLm)KRcj6!`$$aD7fd3t&#L=E`_89b{tn@n-{NR*S8M_^DF> zT*mrpcp^b0eWNii1XAjF{FQ8SWbG)rk!-<(0zG1-v^!y+A-JP3|FbFyL)>9%PFuA= z&m3u-H~oI|{v(#0oXb3uW1A?f#hTXlys}+W3kk~bYitxrkiVjIM;tZsO8?ESa%WkM zrBEUQtz9$w#OXe8=n~Kohs*Z`DYPkV!k&fP6@oyNNh`N@D8~Ec$bvcg?&GX6+hPu$ zyke2nC%56Kf+cJR^Uz_`JY65b)w+);IrSeqC@1~LF6~39LO$aNcOL547qy|bLHOC@ z!|Fsm*NM$LiJ%37_>m2Mtrku^nEZbDCldE2CJkLnQ>CyuP>iQaf-QU4(m|ef{bZKUbK`o}3>|0mj;;%&z^dsn6Yb3H-z;X0?k(o|C!pe|s&Z0dn_#aIuc zRw}X!Lwh!QV#rw!!tr>$FH9*|XGMuUjjwxH-eS#B^<#&*sw5^&3d+ldx*dfI*gJsM?l2yO5~)sqQ3(_3Y1p;4@(wY(S5K^=DROg+@wWvq8=OBE(QOt7ZyRiH3-HU|;{ ztad3~6M3Kd19?Op4@T5;Ge)7bSBrtu2=c{gL<5v3A=HYejP%+%`5j=V zgp7<4K1p>;qMo=oWKF+#dA;Y1LBy(wmga*5ZHpq)9*-Q`l@N^ zfO*eY;Tb@(5Q5_O2OdM@S)|CT1srAdfre-eZ|R2Y*VuP4UqR6KhXAA;^5aWx_#|0Y zW_djiU&t&#!u8{TJ?k%8!X4TLkra53PnqP@wK_iFeXKP@BP*YU*P$@ukO^u3^d!{%S?NIL>< zGDBE65)2#Lbp>|zbD&cdHw&IdZu!&Xgn2;e4xeXfo=_Rv&zfZ}64Ana8BEle+q9!@ zg=sFj>iHI@E%x4BSb0eGT3EM9P{P(2g4Ds8)P%RxZeTfHQ#=uyC0N2JT%O^quWNl zCGGonkS#`1x4v94r`XE~XzhcMJYc>av;gms8B{psZ{OC{)rnX#52|4#Tb^*6x*1A` z=(q3P>t+0i`eH7?XX4RtyxRop;6{U<6909>+bR**hvenOa`rEXawN1(HXSu7LF~#Z zu$l9?eFYf1g1uzZUsH7xwf%7Jf!>vRGrMLQ=QC}kF_vpD2aU>bbuhhbxy{;rHh5oK z`i~zkUc5VAapTx4AV(s?1v9e<5r))jR8f><J_3 zXi%eyTlAn76KYTTCwIUjxy)fnq35XajN$W}Bow46B2 zE3w=Ek4=Tmsh#iM(*aNGYYiPP{lp2aSo^Vd8Ba|u&|Vp@M*OH=pR4K!&k(B-s%~aK zCxwJzah^wyZ2{1QYza?DMH5WkN$HLXW0fp|UQ?AU{@54sbYd`Wa`}mr3zu%1|IWn5 z>l7M_qv>;%N6*6@Q|Q(E`_s8xV3FPhFcRZ;I|^EdzoGl;?AoT#YnskWJY2b74UJdJO63bKbla(7tjwcp3@j;}8J z)tO6V^neIVdQ8JD%{n^V+PRpF36;jB+NXNAOO7k@aTqYm*&+5Y z9CK^#<{Lr2)twv0KJ@liJgl1y!*{DsTB7Nel9Kka_~UieQ~3hV>P$ zugR@{{mO{Yh?M*EbeVCYdLEt(dK^#Uw2m=<;Kj8w?2!Ty>e0a0cD)&Pd>lGDxID}7 zrJAz3)HKbH9Qtb=fs}XLxKlP7V_MH=K0V6JOt*if^sRe#z5Dt8<$1c;Q^w9Zw4V<7 zGVdDZ|Go`6Y0Q?wJNIO=^l$%@Nid;8M^f}4`G9Oz2+|(BA0JMgd4z3ZhRYXPHu7W! z#6(Vbj{Y-xe(Ddlt(Pn%=&iRnc&g97d4kS%>th(Juc1NMf~QM`(&F$F0P@YXsLQp* zi$^qKQ?U>8;jLH^xkCjcZQ7KToW5VmleTO|z&><{KG-{Rc8ubgrUOo&#+KCygm*CqhX_bgD_Qy)hSz)w$0yc`+{=n9 znXS9m@5%TmJ$pVE5Xs(MbNb*wwAY(?*H3_G0V_BX?SAg)>b=6N-e~x5j5?%ed2O11 z`~s8<0(IHl?_Qz6Ikt&K@&I&Z9|i8_ZhCMWo2*#*zinK&8ewJ-a2tSoFhY2ciDEUB ztiSCw&G?ni*qIY%XN2nAY1Z?@iAn^6Vy&OL=!v%(wEnMu1b|&o5HxRah^N+ZOBu~) z7e`-tcvS2QW$-nQ&5;osD4;A_@A8k!cUoP7A*KS;uXNvp(>tGD41}}#?5dBg=PzIL zv(4LfXec<-hQTxwAXc-S5~;%(JY1!mpPoH3;B$SfaKgBI!!MT4Z5Y)z*dufbi5>s? z$c;#72&5Oi+sIg<(Svk*();94n@BgNoo=hh@ZNViKUB~eGM*^hDzE3h^4acRrIf zpsSUw^UYMpyrAyqW)={T>;Yl+?@m^;b+*OE#0D!$SJptTtmIDe(1@Tb6xi~`wxE4U zz56N)*Yn+>kb_yCsPl~rcQw6eVCeYH%LfxLJju(OaxkMb=qZ|(^J3i_KDV{TRN+(! zSX)Lmz+^(8%C)!*(p?Zwc^aH$Y>|AyWt@CI0HLsec`4Jy-V;CKw=UqYwpl;>cEnh$ zp5506H8(F}R0&mwEk@fEwWhtoH=gekrjQvOIY@aeTYjA#o8aH_Bo5Iu0m2$qGO%MbgqFCr_Kr_n;taaXD=?gyB8KByaiCX zgWTfVX%JWUU3u$u)(qfCx8Ki8L)rskt?-}53sTkS)YZATo-k!(90t(1#)7zE+HWnQ zV(aYM%Iv~G6dpuhe!}l!GVfC#cICA1uY6Z_{@T1Lz15qhTUi2I=(_Jc+MQjFIV=uP z;;VijkT<}+kyVX!#Y{UhQFRR#(B_Pe)*t`RuPT{vq-MZ5*VL%|gHPeqFz3cqJZFso zIE!_+;(5M7%9)rmxq37r?|8YiSl#nPL&6P3{3;IThr6VtpzZO6_h3=U7oo>9>_V0s zMM5lAB#3dSR7o1^w~0osi#XU0l-;ZbGVC+H4&w5xI#@Mbc=AbUd^$S1vvUXD5B#?6 zO$s*azxxH~ij`~ZjvqflmbIU9wYjnq=}BcZcixsN)uS|RI%=5jHq9j$XOhX0A>>7~ zssOpuBDN>ldUm2v5DR$yd`1wr^d?zmmb80ZN$CGh$kvuFJv4uxHTKld?-{S8&*oJ> zc%JATZ5KPKR`&{nY7DS!)J(RGGP;N1O05JCF@M%3lLAyAn!>gLvKL=-zxCS?(kjW`Z#d2rP96o$z=SW|%X}Llmd3l*u{5xhWH@l17LIGmDfJFqLu~Wv5?R7Uhj7~TVhVAXPHOTGC5y@iV*eU(@A)hw;bLw)^ z4+|Q{uBThiyq64O>}Z3peTup!STDQhSHH@TO+~vmn|IPyGKQ69xDexD@IS5C7@W=y zj6S)t7V`KB5)3GzB?}ZO5zdENam7F*x>-T1j}WfQ$1OkRc_~5EI}C5?Kr2mswDwq= zIvQxTi~m}H)#wExSJsf%O%3DIWHxiWLjozDq( zw%zEU@_Z$H5LL)yb4t~D*pjJpPR$|5If0p6eXFk3BWuCq)K0Rl0Ac8TOzr$R{;Xjq zTJ0Yy&y%a^yH_*}*GIuv6ms~bO%8>!h)YJcpmNthi=n62%Ji4#t^;5{f;zJ9Z#bQ0 z*v!84Al=jre(^^zH~_?91h*!n(a#42E!16EE!!`Ry$+?1Z3{-5-EDTmsHCu!f|Fvk zwIzDoEn3bXLa90Wl|L1rMJjTaDiZ7xq;RtB0a|mnU$N#1NA)SvD7dZM8c4+;A6p;7 znAsL+K>IQpxJQ?CCi1aWrk5_=%xK-q_MOd-6$84d_!D!)lWSBLTBdGKj*e06<=gLT zw7m<{Nv0wcbKlDcyZWhH3-i6XGt@LpN`p?~UcQ7p$^C=|p$HKKt@zWmy2QkYvoG%} zE&zYz??DVK%uJwlURdZke=@#*srAE`;S@-JW^E80FHmX)yfiqkir@=6vlsunoH~5Z z2?=sLdlA+O)<^HWHJ|zxJlM?xtARDFR($ctw}g7=eij~Dzgnx!to71YeTbgK5r3Gc z6SCV<;*bv{yB~05GeVzuL=Ay`Gx6Z!TqIwcp_2;JJC8qeCiUqBQ)gsGA}aKm<2U{N zJ8rk7(IA1xkLX|?SKKb3v>r9UQNGLP1a9`na8<(-mW`o|k~JNo5WkrvwBC#5p4tddIJdDJDl4&V=G^dDDxf9p{l)5`Kd zhjF#5Xd6$5II_{>-r9dE+q%r$h(hW-=s>h3uMzEL^CR*riHUyI*<~t2?kJhdI<3$5 z=pX&gw-Q3L>0c9$`_jkBo%3~C+my%}lXz+WnrndgS-A0Ax4tG}9Z!Z%Y>Y=9Jd2jd z^w9_-@6k5@4Ht1tS)b*fsr)8ZBR3)XX9mY%E;dhA%Kkfxb=5dVHoCaaH@ z7CB^0CHFU^vy_YKtM~fT16OT0K~&eGK#xc$gcftQxH41g>T-4^i|?}S z;tUwNGH>RW{`;8^+=HR{^N#5Zf{Tdb7dmt1K+2_Uk`_NQwXE_!T9ahF!z!r??{tq5 zJ*2H8>aBdh>g9}@y%n*VVw2>QlH31Rv3PyX&<~7#rT7U_p*q4Q-jQS1D#X>QWiZR=KkI-lggYJD4$ge3B|{ zH(ZOD+I&=GPh3UOz2u*<&gShA272_T)ML}X%{KS=&Z9kk=$RihHA)Toju~8pGN81N zWG2jzo5-Z#SbPsnc$iJlX1nhmH;hPOE}(A3e?UL7a@$4+ou~|j_rDs7DQbhZ&YmZ( zb1)c%uX5cmRs|M-vw22?tET~%-_V~to1H*o3 zA-eW`JI&D5_39PaQYIx`zYYw-!Aix4yOlkBpq&DUa7^1MBqPc}Ue zPPnsy)OOi>mANH66D6ndddhUB2RELVR zz6&w1x9P;qAztC-K45hR(eSDTbB|Auon6B~;@efw!R(Lel+Ra0h7d7FWqNCOBauw_ zqXLik>%^rwlg8tHh4*^$L7N{cgQ>JD6jds;R*x<&-Wl)06FyagmE+tbiQXpYBd|ru zi&bl@nVG;{)3ru)kZBZa=X%YLIcs`j7l`@lG^cig+n4$H{xOioCo0RXuRw|X7YEIr z$`^m4`X+#_*?d?@=X{S%wMe{Iya|ydl%8UahJy9f?C>`Qo;w-Liw7(>JR__poFg&xR$QF#8Aw;pkia z9_{OHAAb`0lwO=CgXvh+HT<)*nbjVcrC+^tNzaH5h}7qtrV{6_GlP0V$7GDP->+Yg>CdP_ZFT?t zCncd1vz>4EE91gz%p_jaZV_7eYpdcur8x0=(O@9jXsK zr4mM%juWqs)fzqAP*AQEknq*cv)*pJWWm}{fYeB)#q`6rm$@> zH|b#(cCG=N3qf@IV{ob~)(U$QOu0Xq*hMrpvl9rf@1wvLx(j@dPi?owg^;D2XH?V@ zOK!SX&xAxxx_?69AW?o8)HSZ!KUk~;#SU|ir)Ov$JM2%E&1e$Y+~ze?r8@zRsR%L- z*w<(c4THW2KXt+p6^+9^qI898`D;Gz0)Pe+XgU5G8xtYT0kM%U?uTVEQ`4Ol$FAp+ z<|5j8Z@*{rg04tgma{cJ`dLT_c!z+#7OG@wcIq6VFLH=t)0L$Yd#c+5Ec>!S^|CvE zyfMi`Xgi2XntH2kf74{sUZkuyc^6OlM#z3jRmh+B!V@BQ>^a{1eRSD($?>Xb?W@^s zlDC^{Z(=qXZ3Oo(gH_-z2eE-_#ptvyqkqa*R`<)~3x`Fnek(dP)}3Qb*F%r2cyVkb z3TMQLU2zh}kl1<#!<~_C0syn2}m`lzN` zYZaOXC=?P&+7W&PpRA*HXkAlgP>lC~)5!8&sjX)5h}KzqngT_8U+rRwZ+{hAmat#@ zZrwAltuz!ewRP9ezNUgz-mMpGx8?a()W;TcBC`eK@;p-S(#-VPqbb!{T)7#7HrGVn zP!u+|HD5lsy4rm_o9kf)ahPNsYc3}&A$QN9jqgdGGm9jLqimg!C)(01asSq^L4NSV zMw>)EM?@vyR?4ZJ$&C5UOK;vjH}zn`Op~k7kavmwbE?bM@9`ng_#C$v!_TEK-ed4c z+@&qq>z2h1XS65VV&(6ss=Kp+2;@G=4qx z)GCjAY3e>gnxnusOc#-0hA(FfGb4xr@p7qb%RV~t3ZU*`7P}}64&P(#wmtMnG9MF{ zL$ixCy{zH@Y6!a5$L*r!h|LCenV#BPa+y96Qiy5k;>y`zxY%F@Hs5GFfM72(BnVL%0%aunJV!Tuzm*{xWXW~3!X4^qfR&8ls!HiZ1f>g;aO_L z18q&UuvnN0BqR(tga(m!gbNV@=92HjVoYo`pdNLm53%0UaV~dgQES^q2If@ncn=9m zPa8wjy(WF1T2a$a!Xx32brzHHJVePRmeygzOd?kXt-v7D+wytG73OO%GvnkO`&FLo z9=%3{!G)#qv7IU0`bBwZ`^iAD!NDgoeoDdC5 zJzwlmx0y9Yc+2VCsl)GM6g?E`%z_Agw9zYPvRJy(^-R=axw^P)rPiq*Mn76HH#r@{ z<}SD8v6qI(^qE_N1*1@5=kwy|EV6rGRUg#Mjw6uQZ%EPO#4llhJ=`Z)z|xjfjiNiK3kW7d$T?| zzR27fRaBJQx`R86UDwLg4M#%yBN}qcSBy3pghQUZ?N> zo+ZGo1!Qu8p8&NE17j1tuZNp7O|+hho=25|ho*^m@6`Opf!y)({W+~==ch%nf8(KEeLlXF~t7ZsSj@$^QEs15OrF-wOLtE#)ylntHV?XT07Jp_a&i+&5pmm z3K{S*Ub787^(Ed`JBIjdYZKTwKv&#?H~QCr zHES}s{{RSJuNlS+ZHiA9yji(t^ekSXz)1haP@Q*%`X17{?-xGmHad37?OWNPx3+d8vDVm*Sry`5Ade_#84M4raS_% zok|yKV#{DT+VlbApU@#39ZJW{6O+=Lsq;fu#J0qZKX|%6wra9E4?JJr^^9wNm5CzH zkiihhLx+v%Sl3K<$ixo`5QoVzEmk_b|DkLetwV{5K`>l!ZjNjL&XKNdH}qrKf(^;R zq4r(Pt4ricZk<8=EsgVmm8*%tGbL2K0{LgsT6nvFZ)wm{sm#5zO1v*teps%rA#{=s z`kR*D_uiCCL6dJwyi^J$h_Q911f5$qCe8E4n>#3NV1_$G9&7TxOIaZMNDRe%nMvUZ zGSZNBAA@7%eQ?PmqfC9ab` ztIQm@wb_Kz198W|UjB0x2&7PXdB1H{0??=qS{L0IV26YTFeU?b}mCzC|O~_{XAEK^1p6c)a z--}3AQHYHC7-hDkWQJr!WM?I$o0aT6DoUZOY>AA^B_rJ8UX`q}Z?YA_wFz;J-+8~^ zx9{(D9uI#sxb8jgb6(>)+Cay5F0(N0d)1%V7R-9Wck?6P@{@UZ@BBoVX^BKDuEzXd(T61FCusU+~`fVQ&C`~QNi?O=Bhj7k}h6#;QBA(%}}oFpyz zU~5}2bH@F~PINQHbvSrshw0@&3RpYp;?dJDqo+e&H|dY(d;K&V8OUg3{a;J6uTRY8 zMSUcO_X?Buq3-9;3-e|X)eD9}UwCSf_x++lons4E|08xC82B*NDKFi|C0;4bvY7~A z1!DP_?>bk73;C6Hrt?H~i>#6Z{D|EJ*8$V}eFp!LR?$dv7qtgul|YBua?Bz2gNEB5 z!$7aP-|05}=lt?4z2 z<|aQFUeQ*T%p|Zz04F=g;G`?+?<#(s_xsez8$FCVZ{_u2*5~ladb;b9YrP*@z}n|# z`4j)qbI<6Xdz3Gz{GaRf z=~cERkb@}&beyHemEUO4ON7`YM7$>eIhfHNQ79A>m4qy0(4d+a!aLspygSGZU~2Hf ziGmxlg2MJIzgc*D?kV~j_>V+DY`I66@O-!w`|?cLFdlE?`Rg+TMW*#02F*oGpStL&P!EeN}}~uf)wb(-`2DnBIb(Kqlpb!w5tJSl2JN9zQ%-DzdyG z9$Frt_)>?wo!})nr`(UuYB9FQmZAS1RvNoatQZ2Tc8zj7pzCkWk|uF=_s9E%zxm_} zrqSCNKUbj~Wgek}z)E(SQv!QDGJcPm${vpc!UsO@+l%`^rr8(y&=!605Z&i2LBT7G zf)6qt@LZxfkt5nYtTeCa9~a84wO8}}Nc(N?G_Tvg+jOq9$=?rBZ~_Z8qXR9)h2AIB zqRX=KDYNj@^XTTg_SQd-2Ip(7+u7&PzL>f^nzOcQAQB|9m1ZH^Tdxu<1AYUY zF6tqG$8@a%%LvHH%!GI=2JW(3?t0nle@A+(b6qRBcVC2_B{_Ho8c_U1E9OL>&5293 z1fR(cFnZJh5WlrnEH7HEvjb`$nZJ5P*FoM`i#k|1nXoHOk#bDVuh{+lXfT@R0XByA zxPWO==JUF<+`ZF1%I|;Os`EIYmc4nY@Evkd9Q6z{6O*%JH|LZABn2YO^S;KVpJ)Se z5S}VJ?NYi9d-7?|I@m=*+4f~IyMFzZO~WdRBYUHA-YgpK&hL>}l|)0VA((Z}U}brx2fvOvHV>L!pzlw1xD_(6*}I0RPh;Nh6D zd-AMP^WTSx->0_EkZd97=Vv9;$B{VmMi>i_FXeC+abPQm(}Cq=)X8*+E$>0l6oRgl zf&_s}JNqqRq)in1b*JN59+J|PyA%eO({IJ>K1NMB6yh}Ju)_5?o&~Y`Qo5Q6J(C%^ z`0L}l(p9DfJlDi~#jQ842icm}7P-&oMv3$@5Zr@%%rJc!?k)}c4p16c<^-y?m!sHY z2NsfAl|B-p@`US3_@ZjqjUr%c@UxZus){}c7?>VzzHNmNp-^5O89)lW!+T-$E#%Av z)ijt7w|?%`I3E%{6&al%9psqm1QzqCYCt60Mu63ce~0h49Q+uJBA?8ZR@kN5qLChp zFn$#3_&$@+@WD?1%tnigC6fgJPyrQX)v|xhG!x_(1oJCw`fusbx+Rh11m$4=wYnd9vOZ6h`T4cB+v+Bz}GttD5uT6JUG9 zYdTI;>$oKmtkfq2oHxeSq-sU<1CNd<$BuA~^YSnIKMHV@B#6dafAwf&^$f$`*0W#g znX$k6PUJ=$+WF-<9o{wBq*U8<+w_l<3x&Jsqf!8&4l)_uTs%JaasGTPsyZOz{ze1C zL%C@ug6Yi$+#O1T9!V3Edeuz66ZGVzxYV#`y4(rzEzh2y-;oNp^k^Jw2=0NET59Rl z^ga7neRVjs8w&TY?j&Un7%#+Zk3ILl;d@VG70eSE9J>^BSHlBtmDL3Zibb&_#Oye7 zb|cbyz^wm4;HmC4nH;^x*`G(!olTVi3mx?(`|Z|{hDzM2!*S>tD`=t4oZ>jM;a9Ye zdWHbXrWfX+dIOo2Vpo*uW_33q(lnij^I;v zs$T(Z>V|B!ub2>=;_z}z_l{Sg@fpi5gA<=ysy1`$Ow=b}$p~zekd~k%UpK-vBrV>* z`^x#1^U;u_++jyef)wT)p+#hOVXP`G!)pDj`JZU^j==&yavo!NM-!Nuzb^~HSxf|p z7HSK17>PcWW(g2CzInyfqoLW6@`Nq*>gyT{@ACdFi3swz=f-1A$`h8#6xEGPzZ4zC z0ll-@i^VQ)0|X`Qeht)>Z+2zjk4vG~{eY1y_P+EKj+KYhsc@FW;5)$F>gWDb7IRaCu+K z$ZH8Gi!et@^yKmYZ4c4noypEl{77_~m14B9)a_s;JAbXbN_m{NYo6^xO?%3-tDR~l zZIBex8_bVbG(9&e@R6cw=DoRyA0aG1h?BWu2;4Fh7Ku>vB;5hKR-I;y+R=T96z3>e zIBbCb9TS&Sk>5r3^H3)kfSHvM6`*_4R92&K%rX4(2*Y@wuz+DJLocivgExye)3SznkupD17H5wb_ z{i_)#lJvPOC4y_Tc{9Z6n=zA>blq!4ycjO84a)VPviDot#!*+r_}M$3<@p+|6#h0M zayl@TKeXIE+BVAEmOqf&ddr>xZqZtvN`8tEfg?yI0op`Ftu0UYTPWPsaPl7wss_6!XnWDv1Ib&A)fnCRhInn-s6T@`ck9f#`$_`{{#i}U=!K1$NV2_w991-7hXScp_>V$zH~`w;qd5(W-Ps7j zu3868kxPZNBHWQ^(;q}(ckA_94Lo&X(|@hLG~kvd>zrPRLx}Qgv9pr@8LAp^2e9o^G9!p6F*+ww7v<3YI%yx!zt-kOEf7n`_tePkgvnb!+?U zX7cKpc~^hx36QU~#@D%~^7crQvRO1tIS{3V5DzLUNiG^9S#x?&i=e%|#C7Mx46a?VwS~q*4*Jm)g?y*pPW=nGBVvkw zv-AY&{2$|D?%=_a1IoN_czsoKkE@@^@+9FgmNg@LsAp=S0V0RO2Hbw3QbQTYm4VtF z1&Nyg&=L9b8y&6I@d`Q@L?z%qH)td%s+&I$7wtIxG*R=;bbjL8eoI!-xVxeh)~RKV zh6&MRQmaXthq0dij`gLR6ip1{n>o?PGCav4I3Ij}XVH-=YrAW6Z;itoJ@wTnkEfm_ z2x_%^Hc(0upBgfM-gUWb&eA2W8#`bUy}#3QD*%&wC-=^tsj=0i&A%z{E$=7Se6{$p zEe+G3NVLlgrwFHY)nro)@BBe%IhYWtcmkHM)7=99Mz|E}O*&i{sVh_!$(uM&l1|g9 zBOR|V4BQjZfk^;aSQM{u(c(3!+W^%wA%5nMU#(TLov~

qzwF*E(`ZBX8&i)gQY6qB-UIQ9P-k5g1sRt~AEl8*+kVTW>4Ev@*w3Gv4p zwY5o~Sa_u@M(;kE>NgE4-*VVs>8R)dtEirLXTdhYhp%MJQ;`ih_|O0XAL()yoXdky zNU^-Izjx^hpPc5_pw2`JsoJs)U5nal74*cE zC2PW+{aWiZ=2p3caXvj!i&)fsf^?KzoRF#feP-3KCSyB0>JXQ9(ciyzUHyvO(uDLU z3Z#T@nkKW+i5sqzZ7sFlQHHG$0hHwjDJw3vAE%uTzkABFRj`-kEUM$+)y{*Z&Xr)* zdSPdq<2%gqx~Kp6d4|;lJg^8lW-e8dc7LP0)mtYivq(y> z)~W%+T2uG!jjn;URAl1HoM@K40e;Ii>e92%ts~!a>;H_Xw-)`*+O%2u4MxaL=#Rj? z5csVDL*A^8j#Tep2{tqSGI_cw-f6IgK2UD*-U5r(M*Q8sv4=j%?pGi9r18z;9+pSr zh_XDQanpWQne69V8IHT-1453CRO5%)nC(vY)upJiWQpU?RvU4NSFq)XdtGm}Th8V9 z)?zPh`KLp1N-7v1NCZ_F2kB7K6+=#iBJNo6;veKyarz-}mwF4UR0M{<+YS^d)NW~U zch}MmCG3ca^;fZ7(N(-JBD}|OL{a38<$InjZ#yopn_M;!g_Xl*v-8jE>Bdw-Fth!m z)1M>(CB=g7nliU2y{6w2zchbHMa?`C#f3k^uR$ zY!=Dbs_&JQxg){3&#ysbrnPZh&9n8(GszqCeYAsKH?*|ug!c3JLDC8`TvzVWW5A;M z;`(fahfo|y(Kura7AFxh)Ekj2Vfwd=K3&_)G5AI~v6`BW-xT*iu8y1k>)p5{E={1Yrmw* zJD!2U(D@0G3`x#upLdx>bsqX3m#d_Qx-o9WPK~zuqV?jsH%*?tn4@*2tjvWkKRLCN z8xxZ+#mK4{JUk+BT*}MsxMcChfb?OD)75rvb3;mx7?|0(`B9MI_RzoIVfFap+syd? zjuoExhr*UXxG+RSQsK3fmMM%Hrh)jcVBkumg8XO@t^*M{b?=WY#FGP9Lrl|dPxPIM0DHVC zOMENWttR0X>0|j{dbE@!NZb>&3(G2Rt)$nrYwPIRi4$USLV_ix^+#3kx_SK4){?eL z2Lh*$Jx(f_ueQ)zn~+v9q*Nm1d5CUOT2U`8r*=?XA2UjZ(%n4e;Bf{|3g!FGnBKvt zeCmn5#jdpgHGlgHg^$c)tkRXo(0S%GG1&%z(3ua@sKKJWg-LNC-UtTPP%laF2B!>S zXw2j~xLuiPfx|W1ZCsF;ty1-nDbb^-)6Zc|5vgKaKSd>ihuh?7&YWmCDf*5~wUHFo z$~t}lljvivYa%V3z^_$WI}Xc(UG|5xC!3;M)C5DP(q6H~{qu=)rq4=%!uKzUa%oxB{H6R*P#WH$fKH_Yt$Z zQ+A{ZEP|&M^6P@9Ed?^p21-c0K!F(o-=HBq$G$S45FQ1C&i16is)?j-Scpy_$TavHg>+U=uD_xUVm+?9&uXJGKl8=FT3(VtBrv=kB@zdrN*ur_|ggYmU~zu zMm(u`wlLeCzTI&5K}5uZvo01pya#0y8?rwvJXz;lkQ~0@y%ra6{q>u859w*EF1}J+ zIast6>y8VZ!}rHrlm4~0&Ml?b0PZBr}<%N+RPd^EL>u1r2tItZ4;%J`W(l?|T z(H1|45}rCCAcxf!7u(J4HX4l9Q5#Uo&~7iOZh93sS!q?$JF}XgcNsfS{MS0kDkXu> zV^$O;PMRZCDyO(OpYD?gEeGNuq0i{PE!RIiQbLF_G?qw>MVVOIU8A|> zS+A4ay9e&^Gsl{j%AEZ>TriP+YvGTq2IY=Vx@&nSRk2mIaoBcbl zH;>5g)-ES-w%qgd{j{u79ynP}_iZA4Zut=Rp@BEjmT4V|rnn#HE#I<#u9M5zvVD-f zWg9WUb)uq#`E6!?qAK8=zcm+ksj?+X-t%u5OS;_kG=KW{bM;6VG_OE%^@Hd5cKIYD zemE+}CSxwso*#!+Pd(qx0mY9RC{U^Ry!q`qjBf=?+w#__xt$71_dEj+&ZQdvlRPt` z_^)~C0MCH;kOhL4jjZ$$FK2pR_dzQ}OB86cLvzUc1cNyn&^)E9pp#xp;GJdXVlNO2 zEz-?1wu&#AN7-F6cgRi{n`p1<>v6eLZ2bC!^|PZ@*A@R(N>|1b&Bf$fGI2Xqfq9kN z*^z5}U27`U+jQ0Lc>La96Blv7+qb;EtPE9byfKQ~ny|YV9o=$ILt}M|O>wF_qU2QJ zo#m?{zI-hVZlZv5=DRFJB`Aab0K(sqCG@|>NnlJ1B!7fSqPm_(|4H?hwR7K`jV)Jg zV?~E?GDxzT2h1Tgn;~11z{e^w9p?cOe=put-w}%AZip<&eIQ$2l0LH`^Mh7~@`=f`Ia=EbML*Pw6YYLjcxRrlE!z0l zg&v-QaS0;Kh~mZAQ8O|5@sTC!X1k{3<^tc6S26QDEtIbTZD8S87yjNqA8{XL@pE7A z%t)GHA+=>`61@FFR(n{&m*eteCK79QYf=4SBD>xJ%Z%BY#hUtu3Bc(~RYVpR)Z;iM?a9Hx)q^90u^ z!+gHhLs*${cd#QcArg7kIDejMk5g*?NldZQb3wTC8x}i3TSH5&Lkr7B{A*Q8x7%vh z6B9tY%*yJ*g_ay2v*fb$7wi*0cTU)pn@P01au4|QXl3%f_vFk|OroKyx&{RwFqIftrpG3VF&rJS zc$KQEC>TpL%MiFQREGg>e1-fF5TKzUq`dsJzL^=3+0e-|u~jp|!yfJGd-_DNRrar% ziHmupQFE5h($@9*_p;4EcK_JL9%Eh;D10(Wos)!f+SpyLujq5yt+(7L-sSVU(*5lg zA^#?|q(ZLsXCjOcX z4J{-d(T)~oD7{(^7H!c?F<8>IX@+jVTGV5@E0+lCQTNdxrQ0cDDf})Q`}6<*FbC1* z<8^D~^B%m{$cGWVX*4U!FJA5r>z30%7B9OS?ZJiChu++XqibJ_Vs5!tfO|o_04KVk z_4lvFJ#K{Ruthg8Cf7~xuVOGR?k~N$;vguhfvwHZ%3PaMRppfh=8Bgt@tmUz+VNfa z4ah@bH1;Qq;xv;Zx3<@x{|Ek_=d7D2$y#L9v1@#HXW_@G)#{`qQeihbMk#0{u+|Td z4`+wBd##lXx&THV_uBt^ugU9E+#x~R^EG)u^=G;(GuVp2-_%C`^|!XPt3ZbJ=20u> zr@hn0N27RJ7``swH_=t{fqCmBb(UK&S-htqCbwTzq<|{$C=&_Lks#2Baf2KfONc4s zzVG5kW`1NpddGd^sU&@AokQTaO&%LQXRuhZ82qv#@VnYDLX}GOrhf^r6d!z7La5r- zRDN2ISOWRYv!2VC|4IN&FbAftOr`90#FhNAay5#e3iX8`oSz$WiwT;d8=qQW-QkFK z)j)grHuAU+RMpY+7vJAa(tmAUH9vTG@z2+GJzaWRTi{kX$v>K460{q*ikUcVq>i0< zpWOhlqZD@K7gySuI6v$v?q}~Rvu}(hN)rwq%*h3U2Wpoc>`+otT2sIdtt&Tp3oRrx zJ5oZTsi`?|0A#-rtk3x8rWrqf{+b+@%Jenu0>p0#sqzlmdFMa&~GngtPvny8S3V zfJ2>AW)@M_?^&7Sl9@$(6+)1)(CWwr42Bo?uof1cV5sclhV|AVlysn`47NoE^76U1>@7)>`fIy~i(!6ue)Fr*Q0RFS!)?p|z zUxj3z_R0SpjR(?J`IFLCgReDi9xZhldUJKM4IQJ{U+W)E59XO>X1Te7 zP|B$>EqRQ1BM&m zjIWQ7$T$REq@GSp6f}P4k8o4djg+M3c)&{Oe?Ri=&hNALc~Am>i42(i&g+y!!+U9n zcr=-3Z|}gKz{(@m-T^~Pp2S;ujk+=Ey>*C8&T5Fx@%;x#2bG{8%w#@&w~&~qOQ!1F z6mR*JnI0e&2j|U(-!AwCQiMA9uj=$O@ATP56kmta|Gh)P=R7z&I-S?gS`+9<>P=ng zOj!@|;{p^ER=%w*6aat)DVi2LlrO6!Hqb~|gX=_^z`V7R$---1+>-m%!RE_TnY$94 zHlxIM&O<|hoL?eMc$xM21DBL(@u^cGR@SC3U(hh~@vs}1?*pD4+3Mg!1OL6gUMu&J zyTAK;^1X?*_)K;3wneSqh+@>9rlt-@3k#sya?xY&UX(Fz>vw9w^@M@vncm2!IQAuSNl2SoMP-H*|9Dc2GQN4p7@g}2 zEh55;LKh*lG2$ZZMG`c9?4gW$*i=@7N>n(f_3j$niv{{DMi*hmh-eSvi4Cy@Qdwra zE_}unZY_fs5Z71vP)BTtY}a*_=5|4UY`3su;&#JmO;9K3O4joG+@JqJe5+-1&&c}7 zu3bG&{bC(TL^ojeEvF=kh>;Y(ZaeydU3g7Wve?HlxXyhLBY8p}XKh|ccik9^Z@NiY z`}q^t+EUiQ)#qxpv?&tj7uc8Kq)}@brGBBJuVp02z`#7Ek9mB2YISFa`1$enW+lcb z$lty8$+`e&Hm`-ti8?FA0Oz4XYoE!*wx8*w%x-{u0SbZgi@>n~Vl5M4lM7(k`_FO& z(NweEtV?pmXzh2l9s;)pwNXJt#8vg}CcR3{(vd>*20k^~+l^ax8GZ-A`f$XaE<3_LvQB>4gB8KA0ncyweJ zSeF3esCyKH<;1I4C3%4Chv5B3d?1ydfFM3Dv6-w7p zYlNZA=sq~e0{zU{GqS$HR&%e^-maZ0TAnvy6TfKLR_k$3IoVB0DKMaB#(JkCC@N_< z!~F?KOsQAwy>)`~$c{0$eD}9A2!o#H#9?S*_HR;0SVZc{U=kEqFdy6OkQ|#BVqPd4 zfDrVNRFmT*B+RF2*>i0t9oU^hh{zs63z~WSJc80dWgy8N|M>t|X6%2oKM|Nz{UC)Y zyE5jkK(8X}`KO-TGbcIs1{_^{NaQM1isVeAR`l3&cV2XXG5zy6xK1kLxuT_f;?R+` zaW;eGfEVgfu=9S-XmEbIn@ehHH6$*s-sT+b@Niq(L_1+{+t4NN zms7c4O^eutmJw|0Rjt<5l8XA2sZHO*ADFG^Z4fouJsBBme=73w&|{S7g^P71c!zI@ znm6bmI_d3f<3RRTfQlotCYL6TNRO|FQ&9>f?>V{<+Vl~sJ5Ii32ZyMbB?Zwx()cza zJ!S_j;{A|GR69Ck#;11)H|Dza|1FO-swNLvz?`~03vp{wlLOL<+^|eh!A__ci4__E zcNcJ|lrb`Gw_y?cb@Nb+0<0 z-@ZLX__DQ+n^{Z4rCm19D|t(Jac1T}LNEXJ`+=IRTlxYT8yCph(l<7`9;KzdNzE(v zzOnFb_@AGFM2s~*L7qP{l~4Af%enn9C2OBNtVywb7LgnBn)bQ`M+lM%REnwX3_Zuh zAP688u*2{L6XAD~i*UtS+E9cdePnIyc^6}@Kf5$j=Q5n-2T(X`e9-i!{kIV|xbRv1 z$@O_xH~$p3V0NlHr|gU7JB35DqUjSIu$+-LONKBiHu!cV?n{F4Ow+`yxWofLOoOIv z&DO|6Vu)O5ap&=t+*>4FPDtICrbM_CXK>;A5~pxT zm*WvWY43|`2}g`)Cd4oHbc;t7K6a@K>ZV*>khN5Z*NQ=gEu217i9;&tp!QGr9jFDy z9Rd->UvgKfZX$^i-b4QrA^<{Z;gb1ama}3e)|3-Yaqv{>O+;q>80&~w5b8K?FuPXQyuq1z4A)JFjh{yyqb^jyTa!1S)NHolBueEK3n4U~BXQ_wFpk z1cOX&H-AIfob%=mEGjzAQOV?oXkgJJG`3P+g0<&!{1rB^DS(q9*&=K<; zEZlYp6HXfv>yiD2T*R+}4(WxBeSqWc2yWZUZYiEu0bD8CTQ@gGRGuPYVKZNb+7PDF zo4R1lqjBG$3EZh$kHCo*rP-q90r#(tAYH>^FO$=eBFC~*da!yWinW{h35U8)Z3B@D zNa^C=xB-pu@Df}#3KjSvWg&rp(%}kq1Q`> z2wuB9d&?i0eJWD!_yr{lGrIdF5$VcjKp>h^ycM316VCdSv3;0rPnx@;EW**d%!H-u zUN-WmIdZ`AqOU-+EUVD@x?#3}@omg*T>=}Bru+*ZI!D-T1Gs%7ifzaQh6%V8Ly_vOG_kOcvBUme164Bs(&+#Y zzCr>ANO}2?R8-K>sG~5;NVzF3_QKKos1eP&xPX~@b3V8-%m1Rep1nmQ8X6bqHi*>B zRy;lkX4o0nOGERIY~1&4>iLRUji4KnE!_8D%GhrA8|~2r6JP6=iCJ2E#ypf&SQlauphf=^yc+&GgFto(yA#{acmf2fwr5fhr`)2fj5tQTDxh?}PeRIoyz5 z1U3q-#jyHJ#IobxH7DPWu!@L?G?V};6pr3t|E`VMlizO^eKPf2zF_Cv)E`?(9xoqb zik3cKcWpN#7&-Vt0@QEFk;wa!?Kqc7vWz(}ykC`Jb*mwY3v`0GeIRx2;H`WA6qG^{ z)1)B%Dp7P|;3|Xo#mK^iKmdE&rWkXIryv?2p$O_@6z2 zm*k<+D*K$uqO~lh+n|mtW~}2o6n)kl$2*%|AK3p+!O8!JSJqGRBP=h@STwT|9(G*c z&2**y!gPn1nT(uG%T_}`uv7T5$bniY95$qlfFh_^3A2OwLCs2HR@{)7#NQ>-sm)Cg znE_~7o4-9EFYaT!C?PmIQJVD0${2g@uXp1}H*kCas<2nf*~)TKRB6aay={{IGG0)l z?qeQ=MtvsNCy0LO)Tp>Wj)=u`@g2n<8mU@akfp2_d#JQ9s4quk>g;W$Y{F(1&7z^y zmLDx#PAP6)DXz$OHsS-FE3L_LolDxKTVHrxKP-LWCEt@0145RDfjR!>a_N~1y8*kC z6k0vKAiw4AxKK#7ajKKn5!k?ivR!)H##HfSVgqfZe*bajgIRY=z+!WRIL=JxJ-nu7 zI}W>DBzyJD9nT^_i@0rV`R{PF(GIg^OcaiMF>0z?MHogd?;9Jo57{G|kKJJp>GL+= z=p@C7@%ipH+h#A@O0}#V3 z+{Kr%oh{qmO4>?wEe*Q#r;VpC!?xrN5*7~cJKjl{wYUqUnGt70VX zX#i0uqO2CFJm^iUVFMvjCWq`3FvsTTU_S|ZRDA8s8KA|5Dam*T*10TEzid&|he)m^ z+ZraJql}t^da}ImR>t4Kovp@Okq&uqJm36ATktC%hhd?xJ+*sJ;=7_fDiOs3ify`f zMDYR;XCN~?ygn5&)b;oa&fplnGsj@;a`4#b<*S#qJHD##fqH8$2%=?GIWTiPPPg|e z+~U);OjG0J7!&&MfaP3!D|x5pXoA46%O=mj20TDALXQ6L8_0Wj*E8%CBE$}0lEedh`ty;#~Q7?^78VMy90PeZ|o5OEct zpZOD_yo17+gz9gGV!Epp*5e0Xawl7t;G&%Lo za~5jn(-L7HVtobcuscUNlGuU73m|OeSS=t{UDvBL)JS@TUXVL+iM3J|GOM5iltK_a z>-LCuBF;)r?x%u06g~T5sMjes@Bjq~Lv=8;DapM5uB1zz=@ME#2=FyOv>cN0b_#*A zeP+zbbQmMUG!Da=X%T%2@)|oF+)f9Et}O3#L+?I)2i$F0gn5!iBUS7=0|H6w#UI+e z;X|FZ&K3Td2o=fjZVt@h#&Wc?TbO|lgj7gx0+Tw0Lm*?wZvf^frW6FJLkDjs)N^(| z^FSPUX<)MWDP`DhDcY~@15gRRfy_cXuu-2?^4uzL#{%6RA|hBAJh3h-1BFp7)U?{w;?l$7=6cR8 z%Ue#bTpw_aAV(lB*KAa`81nG864GfwvB3dEs6fnisWRemp35&H3u8duSm-%UE99Hm z@+vJTci~1*EO@DEhJ;hGIFX-lC?l1?I)kMZ@;qX9(6@uBB+!D;E@S|t@0;}j5wpgs zl+bm%U%W(PbD`)dT@yU{#iiq+mu&4)Ql?yke|P;x^U8k<6nxM{L#3yPAeEk+NpF(&dtA~Lm7<=$%AbOO$cKnRerGBeInAva1W%pBGj*lIqHoT) z*DG@BdndvKp`jjS{u8}%A=)%0T~HXT{zXm;mO-Sb6<8ca2>WC;M9Cn}?~0mB8<{f zcn$Z*{gR?hzLWdVPEgZMEXu0ze2zhG8RJr+OGLh6a}yNTXbZ#@iV8peCkDm7rms){ z#Fes+qTqkoSZ~<-qMe2cR{}mH;v5H|$nk1zUA24Og;WpAWUh-)Lm<09G>V6vbcwyA zNmf>3{>yOuWnq9`FaA;JS$&olA^IYA z*kkB7+Yid=a*X!Wu?J{9d_40+w@an3TrtmtR)_i6Ih zy?%sozqJJ6wL#HOh>8GD0{#fnNh8}nPTD$_1As_d&Hw<`B+FZv;)8p#om2k7 z8o|bh6EX~apd3Me=%W%Kzh1KsEmdGVZGXwc;ttm~VJY)$ymDP=DPq1zgPfKttFG%*m%b2qlQe0!d&AkvHPH&_ zJj{0iUI2#SLdWr4Kmk<;F!l}bWi2ezLW#;Zq@?4%_wttZk7QJTAH|l<0a#a*O20Mk zophL~B_(xA>xW54Po=UsO+Hm4WzQF=gTCbhBw`>4!Zj}0a<&h!5LeF$8w9d|YJ*?c zg;6A5VSu^kR{mFOKhT*WtRc|6#q%TDo3(qMB1rU*_fTByKsKWF_L|)(RbK(B_BeS1 zhJY0=SVKodVd`H|Zhc9S&=+)FYkaK+CyoNN?d|0R`s@VH3np*M_C=BVqvct>-m?$+ z1yP-e6%?udTFAl3TMC(OiW}$0eZ#>)<(ljT+V2qKmds!dK&kDjj1`eno2y9knoO94 zi({WtrO;GeI|-m3)x#&kbE(tX;;sGcXSMfBE?6f&Sc7>v1>1xwuwf2#j@%No{Pw-X zjNq`%W7G2f`f&O(&TsQ}w_@sA-&sLW0)qj?pq_{QtZ3vMt-~~bqEg80``D6rCzG)G zmcr9J>vFrU`>o89JUyKwA{~+B>Q|6p;Wv5~D0LBBy>>(%o@x^jZcR>5sRvchlSoZU zX`%d!L<3*ox)_vYuUz(qKQr{=5^f3xrW;hfak)CQPbcGyZ`=3ckozjtl7QI{U3g0cfBWAZ+qP^%Ouk(&~No;P+qEb*ey`k9j{t=f-9KYuRk#! zF6C>z6wVKARj^r~abGbxcIqNnyxb$+0V7L+RFe;_^rtRJ3#)Kp4RL2+wL$)X2F7h@ zbB%`oW&5QRd3*hzS3Rf|0?p@X)T2*;9;^ZF{;C3}G-+c!QBFE`NBQN?=06q9uP%cD&OZ5OGdL!8Nt(hkCA)&`;2JCc?i$+HJ zshic{^K^L}+`h~E*Rb-61oQ*JFD13Pxr4fn>}UB4OU;k~r0lR~9EkdG65#cz;@9#nWpC?RIW zY5ny_;N8qb16j9!6kJ+F%k@OZ#WHWZg)7iu3;MUHH$mrDKNSd+`9p4X zkyDr^8|Z90uw83@qwlC?2U6@-^zY$Ae032gsTGzE#7Tm*Ej5**7(kt!cq;uyNT3O# z_uvQ!hjus|`l|(=p?|1Ny$a_pXxCyG%5>JWV$H4JXSo`$|Dnz?{uwitBILdNp>Bic zmyov=eDx@H@w3;k^#beMV%KYFh0U(lL@+-9U`*+i7cur&ceQ$W{~n_x4V;kbDFsjgCk^^||?(>A!z|qz0lMdGN!2 zR`HZ<6#N`9bRfNH8mi)6DHXHj7_;Wsz6}b50V=DVEPI3c?J=);m~tB7YJhw6pEoQHbO!MYlLQizf&@1l+K^N$PS}UWV-%jCmk>~}r5*IjF<7LX_<{mF;-SD) z@p4{)b|*C5yTMr{;c^%m=h1(Klx9BolJDU!2!*VzKtYS>cZMQI8RKy{!~GSMo%+ck z4(RMiaF+Mk(G-Cz46~=gT=_LxORJ?tOA8Ng8Q-(Z*hgH7W;_yr)n`N<$)aC@xG z#5vT1{Jp2R;6~_yxXU^`r!?onf~W&~s+aRPE5Qtz^@Jdu@+|m?KS<4lLy_lyKJSL* z?oPXsQo#AF2XD#KaUzv>#>o3BTmpBuV-)D09@*HTh#RBm!>txZQNSVU8F=|;F=s>A zr^I?qY)xP5V@We;tPwVWX!82`{e&_1J5hTae{?iA+tCaT1qOVmMSFWstYSNltGs>v znthypkFP&v69Db=@}ihfFLmtgfd*q$RRqK0{QD?Q=V4~YO21lN2FJ>Csm4rTXFGgV z+CiiIYO!}40<(tmp5)yq*4oR8qNf$CcQ-q1Q+|lc`oms3yR?gwzi51X4{`F#(VagJ zMz%<+mr>ya&-A zc6YueFA&(yx(vQjo%tTp?6DKzB6ZC$r+2`KsLJL!%YA5%sl>TdKuEkY;M5XWVH%Qt z5eRi@(R?o+C=BcEWpgyeWo@iTWoug4gxa|ZnDv2F|+OLDl*AG53! z)!=Yl1XpPuuELV6@yzen{nY_G zNO{sw>A#366a71{AN@D)GSGJ#>Idz-y*2O-4cQqM01KWiOYep+G$4gqYGmS~nV&Z= z4yQfoF*VyKZbIWd*!jU1YaG&S2f9z-P5kva`AkT&p@oIm=csoXLG{18O*x*6YTw7JfnB4ClF-9~K&v>g`cUSamW`9g( z3Y?*L{4Q~}LjONl{<;UeSV2$47gmS0m-v$NxL z)$iZe*x#n5tP5Q98jr*e0mv-2#JC7mx8cz6WT9?sCbqBRsw)&0u9=Uu*zRqK}wY%cGNja6^Wcp>TU zC;`@YG1}p`r<92A>~Z}Dhz{b zeQ$=#(X*i1;vzb+vaqnyr^wv>u^$Dp(ikP(3_4W0?rXGe<}hLR$0_0n;c9CBaX~5E zjjH$aG>U&mldR9V(r3R`RaLzNq)nlIlz~81a}Hh8<=6FDy|kBKcdZts?Tb$^1M2_% z`T)*RUnYMk+3Pzv^PgTGUHs`h;5CZ^J(Vig1uKvif;@OpW%S)ZimYoNJHQKUZ9HYp z%D=+cZ5`yhIC?vC56IhErMVd?~1@Lf*jHQ@G=LM#( zdG_}bU;U0q5x}sHTQ$|j`iDxp8F;xQAX|S@pvn`teRuR#ueZyvG!fh!PC}r{rAKt;U`c(+=O_n;m3jt*zPE zvOH%>OPj6$twI1zoRJm;Li*=V?l;8)d_j@~+J#Vir#o`*#9Fbja^4y4c$7I~M{A3Y zi2YmDiGV0|#(w8>x#ntOa$g;C!#>#=B#Ony%$;-jv(z~gdd~R~+rj{sV_%ti)PM`E zfk9Vyd&V9fS(hIth3MSe4QS>r+)tE9RuVXwnlO-gZtiuALp%LDCrw>lJ%$gps+Z#T z?o4$8W%-ae(cmq8-!@?NE)YCBAS?8s1nw%TfMX4ik@%$Kife(1#=3o$o&l`2_hMAcr zgpk>shv~fbWPb|r}c|TTBF#nLX8b9e49=GftgXu%nu0u*JqY| z>0KXq!5SLj=%&^)$xos}GlR_E{+i50v$4JI>^$&;u-o+fwMOXNKtkIc`&XAnml`vJ z{F?S>`L9b^rkvZ8ogEggdI=S!E_VK_1NIQGQ}JHllLThzTzlFD&wam3JqR3;^a%)y0F)5={F(Ky zgJtLIWX_XccE zw=200(5ETfJ)N$=^mo(GpF#oFY6j^)fBga_F7TI|T5ZYk^6)Ty*@fmifF7UZeP%2U zzG=V)n-Z$NzA;ADc^~()mIvi=-T%dS%oTc>tnF|Ms@G7i~9KpTK_%%N$Eh*`e*0xb+@8>7=fjej;aIXT5|O=3T?KuyOi2Z82jEcV= zqxDm8+o}fAA=Pe6wk|jWKHGERlj24~EfigmYWwC(&_Q5gLbSOS=IBc|Jj}`(9Zdvq zg2j*J?O^|uDHQy7_{{H)SLqi4p1k09Q=74v(pet+i<+9v&A>s8u;Vx0^q4~#Yh3HE z^e2{GO=UtlDGuDF?)Nh+Z9$N9w)VJn2slK zYl)u$c5LhOAmg>OJIHn09@v$=cv*dm0ci5{;yOElb;*rlU0vcsCaoBcI|UM_@=0Qq z{929lHnw!6VZPw>!%F?lD4Lkva^Z58S{Gib{i0m60m3z84qfQ?;tNNsnfwGwe;d<&cb%S0wMiW(i+xUo|8+jUP-{db$YC#_!^DtOI- zYu~jFy*3BjOH8bdM4mjmJo;Gl%TwQ#t2TXQE0ZhB@$0v|&h0Nl;^h4|4KFg; zip+DnPt?2n__&=E*7TYeo=x4V;<2`056YP|JpNnp6B`Sety@wF6TN20cX$N_r315evu;M!M>prWR*h? zW@DSnF@kzLO6JJ}mSBSZ>eXSA>YCSOWp7oD|7o|p;dte~UF?Pcx2Au~%xdGVn`dhS zHy;rd%eQ(7SEAl3d2a4=NA9h(2eYyWp{^<`Uzhbi$;Eqvv-tQW1n2yHY`DF}s2%qC0>ZHAT4aZyK>5$gVQxi+nM&SBZ6Nczysa27CT3go? zY!o5UIJxN{IbX$&kwU{L^5I4kr?pR?1dMvij*wA9E1AE-|0!&;dl+*5CNXNQKu@CceKa((&oF7fr#OyeIb<=y^g8m6zL!c*~B?+P*{0zAA=u z%FAY3tD-(5Lqjc$wTQ~3y3rtb*(^O~TAN4q(8K@?FGxIWd}YPK34ZkF8xl~IfxTe^ zbwX@sXZZo8!bl~SRBnk3SjfO}Ye93|P*!$%C(NGHjp2w8_Hq#B4v`bHe-UFPjHVA+ zqNE`u<>X{wU=wg$nbtEwL;EJiR5u4RU$Nc-K6!M`9%76qPvY+1r{kohMUj%4jtn^I zWvR=^{u=ouil6EqfhVKk8TFy*elL8W2@N~`b%Lzbk?!l=^jE)rK>~v~5xY*1IpeXi z>KFn|o|@2IdreZLqYQR|*RSg^T!|#N&D;pbU8Lfrn6-V0FI+z`oxJMo?_a-pYAP6& zHg&BvCV}*u9g--S5UW3YrVmS{ymnPeT`YK{M=T@yh91W(FQ1TszU?(ZQ{Ctn8@ubc zX0h9sR1Z6^+1uD?A6@)i2+XP%N!t2cA#}FIU^8Lfu=vCH1E%%?zd?aT$Wl2N(mYi% zC~~e3uk_pBMCgIVc2&FdZ8PxC%h650!SOWy)k$V(yev+)>OO<=j7b;n{TsKf6|!~cSw-TacG;O*kjK)-#+A}+ss*vnBL>39dspzk>h7+q z8!{#@aS&rWr%9dx_F%{<9{f9c^J{`YnlhB-x|;5Nk9aC)XN{54;MOe>DX=)FW&ZB zCr>cq3!;vts5m>PyzYiq2{&n;Qy?^HM6F}$j^mqq%iXrLAD@=~De;zd;6$>zqY?74 z;OCoBMC~xH096vv@K~=sUvDXRBKQOtk3tWE>5zU7M?`lP-*9?6*9aq*0Ez3}sP&lk zjF0nQLj8WmYNp_(5>A}A{H-sVQ9@lHE7M=%H*o|y$}uM1v)t9K>hEQt1`1V6H_FRz ztE+3K6k=jNX=EIgbtr_GQM$ygheZ~rE@x0;KO{98=Onl93;bvErBzWTkiQ%m88`ul zAAx&u*EhXDZ`FRGmfF;75^#l@DQ{FaH||u|)ZA{L!68D{BUP+0le-)O-G#eTkrtTM zt*)-fqoZSW9M_pdF)V%}XTh@$hWF*!U&9tg_TPQQ?Qd5Z6z(mvj{eJl=fDIGI5`cK z>+qWGZb6Y=MD{{j&tRpk^ZZ#*Q^Q#$!Xu?dg2A3S%_L6Zzooosqpfc(788XwF$oP- zQ&Ui^Nl)Bc!~%{x=O!r;YWz!D+VR7%wa1B_JEw5z^WH9eh+0}HN>I)gT0#@ciaI>* z*WBC>^gW-Ls6PAZwK@8xKjR9ej_vInboqYU!xd0P-ShVL-r7K^seyo)>>;-qVKV^u zuTHrOptFdw+}zK$B@&>2W%7iAFvBssM8UA~B{LloDQY6n2SE@>v!C;cItEISV&;SQ zdpzHMP~U*>oAuirkF(QJb0i>$4U*V_`F!+T-HZhkRwOMUA%&T0;m&iaFqt_Er?ZCVbs>QOb;rAmnfR@y#U%JpSr3(aE$il> zSXnn8+!yBF>9YbDWGv>Ycv15kqL0AK`+gI+e-aW%dknL=`To6^SM^-!)Wlg%>_Sz* zD6Ujo$axgBa93zFblqwGVP`dj3Y4`2N}3X7wfmxqMk!(PI+6_ZEgDkFv?j^YKYA-kX;c7nhf>Hmh&u z=OY>%Y>EHijMn&BA6Q@i`E$w@bbL!qP2cgwH39x&!$9Q-C~STZTU@xAO*}UCfU08O zA1G{~CNS5Z#W{`u5RjM+v86~&Gdy$VB6-3~yzuai;$K?#tz#cXJ5Cd5c_#_14(YB( zR^(aPr$bmQEx03I6d2FsN6*`kX8=0Rfd1GACfbY-cmQP9!1Zm(xv8Q=^nkp>e$g&n zn*fq1z8ro?!J*K3hoOP;Qgm4@^Sm#5*w*}ZABdqg>V5k?QibSy%qDvQP6iyp=D@ky zz`!Px$$WOTrA1{Gmeh1LbWP2vQ;kH$1sT?giki=~P2mQr@OEGDU#P&TS&2wV^BRAZ zl!Q*DuB8}nO=d^w+ai$@hrKI8C^QUT#v5y(L~M^T!=X-QZ{s=p2{BYli=3RMrpkCx z;dww0MDxqPwb+UwZcJAMY>ekoq+O?<#Cy;$idwLvL=97)9zXAsw?fRBV z25?x?y)8TFUETRQ;JCbmVev}MxSE1idcr6=+h-Nn(Sf*PvnP}q2Eu|Qc~@?aad8Ry z&0wb0t&+*qU*^)23r0Ml%=>@BhARTUeWN^4Vvj!j;K>v4#kRbWzem-@{6SI=Xx8ZsxRwI9aMowx{qz0DE>hh=V{M955)(G z6hZ4?w2PZvBLaAY{8V6sv9WlI?K~1Ua@N8X%6Mp>T>Cc|f6QT}VMK!L=uxhalHEL& z6k5X1nLS%X)HgNtD)FvAl?IJ2B#E_^4J2A>ItiOJlLkr5SSjjjw?W@#ZYwPMzeJ(M zD(4-&9zD8Qy7z~45g1Ie-%Im^-wzh^nr$xh`rf*wsOT>@`t#?HA2Z}IKrrLWt6f~2 zp60j?1xBo!05>-uNO5Bv3Jr3WU`Xx$3wl#<>3eeHrK1FX76>cki8H=>)wO*_u`DPk zE-sxDcJAGE&jqT9Dc+{zxuzr8dNBg90vB54=|dxclRaKGdr**F;=8qq3c%}sHm4*< zc0N^_{KFNPy;Lv4G#Ox+W8x$PT>?eLni^NuNJncp`3RqjgY%{VQJZ~Rn?j%**Ct=L{?vG zX_a~Ib#|G|>;;l2>u6g@UH|Qum#!ys9I2LgQY%p{)&@zVpuTcRy>ijfJv3BB{Lf2K z;P4vU*i3%4Bn8W5HSQ}bDD-PfMPIyd0h-4sD=T(JcV{6cN|Lm(+U{deym7p^p#?YT zJ(?GCyC97*eTI$OW??v9I?%EiuSCJ#E6Ny|Y5bG~c<5FM$AyW&Q#LYk{41J)fn8;{ zY9C}iq4B7F-^Kl>!DawBaAbtjz%#`W+ti~zy{dxZkI^g1;PVD~IXFfndziu4C#|dE zwRX(aB;JtYDrUT;=Y7j)FHKGH4iv8gLc>qQQ(&(D>wLYja3vP$Fn2yX~;c{xu_ z8LT+w4FwjB$xrTlk=c_NT?m+{Z-`Kc5kSKtr?O~jboiBIM1ubspV>Pu3ua0t*!jKC zVsW(AtkRak#~84Vjw;;`)vO8|&XyLQZ>!p;Q$Ng_>)V-}uMr;!D(ftXs$EULpofo- zjNHzi70Skm0#&YSbxL^Ern4Hb1%Gc$en@-iTJhI|u`%WyJ{0cZqq~$BG@O}F*8oQr z2_}^Cv9Ar{YLzZalu7okeG>%@vqUS$K9H`DvY<#^Xh;b<$P}SRX%()Gas|_>Ho9yk znYSkcBz!F7TIrb>x}{2PUS4i)eqz9`Og6Fx5Jd~(`SQfybB#+K$CUuUrknFp6f0&A zZo-o);9Lf0R|y&*GBia$JponT*6Il%)C+0PZ!f;GU|GDamnIAfyKHF?QU71)kjw8^ zzvrYR-@XlFHs~*TsLsa@eqHK5+GrywsiG5HlSDKo{Kvj97A!xQ6zqysf=hyeI^OXc z&si2~y*m~0ot^#IxbLG1_EEV26U(&o=Ud~kD_ajJWn}{?7+hVKmQdNnsh6^ejf@im z{Of7hI4r-0)&`@+OM81WxJIWnGgZz(*ECRip6u{pa}KC9B*i4*FLTbNM*!N>1EmYY%+udP4IiFkC#Q`&QF z3G*dPZ$4cu{Za*O>ikIJbJvv&;dT3Bo*V5r;r6%fs3k@>sY(Cz^hCM%1gd*J&>k$u zpuxJu-yf=E%b%Oe&|mrRA?}3pj|+~DYEsSGS`T3R>1y$8JF@8gox1WoP zBHBzWCYqjs*SL?0sv1~U>FV)#-g8M}VtRVqiP8D_nVDIbD8a<}-5DC%rKv<^WmJ;t zi!heC>E0x?^Q^XF&E~R#(I5bk*BC+5t0n%?qv`^S4<797=*yR(31)*iDfj!=K*kXo zX1F--?kH6Ghsn6M%2vmi?k(0nK2|Ni2Nbf_1W-np8}OV)nZpKbT$bd&Xo<%qm$QNi z$M~8Ki<^Ua`w+-BLS$F@6D(m!4-nk5Gk6_oe_3cXgW*REHWMGSq-m}d1Tt?(>tC1> zs_UhP7Wu?kvthynvCB4;pl(3>5O0-*F2@Tb=(&ILCa5Cl1(D8JR@j7DrF*4&ahkal zNEp1@xNzoQqUN=l!jF5`R3aOG0=Ud~MheW9Cd1$I?YG7?1Uj9#?UHz?9B=qg5*2br&9!0#Kd^7v#mZypr@;8*4jeO9J>k68E2=YEiK$EQxHF5@L5{{i74FSBC~YpN%ZM6H;Zliv}`ZqI@6=7do6F2 zC-vmQv$brgknQg2>5Ni~x9ux%xnnl2R55HzeEZJ+7B4$-*?1_hS4v5My0$(d?nDA@ zwr{G}u)2AOtGp7(`@FgCv+a58o&36DOy*z7{NFzvUiF-6*TMEGNScGL;Dwh*(I`U#|T zlDCAL!WIWU>j=fMrj_Tu^y7E<~Kw6zed+yrgU;&J9c7?t`%-S*Fm}ZlDst< z+tLEzKl=*-Fb}#zvN+%r!8sBjA09jNqrHebsYl#l_6OQ`eQ0l2Ss9m?*FF3DaFO+o z*_VmHsjIBa&8?;o+}Rjkn^URI*1pPBn*O<1#q2F#zn-D37?wehglJbVmkiPf*J^m(gPm%LZHAWJ`1t7P zm{#T8o12pFY`eEs?RsFwVATo`u8E)Yh(A+a6$^v7R%UJeZ3^{__2&^l-fK6!_pFt|E51)FK4;==i(v`1(U)EPC+A-D#xUYBeZth z*Z7ipc$qsTCC{CE{kl3qR!vRmOn6i=@+m3ke*gR^f&*O}3=C`v!3@^2hS$5|V!h19 zvS+!}z~$}g)r_sB1L`-^r2%!NrcL2ZqdpIO2O=QAU3kD>`k=fjO_o}=Sj1zEWQwzD z@hmWPOq6 zTD_#99Y^@m;S>#UqsG*?eQ(uv2K0P;NqgVDOIH^(NLCeso^tZ#GfFVZqz|bXoufQ_ zyuf9AxB{1MWofBP53r!4TsjObE%X%mt`jd2F)To4Yno0S z#q^X`75cDYys~~HmCVvOrKhJ)-wz6W@uIB_Je7KK9R~cjDV<$ioyiXKv0mlfo&o+a z0FkzF;v+ljRU=+k-oGlC&g?eSPE~UixAT~8>nnun_v%%bb4lGi>pkXL^mn>!&kMth zGIJ4*YSH!;CXYtXoQ`BO^ka!?Dt7oa*`0)5{FMhYB?+|5MpF0e1QEy|lsZz&A7*^& zIkNki4p>2Vr~Fr*_5bTo^&(AXe`!az_};Ia3mD_gQBC^9C^LPI=V_SOeOm&IBZ)u;(8Ur-8gp&xnV?X- zCCZl+wK>wo@qj>%;=APp$mRRJ?K{SnkR;X@i(mI^RAVRv*01|CvmuGf&OemBf%({d zb=11*-q5}Y>wcVb<<2dOh)&y>qL6E5Y)RcOo4veH;fYW%0+h;gq&XH#L@F&+Vox3! z$#v`)JA0#fLGznI>Zf076!Q?=46e7kyYoB;2};03#v2B`TA1G93jEA+V>~Q1wZaGb z2}wy0C<7-ZkiCT%f%HW_rn2~8Sy-U|I{1o)h5{M)Agp0<2KXNzRRn7Z&W8 zU3Q;AGh}{r;Vgg=gaD3^Z`PUsB6M?{){&?q^pB&{ZimRGGR+n?-t0abd*ti$H0;57P&9`)##=s(r0=I+Y<^lEzVfp zU;fdZp#C=>yz}AA`jz>wQ390+Aq~QfH)FZULfKsrFStzN{AUaqi}09kQXkglOd|-) zcg^c&5@ibX?;(l%a%DW_&-?qlJEi^Dfqcq=jSFud`_Z@;8viD{H4wRLJx{kuP1M;)8?VsPp(G1=RL!Ym@9 z@cihH*S>{6Mr!~eDFP(2&!72kqM$bheS86{9Nn2$VsU|Q`O-C+ufm~5yE4Chdve%pe@}j|_oD&jJixp?=YJc3?$-d&RK?6{Nz$@S z*5(?cRA_SA%ic>$L(5%R*&wzfAV&kUQ&7;M6+vD$l`v=&K@QND-ggt`NtY=sw6^P? z$@)Bh9(kE!)tgQ1duOL5+;VDH^$STB*Qm%IKGa|kEy|nCm;b0s`*8Rzxv_za2Z1PB zxmUM4;qsf{WfY?(KtUdz7fIAqP(>)O3K|6kr>;Inazl_Y4XM#Ugj%H}5CCyEHn>uu+i|MzEvo~C)^r4ig3HiTnuA z-ZaNSd%fZWj1oB_O?`5%gl#X70`?0S`J^~1=D|T38EB6)3ng8l9!-k@wEbKuOmQYx zl21=M|NN#Hr1S(;;&4r+|2-W5OS&@8Tfv!oXKmmZFV15QKuCA40L{{6Jc$j!T?lc8 zm|noB%D zmdZ*Kli9t1>DP)u4=Azo<=KcwCbUI87SvQQsvPXqEMoTGUi_#HM2t3GiBdSb{8>3n zmHW{Hrx}&?(_C+TYGCVB0d}d^RVYz&qe?vE9Ar?J5tnsBPuh zZ1!#SH7kd0uX}~Y4x$Ko24ctvVLq@YNgqhrfF>a5%wLk-EgyO$?ZTO~ueASkFD|>P zoV}mc;n7cucRW4PxAX^k^_+Y4&iATo4Uk5z1p_vEIth-E56zI3_VsHREoI&S!@xwp z60hZQcaVJLQ_CPq3Z{ye_1O|xoJusz$>yawZlD|e485^YWY?3;kZ=B|Au<9hv!-uj zqj?0`8pBwnNk9&wmYVt`*|)^~Ax~I0)YMp5V0L|q|m6{qAm4cN<|d1t#kRqH8}g6r8dd!$9gMZ)TPJyH zLL+_pdvZlLOFj{ffcofp7r$HriJQAV5LY6O{v`%kVkc2_v+Ic?zaCVHtSa3>MA5E0 zto!#So>lt6WUH^Qj`-n2>n<*yLtNZ_AJ!+7M^|R}Z>}wnwj>M{*=&1M*Ix8mbr>vj z|5f6k;8Bujs`^Ch(IRNB+W*cqhC6{iq$A$G*wO;ft~cHQ4%hmKnTXP5QOCn@{#i;b zxSAXm7EBr*MI0H0zVT*dVI+@rakU2!SjLAyJFa3~ZiQyC_fCcPP=Gwta6m5ys!Tf< z-3S7P?&i^9w>)nq%)MRv^=obI5HlII9?!iJha2;GK|ys|x+H^MR$(m0<)}nIF<89& z6qJ>3y;j0ry3EbI6fXs+>f@RJbcmgBo{bC(3kg}e2g@KGCzI&XI%()hn?7NglN-PC zbaPIh#<~AG$=uY4+uH>w&YmwDK-VIst=WW}_xE53BBJfJV_-1V3cjxqztq%1L*cBC z>{6g~%o=b~?qfhzKf8lz9XNRJE_+u*F|Vwj4UbML%Obbi&U%uCvEODhaSS80!=`oL zObL{Uy;MdL=MW6}wb(X4tq3f)!E?<=|G#boW0?m_SWE3|90w%6reOM~) zJn=9jq@C>JwxgET?bD<4{y$Z9=>eAhHI)A*MnIslQc)4!j6;8~Jsh~&r+=V-57`Mx zNTSETR_*V>6?L4i3z3wpp6#~#zOxu4+?S(m&c#|!M8p;G8Crv0038z$Ffkb(#x zN{CyJ&ga?#)aUah(R^9I^0#k`j)Diz<{+ob;#UOX3#n(t*T*i8epnE5+?f+M8!SU(9~?$kZrOIeG4)6DMSuDf z!qA;$z!FR#?`|o^5_M_SUFg16p25ym*`e&ZU2VwmhL@sX(pPhcZkVu>UUENTwUGPV zApw#Fci5Z_tHAh)#$z`)`ogu>Y2n-J(O%4b#2ueN%4k$etyJEWM|#37Ai&~THH?Ah z1`4lGip7aNg;@ki<*rBPn>$}|fPS4T-Q5mUdb+cV#Rir*I8{~n`LOY1-KvH6XQ#(L z>OTsDm2awSQc`EeKM~`ZA_GNek2xu?rQZf@92^q*nP0%$dyNFxCd0dMZ0YS?T&%x} z0>OtBqP5?#=kD|h!O(BMEX7VQsYn|kwOrl@(lpH70i6fTTo(+KD~msZNC8iknwomn zYsh*_3xD#e#yfNz6LDlKoafKNhfq)FR zn*;VL%2#f^7LIigjqF? z#F+;0^GF4-2r;4KsW!$!fJitmJ({@xaVlXce!RnC;a4`%9|Fq9R;Zb6>q|kR_B>*7 z=D(X&nV{`(HJ?xCB(#l)hCx0qCeJqczf(|k{WwolF~8l-@AL|^&%?EM2gIKULyr09 zF7Vv^rlBpW?4W+%#&bP9yiFs?s+E*wdVs;%qr~t!N7D5;{9qnH!(H{AbTD-ssf3Rz zgyDvkn4>m_PkemfWD}yTa+Hft`!Sp)n;KC`4g=Ej)|>WbQ^%c$Hu5whfCEBMB2ZS>gThu*I5&J%*(jjHqo1HEHapEKzW*1 z1p=Ah`Krwe^iiS;-)*C=IEe6s9D%+fbE}#fg3<<#Ne~GPl4%&r90ovkDngw_+G9^< zbY6g1qH(p^GOgpHRCe0w^wiI-t(|llV|LZut7CQdF@SpN3p30i8Y~Dv=g>nFEEZYX z=LSA7ID3jSK2b_CDD3S8Xk2Zu=WmI78V0}f%NV)18=81|fqxZ+)8a_>!?T;Z*a$5B z>+x=RzrEhfo(ehAFL*)!*!z!e5(wY{sQU_pX9gIf1f_gXJs|uaBBAig+a=vd;(k1V z{6_ilHQ6P$``g>Pr)pAm$2``iOqaZOoHY1@X`QRKW?h ztNsdzTO)F#-Fj2sHpeJ{XY*9)$`y+bVkgXm0Z1yl_~+}w$K@JQl$p>!4;oppD%Yas zY>ONH-S0LvyuYUz`y!eeg;Ge5`1$j36t`6TNj;8qQKm_+fvWwLnH%Y-6wUXq3JX(G zglE7rXbj`K)KKXMxQrs(G@|Et)^}1MlSaY$tp($m)BYXA#(ku+w+SL;H>mTXRaMgA zVNXpp>gRU|2q0`m53klKY#oYa6_-u)iC%K1j`I6|@jFxqF8 z0(g1c>x-?43ti-}3aHEbF)gm}3CMX;2{Bz$fr&;+dsEX_QXuC&rhsuM6B_yir+8G} z2k-A5N%3^WL~=2}v%%t>VRHlJ?B?dC&GOz=lD(Rrzqwu_fTg$6Z(;aiFf6F@-XdqP z5*8cIFG-AUwlJNEzbUq^j#Dl54d#Tn|55|+S!8{vSoiV*&`NjQ+*A+4I@o<` zdwqX%3J!+J;Xdn_hK8xum}m@^blBx9mKyc`{gto;SqdV&pGanW`>Q9p1B@VThYw>( zk`A3V61$+{8l2;MNU!Fh+?+s2^!h&kc6oMkM@m5_o{D=CfdtKTj`-4nEF3g)q1U_j z=W7&i6q|^UM$W_hg^cJ=6joE!)!%bq2sAqCF(**LoLxLN;cSDC$@QuNM|+SDf|Kx| zk;S>{w|pUWJq##3L?Ah(xW7n5A$_1KU`nmp?CSNQD%QnCE^eSx=;na(Ul;kqP{;P< z>nC8l;drCNi*PC_D9~w7E_oTHrNLadJ>$|SJ3E6w{T)pzN)m-wn`obmS9ZcMB~tZI#_d=iHKR#FlHF3r)ZAdBPC@M6XW3EIkdP2N(gHtA3xI4g`GtBFtgbJj0IYs z902M?A4ca||2GnL%Qj;(CLWM2@v&c5F#ae1P4gG~ZzMuA*mL2nUvu7Mk=tyW8FDx< z2<%Km5L4j5Y7${bW9O^(14-wrSbNJ?!{kdG|5`IO>Cv;9&dgn#{JcLbuA|LCX5iM2 zv_X3q@WL(29|l*EamBjuLaT~&nS&TBE6UlEno>vl_4PN8*S9;A-E5xfQcb!Ay>=OI z(*X3YiC^&z&ZIK^_u85OD$+a>9_RDtwh2pkR8&-2H^!p~PiiFEycRvjKX6l_kJlnEvI4_K?N$GQ z+;gHTzJr9(Z1h*u?_Sccv~FU^xJ>~CT3koF@=t|Fd;EM-hxxeD0ie>2(?ULLZw*-e zQcX$Tm~wD*pBER8~nk55G8EHc#7$%fGx%r>@Hpsv(u zQ$MJCv`4yMDr^xXA4sTJ)zsvC86Q4=j8rnG&(wZ8ZR`51O8pPF9I?o*C?tBE5%NV` zRlr-j97r_ZS699%iWmX)`)ZjJ&>jLAz)@_HX%(K@O-l!~1^9pv3|Q*fB)nQ(5eoFN zWCjPj=8PPKjCgJ)`4z)?Pdv`DrJD8BIf9-Arcx*$%4}ZMv zGVmo|e<$wn@@i?hd>IPq`T6(nfxHO*eWIS*UoPEAKKE`-?$*zLkwh7$h#D>vtHyAm z3y7NVsMdzg4_W-Z^2nB)Yj_T1?hH@@$rp6CZ?1vy>D@Npr{yJ^m&K{YE$j^k5K=oWYDE8VC;5Fw0S$Bi%q#F~I6C{h4rGPM_AvAy1Wdd-$|s z*8{K8oP@fnD&y_e%2Mn-suXd#@v&?Oq}`wMB@`d>DcYR6A&G&4)Am>poG)q55ECke?5n zcJSg98I|(y^;ro^Flg*OGSurk6==|!0OQgHle=^;Xj3I+CKkhMFBb1VDikY)oT7M< zJHKiy_rl{Y7H_bl!#fJIzU$s|q9EbUnQ-s-IpE>7wZSHmk&&ODLDpvNCiRpA+&XTg zL#t5pTzybttj z=hs|Aki;R5O_I_?&hg_?U)@8u6fxb7w5r!QixB5u1S$ktJJ1hdXTN+IJaD$_4{FP8YoYtvwWc%zcWy$iP82?pS^e$xWyah}PMiqCXleacH={p19CJ z%;u>y|77quoE;$qM`c>PUM)*h*4XmKM$%V80FNrySlW|=19LH0Znf*-5tSt1S2GBt zrYFi$Ajik8ZS3Y|Umt@Yrlns{cIJ@U!3i}=*8eEs99}>&;yA4DbBG3re>;OD?(hPn zQ*~jE)y>utoL(~I=@IMVD55$le86x)Tig6B!1JE7oBD%_!v-jXR>0*{5i-`bgeM%d z-@&z#*GxDH$dgz!x{(wC$4WkbjuCMh&NUt=0auz_<42EThFSjJeM2wdf;#}U+44LM z{+vT~6fXmbK5+GNCWw+CB9IWkyoYFQ9yn5v@fvJ#Ed{cD)Ke|PA!%^))1mVy*YKcM zarvx;4t*u|qseQK^ljdD6~)82Dk*P97B41$9Dv3Uw0Pl2nj|%iYs3oIJD;>cW(2iPkUgm; ztA^fV$tA3Hu!pth@_eX!CM0+GTs$PD#M8S>QzBGCl}^4R8*(G}il6!^@GVr8j`$-I zDVSm3<+;*yRWwj?k;)s)s=B*}n4t=9P569?O~xqpZGteEG%AIWw7decPd$pB?}hyS z{vINL`S~G(iAIo3=KQlqHj?HF2WJ(K&l}2DAb?&j%^f`ui2^P@f-t)g|7xCKMbov6 z2QY7Y6yG)E$gn6_Qra9ze&F+X?dV))ZR@o72FVC&Ykg&5W`uWrW4>hAp<=sH{7gtE zJ04AgA^;;;0X_)fF$s+7nYR;==4>>9WE0!D-=8b!W9}eKIYB{0Q`C34xCe)))`6_N z(5;SCAQ5GG`T7M?G541^bRS|ocl+B5ayp#&@V&WWa=J(xn>fl1yd?SJMKfe$OZ1~( zgmCsGeT9#%ff%z_A*cR+nBD54B^kF6&lnM#WZ!?~1*xwhw2kY;9i2!-&G%<;iPp;z zzvxyGc_B+wm#%|{_~`p;UW zn{b@sU*ROp-8b+0Qe`(A-+mpG!c zsGnFQQT@sM{V*7>yZsJjfyAU#fyI8+KAs9C_t)`&ASZOPEf-OJ%X+8ozB^rQg)Iz5-&#bEjoK79C|oMD45+ zb>y56QR#B#OCI@qySv%FzeU$UtF64q*UcQoZ_B0Vj(2o9l#d5?h-cTh5vEgA1g*z2 zvlDK30=hjJ0KbrpXg1ltM`bzX=auF#_k~AA9tDt?w+lB%N-L?YIcy=b6hD;R!;dEe zp)&-&K}UNF@I@c)KrEP1%pS4B%G%$v@fDF{Vy*l!%d=hGTZuK~aYwoj2#T~r~y3OXI& z*_vrGPY_BXIxzDRt*)FaLFRoWyj4}SgZ)q-mE@N%6TQ6n?bJ^ilp@&$8nbKp?7g%o z{agR)?3$T@_g}VN9?4Iw)(OnscyOTh0qRvz#_qvx1mU8;8ccZi6>2?R(X3hf=!O}B z;L%~gOh9Nch(_|DV&6tLT`LX-KceDb`z!R<4}1s%m+@Tp{|sCRzAra&Xz~}rqZPLD zS_0gG9Y5!j{EZh$l*q;NB3S!hNOmn-1c14b_T7(5jYuJ^vg)pA%1xe(6(hDk)Jr6h z>uNMvf8bgQwfuCnM|WURcpar_!pc%=3}OK{961lC> zCE55e9D0WLI>^-Zsh`X;>}o?qZY@U-)+4)pI?ljlP?UhNKK8&9X_l0TfKGP3TKTR-AjiFYQl}Tt>omQNNa^( z^F>r@wV&!{J0KWTNH7oexrG(2& z@&kg%+>`!O#yuRVX>>Q&Qrw)>Vfq3RSE4<{yHyNNO26D3r#$fNA?T!%uAEJ5uMjA!WBTv#Vv$_})|q1m zE)uqj#5Jh@`N59iOz?vV7Yv;Utvb#`-(+3yt8BbcG;mF|CS3>$`)ES9##U(PE#BIr zyYez+z}8GU81sN!R3$;hR!$U`n<-#T()QnnsC~oHn~gu!XRAA)Vk=K?pV*h6&@2jk zBPjAof$#_#A@=&+-7N6&5!9_g!UK+_Lp$U%2fNrkf}Mw-yRZ>vlQ0^d__AO%M~-AQ zcReK8;%4`hUZN>_*BZ&nQ~%xR(_b=01B+y55?IdJd`>+Q0rDA$EiEl0Xn}&vCjgNF zvnV~EA(d2V5W)oHA7uTXohWrv3_DOWCm>Kg$JbL_!Xar!SAsiP)dEm*hO9YZ(SpAJ z-56P3#bV(lr853rJ4^T291R8w$}DWFWe7Tk7=rn&4r!M-Pk6MzG~VWisQB^4tSbrl zhr(~|i;l}QIBk_*trn_>BwKWx8fy8#eDH`Y{;T%iWfeD#Cr_P|CAe$J92hL2sAX5$ zE>g=9Jt0W{+b;T~U}uoa+nbNxz#DPx+%2ZqdXF>|-cJER2Z`B$oi7bY;_keKykdSnEoz`Wof+|}dZHe|Qtgv1KUuPxmX zlqVsGC(=~|8F(2O!6+!E@jAzo$Zrz-KMtYEB*O*0W1{ltVJ4@i;$~&7&WS(ds{f$B zlAdJM;1wqrh}Sak5Kb%bXSd&~04#xjz3cu;LgE_+W#<+N{&WL`r|T`#RYJkD_o1Iy z7H^LEGc)9fi5)}H-i_V#Ep>FQ9Fo;@%B7q`2}DM$E`Fa^k5tn9&n4W;UWZF)8&=Bk zQS?U+c{Rb)kql<^_?3C6{P1sMnq$eY7_#y^%i!^Hd%&HYy$QpL35KSrxihK0$> zt!*&2wRS;HP=x+>t2-K(s{YTy)O6l7VRK0pfMSTp33@ngXLw?r1IrxnYvbnEkuID; zfC}Uxp*`!J_Kh=u|AuHmyetl)J*wQ|z;zmbhi4gpeCL1n`spn40~1jEug_4zn=l-F zIhhG>cl7rkW=r&ce$J8q|G%*$;w2J?2zMUAw4pFl8h?HYHjG%~5*be*0=B27)D$z^-suj8IdSQ!!$ z#Y-3O+s-6KcRGFlUq7zgK%`_pE1OyYV`A`+GhtmOBIx6KB~?6@eia%4=pwVJ)NM7wi{{4t{Irfeo5Y7Miy;K!Lr?!|6f`jfQRhe z<-$v-UFWdlS%YswC(FI7{(JBq=IS5|3ukb4RzZH=V0aywQ`el1hyV4F|MQE6Rj*!C z;0@Vs7z?@(+LVlQt;os}{;k|oxO@9P2#BMX55)187ykF#d6OeZZBFPV>IIGA?{+f0 z*eug(nf9FnrAWO1>4IItVlOIGQdL24Px&xgcPRUpf72>E*@4(^ujt>C4v3!ye_1nK zoeR{_4b&XcVW2%aKw72Ma4N8vsEgKI&qF?U-CwUn|Fm@afA4eJGP^X;&EXXKPZZvw z;ovq~jf(z2;5SY054^u~0(F~_e;VAMOoXstzbS+%(+Bi9)G%hLepA!&eBQ~Otgs-ENFRNY3yA(BH%N{=FgaISSGXq%ataLP1_e0|q0|g2CW% zr;*@0G&24>Fc=BD)!n;lRu3P-VDc}bqb<}Tb?KUx*TrU^N>SelR=fL}lMNGfTJLIS zZno4+lA5gT^N$Fwkmt8#BV^%p2GN&oWY3X>1|n!52XP~stDI=mrF9)g&nIm&O4c05 zd$&6rxlE0?wC_5?S)K=(fBND5K%)vb`2E#+`g1LWqgTUHVm_~KJSG_JsXhbC|KeRW z+P+(G>db4agUOE{Yn*ql1wMwQE|~YWD5~m?zyG036@L8LLR+WEVC=@I&iIVK=YmpO zJ@?+5Kl81Iel(;ohHHBlDUY8WyDW!j2?uWc%Km1Xsp_odg^pu+6JOp*!W8>s;~cA8 zBfE^B=E`yO0b_>TqpU(1d*^0$Y1_xjqU-*Y0yp??5gJ>pew|kezjW`|zuiLCxOTz| z_R9svvR~&l9u0fEgpk_%sjX^PSs3L56J1)lNf>|Zl^sn%aSDT&kwRWYpi)U52b|$2 zh{4ec!|2A~KGLUu!FE`XtO)owM5PJwRhX;^v+yab6ptnBek#pdWYU<}M?^u;;21AG zGCbh4a$u|!;Ya|{slcMs=hLKbL{O(w&Pv@oeX;l%1B=kNi==^f-dSEHX~sCeOHw~_l=dQAC2mROnMjku4k2D=f5bMOcm+m zk)w%io^IlvnRb03`lYy_u)wx(+KqajN$$(F@i50%A59FXd8xEt*?cwpLNT7{Nao>G zBRYau4hY9Ne2yKvxH7zRdq?2_AsvXjtaHYP#OVp+6Vf|e;}=ZBR-VbTVo$feF#4kN z+wB3->bX^<)7zZ@88)lPkLjm1kXC%N!m~GKFU?ZKEBCStz6tt1J5f8qzJ^-UT=Tx3 z5SL7^%6-Q+QKw8N_*cX)F4E*Qm2oveHRr37Q8%M*N9j7%e0(OXEvD>~>ru8NvZ}r+ zLXz<0-IHLMG?@=FC138pu;PrtO1q}sQ(Mnz%GS;~d+SN2;-Ai)(xg)4fWQDN<6VoN zo*TkmXAUj)V1}#;(YInYVq0SJVrp2fnX!;lAj4nduA07m6vi*1cun3Z{c^^Ze3>GJ zLaWjbg+WC*MLNX_1u7-~DAQ))Z(n{a#Lh-}UJiZJ6SMiI^^N#t@B41(SG*S2nqs-4 zZu4x*GsX19Y{!gp`zzNO`>9B)e^R(4`%YsV`#JCO-Jj^|Dj$?r(|(6h-~X<64V|Mr z`=*mDlQptI*#yn+9e>%})7)Z`d{T50K1oCq6Oy8steDt*78S^;FQ8baTqcJ~&rF{x zh{^3u+mdH2Y(s~uOQw%xNfdtSJk{|l|7Xti);Et*CqHB9r+zf9JV9bkt9fPuPAb!@jml>HvG*0qSeLKm)Ms}h3tiH2DNsseRF+Z zMZdoODi_8a*P%Rg-z{+?&gX`}O`5p!gtoYx_@@ayaW9K!ZB^Z_?>`e67;5uL-1S_i zqRph8rOo0wD()@|tFSG9Yt*23wldSS^+|LHDh`zt-H~9$XzkmrXZhat>?2Kw^%iuG z;OFw6ZXxd*U;KGHQB!=g=dFcFI-dlZXliu-SV_VDyVd5dogZnSMKt-sr~-LTs7oVJ!(O+C(9-n$!tVIVn_oOzI!|p44w%BqDm2+;nzI8FM8}g}Yx zwT6#|L-7M=w>6Ux6-DU~{+`R5CRq6~PD|yW&fYB7jILP8#v}69FvIhI;(OzJ;+IPf zO4`hn%s9-q9Xdx!ExNQ`J#14=R8QPZ^mdMRai4hQFm9VONNPthI#6=oRsX2``=ZLA z>H^Ec^U+oPWN9O-k-;SR?do-!wTfxg1?Pbt4Hh)ZRr+DO%IYoRZmJKG%7!)`{MMJeaHO;Pr*kB`mVhmjO>WC{i*4~@zeCBfOpc6|%Ip{THUXn~ zOY!I83SuFq8eMw>Bk$*qSN}v5ea=tI)yQ`?o_v5(?r|(Lbk%?9)0&*H+i)g0_|-2a zt9(>85BAXG*49jrXjauLua|pk591!jHzf=02yGmGIm*dVFaMCe+O?Yg=kwVS4zI+P zgoK32L?ykkn6Q}m$V(i{N4Cc<+PU^Nh8N%G&zuvEcA9JCdsiR!)=hla<(Y5hX5LhV@`EAc_2dY9wCWh6Ep8OYXaQd8H zHQ6)$+|U<(gPBKcMB*6}m(S&+^n;v(uD#JRKZmmkqnV{bv8vbZ3*5hWKPAQKZRP%h;n44~*ZAljksU?tluSiW<(ByOANBB&p1WU1TVu3W zz4Njq@sGpH(XAHGHgVtB9edNdmD-m5v&_Gk_au-$Z}qbnbN@_HVBL()iuk zDS3xGqAt?qK4_m{EDz;1?!#bS>@XPS2@JLezJ-~A!Cd)ausIVLOf(J#qjP*)r7i}3 zfPA1NCv%JkiO%*A_zsDqysiuQI|l#9>E6)__y_T0h5NF^Q^Z8n?DY3IsGYzSVG1(R zS{?%n!|onh<^#wTZ#}O|MAUlJ)S~Yct|AcDmjkaNsI_r*8t1r37ar)sY4WnOFRGyp z&k;2SVlLQXu10iRin?{@6iLLB4Z+F%Kf}|vy?*r*%HL@|tS)e97rJTDCgL)s%rN4e zJS`x6L?H9O*u;I)l=}bq#tS&jng2l#7gFngf38(Xul7GbKBHdyKuY$1e_=D^|K$SR z3+Ks^L7!l@1Ep47nfEECJ*L`{Zo4lHk@>%U{twyj9)e4Cf0L1eKroJn81sJ@@7{^? zBz^w;LhKF%k7VM@|6pCCBP}cQa-`V&M+}cX@(Ui8JS!9BVn#^^5PVEdUYX$F5JCX{ zHBT-2>9(J@qREy_&c;K@p>=w}b#^;34#LcV>sLH_4-QWM_nCWW!42uQROq2+yM zC>~w*KmPO(-7Pkxvqg&j^TWz*z%c6Dow#^5~UX z{@k5cQe*uTmdCeEMpF8=tfPzAh+19&cLn~dJ4Z81B?(KU+4f4V-ml*8aBp+oeRf)I zE4T1hC$ZU@=;rjvr-xo4yWm4$$ny(~IR~#(u~nojB$y{!EU1d)k|wr}*MB;+Z=u}9 zs3X7^I;Fr{{o!>Fl$QFm4gR|qXL{(Mn=vy(kTk~`&RTS)ZR$~H;dIzSd%xR;i>ioz z$lO$El@7Jc_fKHlEVyCZQ5b97 zi!x#5ImIU5RYA-8?^$22G5`^%{{DU8V{CzJGNHg{U7On$H0pOaBh17_UL~L{XDhBm zqkHoG-?2({Rcv%s9ONz@Qvdg!d!jy#M~tqVJtM$Ny&^w$mKj1J(LHB>TL^huHBa!6 zJh;?dHWaxiAHfz7?&q&@jV)SX52sKLuD!ST&948|kq4_hCw-R4mU#|JFRqnwVL zb3M7*g}sOzd_Z2E9(=uN^W!A0Gf`n8-nGv448;0J|Mxn+_}4k#rgtb}GdmqdC{%oo zT1+IdK~a@6XTwF`aAx)YVpl+bJo~>#06l)A4X3jzP(vfA2bcSf=j z>Le+ACI9ULn@^$b<)?W^p)nNO@Krpf`Lqj-YP~kCSwtaR(5dl&aw~~g*`_y7x8%`F z+5>qtw*52Ir@m{b2Bx3fTf6FF#{PnZtSXb7@`}EWZ~JGNAdKv6-~Ib)TE^tBj>mFX zE?>raQ-3){l8!z5_fekPn+6X)B2|ggi5ibmoO~b`$*Tz`NjzBaUyaG$Vh4@w9RADZ z1SA^TJp~YLoEGVK6k`7F5bKM|N%(LWu*7Bpr(i@|@NZT5%j>*v5z7cijy_3Z{P z1Ql-s1XGT1C&-5`Vo7Qr1^&x4_d7M(&L8@jRlAneCcHURjJUENz0N+Z&iQ?Vhd3#p zW7-qHjkSmzA3C{>OnR7!UmAS9enmPaFXascjrGHSp(P-Q4=i2+5{C4^Oi^mf8Lm`T z;T&Gr8x|S8C54qM(f8>@w<9I4zFxdt?VB@Lt+uvj#>;nUTESRk%2wPng?H0qVt0== z*sE;{dF5}aTU+BxjUjRm^X)u|VEP+5ZfNUElkHiSOU^1Jd?WQ$S51TQaGiAwyR>OA zv&DMVnKt$N`UYIvH6ZEj@f_zH-mQa~@2kRtBTvyG|H53r?caN4wZ3pmq3Duyq?X2P z5Cw^|naUNCk7m9(U#hytWSQwvbf*j#wkG`IgA`PIBWZ&vPFG2{gd!T}SzXR6oSx#o>Lf@Ub+f0eCO3ca`@EYl17+}^1M|C7ng5b+rXDJF zT20A*d$U<7eiwd~nsz=3+tc!u$j$W2xm6-LtfyTBiP8TX zNwnoVS1x1Csb$2kFTe0qZRuJvu6jJ%nV}T$?rtz8qv$!E8}8qS*jxfuK8*)85UC*fUjj4#v3hJmsg$~vx z&EdD2Wwqk}c3$b}o)Tn@mb9AwH8aUPFK@$KNn-j=F|B*j=A)PibGj9~(<3PtC7jl9}>w&e`t2 zY`wq%^*#cuE(-YumClpR(Nbm*SRLo{D@MHL^D%`GmKz-;c_2`DESB{dc`pjEutFg8 zaNde6NqI{l6aI>cQ9NOp;9mru5%7aT@NGn8I&Iy@mBy!h)-)>N;# zKJq_UZDRI6IHU97L1PFF7e@X4y_XjRNOks(I=5r%+R>PQp(xYsaUN=n&c!QPuT6Z9 zP=B;HUs%uN=mNn;xG6Cj0+G+k zCt_xwl>l^qnXsGF!fzdu-)S#G*?E8OEB^OZDd-3|FI&6ywiw=YZ#6J!n-j^M&g-4> zs+upTUaFesO@-HR;I;!25M{2!=lKS>{r&IiocBiMcpNJoSU3U;W zMot&}yUgXZAK@;bwTO%~js}Zp$p83od(>a@bl3IFhstuP-rGyV)$YVGP$=y>r>dq% zfSZ=HUztAV?fHYJV#xUi&)~(6I4mj`ov)XHdc&h%WjkA(A+<3x z^YiC>PsaZuN_`mGP+sIOLE(+qi-y*}scAxhqsOW7C`>eMLjr!cw-|i_Nf?!|*g+Ds4gEODWd*MS9lQ+4? z+INl=pEZT#|3ct$l?O`$IcmD9=IOCn>@+D4v}(b|~Yf~q-eZUJJcc6-Eoxt8t} z9%G(ZJ_S5*gAVnFTZ0ZZ+x}ad7RLTt0;flKYYaVZ|If9tB48hQ-2dG`1Elle#Db*O zl-R$}s>Mgex|kuaG#&#C#_D+Be*7_F+Flc8*^8gCNJ|;_{J$_uxCzR$x^HA4hy!DP`_7+)DIN<+#!A!sBQPR)ONU1rlm^JfklgCv0c zvK71t%htz)hBhK#?2(Cy2By}vA*8PLKeLy%HxB-IA%niBCoeA*ENNH3%MRDpYQ#1_ z#`ZBY3tecfa^SP}m14|t(|=V|tTAj;hCtis>Y`{&O8OTmiPiKH5)%{2-rkN)Nr@%a zLhEreF-e(c^jMd`8ymAg8dGR#MMa~~h{{THbHR&av$Knfixg2&=V+p%U~FtOG||yG zM)x6CZCmaLaH07{iadkL2aTVfN~x<`S$T?p|FAq-%w|$?k$kPHXIFjtVQ=1S7qi!M z?SH+sfG-rBz26Kp+G{t4l-g?v86^DFE0-mesWABQq$4ViF*y7n3$NJo&KBu$aphuR?|+&V>wwYCvUCuhTZY9JQ?G1ntgNhce&%}rbG&FU;P>z1;y?uW zPp$c~WQ&`L%ABWNKyQ>w!=KpW1`qmVN!T)VaFBLcDj!b&67!v~X1xtmolP44hhOt6 zUR@yC*)6l7J5LhVIy)=(3N|@O85|xiig>U7)WYrd*rHtt4c&Ux-};p{)c_G2bX=p! z4($~LU@L<5BNC`8Qr{+`7xP#d`xF$=F0sE*(k?;Z1-9rC3soGxJo3)lT+81R#ou=e zp15YQL{Gu>JoL3jlMt9xqY}=c-rS5q1o3Opt-2Mdg_@kE12v5jVdS^l#O%I|H1gdX zbclR|X;!4!n3zlwehnf|)OBki{-%`18`5m|1$jkLkJG}=tXy1I2(EH-b8+zy)g5jn zI)M2ohG&0Ta@1S3nv#lH!h>JrJbxk&Z79)I9)ZNe16cKN*G(3w9%D~0EUftf+->P# zciPw=)#sU|f^#4BU2p9(CZG+)maeU>ZES3zufO>O(?Z99%AC{w;^!&An0O(P*edS# z>iY*e`Pjf_ORd4z(-l@ObKT4J1eC#8T(MqHB^Q^Au&rQf>exa*mUFC;Zq&9ap5hv=}92G~OidF1!{odJ1!Jim#;G?SQ?{5_Rcbn+;DFS=~?*MNXSZ9ke8e%B* zMPVm}lTQsF(S%xPG#cBhr^TH9`~69^|H-NCj0dK{fyr-uB@tu#9;_>vr#jD{iaE6Q zgq@G%771ykO87|Sc=;UO;i|mo4ehY3r18tUs=mMXu{_17qp;NbqYyqj|Cr=F z5a|f&qoRi?KUV8=7Zga{yXUxqfn)mf5MtGajK)%!ii+Uigs#?M#&69UJOrxA%J=W9 z5;J+*9v^+alj`fuS4hi)0TsM_kX;o?eYV1Ocaknuj!IRvr!(T;9O!8RY64yBhY_%t zPWfKj$mode#^vSa(niS3ewppErld=}KUMHNa3kLM5aa6RrERIMZzf{8;QjGhlc$046m@9 zWz2MRG-~~exEP-Pe$G>*ryPYbHieABj$clt7HbgoGcj?-Y4|^c@fEs{8dgv~e}0N; zxdZ;IYO^~hjGRTJvbebP+$k!dcrh`|ySsxG+uOucKzYKmk757kM&k630T;@O)r>0pO$WXeSJ8Uny%;GW4ChbEf=82)6qD# zCLe+pNRo14#CK=qQ;^i7M|yhqkYnT5&E&Ptsp-Z?IF7-&jF(v;6S$!!hGYI>PSY0rSTrf78GT!u9sVoA|kvSI<0QC}usjR>IqN%L^y? z2`o6y+kFLEcgz2wxHn-lFhlz;=1|A&3LuFAKC{({KIv3lb5fI&)sW%O$C5r$Qj`PM>tD{gTqHJ~yromKja8XJY0+HiP$LlNm+n_vzhJGgmTN>0a;eNjN-agv87?C0g zvb|2q)6>gK_q=1fco1@PjtEfRTMHn^z=-g;>(>VA08G~&CTTo zEZ5z3!=>rk~C*|mvA~9SA2X%cyQB^20Gz9bc^U%o|{P1JkXUx!fLuM?^&6#wjR(y1Oxxqp7Ql z`yT1$?Q}gN3iLyf1PsC;*o`PlZF2&XniWy256eUu+^T!$3e!H@7v>S>p(DTB6Mt?9 zrkEdPe*XOGwVZ62=;NY(awM2}f|=|7P~sZ?jD70X!VMb|NxFG^dZK7nK%?YW?I%9w z__0clTPl>+-u}@|&?g^akBNX_Q+E)`*FB`eeijtTULR)zF7&~?@sAidru9*U&Ge^7 znicuzocuSb_lb0)qvv*sh|JAFaQtpI=~$BV;OFlcHXLkYbPr?pAGDJSXF79+@iw+r zgaN8z+spk8^RG@enCA(6_&GHy76JkI*=W7LWD%bG*tX_b5Ct{#-Cg2*w$z`Nw=TTA zZ!tRwA%jT6`L=`a$X?ARZuWXs(_1Ejs1a>1<5}MtqilOElwJSYR3fgdPcNU6cx)*- z(q-l}Am5u_f#8y|w)T_gNR!1+L5@*TQPDY-aau}ZCO*b;}fcB+rxMUNHiGSvVDuCTTAlc+FKAm!oNF3SRZ-C_cWj5_GV=w=VSD zWTag5$i;SF&u+TUj+#a3=opYcd74x2O(lNo;n&)2gxw(J`5UH)F~_Kwm}t2F@%}B{FW{Xo6^i1J7HN4S>wKI74P$s)&bu0n2OO0nIEgIyp717ahWD>Xq2 zs~Xsd$hK6rlS5q>VVLvV6j3j^_1%K_xWvR-KmNtL?f#AvXH=C>=!$6seUe8lJPDTzm2qGX` zi)Nt6XZ3eb9BdF|bH&~?bCcANl0gW-vw=fDw)cE}Ae8p;L>pzjSCvb7h2&JxdYdf_ zr+0Z-`PBgD6#R#>i4hi}_T;jWWctC@U}3%eFey2c*y9@n=uT{CA|iYyADo-U`_KO!Hg^6`YPPk_1vPB5-}yx1U=Gl7DWmLg&sMA0IIMqptTWlLfF|Ry@iYD=vnQd)Y(}iH-z3f0$~fa~w`C+JmwwsWI>DVgDk&1~l)vgF zZ`|M}KSg;ol{o58+%h#Y!&LzD@$KqTO+mUXFO7)bI`?sJ4jPdHKo|evcLP;`N|M+1 z(g>s4{bS6*K}bla2Ch+-$gQx3nr{0OF>Gvcagqpj?|mrQ=?h*5J0rDHR$=nt60Jk)YoL{Ah7FgXFR zrk=3ageUYPTvb)IU9xl%cd>b`kAv0hLs;RKOt(`{Dy zWFHuQd@%4Hy4829?@LI)BZrl-GgqEb5rfSmBqS6rBm|MAESIE-v^qoVJ5nLKn;@?j z%b7^wD7@g7NFgu(s}6_+0Y`^mPZXo)kJn;*GhDz_n5UJ}B<>CMG6YwzUE^*6W3y~@ zR5q5WnsKK|67c><`zYVY(9ndS@0NsLU+(XZGJBIdy2t+d@xaMlyJ^x9mf~A()m7yR zO01rq^9_?*39amBfsip+u5Fa_n^s5&Z2Cgm8&3z3op z1qIkiV?RG#U8`&SCO8Z{={A9cgsEwgDCows($g~`mS?45d$jOXg9(h2i;C%BQ3%(_ zCU(#sAfxju?mBMlQ$uijVxNx$)lWPQs{i_!x*6z!8kSvtc(lJH= zDMp4G&P~6u?!jv@rGLIzC3XpB@u+##YooQ^jxpvc67q!)E1VyD|CCuwM{=SN*s4Tk89 zVW4FsO3lp;56|K_ecluF_eLZebFPbJfU3eIL^hig4Kjk(^=_*yTaWRhk{;^{aL27B zk$0g6R?R3m#n^!S9PJ|uzzKVLa;Rc4bH6^cD5vJx4wlz?2i&x{aYH2u5fY+x+Tupe zm6jGUH*Rq#mVw|aei00%fP{{>fC+fV3{-@YIWD~o4gGm4Di*XA4)t&nlKI}lgPTQ= z8AsR?e-1+hxNMxIcmZ`2RO$`j3V4$3WjNnaVZLOgJ%-wxcl#&^! zZV%JJ+Moie9<4+3)KujHebTYc&7GxGB{2xtB>D<__`^D|x0lFaaJhk0)u2%WF$N>KP|23bWy)nWB71E3^FK0 zQi~oTcCezUTDyV((+%i{kc7nhyJfZ{T3R2gyu5RJT`byQa^XO^eRmh=78)98S~Y{b zKxW){QGd7Fq8m*|ww-=HI@jx|wGq2(jbI|_SRR^2_&hnMT5*5(1?tlNMb3|_8vrXX zz0!`UOa$gEhoa)aVZiyDOg<~`f?@Hum1JE269G6jyNhFgG*RXi1j)%vOh!hSloX(o z^L0S9m9+Gn%kpmo3=RjalBy&awE>H9!K())Gz8dcY@bsjP17rL3xap=h$+FSdqpl; z!q9+RUMg$qhlWz{T^Pe9S z+BuFVx+LPnY{dZ@XXzj$1p==@b%G)y?v}uC2lgYMj4C*c1{W2!a9w17V5x zi6o<=p~;L>?fQi>mZm=KRx98JiMZ5f;4#4Tt+6A z*LNSWw1hQ(Br9uf?j;(LVyvd7MR<-zgaL)ZZ7m)h85v1cyK!~`X=X4Vjk85d#}^iY zUpj44ks_X$L=mU_sDv~P7yxyxN_6uvjcGWypttO5x>uVY`w(MskVlp{XOShHmM*I4H{206#C+7M4 zOZL^VAS!g>(Vh+Nf%<&*oyg0#0lVe*cJve1sC>ss-!SA9!}Pmov?* zjZ0V2hUffg3GcauoRTceLG&)yx;BFT)i^z6oLeGT+Q(iz(lIPbr^j@l1^boR57w$E zi!9G&Eqr zcWneLKLD9;5;QM*8JXy7nF!HCyOW6!bc%tXQ#5qj#RCU!7f`o}POO`jJg{|$7GHxg zGo$*?T!4TjGN?ZQWvBiit<|*$Z!HHWyJJf?-2ZSJzr$;tzU`B?XtX3}i(j`E7F#B%o{HYir4P!cN_XiTE zN!i|h^+y=HCPep1slUcvebdZgb>6ZJyQss>`RxXHB3hKeqDO0MSWbc1=+5UIPVg#MGIHAuY=a zD6?h11`pIuhlXg8b=QaqWF__I)h_S#rtR(LlKuWKeaQr0TuMW7+=QS>mS>h~gt(d$ z_ogxrY$?Ea|h-JnY7GzjaqCrCiiw)o7IGHi3pf7aN1!a>o)STN_@b zQA#m}GEVp13|_Vt%&^PzUnog?(wn}s|~m7UKf;iq|fTp*w|07^sE$#q-dhYO9on6>hSaoPmefd zz)^H%jk>2`an8b+-@iWvd2b^Zw%is2g8&f&SOVwGBa{{_AmC9+76)LlAhwj1BP%mA z69HT*37b2AaET6eYt8HJHt@=W;nz6z5&zx&CXwu;3cPWip66q}{WY4>ljK_39aO?o z0^Q}_?|z)jZDGq~CwubGS<)+7bfPK~pRfXRM>`ae(1JHnyt>FB1lu7`8NZ1u8UZUA#KaycCrJTd zuCaM&p6m$H%f9B(CA0y@l70RD9^SbkJ%V;%o7iB1OitCj5X2 zjloKhsww57^56Gr@+>v~uoDFPnW_7dfv&F82kXV@DPSGQ>2+sgL)!oc$?|C0(|SqA zIju)i$>O*?T{2p3U4$@$hH0iANlbK(RGXQ`{^s`f=H@CqFc46Vqef*rLpA45QMo&T zf(lGx{Wot=e``DX9m-b+TW~{ta9x||2azYG69>3QSYch2Es(ge=qG@cF0&u4b6_5u z>MhT&ueTPkI4jnx{ zF@Y1Fobp&dJQ@5i?-}D={U_cj_+R6j9zPq;u~`A6zXawS;t6eL9mr|wI*<9gh39_3 za=nsc3>Rf0vMu_w)kJJChMUUE0%}Je%Tfj+Sfr|!Qq9CfL_{jf*iyBUV)KuH&H47^)*l3EX;rSFVT2MpW5&mOYf3cAIU(FM zVT=+r4x=CvQl-J50GxsA?e!a->+J-X}AD&i%}R5Z*R$cR%KOG^e&k21BLPe z>62$*#5T`SSd6OO*fwF zJnLuu*YU`eU`Y^m`u*j_*|K95c@s8xG@;>%C%^LAA#9 zp1*{-DIR)c9P#=myloO<3)<|Ljq-0OAur=?#jf*u!bSDJ3wO9M()3THk0+ffB`@}W zL#n+{)LJ{VEnrQ3BlWovC=9KO>479A{kG+LC@E9HTw3Qx>$^N-tRLSj4!IlK<^fed z@Dk3NhldE51gn?b7GN40fk6Za%)$cL`T{Y2hp(91TL{UKLPFZwsi4bT19}FnUqOMO zzSXskf}|AVP+DBXrSb8A0I4jMxm|JZXU~9h3LNLC0_G~IA2CNq*7cknHxf}}XjBF^ zSBtCu_*jR0ZuFSX9}c4#`^Jr~7@7N>R?g8Lo6+Dj8IAk=luGhoqYLb&etUQ$S*71$ zvbc4khQgy;2sSp0_D2>3oV|#&v~C7>A|fD64iF7_fbtDY7m~*dj%Ma?ynD-rIIguR z+yeZr9s!WUeX0#XZ`TJNO8m_%6*v&T&Y#ECA7yY>s3f>G_Mny&AGaICsliq2}p;evkJog|Y6WjZ;lT!`BL(=?LWcmh~A-m1wVZhS_ zOjGTk>4Pu;Gn0Y<>p018*loO!M;wt1fl>xPdK>LI>164Fg}5qvP4T!XMDse0CcH}n zT6O-dFC`*6h*5i_T$RV+Jn&K6<5qKRVoCv@w|Q6B4=3 zBR^K>j>%6Ib*5bnZ8U+=as!WexFj;{o`Zv=B+K>Hu}=iV7(qK=q7|cm9zI&-y6;94 z3Zy-V;00vc$Epi2*&{=;fKldA=;YTHUc7myCX{Dv5=T-V=PquHYKswQpW-mQK2p81 zsC;6%)@wYczyFso>0y9u+Ro{y7(z4=9zX`k*DJHmC?h=a&Jo*ua7D{KmXX7W4TDuX zrCQ=|cRX+;fFWLGVVc<2PI`7jjgQ6)bUy%e^^7EMH{ekcah=4p)VALZP@Ra9Vh9@g zAofO&fcIVM_y}TS5ZwA`BPIrN;I@)u&ews*wF2WO@GtJ|?Cc>JMS;9i!f~30hPv|| zf#Y6jWAQ`cBBzIUJ_BzD@OH__pK_cEBMp2;$taj7M>Q70b#Sn^NdV0HJ=%0Uh&OMv zly18NsHy^gTUwgSboY>^+iiZUPM!+o1VUDh=}FMW4MF2|#W0dVEG{M{fizFQ!n6U< zYkq!^xCd-Mrlvsj#G)LFfCitVDI)_65dpw3;xJ6Ag{Gyu_!77J$9tQd9nohjY2Lh8 zK>!HN%uGnNj|B!)EQcmMLiD)l)#6AkU_R)x5j-WjQ(joChIA_S3+VH~W8`#vvp$j) zo&|#^n*`rz;ZdR5&E1P>K8+26-v_D63 z3}@>jDu8yTNDA5Ryl6QKc4C{MK3~so=oqAX-C19ZpCX#w_V0e41u*ZU9RI++^UW z${`IUJV0peSL8&$PV0{HrRykQ&xm^5@vq@`u^9M_&`;6i4?mcPC>+OAc zyE_kYW@}KtHq|x^tje@fobT`40Tq247(Tk+f+H4&o|7H#=5~2`G4=HcK?M(+F5SKN ziclqmmz$dtA!sk=D=I_>^y#c*y^p8q1?FP(zrG|G8iEFMFt8HIqI30-M8Gi|OCXro zpEm{5l#c$Cs<)?*Y7Q($Znsws)?KD0c3Hs*(a%rYCCGrA5Xj>J0s7@Y6e_iR*XABf z369|`oJvn;knmZE^Rk%qE!PVG4SChjFc;VGP$@={S-E+iJD-`a7eBD-rMJHBR$IW! z(1vsz^CQq$v+yNbsB90aR+N>vC4BY^EApat{oAoksn5V;8*Ybdb*XDY4) zJgIZOr&l}M*QMP(Nu$r2-q})8hw&d1!K2Aik=TzHm z?``y_R}F9jS5%5b5E6cTwEgaGP7WX@WuKC&2s%i(k?(ADwI#W@!1?$D8@ROYtO7ET+hAub2nZAl z1JO~BXHKuoW{vt**fTnA*;-iv+2i+v8@k+5J_kVK-Q#lH>^a=0VmRfq@e_nhIsDx_ zw`wr?s5?Q1H(-!zPZsxr+|yQP&k;VJr)2;fnC)nCqP{K(A>qf5pphrNNkz4}*W3(x z$@f3IG|QGWrBg|Zv~-B*$vMAyAMFC=ZI;cXC2qW-u?Srb3pO_i3fuU5_*aqO^RP_Y0}gPiCD`S`3}i)PT(#Ybw^N zgZ0xJLKL;INMfQQXO`Jg4q1Lpm748&4^xoLcnI*`OGaEyQ+l}HycNC3g*!8M%JqrS zL8oKIxP8O0SW0VjiAWZ<`ZzpVi$LWwS?UDD{!s zjd%=?z7`wG+S+Sx^^djxy(?qO4maXkfxjs@xI(DNa~%w`+l;4aTpMWsWmHhWdEmJ- z-WWRFPAsu!`R8k4Ju(lS*gCEjt%9-sKnw#GHZE|8=gE^VGGkTq0~N~ysCTu zwcl(%Sagt5Y3nbUodt6WE5;crEbe_V&c_+$JMWKGm|+Sl6Jdl}Yho7Cw14X=Djb}t z0G7Ln$9BqZMUC6mg@0~K=*hs~C6 ztwm-lvZ$(RYYPdbrhT>qI_K8H_ZDtGVo*FZZ{EOwi8T<*nVOmkS?}pZwepu|&(rke zUtC$N2&Q5dCFIsAa+#iLwQkGAa+=I^*bjScgGMB`#{@RwH#l6Ih7f-It+CM-Y2-g_ z29)6&H{2FQedvYVxNolSIN_`*POYrKl-YZV+pWv`wK8I?Q8V-Nf8Ohz+uPsYfuT^c zvVioM@5OG80$F^EQ5f_~yu9pZi!4+XL##76rj^QO2ox39)=`cYA7B=3=YTyk$MYQn zCChF7vLhgXeEe8rK$ezHllu0d!0|Z~;Pj4(K|oI7VM;=xG6!aAM0!S&7&x1{r&#e^?!e^>-${Sx8j_8d_M2-8WkDw^7$<3BfdUqUCn1S;!b%;g(c*P z_&7PY!nVx}(0=bfbJvF&MXOxy_ObujT=iL(ItkPI`q0_$`{ovP-UBcvKfb(QPr z$jB&lP*C=QO@BEx4dkf4o4UFxhh&|QBfB*`KgqStDo5 zmOvH9?w1>1Dak6J+cSBU#~fv-HWfcUb~|s6U@U7_v*TZ#i7tfdbl-nh8K>RJetLXv zfr0tk8fJ=;akE|#TkUK2{hps8r-C?~c!}V}H_!!Zdo^!*&Y>B78fOl@HKvi{pHK!t;n>o2! zPNE>0>cBbI@wS`-`kF2B>fjlm!e4nIzy33LR648Y`Y9YIXNCsUZw(F5N0ei{61!*L zNt^v`rFj{*q2zq98&0n4G;bV-UtP%1pkZSFH`r}v7x&pE(qiR}x`ojsKW2kGy}Lkn zlFAxVuG5(Of+J*WYp>>5sFfxMh()L#iW%4<4M*~c@+md@>uQEYMZJWoHM23J)RzCq zKmQPjo3`%*f|vk6>ATbpOo)!Jeq`%aI}C%lV{R3R9-O3)sB$)E)({ zbSh0OTA!`)`ta{dJ6u3uU_fs%wYGK$uZB8Jq94pDKYm1E9i4a>5jzf7y3&P*cXWjC z&_Sy6H5~>yMXfx(e;P$~FKH-ICC4>WvnckL&CdNKJhs5owSO;yftS!Vfcyu;p&Ajl zGZG*OF%dNTIrMd3Tmh)Qjn(RaTa~NrcN|n`>;bM-Hq*BO_AFwd6Qu#9%myJ|Zz{M( z(SDOr)FMkAIDrV&fop1K9gm1z9k?w?1%tA%nTp$uk?pUkyYL$Fc4aio%GY0Y?feY` z&DoiO+F7EIU|I$ZC#Rty3|@g(24_z%v8u{O5E^dz`2fk=b=*MGZ6?7D2Qn=p&VcSoAPs`?) z&YaPfgJn z(%lZg9Q0j&U6&H=x2w2Q6i0Zxvul4FfX8pw)Bu@p_6Oi{c$tAEGTmEo5?T(Ck@34= z+LLd}z)V#i3bXtfZmMt7W@fca5_0Rm16gqVmv?d9-&_X#mHl@hlS0e2Rj%m1@SeTx zl`bz&15wBzw(6XwW>~^IC`CM%{(E%h#}2vq|MqEAlSN>pV&l7Ywgzc)|@|I#}IS9{_Y$4yHsCQkX6Id{H!J@DY2*`r+)wmB;!`{^GnXng@7HCddaL|GIzzsbL*0S4+2xJxr+@UOt3J0jfTWyvH52GS;d(}(YpSA9E~oW zHUJ&_HCkGp6h%KItDQ0|NJ+`fO~D-#IqOrIxdRYmt%`2UpW%P<3t45nPAd+6qXxC5AF9;v?zk95($W9 z|J|a7N`ma?0hn^*n!D8Y%EXTEw*OOV< z8QF=rH_TISLS~sZN#y&i$5iL$yNwIpm0B`NI{r#kg__&M`Re^8=^m<=r+p|s-g1zL z{`@(UZ?xs%dA1<3qsl~VJo~Osrnv^MrRp9MG8!DEqh&y%$6JxSuB*!q2<-Aqy@xe5 zfTpub%e`Hay;^tc%-y$QVecWkVZRb^$A%XKzckVbJ?mE@hUZ6k7rq*I~QY!3k z0-}x7-htOkjPmqEHMM?D8tKw;E5s-H!Ne`$ovpF!X*jxT{Xagij$(0X-g>^tzphMZ z3KVCll>6q;65;a(mgLa4VOSrh9&DXs{71k6K%zlR9LK5POm8_iTXPVwGv0sN4)dM$ z9Qho3fKOoya4-}zCj5xUEd?2huboL3f&puM@+5%E zFg(SiM6wjm8CVS5uR=7H?|hV13^{=;suc^Cl{wE>l&d1aI2s5Xp}ZU(+)Wn{U<&5( zT+-s>=a-k4<%Ch&?{5d~s7~8${CeUtRUGZkidVhFPEt#${7-3Hfgy9v0uK7!4^Y(f z!J!#5?O(rA=yYP#gGxRaU8j>|9+u3fPBe*k`Ij;+&*AaP|TjXny>dn1HS3(j`N~pdczuqGQyPGFWaO7409i z?n>fRpnv;C{e7D8`_Oy#JQ;RZ9(j6Z+Q&~W zTzK-NMDM1{Ril?t38LrpUGw+Gzgde7T_fzhe86K(Q(8bJoREb~gZYoZ`;iFbxm`mb z3sGtFP10gv1oRj-U15WI^CnZp%h%Epj|Zv`9&c$`U51!nK& z8T3Oeq62^|BDH-Mm7~w+<-`*KXbo<(yQy$>Q;YwGJE$@xdt9dsniF;wt>Np z-zaEt6WWUaU{OSJzMQV!xe$F|4!z4Y(<}H?bn3wF%Yl0xs%P8><~=u)&P=`Q_R3qu zt(`!YQunlB+3f*<`WE@!ri3H~(yw0{QiOSpN{5;=292bH$(`)tdj|iO1Q@}zUy%Q3 ziR&$6VPxD**Vxq5*w{oJ_#TMd2)nz{hK{^)=+vDyKFE`i`04xO?<9YGVaWc)qs*2P zq=&J-qJ)wq_JpDLkp4Fc!W@7j7NUH`{lfJF_OJ&3w3Kb`UO%{UMw}9lA8n0RG~PNM zwv6xAG1Q>8ZKJoPI-tx2Yr(m_P35jSV|FzfguVQipB_-)cAtG0sz4s+Pqys?{g4tG zsgoLsUp`Zrm^R-!a|Uig(e1?rfO{z_`uWvbyfL5xYLeNzBmDmS$^a9(KfY0sZAtw5 zDs@&P+GJ-j-erij*cPU%!(p3$&|?FWSNn@6tL;IeY%5e>BvZ`?SzJFEc^~>8*ioKd z3CnEjW0r|#MY0oh*yfd$liypZY%iB`et$r%8z{)83ycIrbD`|a9*$M=lzKE3bk2|$ ziwQ*XN^Mnkghjb5Sl&7c)8TAg%{|b$y4I4Vqcm9a{5hayPe2UU(;(e-4-Y7%#Ekxs zZTwgSGCNvN)DJ!WyIonMvJm1Z%Jm{sGAr5wE@{lh08*Mcxmf40ih$@e3|S;&Gkbay z1oUb;wdGFnyO#KJz|OU*g0Y%9@Wg>Y37?Y-Tc{3>rOBVgw3O^*zw?xUEyC=z!GxM~ zYioBhN1XZ2Ydb(_?wM;>#`ZLjaPgXzU!_7|3Io8GNeK!KeR)vw{&=KtSw9Yr?AE(h zR+lb$$iA`ZQs#XJqA1Wp{2_buLHB}2sf2%QgQe69IBvJU)x$U>wZ)Sxnd4Bkn6LCZ zb|FD~4Y(Z(BdTKMli1e+j1X z)EYl_e?Kl{23Z8#LReCS`>Ff`Yl|Xi5RgWlX%+joayUKU*Z^Eef#2qoxj&|K#H5(` zORp~Pu`zYcT#$q!&+bIpOCTr)flW{DG3J^9Ggz6I8W7lDubvs8g(E()*iB;YGe1C1 zU(y3y{C4ZgG}Vy9fn7>>?x?639B6y>aqcHvB5qk$UP`+%0izO03cio^W?qi^pPEYN zDfF4uM~Fve`MN_YR*|$ zpibjuY5ql7FxF8z4QSK?LJfhb-N~a0-hJ@&y>F6vY{~dPm@3rWDeRVZZMa+6#fN`J z)@AQsANPhhP(@{4OmlTRj|BA`}!zt{S&5VSX@INB_TFV5Q2eFx`9MW_oO|d2*beK$%CCk>m~JYNWlGYONJ46 zdAY$|x?}Iv06qg_;OA>=|Lw<{choY8ZgR8k zJW6uE_7=#Ci?R2hYJQ%;SwWvj|8GbbV8TD07ep@v65kIFvHVe9gEjAb{_iL4j6VT8 zwfa#uSyXR{t(RMxLJ9r^MDAuIs|Z>Re~dhed^2txtiDe^EDMWAGN*mNYo6lzh>WhMH;Z#?~@Y&sNf0zfQYxiwY@zl}~mf5Gox{ayo1&~F> zFNx!)k{Ke<{J}T}UH}e4)P`0G7+{?1FK7kZb}F}+`|g(aI28OO^qG~tv-4W3IQ+K` zuxWWJM|bi5TXNmflL?O3bmukDvQv&4p~b>u)RMGa_AH^(_r%_KWmW-II$Z?*SaLU` z`KsXg*y4qVXn*8DowP9q7m5VDM||2Hd=_mNe!ffcmBTLogp&8;vn9X7@z|O_11nyG3#8Zbh1t+&}BxbOT*`gAV^Ac32;piMs=M5bO0GMpu+a}o6(D%8X}If{`(}U6&-E3F`#N>$}D*SY9i^W|Kx0Qut54m)@eiqq)#ANx6j6s$D%L# zvfnSXv=r?ca|*xTb#ZBWbmHT0P~a=8w1e-duk+eFm48XtRvZ$#5S4 z9gOFfjkZ*qZdnHjm5q8`_eEp5&v3gfoJK@~vX-}O<3+>@*WN7~@`k#6;x_Q?;WH%i z)_g6!IMiDTw%pT7RuBo^Ttl7J*{Y)Hi9rSr%IKf?_JRz>{ITwoa7Zr3oiG(GBkCI1 zK4A}K-jl=)3%ac+c^VNl0Nt}yD(rQOS)5j>>q3EvhX8zqgpIS9syb2J6v-Jc(v zn>zLOd;h?%24fx+H=dNW1G&D2a6$Fx<5|Rt)Ob4W_q?=zdp4*oxUFF|Od-O^r=;2> zsGs0(sM3}9M<>6owBSSEYAvQ~mYW!1Qa2~LAa7G`4x~AwyD*Kd5cXU=2d(Mk{O76g z`cXL$!2G;c@lEdunV{}hja^40i?QPea`RRPzOP_p(FdHNx8L>SGaqEMnX=jWM@A{c!R0A9FcigP!jdoI4pj_t-hTfivH0FK zG|6iZpm)q#?_#*V;Qc+ZXmR-Xfr`%I=N4DYGHAt|YJ&PD{GZ7cq>p&8w#l3`W28;{ zrDB#uD_62y)3koPe)zXf*l^Yi%(&xNuS_&}bQcsT4kD)w{v5pR?N( z(Q*c<)n|#kv8Sv%QdJ`e#>(Z+VvSY1*~GDd3rloDK|`BNTjo9T_eioMbY_Z&dN zea)-@7|B>FWwbs@+D^y1ZZ_4lUQK$bz|e1Zzp8!ua`Nb>=!3>suc*`8!p!_FzgTp_ zbE=f|_nj0!`%b#ko-vi?K2v8IkHtT)PoU#3ap4(pX_}u1T@ZTs#AlYC+vy^3_+j{VXJ z{7>MAJQQapL6ShPK9*0O`v`cuxeFG{ja{Pp1+nOhod-eo%qb$B071aR@8S z@^cvZ+}3X-?g0`U-JwgUX*kCXF+t8r@z`)ZAG_9a`QepaZMRHcy4ZG8Py60oHLe=m zQkO)ARreoRDw!MAs!AS?5=Or`BLNY%=H~eEIRDjjSxtztx$My>c~*mop1b0+Rlbz2 z=iOmW++AW4r;`OCBS?wE1Q;>7#}an;j>%zO3XtxgKR@(bna5+~xiSIRm=I(|1?zz_ zjGjmhusk5um7eB^-X-u^vH$MNQMM-0Xnm?AU4w!@p)qPHwAyE zYssa_ZSJ#3iV{Lz-jZc(yrD{sZV z#E)r5Jco>g15W`9T&HY5G9k6HA~6*=u5Fyv)$Kd#l}dYX!AED%sl|{^$25sSR>5aV z&ZqYNt0iashN*7e(BB#%#)!kGZf9FCkG57j6Yb^g$}-dvG^ zJV2ANfe$kusjf+n_C%T=3fcaot}>m9v4=up%shcuZRR9{X87Mu1Yaom;~<0W^4uXP z3&kmzUlxh)AVzZ`f#^N_47mHlyMte2xu=8712oHW2)>DkX`a5aV>uNSDHIKZ?wnb;zW?lNhR=W>hH49YZd-q}%@5R(bc*Ic^cj zHoK{wh(6DSt;YGTWnW$Ov(sLwxf$l!ycey@Mm=+L7Wqcj3UkF4Dy^zo%*v{HvAW4+ z)icM7cf?os?dV2j)Ntgm>q@1iSD`tQoMs!-ulXJ5+N-5gV*L@38KuI+FomEe1iE>} ziGNVhUkk!%4*V@7j)nmYJ~EQ_|4)MV)h2H7hSzu#x7HZC2z_VETimFz=xF66b_dlgzjWx;hy)9aPdhx`dlD5TE;!?+QY4RLbVna&Lz zc3_-erjn?h`@keoO@)5^$UBraQ>;ENiRL|d>kDX%(F3;~32((QYGe`KM!?!?gXu{_He1$ma7Y2mz~5p2xj_#W=z-3X6KPa(Sw|;9x88 zH?%H%+9_hgoa&N5dy=*P^K|+(k@_XEt&wH*oR4v(CWvBx$^(D6)JRAh{q5dx3Gr)C zYb^>Al_Je8|F16p-Yy2%Cz``(bvl|9Q1$dAQV{a`VKK=-Ba(9KV!*K%mIj_B^#J#- ztDFCSY#FpRp)cRLOGQ$UO-IT@GEf+KX0fQ`hO=AxIzRyaH>83;NNH;LqNCJ#UsY6b zFZw#M{6VQ?=ns_^!ipHX=AO_HB)X%!wlhycCmNGIF^^}k6s&~2Y*kgr`y4|$3kV^R zL^*4{dPm*usH<-24u(m9Pg2-@({=UJ~b zDApc8Dk+GrP|VAMiDeh|R|1C>-9;X9DKe&`y7tXne20;EHAY-QlrL@WSyG|O_uwkw z8++m6{fcqXX7OU}XYlhgD=g^PwDyr1;1Wwoezf@8B7{HUIs%veS?tp3m^4})#4w_# zfVY(e?y$2!UfKeR^Y>7|?nCDF8*rU>sPYMlZK9v@d03+jV>b4O@Pkgmy&ZnVl8%m| zfBpR)();g~fQa8k{7HBUxTx3YiWOqxu*%NE7yGwdJGaiEAM@6?U!j@Q5f&mxk$CVP zb5B4ohxBgbpY!=h3i|K=fA6r(PUH^rWuajtDUI?Ggm9Qw#$M!`{0-^d@qv#s0q=zb z!;PCrlvuzZ9r+7gksEIW(TPAjDRBE!9fd0s^PE;k`-C8k>MlgUef$QReiD75U8}AW@X}e!NyWhnae}!09^zXr6%AV))> zmmB#ENJ!hl*PMi3+Ogn|e*N~e=g|+OR<}`P(#*RuwHzo5^UF? zLgBrh(&zL2p37C75@UMmjmM+}_^gxUb!D{EbEK7F z`(s?Wl^8Il8o-);kgV!B>J8d-$Sv#5DFA=h@JvGR?NRi&0JydhK@D z3KN&UpcmS$MxJvnW?C)hY=v0pIsWx_h8dftRfFbPwV#B z6qL5U-twLZZT&7`XH|vyMRX<2j$LjEu2$@54BdV#v=${%8;uMbb$I+M9ZIAxJ$vfa zv=aY}VlKj|5fgu&NF<*P`IO#5BO=?*dI#hc5E|RI??P#$zmK7YhWJI!KQZjqSTWse z>(T!`NR;F8ZxH>L2qAahC2uxLXju_E7R^G8{jRGz2)+-^m1vEb7BfR#CVG7*C*>x~ zg!+KX*8Sb_vty%2jya$V^?2EqGXBse*O9e=e<8O;v92V#)cfcWbD8{CftMGTw=M+C6?Cz6SmZiDYIPOGx;-HV+zaH-qGQu69SdHBP^78-{8v*`7|UI zohhzlJ^S77YER7O*>eh;LDToLh(k70a%RUEQD{J3l|@o&R;>0(}*Vf#Xx~R72enE zGnwGYlxY)(Z}cReS@@@npQ};vOByLv#3aXe;`olrZ;S4)MgoO7s_&7sh0^Zkj(AT7 z3a?#23XM+9&#Rx~w%$xQMfd}m2^oG|t1>r#uJyH6&HPbXQltTWhiu!``xD-1w6LUB zzeA$6z&pHF^)pFRVUYF80H)cV!A^LFv`zoa0}BOd0Df77C$}@c!My~(r-A5aDNG%8 zUoK~6@X~_MrJZ|5Je-V)KHm$tgL5;I0=2sfCb*5HT8{h2bH{j_hZEGg?}|7ZbQvq@ z4k%KM{?~MH>YjG{Wa(@BX1BL_<4dhMcEs>|Y7ORBMJtvHh?UwkPvk?T(=?(-Y4YsX z^arM_MX)oZON{edG_@^fSoG|$lQDhiXsNeEG^we!bl1NUXtO1a#ByJKk82hi&&7>? zP!~f8W_j$A4J18*`usSxsY6Jva#3;1 z-1N5SZ76p3%X8#ZTzjWuPaWlb;w#aS{NqvhMne!kV@ACD+Kn8E>4?6CPYsLWD>1JN z6)PkyiR7I}8Er-aU->2WM>DDN{vJ0cRx{tO_jyZtn?545tXi-|qiJewc{770eJL#g zc^mNOFq(z`_9*UPZS-t{6hZzDs!?HGmKOf2j>Fu0zK)|rjhS<(o5fOHQjlg0_K+p& z0);!#ty$jrql;yhXb*`B9kb06SuixrT}n+Cg9+Acc)3y$-Rf~ch>}nI7QKG4^ZtZr zmBcDb?ZMvpJ*H15MIPB0S>ug$mQ1q8PNT09YorR{h<)Z2qjbe~GLy0&@rviszUO^T z*E{vX_ym0lO=ZnC1+1Okk4NBrgkR;({$6e(ySVTqJRedIIDMPcYM6Ch2Qq-ASyXA?d%`mQQrX@hpQ2)dJtF?RWUxWdEeRuKI} z26R5YIZ8fF8f$V*r1(tP=Z-Ox^jDVEIfZiU`Y$W;dA%K$BRh)R?rU$IHvO7*k~}YL zIQX5VZp!mq%MV4)vadvOPNB&9DbM)4{E&?FSq`lWdVjxXNJWinYEtSIih9cAQRnFT zeE?f!WsQ$%)7AgQj;4d=i^I{Ijdad9`%*4bf zpqheicpJ0x@%=_y#^j?{VkNJCptZnmBn{?mP{FY62vXdd z=lx@uE&O7$q06w5)DHog_zr88PtMr~SD%x*?hok_iIbmYqHCPM;m=h-^SR?e&NC6i z&(q@1Z2mE|wN=(A%}9EExiqKXKo`-xWRu35v3G=FGG^WVp?J{NqW-|(zT2_^MZ%u( zS6}e^NsKJw`FTZz{!Bf>Q{sYhV zvrzJR{oyr**YxFwLXe6V%3cSVPD1g@JN@W|@_ygtE+Drq>jvLMtw#X6>`_>gUF`&Q z^<2v!?_SetPPE(_-e`Rh)|&Q%KU5BMzeDeo=>|outjVMO*kJAu5IaMz%U2%z<4MM{ zEab)W_l0}D28H};Sf!GFGvScw-?!KkvED`a9TcjVRr_mWQ>JQb)c5;fUAQARxOjPm z_D9zzyxZ#S4JlexZ_&7#MLqk(ucl~Kxm2i+_-(57yUSrxTIJ$}`-ev=omW;bD#S=7 z=Ip#uTtDg=Mbv1?zgb~tCBL>Bxiw4pUH^TsU{Zmh(EN7&s{kQe#g@|9-}9duKYxje zJE@wwoVVNX1hQQ}i`pp6fBWSyUzC=%;R);{Nig?tL_IS|3BA+My#ad7KFnd|D^H9X zb5^avw!IGq|1is`g>=3>Eke(mTroVdcs!{oZVT8g-fLcbVz@_{o?R{*?GcZu4Tn2?9+lLK_w`BL%yo5;^(q+s|wxMx^Pm{ z{CLLEtciJ(@x_Oefma%iBq?u3Ds?y&Rz7a)O-v#EBfXmCHd$Slv_W0fNtrw4CKu{* z(!u$aU@yA0RasgX8M!wotzy0w8F?8=U(+k68DSeM+u@BGx~$tK`lYBJ_AjJWoqNWOC%R5qgehJ6bTxi=10io$v7VdBf^0kG&E>%x z8ob^U87-MJvmr&%z6%@b10mJSvaN;h;^nZtSFOZv(zDbu%$i>%N%3cEEVyNs`X2k}_={{cCY(H`v_&Io{sub_rH_}esYs*PtFE9)qd$|P zfdZPD6L@-ZZG#~bk;!&i$ddbVBI)>e*!)3jEw?N-CGw)%E1K^urOsJV+8k;>^l$gP zue)Y=m|E9Gj83A|c2fWqRWgE#5aNndQjz{J zb4$#m+~Mbkpjw#dBV&Egu15lN=@E~k*Cq6k{$p5efGbBGSjM;j=?@6(U|x5gN8&*> zY343b`kt(o6EwoN^EPc>)>O^wN(MuF{)(NdIXcZpEw6^v2n=xmTQZoiDVK70wYs@?Z z=MYOM;Kue6Vs}-4!KPOr);frEu4%v%2Tp>Z(_xn{F0anMMYV5eZ3!myc+}%^llnEH z^IOuGwVj=I-O4G|JC#?oit6 zWTK9fw-Yyc_0?IJEF@3dYZ!X+a7zcL)+vJ>^B~vC6!P&EPB@VRzGTWLJ9uW`h;on# zK|v2%!8a-BT)kjT3N-|4{t2S4=Vr=>nG-cq$PQTF_Zf3N3gkXBD8rrYlJwDWG0Eg_1~8ZM2?qCe|B9~m$fq-bk7r7CBL6H$H_@%Ew z8^rm_BXrU$RcnKwm22(F`lelL5zNIcpd^e{SNwV`nqU5FCpM9PM2gB32Q^9Lte1Y{ zjk*TWAWZVE?5GuwVnlTRYL0hgUkds&S^qSdZ!N?SH!i)@xn|^cnjKEA)Byj7Eu>sJ z@bJ5%Dj4#VLp7~Cs8lXLZ+g68B_8x*MTbVKRYtq9O~}-XG+>wplOQvgo=7e3*Swj+ zl1_{DLekT)jf)<08!4u?vqz&ARRYs7Hphu_&TX5>5)k}wn;#?E!$wmZfLo^6By0n?>*HQQl zI5Tan{CW30b4HbL53zHju>f&t_9uEjkFDyXOptnIrf8&Fn29LuJ`1S16tGM3)Jg*p z`Tr&-y>xCS+{YA^X1hf)EtQPv%hRL{u1<)afNC3LZy-8Fj{WZk?2cRd5Jk;S4@7rp z2EX*3`S;TpVp|3pro}sVX2pkh`$&=SSKqP*W^O5zX`dHN>O*vPvPJSr27_7%dG@1* zecQgDeCQ6cbK9J=H7Nq^?@rRwh}g8Ha@e8v2)ONAWrw{=4BOgIVqw{+v}-4QmnLH&Zqci~O-Z|sr2jnR*3h@1i zj?=-p=xED}_kV7W#l$qTCrD+LYM-$^O?ACG8WE|Fp+S{4$)_AJ?!~&)| z!sL+#Iw6js&KEkjuO%@4wR+t4LZh*-p8s^vzWSBV^JicLCFr_S5={<-7C)f#)?UHE za|fsT6TYJ%%{$cHdQ~?8@JR?rsk=Ld+8$cn;~$XS3oQA%%2smnN0SupAiRN<6?zP8 z`f71il+Cb%YsrZ+@C-!}(x79&h2lObx2P}hrA$;EqENh0;}mlH5377phxPT-W*XHf zQYk{Y*vh*)yA5H@JfU?nha+1glQVqKJl$xt1mrDi@zgr>pHKm`5WR8(xdP*dl-5 zY)-@>x{HBxH6|cDHsr*in`dn(iQ#-|XQjrUVKp71ABNyaZ4~1l?RX5U!wA;_?eT0b z%nwcOwW+3)zbeabxL3#xqs5r-RA|#XUcyS=Bk>+-4sB0$Z=FYFuRyX(j)K0taw<=b zGe)N__XLRx-sxbu-?aJ%L%UHnHsO#e`u=6rfBeR0q8pG2kcwNKCARK%lF!Qb+ey?4 zXpm-m&~5(Gx|=IV$7>bt*Z#hZL&IU+^%6Zt!PO>iJysLd(Js{U&NyAy625kOT|~fM z&Q%U__Xqh8g-lkKoRM(y zm;3AK3hLkpj?irSpKMV?(Gu)x=2|f%o)ESN;i?N)qDl&K>BwuVi?gKGRdi2@OUEEc zbVvi?U&ehlX6Jui*#?KAQiW|7Y5~D~#;$({E#|F3N#2EG)EpG{T)WufgPD)M)#DV1 zibnyWH5@hajOpPKNcbZ#3OL|8d190*u!)wnJ9UvlZ1|$%tJDBB9Be#FG59+ZG00O_ zBkkM+exLd;SHn8i|}3on_<^0#4dy{mDbkx;Qf{Ay#q(0yPCW?|;--}n)83wjOQ zn*U#yS>1Jp-xwn9{#wW^qUZ>C*%EU-Fh@ne$hC+&DsjoD7SM(BMRP2>kWEW^H~Lob z`lN3Dn%njo-S$zsx*nW*l2zppCTmj*WJmA^O`<_DYan9rA6l{DE+e_L&G{&F)8c4V zY)=d08w15I#6wd8H7NQ>vjfA9U=NB=-srn8n@7*%(H8fkQiN=UfT2Lb3$X0gQQD}I zVPV>etlb?O(PSx2!2?uO1S-{w=kZRLaUwnrf>FAx_)E%2oGeG$$f}<e^aJ166@%Dw1_ zGqJa}v_INhN$oE4#BPbfMZs8`Oq%|SS0CXo{!epTrJ=SB&1<={ny2}k)fz~K(a$cK zGT>WO90c>ItTvR4m?lN@xcW?j;8#$d`1Vu&Vt+BiT6;P>bcd*^pypZi^R+m_HbKV4 z`E6kMm$;er?t8fG?YEW=VUq);TuJoROVrs{r&!|Nv!IQRE50v|GR_sIHARaX@7%WG z^@~N$+_-~2p^KkZ{hrGTEIX_2T$`;vo?}d`nFtMiIQe5A2Gpg3w-tG18J?mwL3=I0{O<$SU%JMR zrGdp`ghA+68p+IaCXE#LqueiIcc!IyR*;0ax8u(zMg)G2++y}SBEI0?113JYOhc*5 z4Ub9sa>QS2V|^cy93`^?vqx_q3wX{B=A3v%E+eHhA!4U1yNRk*6!;*$XX_GVj0Iv_ z-DBeB?gKJ&k43>)>zx-1&v9Yud#9`3RdcIM=|+$G@j7VKcFJn3f*R(^jOiPyZ-~yx zhIg_bMZsrd0};PIlEa2*hF2W|jl+3IN9vVX4K+|H8UJ2nTn2K8wtcQMAfpAwNxfZ7 zOdxhJP*qgq-v-!e5J^-dvWp#O9~yekuMF%;YMRa!Kk`J`U|}KQpk4V&Z<{6v@qYP| zas&uwssGh8jJMxh8dv7|*<<@~2j1Ox0mdBv97L?kV4vYdiFqXl=V`#07}gNl@` zPl_{xmWAFE!XDcXywdN;Z^CbN&@&LMr`@sMgsE+Z)usd&*ZKl@z#>Mlgzegkwi1J` z^VwYKjLkezix~ZaTi4yR5cHXc!EOq0wBEegBZK7!FsqWva=&?4ao3$p#XVu>;p%mf zUpx}f?I2@7!XG1I{Che$X(PorKs=>)#NiuQk6M&OM}e3%kn?q|g*Cwk!Vpg@L*MJw z+f=-4d%DET%F4sT@vUp$t(;65FZMS^K-i6MpDD;ZCZXsy6FQ}<3qnG=x}&2&`$xdD z;A;ddnIIz#`9Yh!Y!OkNzKCVeF`YKZV`HnRSm64jz}j_`bCnkxLzP%eqNd9ERRmy> zBj(L^=|7VlBa@er>Jnou99l<9bX0hf)wO9Eo$l?LSLTvU{|q<3?-=>?Wd zXo%@!AUGpY-K<=2$Q(q%oGD+R?Fjkx6YF;*hfKo6e~fGf@U5AHyNvMnw<=E?d#nL( ze88?KOHI@Txlo{NsLpn=!Ba11XY@#EZ%r=zv*~Zsy1F9C+I`Q5-+XUu(n##={<@-X z=rQ*6?9srVAIynG%Uo(x|D=!w&Odp&uDt8f0oD_=cd%UEA9dBM#538&t(Q|U^K{y! zsg44G@jkQfC$^XTF$S~heraca#H>28$Vl|(b^)XSR6be-zAOxG);_#%w(KQZysX`qr_9A0 z8sL~|^-CQ5)%tyk*tw_-9g;T-KgP>!$@-ZZ{~m*r?YH32R93yc=yc;Ax61QXD{c* zV;M(r^h0eC;k1bD_la^9)9rNi-6KdH@x>Noi;7b@Snn`;n3VEE;dG<&EnZWaLt9Ib z=DM~u8#;n1$UB44SFH1JRw3ey<(bwIN|($TQ;8Il!C%dFN&3Wh|Me7w#vA=`2oy%R z(>BbxaqqU`^7RKcFFt$pMG#bl^uex^tri=knNy*fr0yrcNHW*3=oawVk>Ktrm?)T6ti??} zvpCp3AKE6!(AHo=)7qj5k$#ff_*nucVZ4+IlHv)5zJ`WxaI;6wJz?hGfdc|pY162oMHe!d7rbupTodwFM zlxe+vlg(#lUgP!)|1*U%(=LP5pr=q*cSDON>+ zfn;R)*oHBEpbf*g!^T>vr-?D8wRK~5YN}x|Z7Py`mbYwyMA}-LM=H$)Cy;)xHYwS$ zvA7~=U{q*{_*>%amS@*)4cQGnmTk{++gRU_y>sBAmJT1?-qz~HFXvjTX>!d`rP$uV zO|;W`@&PHU^a>c019Tb`sSz?AgE!J;_5JheG+o$;*f#puWi6!WJwTI#bUir-F`x9n z(}o#XA5kK=@`(KL(GQ@RgY?i!!qq0OuYH{74WT%2k6TQ^8b&X-&7F~$-`wB$Q^-f1 z|549TsF25Mr91CYTlEqfIrva^Gri~^BXvJgRaB~ijZsz3dcD(;wJqhG<&`zXAT zYFnBg4`sbu=9`f2v!ZC++Tu=Ewi!57UpCt@Bzilw#0w=-u}i>UtHeY^}wXEN@q2eLnOfHP#Friw!@fwpq|yz+J{2 zT2qp^Qr;l&s)skU2iT7zGL%2$Vp%H_(}8|M$6;Bfjp`Fj3PD`e)$EpLRfEZY+ovP+<(OTExn!(&HrJ;jvZelEZ!g z9F`$oMc0SkZPmC6p-PRmpG3O`gT7qhf(1R zy^`_=zG?9(TQ9$AMS0T!1z(eM%!cMGH&5pb>)y06QWErMPaZhURZ^>F7Fr{vZZ2HM z_n7PT_B69?Cl6NgAI=K^DHw!A^t0U0M*>t1Aqdw|IPb2%oUpuq4#yObyZff5vbN`)~r8N4ocuNET0^x(PS=}Sff40RAT;Wyr` z!0)`9?MZ%j(30zXw|_wZ`ZClOFXUn{QLp1CFw(e4n1&>+z0M#?>j3(>Xl()@R+x zSzRqYNwMYprQL=%%wQ#+o*`}nCNvb*Mj|v6vYxFwi)S?zMK1opDtl`5i8QN4CR{W9 zjIqowVErt7w%%i?Bq->Ok~W=>?vXwUnd1R5f)PdrD3(aMA~oeEOf-mS=tvEp-lmCf z6ON~u_>d|Tx&ypcC^q_Rfdd&!!zjjH8{2Hnl6t}Na3wXP8r3&~!*r}mpItLO!X2NW zbH|g)O!=0=p-uZ)SxvSJ?p^uf8H%R|B8JlK2Yol`nepe_YJ7UBvcLcS^=mR)-VK@2 zmDw}k&>-X)`>l=E^On@Zo-wEZW?mENFgFYB`Ba5S@a)({>pJ_RMD+0alDVQj|K^vF ztXItvzy^Hiv~4j}1JWS>NaFw=iq3BOY;=bY+j#Nt^lo|kY=XxWVW4W?KDQY;ziWAl zdU9FE)b38Kl8WYUGqL~vHeXLnYB3_q8nbo@ajp`#$02;_H?P#khV+(Ggp#~LekW<0 z6Tlr@Fq?+2Y%2FtLE>Sy($%Y7pEg062ttCgjGuDWR6QaB@@w-RA3Cc$slaVBp4FX->GuF*z7)Hv5`fFMTgCVQT1|-Ck z^+8I@;x)gcY`TS!Q<50bDyKL+SuI~^vtMt)JKL1?%ee6-nB97Kc+vEj9o**(3PP|U z{n+@(g8cV8G$P|1lVva-W|YU5fa!DL~yZ_p`_3 zq*E$;WW#We$-aFjJ&Sk#Yw6?CZ$s2KKZLNelR`FY`J$MvG8ZyZy$m>7^^&HL@Ea2Qr#c=@u6`c5qcjE8uj9PM__U$zZVeKGZL zGUn+zoPSvtqa7*DsE~dIH7_O2PH8MWZgb?f&~3%60m|Q8QClr0cmsDyT+{kFpP=_{ zL1CmpinL%a@d|WB_3o_6^mtw?jBYL0jMW6A@MGKBvoxNp2C7faXMIUg&oCOwBB&~^ zb^we0!6oIp)S<o@u3*|@QzaI5-|b+S(Hvi-Po{@MHFU!1l)f+aN|3VWvzB+3$47j z(5@~G1`PEq)OjHgWfMGr%ovqW(TP+AEtJ&#$8jim0v4mgZWmZ|b2t$9lCwjqjTjMY zYXRr7J$t*P7j~PcR4R#Nq~1`Ac1l!p5M-nIBoXkr)wtsL6#q{{8r>5H;SQH@e2l60 zgTnLOwGV5o5!3?2O(z7-hvSW+>=_LVrxb@*1RWM~%NMGN`H|Y8&K3Ae(X>`h-$$LQ zo;mAek@DyJ?U|8(99%PoP&c#-s`fmGqXsx_)9Tl zDcQyJi&vNuu}BSvr@g_xibOuLC*QEM`7`sDSunf7*|%7QdVJ;PP3yUGNe)JLLKl9ScX z#{`H|fg9D>d^z&yLiRQt=D&$RCIB~q!z>e*Ywb|tCi8YmIVn-yI9ktxn4s|F5L=VP z7$tT`(djSjcDd;Qa|}+=JWG#?@E0*A;u&AR6>5lekpl2I)w<`jGS@ObI;OLGk%_GP zv*x7?3z|lqWD`F5%=hQhYFDN-p3DrNR++QPy&vwv8F;d*rQil#%-5{MP|sVVt38%D z9F326^*idK@30SaF1pGrt;qGvdc4RyTM<(U3r8)%P|ZD8nAoSXlYfNdhVhN+H*<`2 zhqu<*XN{}vPlQoCwn%G9G!K>KeoavpYldVqL3#uz)q3)by;xPKkI0cAFww-P>d_1D zuaS3Xt*Ok^dz%xB? zh$^CCDJ|}G!;R;B44P4wsStbi@c3QOBe(4vYlb%B=7a{2lNcE(Ypvmel{Y}CX?>Mf z&VXeIC-P0!W8uF?xyM0>Au}_XN(SL+gREso2mAY zVJ!0^mRSk#7vw11B~d4eE~abYC&t>NWu z`Hr0kjuybQbfH3B5>on6_%=b5r$d8vq{zOk1`XW$td9YsnctmFA)M@sI&uo8G>x!B>GDh0_lzD;wt8v+6llBB2oR#qgJMIlsS~ZO|gB&3M zlcpFzsypIWR`Nd=Vt#0$~*VeRvjK*Nj}hW>uo=XLBQq%T6Y-}2!|VsA8QQ-TRaq!#v?pX zuP)hq20`#?y=p!-5lLu4XKp25=slOzjf=%l>jBXiu{GzFJMLN9v(i=cQu5z z4MskjGVXsKI6Zs^VO7<)#VotpKUAux>h7Zbtqgc4XV1ph{U7zHC2aopVZ*|H&0KET z(TUA5wb?nkD{v2N+4}2&yOZEI+{02KT*>!Yf#`u6kxPEvX<>N;Vg!THB{py7`|E19 z^w^D7F=q+AWRRlrAhmzWNJ14kCg7pS!%?i{*WHkh|0DWIGdybkJ{^kJ7Fo8Nzz;UL z^2ncYb(k$QC$Uvi_mf)${(t z*|ZulOWoOD35#*2DPY4J8JTLOlB8lyDmyYRTUkx_1jwGOuq-dD8HqG}JzLlNmYBwh z378dsd#!@yF=F{V?e!&rP6oOM z8g+vEBM4f7a)UBaVoCpXj||HBc4w)vMF)joBos%ij3hr8Kg!w75#GaBC}r_j=ay{V zl0%*OkzX*E#>WmYwg@HK#g{SmXyO4gRAZ(?+SW1UH#;9U4ors*NO-1w4rV^N`R-qT zsT-5`6-V!J4k2h`Oc6@>m;w@HXJF9H#@sL1^lpVa-np*pL=7}&mQFxvpv38s7~P~q z?p!fMv|@1t5!FaR(XZ5*e;hb@9(*}{$y;_bh>$i?8p|ccS;WZ1w!V(-8!#;;Khtfe zik%PVa)-9hv5wGafQ9H$nl$wryD#0oA)0VeyHHbDV!G2yuXz9)Fj(!`G80#oyPVp3 z&pyS7Dcb@;YC-!)xLC^S3?pTDRHd4&WGOdJC$v;-oH4yh9CD)Sj76cPa&wFNV2R{% zd+;5qRA^1aTI-_G@%lL!Dm2}8J8#6ODJznNYl5#$Dw#te%}Z?}h=-z7@IfgR-OX=s z4I<5Pi6NmiY5OBnz`=o$8vrHsfCw2Hf|#yNFjc&ctR$f`ebQnTS{$giuKmt=n8qK% zRXH-?F{_Xo4V}%PiB&Tqb~kIQLTHee1+%0eM|9SkQWTxq!poq(IJ@GWpU0mvHN|My zC5vh?%Z_w>K4dP)B2(qEn_}L4S!1k$n4H!Kj@p%SrWVbm@1P&>Q%1dC(NCMnVNDC# zHEE+)nQH1{!!M5MpmJ1_em5iYq6za?3N>e6v4~gcvSCos1d5i~iRODg(&K$U+Yf08 zVy4vwFBouG;&}>CQOTFEd?r(eO$`in33yE7SSQ`k?VVo@9<4*L=gvps+)d3lkXkBY zx6t3NiV;!c!_AdAo2Xi0e^vaydV3mj6ODhH0!pYoM;oVS(i>g`3rU0(=nYHF<);!2 z5y|*_HQ;<}Mi%Bwe@6%A-Lw8V1fW`1o*$lcyMe)2!Drz?#q`Dd=R-o_DRW1=l+;;J z2i;wKL2*EA(!Cl-|4{G@Tj!MIodr>`Ttevh+q+|Mqq)B~NCTVD!kqC|XgIzl4!#lX zf_NJMy1^vmju}QW-M4bv-H7TJG4*^g;eVPj`^rkz=e264-rijl!Dn_(6NNXi7d_t% zHKAMvxx8lwvUDn3Q+u_CA`&EYqD2e47>ocZowK%V%B0lNv)cOz+-d8xT4m|`c2oh!P%Nu zP21=t{t#^5DnAYwfh6L}=3_*nJ!^tQNhrb>Z33zg0s1Z-IL-pu9*(zSWY4gckd#B- zH@-bRuhIOS@EWPf}BZ;mV8TlUW z4#IF#Ly|w;@vulnxHipH3SBjTTbH>uI`oduw|L}BIUcbTX9FaQb$mW|;V5q-C7fs# zFX3+uN7)>$i`AA=)CZ(put8{S2gsV2K$FM`Ve=~G+$`3UMvBWo#}l&g`>|8dSf9zQ9;hb@u4#-kPI1pca2gP14$%15 z&oj9X1>bB`*(z~umSBo`pb7XN;13RBhH(bD=(sesLM63N9}z%}^Z7-^(5#@sB!YWB z`59>|=cUK2WIKO|YsH3&^w0%^c7>HDFa7O>qb7^e*Un!)hiR;E7=ZVvwVsC9h^SVD zS}SyDjEN1LW{M>XB|I|z(h0WqV#n{o%q?%cH_UANE3gXV!&n%1pp;s;H5ms`=wO+3OB5xq zm)c;?I@B0lbNF{BxQ%0~zV7W9N7=u$T0?FNfil_KdtSPp9=uV_R-$f)qwL_D_TH-L zpMA%&&b<>>k0{bGOP7k4p!YekXzH!9-Mbc(<}R6Z_R!~o7U}Q|f$1pE;}5@cw{{$l zm~^jkk*ePx+V_U=Ohr&C2Y3a-z&oBvpnRS&B^twRUZvZMD_E8W2M4EQAgmZ~3!V@H zZ3Pf6x`?s+RjcR)WfQ80{P~E9F*!Ek4g&6{p3TeQQ{wJz`B@}xs93yZzdb~@t^rZG z2TxeOLtK0e!cb)@5Dg7Z!Ki(RO#pwA z<5@ScWj=Mlkk-58iA^IEq-P{bG<<>YC^=2r%Jjhthbl7aVCLubASY;{qF+KQSNT=5 zfIjK)T6~**sZH~q(TOlt^TnmV7n-&mv`<{CFI$ufsQbH;(Hm+^Q&@>IrXjv?jV;`eQ<YcnmcSdVErNoB)oFTtX{$_r@bF>J?>9uWt_Xj9;=Y93C^4B0Er5*#~imGcC=_|V6g_s z5oS37Y&>0Q_I|{=-@Mt!|HX(rosrRKwZ0KS)RdbrHZn5mD6*yi5t@YB>dT^n@Tj?t zEN3(Dtwobxs*5~{Fd2CSsDC3-I`PaBdbw41{)~Xmdk33&xzkt+pH$a?4;Tf2pbR;b zl9IT%l0}O8<{H3AIq_`rNyVY{wGfYXS1mLqxe|g^j31g%wZ*Ar9if&~TtHjh;yKgm zu!60XR6dgp+RAE6Z)ghjGc$M>e^a zbH-cQKO`CLtm~!`Vn{rI2{_e?=IS_XpvS8CeQgdpREAh9N$_2B?EhfYBZ|cFnly7t zOC%4-iu~;`Hn!kt;S5=~eN6LNx7FzDD$R2M5qa_1XkA<8@jj@8kEA|Lc*$v}Hhxph zuI=>1OE1KPdNR#@hlS!($=5uPz;=-l73hVO>9J{m~UVRK@AdpuosCb#|7g?j6) zRUH3CAGZ6^ZWMypk{E)&YCmG52siWLFK(qAR7syWhg7C|#%{x{%%L(Yc!StXE> z1K#jbRuI;#pXOX-Sooo%!-P$aY0k z=Vu3U0qA0v^Ue>GIJ*5ELy?r0*^&kChlTk zhpms}TX1fRXO6`~3xPd<&3sqprEE~lZFiu;BY*PVeX90hqVH5ED2jL8WI*Q%{!SUF zv%}=h;G9i8%67!2;My4#bQ`h!-G5@pP@_aga7+w&LF~cFRm51uxm~?{g~V&8_+^wZjamTQf?G38v0l_7I+hl1}lINe+04Ao9*VYQMW8`aYT zB<2dAa6`pWsgqau!RVzBWv;Kl0y?@LJR3;UDS$N&ymj}aq7~zL-^u_ zb7XdDOQ=X8WX7~=noUrAw=o@0qnVai+6KOUhm(XIiE+z&-a&SAZ8%GZR$68tnKB;o}$HIrz zcZzXrHvSIdUj%GT_^!$d?AtdyC$cX+>aB?e^jDm{osN>{5i(-wQ&S@$?d&scXu$sf zx|rEvg{w1IDYduPo~x4(K~0f&`|;q2?h_tmwwb?bX)!mcm^U!HSV%>*_k?)XaFM(n zjH&C^9HvlsxFz+RYXR!BG=I(Dtxhjb3^Jbe`l6(ymkmI5PA$%{0hQ3moE$MKqE&~j zY?KL(UPedLSNeU_WPvFkHCTg8IpH7Xt)=6Utzevthr08V$Lt=iY`uT+$vooIa3${C zn;xRj3tsiSSc2`PF;=3;rAFa6_V?5tLt;-cC9{7b8ZA}q|TXpN-Hz1af48U)D8?K^l~ijb2VBObr^Mr zl~n#%Ds9DD?Jh8X@uC4-U#9I*!grb_M|NPldmVXDVHRD8V9YP9(>>pf>@2Fq#^L5e z_mW;C=bZ`~oe(jnIAhi~Y)(2`51v+B7-c1Xd<);yzQtr<#)t#9A?~d!9ZCk|>xPys z!sx)-e;&bo9ZjG(+ykrX25eLo;MeJI2S}%c+9ynKAT$3di0LrWlt$aI*L%v)Idh29 z8&4m@*JA9#`B+Uvy5qHrV2RH7mejk5--N9+6JBhA0ZK%n%1Y@ZbJwQzEP8rvOJdK0 zHwB2e0v_}g^o&*sw{z)TYq2s>nm3}^>q+*p#9(G~Zk(t~SqzOO*LU5X6#aAUMOKvA zrt10&}dMY>JiAHQT#tH$c7bT?@K4-TE|%?x-M16^dbqyz0~|Pp&?KuhXkc^yaWV z=QsLE^yg$|x8EPrB81nI4IoZ1!OZO977=u4>~57{_`mL9a6y=$C{sE6C&EvNRk;pE-Wjcicd?$< zx24W=+Ak;TIr3yaFp-;VZCwAP@b4e>wzcG#H|euh*vakN){~iGsKGA%yZY~uefpf4 zU2%n`&ptVI%j{BJd%HDYG`41DA$0h@VwI*tsL9xee`9I#hQVTmJ3J7ncm8+y$d24E z^h1e`6W_pxP3T7iF{AtI*&a~*CAH4}y^BRndv2cQzex>9MLP%g|9%gx3#xgE$+<>9 zC5XnrqLQ2r`2s3EL`wj}6o@04VSI8id5A3#v3Hn>GS~%b`Klx#*2wd(e% zyFpPHwzVwmdeYYP(9{!qnfXM#PIwfMc(c;3Yv6=nn0b?f~$(sgZ4Q8LFvzT>OE4EgyDAnHBcR7Ay}N7d^_7A1p$yXvFILZ$#Yc8@#ei2wZ$gDQn6d96&b`Uz??W3r)n{q?7Qxnd6+%@6o#A zHF2`JJM+POm@b9&Dx!eqxUD`-OZo6L1-MM@msla-1yBXF>fCMLuY0Myeef43;gX}L zldvbT#Qw8tAH-1XXg!t=?JFr`f9lmKqY|w&|^>R8V z3&1D7A@(4Nk%rp6uDD9rM6T)JeO`k4(Wl$`62s)73wkf-*B?wylU&9k6e zV?td8?Y0_wsGem$pxBPODgN@T^_h*5uXhRQ+mh42ME|#fU#iRgng;WpBo;sDc)ldI z`kkodiEh_M1?y0?B{&J*l+2xJ&nhdu9LHgYWS0D(OIa00q`aC7YEY7=Tu3p zLuPl@&(^mNuR^@3j@Z7dmfZ(*?j+a5PKgSx>FYp?g)zKgG?}+HSHj+TmHoTjXNYk7 zEtA|@Ni#h9$&j-9%$FdLUQuV2n)}7t0rKM3=$W_ot5$NXA$sHs+{}j*Xx)`jHK16a zYaR7}eXC1%%S)4B>qsc)0e~8zxScrAE!D^+esfqf9&m9V5uaB~AHy)q1T9vfoUxE6L zq`mn&>EXF*F<=}b81$+zOn2!jD8%&Lp8kT06k0%XehMh2kD|g0K`E#Y`{q=tEGj`E zX0xFZD5?D!T)ycftPj2TBw?5C3*auQ=Ey)%3yIOCaBpr-)c<0P`j{ER#B|tZ^*_RR zVpB@4IFvvDzTX?cs<7GGhQg&>*w*ZWfrATFCI{V>uL4 z`7~4cwD_Nu?du|9_;si)(wvlte#46V6+ctgysTf~y_e`qwR$a*6UYDTr)`yW>f-`U z*WUg-gZwrm8}N>vn;PBx*+Fn0v=IMuHo{6Xl3P3lCW>L&F-WE^ zn7QrCg05Tc&_UN_iqL@Dj!YGo5Ry<59T6~&h)3cXjy604NodQ@&;;3NJ7ij^nYsE> zTO@DR5m+^=`O;HwlC~1PH8N*yuRSCVSelvVDHdnmzzmIiA(}Y;s_wTAp=CLwRqMJn zt*eYtr^96jGv=d6=@At@T}J^x@5JEL(oyx|U+(q4Ls(AnC57vL!XMC6K-+8XD9^fW zd|6JIJaf+=e>V%sYc!yL<=ymM3gLQliM`24fIt<06+hFoh$GrR+9SfmM|!P>5}WG`2`3C|fWWz|_WST#zggGnsc!%iW~U z=nxCC%EjKLybr!yP2n-JK!pgFNv+kkx2RhCEuZQYTdE?dMV%CsuNuSYt|Y~7GdA&# zP?he@DqgtU72M)BG}Ci;T}3JfoUJ#rDqg=UzMr$mXUO*&X)TG}xySi!c_;1Zo6uK| zlt$mZhUah=!LAzv%Zhf@-QM|=JyVu*w8rh?_t?ttroJak-F-iwru^WHt+QNp?W@rc zY%wrBfc~7+BVV|61*XpL?>1~ahLNp+g$opz6Z2Z?)#xJZ=F4TthfiO}BTIzL~FKEsqT;lKNh?q&#Qu2t0b-PH&R^alyf@Xr|%vUf0Udxmy|B zXZLiTDjaXx9_$ID)K4-C5`u8ko`3_ zO~1JTvi8L!I{Wdp&MGoo)%1w~T`-Y@iTFz=(nW<{7?`06o%1 z&z~qK4ypJlj&{XtldKn~_mK{rWSR;{zcDY$XPhdtrXrQ8lC9Fu8*% zp>y({a!NpbvR=7ZM!1UQ{he3nc{Va)-fW8xS6Jskquq^104w{?LlF&!D8z{tud(i5 zw`6+Fi{4nVD99^KfDj+=pL@1bivI7%5SXRC~@s_J7F+5Yg7#2xkd7*6NG7w+ z2@zFaV%cBgHF4BwQ z!I$>V)vE_p_5;yXpUcCCQ|n-MW6LkVf^WrIkizo++yKE0rRI;%dlxCaKBL4kD?jK~ zJ;XZT{@S$Ww#|KK_dWQ9Ls1KryOa$oz-#eEew}>?@iI*EC3EG)f#b8!=AS)tCV@iP z*<81|ehXyTfrX7$w0`o$DUlU>VLTcP7sK*ez@=~tQ1xO>^qP;FHfl>(SDIIO`$zZA zT!^Z0eIel`Pu!a*R{i2wi(lZUIQ)huwJLM@D0sJ5z4JAM^f37vc^asp2z?Iojz9X$ z1&huy>M^ksA?DIbPvlHJ|5lM&ieCkNP;cKnGSRa*pmT!sFW76`H}=obDNtN5lM+GM z)NaRa=Yog&)H9_628A$T47h~s?d^_zs|!r}Z?Kvnj4rQNN6NaI4t!sVpC%CxHENRWU=YP9KhOt z5lW!9=69WS`B8Rfq!ExKfUN#*;6meeiYio06Zb%UI=VLZH1#j1c^oK!MG(SZ92`Kn zpTB>;*~A!=b+whp<%u>}jcEVXZRi|SA|?7z`Br_u~TPi7n zXdW+RD*OFm^wLGhQR06>CJbo8Ku^s+n0kTbve2RCWF6jNt!=`W^T;<*k++uXS~=uW znPMEL>ldfEwN`Lcs(yAczU!(b z-~F>ax`x4j0A`+t!@%gz#DqH#()01V_OsjD7ucl4u>)MwYt3St^7T&-Z(kBUT(Y+u zRH1THb9XB!Zg)2yP<2~vF-igkGe5fv`d(esxR%g8<;^T(F*^RAn@u1S(LWWG@G!YA z`0eJn(t7yUk5|8Zqy;!Ic+Q4{pIVS4UC$jY1oC{gRov2f=3M&D`XEtRUF`Dpg{A6-} zrznEU@X3V`3e*n(LdiifmND2t8N%fP57YnW54Z~Hc0NAUfIS(zROTrxdsXH&1_OmB zEUL;FL|&K~LxC}_&ES<0Yzzk<0xHtWSUbPj>u$I2D=XPNcTc61w(!tcM{f0m4;VH@ z*NV=(ImhIS+GdiI$1tjGlgn07T_-RGneU9$atSSYi+l!3Ka8fOhL{c1>(6kQ%K`;u zkEL2P@8Uq4N<@+C?>^m?lnQ9c504ld*j2F1^ouBU6$sh^U0=sWscCmNc#hBZ(&8R_ z9D0mSTgq%>HR-kKuUP{DtbwaXmCD>iqF;EdyIUMau(EcIhg2q2gX;NhRv?*}A!(O* z8`NKqKlJzfeEad^EGgxx`z~Kvrbna9FVD>C zmDIy=i2k$_Gz<)?kV`dm8g{MbS8d0J22lqJidwShUffgZF!h`nj>rjoKEZ}Pr?|e-Rgy%4byFp^ZxL7K$ZRb~J#tnW~K@*b5VvNLyA$Ky~HF z7V}0FQPs%r4Y3&paodtLX&b`m{s%jHhEHkFB3M;*#yZyc>Y+=GurnzRXnQ@bi@ z<-N?xzQRgnf%5K)_CR>hb9wzNX&N;0iqaes9TFe5-WsR}uOm>*IF&OCDZg{-e#K&7 z)KPY)H4eB~!Tqd!VZ>;A7pg@PR31n1z>=SEJIk$?*T*O5;-l^Og``fvf6jfUh5SzU zI9PG2(5)&|G3ekwOzk!nEMa&DR~BKDJySM3UQOJa>qPR*E5$$iYt<%l^KvX}F7G87 zctp;3zq9%ZDpSP~ZxkBEsyUVB`0&L}wQM))eFkVBnV7i_A?~f@^TfG^*K(Ys!D^wwn*2fa!7TX08j1|Ih?+0s{Vory(vj{<)8 zKeIIi&U)|eT-Rs~Gt@c=lA8t*Kw>XR=mDUy9hjjV$;?d23)31;>>wJFg40a2{M+_r zz5MEwekAwMs9zi3Sd~aBpH3>#G9X3iBw;nQaVGGJX=Cf6!n3R(1M%}j$7ky8T(UQj z#5YBx#U9+o{vZLvU~K2ej$;5W;p&RtfVv3vb8$oawA;W?Wj&LgzLM(Xve+?SliAz> z!*Y3`ykx?{npm~(Qyccli!7P*whw8jEwk;woBgyKv}fDs^7+PjuXV*Oe9Tjc1N!33 zisMJUA_eCU&z$?|eD>Sl*H=W&Z9EYvgE!mwBp637`~=F9jOrah$O*(mo8qp#qAzf0 zq@S6+2T{djJB`b!V(xOLmoQnetoo2BSp_gRt$Lfjx5a(RKxHTE#kWpM+j@w3X)}$n zhy%Szn3R9=Y4?u89s3ks8RFjP^?u$2fhU{2e#q`lzr(=K9@8RD)MeJYr`$JVFMn(z zO_BD6Dlu#}1&Zr|I^T}Tx^Z_s^)PoXo#&*DhlQpGK^8Vx95A}wx%k1H%QhM__^14n zv(C1%p*5yR%#0V`bNhKud4=@^6IAK@9-d@%AI%fh7xL%2ZSo_x#PGNsEq>c}HDJ9_ zg1%2@EOkOapF#^J_=&$UcUSO7w-+V0SS7kD4~z)FQCt{C?K2&Mt$F@ddEmAZMEuH! zlo3VZ2}_$soEa!H@_S4LrBioVMe1e5`4^vVlv_w|>}+^tZjEGdU94#9oF;@LIZL}7 ze3Bg59=~~>>bOfR%{Z0M0BC&f#ui7A%nh@oDpb-QzjGNppq|MNv*{2%3KbJFAkx4ZHU zi?7rnrWHU{au4Y}XEOomg}@;Y%9AaY`x6$Jgu=JSLvZS0%{qFSM(#7v7uU&mfB(;8 z)?+r0#qH&$(3tuhcrgEuTdyC{bj@W|wEzyX=^)B+%p_!{hbKbH*SN+e)#;KMI1LQaF zNmEZ+o}4rBUbsXDq)d6x=+>{ZsQm@(>-8f*Ub{mx6!s=`9 zfXU^KNy?kcP5eUnjUF`c!4r!{!8yU_V_A(K(_Jn5N?<+OWIx(;a))NI#m3Lh&))Mv z{}J=SbVSLH%XZ(!8NBAgN#Q!b8d1*ag|*0f-!0W+?5Cgno}j00?Ej9`+4&V8YMXFy z8;6rnO3}-CJGBBsc$n#?EdMi6Qr1==+Y>VuV{!=zr67n;X-gW7dTE56{H-WJ?4AuN`ul=ynzE zQruO-JHd1SIIHL=tVueGfNiX&)8Y9RVvD|#Y0aDZD{Cz86UM%H-og%T zWSRh-6`iuM)}(V8YnoG_cP$0~7DY(c z>B*TDJs+3)R@*wRDnEixH?@$eGKcokYTm)_j_q1mRJb`7x3JW1p65_dMnI7y(*AWz zsT?h_sH-h71#FYb%5)9v6UC!Wfk3e`>CG$~|H06rYt~W<3YSD)XlvPmDv084fKB;e^Ko=O{47 zVy`z0@17uAehLoXJh=9oLxKKO{hl=rgj-KSMV&^<0Lq?*`W#fFtDG8p;-2ODmb zmIb`7{tb^{x{7014L$4LoJ(Uh_6`v{)p4ER>R_h!gjD-pYY;X4?fi=ZP2s!AA6jA@ zvtk{esa5@2_q#q2b%t#&)$1SjN`=^2Q~SK~fqRxsk+K4in}v@r?^sd6)j=Dhj4;xl z8$OqGbeJkEt!=6nKXRrznDoP#Ht%6EFheUshvHxn9oOU{UY0#VITUNFa!>h)bY#)ijOhwWG{2rev6>uoUTCj`DGa_*>xPT0OwfYzB- zBMB!Y2!a)0g+XP3Ii{0Jo0qGEhS^zQWjY>}f5@9y>`iDb4QVg`yux=;vQDipDqLox zYo_{FiYop5{IVo-ADqj&Wz~S(ddReS$F%V z&@Z*5D0J$hbUK`)(goaIblPK@Y;?2-F7F#jc5$cPq;XrwS83LJfU{D~GkA1km%A@i z6ve1bSZ}?v0h;bLJe8o5X(P_k=>$ZJ!nL39^(4a*(MZQ;P9MN(w?rUui@MTOwu`)K z{y{!pt;bx)L7CF@t}5lPRRpf$<9Fan!ShAYVY<(3WXNe>uD>cnHmwRCbLZomAnyg(tQn)TyI26{1#GGyQ}K?qV6ZYcHUZ-wLWn|g#|s*QJL+Pr->xl$myX+7L3wM>G%!# zDUWd&kyO+%LS1|q2Xnv-Dwc`-4INwto~)7vioG7R7Sy}xnM&H}HBc1)AD|AoNBM%{ zIK}n)6c?Ub)^E!O$tJd_X!yj-ZEzs?XDBA>&CKN9yVPmry)> zOODzcYc$`F)Vagw;PPFf*LX&5?rlL?5qsnF=~dmKdc^6|&^JZn-fAdILIQ<^HFb6^ zBI6vqda(Dlm)iFpI^}(G-~LzG{EER!$gz){$7E#08{7B(sL=Ou`dKjtppMHMt<^uv z{8zf~bAVf}@yJu2rZ}0$ds!|%UY#Ax+3P-C2xJ3?StEqE+mzk5&ugZWR%waOEiKF} zp;E;W&+iT%{`;#=H)(YyG|!l}lm-SmBUJ?|YrU1UO9J;zt~65z;YTqz#ZCpM!h(pV zxqkWu{D`$6k}jl5_W=>NP|j_@QA;jqxV7s2u%`+r#szLp4OY@-X8Q*#Sc-bBimPlc zKJ>s*YhR2wxSakUrr@0VtlM3OyAE}DJ>PTu2ADhb$5PZ4C#Ev{9vYC9e=z+T>& zjYpf0Pe5xr>kIZf>}#9%sq8W_-H)_2hzmbS{qr{HRl-yp`<|_B7G}IHPFKsk2D17M zR9**Bky9}eON-0fnU_;}0(RCnt~?LBfAIny;9|CxvUEc4%_PdZ{XF=&^{s_|8vDJu zkiW;>+=e5S$G>2%vWn~d`*B)=WBuP-pXmZqz-u+m0qf;>7h!~5kMG1wW!I{`WXBYcSn{X2BRdyI>3C4wmLVvDln&}BzZZlfC)i-~_n`;5CJvxoU=<|s+ zWlNus0d*daCoIXEP$AZ2p)xtN7T@xlLCN?o!wY9=`whc!3xQ4{z77NGe>R>>ix#QV zR+4=+koCt}M!_I`>InB0&kS(pXX;`=yd`v|;gD1BKN9tJpGV*z0|8aeq&K7b%rc7S z$L>j5*FZc_fc$H@kHQ#n^FlS8+HqVmCnJ4hlshTpDk=6s>Qz zF{s2$aEdU`DfmR%vr}1Ll?3X@zOa1{5GA)P-xl^w)RVnM;FaWzrRWeWB&}mG*MMSS z&{ciS_Q|wTo^;FsW3@w!j-b#$H$O&?MV~cwmkQH^yWIW!jf~W~zXYW1*+Qq;Jkk!2 ze48%P*1k|WePc+Y87HA$EcL@^_KQ6a@165v=VPthT)s*)9PHL_Rig$Dyv+{vB{`=j-12OEG*K)PoyJ~bXPKLJ5aS9hQM9#{Z zTKf1K@y<=%Jw5Xhn{h(}!cI={D84m|os?yor=RxjZs0P4y#D@%Vvu?82_linwW78B zI?Mco`rbq7?@#9#_Y|v5EbruQ)(lSF^P#Tx3&<|7vR@S!5pi>~`=0UrwPiW;061w* zd}Sf8-^a|^}+-50U_bj|_ynym7n1jm0d{ZC0T5t|iBL#`n;R0_nr1mMf5NUM&p zb}hhGc%8s%C0CS22Loh*9RtX@X3|>>Csf|8W86M%OxPzfANg-|s_ss!oPJu9 zTFG7MHWh3iJE@^(I3;ZD9A2a126hu9yjkNYWsPuFe-7Sa?>%)r}~O)^r}I9grE-*8+?)#wOarb^a}wwUS7%;l zbOku5e)^Q!@1f8&qMF3QOzhS^otlvd-UXxelC8&M>e4hOzu#j5HX*+q>=yn&A?EDj z^qz&7^%JA(s5P8=E3VQkJ^ z_`ieO1r$2g#Vq7BLHNhYU0$r@Z-%uCIb-z3)Y1!sRqzNf4>S)%kj>U@eh0GcBg+Lj ztZV=OMZ`6mE^RqD3^2S9i#OCU)qgy}F;$V4KE=2{0{0o%EFq@1`H(Br1>xQYE+u*F zv6DBV;g;NpJh=N-vfn?+j4BT5>WwePe~WL~t317#JN^7kB)Oux@k~15T3Xxkt~#5M zWP>quYly$8646a_y~&W^p&TGf0-J@xkG8(N-0?k?*C~5gT)(yc4>t=>b);2y)Zplz z$qNTXB~@NnX*6!bryYyBROg~9C_v)p2oVxv8(T3sieK}9@pY*JuA*fnS?1f+-CpK&Z_({L@)1wRBRC-sQB#a>G~Kms{I|K2A_8_J5yYecem)A++h~*{a)^= zHAe>7ysoloy%U0NIvgQ#pB} z$yH3cX#6xv@^=z_IC#KkvZl-^8P*XyqhAs26-NJ1@pGnw$bHF0vC!x@TH}hzf{%*>93!jt%EXzc3w>rm zD(y1WewlpfVAvj#wjTUvjRJekvzxZfis_w_mR`}39eOmpnN6NlrajlOymSJvT+ODX zK&w!yXj+N7tqHa-;;T`9n4+v=7}yZ&F`%9;4Qlr;9BM0XKF5i+ZtlGKaT~(6QAZEh z8Lwvl)9s^V{*HO1f0$pl7vQO^#r!-40TnGv# zx^%Fi7T0v)w~-BM=WmGRTEaJuP{wJoTZS9C_7?^vIJ@DdHY)3zXNrz=K7xR=;M8$= zp7FG*md}b^);We$uZ+-9Jf1bGZ?U_PJ{O40o56j2WdENVxa%6Lj_^Id8yxu1VBUu(*oFVS(LxWO?IrS- z>o)FjpwyNglYxRW(0f1Ao1l5y+hriYCZFiFXgIhEMSjn}=ceQX0xQ6tgP*r{Ess>Vzn1WKqvR@6f|0&uj{9T4A^Q?i5Ij1K|*@K(Dz|9akC<%b02$WOf zte#FV`uIOyH6sSZdc($2X0uk@Pj@~M!5Sny?@yV~u!Ghy^<&WWH`jF?*A}WN74aNvv%Ubl(G)zH77$tRNhgk`7yo zitG4sa`9cTav(bp(_&?0Bx!2g5F7iCy45{VtcGK|5%)>@*){gF*M8dHiOGT!mvdbS zaf7^9G2GGINMOovZxsnYx3Wh&qxC6k>tYCDEn#arglw;R1HK;@`E8GBTg+oF$L{{y zoVg%pa{@@P(zO5}?SH zhl_=*pO2p(Wb`)}7LslXx1*L;@M9{^k`ViYh&_TwG8E%lIdxF!oH6IXj`ypz8U#XpG8`y5je;T5At5Y%tKBTMsLTqGBe zPIQ<*Mb7zF+5D>T47SD%nfkI3-<;5n1O6griMI1VD!!5su?e01wq7o1z&;}O0f>|i zaOqW1o3^*vh=5cEAKU|E^BA%8m5flvDRmw&Nz{X(kXFOBpt(CPqT3|BN$4~k^>uF)Ml2m#}tHj$UAH>4Z z&)tr4co~+Ta>ryE08SAKsx9IOfx*eH+nW_8=ATD0OFt-_`UQs%&HEj|PHdWnU5DmM zKg_Qq;~$zRWJ68pEG9G=gomKXx%Y4TCxdL=;-J`J%s7c>545_|0{^#%GQ*>j04^pd z49O&|xw5{jU@LTlYYZ`Q9j(3OT`m{tkuhoYhwV2S>q z!=9eCh#@bWWck%`bBCTUu+kd}bD4}e%;gl{nAkLL*sC_q?E797qtUXi7D+dmlFS!Q zGL;4$*nqP1K%gF;-hUqwUWe5>65D?M^{o!qYprZCI>$IuGofM41&c+@J|xi`hNDZh zUxXYJb>QI&p0HuUYXbM#p$T*}_+k_dIWwNJ`xIbzT0YQC9J<7Ij@ya9YxnJH^5jPL zw9h`i7b2kcnmb+tWvy2HmTKgGqT_?nj)Cru47Fl($a`|Iu;N$1bYTTE@Mu9sE#sAq zOlHuRp`TvyjNOEGfkETloKTC8Eq@G(V|d$lu09YP05MQ~8}cR`L*20a`0mMFyiYQy zqw3Mng!}Km&+_xrIvNgJSL_^y_Qk62${I6sftEBqfQ#FJzS2d|wgxe+jVm@NkRsvp z^vzgy?cC`0Dtx`})bN1T8*&!SsqnNddpaTJLj2AN;_99B5Z}IvZhDQEK8K&nASZh(+UD4=WpZ)G%-H`RV zh~YR?b(Egf^?nI#kAJPa_sksrdCr8xNKP}hj3;g(IIpCIzB*FG;XnE?Lu*uA?T}`o z0R+=Fcj}CyHv9X3fI@^yFoS;7a3zgeY9p)P_2a`L{e0f%7z!b_g;gzv+~_%lyP0~{ z3l^@8u&@^FHWw`6Ejeh;CCvsnb1GDoiKXOrFFZ|*hS`Zh{QG3{Z|o`k3+w%#eR}_o z_xb^~ts;?fr&Qr&`>EEB)4%`6hYn{MSbSLCf>M6+cK}l&J!kAcq@AyRr%Dn;tQ_T8 zBonny_^ThDb8}gc%nVz+@giJ;Gh>wc+2Kx|L`+T;9KN(W1ALsF2xa3jV~k8)MuJQZ zIqhJ^mfHZW6^~{ivr0?G9n=XYr}Yli?aZ$DQl>47-Rj6=l{noqRkUeORYZ;zR2q>o zO>s9_Zlt_vGndfc4ZX>vwEyv0bhkG;PE66k?!O=VG#loz#b&l_TZCVE6{Ukumh7BvA<(&;AzHyfR2 zA;4IF0$_Q8@$A{0-Bd%@{XBdgv?QChr%}^mA&3iKj*ss z-Q7x^>x#!3%zSWa|MTp?ESCIYTROXj8xK=->Vn~rz3$eQiyA%&y9*B8C(%z~timGD zt12p}g*-u$IVU9D+m-Cmkr9=a1f2#1H~`{HF9xs8ESZ-s7xIB_la{>;TMc4d7gitx z5O4ue;DE$ETQhJYf&B~Y9#OHmWKy1(ct(4iL_k6Vt`tG=2ilxs$HK~ihvZ-YfCWwA z-yYCUQQZ{*+d~;)u-{-D77MKYQIDqMk60Pe5dV{aexy$#SYSWDRFtUUT>_e44X^^i zb&s_KhG|Y~=Q5&K4=K^jl%eU+I<~zN0^vtNk6~kTD~n|p^doMcW;Zb@2CokzJAJ4v z9pYa7?U-l887aLC_&>6~1Foh%{{N1Kwv|N7D71wZ8dghb+}mEHB1u}>-FQ49iY6*0 zMfbM%(6}L`q@k%vyR@ZU|IfMSKEMC^exH};MKAXr=X}ri`x)HUS&UB-UmA`Z&C`Qo8LWHs~hiPJa zIHlF!xeZFI7md{EE}f@dArB*3v|#Ctw}!~O$OH%QnFVi%t89#L*Alhv6U;$^0dbAk z+dqMf)M$OZK2!zBsC1p~eHw@=&Ba!Sr^x!E9ya`2Y0~$}Vo8suFR?@oxfmm3H=QIh zS}{#qe$AhOaNhn{H8MLSO19 z3Mj?XeyAuue$LnaWWJO2& zX#jRqQ?!2mZi?(dnqcRILNW^d3j}>MNW;{PH0cgaD=w%&FlXD)98?C8^59$%f=#xd zfOJUoPMfL-2I5@MH`F4CI)HpRp7u>fVwFR&k9q%ET%JDoGa&`xq~v5mLVe$6&axp0RvRhf_ItDB3Gd?OtD$$9mmo< z^*o)|6*%V>5B;~I-85vFh%|(gOOg}gVQwSZB^IKBsuOK~h$yx!9#J{Z9mC#aNue~O z&T}|aLg7^Txf2?94|(;~%E%$Zh*|HmipgEOVhdabox;7bd0G?sQ2Woj0^BshmD#Tz zaV4}aM#&+67%jSdM$i2jb{^#czNn5)5dmd0uq%6U(Oc&T0%(5qvEAkktz}H2fwg=E zjA)CS8!uWSUeirLI6g1oRt zDm`*d_uNS*@X-nntf5kHQ<{<=VYw{~>B3+9gfDzVR1R`yC>QeMssc1yoT!t@vM@yk zB^q?47-{2nLQIMXubAM_`jKfPFTuK47ke?ds{Q-#R!1XSg?<(3k)#$#@0b(x^{Qyw z$M{JyUuLGO(=g5M7fTOQGh$^V8{DZe5YC;jM;!wh(QG`v_TG@IU!NUf&15^fgs!#S zx=ps)OwQ;%piCcp&PtP*B?qiZxqho5d7#f~s4ka(Mv{gu{0|0)8fW6s{pFVz_|2o5 z%m@1x}-F()G&9Qk)X z*H-6RXfj^4r8xo<#jI%>FPZI&(bk$cyX)&3$iiG;!;9|c+D@PY*{O8T9ojQXdFzK5 z2!M;geP%)=2SJU7{KV0LKc=%Dh~3_cCB?0Aq4Vj!X%0@56l5H>cx`(y(2W+2AGKme zmg~xAICcqoXm*+*M!D)FdKC2#n4#gsFRVX?f3%SOswQrHloh&^S1>H zb@nf-g&=ee*<}AL(ify;f`CFCo`sTB6@S}Fl!@lb)_Rty=sbv!>FaN&7dg48bdn)0 z?iw5obX@k+Hf7SZz0ZEFF6;rN4>elH(~ZkP7e}N;$)#*RXG=htZKsPA2gtwS=qtFk{k4r z)Y@O9@n>1O`@dSi0_sRwvS8;Y?79cR>NN@4sGN7hK2~j|!yU}9GAZyCFhr-L%$$mc zqRc$`kf1>Fj-3E5k?oLbq_}Vjxy*G z*cJEi>be!m%OnfXpMCbI1o5A_)^rApSJ4faP(K+ePovT^i%ng+hK$ipL0XhbyO6|j zJC{u!E=IkDXDTXpcf81pNf)35G01&Ru%{dSwrf4zLCec<^mBc+ZsLGyJxUXOeJC=T zWRLFqE!d>5s(&lEnm3u$bK+`EPirQKv}+|>xKD>ZRnh#b)n?!QDou_iXBRWVvrW(M zkn-lBYdkktIMVL&q2h-rpHncElZN=SPKB%(Pjkr#R>y89?j@qXInnt5*0-u_Xm(DK zXotGFH%{mUzA3lJh;A>I`Mj=&B`!>drWNK)Zt;rcyENd9+*fosu$rxHYZW3pKC30y ziUy3XsoOpyY~dvuW{XJtNph(%Ho1B*(&TgXUShrg*)wcCa)Ge8u{*wy;KXaT%qv#B z#NmV7D|kK?0kG;|e0z-L){~Gfu(r`9o)%?$`z`Wz#=F|6#GLj?N4!9BJcE8~neuks zFJ6Z#Bfeg?jlD<7p+dBuqVzQWUaY~h2ji^Erz>*$Xb!NzOg>LwJE(s2o5Dd?VpA1R z&Bz~`DbbWF%c_OgoBf9uQ^l=QGuEFyk}!$?dS8Uwc#b)j;fl;5#3tHrKYT$MEhmND z7mpWBG`-4BIVfbUOML%WVurHvIsa%4`S%8qxc`gy;B1Z{Tvv|Z=RiercNpX$Vx3AA zUOMRghhOvWW4W8o3#qo!31nEFh*AQ}e+XojJ7dsFb&Y&Kdu4X#Ni+?@-L(r@AcPIl9bX0*heD^3h z4cYs({X?y7#gTIhPA*$Rcx{3&;TXYtTK*|U>n@Z2V^=TY1+er={ZiB6lAUuaw$7U( zbLA7VTnVuQUzQY&49SF^Rn#45cyzwHNTAcwmisd8D&3|YOO*b1L~m=|_c=jR^curG z$`6H&)&=IV`x}={Eml8bXv)1Qe`lrSGVMZvVnZQ)8sx(*?-T z9*j#5$W4R7FD5whsQbOVKQ>}qw0DoTsB%A&9;FCC4kx1)nTNZo>wdfbIZYVg6es;N z;D7eP5*rW3x3N%YQQN02`~=i)Py3V-{4}XTzEUb{EA5u(SwMP`=&e5dcKdb$niuD^ z^1)s-^cbqKO;zrQss=OHc-`9SV_3+eUm@Zqgla%IK#yk+I-@S^vrFg)wif(<(Srr< zU5;Tf*(iS}w- zytHEbqL+n_z~_d=74P0Tua@8QMTEY9IUi253?x8AGS_p_TX{)QDw<3mhPXrqx{2r9 zW@||2ZSg|-*dwanQI@F5GA6wHlBr+qdJPI@Pu*wzw<2Z2pW&rc4qSR~|fgJUD+| zsc&gnm9_v0%FdjkiCWLuG0+p_W|gtNcy2+>wvQTli>XduG+flcWGHerC#||;PfLf3 zF4MnaH;xi|CQ=_U+-eR}m((Kh>_LWUHg`j3Z-Y9ny4F;PcTp`Qh`|M@9in}rt4tom zu6^_uf^w_lSLmpWP~^7HAl4}TwfN$Lamj&hmd~u}Ha8k5rUSh91X;=t?tb>@EoIn4 z+DWa=%wXcAS&jvZ%hmTT-H#)8ZQso+cJaw#_<*>p3a?dsZOHrIp|_{lOhWktS#kz9 z(9VE;1wZ`@6IcF)J65xTKvr@XE8l#L33mqj)yOfB~2(3?}=j9Cs&Z`R%oqwE$ugf${IjF`6{Qq{A? zzC879w=yG=y#!sD?A_ z-F{p{IPz%6Qz5nC<6$JNDXBiE>bDn7J4LqC6zm%;QkY3flSh<-Z3#X(jDHMhN z!N2q^i&;+pHHcN*59?M!gLXoh;Ag{lnV^X5$OOkrcSSnmDZ{apsRi2B3BNQY2V%Lm zH8GlQe4G=E+QdAj_tVf)(+r(1twL)iq?K=?*bdBNz7sAjHF_$Dh2x(we5veE{*g>= z?8`-m-c&Csfp#qmqN^AS`EXeOXLSDJ>?BecBF`!kI!}R8HiuY+6d`#GzGmhRuDRfw^ z{vGgjquNmjz<_Movqj&l_F=;qE^dF$JMz55c;dkRFim}O%V0Mu@SEXqtld;g-%YTcKpnyo?%TB|L$-tHb+*_ABQ8 z2gEWx?%0%d{%k)=JYuHT=;sStMSZe(n6+xz3@wZhlbvs|bDfEVwqJFT*f+@i#iP`F7JvR5UKu(%xT zVA+BU48Q=8f24ZD5nCkLbL}ysJ4hn!QYByc8M^bJ3Ys1@B1EP77<^{wT&-AU>4)|> z+E#=7GA;nemfM$@6STS?DI@)T!PE~^W^_&1a{_aL)_o(<3VaP!nGuTZwzRb1@C?24 zMYnl*tsKnMVfxznb!M zG@}kg6KP|Qmx36K{tu0Fssz;ri+mIu8VJeFf1{PJVCg`j_u0GQM+d4O3my}AKoc@- zQq@8w{zJcah7ByB)cJXO-QQw%seH3}RjO=rdn3}`tM~cO2Mj*7={~a0y0=y4Gq($v zvvqH_tY7Nc-1(my8(Np&--tpyXXS~}XjPb}h_+DXH2dk536j&<_Z*Y5JB!eF!K43v zyQSyk{dV~~=o^gu1LBmCi&s259JYrhGdbWUz28-%9wh*(pip7|<$7VFnHG)PZv}J; z8qo27N&|FuT#x?Is<0~>*)yg8`!#T_hWjsBf)=9BG7F*(xW{LJbciqysi?0;@`&!Q zSpEFmJVY&ze-x#z1k#+Gi?-6dbUFD+gTi05!lGx^{?8AGkeo1aRg!$@mH)4?L$BJ} zE6^M8DC2oz@BvyqKwm1CHUiN)_O`6w1u%zcy%S`K>`$CWuF)a(tMI~TU1<5*Xx)FE znNujHRo6s|bYBdmT7zrFobRBS`BoC$Sxm;o{$&(MnGYYaR0ra-NA<\ijCWyR>&-_G_f4Gk@^ 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 diff --git a/doc/testbenches_doc_resources/5g-nsa-faraday-bench.tex b/doc/testbenches_doc_resources/5g-nsa-faraday-bench.tex index a9f12fe3df..07b382222e 100644 --- a/doc/testbenches_doc_resources/5g-nsa-faraday-bench.tex +++ b/doc/testbenches_doc_resources/5g-nsa-faraday-bench.tex @@ -15,27 +15,24 @@ \node[faraday, label={[anchor=south east]above right:Faraday cage}] (faraday) {}; - \node[above left=0cm and 2.8cm of faraday, label=above:obelix] (obelix) + \node[above left=-0.50cm and 2.8cm of faraday, label=above:nepes] (nepes) {\includegraphics[width=1.2cm]{server}}; - \node[right=0.3cm of obelix, label=above:B200-mini] (b210o) - {\includegraphics[width=1.2cm]{b200-mini}} edge (obelix); + \node[right=0.3cm of nepes, label=above:B200-mini] (b210o) + {\includegraphics[width=1.2cm]{b200-mini}} edge (nepes); \node[below right=0.35cm of faraday.north west] (anto) {\includegraphics[width=0.3cm]{antenna}}; \draw (b210o) -| node [pos=0.2, duplexer] {B7} (anto); - \node[below left=0cm and 2.8cm of faraday, label=above:nepes] (nepes) + \node[below left=-0.50cm and 2.8cm of faraday, label=below:ofqot] (ofqot) {\includegraphics[width=1.2cm]{server}}; - \node[right=0.3cm of nepes, label=above:B200-mini] (b210n) - {\includegraphics[width=1.2cm]{b200-mini}} edge (nepes); - \draw (obelix) -- (b210o); + \node[right=0.3cm of ofqot, label=above:B200-mini] (b210n) + {\includegraphics[width=1.2cm]{b200-mini}} edge (ofqot); + \draw (nepes) -- (b210o); \node[above right=0.35cm of faraday.south west] (antn) {\includegraphics[width=0.3cm]{antenna}}; \draw (b210n) -| node [pos=0.2, duplexer] {n78} (antn); - \node[left=5cm of faraday, label=above:porcepix] (porcepix) - {\includegraphics[width=1.2cm]{server}}; - \draw (obelix) -- (porcepix); - \draw (nepes) -- (porcepix); + \draw (nepes) -- (ofqot); \node[right=1.5cm of faraday, label=above:RM500Q-GL] (quectel) {\includegraphics[height=1.2cm]{quectel}}; diff --git a/doc/testbenches_doc_resources/5g-ota-bench.pdf b/doc/testbenches_doc_resources/5g-ota-bench.pdf index 121969030e64e03ac0e4fedc9ba4fc1c692de71c..4199a44166bec66cd44a60d1d093fef880b4760d 100644 GIT binary patch delta 2821 zcmV+g3;OiXmk#un4v-`RGB`Gu@rnZ|f5ll%Zyhxh-M?SqWwWqr-|Op-Z3RRiKni4m zgjhft5K_{#0R%sv!wTs5us5I8eA0d z{=R*@K=EHa&Hws=Wd-yATvy~%crh9xCtWiI7Zi}A`-EZ&0F)vln|JE)VZ8o&s|eOiT_%fuE2U!2B||ZiuXpD)b6wJhpDvLh zniNUe{ukx@M)RF7%)7J+DV)x0`r0*p!J;zaO@+dahkd|?Ia8^iA z{Eg5?f|q1`kaOuv_1=`1h+ve?#vj>KGj)kzarAu1h9qu@OK&vTe{|FJ@Z0TOOH~$@ zYf>+&GW~^c~*mQID z(*|SB>>7+!;7({;gyl1+C8fL~ERW%I*uK4pu*!ibW%xLjf3N_bPCWzGJ#a8S(#{^Z zx7pnTPfN83tD*P@9yu*ZZVy2 ze?NI|g(l_9lj3R{tflmU<4q=JoEap$Zikg^Jf5&3(MVY1WfvPs{|je3i%(kdjTkC~ z7G<%;NM>JNe~C%C11|^R_Zx&D)4sSJ17{ z2>UdYNJBN9|NQOMd!L-w?#dXZ9MMx6&OI}i=3-E*jlxv+k3{l}%J&OHF*%$qEM46< z0>dsWgzUpIFo#{UC%+SyCTt2bpDVEHy;U&RombG;f6IzxBhm9-#WX`oqdQk*)q5*4 zia0Zux8cjkoHv;`ZXWpYk;z45@qGIHFY@*1zq)Ud91hEtb9s~G@`H{?%JV-SN&3@+t_6p#1p$Yy1p>FO1p~!?4l^(_F*Y$XG&V3eD=;ya zfrJAU4l^(_F*Y$XG&V3eD=;yaq=W;;0W_EKiUTNr%~nfq+eQ$+>sQRJF;Z)vd=-WP z+lpPIj_cY+V!-i%qE-@Hi6ltMb^7c3&61Sm1Wg(shX7pe&g{H*W_Oe#Nh%_xEUAP{ z7?G9~XiZvC5XOX6Bn|0+Vgo3o*eX&6lO+I&9O;-606Kw?BqD=tZ4DU>vLq%0!GSTP zB+ns#VLO5f%~L?YM@Eu?1yLA}Oak>0&>?9PK_cV^0XP9iQjrB?WbNqSU?groR*u9~ zx^N?LQWmW%+6JoO&-F-LyQbXKnQK_D$Gmm3JUuR-=$;YQ#Nz-C!KAU(=3xXuH6*Ym zPkvUHn~EJ}=KE~A=D}n6F1G%;=>;ORi*8+aVb6a0>UwW zMC=XD4;~)O-Fz;kwNm07*;wTkO#YdQZ1UoPj_n2qy`s{cl&Z(_8ZcDvPXL+&k>7zMyR%LxtrI~xd zb5ykX(JD`yj#o^|b($A+&*k#KTxC6fYXBM)3$h7+VBb6;3unMGkN*LAx+yaD1sT1Y zOyAcG)JS0XFTZYv9n4Uczi*A8FTB1!#LWvn0^c(m^=#j-dX&~meYtu*A^B0N=i5^B z$EY=gQih+^+ILM`+^;I|x9nHb)|j2Ng(bAB+wbvX(6+8digzFcp53(UmhuRHNW(ip z2M0(89r5GBSV$Cizfb>kS+{k*#yisqIA3cDhNYosSXzpQWkAVR3XUbaaVylu2@Vjbdz z=MnM4j^SaYC;|h+o>5AVXQmi`26$TFJzr0^EWfR><7ZRnYJEeq#B9iv~j+| z|NDSnqy(9yYxYj_x@q|o$l`1+(jI{3k$9iaaItfT>Pd$naf|vVSp>NmZTNcIa3?tu zzvX<(DS!KX;=xIZ)F>CDj%R2)LGmwu$Peb1Gl~OjQ8+R&Get&0Mno_{MK?nm%RO7@U zM??m60iI1h?f>3t0pJ&aWGqsV5z!t*_JXoY-ZvmW0Sx)p-xATRG-&FG8SVJ=oC^~1 z)iVeaEfEoEq4O*MjYXThEj@dpPd+Z512H7uYtIpFSNSv*Iqh%{_MC_Xt*k*ieenlJ XqZ2cir;7s~3o$t{G72RnMNdWw>4jV% delta 2950 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 diff --git a/doc/testbenches_doc_resources/5g-ota-bench.png b/doc/testbenches_doc_resources/5g-ota-bench.png index 86ae6b2b6413025f922887d4e0a17c934acf0297..30309bf9f4487b34cf58c8a35764f29802fa3757 100644 GIT binary patch literal 91775 zcmb@tbySq?7dAQ!C5O`~A*Z=by9Ix$ZX+h9~yD_qDHm-7k#{G^xm0$RQ92mA00e2?Roz2!RkN z(~TkK(t<>P|ik4mW-Y2+X{=3%5=A%8>uGnbB86VCz{bomR>EN@J9%E!%}$Aq&ZjTa$hp933a z`w!n;xR~I2GGABM z1&dTLONdAdlCe%0~m{hWG2slY<~~Ya2I-($52mQB+hH!k`PvB)m{~4%y@Ii=u=n zZ45FH#yNr@720vg0S5^el%NY*Z%@h#QMYH8xqwy{a)IdQ&}WjonNz5PzI!q?C(J|= z7f!1ek*Z8I6Ha^~qJow=S4BLYE}wc)S%daU<+H0C(p^``BW}NS;U(({^LqQn=pu|9 zuHtQUX@}x5bpv$zDaqUC-((~RPpN{RUQ8nzxAjm|or{>N3lx<`9IC_P6i_ zm&XhZ5Sv#I)N1p}-##BmTJU9}d=`G^&KJ?IX&ic2@7*k9dc-vJtUiYMv!4B*3X4)o zGg7z6-L_|UhTap8>#S(6aIet%&>gdBe&+iTeKCKCftH`3vqWI#qMXGeYQI4s^C*6y`n}6Tff^V5_1&X~+ z%VIJVyzQQ0fwg!(9zQNXo|S9x!$``=i+4WxX0m*;<-?}BXENprdO;XJ?1Aj2@un=< z>!)v@K3B_Cd#_gYS^uRge+pW~J4eH4yQH(&yyTL^(?XqJg9p`bs!1j#C%ITvod^7P zWCAXpIv+u7xU^9cDLbiOQc6>rIQSenD5*%|654q0WZsJrRn+0rdYI2yu&0I9q0;Wt z?bV*r(bQqosnw>@4Nbn&Bh&S{cO`W(*`G5yaU^9ou`lr!XP~~1*(+gZzRpyEWO<=| zEw+@gl>L-h!BD*xyAT5v;}6=`)Zd!?Kz}UdR2?uAGkCAJnfo(_PQM$$XI7%Om^esL z$d&X-&)!TlFrCxM-^qENa$atpV4j#hB`RAdODCh}5T@_ht-7V7v}$Ymx3A1 z-P2+%|7sRztdu`fq*(r8@WQw8vVjt@zQlW*7P6%bqiHuRhcs@&t8BMh7&M~G=*zt= zmfuO0-p^&tvndPKBpnqSc{w6mdb5taxhDudh(3bnHE;p(RQMozt&UB#Uth$q+;kn) z@;apAEaX)2w4dY#2^k6Stf)$!2|x3HHvKI0itCllSaj@K`HgZP>*o93V@qQlavcdB znlbEY-}Jug`(*5-1&K@Eq))4P{WYy5J@WNPT5RQ_yP=QR@iW=U?_Yy54*j=j7z!B{ z8H$9?Du=N#weB^Uwx1A}>I?7mJw-*q)8KDV-(I`2B7=t!E_v>k?wNXQe=!@8`dBmI z6P4Hg@>k~XrplWYm&UPEeUF6dtm;x%F9z})-##WFML)?_MSU{Wr0;a;WYA1VdO38j zsQ0Ujv-#iCuXzf@Y}sRORMl2kZ!!+ON{N%Nyc%_O3n69j_e`ivt{d#BmgLl%k8`p@hU0=3bt{R*D z&e3@7&Zo%O0ULMGF;(U zc`>_bm8D|qIy3c#K)!LCeyjGc;fmMfhzW-o2QSm~{rbi|(qWqSN|Y;>%|%}Qe?PwX zXr)e~KCd3#e)7{wiRH{<^Vi@^aWf%zA-CqU8jz|H1LUfIHB8Pvn2c9FNOF_}KMrXM{EF5E3Wnt1Jl z2)si&{JunWn0bhAg?x!c;jh=bqN&QpQ}brKxNLJpyI_^A+uiCP+fSl(Q@w=yYz*oZ z0=bWR)cQNJI?V~oXV!O@SnK`-taf;JE}I|A1gAc*n3f40`uV5cYQ^eJt-govPR35{ z`MoWx?yy7p)BF-N~yJ>%P z=i{fbIB|9%g&D(BRSV>BR2#xXI7KL8aS* z@hwS@^kD#T;&9M1(T6|+Za^SmPa%*a;7ef(5Qw)Z1hQlgfykvnAdH@w4aN$*h#+o`pfQ)l^`9 zlPlA{L6^PHls3lhNjEc^xM*K!z7P)4p_w;I_@$SsvZpOjdm`R}J1{Bb|A1|d5-+Fc$iw`*Oh4GuouTb-&E`BrS4 zrxDAtdHCOV=Vmzn6y^Z_x4F!!_Q7-m>Nxzr3wppR0NlXBJ5JO~F&xm6CDv^6J}cwz z9m2nW(Ch4#^;#6!4dnWOhhnFS3lkR>ZQ>u{)6)$v-8~%UH>lZrB>4ImLIZ&(``q`@hRsvjuN# z;T=c#_e|MGf4-#WArggcoBTF^wl7r@MLH1&9oawlk|yTR5kpXD@J7yi2}WIOZRA1w75$?egh5 zdPd1C?$|}!a`u0(;hy`1j+&Y;G1*Tp(ac+cdn*%Fto?Ro1-V4tgOf1!@op!wvz-qG z-|b{3*@8WJRr0flP)CQ{3zsWrs+{|?ZUxpnh9DR$TU*3U*<*S_N*1>M3z6czJCR%$ zFHX~o<8YNUlx|p-oHMc|(I~>;^pxvDvb_O2Wr{j`Pzv0YR|Ru+7Eqa)K}9~l*?Y8u zO8073%;Em`z2-JDf`QZ^u+SMa9CwY+L3v4lahEGTNk@eY;37w2T&%=T=nL582M1s> zEQD2+{0!WF$vN;nD@#<7+P`?be}_X`O%3~+=n);0P8e_a$u{w|vJlQn+4C#mw8Vp1_uo%a<6_dHyJ%c?BRribiEdbX z!>_6gw)-JNP?Plg*`3~oRz*nJ!cmXzUS5kjrX|u1UiN?AKa!0kQB4ho zS+q))Bk#%oY1r{p5HnvwdK_n)Im5(3~W}vfCL^Hb6n{>R`>6NUbPPV{R zaf!}I$FszPPv9pG!vVVvc0Fk*I<=k?lzs$)?12-d{;hi3n&(2+|GmmC`>JjiQ{d*Y z*)5Tn_&FmjBi;{h^P*HWdj*z5j&?7k&l;;c`Pcs6n=5Xx2OstS0RBwK>c0;JFZ}<{ z^zm<;aB}{C$U()31u&h2oI-%iBgzzp+r%|xKlf8hu4Bx>9WMFLzDFN+zG4LvMO3+Y z*C{?nRY%R9gPG#%8`J$UkFjt`U%BK<06q%1rpYy4g3M}9RMdy9#3;>-zI#;@ zJy~`UiewtQTouXWy2hj9r~o<_oi@Iy#TTWuO;i8I4a}zEHgNmJBoj3cr#kLm3IFmF zSa;WG8sr7>d6K^kQ<_q6Bz8S77sT-HLmc4yb^LNtg^KZ|rSB#Gy)!fmm_c%pdeE+O zw>z?TzC0DX6h_@AC$apE((iP4%r5lQdyNM@&GjM!NVUTyrf`ZetNWJxWkybiUY02W zsPvJ@7SwWIO~t=isbLyS0vF-e|N3N41wWA9=u|q}j0-)H|j ze$pHIuO(aux$NhAPy*)fh``E$F&Ck?ryJ3r)6!8E5wiA35!6I{(h;zz7eu9va32hL zXj%%{{&(#ZxVPdPUu_$_H{qNeyl2UQc7u%-zf+!W@LnD*qxkYh9xPnSMYuwdzsAT6 zq6Df_vcV#Ecj3j`>tR1WJ*Qz;%q+7h@kt<8as9zhA7di6_~MKCj9IxVt~9)82*RM! zUdgar8vJogY*h{190+5ocCf53P{*5LjIV*K4ZIEO`dofC_JD=A&)EVlczS~n3iN-E zLz^ah@XmlHJHZSN$U{0mx)Bl+W*}=2CBA~n*S9p4+3waZTNRiwSiXZHQ{XT8KA^wH z(c@wq40BT)w`icsM{RF#Y+69;}pZ(k7RLUw=qGNe6b z6gU(?*b=j2ipsKT0$8#c(G&S5CNktZTy;sUB0$Y1O#>3%>XFzDOcW7DH$@QGhT4mr z*HY(wf{A(Xw|&vryA!Q@d5b9QOhOQZ zUTibZhrh#hB@a!14UZ?0ic>pY?V8)xkc=jJCjQ64|8J7cr!WwZ-F?o`I64@>QNR}n zH^0W`*w_&RZeu2n8z2gIU=ahAY$E?%jYGxhtHuc6Mf~Zuh#kt#K0Dd@8xF^ql_9X? zek<=g*#cL@=FX1SThGsq38X>PE>%t21@DfS+Xce|0-e8R%ZC05YMY$^R(ik4NM-BT zEMaFQKmYpW3E_#aQ}B}iwjLZ8+S3uzvq9T^(z8S^4nTV2=V6iklyKfSYQ^jV!G7PP8y zpQv;~)9uXOJioO)dox_ahQ-@;II^4(_&7*c4+v-w4*a2qskl2AH^MW%hoP5nTVnU` zXB%Z8OeYUEe|5&O)2Ky)3xbOU2ySzDaf^ZpQ}r3d00(U7WOLyD+H@l!?gg#4(o}<& z(VL?DMOBQZT_Jxjm8cGgeBbM2LezM(TN2ymUrqpC$9~=jWG~3Iv1-)G^;}fpQL{iASyfye8ZZULzmW*{T1V6}VRa z7ls)CHwUqi1I;HyxSNUZ9?C01VNq< z*RFW5&#i(egX-Ypb}0%^+e|Du%G3&WanT@TMWV3z`I4c+Cehp?i0-J)o~#ZWp!o8( z)K(g@0@kug=PA-l7kBEpyxSy*|Gc1S zfbp(!(8mpmDQnU4p8Q~ z@2{XCID}1rY(3}{yc$k|T%u=35EEft0W(>ZB3w5p2@N@3t(^@yrb@$y#z1dfH;09Wu@(k)Ce_z1{7&rvUH1DF1QAd$E~ zS?xMhsEo@^uF!lOAh@dgh{qFboSgf&cJ%;`98L6O@Ge|fw|5lCyI}H#)5n5oOfk5j zp_M4Xs}+BlI(;Gpg)nO!Ztj0}HLi2beXY#T3Xn?RgROIbZFnnS2lCH8WhlmN1n;`+ z2gi(LN!^a(1aEOrwgABtpA3QsY-fQuP__lZw$BJO1`ovl{E8r^U=Xy(fohgGCXZs~ zxj*E`F!O~M#u#_^XyG0Unh)30V7VMrzNX1Lti{x~MN%*dTh~EOPS-=vk7v){Au0yn zTqCG}750P=;+sWMEkh80Lx6B>jBcqkM$|5K29@$^_9pD&1qHY-NQ8?KGO2#To7S8g ze=r^`M}IJF_-39a23JTPwgs*?@6QCTKUA9L8{{|Ozh;B0Ct*)7l3l0MfNXyH^}Le? z{4JbNbc@&fTvQ53hJkE}V|8eTKYf|fk7o2Zlt8eLH~Oy6GzW%%2VuRIJ=He?g8l+E z2$GXi5aGW;IL^VkO5#ngg&jr#iyVAh|3J0_to~?X%m@;T2**`vEAv=&UWdy)X-~yT zF2SiL*28f??=F4#Lz=b?m1Uq1n*$UvY@#ADLHk>gtM%b0A&4 zy?uS%1YiFA{Q2?ruA-BKoUE)Y0T9t7kgqr>gO|R)Mx~~qq4DhbqgFa&*WMH%plGGb zZ7$)<4e3{0j`z36r6eko>`t${-vCksGNXs*_;BjOkv236mujJNo$R3}o1N_8Kn29Q zjZt5~l?GBT9AL_pFc6j)i7`h#8rX$W^V=G*q%vI5 z#TBT6{Rg6C6nz8X+KkVE2~GThES`%iF-sn{|ug---;D0BH~E{6Y{= zR~#n8L!u*(`!4$W3N2h917Yl-a!X#yh^AJsSCNq!y|uXV|RCRbCWN)k%1lEqm{1u$8-!CUf}67ir_dt zZmf@dn%vUbcMsch>L@F9#dBxj<)tD+!q!uu2Q4v0zWVqvBBDuwLqtUW_{e+p%nXB` zgDsE#Np0N*W9L;YF^gnWOw5b9<%k8J0DLyQZ;unUzQP@DQtVhJ{EhoQfI9&Bvuhh<&&Fuv4Y%7xo?v&B9ZRezStrSC&a&0a!uNg{8*+uj~J zQ`FITf}}2ql3=1F$42(%cx8xU)o z>F?jCb!Yp}Z1?Ug0#T+Iw8B*3+}{#RN)0(YL_n;p-K7}m3Cu9jG?$J1(LoRbM0Bg%z4cnV)7qUB2 zs2|o+GtjLa4Jk7#V=%^A!9U~$+n2l>F;`B>>~9$~jzl4w`M(NS9=f#Jcr^!mF8qec z$&9=k(@f<~b!OK}i80X?o}HLzZ||cLO@~6if7cP|v;S73$1mI-Ns%g2ln((Cc75)u zb8P!vDMm)Z$jJJ7OG{-OZ+I`YS2+55PeV-9b2b>EI|(SLShuN^ay#@JydZHc2QO+8 z;+pB);r&a8fs3H*NzFQZ%9WP>HyRaW^60C>3Bs8OF27HP#xfH~c^uOfI(?MlB+ z$yFjXg-`3@_)WlC=Y{LghsjjEa>J`|KxZajf^Kbt7};lH*3G~v9RGyHNFt0xi#v?y z0iKyS%_`v=Pa#c2fi`!+Ri@zqo_u8%^IkwY*U%t|za}yf z_EttP9IK(oUeVx! z9u^2T<+(^c1rXLYGoYvxdZO(I<5#3#3)P<`E)S6=97TN{{7_V# zDkQsA$g(}?^4JR+up1S-I##~(t7aC7ffMHfjelB}bchc+jvLqb6ItM-e9Ym#g=PNT ziiX6|;xim-TbfEi2PKjoT&TmVI^j=9v1Xz8 zK}8FT{9ro{n_Kzw6F6EXU%1A28ALw6XaN=j6a3DHiy1{UHiM|pHB{?Zb&@^oqi$p> zxd=g8R7o0nju=axn4L82X7DMNK^Y;4)^_dI(Y%kGjT$~X1EOPapHFtwVX;BWyYZ~@ zEZ|t^n@4I3eJPPA+Ph@ZK*&D%SjXw^VwB$T{rGvZ;L^V!>5-U>OE2l@s1Chac~z4> zosM^DUw08w`I{}?tm;M*QSs!jqn#mo0mVb23)G|hjnmUMHoC9wST<+k`R-C>%g0#$ zd-$fXetYU#_%A#~FD#@}=-sxq{IG~?+!d)L+;FUZXhP1D=fx zhpWhLl(7pKQ#0u*^8vj~Pqm=34k9G~!MeUCm7X7uCnTk_%R)OSStNd8U(bt1jntI!e?f zf0`Z^6e_j}GIyqY@40e|=BSLQ+E@L))k^lI)jg3`>#BPr{Hww8f|Q~eLJU=wflAon zFloWVtFQ4y`EWm=>VYo@-z*K6i-PA|8=w~t&jYh7N#yVaCY-dY8U`j(+a?Q}cY<5N z>SxrTQtECNYDa%+`(qviajC`oyX{f*ZCwf1HD6ur9>bGWcqsm9>fiva!EH((fJ89} zA*vw_I4bNrhZn7kg{%s^ORPoqQyE<6Sp&B|;wZjR*&Xh*WE~yLI6aTUCkOi}_Xk-F zpxPyKU48w)A}#$3uB#%QGLyh!3A>s3m^st!MFDnQX#;HQ%)^2L>vBYwPW(}8gsT4vlF!7lM5q%f zRB!7Ydfd5C2QO{mG!e3!{kYfhAwRf1-zVB^(G}px6Xha2Jkru4GzW`C*&#>kp#&Z{ zc>jE!Y4LMyZE0z#rNBT?aCO&rIkNqFy>JGs(pj4*G;w(5^v;kr|7B8aO;L zydHFh>xzs2;N}%02z=M%i5vG(6X1m4BI<8o=ck{6PAS99y!J|w_g}|@$XVmr?k3PO zA3G1`XetJAp$;72MKi+fc~Q|`VK#`y9t)IH!uvc&O0AN|@j736g;Q;W8zCt!y4Bn) zqHN;8f?6y39)6$S9_+1p1ujDO``|hVc)R2}-H=Ehf0R*tUJ0f!st0187Kem47@rI!!i zxUcN$sOQJ1Fkb~x3N>>5aD&ufzA}E!n3}6+_2o@EXRfc1s2dFWg0C;g2zeuxsxNyU zwJj1B(X2>uunfE)5q!WP9etYS#^_1F2vKZgAche#{TL3M5R zyjSFSk6?Audx7>6p*0|}Zq4^1_3RlLodE<(@=mYr2$}8D#~m?X@Zt}8>wQCl81MYH zox$S2 z_MSq({ZhycQT(WRa6vR%3|K#w!`4cHv|T=1*{&D{BS`<^5E|Q8NPPEb6obBNETjp) zGE)q9>mGe8jn0HEr&v`pP*HjILKdN((3v}|u#g!M6c^u07v%*qS1 zTATmd8fY^tDtFx%qdF6Hn~ZNx4o*>wyCY~zB0qSQi#cQkAb)GwZ_M5K#1?wh(|3Ps zYmej=+byr(wX+a*b|t0I&|QVg3?N&dT=#XgDbZhC`&m3_4AmyP%I%W;p4G!VdZeck zoaxE?Lcx|BkmTA4yDK|Vl25sLWV$7QR$W$>`2snJe0M zi%&XmJ<^aytPo$BT>*CapGn*>ez1r3T8sIe7wBEQW@c)XWQ$c*|)B|h+UlpW(wgaL1l1kW8-L) zB%k(`>y?^uw#|1P!mFGO-^lr!5Er($M@FivhebzYFwqpWOvP`yRD4tMq{oanPP{OW zDBc&{j&#!10|9fe@7)iN@*4gKmy?v?WM##T1%PRjH@@rSW**x7N6{pe!O!<#7`SV} z4HSyUAQMpbh$?VWtu!HS=F?O!mt!%r+fWaJprqL}uxL-?P?w|Eez)q#@)XkaQhM`7 znC(KOZBWbNUOZ&bxILR?s=_g|$h-j<%@d;;+FZ9&Y!gq@5+=XX6;JfPwaYsd@=^*G zFqPD}o`dBVn#hH0ck67JG1G){0W{*r!a^ zMB@69#DWY}6G&nPp`(pYkrd%cJaMeYyXI~8hW5;x1E{Vb%gjNIE37wF#70LChMM#D zKRopJSAOuoz~F%}Ep1NoEKbbEx-EqSA3X*{A^G!nJ)EegU?q~ek7!>nXSXue5`YCq z=C(*8oodzkx2X?wJ|_%3byqy*05E4%NMpbjU}8PJX!%q1V9cSnV(v7YKloE`2PIZlifb>>t5!n ztYSF~3yAiuAWLM?))w1UE$fr*uBO?ib`NmAM8{@Z@V9+Q5)yS(^W3=`A5y@x^;k*vQ z&Cu;D`o@P*ygvc4cLGk6ZaTjr=iEglcs0Tlb-0gjPzWT;@#{cgd-!R-T z>J|c9$cmp&43Ce_V}Y?5ke(qpzmjttV&*)U{owR7jq5Fs(4VBZ!e{K=28vf}$idsI z;J{1R#ze&tKWoKw%At+q{k^SCf1MydINm7~Vl@ke!>3Txb1Q#M2?`rRyTxd9NkZt+cb8qn!=GEw^ryV%y6kSkf5gT^6-4D)^ir%CpWK#MC2Ksluq(_Ki zVp;jtlT4V?GXNwJS~tH=^IN2&Whi{}Eg>W0P4KB4Eo<4E;vdV)Yin!NCx`t&X3st^ zHTWKX>DF3`vI+E7$>-Egp$|DQE}dxHDCdnFoH${gTgcogm`-A8zpB)5^w$gb@|KT@ z{P*y4D$@_Yb8#(3{weGHA7~a1OKL^9k^EJ-tDu9aZhR})t_YYF9~P(w;RFTRC;to^ zT5#!pqPrgiDIE?lT0YXV-)-+|aUkae2id@|v}KD>Th#+xZ4G0<+9$ag6h^Z_5cY%V z)elJK4@zgnxW^7|qd}*;fBFzcMXVLV%r8&@r_4GeFAF-w8y~9Y6WJb-Yist_(ug_< z>KvODOC#rzhWt-<9HbCsbZY3mw|hc%x(}j*mq!LsuesUVV+l=zbzy+DECd6m*wN|C z;$}xp&PqQZ5I7%IqB%0dBJzt70}rP5KG96o5-u(>GxPB+1?#mcy_SlNii#QL=X&#o zz|io1sSO~DN~A*o(HPR*JunJ9|D~lrg~X}%Zhm|30NNV9fwR_I0mb|JB^Q!*b>dB? zv?07gut=f^x&!Ta-tBCB`#5jck1vK8erDF#Q{FM%WwQxt|17}xC(o6ZZ*~1c{tVzL z0fyA%_`x^T(t19=S%Iy4eEm8(n->$@H=iT=F5T*%4im!?I-4Sn~@;25a~T)+_Z z$B!Qbb&YQHRzN>(Z4HhDz(=Hv>pNU>BIwB)6cnnD>l;AK1mti!T+yj81Nqu)=9T2JOhLJxcG z>Xh(m;T~@838NmY6vg*9#0i4|M$n#~yUR#mk#k)AaY5w#v{W8k5QP_{n;hBFn!l9; zErlpN_V-{6uI*^3R_BMe!Iw|!lt;nMr@3?>64Cc;jk#t?WS5aww-(53U zcXY4}hCSrshY=J_&ql!egsznmB@7P2_|*&OOG1`^2OQfE-7|F%o~(U`rPNGEjNRCYU??t*($Ol} zNQ^4CqT(tTEJyPnm;o*#Ky&S}%5pEy;ZjVVGw2n1=SCl;WakkQXa_7RacLG5F%^9E*t!N=VCUG=I^$~yp(5>eK;XX zmN37-)ggz31TfAXxClwx*kH#n6O`7iSt!(-GN_L-d|JCN$L9??zM6&s9tIPqHQh#0 z3XWJrBW=8t?kfiC4koJP2Rr2lLtI)*6Y0~Ug6;Xr47nF1SYkCwx&$Y-U`jOwkRPJX{%a8p@T)xRG)VIDHMesT4zQlYB8Sc|wjn$gW+h*j2WXQ8|m zs;G$IaKR#{+{8;(-%nIuh_M4L|Iitu?$tXeK9tGZ92Hd|$_Foxu->$$&Oz$x)2K zLCju9iaR6GBLt~+GoN{|PJUeq7&Wu4ttl&qF=!nf`Uko>oH{ub6cB`jC@Ma901T)- zE3(;X*#JhXb%B;H*M^$52WltMie*Jo6X@bNJ&7Xuw5~U(9)z&ILCOU_S^_}^cPUy zC2+GOC~V=ZL?eEUN>yeTBz>F*D7{@vDfDv&cW=V{7{23c!%rGIPYrO7bA%oAqQ5{B zOi6+ZQYA`J1-as0G2OGpG6k$htusQnv0GEvM-aE^Ras>`H|D|#!_2N1VHv1tNWW%k zYsvUhyG&Mtw1t3?QK&dkjG2n0EJ9RCNq)+mgp$PE{D}&&!HuMWVt7-!p2l?}V94tz z+Tx%KWCpts;$CJr5jytc@N0`8i%asKuAaMBAB>_3zxK9=hDaOaO zF&}t^%&}pnLY*y^W#rVWowma@Rw<^Hw@uleBh{_MzYUukEbW^@{VZ6r+#$CAQ2X;hv2O{)K;w70NfJS=8l~!#5e35x$^C39@)xh~3T;ODU z4vRg@2D6E9JF*fQHfFUM7UpOZ=xt5A&1YNik9`t69f z^az4yw%|e?DAEv8Lfn7Ucv{K$KDy5n9*^`#6YI9OPgd(^05r7LtXBHLRbZS*_PLW? zDP)D|8_vg)fr*l54i^)xFnE*0!67Tl&CRVonFnPQ&QuCryBd%8F+B#yxqrU`eoAcs z%31(CMeRi`$_@{Kt(2Xen#vH=PzPaSv$f62LWthOLJt8%M&g?6#zyjMIF~Xvv1kTX zi~_zql=v_uZ?v5MfS;MS=j=VZDiE&6QHG~ma8FAwcm3l{=~V&kk+cN#$WrV(^ADvp ziqYzruxc7=g1MbVK=#Nc1v=}H6F@By5m8dIv*Wma{pR*0cF;SEdaLK_+u2}vL zxz%@Lqd}N{$S2AgoP_@o!Q>`N03O&J-tf0XaX1gN!!vTGd4Ipdd9c57n)|o?Jh>gA zN5)z3(*%KCdZLHd1D60#`MSyc{Z(L3!F!KVc7>L$_+?6WT*1jw@2T|^P7P>4p4XI< z?`#cr#D4+J7rjFfywU2!Y99c+eTxuvmO!H7_JmY_{fc9i_af5LI@s(KX@O8*9k2D+ ze~^PSl#LLAwh3W578$>dw(!&Aa0FFbS-)Q%p@fBc z=~DN5F3%NZ`>fAkDK_@^x3@P5$SJnBDl5Z)biR|%1+-REQxnWC!@LR#5|>?Onp}rA zJOP7cz;48o@NeJbyiZQUHDWnAS)_=dgoMzOQ!KegnRvM)q`1Rp7QV}?rZjINZ@gW=kbLj3%^UA?CCQR06?&#JuUi?X3Sh8>;kvYflG{4Mvi}hcYQpbk`vQ`cfRLDeB27jPCdk#x0cQ56*Sach5Y9a6Xz^PuZKH1!L>&B zM$74Fb)w<*g5h`Z4yRH?8SX$$TevdbUP)|?_wOOka6Ur?3*z{HMrq`VBgfu=CsW~E z5zncP?neH$(VMAt;hMYU3gnt zad8?`8#r$tq4xwWDvt=+LQmpC-KQ92g8YUG3v|PuKGjVHUep2rYS!xcDfhM}f51bQ zRmOkackN`c=#6}MX(=#Xwi!xs69>wnu&|f za4>ho7}9N4iaKt#GGH1uUsw{tJMobSV-r4qybCUc&B~w8X zExk-Kkrk$ozAdcV{@yypz7FIu%cI05rXb)OyxSC~|2Xts=k^G29phfT(yppG;=C)p zP8`Kvxb@^q0)A?%CS*<>)a6v)T*}@io~<{pXRoQ$<ZK9M%KEVQg=XZbFq(aFEk zrq)eqX+JgO_;-xUEz5UTm3iF&6YoGQa|G3gXZCG}jDPJiLracI?oCTUrq92-_^P1VjV;es`258P^qw3sh88a8x|bd~gSW znU1I3ZMHxV+|*-GVSGyjld?A60Jqj*OzW`5DCVMzpM3kIela5k+T8eXyraR04+m-YUG(c>OZ zJly&HyqM^rS4U|kvmkI5K$9T*Bp$Wf9VC7u>P*l2G+_RDUa2{PvD;^vQ2+3bkV^I! zV3IoDhdU1-i@XKLvhVgL%`ea>$V^gC7)->?vsOKO@j{1R4)M5pf!F9;OHQ!L@szaC zD*<5rIx8<*8lj-^_|61OfDbkX@wtKJ4!cDH)$&dGt!7&*K!(6%)% zVD`D4pvaJi%kduc{*=$saIHvuR>&^$E+;M1!utA;w@@t}9wAyc&||;hY5)0!$`fD` zaB{M;QZ12#o0UotE8N1q7npzO>}1oJl1_GT;4+pChT7r~e^_t72rn+pYf;fisT?-0*Oi; zOoVT@Gh)nw_n3h*2Zj2xL4?15yU?3~GVVJ#?C`)Z5Mw{qtBbLg0OZU8`)`O^N{H1p>})dE`P$zYi~ zv)W)^5{F4gb!wRSbybSF`Tg`0+k{!uEjiSzz*Zop^pZHH|G1jrY7Z>rdG~Bmlbt?b z_ytVFOTHP3-;zhmU;XF${!@c>N9FVz_i_g3@G3R^evT%(zxBi$pFD+|uacc`$s`@_ zI%IXxSjnoIhc=$%jEHEq+8(|Ybl&Lk>7mgGo?9%&CYIVMwJMxhO~=jO8nUc04ArPq zltJy9k6MTWzSvlc&zuaN&u(U}&)u__v;r34cK%rFR(g@J^zJPJ@kz}F9)7l@InaZr1TrUd*B3Y8sshscHV1cg(J*7n%BA!3oF(l;99Tft=yq$it$PgoT z{YgZ1&u!OqH*r@X(*nsQJS*R$PegDWt=1wSi>qa^eRcQx0QGbN@RiXh?T!D6Bh=Og zoBgoo&x?wllg1|`q@<*f92~pFsYYBW$pnHgr^WmbVxBBpM z@uF3Q4#%PvZcd;1+R!fCV$^3j)o#?s{IOC>>WNY6>VWx!o?ZBK4{$o!%%;v0{3UIB ztEAK?2M&wxGaoc^TSRa}IBCOQ$SYwDz%{>|Un9l@izg~J%b{Wc$hZblB^{Unva((p zs;@%VC3R$JLy8RT>b*8O0rOyYQs6!m;768KUfkcX%fb6tcQS=D zzeb<2@~K+hkx?f40^)?tO+X}7q;z!5FTBA`aCQc2&4`@zH&lABcX|YpcnGsY*%)h) z^t6{RU%g5PJvbsE28I&%CtaqPFJ7b{X^@PM2kxRkR~e{kjr$7(ml;5y$@N0d@Ab*~ zl8{h>DZqSgQc|S+c#V*gvwBr5y*jFu?G}(AI@t&-tM*r|>S(oq9z04AJmj z9_*FQ+~SY7SXdKCD<^A+5w)Yx@dj@!1I|De5`t(iRJguZW->p?tqY6+$66st+HS6a zizat}7U;I+#VMX0ytvpli3QG%9R5|-dcklNA&98p+jhQZ+nAE@mN=`uonK2>cogyB z=)k4S7C80?%5Y}byHr#oHKzPu0NL{JD%R17R(mAjggW7p`aF?ode7>XeAfUiBmLBV_txZp+Jq`)E zb}b|Xc)mcjSPeL?(S*MISsvXgeAXbh!jX1|#Ci_O*kX;OViqy#2&-n-{C01s<0|C^ zqp|e9_V#;KX3T$o=flUDDce3g(TvNIA*e=;i8ESP^Iw||?9d7d& zLizZzvS<__YMS!$jdjI3y#c$4NlIr-yu2HmC05|xAA4u0NHAH$D&cY#G}nUyC#+B) z=^QP`DUpEfn1G@!+^?arvA({MQ1uz^fEZvHZvu@{ZH7vKLH-dBu2Lv%UHI1IKUCQF zngDRH%Wh41Ah;bcX39Y)yUSu?%051}wjqkN7q5k9F*MnS1oIo#bD*x9_077+SGx)gzw9mHV3W)^sP2 z0L6H#lElt$?Hu2A)#sD7q+vDpJ$gxShMKvpg5FeNJG0hP2W@OV!sJk{WRWXOAyo0& zkB`J{BO}w(efn+mv8i>y+x#-apRjH02trpMAG5`uhty zXk+L8`6f|PRvu&eKDmH^)j3YTfM4S?-&wozd#l$@&t2txeqOY_)LADyN1`|+#OC|* zq`HQNQ9J1OgVrSKkz-@eD{wmhyOb(%af3P>nwgmzVpp?291Lv&*PYX#W_~f3w@-h? zTu}&bnE)YF^n;EE_&xYN_ zf6QWxFw4j3xl#26a~Slo|+l-tjysQye&333>X=(V42@aq$J*O+M01)>OL-Cmz=^ z6-2$nj`?@1wJ9EiOvs=*o(`6)n_!s*XvML5sx(e@ZQ_H7_zW*{z-yO?sm2S^?$YO7 zU9kheZG6vW?uc+)qg-ory`fOk(4SywIaY-g8LO4hOzDam-b78!3L3-g8pE9LRA$!t zy6fAqmFbF*)1M*}g%s5|#_ZcDDIv87W^*4b$O|%GFue#CT2u5uM$r1_W` zV1Q@cTPjh|+d726tB+DzbCv+WAZjZKaaUg4)8}`x#2YS ztwi0sxICEEO#H~qVD*jP6ITo0`2g3d|59C$eSq#$--+o&qVJ+uRHFOwfIAG3n5VMR zN-HOlQ+W5J{kF1aKX_bX4@XBcGtAT02FgD+<_nu(-nsXd9hI)|Sf8!_oEX}X0h}n$ zUAlsA`NTBld*;N(miZBnN|48O=u=t4x*F#;693=WH#sI%^LEy{4AbEDz)r{TCXA1`#x9e>k9OYQ7|0zfEBfDicanJsGs~jmqR=W@~KsZGhFeLQS0ULUl&n zKVBE?cEDBq03vU$W6|+GY?_CPbI_M<#bG{u_@JM_%|o|jjliY>J4PE@GC>M9PVbyw zSXfCl{SZy7C}T9kcSS zuDb30JI`mlKIT2HcXcA_kowuM)iZLUqZ=!6duA4{qH~J8jJQU|yI&1T6`?@CA>W)* z=N3(}>fKr$_?^no+|0-K;|Js~TJA`&$867Se@>ybg|a+6%oi{S{{tO$ z+s>@a45dY+p)NYr6?l5sz6^FqMT$@Ss#lvR9zQa6NTRb-6|C_QE?6zglbro zybq(F)pe6>VGNFzu;?Ys>77T|0bSbvK;_g8x3J`t=!*q+k24z54oVF zFDG6s4eo@X1e|_B#=J2U)K-<0M0|`XYzpk`>}+i8q4#%@d6+;$6tqOk*i<<=g%uP; z-nAJ%ExCHqy_H6hD>yu}a1)GsmTK4B-nv>HD0b^!ol)CFsccM)yp2>Ph3RD&voqtP zzE!P&!#ib*gpb&o@dBOaL>8U2iSco0J|7h6F?isJbb)!1r#0SHhN{gJ(^-mtDlQqC z*|7r{L2LXU?6 zZTyV3NP42UghCTFJ*iz-LKatcmfGh+W|%$8@u5#>qPM{(pU>1DF5z{}#fM`&>OJiR zFD=jIZ!ft~n^$9JB5cc|cc55`#6u32SeCG3leNylFXdeh=d zcbQiUP05r-Xj}PiGXfeW>%ejrs&Nan5RZcAQkJ&yd#j-P-Q;BbKKlMYP=z7z=V%GCb%;S)jX2s*W z6J+2e{&}}7N)?&v=5Gb*TImGbfy#WCwi@EtAaI=jI6vdgWN4ALmpuKN=VN||*Z02% zYjm|xar{Tz15-if&SYqze*gILKKTK|+coClgMo4m0biDrCY83%m~AZCo@YvCw2iPE z-e`@;c4m5R@-UB6cBRnSSv9Zgg8o;`=PsP*lM6}?LVIvI2Hx^q`}gnJvww%5zbo-2 z04xD@hT6!_biC@`eTm?-4>?9LD^sqHZlswqE9U|o8SgHuw=Jv9!N&j`1`MQf<7kJd z=Bvlk;w7kApfm9Xi0W@$9N5Y;C4b=oerUiaVBvx#;5+GBCDJ=QR&hnfa-~gbH(tnu zPg3CaSkQ%22OLd@9*lqZ4^fLff#d_Ax>XKCWHStIyj;~U4xE?-voI!MO#?H?KdjU0 zOjRX18ZNgd&oIwxNuUxqY8N#Mvt&7Zw5e=W>^e!sBJOmv*lq|_-<9bTbpulCp z!68{WG?Y+LVfI{7BaJahVRdvn*>9fujA#j$m3U5MWRSj1VGMA)JN2c7?v;NcK!qS1 zg&3CS%B26C^v%*=IKq0X0@N#rYqa4jEC!V(?M&;#y#X67F8(3Wk9?5#$BXP123@7E zvBbddk(#*L3F|^VlqB&x_bI*niK$LDWw;LLAf6H+q6VM4Fyo6O60RH>tZmhT%DjyOhcjhEssPQYN=Bk=v0j}8lHyM ztA~WBsnxd`Chpzm&KnC(3G#XO^4#tWelI(E)LHseUJ_!|ld@%P7LLjNrHuT#2#DzL z0Nvg9r%qyX>$Yr2n-4}S0II40`xw`=P+|W4>Qq2~_zS{N`rZLPpPM;N7O%lagOcQp z=2n>nZRqVS8nM@^ij;(q0^Fp@@aSRx2;!(gt%8pG?K!)DmP&~Klc^hfx}vt{B(w<5 zC173ue1~Rn#S$#E1%b)J`*(%J9oyBaKfVNgu-K&zyVqLplJ_bn7i^WPe(S1uMr<{C zdvk?!GJAZy_2I(=*}R_Dp`pRSc6RmEb9L0&>N(PgG4{~7CYv_e$ucrEoFt@lU9Tak ztHLIJZFkW8M0gTGi+<}Xc9z<0EF=0hqYh)zP)~g1?)uj1>)#EiNJpizeBBY9FI5b$UC!nY1>?45r zfLI^eWK|e@6+|UK&>D|#M&$FcdhO7OO)Z!oj3%Jz1Ppb!co6>_*A0*O{XP%QHFQ9W zP)Pwx&4Jpq8a*Rg|MSv9v0q$Jh-*+KVh6+^HfSDs0O$kQ9$es^Orm@LlQKTnV}vyG zyLP(*h;!^W-S;zc8#{lET+eNYm+?P)&cFFvA2Jq;a&tC>zYC%vn<_#>P&-jZ+!KsD zsk9}`B#!S^GPQ3K5m+62Q>s$eB6}5bBI7>mre$*qibut9-lyBen4-zuNRu!ND|+xa zj4A0PMk5G>3b(!hgse~tytx1fN08)=e+3t@qLxV)TI#cRD(A-Q%$L?EB@e-3b$DE+ z)h~kfXt?6RZ1m*vo`jST82jUaLYQ#`*b}V0=Nk-a893_lUt25-SX^D4K4f7fE}xN-f-w_u@*J;`7(IfOFp zJ+VzUY1ZX71=CqFp~XB#ATLl8Tp6yvZ@wT4TStY*!}JhuchH>_a@AN`}M>srR+X&1s|975G+=Q z^`^z>@r)O*Qw!xR?2GGA-!=xoVkpmp7OJ55-7F#RPcNcY;-}lzLJ{Al0KKo2f9q(c!!9jVJaH&Ruv{#`2tth)!6%u#&3R(1t4U7SiC>`8 zi|7Izi}h(snBBupKxS{2v{#U@_};ZsmqkgpMY3ViOsqNE8l(Ct7tw|eByX8rF0R0$mN5Yq{~>5;9L@hU_DE%!tV z!PPh*$s5D``mKjN!?#d@Z^pE>qK|GRRt1E@S76B!3cs;c%jqBlEpWnw7(({BM-OFn zzo%>?&=ou-x#0w~_)h~lJ1GZfVjy9RvCOyt*{$%%q>48TlPjWAD%9Z}O?d?Vp{5&@ z0Z5PwO#FC-*BC=Apn1XOLmu8oD5i#xi{=9>@Jm5r3o{8p1>j~T4T*Wjt=%}(cu@Rm zEJ62I!l-*hyeK-Jzo?6as6gRgi)#~|Ov>VHUw#{(u`XuR&Eir@Q%DUyBGo78s)QXV z;+z<})pyM@vcQ~@RH~}h$~Ltu(2vy{cEe+g{J)n5_Q@>6(0&Prd;>^6dW_Lo8x9~6} z%ft%v9zk{q`oV~{6+B)yk8}SDQ3hg_lw7{Yrm`}9NF@D*M#Np+#uM7-&JcOabcMx1 z-|m2dUWAt(Q&v>lK_vq$|7VHNWEBqDXeUc}1Xgl|H(h~J=a~F`*!W`_PJPHD%EEHF z&{W{*-0IbhB5e^d-Ji-{6@?jRN&D6ilpY z>>%(O**!ui61pVkOGRxLZHA0*vp4Csf|cK3Snr?I=i$X?C z*B_rgF=pU9AXs%w%-d&=aD0XQ*$BsYKnUrb29U~v-i(Zu<^{7LeZ2PAn}RxWp0u`_ zy=7~qX=rAh3Aq4xDFD26yl^CrmRV#(9C=eybhp&|CaXxgPfWe>7p~}@mp-+D!U(KJ zKD^+_dc8_w;K_B5Qe~do?_ekustzL4P`3IPXe;1j^f6B1bgjkm#v`zT1hCmEqKs3W_sp@n4i1!nB|0CH&Wc)UaV3`z3*tClu@Vd1< zJUJsdx1`l=>dd4~Bqz0`b27HFed8y`7fy^8T%&4itIl1;E)%miXopheHukgjyF8rs2CuWuBMWV8xQK5NJm>WpaG zHw&Sq!DuBHzCx|w$43zh5(z6f{0N?G+kaCKheF6V(j)SJTF)^p`4Awu9OJ_cjf;w1kOj&kP& zD%=3R4Y>Dx0^cP9+Cg>oX=`cj?kk;dwZJKu=Wtf1aY+3tjI7B@AixYgf9?qcVWdB7 zkCky=595u&>o+X=94EHyQ4*GBd6H#nYPl@K-73QJs`U=VT7c&wg@4`lm5`%Byd29< z0VZ}Lng5Js%Ab&R{`lXz2K+ItKGmPVxBlr-%s4(_=1`Tu^4n(_X`iF}eQM0CCR&F5 zErCPEnnq3gV{ZBClRcjL7~p*;UgdzZxYwt}dl$nRO7;HcxgDSC%+=43b@OQK{%U4u zeP-9QD(|UxdF*(p4#>t2y!>VffaAOqH22uq8R%H6kq1J-e}6m>!pTdjjX8Vht%v?Z z_y<5P3!TqSR4l58b*%xk7Hw^q1^LVs9bi0YkvaAEt!PxIZ|1a4Z?yV+_Q=JziKv_{ z16uON(h4|lpNK7cl(6?0d@_=@g=Y3keijE zZV19F4H2qJ_# zobTCahN#8U8u1d8j9j<4?w>|#sr*Gzqx&KvOm&>2ROO}JgG#?ImT$xjgZB_?J=iow zjMP?>`4gfYjO>1Itl)cm{`)Q)X{vllsei6C#2HJ~iWj9WU5S)yUdUgJ2+A22q&*Ap z`JP`>bT0k0uKhx;TJZD)?ZWn&@Z95#@lzY8-+Xn{Rwgaq><0UAhwZtc;uUme;GtMf`Smc%p_HkccXmBLjVA9)9}F-b^a{ ztlS$)Jqny{+!WVSM80yW{vEl?scMAIsCy?a0hKoSNrKsv5C}lGwMJopKvT!TkO-?wZP{F$TfSaKC>*4xIVts*+L{GJc?jC(s*nv*k?1NU_>{BU>`DK zXNZlvw(|F(szY~3U9)qgEk?CZ)iT6cM!V^E*`JC>%nt$E_|~Mgh*{is`|?I(F3X-( zu5Qi6Es3I#p!OoQ-RHLK#%|GYA@2x`H+*VqgyQkhvP;vAZ{pSt(iSGbZ>DuLUfo#j za1Kz5U2Am?y7%Uf5G`2x@fPC)_!{6Q;QzjxC2|ohY`-~n_r6P5w+D>c^FAa$YJY6& z#HfA!lHr-{t|db>a`So?P{#r=3E*zXz@Umz!xZu4#~4uwpHLCX!j#zkiRjggB6^|x;FYel3s zEyR>^q)p#d0lb7QM(-f;*ez7BWuwb7(7)QxhUB+)uH}?=dHoPBlbWpSuVMDs<|L+BNXVsx^cMYV;v@Uaw&blPs%Z z)!mz#^E`yFY&pCJQh$|UWknUQkFb$)4KUSR@QP0&NQ&il*&7n`3mCp`q#3D|U9fZt zRx$$pj;%M3RMn-Y31t3}o(h#N^RcEm)4pr0d$7vP-wPuS-G1F#+0E8oifJ#Mkixf; z;)v_5)_orjJ#u@nSbia%V#?<@eI z{G=Z@Arh2?A&IR8*%C)r)l~V|5zN*roTJOoY>DtiWTxn+xO{D5kZZnT!rF@2`l|m0xMPTU+#R_67Xf;s5DW>+{80Z|PhG{{39j}-Y3ERDq8(2s?rPKaIkCq4 z{rIrO5h8}>7_v0fD$EkE7omzS26%S5#}Xt`{C+-^NI~5P^zFdE-Xnm*2%#AD&G%Hn zom3RUotGJbr(QRy<@OC>fF=YrGzbV8**MwKs@qXx%DRa810z_dYsbD{dGmQ>`Y|Gf zPj9h4D)u%e_*2;;^VN-#cbQBtGt3H!wKSj2^C3w&=`39GzQq8a#KDJvK@hnAkqCiw zR|Ximfik&BA4aZd3>00bG|&#@GmJd{2(9mwo3~@+Ocw7QfiKo+%Omei&dS+GK{Cpy zo>%`6$bW{5L*8%gqMLE%+s|>HS#Cd9k91LP{w_DQD%tw2whbAU8EO_94tmuroS*Db z#Pez@Q+{uzEZrfrg#>PmE(r;D%7Mk1Tb(}ps7eY|vG2Q`_npup38>c%JZmuXSe7XT zQBdujfC-y}p)j2|HN=c^d+gTpRFY!Q;uIUR zl)KVd%$Yv92&=QoR98)igDU_d=a4g~TW<_ZGEe z{y(H0Oqd}qy79WT^9O`8NPUZJD}Hf%Sm6P6rDX=`}l4XZ!@8V4nHL#+RIRMQ zxeq0d_4GDYej9qd1_C1k>Zo2tRN;})1JJ5>OlXviw8i>)JnNA!ymWx8T-77!TR^>I zOruz&ZlD0nhUgZ9amm9$IR*}Ks7_d4!5|MDq#4Gx@h^`wKz->=Ki{y!-HXH36d|t; za~-qgQ%_WP0FEjIq|!HM)-#f9yvI*|L2TvToMbpwDP3`z%?Nrjv8K)-{iWsM>P907 zc_6NjohsT|eH6p9x-ua8Hb-@T&pzgf)CZ+av=wxDhU8rX5$xl)S8jA&+?W+{)TmpU zJv~>qB#RU_f& zl-!H}US9^^rr*7?e#aidP9ac_&I&n%<)aR!Ho!$tl#QztI~7C7P1knLS~Q4w{|6c9 zVCOp{wQ_|md-1w>Ij4>NDiyxyryx#@=gPOJydLum&wtPiEjHLDZmSCB= zZ|Ryh*DBo^botm9$((l75i)0&#U*a_RiyXY?Q(~*F_m9bx1kqg)WRqKdhaJIdH2u* zJWnTXwX*;cl+Ys6e~v1hgY!y@pWNGORzJC`BhxGTG?{elDa?FZyXF9&1q-n>RaxFB z=U|6z!Ba1k6TxC=Fn|`}cSXu@Krs8?E6|$vKLouls}e6D%5`=61ZXD`d!Poz zUZTVr(Mc z2wp#fIkj?oYVL!X4*Y-NCXZNDoO%aj5M&;t$Y*fiZP(SWK>|!NS5;pkoxFYX2ts62q8;e~GHgHFh|Jr5)>0y&3nYi6 zxx6AyVfO%UWWuG1I|zv!K~JNe9w-qvyNQPzLO5kYmwkM_4{}aqHb{Zsp}Mp7;6Ws* z7@VM?b)WX2B<>`DaN*V z=iEPvq`}PFH9LI-c%jN-UW-pNX4Xi^V~l}XUvQC%5FC%MWedl(@=fH36GJ6}{}OBe zbeEX8#bdUIU>Wy@AQ>!t|I&d3*KZGWeTB+&<^eQ@vV6__21dChbO+y|Lb^4=eZNHz>P8 z#+rak$4;1KURY^4ctP*p7&07W)YOLR29%f4QJ7yIItCpnQwMgb(5Ai?snW2ptze%l z3dy!mYkTy{THJ9bJ-nhG8Q7wrsw^ZKC5E#-Gg96OY zlZjrz)hp*dH*p;U4?;!ko)5dAtHb<{dWg>mAdL#!xo)>rqLP5?0FWuWp53Q6>fuqS z{VO{zg~LAwl@E{22;T9bZ@LJd_2i1ya{UvAV;rAGgucj7e$ck=QMc{3DUmB6+O5oP z5Y{zNyKxxZ%4GT{IjhPo4f30z56N({L{#9SxY14AQAxCm8C3CX#}Q6oC^C)V^)`!3 zGLkvhIuZFmC+7(Cl!|F&Fh&~NW>lT`WvT&&ui9Ez+ws>g?c8f9e~G~^1fCrxQwvK6 zSJgrAng^U)F%ybN8tNE+!6P>t2V~sIfVAH%D*dW+7C)M%q$1#5PUP=JwX-I-KMXlF z9-Q%&Y(qST8fULyj72W{BvK$fUua1Jjzo!5n;`Av4D)dVOTq& zPk!W80T3z+x(4;}qoO`Fng%0R)PWec;%t}}IIsJ`3=?2C0zpVn8WP41nbGk*Lwbu{ zGxVN)3wfdkx4u8dWMiYVTCSGbSzQlkS%Bm5GG&AwIAQo9`R6oL?8qRWd5ZlXN|*kp zj|**XUXyS<1(NXM9yNe`84&8H8MF{D_Y;0?fPWbmZ{%q?aJp1F0xJ%DZlL&w4V66y zu}Zt|AoDYPa#iMKlZc|GhXb8Tk3gc(d9hQ0zXxv*1^u?AOLK7=j_`CMbClXTgJ8>4 zfnOQwjWf^_QIHL}o}r%FI%L1Pr9clBqSn@6YtNeK*-Kn_CV%N2BYp!*yiK<+xpmaH zd>Airju@T=bDqjGd6sCOFu;COh0>N|(Y1wy` z?q2{Mtk*F0l2H{3$x++3AsZD=;DoLsU&4Bp+oc>AXyfBSe!8aXk@^w)rXn*YA}KoF z;9I<{*qq@sp~lKFeu_p9$%tq)KW;clw^qRLuqs@k`F-KIy~Q|H;$EFEu-L$ow2TjE z@j{+TdTP!tKLEz@_rG4yFmS^67F1?3iv949&wAyfDb@@bCL3>QoDv~MAXl~{TyUkm z>aDOLvgxdg(02fE7(y;5%-vS_FhCOlR#RQ)@F?L@i64Enbz;fACc&!AIwVFxTUJw^~2hbNvR>3K&-#9QU*2TN0&y!p=&_WkC52T zUcMVHpOTO6LmXfG2#UZ`{8Vx1ADVS`g8G|!gd>`hO$ z_O-WloU1{_t22sxuhoZr@K}-Y$Hjr@U;B|GI{2P)2H}o1BiHW2mvm*@(r)Tj__-yKzS0OFHkO{yU_; zRj_Hf{u*1i*}qq(WuG}KzcW@wP0YK?kbb@hcVo*7j5D}`3>f}hMU>qU@Dv7+*+5Ng zyf|whpHL!^b=gG72#NVz^3?!_D}km{QP+hTo*|i8fTxd4+-j7gv#XHn!ju&KypI7% zU{D;`&SxO+YxDWi97yxI$Xk2v%2}{5z?BDTSIuIJR8h_1qpCBo%$l_c4j=g?WPp#Z zKMKhoPVJvZD0vX9-Nb}oYM2}_)ny8!Uc;&Sb>UM|LgSBf12u?d`6_oKz*}TBHs0DN ztewvI+uA$f6LM&+_iJjDm;|=@Hzg??2MB1Tg9^l>kuCbec^Y*Tpx;>Iy>%5td8ID~ zAl;FU{_Rua)R@5S*g3}yHof)W!^8QxZUTQcr<=}55>^=84F{yMyu1dhK8CM8#!G#8 ze!0Ekuu~T#mXuf!=&dVr8Fl#n z{vP3J?(n^jcV3yQ9mwg-r_c@11>jQP#uTA)W9+$T8_H!M)ib1=X1m$2LPcxQ9DZ*w z#L?eoQ8&vM!AC#}9@}|)J&=O;QP*qWj~Umimq4cldPOmqk7sdmx9b0IIAmPQt!DIz z2-v@cQ}wz0{K*+fjm#~-=)k#j()E?6sntb^EQ~C_k5jRXO)VSU%g(}4(bt%{O1OGz-8yJz$xkcoG#B_DXT*?BG-Pu+J)YiAZ%py2#ZXI`-sZ`(?O3&u&idpptsJ>8i^1XRBGN*^=Ek0_pm!UXz|0I9oMry{ zr37hW(dd8gR3rqK4+P4<@lv@eXYcD2%p_smHK0Vpz`ydvNdE@|;`l(RBk?V-0{-f? zvcT^W34_)bdQ7Q*v|W?0_um358$*64bI+Qs;(L&n#o|X}4(S%5H^3*6@kb($P;m7l z5OeGvR9(Dh6O!Ih*cmA!=F;AZ#wt085A*OBnqxFsPpxQN)@lwIyOX+A|J){iz_}J| zyf@dP9h?xgO<~na)1tdRm+0{J*d5}{4^ccL%gM^QKKpm}ce%+N(Ye^oX2Zsg&hNh5 z{8{D<<)ev__X^hGwPhg_Q$OvM9aBM1diwIsdN&#A3;M59+dRHB8!rJEyt{GqNqaZw ze<EfJ69q=&AFBiCXPg1f!aDCmaa*vomH>ztA&xI+j4^ogEFf`Ir z-a8MCDZ#ALO?s?9evJ<3uZN@tKyQ$r<^u&|He5hKRPL+o!Xw{jZ&bI74tI_o$M-LTA*csNO>_~bM=(d>GtiIa$C#z3?E=3Vb z3sv(MXx^vo&k#FeT<6fT&mdwq6rXov!a7dR;r44{sE>m#=1XbA{rDEm^{tl@z@ZX7 z$EQdQIu~}KPnPDhEc0u8;Z(mWbS5Azu<*%U7DwmuNmtLF9XZMKL^- zu~X1X#K9y&ae>-XC@EWR<#|$A4p8JJSs}VHJ9{WdV0xFtU$5|~r#^VArY0!~sEL5u z{D_*pt?ZGeR(;)$?B?dx-hF$VTn2{QJY=yLHa4Fdv9aXwv2oWWTMEY*ptEFWqOQCy z7jUb7^mwK>AvHBIF`oI^=l)fmj?b|Ug-9Tt$=daNSZ-3@7Q*BDQ`ywDiQY^0Gjv3tHR3r$8oxq3D69 zZqhmk6@LVx6}_@$_;NzIcfv*##n+_HOvstvlT!6~s5p50rvy5aKQ~q`!dbuw(Dl+^ z=&R0&6Ji<8`TR8Y*Zr6Qk_U*t31NZT-d!!W92S#}xm^|Ea*r8bECy8;aBysth_?0{ zKwm|WEKHAc{@2<&4{x$M&;%D6}#ZKK>xo2+gx5DIr=q)fvtmUbdEo`!|?kIf5$pec0wgp5a zX+thcMvnA^Ub{OpnG?nV(VV}XvhF~Wya1_0{78O6uJ%aBEJqkF~i?tVCCCmxnSI-rO zIXX@;G`AeEjs=$%Hx5?rtQg}kO}-oYnDfu4D2lpS_ZSm5dWrx#0rHYo4H~CeS~p)m zvMzW;BCUDt+TMZ_xdc zp>8AtuC8pC2q&913%396gD*7j&FFedRb9=};2#=~ngL%-XU+0xmtj$Zvr247)pGDx zmM6~Sn_{GgFtB&XUz^EgqwV=6cnWdBH{7Z5)8e=`B2y{d_#XX*2}vUk`h4(CX(v`p z{Ov_JAd`6$u9mvgz|fd|>4EdD%X9@3k1by zmI6;`{bkN>jL!#e;@fhpKgXWjTo;jbDIWH?mljBg8lSc<(R0pL-BBPr{J`n7fBIg~ zm>4{VISV4v8AH~GM0k09P;Zcp#Ej(>U2d?=1RHhUV(2PK*+Dil4zb?&qhen1e++?f zZ)`CLC)>MfU}ti~n|vKK4j4x|JI7rs#;pk1PfK!{Zg20)-L*+uS?%qe-%hLD61y53 zA+B&T<8wC6nrc`LGQM6D3#7Z^b7b#X;sPIN=f|9Yd? zSgXWwwJQrcB$I|mbIU5KVBNeoaH&JLLKXwKSL|A-eOp7LZ!KZ{C)^(aSS(PC^f3)N zfEb_Medg^g_`HLrccyqXTS8yO&6(8cuX8j#PdcXLqOhF?-N{=-LNghoI^V5aY4#aI z#gv5s*MqV5Ye#dlleAkvHUo9!+=P^5<6*~zg|d1aL;Oz!9&JL5LmENVRjG_Gd_th` zBDUtzCf4f;tKgrxSc_Bk(owgu*71%{wKp=tF*;pwJ9qIZ-x9PZG6jNwovS%nSvf9O zFnORRC;PcHi6HX&Na3R{P5Q>sHnNX{e7p?;Lz_h!c zpDJ|}pNO@4?@X*RU%^>@qJ!Pso_vDyzeE5z0SdA;b}9a$tMX7{{rq|G!@}8tIByx7 zadksWnb%Hri{$~`seQ7JC(KT2JDyd3x3>`dZ^V)t#@;-$fhAnQe=p(rBw7Bu12x{X zIeje+{`Q)iwa}dXH%4NwT`+KpC79F=>&h#^1Fi>VgKNzL7bEVrpcj;GY;CQDY%)xA zh@LDr6mo#K%2`I>t9ku9Cl&Y65t@_=VYH|g% zwoG;jhfj_W57K4{KtfDyaRsSv?pi?T;rm0O$sh4a80 z*qOz?uPadD1U#E#-$QPc*w#g*;cLv|m?0HI+8Rh*iZpNjD++ zvMHt-2}!w&FuZU)fu7|U^n2-fwI`O?i)4uIk_XHwaJL`@${sre*2@FI5x7;1HLTr{ z;x#%Cw@zVux{?w$4e>(CZ2QRBcB-nwJ8aW#fCdA_m5cPe>HLQkv`=-+Rm8)IKQ#3R zs-2k?7rhdP_BOFLgN1%_@hMp7uU61PJ?+^4biD!oVvYY#I=9^~scAyg)+)(OK(3WI z_t_bLxdnZ$5@D8thM=b2$Ba4!6O5MYM=VRkIQ1^$Q=jUjQ$-YtO^>-v=k}Ie4%pZO z`0kh(MCE%*yqFoHff3*)3Q94!Q)E{U9ZsB`P}i=#dL=7cKlxR{Q1A@fagY6if_$t% zSK3pUc+!=crOPvtU=P0UN*-sqV~7@U6qrijK3@s;Yu0kCTs? ziu(%8T*dPx`6TtE%wMM3+fM>&W%k7!1Esp!g6W5|nF!vCXQI6T>R8gHuKfSTn)pM_ zCBj~FI3J1Tmtx_NZ}c$}!spexi*tLkrm zhJ9puj2^fJtrNa%H^3S_co5uQItpX-bblOaj~y|n6H->@5*s!gzeU?0i`NRn^WD|i zJ~dYQ{mW5N(4Ei`(rkICkDSV0k_cU?^-B9mdX?f>I)R%F3WmGOB*x4UG(l z5-|V7o(6w*!2z&WoPW-w+=q*V>THFI{V(#1-A7NkWL$)cv_1V9XUQJJBF!7sSycvu zKu(yRo~7dpFmQEQM3;_=O~i6uGl#2jni!wk$!*pnCFJ4r%N(Rn+^wl`v?6hyE#%GT zkBTuX^Fc|uTl5qcTp}z)fyLzz`04Mng`@S79g-c*ud?oaZ2jrHfZ6GFaOh;su6h<# z2hLz{+D=c*R^u7$cows zN7kJHkSqw0>o?s>OfzY+aL9lsIeXTqg)wv26>O2#boZJ`rL<#qs|B(9X>4py6^>eQ zlH5m7_C0&{-8({E(7AKd-3)AOz+SmX0Ky4CM&+F70by8mLHf-14Gj7%9-mCyJEnUO z@P^rEJx&UPH+CwEpb@Y7#z>KQk!sK^#hxhxYKN4dz`SsJZRBpLuiF_P0>xa;vvSeT zsgmIF7^I!LG0BTd?I*7%B{`jbavqRURAsJ8^XYPURxeM*?5Te^247LuJkZ*@ z1PRVIqtdgX$?x7X(9m%GB6hHCN7X-_`p%IGrP!A``go3Jy=#K)|~ zlJg11OwX)@k6255J=iM^lC@f63!?zo#I*iO>`^f^Z(PfnOy7JGNErLe``KgKa~ky; zgq`W>`}2!e_$;X@a(+|cf;DV@KUueujk;@!{&xaOAC4h8*Fjlf@>R8&OF3hXi_~po zSdK5WaA*HIV-;OyQHyAKaBXvl!rpGiAKtO+qI7woI`; z+X&B+_X7B%U51jjCpNWzt6>(c$17UAm`<%-pB??qB43~Y)<)``9!4Gq|96Z3jkBT* zM_5(RX`CBroZIct?l2oSrOGUNR~z~jftYHrb+sVAHAAL^PRVm@YS$D26+-aym6fq5)uz`2@Dom18~&Us}5;767p9rj5aVa z0l$|`)!6J83IoZ8wSmzwZ1)7ukVK8)n-)jajR`;p+t?BQ1gf#%D$`8g060G*O8Jog za1eEN;I>fIR=~#eWKnNjW6;2Z&%_Z!y$f0R{N=h@btQg>X?d)-4@F zn|6lyIZF_i?LJU`1JL#|AekgcQF{IQ;$e_x-IgjR&3K=4xcsvNs;5ZJzch{Dc)?^` znYz)}+g8VM97(|-Wf(%RV&AC`C463MIIE-jEXI@k0aF8}!zmkk|IXA74^d~%q^0FT zYqd`H;_UzirwffpCI>F4eoE?)cyXK<66-2RA9>^4dl$m`%0ZR~8 z;mT{a!&&2bY$D_r=T9_b;*?3#GmRc)Le!2cU$LT|C#eL}o}%(b)hK`)%G-=HE8&)J z*)(}5xt#|x23|XP^i|(>a|DobivOfe5(s8B`Px8eMUQDsLP%I6mZYYbhBDKLX#G_C7r5m)>xw*M*|UF|*I8(} zkL5mq>t9uHP1V1NcdCwbncGcCNdiX%bq@RxY;2R0mln$!%`L8~kuBKR%38Iw`peQ# zf48V(PIWDLB%P1s3mI_Xw;*nGY;4B;FH*2;?q_g*6E&k_$3K>ahytA0bZ*?dJknEW zK$Y2nfWBYtrS_aeWrnU$AWdnCQ=`s^j2NM&ScwuA4J5h~5P-`;8LhCGjk`n`e8gQ~ zYUerA!BedDO~hk2!S^m$3=Yqn!1|zO1m4ErecXSAZcydbGr68QH)cB2avtRIHPO`# z1UR|9 znEn(-yp-Lg<$1C&#>k3vZMe>JAp4@a2T&LQg5dtAR$8O*Y)>WEmn0<(IO})Arn=Ay z3Y}j8SernANM+=tpO;@(zS(~S+liF~_?C`zycA$iiHe>Pn4bq3gUemNSa8l+xc&VV ze#Duwq5|APrcF=eX0GjaTL`#$xbGbqAD@{S1$bJW@nnJy8eAXqGFYD*=bzp>DpYf z&(V>(>_3909_1nr6xm(WtMW4*TpQ8wq`_VDb)QLR{dN$aF*iipKp#8_MENgH0Ko)k z@C>qnU-sr-5S&6_J3qsq&G)g>_+Dl)r!e`B>%hz(4>0am_w#dd!o~G$oRsHkYHkL7 z@^0l7EiG5x=Z9mhBD$^WbWBzc8vDkiLRDno9lgftdK6pua{R`xYeMWMsxIP@F`JSr z>QkXz4D}xy7`{Y+sn4H1IPy?mP5nd zw%`q2+NI{_4izm`!(a)?pEw4VRHMM`+cR$-$f?tR0B130T}w!uhsR5CCb!_uz?XVD zIjIJocx{%ttz!!a)S8ekR&mh8`ttIPEp?~qh)`Do3t(el0rpc6@e#O)o8 zf>O5E^BREv^Xgi>S^3$^+Ciwi^7OG_XCvM2hdZBFKu&2>FFDptx5;rBT%UIOit)-Ws)}X4eg;LWB_51Qf~tTcvxNpnmT}`+dq^Vp zm`si!`N`^grNPTp6tO5%Kn{! z#LgKJrTd$ML@i}Az-I=)44Y{1!REtP25(|ZB;46tFUj7!*7?#j?Atp< z_F?Hjp5n6C|3lN6M?>AdVSJ2zk8DZEZj2?VkS$rqKK8MPY^g}EkgX^cVKAu3GGpI& zB1_q`W+`i>2}!c={GOTdd+u}IKRTz=i}C&5&wXFl=K?jiv~W{lbcu~s;m4Jv zq2BD?n)mcAP!O9s$te!|Bk`9%#9l&EqPtT*aS9Nwge)coqSWw+DrAzH2~ap%q~D9p?&{1uejytsdZZvgPq5jcL<&K7un_OJ;>4GZS4KLDrjPhjui75 zV!pU%h=SAg4znio8$b-r6vRwEAmVp;?&yTxWA{JO?yCV#Uu~uU0L8{*~Nf^2oH_E@vyoVcF)> z#6W3`@NV<*jL?(D`esB{kMPP{*^M-_uD67-R=(+P4NOJXL*Hi7(CM1_PrX5L(P8u2 za(?`JCFyELP1*;A{So$s{GcB zrV@~L*4WrIx}<+(>MRVRWcCp1XC4o^n^mJHf;X!p3fc=pc2Ta_5Pih{%`((gb8qp2 zX9YEW=J6hU4<>F^P?&e;$4KTEYkQa8!hs2iduwj+V8EQBuJ$=#S-b9=|sXx*zN@nlEMSk?Mdi1z*# z64Wa1k%q$c#zvQrRFKdm3=swoNFMpy1g@2cftk#`LT7&x@xo%Z({6~Pgw+b6CQhA> zTl+ueXFd5!9sG{hhl$&Luj=Xmj~xtNzFxe{@ukvjyyV=~Fv8FM-Ej!m>kkafI@rzD zuLA=764k?_-RJCYA3{g+PXp>317K9`?Ce2-o=Z9KCk?TY+Ml|&Rctm&gT8#W%()Xx z7iv7ttpbJ^h%9ewR4TsjlC7n-{TIlq7s(TOl#zB7Rt!`xa6j*FtuNYCz0wC+0#aU* z4x3=fn(RBLhkL9`l?0vvg)8>N>6l*H;Q5tfRt*vltn&BaMPlEKn6~Fw+czcO-brG^>O#b?^K=C!^mYGox#%cf!)EiG%(=Y$i~IJj$6?!Eui>Qc`-eH&Gs%|FrXdQ0WA|C0|3P^a> zYnZLyA_k}w|E?fthWlR5ORt36=W|(?6H_8AgJ)-fYLrZE;%>GU;^8nX9AFN=17lX{c5pt%hH*q{VkB?Fg>lT z6b77l-J)*;w5qj|=stqZWApcUQ zRn9F8YZgBIGmNvT5fD=me7GH* zUP^ZssLm+oJ*Vl_)9bGGur>wSVmec`|T&2d5Pm%NmyY63Qc zeiTj6Tjn*ySv(+Z+YH(}fB4GPV0mbcmTPB2_Lr}WdKy8+@-rBSEnvg`);o%4_~J%C zHd}r&kk%eTJN}$ zdvBZj%|vg_gc-eJ6{cOdGc3_EsGF+fcZG^^qltv&;b9d*&&>^lt)x_y8e{hKFR9}= zqJQUxU3Tb>cX;-fAwMEq~jAgBWW)c=L^CCQGNq(QzVwd145)#cSQ1gY?BaI!OoC0@YU zQJ3F(2t@yeyAVfgi&-$4NFGRk58)u$#m((l>_0C)*3jNaN@lg{*<`itS>B`53pTc% zxo}wm@nXBGZom;T1)hCV)8T})`~3~FA3uQewsIhB`d#RrM<jvQ8xRU$pkoUU}y)uM8g$y*v#8Oh=0-7@u72KWqe=6|J6 z3g|t`VUnm5=_>L0HEvjFWoT&8nn8gfMiFo1aW7e+-t}xxR_LgSzrE+Hnm@>fR0!C6 z@!8?c5wy&nP!PgcrxLp2RjSbRYlGWyy((ZrX}8hx5*6CD>_~O#xbjG~Wg~l0`*UpV zohHNFH--vnPOK{=#h;;=I+>Hbv{@2WuX4e_3G4{f>j zZsw@$&7s0)DXG7I)6+d+)Z80x2EF*HsYlStGXM{B?&{WG^V;4$u$Ef$^$k0C=nbN1 z-5&n7(Rl`5OyhS3AyF~QqlMbcE#t}5M^T>K3UP31cM&6k!_4LWJ_tP`+^xx18rS=; zt~6$skTiHlL5`$`21>G-$^JDGKvH|U-YR;H7fbm>5Zv|Mf7rbCQdbvmI@F^_3kQ*e zIi!rwYC0+5!NeBdRpNT^{cgGBvV@TL<>mY6Wa>|Ek7Gj{w|T$)SUdiDb!8{7=TD64 z=g(Jlb)d9Daga>9%;#bJdMuRU99C*rPFHIRmOmL%{DopsQex2O{QSc%@sw;hf11pk z>xzrs6DzSxyGUmJ_2x!Cmh$Vl|v z_;|92Czo&YaEJ)+2no@UUB_i{w^sz|qW|fv--v9xmBWk1vw%&5#Vxrj()i3pvF|8O6< z>M11-r4C7Kgs1KbYD>v(l!l#sU2#Qk$%$Lmus^rNS-y|u*<|02ftoE73R3-g!XQ|6 z{bwszbSq@0{oMmj+cVbPW&GANe6|;_U%XyfP#Ap>in$? z3@$emY=u!}Fl9gZ_gF}cF5;AjxRMA#FD#^^EW>@7%}XL_?94Ex0_Xaqsw<^Ni$Y3B z52mf{=4NTx(s~Uw@faqhr-yqEE@Csy$F;K>kS-TlZUd2wGLXM|#j;0E8lGr>DHTp> zNmr)R`^~XCP!v``aM0lB`rmb%xpMtF`by+Cp4H_4vbB;H!pC#&+`0C30x?_uF8@EG zY5KF#4FwX6Wc9vlN&|y+ZjXc+Zz5p`0hFr*+gE)wf`!}2*!ArC-FYAUc~1C>Wt5P< zh{qe|d>Onbyc91TYzl9dK6e$NEaLG{Z$Go=BO$QHdv<0mNb4B8smm|4Yg>$o z(5ja)+%`oyyeSouW7W!5caodn?evv1QWnN77tfQ+F>&q-_F61%vItJpNJ>)rDaPdF z>2RkdCt69xbo{un=;M)J1(%}QS}`)f-??Hs4gMX{YC~7Z%-HrBkII6I=2$5S7tlgt zFwm1YiZAGT)-rY_H*Xh<=`Ry1!d1ew-t6qPwUPYZWWf1v`vB0U6J~PfgRR7la&}(- zxD&>Ka_9ST|3YD(XC=4H2Zq1*J`sMba%SD#GS$x_B0he!sRddMAfyNy^Nes4{?u(? z33BG>=$L94vaQZuvR-N5D|h|&p=P*6G0e$}%vGVCwe+<<8o+)u^d}10qJUcM-ugd? zt?ksbu!MH~XyWF70RfSbfyy9)?J3jYDzz~ZX(eVo{A=FL?ZrCh%7;BT506kQkeEo> zfl#4WmtI8CFwd`?`*6TMa{XywWjDF_$#ddx`6^NGL`_mPt=znZcH#mlYJ~YIWCpY- zpienVG~a;#>~o6fMm?M-lHkud5?QmK%Iso8I=d>8@@KjvZTZ;rT4Or~G3x2vF5cm= z&~yrwfB`L=UNf)rNS^V)Hz1GX_||yxQTJ(b{K{hry$go5yyKq(1>#LMBC2LePdR%0 z?3-1|DN);wCCML|+zGjEPZ%`;gjNnIKrcLOOOgp>-6ykdMGCv%*4?h9WTk`R4STzK zF@!2HR-DgNg(}4ndX~&gX;drjQ~&$>Oa14*b;VazWorXMtg7mrJKy59fpKb_X>bvX zKJMutAs1z&E`cgm;Y-{#SbzQN3K_fw3jZc8^+85o6C}J5nV4-qp1|*lt^1Mwx5!0^ z#97G;K8mSS9wc9~q7phNR=*RYG4E3l* zmwQCOI=;f#t(yu)$L8FZLf2RdQIgi#bYz35uEeW}G~vd7`j9f!w$1R&Vc(HE-b1iJ zLlm#}8-MGSiT|2iRbfj9XL5e2Lj_|TNR;O=!p1I&#^l=K3gJi>Ip+Icpnc`CjPhuD ze*Cg|ZY6Bd`g3KDo8*y7_08hc)3;$Uo^D?Q|2g(z-1;HKHnVjs98?biXBrp3lbyI0 zl0qNiWeEi-xf|(qso{Glc1LHGCG6!w)Jq7$gl>z&jq6E(qx%m-C=actOTJdq6`coy zg|F8r)KG6DlztECsPmqS-4ZP|6C%4uVCp_)=6rNMjni9uKl5;+@=@he+i%!yqkY`A z56PeYe0kx1gUWWG?UR60KYoDA&uL+@wPE4Ou*Wzx(E*bh4guV8f?J^8@GGFTt|^~t z3sl7@i%DJ=+;CV5d$jq=*U5xJ6?=%f8D-=4VDi6GcrVN%Q|z568*Tg=!X%;FB1&@U zkN5|>nIO~r#K8FlKV+#vTK~{cw=PAThJAe%xD)pGfBLy{(ez!WzRzA-CWKJ|8ZVrs z1hGa6(C$|$TRWRLK6T=HVnqyf?+qumnBxwd?BgV>Oxc3+5)tzx8lo+B?GeQ%ATuCn z4bXvq^Ubo*?e;%}Os{0FW;eQJE#TZG`Jb~0#wg<3vf?gDc4n;kG)?VZw=Wl~1@AR% zsBif2)n}m|oUavFz=U`dYH~4eP$-{@&2>BpuppNK82k(qC*tOIu#9_LwS$JMsyJ-6C@t z+mvr6@Hy>bb7}*W7E0of<psBt=mzS4Sz7FnVYr?oBBU>??<&wS zw`LzZHm85y7-^)vv?@iLu;x?zP!2U#XB)kR15C%{DJaG`Uno5OrPpQpUW+Bc=g)QY zu8pi6TJt$biKDJWi8RlNsUk++U`e{CTaC`E>vJ}~2N@K~4WR*qNQof& zaCC?^=oc?a$i-h`aH1g}2{k=7IIL~nNjvMw>&Z)(L8B@5W5r3+#G9gfa4?ESJkIA( z5wfruHo{Qn^r4_Jo}*ga({v0L(0Rz@pQSYk*5X_e*8V21L0&G0^j1_< zGZa`+IqGy_qjxeg;^GF4m)6C4WnV<-1a5Z46E63ZQ&Vr!(%uxTY#ZIi_n9$_R{LL= z_V=(g9wOr_P|Iq8OX8b^dIgCfzO-g-h7U5IK3t;-h9%+Uk;N9UQO_3lMEHSsnqH9v zv!)kxiK40Ek3(^g4$1~QtkhB={1`_GPb+=+&`aTVZHS$a3~I$FFukKy$OZK>L{j2H z@0NJEwEl$6v3-0eWy%CuyIsMO{9XuQmD$(Ft}aZACIJD-K#C#(1^@hG6ZK8S9B0Ka zqg8Pg%1^Do9Pj;eC<=v&-i68>yBu{NB(!gRA}9QG;^JCbKs4+%^3Y={#;}x>x;hEV zzs+a1o7Ar$y!Al!{yuI!lfw-Py2J4H`^6)Hiwqy8B-Y3R1H!dg7id^dEz>?(+g!fZ zi9773RQbL7pw`S!M2I$)EiQMxaW*}PVcqi9WQ}v@OjAw8tCWo1Vrx9vvkK9zwN1RT z3%nNN?_ZvH(DR$ZbZZsI;MF_VUF;$`n~B@Ze;C}m#6PgXF*#A2SFQ5O`({0URTGa} z-mfmfR@HP=Fn$wDLbhl%hT>Si)cLIcT6s@CH9b8A?lnJtWMw`55#^g4r>5CFj~9-T z3GHi|8Gz9@#C8X1eO|{aoSu>(P42i!R&YUBv}CC3Utx-`z$px5kz^zVpg{H;@C0=# zfa-af_xj*)cg3vhQS|Y!B-@>Y?YmV5EtNG$+??Lrt@u25Np~cr(56%^&$i9Vz}}I> zZ+PUTqH2h^zd(HdWcp~)6ifSm>Bupi!`6K7N9?9oGANpenl1+5L<7gjst#S*MLx$I zx0-T5-k=yWRQIw&M=2kv6QN3)VPViM9`OXG9k1y^*P3>o&JVLKXk%1xoYwpb2s z17Tb0S%P~z)b@Ypz~FrKOzENy6p*sk=}*gUCvDpv6!OPz?w8k?R+lhTyPa_zkkzw7 zCfS|$unw@sIEv3AibP)uQpSD!D#q3Vc-;QCXQd)^%4eU`Hkw`Dc-P7P{StlsHyyot z^6-ZtCPUkkB=n31O}xtxVyci|*SN)pgtYiE@Y?A&D>2OBK>q(ZSwt~N6D#bpd!s7J z=-uI7}d9BP2mUBfWj z%!dQB^!aFE$McePFN!{Uqe0$*dmB|M`o?NucA74JtP*@iAWinA<4c;-&Llk@^_TCm z@DH*OK^dqZ3k=1>U(P-IH6^Q%gshTHW1cj65w3?g&XO`SF!viSBg`7+)V zfI{6L7Z1|diu%K%cH;pk`8ZicSHB+jma1`3wLfCD$$W7+Z3xY3cEekUW9V*jGPeJgo-pLeKB6M-aPT4c=D76 zv&M_o%HjW1f7H3sKW?K#(D=BT^Y;vVUMpgwWBHP4X%4G?1ru4eyVu*IYYla_9yhW- z@6mk2RFUsDjqap3fs;@>3kB1<$yzsNV-$?i_wQ79|NDE?Gz=S?o2%cOseiR*?DJVK zF_CB|KlTyp1Z3SvjFfWVS3-i@yp_{2^qH9XYi;@X|29<2V2gEC zRg!t}`z8PUpE~h!;9lr)CQDHvCX%b9JwlK5<~#}YKArg?Uw+X!QR2oPO1cBHTcK>a z_~)=BuFPG!Je)THqCJ`9whCLhq#HM=9d3%K%0MvEMGXhU^NVpF{oK!7 zFUm`|^s`mu+dE9!eu>@W`c=BK+G+Q0YnlA^OmvKaAqGkfV>j4qN2cp~+e};i$J|i? z$Mi7Cd`W+eW6$^2#1+eEtGiln<%hdEfMsHPNcOFVdh? z<~%DqW>Hqf)Seh@_vxm%vz;ROc`R^uU?gWlL24%mstOSaYElsq;!VSFwvEo z8|YtO89t0C+u9LM``Sr!N%S&hg^8Qy0M54N#;*y|ny!6oBgH;<(_^2=SaRW)Asl0# zr%vu~Y%F5$Ii*9Q_H9@81EHObiGSlzEpKRlQPhO}=<&c02w($2p>w$4~z z8Kq-}oyV`U2k22gapJxF%i&XVN+_9JAE98k^TjOKB%bf?%}6KR{EFz)P9REJ!cN8_ z8~quySipCocc~Yt4a3dFebXWPvbMFJF}NaX9)WbUWTJeq*!ZO_soZ6)a-N%Ucu(w^As^RAM2=_Zj&7n7SE zdS#yAeSWe>kFmerqR?j%D=!&I6w;gIe}>Q`JGW$*h-stQ{CVLu8Twk^O^<^{ukiuZ zl_}af5XBqx0HPgxwA%Uo1JFr-&iVdbE#y7hKOuUX$JYwjPGcJN?VhmS`Z-px^@eug z2r;+!t{PS9d2)Afx}e!MXGd-PhbDm-7>A?3OS;aHlMo9?HFF$mXJN^Bq*Ds@Q;IH9 zR+AGB(foGf0ZD?w-b{VMY@?PVY#7#m{kV^M3lq|f!gl&8<=i~)|1(oqIAvxfSY3){ zLZl=~Ih~V9Og*fMXM8wV|CSM^Kr6djNa3-udN#i6EAledr_P1UYkGg^a(?+A4yFwz z$sillRy20C^sY?#I4rA(iq>@-EA6HH#0eHg=r_Vv&&*xypo1^SO7tNrS&h}m`c>=v zVFqzny378=!%s94(?k&@gt46V+aqt}2=QJ*Kfl}2Gz`^IHGmpxG4d4~m*7tcBcJ3>g^l{t3nAu^cQxBAcEo z51q}qb>5UF+I3qAE>Vf%%`0PobWyghRlJfU7suEjGum9x6j$ow#8k6vTiDbK@5gG@ zLv+JW^@crltR;PY7|LpLGE zKu|yzb+1a8XAncf^%EJ6d5D$et1|r)RFBa-eO#^jg*BPPGq4EVM?V7eH`QiNTT3bR%vbt8$*1B{f<}4Gs8#wk#$u(p{BEXy&EkRtitW`*0);` zO&5zh%Q5rb3cN;!{h#DgAu(DwbWOs{mF3x6m*@1Wk{HEQ&1p|%G;tDRvVdYPoJHrj5EKZb5q31y<8 z{AMf~>?&+gxtm^|{1Hwm`^QarVt+QDG+NeWz0Nm0INBe|&NT!(%EiUh)S9-8huo62 z7S*53YTQ~qC5yVpmEVd)vgE|cR_Ml6CkGfTO8$;`mKK|ngPnTMsG(KkIhqr*@!mj$UuJN&IVAWfs6fnJ zNiV#64K{}F7g$U0NWzP$ha-3E8Y=?jRp2^#xrpo_b|HMT#5Nr*BY5?wbRm->bXY|e zx3%ntSm7-dYqgdi+|uX``e&7`qoVMBtVU~ zbM66;jL{`f#Rnh6IV3=>yEfZ^C(Z`Z{Kl&Olk^6bq?9#$a|%;J3PmJE3^go7ekZ;= zh3rL_QW5(<4g%(U9<+aFe`@J@L|w?24a)gK{5dafFYci2$pqs@k|CMq8k(wZ8!^^h zlwd6y4x!-B;I`%CZt0eEz^6{Qe0LvMtFECm1dl_m95_fF^!f4W%(t!O#KdTZrF?(u zM_J2k(-XrwMXu+%GQuK?Pq{UzPSPQMM~ZDZ?<#IuRDDv)VQ{a%Ux=?89G)1Jjk@vm zdWUcD?tZzb=0s(5mNLd%LEPLP<>ok9C|~^tCtk|Mu3dhB6$+0lIe(7WRvhgbfxSw1 z_YjL19Gm`cIQ^AeJlr}7QH?63eExzky@gbxvQ0o@Xw}Hbh?&z+!8m9}6Csq8XqnGg zT`Maww?q{gwmj^rX8@TF>MhUppH0yoJoxrJM1Bn2`gV6yN~ z6QcNk_|KhE!i1OcI9NvC`^k@sLSjf)p=sv4H6K#HO8$SIGfn&nHw-lI#2A(q5@A#n zCajzBldgOJ`t2mo0reyp$D)dak$Y0-Ti*Y85PrAWHXmXh2`Ebk>JC4^z)pF0Zo0vjmvGu8`6;p;4M=>A^Wp;#2FvA~-JbC0HT3Xh{!t2XOq*MdKv8QN#7ew&3(037IN zr$4ko2o~ieIx$H}ss6sC18p$V-e~@DWA%%REZ6fFfVHut~0drp@#EJRJ8h-_om#O6t;%}IXW zDRFM@EqQ@>ZmNBebFozt278dXf)lMe!861@4~+{@7k}0%y>r>(r_u zZ460@l@oQ(Soli&C3n>|I$cz0$@Oi^`bz}|0Vr0#VDWy9*u@Z=AtvRd7@e-uP2I(v zy|ARF^cYv+o$l8hU;fTEw4r&l5)o*4@o5VDT!4*}8O@s;MYj$c?&GjJrz&_XO>tsQ zyNVEb1#u978fGYJ{^++KEJ;WMmIPLem=a45amUMrqv%R9dNjkmf#?tN(WmtoJO+%SdiD}EE#pG25Jy?}@ouquOPOa4MIY5Z|Js)f?^Anuedbe@z2 zAzfrA0lOF_Yeb*JsV|*kAEe@F9z-Yz6_;aKa4<>mPeBsTim(jIlRS_bS}WuZvtBu% zE^RCJNQmP(EZM%4Tk3XOya1y?eFL?PN7#Q5SJ@XW;kdgxk5aue+GKYeZA5j#Awe9a zYlpoOOq#}7MTSYPu5!H>3vm8Umrbu+-wl|&;$2;Xt0>;7Qc|5rXcWbs+W0xHb<5Sd zRh%=Bkt%*A8s&wBKs`*+#@90p!#*{4X3L$IEWopDbaXgXW1}IyMjD2u z0PAZ`PB%CHOPgcGleOVY=YXSUjIl1zyy7`MqxY~7oWXs{ik0_-*U&OfIF|ASBmcQS z)LG|XJ^I3UM^R$y*#n#O{>+>z_kxOb0Puby~19Z50u zWF(M_Y6j`rBOa$%bwW@Lc-Cdn>!;ys8 zUda_3Ib^L$$%c_tBgW3dLm{Mw=NKKP^C)MNK^rxz$TB!#9SL{F+X>;7h4Dd24$S*1 zu$vg^6mD|i7A5yAyU0~;`ww1ft6uG;?9E%J2Ao+qoLDj*8tlGWyL6QPB^!LMSUI{q zlWupJ?do7y<(#s}42iH!=v|V9gY1ZSvL2zwVu{;1mVK|UM3Hg(-ZtIz4v#OPJZGpq z4&JVYb{v!a8t8lbT9uk`eY|jd<7~=_eLak*?5al4yeH*QJk4_~c27)50_8Qp$HWL1 zuZ+Vap4SfN?SUr;?M>1{E)YI%<`v$PFVCSN9^R3j3avHiQIi%3l+pHLx%VBdUI-bb z6uc7aZScBB{g#`!88YK>hmyd(iJBXOvW|{t70c`&aud=8kT3tnp)+R>x4wL+Dl zZr@Qo<#cwq>^*voZm2>+s9p7OX4OcAXza!wNVpH#@4Et;%-q~$&Wz6<)rx5}a=Mnt z3K#Qy2$9enHjL~h!*|30_=7#A87xMtkp1v(Tbt1N3Qc-(vei4G`;)UrTUS_iz@JR$ zJ4&1h5T?8qu2)dR|2=jrH^M-nijP%JH!o||Enxmyr-ZblB&UXTT%#&RnlRw;z2I=t zb43a=ekX2CsIfT+i_W$AZgF=~00etd+=xIs5KW{88ed=lJGAsTHcle1`DPz45cM1y zx1<)?cyaqRSBb)VR(ky$u={TsE8;Qtm0xBfb?4OgmHRx3V4Mu??z0m=Mgx!3#_|N> z2O2uD)umB*rS`o#mrTz^_ZteTW%oH)_RC8$60a5~PWPhwKmB*vdHLXm!+-EQydPzl z`Ce(C;9SEH_{IDoh=~ac%ktz4ACIIV-sK@)V;(|5sy-8n_vpUth8!r0Q!(xu>)Tp} zI9jyawXCyEkBKKmC%#N4Db}SAQL0KZ4(FBqr+lK4u+y{&zDr;Q4PF%t;3CM=+kKhV7IWHnpNx9Rqv~Os^wu4Ts~A@tYD{5hRq|q% zw`6KjZ9F|zZl;of--=51V^Hm116}Y9yx4H7ry>R2ky;bZ(AyHBWbB+;YiH3>e{g6+ zmxdG;wAM=P3v7}l3@?zk{>r@wL!!VrJT|;NgfTZuK1kXPjq-yjhQ8 zbO)b4E%Kkge*H|uK*5^i78k1kZBlzX04O7r^SdrYLj8r*kZl6yexud2jZ*^VOq(*b zwaN;w2IUi=SUfEnZ(ZLLPmkViP!C7eI z>Jq>nz1HdXHi`Z@+tc`2?bLT48Vtg9K34i&F6SLbs%L$9>>87i7hPo!mgPnxwy2lm z#S%R2JFf$;B(xXEzr;t`2+5+}UteO1uUYxpF#Zm|Gkv6ASmZw-b15N9z<+xf$uHdA z`hCk=tyI+~tBP%5OcGY$HC~`M#s9)=)Bsf9?|Z@e7Y#s>;U5s{=zUkd+%>f9Tir9F z-~W+ zbYAC*Q0MuV*Lj|r%`$)5saf@&o6Ld^D@}s3X|yNaiWyF*;!O**3WUr-K`o^R(p^EC z@6LTPWuE{QB-VD_)a zj`pmsQ|a$VXNYx-YGt=V0IZVWaA{k6+i^{fFJn{JF~C?)m-~td&QbF;Q7$deF~m$K zobmN_M)H-J>{S6u!~C|2iq7(gDHJB(iX^ZWea}P~X67MH>|5OjoYbUbh7V0=WS^eP zhfxm=w<-z`4VQnI=+-b}L(#?hh7ZV64*NEG8I`dmC9F|seK!_}aVRdf#9M8pBo1)G z=f&5y-Ii#am&qI`r;$lDu-`(^yHolRCe_-J|k`Ie!i~$v%m8yC1#Z8NB zdcV_Gd`=Il(@Tch-KulDR#C3)9NWR$a$-e#NR-)br@6mcULYKIpDnjVhVW_Dbb5lM;Qjt(Nr*nt%q92iVyh2vhC!WSOf zU={v*y|*rjsndY@+LX44Uhw>>i3f^)=&KwpM0lR#trf%k#|oq%rKpvG!fV<68Pl(k1hY^a1(Amq`=wDkYsB2{73PajlDyW zn(iPIi?RDR!50nV*gLc*{Xinv<=Vdzg3MDgLyb z`8!+(dpaq8$$~}8&=QkJnxjJXhDr`&X{O2)+^Wpra^*d(dY<`Q^XNT8E6MjH*O-Rn= zi#psC{Jh(rF{5)sLr*ASVJulE1&1 z@O}UiOR_;y>Qv$#+RbB3u^t_U4W8fe_RCv~B0uF5EwE48{~6;OQbnFTGky;~e>Z3e z#UAS%$A?vvf|5SYoR(ZeAbJ?EWLM20Ukb4eLFn~k%U0V-FqlbsltTyx(y}Zvcr``1)n+C+{yy~%0F-l6rG>I4I z?zwJleP=uo3GZNIZcUzQ+8LB zKCg~Ubk!7sa4~>Kmb9ICw#l2>0&@@~y*sbww}I0G(t(+{L|pM_1^ETPA3Zj7)fkIX zHW|;^qj>F!YI0UqtVSD=Y&pirb?Nt4x3|F@Kt%}-tGbY68c~X>AuJ>;)^l+h|8`t? zK<|ic0@kamF14c!cURIGd|0SNibmsO=}NLR>)DogDv9D__L{ z7qOptk{@_PO20m)I>{~gwmVPsJ8HHmRz@k~N%6xZs*+!nDn6#6l-1CrV?Yd-b1j@X&_u{m#q4jZk{*gD5# zeB-84S3~>bLzALn&S-pycbe_a)%Fn{cl+%x&Oo)l!t}9owjn7F1jR@xSXi)Q=hy-W zWj04&1C=O)8kd@ZiV%}2T$;_^pB^-w%U>O#A-i>uwfdq%8hVp4^^(ijDMvzfRE|E2 z9xIji*1DSrn?Z(795y_@mU!Gt44OoBTcU|WergiO_3F^Q$3=Gj#4nC1gOp4AZa*D8 z8bcBSe>)MgYf2NIxL`uf%60K3ftt+Q#_3)i2&!!1zV&=Xzw5bUCv!GAnOr&q3K$TrQ^S^G29~eI=muojfAoTSS&^KwqtzDl#fA}zM zcMH7rI3x(Hy}%}yzQx~kKS8t2$p%qSsILzQKpJW0i@vxIX_Ym0Qt+u|fLmGowAPDu z-PEsQ0(Xc4aEEB_jGnTvuo6o;CloJo8J0Blyl3y=jPy7!FQ7FBDbaSczTj-@?+c)1ohV59gRKmr)UiWi zn^Tz<5PSQ-e|dRZc2=d|%l}uj@KuUapj$jLLMK4@=VQ9K`$_MLDR4gL%{vC`;yV)L zc(I*pbUF%hGuY)iS^QuAOyV+7=2RUi!lfPkCh{-)M7jzmD;udb65tRz^Mnjsut65& zf8T(Iix6PpomR2%UVk9rx-}_dfw7TfCeQ845H0f>CWOU0smX&{_o8^^34#T)UJJk@OgU7qz!N9u!81H^UT`8 z$0D`&Rnr|a7a2^aJx0I+Lx_Tg(fdo8&m6UXOMUX}`AFy><1na#qw#LPK2VHm3^z`F zn0-yR|6J+EX4JT#eG4J)omYwGq5Q#z?|e+u#I*5tx^E{DxEgnKf{dTc69_~=8u*;S ztAe+W7*Ycv0UW4Jbb)z?5LWf#ES&ZPY^)+)l;cSUvV_2C0oV&bI24!lS`0Snura5P zFOVcu*t_oOpSPjhQn%{LzN@#mVtV1p)(6z)=EiF7q@*fn1@UOKJclBM*b><3lm|dZ zI3o2X&Cd2sns*R=PA-AZ3ATDy?sT;4j_n>DsolJ3VF8$&2?=Rw34*jbU`Ax7s0al@ z359z3DcFxty&NvvIFtgGye^D88549o z%^JnUUS9b%NHo44GwRhb9EkC4^=7=)8!}6E-NVBqetCbY!gXn^?=_i`(PD_v%79}W zg{EG(c|HZu`}5>gKXP@~03s4SJ>kq@cM60auIKB&ySjC+6z+9IFMf_Br>%C(BVnXJ z%G-dsr=Q~asGdljU3C3bu%%jk+k{hU@hV-5U>)xYzb>Ue>>QXDq&0K)!UeRZtX*h< zPFAOxf;cCPcK2Z_g*IXkx4BN7?rTV#e&Y07>Le#th>oo5S@aK&Dj_oDky5eIGSzYw<6V06E92KQ%s}jJ#1lomZs9$y*@S7sjy;E+UI_3716lO zMfn>%h)&#c(Y7F8C|I8_{&P}zpFAM=aUZ?qHaUDv#YpVV#sE;Qs26V+>?4P4K%F)I zg9KIZh&~|_?m|{SfLBT0=ZWJ^07ahI4+jC5jDg+-5?-AjRJgWU%a}i&`0+V7P7SC? zSSyplmp9qI5t*0pn%qynNu1iD$7mQI^EtvN_xyRCEmFG(0F=mQ|46iE%9Cxc8gVP= zeje^zC~+bx`gf88aHB;hDV+^c05fwckTK`VIEw>Cxl_97u+)qR{a0|7?E%{6{jgdB zVf(Xe{l3DS4h&%WG;$N*){&a=E}sL|Z{E0J%QINvy8bIZemKGw2UWj*_6idtWr=;0 zuds-g?9s+{vPj#_i-WmFge?&r8Ch_C3$J;3K7s&kBl)@(^ol|GzG7FAnoYhv(rd z*L1_#1CLz{Y>-Hlrw18WVi5A8pnyulCF2;DhusO)j#j1YT7z5T$7Az(jqyU(6+LN} z-4Ar2+{FV^=X7<^?H3f*yC|=-vgtZvpCbx%z^e!xwl*PQ>~Nr&llUz2x!fE@=mmy* z$HoE_=88x?KHrJqCEUz_au8zfYQR+AG~w8NoEDoIBiAUaPk(at?Ze*pHimHk`ihJ(?hMA`0-He z3h%7)`=B~b7X!^1wsa^$PDZ#_bT25gCBZh&1zZaXuE_bsZQt-90FtzM$)xNPJM_ZC zk&$Q3M&h!1#4P-B08XETZ z178n7(@cpkTUPuX@w~)s60duDt#&35_-}<*`EIO zmJAykWb=1Gke+%PINY?rebA90x#wY{t5E>=C7bP(s*)%+_6S${ zz^te5s>!2^GxCwv@1^+OqD0`Rm*4P`=Og~PPoDi8cwo3ryF{l~FXIvH`f=?@D}ceY z-Z;~WIJtRK$0tWj&e0l`{=R%HX*00UztBI@xa7U_35-D8R52# zcURlmPf~s4^t5N$_zbW^NI~>JGPWe3K`+ zyUm*@B{su^TF1R&$9bhW_`Q?~w=Zlq8Jy95*&I1m)51bnzG#Eg_z_QjVLSmnN#@{? zuO2VbA0H3U0-ls0h4Bc`%#j1C-ReoKHAO)+gAod_0uNJFRenXq%Uiz;GOR0e41zY| zV7${)5i*;xp`XAr`f}Ob?=PflJOnqh-dtu}7+W6Fe)k`{=|7+x>QB33L|1iH9^%~5 z_^&PQd>CrF!nM!>iF0wvOxdBPpr9Bjm8RTEK!65$l4!NDJ6i1A_>6 zDIQOjnu^3{wmM!u8{=wSF|pMoq-WvPYU1~z{NekmDl9fnXD^g40)lRNmBx6mHCfvH zca|C0LmL|EY3c2mnRPRSz04~LSdmqY&Nx4+5mvR*>Ob@@4Vogi zT4hNq&dF;XRg`MBS@#kq1MG%S`Iswsl zlgX_+i?#x~KgQ;~)My@RWse}$aL7VQNlVEzHTbs3OX=X1-in*m5Bb(+;?%9M9H-U4 zRo!C0wqNT%d-S_7cz^gaZXD2mve2wTj8u!kL(Wb1jWZRlxrPUlZUx-FiMJ*#Uh$!w zJ0pn-QhvV_0$jVS?5V?!W@u<+F`W&2O+h&4f&9xk`HV;o=E{)%ooIqwORc~X6z8|v zPj^yhD|$FZMw$2bw*St)3tc(70me%N)zI_w1eY_b#6Z{Ig>94eOxEv^I8Il;_)&4| z4;Iy~5{x@y7hh4)kbpyV3o+I+AFHzl4aPxH+cw`j?BBfF;+;9EUK@Gv!Dc*@4DLGi z87*7dy=H!UQJ51}FRuK-jF%Mdf>}7PPphyZ9U8j3->cci47@zgYGPw+YYQa+DFPTi zLzx+Pz?!6T0NQ*|;lDDVN)B=zZ}YH1t8R%~`<~$aYRt;O)72F@Jy>Y@Rz*1w5*hjaeP-s3ABOwP3>(}2{?sZ* z4|dn2-$SZUqPc;aSp835toQ{5FI`fw<{qzhKd<2QMu}ElVbRa?Qki?aoC? zuj_=|D*aa}3!81P#=3P`Rl3*S_J8QiwO0m2k?!u%(XVGfOSHDOL2-K;fp{L11b4Lq zFNn>J#Ew|SG%js)Ts~-JxMOHOVsrJ_Qz8p zvO=@ry%5AxPEG5{h{VLCO@UQ(OcT%{+y}-8k2m^lSl7rRS2fLI429<7YpeoS|GNd; z16MOhCizoP(5?*EC91fUM7Y3MUoo@D^<#ZRDXl!-Ys?u041D@M;TcwBMov8KbEIhB zb0_r4kIuivb=R%vDB>w4rneV^Kd-N)qkpn}l+mAl%(@}&2sh$K)9Y;U7h3wR-6){; z3&nCMQM7v(OngOHRrH)w$NDr^+q$PVx;0-BTDD0?<{InF&R0-q*2{?Vy`^(9DY`Q~ zhvwCb{mK5)x6ndSH3*vf#z>{!=Q_&hl{ADd@~6A=u6^Rv^o{(45KPHNJYQ}bg(m=B1sMI(Jy-)w84w9w zVEH$n^XH$h$aaYJLYx``>g5Ww3r)ZO<$QR}bO%ay75X2=9|+W+@-N*~dy{6$|ERhO zu&B0ZJu`F&NQ+7iHKYm(2oi#%(kUsRps0u}KQQcuABU>on0=O6y)!QtA!XDKuZIuMD}4 zH6EuivlO-{6d5Q_f1oC{4BN*yANT&nn%!HEW)&ZueWe;kN3?$D7yF~KtLFrrAFbn- z2d>_q(n`Ud{+)-3@GJ(66W@bdMQ9>*hWs(W z@G0<+@mdH!=U039=-F1OTPp|ju(oeL@{n8R(y|%UW?VFHs)|wcOJ}xCF zh=e342&m>?efK$q3b<^Lkr4;iJ(?ewNaiw>Jvi9)C)(XmSF&>{p_>AJn?RZ>+w$3$ zFWi0^gY)a8j}2^QCy1rapO7bPdLPL%W>0n%P;@$15@4&n~`IgQ*srCF*tdLAM@tKfNUYmDTwTh2Z zJvF=|EzYzI+r*jB6cG}`gi(~h z{u30D21k|9^ct}|=$OSA+T;1+_{|6`m9fVpWvB1t@4NKEj`DwH*YUAbc-dOp#E+r1 zAo{3&dj52MuauQMH+;VE-Nnr_CCl;rGu`_uW98qtZZNU&Q~G}yl2&FD>6Nk|_vNHQeD!Lzh3(A+ps&85CvcCtuAGcLr8+Jq{jjAM-2Hr%{BR%~*hWP=zu-t%8xUB!r6 zrJio;X9|u;mC}6)3gl$*dOM7$IqSJM#dH4~w|Bc_PqaRl0^PYKVBK# z^JqIZZ|Tb4C*s15>1V7m-ufD#<9)ndcII>A#1zN5!-{RP<#w_nZ}8i@_~F%5o!H`wn`sEK zk9rFM!i=qF2ePjw+#z9K@%%J@RIH}PmE?ntoy2o3tTmA3Y>Cj-UHGAy)SM!tK9G0i zO6F6#x1Q^TrWTbL5)xpq!5@_*xX=$E66wc^bFN^-tMSSDJQ1T^dg_h)(=`V>KR@U- zT>|zg1qM&)yyW=(=_t4AbxL(w@XfunAhjUgYuDlfK_Q9T%$>ho9~u zs;OC@CnF>BtZq|g^1bv@YN0W@>)csM@jvQj{?+1~bzHVQB`2tWl_ zqQ9Q%;tyP1@7;4H!Q-*@F@Z{;zVqGNTT7KY|4?mua)#V#cn=KudlSj7eZJon&c^$O zj3S6pVg)+UZ<2+CW@~6sKI)#{`eqAt;YlQ%1gg^z(9VRIbuzxt1&tTtN}BVlV%a$y z5j%8}RAAyM%c>IzH=bmQD=TzTrOh|UYQIF!90^eCKi^JSx5Be9Z)@dvDe21OC?Z`H zV}jJ4*tD)DVag=fD+Vdmxsf@>qFZsDM2A&HZC;9cdx6O$`Jp^<)s%{!pwIepaz38o z&b387qoC~E+Ooootkg}Hz@}T@t2sKa%UJzjkM&D*YYn>OwIF-xE8nlA zP}U{i`XV`v$??{&5v<)?m{?KR@g{hpjtO`c#Y)A2G#uVF9E_#SxtGvv!RChcLG0*t z4I-*!4*R!@zOcw0YwLYe;*}Al!fKzp_#GzV)*%)hRKTkn;seGl`>W)=?9=tjDFu0E z^yCysQV033J~{hgz6wcmN5#3hQb~Im1v0}PQf5WzsB6CSwx6SS`wQj2zLf4UrGSG3 z&{+Xy5|kNr?6GYd$lk`^Kv|`2l2&}dMgeJpl6mi}zq0b(k-Sok}vRv>FEK*ob2{QAO(jv zs_^ROz*TKow6aP#Te;0+a>lC7Vc@v2D_U&df#;3kC(6za3W}7cf#xg#!1)(4(fJ)o zR8%A5w)p4!xa{HnQnsf2!MB|~UaoFyDK{FmD_7LjFO%%;Woh{J^+Ctb6(3o-#{tR7 zV3ejg%wvoMXt0Y}tgaD#bi4awV9+=}RDu%wSxlCV13mXO5=Rc4G+Xy;g;BY*&OWvyH zgu^#+DJd+mn!39B2Un_@p5M>-ch-q$PHZjt`W~3dq%Ho+_6xE1>;7)q{s8vKQI8hB zQC-bjUs1;BFkI#Mb=f((Zh8?4nynp;_#&(|`Mkv~u3k$N06dzimb*Tk^$0kjNa3<- z8OOL>K3GtaNk66LYz~>Ise~%?@`x<4Z2LH{%OI14YnuT!bKC^m2xpn}dogn8)jL7z z&#jWBVUyy9w?TR79;0b!Z!p=g$(yg0|K{0GKq>~q+`^-aqT&1^B5J&$hV(Sq#=>C8 z>{S-)Ezo&$$&0}8iFjpRHJ0hs3%e>JgjJd3Yq&C<}xkCUt~9Zr`K?T!nzh|JcvpQNIGI3AY`;_ zUK+)(jJ$!`f~EJorLIr|uuI8%#XJ`0lcb7T_78F|ul#%uwm z`DvKq9qi7<^hx(b^pqs6x23OHg%R`I^BsTPuC}lH+MDvfIJO3B57vu(_ciUJ^txJM z#mF<$yUnI#J$^?mV@a;dy-)C}OnjG$^W*Lng16eO0tJSH%E|94}ccj7* zb*BhlQo=yRI%V@u3P1CP0n!`fJDsQ|Rbl11j>o+u=)*YAIfhhZn8CW2}dndEj@6;b_k*TA6+YDYBBhDbBpxjTIVQmg_3z)!awv$~p<8nHgF*)Sng^!fRE zyAYmE+=+F0=4Hxx`88@_`^?D5LbkVAt9R}5@5s$R-%I&NXQZrw87I;77hpqXhr0MQ zf^}?)Z~e*V&N$rhwtU0a%+owywDV$#RRuq#_k(HG*AG&TkalR@$`39(TeP|yMejVb zmYE$HDRK03dwZ*zHj9$Y!*`4oF3!#_E)R%LpUEDqt^!4voJ!Li*h9fE1P)i0OOMA1 z3to&9_FOPjD4}cXvzwr}8!MoS$*xkB3BNdr;gw$bDTdgpT25AXt>Q#LlXky*iH#&o z6AoZdV0v%ExZB;|)47z>^pwttz{T@eDLrppOSILm^p%OkQ&zs!p5UyfTVzQ@idvfj z4c^L!hClBOKYFhk>9g-gLPf*=v$W;F^XSo{IOx<4>n)klF zDbyKyy=$?zNoFiHNG)eaDK7f27Z4|E%XpmpFn2rxw8pp{^Jxv4b4x%-ZRGI4O}E}1z(_dhoc6d7+#}vHF$b&e8k9p# zuXpw~rSr&q&EO-kQSl!(?;4#@rKgUUZaKLk5YF1-FldJSjY*8WHQ++(Ya}m{0SKoYyFLE z&!gX<^|=tw#r*V+FJIc)I!c+-`)=BVYoJN+?Vqp5g$O^%ZP3fc<5qin{bpto6FU;n zFHhm}ub!)Oc;IrH&Hqxk(nUj@O^QZRQ%}!@3k-ay%P%&T?w%!LXV)X)Sa(U1S_xuF z^`fcpbe;b4ei&nJ@#s;S*O%2TJl*vC3w+6)lcQ5p^YfD=C(gg~J!}s5E4XFyZKzy9 zH@fel%a3=!O?iR=*vZcio;efLgfq=Q>m)!j=d4zTc#@3@D6@dL+-AcK;L|JU0R#6(Pi$^=YeXP{{1CI5%$_@Vq$kejBbvAo@+PP zRZV_+nYb3RY&7#ZVHSycKCEy~f7Gl~H` zhWXC44*RM)F4NIaJSb1gjlo)tr+gcdHBv(JN1dMai7LiRnUKzM* z@$v5h)rs>DXS!#<){WMi-L~dj{vF8|Q_UV6KTxn?Qa+b-<5C3i!zF+!Em@AX@yBTZ z^iVMGo~^O*=uhdq)j7x#j)oFbQmU!(`FfjrlM|`;qm-2DYX4^p@9%cCqN=Lr2k-u1 zUS4??+0{uB!Ew0o{>%-(5g)ImYL!sG#a|?A&Cpz9F*d;zCqKN9NOddCYdtVuk`vR~)-VNboDUTV$RvUlF zKLH2bmX^?V&-|YdCqP~X3Nr(}-u=*l+N(1Mzei_BXLTNrH#Q5~#-E}R-#i17Xqn2V zUM#*=Qg6?XxE0a;Hj_$QKuW4jpXx;>SACFQHGcZu-X-D|lF=(b?BKab~Km^=6s;^vpv^xC*l{X>scy;96v@XfJf@uTSa1Sx_bFQtzSyR zKc14#Ncbh(pl?4jB9xC!vB6>HNIr~~lB9`jX=zQggdYbuztj&3M?*y-W|;#7%R_xO zmA)~V99MTkRsQ_WcU|kwOv%szVS1xkts}?29VBt79Sdi)el@iUlhy>U1%NuMc0WFf z(-Ze35Fp_dr$+w>uX{T?N8#w^`(pxMLq1TK!avGtxwFv)V*rMy*L zla3He$@sy^opqRj-FH)+j^|HQ zSMzT!7H`uC$7KnKt5KYfbytm-iZ?Qdk2n2n#iN~m?I!;!aRu(USZngdT(_>UV-WL% zOPB$f(bhUhW_|KDXxvy4j!gXcowvrP)OXMOLXi7(laHgR0d~DO2X#jJVxOx(&vW|3 z+<>@6vGTjJT)oFc7*?DHOBNSQ*kkJ)#2Hzx`lN}OCrat&5gWZ8sSqDda5eYFqRUUl)eOy(*P~lNg5u|QREmE7JpSQyglrfQ zKt1j4uVa98Q;_6sO3+$dn_8a-B3Ed%zrO-t?oi11RKhlu#|jCxi@(}k4*tE5o%gR*Uv^M<&F zL(D~>V)mUs`P>{cV_MW^Ko+HU$w0wv8tuE)u7AEA?AQIBB_jR1L@R71+GHrw@@`ZA zB#@)WZdwsXCwpy|^oC;_SB6KU`s&fjWB|CwfJT*wXv0 z@8N|<%76>6mLk41=|H_Us#DKI5tQ|~mCGhd*sLuij$YV-T_Y}BqN^4_8er2OHEx2Ff^h+0yMh_YH3H+a;-HH|R4#sqi?8 zlRuXR zA$4k12a&VPY`Bl8F->*V=5TUMww*~7OjjPTD8u^W~E~turxk5J)#F zFt{lRgh4?oVuVKaz(Jn?*RIf-SAh4~fYHzPfcGmsNmj<@8oZyY%B?0~F!vo-W z8ZT1$Alh@w#F#l`o_}9oAS*=8Gqr(Sucpl4SiVm1Gb;@Z&z+R>b(!ngRBeC#5A~job9DrhDLjwp$FB zR=hj7@O3kg_^DU+?H>tNsTWR4xjT=43MOy!P|o+0NWyYicmk<=El)Y9$JuLJ`k~Cg zv!;x3dE?=IeE|S0zZMlCndN-EVXi?)RoX8@+LwJ!O?y|u2jr4#z6J|i@X@y+B#4n(&#J=m&kI;Jcukj3S1df>GZyX<=l zkln3CCEvhHOT2-idfYxd2)koV?siF8PS2RPg}?H-JO6}zxMuAlC9IK1z*IlZKMSyj|vh~e~PLpH#@ zsS+iYa|Ru`FE;Rz43(CE$$sL?B_Uks7#suljP>`?&FnmzE3@^(|y>(C2a=6h>#?-cNC{^+{B4K%AR z**c3muh0GYzA&UNa+H#)gPEPzMn}}$jAO(OzoCt>Eig)Z#7BYGE7~qFzR?vwb*SV} zcERegqNnhNc8jed-oxZ)&j?7OqRYOwcW!-E;XNmL1vS82z@?s0bVDttu=;uqUpJ zhq2tK5)iIQdjwmlGDBm+(5&!~?@5O`wWWkYZ0k;Pp-gDe3KPb|ur>VL4()ZUfT zD{-%|=ayw|8q>t@MI%RAAqO`e1`pVMd zPHA@UTOWQE^a!LRNHD)?-Hu+rC(LkEB+|b2lWNVA_1~4{@9h&q0?k%*=2IcoH(RY5 zc3As4+%x1}0rV=UwkrQcE4`tkL`@OV=i--&$jx-B1f1gi7Ol_A>mJo4L=hv&x< z7YOtixr(Vt50MO!sGX(UL!rzW)s+L^TvR;5ba!HxbT0WYC&Pp@^NB6=A_S zK3W{pZ{$x0n$~bW2fL5yukDlL@o)F;R9vnt4^fktQ!oE z7SL`jW7#Uc!7Jn|%yN2?xpXx|i(XXFR@d^GdR#(T)xC@KqPDi#?P|WhzK3-R(c80A zAm(s^$}fAY#o1JHxus0Z?smgQEcCT!`Yt{`s1cxRt>ywMz%EFI5WG;}y`qhBIX?vDKRlJDebNqF0)lLc2m2nmjbG@asLdF`X70 z5SxE1by%XhB%6ODpXeXy7=J<*9;;}k9%DUA5O^V@4QMW~YXiogAh-(+X@PkAXdRjp z>U7JRe~K2+eS%vrxTP3e#RB@O@xxYzn*oK3n5@pFlbcb1dimkJLngOX?-2t_^)`mY zn=+#Qxwt34(1>F z4j&mDP-XkA%><&)ebkg=bd zxW^dsp1!Q-Y+ibqL-Y6Kp0xP=pIMSI$3xlOh+nhB|NZthcg-)a(kI6zOv>lrgP%t@ zGe#puKSQ5ubb#xg{h!kv9z>=?As&I{O(GO=tJB%cbba#&k7+G^JQmFtx!Md$M|97N zgwMGBB@nkemgLmeej~y=M%W3wy-;Vu!tKLiPc^+FO&7X!r6&kTR6OUUI7A**YJUC0 z=X~iAerUeKW|5)2Fc#3d163{6)rV(3Hy)Gf^tC?_TC+me>@HRU$3Pl)cy?}4`jm72 zyisA?k@c#~T85H_M_#KxVn$LwSMw*$sd&VfY{YK}U%&08tHFSqYr+-_=}YQhIDU1pGh_X} zT^w$#`IySzD67@#=rlGS6v`sX{On*NtRFEkix<<;`%m#Aum;hwS{b9j_YonQ-$i8b z<4PYG>ZH8{vkLcLuVe0Ff1_ER0q%%Fn&V41xIiQkEKy3-t)%;WG<)< zba}u5;E3G~CZF+kLRI`MO zMT8jmswiwI5|ys*cjM`2@_+3ub*;@IR=CQeFX(+8*}s78R9tKFG*Vrl5k?jz-7zQx zliLR1&No}nkaC2P(7MYJDkus*uH~dg!X>3o)xuhwh_c?~%T4m7qr*IYQI=fQN}lM0 zmwT$)k@=65u=P;Ap~cHn6ow#5{7uBqJPoyg96ylXf=f4t+(ezXI=W~3sPMwZPa{uU zaM@C`o`Ib*nX0KAISo<`iN%B-s*mg-=(-FzM=mZw)DD`1=K`CXh^f0L7ou*;BwDKP+*nd#Q6DV;Rb8+r`MJObIz zg)|&5#}2qm9;bN!M$>^OLG}$8i1ny&zbPx3$~HE0&tEUcNXU5y%rq2W-F z5Egx46#%)aVh9X`QOIWqOhKcKp%@G~|3*gRWOf85dHA8{kJ;Ot4j$dB>5~ehZ|W@k zD^Z&ai~K*e`%;!DpdDPCmV%zUO^FX2{{a9Xn?u1uaZF+iznNbsq$Aeq2;+)J$QRg# zB+G9kV$Q3to>8I1@IXFfdVeGj=fpWTAehtpOi4eoCqR8E}^t?7#~E9zceGEIF=K%OG{OO}Mo?fpUaH!4e8~Ac>ks6&0n{U}p_Q zI+(7j3by1Ik^_6(Ul}kK1O#!{pW63ptXX)Bi-xCpn0JDUUk%OZ0`i5j{bA1fi-Egl zv`)xbvksvWntD|CI0D~5SPTXQO@X{~N|F*ayz>N>q?6xTQFDLb{e8v0B>_EhyXJ@Y zd-RbrY}X||qxOKp<+>G>LxLm7{$EQ$&krS_X$2NL!Hb&oGOfLFxI(}zo7W}bvkx9% z&Q(Az!w0@S*Bl||%vV=P5IO>_^%Sh@z_|IoGRRYrFc8lz zdNFRD1JFev@2^p&D6A~LcqV!hB#D$Al*`lR?B-l;;I?&UXlQr@G5s>3$WM>GdU2pk zLt~BvIbwTJa;9L< zn^%brYoJF$E}cLJGnd}-&_QOU47SHnIs3-UtL*c!!J2CpY*wXo(2r1=hzRBTAJ03d zcQ-5KQ{}5v&$+x;sr1hzC#C(P%B9u=vepTMvpql?AV>|M?OIHHp0Xcl9-jOo!|G7+ zYAp4MmoSd~@Xz|patS1AxHj$a-uVr*xE%>1rZvMG9=SZ~aE-qxD^{$43Y*@GKb?iR zChP>uFFhqW@-3GPD|uYIT@57aT3Y0z3mYEk?hjY~jDIhme)M%D()`;ijR}H`50)A{ zS>j>524mmgP4weWub?b%d6(Awe)d>3KmlI(jY!urD~%&30lujqyLycVEJYY1W}4Yj z`Lo1zc@!dqF+X!JI}CXfg?)9j%D*#sz3$xhRg&woDFyg6YW3G`tKdKv{+J#1+k^c7$ z%m7gV?luT$6*I$^AGgmzj7TnVH~wi&B zfCP~Uh0jxXrVPB=$Lpx?#u%F$^=-W%HekB}KTD0M%*_cz0gTprWtJt)XC<%LJq13u z$M|u*dYa7c!uIW0@fqtIsP5aU?@#`Eh@!bGQ#HHHH+skA+=my+Vl9{0!)4h#P)FWK zRP>Hg(iMwZThz(){OCa@UA^~(j=zCQ{=9{AtMWk%^m^csUWVm*!uJFeSM;(%R53sS zB1svwh)n#DAoCM};vR_UdZ?wkR+B#{HQt)wHpgR3c z+{dS6_EJX)7%F)&(Cvie7yv673OcO59u$isl&{~6!;CJ`H~bd0WzvrKd#6x&U-e(T zx7a8^CHrm4bU=AYQP1~ zQ0)L|3u(VpqI4HA2?#8ohinxrs{}khSRkxDMEX*OATxj=Ol&xxO{Wx~g`;MXTK@$o zR13{sAqr@#?_VI?Fn@$4Te!C_crOwsDD+VeSUy3QfebDOK3gdzm8^znd{7Y=i%*^1m0ysU%vzw2L6?Iu^9gRp9~u~ zp0=Hm60ghcT>jyOp#iY)l}Z<1iTKOZV7oY@t6XTjr=nh0!d5Uy^7Li%u@4RR_v9eS z6ZkTtI=_OBA&5_VdHK_~p`WQy96sNOGhqz*j3nU6b>0S-GFf_f$MJ{jBRfvm`xQeD z2OY-Z_S=W9@YY9P&AILgu^f=P9qNZbf?~rEeXOO0QSI3Y7PXm z=}&7|*=D+%Fl6bhLsTd_2B8y?O=8GX!dLu? z>`x)>wQz&u;Spzuzv@PY2+8npNe#UO(`6MECgibj$FC?gR~Vat_tZvAZRGbhi=e$F zo6dT&nzOX>x?ATWCF|TVkm&YBDE*FJV%?r6TuJfr=a61tEaNF{cLeQr{IT8#VB^>C zQqIZ2p#CC*6!C}2B67fUpgZe!p zzxj%Y`Mql7*#)n59bD%9AcG%%5-?^5f^sZ6o^S-cxeE>27a@mJZwml9fUUvZ1ll2N z@_7e~Af1no#RiZ5Kr>N)PNgGj0a*haU=wEw2Zs?L7sVI_n;|^hYa9|`ke1Q%S4R3v_Xc5N9}f9oX+aVIP_y>BGZn&cpc4_f@s%{4-Gc;A3zRN>oJy)0_Wu zYm~1+!A+P!q!-B>tU@RX&OR9)14z#WGcgGT8WH?Vc8G>-BjKeGUX=RCDd977lk#_% zL`CKREZo4Zg@!RU`BPl4sIBRl1@9*kH+4gyqX3IH?6v?Kjv=v=2{=~Aq&O&eJq_Er zgpAI?ag;T^{mZAdXVVN_M9rm8`q66$9VhCfhEk!&5G0fM?(;{THGV5mAnE-4S%dxe z8X+P)T6rBhnLJ!emwk)i zb1vhuEA;5F=8TU)B_?Z*kwU{Ys0*z@35JtEP?teq&inwFJqM9rtd`9l18vjpE19jr ze7B70SvwvRYve!p3kR_`ye^_kA&Yc>pdkg$2`IPm#ZSMU5s9O~FP#)akkz}JdCR(6 z0^X?mR!|=NgkMMS@uC;kI2u4%rp33CIK_i3qqImJXLFOuPL2_=F%`B1cN)jnHdl@u zIx)z;DhDwYAs97CM?Lc5?sf&c0;d+fQP70g}j_LTrrppz6O* z26ECd!2X7u3qK`R7E2X7IR>j+d>)L0ZuWocu+hb8J>;Mjq;m|x3iy@@vTDmf?+OyV z@`HS8*6WbgwLH-&IA{;|S^1zuJz;=yZyOxi;D^G302h=d^6w>a3bJ4wE$_bvvI{Ql zn~)$2r~}fGdO-hGE8-sgB~2bsx;zjgb_o=+Ncq9v*9i=6f>5vWf#+dGQGn17LIKv{ zbtD4Q&>#%zpQY6wOGX>$DB7=-IhKGtZGFcPWLp|A3WL8_A4zLZ5WUdS!1rufDr1dr zSRoVo(@XF9d67S_zeoW+FIQrcp`bQL^i#*T5h0LtqyQr(NKtW0K*9LW)*R7>)-owG zlCnL;Y>S39=$tAgZl41=-39N>;F{0TOZa9eYk1gaRr6AWZ3L>r(2wyb3~8OjjKcxO z^+34yWCp^_Wlam0Bvu&s!UZ!;obnprUdsnm>u%YS4AjP$DQ(A&Y z1V|d|zh^y*`DSj z3PD3TS#&4a7Gz`E@&E3HT2$550wb^1Aa!`xcLPS~^I+3@Uwd-B5Z-;Ce7K5iW{H)4 zo|???lS!_48a7=Z?*>xm;W^_GH)igD6peQaowMk#%y&bVDIPBy>k20_S5vX7Sc zYhcUhH5zeSxS0D^b43YT{st;EKtJc-4^5^3y>gy)^-zq}Z{9F-0!b|p0p-sYnut;I z2$Vy_l*5-aTK&>=>O*9gNJnI;Nv{B-C&1HNeUF$SHTrkL&gZ61o2@*Nva78;*W^4? z;(?LRFznTOoD>Z80O5^cH{S!;*t&L)JD$UPxt@%}Tj$^*Y?{8tw@ffptwU@>dB5Cf z2&7=((<*+%fxKZBh#$AXC@@d}&Z;OtNGrCk$D~8p;nnw~1UDTB{&M=({csm9M2SaX zH9iiqe`pzEu%5DDCJLMyKaY2G^ij*g(`o+yq>#gqk9hOQUt3iCOA;(d{tm;wFilXz4;odQ zj|MO%X9^ibz@*}j=sHt&tTLk5wK2XQ!-?~?LJrj&R59>RP7Y=ZAsQsx)&@iX-ncr7 z;PfPb(hVT^J3lO=zQECdmqd;P?$DH%&$fL!a?(S?Jsev!a;ytM%J?UFq=_|WAKl(z z)9Ew@SyXpY`;)R3F%S`O%KOwurxwM-6-BHJYz?L95BMM_+i_(s@gW9Lb@8ly{HVZF z%)JNZA2H9xgF0|Hg(_tf06V-f5O z8^o7}Qi-+b&n|EkCB|Od1qUo*WwT)spmrVb%G=9Rt0;z3JwU+pBk~EcWE4^Gg%qcy zg4<`TQ(Tz>dW?c0sLoo6!`{7u)d*X&TOd0IU*wqr+u}Tz=y)t^@B9X_OumW4i_Yt% zpxBG&--@;tEHcPi!_m^8R{thkl&N>8ryc4vApdvpyX297ACS(P4U!~QSjg0z%v z{Uq@J)OjM<7;EFbpY)uB;-P_{no*w1twFD#Kt+#9uD1xRV7RyY;Rkz6pm|i)-~wU0 zg)bxff7i)c5ZH#Ns}E+~>Md9-ei90$_jb~A3X0&>7Hs#g0(;zRu_bl&-E2y&51c0b zts^WcRvrVT|9wmFH~l1ct^$0#QQWYN*{PlxCF<-?Wepq)UOXzm)XP%B?j|rMb z09_|vc{d^Rb|KnHqc^l6)u~Y%)}cnoqXqwdQ^x5#R512wAjfFJCI>uRv$7IdIdS=> zr8Fl|FbEQvf1cy_11J^FUk14_X2tQ<9m<1L@qPySQNn|++op!WDnrS-gksKP6b zzE~%{F7=_J6CV`Zi;RjV6?b3$PB8XY55V1SVZ#5PE88c5Y%Dh=-@aX+AE^;xKThqt zmoZ4Rzq<(u!S<&-J@u3I4mDZG1<{>~ncqMiS)$0%@@o|-C-d9j$t5a%InJ@DzPO9nn+J+28b@Z~aw^h5ouFSZ4M7D)I!JV2^5#n{Xj^+r5BjdOF>pC4p)Sjgcov|c zBcT8)TMt0IOFpJLP@s}!T(?(~&H#tq@5u>d6%~w(QZgZwZC<~Yeu=h$)xdhFzU;9A z@BzA-{<>zcwyzu{lL9?HikJYog+xQ?2SB)VF(8{SH()DgJ!21>1FB)k1c6IC2+a z;DHl6D)LfdJ~(%H8RgLm6>CploHqW>^c)~^vMw1>U<4He>7_^*gzy?!LO3nd1a>;X z^n?u9y4eFk%{YEEO>BQX9qyP5z zGL(Lq$xzPu{sPr!lNCo!yuO9ksgmVZaY+t(8g+L9e&g45VfRld5N9B$Kw1+Z_<35$ z86%1-^M(^FX%m}U7Hh2b_2C^jynp>$VDb(K?=A%ZNV?N?c|Z*+B_&Zd7g6v%2PEmt z!Ry`kizEGqGP6AE(K~7=p||ux+g|Xkqz$Oj!wXyFT{{gM2$H%J*fFE9RMIp4Kxg%5 z3|IrOFJK)H%jCCL;9>+?H1esCwO@-@Z`GhppH~|l6v=eKXv?HidMW=+tQNI4NU}ai zM*K2*NgPUrN-dCuz;d#hQn2nk0#x0j6$L8+uxJcX`CzSPRPZ3}JKR#d>W`eqj%*=! zm0=W_10n*m#XmpXzo9FwK2V4hN?~-I6v)~QttokngzF(7P&W;mg46avN>|mM{Y$cK zC5HDcL9%zJ^nomt9{m0Q%46b@u?0cxeX5x9W}6g-LIHUV_Fws9EII-o2G1G1c;!w0+olITt#hUF+LI7SJu z#3i`Zr3MtCpb07R@Ujd4UNNo9Ds2gng#W6Dsh>Rct!Y*bk7VN@q0WAychRxa^m&PvX^wE+UeGqx8cYAE%mrKT<}KU|}_{w|g+fe4Of_5;{R(bLsUgzq=!1&r}`#NI}W*4T05~ zHW0eQ$Kspez*#;xxVYRWds=-IIeQ?_7mQi!zvR>`{RV{zr!W-MVEzi~;rheqq}1p? zmmz4hD~e9{sIj3=&|_}h=v*~o#@|rhTn>^4(`Xp$(|oq8jpvWh^Fa*$#RGDyPd!vtOFKmqxaVhKZPJ$;N;6_5km(7gUHzUBe@u<+5M%c-~bQub- zB0H=Np7;x5F7W)d`G0GL$G>+Yca;;aeJzN$avQ>lIrFjU8s{i~s=y5#Ba5XrJQVcl z%MZq88@@^&?i8d4w2!}?_02gG9h&h(O&l!1 z&h6AF()lz#*nPG19T{imiH8py9;GJub#{s*$gxEOdOjX11@M>_;9ch*o8|XbJc&2B zryc-I=#b4&%T*QF)*K;&{#jNQ@$SX0xC4?9XzdV#6Pi*6DpmvRIK(h-t(~b3#?Dm9 zL$Olu!p4Vzj!X6RnM!zKt^D){NShhfh&L3n+1F_C=*|X%X$1vB&K+a%jUr^6Hw~NN?-AhR-FytEW*j%f6)n9%`>%fkp9j~`xDzL> z9zyhk^n-|<^K(nN7Y1jMqKFa9C>qea_~V5H!HtFRJzSq? zg>!R4pW%5J>6$L^(K;3oIH#K(A z_U0XsG^}sfKo0zzzjM&I_jH@K?%P+84B|^?KoVI89=-Hq9bROouk<{=hd5rc_#4qX zacot|erCTOIpHg!h;TRCa4b_?KQz4(W%Ga{V=u2ys$*$6YyPuI&-2o}ZNk;XA;rWyDKE zD}y*6*&>+!aqsH7pwJFeg?YA?p3jOn{ymJ6EbwNN$VNgD#aIV}wNwn%Nx7@hI38q; z_k#iSJ^)Q2TnZE-q<^6sMsi<-0QZKM>up1EU=}tvpz8!=HwNs&ZuxxL3*J6j=l%sG zjd+m+$TE|9Vg{|ua{~}m0Pq4L3OMSJbF_f4y3p~i;nZq5ufd;7XAJd9pp{8$VT#~= zv@I1XULto>;bzrLr zh`A4*S0gZj4XL0L#CmONUBUwEk#RC@@}~$=9ZQErCXh5a5&YXEQ#Xji@WNR{*79Eu z6sV}cqx}5u5bKD2e_+Z0xU?j}5l}G4CAc8V)q}6DAki%+Kqk-KPw9+-68)PO$~xIT zC?8fMw};jQam{IRMQWh)i`CcuDrU?dD;Bagt`j}*g5JKHq!Oa(_nw}ZEB}M^7foxTgEg7$n&P3s;c-@JdaRlF=uC; zJVwfbjJFMTm@oi&I+4O{KKflQMa9C~`-wKJmuSL?9L(2X7VTo?KX7jbYN+1z^&;Sj z1PF>??=*@S0HgsXB(l{|>)(e-D6BbVZ^2LuAk$#hrLz0w{aG=p026ffOw^ zJdJSM1U*d+w^8pEoMMCceNXGz>%!iWG^-b#vxk{gWX5+>kM_vvCF6yBI zlPq4-O1!HO32__uJ9r8~%|HjiXM@`nAbo|qcaNG-!!7SzX(lkVreRcA08=!X#36Dg z13w0FqCY)42ZfpIbT*}aP+kM7<^9Q#hu+7REk-Nr+|a&DNwrTdy;V^mAwj{4T+b;5 z7|mYqbd{ZtX?v%)sz{=qpD2pMiGJx~3Gmm*Hjt4D-Y@9CUaEwSg?pN2lwp)X^(aWT z=)6pbG9_&M2oBqcL!Xe{6ic6y>H~L!&8n`{YCrHr;9}Xk4Yic3}@gcq!MD-f)T$-7Mq13r1edSUPH*f z#PE!7l^?QZzMC?yMM$(KwqU8YzxwG&o0o{-ZJ)nHqK?VB6AO}Yw6eu5a*18xyD`Xw z+NChb__!bq-l*v4l#~QA$i#|~F?gDZ@^yS*GuctjIE}cuqTY42;JrgMXh!p)`>at8 zSF2MeR8t}bvIk=|IM2N=F3wUGzI|}zpe&5HK87=+w_Y*7r_6lr0g(JNFstz`HKo5~ zI$CvZrbakY1^Z3K2yzyv$C9kaIpn z_>BEUN`SlZ2H{waZ$pZRKo+rJ$1>_HW@4?TYxhDI2Fqh(delK0Cn5}KkOS}4*x0Hn zuaTnHba^_g;eI_P(k02}%~c~BpI38pL$m{kwe$Os+L2zVz^I_d#7gC!cgINeP&4v< zr~u3XeR>^&rAIE0%jD06_7gOM6Pf}`q?oY_8 zV(z~2zCZaw$bSqzqnexG+h6yRQ`QkgUz8NEiqSY;oHQ8S`GnUQwC^G{l;8+SC4>**zutH`lCU~CC>p_{t zItDKIZe`|jyW8{7RL>OOK!IZvo#`NM7sp1AEnuTRp>C4Xf>onI*mnvrB)%xZZp`@C zO~ID~>T2;`Qq%MZQ1sBD{edvnSrGp)Z>xXsWe#Z_7dup`81+(%^TCbA)+@63W_2oz^5fqVRL$|ey^Qx4n8493cp2ho> zJ+TxMd3}`0!`EU(p!(s`v|ACAZ|3Qeo7B2XzXCRj2H$VW$ca4*^Hbt_ZGZY%dAV6z z&cNz^fq_hl*%3yj*>ye!9fR1Uq!baHXlY4r@0}6mT3bZxPV&$~e6s~9_-(Jw3cMfU z&4>=P2QzQnB_)uqnY#MX1|EtHm&JE3u_sttHBu467F;DzBDFyIb7)3)>6eu-3#_=n z{f~AY#Hk)%NWG*wX?zC7EdgUc7ft{aJCUU80vi?u5EStnmdsULlI`*y6R~!iNCb9qx1c zAygu3gZH%NkcQP~_NxCoHjy?_ZP7yoj_Wgo+(Mfu>$Uo)tDk&u?ACSG1XhNx61L{nMhj5^N#@$OEIVB(#T%cV2D>Lu7d!eZ#@D4|EI zUXm)o8rD31ExlNWp8X6QL3H|8i)i)V{(Rju2RG0XIf+{VlLcA|cdBUVl_!mwCv{Uf zzr)>Fv*6T0J3++Vc@)NWd2#rECw+j=$*Mp%d=8t+rXBu@)C8DW0tT>uQdg(OtLndEynC?vKVA)-j?tyl` z^?RWC^^wt%Zu^W5B^1?LIw}A!s_E3p^6;D~O;<3eO6rW?NYT3elsF1$V%Ie__4n7K z1#s;h9bGJd;@yJPf6ry3d*`(VcyL4ir>-l3hVpy=GqOvh&}zvbyQJkS6(TbzB?gl% zAwmiv%UD9+N@9j-ku?(;TlOVen&$y)x{%>!mUjgH2a{G&G3%c`Ttibi4rGDof5*G;N|HS)1A{$-RQYDd zZi~?V&XCG{J7Hyz9l&tmDk|k`SP2yzIS!()aMA|dzc*$te0T!~)j8m)+Y>7Y^h_1i z)h6A024C`A1yJ0tr&f;I4B)^2Mk*suH-k#DzCPha*30Cc5biMJeV6VpXNA68Pl%k? zJ;fN1oAiI4($wQMSaAguv)FGgd_8AGq5S9vb}go+hYo3E(R^^a-6%ZT?y|$qWA|U3IJT`hhrO1 z(C5Z;?5!SuJl7E!Jq{rh!eVO!*F-i5WB2{LO$tKMXw~Jtfi6hM=enR)vbU|G;FP?9 zUjzyA`fv{5G|BX1CQQ3>3aqhLPJ9h7;l)}QC`t(ni99+kF8;xVHXxsGz64CmX|#A( zIk9LldqXpv_;0H)!1{a4FC_OYJNp^uJ;8v4!HLEMNyceu^PfgBD=7?B}j8&_uH_ z&c9V=F(o7#z5Fta&GOJ3E|mWtn4EIUwU-O-5rkOwxNPvXsRW|^IXgG!O4xAlH_N4( z;+4bzJi9FDdQiR1gBrz~iyn5F;*2(=PFSESrl4@3Feymx%a|I34^+b$U6NeoNH{7% zNRo0Uj$P9iH2;EzOWRVeSj`k{fNyT3V!lB9FJ}!^cFX9#iZy26UC`UA(DGj#6CDE$ zmW~O83{)tNPTpnKjMr^L*3AO?f2%mH}|8TNC6tb*fbMd5sV&v^|SP4I6Pk&e0AiS6Ug?&G$$9_mq! zqn{2!vPE-q4NXlar)w`pzRxFVZR5H|Mh`Pi1VxiARN= z$iU<7wnCu!aF3r1Wl(mvp`x~cr@C6?x8?4{VY!@KyI=QU8)Vx8VzXRawaSY?J3zYn zX1w)WKCHwG#@0a{v^|g(5(JxQp80{@x41*AffnKN;#iR7CXKp7Gzjf8$ka$MZVDn1`0z)2`cofm{CQKt1s z5EYGuV`u4eaHGXu;J8^tWHXlOnw-M&Bve5_aWKTrH75!;ey%iEH$?2HVXXu<+E<}x z{Mv|b4lEhZbx8c(qHV3EPbUMX`bn$CT8AIzx^lP)&Gz@qh6?5<^t;mfou)@5QGSo0 zY|`q}Tz`jOt2)f!LH-) zGrl*I(>Xt1zOF_W0Jt9M4RLcx?y>wE`=^CwTTK-y1{mXB*1YFIK~q@+#3_~e|iAB2gTP2hT{MKz9L zCV&GI^XYMf+YY}!-mLzMGC1&rB0#OP!6XELawCwUX*@^YVUk{o_KPs6UdSjVJ;MF^ z>>u~*Y>-Ng(bX;N`m+7Z^VHLJ#{1HYqBZOf3JYR`tdvX)WVd3HjPivLE~BdrPH|=ndw%~Sh@TQG93qKS$ujow7yqpX^ER>P{f@0NcX`pD(wk^3S6n4 zyGPV|o!#ooxJ`Z$+vlg5crUb&wx{zI3P(j2?lC@7gfR}zXR8q~w*wItL3RSTH8|5#+Y(hiOze%8ph!7LN*cH!ZU z$fL=(fj1LEKp}tU;};8?jjR8F{Q~*@xWgo z)>O)1+ddXRe*hj2v%n+*%~OF;zCON|F3Dv&CvOGRc=b~694qXq!G0bE-Ye1ooJW;b zvSzovxJ7_M#onxOp|w9yvuIwH0)kH51pL$fW=f>)uy{^qzBd`jl;yq@iSzDuK38eI z^n^mOz1ZK66&B{;xX1qckGqG57rUPR?9wZ(vs{F3YTFpZ%kUy=Jg~a?;UOh10Ss>A zSN!W9o6=c+pk-zq$qCri;C3w3qwBGil!LPT;ZE)f#6pyPLllJ)eQkKt=OWizo8r}g zf?iDx@6J2(XM5%;w2u{1VQRtRFzeeU_xOm1nX{OD)_h}261q~sP9d* zz+1tqpkt4dlxLoP(0A~L5FOA%_}$ zL8hV-hvndi{m`^odE1>k5hwcqe-Q8=*noH~;^aubLx|;G03u+=*H=8M8ycFtZ@VEnn;YZ~OEYbZ z6qP9}HgE~JI;mjh9D!w}UHn>H zyYI_xm=p%MNY%V!l7QxCQ0GnyC1g0Ja%+@|8H~fGZx@l+yyR(F=NrrVe#M`lOgmAb z0gdClt|j2`39$0`<7Ij=j;qwuqKpiC@>xE;1;;cyj-U_)gPDh^{ms`p?-X86ZhRE_ z>Q5$#ixdcX<69DyW-0W^Ft`dH{seuijjst(22C8=J@I0xp;*NDI<?}2-L4lr-fToABxBlh)~9N} zl?Rho!+HI_v(+DURFxG}cKp1*aifTQXx)`>q$2-;%^F!mSwU_<-2noIyZ~;6$Lq0( z_+hrp~>lY)bFHDp)&`NKfEszf28|3pTG}125;~(;D{vK z=gXEBD=R_h{Q)$lK_z~O_FXqxEY3W0U zE*xca?A-YFZA!?W;Y1)9lE>z=G6g!`bFnU0W__RAw&m46+f^n4b1NC3N{Aq&IcrX= zkvYe9cU7%#F`Su@3l$L19`&UT0%cf`t2?u6Yipk@y5cJL@`J+FLE6iR12_zg!=y6y z(qcJ;9Nr$R(2AwNUTAohWcrFfD7k77@N{lNH7onw#TiL@Crc&eR>^gsR}%D0JL+$B z71n`t=#u97AN)%Hdl_4|{x47M@9P^$CvE_vq4W3P1Iji>73}Qz@-hb-wl@HLK+vQH z@B;*c;M~2T3E=U;%E299tsy2Sw|~EqA?F#KXS8x#rn#M6Jq=7MPoLi6==QsT&K*IQ z5_j)QakK8rDz*xkUGFL{f4Fx#|9=1RciY(qbT+ew7tivJC>|u<3)vypO0q3Gc~!x%gP;J0JT@T38QlA z-r_Xp_+*TTBtCOw75&;Q8UdVRr7CuaR+wWZk zvXTeGA-at80#~~MaNURSpc!XQrAnY6F3Hl`-4@s#U+=_lJ-iuPdRV*L&Sfj4a-)am5(6dD6CFWhS<+O7HFUAB7-U^wz0i|Z}4mKuuou| zxU(jmYi&CQ#Iv0&1PmrxK&vdy&rkQ#*`ng& z!r7UU?uzxbTcSNB-&qK8*E(Bget%zGZtkf#zB<}Smih+3Ri~X9Gv&Rj-}y|l@yC~% zdH{n<-|giKw_c?{nTpx=#pwr1`ty^4f9h&Q^|P`X8t&aQ3Je4#y$5mo7OUKRZj$mM z(-@3mZySxS%8H~UV2#+4sA#z~ZcQKW08QSm!lu3h>K0`lX&GZw!_>^ohzLo2@EOYe z7nK5|ZEc4d?ncC@CqzXl^#tbf?))=-$j$4%w9niRAD0T3Rr$jOHiuL_RYwd61cQyN zsUss_@+J1*?n|q-4)2U_`A22X>>OaCZp8BlKTA_G{}Hm~$eK^EM2&>h@b$6vPpJiZ zhCH%2&%YswIcW~U`5gzbdKa?`e|lcfwEf*EWW0mnvdK!gy9(Wwbeb5kb3CpImJY^& zD-<*usUd*D*o5b6T!m#4>J@bLaP6?20T|ZRf+FId#ieE44W+++RUSLU$Sq$UZD~A4 z{WD#)I`L$e3_t#K{Zqcn%a&30>l(!$ zUZRxzD})_c1?sQ_XTh%q1;9)CS{&q|&Q*T43ByE)h+0tCgy5l$u%w&KW~_0 zY+?yQtR$W<2ELdbm3zc? zd-RR0D(Xg|Z^`4t5Gxp0__u(V*3LZ#695WYi+UB7vo_T|{)Fuok|k6Il>2?l8@6Bm z67jxo*bVD|)|ejo?apt{szJ{r#=u)#LSoA)87u}n4L=m(+kbjfq>c2$*a zYeyoID7)m}{H?s$&D4Z|aCj>Gid&I7War+u?>;3aKHQ6>%5UEi(B8pd^SgVCi@nPU zGXr%kX{Sy#13+fNbRjDSHw;V9I@aj-r+&!aXn>%4W~H#UmU!a-GaUt?<}N$ILT zdPbCi=^GUnE;K`}u!C!he|%2LndzQfTIS%Orx~6(`tp{|csaIm~tc8l~~ zlfxpg@1wNlM@3T>X{N^0mPt2kTE#EYZ!Tm`X_@m7M^1S4M~BIwcg_U0vy5gNA zyLjHYQ~^CLP``X*qTB;9yowL=KdAG(+q1`^F=2mgq2sQ2d6`B3&6~e}|FuB8Ju>_0 z@!)*fJ;|&bvm(`iGnQzwnha7_?*he2?^2PMbeyasHv-~ z;^JY$sTq}#>KeQvETGcmK099(IG%NU-F)e-5_xS}blu03Ue%c~H`I`kQSMEj^uP42 ztc^hj72SpY?qYfb5*tlRV;+_IOi?7QT>{3YBC;>KxdpC|Y;2&>Uc|=tznHekA}Qbt zg|d-?;pnJhP=O7#Z)y;XygwX`GTb<6+t?VPGb9_Q2RVqw--pSv#V`XC6^Q6puXWGgotse)?D;CsyAdyPe_%E|eA6a9gxOa`t~iqL zE$2mhs?gQ&&;@_197blru)XF*QdbCDa8QABhavgTYtgdnyLau{wr%^h+A7$nrmw7g z{d#@e;;u1c*DCM{nq~B^5!JkYzK^B|$S5E&QJ*;6M=WFFO^=8Qt+1ME{WyM6v)wb! z7HMUo)VIf;IGApy-OU%J~Fu7qc%KvcUz_4^Exh>l(#ZZws+_`a+hC*jPEk!8G zoX2#(%8&W`5%`=6T5N2zxV^oC<>N>H6#Z$Abkn>DbY;zqW1Qp1rhL@NRi3kBz3b$O z$BeB7LCvImlFgVb!Ou7R*0;eTuilj*fVP`^%p-NFaTD*XPij%or%wqYrWd|6HGxD| z0pO)Polb_&tlYSCN%vMOGN9WeT|s+BUo&{?&LeJCwkhTDy8%q|;luR3bzS3K(~lg# zjiW(l7U~L13Wo*#k2%&Y4Vt~(B~@!`dF^mC!s8?r;Je_2@NI6~Hiu6rrmKGg_~1e7 zLIo~tV6de6e(`>w)Ri)$bFa9H&5wxfLf*f9DC8qZN5aF|g~dqr^oEq56RRuJHJYZ_ z(dn>Ty4rYhpxMI?xf>PaK+gh3spv{X}=*! zg(o(KIs5_p+|NVK!jyqOr}Dv=aS$aDzFf#SksAzgT8@hX9gy9q!8pbfI-1cR`2P-df8m)-g=%UMd8FMcW|v zTJ^^hVaUSj5TmsG>v~Giw9ba=K3aM^sXgQ*kIK!-#*kVB2{m?E2BNj~@JL)0qzKtX zdIw@0BAJS;aPyb;&MFYz(E3nTVWe{$)%20GeCq{Uq~qM+-E4-$kTV7za8JO5-v}M2 zx0hL7-_3w&Ba?Lrd<+m|{v}H=ATS?yp|=NeoLP`#R>B6bi1J4*Eh9lk8<*nq`GvW2 z%d6z7-omI~XtOD{s~ZV(Q{)Zz^TAmLb1vxx5={;10T769sXv5b#`Yen_d;Q%)od}) zql?Cai+hZO+5deB6dh#&ddMI=G*cTw3LoX6y!-$~pIte0XLRqshxw7Ga?y{BF^Grs z@eWEG=>0Z`%Q0)dPl#`>Q_;F1q)SGtT?fqhC!`xgZ z2o3?4Q4C#fs<-9}F@&_)8~-h+w{C|8st-#n;7=aHNd-J$iASRuCFVx)?73O&wWt35l-G?!sau^wC|*0V5qG!pbW%kucy!p&`*)Cy=nbYlgh! z!z<{z0Ul_f3LLS`DCZYAEEb|+bPfq>E1I%N>VQqWwswiD7(XW~ixJ97i!_GDCX4>Z z5*wwN*%>0SU@wMT(IBS?B>M&ra+-V;NLg%*+Wsk@Z6(MVvSF8iLmK%#M%jKL5}g~- zXF)Q|yAyq8^MN0ao>TPMLlx#-k3M_0kvTk(XF)R4ZpG@GGymNY+FkIp zSG(wL4}M@&lvVaCDDPL;chGp>el^vDYWw%eDeqHLRyKUijhyiR9pLI_=j3qX|3BbZ wLY^TwU@P(tSM40si1zLtPHtB)XS7c$>{C1-#%96u5BNQXpmSO~TgxWoKfZS2#Q*>R literal 96734 zcmc$_Wn5KT_%*uel2SyZ5tI&~n%c*Yp>or;nSE;cDP1Oma8la*A1Kv3c!5F|+gbl@Gd zhqLk!2-Xv82?-TzQ&R{;_FY1PrOHR$2OWDb!HrPSyW-(060uB=1LFzw7>0@qMAtJ^ zr0m|-A~U~#%PAEjh4er_;gPKr5zdETWMbDhEXdvUPWM&CbRFj(W}H%pG~TB83^?4l ztj@U%oI4^>zkOrg*5mU^t-f`-E9xN$Q7`H|Lqt|$`@xYb%KT^pA*A$&PyPJBc^M{Q zto7AOZEd6TIb*OZBzxO@tXE!HcQLo;1%C8xyQQ{Hh5o{`d7Z`e0IzL@{uY*txTUQ= z1ONNj5OT%~GQ8WcTbI4}8oa?r69t`i`O3~3-df4jaVg)1uqcyyj)>@Fem;@(SKq%) zxT5$hV*xFfw0G`)B4+3Mf*%%u$MuYZ6V=G_;MbNyG>!Caz<{NcQPZ+FWWohPec9~2 zm;iCzMHamn)K-37S03*R;oseVj+%1oU66o_iy4T#E{e{8teK7F8bru}lGsYj3wf}B zX{y8>6>q&u-`%JR<6YL5Q8Xt z$(r2qobbVq>mDR`!h%dpdO7-&sb7#=JkKX_AsG&mwaPUo58(iV$pZ=0#GIi2X2Gx*~$UcZNT7{9{vt8_P}0PYyK?)en81rY2{SD6@#$rRh}b zgipjwFk@$aQd(5uR&i!niGLn15U=ahSR2NxE%?H>*t7bK??Cl{4=Xh!CnQ|*ljIl4 zsvnB)teFyv#N4u_RbYi31=@x8I79N~mxj*1W_(4T;+mqPkgy!|I^y*vytcf67|_Wj za3&rl^(K}iHc~U1QQyHukB)6+FiE$F;1HH)ly%B`^!Y@#S{_%fPoYO{T3$x}fqab| zzCu8}NjGojkDl$Mjd-s|AL2$6kK_8{gdX`Qx@$$TSu%DcF~suHqmjx{TP$iXc_cBY^;GH0i-S*pBJL`7=`m^*zSxKx!pW!m`2B^k7KcyD zBXciv%auDT{3}Q+X!jG}XUS*Er*+@c3}$-CC13rbT1GQ3KX0`xv3Ts$i7Z8VzgD!W zNZuS&xV&u$^Y=vQVByogIEw=vz7pcG$ zQ0gaxG4^!y-6&tl^IGhtZePuzq6^L3dOBb}<=cPQt)-M0P-IswSKmu$PW7+2@xKGw@L_k!UQ=M!%bLs@SsN2()g z?7pQNe%HHKmv7P+lJH(LSu-Qycd9jojo*l#Rj%DV3v~xruhuAc`?o>&_qjjcEu}9v zRz9z|H&H#^=fYb1vNmal(1-p~{1OSH=uMVH!gqC<`yEyt#4@oT-wj(pd-|;`wb!ou za|O{Tv&Mhit4pr4bJ}owTz65cRVVB+?0&D-t>ODF+b+gT$5>IhqTcidlAtFghG3>= zrJ%Y{z;U~$m$#;m&?)y(|M4y?=iKhd`owF^cJ1Y#%L(OS_V48#S%0Yh%rA>j8B?i{ zQr{WDpSkNPAj{3bUBFC2woNrdB3RRJGTY|%k>&{v8V&81?pD?K{0wyijmh`W$oNPT zvVo9K5-8$n5)Zx$wr_r4ZTDK_T6`p&6!7-;Sv&V9mRC=Pm?}A)nYx*ZIBoc+m@Y(* zUPLIq@anZ=;rYgN;+F2B|9Zx^QReF4>~lz9$Tvy-S5|-O=xo${)f_5cIlCVkzgLnM zd(ScY=-9YOc7e(2#halqsN3h^B$1ytclsg>9xkPfrHrQRRb5r}n<<#lo1Z!i&3&~T z)`&9gS58w+J5Td*PH^#9j&fMEE1brDjXOV8rRetZ=3Cc}(zNn6_4eEOgO{0NhSqb_ z8At*Ru=|HKYs%ZsQ=@9sTGR|Av#;wKPB2FBzlhw~)@_1153IGnZGS0+F0~@{;pf$# zmm;J$ItNQba|KN(kMkePoS2+E-XWWGsJfLbl(bgb_jL6NZOJO;B2wY8J2-U3+q#tiR!h z)<=0qUL2Yxo2GPR@|^J;UH`Z#EL8pWrQl%rAaAMt-WPnh|-a=wTV#-Gv z`n?;wTNmwOds_pt+|qR--UO%3pX@m;5$Wzivo2wN`Nt&}ld0}{J|-9=UkW8g9u6~Z zV{eiQt~r-Mrz;z-wVRBhv$Tbc{KO8$yQJEGUVTtVa%Sr@P^uI3d3@0=Iq)m9LmQ=h zZtr-LqIT7L=a*Z@mi8sDUy{AfEN{T@pVhjT+b=U}6dgQ{(vE6wEe>B^25yLUP*e#R zc=YYLVRKPK}mi<=EXt!jLDba!1%(X{rhn>l-e&|YKWPf0Ra}$47wVJS6Too{RGs=!lq}WW{Xn4_Z7TKG&Qq)O&f4YJ%!Q7~MBruT$>_o0jj;#mM#KF>FHZ&(M&FI7>|PM;Ap6060;b%Bm?s zAl^?PkiZZKJWC-MeV|u-+An*bDD+L+JTXku3KA@PUfe zTWZGt^EjB|IW)m}|HB`^3=PAK`yY-&O+@%Hxa3ICa+>ebKp z_nEaio&Nnf0qWuuVK{gljuK*)uX++$Uw*SI%$4I4_C4EQ{2q?-fZeds^Wf9k3OS$i za%U{9_eB914Ah)p5RvdYF0=-r1cARcS^4<#Vy!pjxn)23wQxq<>PA<00<(6Rej)U^ zgNOZKk#7Lv(tbIS3h-Lg~g?%`AQvjwiAk2|)cyVsuSi%O#LC>1VmK8m4>J+h;ACGb-5fkZsK-RD4%t+|u3R(5*_8`9D*$J@Ny5 z^0ngz)ve~%h0)oHre=JwVCWoVl%~0vYcCfl%>&$&m!kzRQJp$D1v&iwy9EmT7I{a3>c<&!Q^ z6EzEAwv|yc&U1-t&(hU-&K?a*vE>^olnsgsrpOVMTO1;1RCooCBk#UycHOEtaXKiw z6~l;HtvRmZbKi_lxa^QpP2C zo9JcC4Qu{8Gf;DCssEqSi{!WefAiFTukZg;BK#Q)&tr2G6*@rfL({2qUI9`DOhhC+ zGZ{!D>3lAMK`0n_-M^CZ zV|@7Y1@HbVtkBvIh-@~MB$E>@PM0+O37#XY+M>l5;5j!r_H@PN;tD&Q=n*`hnJcg+ zJ2x=6IyXorgX!q+D<|)NrGAx?0wC@7rE*mp^MYAQJzxLQnUMsKjN5La!lV^UxFtGB zFERzafaKA-+Ly-TFn7mwYpfJV+nM9G?$1&~e_XX6!&iUE`VNP~gZUOcjQoFHLi@2e zsvP1^=je!u#kJF*H59v{51GgYxxkawoqa)}jCVwoeU5xHl-Qx>Y|2-J5csMm0uXOFu(`4;+PuDtqwj}1$4aOr?5-y3hI6Q^8mpk&$ zi8vV@odty2j#u3T{=K5#<6ao>OgIk*w3q}v8wR6I8KH~`*iu$*e_0;I5f?d>i^tY;M{U52;obd;607Z7=^Bf zSq3q+Oh-i2LyBCqccC~B*z{`&tl-tIFEHVQHk)CcPE?u-|Kh1XPy}*iqQ?l!#z}R% z88q0kmh{;&QhuVj#BYsCPyBn)5OE=Dsrb`{SF1mJKcuvpMhl%xm_{S9UVhykuEbX< zH*E4g-b8X}K3`39XvT8g82YS`#MT;ub??cGWXRFZbdAG&b8G7*#+=B$|H8?%L(BEa zbagf(=4UMIxCjRGkgd`76%Nuw)e3Uxy{cNwfYqH&)<0k|`RDb&R=B=Qrr*h=WtJcD z+aeOpr$vuq;=w?Dk-_h_IZ`N^1;33bXYeq~YH@>4A9UgK<&Vn3=col5g=(MW6TW84 z1$_*LQpcOE698$9BEt2Bcku8E*QOg{mi%5C-~4eD0!@v{jCrf|zs^F8K%VLEy&!D2 z(v&V8Iyl-aE^ofrEN)$BdxL>bc?T5(3=|NXd|4JgItF7jstaHEY~c;QFysV&FNCwD zW{KTij-VNOZ+uQN^p+FG$T5?!qz;w9j>|+9XwDoj(1u;E7K{W3_k31=&O`qsRI{HR z@9)fppN>5C-xHx}U)PW>e5O}z)Z%}Aag37bvs<^8>7#^m*XusD3z^#Q<-;tm)fCH2 zFZ5(SmsJ39xV@Gy6k-G}J)SNTRS4UI=x{xr6DOJI(Rp*-FEbM{6E_6%=xnAUU%Rxx zfc|TNi95+478rwQZ#8wUeN;W)rUk|4i}SUtfSdERRz;p!*I`8-B(366n`+{l3awJT zYMW8ye8n{G*HfI?n#JH7L&zI`r?VdOey4I@i&ZMKjXP7Ly)vSdzW)Y8g>_j*3JmKU zXE+EeVGsPqpGHQ}tezW=|ch!}&tj(z}bxHvdx$}*FT{LfdCjL_!- zZm&=01E{R#oBb|NcHB^HO>juH74*O$tw2mw?coY=dhE?L`JC?7FFBi3lU002tSL$ZPl~uYBlx|x zj8cktmh{%7V|vbhYnsPUlsj_vVw&e*NGH>`7ubYW98Txl=uE=(EGMBg?XT zb_oW`_2uc_d`pYFz5l__2S&aJp;2)e&m6m38Bv0eK4QF}r0!?Q2~nT1<#@l(@W0`R zxR?ZzGYB&=@J4q0S#TvxoZ;XUf1Dz}@3Bi6F(S#+;8zeYpII+KBeT$1K-Bu@XY21A ziEum$>>2N^(waGM+phTm1SEavxfuGu&~p*ZxFd=}zeUJ*7s_fSBI(#dr zX0?Uuw)9-|!*AjoR`Hcfn!l$bB?mA6j|fP>&^b(G2R{%Wgcl^!U+Z{NuD+=V#HxOD zgStx0`EMmz*Pz?)uR2x7N@0Okz_o`z>@pv481I9vaA(lh3QHF{-}3gK!n|jCr^Ujl6$(8cm2W+F*c*Kn6|>?G zb2)LnTK8R!iIP=L&ec2jtJgwccvKX4ZggnD{aP^n$^JuqD=;sCt3~lW?4r-|J$%<} z{CqS1!3~*t=k+a^Y|mH8s2Rb)`kN2;XIut^PG?*Wg+Lr-|6)S4HS8Mb1i-{NMRrt2 z&X)BK9fz1jr@}ah+yl7sWRZZIt8-Wo29@uA8wM5X6U9_c>mfS7%&u~dt_?nsq>ebe z)i6u=9T5+TZGS*vUn14nshBy=_+s@Nd6h&IVblu+cJ|uXt=HHxUOBoW7mB892dEO6PU@^8?H8yBfTg+v;|Dc8>sz zY+73Z*L7eHV=N#FjKgtccLctLFUJx}_Vnvn`z`l0avtz9TqQalg$Dmk$-kE@#9}3$ za3s^-g(`tXM8g4HV`wA-!44-= zP!}h86^5yx)Ob4Qv)6P=$?T8CZ+Fi|6#DIP`l|2!!>)}++yeLblyc(RY05n;YMU%f5-)$QIQlm`gdr~+ z?`E44hY5|kF8lku*IRMw-?=m;_I0_(0ek#H~Yu>CjNH!|Wz3 zC0goCf^cagWI2^P&)NNIO6Y{95QfJ{e;{U7&~1rL7Q zbTK-__ zm?UTo3y>~FTu<{wz1R8S3XrO|HXf$PngoukSBZ+6hy%Uon&9^+ObfBW8S1Mk+w!K9 zDO*uHFiMm-b1J%`@C3^py;hI0&`<@! zx?Xv;tADlgKdq{zz<_zp4aayE1$8a8ZQ-EFVOO|Lth%Se5FM^~q1nnG5dMwvP?w;c zFNRb3pDl*}1LJ0EtEoTObS^2hopw+KTpe^!1;N$^vP1$X3v_cVLR0TAUxMv!Jf6y>?K_CO3wv5m%3M+FWUJH5xD6yvUHF;0;lpb#dG1&BeGsLfMZ9C zwI5)Zs^WXme%Is5^1s-CX8B|5*TbR0AslhYV-S({Dar9CuDdRueK7^ABKp|{7|3wE zQ$7L@*^%&!x5Ji?m2|;LM#l_j-6o;Ly;|bDy1;qo&axKA7z_Wl>g@iMxcA2@%;T%F(`KJ4ZKO^QxmA? zRqEY?`F>wXrkK+&bM!Nja5Bt=O$7FBz&1UX-JaBtn&0}c-A?DkkEgn3E}VE3>2^3t zW2M>_-9K{13>7d8hrLvYLe8FC^saXFw~wyV(l@N3Z-HTnI|a7T`rr0dPhfSN`z-pB zA6~V>5}_;mS1~VTkl8x)m*V!1i+5sLS6o_zq?BEnB4$=^k}3Z9&dxlgtuYb9s&YSP z0-2V_@5YS+u8+r!Fy?$=eLVBN>N0qJKpPIA6j4OFVDBg__%glY-azpd9>23ta@Ra_ ztL52*DXHH-NUK<#Xq7`mU3q?|feFW+U0GSnWkcp`2>NN?L1tlzV`QAsW zmU#W#Ue6cpT1gvFXg_Y-cI_?BmPCrg?RM=>c`8#q^z`FP6#kl?nTmlm5pGX)2nSBa zD*Dw7d5VQt;?~;Lcs=HwmPk+Kv9yTk{=Ky!6af5L)dCe4dlAN)bzH4LK9^wL${6Xw z6Js;lt!Tz9AK5YRu!n=F%HJM$%x;0(D5*D!oIBg4fstXk27RV_ET&;H;i@Jy;s9U`WhHt~pv$?e zh10uzW%}LI(AF;nOm@hYaIGVTeHDs;IMPqC{~KmS)KO< zrT%M09v3rn`_p^C19_Zc+r-{)+cz`Afs-7nXh_@m;h*Oej4yJv{~cdsgo}pn3QThs z8}!uh5RFn-U4jRS2BK2(KVstsgLonUSlFlY_<51?WC`5zQAz)_hvJ4^#inoD?@}aK z&~Whda*dOStuq!?XV+v!k@KI_HeYcUq=hyV-zPY1NH2DK<*k=hw@OY+E97UtS};2k zGBHE$Nc2`{v>zzL3@jyZCO+iPKloSh8jkNd_s3p3G#!t<z(6i z8a2_=r)wS7{#iHhh-g&rcQS><44$%Te&#;i9pVvZOf(_Nq(;N6z9_Bs^FBPLDn~0y zwSBGukJ3||^XnU6wi6K{2%`6Bc;Vj{9U*W)&)5p~I_Z2UlG0$!A_Omdc*7Mr za8rbwT)){@GQg>y8tux9et+fAK(k*=3=Y7<>o>`=8;}0(EJyXQO+Y8Z=;sE za`>}B`a9WJI#dZlwkw{@ml-s;?@XZ`!F#z(Q4mgO6e9b=*`1Sz`<=AWf`^^77j-s> zTsjV0(;8M`Nb!7c|JgCoh$c?_iLFGXYdEw(yb5AbR%qdV$2)j>hw;^y>_iC?bNO(l z=#S8i^UJ3)3(n~+RmGt_ODCR1SgF%%?$xR_K>8kk|97c|;16w20t_T5kRXEv&7grB zu8YJ#{((2Yi4LU^jVek!*sq@-2G)SV2`CC zuhaO;$%VYHcvob02*&Qx8Gm7*X?@kjxGAf+w*tftuBR6oMiMi0eq|&*%htJ$g*XXk zqSq8C3L=s1dh!gsSxO740E2_|1)K<;t>@p| z*0U9e*J9vJ1O>A?XHjD?s0ki`fw7%c5zu~v6_}d>jE8~wR`2tjXF`VBX6i()Dm=ds zpRf?G=xa37=H4lMEOK1#llK&=u`APRqlxRomgKDC%uiAMLnJrdO24`1^DTy_*ZJAm z1+uVkbF;s{_hp?RbeJWg=-_yZ=k-)Gg-(tA^+jJ=5VQ8(d+Y|tm6gw)M+WE52_Hix zwX@B8fMxOx73ijcyicnsFgE?NnI~8!=J&g!5p`vu;}P}Ac}q$DP-Jl=7y!CKv-$x3s86GH)Iy<8c5zUH5} z*g{0fy~rB&DuKD8p4FCwn^w$#tOI)>fTY-#RdzxK^^K>St}&O0J;A*^+@YI5aE zIht|7-W|{3l+KOvYLq}P9|lfmtFU=|>;aC!mQg*!g3?V*vULPP}y{tJ+4$&+*T4ZT|Il1rMmQo%Oy^>5N!GXPq^ zJ4tJdq+{fAJxk&=d@WHUGuf?HWf_c7Qep(}$%Z(=svB<=MFpOm(*j&<%^aytW>6V2 z>FTWNg~yPOU^kq&(q)yb99!LRf_7w3V$ek?lV|Z#I+l3=BWW*Mg+G~R51N(8=H~H; zG$ZQ5&4si0y@uw^kL~uy8S(KmM>7^Od8#a&{LIJ9HtUpFJ($`{Cr(Vc&>gNf_;QvF1GXi5_kJwc;pP$&ZOsw?MBYY zR10~}GqkPJDb`boEV+|K*OIz;c-HG%(2P+OkJ_IeBDc4NGC73$T!UY0w*Cx0x0LJy@7QwJcZD5`-Gr$lKYekWWP9-`j$MT&i} zI|Iz*4siM#_b(Nhv;S8kCRXSpEVR%a)c^jB zzHO92l+;|>qvP}CmA7C+YpM$;Td1z|YcjuLZ@yra)Et*WK!h@=cO68g3wS^T5CggN z6G(Cds6ojY!>;0Sjqa^?u)&Cn!Uxa*fWjz;G(hf|NC6oht|)Uw@az1HCM}&Q1xI1X zbMVMal4;uo@eTlb0^vl61{Nkh7#MI}^TopjxCCY}q+0$H4ZDcEa&gh_LltZPH%Mi- zWx@ax{zUG!>Ip-u>MvkOH_M23nf=7Zj(PX4t80>Chz}#1^nlK_PT>0M@StFL!*>oJ zvtmOBB~_?5)+EK-)l#Pi*o>2d>eh4iO##2^g-AVAGw0}I3Zmwm?WFH#Xo z^3fSw9;Ec!>9GtiLaerzw``0gDlxsxpsMJ&80;I3D)xJyFKoYluP*FFL>*{tfcn(w z1lMG87QS@TfJ)5!=Qr@ECl*Ki4|!z!f6WmKa5g`^>(UuiUrx4ljS={JBnlNfzNBp( z-hY_7o)PuM1hZtM3*r&ISo#A-;8!ne*w89cSsgeac#8yhU8m9vx)qCdZNZ^9N$ePL zab@~)if9bhsxO|SXOL2a@TZP`gblj9(O1Hg;tGFyGdg=A zSA2E}%scv7ia6=;&rdf;1^xPEIc{F_8kD=-*1z#IPDVm(R(Ep)O@5_;EG zX7%(;g<;~`>uKP%d;zE?!UOxh?Q3+ImVzEHx>FEbrBW0~gi~XpMI*S~S6y(Kh96ZD z&b^`_gwKyr1cxv%h6l|NDorx+Cd2a6DNdfhHR2kggkcV5Q0=l33cL~_-Gv9sEwnSW zO(ke@NY7ZAS(*D5viDC;fq|i~lr+48>L_5}u@gAlv$1j%2b76MlkXD%hQES!Cv0-% z5?B!pjre3f5)(3sdNJCg)st(=-mo`dknB>9TZkqb)!VC&Ef$OjP~!N zUL*5Hqm6F-3Dl-vbV`58@`*8EuDkv%2FLpdPXiMA9TE=XR){eI#iP{2WnZ4zgTjgd zA0o`&WzFQgd+${%;8IaZitlc)BW)$(=^zopqyGCF=-aVI?2j)#nRH{>@q+p0G`O#rNMg-;n4~Q+Ppco zoI`wF$Ipg#Y;p}YLd;5uxj8DsU1m0GN1ck11mDpg&>3Iy>+XPl5%XNibT>BaXi!e{hHI!M3sZHy-M{uN1PW$ zus(OUSR1;ZTtD3_cj9m2nmnB!c8>4?>=UGGr^_9^)-AnJJomAn&>B4eZe|JjjIm_v z^SR9lda7hVi#64Ql|McKx`#p(z{GaAyfJ*(KyZi=Dpj=rn4;Prh5q_S^oW(hE#Iq? zs#*m&m+M{xaU1|abRchi*-;mzL;CmNJ)N4ANC(k~*VbQ2_Mh^y5Yq`t>p7AHa5Pj- ztGDsC1g=mV2cqMGB%>(L2%;Tx_r5`Vqs3#kny?Ip*-mesdR(kMkszb%DqS8)I1%qg zW8U*ayQQkg0dDj^s~vsOY52T`&W+2SSgoq$KUE>-s>J3p=VnHk{$^R+ju%Vf49q=Qky9$o4|>)qEK7@ zRP&YqL*a0(VqH;J5B^kJ3)1jir*C`?Sx$bxmJ$shPUWR323MNp#r5%1gkBqJYC{Rk ztw3H#V%J4RMJtpYn{5E+4+SO`qjs6E7d00Uthhk)w>Jkgew@CZE0| zEGDA%3UEZ|xF~AIGYY1|eKQ}6#_V6cx}klDCdP1nh0x_K z-Nl~OnAor32rC~5_QDk53WeQ`?R@Iuc>_WP9j8zLk~L}$17GECXFcs!;9uGfgVJaF zO9!RTo!Vrtr?6M~ z7p=>iEmO{tx>Ylfr?6pi{Hvv`Yx2`{g;qZpGt|M+Z*)HB{>dEZR|*VP_lUazHsm+COIY};NtKJJubRRQl?lot zK;qJ_fA3ktMCWFNMVFcY6{BdqzPAYDHmU8EQ_DgX)(Q zHGUSnSG^xZNGlDn`pBNgquNG($C`ZpXrY`aVAgv~dh+`-?JJNB;kJYL`e~L{g5FT8 zLF58BVN)H1Fm=d_EL0+8^kefsf?=a|BPOLD4!d2TnS@ZT0e%@@?dk$4b(xlaX|3M* zypaMQI3Pm5QG}LZ56ajdJ}fe^X+vHK?Co~uji|;z!hW+WbPyRObz@5eIkx2!xL<|w zCDryx{q1}Y5YyA^S1$NsT-f>29j}xK%WPinKFjuM5$s^IFv@beojo-ex8mI8lae0Y;D3H;Ol!Mw8aC`ZVJ`5M-o5TDUQ3EXGa z$mh8Hwg`!wI6pns8i>Ne)&%CwtL`bS)xA(h(^d?B>C#~&xuDp7=`lMC2_&+j4-Euz z>-x_COV)}pHYOO3w;2ukqTXQC6SDy8uZf&LudF6Qr*F?IY(Uk*B#wfr_9}gsIbtpC zsvX8lTYXO65yj-%4!dX_2MuG3HYA8%v-)@Eaup~#XYk+utCtAw5KqOU6hG0`@IB*vW%1u`O{t#LRfSzNxe+ST?0Pc?I4PX+Lt z;tY&yljWoi`vnpT^LPcn!$GU2+74{r8V0?JFH%0C$s6+Uf+}%HdbT64%}jw)?yrq; zM$XEnfqT9I?Qp*vgxh|m)@e!Fc;OmlkIGZzH{5nKjj|Wz<=^2QxRkGW@_m zF53mi;W>7wf>z>tLxT$A#rFHmfl?n5*$kTap$sGG?Fw8LIls4Mk3!s$Z5%$O%E;v3 zs7s_0@6SL744qUX)z_B}Bm3I|?q1bWt!e{4VCdS=dZ5{xOn1o?Tf2HHdhGhiQ|`5; zLW<;7y*T|SXYqP{CFu7YwC^%h*W)c=caV@#dg8ILk?pvPM=elTh^viGXL&sLg-#(s zK{PZuIRJ;f*8lTYkx-Q$5*3w$13Npb?CxZh%UTqLs9vgbgvZWQoeNayP=NU>#z702 zYhV9e?3jqo+yyz4m?ht=>SpZsLu`_F-8qcI>G|Is3gpFf!3by0MA4DB* z;hC8NpVL)kH&^XZFPH!F^KQ8M&m+RvN?>;c+Ceq}aFb2Rb#2AgB~}37&e7Dg3{7q^ zW(g!hd7VD(LQL(GI$w7Vsr=P^i|(oDomfvo&8Z57QjGq3>=}}=S`bKZgYqZBTv$fT zAOKei=nX!G2Cstpak(MxUxX@EyEt8eI}Q2qi1o^q%U<63^BJlYM;u6vS4S+p{3$DQUwSGcC4VIMjew4ux57SOTn zNp}(Sqlw%6q&_N>R(Cesf-%0Df-%sBKKHZvL8?Bum$h#?LEP24qI0hr%B-N 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⁣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 diff --git a/doc/testbenches_doc_resources/5g-ota-bench.tex b/doc/testbenches_doc_resources/5g-ota-bench.tex index c3a524fb0a..b7413af7db 100644 --- a/doc/testbenches_doc_resources/5g-ota-bench.tex +++ b/doc/testbenches_doc_resources/5g-ota-bench.tex @@ -16,7 +16,7 @@ \node[label=above:porcepix] (porcepix) {\includegraphics[width=1.2cm]{server}}; - \node[above right=1cm and 2cm of porcepix, label=above:asterix] (asterix) + \node[above right=-0.4cm and 2cm of porcepix, label=above:asterix] (asterix) {\includegraphics[width=1.2cm]{server}} edge (porcepix); \node[right=0.3cm of asterix, label=above:N310] (n310a) @@ -27,7 +27,7 @@ \node[above right=-0.1cm and 0.35cm of b78o.east] (anto2) {\includegraphics[width=0.3cm]{antenna}} edge (b78o); - \node[right=2cm of porcepix, label=above:obelix] (obelix) + \node[below right=-0.4cm and 2cm of porcepix, label=above:obelix] (obelix) {\includegraphics[width=1.2cm]{server}} edge (porcepix); \node[above right=-0.5cm and 0.3cm of obelix, label=above:N310] (n310o) @@ -45,7 +45,7 @@ \node[above right=-0.1cm and 0.35cm of b78o.east] (anto2) {\includegraphics[width=0.3cm]{antenna}} edge (b78o); - \node[above right=-1.3cm and 5.0cm of n310a.east, label=above:RM500Q-GL] (quectel) + \node[above right=-0.5cm and 5.0cm of n310o.east, label=above:RM500Q-GL] (quectel) {\includegraphics[height=1.2cm]{quectel}}; \node[above left=-0.1cm and 0.8cm of quectel.west] (aq2) {\includegraphics[width=0.3cm]{antenna}} edge (quectel); @@ -59,10 +59,5 @@ {\includegraphics[width=1.2cm]{server}} edge (quectel); - \node[above right=-0.3cm and 5.0cm of x310o.east, label=above:amariue] (amariue) - {\includegraphics[height=1.2cm]{server}}; - \node[left=0.8cm of amariue.west] (aa1) - {\includegraphics[width=0.3cm]{antenna}} edge (amariue); - \end{tikzpicture} \end{document} From 7fb242d33e05b84ebefb95e217772586d7b1e0db Mon Sep 17 00:00:00 2001 From: Robert Schmidt Date: Mon, 13 Mar 2023 18:48:27 +0100 Subject: [PATCH 48/89] Send NAS_CONN_RELEASE_IND to correct NAS UE task from RRC NR UE --- openair2/RRC/NR_UE/rrc_UE.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openair2/RRC/NR_UE/rrc_UE.c b/openair2/RRC/NR_UE/rrc_UE.c index 4bc1724fc7..65d91e0b9a 100644 --- a/openair2/RRC/NR_UE/rrc_UE.c +++ b/openair2/RRC/NR_UE/rrc_UE.c @@ -2288,7 +2288,7 @@ nr_rrc_ue_establish_srb2( NR_RRCRelease_IEs__deprioritisationReq__deprioritisationType_frequency; } - itti_send_msg_to_task(TASK_NAS_UE, ctxt_pP->instance, msg_p); + itti_send_msg_to_task(TASK_NAS_NRUE, ctxt_pP->instance, msg_p); break; case NR_DL_DCCH_MessageType__c1_PR_ueCapabilityEnquiry: LOG_I(NR_RRC, "[UE %d] Received Capability Enquiry (gNB %d)\n", ctxt_pP->module_id,gNB_indexP); From 8e6554bdf3f750035a211bafed25f0024fcbd918 Mon Sep 17 00:00:00 2001 From: Robert Schmidt Date: Mon, 13 Mar 2023 18:49:08 +0100 Subject: [PATCH 49/89] Print log on receiving deregistration accept, on CONN Release stop nr-uesoftmodem --- openair3/NAS/NR_UE/nr_nas_msg_sim.c | 7 +++++-- openair3/NAS/NR_UE/nr_nas_msg_sim.h | 1 + 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/openair3/NAS/NR_UE/nr_nas_msg_sim.c b/openair3/NAS/NR_UE/nr_nas_msg_sim.c index 6380713a19..e411c53994 100644 --- a/openair3/NAS/NR_UE/nr_nas_msg_sim.c +++ b/openair3/NAS/NR_UE/nr_nas_msg_sim.c @@ -1001,7 +1001,7 @@ void *nas_nrue_task(void *args_p) case NAS_CONN_RELEASE_IND: LOG_I(NAS, "[UE %ld] Received %s: cause %u\n", instance, ITTI_MSG_NAME (msg_p), NAS_CONN_RELEASE_IND (msg_p).cause); - + itti_wait_tasks_unblock(); /* will unblock ITTI to stop nr-uesoftmodem */ break; case NAS_UPLINK_DATA_CNF: @@ -1051,6 +1051,9 @@ void *nas_nrue_task(void *args_p) case FGS_DOWNLINK_NAS_TRANSPORT: decodeDownlinkNASTransport(&initialNasMsg, pdu_buffer); break; + case FGS_DEREGISTRATION_ACCEPT: + LOG_I(NAS, "received deregistration accept\n"); + break; case FGS_PDU_SESSION_ESTABLISHMENT_ACC: { uint8_t offset = 0; @@ -1082,7 +1085,7 @@ void *nas_nrue_task(void *args_p) } break; default: - LOG_W(NR_RRC,"unknow message type %d\n",msg_type); + LOG_W(NR_RRC,"unknown message type %d\n",msg_type); break; } diff --git a/openair3/NAS/NR_UE/nr_nas_msg_sim.h b/openair3/NAS/NR_UE/nr_nas_msg_sim.h index fbf071562d..fbf0362949 100644 --- a/openair3/NAS/NR_UE/nr_nas_msg_sim.h +++ b/openair3/NAS/NR_UE/nr_nas_msg_sim.h @@ -52,6 +52,7 @@ #define REGISTRATION_ACCEPT 0b01000010 /* 66 = 0x42 */ #define REGISTRATION_COMPLETE 0b01000011 /* 67 = 0x43 */ #define FGS_DEREGISTRATION_REQUEST_UE_ORIGINATING 0b01000101 +#define FGS_DEREGISTRATION_ACCEPT 0b01000110 #define FGS_AUTHENTICATION_REQUEST 0b01010110 /* 86 = 0x56 */ #define FGS_AUTHENTICATION_RESPONSE 0b01010111 /* 87 = 0x57 */ #define FGS_IDENTITY_REQUEST 0b01011011 /* 91 = 0x5b */ From 0631c04fbadd5d627f101f4ced305a045b7b7929 Mon Sep 17 00:00:00 2001 From: Robert Schmidt Date: Tue, 21 Mar 2023 08:47:12 +0100 Subject: [PATCH 50/89] Revert 5G RFsim timeout changes for tests with CN: deregistration triggers UE disconnect --- ci-scripts/yaml_files/5g_f1_rfsimulator/docker-compose.yaml | 2 +- ci-scripts/yaml_files/5g_fdd_rfsimulator/docker-compose.yaml | 2 +- ci-scripts/yaml_files/5g_rfsimulator/docker-compose.yaml | 2 +- ci-scripts/yaml_files/5g_rfsimulator_24prb/docker-compose.yaml | 2 +- ci-scripts/yaml_files/5g_rfsimulator_2x2/docker-compose.yaml | 2 +- ci-scripts/yaml_files/5g_rfsimulator_e1/docker-compose.yaml | 2 +- .../yaml_files/5g_rfsimulator_u0_25prb/docker-compose.yaml | 2 +- 7 files changed, 7 insertions(+), 7 deletions(-) diff --git a/ci-scripts/yaml_files/5g_f1_rfsimulator/docker-compose.yaml b/ci-scripts/yaml_files/5g_f1_rfsimulator/docker-compose.yaml index 4a2195f37d..b940794425 100644 --- a/ci-scripts/yaml_files/5g_f1_rfsimulator/docker-compose.yaml +++ b/ci-scripts/yaml_files/5g_f1_rfsimulator/docker-compose.yaml @@ -219,7 +219,7 @@ services: F1_DU_IP_ADDRESS: 192.168.71.142 F1_CU_D_PORT: 2153 F1_DU_D_PORT: 2153 - USE_ADDITIONAL_OPTIONS: --sa --rfsim --rfsimulator.wait_timeout 20 --log_config.global_log_options level,nocolor,time + USE_ADDITIONAL_OPTIONS: --sa --rfsim --log_config.global_log_options level,nocolor,time depends_on: - oai-cu networks: diff --git a/ci-scripts/yaml_files/5g_fdd_rfsimulator/docker-compose.yaml b/ci-scripts/yaml_files/5g_fdd_rfsimulator/docker-compose.yaml index 38a21e4dd0..404860d390 100644 --- a/ci-scripts/yaml_files/5g_fdd_rfsimulator/docker-compose.yaml +++ b/ci-scripts/yaml_files/5g_fdd_rfsimulator/docker-compose.yaml @@ -180,7 +180,7 @@ services: GNB_NGA_IP_ADDRESS: 192.168.71.140 GNB_NGU_IF_NAME: eth0 GNB_NGU_IP_ADDRESS: 192.168.71.140 - USE_ADDITIONAL_OPTIONS: --sa -E --rfsim --rfsimulator.wait_timeout 20 --log_config.global_log_options level,nocolor,time + USE_ADDITIONAL_OPTIONS: --sa -E --rfsim --log_config.global_log_options level,nocolor,time depends_on: - oai-ext-dn networks: diff --git a/ci-scripts/yaml_files/5g_rfsimulator/docker-compose.yaml b/ci-scripts/yaml_files/5g_rfsimulator/docker-compose.yaml index 98bf516ae0..f1bf2e0b54 100644 --- a/ci-scripts/yaml_files/5g_rfsimulator/docker-compose.yaml +++ b/ci-scripts/yaml_files/5g_rfsimulator/docker-compose.yaml @@ -181,7 +181,7 @@ services: GNB_NGU_IF_NAME: eth0 GNB_NGU_IP_ADDRESS: 192.168.71.140 SDR_ADDRS: serial=XXXXXXX - USE_ADDITIONAL_OPTIONS: --sa -E --rfsim --rfsimulator.wait_timeout 20 --log_config.global_log_options level,nocolor,time + USE_ADDITIONAL_OPTIONS: --sa -E --rfsim --log_config.global_log_options level,nocolor,time depends_on: - oai-ext-dn networks: diff --git a/ci-scripts/yaml_files/5g_rfsimulator_24prb/docker-compose.yaml b/ci-scripts/yaml_files/5g_rfsimulator_24prb/docker-compose.yaml index 0009277e01..32f769ec3a 100644 --- a/ci-scripts/yaml_files/5g_rfsimulator_24prb/docker-compose.yaml +++ b/ci-scripts/yaml_files/5g_rfsimulator_24prb/docker-compose.yaml @@ -169,7 +169,7 @@ services: environment: RFSIMULATOR: server USE_VOLUMED_CONF: 'yes' - USE_ADDITIONAL_OPTIONS: --sa --rfsim --rfsimulator.wait_timeout 20 --log_config.global_log_options level,nocolor,time + USE_ADDITIONAL_OPTIONS: --sa --rfsim --log_config.global_log_options level,nocolor,time depends_on: - oai-ext-dn networks: diff --git a/ci-scripts/yaml_files/5g_rfsimulator_2x2/docker-compose.yaml b/ci-scripts/yaml_files/5g_rfsimulator_2x2/docker-compose.yaml index 5f55ae8877..5a62f8ba5c 100644 --- a/ci-scripts/yaml_files/5g_rfsimulator_2x2/docker-compose.yaml +++ b/ci-scripts/yaml_files/5g_rfsimulator_2x2/docker-compose.yaml @@ -169,7 +169,7 @@ services: environment: RFSIMULATOR: server USE_VOLUMED_CONF: 'yes' - USE_ADDITIONAL_OPTIONS: --sa --rfsim --rfsimulator.wait_timeout 20 --log_config.global_log_options level,nocolor,time + USE_ADDITIONAL_OPTIONS: --sa --rfsim --log_config.global_log_options level,nocolor,time depends_on: - oai-ext-dn networks: diff --git a/ci-scripts/yaml_files/5g_rfsimulator_e1/docker-compose.yaml b/ci-scripts/yaml_files/5g_rfsimulator_e1/docker-compose.yaml index 26e01febb7..f29f9e1ac6 100644 --- a/ci-scripts/yaml_files/5g_rfsimulator_e1/docker-compose.yaml +++ b/ci-scripts/yaml_files/5g_rfsimulator_e1/docker-compose.yaml @@ -216,7 +216,7 @@ services: environment: RFSIMULATOR: server USE_VOLUMED_CONF: 'yes' - USE_ADDITIONAL_OPTIONS: --sa --rfsim --rfsimulator.wait_timeout 20 --log_config.global_log_options level,nocolor,time + USE_ADDITIONAL_OPTIONS: --sa --rfsim --log_config.global_log_options level,nocolor,time networks: ran_net: ipv4_address: 192.168.72.142 diff --git a/ci-scripts/yaml_files/5g_rfsimulator_u0_25prb/docker-compose.yaml b/ci-scripts/yaml_files/5g_rfsimulator_u0_25prb/docker-compose.yaml index 93c18cdfd9..1282edc470 100644 --- a/ci-scripts/yaml_files/5g_rfsimulator_u0_25prb/docker-compose.yaml +++ b/ci-scripts/yaml_files/5g_rfsimulator_u0_25prb/docker-compose.yaml @@ -169,7 +169,7 @@ services: environment: RFSIMULATOR: server USE_VOLUMED_CONF: 'yes' - USE_ADDITIONAL_OPTIONS: --sa --rfsim --rfsimulator.wait_timeout 20 --log_config.global_log_options level,nocolor,time + USE_ADDITIONAL_OPTIONS: --sa --rfsim --log_config.global_log_options level,nocolor,time depends_on: - oai-ext-dn networks: From 06436655fffc9a753c9b80642f5795a68fc59077 Mon Sep 17 00:00:00 2001 From: Robert Schmidt Date: Fri, 31 Mar 2023 12:44:19 +0200 Subject: [PATCH 51/89] Bugfix: Forward F1 DL RRC Message transfer on correct SRB --- openair2/LAYER2/nr_pdcp/nr_pdcp_oai_api.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openair2/LAYER2/nr_pdcp/nr_pdcp_oai_api.c b/openair2/LAYER2/nr_pdcp/nr_pdcp_oai_api.c index e4bb7bb4f5..377da3a68d 100644 --- a/openair2/LAYER2/nr_pdcp/nr_pdcp_oai_api.c +++ b/openair2/LAYER2/nr_pdcp/nr_pdcp_oai_api.c @@ -774,7 +774,7 @@ srb_found: //printf("\n"); if ((RC.nrrrc == NULL) || (!NODE_IS_CU(node_type))) { if (entity->is_gnb) { - f1ap_dl_rrc_message_t dl_rrc = {.old_gNB_DU_ue_id = 0xFFFFFF, .rrc_container = (uint8_t *)buf, .rrc_container_length = size, .rnti = ue->rntiMaybeUEid, .srb_id = DCCH}; + f1ap_dl_rrc_message_t dl_rrc = {.old_gNB_DU_ue_id = 0xFFFFFF, .rrc_container = (uint8_t *)buf, .rrc_container_length = size, .rnti = ue->rntiMaybeUEid, .srb_id = srb_id}; gNB_RRC_INST *rrc = RC.nrrrc[0]; rrc->mac_rrc.dl_rrc_message_transfer(0, &dl_rrc); } else { // UE From 25b3aaf23000110b50a92fe8803a73b6ef0af707 Mon Sep 17 00:00:00 2001 From: Robert Schmidt Date: Fri, 31 Mar 2023 12:44:47 +0200 Subject: [PATCH 52/89] NR UE RRC: DCCH messages can be on SRB1 or SRB2 --- openair2/RRC/NR_UE/rrc_UE.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/openair2/RRC/NR_UE/rrc_UE.c b/openair2/RRC/NR_UE/rrc_UE.c index 65d91e0b9a..6384cbec4e 100644 --- a/openair2/RRC/NR_UE/rrc_UE.c +++ b/openair2/RRC/NR_UE/rrc_UE.c @@ -2229,11 +2229,9 @@ nr_rrc_ue_establish_srb2( NR_DL_DCCH_Message_t *dl_dcch_msg = NULL; MessageDef *msg_p; - if (Srb_id != 1) { + if (Srb_id != 1 && Srb_id != 2) { LOG_E(NR_RRC,"[UE %d] Frame %d: Received message on DL-DCCH (SRB%ld), should not have ...\n", ctxt_pP->module_id, ctxt_pP->frame, Srb_id); - } else { - LOG_D(NR_RRC, "Received message on SRB%ld\n", Srb_id); } LOG_D(NR_RRC, "Decoding DL-DCCH Message\n"); From a6149386dc54a384cca2a2ec008b4eab44ebc118 Mon Sep 17 00:00:00 2001 From: Robert Schmidt Date: Fri, 31 Mar 2023 12:47:23 +0200 Subject: [PATCH 53/89] NR UE MAC: Decode TBS and print warning if unknown LCID --- openair2/LAYER2/NR_MAC_UE/nr_ue_procedures.c | 53 +++++++------------- 1 file changed, 19 insertions(+), 34 deletions(-) diff --git a/openair2/LAYER2/NR_MAC_UE/nr_ue_procedures.c b/openair2/LAYER2/NR_MAC_UE/nr_ue_procedures.c index 4190331a3e..92efadf9aa 100644 --- a/openair2/LAYER2/NR_MAC_UE/nr_ue_procedures.c +++ b/openair2/LAYER2/NR_MAC_UE/nr_ue_procedures.c @@ -3274,41 +3274,26 @@ void nr_ue_process_mac_pdu(nr_downlink_indication_t *dl_info, // check if LCID is valid at current time. case DL_SCH_LCID_DCCH1: // check if LCID is valid at current time. + case DL_SCH_LCID_DTCH ... (DL_SCH_LCID_DTCH + 28): + if (!get_mac_len(pduP, pdu_len, &mac_len, &mac_subheader_len)) + return; + LOG_D(NR_MAC, "%4d.%2d : DLSCH -> LCID %d %d bytes\n", frameP, slot, rx_lcid, mac_len); + + mac_rlc_data_ind(module_idP, + mac->crnti, + gNB_index, + frameP, + ENB_FLAG_NO, + MBMS_FLAG_NO, + rx_lcid, + (char *)(pduP + mac_subheader_len), + mac_len, + 1, + NULL); + break; default: - { - if (!get_mac_len(pduP, pdu_len, &mac_len, &mac_subheader_len)) - return; - LOG_D(NR_MAC, "[UE %d] %4d.%2d : DLSCH -> DL-DTCH %d (gNB %d, %d bytes)\n", module_idP, frameP, slot, rx_lcid, gNB_index, mac_len); - - #if defined(ENABLE_MAC_PAYLOAD_DEBUG) - LOG_T(MAC, "[UE %d] First 32 bytes of DLSCH : \n", module_idP); - - for (i = 0; i < 32; i++) - LOG_T(MAC, "%x.", (pduP + mac_subheader_len)[i]); - - LOG_T(MAC, "\n"); - #endif - - if (rx_lcid < NB_RB_MAX && rx_lcid >= DL_SCH_LCID_DCCH) { - - mac_rlc_data_ind(module_idP, - mac->crnti, - gNB_index, - frameP, - ENB_FLAG_NO, - MBMS_FLAG_NO, - rx_lcid, - (char *) (pduP + mac_subheader_len), - mac_len, - 1, - NULL); - } else { - LOG_E(MAC, "[UE %d] Frame %d : unknown LCID %d (gNB %d)\n", module_idP, frameP, rx_lcid, gNB_index); - } - - - break; - } + LOG_W(MAC, "unknown lcid %02x\n", rx_lcid); + break; } pduP += ( mac_subheader_len + mac_len ); pdu_len -= ( mac_subheader_len + mac_len ); From c4a9f0ea03dbe63100583207250ca1c985a8550e Mon Sep 17 00:00:00 2001 From: Robert Schmidt Date: Fri, 31 Mar 2023 12:58:58 +0200 Subject: [PATCH 54/89] gNB RRC: Shorten time till UE is removed for RRCRelease --- openair2/RRC/NR/rrc_gNB.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/openair2/RRC/NR/rrc_gNB.c b/openair2/RRC/NR/rrc_gNB.c index a4f57b0fae..33316fb417 100644 --- a/openair2/RRC/NR/rrc_gNB.c +++ b/openair2/RRC/NR/rrc_gNB.c @@ -3687,6 +3687,15 @@ rrc_gNB_generate_RRCRelease( rrc_gNB_send_NGAP_UE_CONTEXT_RELEASE_COMPLETE(ctxt_pP->instance, ue_context_pP->ue_context.gNB_ue_ngap_id); ue_context_pP->ue_context.ue_release_timer_rrc = 1; + /* TODO: 38.331 says for RRC Release that the UE should release everything + * after 60ms or if lower layers acked receipt of release. Hence, from the + * gNB POV, we can free the UE's RRC context as soon as we sent the msg. + * Currently, without the F1 interface, the ue_release timer expiration also + * triggers MAC, so we give it some time. If we send an F1 UE Context release + * message, we can free it immediately. The MAC should release it after these + * 60ms, or the ack of the DLSCH transmission. */ + ue_context_pP->ue_context.ue_release_timer_thres_rrc = 5; + LOG_I(RRC, "delaying UE %ld context removal by 5ms\n", ctxt_pP->rntiMaybeUEid); if (NODE_IS_CU(RC.nrrrc[ctxt_pP->module_id]->node_type)) { uint8_t *message_buffer = itti_malloc (TASK_RRC_GNB, TASK_CU_F1, size); From 5c333cca65bc7c19302aa4d5bc59b36081cbec76 Mon Sep 17 00:00:00 2001 From: francescomani Date: Thu, 23 Mar 2023 17:46:50 +0100 Subject: [PATCH 55/89] move radio link failure detection on pbch at MAC --- executables/nr-ue.c | 5 - .../nfapi/public_inc/fapi_nr_ue_interface.h | 3 +- openair1/PHY/INIT/nr_init_ue.c | 3 - .../PHY/NR_UE_TRANSPORT/nr_initial_sync.c | 14 +-- openair1/PHY/NR_UE_TRANSPORT/nr_pbch.c | 57 +++++----- .../NR_UE_TRANSPORT/nr_transport_proto_ue.h | 1 - openair1/PHY/defs_nr_UE.h | 14 --- openair1/SCHED_NR_UE/defs.h | 2 +- openair1/SCHED_NR_UE/phy_procedures_nr_ue.c | 61 ++++------- openair1/SIMULATION/NR_PHY/pbchsim.c | 5 +- openair2/LAYER2/NR_MAC_UE/mac_defs.h | 5 +- openair2/LAYER2/NR_MAC_UE/main_ue_nr.c | 1 + openair2/LAYER2/NR_MAC_UE/nr_ra_procedures.c | 4 +- openair2/LAYER2/NR_MAC_UE/nr_ue_procedures.c | 6 +- openair2/NR_UE_PHY_INTERFACE/NR_IF_Module.c | 103 ++++++++++-------- 15 files changed, 124 insertions(+), 160 deletions(-) diff --git a/executables/nr-ue.c b/executables/nr-ue.c index 10509a2870..1dab8c8078 100644 --- a/executables/nr-ue.c +++ b/executables/nr-ue.c @@ -755,7 +755,6 @@ void *UE_thread(void *arg) { fapi_nr_config_request_t *cfg = &UE->nrUE_config; AssertFatal(0== openair0_device_load(&(UE->rfdevice), &openair0_cfg[0]), ""); UE->rfdevice.host_type = RAU_HOST; - UE->lost_sync = 0; UE->is_synchronized = 0; AssertFatal(UE->rfdevice.trx_start_func(&UE->rfdevice) == 0, "Could not start the device\n"); @@ -776,10 +775,6 @@ void *UE_thread(void *arg) { int absolute_slot=0, decoded_frame_rx=INT_MAX, trashed_frames=0; while (!oai_exit) { - if (UE->lost_sync) { - UE->is_synchronized = 0; - UE->lost_sync = 0; - } if (syncRunning) { notifiedFIFO_elt_t *res=tryPullTpool(&nf,&(get_nrUE_params()->Tpool)); diff --git a/nfapi/open-nFAPI/nfapi/public_inc/fapi_nr_ue_interface.h b/nfapi/open-nFAPI/nfapi/public_inc/fapi_nr_ue_interface.h index d429bf43ba..544b175b7c 100644 --- a/nfapi/open-nFAPI/nfapi/public_inc/fapi_nr_ue_interface.h +++ b/nfapi/open-nFAPI/nfapi/public_inc/fapi_nr_ue_interface.h @@ -116,7 +116,8 @@ typedef struct { } fapi_nr_pdsch_pdu_t; typedef struct { - uint8_t* pdu; // 3bytes + bool decoded_pdu; + uint8_t pdu[3]; uint8_t additional_bits; uint8_t ssb_index; uint8_t ssb_length; diff --git a/openair1/PHY/INIT/nr_init_ue.c b/openair1/PHY/INIT/nr_init_ue.c index 32e5647b69..55e1bca868 100644 --- a/openair1/PHY/INIT/nr_init_ue.c +++ b/openair1/PHY/INIT/nr_init_ue.c @@ -202,7 +202,6 @@ int init_nr_ue_signal(PHY_VARS_NR_UE *ue, int nb_connected_gNB) // create shortcuts NR_DL_FRAME_PARMS *const fp = &ue->frame_parms; NR_UE_COMMON *const common_vars = &ue->common_vars; - NR_UE_PBCH **const pbch_vars = ue->pbch_vars; NR_UE_PRACH **const prach_vars = ue->prach_vars; NR_UE_CSI_IM **const csiim_vars = ue->csiim_vars; NR_UE_CSI_RS **const csirs_vars = ue->csirs_vars; @@ -357,7 +356,6 @@ int init_nr_ue_signal(PHY_VARS_NR_UE *ue, int nb_connected_gNB) // DLSCH for (gNB_id = 0; gNB_id < ue->n_connected_gNB; gNB_id++) { prach_vars[gNB_id] = (NR_UE_PRACH *)malloc16_clear(sizeof(NR_UE_PRACH)); - pbch_vars[gNB_id] = (NR_UE_PBCH *)malloc16_clear(sizeof(NR_UE_PBCH)); csiim_vars[gNB_id] = (NR_UE_CSI_IM *)malloc16_clear(sizeof(NR_UE_CSI_IM)); csirs_vars[gNB_id] = (NR_UE_CSI_RS *)malloc16_clear(sizeof(NR_UE_CSI_RS)); srs_vars[gNB_id] = (NR_UE_SRS *)malloc16_clear(sizeof(NR_UE_SRS)); @@ -476,7 +474,6 @@ void term_nr_ue_signal(PHY_VARS_NR_UE *ue, int nb_connected_gNB) free_and_zero(ue->csirs_vars[gNB_id]); free_and_zero(ue->srs_vars[gNB_id]); - free_and_zero(ue->pbch_vars[gNB_id]); free_and_zero(ue->prach_vars[gNB_id]); } diff --git a/openair1/PHY/NR_UE_TRANSPORT/nr_initial_sync.c b/openair1/PHY/NR_UE_TRANSPORT/nr_initial_sync.c index 061eede6dd..da99ba7405 100644 --- a/openair1/PHY/NR_UE_TRANSPORT/nr_initial_sync.c +++ b/openair1/PHY/NR_UE_TRANSPORT/nr_initial_sync.c @@ -155,12 +155,11 @@ int nr_pbch_detection(UE_nr_rxtx_proc_t * proc, PHY_VARS_NR_UE *ue, int pbch_ini proc,i,i-pbch_initial_symbol,temp_ptr->i_ssb,temp_ptr->n_hf,rxdataF); stop_meas(&ue->dlsch_channel_estimation_stats); - fapiPbch_t result; + fapiPbch_t result = {0}; ret = nr_rx_pbch(ue, proc, estimateSz, dl_ch_estimates, - ue->pbch_vars[0], frame_parms, temp_ptr->i_ssb, SISO, @@ -188,11 +187,6 @@ int nr_pbch_detection(UE_nr_rxtx_proc_t * proc, PHY_VARS_NR_UE *ue, int pbch_ini // openair_daq_vars.dlsch_transmission_mode = (pbch_tx_ant>1) ? 2 : 1; - // flip byte endian on 24-bits for MIB - // dummy = ue->pbch_vars[0]->decoded_output[0]; - // ue->pbch_vars[0]->decoded_output[0] = ue->pbch_vars[0]->decoded_output[2]; - // ue->pbch_vars[0]->decoded_output[2] = dummy; - #ifdef DEBUG_INITIAL_SYNCH LOG_I(PHY,"[UE%d] Initial sync: pbch decoded sucessfully\n",ue->Mod_id); #endif @@ -447,8 +441,6 @@ int nr_initial_sync(UE_nr_rxtx_proc_t *proc, // send sync status to higher layers later when timing offset converge to target timing - ue->pbch_vars[0]->pdu_errors_conseq=0; - } @@ -466,10 +458,6 @@ int nr_initial_sync(UE_nr_rxtx_proc_t *proc, frame_parms->Nid_cell,frame_parms->frame_type); #endif - ue->pbch_vars[0]->pdu_errors_last=ue->pbch_vars[0]->pdu_errors; - ue->pbch_vars[0]->pdu_errors++; - ue->pbch_vars[0]->pdu_errors_conseq++; - } // gain control diff --git a/openair1/PHY/NR_UE_TRANSPORT/nr_pbch.c b/openair1/PHY/NR_UE_TRANSPORT/nr_pbch.c index 0c2149e997..3d6abc661e 100644 --- a/openair1/PHY/NR_UE_TRANSPORT/nr_pbch.c +++ b/openair1/PHY/NR_UE_TRANSPORT/nr_pbch.c @@ -302,8 +302,7 @@ void nr_pbch_detection_mrc(NR_DL_FRAME_PARMS *frame_parms, #endif } -static void nr_pbch_unscrambling(NR_UE_PBCH *pbch, - int16_t *demod_pbch_e, +static void nr_pbch_unscrambling(int16_t *demod_pbch_e, uint16_t Nid, uint8_t nushift, uint16_t M, @@ -311,7 +310,8 @@ static void nr_pbch_unscrambling(NR_UE_PBCH *pbch, uint8_t bitwise, uint32_t unscrambling_mask, uint32_t pbch_a_prime, - uint32_t *pbch_a_interleaved) { + uint32_t *pbch_a_interleaved) +{ uint8_t reset, offset; uint32_t x1 = 0, x2 = 0, s = 0; uint8_t k=0; @@ -384,17 +384,17 @@ unsigned char sign(int8_t x) { uint8_t pbch_deinterleaving_pattern[32] = {28,0,31,30,7,29,25,27,5,8,24,9,10,11,12,13,1,4,3,14,15,16,17,2,26,18,19,20,21,22,6,23}; -int nr_rx_pbch( PHY_VARS_NR_UE *ue, - UE_nr_rxtx_proc_t *proc, - int estimateSz, struct complex16 dl_ch_estimates [][estimateSz], - NR_UE_PBCH *nr_ue_pbch_vars, - NR_DL_FRAME_PARMS *frame_parms, - uint8_t i_ssb, - MIMO_mode_t mimo_mode, - nr_phy_data_t *phy_data, - fapiPbch_t *result, - c16_t rxdataF[][ue->frame_parms.samples_per_slot_wCP]) { - +int nr_rx_pbch(PHY_VARS_NR_UE *ue, + UE_nr_rxtx_proc_t *proc, + int estimateSz, + struct complex16 dl_ch_estimates [][estimateSz], + NR_DL_FRAME_PARMS *frame_parms, + uint8_t i_ssb, + MIMO_mode_t mimo_mode, + nr_phy_data_t *phy_data, + fapiPbch_t *result, + c16_t rxdataF[][ue->frame_parms.samples_per_slot_wCP]) +{ int max_h=0; int symbol; //uint8_t pbch_a[64]; @@ -456,7 +456,7 @@ int nr_rx_pbch( PHY_VARS_NR_UE *ue, } #ifdef DEBUG_PBCH - LOG_I(PHY,"[PHY] PBCH log2_maxh = %d (%d)\n",nr_ue_pbch_vars->log2_maxh,max_h); + LOG_I(PHY,"[PHY] PBCH log2_maxh = %d (%d)\n", log2_maxh, max_h); #endif __attribute__ ((aligned(32))) struct complex16 rxdataF_comp[frame_parms->nb_antennas_rx][PBCH_MAX_RE_PER_SYMBOL]; nr_pbch_channel_compensation(rxdataF_ext, @@ -464,7 +464,7 @@ int nr_rx_pbch( PHY_VARS_NR_UE *ue, nb_re, rxdataF_comp, frame_parms, - log2_maxh); // log2_maxh+I0_shift + log2_maxh); // log2_maxh+I0_shift /*if (frame_parms->nb_antennas_rx > 1) pbch_detection_mrc(frame_parms, @@ -504,16 +504,27 @@ int nr_rx_pbch( PHY_VARS_NR_UE *ue, uint32_t unscrambling_mask = (Lmax==64)?0x100006D:0x1000041; uint32_t pbch_a_interleaved=0; uint32_t pbch_a_prime=0; - nr_pbch_unscrambling(nr_ue_pbch_vars, pbch_e_rx, frame_parms->Nid_cell, nushift, M, NR_POLAR_PBCH_E, + nr_pbch_unscrambling(pbch_e_rx, frame_parms->Nid_cell, nushift, M, NR_POLAR_PBCH_E, 0, 0, pbch_a_prime, &pbch_a_interleaved); //polar decoding de-rate matching uint64_t tmp=0; decoderState = polar_decoder_int16(pbch_e_rx,(uint64_t *)&tmp,0, NR_POLAR_PBCH_MESSAGE_TYPE, NR_POLAR_PBCH_PAYLOAD_BITS, NR_POLAR_PBCH_AGGREGATION_LEVEL); - pbch_a_prime=tmp; - if(decoderState) - return(decoderState); + pbch_a_prime = tmp; + nr_downlink_indication_t dl_indication; + fapi_nr_rx_indication_t *rx_ind = calloc(sizeof(*rx_ind), 1); + uint16_t number_pdus = 1; + + if(decoderState) { + nr_fill_dl_indication(&dl_indication, NULL, rx_ind, proc, ue, phy_data); + nr_fill_rx_indication(rx_ind, FAPI_NR_RX_PDU_TYPE_SSB, ue, NULL, NULL, number_pdus, proc, NULL); + if (ue->if_inst && ue->if_inst->dl_indication) + ue->if_inst->dl_indication(&dl_indication, NULL); + else + free(rx_ind); // dl_indication would free(), so free() here if not called + return(decoderState); + } // printf("polar decoder output 0x%08x\n",pbch_a_prime); // Decoder reversal uint32_t a_reversed=0; @@ -526,7 +537,7 @@ int nr_rx_pbch( PHY_VARS_NR_UE *ue, M = (Lmax == 64)? (NR_POLAR_PBCH_PAYLOAD_BITS - 6) : (NR_POLAR_PBCH_PAYLOAD_BITS - 3); nushift = ((pbch_a_prime>>24)&1) ^ (((pbch_a_prime>>6)&1)<<1); pbch_a_interleaved=0; - nr_pbch_unscrambling(nr_ue_pbch_vars, pbch_e_rx, frame_parms->Nid_cell, nushift, M, NR_POLAR_PBCH_PAYLOAD_BITS, + nr_pbch_unscrambling(pbch_e_rx, frame_parms->Nid_cell, nushift, M, NR_POLAR_PBCH_PAYLOAD_BITS, 1, unscrambling_mask, pbch_a_prime, &pbch_a_interleaved); //printf("nushift %d sfn 3rd %d 2nd %d", nushift,((pbch_a_prime>>6)&1), ((pbch_a_prime>>24)&1) ); //payload deinterleaving @@ -577,10 +588,6 @@ int nr_rx_pbch( PHY_VARS_NR_UE *ue, } #endif - nr_downlink_indication_t dl_indication; - fapi_nr_rx_indication_t *rx_ind=calloc(sizeof(*rx_ind),1); - uint16_t number_pdus = 1; - nr_fill_dl_indication(&dl_indication, NULL, rx_ind, proc, ue, phy_data); nr_fill_rx_indication(rx_ind, FAPI_NR_RX_PDU_TYPE_SSB, ue, NULL, NULL, number_pdus, proc,(void *)result); 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 0d6c334c45..b90ba5d20e 100644 --- a/openair1/PHY/NR_UE_TRANSPORT/nr_transport_proto_ue.h +++ b/openair1/PHY/NR_UE_TRANSPORT/nr_transport_proto_ue.h @@ -285,7 +285,6 @@ int nr_rx_pbch(PHY_VARS_NR_UE *ue, UE_nr_rxtx_proc_t *proc, const int estimateSz, struct complex16 dl_ch_estimates[][estimateSz], - NR_UE_PBCH *nr_ue_pbch_vars, NR_DL_FRAME_PARMS *frame_parms, uint8_t i_ssb, MIMO_mode_t mimo_mode, diff --git a/openair1/PHY/defs_nr_UE.h b/openair1/PHY/defs_nr_UE.h index 9ba7233770..47fe45a1f9 100644 --- a/openair1/PHY/defs_nr_UE.h +++ b/openair1/PHY/defs_nr_UE.h @@ -316,17 +316,6 @@ typedef struct { #define PBCH_A 24 -typedef struct { - /// \brief Total number of PDU errors. - uint32_t pdu_errors; - /// \brief Total number of PDU errors 128 frames ago. - uint32_t pdu_errors_last; - /// \brief Total number of consecutive PDU errors. - uint32_t pdu_errors_conseq; - /// \brief FER (in percent) . - //uint32_t pdu_fer; -} NR_UE_PBCH; - typedef struct { int16_t amp; bool active; @@ -402,8 +391,6 @@ typedef struct { int is_synchronized; /// \brief Target gNB Nid_cell when UE is resynchronizing int target_Nid_cell; - /// \brief Indicator that UE lost frame synchronization - int lost_sync; /// Data structure for UE process scheduling UE_nr_proc_t proc; /// Flag to indicate the UE shouldn't do timing correction at all @@ -444,7 +431,6 @@ typedef struct { fapi_nr_config_request_t nrUE_config; nr_synch_request_t synch_request; - NR_UE_PBCH *pbch_vars[NUMBER_OF_CONNECTED_gNB_MAX]; NR_UE_PRACH *prach_vars[NUMBER_OF_CONNECTED_gNB_MAX]; NR_UE_CSI_IM *csiim_vars[NUMBER_OF_CONNECTED_gNB_MAX]; NR_UE_CSI_RS *csirs_vars[NUMBER_OF_CONNECTED_gNB_MAX]; diff --git a/openair1/SCHED_NR_UE/defs.h b/openair1/SCHED_NR_UE/defs.h index 1439c9fd01..628c8f4959 100644 --- a/openair1/SCHED_NR_UE/defs.h +++ b/openair1/SCHED_NR_UE/defs.h @@ -83,7 +83,7 @@ typedef struct { - uint8_t decoded_output[64]; + uint8_t decoded_output[3]; // PBCH paylod not larger than 3B uint8_t xtra_byte; } fapiPbch_t; diff --git a/openair1/SCHED_NR_UE/phy_procedures_nr_ue.c b/openair1/SCHED_NR_UE/phy_procedures_nr_ue.c index f88b81ad52..6fa14c3cd0 100644 --- a/openair1/SCHED_NR_UE/phy_procedures_nr_ue.c +++ b/openair1/SCHED_NR_UE/phy_procedures_nr_ue.c @@ -118,10 +118,8 @@ void nr_fill_rx_indication(fapi_nr_rx_indication_t *rx_ind, NR_UE_DLSCH_t *dlsch1, uint16_t n_pdus, UE_nr_rxtx_proc_t *proc, - void *typeSpecific){ - - NR_DL_FRAME_PARMS *frame_parms = &ue->frame_parms; - + void *typeSpecific) +{ if (n_pdus > 1){ LOG_E(PHY, "In %s: multiple number of DL PDUs not supported yet...\n", __FUNCTION__); } @@ -154,17 +152,23 @@ void nr_fill_rx_indication(fapi_nr_rx_indication_t *rx_ind, AssertFatal(1==0,"Second codeword currently not supported\n"); } break; - case FAPI_NR_RX_PDU_TYPE_SSB: - rx_ind->rx_indication_body[n_pdus - 1].ssb_pdu.pdu=malloc(sizeof(((fapiPbch_t*)typeSpecific)->decoded_output)); - memcpy(rx_ind->rx_indication_body[n_pdus - 1].ssb_pdu.pdu, - ((fapiPbch_t*)typeSpecific)->decoded_output, - sizeof(((fapiPbch_t*)typeSpecific)->decoded_output)); - rx_ind->rx_indication_body[n_pdus - 1].ssb_pdu.additional_bits = ((fapiPbch_t*)typeSpecific)->xtra_byte; - rx_ind->rx_indication_body[n_pdus - 1].ssb_pdu.ssb_index = (frame_parms->ssb_index)&0x7; - rx_ind->rx_indication_body[n_pdus - 1].ssb_pdu.ssb_length = frame_parms->Lmax; - rx_ind->rx_indication_body[n_pdus - 1].ssb_pdu.cell_id = frame_parms->Nid_cell; - rx_ind->rx_indication_body[n_pdus - 1].ssb_pdu.ssb_start_subcarrier = frame_parms->ssb_start_subcarrier; - rx_ind->rx_indication_body[n_pdus - 1].ssb_pdu.rsrp_dBm = ue->measurements.ssb_rsrp_dBm[frame_parms->ssb_index]; + case FAPI_NR_RX_PDU_TYPE_SSB: { + fapi_nr_ssb_pdu_t *ssb_pdu = &rx_ind->rx_indication_body[n_pdus - 1].ssb_pdu; + if(typeSpecific) { + NR_DL_FRAME_PARMS *frame_parms = &ue->frame_parms; + fapiPbch_t *pbch = (fapiPbch_t *)typeSpecific; + memcpy(ssb_pdu->pdu, pbch->decoded_output, sizeof(pbch->decoded_output)); + ssb_pdu->additional_bits = pbch->xtra_byte; + ssb_pdu->ssb_index = (frame_parms->ssb_index)&0x7; + ssb_pdu->ssb_length = frame_parms->Lmax; + ssb_pdu->cell_id = frame_parms->Nid_cell; + ssb_pdu->ssb_start_subcarrier = frame_parms->ssb_start_subcarrier; + ssb_pdu->rsrp_dBm = ue->measurements.ssb_rsrp_dBm[frame_parms->ssb_index]; + ssb_pdu->decoded_pdu = true; + } + else + ssb_pdu->decoded_pdu = false; + } break; case FAPI_NR_CSIRS_IND: memcpy(&rx_ind->rx_indication_body[n_pdus - 1].csirs_measurements, @@ -382,7 +386,6 @@ static void nr_ue_pbch_procedures(PHY_VARS_NR_UE *ue, proc, estimateSz, dl_ch_estimates, - ue->pbch_vars[gNB_id], &ue->frame_parms, (ue->frame_parms.ssb_index)&7, SISO, @@ -392,8 +395,6 @@ static void nr_ue_pbch_procedures(PHY_VARS_NR_UE *ue, if (ret==0) { - ue->pbch_vars[gNB_id]->pdu_errors_conseq = 0; - #ifdef DEBUG_PHY_PROC uint16_t frame_tx; LOG_D(PHY,"[UE %d] frame %d, nr_slot_rx %d, Received PBCH (MIB): frame_tx %d. N_RB_DL %d\n", @@ -426,36 +427,12 @@ static void nr_ue_pbch_procedures(PHY_VARS_NR_UE *ue, write_output("H10.m","h10",&(ue->common_vars.dl_ch_estimates[0][2][0]),((ue->frame_parms.Ncp==0)?7:6)*(ue->frame_parms.ofdm_symbol_size),1,1); write_output("rxsigF0.m","rxsF0", ue->common_vars.rxdataF[0],8*ue->frame_parms.ofdm_symbol_size,1,1); - write_output("PBCH_rxF0_ext.m","pbch0_ext",ue->pbch_vars[0]->rxdataF_ext[0],12*4*6,1,1); - write_output("PBCH_rxF0_comp.m","pbch0_comp",ue->pbch_vars[0]->rxdataF_comp[0],12*4*6,1,1); - write_output("PBCH_rxF_llr.m","pbch_llr",ue->pbch_vars[0]->llr,(ue->frame_parms.Ncp==0) ? 1920 : 1728,1,4); exit(-1); */ - ue->pbch_vars[gNB_id]->pdu_errors_conseq++; - ue->pbch_vars[gNB_id]->pdu_errors++; - - if (ue->pbch_vars[gNB_id]->pdu_errors_conseq>=100) { - if (get_softmodem_params()->non_stop) { - LOG_E(PHY,"More that 100 consecutive PBCH errors! Going back to Sync mode!\n"); - ue->lost_sync = 1; - } else { - LOG_E(PHY,"More that 100 consecutive PBCH errors! Exiting!\n"); - exit_fun("More that 100 consecutive PBCH errors! Exiting!\n"); - } - } } - if (frame_rx % 100 == 0) { - ue->pbch_vars[gNB_id]->pdu_errors_last = ue->pbch_vars[gNB_id]->pdu_errors; - } -#ifdef DEBUG_PHY_PROC - LOG_D(PHY,"[UE %d] frame %d, slot %d, PBCH errors = %d, consecutive errors = %d!\n", - ue->Mod_id,frame_rx, nr_slot_rx, - ue->pbch_vars[gNB_id]->pdu_errors, - ue->pbch_vars[gNB_id]->pdu_errors_conseq); -#endif VCD_SIGNAL_DUMPER_DUMP_FUNCTION_BY_NAME(VCD_SIGNAL_DUMPER_FUNCTIONS_UE_PBCH_PROCEDURES, VCD_FUNCTION_OUT); } diff --git a/openair1/SIMULATION/NR_PHY/pbchsim.c b/openair1/SIMULATION/NR_PHY/pbchsim.c index ee67e8d52e..cdaf9ce214 100644 --- a/openair1/SIMULATION/NR_PHY/pbchsim.c +++ b/openair1/SIMULATION/NR_PHY/pbchsim.c @@ -793,8 +793,8 @@ int main(int argc, char **argv) fapiPbch_t result; ret = nr_rx_pbch(UE, &proc, - estimateSz, dl_ch_estimates, - UE->pbch_vars[0], + estimateSz, + dl_ch_estimates, frame_parms, ssb_index%8, SISO, @@ -813,7 +813,6 @@ int main(int argc, char **argv) for (i=0;i<3;i++){ payload_ret += (result.decoded_output[i] == ((msgDataTx.ssb[ssb_index].ssb_pdu.ssb_pdu_rel15.bchPayload>>(8*i)) & 0xff)); } - //printf("xtra byte gNB: 0x%02x UE: 0x%02x\n",gNB_xtra_byte, UE->pbch_vars[0]->xtra_byte); //printf("ret %d\n", payload_ret); if (payload_ret!=4) n_errors_payload++; diff --git a/openair2/LAYER2/NR_MAC_UE/mac_defs.h b/openair2/LAYER2/NR_MAC_UE/mac_defs.h index b1af5393a1..6e5a9ddc0c 100644 --- a/openair2/LAYER2/NR_MAC_UE/mac_defs.h +++ b/openair2/LAYER2/NR_MAC_UE/mac_defs.h @@ -376,8 +376,9 @@ typedef struct { uint32_t ssb_index; /// SSB RSRP in dBm short ssb_rsrp_dBm; + int consecutive_bch_failures; -} NR_PHY_meas_t; +} NR_SSB_meas_t; /*!\brief Top level UE MAC structure */ typedef struct { @@ -465,7 +466,7 @@ typedef struct { uint16_t nr_band; uint8_t ssb_subcarrier_offset; - NR_PHY_meas_t phy_measurements; + NR_SSB_meas_t ssb_measurements; dci_pdu_rel15_t def_dci_pdu_rel15[8]; diff --git a/openair2/LAYER2/NR_MAC_UE/main_ue_nr.c b/openair2/LAYER2/NR_MAC_UE/main_ue_nr.c index 2babe56ec8..31c665524e 100644 --- a/openair2/LAYER2/NR_MAC_UE/main_ue_nr.c +++ b/openair2/LAYER2/NR_MAC_UE/main_ue_nr.c @@ -55,6 +55,7 @@ NR_UE_MAC_INST_t * nr_l2_init_ue(NR_UE_RRC_INST_t* rrc_inst) { nr_ue_mac_inst->sib1_decoded = false; nr_ue_mac_inst->phy_config_request_sent = false; nr_ue_mac_inst->state = UE_NOT_SYNC; + memset(&nr_ue_mac_inst->ssb_measurements, 0, sizeof(nr_ue_mac_inst->ssb_measurements)); for (int j=0;jra; - int16_t pathloss = compute_nr_SSB_PL(mac, mac->phy_measurements.ssb_rsrp_dBm); + int16_t pathloss = compute_nr_SSB_PL(mac, mac->ssb_measurements.ssb_rsrp_dBm); int16_t ra_preamble_rx_power = (int16_t)(ra->prach_resources.ra_PREAMBLE_RECEIVED_TARGET_POWER - pathloss + 30); return min(nr_get_Pcmax(mod_id), ra_preamble_rx_power); @@ -674,7 +674,7 @@ void nr_get_prach_resources(module_id_t mod_id, } else { /* TODO: This controls the tx_power of UE and the ramping procedure of RA of UE. Later we can abstract this, perhaps in the proxy. But for the time being lets leave it as below. */ - int16_t dl_pathloss = !get_softmodem_params()->emulate_l1 ? compute_nr_SSB_PL(mac, mac->phy_measurements.ssb_rsrp_dBm) : 0; + int16_t dl_pathloss = !get_softmodem_params()->emulate_l1 ? compute_nr_SSB_PL(mac, mac->ssb_measurements.ssb_rsrp_dBm) : 0; ssb_rach_config(ra, prach_resources, nr_rach_ConfigCommon); ra_preambles_config(prach_resources, mac, dl_pathloss); LOG_D(MAC, "[RAPROC] - Selected RA preamble index %d for contention-based random access procedure... \n", ra->ra_PreambleIndex); diff --git a/openair2/LAYER2/NR_MAC_UE/nr_ue_procedures.c b/openair2/LAYER2/NR_MAC_UE/nr_ue_procedures.c index 3978d52711..d5fc404a6e 100644 --- a/openair2/LAYER2/NR_MAC_UE/nr_ue_procedures.c +++ b/openair2/LAYER2/NR_MAC_UE/nr_ue_procedures.c @@ -238,6 +238,8 @@ int8_t nr_ue_decode_mib(module_id_t module_id, AssertFatal(mac->mib != NULL, "nr_ue_decode_mib() mac->mib == NULL\n"); + mac->ssb_measurements.consecutive_bch_failures = 0; // resetting decoding failures + uint16_t frame = (mac->mib->systemFrameNumber.buf[0] >> mac->mib->systemFrameNumber.bits_unused); uint16_t frame_number_4lsb = 0; @@ -1564,7 +1566,7 @@ int16_t get_pucch_tx_power_ue(NR_UE_MAC_INST_t *mac, return (PUCCH_POWER_DEFAULT); } - int16_t pathloss = compute_nr_SSB_PL(mac, mac->phy_measurements.ssb_rsrp_dBm); + int16_t pathloss = compute_nr_SSB_PL(mac, mac->ssb_measurements.ssb_rsrp_dBm); int M_pucch_component = (10 * log10((double)(pow(2,scs) * nb_of_prbs))); int16_t pucch_power = P_O_PUCCH + M_pucch_component + pathloss + delta_F_PUCCH + DELTA_TF + G_b_f_c; @@ -2179,7 +2181,7 @@ uint8_t get_ssb_rsrp_payload(NR_UE_MAC_INST_t *mac, } } AssertFatal(*SSB_resource.list.array[ssb_rsrp[0][0]] == mac->mib_ssb, "Couldn't find corresponding SSB in csi_SSB_ResourceList\n"); - ssb_rsrp[1][0] = mac->phy_measurements.ssb_rsrp_dBm; + ssb_rsrp[1][0] = mac->ssb_measurements.ssb_rsrp_dBm; uint8_t ssbi; diff --git a/openair2/NR_UE_PHY_INTERFACE/NR_IF_Module.c b/openair2/NR_UE_PHY_INTERFACE/NR_IF_Module.c index 5ddd59fcc1..0506b40368 100644 --- a/openair2/NR_UE_PHY_INTERFACE/NR_IF_Module.c +++ b/openair2/NR_UE_PHY_INTERFACE/NR_IF_Module.c @@ -304,25 +304,24 @@ static void fill_dl_info_with_pdcch(fapi_nr_dci_indication_t *dci, nfapi_nr_dl_d static void fill_mib_in_rx_ind(nfapi_nr_dl_tti_request_pdu_t *pdu_list, fapi_nr_rx_indication_t *rx_ind, int pdu_idx, int pdu_type) { - AssertFatal(pdu_idx < sizeof(rx_ind->rx_indication_body) / sizeof(rx_ind->rx_indication_body[0]), - "pdu_index (%d) is greater than rx_indication_body size!\n", pdu_idx); - AssertFatal(pdu_idx == rx_ind->number_pdus, "Invalid pdu_idx %d!\n", pdu_idx); + AssertFatal(pdu_idx < sizeof(rx_ind->rx_indication_body) / sizeof(rx_ind->rx_indication_body[0]), + "pdu_index (%d) is greater than rx_indication_body size!\n", pdu_idx); + AssertFatal(pdu_idx == rx_ind->number_pdus, "Invalid pdu_idx %d!\n", pdu_idx); - LOG_T(NR_MAC, "Recevied an SSB and are filling rx_ind with the MIB!\n"); - - nfapi_nr_dl_tti_ssb_pdu_rel15_t *ssb_pdu = &pdu_list->ssb_pdu.ssb_pdu_rel15; - rx_ind->rx_indication_body[pdu_idx].ssb_pdu.cell_id = ssb_pdu->PhysCellId; - rx_ind->rx_indication_body[pdu_idx].ssb_pdu.pdu = MALLOC(3 * sizeof(*rx_ind->rx_indication_body[pdu_idx].ssb_pdu.pdu)); - rx_ind->rx_indication_body[pdu_idx].ssb_pdu.pdu[0] = (ssb_pdu->bchPayload) & 0xff; - rx_ind->rx_indication_body[pdu_idx].ssb_pdu.pdu[1] = (ssb_pdu->bchPayload >> 8) & 0xff; - rx_ind->rx_indication_body[pdu_idx].ssb_pdu.pdu[2] = (ssb_pdu->bchPayload >> 16) & 0xff; - rx_ind->rx_indication_body[pdu_idx].ssb_pdu.rsrp_dBm = ssb_pdu->ssbRsrp; - rx_ind->rx_indication_body[pdu_idx].ssb_pdu.ssb_index = ssb_pdu->SsbBlockIndex; - rx_ind->rx_indication_body[pdu_idx].ssb_pdu.ssb_length = pdu_list->PDUSize; - rx_ind->rx_indication_body[pdu_idx].ssb_pdu.ssb_start_subcarrier = ssb_pdu->SsbSubcarrierOffset; - rx_ind->rx_indication_body[pdu_idx].pdu_type = pdu_type; - rx_ind->number_pdus = pdu_idx + 1; + LOG_T(NR_MAC, "Recevied an SSB and are filling rx_ind with the MIB!\n"); + nfapi_nr_dl_tti_ssb_pdu_rel15_t *ssb_pdu = &pdu_list->ssb_pdu.ssb_pdu_rel15; + rx_ind->rx_indication_body[pdu_idx].ssb_pdu.cell_id = ssb_pdu->PhysCellId; + rx_ind->rx_indication_body[pdu_idx].ssb_pdu.pdu[0] = (ssb_pdu->bchPayload) & 0xff; + rx_ind->rx_indication_body[pdu_idx].ssb_pdu.pdu[1] = (ssb_pdu->bchPayload >> 8) & 0xff; + rx_ind->rx_indication_body[pdu_idx].ssb_pdu.pdu[2] = (ssb_pdu->bchPayload >> 16) & 0xff; + rx_ind->rx_indication_body[pdu_idx].ssb_pdu.rsrp_dBm = ssb_pdu->ssbRsrp; + rx_ind->rx_indication_body[pdu_idx].ssb_pdu.ssb_index = ssb_pdu->SsbBlockIndex; + rx_ind->rx_indication_body[pdu_idx].ssb_pdu.ssb_length = pdu_list->PDUSize; + rx_ind->rx_indication_body[pdu_idx].ssb_pdu.ssb_start_subcarrier = ssb_pdu->SsbSubcarrierOffset; + rx_ind->rx_indication_body[pdu_idx].ssb_pdu.decoded_pdu = true; + rx_ind->rx_indication_body[pdu_idx].pdu_type = pdu_type; + rx_ind->number_pdus = pdu_idx + 1; } static bool is_my_dci(NR_UE_MAC_INST_t *mac, nfapi_nr_dl_dci_pdu_t *received_pdu) @@ -1045,18 +1044,26 @@ int handle_bcch_bch(module_id_t module_id, int cc_id, uint16_t ssb_start_subcarrier, uint16_t cell_id){ return nr_ue_decode_mib(module_id, - cc_id, - gNB_index, - phy_data, - additional_bits, - ssb_length, // Lssb = 64 is not support - ssb_index, - pduP, - ssb_start_subcarrier, - cell_id); + cc_id, + gNB_index, + phy_data, + additional_bits, + ssb_length, // Lssb = 64 is not support + ssb_index, + pduP, + ssb_start_subcarrier, + cell_id); } +void handle_bch_failure(NR_UE_MAC_INST_t *mac) +{ + mac->ssb_measurements.consecutive_bch_failures++; + //TODO handle this properly by scheduling re-synchronization + AssertFatal(mac->ssb_measurements.consecutive_bch_failures < 100, + "Radio link failure caused by 100 consecutive PBCH detection failures.\n"); +} + // L2 Abstraction Layer int handle_bcch_dlsch(module_id_t module_id, int cc_id, unsigned int gNB_index, uint8_t ack_nack, uint8_t *pduP, uint32_t pdu_len){ return nr_ue_decode_BCCH_DL_SCH(module_id, cc_id, gNB_index, ack_nack, pduP, pdu_len); @@ -1069,10 +1076,10 @@ int handle_dci(module_id_t module_id, int cc_id, unsigned int gNB_index, frame_t } -void handle_ssb_meas(NR_UE_MAC_INST_t *mac, uint8_t ssb_index, int16_t rsrp_dbm) +void handle_ssb_meas(NR_UE_MAC_INST_t *mac, uint8_t ssb_index, int16_t rsrp_dbm) { - mac->phy_measurements.ssb_index = ssb_index; - mac->phy_measurements.ssb_rsrp_dBm = rsrp_dbm; + mac->ssb_measurements.ssb_index = ssb_index; + mac->ssb_measurements.ssb_rsrp_dBm = rsrp_dbm; } // L2 Abstraction Layer @@ -1180,31 +1187,35 @@ int nr_ue_dl_indication(nr_downlink_indication_t *dl_info, NR_UL_TIME_ALIGNMENT_ for (int i=0; irx_ind->number_pdus; ++i) { + fapi_nr_rx_indication_body_t rx_indication_body = dl_info->rx_ind->rx_indication_body[i]; LOG_D(NR_MAC, "In %s sending DL indication to MAC. 1 PDU type %d of %d total number of PDUs \n", __FUNCTION__, - dl_info->rx_ind->rx_indication_body[i].pdu_type, + rx_indication_body.pdu_type, dl_info->rx_ind->number_pdus); - switch(dl_info->rx_ind->rx_indication_body[i].pdu_type){ + switch(rx_indication_body.pdu_type){ case FAPI_NR_RX_PDU_TYPE_SSB: - handle_ssb_meas(mac, - (dl_info->rx_ind->rx_indication_body+i)->ssb_pdu.ssb_index, - (dl_info->rx_ind->rx_indication_body+i)->ssb_pdu.rsrp_dBm); - ret_mask |= (handle_bcch_bch(dl_info->module_id, dl_info->cc_id, dl_info->gNB_index, dl_info->phy_data, - (dl_info->rx_ind->rx_indication_body+i)->ssb_pdu.pdu, - (dl_info->rx_ind->rx_indication_body+i)->ssb_pdu.additional_bits, - (dl_info->rx_ind->rx_indication_body+i)->ssb_pdu.ssb_index, - (dl_info->rx_ind->rx_indication_body+i)->ssb_pdu.ssb_length, - (dl_info->rx_ind->rx_indication_body+i)->ssb_pdu.ssb_start_subcarrier, - (dl_info->rx_ind->rx_indication_body+i)->ssb_pdu.cell_id)) << FAPI_NR_RX_PDU_TYPE_SSB; - free((dl_info->rx_ind->rx_indication_body+i)->ssb_pdu.pdu); + if(rx_indication_body.ssb_pdu.decoded_pdu) { + handle_ssb_meas(mac, + rx_indication_body.ssb_pdu.ssb_index, + rx_indication_body.ssb_pdu.rsrp_dBm); + ret_mask |= (handle_bcch_bch(dl_info->module_id, dl_info->cc_id, dl_info->gNB_index, dl_info->phy_data, + rx_indication_body.ssb_pdu.pdu, + rx_indication_body.ssb_pdu.additional_bits, + rx_indication_body.ssb_pdu.ssb_index, + rx_indication_body.ssb_pdu.ssb_length, + rx_indication_body.ssb_pdu.ssb_start_subcarrier, + rx_indication_body.ssb_pdu.cell_id)) << FAPI_NR_RX_PDU_TYPE_SSB; + } + else + handle_bch_failure(mac); break; case FAPI_NR_RX_PDU_TYPE_SIB: ret_mask |= (handle_bcch_dlsch(dl_info->module_id, dl_info->cc_id, dl_info->gNB_index, - (dl_info->rx_ind->rx_indication_body+i)->pdsch_pdu.ack_nack, - (dl_info->rx_ind->rx_indication_body+i)->pdsch_pdu.pdu, - (dl_info->rx_ind->rx_indication_body+i)->pdsch_pdu.pdu_length)) << FAPI_NR_RX_PDU_TYPE_SIB; + rx_indication_body.pdsch_pdu.ack_nack, + rx_indication_body.pdsch_pdu.pdu, + rx_indication_body.pdsch_pdu.pdu_length)) << FAPI_NR_RX_PDU_TYPE_SIB; break; case FAPI_NR_RX_PDU_TYPE_DLSCH: ret_mask |= (handle_dlsch(dl_info, ul_time_alignment, i)) << FAPI_NR_RX_PDU_TYPE_DLSCH; @@ -1216,7 +1227,7 @@ int nr_ue_dl_indication(nr_downlink_indication_t *dl_info, NR_UL_TIME_ALIGNMENT_ ret_mask |= (handle_csirs_measurements(dl_info->module_id, dl_info->frame, dl_info->slot, - &(dl_info->rx_ind->rx_indication_body+i)->csirs_measurements)) << FAPI_NR_CSIRS_IND; + &rx_indication_body.csirs_measurements)) << FAPI_NR_CSIRS_IND; break; default: break; From 15c12caa376311d8c1a6fcf3249f03749c342e94 Mon Sep 17 00:00:00 2001 From: Sakthivel Velumani Date: Tue, 22 Nov 2022 10:23:22 -0500 Subject: [PATCH 56/89] Contains MAC changes to run || DLSCH DL config as array in MAC Run RX slot processing in Tpool thread Fixed race in phy config Changed mac_IF_mutex Proc Tx: Lets wait for Proc Rx to get ACK/NACK -(only on slots that carry ACK/NACK) Fix problem with rfsim Separated PDCCH and PDSCH decoding functions. Run PDCCH decoding in main UE_thread. Fixed race in MAC dci config struct needed for parallel processing of slots Moved DLSCH transport block buffer to stack rx_ind as static allocation L2sim: Fixed calling of MAC sched in wrong slot Fix DCI extract bug for RA procedure --- executables/nr-ue.c | 78 ++++++++---- .../nfapi/public_inc/fapi_nr_ue_interface.h | 4 +- openair1/PHY/INIT/nr_init_ue.c | 9 -- openair1/PHY/NR_UE_TRANSPORT/csi_rx.c | 8 +- .../PHY/NR_UE_TRANSPORT/nr_dlsch_decoding.c | 10 +- openair1/PHY/NR_UE_TRANSPORT/nr_pbch.c | 8 +- .../NR_UE_TRANSPORT/nr_transport_proto_ue.h | 4 +- .../PHY/NR_UE_TRANSPORT/nr_transport_ue.h | 2 - openair1/PHY/defs_nr_UE.h | 5 + openair1/SCHED_NR_UE/defs.h | 18 +-- openair1/SCHED_NR_UE/fapi_nr_ue_l1.c | 4 +- openair1/SCHED_NR_UE/phy_procedures_nr_ue.c | 113 ++++++++++++------ openair1/SIMULATION/NR_PHY/dlschsim.c | 12 +- openair1/SIMULATION/NR_PHY/dlsim.c | 19 ++- openair1/SIMULATION/NR_PHY/pbchsim.c | 3 +- openair2/LAYER2/NR_MAC_UE/config_ue.c | 17 ++- openair2/LAYER2/NR_MAC_UE/mac_defs.h | 4 +- openair2/LAYER2/NR_MAC_UE/mac_proto.h | 4 +- .../NR_MAC_UE/nr_ue_dci_configuration.c | 2 +- openair2/LAYER2/NR_MAC_UE/nr_ue_procedures.c | 56 +++++---- openair2/LAYER2/NR_MAC_UE/nr_ue_scheduler.c | 95 ++++++++++----- openair2/NR_UE_PHY_INTERFACE/NR_IF_Module.c | 27 +++-- 22 files changed, 333 insertions(+), 169 deletions(-) diff --git a/executables/nr-ue.c b/executables/nr-ue.c index 10509a2870..3d468ad64b 100644 --- a/executables/nr-ue.c +++ b/executables/nr-ue.c @@ -203,6 +203,7 @@ static void process_queued_nr_nfapi_msgs(NR_UE_MAC_INST_t *mac, int sfn_slot) nfapi_nr_ul_tti_request_t *ul_tti_request_crc = unqueue_matching(&nr_ul_tti_req_queue, MAX_QUEUE_SIZE, sfn_slot_matcher, &mac->nr_ue_emul_l1.harq[i].active_ul_harq_sfn_slot); if (ul_tti_request_crc && ul_tti_request_crc->n_pdus > 0) { check_and_process_dci(NULL, NULL, NULL, ul_tti_request_crc); + free_and_zero(ul_tti_request_crc); } } @@ -232,6 +233,8 @@ static void process_queued_nr_nfapi_msgs(NR_UE_MAC_INST_t *mac, int sfn_slot) if (get_softmodem_params()->nsa) save_nr_measurement_info(dl_tti_request); check_and_process_dci(dl_tti_request, tx_data_request, NULL, NULL); + free_and_zero(dl_tti_request); + free_and_zero(tx_data_request); } else { AssertFatal(false, "We dont have PDUs in either dl_tti %d or tx_req %d\n", @@ -240,6 +243,7 @@ static void process_queued_nr_nfapi_msgs(NR_UE_MAC_INST_t *mac, int sfn_slot) } if (ul_dci_request && ul_dci_request->numPdus > 0) { check_and_process_dci(NULL, NULL, ul_dci_request, NULL); + free_and_zero(ul_dci_request); } } @@ -324,14 +328,6 @@ static void *NRUE_phy_stub_standalone_pnf_task(void *arg) if (pthread_mutex_lock(&mac->mutex_dl_info)) abort(); - memset(&mac->dl_info, 0, sizeof(mac->dl_info)); - mac->dl_info.cc_id = CC_id; - mac->dl_info.gNB_index = gNB_id; - mac->dl_info.module_id = mod_id; - mac->dl_info.frame = frame; - mac->dl_info.slot = slot; - mac->dl_info.dci_ind = NULL; - mac->dl_info.rx_ind = NULL; if (ch_info) { mac->nr_ue_emul_l1.pmi = ch_info->csi[0].pmi; mac->nr_ue_emul_l1.ri = ch_info->csi[0].ri; @@ -343,6 +339,14 @@ static void *NRUE_phy_stub_standalone_pnf_task(void *arg) mac->scc->tdd_UL_DL_ConfigurationCommon : mac->scc_SIB->tdd_UL_DL_ConfigurationCommon, ul_info.slot_rx)) { + memset(&mac->dl_info, 0, sizeof(mac->dl_info)); + mac->dl_info.cc_id = CC_id; + mac->dl_info.gNB_index = gNB_id; + mac->dl_info.module_id = mod_id; + mac->dl_info.frame = frame; + mac->dl_info.slot = slot; + mac->dl_info.dci_ind = NULL; + mac->dl_info.rx_ind = NULL; nr_ue_dl_indication(&mac->dl_info, &ul_time_alignment); } @@ -563,6 +567,13 @@ void processSlotTX(void *arg) { LOG_D(PHY,"%d.%d => slot type %d\n", proc->frame_tx, proc->nr_slot_tx, proc->tx_slot_type); if (proc->tx_slot_type == NR_UPLINK_SLOT || proc->tx_slot_type == NR_MIXED_SLOT){ + // wait for rx slots to send indication + for(int i=0; i < UE->tx_wait_for_dlsch[proc->nr_slot_tx]; i++) { + notifiedFIFO_elt_t *res = pullNotifiedFIFO(UE->tx_resume_ind_fifo[proc->nr_slot_tx]); + delNotifiedFIFO_elt(res); + } + UE->tx_wait_for_dlsch[proc->nr_slot_tx] = 0; + // trigger L2 to run ue_scheduler thru IF module // [TODO] mapping right after NR initial sync if(UE->if_inst != NULL && UE->if_inst->ul_indication != NULL) { @@ -589,10 +600,8 @@ void processSlotTX(void *arg) { RU_write(rxtxD); } -void UE_processing(nr_rxtx_thread_data_t *rxtxD) { +nr_phy_data_t UE_dl_preprocessing(PHY_VARS_NR_UE *UE, UE_nr_rxtx_proc_t *proc) { - UE_nr_rxtx_proc_t *proc = &rxtxD->proc; - PHY_VARS_NR_UE *UE = rxtxD->UE; nr_phy_data_t phy_data = {0}; if (IS_SOFTMODEM_NOS1 || get_softmodem_params()->sa) { @@ -624,17 +633,28 @@ void UE_processing(nr_rxtx_thread_data_t *rxtxD) { UE->if_inst->dl_indication(&dl_indication, NULL); } - // Process Rx data for one sub-frame -#ifdef UE_SLOT_PARALLELISATION - phy_procedures_slot_parallelization_nrUE_RX( UE, proc, 0, 0, 1, no_relay, NULL ); -#else uint64_t a=rdtsc_oai(); - phy_procedures_nrUE_RX(UE, proc, &phy_data); + pbch_pdcch_processing(UE, proc, &phy_data); + if (phy_data.dlsch[0].active) { + // indicate to tx thread to wait for DLSCH decoding + const int ack_nack_slot = (proc->nr_slot_rx + phy_data.dlsch[0].dlsch_config.k1_feedback) % UE->frame_parms.slots_per_frame; + UE->tx_wait_for_dlsch[ack_nack_slot]++; + } + LOG_D(PHY, "In %s: slot %d, time %llu\n", __FUNCTION__, proc->nr_slot_rx, (rdtsc_oai()-a)/3500); -#endif } ue_ta_procedures(UE, proc->nr_slot_tx, proc->frame_tx); + return phy_data; +} + +void UE_dl_processing(void *arg) { + nr_rxtx_thread_data_t *rxtxD = (nr_rxtx_thread_data_t *) arg; + UE_nr_rxtx_proc_t *proc = &rxtxD->proc; + PHY_VARS_NR_UE *UE = rxtxD->UE; + nr_phy_data_t *phy_data = &rxtxD->phy_data; + + pdsch_processing(UE, proc, phy_data); } void dummyWrite(PHY_VARS_NR_UE *UE,openair0_timestamp timestamp, int writeBlockSize) { @@ -774,6 +794,13 @@ void *UE_thread(void *arg) { bool syncRunning=false; const int nb_slot_frame = UE->frame_parms.slots_per_frame; int absolute_slot=0, decoded_frame_rx=INT_MAX, trashed_frames=0; + initNotifiedFIFO(&UE->phy_config_ind); + + int num_ind_fifo = nb_slot_frame; + for(int i=0; i < num_ind_fifo; i++) { + UE->tx_resume_ind_fifo[i] = malloc(sizeof(*UE->tx_resume_ind_fifo[i])); + initNotifiedFIFO(UE->tx_resume_ind_fifo[i]); + } while (!oai_exit) { if (UE->lost_sync) { @@ -796,6 +823,11 @@ void *UE_thread(void *arg) { decoded_frame_rx=(((mac->mib->systemFrameNumber.buf[0] >> mac->mib->systemFrameNumber.bits_unused)<<4) | tmp->proc.decoded_frame_rx); // shift the frame index with all the frames we trashed meanwhile we perform the synch search decoded_frame_rx=(decoded_frame_rx + UE->init_sync_frame + trashed_frames) % MAX_FRAME_NUMBER; + // wait for RRC to configure PHY parameters from SIB + if (get_softmodem_params()->sa) { + notifiedFIFO_elt_t *phy_config_res = pullNotifiedFIFO(&UE->phy_config_ind); + delNotifiedFIFO_elt(phy_config_res); + } } delNotifiedFIFO_elt(res); start_rx_stream=0; @@ -935,13 +967,19 @@ void *UE_thread(void *arg) { curMsgTx->UE = UE; pushTpool(&(get_nrUE_params()->Tpool), newElt); - // RX slot processing - UE_processing(&curMsg); + // RX slot processing. We launch and forget. + nr_phy_data_t phy_data = UE_dl_preprocessing(UE, &curMsg.proc); + newElt = newNotifiedFIFO_elt(sizeof(nr_rxtx_thread_data_t), curMsg.proc.nr_slot_rx, NULL, UE_dl_processing); + nr_rxtx_thread_data_t *curMsgRx = (nr_rxtx_thread_data_t *) NotifiedFifoData(newElt); + curMsgRx->proc = curMsg.proc; + curMsgRx->UE = UE; + curMsgRx->phy_data = phy_data; + pushTpool(&(get_nrUE_params()->Tpool), newElt); if (curMsg.proc.decoded_frame_rx != -1) decoded_frame_rx=(((mac->mib->systemFrameNumber.buf[0] >> mac->mib->systemFrameNumber.bits_unused)<<4) | curMsg.proc.decoded_frame_rx); else - decoded_frame_rx=-1; + decoded_frame_rx=-1; if (decoded_frame_rx>0 && decoded_frame_rx != curMsg.proc.frame_rx) LOG_E(PHY,"Decoded frame index (%d) is not compatible with current context (%d), UE should go back to synch mode\n", diff --git a/nfapi/open-nFAPI/nfapi/public_inc/fapi_nr_ue_interface.h b/nfapi/open-nFAPI/nfapi/public_inc/fapi_nr_ue_interface.h index d429bf43ba..e227bc5fa2 100644 --- a/nfapi/open-nFAPI/nfapi/public_inc/fapi_nr_ue_interface.h +++ b/nfapi/open-nFAPI/nfapi/public_inc/fapi_nr_ue_interface.h @@ -21,6 +21,7 @@ #include "stddef.h" #include "platform_types.h" #include "fapi_nr_ue_constants.h" +#include "PHY/impl_defs_top.h" #include "PHY/impl_defs_nr.h" #include "common/utils/nr/nr_common.h" @@ -390,8 +391,6 @@ typedef struct { } fapi_nr_ul_config_request_pdu_t; typedef struct { - //uint16_t sfn; - //uint16_t slot; uint16_t sfn; uint16_t slot; uint8_t number_pdus; @@ -483,6 +482,7 @@ typedef struct { uint8_t nscid; uint16_t dlDmrsScramblingId; uint16_t pduBitmap; + uint32_t k1_feedback; } fapi_nr_dl_config_dlsch_pdu_rel15_t; typedef struct { diff --git a/openair1/PHY/INIT/nr_init_ue.c b/openair1/PHY/INIT/nr_init_ue.c index 32e5647b69..72c0fbe070 100644 --- a/openair1/PHY/INIT/nr_init_ue.c +++ b/openair1/PHY/INIT/nr_init_ue.c @@ -523,7 +523,6 @@ void free_nr_ue_dl_harq(NR_DL_UE_HARQ_t harq_list[2][NR_MAX_DLSCH_HARQ_PROCESSES for (int j=0; j < 2; j++) { for (int i=0; iif_inst && ue->if_inst->dl_indication) { ue->if_inst->dl_indication(&dl_indication, NULL); - } else { - free(rx_ind); } return 0; diff --git a/openair1/PHY/NR_UE_TRANSPORT/nr_dlsch_decoding.c b/openair1/PHY/NR_UE_TRANSPORT/nr_dlsch_decoding.c index db6a15213c..bf18d79189 100644 --- a/openair1/PHY/NR_UE_TRANSPORT/nr_dlsch_decoding.c +++ b/openair1/PHY/NR_UE_TRANSPORT/nr_dlsch_decoding.c @@ -77,7 +77,7 @@ void nr_dlsch_unscrambling(int16_t *llr, uint32_t size, uint8_t q, uint32_t Nid, nr_codeword_unscrambling(llr, size, q, Nid, n_RNTI); } -bool nr_ue_postDecode(PHY_VARS_NR_UE *phy_vars_ue, notifiedFIFO_elt_t *req, bool last, notifiedFIFO_t *nf_p) { +bool nr_ue_postDecode(PHY_VARS_NR_UE *phy_vars_ue, notifiedFIFO_elt_t *req, bool last, notifiedFIFO_t *nf_p, int b_size, uint8_t b[b_size]) { ldpcDecode_ue_t *rdata = (ldpcDecode_ue_t*) NotifiedFifoData(req); NR_DL_UE_HARQ_t *harq_process = rdata->harq_process; NR_UE_DLSCH_t *dlsch = (NR_UE_DLSCH_t *) rdata->dlsch; @@ -90,7 +90,7 @@ bool nr_ue_postDecode(PHY_VARS_NR_UE *phy_vars_ue, notifiedFIFO_elt_t *req, bool bool decodeSuccess = (rdata->decodeIterations < (1+dlsch->max_ldpc_iterations)); if (decodeSuccess) { - memcpy(harq_process->b+rdata->offset, + memcpy(b+rdata->offset, harq_process->c[r], rdata->Kr_bytes - (harq_process->F>>3) -((harq_process->C>1)?3:0)); @@ -311,7 +311,9 @@ uint32_t nr_dlsch_decoding(PHY_VARS_NR_UE *phy_vars_ue, uint32_t frame, uint16_t nb_symb_sch, uint8_t nr_slot_rx, - uint8_t harq_pid) { + uint8_t harq_pid, + int b_size, + uint8_t b[b_size]) { uint32_t A,E; uint32_t G; uint32_t ret,offset; @@ -493,7 +495,7 @@ uint32_t nr_dlsch_decoding(PHY_VARS_NR_UE *phy_vars_ue, bool last = false; if (r == nbDecode - 1) last = true; - bool stop = nr_ue_postDecode(phy_vars_ue, req, last, &nf); + bool stop = nr_ue_postDecode(phy_vars_ue, req, last, &nf, b_size, b); delNotifiedFIFO_elt(req); if (stop) break; diff --git a/openair1/PHY/NR_UE_TRANSPORT/nr_pbch.c b/openair1/PHY/NR_UE_TRANSPORT/nr_pbch.c index 0c2149e997..d8c1010536 100644 --- a/openair1/PHY/NR_UE_TRANSPORT/nr_pbch.c +++ b/openair1/PHY/NR_UE_TRANSPORT/nr_pbch.c @@ -578,16 +578,14 @@ int nr_rx_pbch( PHY_VARS_NR_UE *ue, #endif nr_downlink_indication_t dl_indication; - fapi_nr_rx_indication_t *rx_ind=calloc(sizeof(*rx_ind),1); + fapi_nr_rx_indication_t rx_ind = {0}; uint16_t number_pdus = 1; - nr_fill_dl_indication(&dl_indication, NULL, rx_ind, proc, ue, phy_data); - nr_fill_rx_indication(rx_ind, FAPI_NR_RX_PDU_TYPE_SSB, ue, NULL, NULL, number_pdus, proc,(void *)result); + nr_fill_dl_indication(&dl_indication, NULL, &rx_ind, proc, ue, phy_data); + nr_fill_rx_indication(&rx_ind, FAPI_NR_RX_PDU_TYPE_SSB, ue, NULL, NULL, number_pdus, proc,(void *)result, NULL); if (ue->if_inst && ue->if_inst->dl_indication) ue->if_inst->dl_indication(&dl_indication, NULL); - else - free(rx_ind); // dl_indication would free(), so free() here if not called return 0; } 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 0d6c334c45..42951d687e 100644 --- a/openair1/PHY/NR_UE_TRANSPORT/nr_transport_proto_ue.h +++ b/openair1/PHY/NR_UE_TRANSPORT/nr_transport_proto_ue.h @@ -205,7 +205,9 @@ uint32_t nr_dlsch_decoding(PHY_VARS_NR_UE *phy_vars_ue, uint32_t frame, uint16_t nb_symb_sch, uint8_t nr_slot_rx, - uint8_t harq_pid); + uint8_t harq_pid, + int b_size, + uint8_t b[b_size]); int nr_ulsch_encoding(PHY_VARS_NR_UE *ue, NR_UE_ULSCH_t *ulsch, diff --git a/openair1/PHY/NR_UE_TRANSPORT/nr_transport_ue.h b/openair1/PHY/NR_UE_TRANSPORT/nr_transport_ue.h index dc09714da9..811fa7711b 100644 --- a/openair1/PHY/NR_UE_TRANSPORT/nr_transport_ue.h +++ b/openair1/PHY/NR_UE_TRANSPORT/nr_transport_ue.h @@ -119,8 +119,6 @@ typedef struct { uint32_t TBS; /// The payload + CRC size in bits uint32_t B; - /// Pointer to the payload - uint8_t *b; /// Pointers to transport block segments uint8_t **c; /// soft bits for each received segment ("d"-sequence)(for definition see 36-212 V8.6 2009-03, p.15) diff --git a/openair1/PHY/defs_nr_UE.h b/openair1/PHY/defs_nr_UE.h index 9ba7233770..e5bc7bfbce 100644 --- a/openair1/PHY/defs_nr_UE.h +++ b/openair1/PHY/defs_nr_UE.h @@ -664,6 +664,10 @@ typedef struct { void *phy_sim_pdsch_rxdataF_comp; void *phy_sim_pdsch_dl_ch_estimates; void *phy_sim_pdsch_dl_ch_estimates_ext; + uint8_t *phy_sim_dlsch_b; + notifiedFIFO_t phy_config_ind; + notifiedFIFO_t *tx_resume_ind_fifo[NR_MAX_SLOTS_PER_FRAME]; + int tx_wait_for_dlsch[NR_MAX_SLOTS_PER_FRAME]; } PHY_VARS_NR_UE; typedef struct nr_phy_data_tx_s { @@ -682,6 +686,7 @@ typedef struct nr_rxtx_thread_data_s { UE_nr_rxtx_proc_t proc; PHY_VARS_NR_UE *UE; int writeBlockSize; + nr_phy_data_t phy_data; } nr_rxtx_thread_data_t; typedef struct LDPCDecode_ue_s { diff --git a/openair1/SCHED_NR_UE/defs.h b/openair1/SCHED_NR_UE/defs.h index 1439c9fd01..f7128fd85a 100644 --- a/openair1/SCHED_NR_UE/defs.h +++ b/openair1/SCHED_NR_UE/defs.h @@ -98,17 +98,16 @@ typedef struct { */ void phy_procedures_nrUE_TX(PHY_VARS_NR_UE *ue, UE_nr_rxtx_proc_t *proc, nr_phy_data_tx_t *phy_data); -/*! \brief Scheduling for UE RX procedures in normal subframes. - @param ue Pointer to UE variables on which to act - @param proc Pointer to proc information - @param dlsch_parallel use multithreaded dlsch processing - @param phy_pdcch_config PDCCH Config for this slot - @param txFifo Result fifo if PDSCH is run in parallel -*/ -int phy_procedures_nrUE_RX(PHY_VARS_NR_UE *ue, +void send_slot_ind(notifiedFIFO_t *nf, int slot); + +void pbch_pdcch_processing(PHY_VARS_NR_UE *ue, UE_nr_rxtx_proc_t *proc, nr_phy_data_t *phy_data); +void pdsch_processing(PHY_VARS_NR_UE *ue, + UE_nr_rxtx_proc_t *proc, + nr_phy_data_t *phy_data); + int phy_procedures_slot_parallelization_nrUE_RX(PHY_VARS_NR_UE *ue, UE_nr_rxtx_proc_t *proc, uint8_t abstraction_flag, uint8_t do_pdcch_flag, relaying_type_t r_type); void processSlotTX(void *arg); @@ -166,7 +165,8 @@ void nr_fill_rx_indication(fapi_nr_rx_indication_t *rx_ind, NR_UE_DLSCH_t *dlsch1, uint16_t n_pdus, UE_nr_rxtx_proc_t *proc, - void *typeSpecific); + void *typeSpecific, + uint8_t *b); bool nr_ue_dlsch_procedures(PHY_VARS_NR_UE *ue, UE_nr_rxtx_proc_t *proc, diff --git a/openair1/SCHED_NR_UE/fapi_nr_ue_l1.c b/openair1/SCHED_NR_UE/fapi_nr_ue_l1.c index 255ecf17ca..64ec900b2a 100644 --- a/openair1/SCHED_NR_UE/fapi_nr_ue_l1.c +++ b/openair1/SCHED_NR_UE/fapi_nr_ue_l1.c @@ -544,8 +544,10 @@ int8_t nr_ue_scheduled_response(nr_scheduled_response_t *scheduled_response){ int8_t nr_ue_phy_config_request(nr_phy_config_t *phy_config) { fapi_nr_config_request_t *nrUE_config = &PHY_vars_UE_g[phy_config->Mod_id][phy_config->CC_id]->nrUE_config; - if(phy_config != NULL) + if(phy_config != NULL) { memcpy(nrUE_config,&phy_config->config_req,sizeof(fapi_nr_config_request_t)); + pushNotifiedFIFO(&PHY_vars_UE_g[phy_config->Mod_id][phy_config->CC_id]->phy_config_ind, newNotifiedFIFO_elt(1,0,NULL,NULL)); + } return 0; } diff --git a/openair1/SCHED_NR_UE/phy_procedures_nr_ue.c b/openair1/SCHED_NR_UE/phy_procedures_nr_ue.c index f88b81ad52..9af0c591c1 100644 --- a/openair1/SCHED_NR_UE/phy_procedures_nr_ue.c +++ b/openair1/SCHED_NR_UE/phy_procedures_nr_ue.c @@ -118,7 +118,8 @@ void nr_fill_rx_indication(fapi_nr_rx_indication_t *rx_ind, NR_UE_DLSCH_t *dlsch1, uint16_t n_pdus, UE_nr_rxtx_proc_t *proc, - void *typeSpecific){ + void *typeSpecific, + uint8_t *b){ NR_DL_FRAME_PARMS *frame_parms = &ue->frame_parms; @@ -131,7 +132,7 @@ void nr_fill_rx_indication(fapi_nr_rx_indication_t *rx_ind, if ((pdu_type != FAPI_NR_RX_PDU_TYPE_SSB) && dlsch0) { dl_harq0 = &ue->dl_harq_processes[0][dlsch0->dlsch_config.harq_process_nbr]; trace_NRpdu(DIRECTION_DOWNLINK, - dl_harq0->b, + b, dlsch0->dlsch_config.TBS / 8, WS_C_RNTI, dlsch0->rnti, @@ -147,7 +148,7 @@ void nr_fill_rx_indication(fapi_nr_rx_indication_t *rx_ind, dl_harq0 = &ue->dl_harq_processes[0][dlsch0->dlsch_config.harq_process_nbr]; rx_ind->rx_indication_body[n_pdus - 1].pdsch_pdu.harq_pid = dlsch0->dlsch_config.harq_process_nbr; rx_ind->rx_indication_body[n_pdus - 1].pdsch_pdu.ack_nack = dl_harq0->ack; - rx_ind->rx_indication_body[n_pdus - 1].pdsch_pdu.pdu = dl_harq0->b; + rx_ind->rx_indication_body[n_pdus - 1].pdsch_pdu.pdu = b; rx_ind->rx_indication_body[n_pdus - 1].pdsch_pdu.pdu_length = dlsch0->dlsch_config.TBS / 8; } if(dlsch1) { @@ -696,6 +697,15 @@ int nr_ue_pdsch_procedures(PHY_VARS_NR_UE *ue, return 0; } +void send_slot_ind(notifiedFIFO_t *nf, int slot) { + if (nf) { + notifiedFIFO_elt_t *newElt = newNotifiedFIFO_elt(sizeof(int), 0, NULL, NULL); + int *msgData = (int *) NotifiedFifoData(newElt); + *msgData = slot; + pushNotifiedFIFO(nf, newElt); + } +} + bool nr_ue_dlsch_procedures(PHY_VARS_NR_UE *ue, UE_nr_rxtx_proc_t *proc, NR_UE_DLSCH_t dlsch[2], @@ -716,7 +726,7 @@ bool nr_ue_dlsch_procedures(PHY_VARS_NR_UE *ue, NR_DL_UE_HARQ_t *dl_harq1 = &ue->dl_harq_processes[1][harq_pid]; uint16_t dmrs_len = get_num_dmrs(dlsch[0].dlsch_config.dlDmrsSymbPos); nr_downlink_indication_t dl_indication; - fapi_nr_rx_indication_t *rx_ind = calloc(1, sizeof(*rx_ind)); + fapi_nr_rx_indication_t rx_ind = {0}; uint16_t number_pdus = 1; // params for UL time alignment procedure NR_UL_TIME_ALIGNMENT_t *ul_time_alignment = &ue->ul_time_alignment[gNB_id]; @@ -739,8 +749,12 @@ bool nr_ue_dlsch_procedures(PHY_VARS_NR_UE *ue, LOG_D(PHY,"AbsSubframe %d.%d Start LDPC Decoder for CW1 [harq_pid %d] ? %d \n", frame_rx%1024, nr_slot_rx, harq_pid, is_cw1_active); // exit dlsch procedures as there are no active dlsch - if (is_cw0_active != ACTIVE && is_cw1_active != ACTIVE) + if (is_cw0_active != ACTIVE && is_cw1_active != ACTIVE) { + // don't wait anymore + const int ack_nack_slot = (proc->nr_slot_rx + dlsch[0].dlsch_config.k1_feedback) % ue->frame_parms.slots_per_frame; + send_slot_ind(ue->tx_resume_ind_fifo[ack_nack_slot], proc->nr_slot_rx); return false; + } // start ldpc decode for CW 0 dl_harq0->G = nr_get_G(dlsch[0].dlsch_config.number_rbs, @@ -762,6 +776,16 @@ bool nr_ue_dlsch_procedures(PHY_VARS_NR_UE *ue, start_meas(&ue->dlsch_decoding_stats); + // create memory to store decoder output + int a_segments = MAX_NUM_NR_DLSCH_SEGMENTS_PER_LAYER*NR_MAX_NB_LAYERS; //number of segments to be allocated + int num_rb = dlsch[0].dlsch_config.number_rbs; + if (num_rb != 273) { + a_segments = a_segments*num_rb; + a_segments = (a_segments/273)+1; + } + uint32_t dlsch_bytes = a_segments*1056; // allocated bytes per segment + __attribute__ ((aligned(32))) uint8_t p_b[dlsch_bytes]; + ret = nr_dlsch_decoding(ue, proc, gNB_id, @@ -772,7 +796,9 @@ bool nr_ue_dlsch_procedures(PHY_VARS_NR_UE *ue, frame_rx, nb_symb_sch, nr_slot_rx, - harq_pid); + harq_pid, + dlsch_bytes, + p_b); LOG_T(PHY,"dlsch decoding, ret = %d\n", ret); @@ -799,8 +825,8 @@ bool nr_ue_dlsch_procedures(PHY_VARS_NR_UE *ue, break; } - nr_fill_dl_indication(&dl_indication, NULL, rx_ind, proc, ue, NULL); - nr_fill_rx_indication(rx_ind, ind_type, ue, &dlsch[0], NULL, number_pdus, proc, NULL); + nr_fill_dl_indication(&dl_indication, NULL, &rx_ind, proc, ue, NULL); + nr_fill_rx_indication(&rx_ind, ind_type, ue, &dlsch[0], NULL, number_pdus, proc, NULL, p_b); LOG_D(PHY, "In %s DL PDU length in bits: %d, in bytes: %d \n", __FUNCTION__, dlsch[0].dlsch_config.TBS, dlsch[0].dlsch_config.TBS / 8); @@ -840,7 +866,9 @@ bool nr_ue_dlsch_procedures(PHY_VARS_NR_UE *ue, frame_rx, nb_symb_sch, nr_slot_rx, - harq_pid); + harq_pid, + dlsch_bytes, + p_b); LOG_T(PHY,"CW dlsch decoding, ret1 = %d\n", ret1); stop_meas(&ue->dlsch_decoding_stats); @@ -857,6 +885,13 @@ bool nr_ue_dlsch_procedures(PHY_VARS_NR_UE *ue, ue->if_inst->dl_indication(&dl_indication, ul_time_alignment); } + // DLSCH decoding finished! don't wait anymore + const int ack_nack_slot = (proc->nr_slot_rx + dlsch[0].dlsch_config.k1_feedback) % ue->frame_parms.slots_per_frame; + send_slot_ind(ue->tx_resume_ind_fifo[ack_nack_slot], proc->nr_slot_rx); + + if (ue->phy_sim_dlsch_b) + memcpy(ue->phy_sim_dlsch_b, p_b, dlsch_bytes); + if (ue->mac_enabled == 1) { // TODO: move this from PHY to MAC layer! /* Time Alignment procedure @@ -989,8 +1024,7 @@ bool nr_ue_dlsch_procedures(PHY_VARS_NR_UE *ue, return dec; } - -int phy_procedures_nrUE_RX(PHY_VARS_NR_UE *ue, +void pbch_pdcch_processing(PHY_VARS_NR_UE *ue, UE_nr_rxtx_proc_t *proc, nr_phy_data_t *phy_data) { @@ -1169,41 +1203,44 @@ int phy_procedures_nrUE_RX(PHY_VARS_NR_UE *ue, } dci_cnt = dci_cnt + nr_ue_pdcch_procedures(ue, proc, pdcch_est_size, pdcch_dl_ch_estimates, phy_data, n_ss, rxdataF); } + LOG_D(PHY,"[UE %d] Frame %d, nr_slot_rx %d: found %d DCIs\n", ue->Mod_id, frame_rx, nr_slot_rx, dci_cnt); phy_pdcch_config->nb_search_space = 0; VCD_SIGNAL_DUMPER_DUMP_FUNCTION_BY_NAME(VCD_SIGNAL_DUMPER_FUNCTIONS_UE_SLOT_FEP_PDCCH, VCD_FUNCTION_OUT); +} + +void pdsch_processing(PHY_VARS_NR_UE *ue, + UE_nr_rxtx_proc_t *proc, + nr_phy_data_t *phy_data) { + + int frame_rx = proc->frame_rx; + int nr_slot_rx = proc->nr_slot_rx; + int gNB_id = proc->gNB_id; NR_UE_DLSCH_t *dlsch = &phy_data->dlsch[0]; - if (dci_cnt > 0) { - - LOG_D(PHY,"[UE %d] Frame %d, nr_slot_rx %d: found %d DCIs\n", ue->Mod_id, frame_rx, nr_slot_rx, dci_cnt); - - if (dlsch->active) { - VCD_SIGNAL_DUMPER_DUMP_FUNCTION_BY_NAME(VCD_SIGNAL_DUMPER_FUNCTIONS_UE_SLOT_FEP_PDSCH, VCD_FUNCTION_IN); - uint16_t nb_symb_sch = dlsch[0].dlsch_config.number_symbols; - uint16_t start_symb_sch = dlsch[0].dlsch_config.start_symbol; - - LOG_D(PHY," ------ --> PDSCH ChannelComp/LLR Frame.slot %d.%d ------ \n", frame_rx%1024, nr_slot_rx); - //to update from pdsch config - - for (uint16_t m=start_symb_sch;m<(nb_symb_sch+start_symb_sch) ; m++){ - nr_slot_fep(ue, - proc, - m, //to be updated from higher layer - rxdataF); - } - VCD_SIGNAL_DUMPER_DUMP_FUNCTION_BY_NAME(VCD_SIGNAL_DUMPER_FUNCTIONS_UE_SLOT_FEP_PDSCH, VCD_FUNCTION_OUT); - } - } else { - LOG_D(PHY,"[UE %d] Frame %d, nr_slot_rx %d: No DCIs found\n", ue->Mod_id, frame_rx, nr_slot_rx); - } - #endif //NR_PDCCH_SCHED - start_meas(&ue->generic_stat); // do procedures for C-RNTI int ret_pdsch = 0; + + const uint32_t rxdataF_sz = ue->frame_parms.samples_per_slot_wCP; + __attribute__ ((aligned(32))) c16_t rxdataF[ue->frame_parms.nb_antennas_rx][rxdataF_sz]; if (dlsch[0].active) { + VCD_SIGNAL_DUMPER_DUMP_FUNCTION_BY_NAME(VCD_SIGNAL_DUMPER_FUNCTIONS_UE_SLOT_FEP_PDSCH, VCD_FUNCTION_IN); + uint16_t nb_symb_sch = dlsch[0].dlsch_config.number_symbols; + uint16_t start_symb_sch = dlsch[0].dlsch_config.start_symbol; + + LOG_D(PHY," ------ --> PDSCH ChannelComp/LLR Frame.slot %d.%d ------ \n", frame_rx%1024, nr_slot_rx); + //to update from pdsch config + + for (uint16_t m=start_symb_sch;m<(nb_symb_sch+start_symb_sch) ; m++){ + nr_slot_fep(ue, + proc, + m, //to be updated from higher layer + rxdataF); + } + VCD_SIGNAL_DUMPER_DUMP_FUNCTION_BY_NAME(VCD_SIGNAL_DUMPER_FUNCTIONS_UE_SLOT_FEP_PDSCH, VCD_FUNCTION_OUT); + uint8_t nb_re_dmrs; if (dlsch[0].dlsch_config.dmrsConfigType == NFAPI_NR_DMRS_TYPE1) { nb_re_dmrs = 6*dlsch[0].dlsch_config.n_dmrs_cdm_groups; @@ -1243,6 +1280,9 @@ int phy_procedures_nrUE_RX(PHY_VARS_NR_UE *ue, if (ret_pdsch >= 0) nr_ue_dlsch_procedures(ue, proc, dlsch, llr); + else + // don't wait anymore + send_slot_ind(ue->tx_resume_ind_fifo[(proc->nr_slot_rx + dlsch[0].dlsch_config.k1_feedback) % ue->frame_parms.slots_per_frame], proc->nr_slot_rx); stop_meas(&ue->dlsch_procedures_stat); if (cpumeas(CPUMEAS_GETSTATE)) { @@ -1333,7 +1373,6 @@ int phy_procedures_nrUE_RX(PHY_VARS_NR_UE *ue, LOG_D(PHY," ****** end RX-Chain for AbsSubframe %d.%d ****** \n", frame_rx%1024, nr_slot_rx); UEscopeCopy(ue, commonRxdataF, rxdataF, sizeof(int32_t), ue->frame_parms.nb_antennas_rx, rxdataF_sz); - return (0); } diff --git a/openair1/SIMULATION/NR_PHY/dlschsim.c b/openair1/SIMULATION/NR_PHY/dlschsim.c index cdbe21465f..8b56ed0f4e 100644 --- a/openair1/SIMULATION/NR_PHY/dlschsim.c +++ b/openair1/SIMULATION/NR_PHY/dlschsim.c @@ -542,9 +542,17 @@ int main(int argc, char **argv) vcd_signal_dumper_dump_function_by_name(VCD_SIGNAL_DUMPER_FUNCTIONS_DLSCH_DECODING0, VCD_FUNCTION_IN); + int a_segments = MAX_NUM_NR_DLSCH_SEGMENTS_PER_LAYER*NR_MAX_NB_LAYERS; //number of segments to be allocated + int num_rb = dlsch0_ue->dlsch_config.number_rbs; + if (num_rb != 273) { + a_segments = a_segments*num_rb; + a_segments = (a_segments/273)+1; + } + uint32_t dlsch_bytes = a_segments*1056; // allocated bytes per segment + __attribute__ ((aligned(32))) uint8_t b[dlsch_bytes]; ret = nr_dlsch_decoding(UE, &proc, 0, channel_output_fixed, &UE->frame_parms, dlsch0_ue, harq_process, frame, nb_symb_sch, - slot,harq_pid); + slot,harq_pid,dlsch_bytes,b); vcd_signal_dumper_dump_function_by_name(VCD_SIGNAL_DUMPER_FUNCTIONS_DLSCH_DECODING0, VCD_FUNCTION_OUT); @@ -555,7 +563,7 @@ int main(int argc, char **argv) errors_bit = 0; for (i = 0; i < TBS; i++) { - estimated_output_bit[i] = (harq_process->b[i / 8] & (1 << (i & 7))) >> (i & 7); + estimated_output_bit[i] = (b[i / 8] & (1 << (i & 7))) >> (i & 7); test_input_bit[i] = (test_input[i / 8] & (1 << (i & 7))) >> (i & 7); // Further correct for multiple segments if (estimated_output_bit[i] != test_input_bit[i]) { diff --git a/openair1/SIMULATION/NR_PHY/dlsim.c b/openair1/SIMULATION/NR_PHY/dlsim.c index b2c9ac5a33..dce9817557 100644 --- a/openair1/SIMULATION/NR_PHY/dlsim.c +++ b/openair1/SIMULATION/NR_PHY/dlsim.c @@ -940,6 +940,13 @@ int main(int argc, char **argv) UE->phy_sim_pdsch_rxdataF_comp = calloc(sizeof(int32_t *) * frame_parms->nb_antennas_rx * g_nrOfLayers, rx_size); UE->phy_sim_pdsch_dl_ch_estimates = calloc(sizeof(int32_t *) * frame_parms->nb_antennas_rx * g_nrOfLayers, rx_size); UE->phy_sim_pdsch_dl_ch_estimates_ext = calloc(sizeof(int32_t *) * frame_parms->nb_antennas_rx * g_nrOfLayers, rx_size); + int a_segments = MAX_NUM_NR_DLSCH_SEGMENTS_PER_LAYER*NR_MAX_NB_LAYERS; //number of segments to be allocated + if (g_rbSize != 273) { + a_segments = a_segments*g_rbSize; + a_segments = (a_segments/273)+1; + } + uint32_t dlsch_bytes = a_segments*1056; // allocated bytes per segment + UE->phy_sim_dlsch_b = calloc(1, dlsch_bytes); for (SNR = snr0; SNR < snr1; SNR += .2) { @@ -1153,9 +1160,12 @@ int main(int argc, char **argv) nr_ue_dcireq(&dcireq); //to be replaced with function pointer later nr_ue_scheduled_response(&scheduled_response); - phy_procedures_nrUE_RX(UE, - &UE_proc, - &phy_data); + pbch_pdcch_processing(UE, + &UE_proc, + &phy_data); + pdsch_processing(UE, + &UE_proc, + &phy_data); //---------------------------------------------------------- //---------------------- count errors ---------------------- @@ -1198,7 +1208,7 @@ int main(int argc, char **argv) for (i = 0; i < TBS; i++) { - estimated_output_bit[i] = (UE_harq_process->b[i/8] & (1 << (i & 7))) >> (i & 7); + estimated_output_bit[i] = (UE->phy_sim_dlsch_b[i/8] & (1 << (i & 7))) >> (i & 7); test_input_bit[i] = (gNB_dlsch->harq_process.b[i / 8] & (1 << (i & 7))) >> (i & 7); // Further correct for multiple segments if (estimated_output_bit[i] != test_input_bit[i]) { @@ -1347,6 +1357,7 @@ int main(int argc, char **argv) free(UE->phy_sim_pdsch_rxdataF_comp); free(UE->phy_sim_pdsch_dl_ch_estimates); free(UE->phy_sim_pdsch_dl_ch_estimates_ext); + free(UE->phy_sim_dlsch_b); if (output_fd) fclose(output_fd); diff --git a/openair1/SIMULATION/NR_PHY/pbchsim.c b/openair1/SIMULATION/NR_PHY/pbchsim.c index ee67e8d52e..f1bcf65cc5 100644 --- a/openair1/SIMULATION/NR_PHY/pbchsim.c +++ b/openair1/SIMULATION/NR_PHY/pbchsim.c @@ -96,7 +96,8 @@ void nr_fill_rx_indication(fapi_nr_rx_indication_t *rx_ind, NR_UE_DLSCH_t *dlsch1, uint16_t n_pdus, UE_nr_rxtx_proc_t *proc, - void *typeSpecific ) {} + void *typeSpecific, + uint8_t *b) {} int nr_ue_pdcch_procedures(PHY_VARS_NR_UE *ue, UE_nr_rxtx_proc_t *proc, diff --git a/openair2/LAYER2/NR_MAC_UE/config_ue.c b/openair2/LAYER2/NR_MAC_UE/config_ue.c index bd08153aff..8dfc05952d 100644 --- a/openair2/LAYER2/NR_MAC_UE/config_ue.c +++ b/openair2/LAYER2/NR_MAC_UE/config_ue.c @@ -701,6 +701,9 @@ int nr_rrc_mac_config_req_ue(module_id_t module_id, RA_config_t *ra = &mac->ra; fapi_nr_config_request_t *cfg = &mac->phy_config.config_req; + if (mac->dl_config_request == NULL) // for SIB1 reception + mac->dl_config_request = calloc(NR_MAX_SLOTS_PER_FRAME, sizeof(*mac->dl_config_request)); + // TODO do something FAPI-like P5 L1/L2 config interface in config_si, config_mib, etc. if(mibP != NULL){ @@ -726,9 +729,21 @@ int nr_rrc_mac_config_req_ue(module_id_t module_id, } } LOG_I(NR_MAC, "Initializing ul_config_request. num_slots_ul = %d\n", num_slots_ul); - mac->ul_config_request = (fapi_nr_ul_config_request_t *)calloc(num_slots_ul, sizeof(fapi_nr_ul_config_request_t)); + mac->ul_config_request = calloc(num_slots_ul, sizeof(*mac->ul_config_request)); for (int i=0; iul_config_request[i].mutex_ul_config), NULL); + + int num_slots_dl = nr_slots_per_frame[mac->mib->subCarrierSpacingCommon]; + if (cfg->cell_config.frame_duplex_type == TDD) { + num_slots_dl = mac->scc_SIB->tdd_UL_DL_ConfigurationCommon->pattern1.nrofDownlinkSlots; + if (mac->scc_SIB->tdd_UL_DL_ConfigurationCommon->pattern1.nrofDownlinkSymbols > 0) { + num_slots_dl++; + } + } + LOG_I(NR_MAC, "Initializing dl_config_request. num_slots_dl = %d\n", num_slots_dl); + mac->dl_config_request = realloc(mac->dl_config_request, num_slots_dl*sizeof(*mac->dl_config_request)); + memset(mac->dl_config_request, 0, num_slots_dl*sizeof(fapi_nr_dl_config_request_t)); + // Setup the SSB to Rach Occasionsif (cell_group_config->spCellConfig) { mapping according to the config build_ssb_to_ro_map(mac);//->scc, mac->phy_config.config_req.cell_config.frame_duplex_type); if (!get_softmodem_params()->emulate_l1) diff --git a/openair2/LAYER2/NR_MAC_UE/mac_defs.h b/openair2/LAYER2/NR_MAC_UE/mac_defs.h index b1af5393a1..c91521a807 100644 --- a/openair2/LAYER2/NR_MAC_UE/mac_defs.h +++ b/openair2/LAYER2/NR_MAC_UE/mac_defs.h @@ -441,7 +441,7 @@ typedef struct { int first_ul_tx[NR_MAX_HARQ_PROCESSES]; //// FAPI-like interface message fapi_nr_ul_config_request_t *ul_config_request; - fapi_nr_dl_config_request_t dl_config_request; + fapi_nr_dl_config_request_t *dl_config_request; /// Interface module instances nr_ue_if_module_t *if_module; @@ -467,7 +467,7 @@ typedef struct { NR_PHY_meas_t phy_measurements; - dci_pdu_rel15_t def_dci_pdu_rel15[8]; + dci_pdu_rel15_t def_dci_pdu_rel15[NR_MAX_SLOTS_PER_FRAME][8]; // Defined for abstracted mode nr_downlink_indication_t dl_info; diff --git a/openair2/LAYER2/NR_MAC_UE/mac_proto.h b/openair2/LAYER2/NR_MAC_UE/mac_proto.h index 9776003e3f..9a7f6084e0 100644 --- a/openair2/LAYER2/NR_MAC_UE/mac_proto.h +++ b/openair2/LAYER2/NR_MAC_UE/mac_proto.h @@ -441,9 +441,11 @@ static uint8_t nr_extract_dci_info(NR_UE_MAC_INST_t *mac, uint16_t rnti, int ss_type, uint64_t *dci_pdu, - dci_pdu_rel15_t *dci_pdu_rel15); + dci_pdu_rel15_t *dci_pdu_rel15, + int slot); fapi_nr_ul_config_request_t *get_ul_config_request(NR_UE_MAC_INST_t *mac, int slot); +fapi_nr_dl_config_request_t *get_dl_config_request(NR_UE_MAC_INST_t *mac, int slot); void fill_ul_config(fapi_nr_ul_config_request_t *ul_config, frame_t frame_tx, int slot_tx, uint8_t pdu_type); 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 45b6a56978..5aa4438af0 100644 --- a/openair2/LAYER2/NR_MAC_UE/nr_ue_dci_configuration.c +++ b/openair2/LAYER2/NR_MAC_UE/nr_ue_dci_configuration.c @@ -172,7 +172,7 @@ void config_dci_pdu(NR_UE_MAC_INST_t *mac, fapi_nr_dl_config_dci_dl_pdu_rel15_t rel15->dci_length_options[i] = nr_dci_size(current_DL_BWP, current_UL_BWP, mac->cg, - &mac->def_dci_pdu_rel15[dci_format], + &mac->def_dci_pdu_rel15[dl_config->slot][dci_format], dci_format, NR_RNTI_TC, coreset, diff --git a/openair2/LAYER2/NR_MAC_UE/nr_ue_procedures.c b/openair2/LAYER2/NR_MAC_UE/nr_ue_procedures.c index 3978d52711..0ea9dc0f9d 100644 --- a/openair2/LAYER2/NR_MAC_UE/nr_ue_procedures.c +++ b/openair2/LAYER2/NR_MAC_UE/nr_ue_procedures.c @@ -316,9 +316,6 @@ int8_t nr_ue_decode_mib(module_id_t module_id, mac->first_sync_frame = frame; } - mac->dl_config_request.sfn = frame; - mac->dl_config_request.slot = ssb_start_symbol/14; - if(get_softmodem_params()->phy_test) mac->state = UE_CONNECTED; else if(mac->state == UE_NOT_SYNC) @@ -419,15 +416,15 @@ int8_t nr_ue_process_dci_freq_dom_resource_assignment(nfapi_nr_ue_pusch_pdu_t *p int nr_ue_process_dci_indication_pdu(module_id_t module_id,int cc_id, int gNB_index, frame_t frame, int slot, fapi_nr_dci_indication_pdu_t *dci) { NR_UE_MAC_INST_t *mac = get_mac_inst(module_id); - dci_pdu_rel15_t *def_dci_pdu_rel15 = &mac->def_dci_pdu_rel15[dci->dci_format]; + dci_pdu_rel15_t *def_dci_pdu_rel15 = &mac->def_dci_pdu_rel15[slot][dci->dci_format]; LOG_D(MAC,"Received dci indication (rnti %x,dci format %d,n_CCE %d,payloadSize %d,payload %llx)\n", dci->rnti,dci->dci_format,dci->n_CCE,dci->payloadSize,*(unsigned long long*)dci->payloadBits); - int8_t ret = nr_extract_dci_info(mac, dci->dci_format, dci->payloadSize, dci->rnti, dci->ss_type, (uint64_t *)dci->payloadBits, def_dci_pdu_rel15); + int8_t ret = nr_extract_dci_info(mac, dci->dci_format, dci->payloadSize, dci->rnti, dci->ss_type, (uint64_t *)dci->payloadBits, def_dci_pdu_rel15, slot); if ((ret&1) == 1) return -1; else if (ret == 2) { dci->dci_format = NR_UL_DCI_FORMAT_0_0; - def_dci_pdu_rel15 = &mac->def_dci_pdu_rel15[dci->dci_format]; + def_dci_pdu_rel15 = &mac->def_dci_pdu_rel15[slot][dci->dci_format]; } int8_t ret_proc = nr_ue_process_dci(module_id, cc_id, gNB_index, frame, slot, def_dci_pdu_rel15, dci); return ret_proc; @@ -445,7 +442,7 @@ int8_t nr_ue_process_dci(module_id_t module_id, int cc_id, uint8_t gNB_index, fr bool valid_ptrs_setup = 0; NR_UE_MAC_INST_t *mac = get_mac_inst(module_id); RA_config_t *ra = &mac->ra; - fapi_nr_dl_config_request_t *dl_config = &mac->dl_config_request; + fapi_nr_dl_config_request_t *dl_config = get_dl_config_request(mac, slot); uint8_t is_Msg3 = 0; NR_UE_DL_BWP_t *current_DL_BWP = &mac->current_DL_BWP; NR_UE_UL_BWP_t *current_UL_BWP = &mac->current_UL_BWP; @@ -809,6 +806,8 @@ int8_t nr_ue_process_dci(module_id_t module_id, int cc_id, uint8_t gNB_index, fr dlsch_config_pdu_1_0->accumulated_delta_PUCCH, dci->pucch_resource_indicator, 1+dci->pdsch_to_harq_feedback_timing_indicator.val); + + dlsch_config_pdu_1_0->k1_feedback = 1+dci->pdsch_to_harq_feedback_timing_indicator.val; LOG_D(MAC,"(nr_ue_procedures.c) pdu_type=%d\n\n",dl_config->dl_config_list[dl_config->number_pdus].pdu_type); @@ -1071,7 +1070,7 @@ int8_t nr_ue_process_dci(module_id_t module_id, int cc_id, uint8_t gNB_index, fr dlsch_config_pdu_1_1->n_front_load_symb); /* TCI */ - if (mac->dl_config_request.dl_config_list[0].dci_config_pdu.dci_config_rel15.coreset.tci_present_in_dci == 1){ + if (dl_config->dl_config_list[dl_config->number_pdus].dci_config_pdu.dci_config_rel15.coreset.tci_present_in_dci == 1){ // 0 bit if higher layer parameter tci-PresentInDCI is not enabled // otherwise 3 bits as defined in Subclause 5.1.5 of [6, TS38.214] dlsch_config_pdu_1_1->tci_state = dci->transmission_configuration_indication.val; @@ -1106,6 +1105,10 @@ int8_t nr_ue_process_dci(module_id_t module_id, int cc_id, uint8_t gNB_index, fr LOG_D(MAC,"(nr_ue_procedures.c) pdu_type=%d\n\n",dl_config->dl_config_list[dl_config->number_pdus].pdu_type); dl_config->number_pdus = dl_config->number_pdus + 1; + + // send the ack/nack slot number to phy to indicate tx thread to wait for DLSCH decoding + dlsch_config_pdu_1_1->k1_feedback = feedback_ti; + /* TODO same calculation for MCS table as done in UL */ dlsch_config_pdu_1_1->mcs_table = (pdsch_Config->mcs_Table) ? (*pdsch_Config->mcs_Table + 1) : 0; dlsch_config_pdu_1_1->qamModOrder = nr_get_Qm_dl(dlsch_config_pdu_1_1->mcs, dlsch_config_pdu_1_1->mcs_table); @@ -1208,14 +1211,12 @@ void set_harq_status(NR_UE_MAC_INST_t *mac, // FIXME k0 != 0 currently not taken into consideration current_harq->dl_frame = frame; current_harq->dl_slot = slot; - if (get_softmodem_params()->emulate_l1) { - int scs = get_softmodem_params()->numerology; - int slots_per_frame = nr_slots_per_frame[scs]; - slot += data_toul_fb; - if (slot >= slots_per_frame) { - frame = (frame + 1) % 1024; - slot %= slots_per_frame; - } + int scs = get_softmodem_params()->numerology; + int slots_per_frame = nr_slots_per_frame[scs]; + slot += data_toul_fb; + if (slot >= slots_per_frame) { + frame = (frame + 1) % 1024; + slot %= slots_per_frame; } LOG_D(NR_PHY,"Setting harq_status for harq_id %d, dl %d.%d, sched ul %d.%d\n", @@ -1820,10 +1821,14 @@ uint8_t get_downlink_ack(NR_UE_MAC_INST_t *mac, } number_harq_feedback++; - if (current_harq->ack_received) + if (current_harq->ack_received) { ack_data[code_word][dai_current - 1] = current_harq->ack; - else + current_harq->active = false; + current_harq->ack_received = false; + } else { + LOG_W(NR_MAC, "DLSCH ACK/NACK reporting initiated for harq pid %d before DLSCH decoding completed\n", dl_harq_pid); ack_data[code_word][dai_current - 1] = 0; + } dai[code_word][dai_current - 1] = dai_current; pucch->resource_indicator = current_harq->pucch_resource_indicator; @@ -1831,9 +1836,7 @@ uint8_t get_downlink_ack(NR_UE_MAC_INST_t *mac, pucch->N_CCE = current_harq->N_CCE; pucch->delta_pucch = current_harq->delta_pucch; pucch->is_common = current_harq->is_common; - current_harq->active = false; - current_harq->ack_received = false; - LOG_D(PHY,"%4d.%2d Sent %d ack on harq pid %d\n", frame, slot, current_harq->ack, dl_harq_pid); + LOG_D(PHY,"%4d.%2d Sent %d ack on harq pid %d\n", frame, slot, current_harq->ack, dl_harq_pid); } } } @@ -2398,7 +2401,8 @@ static uint8_t nr_extract_dci_info(NR_UE_MAC_INST_t *mac, uint16_t rnti, int ss_type, uint64_t *dci_pdu, - dci_pdu_rel15_t *dci_pdu_rel15) + dci_pdu_rel15_t *dci_pdu_rel15, + int slot) { int pos = 0; @@ -2466,8 +2470,8 @@ static uint8_t nr_extract_dci_info(NR_UE_MAC_INST_t *mac, //switch to DCI_0_0 if (dci_pdu_rel15->format_indicator == 0) { - dci_pdu_rel15 = &mac->def_dci_pdu_rel15[NR_UL_DCI_FORMAT_0_0]; - return 2+nr_extract_dci_info(mac, NR_UL_DCI_FORMAT_0_0, dci_size, rnti, ss_type, dci_pdu, dci_pdu_rel15); + dci_pdu_rel15 = &mac->def_dci_pdu_rel15[slot][NR_UL_DCI_FORMAT_0_0]; + return 2+nr_extract_dci_info(mac, NR_UL_DCI_FORMAT_0_0, dci_size, rnti, ss_type, dci_pdu, dci_pdu_rel15, slot); } #ifdef DEBUG_EXTRACT_DCI LOG_D(MAC,"Format indicator %d (%d bits) N_RB_BWP %d => %d (0x%lx)\n",dci_pdu_rel15->format_indicator,1,N_RB,dci_size-pos,*dci_pdu); @@ -2657,8 +2661,8 @@ static uint8_t nr_extract_dci_info(NR_UE_MAC_INST_t *mac, //switch to DCI_0_0 if (dci_pdu_rel15->format_indicator == 0) { - dci_pdu_rel15 = &mac->def_dci_pdu_rel15[NR_UL_DCI_FORMAT_0_0]; - return 2+nr_extract_dci_info(mac, NR_UL_DCI_FORMAT_0_0, dci_size, rnti, ss_type, dci_pdu, dci_pdu_rel15); + dci_pdu_rel15 = &mac->def_dci_pdu_rel15[slot][NR_UL_DCI_FORMAT_0_0]; + return 2+nr_extract_dci_info(mac, NR_UL_DCI_FORMAT_0_0, dci_size, rnti, ss_type, dci_pdu, dci_pdu_rel15, slot); } if (dci_pdu_rel15->format_indicator == 0) diff --git a/openair2/LAYER2/NR_MAC_UE/nr_ue_scheduler.c b/openair2/LAYER2/NR_MAC_UE/nr_ue_scheduler.c index b0d181f3da..5a0a8eb303 100644 --- a/openair2/LAYER2/NR_MAC_UE/nr_ue_scheduler.c +++ b/openair2/LAYER2/NR_MAC_UE/nr_ue_scheduler.c @@ -112,11 +112,8 @@ fapi_nr_ul_config_request_t *get_ul_config_request(NR_UE_MAC_INST_t *mac, int sl NR_TDD_UL_DL_ConfigCommon_t *tdd_config = mac->scc==NULL ? mac->scc_SIB->tdd_UL_DL_ConfigurationCommon : mac->scc->tdd_UL_DL_ConfigurationCommon; - //Check if request to access ul_config is for a UL slot - if (is_nr_UL_slot(tdd_config, slot, mac->frame_type) == 0) { - LOG_W(NR_MAC, "Slot %d is not a UL slot. %s called for wrong slot!!!\n", slot, __FUNCTION__); - return NULL; - } + //Check if requested on the right slot + AssertFatal(is_nr_UL_slot(tdd_config, slot, mac->frame_type) != 0, "%s called at wrong slot %d\n", __func__, slot); // Calculate the index of the UL slot in mac->ul_config_request list. This is // based on the TDD pattern (slot configuration period) and number of UL+mixed @@ -127,16 +124,57 @@ fapi_nr_ul_config_request_t *get_ul_config_request(NR_UE_MAC_INST_t *mac, int sl const int num_slots_ul = tdd_config ? (tdd_config->pattern1.nrofUplinkSlots + (tdd_config->pattern1.nrofUplinkSymbols != 0)) : n; int index = slot % num_slots_ul; - LOG_D(NR_MAC, "In %s slots per %s: %d, num_slots_ul %d, index %d\n", + LOG_D(NR_MAC, "In %s slots per %s: %d, num_slots %d, index %d\n", __FUNCTION__, tdd_config ? "TDD" : "FDD", num_slots_per_tdd, num_slots_ul, index); - if(!mac->ul_config_request) + if (mac->ul_config_request) return &mac->ul_config_request[index]; + else { + LOG_E(NR_MAC, "mac->ul_config_request not set\n"); return NULL; - return &mac->ul_config_request[index]; + } +} + +/* + * This function returns the DL config corresponding to a given DL slot + * from MAC instance . + */ +fapi_nr_dl_config_request_t *get_dl_config_request(NR_UE_MAC_INST_t *mac, int slot) +{ + int index; + if (!mac->scc && !mac->scc_SIB) + index = 0; + else { + NR_TDD_UL_DL_ConfigCommon_t *tdd_config = mac->scc==NULL ? mac->scc_SIB->tdd_UL_DL_ConfigurationCommon : mac->scc->tdd_UL_DL_ConfigurationCommon; + + //Check if requested on the right slot + AssertFatal(is_nr_DL_slot(tdd_config, slot) != 0, "%s called at wrong slot %d\n", __func__, slot); + + // Calculate the index of the DL slot in mac->ul_config_request list. This is + // based on the TDD pattern (slot configuration period) and number of DL+mixed + // slots in the period. TS 38.213 Sec 11.1 + int mu = mac->current_UL_BWP.scs; + const int n = nr_slots_per_frame[mu]; + const int num_slots_per_tdd = tdd_config ? (n >> (7 - tdd_config->pattern1.dl_UL_TransmissionPeriodicity)) : n; + const int num_slots_dl = tdd_config ? (tdd_config->pattern1.nrofDownlinkSlots + (tdd_config->pattern1.nrofDownlinkSymbols != 0)) : n; + + index = slot % num_slots_dl; + LOG_D(NR_MAC, "In %s slots per %s: %d, num_slots %d, index %d\n", + __FUNCTION__, + tdd_config ? "TDD" : "FDD", + num_slots_per_tdd, + num_slots_dl, + index); + } + + if (mac->dl_config_request) return &mac->dl_config_request[index]; + else { + LOG_E(NR_MAC, "mac->dl_config_request not set\n"); + return NULL; + } } void ul_layers_config(NR_UE_MAC_INST_t *mac, nfapi_nr_ue_pusch_pdu_t *pusch_config_pdu, dci_pdu_rel15_t *dci, nr_dci_format_t dci_format) @@ -895,7 +933,9 @@ void nr_ue_dl_scheduler(nr_downlink_indication_t *dl_info) slot_t rx_slot = dl_info->slot; NR_UE_MAC_INST_t *mac = get_mac_inst(mod_id); - fapi_nr_dl_config_request_t *dl_config = &mac->dl_config_request; + fapi_nr_dl_config_request_t *dl_config = get_dl_config_request(mac, rx_slot); + dl_config->sfn = rx_frame; + dl_config->slot = rx_slot; nr_scheduled_response_t scheduled_response; nr_dcireq_t dcireq; @@ -909,11 +949,11 @@ void nr_ue_dl_scheduler(nr_downlink_indication_t *dl_info) dcireq.slot = rx_slot; dcireq.dl_config_req.number_pdus = 0; nr_ue_dcireq(&dcireq); //to be replaced with function pointer later - mac->dl_config_request = dcireq.dl_config_req; + *dl_config = dcireq.dl_config_req; nr_schedule_csirs_reception(mac, rx_frame, rx_slot); nr_schedule_csi_for_im(mac, rx_frame, rx_slot); - dcireq.dl_config_req = mac->dl_config_request; + dcireq.dl_config_req = *dl_config; fill_scheduled_response(&scheduled_response, &dcireq.dl_config_req, NULL, NULL, mod_id, cc_id, rx_frame, rx_slot, dl_info->phy_data); if(mac->if_module != NULL && mac->if_module->scheduled_response != NULL) { @@ -2177,7 +2217,7 @@ void nr_schedule_csi_for_im(NR_UE_MAC_INST_t *mac, int frame, int slot) { if (csi_measconfig->csi_IM_ResourceToAddModList == NULL) return; - fapi_nr_dl_config_request_t *dl_config = &mac->dl_config_request; + fapi_nr_dl_config_request_t *dl_config = get_dl_config_request(mac, slot); NR_CSI_IM_Resource_t *imcsi; int period, offset; @@ -2231,7 +2271,7 @@ void nr_schedule_csirs_reception(NR_UE_MAC_INST_t *mac, int frame, int slot) { if (csi_measconfig->nzp_CSI_RS_ResourceToAddModList == NULL) return; - fapi_nr_dl_config_request_t *dl_config = &mac->dl_config_request; + fapi_nr_dl_config_request_t *dl_config = get_dl_config_request(mac, slot); NR_NZP_CSI_RS_Resource_t *nzpcsi; int period, offset; NR_UE_DL_BWP_t *current_DL_BWP = &mac->current_DL_BWP; @@ -2553,9 +2593,6 @@ void nr_ue_sib1_scheduler(module_id_t module_idP, NR_UE_MAC_INST_t *mac = get_mac_inst(module_idP); nr_scheduled_response_t scheduled_response; - int frame_s,slot_s; - fapi_nr_dl_config_request_t *dl_config = &mac->dl_config_request; - fapi_nr_dl_config_dci_dl_pdu_rel15_t *rel15; uint8_t scs_ssb = get_softmodem_params()->numerology; uint16_t ssb_offset_point_a = (ssb_start_subcarrier - ssb_subcarrier_offset)/12; @@ -2573,17 +2610,7 @@ void nr_ue_sib1_scheduler(module_id_t module_idP, 1, // If the UE is not configured with a periodicity, the UE assumes a periodicity of a half frame ssb_offset_point_a); - if(mac->search_space_zero == NULL) mac->search_space_zero=calloc(1,sizeof(*mac->search_space_zero)); - if(mac->coreset0 == NULL) mac->coreset0 = calloc(1,sizeof(*mac->coreset0)); - - fill_coresetZero(mac->coreset0, &mac->type0_PDCCH_CSS_config); - fill_searchSpaceZero(mac->search_space_zero, &mac->type0_PDCCH_CSS_config); - rel15 = &dl_config->dl_config_list[dl_config->number_pdus].dci_config_pdu.dci_config_rel15; - rel15->num_dci_options = 1; - rel15->dci_format_options[0] = NR_DL_DCI_FORMAT_1_0; - config_dci_pdu(mac, rel15, dl_config, NR_RNTI_SI, -1); - fill_dci_search_candidates(mac->search_space_zero, rel15, -1, -1); - + int frame_s,slot_s; if(mac->type0_PDCCH_CSS_config.type0_pdcch_ss_mux_pattern == 1){ // same frame as ssb if ((mac->type0_PDCCH_CSS_config.frame & 0x1) == mac->type0_PDCCH_CSS_config.sfn_c) @@ -2596,6 +2623,20 @@ void nr_ue_sib1_scheduler(module_id_t module_idP, frame_s = 0; // same frame as ssb slot_s = mac->type0_PDCCH_CSS_config.n_c; } + fapi_nr_dl_config_request_t *dl_config = &mac->dl_config_request[0]; // Take the first dl_config_request for SIB1 + fapi_nr_dl_config_dci_dl_pdu_rel15_t *rel15; + + if(mac->search_space_zero == NULL) mac->search_space_zero=calloc(1,sizeof(*mac->search_space_zero)); + if(mac->coreset0 == NULL) mac->coreset0 = calloc(1,sizeof(*mac->coreset0)); + + fill_coresetZero(mac->coreset0, &mac->type0_PDCCH_CSS_config); + fill_searchSpaceZero(mac->search_space_zero, &mac->type0_PDCCH_CSS_config); + rel15 = &dl_config->dl_config_list[dl_config->number_pdus].dci_config_pdu.dci_config_rel15; + rel15->num_dci_options = 1; + rel15->dci_format_options[0] = NR_DL_DCI_FORMAT_1_0; + config_dci_pdu(mac, rel15, dl_config, NR_RNTI_SI, -1); + fill_dci_search_candidates(mac->search_space_zero, rel15, -1, -1); + LOG_D(MAC,"Calling fill_scheduled_response, type0_pdcch, num_pdus %d\n",dl_config->number_pdus); fill_scheduled_response(&scheduled_response, dl_config, NULL, NULL, module_idP, cc_id, frame_s, slot_s, phy_data); diff --git a/openair2/NR_UE_PHY_INTERFACE/NR_IF_Module.c b/openair2/NR_UE_PHY_INTERFACE/NR_IF_Module.c index 5ddd59fcc1..a950d746db 100644 --- a/openair2/NR_UE_PHY_INTERFACE/NR_IF_Module.c +++ b/openair2/NR_UE_PHY_INTERFACE/NR_IF_Module.c @@ -66,6 +66,7 @@ queue_t nr_dl_tti_req_queue; queue_t nr_tx_req_queue; queue_t nr_ul_dci_req_queue; queue_t nr_ul_tti_req_queue; +pthread_mutex_t mac_IF_mutex; void nrue_init_standalone_socket(int tx_port, int rx_port) { @@ -692,7 +693,6 @@ void check_and_process_dci(nfapi_nr_dl_tti_request_t *dl_tti_request, slot = dl_tti_request->Slot; LOG_D(NR_PHY, "[%d, %d] dl_tti_request\n", frame, slot); copy_dl_tti_req_to_dl_info(&mac->dl_info, dl_tti_request); - free_and_zero(dl_tti_request); } /* This checks if the previously recevied DCI matches our current RNTI value. The assumption is that if the DCI matches our RNTI, then the @@ -711,14 +711,12 @@ void check_and_process_dci(nfapi_nr_dl_tti_request_t *dl_tti_request, else { LOG_D(NR_MAC, "Unexpected tx_data_req\n"); } - free_and_zero(tx_data_request); } else if (ul_dci_request) { frame = ul_dci_request->SFN; slot = ul_dci_request->Slot; LOG_D(NR_PHY, "[%d, %d] ul_dci_request\n", frame, slot); copy_ul_dci_data_req_to_dl_info(&mac->dl_info, ul_dci_request); - free_and_zero(ul_dci_request); } else if (ul_tti_request) { frame = ul_tti_request->SFN; @@ -735,7 +733,10 @@ void check_and_process_dci(nfapi_nr_dl_tti_request_t *dl_tti_request, NR_UL_TIME_ALIGNMENT_t ul_time_alignment; memset(&ul_time_alignment, 0, sizeof(ul_time_alignment)); - fill_dci_from_dl_config(&mac->dl_info, &mac->dl_config_request); + if (dl_tti_request || tx_data_request || ul_dci_request) { + fapi_nr_dl_config_request_t *dl_config = get_dl_config_request(mac, slot); + fill_dci_from_dl_config(&mac->dl_info, dl_config); + } nr_ue_dl_scheduler(&mac->dl_info); nr_ue_dl_indication(&mac->dl_info, &ul_time_alignment); @@ -1116,6 +1117,9 @@ void update_harq_status(module_id_t module_id, uint8_t harq_pid, uint8_t ack_nac int nr_ue_ul_indication(nr_uplink_indication_t *ul_info) { + + pthread_mutex_lock(&mac_IF_mutex); + module_id_t module_id = ul_info->module_id; NR_UE_MAC_INST_t *mac = get_mac_inst(module_id); @@ -1125,15 +1129,19 @@ int nr_ue_ul_indication(nr_uplink_indication_t *ul_info) NR_TDD_UL_DL_ConfigCommon_t *tdd_UL_DL_ConfigurationCommon = mac->scc != NULL ? mac->scc->tdd_UL_DL_ConfigurationCommon : mac->scc_SIB->tdd_UL_DL_ConfigurationCommon; if (mac->phy_config_request_sent && is_nr_UL_slot(tdd_UL_DL_ConfigurationCommon, ul_info->slot_tx, mac->frame_type)) nr_ue_ul_scheduler(ul_info); + + pthread_mutex_unlock(&mac_IF_mutex); + return 0; } int nr_ue_dl_indication(nr_downlink_indication_t *dl_info, NR_UL_TIME_ALIGNMENT_t *ul_time_alignment){ + pthread_mutex_lock(&mac_IF_mutex); uint32_t ret_mask = 0x0; module_id_t module_id = dl_info->module_id; NR_UE_MAC_INST_t *mac = get_mac_inst(module_id); - fapi_nr_dl_config_request_t *dl_config = &mac->dl_config_request; + fapi_nr_dl_config_request_t *dl_config = get_dl_config_request(mac, dl_info->slot); if ((!dl_info->dci_ind && !dl_info->rx_ind)) { // UL indication to schedule DCI reception @@ -1160,7 +1168,7 @@ int nr_ue_dl_indication(nr_downlink_indication_t *dl_info, NR_UL_TIME_ALIGNMENT_ LOG_D(NR_MAC, "We are filtering a UL_DCI to prevent it from being treated like a DL_DCI\n"); continue; } - dci_pdu_rel15_t *def_dci_pdu_rel15 = &mac->def_dci_pdu_rel15[dci_index->dci_format]; + dci_pdu_rel15_t *def_dci_pdu_rel15 = &mac->def_dci_pdu_rel15[dl_info->slot][dci_index->dci_format]; g_harq_pid = def_dci_pdu_rel15->harq_pid; LOG_T(NR_MAC, "Setting harq_pid = %d and dci_index = %d (based on format)\n", g_harq_pid, dci_index->dci_format); @@ -1222,10 +1230,10 @@ int nr_ue_dl_indication(nr_downlink_indication_t *dl_info, NR_UL_TIME_ALIGNMENT_ break; } } - free(dl_info->rx_ind); dl_info->rx_ind = NULL; } } + pthread_mutex_unlock(&mac_IF_mutex); return 0; } @@ -1247,6 +1255,7 @@ nr_ue_if_module_t *nr_ue_if_module_init(uint32_t module_id){ nr_ue_if_module_inst[module_id]->dl_indication = nr_ue_dl_indication; nr_ue_if_module_inst[module_id]->ul_indication = nr_ue_ul_indication; } + pthread_mutex_init(&mac_IF_mutex, NULL); return nr_ue_if_module_inst[module_id]; } @@ -1263,8 +1272,8 @@ int nr_ue_dcireq(nr_dcireq_t *dcireq) { fapi_nr_dl_config_request_t *dl_config = &dcireq->dl_config_req; NR_UE_MAC_INST_t *UE_mac = get_mac_inst(0); - dl_config->sfn = UE_mac->dl_config_request.sfn; - dl_config->slot = UE_mac->dl_config_request.slot; + dl_config->sfn = dcireq->frame; + dl_config->slot = dcireq->slot; LOG_T(PHY, "Entering UE DCI configuration frame %d slot %d \n", dcireq->frame, dcireq->slot); From 1d9a4a42e7a58d6a75fd12f4900cf2f66568b20a Mon Sep 17 00:00:00 2001 From: Sakthivel Velumani Date: Tue, 22 Nov 2022 10:42:28 -0500 Subject: [PATCH 57/89] Corrected thread pool help message --- executables/softmodem-common.h | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/executables/softmodem-common.h b/executables/softmodem-common.h index d9aedd768c..cf6ef56ea4 100644 --- a/executables/softmodem-common.h +++ b/executables/softmodem-common.h @@ -38,11 +38,12 @@ extern "C" #endif /* help strings definition for command line options, used in CMDLINE_XXX_DESC macros and printed when -h option is used */ #define CONFIG_HLP_RFCFGF "Configuration file for front-end (e.g. LMS7002M)\n" -#define CONFIG_HLP_SPLIT73 "Split 7.3 (below rate matching) option: ::" +#define CONFIG_HLP_SPLIT73 "Split 7.3 (below rate matching) option: ::\n" #define CONFIG_HLP_TPOOL "Thread pool configuration: \n\ - default no pool (runs in calling thread),\n\ list of cores, comma separated (negative value is no core affinity)\n\ - example: -1,3 launches two working threads one floating, the second set on core 3" + example: -1,3 launches two working threads one floating, the second set on core 3\n\ + default 8 floating threads\n\ + use N for no pool (runs in calling thread) recommended with rfsim.\n" #define CONFIG_HLP_ULMAXE "set the eNodeB max ULSCH erros\n" #define CONFIG_HLP_CALUER "set UE RX calibration\n" #define CONFIG_HLP_CALUERM "" From f3be849904df24f0644cb8ce7fc3d8d72169c7ed Mon Sep 17 00:00:00 2001 From: Sakthivel Velumani Date: Mon, 28 Nov 2022 16:51:07 -0500 Subject: [PATCH 58/89] Delete job data when no response FIFO --- common/utils/threadPool/thread-pool.c | 2 ++ common/utils/threadPool/thread-pool.h | 2 ++ 2 files changed, 4 insertions(+) diff --git a/common/utils/threadPool/thread-pool.c b/common/utils/threadPool/thread-pool.c index b351a667b5..f0f4769faf 100644 --- a/common/utils/threadPool/thread-pool.c +++ b/common/utils/threadPool/thread-pool.c @@ -98,6 +98,8 @@ void *one_thread(void *arg) { myThread->runningOnKey=-1; mutexunlock(tp->incomingFifo.lockF); } + else + delNotifiedFIFO_elt(elt); } while (!myThread->terminate); return NULL; } diff --git a/common/utils/threadPool/thread-pool.h b/common/utils/threadPool/thread-pool.h index 0c544e5b0f..b06671c728 100644 --- a/common/utils/threadPool/thread-pool.h +++ b/common/utils/threadPool/thread-pool.h @@ -279,6 +279,8 @@ static inline void pushTpool(tpool_t *t, notifiedFIFO_elt_t *msg) { if (msg->reponseFifo) pushNotifiedFIFO(msg->reponseFifo, msg); + else + delNotifiedFIFO_elt(msg); } } From a03864ee8c0a58a518ad527a6a37ba63b27989d0 Mon Sep 17 00:00:00 2001 From: Sakthivel Velumani Date: Wed, 30 Nov 2022 12:47:24 -0500 Subject: [PATCH 59/89] Fix printing help message --- executables/nr-ue.c | 4 +-- executables/nr-uesoftmodem.c | 57 +++++++++++---------------------- executables/nr-uesoftmodem.h | 61 +++++++++++++++++------------------- openair1/PHY/defs_nr_UE.h | 3 -- 4 files changed, 50 insertions(+), 75 deletions(-) diff --git a/executables/nr-ue.c b/executables/nr-ue.c index 3d468ad64b..c0021ee337 100644 --- a/executables/nr-ue.c +++ b/executables/nr-ue.c @@ -450,7 +450,7 @@ static void UE_synch(void *arg) { break; */ case pbch: - LOG_I(PHY, "[UE thread Synch] Running Initial Synch (mode %d)\n",UE->mode); + LOG_I(PHY, "[UE thread Synch] Running Initial Synch \n"); uint64_t dl_carrier, ul_carrier; nr_get_carrier_frequencies(UE, &dl_carrier, &ul_carrier); @@ -708,7 +708,7 @@ void readFrame(PHY_VARS_NR_UE *UE, openair0_timestamp *timestamp, bool toTrash) void syncInFrame(PHY_VARS_NR_UE *UE, openair0_timestamp *timestamp) { - LOG_I(PHY,"Resynchronizing RX by %d samples (mode = %d)\n",UE->rx_offset,UE->mode); + LOG_I(PHY,"Resynchronizing RX by %d samples\n",UE->rx_offset); if (IS_SOFTMODEM_IQPLAYER || IS_SOFTMODEM_IQRECORDER) { // Resynchonize by slot (will work with numerology 1 only) diff --git a/executables/nr-uesoftmodem.c b/executables/nr-uesoftmodem.c index 3c884ea390..8bf0f6efec 100644 --- a/executables/nr-uesoftmodem.c +++ b/executables/nr-uesoftmodem.c @@ -264,7 +264,6 @@ static void get_options(void) { paramdef_t cmdline_params[] =CMDLINE_NRUEPARAMS_DESC ; int numparams = sizeof(cmdline_params)/sizeof(paramdef_t); config_get(cmdline_params,numparams,NULL); - config_process_cmdline( cmdline_params,numparams,NULL); if (vcdflag > 0) ouput_vcd = 1; @@ -272,38 +271,7 @@ static void get_options(void) { // set PHY vars from command line void set_options(int CC_id, PHY_VARS_NR_UE *UE){ - NR_DL_FRAME_PARMS *fp = &UE->frame_parms; - paramdef_t cmdline_params[] = CMDLINE_NRUE_PHYPARAMS_DESC ; - int numparams = sizeof(cmdline_params)/sizeof(paramdef_t); - - UE->mode = normal_txrx; - - config_get(cmdline_params,numparams,NULL); - - int pindex = config_paramidx_fromname(cmdline_params,numparams, CALIBRX_OPT); - if ( (cmdline_params[pindex].paramflags & PARAMFLAG_PARAMSET) != 0) UE->mode = rx_calib_ue; - - pindex = config_paramidx_fromname(cmdline_params,numparams, CALIBRXMED_OPT); - if ( (cmdline_params[pindex].paramflags & PARAMFLAG_PARAMSET) != 0) UE->mode = rx_calib_ue_med; - - pindex = config_paramidx_fromname(cmdline_params,numparams, CALIBRXBYP_OPT); - if ( (cmdline_params[pindex].paramflags & PARAMFLAG_PARAMSET) != 0) UE->mode = rx_calib_ue_byp; - - pindex = config_paramidx_fromname(cmdline_params,numparams, DBGPRACH_OPT); - if (cmdline_params[pindex].uptr) - if ( *(cmdline_params[pindex].uptr) > 0) UE->mode = debug_prach; - - pindex = config_paramidx_fromname(cmdline_params,numparams,NOL2CONNECT_OPT ); - if (cmdline_params[pindex].uptr) - if ( *(cmdline_params[pindex].uptr) > 0) UE->mode = no_L2_connect; - - pindex = config_paramidx_fromname(cmdline_params,numparams,CALIBPRACH_OPT ); - if (cmdline_params[pindex].uptr) - if ( *(cmdline_params[pindex].uptr) > 0) UE->mode = calib_prach_tx; - - pindex = config_paramidx_fromname(cmdline_params,numparams,DUMPFRAME_OPT ); - if ((cmdline_params[pindex].paramflags & PARAMFLAG_PARAMSET) != 0) - UE->mode = rx_dump_frame; + NR_DL_FRAME_PARMS *fp = &UE->frame_parms; // Init power variables tx_max_power[CC_id] = tx_max_power[0]; @@ -316,9 +284,18 @@ void set_options(int CC_id, PHY_VARS_NR_UE *UE){ UE->tx_power_max_dBm = tx_max_power[CC_id]; UE->rf_map.card = card_offset; UE->rf_map.chain = CC_id + chain_offset; + UE->max_ldpc_iterations = nrUE_params.max_ldpc_iterations; + UE->UE_scan_carrier = nrUE_params.UE_scan_carrier; + UE->UE_fo_compensation = nrUE_params.UE_fo_compensation; + UE->if_freq = nrUE_params.if_freq; + UE->if_freq_off = nrUE_params.if_freq_off; + UE->chest_freq = nrUE_params.chest_freq; + UE->chest_time = nrUE_params.chest_time; + UE->no_timing_correction = nrUE_params.no_timing_correction; + UE->timing_advance = nrUE_params.timing_advance; - LOG_I(PHY,"Set UE mode %d, UE_fo_compensation %d, UE_scan_carrier %d, UE_no_timing_correction %d \n, chest-freq %d\n", - UE->mode, UE->UE_fo_compensation, UE->UE_scan_carrier, UE->no_timing_correction, UE->chest_freq); + 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); // Set FP variables @@ -327,10 +304,14 @@ void set_options(int CC_id, PHY_VARS_NR_UE *UE){ LOG_I(PHY, "Set UE frame_type %d\n", fp->frame_type); } - LOG_I(PHY, "Set UE nb_rx_antenna %d, nb_tx_antenna %d, threequarter_fs %d, ssb_start_subcarrier %d\n", fp->nb_antennas_rx, fp->nb_antennas_tx, fp->threequarter_fs, fp->ssb_start_subcarrier); + fp->nb_antennas_rx = nrUE_params.nb_antennas_rx; + fp->nb_antennas_tx = nrUE_params.nb_antennas_tx; + fp->threequarter_fs = nrUE_params.threequarter_fs; + fp->N_RB_DL = nrUE_params.N_RB_DL; + fp->ssb_start_subcarrier = nrUE_params.ssb_start_subcarrier; + fp->ofdm_offset_divisor = nrUE_params.ofdm_offset_divisor; - fp->ofdm_offset_divisor = nrUE_params.ofdm_offset_divisor; - UE->max_ldpc_iterations = nrUE_params.max_ldpc_iterations; + LOG_I(PHY, "Set UE nb_rx_antenna %d, nb_tx_antenna %d, threequarter_fs %d, ssb_start_subcarrier %d\n", fp->nb_antennas_rx, fp->nb_antennas_tx, fp->threequarter_fs, fp->ssb_start_subcarrier); } diff --git a/executables/nr-uesoftmodem.h b/executables/nr-uesoftmodem.h index 60b9d91f72..99c97eae28 100644 --- a/executables/nr-uesoftmodem.h +++ b/executables/nr-uesoftmodem.h @@ -36,54 +36,51 @@ {"single-thread-disable", CONFIG_HLP_NOSNGLT, PARAMFLAG_BOOL, .iptr=&single_thread_flag, .defintval=1, TYPE_INT, 0}, \ {"dlsch-parallel", CONFIG_HLP_DLSCH_PARA, 0, .u8ptr=NULL, .defintval=0, TYPE_UINT8, 0}, \ {"offset-divisor", CONFIG_HLP_OFFSET_DIV, 0, .uptr=&nrUE_params.ofdm_offset_divisor, .defuintval=8, TYPE_UINT32, 0}, \ - {"max-ldpc-iterations", CONFIG_HLP_MAX_LDPC_ITERATIONS, 0, .u8ptr=&nrUE_params.max_ldpc_iterations, .defuintval=5, TYPE_UINT8, 0}, \ + {"max-ldpc-iterations", CONFIG_HLP_MAX_LDPC_ITERATIONS, 0, .iptr=&nrUE_params.max_ldpc_iterations, .defuintval=5, TYPE_UINT8, 0}, \ {"nr-dlsch-demod-shift", CONFIG_HLP_DLSHIFT, 0, .iptr=(int32_t *)&nr_dlsch_demod_shift, .defintval=0, TYPE_INT, 0}, \ {"V" , CONFIG_HLP_VCD, PARAMFLAG_BOOL, .iptr=&vcdflag, .defintval=0, TYPE_INT, 0}, \ {"uecap_file", CONFIG_HLP_UECAP_FILE, 0, .strptr=&uecap_file, .defstrval="./uecap_ports1.xml", TYPE_STRING, 0}, \ {"rrc_config_path", CONFIG_HLP_RRC_CFG_PATH, 0, .strptr=&rrc_config_path, .defstrval="./", TYPE_STRING, 0}, \ - {"ue-idx-standalone", NULL, 0, .u16ptr=&ue_idx_standalone, .defuintval=0xFFFF, TYPE_UINT16, 0} \ -} -// clang-format on - -/*------------------------------------------------------------------------------------------------------------------------------------------*/ -/* command line parameters defining UE running mode */ -/* optname helpstr paramflags XXXptr defXXXval type numelt */ -/*------------------------------------------------------------------------------------------------------------------------------------------*/ -// clang-format off -#define CMDLINE_NRUE_PHYPARAMS_DESC { \ - { CALIBRX_OPT, CONFIG_HLP_CALUER, 0, .iptr=&rx_input_level_dBm, .defintval=0, TYPE_INT, 0}, \ - { CALIBRXMED_OPT, CONFIG_HLP_CALUERM, 0, .iptr=&rx_input_level_dBm, .defintval=0, TYPE_INT, 0}, \ - { CALIBRXBYP_OPT, CONFIG_HLP_CALUERB, 0, .iptr=&rx_input_level_dBm, .defintval=0, TYPE_INT, 0}, \ - { DBGPRACH_OPT, CONFIG_HLP_DBGUEPR, PARAMFLAG_BOOL, .uptr=NULL, .defuintval=1, TYPE_INT, 0}, \ - { NOL2CONNECT_OPT, CONFIG_HLP_NOL2CN, PARAMFLAG_BOOL, .uptr=NULL, .defuintval=1, TYPE_INT, 0}, \ - { CALIBPRACH_OPT, CONFIG_HLP_CALPRACH, PARAMFLAG_BOOL, .uptr=NULL, .defuintval=1, TYPE_INT, 0}, \ - { DUMPFRAME_OPT, CONFIG_HLP_DUMPFRAME, PARAMFLAG_BOOL, .iptr=&dumpframe, .defintval=0, TYPE_INT, 0}, \ + {"ue-idx-standalone", NULL, 0, .u16ptr=&ue_idx_standalone, .defuintval=0xFFFF, TYPE_UINT16, 0}, \ {"ue-rxgain", CONFIG_HLP_UERXG, 0, .dblptr=&(rx_gain[0][0]), .defdblval=110, TYPE_DOUBLE, 0}, \ {"ue-rxgain-off", CONFIG_HLP_UERXGOFF, 0, .dblptr=&rx_gain_off, .defdblval=0, TYPE_DOUBLE, 0}, \ {"ue-txgain", CONFIG_HLP_UETXG, 0, .dblptr=&(tx_gain[0][0]), .defdblval=0, TYPE_DOUBLE, 0}, \ - {"ue-nb-ant-rx", CONFIG_HLP_UENANTR, 0, .u8ptr=&(fp->nb_antennas_rx), .defuintval=1, TYPE_UINT8, 0}, \ - {"ue-nb-ant-tx", CONFIG_HLP_UENANTT, 0, .u8ptr=&(fp->nb_antennas_tx), .defuintval=1, TYPE_UINT8, 0}, \ - {"ue-scan-carrier", CONFIG_HLP_UESCAN, PARAMFLAG_BOOL, .iptr=&(UE->UE_scan_carrier), .defintval=0, TYPE_INT, 0}, \ - {"ue-fo-compensation", CONFIG_HLP_UEFO, PARAMFLAG_BOOL, .iptr=&(UE->UE_fo_compensation), .defintval=0, TYPE_INT, 0}, \ + {"ue-nb-ant-rx", CONFIG_HLP_UENANTR, 0, .iptr=&(nrUE_params.nb_antennas_rx), .defuintval=1, TYPE_UINT8, 0}, \ + {"ue-nb-ant-tx", CONFIG_HLP_UENANTT, 0, .iptr=&(nrUE_params.nb_antennas_tx), .defuintval=1, TYPE_UINT8, 0}, \ + {"ue-scan-carrier", CONFIG_HLP_UESCAN, PARAMFLAG_BOOL, .iptr=&(nrUE_params.UE_scan_carrier), .defintval=0, TYPE_INT, 0}, \ + {"ue-fo-compensation", CONFIG_HLP_UEFO, PARAMFLAG_BOOL, .iptr=&(nrUE_params.UE_fo_compensation), .defintval=0, TYPE_INT, 0}, \ {"ue-max-power", NULL, 0, .iptr=&(tx_max_power[0]), .defintval=90, TYPE_INT, 0}, \ - {"A" , CONFIG_HLP_TADV, 0, .iptr=&(UE->timing_advance), .defintval=0, TYPE_INT, 0}, \ - {"E" , CONFIG_HLP_TQFS, PARAMFLAG_BOOL, .u8ptr=&(fp->threequarter_fs), .defintval=0, TYPE_UINT8, 0}, \ - {"r" , CONFIG_HLP_PRB_SA, 0, .iptr=&(fp->N_RB_DL), .defintval=106, TYPE_UINT, 0}, \ - {"ssb", CONFIG_HLP_SSC, 0, .u16ptr=&(fp->ssb_start_subcarrier), .defintval=516, TYPE_UINT16, 0}, \ + {"A" , CONFIG_HLP_TADV, 0, .iptr=&(nrUE_params.timing_advance), .defintval=0, TYPE_INT, 0}, \ + {"E" , CONFIG_HLP_TQFS, PARAMFLAG_BOOL, .iptr=&(nrUE_params.threequarter_fs), .defintval=0, TYPE_UINT8, 0}, \ + {"r" , CONFIG_HLP_PRB_SA, 0, .iptr=&(nrUE_params.N_RB_DL), .defintval=106, TYPE_UINT, 0}, \ + {"ssb", CONFIG_HLP_SSC, 0, .iptr=&(nrUE_params.ssb_start_subcarrier), .defintval=516, TYPE_UINT16, 0}, \ {"T" , CONFIG_HLP_TDD, PARAMFLAG_BOOL, .iptr=&tddflag, .defintval=0, TYPE_INT, 0}, \ - {"if_freq" , CONFIG_HLP_IF_FREQ, 0, .u64ptr=&(UE->if_freq), .defuintval=0, TYPE_UINT64, 0}, \ - {"if_freq_off" , CONFIG_HLP_IF_FREQ_OFF, 0, .iptr=&(UE->if_freq_off), .defuintval=0, TYPE_INT, 0}, \ - {"chest-freq", CONFIG_HLP_CHESTFREQ, 0, .iptr=&(UE->chest_freq), .defintval=0, TYPE_INT, 0}, \ - {"chest-time", CONFIG_HLP_CHESTTIME, 0, .iptr=&(UE->chest_time), .defintval=0, TYPE_INT, 0}, \ - {"ue-timing-correction-disable", CONFIG_HLP_DISABLETIMECORR, PARAMFLAG_BOOL, .iptr=&(UE->no_timing_correction), .defintval=0, TYPE_INT, 0}, \ + {"if_freq" , CONFIG_HLP_IF_FREQ, 0, .u64ptr=&(nrUE_params.if_freq), .defuintval=0, TYPE_UINT64, 0}, \ + {"if_freq_off" , CONFIG_HLP_IF_FREQ_OFF, 0, .iptr=&(nrUE_params.if_freq_off), .defuintval=0, TYPE_INT, 0}, \ + {"chest-freq", CONFIG_HLP_CHESTFREQ, 0, .iptr=&(nrUE_params.chest_freq), .defintval=0, TYPE_INT, 0}, \ + {"chest-time", CONFIG_HLP_CHESTTIME, 0, .iptr=&(nrUE_params.chest_time), .defintval=0, TYPE_INT, 0}, \ + {"ue-timing-correction-disable", CONFIG_HLP_DISABLETIMECORR, PARAMFLAG_BOOL, .iptr=&(nrUE_params.no_timing_correction), .defintval=0, TYPE_INT, 0}, \ } // clang-format on typedef struct { uint64_t optmask; //mask to store boolean config options uint32_t ofdm_offset_divisor; // Divisor for sample offset computation for each OFDM symbol - uint8_t max_ldpc_iterations; // number of maximum LDPC iterations + int max_ldpc_iterations; // number of maximum LDPC iterations tpool_t Tpool; // thread pool + int UE_scan_carrier; + int UE_fo_compensation; + int timing_advance; + uint64_t if_freq; + int if_freq_off; + int chest_freq; + int chest_time; + int no_timing_correction; + int nb_antennas_rx; + int nb_antennas_tx; + int threequarter_fs; + int N_RB_DL; + int ssb_start_subcarrier; } nrUE_params_t; extern uint64_t get_nrUE_optmask(void); extern uint64_t set_nrUE_optmask(uint64_t bitmask); diff --git a/openair1/PHY/defs_nr_UE.h b/openair1/PHY/defs_nr_UE.h index e5bc7bfbce..15ae6a466a 100644 --- a/openair1/PHY/defs_nr_UE.h +++ b/openair1/PHY/defs_nr_UE.h @@ -385,9 +385,6 @@ typedef struct { uint8_t CC_id; /// \brief Mapping of CC_id antennas to cards openair0_rf_map rf_map; - //uint8_t local_flag; - /// \brief Indicator of current run mode of UE (normal_txrx, rx_calib_ue, no_L2_connect, debug_prach) - runmode_t mode; /// \brief Indicator that UE should perform band scanning int UE_scan; /// \brief Indicator that UE should perform coarse scanning around carrier From f1841639f577ebef2497c5fa650a1e943c632605 Mon Sep 17 00:00:00 2001 From: Sakthivel Velumani Date: Sun, 22 Jan 2023 21:51:52 +0530 Subject: [PATCH 60/89] Updated documentation --- doc/nr-ue-design.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/doc/nr-ue-design.md b/doc/nr-ue-design.md index 32abe6bd80..6b8bd89bf1 100644 --- a/doc/nr-ue-design.md +++ b/doc/nr-ue-design.md @@ -27,8 +27,8 @@ graph TD graph TD E[syncInFrame
--shift first sample to start of frame] -->|UE_thread| F["trx_read_func (slot n)"] F --> |Tpool thread| G["processSlotTX (slot n+4)
--PUSCH encode
--PUCCH encode
--trx_write_func"] - F --> |UE_thread| H["UE_processing (slot n)
--PDCCH decode
--PDSCH decode"] - G --> |Tpool thread| I(Merge) - H --> |UE_thread| I(Merge) + F --> |Tpool thread| J["UE_processing (slot n)
--PDCCH decode
--PDSCH decode"] + F --> |UE_thread| I(Merge) + G --> |Tpool thread| I I --> |Go to next slot
UE_thread| F ``` From 7d23ec27b5b2ca2ef31d13b38a01111979380985 Mon Sep 17 00:00:00 2001 From: Sakthivel Velumani Date: Fri, 3 Feb 2023 09:33:39 -0500 Subject: [PATCH 61/89] Changed NR_UE_CAPABILITY_SLOT_RX_TO_TX to 3 Tested with X300 and there was no real-time problems. Could get the same throughput as when NR_UE_CAPABILITY_SLOT_RX_TO_TX is 4. --- openair1/PHY/impl_defs_top.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openair1/PHY/impl_defs_top.h b/openair1/PHY/impl_defs_top.h index 6afbd1bf2b..679fd34a49 100644 --- a/openair1/PHY/impl_defs_top.h +++ b/openair1/PHY/impl_defs_top.h @@ -270,7 +270,7 @@ /* FFS_NR_TODO it defines ue capability which is the number of slots */ /* - between reception of pdsch and tarnsmission of its acknowlegment */ /* - between reception of un uplink grant and its related transmission */ -#define NR_UE_CAPABILITY_SLOT_RX_TO_TX (4) +#define NR_UE_CAPABILITY_SLOT_RX_TO_TX (3) #ifndef NO_RAT_NR #define DURATION_RX_TO_TX (NR_UE_CAPABILITY_SLOT_RX_TO_TX) /* for NR this will certainly depends to such UE capability which is not yet defined */ From 5c6778be0c54b4f09add1800fbbd3194856b97ca Mon Sep 17 00:00:00 2001 From: Sakthivel Velumani Date: Tue, 7 Feb 2023 10:49:39 -0500 Subject: [PATCH 62/89] Fixed Harq stats print --- openair1/PHY/defs_nr_UE.h | 2 +- openair1/SCHED_NR_UE/phy_procedures_nr_ue.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/openair1/PHY/defs_nr_UE.h b/openair1/PHY/defs_nr_UE.h index 15ae6a466a..9301f3fb9d 100644 --- a/openair1/PHY/defs_nr_UE.h +++ b/openair1/PHY/defs_nr_UE.h @@ -652,7 +652,7 @@ typedef struct { SLIST_HEAD(ral_thresholds_lte_poll_s, ral_threshold_phy_t) ral_thresholds_lte_polled[RAL_LINK_PARAM_LTE_MAX]; #endif int dl_errors; - int dl_stats[8]; + _Atomic(int) dl_stats[16]; void* scopeData; // Pointers to hold PDSCH data only for phy simulators void *phy_sim_rxdataF; diff --git a/openair1/SCHED_NR_UE/phy_procedures_nr_ue.c b/openair1/SCHED_NR_UE/phy_procedures_nr_ue.c index 9af0c591c1..0a9a996017 100644 --- a/openair1/SCHED_NR_UE/phy_procedures_nr_ue.c +++ b/openair1/SCHED_NR_UE/phy_procedures_nr_ue.c @@ -1158,7 +1158,7 @@ void pbch_pdcch_processing(PHY_VARS_NR_UE *ue, p += snprintf(p, end - p, "Harq round stats for Downlink: %d", ue->dl_stats[0]); for (int round = 1; round < 16 && (round < 3 || ue->dl_stats[round] != 0); ++round) p += snprintf(p, end - p,"/%d", ue->dl_stats[round]); - LOG_I(NR_PHY,"%s/0\n", output); + LOG_I(NR_PHY,"%s\n", output); LOG_I(NR_PHY,"============================================\n"); } From e45a051c80d76eeec721ac007bc67a21a2f0b479 Mon Sep 17 00:00:00 2001 From: Sakthivel Velumani Date: Wed, 1 Feb 2023 10:17:59 -0500 Subject: [PATCH 63/89] Perform CRC check for whole TB --- .../PHY/NR_UE_TRANSPORT/nr_dlsch_decoding.c | 37 ++++++++++--------- openair1/PHY/defs_nr_UE.h | 1 + 2 files changed, 21 insertions(+), 17 deletions(-) diff --git a/openair1/PHY/NR_UE_TRANSPORT/nr_dlsch_decoding.c b/openair1/PHY/NR_UE_TRANSPORT/nr_dlsch_decoding.c index bf18d79189..470164817b 100644 --- a/openair1/PHY/NR_UE_TRANSPORT/nr_dlsch_decoding.c +++ b/openair1/PHY/NR_UE_TRANSPORT/nr_dlsch_decoding.c @@ -51,15 +51,8 @@ #define OAI_UL_LDPC_MAX_NUM_LLR 27000//26112 // NR_LDPC_NCOL_BG1*NR_LDPC_ZMAX = 68*384 //#define OAI_LDPC_MAX_NUM_LLR 27000//26112 // NR_LDPC_NCOL_BG1*NR_LDPC_ZMAX -static uint64_t nb_total_decod =0; -static uint64_t nb_error_decod =0; - static extended_kpi_ue kpiStructure = {0}; -notifiedFIFO_t freeBlocks_dl; -notifiedFIFO_elt_t *msgToPush_dl; -int nbDlProcessing =0; - extended_kpi_ue* getKPIUE(void) { return &kpiStructure; } @@ -77,7 +70,7 @@ void nr_dlsch_unscrambling(int16_t *llr, uint32_t size, uint8_t q, uint32_t Nid, nr_codeword_unscrambling(llr, size, q, Nid, n_RNTI); } -bool nr_ue_postDecode(PHY_VARS_NR_UE *phy_vars_ue, notifiedFIFO_elt_t *req, bool last, notifiedFIFO_t *nf_p, int b_size, uint8_t b[b_size]) { +bool nr_ue_postDecode(PHY_VARS_NR_UE *phy_vars_ue, notifiedFIFO_elt_t *req, bool last, notifiedFIFO_t *nf_p, int b_size, uint8_t b[b_size], int *num_seg_ok, UE_nr_rxtx_proc_t *proc) { ldpcDecode_ue_t *rdata = (ldpcDecode_ue_t*) NotifiedFifoData(req); NR_DL_UE_HARQ_t *harq_process = rdata->harq_process; NR_UE_DLSCH_t *dlsch = (NR_UE_DLSCH_t *) rdata->dlsch; @@ -94,6 +87,7 @@ bool nr_ue_postDecode(PHY_VARS_NR_UE *phy_vars_ue, notifiedFIFO_elt_t *req, bool harq_process->c[r], rdata->Kr_bytes - (harq_process->F>>3) -((harq_process->C>1)?3:0)); + (*num_seg_ok)++; } else { if ( !last ) { int nb=abortTpoolJob(&get_nrUE_params()->Tpool, req->key); @@ -111,7 +105,20 @@ bool nr_ue_postDecode(PHY_VARS_NR_UE *phy_vars_ue, notifiedFIFO_elt_t *req, bool kpiStructure.dl_mcs = dlsch->dlsch_config.mcs; kpiStructure.nofRBs = dlsch->dlsch_config.number_rbs; - if (decodeSuccess) { + if (*num_seg_ok == harq_process->C) { + if (harq_process->C > 1) { + /* check global CRC */ + int A = dlsch->dlsch_config.TBS; + int crc_length = A > 3824 ? 3 : 2; + int crc_type = A > 3824 ? CRC24_A : CRC16; + if (!check_crc(b, A + crc_length*8, 0 /* F - unused */, crc_type)) { + harq_process->ack = 0; + dlsch->last_iteration_cnt = dlsch->max_ldpc_iterations + 1; + LOG_E(PHY, " Frame %d.%d LDPC global CRC fails, but individual LDPC CRC succeeded. %d segs\n", proc->frame_rx, proc->nr_slot_rx, harq_process->C); + LOG_D(PHY, "DLSCH received nok \n"); + return true; //stop + } + } //LOG_D(PHY,"[UE %d] DLSCH: Setting ACK for nr_slot_rx %d TBS %d mcs %d nb_rb %d harq_process->round %d\n", // phy_vars_ue->Mod_id,nr_slot_rx,harq_process->TBS,harq_process->mcs,harq_process->nb_rb, harq_process->round); harq_process->status = SCH_IDLE; @@ -277,7 +284,7 @@ void nr_processDLSegment(void* arg) { if (no_iteration_ldpc > dlsch->max_ldpc_iterations) no_iteration_ldpc = dlsch->max_ldpc_iterations; } else { - LOG_D(PHY,"CRC NOT OK\n"); + LOG_I(PHY,"%d.%d CRC NOT OK\n",rdata->proc->frame_rx,rdata->proc->nr_slot_rx); no_iteration_ldpc = dlsch->max_ldpc_iterations + 1; } @@ -287,12 +294,6 @@ void nr_processDLSegment(void* arg) { rdata->decodeIterations = no_iteration_ldpc; - nb_total_decod++; - - if (no_iteration_ldpc > dlsch->max_ldpc_iterations) { - nb_error_decod++; - } - for (int m=0; m < Kr>>3; m ++) { harq_process->c[r][m]= (uint8_t) llrProcBuf[m]; } @@ -478,6 +479,7 @@ uint32_t nr_dlsch_decoding(PHY_VARS_NR_UE *phy_vars_ue, rdata->offset = offset; rdata->dlsch = dlsch; rdata->dlsch_id = 0; + rdata->proc = proc; reset_meas(&rdata->ts_deinterleave); reset_meas(&rdata->ts_rate_unmatch); reset_meas(&rdata->ts_ldpc_decode); @@ -488,6 +490,7 @@ uint32_t nr_dlsch_decoding(PHY_VARS_NR_UE *phy_vars_ue, offset += (Kr_bytes - (harq_process->F>>3) - ((harq_process->C>1)?3:0)); ////////////////////////////////////////////////////////////////////////////////////////// } + int num_seg_ok = 0; for (r=0; rTpool); if (req == NULL) @@ -495,7 +498,7 @@ uint32_t nr_dlsch_decoding(PHY_VARS_NR_UE *phy_vars_ue, bool last = false; if (r == nbDecode - 1) last = true; - bool stop = nr_ue_postDecode(phy_vars_ue, req, last, &nf, b_size, b); + bool stop = nr_ue_postDecode(phy_vars_ue, req, last, &nf, b_size, b, &num_seg_ok, proc); delNotifiedFIFO_elt(req); if (stop) break; diff --git a/openair1/PHY/defs_nr_UE.h b/openair1/PHY/defs_nr_UE.h index 9301f3fb9d..2276def8cd 100644 --- a/openair1/PHY/defs_nr_UE.h +++ b/openair1/PHY/defs_nr_UE.h @@ -709,6 +709,7 @@ typedef struct LDPCDecode_ue_s { time_stats_t ts_deinterleave; time_stats_t ts_rate_unmatch; time_stats_t ts_ldpc_decode; + UE_nr_rxtx_proc_t *proc; } ldpcDecode_ue_t; #include "SIMULATION/ETH_TRANSPORT/defs.h" From 9e4d52293771f2b10c5f7c48c8eb2d0c7fc31c33 Mon Sep 17 00:00:00 2001 From: Sakthivel Velumani Date: Wed, 1 Mar 2023 15:31:58 -0500 Subject: [PATCH 64/89] Make kpi variables atomic Mutex for phy scope: Repeating this change again. My earlier change in commit 06ef311975d was erased by someone during merge of QT scope. --- CMakeLists.txt | 4 +++- openair1/PHY/TOOLS/nr_phy_qt_scope.cpp | 1 + openair1/PHY/TOOLS/nr_phy_scope.c | 4 +--- openair1/PHY/TOOLS/phy_scope_interface.c | 18 ++++++++++++++---- openair1/PHY/TOOLS/phy_scope_interface.h | 17 ++++++++++++----- openair1/PHY/defs_nr_UE.h | 4 ++++ 6 files changed, 35 insertions(+), 13 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 691cb4aaa8..113197f673 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2016,7 +2016,9 @@ add_boolean_option(ENABLE_NRQTSCOPE OFF "Build the Qt-Scope" OFF) if (ENABLE_NRQTSCOPE) find_package(Qt5 REQUIRED COMPONENTS Widgets Charts) message ("Qt5 Widgets and Charts found for nrqtscope") - set(QTSCOPE_SOURCE_NR ${OPENAIR1_DIR}/PHY/TOOLS/nr_phy_qt_scope.cpp) + set(QTSCOPE_SOURCE_NR + ${OPENAIR1_DIR}/PHY/TOOLS/nr_phy_qt_scope.cpp + ${OPENAIR1_DIR}/PHY/TOOLS/phy_scope_interface.c) # Creates rules for calling the Meta-Object Compiler (moc) on the given source files qt5_wrap_cpp(QTSCOPE_SOURCE_NR ${OPENAIR1_DIR}/PHY/TOOLS/nr_phy_qt_scope.h) add_library(nrqtscope MODULE ${QTSCOPE_SOURCE_NR}) diff --git a/openair1/PHY/TOOLS/nr_phy_qt_scope.cpp b/openair1/PHY/TOOLS/nr_phy_qt_scope.cpp index fecfefd131..f1da2dce36 100644 --- a/openair1/PHY/TOOLS/nr_phy_qt_scope.cpp +++ b/openair1/PHY/TOOLS/nr_phy_qt_scope.cpp @@ -1147,6 +1147,7 @@ void nrUEinitQtScope(PHY_VARS_NR_UE *ue) scope->liveData = calloc(sizeof(scopeGraphData_t *), UEdataTypeNumberOfItems); scope->copyData = UEcopyData; + UEcopyDataMutexInit(); ue->scopeData = scope; diff --git a/openair1/PHY/TOOLS/nr_phy_scope.c b/openair1/PHY/TOOLS/nr_phy_scope.c index 206ed490e5..17141c7158 100644 --- a/openair1/PHY/TOOLS/nr_phy_scope.c +++ b/openair1/PHY/TOOLS/nr_phy_scope.c @@ -1137,8 +1137,6 @@ static void *nrUEscopeThread(void *arg) { } #endif -pthread_mutex_t UEcopyDataMutex; - STATICFORXSCOPE void nrUEinitScope(PHY_VARS_NR_UE *ue) { AssertFatal(ue->scopeData=malloc(sizeof(scopeData_t)),""); @@ -1148,8 +1146,8 @@ STATICFORXSCOPE void nrUEinitScope(PHY_VARS_NR_UE *ue) #ifndef WEBSRVSCOPE pthread_t forms_thread; threadCreate(&forms_thread, nrUEscopeThread, ue, "scope", -1, OAI_PRIORITY_RT_LOW); + UEcopyDataMutexInit(); #endif - pthread_mutex_init(&UEcopyDataMutex, NULL); } void nrscope_autoinit(void *dataptr) { diff --git a/openair1/PHY/TOOLS/phy_scope_interface.c b/openair1/PHY/TOOLS/phy_scope_interface.c index b567e96b03..6dfcc36c75 100644 --- a/openair1/PHY/TOOLS/phy_scope_interface.c +++ b/openair1/PHY/TOOLS/phy_scope_interface.c @@ -35,9 +35,16 @@ #include "phy_scope_interface.h" #define SOFTSCOPE_ENDFUNC_IDX 0 +#define THREAD_MEM 4 static loader_shlibfunc_t scope_fdesc[]= {{"end_forms",NULL}}; +pthread_mutex_t UEcopyDataMutex; + +int UEcopyDataMutexInit(void) { + return pthread_mutex_init(&UEcopyDataMutex, NULL); +} + int load_softscope(char *exectype, void *initarg) { char libname[64]; sprintf(libname,"%.10sscope",exectype); @@ -56,16 +63,19 @@ int end_forms(void) { void UEcopyData(PHY_VARS_NR_UE *ue, enum UEdataType type, void *dataIn, int elementSz, int colSz, int lineSz) { // Local static copy of the scope data bufs // The active data buf is alterned to avoid interference between the Scope thread (display) and the Rx thread (data input) - // Index of "2" could be set to the number of Rx threads + 1 - static scopeGraphData_t *copyDataBufs[UEdataTypeNumberOfItems][2] = {0}; + // Index of THREAD_MEM could be set to the number of Rx threads + 1. Rx slots could run asynchronous to each other. + // THREAD_MEM = 4 slot process running in parallel is an assumption. THREAD_MEM can be increased if scope appears inconsistent. + static scopeGraphData_t *copyDataBufs[UEdataTypeNumberOfItems][THREAD_MEM] = {0}; static int copyDataBufsIdx[UEdataTypeNumberOfItems] = {0}; scopeData_t *tmp = (scopeData_t *)ue->scopeData; if (tmp) { - // Begin of critical zone between UE Rx threads that might copy new data at the same time: might require a mutex - int newCopyDataIdx = (copyDataBufsIdx[type]==0)?1:0; + // Begin of critical zone between UE Rx threads that might copy new data at the same time: + pthread_mutex_lock(&UEcopyDataMutex); + int newCopyDataIdx = (copyDataBufsIdx[type]<(THREAD_MEM-1))?copyDataBufsIdx[type]+1:0; copyDataBufsIdx[type] = newCopyDataIdx; + pthread_mutex_unlock(&UEcopyDataMutex); // End of critical zone between UE Rx threads // New data will be copied in a different buffer than the live one diff --git a/openair1/PHY/TOOLS/phy_scope_interface.h b/openair1/PHY/TOOLS/phy_scope_interface.h index b6d4480762..19395891ad 100644 --- a/openair1/PHY/TOOLS/phy_scope_interface.h +++ b/openair1/PHY/TOOLS/phy_scope_interface.h @@ -31,15 +31,21 @@ */ #ifndef __PHY_SCOPE_INTERFACE_H__ #define __PHY_SCOPE_INTERFACE_H__ + +#ifdef __cplusplus +#include +#define _Atomic(X) std::atomic< X > +#endif + #include #include typedef struct { - uint32_t nb_total; - uint32_t nb_nack; - uint32_t blockSize; // block size, to be used for throughput calculation - uint16_t nofRBs; - uint8_t dl_mcs; + _Atomic(uint32_t) nb_total; + _Atomic(uint32_t) nb_nack; + _Atomic(uint32_t) blockSize; // block size, to be used for throughput calculation + _Atomic(uint16_t) nofRBs; + _Atomic(uint8_t ) dl_mcs; } extended_kpi_ue; typedef struct { @@ -80,6 +86,7 @@ typedef struct { int load_softscope(char *exectype, void *initarg); int end_forms(void) ; +int UEcopyDataMutexInit(void); void UEcopyData(PHY_VARS_NR_UE *ue, enum UEdataType type, void *dataIn, int elementSz, int colSz, int lineSz); #define UEscopeCopy(ue, type, ...) if(ue->scopeData) ((scopeData_t*)ue->scopeData)->copyData(ue, type, ##__VA_ARGS__); diff --git a/openair1/PHY/defs_nr_UE.h b/openair1/PHY/defs_nr_UE.h index 2276def8cd..ac5379fe42 100644 --- a/openair1/PHY/defs_nr_UE.h +++ b/openair1/PHY/defs_nr_UE.h @@ -32,6 +32,10 @@ #ifndef __PHY_DEFS_NR_UE__H__ #define __PHY_DEFS_NR_UE__H__ +#ifdef __cplusplus +#include +#define _Atomic(X) std::atomic< X > +#endif #include "defs_nr_common.h" #include "CODING/nrPolar_tools/nr_polar_pbch_defs.h" From 0004626ac7ff46b68664b8e243948ce93273c767 Mon Sep 17 00:00:00 2001 From: Sakthivel Velumani Date: Wed, 1 Mar 2023 19:24:57 -0500 Subject: [PATCH 65/89] Fix bug in QT scope The UE copies scope data not in a single buffer to avoid overwiring in case of multiple slots processing in parallel. But the scope upates data from single buffer thats assigned during selection. A getPlotValue() method is created to update the data pointer on every timerEvent. --- openair1/PHY/TOOLS/nr_phy_qt_scope.cpp | 119 +++++++++++++++++++++++-- openair1/PHY/TOOLS/nr_phy_qt_scope.h | 61 ++++++++++++- 2 files changed, 170 insertions(+), 10 deletions(-) diff --git a/openair1/PHY/TOOLS/nr_phy_qt_scope.cpp b/openair1/PHY/TOOLS/nr_phy_qt_scope.cpp index f1da2dce36..5aecc3906b 100644 --- a/openair1/PHY/TOOLS/nr_phy_qt_scope.cpp +++ b/openair1/PHY/TOOLS/nr_phy_qt_scope.cpp @@ -317,6 +317,34 @@ void CIRPlot::timerEvent(QTimerEvent *event) this->series->replace(points); } +void CIRPlotUE::timerEvent(QTimerEvent *event) +{ + if (!this->isVisible()) + return; + + scopeGraphData_t *data = this->valueProvider->getPlotValue(); + if (data) { + this->data = (complex16 *)(data + 1); + this->len = data->lineSz; + QVector points(this->len); + float maxY = this->axisY->max(); + + for (int i = 0; i < this->len / 2; i++) { + float value = SquaredNorm(this->data[i + this->len / 2]); + points[i] = QPointF(i - this->len / 2, value); + maxY = std::max(maxY, value); + } + for (int i = 0; i < this->len / 2; i++) { + float value = SquaredNorm(this->data[i]); + points[i + this->len / 2] = QPointF(i, value); + maxY = std::max(maxY, value); + } + + this->axisY->setMax(maxY); + this->series->replace(points); + } +} + LLRPlot::LLRPlot(int16_t *data, int len) : data(data), len(len) { this->legend()->hide(); @@ -361,6 +389,28 @@ void LLRPlot::timerEvent(QTimerEvent *event) this->series->replace(points); } +void LLRPlotUE::timerEvent(QTimerEvent *event) +{ + if (!this->isVisible()) + return; + + scopeGraphData_t *data = this->valueProvider->getPlotValue(); + if (data) { + this->data = (int16_t *)(data + 1); + this->len = data->lineSz; + QVector points(this->len); + int maxY = this->axisY->max(); + + for (int i = 0; i < this->len; i++) { + points[i] = QPointF(i, this->data[i]); + maxY = std::max(maxY, abs(this->data[i])); + } + + this->axisY->setRange(-maxY, maxY); + this->series->replace(points); + } +} + IQPlot::IQPlot(complex16 *data, int len) : data(data), len(len) { this->legend()->hide(); @@ -407,6 +457,32 @@ void IQPlot::timerEvent(QTimerEvent *event) this->series->replace(points); } +void IQPlotUE::timerEvent(QTimerEvent *event) +{ + if (!this->isVisible()) + return; + + scopeGraphData_t *data = this->valueProvider->getPlotValue(); + if (data) { + this->data = (complex16 *)(data + 1); + this->len = data->lineSz; + QVector points(this->len); + int maxX = this->axisX->max(); + int maxY = this->axisY->max(); + + for (int i = 0; i < this->len; i++) { + points[i] = QPointF(this->data[i].r, this->data[i].i); + + maxX = std::max(maxX, abs(this->data[i].r)); + maxY = std::max(maxY, abs(this->data[i].i)); + } + + this->axisX->setRange(-maxX, maxX); + this->axisY->setRange(-maxY, maxY); + this->series->replace(points); + } +} + KPIPlot::KPIPlot(ValueProvider *valueProvider, float *limits) : valueProvider(valueProvider), limits(limits) { this->series = new QLineSeries(); @@ -815,11 +891,42 @@ float PainterWidgetUE::getValue() case PlotTypeUE::timingAdvance: return (float)this->ue->timing_advance; + default: return 0; } } +scopeGraphData_t *PainterWidgetUE::getPlotValue() +{ + scopeData_t *scope = (scopeData_t *)this->ue->scopeData; + scopeGraphData_t **data = (scopeGraphData_t **)scope->liveData; + switch (this->plotType) { + case PlotTypeUE::CIR: + return data[pbchDlChEstimateTime]; + + case PlotTypeUE::pbchLLR: + return data[pbchLlr]; + case PlotTypeUE::pbchIQ: + return data[pbchRxdataF_comp]; + + case PlotTypeUE::pdcchLLR: + return data[pdcchLlr]; + + case PlotTypeUE::pdcchIQ: + return data[pdcchRxdataF_comp]; + + case PlotTypeUE::pdschLLR: + return data[pdschLlr]; + + case PlotTypeUE::pdschIQ: + return data[pdschRxdataF_comp]; + + default: + return nullptr; + } +} + /* @UE Class: this function is called when a resize event is detected, then the widget will be adjusted accordignly. */ void PainterWidgetUE::resizeEvent(QResizeEvent *event) { @@ -890,7 +997,7 @@ void PainterWidgetUE::makeConnections(int type) this->comboBox->setCurrentIndex(static_cast(PlotTypeUE::empty)); break; } - newChart = new LLRPlot((int16_t *)(data[pbchLlr] + 1), data[pbchLlr]->lineSz); + newChart = new LLRPlotUE((int16_t *)(data[pbchLlr] + 1), data[pbchLlr]->lineSz, this); break; } case PlotTypeUE::pbchIQ: { @@ -900,7 +1007,7 @@ void PainterWidgetUE::makeConnections(int type) this->comboBox->setCurrentIndex(static_cast(PlotTypeUE::empty)); break; } - newChart = new IQPlot((complex16 *)(data[pbchRxdataF_comp] + 1), data[pbchRxdataF_comp]->lineSz); + newChart = new IQPlotUE((complex16 *)(data[pbchRxdataF_comp] + 1), data[pbchRxdataF_comp]->lineSz, this); break; } case PlotTypeUE::pdcchLLR: { @@ -910,7 +1017,7 @@ void PainterWidgetUE::makeConnections(int type) this->comboBox->setCurrentIndex(static_cast(PlotTypeUE::empty)); break; } - newChart = new LLRPlot((int16_t *)(data[pdcchLlr] + 1), data[pdcchLlr]->lineSz); + newChart = new LLRPlotUE((int16_t *)(data[pdcchLlr] + 1), data[pdcchLlr]->lineSz, this); break; } case PlotTypeUE::pdcchIQ: { @@ -920,7 +1027,7 @@ void PainterWidgetUE::makeConnections(int type) this->comboBox->setCurrentIndex(static_cast(PlotTypeUE::empty)); break; } - newChart = new IQPlot((complex16 *)(data[pdcchRxdataF_comp] + 1), data[pdcchRxdataF_comp]->lineSz); + newChart = new IQPlotUE((complex16 *)(data[pdcchRxdataF_comp] + 1), data[pdcchRxdataF_comp]->lineSz, this); break; } case PlotTypeUE::pdschLLR: { @@ -930,7 +1037,7 @@ void PainterWidgetUE::makeConnections(int type) this->comboBox->setCurrentIndex(static_cast(PlotTypeUE::empty)); break; } - newChart = new LLRPlot((int16_t *)(data[pdschLlr] + 1), data[pdschLlr]->lineSz); + newChart = new LLRPlotUE((int16_t *)(data[pdschLlr] + 1), data[pdschLlr]->lineSz, this); break; } case PlotTypeUE::pdschIQ: { @@ -940,7 +1047,7 @@ void PainterWidgetUE::makeConnections(int type) this->comboBox->setCurrentIndex(static_cast(PlotTypeUE::empty)); break; } - newChart = new IQPlot((complex16 *)(data[pdschRxdataF_comp] + 1), data[pdschRxdataF_comp]->lineSz); + newChart = new IQPlotUE((complex16 *)(data[pdschRxdataF_comp] + 1), data[pdschRxdataF_comp]->lineSz, this); break; } diff --git a/openair1/PHY/TOOLS/nr_phy_qt_scope.h b/openair1/PHY/TOOLS/nr_phy_qt_scope.h index f10fef9d61..8a00d5fa96 100644 --- a/openair1/PHY/TOOLS/nr_phy_qt_scope.h +++ b/openair1/PHY/TOOLS/nr_phy_qt_scope.h @@ -91,6 +91,14 @@ class ValueProvider { virtual float getValue() = 0; }; +class ValueProviderUE : public ValueProvider { + public: + /// This pure virtual function is meant to provide the graph values to be plotted + virtual scopeGraphData_t *getPlotValue() { + return nullptr; + } +}; + /// An editable GUI field for a dialog box to set certain KPI configurations class ConfigBoxFloat : public QLineEdit { Q_OBJECT @@ -206,7 +214,6 @@ class CIRPlot : public QChart { /// \param event Pointer to the timer event virtual void timerEvent(QTimerEvent *event) override; - private: /// Pointer to the CIR data complex16 *data; @@ -223,6 +230,21 @@ class CIRPlot : public QChart { QValueAxis *axisY; }; +class CIRPlotUE : public CIRPlot { + Q_OBJECT + + public: + CIRPlotUE(complex16 *data, int len, ValueProviderUE *valueProvider) : CIRPlot(data, len), valueProvider(valueProvider) {} + + protected: + /// This function is triggered when the own timer expires. It updates the plotted CIR + /// \param event Pointer to the timer event + virtual void timerEvent(QTimerEvent *event) override; + + private: + ValueProviderUE *valueProvider; +}; + /// Chart class for plotting LLRs class LLRPlot : public QChart { Q_OBJECT @@ -238,7 +260,6 @@ class LLRPlot : public QChart { /// \param event Pointer to the timer event virtual void timerEvent(QTimerEvent *event) override; - private: /// Pointer to the LLR data int16_t *data; @@ -255,6 +276,21 @@ class LLRPlot : public QChart { QValueAxis *axisY; }; +class LLRPlotUE : public LLRPlot { + Q_OBJECT + + public: + LLRPlotUE(int16_t *data, int len, ValueProviderUE *valueProvider) : LLRPlot(data, len), valueProvider(valueProvider) {} + + protected: + /// This function is triggered when the own timer expires. It updates the plotted I/Q constellation diagram + /// \param event Pointer to the timer event + virtual void timerEvent(QTimerEvent *event) override; + + private: + ValueProviderUE *valueProvider; +}; + /// Chart class for plotting the I/Q constellation diagram class IQPlot : public QChart { Q_OBJECT @@ -270,7 +306,6 @@ class IQPlot : public QChart { /// \param event Pointer to the timer event virtual void timerEvent(QTimerEvent *event) override; - private: /// Pointer to the I/Q data complex16 *data; @@ -287,6 +322,22 @@ class IQPlot : public QChart { QValueAxis *axisY; }; +class IQPlotUE : public IQPlot { + Q_OBJECT + + public: + IQPlotUE(complex16 *data, int len, ValueProviderUE *valueProvider) : IQPlot(data, len), valueProvider(valueProvider) {} + + protected: + /// This function is triggered when the own timer expires. It updates the plotted I/Q constellation diagram + /// \param event Pointer to the timer event + virtual void timerEvent(QTimerEvent *event) override; + + private: + /// Pointer to an instance of a class that implements the ValueProvider interface + ValueProviderUE *valueProvider; +}; + /// Generic class for plotting KPI values with min., max. and average bars class KPIPlot : public QChart { Q_OBJECT @@ -422,7 +473,7 @@ class PainterWidgetGnb : public QWidget, public ValueProvider { }; /// Widget showing one selectable UE KPI -class PainterWidgetUE : public QWidget, public ValueProvider { +class PainterWidgetUE : public QWidget, public ValueProviderUE { Q_OBJECT public: @@ -435,6 +486,8 @@ class PainterWidgetUE : public QWidget, public ValueProvider { /// This function provides the current KPI value to be plotted virtual float getValue() override; + virtual scopeGraphData_t *getPlotValue() override; + protected: /// This function is called to change the widget size /// \param event Pointer to the resize event From c8a61331ea60e72d2b7f888274a7af13d450d0f8 Mon Sep 17 00:00:00 2001 From: Sakthivel Velumani Date: Thu, 9 Mar 2023 14:25:37 -0500 Subject: [PATCH 66/89] Update UE design doc --- doc/nr-ue-design.md | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/doc/nr-ue-design.md b/doc/nr-ue-design.md index 6b8bd89bf1..7ee42335d6 100644 --- a/doc/nr-ue-design.md +++ b/doc/nr-ue-design.md @@ -16,19 +16,20 @@ The UE exits when at any point in operation it gets out of synchronization. When ## Initial Synchronization Block ```mermaid graph TD - A(Start) -->|UE_thread| B["readFrame
--Reads samples worth 2 frames"] - B --> |Tpool thread| C["UE_synch
--PSS & SSS detection
--PBCH decode"] - B --> |UE_thread| D["readFrame
--trash samples to unblock radio"] - C --> |Tpool thread| E[syncInFrame
--shift first sample to start of frame] - D --> |UE_thread| E + start(Start) -->|UE_thread| rxRu["RU read
(Reads two frames)"] + rxRu --> |Tpool thread| sync["SSB detection
(PSS & SSS detection
PBCH decoding
SIB decoding)"] + rxRu --> |UE_thread| rxRuDummy["RU read
(Dummy read till sync detection to avoid buffer overflow at radio)"] + sync --> |Tpool thread| frameSync["Frame synchronization
(Shift received samples to align with frame)"] + rxRuDummy --> |UE_thread| frameSync ``` ## Regular Slot Processing ```mermaid graph TD - E[syncInFrame
--shift first sample to start of frame] -->|UE_thread| F["trx_read_func (slot n)"] - F --> |Tpool thread| G["processSlotTX (slot n+4)
--PUSCH encode
--PUCCH encode
--trx_write_func"] - F --> |Tpool thread| J["UE_processing (slot n)
--PDCCH decode
--PDSCH decode"] - F --> |UE_thread| I(Merge) - G --> |Tpool thread| I - I --> |Go to next slot
UE_thread| F + sync["Frame synchronization
(Shift received samples to align with frame)"] -->|UE_thread| hw_read["RU read (slot n)"] + hw_read --> |UE_thread| rxPreProc["PBCH & PDCCH decoding (slot n)"] + hw_read --> |Tpool thread| txProc["Tx processing (slot n+3)
PUSCH encode
PUCCH encode (wait for DLSCH in slot n+3-k1 to finish)
RU write"] + rxPreProc --> |Tpool thread| rxProc["PDSCH decoding (slot n)"] + rxPreProc --> |UE_thread| join(Merge) + txProc --> |Tpool thread| join + join --> |Go to next slot
UE_thread| hw_read ``` From 2bfe9ba2298ab4f1691a7f96bedf7d65dc68404f Mon Sep 17 00:00:00 2001 From: Sakthivel Velumani Date: Tue, 14 Mar 2023 18:52:13 -0400 Subject: [PATCH 67/89] Remove dead code --- openair2/LAYER2/NR_MAC_UE/nr_ue_procedures.c | 3 --- 1 file changed, 3 deletions(-) diff --git a/openair2/LAYER2/NR_MAC_UE/nr_ue_procedures.c b/openair2/LAYER2/NR_MAC_UE/nr_ue_procedures.c index 0ea9dc0f9d..377a7e2f08 100644 --- a/openair2/LAYER2/NR_MAC_UE/nr_ue_procedures.c +++ b/openair2/LAYER2/NR_MAC_UE/nr_ue_procedures.c @@ -2665,9 +2665,6 @@ static uint8_t nr_extract_dci_info(NR_UE_MAC_INST_t *mac, return 2+nr_extract_dci_info(mac, NR_UL_DCI_FORMAT_0_0, dci_size, rnti, ss_type, dci_pdu, dci_pdu_rel15, slot); } - if (dci_pdu_rel15->format_indicator == 0) - return 1; // discard dci, format indicator not corresponding to dci_format - // Freq domain assignment 0-16 bit fsize = (int)ceil(log2((N_RB * (N_RB + 1)) >> 1)); pos+=fsize; From 1f6d111f6a179dc3777e689ca24b1150e8d3263f Mon Sep 17 00:00:00 2001 From: Sakthivel Velumani Date: Mon, 20 Mar 2023 13:48:07 -0400 Subject: [PATCH 68/89] Num wait idication as Tpool message field --- executables/nr-ue.c | 11 ++++++----- openair1/PHY/defs_nr_UE.h | 1 + 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/executables/nr-ue.c b/executables/nr-ue.c index c0021ee337..c0eb210420 100644 --- a/executables/nr-ue.c +++ b/executables/nr-ue.c @@ -567,12 +567,11 @@ void processSlotTX(void *arg) { LOG_D(PHY,"%d.%d => slot type %d\n", proc->frame_tx, proc->nr_slot_tx, proc->tx_slot_type); if (proc->tx_slot_type == NR_UPLINK_SLOT || proc->tx_slot_type == NR_MIXED_SLOT){ - // wait for rx slots to send indication - for(int i=0; i < UE->tx_wait_for_dlsch[proc->nr_slot_tx]; i++) { + // wait for rx slots to send indication (if any) that DLSCH decoding is finished + for(int i=0; i < rxtxD->tx_wait_for_dlsch; i++) { notifiedFIFO_elt_t *res = pullNotifiedFIFO(UE->tx_resume_ind_fifo[proc->nr_slot_tx]); delNotifiedFIFO_elt(res); } - UE->tx_wait_for_dlsch[proc->nr_slot_tx] = 0; // trigger L2 to run ue_scheduler thru IF module // [TODO] mapping right after NR initial sync @@ -798,6 +797,7 @@ void *UE_thread(void *arg) { int num_ind_fifo = nb_slot_frame; for(int i=0; i < num_ind_fifo; i++) { + UE->tx_wait_for_dlsch[num_ind_fifo] = 0; UE->tx_resume_ind_fifo[i] = malloc(sizeof(*UE->tx_resume_ind_fifo[i])); initNotifiedFIFO(UE->tx_resume_ind_fifo[i]); } @@ -965,15 +965,16 @@ void *UE_thread(void *arg) { curMsgTx->writeBlockSize = writeBlockSize; curMsgTx->proc.timestamp_tx = writeTimestamp; curMsgTx->UE = UE; + curMsgTx->tx_wait_for_dlsch = UE->tx_wait_for_dlsch[curMsgTx->proc.nr_slot_tx]; + UE->tx_wait_for_dlsch[curMsgTx->proc.nr_slot_tx] = 0; pushTpool(&(get_nrUE_params()->Tpool), newElt); // RX slot processing. We launch and forget. - nr_phy_data_t phy_data = UE_dl_preprocessing(UE, &curMsg.proc); newElt = newNotifiedFIFO_elt(sizeof(nr_rxtx_thread_data_t), curMsg.proc.nr_slot_rx, NULL, UE_dl_processing); nr_rxtx_thread_data_t *curMsgRx = (nr_rxtx_thread_data_t *) NotifiedFifoData(newElt); curMsgRx->proc = curMsg.proc; curMsgRx->UE = UE; - curMsgRx->phy_data = phy_data; + curMsgRx->phy_data = UE_dl_preprocessing(UE, &curMsg.proc); pushTpool(&(get_nrUE_params()->Tpool), newElt); if (curMsg.proc.decoded_frame_rx != -1) diff --git a/openair1/PHY/defs_nr_UE.h b/openair1/PHY/defs_nr_UE.h index ac5379fe42..fabe35ef8d 100644 --- a/openair1/PHY/defs_nr_UE.h +++ b/openair1/PHY/defs_nr_UE.h @@ -688,6 +688,7 @@ typedef struct nr_rxtx_thread_data_s { PHY_VARS_NR_UE *UE; int writeBlockSize; nr_phy_data_t phy_data; + int tx_wait_for_dlsch; } nr_rxtx_thread_data_t; typedef struct LDPCDecode_ue_s { From 540319899694f44a407f03d15da00d3c755da2c7 Mon Sep 17 00:00:00 2001 From: Angelo Athanassopoulos Date: Tue, 4 Apr 2023 00:16:15 +0200 Subject: [PATCH 69/89] NR RRC - RRCReestablishment - Fix MultiplePDU Regression Added struct member to gNB_RRC_UE_t to store the established DRBs for a UE, this can be used to generate ASN1 structs, during RRCReconfiguration --- openair2/RRC/NR/nr_rrc_defs.h | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/openair2/RRC/NR/nr_rrc_defs.h b/openair2/RRC/NR/nr_rrc_defs.h index e53ad60685..fd19220188 100644 --- a/openair2/RRC/NR/nr_rrc_defs.h +++ b/openair2/RRC/NR/nr_rrc_defs.h @@ -271,6 +271,40 @@ typedef struct pdu_session_param_s { uint8_t cause_value; } rrc_pdu_session_param_t; +typedef struct drb_s { + int status; + int defaultDRBid; + int drb_id; + int reestablishPDCP; + int recoverPDCP; + int daps_Config_r16; + struct cnAssociation_s { + int present; + int eps_BearerIdentity; + struct sdap_config_s { + bool defaultDRB; + int pdusession_id; + int sdap_HeaderDL; + int sdap_HeaderUL; + int mappedQoS_FlowsToAdd[QOSFLOW_MAX_VALUE]; + } sdap_config; + } cnAssociation; + struct pdcp_config_s { + int discardTimer; + int pdcp_SN_SizeUL; + int pdcp_SN_SizeDL; + int t_Reordering; + int integrityProtection; + struct headerCompression_s{ + int NotUsed; + int present; + } headerCompression; + struct ext1_s { + int cipheringDisabled; + } ext1; + } pdcp_config; +} drb_t; + typedef struct gNB_RRC_UE_s { uint8_t primaryCC_id; NR_SRB_ToAddModList_t *SRB_configList; @@ -278,6 +312,7 @@ typedef struct gNB_RRC_UE_s { NR_DRB_ToAddModList_t *DRB_configList; NR_DRB_ToAddModList_t *DRB_configList2[NR_RRC_TRANSACTION_IDENTIFIER_NUMBER]; NR_DRB_ToReleaseList_t *DRB_Release_configList2[NR_RRC_TRANSACTION_IDENTIFIER_NUMBER]; + drb_t established_drbs[NGAP_MAX_DRBS_PER_UE]; uint8_t DRB_active[NGAP_MAX_DRBS_PER_UE]; NR_SRB_INFO_TABLE_ENTRY Srb[maxSRBs]; // 3gpp max is 3 SRBs, number 1..3, we waste the entry 0 for code simplicity From af8b3b048601e4dbdc8823c442503088060e006f Mon Sep 17 00:00:00 2001 From: Angelo Athanassopoulos Date: Tue, 4 Apr 2023 00:28:50 +0200 Subject: [PATCH 70/89] NR RRC - RRCReestablishment - Fix MultiplePDU Regression Added function to create ASN1 DRB-ToAddMod structs from, gNB_RRC_UE_t established_drbs --- openair2/RRC/NR/rrc_gNB_radio_bearers.c | 47 +++++++++++++++++++++++++ openair2/RRC/NR/rrc_gNB_radio_bearers.h | 4 +++ 2 files changed, 51 insertions(+) diff --git a/openair2/RRC/NR/rrc_gNB_radio_bearers.c b/openair2/RRC/NR/rrc_gNB_radio_bearers.c index 1848601de8..acbcd2c9ab 100644 --- a/openair2/RRC/NR/rrc_gNB_radio_bearers.c +++ b/openair2/RRC/NR/rrc_gNB_radio_bearers.c @@ -98,6 +98,53 @@ NR_DRB_ToAddMod_t *generateDRB(gNB_RRC_UE_t *ue, uint8_t drb_id, rrc_pdu_session return DRB_config; } +NR_DRB_ToAddMod_t *generateDRB_ASN1(drb_t drb_asn1) +{ + NR_DRB_ToAddMod_t *DRB_config = CALLOC(1, sizeof(*DRB_config)); + NR_SDAP_Config_t *SDAP_config = CALLOC(1, sizeof(NR_SDAP_Config_t)); + + asn1cCalloc(DRB_config->cnAssociation, association); + asn1cCalloc(SDAP_config->mappedQoS_FlowsToAdd, sdapFlows); + asn1cCalloc(DRB_config->pdcp_Config, pdcpConfig); + asn1cCalloc(pdcpConfig->drb, drb); + + DRB_config->drb_Identity = drb_asn1.drb_id; + association->present = drb_asn1.cnAssociation.present; + + /* SDAP Configuration */ + SDAP_config->pdu_Session = drb_asn1.cnAssociation.sdap_config.pdusession_id; + SDAP_config->sdap_HeaderDL = drb_asn1.cnAssociation.sdap_config.sdap_HeaderDL; + SDAP_config->sdap_HeaderUL = drb_asn1.cnAssociation.sdap_config.sdap_HeaderUL; + SDAP_config->defaultDRB = drb_asn1.cnAssociation.sdap_config.defaultDRB; + + for (int qos_flow_index = 0; qos_flow_index < QOSFLOW_MAX_VALUE; qos_flow_index++) + { + if(drb_asn1.cnAssociation.sdap_config.mappedQoS_FlowsToAdd[qos_flow_index] != 0) { + asn1cSequenceAdd(sdapFlows->list, NR_QFI_t, qfi); + *qfi = drb_asn1.cnAssociation.sdap_config.mappedQoS_FlowsToAdd[qos_flow_index]; + } + } + + association->choice.sdap_Config = SDAP_config; + + /* PDCP Configuration */ + asn1cCallocOne(drb->discardTimer, drb_asn1.pdcp_config.discardTimer); + asn1cCallocOne(drb->pdcp_SN_SizeUL, drb_asn1.pdcp_config.pdcp_SN_SizeUL); + asn1cCallocOne(drb->pdcp_SN_SizeDL, drb_asn1.pdcp_config.pdcp_SN_SizeDL); + asn1cCallocOne(pdcpConfig->t_Reordering, drb_asn1.pdcp_config.t_Reordering); + asn1cCallocOne(drb->integrityProtection, drb_asn1.pdcp_config.integrityProtection); + + drb->headerCompression.present = drb_asn1.pdcp_config.headerCompression.present; + drb->headerCompression.choice.notUsed = drb_asn1.pdcp_config.headerCompression.NotUsed; + + if (!drb_asn1.pdcp_config.ext1.cipheringDisabled) { + asn1cCalloc(pdcpConfig->ext1, ext1); + asn1cCallocOne(ext1->cipheringDisabled, drb_asn1.pdcp_config.ext1.cipheringDisabled); + } + + return DRB_config; +} + uint8_t next_available_drb(gNB_RRC_UE_t *ue, rrc_pdu_session_param_t *pdusession, bool is_gbr) { uint8_t drb_id; diff --git a/openair2/RRC/NR/rrc_gNB_radio_bearers.h b/openair2/RRC/NR/rrc_gNB_radio_bearers.h index 62065c1d87..f8049b9abb 100644 --- a/openair2/RRC/NR/rrc_gNB_radio_bearers.h +++ b/openair2/RRC/NR/rrc_gNB_radio_bearers.h @@ -35,6 +35,10 @@ #define GBR_FLOW (1) #define NONGBR_FLOW (0) +/// @brief Generates an ASN1 DRB-ToAddMod, from the established_drbs in gNB_RRC_UE_t. +/// @param drb_t drb_asn1 +/// @return Returns the ASN1 DRB-ToAddMod structs. +NR_DRB_ToAddMod_t *generateDRB_ASN1(drb_t drb_asn1); NR_DRB_ToAddMod_t *generateDRB(gNB_RRC_UE_t *rrc_ue, uint8_t drb_id, rrc_pdu_session_param_t *pduSession, bool enable_sdap, int do_drb_integrity, int do_drb_ciphering); uint8_t next_available_drb(gNB_RRC_UE_t *ue, rrc_pdu_session_param_t *pdusession, bool is_gbr); bool drb_is_active(gNB_RRC_UE_t *ue, uint8_t drb_id); From 33c09b777623da2e4632fe4adb5630beab637fcc Mon Sep 17 00:00:00 2001 From: Angelo Athanassopoulos Date: Tue, 4 Apr 2023 00:44:22 +0200 Subject: [PATCH 71/89] NR RRC - RRCReestablishment - Fix MultiplePDU Regression The generateDRB function now creates a DRB and puts it in the struct member of gNB_RRC_UE_t, from this struct we can generate ASN1 messages by calling the generateDRB_ASN1 --- openair2/RRC/NR/rrc_gNB.c | 3 +- openair2/RRC/NR/rrc_gNB_radio_bearers.c | 107 +++++++++++------------- openair2/RRC/NR/rrc_gNB_radio_bearers.h | 10 ++- 3 files changed, 61 insertions(+), 59 deletions(-) diff --git a/openair2/RRC/NR/rrc_gNB.c b/openair2/RRC/NR/rrc_gNB.c index 8bdc83d58a..0f07f310e4 100644 --- a/openair2/RRC/NR/rrc_gNB.c +++ b/openair2/RRC/NR/rrc_gNB.c @@ -643,7 +643,8 @@ void fill_DRB_configList(const protocol_ctxt_t *const ctxt_pP, if(drb_is_active(ue_p, drb_id)){ /* Non-GBR flow using the same DRB or a GBR flow with no available DRBs*/ nb_drb_to_setup--; } else { - NR_DRB_ToAddMod_t *DRB_config = generateDRB(ue_p, drb_id, &ue_p->pduSession[i], rrc->configuration.enable_sdap, rrc->security.do_drb_integrity, rrc->security.do_drb_ciphering); + generateDRB(ue_p, drb_id, &ue_p->pduSession[i], rrc->configuration.enable_sdap, rrc->security.do_drb_integrity, rrc->security.do_drb_ciphering); + NR_DRB_ToAddMod_t *DRB_config = generateDRB_ASN1(ue_p->established_drbs[drb_id-1]); if (drb_id_to_setup_start == 0) drb_id_to_setup_start = DRB_config->drb_Identity; asn1cSeqAdd(&ue_p->DRB_configList->list, DRB_config); diff --git a/openair2/RRC/NR/rrc_gNB_radio_bearers.c b/openair2/RRC/NR/rrc_gNB_radio_bearers.c index acbcd2c9ab..af953fc36d 100644 --- a/openair2/RRC/NR/rrc_gNB_radio_bearers.c +++ b/openair2/RRC/NR/rrc_gNB_radio_bearers.c @@ -36,66 +36,59 @@ rrc_pdu_session_param_t *find_pduSession(gNB_RRC_UE_t *ue, int id, bool create) return ue->pduSession + j; } -NR_DRB_ToAddMod_t *generateDRB(gNB_RRC_UE_t *ue, uint8_t drb_id, rrc_pdu_session_param_t *pduSession, bool enable_sdap, int do_drb_integrity, int do_drb_ciphering) +void generateDRB(gNB_RRC_UE_t *ue, + uint8_t drb_id, + rrc_pdu_session_param_t *pduSession, + bool enable_sdap, + int do_drb_integrity, + int do_drb_ciphering) { - NR_DRB_ToAddMod_t *DRB_config = CALLOC(1, sizeof(*DRB_config)); - DRB_config->drb_Identity = drb_id; - asn1cCalloc(DRB_config->cnAssociation, association); - association->present = NR_DRB_ToAddMod__cnAssociation_PR_sdap_Config; + int i; + int qos_flow_index; + if (ue->established_drbs[drb_id - 1].status == DRB_INACTIVE) { + /* DRB Management */ + ue->established_drbs[drb_id - 1].drb_id = drb_id; + ue->established_drbs[drb_id - 1].reestablishPDCP = -1; + ue->established_drbs[drb_id - 1].recoverPDCP = -1; + for (i = 0; i < NGAP_MAX_DRBS_PER_UE; i++) { + if ((ue->established_drbs[drb_id - 1].cnAssociation.sdap_config.pdusession_id == 0 || + ue->established_drbs[drb_id - 1].cnAssociation.sdap_config.pdusession_id == pduSession->param.pdusession_id) && + ue->established_drbs[drb_id - 1].defaultDRBid == 0) { + ue->established_drbs[drb_id - 1].cnAssociation.sdap_config.defaultDRB = true; + ue->established_drbs[drb_id - 1].defaultDRBid = drb_id; + } + } + /* SDAP Configuration */ + ue->established_drbs[drb_id - 1].cnAssociation.present = NR_DRB_ToAddMod__cnAssociation_PR_sdap_Config; + ue->established_drbs[drb_id - 1].cnAssociation.sdap_config.pdusession_id = pduSession->param.pdusession_id; + if (enable_sdap) { + ue->established_drbs[drb_id - 1].cnAssociation.sdap_config.sdap_HeaderDL = NR_SDAP_Config__sdap_HeaderDL_present; + ue->established_drbs[drb_id - 1].cnAssociation.sdap_config.sdap_HeaderUL = NR_SDAP_Config__sdap_HeaderUL_present; + } else { + ue->established_drbs[drb_id - 1].cnAssociation.sdap_config.sdap_HeaderDL = NR_SDAP_Config__sdap_HeaderDL_absent; + ue->established_drbs[drb_id - 1].cnAssociation.sdap_config.sdap_HeaderUL = NR_SDAP_Config__sdap_HeaderUL_absent; + } + for (qos_flow_index = 0; qos_flow_index < pduSession->param.nb_qos; qos_flow_index++) + { + ue->established_drbs[drb_id - 1].cnAssociation.sdap_config.mappedQoS_FlowsToAdd[qos_flow_index] = pduSession->param.qos[qos_flow_index].qfi; + if(pduSession->param.qos[qos_flow_index].fiveQI > 5) + ue->established_drbs[drb_id - 1].status = DRB_ACTIVE_NONGBR; + else + ue->established_drbs[drb_id - 1].status = DRB_ACTIVE; + } + /* PDCP Configuration */ + ue->established_drbs[drb_id - 1].pdcp_config.discardTimer = NR_PDCP_Config__drb__discardTimer_infinity; + ue->established_drbs[drb_id - 1].pdcp_config.pdcp_SN_SizeDL = NR_PDCP_Config__drb__pdcp_SN_SizeDL_len18bits; + ue->established_drbs[drb_id - 1].pdcp_config.pdcp_SN_SizeUL = NR_PDCP_Config__drb__pdcp_SN_SizeUL_len18bits; + ue->established_drbs[drb_id - 1].pdcp_config.t_Reordering = NR_PDCP_Config__t_Reordering_ms100; + ue->established_drbs[drb_id - 1].pdcp_config.headerCompression.present = NR_PDCP_Config__drb__headerCompression_PR_notUsed; + ue->established_drbs[drb_id - 1].pdcp_config.headerCompression.NotUsed = 0; + if (do_drb_integrity) + ue->established_drbs[drb_id - 1].pdcp_config.integrityProtection = NR_PDCP_Config__drb__integrityProtection_enabled; + if (!do_drb_ciphering) + ue->established_drbs[drb_id - 1].pdcp_config.ext1.cipheringDisabled = NR_PDCP_Config__ext1__cipheringDisabled_true; - /* SDAP Configuration */ - NR_SDAP_Config_t *SDAP_config = CALLOC(1, sizeof(NR_SDAP_Config_t)); - asn1cCalloc(SDAP_config->mappedQoS_FlowsToAdd, sdapFlows); - - SDAP_config->pdu_Session = pduSession->param.pdusession_id; - - if (enable_sdap) { - SDAP_config->sdap_HeaderDL = NR_SDAP_Config__sdap_HeaderDL_present; - SDAP_config->sdap_HeaderUL = NR_SDAP_Config__sdap_HeaderUL_present; - } else { - SDAP_config->sdap_HeaderDL = NR_SDAP_Config__sdap_HeaderDL_absent; - SDAP_config->sdap_HeaderUL = NR_SDAP_Config__sdap_HeaderUL_absent; } - - SDAP_config->defaultDRB = true; - - for (int qos_flow_index = 0; qos_flow_index < pduSession->param.nb_qos; qos_flow_index++) - { - asn1cSequenceAdd(sdapFlows->list, NR_QFI_t, qfi); - *qfi = pduSession->param.qos[qos_flow_index].qfi; - if(pduSession->param.qos[qos_flow_index].fiveQI > 5) - pduSession->param.used_drbs[drb_id - 1] = DRB_ACTIVE_NONGBR; - else - pduSession->param.used_drbs[drb_id - 1] = DRB_ACTIVE; - } - - association->choice.sdap_Config = SDAP_config; - - /* PDCP Configuration */ - asn1cCalloc(DRB_config->pdcp_Config, pdcpConfig); - asn1cCalloc(pdcpConfig->drb, drb); - - asn1cCallocOne(drb->discardTimer, NR_PDCP_Config__drb__discardTimer_infinity); - - asn1cCallocOne(drb->pdcp_SN_SizeUL, NR_PDCP_Config__drb__pdcp_SN_SizeUL_len18bits); - asn1cCallocOne(drb->pdcp_SN_SizeDL, NR_PDCP_Config__drb__pdcp_SN_SizeDL_len18bits); - - drb->headerCompression.present = NR_PDCP_Config__drb__headerCompression_PR_notUsed; - drb->headerCompression.choice.notUsed = 0; - - asn1cCallocOne(pdcpConfig->t_Reordering, NR_PDCP_Config__t_Reordering_ms100); - - if (do_drb_integrity) { - asn1cCallocOne(drb->integrityProtection, NR_PDCP_Config__drb__integrityProtection_enabled); - } - if (!do_drb_ciphering) { - asn1cCalloc(pdcpConfig->ext1, ext1); - asn1cCallocOne(ext1->cipheringDisabled, NR_PDCP_Config__ext1__cipheringDisabled_true); - } - - ue->DRB_active[drb_id-1] = DRB_ACTIVE; - - return DRB_config; } NR_DRB_ToAddMod_t *generateDRB_ASN1(drb_t drb_asn1) diff --git a/openair2/RRC/NR/rrc_gNB_radio_bearers.h b/openair2/RRC/NR/rrc_gNB_radio_bearers.h index f8049b9abb..10f1ff8c05 100644 --- a/openair2/RRC/NR/rrc_gNB_radio_bearers.h +++ b/openair2/RRC/NR/rrc_gNB_radio_bearers.h @@ -39,7 +39,15 @@ /// @param drb_t drb_asn1 /// @return Returns the ASN1 DRB-ToAddMod structs. NR_DRB_ToAddMod_t *generateDRB_ASN1(drb_t drb_asn1); -NR_DRB_ToAddMod_t *generateDRB(gNB_RRC_UE_t *rrc_ue, uint8_t drb_id, rrc_pdu_session_param_t *pduSession, bool enable_sdap, int do_drb_integrity, int do_drb_ciphering); +/// @brief Creates and stores a DRB in the gNB_RRC_UE_t struct, it doesn't create the actual entity, +/// to create the actual entity use the generateDRB_ASN1. +/// @param ue The gNB_RRC_UE_t struct that holds information for the UEs +/// @param drb_id The Data Radio Bearer Identity to be created for the established DRB. +/// @param pduSession The PDU Session that the DRB is created for. +/// @param enable_sdap If true the SDAP header will be added to the packet, else it will not add or search for SDAP header. +/// @param do_drb_integrity +/// @param do_drb_ciphering +void generateDRB(gNB_RRC_UE_t *ue, uint8_t drb_id, rrc_pdu_session_param_t *pduSession, bool enable_sdap, int do_drb_integrity, int do_drb_ciphering); uint8_t next_available_drb(gNB_RRC_UE_t *ue, rrc_pdu_session_param_t *pdusession, bool is_gbr); bool drb_is_active(gNB_RRC_UE_t *ue, uint8_t drb_id); From 22db2d613f965e8b2063fae6bca14b828bfe58d1 Mon Sep 17 00:00:00 2001 From: Robert Schmidt Date: Tue, 4 Apr 2023 08:40:48 +0200 Subject: [PATCH 72/89] Correctly allocate GUTI in NAS UE structures --- openair3/NAS/NR_UE/nr_nas_msg_sim.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openair3/NAS/NR_UE/nr_nas_msg_sim.c b/openair3/NAS/NR_UE/nr_nas_msg_sim.c index e411c53994..e20a0fc99e 100644 --- a/openair3/NAS/NR_UE/nr_nas_msg_sim.c +++ b/openair3/NAS/NR_UE/nr_nas_msg_sim.c @@ -622,7 +622,7 @@ static void decodeRegistrationAccept(uint8_t *buf, int len, nr_ue_nas_t *nas) AssertFatal(reg_acc.guti->guti.typeofidentity == FGS_MOBILE_IDENTITY_5G_GUTI, "registration accept 5GS Mobile Identity is not GUTI, but %d\n", reg_acc.guti->guti.typeofidentity); - nas->guti = malloc(sizeof(nas->guti)); + nas->guti = malloc(sizeof(*nas->guti)); AssertFatal(nas->guti, "out of memory\n"); *nas->guti = reg_acc.guti->guti; free(reg_acc.guti); /* no proper memory management for NAS decoded messages */ From 0a4e28688bfb5e13051e5d92fae322db1be4539c Mon Sep 17 00:00:00 2001 From: Robert Schmidt Date: Tue, 4 Apr 2023 09:39:28 +0200 Subject: [PATCH 73/89] Update phytest-timing test timing thresholds --- ci-scripts/datalog_rt_stats.1x1.60.yaml | 4 ++-- ci-scripts/datalog_rt_stats.60.2x2.yaml | 6 +++--- ci-scripts/datalog_rt_stats.default.yaml | 8 ++++---- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/ci-scripts/datalog_rt_stats.1x1.60.yaml b/ci-scripts/datalog_rt_stats.1x1.60.yaml index bf22c6f432..c65c4614d2 100644 --- a/ci-scripts/datalog_rt_stats.1x1.60.yaml +++ b/ci-scripts/datalog_rt_stats.1x1.60.yaml @@ -14,8 +14,8 @@ Ref : L1 Tx processing : 210.0 DLSCH encoding : 137.0 L1 Rx processing : 345.0 - PUSCH inner-receiver : 184.0 - PUSCH decoding : 276.0 + PUSCH inner-receiver : 210.0 + PUSCH decoding : 210.0 Schedule Response : 48.0 DL & UL scheduling timing : 13.0 UL Indication : 60.0 diff --git a/ci-scripts/datalog_rt_stats.60.2x2.yaml b/ci-scripts/datalog_rt_stats.60.2x2.yaml index b0d861d758..50444cc6b0 100644 --- a/ci-scripts/datalog_rt_stats.60.2x2.yaml +++ b/ci-scripts/datalog_rt_stats.60.2x2.yaml @@ -14,10 +14,10 @@ Ref : L1 Tx processing : 400.0 DLSCH encoding : 177.0 L1 Rx processing : 345.0 - PUSCH inner-receiver : 184.0 - PUSCH decoding : 276.0 + PUSCH inner-receiver : 210.0 + PUSCH decoding : 210.0 Schedule Response : 111.0 - DL & UL scheduling timing : 37.0 + DL & UL scheduling timing : 20.0 UL Indication : 127.0 Threshold : feprx : 1.25 diff --git a/ci-scripts/datalog_rt_stats.default.yaml b/ci-scripts/datalog_rt_stats.default.yaml index 971e72944a..8f7f0f015c 100644 --- a/ci-scripts/datalog_rt_stats.default.yaml +++ b/ci-scripts/datalog_rt_stats.default.yaml @@ -14,11 +14,11 @@ Ref : L1 Tx processing : 170.0 DLSCH encoding : 118.0 L1 Rx processing : 305.0 - PUSCH inner-receiver : 117.0 - PUSCH decoding : 303.0 - Schedule Response : 25.0 + PUSCH inner-receiver : 150.0 + PUSCH decoding : 210.0 + Schedule Response : 20.0 DL & UL scheduling timing : 10.0 - UL Indication : 34.0 + UL Indication : 30.0 Threshold : feprx : 1.25 feptx_prec : 1.25 From 26909e7c3249cf237533308da49c78066c1c9ff2 Mon Sep 17 00:00:00 2001 From: Angelo Athanassopoulos Date: Tue, 4 Apr 2023 11:28:56 +0200 Subject: [PATCH 74/89] NR RRC - RRCReestablishment - Fix MultiplePDU Regression Cosmetic changes, added doxygen configuration, and changed the rrc_gNB_process_RRCReestablishmentComplete function arguments' alignment, so in the future if an argument is removed or added to be better visible in the commits --- openair2/RRC/NR/rrc_gNB.c | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/openair2/RRC/NR/rrc_gNB.c b/openair2/RRC/NR/rrc_gNB.c index 0f07f310e4..df7d70a099 100644 --- a/openair2/RRC/NR/rrc_gNB.c +++ b/openair2/RRC/NR/rrc_gNB.c @@ -1280,9 +1280,19 @@ void rrc_gNB_generate_RRCReestablishment(const protocol_ctxt_t *ctxt_pP, nr_pdcp_data_req_srb(ctxt_pP->rntiMaybeUEid, DCCH, rrc_gNB_mui++, size, buffer, deliver_pdu_srb_f1, rrc); } -//----------------------------------------------------------------------------- -void rrc_gNB_process_RRCReestablishmentComplete(const protocol_ctxt_t *const ctxt_pP, const rnti_t reestablish_rnti, rrc_gNB_ue_context_t *ue_context_pP, const uint8_t xid) -//----------------------------------------------------------------------------- +/* + * Handle RRC Reestablishment Complete Functions + */ + +/// @brief Function tha processes RRCReestablishmentComplete message sent by the UE, after RRCReestasblishment request. +/// @param ctxt_pP Protocol context containing information regarding the UE and gNB +/// @param reestablish_rnti is the old C-RNTI +/// @param ue_context_pP UE context container information regarding the UE +/// @param xid Transaction Identifier used in RRC messages +void rrc_gNB_process_RRCReestablishmentComplete(const protocol_ctxt_t *const ctxt_pP, + const rnti_t reestablish_rnti, + rrc_gNB_ue_context_t *ue_context_pP, + const uint8_t xid) { gNB_RRC_UE_t *ue_p = &ue_context_pP->ue_context; LOG_I(NR_RRC, "[RAPROC] UE %04x Logical Channel UL-DCCH, processing NR_RRCReestablishmentComplete from UE (SRB1 Active)\n", ue_p->rnti); From 7eb2e99dcd9560dba7e98def97b45f034d37ad0b Mon Sep 17 00:00:00 2001 From: Angelo Athanassopoulos Date: Tue, 4 Apr 2023 11:37:36 +0200 Subject: [PATCH 75/89] NR RRC - RRCReestablishment - Fix MultiplePDU Regression Simplify RRCReestablishmentComplete procedure with respective function, removed the block that frees all the NAS PDUs and created a function for this. --- openair2/RRC/NR/rrc_gNB.c | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/openair2/RRC/NR/rrc_gNB.c b/openair2/RRC/NR/rrc_gNB.c index df7d70a099..f008a1d060 100644 --- a/openair2/RRC/NR/rrc_gNB.c +++ b/openair2/RRC/NR/rrc_gNB.c @@ -1284,6 +1284,20 @@ void rrc_gNB_generate_RRCReestablishment(const protocol_ctxt_t *ctxt_pP, * Handle RRC Reestablishment Complete Functions */ +/// @brief Function used in RRCReestablishmentComplete procedure to Free all the NAS PDU buffers. +void RRCReestablishmentComplete_nas_pdu_free(rrc_gNB_ue_context_t *ue_context_pP) +{ + gNB_RRC_UE_t *ue_p = &ue_context_pP->ue_context; + /* Free all NAS PDUs */ + for (int i = 0; i < ue_p->nb_of_pdusessions; i++) { + if (ue_p->pduSession[i].param.nas_pdu.buffer != NULL) { + /* Free the NAS PDU buffer and invalidate it */ + free(ue_p->pduSession[i].param.nas_pdu.buffer); + ue_p->pduSession[i].param.nas_pdu.buffer = NULL; + } + } +} + /// @brief Function tha processes RRCReestablishmentComplete message sent by the UE, after RRCReestasblishment request. /// @param ctxt_pP Protocol context containing information regarding the UE and gNB /// @param reestablish_rnti is the old C-RNTI @@ -1475,14 +1489,7 @@ cellGroupConfig->physicalCellGroupConfig = ue_p->masterCellGroup->physicalCellGr LOG_DUMPMSG(NR_RRC,DEBUG_RRC,(char *)buffer,size, "[MSG] RRC Reconfiguration\n"); - /* Free all NAS PDUs */ - for (i = 0; i < ue_p->nb_of_pdusessions; i++) { - if (ue_p->pduSession[i].param.nas_pdu.buffer != NULL) { - /* Free the NAS PDU buffer and invalidate it */ - free(ue_p->pduSession[i].param.nas_pdu.buffer); - ue_p->pduSession[i].param.nas_pdu.buffer = NULL; - } - } + RRCReestablishmentComplete_nas_pdu_free(ue_context_pP); if (size < 0) { LOG_E(NR_RRC, "RRC decode err!!! do_RRCReconfiguration\n"); From bf844247bf990aa7a3884f681101bff5cd4a74cc Mon Sep 17 00:00:00 2001 From: Angelo Athanassopoulos Date: Tue, 4 Apr 2023 11:46:54 +0200 Subject: [PATCH 76/89] NR RRC - RRCReestablishment - Fix MultiplePDU Regression Simplify RRCReestablishmentComplete procedure with respective function, removed the block that updates NAS PDUSession and created a function, with doxygen --- openair2/RRC/NR/rrc_gNB.c | 26 +++++++++++++++++--------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/openair2/RRC/NR/rrc_gNB.c b/openair2/RRC/NR/rrc_gNB.c index f008a1d060..3338cfbad0 100644 --- a/openair2/RRC/NR/rrc_gNB.c +++ b/openair2/RRC/NR/rrc_gNB.c @@ -1284,6 +1284,22 @@ void rrc_gNB_generate_RRCReestablishment(const protocol_ctxt_t *ctxt_pP, * Handle RRC Reestablishment Complete Functions */ +/// @brief Function used in RRCReestablishmentComplete procedure to update the NAS PDUSessions and the xid. +/// @param old_xid Refers to the old transaction identifier passed to rrc_gNB_process_RRCReestablishmentComplete as xid. +/// @todo parameters yet to process inside the for loop. +/// @todo should test if pdu session are Ok before! inside the for loop. +void RRCReestablishmentComplete_nas_pdu_update(rrc_gNB_ue_context_t *ue_context_pP, + const uint8_t old_xid) +{ + gNB_RRC_UE_t *ue_p = &ue_context_pP->ue_context; + /* Add all NAS PDUs to the list */ + for (int i = 0; i < ue_p->nb_of_pdusessions; i++) { + ue_p->pduSession[i].status = PDU_SESSION_STATUS_DONE; + ue_p->pduSession[i].xid = old_xid; + LOG_D(NR_RRC, "RRC Reestablishment - setting the status for the default DRB (index %d) to (%d,%s)\n", i, ue_p->pduSession[i].status, "PDU_SESSION_STATUS_DONE"); + } +} + /// @brief Function used in RRCReestablishmentComplete procedure to Free all the NAS PDU buffers. void RRCReestablishmentComplete_nas_pdu_free(rrc_gNB_ue_context_t *ue_context_pP) { @@ -1425,6 +1441,7 @@ void rrc_gNB_process_RRCReestablishmentComplete(const protocol_ctxt_t *const ctx return; } } + RRCReestablishmentComplete_nas_pdu_update(ue_context_pP, xid); /* Update RNTI in ue_context */ LOG_I(NR_RRC, "Updating UEid from %04x to %lx\n", ue_p->rnti, ctxt_pP->rntiMaybeUEid); @@ -1444,15 +1461,6 @@ void rrc_gNB_process_RRCReestablishmentComplete(const protocol_ctxt_t *const ctx /* TODO: hardcoded to 13 for the time being, to be changed? */ long drb_priority[NGAP_MAX_DRBS_PER_UE] = {13}; - /* Add all NAS PDUs to the list */ - for (i = 0; i < ue_p->nb_of_pdusessions; i++) { - /* TODO parameters yet to process ... */ - /* TODO should test if pdu session are Ok before! */ - ue_p->pduSession[i].status = PDU_SESSION_STATUS_DONE; - ue_p->pduSession[i].xid = xid; - LOG_D(NR_RRC, "setting the status for the default DRB (index %d) to (%d,%s)\n", i, ue_p->pduSession[i].status, "PDU_SESSION_STATUS_DONE"); - } - gNB_RRC_INST *rrc = RC.nrrrc[ctxt_pP->module_id]; NR_CellGroupConfig_t *cellGroupConfig = calloc(1, sizeof(NR_CellGroupConfig_t)); From a3c13fdc7183bd8bffa238083c704d7ecd93d9b6 Mon Sep 17 00:00:00 2001 From: Angelo Athanassopoulos Date: Tue, 4 Apr 2023 11:55:10 +0200 Subject: [PATCH 77/89] NR RRC - RRCReestablishment - Fix MultiplePDU Regression Simplify RRCReestablishmentComplete procedure with respective function, removed the block that updates NGU Tunnels and created a function, with doxygen --- openair2/RRC/NR/rrc_gNB.c | 85 ++++++++++++++++++++++----------------- 1 file changed, 49 insertions(+), 36 deletions(-) diff --git a/openair2/RRC/NR/rrc_gNB.c b/openair2/RRC/NR/rrc_gNB.c index 3338cfbad0..97aea26bcf 100644 --- a/openair2/RRC/NR/rrc_gNB.c +++ b/openair2/RRC/NR/rrc_gNB.c @@ -1284,6 +1284,54 @@ void rrc_gNB_generate_RRCReestablishment(const protocol_ctxt_t *ctxt_pP, * Handle RRC Reestablishment Complete Functions */ +/// @brief Function used in RRCReestablishmentComplete procedure to update the NGU Tunnels. +/// @param reestablish_rnti is the old C-RNTI +void RRCReestablishmentComplete_update_ngu_tunnel(const protocol_ctxt_t *const ctxt_pP, + rrc_gNB_ue_context_t *ue_context_pP, + const rnti_t reestablish_rnti) +{ + gNB_RRC_UE_t *ue_p = &ue_context_pP->ue_context; + int i = 0; + int j = 0; + int ret = 0; + + if (get_softmodem_params()->sa) { + LOG_W(NR_RRC, "RRC Reestablishment - Rework identity mapping need to be done properly!\n"); + gtpv1u_gnb_create_tunnel_req_t create_tunnel_req={0}; + /* Save e RAB information for later */ + + for (j = 0, i = 0; i < NB_RB_MAX; i++) { + if (ue_p->pduSession[i].status == PDU_SESSION_STATUS_ESTABLISHED || ue_p->pduSession[i].status == PDU_SESSION_STATUS_DONE) { + create_tunnel_req.pdusession_id[j] = ue_p->pduSession[i].param.pdusession_id; + create_tunnel_req.incoming_rb_id[j] = i+1; + create_tunnel_req.outgoing_teid[j] = ue_p->pduSession[i].param.gtp_teid; + // to be developped, use the first QFI only + create_tunnel_req.outgoing_qfi[j] = ue_p->pduSession[i].param.qos[0].qfi; + memcpy(create_tunnel_req.dst_addr[j].buffer, ue_p->pduSession[i].param.upf_addr.buffer, sizeof(uint8_t) * 20); + create_tunnel_req.dst_addr[j].length = ue_p->pduSession[i].param.upf_addr.length; + j++; + } + } + + create_tunnel_req.ue_id = ctxt_pP->rntiMaybeUEid; // warning put zero above + create_tunnel_req.num_tunnels = j; + ret = gtpv1u_update_ngu_tunnel(ctxt_pP->instance, + &create_tunnel_req, + reestablish_rnti); + + if ( ret != 0 ) { + LOG_E(NR_RRC, "RRC Reestablishment - gtpv1u_update_ngu_tunnel failed,start to release UE %x\n", reestablish_rnti); + ue_p->ue_release_timer_s1 = 1; + ue_p->ue_release_timer_thres_s1 = 100; + ue_p->ue_release_timer = 0; + ue_p->ue_reestablishment_timer = 0; + ue_p->ul_failure_timer = 20000; // set ul_failure to 20000 for triggering rrc_eNB_send_S1AP_UE_CONTEXT_RELEASE_REQ + ue_p->ul_failure_timer = 0; + return; + } + } +} + /// @brief Function used in RRCReestablishmentComplete procedure to update the NAS PDUSessions and the xid. /// @param old_xid Refers to the old transaction identifier passed to rrc_gNB_process_RRCReestablishmentComplete as xid. /// @todo parameters yet to process inside the for loop. @@ -1405,42 +1453,7 @@ void rrc_gNB_process_RRCReestablishmentComplete(const protocol_ctxt_t *const ctx ue_p->Srb[1].Active = 1; - if (get_softmodem_params()->sa) { - LOG_W(NR_RRC, "Rework identity mapping need to be done properly!\n"); - gtpv1u_gnb_create_tunnel_req_t create_tunnel_req={0}; - /* Save e RAB information for later */ - int j; - for (j = 0, i = 0; i < NB_RB_MAX; i++) { - if (ue_p->pduSession[i].status == PDU_SESSION_STATUS_ESTABLISHED || ue_p->pduSession[i].status == PDU_SESSION_STATUS_DONE) { - create_tunnel_req.pdusession_id[j] = ue_p->pduSession[i].param.pdusession_id; - create_tunnel_req.incoming_rb_id[j] = i+1; - create_tunnel_req.outgoing_teid[j] = ue_p->pduSession[i].param.gtp_teid; - // to be developped, use the first QFI only - create_tunnel_req.outgoing_qfi[j] = ue_p->pduSession[i].param.qos[0].qfi; - memcpy(create_tunnel_req.dst_addr[j].buffer, ue_p->pduSession[i].param.upf_addr.buffer, sizeof(uint8_t) * 20); - create_tunnel_req.dst_addr[j].length = ue_p->pduSession[i].param.upf_addr.length; - j++; - } - } - - create_tunnel_req.ue_id = ctxt_pP->rntiMaybeUEid; // warning put zero above - create_tunnel_req.num_tunnels = j; - ret = gtpv1u_update_ngu_tunnel( - ctxt_pP->instance, - &create_tunnel_req, - reestablish_rnti); - - if ( ret != 0 ) { - LOG_E(NR_RRC, "gtpv1u_update_ngu_tunnel failed,start to release UE %x\n", reestablish_rnti); - ue_p->ue_release_timer_s1 = 1; - ue_p->ue_release_timer_thres_s1 = 100; - ue_p->ue_release_timer = 0; - ue_p->ue_reestablishment_timer = 0; - ue_p->ul_failure_timer = 20000; // set ul_failure to 20000 for triggering rrc_eNB_send_S1AP_UE_CONTEXT_RELEASE_REQ - ue_p->ul_failure_timer = 0; - return; - } - } + RRCReestablishmentComplete_update_ngu_tunnel(ctxt_pP, ue_context_pP, reestablish_rnti); RRCReestablishmentComplete_nas_pdu_update(ue_context_pP, xid); /* Update RNTI in ue_context */ From 914235706987d68924434ea8945eb83bb46ef58b Mon Sep 17 00:00:00 2001 From: Angelo Athanassopoulos Date: Tue, 4 Apr 2023 13:07:17 +0200 Subject: [PATCH 78/89] NR RRC - RRCReestablishment - Fix MultiplePDU Regression Simplify RRCReestablishmentComplete procedure with respective function, removed the block that creates the ASN1 DRBs and created a function, with doxygen --- openair2/RRC/NR/rrc_gNB.c | 47 +++++++++++++++++++++++++-------------- 1 file changed, 30 insertions(+), 17 deletions(-) diff --git a/openair2/RRC/NR/rrc_gNB.c b/openair2/RRC/NR/rrc_gNB.c index 97aea26bcf..c9d57cc3fb 100644 --- a/openair2/RRC/NR/rrc_gNB.c +++ b/openair2/RRC/NR/rrc_gNB.c @@ -1284,6 +1284,35 @@ void rrc_gNB_generate_RRCReestablishment(const protocol_ctxt_t *ctxt_pP, * Handle RRC Reestablishment Complete Functions */ +/// @brief Function used in RRCReestablishmentComplete procedure to reestablish the DRBs +/// that the UE previously had, it gets the information from the established_drbs +/// struct. +/// @param new_xid The new RRC transaction id. +void RRCReestablishmentComplete_fill_DRB_configList(const protocol_ctxt_t *const ctxt_pP, + rrc_gNB_ue_context_t *ue_context_pP, + const uint8_t new_xid) +{ + gNB_RRC_UE_t *ue_p = &ue_context_pP->ue_context; + NR_DRB_ToAddMod_t *DRB_config = NULL; + NR_DRB_ToAddModList_t **DRB_configList2 = &(ue_p->DRB_configList2[new_xid]); + + if (*DRB_configList2) { + free(*DRB_configList2); + LOG_D(NR_RRC, "RRC Reestablishment - free(ue_p->DRB_configList2[%d])\n", new_xid); + } + + *DRB_configList2 = CALLOC(1, sizeof(**DRB_configList2)); + + for (int i = 0; i < NGAP_MAX_DRBS_PER_UE; i++) { + if (ue_p->established_drbs[i].status != DRB_INACTIVE) { + ue_p->established_drbs[i].reestablishPDCP = NR_DRB_ToAddMod__reestablishPDCP_true; + DRB_config = generateDRB_ASN1(ue_p->established_drbs[i]); + asn1cCallocOne(DRB_config->reestablishPDCP, NR_DRB_ToAddMod__reestablishPDCP_true); + asn1cSeqAdd(&(*DRB_configList2)->list, DRB_config); + } + } +} + /// @brief Function used in RRCReestablishmentComplete procedure to update the NGU Tunnels. /// @param reestablish_rnti is the old C-RNTI void RRCReestablishmentComplete_update_ngu_tunnel(const protocol_ctxt_t *const ctxt_pP, @@ -1433,23 +1462,7 @@ void rrc_gNB_process_RRCReestablishmentComplete(const protocol_ctxt_t *const ctx LOG_W(NR_RRC,"SRB2 configuration does not exist in SRB configuration list\n"); } - if (*DRB_configList2) { - free(*DRB_configList2); - LOG_D(NR_RRC, "free(ue_p->DRB_configList2[%d])\n", new_xid); - } - - *DRB_configList2 = CALLOC(1, sizeof(**DRB_configList2)); - - if (DRB_configList != NULL) { - LOG_D(NR_RRC, "get DRB_config from (ue_p->DRB_configList)\n"); - - for (i = 0; (i < DRB_configList->list.count) && (i < 3); i++) { - DRB_config = DRB_configList->list.array[i]; - asn1cCallocOne(DRB_config->reestablishPDCP, NR_DRB_ToAddMod__reestablishPDCP_true); - // Add DRB to DRB configuration list, for LTE_RRCConnectionReconfigurationComplete - asn1cSeqAdd(&(*DRB_configList2)->list, DRB_config); - } - } + RRCReestablishmentComplete_fill_DRB_configList(ctxt_pP, ue_context_pP, new_xid); ue_p->Srb[1].Active = 1; From 1923bed4a32b1bcce278f421c4c6da70be520cb2 Mon Sep 17 00:00:00 2001 From: Angelo Athanassopoulos Date: Tue, 4 Apr 2023 13:19:51 +0200 Subject: [PATCH 79/89] NR RRC - RRCReestablishment - Fix MultiplePDU Regression Simplify RRCReestablishmentComplete procedure with respective function, removed the block that creates the SRB2 and created a function, with doxygen --- openair2/RRC/NR/rrc_gNB.c | 118 ++++++++++++++++++++------------------ 1 file changed, 63 insertions(+), 55 deletions(-) diff --git a/openair2/RRC/NR/rrc_gNB.c b/openair2/RRC/NR/rrc_gNB.c index c9d57cc3fb..b508495ba0 100644 --- a/openair2/RRC/NR/rrc_gNB.c +++ b/openair2/RRC/NR/rrc_gNB.c @@ -1284,6 +1284,68 @@ void rrc_gNB_generate_RRCReestablishment(const protocol_ctxt_t *ctxt_pP, * Handle RRC Reestablishment Complete Functions */ +/// @brief Function used in RRCReestablishmentComplete procedure to reestablish the SRB2. +/// @param old_xid The old RRC transaction id. +/// @param new_xid The new RRC transaction id. +void RRCReestablishmentComplete_fill_SRB2_configList(const protocol_ctxt_t *const ctxt_pP, + rrc_gNB_ue_context_t *ue_context_pP, + const uint8_t old_xid, + const uint8_t new_xid) +{ + gNB_RRC_UE_t *ue_p = &ue_context_pP->ue_context; + NR_SRB_ToAddMod_t *SRB2_config = NULL; + NR_SRB_ToAddModList_t *SRB_configList = ue_p->SRB_configList; + NR_SRB_ToAddModList_t **SRB_configList2 = NULL; + + SRB_configList2 = &ue_p->SRB_configList2[old_xid]; + + // get old configuration of SRB2 + if (*SRB_configList2 != NULL) { + if((*SRB_configList2)->list.count!=0) { + LOG_D(NR_RRC, "RRC Reestablishment - SRB_configList2(%p) count is %d\n SRB_configList2->list.array[0] addr is %p\n", + SRB_configList2, (*SRB_configList2)->list.count, (*SRB_configList2)->list.array[0]); + } + + for (int i = 0; (i < (*SRB_configList2)->list.count) && (i < 3); i++) { + if ((*SRB_configList2)->list.array[i]->srb_Identity == 2 ) { + LOG_D(NR_RRC, "RRC Reestablishment - get SRB2_config from (ue_p->SRB_configList2[%d])\n", old_xid); + SRB2_config = (*SRB_configList2)->list.array[i]; + SRB2_config->reestablishPDCP = CALLOC(1, sizeof(*SRB2_config->reestablishPDCP)); + *SRB2_config->reestablishPDCP = NR_SRB_ToAddMod__reestablishPDCP_true; + break; + } + } + } + + SRB_configList2 = &(ue_p->SRB_configList2[new_xid]); + + if (*SRB_configList2) { + free(*SRB_configList2); + LOG_D(NR_RRC, "RRC Reestablishment - free(ue_p->SRB_configList2[%d])\n", new_xid); + } + + *SRB_configList2 = CALLOC(1, sizeof(**SRB_configList2)); + + if (SRB2_config != NULL) { + // Add SRB2 to SRB configuration list + asn1cSeqAdd(&SRB_configList->list, SRB2_config); + asn1cSeqAdd(&(*SRB_configList2)->list, SRB2_config); + LOG_D(NR_RRC, "RRC Reestablishment - Add SRB2_config (srb_Identity:%ld) to ue_p->SRB_configList\n", SRB2_config->srb_Identity); + LOG_D(NR_RRC, "RRC Reestablishment - Add SRB2_config (srb_Identity:%ld) to ue_p->SRB_configList2[%d]\n", SRB2_config->srb_Identity, new_xid); + } else { + // SRB configuration list only contains SRB1. + LOG_W(NR_RRC,"RRC Reestablishment - SRB2 configuration does not exist in SRB configuration list\n"); + } + + ue_p->Srb[1].Active = 1; + + if (get_softmodem_params()->sa) { + uint8_t send_security_mode_command = false; + nr_rrc_pdcp_config_security(ctxt_pP, ue_context_pP, send_security_mode_command); + LOG_D(NR_RRC, "RRC Reestablishment - set security successfully \n"); + } +} + /// @brief Function used in RRCReestablishmentComplete procedure to reestablish the DRBs /// that the UE previously had, it gets the information from the established_drbs /// struct. @@ -1418,54 +1480,9 @@ void rrc_gNB_process_RRCReestablishmentComplete(const protocol_ctxt_t *const ctx ue_p->StatusRrc = NR_RRC_CONNECTED; ue_p->ue_rrc_inactivity_timer = 1; // set rrc inactivity when UE goes into RRC_CONNECTED ue_p->reestablishment_xid = new_xid; - SRB_configList2 = &ue_p->SRB_configList2[xid]; - - // get old configuration of SRB2 - if (*SRB_configList2 != NULL) { - if((*SRB_configList2)->list.count!=0) { - LOG_D(NR_RRC, "SRB_configList2(%p) count is %d\n SRB_configList2->list.array[0] addr is %p", - SRB_configList2, (*SRB_configList2)->list.count, (*SRB_configList2)->list.array[0]); - } - - for (i = 0; (i < (*SRB_configList2)->list.count) && (i < 3); i++) { - if ((*SRB_configList2)->list.array[i]->srb_Identity == 2 ) { - LOG_D(NR_RRC, "get SRB2_config from (ue_p->SRB_configList2[%d])\n", xid); - SRB2_config = (*SRB_configList2)->list.array[i]; - SRB2_config->reestablishPDCP = CALLOC(1, sizeof(*SRB2_config->reestablishPDCP)); - *SRB2_config->reestablishPDCP = NR_SRB_ToAddMod__reestablishPDCP_true; - break; - } - } - } - - // SRB2_config = CALLOC(1, sizeof(*SRB2_config)); - // SRB2_config->srb_Identity = 2; - - SRB_configList2 = &(ue_p->SRB_configList2[new_xid]); - DRB_configList2 = &(ue_p->DRB_configList2[new_xid]); - - if (*SRB_configList2) { - free(*SRB_configList2); - LOG_D(NR_RRC, "free(ue_p->SRB_configList2[%d])\n", new_xid); - } - - *SRB_configList2 = CALLOC(1, sizeof(**SRB_configList2)); - - if (SRB2_config != NULL) { - // Add SRB2 to SRB configuration list - asn1cSeqAdd(&SRB_configList->list, SRB2_config); - asn1cSeqAdd(&(*SRB_configList2)->list, SRB2_config); - LOG_D(NR_RRC, "Add SRB2_config (srb_Identity:%ld) to ue_p->SRB_configList\n", SRB2_config->srb_Identity); - LOG_D(NR_RRC, "Add SRB2_config (srb_Identity:%ld) to ue_p->SRB_configList2[%d]\n", SRB2_config->srb_Identity, new_xid); - } else { - // SRB configuration list only contains SRB1. - LOG_W(NR_RRC,"SRB2 configuration does not exist in SRB configuration list\n"); - } + RRCReestablishmentComplete_fill_SRB2_configList(ctxt_pP, ue_context_pP, xid, new_xid); RRCReestablishmentComplete_fill_DRB_configList(ctxt_pP, ue_context_pP, new_xid); - - ue_p->Srb[1].Active = 1; - RRCReestablishmentComplete_update_ngu_tunnel(ctxt_pP, ue_context_pP, reestablish_rnti); RRCReestablishmentComplete_nas_pdu_update(ue_context_pP, xid); @@ -1473,15 +1490,6 @@ void rrc_gNB_process_RRCReestablishmentComplete(const protocol_ctxt_t *const ctx LOG_I(NR_RRC, "Updating UEid from %04x to %lx\n", ue_p->rnti, ctxt_pP->rntiMaybeUEid); rrc_gNB_update_ue_context_rnti(ctxt_pP->rntiMaybeUEid, RC.nrrrc[ctxt_pP->module_id], ue_p->gNB_ue_ngap_id); - if (get_softmodem_params()->sa) { - uint8_t send_security_mode_command = false; - nr_rrc_pdcp_config_security( - ctxt_pP, - ue_context_pP, - send_security_mode_command ? 0 : 1); - LOG_D(NR_RRC, "set security successfully \n"); - } - uint8_t drb_id_to_setup_start = DRB_configList ? DRB_configList->list.array[0]->drb_Identity : 1; uint8_t nb_drb_to_setup = DRB_configList ? DRB_configList->list.count : ue_p->nb_of_pdusessions; /* TODO: hardcoded to 13 for the time being, to be changed? */ From ca010654bffbb5af5c523affcd110ce75e410806 Mon Sep 17 00:00:00 2001 From: Angelo Athanassopoulos Date: Tue, 4 Apr 2023 13:22:51 +0200 Subject: [PATCH 80/89] NR RRC - RRCReestablishment - Fix MultiplePDU Regression Changed the SRB2 and DRB at the do_RRCReconfiguration arguments, to pass the correct structures that are generated --- openair2/RRC/NR/rrc_gNB.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openair2/RRC/NR/rrc_gNB.c b/openair2/RRC/NR/rrc_gNB.c index b508495ba0..d324adb035 100644 --- a/openair2/RRC/NR/rrc_gNB.c +++ b/openair2/RRC/NR/rrc_gNB.c @@ -1516,8 +1516,8 @@ cellGroupConfig->physicalCellGroupConfig = ue_p->masterCellGroup->physicalCellGr buffer, RRC_BUF_SIZE, new_xid, - *SRB_configList2, - DRB_configList, + *ue_p->SRB_configList2, + *ue_p->DRB_configList2, NULL, NULL, NULL, From ad598a2d5da76f50580a42f83fc32ef55d08eb94 Mon Sep 17 00:00:00 2001 From: Angelo Athanassopoulos Date: Tue, 4 Apr 2023 13:29:08 +0200 Subject: [PATCH 81/89] NR RRC - RRCReestablishment - Fix MultiplePDU Regression Removed the unused variables, and changed the tenary operator conditions of drb_id_to_setup_start & nb_drb_to_setup, to the correct ones --- openair2/RRC/NR/rrc_gNB.c | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/openair2/RRC/NR/rrc_gNB.c b/openair2/RRC/NR/rrc_gNB.c index d324adb035..6fc7b57c2f 100644 --- a/openair2/RRC/NR/rrc_gNB.c +++ b/openair2/RRC/NR/rrc_gNB.c @@ -1466,17 +1466,9 @@ void rrc_gNB_process_RRCReestablishmentComplete(const protocol_ctxt_t *const ctx gNB_RRC_UE_t *ue_p = &ue_context_pP->ue_context; LOG_I(NR_RRC, "[RAPROC] UE %04x Logical Channel UL-DCCH, processing NR_RRCReestablishmentComplete from UE (SRB1 Active)\n", ue_p->rnti); - NR_DRB_ToAddModList_t *DRB_configList = ue_p->DRB_configList; - NR_SRB_ToAddModList_t *SRB_configList = ue_p->SRB_configList; - NR_SRB_ToAddModList_t **SRB_configList2 = NULL; - NR_DRB_ToAddModList_t **DRB_configList2 = NULL; - NR_SRB_ToAddMod_t *SRB2_config = NULL; - NR_DRB_ToAddMod_t *DRB_config = NULL; - //NR_SDAP_Config_t *sdap_config = NULL; int i = 0; uint8_t new_xid = rrc_gNB_get_next_transaction_identifier(ctxt_pP->module_id); - int ret = 0; ue_p->StatusRrc = NR_RRC_CONNECTED; ue_p->ue_rrc_inactivity_timer = 1; // set rrc inactivity when UE goes into RRC_CONNECTED ue_p->reestablishment_xid = new_xid; @@ -1490,8 +1482,8 @@ void rrc_gNB_process_RRCReestablishmentComplete(const protocol_ctxt_t *const ctx LOG_I(NR_RRC, "Updating UEid from %04x to %lx\n", ue_p->rnti, ctxt_pP->rntiMaybeUEid); rrc_gNB_update_ue_context_rnti(ctxt_pP->rntiMaybeUEid, RC.nrrrc[ctxt_pP->module_id], ue_p->gNB_ue_ngap_id); - uint8_t drb_id_to_setup_start = DRB_configList ? DRB_configList->list.array[0]->drb_Identity : 1; - uint8_t nb_drb_to_setup = DRB_configList ? DRB_configList->list.count : ue_p->nb_of_pdusessions; + uint8_t drb_id_to_setup_start = ue_p->DRB_configList ? ue_p->DRB_configList->list.array[0]->drb_Identity : 1; + uint8_t nb_drb_to_setup = ue_p->DRB_configList ? ue_p->DRB_configList->list.count : ue_p->nb_of_pdusessions; /* TODO: hardcoded to 13 for the time being, to be changed? */ long drb_priority[NGAP_MAX_DRBS_PER_UE] = {13}; From 778c88d8a9ba8bfab6db118a2af62bd9a5582d68 Mon Sep 17 00:00:00 2001 From: Angelo Athanassopoulos Date: Tue, 4 Apr 2023 13:34:31 +0200 Subject: [PATCH 82/89] NR RRC - RRCReestablishment - Fix MultiplePDU Regression Cosmetic changes, also changed the LOG message for updating the UEid, and moved the drb_id_to_setup_start and nb_drb_to_setup right before the function that they are used --- openair2/RRC/NR/rrc_gNB.c | 54 +++++++++++++++++++-------------------- 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/openair2/RRC/NR/rrc_gNB.c b/openair2/RRC/NR/rrc_gNB.c index 6fc7b57c2f..5bf4843180 100644 --- a/openair2/RRC/NR/rrc_gNB.c +++ b/openair2/RRC/NR/rrc_gNB.c @@ -1469,9 +1469,9 @@ void rrc_gNB_process_RRCReestablishmentComplete(const protocol_ctxt_t *const ctx int i = 0; uint8_t new_xid = rrc_gNB_get_next_transaction_identifier(ctxt_pP->module_id); - ue_p->StatusRrc = NR_RRC_CONNECTED; + ue_p->StatusRrc = NR_RRC_CONNECTED; ue_p->ue_rrc_inactivity_timer = 1; // set rrc inactivity when UE goes into RRC_CONNECTED - ue_p->reestablishment_xid = new_xid; + ue_p->reestablishment_xid = new_xid; RRCReestablishmentComplete_fill_SRB2_configList(ctxt_pP, ue_context_pP, xid, new_xid); RRCReestablishmentComplete_fill_DRB_configList(ctxt_pP, ue_context_pP, new_xid); @@ -1479,22 +1479,22 @@ void rrc_gNB_process_RRCReestablishmentComplete(const protocol_ctxt_t *const ctx RRCReestablishmentComplete_nas_pdu_update(ue_context_pP, xid); /* Update RNTI in ue_context */ - LOG_I(NR_RRC, "Updating UEid from %04x to %lx\n", ue_p->rnti, ctxt_pP->rntiMaybeUEid); + LOG_I(NR_RRC, "RRC Reestablishment - Updating UEid from %04x to %lx\n", ue_p->rnti, ctxt_pP->rntiMaybeUEid); rrc_gNB_update_ue_context_rnti(ctxt_pP->rntiMaybeUEid, RC.nrrrc[ctxt_pP->module_id], ue_p->gNB_ue_ngap_id); - uint8_t drb_id_to_setup_start = ue_p->DRB_configList ? ue_p->DRB_configList->list.array[0]->drb_Identity : 1; - uint8_t nb_drb_to_setup = ue_p->DRB_configList ? ue_p->DRB_configList->list.count : ue_p->nb_of_pdusessions; - /* TODO: hardcoded to 13 for the time being, to be changed? */ - long drb_priority[NGAP_MAX_DRBS_PER_UE] = {13}; - gNB_RRC_INST *rrc = RC.nrrrc[ctxt_pP->module_id]; NR_CellGroupConfig_t *cellGroupConfig = calloc(1, sizeof(NR_CellGroupConfig_t)); // Revert spCellConfig stored in spCellConfigReestablishment before had been dropped during RRC Reestablishment - ue_p->masterCellGroup->spCellConfig = ue_p->spCellConfigReestablishment; - ue_p->spCellConfigReestablishment = NULL; - cellGroupConfig->spCellConfig = ue_p->masterCellGroup->spCellConfig; -cellGroupConfig->physicalCellGroupConfig = ue_p->masterCellGroup->physicalCellGroupConfig; + ue_p->masterCellGroup->spCellConfig = ue_p->spCellConfigReestablishment; + ue_p->spCellConfigReestablishment = NULL; + cellGroupConfig->spCellConfig = ue_p->masterCellGroup->spCellConfig; + cellGroupConfig->physicalCellGroupConfig = ue_p->masterCellGroup->physicalCellGroupConfig; + + uint8_t drb_id_to_setup_start = ue_p->DRB_configList ? ue_p->DRB_configList->list.array[0]->drb_Identity : 1; + uint8_t nb_drb_to_setup = ue_p->DRB_configList ? ue_p->DRB_configList->list.count : ue_p->nb_of_pdusessions; + /* TODO: hardcoded to 13 for the time being, to be changed? */ + long drb_priority[NGAP_MAX_DRBS_PER_UE] = {13}; fill_mastercellGroupConfig(cellGroupConfig, ue_p->masterCellGroup, rrc->um_on_default_drb, (drb_id_to_setup_start < 2) ? 1 : 0, drb_id_to_setup_start, nb_drb_to_setup, drb_priority); @@ -1505,21 +1505,21 @@ cellGroupConfig->physicalCellGroupConfig = ue_p->masterCellGroup->physicalCellGr uint8_t buffer[RRC_BUF_SIZE] = {0}; int size = do_RRCReconfiguration(ctxt_pP, - buffer, - RRC_BUF_SIZE, - new_xid, - *ue_p->SRB_configList2, - *ue_p->DRB_configList2, - NULL, - NULL, - NULL, - NULL, // MeasObj_list, - NULL, - ue_context_pP, - &rrc->carrier, - NULL, - NULL, - cellGroupConfig); + buffer, + RRC_BUF_SIZE, + new_xid, + *ue_p->SRB_configList2, + *ue_p->DRB_configList2, + NULL, + NULL, + NULL, + NULL, // MeasObj_list, + NULL, + ue_context_pP, + &rrc->carrier, + NULL, + NULL, + cellGroupConfig); LOG_DUMPMSG(NR_RRC,DEBUG_RRC,(char *)buffer,size, "[MSG] RRC Reconfiguration\n"); From f4dc36224755633d86a0473aaa0387233bec4ebd Mon Sep 17 00:00:00 2001 From: Angelo Athanassopoulos Date: Tue, 4 Apr 2023 13:59:14 +0200 Subject: [PATCH 83/89] NR RRC - RRCReestablishment - Fix MultiplePDU Regression Fixed a bug where the integrity protection was included in the ASN1 and caused significant throughput bottleneck. --- openair2/RRC/NR/rrc_gNB_radio_bearers.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/openair2/RRC/NR/rrc_gNB_radio_bearers.c b/openair2/RRC/NR/rrc_gNB_radio_bearers.c index af953fc36d..68bf78482a 100644 --- a/openair2/RRC/NR/rrc_gNB_radio_bearers.c +++ b/openair2/RRC/NR/rrc_gNB_radio_bearers.c @@ -125,11 +125,13 @@ NR_DRB_ToAddMod_t *generateDRB_ASN1(drb_t drb_asn1) asn1cCallocOne(drb->pdcp_SN_SizeUL, drb_asn1.pdcp_config.pdcp_SN_SizeUL); asn1cCallocOne(drb->pdcp_SN_SizeDL, drb_asn1.pdcp_config.pdcp_SN_SizeDL); asn1cCallocOne(pdcpConfig->t_Reordering, drb_asn1.pdcp_config.t_Reordering); - asn1cCallocOne(drb->integrityProtection, drb_asn1.pdcp_config.integrityProtection); drb->headerCompression.present = drb_asn1.pdcp_config.headerCompression.present; drb->headerCompression.choice.notUsed = drb_asn1.pdcp_config.headerCompression.NotUsed; + if (!drb_asn1.pdcp_config.integrityProtection) { + asn1cCallocOne(drb->integrityProtection, drb_asn1.pdcp_config.integrityProtection); + } if (!drb_asn1.pdcp_config.ext1.cipheringDisabled) { asn1cCalloc(pdcpConfig->ext1, ext1); asn1cCallocOne(ext1->cipheringDisabled, drb_asn1.pdcp_config.ext1.cipheringDisabled); From 6fb10cd5fa39308870922c8ab3204e4fd8ad8f89 Mon Sep 17 00:00:00 2001 From: luis_pereira87 Date: Tue, 4 Apr 2023 18:13:11 +0100 Subject: [PATCH 84/89] Fix SRB_configList2 and DRB_configList2 array position (new_xid) while creating RRCReconfiguration message after RRCReestablishmentComplete --- openair2/RRC/NR/rrc_gNB.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openair2/RRC/NR/rrc_gNB.c b/openair2/RRC/NR/rrc_gNB.c index 5bf4843180..301e069adb 100644 --- a/openair2/RRC/NR/rrc_gNB.c +++ b/openair2/RRC/NR/rrc_gNB.c @@ -1508,8 +1508,8 @@ void rrc_gNB_process_RRCReestablishmentComplete(const protocol_ctxt_t *const ctx buffer, RRC_BUF_SIZE, new_xid, - *ue_p->SRB_configList2, - *ue_p->DRB_configList2, + ue_p->SRB_configList2[new_xid], + ue_p->DRB_configList2[new_xid], NULL, NULL, NULL, From 3cf3a42b44fcb4d547b05f868ebc20035f24c02b Mon Sep 17 00:00:00 2001 From: luis_pereira87 Date: Tue, 4 Apr 2023 18:20:58 +0100 Subject: [PATCH 85/89] Flag integrityProtection work in inverse logic state, '0' means enabled which is the default the value, we need to set it to '1' to disable it --- openair2/RRC/NR/rrc_gNB_radio_bearers.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/openair2/RRC/NR/rrc_gNB_radio_bearers.c b/openair2/RRC/NR/rrc_gNB_radio_bearers.c index 68bf78482a..d969ebccf6 100644 --- a/openair2/RRC/NR/rrc_gNB_radio_bearers.c +++ b/openair2/RRC/NR/rrc_gNB_radio_bearers.c @@ -84,8 +84,12 @@ void generateDRB(gNB_RRC_UE_t *ue, ue->established_drbs[drb_id - 1].pdcp_config.headerCompression.present = NR_PDCP_Config__drb__headerCompression_PR_notUsed; ue->established_drbs[drb_id - 1].pdcp_config.headerCompression.NotUsed = 0; if (do_drb_integrity) - ue->established_drbs[drb_id - 1].pdcp_config.integrityProtection = NR_PDCP_Config__drb__integrityProtection_enabled; - if (!do_drb_ciphering) + ue->established_drbs[drb_id - 1].pdcp_config.integrityProtection = NR_PDCP_Config__drb__integrityProtection_enabled; + else + ue->established_drbs[drb_id - 1].pdcp_config.integrityProtection = 1; + if (do_drb_ciphering) + ue->established_drbs[drb_id - 1].pdcp_config.ext1.cipheringDisabled = 1; + else ue->established_drbs[drb_id - 1].pdcp_config.ext1.cipheringDisabled = NR_PDCP_Config__ext1__cipheringDisabled_true; } From 5b35523c2cc4a2786c3c873e15af2e89e024dac9 Mon Sep 17 00:00:00 2001 From: Angelo Athanassopoulos Date: Wed, 5 Apr 2023 11:22:59 +0200 Subject: [PATCH 86/89] NR RRC - RRCReestablishment - Apply clang format --- openair2/RRC/NR/nr_rrc_defs.h | 2 +- openair2/RRC/NR/rrc_gNB.c | 130 ++++++++++++++---------- openair2/RRC/NR/rrc_gNB_radio_bearers.c | 44 ++++---- openair2/RRC/NR/rrc_gNB_radio_bearers.h | 17 ++-- 4 files changed, 111 insertions(+), 82 deletions(-) diff --git a/openair2/RRC/NR/nr_rrc_defs.h b/openair2/RRC/NR/nr_rrc_defs.h index fd19220188..f8d69e942a 100644 --- a/openair2/RRC/NR/nr_rrc_defs.h +++ b/openair2/RRC/NR/nr_rrc_defs.h @@ -295,7 +295,7 @@ typedef struct drb_s { int pdcp_SN_SizeDL; int t_Reordering; int integrityProtection; - struct headerCompression_s{ + struct headerCompression_s { int NotUsed; int present; } headerCompression; diff --git a/openair2/RRC/NR/rrc_gNB.c b/openair2/RRC/NR/rrc_gNB.c index 301e069adb..ec5ded4989 100644 --- a/openair2/RRC/NR/rrc_gNB.c +++ b/openair2/RRC/NR/rrc_gNB.c @@ -1281,7 +1281,7 @@ void rrc_gNB_generate_RRCReestablishment(const protocol_ctxt_t *ctxt_pP, } /* - * Handle RRC Reestablishment Complete Functions + * Handle RRC Reestablishment Complete Functions */ /// @brief Function used in RRCReestablishmentComplete procedure to reestablish the SRB2. @@ -1292,25 +1292,28 @@ void RRCReestablishmentComplete_fill_SRB2_configList(const protocol_ctxt_t *cons const uint8_t old_xid, const uint8_t new_xid) { - gNB_RRC_UE_t *ue_p = &ue_context_pP->ue_context; - NR_SRB_ToAddMod_t *SRB2_config = NULL; - NR_SRB_ToAddModList_t *SRB_configList = ue_p->SRB_configList; + gNB_RRC_UE_t *ue_p = &ue_context_pP->ue_context; + NR_SRB_ToAddMod_t *SRB2_config = NULL; + NR_SRB_ToAddModList_t *SRB_configList = ue_p->SRB_configList; NR_SRB_ToAddModList_t **SRB_configList2 = NULL; SRB_configList2 = &ue_p->SRB_configList2[old_xid]; // get old configuration of SRB2 if (*SRB_configList2 != NULL) { - if((*SRB_configList2)->list.count!=0) { - LOG_D(NR_RRC, "RRC Reestablishment - SRB_configList2(%p) count is %d\n SRB_configList2->list.array[0] addr is %p\n", - SRB_configList2, (*SRB_configList2)->list.count, (*SRB_configList2)->list.array[0]); + if ((*SRB_configList2)->list.count != 0) { + LOG_D(NR_RRC, + "RRC Reestablishment - SRB_configList2(%p) count is %d\n SRB_configList2->list.array[0] addr is %p\n", + SRB_configList2, + (*SRB_configList2)->list.count, + (*SRB_configList2)->list.array[0]); } for (int i = 0; (i < (*SRB_configList2)->list.count) && (i < 3); i++) { - if ((*SRB_configList2)->list.array[i]->srb_Identity == 2 ) { + if ((*SRB_configList2)->list.array[i]->srb_Identity == 2) { LOG_D(NR_RRC, "RRC Reestablishment - get SRB2_config from (ue_p->SRB_configList2[%d])\n", old_xid); SRB2_config = (*SRB_configList2)->list.array[i]; - SRB2_config->reestablishPDCP = CALLOC(1, sizeof(*SRB2_config->reestablishPDCP)); + SRB2_config->reestablishPDCP = CALLOC(1, sizeof(*SRB2_config->reestablishPDCP)); *SRB2_config->reestablishPDCP = NR_SRB_ToAddMod__reestablishPDCP_true; break; } @@ -1331,10 +1334,13 @@ void RRCReestablishmentComplete_fill_SRB2_configList(const protocol_ctxt_t *cons asn1cSeqAdd(&SRB_configList->list, SRB2_config); asn1cSeqAdd(&(*SRB_configList2)->list, SRB2_config); LOG_D(NR_RRC, "RRC Reestablishment - Add SRB2_config (srb_Identity:%ld) to ue_p->SRB_configList\n", SRB2_config->srb_Identity); - LOG_D(NR_RRC, "RRC Reestablishment - Add SRB2_config (srb_Identity:%ld) to ue_p->SRB_configList2[%d]\n", SRB2_config->srb_Identity, new_xid); + LOG_D(NR_RRC, + "RRC Reestablishment - Add SRB2_config (srb_Identity:%ld) to ue_p->SRB_configList2[%d]\n", + SRB2_config->srb_Identity, + new_xid); } else { // SRB configuration list only contains SRB1. - LOG_W(NR_RRC,"RRC Reestablishment - SRB2 configuration does not exist in SRB configuration list\n"); + LOG_W(NR_RRC, "RRC Reestablishment - SRB2 configuration does not exist in SRB configuration list\n"); } ue_p->Srb[1].Active = 1; @@ -1346,16 +1352,16 @@ void RRCReestablishmentComplete_fill_SRB2_configList(const protocol_ctxt_t *cons } } -/// @brief Function used in RRCReestablishmentComplete procedure to reestablish the DRBs -/// that the UE previously had, it gets the information from the established_drbs +/// @brief Function used in RRCReestablishmentComplete procedure to reestablish the DRBs +/// that the UE previously had, it gets the information from the established_drbs /// struct. -/// @param new_xid The new RRC transaction id. +/// @param new_xid The new RRC transaction id. void RRCReestablishmentComplete_fill_DRB_configList(const protocol_ctxt_t *const ctxt_pP, rrc_gNB_ue_context_t *ue_context_pP, const uint8_t new_xid) { - gNB_RRC_UE_t *ue_p = &ue_context_pP->ue_context; - NR_DRB_ToAddMod_t *DRB_config = NULL; + gNB_RRC_UE_t *ue_p = &ue_context_pP->ue_context; + NR_DRB_ToAddMod_t *DRB_config = NULL; NR_DRB_ToAddModList_t **DRB_configList2 = &(ue_p->DRB_configList2[new_xid]); if (*DRB_configList2) { @@ -1376,26 +1382,26 @@ void RRCReestablishmentComplete_fill_DRB_configList(const protocol_ctxt_t *const } /// @brief Function used in RRCReestablishmentComplete procedure to update the NGU Tunnels. -/// @param reestablish_rnti is the old C-RNTI +/// @param reestablish_rnti is the old C-RNTI void RRCReestablishmentComplete_update_ngu_tunnel(const protocol_ctxt_t *const ctxt_pP, rrc_gNB_ue_context_t *ue_context_pP, const rnti_t reestablish_rnti) { gNB_RRC_UE_t *ue_p = &ue_context_pP->ue_context; - int i = 0; - int j = 0; + int i = 0; + int j = 0; int ret = 0; if (get_softmodem_params()->sa) { LOG_W(NR_RRC, "RRC Reestablishment - Rework identity mapping need to be done properly!\n"); - gtpv1u_gnb_create_tunnel_req_t create_tunnel_req={0}; + gtpv1u_gnb_create_tunnel_req_t create_tunnel_req = {0}; /* Save e RAB information for later */ - + for (j = 0, i = 0; i < NB_RB_MAX; i++) { if (ue_p->pduSession[i].status == PDU_SESSION_STATUS_ESTABLISHED || ue_p->pduSession[i].status == PDU_SESSION_STATUS_DONE) { - create_tunnel_req.pdusession_id[j] = ue_p->pduSession[i].param.pdusession_id; - create_tunnel_req.incoming_rb_id[j] = i+1; - create_tunnel_req.outgoing_teid[j] = ue_p->pduSession[i].param.gtp_teid; + create_tunnel_req.pdusession_id[j] = ue_p->pduSession[i].param.pdusession_id; + create_tunnel_req.incoming_rb_id[j] = i + 1; + create_tunnel_req.outgoing_teid[j] = ue_p->pduSession[i].param.gtp_teid; // to be developped, use the first QFI only create_tunnel_req.outgoing_qfi[j] = ue_p->pduSession[i].param.qos[0].qfi; memcpy(create_tunnel_req.dst_addr[j].buffer, ue_p->pduSession[i].param.upf_addr.buffer, sizeof(uint8_t) * 20); @@ -1404,20 +1410,18 @@ void RRCReestablishmentComplete_update_ngu_tunnel(const protocol_ctxt_t *const c } } - create_tunnel_req.ue_id = ctxt_pP->rntiMaybeUEid; // warning put zero above + create_tunnel_req.ue_id = ctxt_pP->rntiMaybeUEid; // warning put zero above create_tunnel_req.num_tunnels = j; - ret = gtpv1u_update_ngu_tunnel(ctxt_pP->instance, - &create_tunnel_req, - reestablish_rnti); + ret = gtpv1u_update_ngu_tunnel(ctxt_pP->instance, &create_tunnel_req, reestablish_rnti); - if ( ret != 0 ) { + if (ret != 0) { LOG_E(NR_RRC, "RRC Reestablishment - gtpv1u_update_ngu_tunnel failed,start to release UE %x\n", reestablish_rnti); - ue_p->ue_release_timer_s1 = 1; + ue_p->ue_release_timer_s1 = 1; ue_p->ue_release_timer_thres_s1 = 100; - ue_p->ue_release_timer = 0; - ue_p->ue_reestablishment_timer = 0; - ue_p->ul_failure_timer = 20000; // set ul_failure to 20000 for triggering rrc_eNB_send_S1AP_UE_CONTEXT_RELEASE_REQ - ue_p->ul_failure_timer = 0; + ue_p->ue_release_timer = 0; + ue_p->ue_reestablishment_timer = 0; + ue_p->ul_failure_timer = 20000; // set ul_failure to 20000 for triggering rrc_eNB_send_S1AP_UE_CONTEXT_RELEASE_REQ + ue_p->ul_failure_timer = 0; return; } } @@ -1427,19 +1431,22 @@ void RRCReestablishmentComplete_update_ngu_tunnel(const protocol_ctxt_t *const c /// @param old_xid Refers to the old transaction identifier passed to rrc_gNB_process_RRCReestablishmentComplete as xid. /// @todo parameters yet to process inside the for loop. /// @todo should test if pdu session are Ok before! inside the for loop. -void RRCReestablishmentComplete_nas_pdu_update(rrc_gNB_ue_context_t *ue_context_pP, - const uint8_t old_xid) +void RRCReestablishmentComplete_nas_pdu_update(rrc_gNB_ue_context_t *ue_context_pP, const uint8_t old_xid) { gNB_RRC_UE_t *ue_p = &ue_context_pP->ue_context; /* Add all NAS PDUs to the list */ for (int i = 0; i < ue_p->nb_of_pdusessions; i++) { ue_p->pduSession[i].status = PDU_SESSION_STATUS_DONE; ue_p->pduSession[i].xid = old_xid; - LOG_D(NR_RRC, "RRC Reestablishment - setting the status for the default DRB (index %d) to (%d,%s)\n", i, ue_p->pduSession[i].status, "PDU_SESSION_STATUS_DONE"); + LOG_D(NR_RRC, + "RRC Reestablishment - setting the status for the default DRB (index %d) to (%d,%s)\n", + i, + ue_p->pduSession[i].status, + "PDU_SESSION_STATUS_DONE"); } } -/// @brief Function used in RRCReestablishmentComplete procedure to Free all the NAS PDU buffers. +/// @brief Function used in RRCReestablishmentComplete procedure to Free all the NAS PDU buffers. void RRCReestablishmentComplete_nas_pdu_free(rrc_gNB_ue_context_t *ue_context_pP) { gNB_RRC_UE_t *ue_p = &ue_context_pP->ue_context; @@ -1464,14 +1471,16 @@ void rrc_gNB_process_RRCReestablishmentComplete(const protocol_ctxt_t *const ctx const uint8_t xid) { gNB_RRC_UE_t *ue_p = &ue_context_pP->ue_context; - LOG_I(NR_RRC, "[RAPROC] UE %04x Logical Channel UL-DCCH, processing NR_RRCReestablishmentComplete from UE (SRB1 Active)\n", ue_p->rnti); + LOG_I(NR_RRC, + "[RAPROC] UE %04x Logical Channel UL-DCCH, processing NR_RRCReestablishmentComplete from UE (SRB1 Active)\n", + ue_p->rnti); int i = 0; uint8_t new_xid = rrc_gNB_get_next_transaction_identifier(ctxt_pP->module_id); - ue_p->StatusRrc = NR_RRC_CONNECTED; + ue_p->StatusRrc = NR_RRC_CONNECTED; ue_p->ue_rrc_inactivity_timer = 1; // set rrc inactivity when UE goes into RRC_CONNECTED - ue_p->reestablishment_xid = new_xid; + ue_p->reestablishment_xid = new_xid; RRCReestablishmentComplete_fill_SRB2_configList(ctxt_pP, ue_context_pP, xid, new_xid); RRCReestablishmentComplete_fill_DRB_configList(ctxt_pP, ue_context_pP, new_xid); @@ -1486,9 +1495,9 @@ void rrc_gNB_process_RRCReestablishmentComplete(const protocol_ctxt_t *const ctx NR_CellGroupConfig_t *cellGroupConfig = calloc(1, sizeof(NR_CellGroupConfig_t)); // Revert spCellConfig stored in spCellConfigReestablishment before had been dropped during RRC Reestablishment - ue_p->masterCellGroup->spCellConfig = ue_p->spCellConfigReestablishment; - ue_p->spCellConfigReestablishment = NULL; - cellGroupConfig->spCellConfig = ue_p->masterCellGroup->spCellConfig; + ue_p->masterCellGroup->spCellConfig = ue_p->spCellConfigReestablishment; + ue_p->spCellConfigReestablishment = NULL; + cellGroupConfig->spCellConfig = ue_p->masterCellGroup->spCellConfig; cellGroupConfig->physicalCellGroupConfig = ue_p->masterCellGroup->physicalCellGroupConfig; uint8_t drb_id_to_setup_start = ue_p->DRB_configList ? ue_p->DRB_configList->list.array[0]->drb_Identity : 1; @@ -1496,10 +1505,17 @@ void rrc_gNB_process_RRCReestablishmentComplete(const protocol_ctxt_t *const ctx /* TODO: hardcoded to 13 for the time being, to be changed? */ long drb_priority[NGAP_MAX_DRBS_PER_UE] = {13}; - fill_mastercellGroupConfig(cellGroupConfig, ue_p->masterCellGroup, rrc->um_on_default_drb, (drb_id_to_setup_start < 2) ? 1 : 0, drb_id_to_setup_start, nb_drb_to_setup, drb_priority); + fill_mastercellGroupConfig(cellGroupConfig, + ue_p->masterCellGroup, + rrc->um_on_default_drb, + (drb_id_to_setup_start < 2) ? 1 : 0, + drb_id_to_setup_start, + nb_drb_to_setup, + drb_priority); - for(i = 0; i < cellGroupConfig->rlc_BearerToAddModList->list.count; i++) { - cellGroupConfig->rlc_BearerToAddModList->list.array[i]->reestablishRLC = CALLOC(1, sizeof(*cellGroupConfig->rlc_BearerToAddModList->list.array[i]->reestablishRLC)); + for (i = 0; i < cellGroupConfig->rlc_BearerToAddModList->list.count; i++) { + cellGroupConfig->rlc_BearerToAddModList->list.array[i]->reestablishRLC = + CALLOC(1, sizeof(*cellGroupConfig->rlc_BearerToAddModList->list.array[i]->reestablishRLC)); *cellGroupConfig->rlc_BearerToAddModList->list.array[i]->reestablishRLC = NR_RLC_BearerConfig__reestablishRLC_true; } @@ -1521,7 +1537,7 @@ void rrc_gNB_process_RRCReestablishmentComplete(const protocol_ctxt_t *const ctx NULL, cellGroupConfig); - LOG_DUMPMSG(NR_RRC,DEBUG_RRC,(char *)buffer,size, "[MSG] RRC Reconfiguration\n"); + LOG_DUMPMSG(NR_RRC, DEBUG_RRC, (char *)buffer, size, "[MSG] RRC Reconfiguration\n"); RRCReestablishmentComplete_nas_pdu_free(ue_context_pP); @@ -1529,9 +1545,15 @@ void rrc_gNB_process_RRCReestablishmentComplete(const protocol_ctxt_t *const ctx LOG_E(NR_RRC, "RRC decode err!!! do_RRCReconfiguration\n"); return; } else { - LOG_I(NR_RRC, "[gNB %d] Frame %d, Logical Channel DL-DCCH, Generate NR_RRCReconfiguration (bytes %d, UE id %04x)\n", ctxt_pP->module_id, ctxt_pP->frame, size, ue_p->rnti); + LOG_I(NR_RRC, + "[gNB %d] Frame %d, Logical Channel DL-DCCH, Generate NR_RRCReconfiguration (bytes %d, UE id %04x)\n", + ctxt_pP->module_id, + ctxt_pP->frame, + size, + ue_p->rnti); LOG_D(NR_RRC, - "[FRAME %05d][RRC_gNB][MOD %u][][--- PDCP_DATA_REQ/%d Bytes (RRCReconfiguration to UE %04x MUI %d) --->][PDCP][MOD %u][RB %u]\n", + "[FRAME %05d][RRC_gNB][MOD %u][][--- PDCP_DATA_REQ/%d Bytes (RRCReconfiguration to UE %04x MUI %d) --->][PDCP][MOD " + "%u][RB %u]\n", ctxt_pP->frame, ctxt_pP->module_id, size, @@ -1545,12 +1567,16 @@ void rrc_gNB_process_RRCReestablishmentComplete(const protocol_ctxt_t *const ctx } if (NODE_IS_DU(RC.nrrrc[ctxt_pP->module_id]->node_type) || NODE_IS_MONOLITHIC(RC.nrrrc[ctxt_pP->module_id]->node_type)) { - uint32_t delay_ms = ue_p->masterCellGroup && ue_p->masterCellGroup->spCellConfig && ue_p->masterCellGroup->spCellConfig->spCellConfigDedicated + uint32_t delay_ms = ue_p->masterCellGroup && ue_p->masterCellGroup->spCellConfig + && ue_p->masterCellGroup->spCellConfig->spCellConfigDedicated && ue_p->masterCellGroup->spCellConfig->spCellConfigDedicated->downlinkBWP_ToAddModList ? NR_RRC_RECONFIGURATION_DELAY_MS + NR_RRC_BWP_SWITCHING_DELAY_MS : NR_RRC_RECONFIGURATION_DELAY_MS; - nr_mac_enable_ue_rrc_processing_timer(ctxt_pP->module_id, ue_p->rnti, *RC.nrrrc[ctxt_pP->module_id]->carrier.servingcellconfigcommon->ssbSubcarrierSpacing, delay_ms); + nr_mac_enable_ue_rrc_processing_timer(ctxt_pP->module_id, + ue_p->rnti, + *RC.nrrrc[ctxt_pP->module_id]->carrier.servingcellconfigcommon->ssbSubcarrierSpacing, + delay_ms); } } //----------------------------------------------------------------------------- diff --git a/openair2/RRC/NR/rrc_gNB_radio_bearers.c b/openair2/RRC/NR/rrc_gNB_radio_bearers.c index d969ebccf6..e9fa7922fb 100644 --- a/openair2/RRC/NR/rrc_gNB_radio_bearers.c +++ b/openair2/RRC/NR/rrc_gNB_radio_bearers.c @@ -47,19 +47,19 @@ void generateDRB(gNB_RRC_UE_t *ue, int qos_flow_index; if (ue->established_drbs[drb_id - 1].status == DRB_INACTIVE) { /* DRB Management */ - ue->established_drbs[drb_id - 1].drb_id = drb_id; + ue->established_drbs[drb_id - 1].drb_id = drb_id; ue->established_drbs[drb_id - 1].reestablishPDCP = -1; - ue->established_drbs[drb_id - 1].recoverPDCP = -1; + ue->established_drbs[drb_id - 1].recoverPDCP = -1; for (i = 0; i < NGAP_MAX_DRBS_PER_UE; i++) { - if ((ue->established_drbs[drb_id - 1].cnAssociation.sdap_config.pdusession_id == 0 || - ue->established_drbs[drb_id - 1].cnAssociation.sdap_config.pdusession_id == pduSession->param.pdusession_id) && - ue->established_drbs[drb_id - 1].defaultDRBid == 0) { + if ((ue->established_drbs[drb_id - 1].cnAssociation.sdap_config.pdusession_id == 0 + || ue->established_drbs[drb_id - 1].cnAssociation.sdap_config.pdusession_id == pduSession->param.pdusession_id) + && ue->established_drbs[drb_id - 1].defaultDRBid == 0) { ue->established_drbs[drb_id - 1].cnAssociation.sdap_config.defaultDRB = true; ue->established_drbs[drb_id - 1].defaultDRBid = drb_id; } } /* SDAP Configuration */ - ue->established_drbs[drb_id - 1].cnAssociation.present = NR_DRB_ToAddMod__cnAssociation_PR_sdap_Config; + ue->established_drbs[drb_id - 1].cnAssociation.present = NR_DRB_ToAddMod__cnAssociation_PR_sdap_Config; ue->established_drbs[drb_id - 1].cnAssociation.sdap_config.pdusession_id = pduSession->param.pdusession_id; if (enable_sdap) { ue->established_drbs[drb_id - 1].cnAssociation.sdap_config.sdap_HeaderDL = NR_SDAP_Config__sdap_HeaderDL_present; @@ -68,19 +68,19 @@ void generateDRB(gNB_RRC_UE_t *ue, ue->established_drbs[drb_id - 1].cnAssociation.sdap_config.sdap_HeaderDL = NR_SDAP_Config__sdap_HeaderDL_absent; ue->established_drbs[drb_id - 1].cnAssociation.sdap_config.sdap_HeaderUL = NR_SDAP_Config__sdap_HeaderUL_absent; } - for (qos_flow_index = 0; qos_flow_index < pduSession->param.nb_qos; qos_flow_index++) - { - ue->established_drbs[drb_id - 1].cnAssociation.sdap_config.mappedQoS_FlowsToAdd[qos_flow_index] = pduSession->param.qos[qos_flow_index].qfi; - if(pduSession->param.qos[qos_flow_index].fiveQI > 5) + for (qos_flow_index = 0; qos_flow_index < pduSession->param.nb_qos; qos_flow_index++) { + ue->established_drbs[drb_id - 1].cnAssociation.sdap_config.mappedQoS_FlowsToAdd[qos_flow_index] = + pduSession->param.qos[qos_flow_index].qfi; + if (pduSession->param.qos[qos_flow_index].fiveQI > 5) ue->established_drbs[drb_id - 1].status = DRB_ACTIVE_NONGBR; else ue->established_drbs[drb_id - 1].status = DRB_ACTIVE; } /* PDCP Configuration */ - ue->established_drbs[drb_id - 1].pdcp_config.discardTimer = NR_PDCP_Config__drb__discardTimer_infinity; - ue->established_drbs[drb_id - 1].pdcp_config.pdcp_SN_SizeDL = NR_PDCP_Config__drb__pdcp_SN_SizeDL_len18bits; - ue->established_drbs[drb_id - 1].pdcp_config.pdcp_SN_SizeUL = NR_PDCP_Config__drb__pdcp_SN_SizeUL_len18bits; - ue->established_drbs[drb_id - 1].pdcp_config.t_Reordering = NR_PDCP_Config__t_Reordering_ms100; + ue->established_drbs[drb_id - 1].pdcp_config.discardTimer = NR_PDCP_Config__drb__discardTimer_infinity; + ue->established_drbs[drb_id - 1].pdcp_config.pdcp_SN_SizeDL = NR_PDCP_Config__drb__pdcp_SN_SizeDL_len18bits; + ue->established_drbs[drb_id - 1].pdcp_config.pdcp_SN_SizeUL = NR_PDCP_Config__drb__pdcp_SN_SizeUL_len18bits; + ue->established_drbs[drb_id - 1].pdcp_config.t_Reordering = NR_PDCP_Config__t_Reordering_ms100; ue->established_drbs[drb_id - 1].pdcp_config.headerCompression.present = NR_PDCP_Config__drb__headerCompression_PR_notUsed; ue->established_drbs[drb_id - 1].pdcp_config.headerCompression.NotUsed = 0; if (do_drb_integrity) @@ -91,7 +91,6 @@ void generateDRB(gNB_RRC_UE_t *ue, ue->established_drbs[drb_id - 1].pdcp_config.ext1.cipheringDisabled = 1; else ue->established_drbs[drb_id - 1].pdcp_config.ext1.cipheringDisabled = NR_PDCP_Config__ext1__cipheringDisabled_true; - } } @@ -106,17 +105,16 @@ NR_DRB_ToAddMod_t *generateDRB_ASN1(drb_t drb_asn1) asn1cCalloc(pdcpConfig->drb, drb); DRB_config->drb_Identity = drb_asn1.drb_id; - association->present = drb_asn1.cnAssociation.present; + association->present = drb_asn1.cnAssociation.present; /* SDAP Configuration */ - SDAP_config->pdu_Session = drb_asn1.cnAssociation.sdap_config.pdusession_id; + SDAP_config->pdu_Session = drb_asn1.cnAssociation.sdap_config.pdusession_id; SDAP_config->sdap_HeaderDL = drb_asn1.cnAssociation.sdap_config.sdap_HeaderDL; SDAP_config->sdap_HeaderUL = drb_asn1.cnAssociation.sdap_config.sdap_HeaderUL; - SDAP_config->defaultDRB = drb_asn1.cnAssociation.sdap_config.defaultDRB; - - for (int qos_flow_index = 0; qos_flow_index < QOSFLOW_MAX_VALUE; qos_flow_index++) - { - if(drb_asn1.cnAssociation.sdap_config.mappedQoS_FlowsToAdd[qos_flow_index] != 0) { + SDAP_config->defaultDRB = drb_asn1.cnAssociation.sdap_config.defaultDRB; + + for (int qos_flow_index = 0; qos_flow_index < QOSFLOW_MAX_VALUE; qos_flow_index++) { + if (drb_asn1.cnAssociation.sdap_config.mappedQoS_FlowsToAdd[qos_flow_index] != 0) { asn1cSequenceAdd(sdapFlows->list, NR_QFI_t, qfi); *qfi = drb_asn1.cnAssociation.sdap_config.mappedQoS_FlowsToAdd[qos_flow_index]; } @@ -130,7 +128,7 @@ NR_DRB_ToAddMod_t *generateDRB_ASN1(drb_t drb_asn1) asn1cCallocOne(drb->pdcp_SN_SizeDL, drb_asn1.pdcp_config.pdcp_SN_SizeDL); asn1cCallocOne(pdcpConfig->t_Reordering, drb_asn1.pdcp_config.t_Reordering); - drb->headerCompression.present = drb_asn1.pdcp_config.headerCompression.present; + drb->headerCompression.present = drb_asn1.pdcp_config.headerCompression.present; drb->headerCompression.choice.notUsed = drb_asn1.pdcp_config.headerCompression.NotUsed; if (!drb_asn1.pdcp_config.integrityProtection) { diff --git a/openair2/RRC/NR/rrc_gNB_radio_bearers.h b/openair2/RRC/NR/rrc_gNB_radio_bearers.h index 10f1ff8c05..732394ef82 100644 --- a/openair2/RRC/NR/rrc_gNB_radio_bearers.h +++ b/openair2/RRC/NR/rrc_gNB_radio_bearers.h @@ -35,19 +35,24 @@ #define GBR_FLOW (1) #define NONGBR_FLOW (0) -/// @brief Generates an ASN1 DRB-ToAddMod, from the established_drbs in gNB_RRC_UE_t. -/// @param drb_t drb_asn1 +/// @brief Generates an ASN1 DRB-ToAddMod, from the established_drbs in gNB_RRC_UE_t. +/// @param drb_t drb_asn1 /// @return Returns the ASN1 DRB-ToAddMod structs. NR_DRB_ToAddMod_t *generateDRB_ASN1(drb_t drb_asn1); -/// @brief Creates and stores a DRB in the gNB_RRC_UE_t struct, it doesn't create the actual entity, +/// @brief Creates and stores a DRB in the gNB_RRC_UE_t struct, it doesn't create the actual entity, /// to create the actual entity use the generateDRB_ASN1. /// @param ue The gNB_RRC_UE_t struct that holds information for the UEs /// @param drb_id The Data Radio Bearer Identity to be created for the established DRB. /// @param pduSession The PDU Session that the DRB is created for. /// @param enable_sdap If true the SDAP header will be added to the packet, else it will not add or search for SDAP header. -/// @param do_drb_integrity -/// @param do_drb_ciphering -void generateDRB(gNB_RRC_UE_t *ue, uint8_t drb_id, rrc_pdu_session_param_t *pduSession, bool enable_sdap, int do_drb_integrity, int do_drb_ciphering); +/// @param do_drb_integrity +/// @param do_drb_ciphering +void generateDRB(gNB_RRC_UE_t *ue, + uint8_t drb_id, + rrc_pdu_session_param_t *pduSession, + bool enable_sdap, + int do_drb_integrity, + int do_drb_ciphering); uint8_t next_available_drb(gNB_RRC_UE_t *ue, rrc_pdu_session_param_t *pdusession, bool is_gbr); bool drb_is_active(gNB_RRC_UE_t *ue, uint8_t drb_id); From 733c7e5e8949c33b174bdf7ce98ec5ca8594faa1 Mon Sep 17 00:00:00 2001 From: Cedric Roux Date: Sun, 19 Feb 2023 17:42:52 +0100 Subject: [PATCH 87/89] bugfix: change memory management of interface between PHY and MAC scheduler This commit fixes a bug caused by global variables in the gNB_MAC_INST structure used to transfer MAC scheduler's results to the PHY layer. When using several threads in the PHY layer, it may happen that the MAC scheduler is called a second time by the second PHY thread for the next slot before the content of the global variables in the gNB_MAC_INST structure used to store the result of the MAC scheduler for the current slot have been consumed by the first thread, leading to unpredictable results (all of them certainly wrong). The solution is to introduce a small memory management module for the interface between the PHY threads and the MAC scheduler. The MAC scheduler now fills the data structures passed to it, and not a global variable anymore. The PHY layer manages the structures and ensures that no data is overwritten before being fully consumed by the various threads of the PHY layer. Some care has to be taken because several PHY threads will access the same structures at the same time for a given slot (in read mode, so it's okay). A mechanism of reference counting has thus been introduced. Only one CC is supported. This restriction should not be too hard to remove if/when needed. (The code before this commit does not seem to work for more than one CC anyway.) --- CMakeLists.txt | 1 + executables/nr-gnb.c | 3 + openair1/PHY/defs_gNB.h | 2 + openair1/SCHED_NR/fapi_nr_l1.c | 20 ++- openair1/SIMULATION/NR_PHY/dlschsim.c | 11 ++ openair1/SIMULATION/NR_PHY/dlsim.c | 33 ++-- openair1/SIMULATION/NR_PHY/pbchsim.c | 11 ++ openair1/SIMULATION/NR_PHY/prachsim.c | 11 ++ openair1/SIMULATION/NR_PHY/pucchsim.c | 11 ++ openair1/SIMULATION/NR_PHY/ulschsim.c | 11 ++ openair1/SIMULATION/NR_PHY/ulsim.c | 4 +- openair2/LAYER2/NR_MAC_gNB/gNB_scheduler.c | 81 +++++++--- openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_RA.c | 85 ++++++---- .../LAYER2/NR_MAC_gNB/gNB_scheduler_bch.c | 28 ++-- .../LAYER2/NR_MAC_gNB/gNB_scheduler_dlsch.c | 18 ++- .../NR_MAC_gNB/gNB_scheduler_primitives.c | 9 +- .../LAYER2/NR_MAC_gNB/gNB_scheduler_ulsch.c | 4 +- openair2/LAYER2/NR_MAC_gNB/mac_proto.h | 69 +++++--- openair2/LAYER2/NR_MAC_gNB/nr_mac_gNB.h | 9 -- openair2/NR_PHY_INTERFACE/NR_IF_Module.c | 30 ++-- openair2/NR_PHY_INTERFACE/NR_IF_Module.h | 10 +- openair2/NR_PHY_INTERFACE/nr_sched_response.c | 153 ++++++++++++++++++ openair2/NR_PHY_INTERFACE/nr_sched_response.h | 32 ++++ 23 files changed, 497 insertions(+), 149 deletions(-) create mode 100644 openair2/NR_PHY_INTERFACE/nr_sched_response.c create mode 100644 openair2/NR_PHY_INTERFACE/nr_sched_response.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 691cb4aaa8..04f87ad8ba 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1474,6 +1474,7 @@ set (MAC_SRC set (MAC_NR_SRC ${NR_PHY_INTERFACE_DIR}/NR_IF_Module.c + ${NR_PHY_INTERFACE_DIR}/nr_sched_response.c ${NR_GNB_MAC_DIR}/main.c ${NR_GNB_MAC_DIR}/config.c ${NR_GNB_MAC_DIR}/gNB_scheduler.c diff --git a/executables/nr-gnb.c b/executables/nr-gnb.c index 05516e01b4..773f89416c 100644 --- a/executables/nr-gnb.c +++ b/executables/nr-gnb.c @@ -51,6 +51,7 @@ #include "PHY/NR_TRANSPORT/nr_transport_proto.h" #include "PHY/MODULATION/nr_modulation.h" #include "PHY/NR_TRANSPORT/nr_dlsch.h" +#include "openair2/NR_PHY_INTERFACE/nr_sched_response.h" #undef MALLOC //there are two conflicting definitions, so we better make sure we don't use it at all //#undef FRAME_LENGTH_COMPLEX_SAMPLES //there are two conflicting definitions, so we better make sure we don't use it at all @@ -126,6 +127,8 @@ void tx_func(void *param) { 1); clock_gettime(CLOCK_MONOTONIC,&info->gNB->rt_L1_profiling.return_L1_TX[rt_prof_idx]); + /* this thread is done with the sched_info, decrease the reference counter */ + deref_sched_response(info->sched_response_id); } void rx_func(void *param) diff --git a/openair1/PHY/defs_gNB.h b/openair1/PHY/defs_gNB.h index 1c248bc4a9..71853765af 100644 --- a/openair1/PHY/defs_gNB.h +++ b/openair1/PHY/defs_gNB.h @@ -823,6 +823,8 @@ typedef struct processingData_L1tx { uint16_t num_pdsch_slot; int num_dl_pdcch; int num_ul_pdcch; + /* a reference to the sched_response, to release it when not needed anymore */ + int sched_response_id; } processingData_L1tx_t; #endif diff --git a/openair1/SCHED_NR/fapi_nr_l1.c b/openair1/SCHED_NR/fapi_nr_l1.c index 43223906d5..6f644c4ba5 100644 --- a/openair1/SCHED_NR/fapi_nr_l1.c +++ b/openair1/SCHED_NR/fapi_nr_l1.c @@ -35,6 +35,7 @@ #include "PHY/NR_TRANSPORT/nr_dlsch.h" #include "PHY/NR_TRANSPORT/nr_dci.h" #include "nfapi/oai_integration/vendor_ext.h" +#include "openair2/NR_PHY_INTERFACE/nr_sched_response.h" extern int oai_nfapi_dl_tti_req(nfapi_nr_dl_tti_request_t *dl_config_req); extern int oai_nfapi_tx_data_req(nfapi_nr_tx_data_request_t *tx_data_req); @@ -131,10 +132,10 @@ void nr_schedule_response(NR_Sched_Rsp_t *Sched_INFO) { // copy data from L2 interface into L1 structures module_id_t Mod_id = Sched_INFO->module_id; - nfapi_nr_dl_tti_request_t *DL_req = Sched_INFO->DL_req; - nfapi_nr_tx_data_request_t *TX_req = Sched_INFO->TX_req; - nfapi_nr_ul_tti_request_t *UL_tti_req = Sched_INFO->UL_tti_req; - nfapi_nr_ul_dci_request_t *UL_dci_req = Sched_INFO->UL_dci_req; + nfapi_nr_dl_tti_request_t *DL_req = &Sched_INFO->DL_req; + nfapi_nr_tx_data_request_t *TX_req = &Sched_INFO->TX_req; + nfapi_nr_ul_tti_request_t *UL_tti_req = &Sched_INFO->UL_tti_req; + nfapi_nr_ul_dci_request_t *UL_dci_req = &Sched_INFO->UL_dci_req; frame_t frame = Sched_INFO->frame; sub_frame_t slot = Sched_INFO->slot; @@ -169,6 +170,8 @@ void nr_schedule_response(NR_Sched_Rsp_t *Sched_INFO) msgTx->num_ul_pdcch = number_ul_dci_pdu; msgTx->slot = slot; msgTx->frame = frame; + /* store the sched_response_id for the TX thread to release it when done */ + msgTx->sched_response_id = Sched_INFO->sched_response_id; for (int i=0;idl_tti_request_body.dl_tti_pdu_list[i]; @@ -208,6 +211,11 @@ void nr_schedule_response(NR_Sched_Rsp_t *Sched_INFO) for (int i=0; iul_pdcch_pdu[i] = UL_dci_req->ul_dci_pdu_list[i]; + /* Both the current thread and the TX thread will access the sched_info + * at the same time, so increase its reference counter, so that it is + * released only when both threads are done with it. + */ + inc_ref_sched_response(Sched_INFO->sched_response_id); pushNotifiedFIFO(&gNB->L1_tx_filled,res); } @@ -250,5 +258,9 @@ void nr_schedule_response(NR_Sched_Rsp_t *Sched_INFO) if (number_dl_pdu>0) oai_nfapi_dl_tti_req(DL_req); } + + /* this thread is done with the sched_info, decrease the reference counter */ + deref_sched_response(Sched_INFO->sched_response_id); + stop_meas(&gNB->schedule_response_stats); } diff --git a/openair1/SIMULATION/NR_PHY/dlschsim.c b/openair1/SIMULATION/NR_PHY/dlschsim.c index cdbe21465f..317159f6bd 100644 --- a/openair1/SIMULATION/NR_PHY/dlschsim.c +++ b/openair1/SIMULATION/NR_PHY/dlschsim.c @@ -81,6 +81,17 @@ void init_downlink_harq_status(NR_DL_UE_HARQ_t *dl_harq) {} NR_IF_Module_t *NR_IF_Module_init(int Mod_id) { return (NULL); } nfapi_mode_t nfapi_getmode(void) { return NFAPI_MODE_UNKNOWN; } +void inc_ref_sched_response(int _) +{ + LOG_E(PHY, "fatal\n"); + exit(1); +} +void deref_sched_response(int _) +{ + LOG_E(PHY, "fatal\n"); + exit(1); +} + nrUE_params_t nrUE_params={0}; nrUE_params_t *get_nrUE_params(void) { diff --git a/openair1/SIMULATION/NR_PHY/dlsim.c b/openair1/SIMULATION/NR_PHY/dlsim.c index b2c9ac5a33..ce3c564773 100644 --- a/openair1/SIMULATION/NR_PHY/dlsim.c +++ b/openair1/SIMULATION/NR_PHY/dlsim.c @@ -328,7 +328,7 @@ int main(int argc, char **argv) //int frame_length_complex_samples_no_prefix; NR_DL_FRAME_PARMS *frame_parms; UE_nr_rxtx_proc_t UE_proc; - NR_Sched_Rsp_t Sched_INFO; + NR_Sched_Rsp_t *Sched_INFO; gNB_MAC_INST *gNB_mac; NR_UE_MAC_INST_t *UE_mac; int cyclic_prefix_type = NFAPI_CP_NORMAL; @@ -994,28 +994,33 @@ int main(int argc, char **argv) round = 0; 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; + while ((roundack==0)) { round_trials[round]++; - clear_nr_nfapi_information(RC.nrmac[0], 0, frame, slot); + clear_nr_nfapi_information(RC.nrmac[0], 0, frame, slot, &Sched_INFO->DL_req, &Sched_INFO->TX_req, &Sched_INFO->UL_dci_req); UE_info->UE_sched_ctrl.harq_processes[harq_pid].ndi = !(trial&1); UE_info->UE_sched_ctrl.harq_processes[harq_pid].round = round; - nr_schedule_ue_spec(0, frame, slot); - Sched_INFO.module_id = 0; - Sched_INFO.CC_id = 0; - Sched_INFO.frame = frame; - Sched_INFO.slot = slot; - Sched_INFO.DL_req = &gNB_mac->DL_req[0]; - Sched_INFO.UL_tti_req = gNB_mac->UL_tti_req_ahead[0]; - Sched_INFO.UL_dci_req = NULL; - Sched_INFO.TX_req = &gNB_mac->TX_req[0]; + nr_schedule_ue_spec(0, frame, slot, &Sched_INFO->DL_req, &Sched_INFO->TX_req); + Sched_INFO->module_id = 0; + Sched_INFO->CC_id = 0; + Sched_INFO->frame = frame; + Sched_INFO->slot = slot; + Sched_INFO->UL_dci_req.numPdus = 0; pushNotifiedFIFO(&gNB->L1_tx_free,msgL1Tx); - nr_schedule_response(&Sched_INFO); + nr_schedule_response(Sched_INFO); /* PTRS values for DLSIM calculations */ - nfapi_nr_dl_tti_request_body_t *dl_req = &gNB_mac->DL_req[Sched_INFO.CC_id].dl_tti_request_body; + nfapi_nr_dl_tti_request_body_t *dl_req = &Sched_INFO->DL_req.dl_tti_request_body; nfapi_nr_dl_tti_request_pdu_t *dl_tti_pdsch_pdu = &dl_req->dl_tti_pdu_list[1]; nfapi_nr_dl_tti_pdsch_pdu_rel15_t *pdsch_pdu_rel15 = &dl_tti_pdsch_pdu->pdsch_pdu.pdsch_pdu_rel15; pdu_bit_map = pdsch_pdu_rel15->pduBitmap; diff --git a/openair1/SIMULATION/NR_PHY/pbchsim.c b/openair1/SIMULATION/NR_PHY/pbchsim.c index ee67e8d52e..5f254bc1cd 100644 --- a/openair1/SIMULATION/NR_PHY/pbchsim.c +++ b/openair1/SIMULATION/NR_PHY/pbchsim.c @@ -67,6 +67,17 @@ openair0_config_t openair0_cfg[MAX_CARDS]; uint8_t const nr_rv_round_map[4] = {0, 2, 3, 1}; +void inc_ref_sched_response(int _) +{ + LOG_E(PHY, "fatal\n"); + exit(1); +} +void deref_sched_response(int _) +{ + LOG_E(PHY, "fatal\n"); + exit(1); +} + uint64_t get_softmodem_optmask(void) {return 0;} static softmodem_params_t softmodem_params; softmodem_params_t *get_softmodem_params(void) { diff --git a/openair1/SIMULATION/NR_PHY/prachsim.c b/openair1/SIMULATION/NR_PHY/prachsim.c index 629b7316b0..aa441e4c61 100644 --- a/openair1/SIMULATION/NR_PHY/prachsim.c +++ b/openair1/SIMULATION/NR_PHY/prachsim.c @@ -86,6 +86,17 @@ softmodem_params_t *get_softmodem_params(void) {return 0;} instance_t DUuniqInstance=0; instance_t CUuniqInstance=0; +void inc_ref_sched_response(int _) +{ + LOG_E(PHY, "fatal\n"); + exit(1); +} +void deref_sched_response(int _) +{ + LOG_E(PHY, "fatal\n"); + exit(1); +} + int nr_derive_key_ng_ran_star(uint16_t pci, uint64_t nr_arfcn_dl, const uint8_t key[32], uint8_t *key_ng_ran_star) { return 0; diff --git a/openair1/SIMULATION/NR_PHY/pucchsim.c b/openair1/SIMULATION/NR_PHY/pucchsim.c index 01eec03c4d..a0631767ba 100644 --- a/openair1/SIMULATION/NR_PHY/pucchsim.c +++ b/openair1/SIMULATION/NR_PHY/pucchsim.c @@ -73,6 +73,17 @@ void init_downlink_harq_status(NR_DL_UE_HARQ_t *dl_harq) {} NR_IF_Module_t *NR_IF_Module_init(int Mod_id) { return (NULL); } nfapi_mode_t nfapi_getmode(void) { return NFAPI_MODE_UNKNOWN; } +void inc_ref_sched_response(int _) +{ + LOG_E(PHY, "fatal\n"); + exit(1); +} +void deref_sched_response(int _) +{ + LOG_E(PHY, "fatal\n"); + exit(1); +} + nrUE_params_t nrUE_params={0}; nrUE_params_t *get_nrUE_params(void) { diff --git a/openair1/SIMULATION/NR_PHY/ulschsim.c b/openair1/SIMULATION/NR_PHY/ulschsim.c index 344f3f3310..69b12e78ee 100644 --- a/openair1/SIMULATION/NR_PHY/ulschsim.c +++ b/openair1/SIMULATION/NR_PHY/ulschsim.c @@ -80,6 +80,17 @@ PHY_VARS_NR_UE *PHY_vars_UE_g[1][1] = { { NULL } }; uint16_t n_rnti = 0x1234; openair0_config_t openair0_cfg[MAX_CARDS]; +void inc_ref_sched_response(int _) +{ + LOG_E(PHY, "fatal\n"); + exit(1); +} +void deref_sched_response(int _) +{ + LOG_E(PHY, "fatal\n"); + exit(1); +} + int nr_postDecode_sim(PHY_VARS_gNB *gNB, notifiedFIFO_elt_t *req) { ldpcDecode_t *rdata = (ldpcDecode_t*) NotifiedFifoData(req); NR_UL_gNB_HARQ_t *ulsch_harq = rdata->ulsch_harq; diff --git a/openair1/SIMULATION/NR_PHY/ulsim.c b/openair1/SIMULATION/NR_PHY/ulsim.c index 109b4faf20..d8ffdcc55e 100644 --- a/openair1/SIMULATION/NR_PHY/ulsim.c +++ b/openair1/SIMULATION/NR_PHY/ulsim.c @@ -759,10 +759,10 @@ int main(int argc, char **argv) NR_gNB_ULSCH_t *ulsch_gNB = &gNB->ulsch[UE_id]; // nfapi_nr_ul_config_ulsch_pdu *rel15_ul = &ulsch_gNB->harq_process->ulsch_pdu; - nfapi_nr_ul_tti_request_t *UL_tti_req = malloc(sizeof(*UL_tti_req)); NR_Sched_Rsp_t *Sched_INFO = malloc(sizeof(*Sched_INFO)); memset((void*)Sched_INFO,0,sizeof(*Sched_INFO)); - Sched_INFO->UL_tti_req=UL_tti_req; + nfapi_nr_ul_tti_request_t *UL_tti_req = &Sched_INFO->UL_tti_req; + Sched_INFO->sched_response_id = -1; nr_phy_data_tx_t phy_data = {0}; diff --git a/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler.c b/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler.c index 41a2153cf8..c4ed4e2d2a 100644 --- a/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler.c +++ b/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler.c @@ -56,10 +56,13 @@ const uint8_t nr_rv_round_map[4] = { 0, 2, 3, 1 }; uint16_t nr_pdcch_order_table[6] = { 31, 31, 511, 2047, 2047, 8191 }; -void clear_nr_nfapi_information(gNB_MAC_INST * gNB, +void clear_nr_nfapi_information(gNB_MAC_INST *gNB, int CC_idP, frame_t frameP, - sub_frame_t slotP) + sub_frame_t slotP, + nfapi_nr_dl_tti_request_t *DL_req, + nfapi_nr_tx_data_request_t *TX_req, + nfapi_nr_ul_dci_request_t *UL_dci_req) { NR_ServingCellConfigCommon_t *scc = gNB->common_channels->ServingCellConfigCommon; @@ -67,10 +70,7 @@ void clear_nr_nfapi_information(gNB_MAC_INST * gNB, UL_tti_req_ahead_initialization(gNB, scc, num_slots, CC_idP, frameP, slotP, *scc->ssbSubcarrierSpacing); - nfapi_nr_dl_tti_request_t *DL_req = &gNB->DL_req[0]; nfapi_nr_dl_tti_pdcch_pdu_rel15_t **pdcch = (nfapi_nr_dl_tti_pdcch_pdu_rel15_t **)gNB->pdcch_pdu_idx[CC_idP]; - nfapi_nr_ul_dci_request_t *UL_dci_req = &gNB->UL_dci_req[0]; - nfapi_nr_tx_data_request_t *TX_req = &gNB->TX_req[0]; gNB->pdu_index[CC_idP] = 0; @@ -96,23 +96,53 @@ void clear_nr_nfapi_information(gNB_MAC_INST * gNB, future_ul_tti_req->n_ulcch = 0; future_ul_tti_req->n_group = 0; - /* UL_tti_req is a simple pointer into the current UL_tti_req_ahead, i.e., - * it walks over UL_tti_req_ahead in a circular fashion */ - const int current_index = ul_buffer_index(frameP, slotP, *scc->ssbSubcarrierSpacing, gNB->UL_tti_req_ahead_size); - gNB->UL_tti_req[CC_idP] = &gNB->UL_tti_req_ahead[CC_idP][current_index]; TX_req[CC_idP].Number_of_PDUs = 0; - } bool is_xlsch_in_slot(uint64_t bitmap, sub_frame_t slot) { return (bitmap >> (slot % 64)) & 0x01; } -void gNB_dlsch_ulsch_scheduler(module_id_t module_idP, - frame_t frame, - sub_frame_t slot) +/* the structure nfapi_nr_ul_tti_request_t is very big, let's copy only what is necessary */ +static void copy_ul_tti_req(nfapi_nr_ul_tti_request_t *to, nfapi_nr_ul_tti_request_t *from) { + int i; + to->header = from->header; + to->SFN = from->SFN; + to->Slot = from->Slot; + to->n_pdus = from->n_pdus; + to->rach_present = from->rach_present; + to->n_ulsch = from->n_ulsch; + to->n_ulcch = from->n_ulcch; + to->n_group = from->n_group; + + for (i = 0; i < from->n_pdus; i++) { + to->pdus_list[i].pdu_type = from->pdus_list[i].pdu_type; + to->pdus_list[i].pdu_size = from->pdus_list[i].pdu_size; + + switch (from->pdus_list[i].pdu_type) { + case NFAPI_NR_UL_CONFIG_PRACH_PDU_TYPE: + to->pdus_list[i].prach_pdu = from->pdus_list[i].prach_pdu; + break; + case NFAPI_NR_UL_CONFIG_PUSCH_PDU_TYPE: + to->pdus_list[i].pusch_pdu = from->pdus_list[i].pusch_pdu; + break; + case NFAPI_NR_UL_CONFIG_PUCCH_PDU_TYPE: + to->pdus_list[i].pucch_pdu = from->pdus_list[i].pucch_pdu; + break; + case NFAPI_NR_UL_CONFIG_SRS_PDU_TYPE: + to->pdus_list[i].srs_pdu = from->pdus_list[i].srs_pdu; + break; + } + } + + for (i = 0; i < from->n_group; i++) + to->groups_list[i] = from->groups_list[i]; +} + +void gNB_dlsch_ulsch_scheduler(module_id_t module_idP, frame_t frame, sub_frame_t slot, NR_Sched_Rsp_t *sched_info) +{ protocol_ctxt_t ctxt = {0}; PROTOCOL_CTXT_SET_BY_MODULE_ID(&ctxt, module_idP, ENB_FLAG_YES, NOT_A_RNTI, frame, slot,module_idP); @@ -157,8 +187,7 @@ void gNB_dlsch_ulsch_scheduler(module_id_t module_idP, uint16_t *vrb_map_UL = cc[CC_id].vrb_map_UL; memcpy(&vrb_map_UL[prev_slot % size * MAX_BWP_SIZE], &gNB->ulprbbl, sizeof(uint16_t) * MAX_BWP_SIZE); - clear_nr_nfapi_information(gNB, CC_id, frame, slot); - + clear_nr_nfapi_information(gNB, CC_id, frame, slot, &sched_info->DL_req, &sched_info->TX_req, &sched_info->UL_dci_req); } if ((slot == 0) && (frame & 127) == 0) { @@ -172,12 +201,11 @@ void gNB_dlsch_ulsch_scheduler(module_id_t module_idP, schedule_nr_bwp_switch(module_idP, frame, slot); // This schedules MIB - schedule_nr_mib(module_idP, frame, slot); + schedule_nr_mib(module_idP, frame, slot, &sched_info->DL_req); // This schedules SIB1 if (get_softmodem_params()->sa == 1) - schedule_nr_sib1(module_idP, frame, slot); - + schedule_nr_sib1(module_idP, frame, slot, &sched_info->DL_req, &sched_info->TX_req); // This schedule PRACH if we are not in phy_test mode if (get_softmodem_params()->phy_test == 0) { @@ -194,7 +222,7 @@ void gNB_dlsch_ulsch_scheduler(module_id_t module_idP, } // Schedule CSI-RS transmission - nr_csirs_scheduling(module_idP, frame, slot, nr_slots_per_frame[*scc->ssbSubcarrierSpacing]); + nr_csirs_scheduling(module_idP, frame, slot, nr_slots_per_frame[*scc->ssbSubcarrierSpacing], &sched_info->DL_req); // Schedule CSI measurement reporting nr_csi_meas_reporting(module_idP, frame, slot); @@ -204,22 +232,29 @@ void gNB_dlsch_ulsch_scheduler(module_id_t module_idP, // This schedule RA procedure if not in phy_test mode // Otherwise consider 5G already connected if (get_softmodem_params()->phy_test == 0) { - nr_schedule_RA(module_idP, frame, slot); + nr_schedule_RA(module_idP, frame, slot, &sched_info->UL_dci_req, &sched_info->DL_req, &sched_info->TX_req); } // This schedules the DCI for Uplink and subsequently PUSCH - nr_schedule_ulsch(module_idP, frame, slot); + nr_schedule_ulsch(module_idP, frame, slot, &sched_info->UL_dci_req); // This schedules the DCI for Downlink and PDSCH start_meas(&gNB->schedule_dlsch); - nr_schedule_ue_spec(module_idP, frame, slot); + nr_schedule_ue_spec(module_idP, frame, slot, &sched_info->DL_req, &sched_info->TX_req); stop_meas(&gNB->schedule_dlsch); nr_sr_reporting(gNB, frame, slot); nr_schedule_pucch(gNB, frame, slot); + /* TODO: we copy from gNB->UL_tti_req_ahead[0][current_index], ie. CC_id == 0, + * is more than 1 CC supported? + */ + AssertFatal(MAX_NUM_CCs == 1, "only 1 CC supported\n"); + const int current_index = ul_buffer_index(frame, slot, *scc->ssbSubcarrierSpacing, gNB->UL_tti_req_ahead_size); + copy_ul_tti_req(&sched_info->UL_tti_req, &gNB->UL_tti_req_ahead[0][current_index]); + stop_meas(&gNB->eNB_scheduler); - + VCD_SIGNAL_DUMPER_DUMP_FUNCTION_BY_NAME(VCD_SIGNAL_DUMPER_FUNCTIONS_gNB_DLSCH_ULSCH_SCHEDULER,VCD_FUNCTION_OUT); } diff --git a/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_RA.c b/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_RA.c index 587575241f..d08d2fefb3 100644 --- a/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_RA.c +++ b/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_RA.c @@ -662,8 +662,13 @@ void nr_initiate_ra_proc(module_id_t module_idP, VCD_SIGNAL_DUMPER_DUMP_FUNCTION_BY_NAME(VCD_SIGNAL_DUMPER_FUNCTIONS_INITIATE_RA_PROC, 0); } -void nr_schedule_RA(module_id_t module_idP, frame_t frameP, sub_frame_t slotP) { - +void nr_schedule_RA(module_id_t module_idP, + frame_t frameP, + sub_frame_t slotP, + nfapi_nr_ul_dci_request_t *ul_dci_req, + nfapi_nr_dl_tti_request_t *DL_req, + nfapi_nr_tx_data_request_t *TX_req) +{ gNB_MAC_INST *mac = RC.nrmac[module_idP]; start_meas(&mac->schedule_ra); @@ -674,15 +679,15 @@ void nr_schedule_RA(module_id_t module_idP, frame_t frameP, sub_frame_t slotP) { LOG_D(NR_MAC, "RA[state:%d]\n", ra->state); switch (ra->state) { case Msg2: - nr_generate_Msg2(module_idP, CC_id, frameP, slotP, ra); + nr_generate_Msg2(module_idP, CC_id, frameP, slotP, ra, DL_req, TX_req); break; case Msg3_retransmission: - nr_generate_Msg3_retransmission(module_idP, CC_id, frameP, slotP, ra); + nr_generate_Msg3_retransmission(module_idP, CC_id, frameP, slotP, ra, ul_dci_req); break; case Msg3_dcch_dtch: - nr_generate_Msg3_dcch_dtch_response(module_idP, CC_id, frameP, slotP, ra); + nr_generate_Msg3_dcch_dtch_response(module_idP, CC_id, frameP, slotP, ra, DL_req, TX_req); case Msg4: - nr_generate_Msg4(module_idP, CC_id, frameP, slotP, ra); + nr_generate_Msg4(module_idP, CC_id, frameP, slotP, ra, DL_req, TX_req); break; case WAIT_Msg4_ACK: nr_check_Msg4_Ack(module_idP, CC_id, frameP, slotP, ra); @@ -695,9 +700,13 @@ void nr_schedule_RA(module_id_t module_idP, frame_t frameP, sub_frame_t slotP) { stop_meas(&mac->schedule_ra); } - -void nr_generate_Msg3_retransmission(module_id_t module_idP, int CC_id, frame_t frame, sub_frame_t slot, NR_RA_t *ra) { - +void nr_generate_Msg3_retransmission(module_id_t module_idP, + int CC_id, + frame_t frame, + sub_frame_t slot, + NR_RA_t *ra, + nfapi_nr_ul_dci_request_t *ul_dci_req) +{ gNB_MAC_INST *nr_mac = RC.nrmac[module_idP]; NR_COMMON_channels_t *cc = &nr_mac->common_channels[CC_id]; NR_ServingCellConfigCommon_t *scc = cc->ServingCellConfigCommon; @@ -784,8 +793,6 @@ void nr_generate_Msg3_retransmission(module_id_t module_idP, int CC_id, frame_t NR_ControlResourceSet_t *coreset = ra->coreset; AssertFatal(coreset!=NULL,"Coreset cannot be null for RA-Msg3 retransmission\n"); - nfapi_nr_ul_dci_request_t *ul_dci_req = &nr_mac->UL_dci_req[CC_id]; - const int coresetid = coreset->controlResourceSetId; nfapi_nr_dl_tti_pdcch_pdu_rel15_t *pdcch_pdu_rel15 = nr_mac->pdcch_pdu_idx[CC_id][coresetid]; if (!pdcch_pdu_rel15) { @@ -1148,8 +1155,14 @@ void nr_add_msg3(module_id_t module_idP, int CC_id, frame_t frameP, sub_frame_t nr_fill_rar(module_idP, ra, RAR_pdu, pusch_pdu); } -void nr_generate_Msg2(module_id_t module_idP, int CC_id, frame_t frameP, sub_frame_t slotP, NR_RA_t *ra) { - +void nr_generate_Msg2(module_id_t module_idP, + int CC_id, + frame_t frameP, + sub_frame_t slotP, + NR_RA_t *ra, + nfapi_nr_dl_tti_request_t *DL_req, + nfapi_nr_tx_data_request_t *TX_req) +{ gNB_MAC_INST *nr_mac = RC.nrmac[module_idP]; NR_COMMON_channels_t *cc = &nr_mac->common_channels[CC_id]; NR_UE_DL_BWP_t *dl_bwp = &ra->DL_BWP; @@ -1198,7 +1211,7 @@ void nr_generate_Msg2(module_id_t module_idP, int CC_id, frame_t frameP, sub_fra } // Checking if the DCI allocation is feasible in current subframe - nfapi_nr_dl_tti_request_body_t *dl_req = &nr_mac->DL_req[CC_id].dl_tti_request_body; + nfapi_nr_dl_tti_request_body_t *dl_req = &DL_req->dl_tti_request_body; if (dl_req->nPDUs > NFAPI_NR_MAX_DL_TTI_PDUS - 2) { LOG_I(NR_MAC, "[RAPROC] Subframe %d: FAPI DL structure is full, skip scheduling UE %d\n", slotP, ra->RA_rnti); return; @@ -1369,7 +1382,7 @@ void nr_generate_Msg2(module_id_t module_idP, int CC_id, frame_t frameP, sub_fra nr_mac->cset0_bwp_size); // DL TX request - nfapi_nr_pdu_t *tx_req = &nr_mac->TX_req[CC_id].pdu_list[nr_mac->TX_req[CC_id].Number_of_PDUs]; + nfapi_nr_pdu_t *tx_req = &TX_req->pdu_list[TX_req->Number_of_PDUs]; // Program UL processing for Msg3 nr_get_Msg3alloc(module_idP, CC_id, scc, slotP, frameP, ra, nr_mac->tdd_beam_association); @@ -1395,9 +1408,9 @@ void nr_generate_Msg2(module_id_t module_idP, int CC_id, frame_t frameP, sub_fra tx_req->PDU_index = pduindex; tx_req->num_TLV = 1; tx_req->TLVs[0].length = tx_req->PDU_length + 2; - nr_mac->TX_req[CC_id].SFN = frameP; - nr_mac->TX_req[CC_id].Number_of_PDUs++; - nr_mac->TX_req[CC_id].Slot = slotP; + TX_req->SFN = frameP; + TX_req->Number_of_PDUs++; + TX_req->Slot = slotP; // Mark the corresponding symbols RBs as used fill_pdcch_vrb_map(nr_mac, @@ -1600,7 +1613,13 @@ void prepare_dl_pdus(gNB_MAC_INST *nr_mac, LOG_D(NR_MAC,"numDlDci: %i\n", pdcch_pdu_rel15->numDlDci); } -void nr_generate_Msg3_dcch_dtch_response(module_id_t module_idP, int CC_id, frame_t frameP, sub_frame_t slotP, NR_RA_t *ra) +void nr_generate_Msg3_dcch_dtch_response(module_id_t module_idP, + int CC_id, + frame_t frameP, + sub_frame_t slotP, + NR_RA_t *ra, + nfapi_nr_dl_tti_request_t *DL_req, + nfapi_nr_tx_data_request_t *TX_req) { gNB_MAC_INST *nr_mac = RC.nrmac[module_idP]; @@ -1654,7 +1673,7 @@ void nr_generate_Msg3_dcch_dtch_response(module_id_t module_idP, int CC_id, fram } // Checking if the DCI allocation is feasible in current subframe - nfapi_nr_dl_tti_request_body_t *dl_req = &nr_mac->DL_req[CC_id].dl_tti_request_body; + nfapi_nr_dl_tti_request_body_t *dl_req = &DL_req->dl_tti_request_body; if (dl_req->nPDUs > NFAPI_NR_MAX_DL_TTI_PDUS - 2) { LOG_I(NR_MAC, "[RAPROC] Subframe %d: FAPI DL structure is full, skip scheduling UE %d\n", slotP, rnti); return; @@ -1734,15 +1753,15 @@ void nr_generate_Msg3_dcch_dtch_response(module_id_t module_idP, int CC_id, fram 0, time_domain_assignment, CC_id, rnti, 0, mcsIndex, tb_scaling, pduindex, rbStart, rbSize); // DL TX request - nfapi_nr_pdu_t *tx_req = &nr_mac->TX_req[CC_id].pdu_list[nr_mac->TX_req[CC_id].Number_of_PDUs]; + nfapi_nr_pdu_t *tx_req = &TX_req->pdu_list[TX_req->Number_of_PDUs]; memcpy(tx_req->TLVs[0].value.direct, buf, sizeof(uint8_t) * tb_size); tx_req->PDU_length = tb_size; tx_req->PDU_index = pduindex; tx_req->num_TLV = 1; tx_req->TLVs[0].length = tb_size + 2; - nr_mac->TX_req[CC_id].SFN = frameP; - nr_mac->TX_req[CC_id].Number_of_PDUs++; - nr_mac->TX_req[CC_id].Slot = slotP; + TX_req->SFN = frameP; + TX_req->Number_of_PDUs++; + TX_req->Slot = slotP; // Mark the corresponding symbols and RBs as used fill_pdcch_vrb_map(nr_mac, @@ -1780,7 +1799,13 @@ void nr_generate_Msg3_dcch_dtch_response(module_id_t module_idP, int CC_id, fram sched_ctrl->ul_failure = 0; } -void nr_generate_Msg4(module_id_t module_idP, int CC_id, frame_t frameP, sub_frame_t slotP, NR_RA_t *ra) +void nr_generate_Msg4(module_id_t module_idP, + int CC_id, + frame_t frameP, + sub_frame_t slotP, + NR_RA_t *ra, + nfapi_nr_dl_tti_request_t *DL_req, + nfapi_nr_tx_data_request_t *TX_req) { gNB_MAC_INST *nr_mac = RC.nrmac[module_idP]; NR_COMMON_channels_t *cc = &nr_mac->common_channels[CC_id]; @@ -1854,7 +1879,7 @@ void nr_generate_Msg4(module_id_t module_idP, int CC_id, frame_t frameP, sub_fra } // Checking if the DCI allocation is feasible in current subframe - nfapi_nr_dl_tti_request_body_t *dl_req = &nr_mac->DL_req[CC_id].dl_tti_request_body; + nfapi_nr_dl_tti_request_body_t *dl_req = &DL_req->dl_tti_request_body; if (dl_req->nPDUs > NFAPI_NR_MAX_DL_TTI_PDUS - 2) { LOG_I(NR_MAC, "[RAPROC] Subframe %d: FAPI DL structure is full, skip scheduling UE %d\n", slotP, ra->rnti); return; @@ -2006,15 +2031,15 @@ void nr_generate_Msg4(module_id_t module_idP, int CC_id, frame_t frameP, sub_fra T_INT(frameP), T_INT(slotP), T_INT(current_harq_pid), T_BUFFER(harq->transportBlock, harq->tb_size)); // DL TX request - nfapi_nr_pdu_t *tx_req = &nr_mac->TX_req[CC_id].pdu_list[nr_mac->TX_req[CC_id].Number_of_PDUs]; + nfapi_nr_pdu_t *tx_req = &TX_req->pdu_list[TX_req->Number_of_PDUs]; memcpy(tx_req->TLVs[0].value.direct, harq->transportBlock, sizeof(uint8_t) * harq->tb_size); tx_req->PDU_length = harq->tb_size; tx_req->PDU_index = pduindex; tx_req->num_TLV = 1; tx_req->TLVs[0].length = harq->tb_size + 2; - nr_mac->TX_req[CC_id].SFN = frameP; - nr_mac->TX_req[CC_id].Number_of_PDUs++; - nr_mac->TX_req[CC_id].Slot = slotP; + TX_req->SFN = frameP; + TX_req->Number_of_PDUs++; + TX_req->Slot = slotP; // Mark the corresponding symbols and RBs as used fill_pdcch_vrb_map(nr_mac, diff --git a/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_bch.c b/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_bch.c index 121ad179cc..37ceeddbee 100644 --- a/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_bch.c +++ b/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_bch.c @@ -89,10 +89,10 @@ void schedule_ssb(frame_t frame, sub_frame_t slot, } -void schedule_nr_mib(module_id_t module_idP, frame_t frameP, sub_frame_t slotP) { +void schedule_nr_mib(module_id_t module_idP, frame_t frameP, sub_frame_t slotP, nfapi_nr_dl_tti_request_t *DL_req) +{ gNB_MAC_INST *gNB = RC.nrmac[module_idP]; NR_COMMON_channels_t *cc; - nfapi_nr_dl_tti_request_t *dl_tti_request; nfapi_nr_dl_tti_request_body_t *dl_req; NR_MIB_t *mib = RC.nrrrc[module_idP]->carrier.mib.message.choice.mib; uint8_t num_tdd_period,num_ssb; @@ -103,8 +103,7 @@ void schedule_nr_mib(module_id_t module_idP, frame_t frameP, sub_frame_t slotP) cc = &gNB->common_channels[CC_id]; NR_ServingCellConfigCommon_t *scc = cc->ServingCellConfigCommon; const int slots_per_frame = nr_slots_per_frame[*scc->ssbSubcarrierSpacing]; - dl_tti_request = &gNB->DL_req[CC_id]; - dl_req = &dl_tti_request->dl_tti_request_body; + dl_req = &DL_req->dl_tti_request_body; // get MIB every 8 frames if(((slotP == 0) && (frameP & 7) == 0) || @@ -255,7 +254,6 @@ void schedule_nr_mib(module_id_t module_idP, frame_t frameP, sub_frame_t slotP) } } - void schedule_nr_SI(module_id_t module_idP, frame_t frameP, sub_frame_t subframeP) { //---------------------------------------- } @@ -520,8 +518,12 @@ void nr_fill_nfapi_dl_sib1_pdu(int Mod_idP, } -void schedule_nr_sib1(module_id_t module_idP, frame_t frameP, sub_frame_t slotP) { - +void schedule_nr_sib1(module_id_t module_idP, + frame_t frameP, + sub_frame_t slotP, + nfapi_nr_dl_tti_request_t *DL_req, + nfapi_nr_tx_data_request_t *TX_req) +{ // TODO: Get these values from RRC const int CC_id = 0; uint8_t candidate_idx = 0; @@ -586,12 +588,12 @@ void schedule_nr_sib1(module_id_t module_idP, frame_t frameP, sub_frame_t slotP) candidate_idx, sib1_sdu_length); - nfapi_nr_dl_tti_request_body_t *dl_req = &gNB_mac->DL_req[CC_id].dl_tti_request_body; + 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_sib1_pdu(module_idP, dl_req, pdu_index, type0_PDCCH_CSS_config, TBS, tda_info.startSymbolIndex, tda_info.nrOfSymbols); - const int ntx_req = gNB_mac->TX_req[CC_id].Number_of_PDUs; - nfapi_nr_pdu_t *tx_req = &gNB_mac->TX_req[CC_id].pdu_list[ntx_req]; + 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, sib1_payload, TBS); @@ -600,9 +602,9 @@ void schedule_nr_sib1(module_id_t module_idP, frame_t frameP, sub_frame_t slotP) tx_req->PDU_index = pdu_index; tx_req->num_TLV = 1; tx_req->TLVs[0].length = TBS + 2; - gNB_mac->TX_req[CC_id].Number_of_PDUs++; - gNB_mac->TX_req[CC_id].SFN = frameP; - gNB_mac->TX_req[CC_id].Slot = slotP; + TX_req->Number_of_PDUs++; + TX_req->SFN = frameP; + TX_req->Slot = slotP; type0_PDCCH_CSS_config->active = false; } diff --git a/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_dlsch.c b/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_dlsch.c index 7d3cd511e9..e6f32b1405 100644 --- a/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_dlsch.c +++ b/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_dlsch.c @@ -873,8 +873,10 @@ nr_pp_impl_dl nr_init_fr1_dlsch_preprocessor(int CC_id) { void nr_schedule_ue_spec(module_id_t module_id, frame_t frame, - sub_frame_t slot) { - + sub_frame_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_id]; if (!is_xlsch_in_slot(gNB_mac->dlsch_slot_bitmap[slot / 64], slot)) @@ -885,7 +887,7 @@ void nr_schedule_ue_spec(module_id_t module_id, const int CC_id = 0; NR_ServingCellConfigCommon_t *scc = gNB_mac->common_channels[CC_id].ServingCellConfigCommon; NR_UEs_t *UE_info = &gNB_mac->UE_info; - nfapi_nr_dl_tti_request_body_t *dl_req = &gNB_mac->DL_req[CC_id].dl_tti_request_body; + nfapi_nr_dl_tti_request_body_t *dl_req = &DL_req->dl_tti_request_body; UE_iterator(UE_info->list, UE) { NR_UE_sched_ctrl_t *sched_ctrl = &UE->UE_sched_ctrl; @@ -1326,16 +1328,16 @@ void nr_schedule_ue_spec(module_id_t module_id, T_INT(frame), T_INT(slot), T_INT(current_harq_pid), T_BUFFER(harq->transportBlock, TBS)); } - const int ntx_req = gNB_mac->TX_req[CC_id].Number_of_PDUs; - nfapi_nr_pdu_t *tx_req = &gNB_mac->TX_req[CC_id].pdu_list[ntx_req]; + const int ntx_req = TX_req->Number_of_PDUs; + nfapi_nr_pdu_t *tx_req = &TX_req->pdu_list[ntx_req]; tx_req->PDU_length = TBS; tx_req->PDU_index = pduindex; tx_req->num_TLV = 1; tx_req->TLVs[0].length = TBS + 2; memcpy(tx_req->TLVs[0].value.direct, harq->transportBlock, TBS); - gNB_mac->TX_req[CC_id].Number_of_PDUs++; - gNB_mac->TX_req[CC_id].SFN = frame; - gNB_mac->TX_req[CC_id].Slot = slot; + TX_req->Number_of_PDUs++; + TX_req->SFN = frame; + TX_req->Slot = slot; /* mark UE as scheduled */ sched_pdsch->rbSize = 0; } diff --git a/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_primitives.c b/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_primitives.c index 09d4d95458..99ce0ab235 100644 --- a/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_primitives.c +++ b/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_primitives.c @@ -2538,11 +2538,8 @@ int get_pdsch_to_harq_feedback(NR_PUCCH_Config_t *pucch_Config, } } -void nr_csirs_scheduling(int Mod_idP, - frame_t frame, - sub_frame_t slot, - int n_slots_frame){ - +void nr_csirs_scheduling(int Mod_idP, frame_t frame, sub_frame_t slot, int n_slots_frame, nfapi_nr_dl_tti_request_t *DL_req) +{ int CC_id = 0; NR_UEs_t *UE_info = &RC.nrmac[Mod_idP]->UE_info; gNB_MAC_INST *gNB_mac = RC.nrmac[Mod_idP]; @@ -2581,7 +2578,7 @@ void nr_csirs_scheduling(int Mod_idP, NR_NZP_CSI_RS_Resource_t *nzpcsi; int period, offset; - nfapi_nr_dl_tti_request_body_t *dl_req = &gNB_mac->DL_req[CC_id].dl_tti_request_body; + nfapi_nr_dl_tti_request_body_t *dl_req = &DL_req->dl_tti_request_body; for (int id = 0; id < csi_measconfig->nzp_CSI_RS_ResourceToAddModList->list.count; id++){ nzpcsi = csi_measconfig->nzp_CSI_RS_ResourceToAddModList->list.array[id]; diff --git a/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_ulsch.c b/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_ulsch.c index 09aad3d9cb..3c314c6c86 100644 --- a/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_ulsch.c +++ b/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_ulsch.c @@ -1997,7 +1997,7 @@ nr_pp_impl_ul nr_init_fr1_ulsch_preprocessor(int CC_id) return nr_fr1_ulsch_preprocessor; } -void nr_schedule_ulsch(module_id_t module_id, frame_t frame, sub_frame_t slot) +void nr_schedule_ulsch(module_id_t module_id, frame_t frame, sub_frame_t slot, nfapi_nr_ul_dci_request_t *ul_dci_req) { gNB_MAC_INST *nr_mac = RC.nrmac[module_id]; /* Uplink data ONLY can be scheduled when the current slot is downlink slot, @@ -2010,8 +2010,6 @@ void nr_schedule_ulsch(module_id_t module_id, frame_t frame, sub_frame_t slot) if (!do_sched) return; - const int CC_id = 0; - nfapi_nr_ul_dci_request_t *ul_dci_req = &nr_mac->UL_dci_req[CC_id]; ul_dci_req->SFN = frame; ul_dci_req->Slot = slot; /* a PDCCH PDU groups DCIs per BWP and CORESET. Save a pointer to each diff --git a/openair2/LAYER2/NR_MAC_gNB/mac_proto.h b/openair2/LAYER2/NR_MAC_gNB/mac_proto.h index b62984943c..a700c5c652 100644 --- a/openair2/LAYER2/NR_MAC_gNB/mac_proto.h +++ b/openair2/LAYER2/NR_MAC_gNB/mac_proto.h @@ -60,17 +60,19 @@ bool nr_mac_prepare_ra_nsa_ue(gNB_MAC_INST *nrmac, uint32_t rnti, NR_CellGroupCo bool nr_mac_update_cellgroup(gNB_MAC_INST *nrmac, uint32_t rnti, NR_CellGroupConfig_t *CellGroup); bool nr_mac_update_RA(gNB_MAC_INST *nrmac, uint32_t rnti, NR_CellGroupConfig_t *CellGroup); -void clear_nr_nfapi_information(gNB_MAC_INST * gNB, +void clear_nr_nfapi_information(gNB_MAC_INST *gNB, int CC_idP, - frame_t frameP, - sub_frame_t subframeP); + frame_t frameP, + sub_frame_t slotP, + nfapi_nr_dl_tti_request_t *DL_req, + nfapi_nr_tx_data_request_t *TX_req, + nfapi_nr_ul_dci_request_t *UL_dci_req); void nr_mac_update_timers(module_id_t module_id, frame_t frame, sub_frame_t slot); -void gNB_dlsch_ulsch_scheduler(module_id_t module_idP, - frame_t frame_rxP, sub_frame_t slot_rxP); +void gNB_dlsch_ulsch_scheduler(module_id_t module_idP, frame_t frame_rxP, sub_frame_t slot_rxP, NR_Sched_Rsp_t *sched_info); void schedule_nr_bwp_switch(module_id_t module_id, frame_t frame, @@ -81,7 +83,9 @@ void schedule_nr_bwp_switch(module_id_t module_id, * messages, statistics, HARQ handling, CEs, ... */ void nr_schedule_ue_spec(module_id_t module_id, frame_t frame, - sub_frame_t slot); + sub_frame_t slot, + nfapi_nr_dl_tti_request_t *DL_req, + nfapi_nr_tx_data_request_t *TX_req); uint32_t schedule_control_sib1(module_id_t module_id, int CC_id, @@ -95,21 +99,30 @@ uint32_t schedule_control_sib1(module_id_t module_id, /* \brief default FR1 DL preprocessor init routine, returns preprocessor to call */ nr_pp_impl_dl nr_init_fr1_dlsch_preprocessor(int CC_id); -void schedule_nr_sib1(module_id_t module_idP, frame_t frameP, sub_frame_t subframeP); +void schedule_nr_sib1(module_id_t module_idP, + frame_t frameP, + sub_frame_t slotP, + nfapi_nr_dl_tti_request_t *DL_req, + nfapi_nr_tx_data_request_t *TX_req); -void schedule_nr_mib(module_id_t module_idP, frame_t frameP, sub_frame_t slotP); +void schedule_nr_mib(module_id_t module_idP, frame_t frameP, sub_frame_t slotP, nfapi_nr_dl_tti_request_t *DL_req); /* \brief main UL scheduler function. Calls a preprocessor to decide on * resource allocation, then "post-processes" resource allocation (nFAPI * messages, statistics, HARQ handling, ... */ -void nr_schedule_ulsch(module_id_t module_id, frame_t frame, sub_frame_t slot); +void nr_schedule_ulsch(module_id_t module_id, frame_t frame, sub_frame_t slot, nfapi_nr_ul_dci_request_t *ul_dci_req); /* \brief default FR1 UL preprocessor init routine, returns preprocessor to call */ nr_pp_impl_ul nr_init_fr1_ulsch_preprocessor(int CC_id); /////// Random Access MAC-PHY interface functions and primitives /////// -void nr_schedule_RA(module_id_t module_idP, frame_t frameP, sub_frame_t slotP); +void nr_schedule_RA(module_id_t module_idP, + frame_t frameP, + sub_frame_t slotP, + nfapi_nr_ul_dci_request_t *ul_dci_req, + nfapi_nr_dl_tti_request_t *DL_req, + nfapi_nr_tx_data_request_t *TX_req); /* \brief Function to indicate a received preamble on PRACH. It initiates the RA procedure. @param module_idP Instance ID of gNB @@ -134,7 +147,12 @@ void nr_get_Msg3alloc(module_id_t module_id, NR_RA_t *ra, int16_t *tdd_beam_association); -void nr_generate_Msg3_retransmission(module_id_t module_idP, int CC_id, frame_t frameP, sub_frame_t slotP, NR_RA_t *ra); +void nr_generate_Msg3_retransmission(module_id_t module_idP, + int CC_id, + frame_t frameP, + sub_frame_t slotP, + NR_RA_t *ra, + nfapi_nr_ul_dci_request_t *ul_dci_req); /* \brief Function in gNB to fill RAR pdu when requested by PHY. @param ra Instance of RA resources of gNB @@ -201,10 +219,7 @@ void nr_srs_ri_computation(const nfapi_nr_srs_normalized_channel_iq_matrix_t *nr void nr_schedule_srs(int module_id, frame_t frame, int slot); -void nr_csirs_scheduling(int Mod_idP, - frame_t frame, - sub_frame_t slot, - int n_slots_frame); +void nr_csirs_scheduling(int Mod_idP, frame_t frame, sub_frame_t slot, int n_slots_frame, nfapi_nr_dl_tti_request_t *DL_req); void nr_csi_meas_reporting(int Mod_idP, frame_t frameP, @@ -365,13 +380,31 @@ int nr_write_ce_dlsch_pdu(module_id_t module_idP, unsigned char drx_cmd, unsigned char *ue_cont_res_id); -void nr_generate_Msg2(module_id_t module_idP, int CC_id, frame_t frameP, sub_frame_t slotP, NR_RA_t *ra); +void nr_generate_Msg2(module_id_t module_idP, + int CC_id, + frame_t frameP, + sub_frame_t slotP, + NR_RA_t *ra, + nfapi_nr_dl_tti_request_t *dl_req, + nfapi_nr_tx_data_request_t *TX_req); -void nr_generate_Msg4(module_id_t module_idP, int CC_id, frame_t frameP, sub_frame_t slotP, NR_RA_t *ra); +void nr_generate_Msg4(module_id_t module_idP, + int CC_id, + frame_t frameP, + sub_frame_t slotP, + NR_RA_t *ra, + nfapi_nr_dl_tti_request_t *DL_req, + nfapi_nr_tx_data_request_t *TX_req); void nr_check_Msg4_Ack(module_id_t module_id, int CC_id, frame_t frame, sub_frame_t slot, NR_RA_t *ra); -void nr_generate_Msg3_dcch_dtch_response(module_id_t module_idP, int CC_id, frame_t frameP, sub_frame_t slotP, NR_RA_t *ra); +void nr_generate_Msg3_dcch_dtch_response(module_id_t module_idP, + int CC_id, + frame_t frameP, + sub_frame_t slotP, + NR_RA_t *ra, + nfapi_nr_dl_tti_request_t *DL_req, + nfapi_nr_tx_data_request_t *TX_req); int binomial(int n, int k); diff --git a/openair2/LAYER2/NR_MAC_gNB/nr_mac_gNB.h b/openair2/LAYER2/NR_MAC_gNB/nr_mac_gNB.h index 96734aa8b1..1cd0f89736 100644 --- a/openair2/LAYER2/NR_MAC_gNB/nr_mac_gNB.h +++ b/openair2/LAYER2/NR_MAC_gNB/nr_mac_gNB.h @@ -719,24 +719,15 @@ typedef struct gNB_MAC_INST_s { uint16_t ulprbbl[MAX_BWP_SIZE]; /// NFAPI Config Request Structure nfapi_nr_config_request_scf_t config[NFAPI_CC_MAX]; - /// NFAPI DL Config Request Structure - nfapi_nr_dl_tti_request_t DL_req[NFAPI_CC_MAX]; /// a PDCCH PDU groups DCIs per BWP and CORESET. The following structure /// keeps pointers to PDCCH PDUs within DL_req so that we can easily track /// PDCCH PDUs per CC/BWP/CORESET nfapi_nr_dl_tti_pdcch_pdu_rel15_t *pdcch_pdu_idx[NFAPI_CC_MAX][MAX_NUM_CORESET]; - /// NFAPI UL TTI Request Structure, simple pointer into structure - /// UL_tti_req_ahead for current frame/slot - nfapi_nr_ul_tti_request_t *UL_tti_req[NFAPI_CC_MAX]; /// NFAPI UL TTI Request Structure for future TTIs, dynamically allocated /// because length depends on number of slots nfapi_nr_ul_tti_request_t *UL_tti_req_ahead[NFAPI_CC_MAX]; int UL_tti_req_ahead_size; int vrb_map_UL_size; - /// NFAPI HI/DCI0 Config Request Structure - nfapi_nr_ul_dci_request_t UL_dci_req[NFAPI_CC_MAX]; - /// NFAPI DL PDU structure - nfapi_nr_tx_data_request_t TX_req[NFAPI_CC_MAX]; NR_UEs_t UE_info; diff --git a/openair2/NR_PHY_INTERFACE/NR_IF_Module.c b/openair2/NR_PHY_INTERFACE/NR_IF_Module.c index 60db353949..117b028c9a 100644 --- a/openair2/NR_PHY_INTERFACE/NR_IF_Module.c +++ b/openair2/NR_PHY_INTERFACE/NR_IF_Module.c @@ -39,12 +39,12 @@ #include "nfapi/oai_integration/vendor_ext.h" #include "nfapi/oai_integration/gnb_ind_vars.h" #include "openair2/PHY_INTERFACE/queue_t.h" +#include "openair2/NR_PHY_INTERFACE/nr_sched_response.h" #define MAX_IF_MODULES 100 //#define UL_HARQ_PRINT static NR_IF_Module_t *nr_if_inst[MAX_IF_MODULES]; -static NR_Sched_Rsp_t NR_Sched_INFO[MAX_IF_MODULES][MAX_NUM_CCs]; extern int oai_nfapi_harq_indication(nfapi_harq_indication_t *harq_ind); extern int oai_nfapi_crc_indication(nfapi_crc_indication_t *crc_ind); extern int oai_nfapi_cqi_indication(nfapi_cqi_indication_t *cqi_ind); @@ -389,7 +389,7 @@ void NR_UL_indication(NR_UL_IND_t *UL_info) { AssertFatal(UL_info!=NULL,"UL_info is null\n"); module_id_t module_id = UL_info->module_id; int CC_id = UL_info->CC_id; - NR_Sched_Rsp_t *sched_info = &NR_Sched_INFO[module_id][CC_id]; + NR_Sched_Rsp_t *sched_info; NR_IF_Module_t *ifi = nr_if_inst[module_id]; LOG_D(NR_PHY,"SFN/SLOT:%d.%d module_id:%d CC_id:%d UL_info[rach_pdus:%zu rx_ind:%zu crcs:%zu]\n", @@ -451,8 +451,6 @@ void NR_UL_indication(NR_UL_IND_t *UL_info) { } if (NFAPI_MODE != NFAPI_MODE_PNF) { gNB_MAC_INST *mac = RC.nrmac[module_id]; - // clear UL DCI prior to handling ULSCH - mac->UL_dci_req[CC_id].numPdus = 0; if (ifi->CC_mask==0) { ifi->current_frame = UL_info->frame; ifi->current_slot = UL_info->slot; @@ -471,21 +469,20 @@ void NR_UL_indication(NR_UL_IND_t *UL_info) { */ nfapi_nr_config_request_scf_t *cfg = &mac->config[CC_id]; int spf = get_spf(cfg); + sched_info = allocate_sched_response(); + // clear UL DCI prior to handling ULSCH + sched_info->UL_dci_req.numPdus = 0; gNB_dlsch_ulsch_scheduler(module_id, - (UL_info->frame+((UL_info->slot>(spf-1-ifi->sl_ahead))?1:0)) % 1024, - (UL_info->slot+ifi->sl_ahead)%spf); + (UL_info->frame + ((UL_info->slot > (spf - 1 - ifi->sl_ahead)) ? 1 : 0)) % 1024, + (UL_info->slot + ifi->sl_ahead) % spf, + sched_info); ifi->CC_mask = 0; sched_info->module_id = module_id; sched_info->CC_id = CC_id; sched_info->frame = (UL_info->frame + ((UL_info->slot>(spf-1-ifi->sl_ahead)) ? 1 : 0)) % 1024; sched_info->slot = (UL_info->slot+ifi->sl_ahead)%spf; - sched_info->DL_req = &mac->DL_req[CC_id]; - sched_info->UL_dci_req = &mac->UL_dci_req[CC_id]; - sched_info->UL_tti_req = mac->UL_tti_req[CC_id]; - - sched_info->TX_req = &mac->TX_req[CC_id]; #ifdef DUMP_FAPI dump_dl(sched_info); #endif @@ -496,10 +493,11 @@ void NR_UL_indication(NR_UL_IND_t *UL_info) { CC_id); ifi->NR_Schedule_response(sched_info); - LOG_D(NR_PHY,"NR_Schedule_response: SFN SLOT:%d %d dl_pdus:%d\n", - sched_info->frame, - sched_info->slot, - sched_info->DL_req->dl_tti_request_body.nPDUs); + LOG_D(NR_PHY, + "NR_Schedule_response: SFN SLOT:%d %d dl_pdus:%d\n", + sched_info->frame, + sched_info->slot, + sched_info->DL_req.dl_tti_request_body.nPDUs); } } } @@ -520,5 +518,7 @@ NR_IF_Module_t *NR_IF_Module_init(int Mod_id) { "allocation of nr_if_inst[%d]->if_mutex fails\n",Mod_id); } + init_sched_response(); + return nr_if_inst[Mod_id]; } diff --git a/openair2/NR_PHY_INTERFACE/NR_IF_Module.h b/openair2/NR_PHY_INTERFACE/NR_IF_Module.h index 47e961da2b..f85d44398f 100644 --- a/openair2/NR_PHY_INTERFACE/NR_IF_Module.h +++ b/openair2/NR_PHY_INTERFACE/NR_IF_Module.h @@ -83,6 +83,8 @@ typedef struct { typedef struct { + /// the ID of this sched_response - used by sched_reponse memory management + int sched_response_id; /// Module ID module_id_t module_id; /// CC ID @@ -92,13 +94,13 @@ typedef struct { /// slot slot_t slot; /// nFAPI DL Config Request - nfapi_nr_dl_tti_request_t *DL_req; + nfapi_nr_dl_tti_request_t DL_req; /// nFAPI UL Config Request - nfapi_nr_ul_tti_request_t *UL_tti_req; + nfapi_nr_ul_tti_request_t UL_tti_req; /// nFAPI UL_DCI Request - nfapi_nr_ul_dci_request_t *UL_dci_req; + nfapi_nr_ul_dci_request_t UL_dci_req; /// Pointers to DL SDUs - nfapi_nr_tx_data_request_t *TX_req; + nfapi_nr_tx_data_request_t TX_req; } NR_Sched_Rsp_t; typedef struct { diff --git a/openair2/NR_PHY_INTERFACE/nr_sched_response.c b/openair2/NR_PHY_INTERFACE/nr_sched_response.c new file mode 100644 index 0000000000..f0aa25781c --- /dev/null +++ b/openair2/NR_PHY_INTERFACE/nr_sched_response.c @@ -0,0 +1,153 @@ +/* + * 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 + */ + +/* sched response memory management */ + +/* A system with reference counting is implemented. + * It is needed because several threads will access the structure, + * which has to be released only when all the threads are done with it. + * So the main thread allocates a sched_response with allocate_sched_response() + * which allocates a sched_response with a reference counter == 1 + * and then for each other thread that will access sched_response, a + * call to inc_ref_sched_response() is done. When a thread has finished + * using the sched_response, it calls deref_sched_response() which will + * in turn call release_sched_response() when the reference counter becomes 0. + * + * The several threads in question are accessing the _same_ sched_response, + * it has not to be confused with the various TX processes that may run in + * parallel and which are accessing _different_ sched_response (the maximum + * number of parallel processes is N_RESP). + */ + +#include "nr_sched_response.h" + +#include +#include + +#include "common/utils/LOG/log.h" +#include "common/utils/assertions.h" + +#define N_RESP 3 +static NR_Sched_Rsp_t resp[N_RESP]; +static int resp_refcount[N_RESP]; +static int resp_freelist_next[N_RESP]; +static int resp_freelist_head; +static pthread_mutex_t resp_mutex = PTHREAD_MUTEX_INITIALIZER; +static bool resp_freelist_inited = false; + +void init_sched_response(void) +{ + /* init only once */ + if (pthread_mutex_lock(&resp_mutex)) + AssertFatal(0, "pthread_mutex_lock failed\n"); + if (resp_freelist_inited) { + if (pthread_mutex_unlock(&resp_mutex)) + AssertFatal(0, "pthread_mutex_unlock failed\n"); + return; + } + resp_freelist_inited = true; + if (pthread_mutex_unlock(&resp_mutex)) + AssertFatal(0, "pthread_mutex_unlock failed\n"); + + int i; + for (i = 0; i < N_RESP - 1; i++) + resp_freelist_next[i] = i + 1; + resp_freelist_next[N_RESP - 1] = -1; + resp_freelist_head = 0; +} + +NR_Sched_Rsp_t *allocate_sched_response(void) +{ + NR_Sched_Rsp_t *ret; + int new_head; + + if (pthread_mutex_lock(&resp_mutex)) + AssertFatal(0, "pthread_mutex_lock failed\n"); + + AssertFatal(resp_freelist_inited, "sched_response used before init\n"); + + if (resp_freelist_head == -1) { + LOG_E(PHY, "fatal: sched_response cannot be allocated, increase N_RESP\n"); + exit(1); + } + + ret = &resp[resp_freelist_head]; + ret->sched_response_id = resp_freelist_head; + resp_refcount[resp_freelist_head] = 1; + + new_head = resp_freelist_next[resp_freelist_head]; + resp_freelist_next[resp_freelist_head] = -1; + resp_freelist_head = new_head; + + if (pthread_mutex_unlock(&resp_mutex)) + AssertFatal(0, "pthread_mutex_unlock failed\n"); + + return ret; +} + +static void release_sched_response(int sched_response_id) +{ + resp_freelist_next[sched_response_id] = resp_freelist_head; + resp_freelist_head = sched_response_id; +} + +void deref_sched_response(int sched_response_id) +{ + /* simulators (ulsim/dlsim) deal with their own sched_response but call + * functions that call this one, let's handle this case with a special + * value -1 where we do nothing (yes it's a hack) + */ + if (sched_response_id == -1) + return; + + if (pthread_mutex_lock(&resp_mutex)) + AssertFatal(0, "pthread_mutex_lock failed\n"); + + AssertFatal(resp_freelist_inited, "sched_response used before init\n"); + AssertFatal(resp_refcount[sched_response_id] > 0, "sched_reponse decreased too much\n"); + + resp_refcount[sched_response_id]--; + if (resp_refcount[sched_response_id] == 0) + release_sched_response(sched_response_id); + + if (pthread_mutex_unlock(&resp_mutex)) + AssertFatal(0, "pthread_mutex_unlock failed\n"); +} + +void inc_ref_sched_response(int sched_response_id) +{ + /* simulators (ulsim/dlsim) deal with their own sched_response but call + * functions that call this one, let's handle this case with a special + * value -1 where we do nothing (yes it's a hack) + */ + if (sched_response_id == -1) + return; + + if (pthread_mutex_lock(&resp_mutex)) + AssertFatal(0, "pthread_mutex_lock failed\n"); + + AssertFatal(resp_freelist_inited, "sched_response used before init\n"); + + resp_refcount[sched_response_id]++; + + if (pthread_mutex_unlock(&resp_mutex)) + AssertFatal(0, "pthread_mutex_unlock failed\n"); +} diff --git a/openair2/NR_PHY_INTERFACE/nr_sched_response.h b/openair2/NR_PHY_INTERFACE/nr_sched_response.h new file mode 100644 index 0000000000..33a0af93d0 --- /dev/null +++ b/openair2/NR_PHY_INTERFACE/nr_sched_response.h @@ -0,0 +1,32 @@ +/* + * 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 OPENAIR2_NR_PHY_INTERFACE_NR_SCHED_RESPONSE_H +#define OPENAIR2_NR_PHY_INTERFACE_NR_SCHED_RESPONSE_H + +#include "NR_IF_Module.h" + +void init_sched_response(void); +NR_Sched_Rsp_t *allocate_sched_response(void); +void deref_sched_response(int sched_response_id); +void inc_ref_sched_response(int sched_response_id); + +#endif /* OPENAIR2_NR_PHY_INTERFACE_NR_SCHED_RESPONSE_H */ From 1679cf38ba30794248b648f7657940a674cdfac6 Mon Sep 17 00:00:00 2001 From: Angelo Athanassopoulos Date: Wed, 5 Apr 2023 22:26:08 +0200 Subject: [PATCH 88/89] NR RRC - RRCReestablishment - Reviewer comments Changes 1. Passing function argument by reference. 2. Use a pointer for ue->established_drbs[drb_id - 1] in generateDRB. --- openair2/RRC/NR/rrc_gNB.c | 4 +- openair2/RRC/NR/rrc_gNB_radio_bearers.c | 96 ++++++++++++------------- openair2/RRC/NR/rrc_gNB_radio_bearers.h | 2 +- 3 files changed, 51 insertions(+), 51 deletions(-) diff --git a/openair2/RRC/NR/rrc_gNB.c b/openair2/RRC/NR/rrc_gNB.c index ec5ded4989..f9447fc57a 100644 --- a/openair2/RRC/NR/rrc_gNB.c +++ b/openair2/RRC/NR/rrc_gNB.c @@ -644,7 +644,7 @@ void fill_DRB_configList(const protocol_ctxt_t *const ctxt_pP, nb_drb_to_setup--; } else { generateDRB(ue_p, drb_id, &ue_p->pduSession[i], rrc->configuration.enable_sdap, rrc->security.do_drb_integrity, rrc->security.do_drb_ciphering); - NR_DRB_ToAddMod_t *DRB_config = generateDRB_ASN1(ue_p->established_drbs[drb_id-1]); + NR_DRB_ToAddMod_t *DRB_config = generateDRB_ASN1(&ue_p->established_drbs[drb_id-1]); if (drb_id_to_setup_start == 0) drb_id_to_setup_start = DRB_config->drb_Identity; asn1cSeqAdd(&ue_p->DRB_configList->list, DRB_config); @@ -1374,7 +1374,7 @@ void RRCReestablishmentComplete_fill_DRB_configList(const protocol_ctxt_t *const for (int i = 0; i < NGAP_MAX_DRBS_PER_UE; i++) { if (ue_p->established_drbs[i].status != DRB_INACTIVE) { ue_p->established_drbs[i].reestablishPDCP = NR_DRB_ToAddMod__reestablishPDCP_true; - DRB_config = generateDRB_ASN1(ue_p->established_drbs[i]); + DRB_config = generateDRB_ASN1(&ue_p->established_drbs[i]); asn1cCallocOne(DRB_config->reestablishPDCP, NR_DRB_ToAddMod__reestablishPDCP_true); asn1cSeqAdd(&(*DRB_configList2)->list, DRB_config); } diff --git a/openair2/RRC/NR/rrc_gNB_radio_bearers.c b/openair2/RRC/NR/rrc_gNB_radio_bearers.c index e9fa7922fb..b4d6c55762 100644 --- a/openair2/RRC/NR/rrc_gNB_radio_bearers.c +++ b/openair2/RRC/NR/rrc_gNB_radio_bearers.c @@ -45,56 +45,56 @@ void generateDRB(gNB_RRC_UE_t *ue, { int i; int qos_flow_index; - if (ue->established_drbs[drb_id - 1].status == DRB_INACTIVE) { + drb_t *est_drb = &ue->established_drbs[drb_id - 1]; + if (est_drb->status == DRB_INACTIVE) { /* DRB Management */ - ue->established_drbs[drb_id - 1].drb_id = drb_id; - ue->established_drbs[drb_id - 1].reestablishPDCP = -1; - ue->established_drbs[drb_id - 1].recoverPDCP = -1; + est_drb->drb_id = drb_id; + est_drb->reestablishPDCP = -1; + est_drb->recoverPDCP = -1; for (i = 0; i < NGAP_MAX_DRBS_PER_UE; i++) { - if ((ue->established_drbs[drb_id - 1].cnAssociation.sdap_config.pdusession_id == 0 - || ue->established_drbs[drb_id - 1].cnAssociation.sdap_config.pdusession_id == pduSession->param.pdusession_id) - && ue->established_drbs[drb_id - 1].defaultDRBid == 0) { - ue->established_drbs[drb_id - 1].cnAssociation.sdap_config.defaultDRB = true; - ue->established_drbs[drb_id - 1].defaultDRBid = drb_id; + if ((est_drb->cnAssociation.sdap_config.pdusession_id == 0 + || est_drb->cnAssociation.sdap_config.pdusession_id == pduSession->param.pdusession_id) + && est_drb->defaultDRBid == 0) { + est_drb->cnAssociation.sdap_config.defaultDRB = true; + est_drb->defaultDRBid = drb_id; } } /* SDAP Configuration */ - ue->established_drbs[drb_id - 1].cnAssociation.present = NR_DRB_ToAddMod__cnAssociation_PR_sdap_Config; - ue->established_drbs[drb_id - 1].cnAssociation.sdap_config.pdusession_id = pduSession->param.pdusession_id; + est_drb->cnAssociation.present = NR_DRB_ToAddMod__cnAssociation_PR_sdap_Config; + est_drb->cnAssociation.sdap_config.pdusession_id = pduSession->param.pdusession_id; if (enable_sdap) { - ue->established_drbs[drb_id - 1].cnAssociation.sdap_config.sdap_HeaderDL = NR_SDAP_Config__sdap_HeaderDL_present; - ue->established_drbs[drb_id - 1].cnAssociation.sdap_config.sdap_HeaderUL = NR_SDAP_Config__sdap_HeaderUL_present; + est_drb->cnAssociation.sdap_config.sdap_HeaderDL = NR_SDAP_Config__sdap_HeaderDL_present; + est_drb->cnAssociation.sdap_config.sdap_HeaderUL = NR_SDAP_Config__sdap_HeaderUL_present; } else { - ue->established_drbs[drb_id - 1].cnAssociation.sdap_config.sdap_HeaderDL = NR_SDAP_Config__sdap_HeaderDL_absent; - ue->established_drbs[drb_id - 1].cnAssociation.sdap_config.sdap_HeaderUL = NR_SDAP_Config__sdap_HeaderUL_absent; + est_drb->cnAssociation.sdap_config.sdap_HeaderDL = NR_SDAP_Config__sdap_HeaderDL_absent; + est_drb->cnAssociation.sdap_config.sdap_HeaderUL = NR_SDAP_Config__sdap_HeaderUL_absent; } for (qos_flow_index = 0; qos_flow_index < pduSession->param.nb_qos; qos_flow_index++) { - ue->established_drbs[drb_id - 1].cnAssociation.sdap_config.mappedQoS_FlowsToAdd[qos_flow_index] = - pduSession->param.qos[qos_flow_index].qfi; + est_drb->cnAssociation.sdap_config.mappedQoS_FlowsToAdd[qos_flow_index] = pduSession->param.qos[qos_flow_index].qfi; if (pduSession->param.qos[qos_flow_index].fiveQI > 5) - ue->established_drbs[drb_id - 1].status = DRB_ACTIVE_NONGBR; + est_drb->status = DRB_ACTIVE_NONGBR; else - ue->established_drbs[drb_id - 1].status = DRB_ACTIVE; + est_drb->status = DRB_ACTIVE; } /* PDCP Configuration */ - ue->established_drbs[drb_id - 1].pdcp_config.discardTimer = NR_PDCP_Config__drb__discardTimer_infinity; - ue->established_drbs[drb_id - 1].pdcp_config.pdcp_SN_SizeDL = NR_PDCP_Config__drb__pdcp_SN_SizeDL_len18bits; - ue->established_drbs[drb_id - 1].pdcp_config.pdcp_SN_SizeUL = NR_PDCP_Config__drb__pdcp_SN_SizeUL_len18bits; - ue->established_drbs[drb_id - 1].pdcp_config.t_Reordering = NR_PDCP_Config__t_Reordering_ms100; - ue->established_drbs[drb_id - 1].pdcp_config.headerCompression.present = NR_PDCP_Config__drb__headerCompression_PR_notUsed; - ue->established_drbs[drb_id - 1].pdcp_config.headerCompression.NotUsed = 0; + est_drb->pdcp_config.discardTimer = NR_PDCP_Config__drb__discardTimer_infinity; + est_drb->pdcp_config.pdcp_SN_SizeDL = NR_PDCP_Config__drb__pdcp_SN_SizeDL_len18bits; + est_drb->pdcp_config.pdcp_SN_SizeUL = NR_PDCP_Config__drb__pdcp_SN_SizeUL_len18bits; + est_drb->pdcp_config.t_Reordering = NR_PDCP_Config__t_Reordering_ms100; + est_drb->pdcp_config.headerCompression.present = NR_PDCP_Config__drb__headerCompression_PR_notUsed; + est_drb->pdcp_config.headerCompression.NotUsed = 0; if (do_drb_integrity) - ue->established_drbs[drb_id - 1].pdcp_config.integrityProtection = NR_PDCP_Config__drb__integrityProtection_enabled; + est_drb->pdcp_config.integrityProtection = NR_PDCP_Config__drb__integrityProtection_enabled; else - ue->established_drbs[drb_id - 1].pdcp_config.integrityProtection = 1; + est_drb->pdcp_config.integrityProtection = 1; if (do_drb_ciphering) - ue->established_drbs[drb_id - 1].pdcp_config.ext1.cipheringDisabled = 1; + est_drb->pdcp_config.ext1.cipheringDisabled = 1; else - ue->established_drbs[drb_id - 1].pdcp_config.ext1.cipheringDisabled = NR_PDCP_Config__ext1__cipheringDisabled_true; + est_drb->pdcp_config.ext1.cipheringDisabled = NR_PDCP_Config__ext1__cipheringDisabled_true; } } -NR_DRB_ToAddMod_t *generateDRB_ASN1(drb_t drb_asn1) +NR_DRB_ToAddMod_t *generateDRB_ASN1(const drb_t *drb_asn1) { NR_DRB_ToAddMod_t *DRB_config = CALLOC(1, sizeof(*DRB_config)); NR_SDAP_Config_t *SDAP_config = CALLOC(1, sizeof(NR_SDAP_Config_t)); @@ -104,39 +104,39 @@ NR_DRB_ToAddMod_t *generateDRB_ASN1(drb_t drb_asn1) asn1cCalloc(DRB_config->pdcp_Config, pdcpConfig); asn1cCalloc(pdcpConfig->drb, drb); - DRB_config->drb_Identity = drb_asn1.drb_id; - association->present = drb_asn1.cnAssociation.present; + DRB_config->drb_Identity = drb_asn1->drb_id; + association->present = drb_asn1->cnAssociation.present; /* SDAP Configuration */ - SDAP_config->pdu_Session = drb_asn1.cnAssociation.sdap_config.pdusession_id; - SDAP_config->sdap_HeaderDL = drb_asn1.cnAssociation.sdap_config.sdap_HeaderDL; - SDAP_config->sdap_HeaderUL = drb_asn1.cnAssociation.sdap_config.sdap_HeaderUL; - SDAP_config->defaultDRB = drb_asn1.cnAssociation.sdap_config.defaultDRB; + SDAP_config->pdu_Session = drb_asn1->cnAssociation.sdap_config.pdusession_id; + SDAP_config->sdap_HeaderDL = drb_asn1->cnAssociation.sdap_config.sdap_HeaderDL; + SDAP_config->sdap_HeaderUL = drb_asn1->cnAssociation.sdap_config.sdap_HeaderUL; + SDAP_config->defaultDRB = drb_asn1->cnAssociation.sdap_config.defaultDRB; for (int qos_flow_index = 0; qos_flow_index < QOSFLOW_MAX_VALUE; qos_flow_index++) { - if (drb_asn1.cnAssociation.sdap_config.mappedQoS_FlowsToAdd[qos_flow_index] != 0) { + if (drb_asn1->cnAssociation.sdap_config.mappedQoS_FlowsToAdd[qos_flow_index] != 0) { asn1cSequenceAdd(sdapFlows->list, NR_QFI_t, qfi); - *qfi = drb_asn1.cnAssociation.sdap_config.mappedQoS_FlowsToAdd[qos_flow_index]; + *qfi = drb_asn1->cnAssociation.sdap_config.mappedQoS_FlowsToAdd[qos_flow_index]; } } association->choice.sdap_Config = SDAP_config; /* PDCP Configuration */ - asn1cCallocOne(drb->discardTimer, drb_asn1.pdcp_config.discardTimer); - asn1cCallocOne(drb->pdcp_SN_SizeUL, drb_asn1.pdcp_config.pdcp_SN_SizeUL); - asn1cCallocOne(drb->pdcp_SN_SizeDL, drb_asn1.pdcp_config.pdcp_SN_SizeDL); - asn1cCallocOne(pdcpConfig->t_Reordering, drb_asn1.pdcp_config.t_Reordering); + asn1cCallocOne(drb->discardTimer, drb_asn1->pdcp_config.discardTimer); + asn1cCallocOne(drb->pdcp_SN_SizeUL, drb_asn1->pdcp_config.pdcp_SN_SizeUL); + asn1cCallocOne(drb->pdcp_SN_SizeDL, drb_asn1->pdcp_config.pdcp_SN_SizeDL); + asn1cCallocOne(pdcpConfig->t_Reordering, drb_asn1->pdcp_config.t_Reordering); - drb->headerCompression.present = drb_asn1.pdcp_config.headerCompression.present; - drb->headerCompression.choice.notUsed = drb_asn1.pdcp_config.headerCompression.NotUsed; + drb->headerCompression.present = drb_asn1->pdcp_config.headerCompression.present; + drb->headerCompression.choice.notUsed = drb_asn1->pdcp_config.headerCompression.NotUsed; - if (!drb_asn1.pdcp_config.integrityProtection) { - asn1cCallocOne(drb->integrityProtection, drb_asn1.pdcp_config.integrityProtection); + if (!drb_asn1->pdcp_config.integrityProtection) { + asn1cCallocOne(drb->integrityProtection, drb_asn1->pdcp_config.integrityProtection); } - if (!drb_asn1.pdcp_config.ext1.cipheringDisabled) { + if (!drb_asn1->pdcp_config.ext1.cipheringDisabled) { asn1cCalloc(pdcpConfig->ext1, ext1); - asn1cCallocOne(ext1->cipheringDisabled, drb_asn1.pdcp_config.ext1.cipheringDisabled); + asn1cCallocOne(ext1->cipheringDisabled, drb_asn1->pdcp_config.ext1.cipheringDisabled); } return DRB_config; diff --git a/openair2/RRC/NR/rrc_gNB_radio_bearers.h b/openair2/RRC/NR/rrc_gNB_radio_bearers.h index 732394ef82..219bc21948 100644 --- a/openair2/RRC/NR/rrc_gNB_radio_bearers.h +++ b/openair2/RRC/NR/rrc_gNB_radio_bearers.h @@ -38,7 +38,7 @@ /// @brief Generates an ASN1 DRB-ToAddMod, from the established_drbs in gNB_RRC_UE_t. /// @param drb_t drb_asn1 /// @return Returns the ASN1 DRB-ToAddMod structs. -NR_DRB_ToAddMod_t *generateDRB_ASN1(drb_t drb_asn1); +NR_DRB_ToAddMod_t *generateDRB_ASN1(const drb_t *drb_asn1); /// @brief Creates and stores a DRB in the gNB_RRC_UE_t struct, it doesn't create the actual entity, /// to create the actual entity use the generateDRB_ASN1. /// @param ue The gNB_RRC_UE_t struct that holds information for the UEs From daa1a911ac03227a566050668e7114151dc1644a Mon Sep 17 00:00:00 2001 From: Florian Kaltenberger Date: Fri, 7 Apr 2023 10:04:13 +0000 Subject: [PATCH 89/89] Update radio/USRP/README.md --- radio/USRP/README.md | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/radio/USRP/README.md b/radio/USRP/README.md index 131be06436..b89cabe649 100644 --- a/radio/USRP/README.md +++ b/radio/USRP/README.md @@ -4,33 +4,46 @@ OAI works with the most common USRP models like, B200, B200mini, B210, X310, N30 The USRP can be configured in the RU section of the config file. The field "sdr_addrs" uses the same syntax as the USRP device identification string (https://files.ettus.com/manual/page_identification.html). Here are a few examples +```bash sdr_addrs = "addr=192.168.10.2" # uses a single 10Gb Ethernet interface on an N3x0 or X3x0 or X4x0 + sdr_addrs = "addr=192.168.10.2,second_addr=192.168.20.2" # uses 2 10Gb Ethernet interfaces on a N3x0 or X3x0 or X4x0 (requires that you flashed the FPGA wth the XG image) +``` you can also use the multi USRP feature and specify multiple USRPs, in which case you will get the aggregated number of channels on all the devices +```bash sdr_addrs = "addr0=192.168.10.2,addr1=192.168.30.2" +``` You can specify if you want to use external or interal clock or time source either by adding the parameters in the sdr_addrs field or by using the fields clock_src or time_src +```bash sdr_addrs = "addr=192.168.10.2,clock_source=external,time_source=external" +``` is equivalent to +```bash sdr_addrs = "addr=192.168.10.2" clock_src = "external" time_src = "external" +``` -Valid choices for clock and time source are "internal", "external", and "gpsdo" +Valid choices for clock and time source are "internal", "external", and "gpsdo". -Careful: the USRP remembers the choice of the clock source. If you want to make sure it uses always the same, always specify the clock_source and time_source. +Note 1: the USRP remembers the choice of the clock source. If you want to make sure it uses always the same, always specify the clock_source and time_source. + +Note 2: when using multiple USRPs they always have to be synchronized using "external" or "gpsdo" Last but not least you may specify that only a specfic subdevice of the USRP is used. See also https://files.ettus.com/manual/page_configuration.html#config_subdev For example on a USRP N310 the following fields will specify that you use channel 0 of subdevice A. +```bash tx_subdev = "A:0" rx_subdev = "A:0" +``` -When combining this with the multi USRP feature you can easily create a distributed antenna array with only 1 channel used at each USRP. \ No newline at end of file +When combining this with the multi USRP feature you can easily create a distributed antenna array with only 1 channel used at each USRP.