/* * 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 gNB_scheduler_primitives.c * \brief primitives used by gNB for BCH, RACH, ULSCH, DLSCH scheduling * \author Raymond Knopp, Guy De Souza * \date 2018, 2019 * \email: knopp@eurecom.fr, desouza@eurecom.fr * \version 1.0 * \company Eurecom * @ingroup _mac */ #include #include "assertions.h" #include "NR_MAC_gNB/nr_mac_gNB.h" #include "NR_MAC_gNB/mac_proto.h" #include "common/utils/LOG/log.h" #include "common/utils/LOG/vcd_signal_dumper.h" #include "common/utils/nr/nr_common.h" #include "UTIL/OPT/opt.h" #include "openair2/LAYER2/nr_rlc/nr_rlc_oai_api.h" #include "F1AP_CauseRadioNetwork.h" #include "intertask_interface.h" #include "openair2/F1AP/f1ap_ids.h" #include "F1AP_CauseRadioNetwork.h" #include "T.h" #include "uper_encoder.h" #include "uper_decoder.h" #include "SIMULATION/TOOLS/sim.h" // for taus #define ENABLE_MAC_PAYLOAD_DEBUG #define DEBUG_gNB_SCHEDULER 1 #include "common/ran_context.h" #include "nfapi/oai_integration/vendor_ext.h" #include "common/utils/alg/find.h" // 3GPP TS 38.331 Section 12 Table 12.1-1: UE performance requirements for RRC procedures for UEs #define NR_RRC_SETUP_DELAY_MS 10 #define NR_RRC_RECONFIGURATION_DELAY_MS 10 #define NR_RRC_BWP_SWITCHING_DELAY_MS 6 // #define DEBUG_DCI // CQI TABLES (10 times the value in 214 to adequately compare with R) // Table 1 (38.214 5.2.2.1-2) static const uint16_t cqi_table1[16][2] = {{0, 0}, {2, 780}, {2, 1200}, {2, 1930}, {2, 3080}, {2, 4490}, {2, 6020}, {4, 3780}, {4, 4900}, {4, 6160}, {6, 4660}, {6, 5670}, {6, 6660}, {6, 7720}, {6, 8730}, {6, 9480}}; // Table 2 (38.214 5.2.2.1-3) static const uint16_t cqi_table2[16][2] = {{0, 0}, {2, 780}, {2, 1930}, {2, 4490}, {4, 3780}, {4, 4900}, {4, 6160}, {6, 4660}, {6, 5670}, {6, 6660}, {6, 7720}, {6, 8730}, {8, 7110}, {8, 7970}, {8, 8850}, {8, 9480}}; // Table 2 (38.214 5.2.2.1-4) static const uint16_t cqi_table3[16][2] = {{0, 0}, {2, 300}, {2, 500}, {2, 780}, {2, 1200}, {2, 1930}, {2, 3080}, {2, 4490}, {2, 6020}, {4, 3780}, {4, 4900}, {4, 6160}, {6, 4660}, {6, 5670}, {6, 6660}, {6, 7720}}; static void determine_aggregation_level_search_order(int agg_level_search_order[NUM_PDCCH_AGG_LEVELS], float pdcch_cl_adjust); uint8_t get_dl_nrOfLayers(const NR_UE_sched_ctrl_t *sched_ctrl, const nr_dci_format_t dci_format) { // TODO check this but it should be enough for now // if there is not csi report activated RI is 0 from initialization if(dci_format == NR_DL_DCI_FORMAT_1_0) return 1; else return sched_ctrl->CSI_report.cri_ri_li_pmi_cqi_report.ri + 1; } int get_ul_nrOfLayers(const NR_UE_sched_ctrl_t *sched_ctrl, const nr_dci_format_t dci_format) { if(dci_format == NR_UL_DCI_FORMAT_0_0) return 1; else return sched_ctrl->srs_feedback.ul_ri + 1; } // Table 5.2.2.2.1-3 and Table 5.2.2.2.1-4 in 38.214 void get_k1_k2_indices(const int layers, const int N1, const int N2, const int i13, int *k1, int *k2) { AssertFatal(layers > 0 && layers < 5, "Number of layers %d not supported\n", layers); *k1 = 0; *k2 = 0; if (layers == 2) { if (N2 == 1) *k1 = i13; else if (N1 == N2) { *k1 = i13 & 1; *k2 = i13 >> 1; } else { *k1 = (i13 & 1) + (i13 == 3); *k2 = (i13 == 2); } } if (layers == 3 || layers == 4) { if (N2 == 1) *k1 = i13 + 1; else if (N1 == 2 && N2 == 2) { *k1 = !(i13 & 1); *k2 = (i13 > 0); } else { if (i13 == 0) *k1 = 1; if (i13 == 1) *k2 = 1; if (i13 == 2) { *k1 = 1; *k2 = 1; } if (i13 == 3) *k1 = 2; } } } uint16_t get_pm_index(const gNB_MAC_INST *nrmac, const NR_UE_info_t *UE, nr_dci_format_t dci_format, int layers, int xp_pdsch_antenna_ports) { if (dci_format == NR_DL_DCI_FORMAT_1_0 || nrmac->identity_pm || xp_pdsch_antenna_ports == 1) return 0; //identity matrix (basic 5G configuration handled by PMI report is with XP antennas) const NR_UE_sched_ctrl_t *sched_ctrl = &UE->UE_sched_ctrl; const int report_id = sched_ctrl->CSI_report.cri_ri_li_pmi_cqi_report.csi_report_id; const nr_csi_report_t *csi_report = &UE->csi_report_template[report_id]; const int N1 = csi_report->N1; const int N2 = csi_report->N2; const int antenna_ports = (N1 * N2) << 1; if (antenna_ports < 2) return 0; // single antenna port int x1 = sched_ctrl->CSI_report.cri_ri_li_pmi_cqi_report.pmi_x1; const int x2 = sched_ctrl->CSI_report.cri_ri_li_pmi_cqi_report.pmi_x2; LOG_D(NR_MAC,"PMI report: x1 %d x2 %d layers: %d\n", x1, x2, layers); int prev_layers_size = 0; for (int i = 1; i < layers; i++) prev_layers_size += nrmac->precoding_matrix_size[i - 1]; // need to return PM index to matrix initialized in init_DL_MIMO_codebook // index 0 is for identity matrix // order of matrices depends on layers to be transmitted // elements from 1 to n for 1 layer // elements from n+1 to m for 2 layers etc. if (antenna_ports == 2) return 1 + prev_layers_size + x2; // 0 for identity matrix else { int idx = layers - 1; // the order of i1x in X1 report needs to be verified // the standard is not very clear (Table 6.3.1.1.2-7 in 38.212) // it says: PMI wideband information fields X1 , from left to right int bitlen = csi_report->csi_meas_bitlen.pmi_i13_bitlen[idx]; if (layers == 1 && bitlen != 0) { LOG_E(NR_MAC, "Invalid i13 bit length %d for single layer! It should be 0\n", bitlen); return 0; } const int i13 = x1 & ((1 << bitlen) - 1); x1 >>= bitlen; bitlen = csi_report->csi_meas_bitlen.pmi_i12_bitlen[idx]; const int i12 = x1 & ((1 << bitlen) - 1); x1 >>= bitlen; bitlen = csi_report->csi_meas_bitlen.pmi_i11_bitlen[idx]; const int i11 = x1 & ((1 << bitlen) - 1); const int i2 = x2; int k1, k2; get_k1_k2_indices(layers, N1, N2, i13, &k1, &k2); // get indices k1 and k2 for PHY matrix (not actual k1 and k2 values) const int O2 = N2 == 1 ? 1 : 4; int O1 = 4; //Horizontal beam oversampling = 4 for more than 2 antenna ports int max_i2 = 0; int lay_index = 0; max_i2 = layers == 1 ? 4 : 2; int K1, K2; get_K1_K2(N1, N2, &K1, &K2, layers); // computing precoding matrix index according to rule set in allocation function init_codebook_gNB lay_index = i2 + (i11 * max_i2) + (i12 * max_i2 * N1 * O1) + (k1 * max_i2 * N1 * O1 * N2 * O2) + (k2 * max_i2 * N1 * O1 * N2 * O2 * K1); return 1 + prev_layers_size + lay_index; } } uint8_t get_mcs_from_cqi(int mcs_table, int cqi_table, int cqi_idx) { if (cqi_idx <= 0) { LOG_E(NR_MAC, "invalid cqi_idx %d, default to MCS 9\n", cqi_idx); return 9; } if (mcs_table != cqi_table) { LOG_E(NR_MAC, "indices of CQI (%d) and MCS (%d) tables don't correspond yet\n", cqi_table, mcs_table); return 9; } uint16_t target_coderate, target_qm; switch (cqi_table) { case 0: target_qm = cqi_table1[cqi_idx][0]; target_coderate = cqi_table1[cqi_idx][1]; break; case 1: target_qm = cqi_table2[cqi_idx][0]; target_coderate = cqi_table2[cqi_idx][1]; break; case 2: target_qm = cqi_table3[cqi_idx][0]; target_coderate = cqi_table3[cqi_idx][1]; break; default: AssertFatal(1==0,"Invalid cqi table index %d\n",cqi_table); } const int max_mcs = mcs_table == 1 ? 27 : 28; for (int i = 0; i <= max_mcs; i++) { const int R = nr_get_code_rate_dl(i, mcs_table); const int Qm = nr_get_Qm_dl(i, mcs_table); if (Qm == target_qm && target_coderate <= R) return i; } LOG_E(NR_MAC, "could not find maximum MCS from cqi_idx %d, default to 9\n", cqi_idx); return 9; } NR_pdsch_dmrs_t get_dl_dmrs_params(const NR_ServingCellConfigCommon_t *scc, const NR_UE_DL_BWP_t *dl_bwp, const NR_tda_info_t *tda_info, const int Layers) { NR_pdsch_dmrs_t dmrs = {0}; int frontloaded_symb = 1; // default value nr_dci_format_t dci_format = dl_bwp ? dl_bwp->dci_format : NR_DL_DCI_FORMAT_1_0; if (dci_format == NR_DL_DCI_FORMAT_1_0) { dmrs.numDmrsCdmGrpsNoData = tda_info->nrOfSymbols <= 2 ? 1 : 2; dmrs.dmrs_ports_id = 0; } else { //TODO first basic implementation of dmrs port selection // only vaild for a single codeword // for now it assumes a selection of Nl consecutive dmrs ports // and a single front loaded symbol // dmrs_ports_id is the index of Tables 7.3.1.2.2-1/2/3/4 // number of front loaded symbols need to be consistent with maxLength // when a more complete implementation is done switch (Layers) { case 1: #ifdef ENABLE_AERIAL dmrs.dmrs_ports_id = 3; dmrs.numDmrsCdmGrpsNoData = 2; #else dmrs.dmrs_ports_id = 0; dmrs.numDmrsCdmGrpsNoData = 1; #endif frontloaded_symb = 1; break; case 2: #ifdef ENABLE_AERIAL dmrs.dmrs_ports_id = 7; dmrs.numDmrsCdmGrpsNoData = 2; #else dmrs.dmrs_ports_id = 2; dmrs.numDmrsCdmGrpsNoData = 1; #endif frontloaded_symb = 1; break; case 3: dmrs.dmrs_ports_id = 9; dmrs.numDmrsCdmGrpsNoData = 2; frontloaded_symb = 1; break; case 4: dmrs.dmrs_ports_id = 10; dmrs.numDmrsCdmGrpsNoData = 2; frontloaded_symb = 1; break; default: AssertFatal(1==0,"Number of layers %d\n not supported or not valid\n",Layers); } } dmrs.n_scid = 0; long *scramblingID = NULL; NR_PDSCH_Config_t *pdsch_Config = dl_bwp ? dl_bwp->pdsch_Config : NULL; if (pdsch_Config) { NR_DMRS_DownlinkConfig_t *dmrs_Config = tda_info->mapping_type == typeB ? pdsch_Config->dmrs_DownlinkForPDSCH_MappingTypeB->choice.setup : pdsch_Config->dmrs_DownlinkForPDSCH_MappingTypeA->choice.setup; dmrs.phaseTrackingRS = dmrs_Config->phaseTrackingRS ? dmrs_Config->phaseTrackingRS->choice.setup : NULL; dmrs.dmrsConfigType = dmrs_Config->dmrs_Type != NULL; scramblingID = dmrs.n_scid ? dmrs_Config->scramblingID1 : dmrs_Config->scramblingID0; } else { dmrs.dmrsConfigType = NFAPI_NR_DMRS_TYPE1; dmrs.phaseTrackingRS = NULL; } dmrs.scrambling_id = scramblingID ? *scramblingID : *scc->physCellId; dmrs.N_PRB_DMRS = dmrs.numDmrsCdmGrpsNoData * (dmrs.dmrsConfigType == NFAPI_NR_DMRS_TYPE1 ? 6 : 4); dmrs.dl_dmrs_symb_pos = fill_dmrs_mask(pdsch_Config, dci_format, scc->dmrs_TypeA_Position, tda_info->nrOfSymbols, tda_info->startSymbolIndex, tda_info->mapping_type, frontloaded_symb); dmrs.N_DMRS_SLOT = get_num_dmrs(dmrs.dl_dmrs_symb_pos); LOG_D(NR_MAC,"Filling dmrs info, ps->N_PRB_DMRS %d, ps->dl_dmrs_symb_pos %x, ps->N_DMRS_SLOT %d\n",dmrs.N_PRB_DMRS,dmrs.dl_dmrs_symb_pos,dmrs.N_DMRS_SLOT); return dmrs; } NR_ControlResourceSet_t *get_coreset(gNB_MAC_INST *nrmac, NR_ServingCellConfigCommon_t *scc, void *bwp, NR_SearchSpace_t *ss, NR_SearchSpace__searchSpaceType_PR ss_type) { NR_ControlResourceSetId_t coreset_id = *ss->controlResourceSetId; if (ss_type == NR_SearchSpace__searchSpaceType_PR_common) { // common search space NR_ControlResourceSet_t *coreset; if(coreset_id == 0) { coreset = nrmac->sched_ctrlCommon->coreset; // this is coreset 0 } else if (bwp) { coreset = ((NR_BWP_Downlink_t*)bwp)->bwp_Common->pdcch_ConfigCommon->choice.setup->commonControlResourceSet; } else if (scc->downlinkConfigCommon->initialDownlinkBWP->pdcch_ConfigCommon->choice.setup->commonControlResourceSet) { coreset = scc->downlinkConfigCommon->initialDownlinkBWP->pdcch_ConfigCommon->choice.setup->commonControlResourceSet; } else { coreset = NULL; } if (coreset) AssertFatal(coreset_id == coreset->controlResourceSetId, "ID of common ss coreset does not correspond to id set in the " "search space\n"); return coreset; } else { const int n = ((NR_BWP_DownlinkDedicated_t*)bwp)->pdcch_Config->choice.setup->controlResourceSetToAddModList->list.count; for (int i = 0; i < n; i++) { NR_ControlResourceSet_t *coreset = ((NR_BWP_DownlinkDedicated_t*)bwp)->pdcch_Config->choice.setup->controlResourceSetToAddModList->list.array[i]; if (coreset_id == coreset->controlResourceSetId) { return coreset; } } AssertFatal(0, "Couldn't find coreset with id %ld\n", coreset_id); } } static NR_SearchSpace_t *get_searchspace(NR_ServingCellConfigCommon_t *scc, NR_BWP_DownlinkDedicated_t *bwp_Dedicated, NR_SearchSpace__searchSpaceType_PR target_ss) { int n = 0; if(bwp_Dedicated) n = bwp_Dedicated->pdcch_Config->choice.setup->searchSpacesToAddModList->list.count; else n = scc->downlinkConfigCommon->initialDownlinkBWP->pdcch_ConfigCommon->choice.setup->commonSearchSpaceList->list.count; for (int i=0;ipdcch_Config->choice.setup->searchSpacesToAddModList->list.array[i]; else ss = scc->downlinkConfigCommon->initialDownlinkBWP->pdcch_ConfigCommon->choice.setup->commonSearchSpaceList->list.array[i]; AssertFatal(ss->controlResourceSetId != NULL, "ss->controlResourceSetId is null\n"); AssertFatal(ss->searchSpaceType != NULL, "ss->searchSpaceType is null\n"); if (ss->searchSpaceType->present == target_ss) { return ss; } } AssertFatal(0, "Couldn't find an adequate searchspace bwp_Dedicated %p\n",bwp_Dedicated); } NR_sched_pdcch_t set_pdcch_structure(gNB_MAC_INST *gNB_mac, NR_SearchSpace_t *ss, NR_ControlResourceSet_t *coreset, NR_ServingCellConfigCommon_t *scc, NR_BWP_t *bwp, NR_Type0_PDCCH_CSS_config_t *type0_PDCCH_CSS_config) { int sps; NR_sched_pdcch_t pdcch; AssertFatal(*ss->controlResourceSetId == coreset->controlResourceSetId, "coreset id in SS %ld does not correspond to the one in coreset %ld", *ss->controlResourceSetId, coreset->controlResourceSetId); if (bwp) { // This is not for SIB1 if(coreset->controlResourceSetId == 0){ pdcch.BWPSize = gNB_mac->cset0_bwp_size; pdcch.BWPStart = gNB_mac->cset0_bwp_start; } else { pdcch.BWPSize = NRRIV2BW(bwp->locationAndBandwidth, MAX_BWP_SIZE); pdcch.BWPStart = NRRIV2PRBOFFSET(bwp->locationAndBandwidth, MAX_BWP_SIZE); } pdcch.SubcarrierSpacing = bwp->subcarrierSpacing; pdcch.CyclicPrefix = (bwp->cyclicPrefix==NULL) ? 0 : *bwp->cyclicPrefix; //AssertFatal(pdcch_scs==kHz15, "PDCCH SCS above 15kHz not allowed if a symbol above 2 is monitored"); sps = bwp->cyclicPrefix == NULL ? 14 : 12; } else { AssertFatal(type0_PDCCH_CSS_config!=NULL,"type0_PDCCH_CSS_config is null,bwp %p\n",bwp); pdcch.BWPSize = type0_PDCCH_CSS_config->num_rbs; pdcch.BWPStart = type0_PDCCH_CSS_config->cset_start_rb; pdcch.SubcarrierSpacing = type0_PDCCH_CSS_config->scs_pdcch; pdcch.CyclicPrefix = 0; sps = 14; } AssertFatal(ss->monitoringSymbolsWithinSlot!=NULL,"ss->monitoringSymbolsWithinSlot is null\n"); AssertFatal(ss->monitoringSymbolsWithinSlot->buf!=NULL,"ss->monitoringSymbolsWithinSlot->buf is null\n"); // for SPS=14 8 MSBs in positions 13 downto 6 uint16_t monitoringSymbolsWithinSlot = (ss->monitoringSymbolsWithinSlot->buf[0]<<(sps-8)) | (ss->monitoringSymbolsWithinSlot->buf[1]>>(16-sps)); for (int i=0; i>(sps-1-i))&1) { pdcch.StartSymbolIndex=i; break; } } pdcch.DurationSymbols = coreset->duration; //cce-REG-MappingType pdcch.CceRegMappingType = coreset->cce_REG_MappingType.present == NR_ControlResourceSet__cce_REG_MappingType_PR_interleaved? NFAPI_NR_CCE_REG_MAPPING_INTERLEAVED : NFAPI_NR_CCE_REG_MAPPING_NON_INTERLEAVED; if (pdcch.CceRegMappingType == NFAPI_NR_CCE_REG_MAPPING_INTERLEAVED) { pdcch.RegBundleSize = (coreset->cce_REG_MappingType.choice.interleaved->reg_BundleSize == NR_ControlResourceSet__cce_REG_MappingType__interleaved__reg_BundleSize_n6) ? 6 : (2+coreset->cce_REG_MappingType.choice.interleaved->reg_BundleSize); pdcch.InterleaverSize = (coreset->cce_REG_MappingType.choice.interleaved->interleaverSize == NR_ControlResourceSet__cce_REG_MappingType__interleaved__interleaverSize_n6) ? 6 : (2+coreset->cce_REG_MappingType.choice.interleaved->interleaverSize); AssertFatal(scc->physCellId != NULL,"scc->physCellId is null\n"); pdcch.ShiftIndex = coreset->cce_REG_MappingType.choice.interleaved->shiftIndex != NULL ? *coreset->cce_REG_MappingType.choice.interleaved->shiftIndex : *scc->physCellId; } else { pdcch.RegBundleSize = 6; pdcch.InterleaverSize = 0; pdcch.ShiftIndex = 0; } uint16_t N_rb = 0; // nb of rbs of coreset per symbol uint16_t rb_start = 0; for (int i = 0; i < 6; i++) { for (int t = 0; t < 8; t++) { if (coreset->frequencyDomainResources.buf[i] >> (7 - t) & 1) { if (N_rb == 0) { rb_start = 48 * i + t * 6; } N_rb++; } } } pdcch.rb_start = rb_start; pdcch.n_rb = N_rb *= 6; // each bit of frequencyDomainResources represents 6 PRBs return pdcch; } int find_pdcch_candidate(const gNB_MAC_INST *mac, int cc_id, int aggregation, int nr_of_candidates, int beam_idx, const NR_sched_pdcch_t *pdcch, const NR_ControlResourceSet_t *coreset, uint32_t Y) { const uint16_t *vrb_map = mac->common_channels[cc_id].vrb_map[beam_idx]; const int N_ci = 0; const int N_rb = pdcch->n_rb; // nb of rbs of coreset per symbol const int N_symb = coreset->duration; // nb of coreset symbols const int N_regs = N_rb * N_symb; // nb of REGs per coreset const int N_cces = N_regs / NR_NB_REG_PER_CCE; // nb of cces in coreset const int R = pdcch->InterleaverSize; const int L = pdcch->RegBundleSize; const int C = R > 0 ? N_regs / (L * R) : 0; const int B_rb = L / N_symb; // nb of RBs occupied by each REG bundle // loop over all the available candidates // this implements TS 38.211 Sec. 7.3.2.2 for(int m = 0; m < nr_of_candidates; m++) { // loop over candidates bool taken = false; // flag if the resource for a given candidate are taken int first_cce = aggregation * ((Y + ((m * N_cces) / (aggregation * nr_of_candidates)) + N_ci) % (N_cces / aggregation)); LOG_D(NR_MAC,"Candidate %d of %d first_cce %d (L %d N_cces %d Y %d)\n", m, nr_of_candidates, first_cce, aggregation, N_cces, Y); for (int j = first_cce; (j < first_cce + aggregation) && !taken; j++) { // loop over CCEs for (int k = 6 * j / L; (k < (6 * j / L + 6 / L)) && !taken; k++) { // loop over REG bundles int f = cce_to_reg_interleaving(R, k, pdcch->ShiftIndex, C, L, N_regs); for(int rb = 0; rb < B_rb; rb++) { // loop over the RBs of the bundle if(vrb_map[pdcch->BWPStart + f * B_rb + rb] & SL_to_bitmap(pdcch->StartSymbolIndex,N_symb)) { taken = true; break; } } } } if(!taken) return first_cce; } return -1; } int get_cce_index(const gNB_MAC_INST *nrmac, const int CC_id, const int slot, const rnti_t rnti, uint8_t *aggregation_level, int beam_idx, const NR_SearchSpace_t *ss, const NR_ControlResourceSet_t *coreset, NR_sched_pdcch_t *sched_pdcch, bool is_common, float pdcch_cl_adjust) { const uint32_t Y = is_common ? 0 : get_Y(ss, slot, rnti); uint8_t nr_of_candidates; int agg_level_search_order[NUM_PDCCH_AGG_LEVELS]; determine_aggregation_level_search_order(agg_level_search_order, pdcch_cl_adjust); for (int i = 0; i < NUM_PDCCH_AGG_LEVELS; i++) { find_aggregation_candidates(aggregation_level, &nr_of_candidates, ss, 1 << agg_level_search_order[i]); if (nr_of_candidates > 0) break; } int CCEIndex = find_pdcch_candidate(nrmac, CC_id, *aggregation_level, nr_of_candidates, beam_idx, sched_pdcch, coreset, Y); return CCEIndex; } void fill_pdcch_vrb_map(gNB_MAC_INST *mac, int CC_id, NR_sched_pdcch_t *pdcch, int first_cce, int aggregation, int beam) { uint16_t *vrb_map = mac->common_channels[CC_id].vrb_map[beam]; int N_rb = pdcch->n_rb; // nb of rbs of coreset per symbol int L = pdcch->RegBundleSize; int R = pdcch->InterleaverSize; int n_shift = pdcch->ShiftIndex; int N_symb = pdcch->DurationSymbols; int N_regs = N_rb*N_symb; // nb of REGs per coreset int B_rb = L/N_symb; // nb of RBs occupied by each REG bundle int C = R>0 ? N_regs/(L*R) : 0; for (int j=first_cce; jBWPStart + f*B_rb + rb] |= SL_to_bitmap(pdcch->StartSymbolIndex, N_symb); } } } static bool multiple_2_3_5(int rb) { while (rb % 2 == 0) rb /= 2; while (rb % 3 == 0) rb /= 3; while (rb % 5 == 0) rb /= 5; return (rb == 1); } bool nr_find_nb_rb(uint16_t Qm, uint16_t R, long transform_precoding, uint8_t nrOfLayers, uint16_t nb_symb_sch, uint16_t nb_dmrs_prb, uint32_t bytes, uint16_t nb_rb_min, uint16_t nb_rb_max, uint32_t *tbs, uint16_t *nb_rb) { // for transform precoding only RB = 2^a_2 * 3^a_3 * 5^a_5 is allowed with a non-negative while(transform_precoding == NR_PUSCH_Config__transformPrecoder_enabled && !multiple_2_3_5(nb_rb_max)) nb_rb_max--; /* is the maximum (not even) enough? */ *nb_rb = nb_rb_max; *tbs = nr_compute_tbs(Qm, R, *nb_rb, nb_symb_sch, nb_dmrs_prb, 0, 0, nrOfLayers) >> 3; /* check whether it does not fit, or whether it exactly fits. Some algorithms * might depend on the return value! */ if (bytes > *tbs) return false; if (bytes == *tbs) return true; /* is the minimum enough? */ *nb_rb = nb_rb_min; *tbs = nr_compute_tbs(Qm, R, *nb_rb, nb_symb_sch, nb_dmrs_prb, 0, 0, nrOfLayers) >> 3; if (bytes <= *tbs) return true; /* perform binary search to allocate all bytes within a TBS up to nb_rb_max * RBs */ int hi = nb_rb_max; int lo = nb_rb_min; for (int p = (hi + lo) / 2; lo + 1 < hi; p = (hi + lo) / 2) { // for transform precoding only RB = 2^a_2 * 3^a_3 * 5^a_5 is allowed with a non-negative while(transform_precoding == NR_PUSCH_Config__transformPrecoder_enabled && !multiple_2_3_5(p)) p++; // If by increasing p for transform precoding we already hit the high, break to avoid infinite loop if (p == hi) break; const uint32_t TBS = nr_compute_tbs(Qm, R, p, nb_symb_sch, nb_dmrs_prb, 0, 0, nrOfLayers) >> 3; if (bytes == TBS) { hi = p; break; } else if (bytes < TBS) { hi = p; } else { lo = p; } } *nb_rb = hi; *tbs = nr_compute_tbs(Qm, R, *nb_rb, nb_symb_sch, nb_dmrs_prb, 0, 0, nrOfLayers) >> 3; /* return whether we could allocate all bytes and stay below nb_rb_max */ return *tbs >= bytes && *nb_rb <= nb_rb_max; } const NR_DMRS_UplinkConfig_t *get_DMRS_UplinkConfig(const NR_PUSCH_Config_t *pusch_Config, const NR_tda_info_t *tda_info) { if (pusch_Config == NULL) return NULL; return tda_info->mapping_type == typeA ? pusch_Config->dmrs_UplinkForPUSCH_MappingTypeA->choice.setup : pusch_Config->dmrs_UplinkForPUSCH_MappingTypeB->choice.setup; } NR_pusch_dmrs_t get_ul_dmrs_params(const NR_ServingCellConfigCommon_t *scc, const NR_UE_UL_BWP_t *ul_bwp, const NR_tda_info_t *tda_info, const int Layers) { NR_pusch_dmrs_t dmrs = {0}; // TODO setting of cdm groups with no data to be redone for MIMO if(NFAPI_MODE == NFAPI_MODE_AERIAL) { dmrs.num_dmrs_cdm_grps_no_data = 2; } else { if (ul_bwp->transform_precoding && Layers < 3) dmrs.num_dmrs_cdm_grps_no_data = ul_bwp->dci_format == NR_UL_DCI_FORMAT_0_1 || tda_info->nrOfSymbols <= 2 ? 1 : 2; else dmrs.num_dmrs_cdm_grps_no_data = 2; } const NR_DMRS_UplinkConfig_t *NR_DMRS_UplinkConfig = get_DMRS_UplinkConfig(ul_bwp->pusch_Config, tda_info); dmrs.ptrsConfig = NR_DMRS_UplinkConfig && NR_DMRS_UplinkConfig->phaseTrackingRS ? NR_DMRS_UplinkConfig->phaseTrackingRS->choice.setup : NULL; dmrs.pusch_identity = *scc->physCellId; dmrs.dmrs_scrambling_id = *scc->physCellId; dmrs.scid = 0; if (ul_bwp->transform_precoding) { // transform precoding disabled long *scramblingid = NULL; if (NR_DMRS_UplinkConfig && dmrs.scid == 0) scramblingid = NR_DMRS_UplinkConfig->transformPrecodingDisabled->scramblingID0; else if (NR_DMRS_UplinkConfig) scramblingid = NR_DMRS_UplinkConfig->transformPrecodingDisabled->scramblingID1; if (scramblingid) dmrs.dmrs_scrambling_id = *scramblingid; } else { if (NR_DMRS_UplinkConfig && NR_DMRS_UplinkConfig->transformPrecodingEnabled) { if (NR_DMRS_UplinkConfig->transformPrecodingEnabled->nPUSCH_Identity) dmrs.pusch_identity = *NR_DMRS_UplinkConfig->transformPrecodingEnabled->nPUSCH_Identity; if ((!NR_DMRS_UplinkConfig->transformPrecodingEnabled->sequenceGroupHopping && !NR_DMRS_UplinkConfig->transformPrecodingEnabled->sequenceHopping) && !scc->uplinkConfigCommon->initialUplinkBWP->pusch_ConfigCommon->choice.setup->groupHoppingEnabledTransformPrecoding) dmrs.low_papr_sequence_number = 0; else AssertFatal(false, "Hopping mode is not supported in transform precoding\n"); } } dmrs.dmrs_config_type = NR_DMRS_UplinkConfig && NR_DMRS_UplinkConfig->dmrs_Type ? 1 : 0; const pusch_dmrs_AdditionalPosition_t additional_pos = (NR_DMRS_UplinkConfig && NR_DMRS_UplinkConfig->dmrs_AdditionalPosition) ? (*NR_DMRS_UplinkConfig->dmrs_AdditionalPosition == NR_DMRS_UplinkConfig__dmrs_AdditionalPosition_pos3 ? 3 : *NR_DMRS_UplinkConfig->dmrs_AdditionalPosition) : 2; const pusch_maxLength_t pusch_maxLength = NR_DMRS_UplinkConfig ? (NR_DMRS_UplinkConfig->maxLength == NULL ? 1 : 2) : 1; dmrs.ul_dmrs_symb_pos = get_l_prime(tda_info->nrOfSymbols, tda_info->mapping_type, additional_pos, pusch_maxLength, tda_info->startSymbolIndex, scc->dmrs_TypeA_Position); dmrs.num_dmrs_symb = count_bits64_with_mask(dmrs.ul_dmrs_symb_pos, tda_info->startSymbolIndex, tda_info->nrOfSymbols); dmrs.N_PRB_DMRS = dmrs.num_dmrs_cdm_grps_no_data * (dmrs.dmrs_config_type == 0 ? 6 : 4); return dmrs; } #define BLER_UPDATE_FRAME 10 #define BLER_FILTER 0.9f int get_mcs_from_bler(const NR_bler_options_t *bler_options, const NR_mac_dir_stats_t *stats, NR_bler_stats_t *bler_stats, int max_mcs, frame_t frame) { int diff = frame - bler_stats->last_frame; if (diff < 0) // wrap around diff += 1024; max_mcs = min(max_mcs, bler_options->max_mcs); const uint8_t old_mcs = min(bler_stats->mcs, max_mcs); if (diff < BLER_UPDATE_FRAME) return old_mcs; // no update // last update is longer than x frames ago const int num_dl_sched = (int)(stats->rounds[0] - bler_stats->rounds[0]); const int num_dl_retx = (int)(stats->rounds[1] - bler_stats->rounds[1]); const float bler_window = num_dl_sched > 0 ? (float) num_dl_retx / num_dl_sched : bler_stats->bler; bler_stats->bler = BLER_FILTER * bler_stats->bler + (1 - BLER_FILTER) * bler_window; int new_mcs = old_mcs; if (bler_stats->bler < bler_options->lower && old_mcs < max_mcs && num_dl_sched > 3) new_mcs += 1; else if (bler_stats->bler > bler_options->upper || num_dl_sched <= 3) // above threshold or no activity new_mcs -= 1; // else we are within threshold boundaries new_mcs = max(new_mcs, bler_options->min_mcs); bler_stats->last_frame = frame; bler_stats->mcs = new_mcs; memcpy(bler_stats->rounds, stats->rounds, sizeof(stats->rounds)); LOG_D(MAC, "frame %4d MCS %d -> %d (num_dl_sched %d, num_dl_retx %d, BLER wnd %.3f avg %.6f)\n", frame, old_mcs, new_mcs, num_dl_sched, num_dl_retx, bler_window, bler_stats->bler); return new_mcs; } nfapi_nr_dl_dci_pdu_t *prepare_dci_pdu(nfapi_nr_dl_tti_pdcch_pdu_rel15_t *pdcch_pdu, const NR_ServingCellConfigCommon_t *scc, const NR_SearchSpace_t *ss, const NR_ControlResourceSet_t *coreset, int aggregation_level, int cce_index, int beam_index, int rnti) { nfapi_nr_dl_dci_pdu_t *dci_pdu = &pdcch_pdu->dci_pdu[pdcch_pdu->numDlDci]; dci_pdu->RNTI = rnti; dci_pdu->AggregationLevel = aggregation_level; dci_pdu->CceIndex = cce_index; if (coreset->pdcch_DMRS_ScramblingID && ss->searchSpaceType->present == NR_SearchSpace__searchSpaceType_PR_ue_Specific) { dci_pdu->ScramblingId = *coreset->pdcch_DMRS_ScramblingID; dci_pdu->ScramblingRNTI = rnti; } else { dci_pdu->ScramblingId = *scc->physCellId; dci_pdu->ScramblingRNTI = 0; } dci_pdu->beta_PDCCH_1_0 = 0; dci_pdu->powerControlOffsetSS = 1; dci_pdu->precodingAndBeamforming.num_prgs = 0; dci_pdu->precodingAndBeamforming.prg_size = 0; dci_pdu->precodingAndBeamforming.dig_bf_interfaces = 1; dci_pdu->precodingAndBeamforming.prgs_list[0].pm_idx = 0; dci_pdu->precodingAndBeamforming.prgs_list[0].dig_bf_interface_list[0].beam_idx = beam_index; return dci_pdu; } dci_pdu_rel15_t prepare_dci_dl_payload(const gNB_MAC_INST *gNB_mac, const NR_UE_info_t *UE, nr_rnti_type_t rnti_type, NR_SearchSpace__searchSpaceType_PR ss_type, const nfapi_nr_dl_tti_pdsch_pdu_rel15_t *pdsch_pdu, const NR_sched_pdsch_t *sched_pdsch, const NR_sched_pucch_t *pucch, int harq_pid, int tb_scaling, bool is_sib1) { const NR_UE_DL_BWP_t *dl_BWP = UE ? &UE->current_DL_BWP : NULL; dci_pdu_rel15_t dci_payload = {0}; dci_payload.format_indicator = 1; dci_payload.time_domain_assignment.val = sched_pdsch->time_domain_allocation; dci_payload.mcs = sched_pdsch->mcs; dci_payload.rv = pdsch_pdu->rvIndex[0]; dci_payload.vrb_to_prb_mapping.val = 0; int riv_bwp = pdsch_pdu->BWPSize; if (!UE) riv_bwp = gNB_mac->cset0_bwp_size; else if (dl_BWP->dci_format == NR_DL_DCI_FORMAT_1_0 && ss_type == NR_SearchSpace__searchSpaceType_PR_common) { if (gNB_mac->cset0_bwp_size > 0) riv_bwp = gNB_mac->cset0_bwp_size; else riv_bwp = UE->sc_info.initial_dl_BWPSize; } dci_payload.frequency_domain_assignment.val = PRBalloc_to_locationandbandwidth0(pdsch_pdu->rbSize, pdsch_pdu->rbStart, riv_bwp); if (rnti_type == TYPE_SI_RNTI_) { dci_payload.system_info_indicator = !is_sib1; return dci_payload; } if (rnti_type == TYPE_RA_RNTI_) { dci_payload.tb_scaling = tb_scaling; return dci_payload; } const NR_UE_sched_ctrl_t *sched_ctrl = &UE->UE_sched_ctrl; dci_payload.dmrs_sequence_initialization.val = pdsch_pdu->SCID; dci_payload.antenna_ports.val = sched_pdsch->dmrs_parms.dmrs_ports_id; dci_payload.tpc = sched_ctrl->tpc1; const NR_UE_harq_t *harq = &sched_ctrl->harq_processes[harq_pid]; AssertFatal(harq, "HARQ process should be available for DCI with RNTI %s\n", rnti_types(rnti_type)); dci_payload.harq_pid.val = harq_pid; dci_payload.ndi = harq->ndi; dci_payload.pucch_resource_indicator = pucch ? pucch->resource_indicator : 0; dci_payload.pdsch_to_harq_feedback_timing_indicator.val = pucch ? pucch->timing_indicator : 0; // PDSCH to HARQ TI dci_payload.dai[0].val = pucch ? (pucch->dai_c - 1) & 3 : 0; // bwp indicator // as per table 7.3.1.1.2-1 in 38.212 if (dl_BWP) dci_payload.bwp_indicator.val = UE->sc_info.n_dl_bwp < 4 ? dl_BWP->bwp_id : dl_BWP->bwp_id - 1; return dci_payload; } void config_uldci(const NR_UE_ServingCell_Info_t *sc_info, const nfapi_nr_pusch_pdu_t *pusch_pdu, dci_pdu_rel15_t *dci_pdu_rel15, nr_srs_feedback_t *srs_feedback, int *tpmi, int time_domain_assignment, uint8_t tpc, uint8_t ndi, NR_UE_UL_BWP_t *ul_bwp, NR_SearchSpace__searchSpaceType_PR ss_type) { int bwp_id = ul_bwp->bwp_id; nr_dci_format_t dci_format = ul_bwp->dci_format; // 3GPP TS 38.214 Section 6.1.2.2.2 Uplink resource allocation type 1 uint16_t riv_bwp_size = ul_bwp->BWPSize; if (dci_format == NR_UL_DCI_FORMAT_0_0 && ss_type == NR_SearchSpace__searchSpaceType_PR_common && ul_bwp->pusch_Config && ul_bwp->pusch_Config->resourceAllocation == NR_PUSCH_Config__resourceAllocation_resourceAllocationType1) { riv_bwp_size = sc_info->initial_ul_BWPSize; } dci_pdu_rel15->frequency_domain_assignment.val = PRBalloc_to_locationandbandwidth0(pusch_pdu->rb_size, pusch_pdu->rb_start, riv_bwp_size); dci_pdu_rel15->time_domain_assignment.val = time_domain_assignment; dci_pdu_rel15->frequency_hopping_flag.val = pusch_pdu->frequency_hopping; dci_pdu_rel15->mcs = pusch_pdu->mcs_index; dci_pdu_rel15->ndi = ndi; dci_pdu_rel15->rv = pusch_pdu->pusch_data.rv_index; dci_pdu_rel15->harq_pid.val = pusch_pdu->pusch_data.harq_process_id; dci_pdu_rel15->tpc = tpc; NR_PUSCH_Config_t *pusch_Config = ul_bwp->pusch_Config; if (pusch_Config) AssertFatal(pusch_Config->resourceAllocation == NR_PUSCH_Config__resourceAllocation_resourceAllocationType1, "Only frequency resource allocation type 1 is currently supported\n"); switch (dci_format) { case NR_UL_DCI_FORMAT_0_0: dci_pdu_rel15->format_indicator = 0; break; case NR_UL_DCI_FORMAT_0_1: LOG_D(NR_MAC,"Configuring DCI Format 0_1\n"); dci_pdu_rel15->dai[0].val = 0; //TODO // bwp indicator as per table 7.3.1.1.2-1 in 38.212 dci_pdu_rel15->bwp_indicator.val = sc_info->n_ul_bwp < 4 ? bwp_id : bwp_id - 1; // SRS resource indicator if (pusch_Config && pusch_Config->txConfig != NULL) { AssertFatal(*pusch_Config->txConfig == NR_PUSCH_Config__txConfig_codebook, "Non Codebook configuration non supported\n"); compute_srs_resource_indicator(sc_info->maxMIMO_Layers_PUSCH, pusch_Config, ul_bwp->srs_Config, srs_feedback, &dci_pdu_rel15->srs_resource_indicator.val); } compute_precoding_information(pusch_Config, ul_bwp->srs_Config, dci_pdu_rel15->srs_resource_indicator, srs_feedback, &pusch_pdu->nrOfLayers, tpmi, &dci_pdu_rel15->precoding_information.val); // antenna_ports.val = 0 for transform precoder is disabled, dmrs-Type=1, maxLength=1, Rank=1/2/3/4 // Antenna Ports dci_pdu_rel15->antenna_ports.val = NFAPI_MODE == NFAPI_MODE_AERIAL ? 2 : 0; // DMRS sequence initialization dci_pdu_rel15->dmrs_sequence_initialization.val = pusch_pdu->scid; break; default : AssertFatal(0, "Valid UL formats are 0_0 and 0_1\n"); } LOG_D(NR_MAC, "%s() ULDCI type 0 payload: dci_format %d, freq_alloc %d, time_alloc %d, freq_hop_flag %d, precoding_information.val %d antenna_ports.val %d mcs %d tpc %d ndi %d rv %d\n", __func__, dci_format, dci_pdu_rel15->frequency_domain_assignment.val, dci_pdu_rel15->time_domain_assignment.val, dci_pdu_rel15->frequency_hopping_flag.val, dci_pdu_rel15->precoding_information.val, dci_pdu_rel15->antenna_ports.val, dci_pdu_rel15->mcs, dci_pdu_rel15->tpc, dci_pdu_rel15->ndi, dci_pdu_rel15->rv); } const int default_pucch_fmt[] = {0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1}; const int default_pucch_firstsymb[] = {12,12,12,10,10,10,10,4,4,4,4,0,0,0,0,0}; const int default_pucch_numbsymb[] = {2,2,2,2,4,4,4,4,10,10,10,10,14,14,14,14,14}; const int default_pucch_prboffset[] = {0,0,3,0,0,2,4,0,0,2,4,0,0,2,4,-1}; const int default_pucch_csset[] = {2,3,3,2,4,4,4,2,4,4,4,2,4,4,4,4}; int nr_get_default_pucch_res(int pucch_ResourceCommon) { AssertFatal(pucch_ResourceCommon>=0 && pucch_ResourceCommon < 16, "illegal pucch_ResourceCommon %d\n",pucch_ResourceCommon); return(default_pucch_csset[pucch_ResourceCommon]); } void nr_configure_pdcch(nfapi_nr_dl_tti_pdcch_pdu_rel15_t *pdcch_pdu, NR_ControlResourceSet_t *coreset, NR_sched_pdcch_t *pdcch) { pdcch_pdu->BWPSize = pdcch->BWPSize; pdcch_pdu->BWPStart = pdcch->BWPStart; pdcch_pdu->SubcarrierSpacing = pdcch->SubcarrierSpacing; pdcch_pdu->CyclicPrefix = pdcch->CyclicPrefix; pdcch_pdu->StartSymbolIndex = pdcch->StartSymbolIndex; pdcch_pdu->DurationSymbols = coreset->duration; for (int i = 0; i < 6; i++) pdcch_pdu->FreqDomainResource[i] = coreset->frequencyDomainResources.buf[i]; LOG_D(NR_MAC, "Coreset : BWPstart %d, BWPsize %d, SCS %d, freq %x, , duration %ld\n", pdcch_pdu->BWPStart, pdcch_pdu->BWPSize, pdcch_pdu->SubcarrierSpacing, coreset->frequencyDomainResources.buf[0], coreset->duration); pdcch_pdu->CceRegMappingType = pdcch->CceRegMappingType; pdcch_pdu->RegBundleSize = pdcch->RegBundleSize; pdcch_pdu->InterleaverSize = pdcch->InterleaverSize; pdcch_pdu->ShiftIndex = pdcch->ShiftIndex; pdcch_pdu->CoreSetType = coreset->controlResourceSetId != 0 ? NFAPI_NR_CSET_CONFIG_PDCCH_CONFIG : NFAPI_NR_CSET_CONFIG_MIB_SIB1; //precoderGranularity pdcch_pdu->precoderGranularity = coreset->precoderGranularity; } int nr_get_pucch_resource(NR_ControlResourceSet_t *coreset, NR_PUCCH_Config_t *pucch_Config, int CCEIndex) { int r_pucch = -1; if(pucch_Config == NULL) { int n_rb,rb_offset; get_coreset_rballoc(coreset->frequencyDomainResources.buf,&n_rb,&rb_offset); const uint16_t N_cce = n_rb * coreset->duration / NR_NB_REG_PER_CCE; const int delta_PRI=0; r_pucch = ((CCEIndex<<1)/N_cce)+(delta_PRI<<1); } return r_pucch; } // This function configures pucch pdu fapi structure void nr_configure_pucch(nfapi_nr_pucch_pdu_t *pucch_pdu, NR_ServingCellConfigCommon_t *scc, NR_UE_info_t *UE, uint8_t pucch_resource, uint16_t O_csi, uint16_t O_ack, uint8_t O_sr, int r_pucch) { NR_PUCCH_Resource_t *pucchres; NR_PUCCH_FormatConfig_t *pucchfmt; NR_UE_UL_BWP_t *current_BWP = &UE->current_UL_BWP; int res_found = 0; pucch_pdu->bit_len_harq = O_ack; pucch_pdu->bit_len_csi_part1 = O_csi; uint16_t O_uci = O_csi + O_ack; NR_PUSCH_Config_t *pusch_Config = current_BWP->pusch_Config; long *pusch_id = pusch_Config ? pusch_Config->dataScramblingIdentityPUSCH : NULL; long *id0 = NULL; if (pusch_Config && pusch_Config->dmrs_UplinkForPUSCH_MappingTypeA != NULL && pusch_Config->dmrs_UplinkForPUSCH_MappingTypeA->choice.setup->transformPrecodingDisabled != NULL) id0 = pusch_Config->dmrs_UplinkForPUSCH_MappingTypeA->choice.setup->transformPrecodingDisabled->scramblingID0; else if (pusch_Config && pusch_Config->dmrs_UplinkForPUSCH_MappingTypeB != NULL && pusch_Config->dmrs_UplinkForPUSCH_MappingTypeB->choice.setup->transformPrecodingDisabled != NULL) id0 = pusch_Config->dmrs_UplinkForPUSCH_MappingTypeB->choice.setup->transformPrecodingDisabled->scramblingID0; else id0 = scc->physCellId; NR_PUCCH_ConfigCommon_t *pucch_ConfigCommon = current_BWP->pucch_ConfigCommon; // hop flags and hopping id are valid for any BWP switch (pucch_ConfigCommon->pucch_GroupHopping){ case 0 : // if neither, both disabled pucch_pdu->group_hop_flag = 0; pucch_pdu->sequence_hop_flag = 0; break; case 1 : // if enable, group enabled pucch_pdu->group_hop_flag = 1; pucch_pdu->sequence_hop_flag = 0; break; case 2 : // if disable, sequence disabled pucch_pdu->group_hop_flag = 0; pucch_pdu->sequence_hop_flag = 1; break; default: AssertFatal(1==0,"Group hopping flag %ld undefined (0,1,2) \n", pucch_ConfigCommon->pucch_GroupHopping); } if (pucch_ConfigCommon->hoppingId != NULL) pucch_pdu->hopping_id = *pucch_ConfigCommon->hoppingId; else pucch_pdu->hopping_id = *scc->physCellId; pucch_pdu->bwp_size = current_BWP->BWPSize; pucch_pdu->bwp_start = current_BWP->BWPStart; pucch_pdu->subcarrier_spacing = current_BWP->scs; pucch_pdu->cyclic_prefix = (current_BWP->cyclicprefix==NULL) ? 0 : *current_BWP->cyclicprefix; NR_PUCCH_Config_t *pucch_Config = current_BWP->pucch_Config; if (r_pucch < 0 || pucch_Config) { LOG_D(NR_MAC, "pucch_acknak: Filling dedicated configuration for PUCCH\n"); int resource_id = get_pucch_resourceid(pucch_Config, O_uci, pucch_resource); AssertFatal(pucch_Config->resourceToAddModList!=NULL, "PUCCH resourceToAddModList is null\n"); int n_list = pucch_Config->resourceToAddModList->list.count; AssertFatal(n_list > 0, "PUCCH resourceToAddModList is empty\n"); // going through the list of PUCCH resources to find the one indexed by resource_id for (int i = 0; i < n_list; i++) { pucchres = pucch_Config->resourceToAddModList->list.array[i]; if (pucchres->pucch_ResourceId == resource_id) { res_found = 1; pucch_pdu->prb_start = pucchres->startingPRB; pucch_pdu->rnti = UE->rnti; // FIXME why there is only one frequency hopping flag // what about inter slot frequency hopping? pucch_pdu->freq_hop_flag = pucchres->intraSlotFrequencyHopping ? 1 : 0; pucch_pdu->second_hop_prb = pucchres->secondHopPRB!= NULL ? *pucchres->secondHopPRB : 0; switch(pucchres->format.present) { case NR_PUCCH_Resource__format_PR_format0 : pucch_pdu->format_type = 0; pucch_pdu->initial_cyclic_shift = pucchres->format.choice.format0->initialCyclicShift; pucch_pdu->nr_of_symbols = pucchres->format.choice.format0->nrofSymbols; pucch_pdu->start_symbol_index = pucchres->format.choice.format0->startingSymbolIndex; pucch_pdu->sr_flag = O_sr; pucch_pdu->prb_size = 1; break; case NR_PUCCH_Resource__format_PR_format1 : pucch_pdu->format_type = 1; pucch_pdu->initial_cyclic_shift = pucchres->format.choice.format1->initialCyclicShift; pucch_pdu->nr_of_symbols = pucchres->format.choice.format1->nrofSymbols; pucch_pdu->start_symbol_index = pucchres->format.choice.format1->startingSymbolIndex; pucch_pdu->time_domain_occ_idx = pucchres->format.choice.format1->timeDomainOCC; pucch_pdu->sr_flag = O_sr; pucch_pdu->prb_size = 1; break; case NR_PUCCH_Resource__format_PR_format2 : pucch_pdu->format_type = 2; pucch_pdu->sr_flag = O_sr; pucch_pdu->nr_of_symbols = pucchres->format.choice.format2->nrofSymbols; pucch_pdu->start_symbol_index = pucchres->format.choice.format2->startingSymbolIndex; pucch_pdu->data_scrambling_id = pusch_id ? *pusch_id : *scc->physCellId; pucch_pdu->dmrs_scrambling_id = id0 ? *id0 : *scc->physCellId; pucch_pdu->prb_size = compute_pucch_prb_size(2, pucchres->format.choice.format2->nrofPRBs, O_csi, O_ack, O_sr, pucch_Config->format2->choice.setup->maxCodeRate, 2, pucchres->format.choice.format2->nrofSymbols, 8); pucch_pdu->bit_len_csi_part1 = O_csi; break; case NR_PUCCH_Resource__format_PR_format3 : pucch_pdu->format_type = 3; pucch_pdu->nr_of_symbols = pucchres->format.choice.format3->nrofSymbols; pucch_pdu->start_symbol_index = pucchres->format.choice.format3->startingSymbolIndex; pucch_pdu->data_scrambling_id = pusch_id ? *pusch_id : *scc->physCellId; if (pucch_Config->format3 == NULL) { pucch_pdu->pi_2bpsk = 0; pucch_pdu->add_dmrs_flag = 0; } else { pucchfmt = pucch_Config->format3->choice.setup; pucch_pdu->pi_2bpsk = pucchfmt->pi2BPSK ? 1 : 0; pucch_pdu->add_dmrs_flag = pucchfmt->additionalDMRS ? 1 : 0; } int f3_dmrs_symbols = get_f3_dmrs_symbols(pucchres, pucch_Config); pucch_pdu->prb_size = compute_pucch_prb_size(3, pucchres->format.choice.format3->nrofPRBs, O_csi, O_ack, O_sr, pucch_Config->format3->choice.setup->maxCodeRate, 2 - pucch_pdu->pi_2bpsk, pucchres->format.choice.format3->nrofSymbols - f3_dmrs_symbols, 12); pucch_pdu->bit_len_csi_part1 = O_csi; break; case NR_PUCCH_Resource__format_PR_format4 : pucch_pdu->format_type = 4; pucch_pdu->nr_of_symbols = pucchres->format.choice.format4->nrofSymbols; pucch_pdu->start_symbol_index = pucchres->format.choice.format4->startingSymbolIndex; pucch_pdu->pre_dft_occ_len = pucchres->format.choice.format4->occ_Length; pucch_pdu->pre_dft_occ_idx = pucchres->format.choice.format4->occ_Index; pucch_pdu->data_scrambling_id = pusch_id!= NULL ? *pusch_id : *scc->physCellId; if (pucch_Config->format3 == NULL) { pucch_pdu->pi_2bpsk = 0; pucch_pdu->add_dmrs_flag = 0; } else { pucchfmt = pucch_Config->format3->choice.setup; pucch_pdu->pi_2bpsk = pucchfmt->pi2BPSK != NULL; pucch_pdu->add_dmrs_flag = pucchfmt->additionalDMRS != NULL; } pucch_pdu->bit_len_csi_part1 = O_csi; break; default : AssertFatal(1==0,"Undefined PUCCH format \n"); } } } AssertFatal(res_found == 1, "No PUCCH resource found corresponding to id %d\n", resource_id); LOG_D(NR_MAC, "Configure pucch: pucch_pdu->format_type %d pucch_pdu->bit_len_harq %d, pucch->pdu->bit_len_csi %d\n", pucch_pdu->format_type, pucch_pdu->bit_len_harq, pucch_pdu->bit_len_csi_part1); } else { // this is the default PUCCH configuration, PUCCH format 0 or 1 LOG_D(NR_MAC, "pucch_acknak: Filling default PUCCH configuration from Tables (r_pucch %d, pucch_Config %p)\n", r_pucch, pucch_Config); int rsetindex = *pucch_ConfigCommon->pucch_ResourceCommon; int prb_start, second_hop_prb, nr_of_symb, start_symb; set_r_pucch_parms(rsetindex, r_pucch, pucch_pdu->bwp_size, &prb_start, &second_hop_prb, &nr_of_symb, &start_symb); pucch_pdu->prb_start = prb_start; pucch_pdu->rnti = UE->rnti; pucch_pdu->freq_hop_flag = 1; pucch_pdu->second_hop_prb = second_hop_prb; pucch_pdu->format_type = default_pucch_fmt[rsetindex]; pucch_pdu->initial_cyclic_shift = r_pucch%default_pucch_csset[rsetindex]; if (rsetindex==3||rsetindex==7||rsetindex==11) pucch_pdu->initial_cyclic_shift*=6; else if (rsetindex==1||rsetindex==2) pucch_pdu->initial_cyclic_shift*=4; else pucch_pdu->initial_cyclic_shift*=3; pucch_pdu->nr_of_symbols = nr_of_symb; pucch_pdu->start_symbol_index = start_symb; if (pucch_pdu->format_type == 1) pucch_pdu->time_domain_occ_idx = 0; // check this!! pucch_pdu->sr_flag = O_sr; pucch_pdu->prb_size=1; } // Beamforming pucch_pdu->beamforming.num_prgs = 0; pucch_pdu->beamforming.prg_size = 0; // pucch_pdu->prb_size; pucch_pdu->beamforming.dig_bf_interface = 0; pucch_pdu->beamforming.prgs_list[0].dig_bf_interface_list[0].beam_idx = UE->UE_beam_index; } void set_r_pucch_parms(int rsetindex, int r_pucch, int bwp_size, int *prb_start, int *second_hop_prb, int *nr_of_symbols, int *start_symbol_index) { // procedure described in 38.213 section 9.2.1 int prboffset = r_pucch/default_pucch_csset[rsetindex]; int prboffsetm8 = (r_pucch-8)/default_pucch_csset[rsetindex]; *prb_start = (r_pucch>>3)==0 ? default_pucch_prboffset[rsetindex] + prboffset: bwp_size-1-default_pucch_prboffset[rsetindex]-prboffsetm8; *second_hop_prb = (r_pucch>>3)==0? bwp_size-1-default_pucch_prboffset[rsetindex]-prboffset: default_pucch_prboffset[rsetindex] + prboffsetm8; *nr_of_symbols = default_pucch_numbsymb[rsetindex]; *start_symbol_index = default_pucch_firstsymb[rsetindex]; } static void prepare_dci_X1(const NR_UE_ServingCell_Info_t *servingCellInfo, const NR_UE_DL_BWP_t *current_BWP, const NR_ControlResourceSet_t *coreset, dci_pdu_rel15_t *dci_pdu_rel15, nr_dci_format_t format) { const NR_PDSCH_Config_t *pdsch_Config = current_BWP ? current_BWP->pdsch_Config : NULL; switch(format) { case NR_UL_DCI_FORMAT_0_1: // format indicator dci_pdu_rel15->format_indicator = 0; // carrier indicator if (servingCellInfo->crossCarrierSchedulingConfig != NULL) AssertFatal(1==0,"Cross Carrier Scheduling Config currently not supported\n"); // supplementary uplink if (servingCellInfo->supplementaryUplink != NULL) AssertFatal(1==0,"Supplementary Uplink currently not supported\n"); // SRS request dci_pdu_rel15->srs_request.val = 0; dci_pdu_rel15->ulsch_indicator = 1; break; case NR_DL_DCI_FORMAT_1_1: // format indicator dci_pdu_rel15->format_indicator = 1; // carrier indicator if (servingCellInfo->crossCarrierSchedulingConfig != NULL) AssertFatal(1==0,"Cross Carrier Scheduling Config currently not supported\n"); //vrb to prb mapping if (pdsch_Config->vrb_ToPRB_Interleaver==NULL) dci_pdu_rel15->vrb_to_prb_mapping.val = 0; else dci_pdu_rel15->vrb_to_prb_mapping.val = 1; //bundling size indicator if (pdsch_Config->prb_BundlingType.present == NR_PDSCH_Config__prb_BundlingType_PR_dynamicBundling) AssertFatal(1==0,"Dynamic PRB bundling type currently not supported\n"); //rate matching indicator uint16_t msb = (pdsch_Config->rateMatchPatternGroup1==NULL)?0:1; uint16_t lsb = (pdsch_Config->rateMatchPatternGroup2==NULL)?0:1; dci_pdu_rel15->rate_matching_indicator.val = lsb | (msb<<1); // aperiodic ZP CSI-RS trigger if (pdsch_Config->aperiodic_ZP_CSI_RS_ResourceSetsToAddModList != NULL) AssertFatal(1==0,"Aperiodic ZP CSI-RS currently not supported\n"); // transmission configuration indication if (coreset->tci_PresentInDCI != NULL) AssertFatal(1==0,"TCI in DCI currently not supported\n"); //srs resource set if (servingCellInfo->carrierSwitching) { if (servingCellInfo->carrierSwitching->srs_TPC_PDCCH_Group) { switch (servingCellInfo->carrierSwitching->srs_TPC_PDCCH_Group->present) { case NR_SRS_CarrierSwitching__srs_TPC_PDCCH_Group_PR_NOTHING: dci_pdu_rel15->srs_request.val = 0; break; case NR_SRS_CarrierSwitching__srs_TPC_PDCCH_Group_PR_typeA: AssertFatal(1==0,"SRS TPC PRCCH group type A currently not supported\n"); break; case NR_SRS_CarrierSwitching__srs_TPC_PDCCH_Group_PR_typeB: AssertFatal(1==0,"SRS TPC PRCCH group type B currently not supported\n"); break; } } else dci_pdu_rel15->srs_request.val = 0; } else dci_pdu_rel15->srs_request.val = 0; // CBGTI and CBGFI if (servingCellInfo->pdsch_CGB_Transmission) AssertFatal(1 == 0, "CBG transmission currently not supported\n"); break; default : AssertFatal(1==0,"Prepare dci currently only implemented for 1_1 and 0_1 \n"); } } void fill_dci_pdu_rel15(const NR_UE_ServingCell_Info_t *servingCellInfo, const NR_UE_DL_BWP_t *current_DL_BWP, const NR_UE_UL_BWP_t *current_UL_BWP, nfapi_nr_dl_dci_pdu_t *pdcch_dci_pdu, dci_pdu_rel15_t *dci_pdu_rel15, int dci_format, int rnti_type, int bwp_id, NR_SearchSpace_t *ss, NR_ControlResourceSet_t *coreset, long pdsch_HARQ_ACK_Codebook, uint16_t cset0_bwp_size) { uint8_t fsize = 0, pos = 0; uint64_t *dci_pdu = (uint64_t *)pdcch_dci_pdu->Payload; *dci_pdu = 0; uint16_t alt_size = 0; uint16_t N_RB; if(current_DL_BWP) { N_RB = get_rb_bwp_dci(dci_format, ss->searchSpaceType->present, cset0_bwp_size, current_UL_BWP->BWPSize, current_DL_BWP->BWPSize, servingCellInfo->initial_ul_BWPSize, servingCellInfo->initial_dl_BWPSize); // computing alternative size for padding dci_pdu_rel15_t temp_pdu; if(dci_format == NR_DL_DCI_FORMAT_1_0) alt_size = nr_dci_size(current_DL_BWP, current_UL_BWP, servingCellInfo, pdsch_HARQ_ACK_Codebook, &temp_pdu, NR_UL_DCI_FORMAT_0_0, rnti_type, coreset, bwp_id, ss->searchSpaceType->present, cset0_bwp_size, 0); if(dci_format == NR_UL_DCI_FORMAT_0_0) alt_size = nr_dci_size(current_DL_BWP, current_UL_BWP, servingCellInfo, pdsch_HARQ_ACK_Codebook, &temp_pdu, NR_DL_DCI_FORMAT_1_0, rnti_type, coreset, bwp_id, ss->searchSpaceType->present, cset0_bwp_size, 0); } else N_RB = cset0_bwp_size; int dci_size = nr_dci_size(current_DL_BWP, current_UL_BWP, servingCellInfo, pdsch_HARQ_ACK_Codebook, dci_pdu_rel15, dci_format, rnti_type, coreset, bwp_id, ss->searchSpaceType->present, cset0_bwp_size, alt_size); if (dci_size == 0) return; pdcch_dci_pdu->PayloadSizeBits = dci_size; AssertFatal(dci_size <= 64, "DCI sizes above 64 bits not yet supported"); if (dci_format == NR_DL_DCI_FORMAT_1_1 || dci_format == NR_UL_DCI_FORMAT_0_1) prepare_dci_X1(servingCellInfo, current_DL_BWP, coreset, dci_pdu_rel15, dci_format); /// Payload generation switch (dci_format) { case NR_DL_DCI_FORMAT_1_0: switch (rnti_type) { case TYPE_RA_RNTI_: // Freq domain assignment fsize = (int)ceil(log2((N_RB * (N_RB + 1)) >> 1)); pos = fsize; *dci_pdu |= (((uint64_t)dci_pdu_rel15->frequency_domain_assignment.val & ((1 << fsize) - 1)) << (dci_size - pos)); LOG_D(NR_MAC, "RA_RNTI, size %d frequency-domain assignment %d (%d bits) N_RB_BWP %d=> %d (0x%lx)\n", dci_size, dci_pdu_rel15->frequency_domain_assignment.val, fsize, N_RB, dci_size - pos, *dci_pdu); // Time domain assignment pos += 4; *dci_pdu |= (((uint64_t)dci_pdu_rel15->time_domain_assignment.val & 0xf) << (dci_size - pos)); LOG_D(NR_MAC, "time-domain assignment %d (4 bits)=> %d (0x%lx)\n", dci_pdu_rel15->time_domain_assignment.val, dci_size - pos, *dci_pdu); // VRB to PRB mapping pos++; *dci_pdu |= ((uint64_t)dci_pdu_rel15->vrb_to_prb_mapping.val & 0x1) << (dci_size - pos); LOG_D(NR_MAC, "vrb to prb mapping %d (1 bits)=> %d (0x%lx)\n", dci_pdu_rel15->vrb_to_prb_mapping.val, dci_size - pos, *dci_pdu); // MCS pos += 5; *dci_pdu |= ((uint64_t)dci_pdu_rel15->mcs & 0x1f) << (dci_size - pos); LOG_D(NR_MAC, "mcs %d (5 bits)=> %d (0x%lx)\n", dci_pdu_rel15->mcs, dci_size - pos, *dci_pdu); // TB scaling pos += 2; *dci_pdu |= ((uint64_t)dci_pdu_rel15->tb_scaling & 0x3) << (dci_size - pos); LOG_D(NR_MAC, "tb_scaling %d (2 bits)=> %d (0x%lx)\n", dci_pdu_rel15->tb_scaling, dci_size - pos, *dci_pdu); break; case TYPE_C_RNTI_: // indicating a DL DCI format 1bit pos++; *dci_pdu |= ((uint64_t)1) << (dci_size - pos); LOG_D(NR_MAC, "DCI1_0 (size %d): Format indicator %d (%d bits) N_RB_BWP %d => %d (0x%lx)\n", dci_size, dci_pdu_rel15->format_indicator, 1, N_RB, dci_size - pos, *dci_pdu); // Freq domain assignment (275rb >> fsize = 16) fsize = (int)ceil(log2((N_RB * (N_RB + 1)) >> 1)); pos += fsize; *dci_pdu |= (((uint64_t)dci_pdu_rel15->frequency_domain_assignment.val & ((1 << fsize) - 1)) << (dci_size - pos)); LOG_D(NR_MAC, "Freq domain assignment %d (%d bits)=> %d (0x%lx)\n", dci_pdu_rel15->frequency_domain_assignment.val, fsize, dci_size - pos, *dci_pdu); uint16_t is_ra = 1; for (int i = 0; i < fsize; i++) { if (!((dci_pdu_rel15->frequency_domain_assignment.val >> i) & 1)) { is_ra = 0; break; } } if (is_ra) { // fsize are all 1 38.212 p86 // ra_preamble_index 6 bits pos += 6; *dci_pdu |= ((dci_pdu_rel15->ra_preamble_index & 0x3f) << (dci_size - pos)); // UL/SUL indicator 1 bit pos++; *dci_pdu |= (dci_pdu_rel15->ul_sul_indicator.val & 1) << (dci_size - pos); // SS/PBCH index 6 bits pos += 6; *dci_pdu |= ((dci_pdu_rel15->ss_pbch_index & 0x3f) << (dci_size - pos)); // prach_mask_index 4 bits pos += 4; *dci_pdu |= ((dci_pdu_rel15->prach_mask_index & 0xf) << (dci_size - pos)); } else { // Time domain assignment 4bit pos += 4; *dci_pdu |= ((dci_pdu_rel15->time_domain_assignment.val & 0xf) << (dci_size - pos)); LOG_D(NR_MAC, "Time domain assignment %d (%d bits)=> %d (0x%lx)\n", dci_pdu_rel15->time_domain_assignment.val, 4, dci_size - pos, *dci_pdu); // VRB to PRB mapping 1bit pos++; *dci_pdu |= (dci_pdu_rel15->vrb_to_prb_mapping.val & 1) << (dci_size - pos); LOG_D(NR_MAC, "VRB to PRB %d (%d bits)=> %d (0x%lx)\n", dci_pdu_rel15->vrb_to_prb_mapping.val, 1, dci_size - pos, *dci_pdu); // MCS 5bit //bit over 32, so dci_pdu ++ pos += 5; *dci_pdu |= (dci_pdu_rel15->mcs & 0x1f) << (dci_size - pos); LOG_D(NR_MAC, "MCS %d (%d bits)=> %d (0x%lx)\n", dci_pdu_rel15->mcs, 5, dci_size - pos, *dci_pdu); // New data indicator 1bit pos++; *dci_pdu |= (dci_pdu_rel15->ndi & 1) << (dci_size - pos); LOG_D(NR_MAC, "NDI %d (%d bits)=> %d (0x%lx)\n", dci_pdu_rel15->ndi, 1, dci_size - pos, *dci_pdu); // Redundancy version 2bit pos += 2; *dci_pdu |= (dci_pdu_rel15->rv & 0x3) << (dci_size - pos); LOG_D(NR_MAC, "RV %d (%d bits)=> %d (0x%lx)\n", dci_pdu_rel15->rv, 2, dci_size - pos, *dci_pdu); // HARQ process number 4bit/5bit fsize = dci_pdu_rel15->harq_pid.nbits; pos += fsize; *dci_pdu |= ((dci_pdu_rel15->harq_pid.val & ((1 << fsize) - 1)) << (dci_size - pos)); LOG_D(NR_MAC, "HARQ_PID %d (%d bits)=> %d (0x%lx)\n", dci_pdu_rel15->harq_pid.val, fsize, dci_size - pos, *dci_pdu); // Downlink assignment index 2bit pos += 2; *dci_pdu |= ((dci_pdu_rel15->dai[0].val & 3) << (dci_size - pos)); LOG_D(NR_MAC, "DAI %d (%d bits)=> %d (0x%lx)\n", dci_pdu_rel15->dai[0].val, 2, dci_size - pos, *dci_pdu); // TPC command for scheduled PUCCH 2bit pos += 2; *dci_pdu |= ((dci_pdu_rel15->tpc & 3) << (dci_size - pos)); LOG_D(NR_MAC, "TPC %d (%d bits)=> %d (0x%lx)\n", dci_pdu_rel15->tpc, 2, dci_size - pos, *dci_pdu); // PUCCH resource indicator 3bit pos += 3; *dci_pdu |= ((dci_pdu_rel15->pucch_resource_indicator & 0x7) << (dci_size - pos)); LOG_D(NR_MAC, "PUCCH RI %d (%d bits)=> %d (0x%lx)\n", dci_pdu_rel15->pucch_resource_indicator, 3, dci_size - pos, *dci_pdu); // PDSCH-to-HARQ_feedback timing indicator 3bit pos += 3; *dci_pdu |= ((dci_pdu_rel15->pdsch_to_harq_feedback_timing_indicator.val & 0x7) << (dci_size - pos)); LOG_D(NR_MAC, "PDSCH to HARQ TI %d (%d bits)=> %d (0x%lx)\n", dci_pdu_rel15->pdsch_to_harq_feedback_timing_indicator.val, 3, dci_size - pos, *dci_pdu); } // end else break; case TYPE_P_RNTI_: // Short Messages Indicator – 2 bits for (int i = 0; i < 2; i++) *dci_pdu |= (((uint64_t)dci_pdu_rel15->short_messages_indicator >> (1 - i)) & 1) << (dci_size - pos++); // Short Messages – 8 bits for (int i = 0; i < 8; i++) *dci_pdu |= (((uint64_t)dci_pdu_rel15->short_messages >> (7 - i)) & 1) << (dci_size - pos++); // Freq domain assignment 0-16 bit fsize = (int)ceil(log2((N_RB * (N_RB + 1)) >> 1)); for (int i = 0; i < fsize; i++) *dci_pdu |= (((uint64_t)dci_pdu_rel15->frequency_domain_assignment.val >> (fsize - i - 1)) & 1) << (dci_size - pos++); // Time domain assignment 4 bit for (int i = 0; i < 4; i++) *dci_pdu |= (((uint64_t)dci_pdu_rel15->time_domain_assignment.val >> (3 - i)) & 1) << (dci_size - pos++); // VRB to PRB mapping 1 bit *dci_pdu |= ((uint64_t)dci_pdu_rel15->vrb_to_prb_mapping.val & 1) << (dci_size - pos++); // MCS 5 bit for (int i = 0; i < 5; i++) *dci_pdu |= (((uint64_t)dci_pdu_rel15->mcs >> (4 - i)) & 1) << (dci_size - pos++); // TB scaling 2 bit for (int i = 0; i < 2; i++) *dci_pdu |= (((uint64_t)dci_pdu_rel15->tb_scaling >> (1 - i)) & 1) << (dci_size - pos++); break; case TYPE_SI_RNTI_: pos = 1; // Freq domain assignment 0-16 bit fsize = (int)ceil(log2((N_RB * (N_RB + 1)) >> 1)); LOG_D(NR_MAC, "fsize = %i\n", fsize); for (int i = 0; i < fsize; i++) *dci_pdu |= (((uint64_t)dci_pdu_rel15->frequency_domain_assignment.val >> (fsize - i - 1)) & 1) << (dci_size - pos++); LOG_D(NR_MAC, "dci_pdu_rel15->frequency_domain_assignment.val = %i\n", dci_pdu_rel15->frequency_domain_assignment.val); // Time domain assignment 4 bit for (int i = 0; i < 4; i++) *dci_pdu |= (((uint64_t)dci_pdu_rel15->time_domain_assignment.val >> (3 - i)) & 1) << (dci_size - pos++); LOG_D(NR_MAC, "dci_pdu_rel15->time_domain_assignment.val = %i\n", dci_pdu_rel15->time_domain_assignment.val); // VRB to PRB mapping 1 bit *dci_pdu |= ((uint64_t)dci_pdu_rel15->vrb_to_prb_mapping.val & 1) << (dci_size - pos++); LOG_D(NR_MAC, "dci_pdu_rel15->vrb_to_prb_mapping.val = %i\n", dci_pdu_rel15->vrb_to_prb_mapping.val); // MCS 5bit //bit over 32, so dci_pdu ++ for (int i = 0; i < 5; i++) *dci_pdu |= (((uint64_t)dci_pdu_rel15->mcs >> (4 - i)) & 1) << (dci_size - pos++); LOG_D(NR_MAC, "dci_pdu_rel15->mcs = %i\n", dci_pdu_rel15->mcs); // Redundancy version 2bit for (int i = 0; i < 2; i++) *dci_pdu |= (((uint64_t)dci_pdu_rel15->rv >> (1 - i)) & 1) << (dci_size - pos++); LOG_D(NR_MAC, "dci_pdu_rel15->rv = %i\n", dci_pdu_rel15->rv); // System information indicator 1bit *dci_pdu |= ((uint64_t)dci_pdu_rel15->system_info_indicator & 1) << (dci_size - pos++); LOG_D(NR_MAC, "dci_pdu_rel15->system_info_indicator = %i\n", dci_pdu_rel15->system_info_indicator); break; case TYPE_TC_RNTI_: pos = 1; // indicating a DL DCI format 1bit *dci_pdu |= ((uint64_t)dci_pdu_rel15->format_indicator & 1) << (dci_size - pos++); // Freq domain assignment 0-16 bit fsize = (int)ceil(log2((N_RB * (N_RB + 1)) >> 1)); for (int i = 0; i < fsize; i++) *dci_pdu |= (((uint64_t)dci_pdu_rel15->frequency_domain_assignment.val >> (fsize - i - 1)) & 1) << (dci_size - pos++); // Time domain assignment 4 bit for (int i = 0; i < 4; i++) *dci_pdu |= (((uint64_t)dci_pdu_rel15->time_domain_assignment.val >> (3 - i)) & 1) << (dci_size - pos++); // VRB to PRB mapping 1 bit *dci_pdu |= ((uint64_t)dci_pdu_rel15->vrb_to_prb_mapping.val & 1) << (dci_size - pos++); // MCS 5bit //bit over 32, so dci_pdu ++ for (int i = 0; i < 5; i++) *dci_pdu |= (((uint64_t)dci_pdu_rel15->mcs >> (4 - i)) & 1) << (dci_size - pos++); // New data indicator 1bit *dci_pdu |= ((uint64_t)dci_pdu_rel15->ndi & 1) << (dci_size - pos++); // Redundancy version 2bit for (int i = 0; i < 2; i++) *dci_pdu |= (((uint64_t)dci_pdu_rel15->rv >> (1 - i)) & 1) << (dci_size - pos++); // HARQ process number 4bit/5bit fsize = dci_pdu_rel15->harq_pid.nbits; for (int i = 0; i < fsize; i++) *dci_pdu |= (((uint64_t)dci_pdu_rel15->harq_pid.val >> (fsize - i - 1)) & 1) << (dci_size - pos++); // Downlink assignment index – 2 bits for (int i = 0; i < 2; i++) *dci_pdu |= (((uint64_t)dci_pdu_rel15->dai[0].val >> (1 - i)) & 1) << (dci_size - pos++); // TPC command for scheduled PUCCH – 2 bits for (int i = 0; i < 2; i++) *dci_pdu |= (((uint64_t)dci_pdu_rel15->tpc >> (1 - i)) & 1) << (dci_size - pos++); // PUCCH resource indicator – 3 bits for (int i = 0; i < 3; i++) *dci_pdu |= (((uint64_t)dci_pdu_rel15->pucch_resource_indicator >> (2 - i)) & 1) << (dci_size - pos++); // PDSCH-to-HARQ_feedback timing indicator – 3 bits for (int i = 0; i < 3; i++) *dci_pdu |= (((uint64_t)dci_pdu_rel15->pdsch_to_harq_feedback_timing_indicator.val >> (2 - i)) & 1) << (dci_size - pos++); LOG_D(NR_MAC, "N_RB = %i\n", N_RB); LOG_D(NR_MAC, "dci_size = %i\n", dci_size); LOG_D(NR_MAC, "fsize = %i\n", fsize); LOG_D(NR_MAC, "dci_pdu_rel15->format_indicator = %i\n", dci_pdu_rel15->format_indicator); LOG_D(NR_MAC, "dci_pdu_rel15->frequency_domain_assignment.val = %i\n", dci_pdu_rel15->frequency_domain_assignment.val); LOG_D(NR_MAC, "dci_pdu_rel15->time_domain_assignment.val = %i\n", dci_pdu_rel15->time_domain_assignment.val); LOG_D(NR_MAC, "dci_pdu_rel15->vrb_to_prb_mapping.val = %i\n", dci_pdu_rel15->vrb_to_prb_mapping.val); LOG_D(NR_MAC, "dci_pdu_rel15->mcs = %i\n", dci_pdu_rel15->mcs); LOG_D(NR_MAC, "dci_pdu_rel15->rv = %i\n", dci_pdu_rel15->rv); LOG_D(NR_MAC, "dci_pdu_rel15->harq_pid = %i\n", dci_pdu_rel15->harq_pid.val); LOG_D(NR_MAC, "dci_pdu_rel15->dai[0].val = %i\n", dci_pdu_rel15->dai[0].val); LOG_D(NR_MAC, "dci_pdu_rel15->tpc = %i\n", dci_pdu_rel15->tpc); LOG_D(NR_MAC, "dci_pdu_rel15->pdsch_to_harq_feedback_timing_indicator.val = %i\n", dci_pdu_rel15->pdsch_to_harq_feedback_timing_indicator.val); break; } break; case NR_UL_DCI_FORMAT_0_0: switch (rnti_type) { case TYPE_C_RNTI_: LOG_D(NR_MAC, "Filling format 0_0 DCI for CRNTI (size %d bits, format ind %d)\n", dci_size, dci_pdu_rel15->format_indicator); // indicating a UL DCI format 1bit pos = 1; *dci_pdu |= ((uint64_t)dci_pdu_rel15->format_indicator & 1) << (dci_size - pos); // Freq domain assignment max 16 bit fsize = dci_pdu_rel15->frequency_domain_assignment.nbits; pos += fsize; *dci_pdu |= ((uint64_t)dci_pdu_rel15->frequency_domain_assignment.val & ((1 << fsize) - 1)) << (dci_size - pos); // Time domain assignment 4bit pos += 4; *dci_pdu |= ((uint64_t)dci_pdu_rel15->time_domain_assignment.val & ((1 << 4) - 1)) << (dci_size - pos); // Frequency hopping flag – 1 bit pos++; *dci_pdu |= ((uint64_t)dci_pdu_rel15->frequency_hopping_flag.val & 1) << (dci_size - pos); // MCS 5 bit pos += 5; *dci_pdu |= ((uint64_t)dci_pdu_rel15->mcs & 0x1f) << (dci_size - pos); // New data indicator 1bit pos++; *dci_pdu |= ((uint64_t)dci_pdu_rel15->ndi & 1) << (dci_size - pos); // Redundancy version 2bit pos += 2; *dci_pdu |= ((uint64_t)dci_pdu_rel15->rv & 0x3) << (dci_size - pos); // HARQ process number 4bit/5bit fsize = dci_pdu_rel15->harq_pid.nbits; pos += fsize; *dci_pdu |= ((uint64_t)dci_pdu_rel15->harq_pid.val & ((1 << fsize) - 1)) << (dci_size - pos); // TPC command for scheduled PUSCH – 2 bits pos += 2; *dci_pdu |= ((uint64_t)dci_pdu_rel15->tpc & 0x3) << (dci_size - pos); // Padding bits for (int a = pos; a < dci_size; a++) *dci_pdu |= ((uint64_t)dci_pdu_rel15->padding & 1) << (dci_size - pos++); // UL/SUL indicator – 1 bit /* commented for now (RK): need to get this from BWP descriptor if (cfg->pucch_config.pucch_GroupHopping.value) *dci_pdu |= ((uint64_t)dci_pdu_rel15->ul_sul_indicator.val&1)<<(dci_size-pos++); */ LOG_D(NR_MAC, "N_RB = %i\n", N_RB); LOG_D(NR_MAC, "dci_size = %i\n", dci_size); LOG_D(NR_MAC, "fsize = %i\n", fsize); LOG_D(NR_MAC, "dci_pdu_rel15->frequency_domain_assignment.val = %i\n", dci_pdu_rel15->frequency_domain_assignment.val); LOG_D(NR_MAC, "dci_pdu_rel15->time_domain_assignment.val = %i\n", dci_pdu_rel15->time_domain_assignment.val); LOG_D(NR_MAC, "dci_pdu_rel15->frequency_hopping_flag.val = %i\n", dci_pdu_rel15->frequency_hopping_flag.val); LOG_D(NR_MAC, "dci_pdu_rel15->mcs = %i\n", dci_pdu_rel15->mcs); LOG_D(NR_MAC, "dci_pdu_rel15->ndi = %i\n", dci_pdu_rel15->ndi); LOG_D(NR_MAC, "dci_pdu_rel15->rv = %i\n", dci_pdu_rel15->rv); LOG_D(NR_MAC, "dci_pdu_rel15->harq_pid = %i\n", dci_pdu_rel15->harq_pid.val); LOG_D(NR_MAC, "dci_pdu_rel15->tpc = %i\n", dci_pdu_rel15->tpc); LOG_D(NR_MAC, "dci_pdu_rel15->padding = %i\n", dci_pdu_rel15->padding); break; case TYPE_TC_RNTI_: // indicating a UL DCI format 1bit pos = 1; *dci_pdu |= ((uint64_t)dci_pdu_rel15->format_indicator & 1) << (dci_size - pos); // Freq domain assignment max 16 bit fsize = dci_pdu_rel15->frequency_domain_assignment.nbits; pos += fsize; *dci_pdu |= ((uint64_t)dci_pdu_rel15->frequency_domain_assignment.val & ((1 << fsize) - 1)) << (dci_size - pos); // Time domain assignment 4bit pos += 4; *dci_pdu |= ((uint64_t)dci_pdu_rel15->time_domain_assignment.val & ((1 << 4) - 1)) << (dci_size - pos); // Frequency hopping flag – 1 bit pos++; *dci_pdu |= ((uint64_t)dci_pdu_rel15->frequency_hopping_flag.val & 1) << (dci_size - pos); // MCS 5 bit pos += 5; *dci_pdu |= ((uint64_t)dci_pdu_rel15->mcs & 0x1f) << (dci_size - pos); // New data indicator 1bit pos++; *dci_pdu |= ((uint64_t)dci_pdu_rel15->ndi & 1) << (dci_size - pos); // Redundancy version 2bit pos += 2; *dci_pdu |= ((uint64_t)dci_pdu_rel15->rv & 0x3) << (dci_size - pos); // HARQ process number 4bit/5bit fsize = dci_pdu_rel15->harq_pid.nbits; pos += fsize; *dci_pdu |= ((uint64_t)dci_pdu_rel15->harq_pid.val & ((1 << fsize) - 1)) << (dci_size - pos); // Padding bits for (int a = pos; a < dci_size; a++) *dci_pdu |= ((uint64_t)dci_pdu_rel15->padding & 1) << (dci_size - pos++); // UL/SUL indicator – 1 bit /* commented for now (RK): need to get this from BWP descriptor if (cfg->pucch_config.pucch_GroupHopping.value) *dci_pdu |= ((uint64_t)dci_pdu_rel15->ul_sul_indicator.val&1)<<(dci_size-pos++); */ LOG_D(NR_MAC, "N_RB = %i\n", N_RB); LOG_D(NR_MAC, "dci_size = %i\n", dci_size); LOG_D(NR_MAC, "fsize = %i\n", fsize); LOG_D(NR_MAC, "dci_pdu_rel15->frequency_domain_assignment.val = %i\n", dci_pdu_rel15->frequency_domain_assignment.val); LOG_D(NR_MAC, "dci_pdu_rel15->time_domain_assignment.val = %i\n", dci_pdu_rel15->time_domain_assignment.val); LOG_D(NR_MAC, "dci_pdu_rel15->frequency_hopping_flag.val = %i\n", dci_pdu_rel15->frequency_hopping_flag.val); LOG_D(NR_MAC, "dci_pdu_rel15->mcs = %i\n", dci_pdu_rel15->mcs); LOG_D(NR_MAC, "dci_pdu_rel15->ndi = %i\n", dci_pdu_rel15->ndi); LOG_D(NR_MAC, "dci_pdu_rel15->rv = %i\n", dci_pdu_rel15->rv); LOG_D(NR_MAC, "dci_pdu_rel15->harq_pid = %i\n", dci_pdu_rel15->harq_pid.val); LOG_D(NR_MAC, "dci_pdu_rel15->tpc = %i\n", dci_pdu_rel15->tpc); LOG_D(NR_MAC, "dci_pdu_rel15->padding = %i\n", dci_pdu_rel15->padding); break; } break; case NR_UL_DCI_FORMAT_0_1: switch (rnti_type) { case TYPE_C_RNTI_: LOG_D(NR_MAC, "Filling NR_UL_DCI_FORMAT_0_1 size %d format indicator %d\n", dci_size, dci_pdu_rel15->format_indicator); // Indicating a DL DCI format 1bit pos = 1; *dci_pdu |= ((uint64_t)dci_pdu_rel15->format_indicator & 0x1) << (dci_size - pos); // Carrier indicator pos += dci_pdu_rel15->carrier_indicator.nbits; *dci_pdu |= ((uint64_t)dci_pdu_rel15->carrier_indicator.val & ((1 << dci_pdu_rel15->carrier_indicator.nbits) - 1)) << (dci_size - pos); // UL/SUL Indicator pos += dci_pdu_rel15->ul_sul_indicator.nbits; *dci_pdu |= ((uint64_t)dci_pdu_rel15->ul_sul_indicator.val & ((1 << dci_pdu_rel15->ul_sul_indicator.nbits) - 1)) << (dci_size - pos); // BWP indicator pos += dci_pdu_rel15->bwp_indicator.nbits; *dci_pdu |= ((uint64_t)dci_pdu_rel15->bwp_indicator.val & ((1 << dci_pdu_rel15->bwp_indicator.nbits) - 1)) << (dci_size - pos); // Frequency domain resource assignment pos += dci_pdu_rel15->frequency_domain_assignment.nbits; *dci_pdu |= ((uint64_t)dci_pdu_rel15->frequency_domain_assignment.val & ((1 << dci_pdu_rel15->frequency_domain_assignment.nbits) - 1)) << (dci_size - pos); // Time domain resource assignment pos += dci_pdu_rel15->time_domain_assignment.nbits; *dci_pdu |= ((uint64_t)dci_pdu_rel15->time_domain_assignment.val & ((1 << dci_pdu_rel15->time_domain_assignment.nbits) - 1)) << (dci_size - pos); // Frequency hopping pos += dci_pdu_rel15->frequency_hopping_flag.nbits; *dci_pdu |= ((uint64_t)dci_pdu_rel15->frequency_hopping_flag.val & ((1 << dci_pdu_rel15->frequency_hopping_flag.nbits) - 1)) << (dci_size - pos); // MCS 5bit pos += 5; *dci_pdu |= ((uint64_t)dci_pdu_rel15->mcs & 0x1f) << (dci_size - pos); // New data indicator 1bit pos += 1; *dci_pdu |= ((uint64_t)dci_pdu_rel15->ndi & 0x1) << (dci_size - pos); // Redundancy version 2bit pos += 2; *dci_pdu |= ((uint64_t)dci_pdu_rel15->rv & 0x3) << (dci_size - pos); // HARQ process number 4bit/5bit fsize = dci_pdu_rel15->harq_pid.nbits; pos += fsize; *dci_pdu |= ((uint64_t)dci_pdu_rel15->harq_pid.val & ((1 << fsize) - 1)) << (dci_size - pos); // 1st Downlink assignment index pos += dci_pdu_rel15->dai[0].nbits; *dci_pdu |= ((uint64_t)dci_pdu_rel15->dai[0].val & ((1 << dci_pdu_rel15->dai[0].nbits) - 1)) << (dci_size - pos); // 2nd Downlink assignment index pos += dci_pdu_rel15->dai[1].nbits; *dci_pdu |= ((uint64_t)dci_pdu_rel15->dai[1].val & ((1 << dci_pdu_rel15->dai[1].nbits) - 1)) << (dci_size - pos); // TPC command for scheduled PUSCH 2bit pos += 2; *dci_pdu |= ((uint64_t)dci_pdu_rel15->tpc & 0x3) << (dci_size - pos); // SRS resource indicator pos += dci_pdu_rel15->srs_resource_indicator.nbits; *dci_pdu |= ((uint64_t)dci_pdu_rel15->srs_resource_indicator.val & ((1 << dci_pdu_rel15->srs_resource_indicator.nbits) - 1)) << (dci_size - pos); // Precoding info and n. of layers pos += dci_pdu_rel15->precoding_information.nbits; *dci_pdu |= ((uint64_t)dci_pdu_rel15->precoding_information.val & ((1 << dci_pdu_rel15->precoding_information.nbits) - 1)) << (dci_size - pos); // Antenna ports pos += dci_pdu_rel15->antenna_ports.nbits; *dci_pdu |= ((uint64_t)dci_pdu_rel15->antenna_ports.val & ((1 << dci_pdu_rel15->antenna_ports.nbits) - 1)) << (dci_size - pos); // SRS request pos += dci_pdu_rel15->srs_request.nbits; *dci_pdu |= ((uint64_t)dci_pdu_rel15->srs_request.val & ((1 << dci_pdu_rel15->srs_request.nbits) - 1)) << (dci_size - pos); // CSI request pos += dci_pdu_rel15->csi_request.nbits; *dci_pdu |= ((uint64_t)dci_pdu_rel15->csi_request.val & ((1 << dci_pdu_rel15->csi_request.nbits) - 1)) << (dci_size - pos); // CBG transmission information pos += dci_pdu_rel15->cbgti.nbits; *dci_pdu |= ((uint64_t)dci_pdu_rel15->cbgti.val & ((1 << dci_pdu_rel15->cbgti.nbits) - 1)) << (dci_size - pos); // PTRS DMRS association pos += dci_pdu_rel15->ptrs_dmrs_association.nbits; *dci_pdu |= ((uint64_t)dci_pdu_rel15->ptrs_dmrs_association.val & ((1 << dci_pdu_rel15->ptrs_dmrs_association.nbits) - 1)) << (dci_size - pos); // Beta offset indicator pos += dci_pdu_rel15->beta_offset_indicator.nbits; *dci_pdu |= ((uint64_t)dci_pdu_rel15->beta_offset_indicator.val & ((1 << dci_pdu_rel15->beta_offset_indicator.nbits) - 1)) << (dci_size - pos); // DMRS sequence initialization pos += dci_pdu_rel15->dmrs_sequence_initialization.nbits; *dci_pdu |= ((uint64_t)dci_pdu_rel15->dmrs_sequence_initialization.val & ((1 << dci_pdu_rel15->dmrs_sequence_initialization.nbits) - 1)) << (dci_size - pos); // UL-SCH indicator pos += 1; *dci_pdu |= ((uint64_t)dci_pdu_rel15->ulsch_indicator & 0x1) << (dci_size - pos); #ifdef DEBUG_DCI LOG_I(NR_MAC,"============= NR_UL_DCI_FORMAT_0_1 =============\n"); LOG_I(NR_MAC,"dci_size = %i\n", dci_size); LOG_I(NR_MAC,"dci_pdu_rel15->format_indicator = %i\n", dci_pdu_rel15->format_indicator); LOG_I(NR_MAC,"dci_pdu_rel15->carrier_indicator.val = %i\n", dci_pdu_rel15->carrier_indicator.val); LOG_I(NR_MAC,"dci_pdu_rel15->ul_sul_indicator.val = %i\n", dci_pdu_rel15->ul_sul_indicator.val); LOG_I(NR_MAC,"dci_pdu_rel15->bwp_indicator.val = %i\n", dci_pdu_rel15->bwp_indicator.val); LOG_I(NR_MAC,"dci_pdu_rel15->frequency_domain_assignment.val = %i\n", dci_pdu_rel15->frequency_domain_assignment.val); LOG_I(NR_MAC,"dci_pdu_rel15->time_domain_assignment.val = %i\n", dci_pdu_rel15->time_domain_assignment.val); LOG_I(NR_MAC,"dci_pdu_rel15->frequency_hopping_flag.val = %i\n", dci_pdu_rel15->frequency_hopping_flag.val); LOG_I(NR_MAC,"dci_pdu_rel15->mcs = %i\n", dci_pdu_rel15->mcs); LOG_I(NR_MAC,"dci_pdu_rel15->ndi = %i\n", dci_pdu_rel15->ndi); LOG_I(NR_MAC,"dci_pdu_rel15->rv= %i\n", dci_pdu_rel15->rv); LOG_I(NR_MAC,"dci_pdu_rel15->harq_pid = %i\n", dci_pdu_rel15->harq_pid); LOG_I(NR_MAC,"dci_pdu_rel15->dai[0].val = %i\n", dci_pdu_rel15->dai[0].val); LOG_I(NR_MAC,"dci_pdu_rel15->dai[1].val = %i\n", dci_pdu_rel15->dai[1].val); LOG_I(NR_MAC,"dci_pdu_rel15->tpc = %i\n", dci_pdu_rel15->tpc); LOG_I(NR_MAC,"dci_pdu_rel15->srs_resource_indicator.val = %i\n", dci_pdu_rel15->srs_resource_indicator.val); LOG_I(NR_MAC,"dci_pdu_rel15->precoding_information.val = %i\n", dci_pdu_rel15->precoding_information.val); LOG_I(NR_MAC,"dci_pdu_rel15->antenna_ports.val = %i\n", dci_pdu_rel15->antenna_ports.val); LOG_I(NR_MAC,"dci_pdu_rel15->srs_request.val = %i\n", dci_pdu_rel15->srs_request.val); LOG_I(NR_MAC,"dci_pdu_rel15->csi_request.val = %i\n", dci_pdu_rel15->csi_request.val); LOG_I(NR_MAC,"dci_pdu_rel15->cbgti.val = %i\n", dci_pdu_rel15->cbgti.val); LOG_I(NR_MAC,"dci_pdu_rel15->ptrs_dmrs_association.val = %i\n", dci_pdu_rel15->ptrs_dmrs_association.val); LOG_I(NR_MAC,"dci_pdu_rel15->beta_offset_indicator.val = %i\n", dci_pdu_rel15->beta_offset_indicator.val); LOG_I(NR_MAC,"dci_pdu_rel15->dmrs_sequence_initialization.val = %i\n", dci_pdu_rel15->dmrs_sequence_initialization.val); LOG_I(NR_MAC,"dci_pdu_rel15->ulsch_indicator = %i\n", dci_pdu_rel15->ulsch_indicator); #endif break; } break; case NR_DL_DCI_FORMAT_1_1: // Indicating a DL DCI format 1bit LOG_D(NR_MAC,"Filling Format 1_1 DCI of size %d\n",dci_size); pos = 1; *dci_pdu |= ((uint64_t)dci_pdu_rel15->format_indicator & 0x1) << (dci_size - pos); // Carrier indicator pos += dci_pdu_rel15->carrier_indicator.nbits; *dci_pdu |= ((uint64_t)dci_pdu_rel15->carrier_indicator.val & ((1 << dci_pdu_rel15->carrier_indicator.nbits) - 1)) << (dci_size - pos); // BWP indicator pos += dci_pdu_rel15->bwp_indicator.nbits; *dci_pdu |= ((uint64_t)dci_pdu_rel15->bwp_indicator.val & ((1 << dci_pdu_rel15->bwp_indicator.nbits) - 1)) << (dci_size - pos); // Frequency domain resource assignment pos += dci_pdu_rel15->frequency_domain_assignment.nbits; *dci_pdu |= ((uint64_t)dci_pdu_rel15->frequency_domain_assignment.val & ((1 << dci_pdu_rel15->frequency_domain_assignment.nbits) - 1)) << (dci_size - pos); // Time domain resource assignment pos += dci_pdu_rel15->time_domain_assignment.nbits; *dci_pdu |= ((uint64_t)dci_pdu_rel15->time_domain_assignment.val & ((1 << dci_pdu_rel15->time_domain_assignment.nbits) - 1)) << (dci_size - pos); // VRB-to-PRB mapping pos += dci_pdu_rel15->vrb_to_prb_mapping.nbits; *dci_pdu |= ((uint64_t)dci_pdu_rel15->vrb_to_prb_mapping.val & ((1 << dci_pdu_rel15->vrb_to_prb_mapping.nbits) - 1)) << (dci_size - pos); // PRB bundling size indicator pos += dci_pdu_rel15->prb_bundling_size_indicator.nbits; *dci_pdu |= ((uint64_t)dci_pdu_rel15->prb_bundling_size_indicator.val & ((1 << dci_pdu_rel15->prb_bundling_size_indicator.nbits) - 1)) << (dci_size - pos); // Rate matching indicator pos += dci_pdu_rel15->rate_matching_indicator.nbits; *dci_pdu |= ((uint64_t)dci_pdu_rel15->rate_matching_indicator.val & ((1 << dci_pdu_rel15->rate_matching_indicator.nbits) - 1)) << (dci_size - pos); // ZP CSI-RS trigger pos += dci_pdu_rel15->zp_csi_rs_trigger.nbits; *dci_pdu |= ((uint64_t)dci_pdu_rel15->zp_csi_rs_trigger.val & ((1 << dci_pdu_rel15->zp_csi_rs_trigger.nbits) - 1)) << (dci_size - pos); // TB1 // MCS 5bit pos += 5; *dci_pdu |= ((uint64_t)dci_pdu_rel15->mcs & 0x1f) << (dci_size - pos); // New data indicator 1bit pos += 1; *dci_pdu |= ((uint64_t)dci_pdu_rel15->ndi & 0x1) << (dci_size - pos); // Redundancy version 2bit pos += 2; *dci_pdu |= ((uint64_t)dci_pdu_rel15->rv & 0x3) << (dci_size - pos); // TB2 // MCS 5bit pos += dci_pdu_rel15->mcs2.nbits; *dci_pdu |= ((uint64_t)dci_pdu_rel15->mcs2.val & ((1 << dci_pdu_rel15->mcs2.nbits) - 1)) << (dci_size - pos); // New data indicator 1bit pos += dci_pdu_rel15->ndi2.nbits; *dci_pdu |= ((uint64_t)dci_pdu_rel15->ndi2.val & ((1 << dci_pdu_rel15->ndi2.nbits) - 1)) << (dci_size - pos); // Redundancy version 2bit pos += dci_pdu_rel15->rv2.nbits; *dci_pdu |= ((uint64_t)dci_pdu_rel15->rv2.val & ((1 << dci_pdu_rel15->rv2.nbits) - 1)) << (dci_size - pos); // HARQ process number 4bit/5bit fsize = dci_pdu_rel15->harq_pid.nbits; pos += fsize; *dci_pdu |= ((uint64_t)dci_pdu_rel15->harq_pid.val & ((1 << fsize) - 1)) << (dci_size - pos); // Downlink assignment index pos += dci_pdu_rel15->dai[0].nbits; *dci_pdu |= ((uint64_t)dci_pdu_rel15->dai[0].val & ((1 << dci_pdu_rel15->dai[0].nbits) - 1)) << (dci_size - pos); // TPC command for scheduled PUCCH 2bit pos += 2; *dci_pdu |= ((uint64_t)dci_pdu_rel15->tpc & 0x3) << (dci_size - pos); // PUCCH resource indicator 3bit pos += 3; *dci_pdu |= ((uint64_t)dci_pdu_rel15->pucch_resource_indicator & 0x7) << (dci_size - pos); // PDSCH-to-HARQ_feedback timing indicator pos += dci_pdu_rel15->pdsch_to_harq_feedback_timing_indicator.nbits; *dci_pdu |= ((uint64_t)dci_pdu_rel15->pdsch_to_harq_feedback_timing_indicator.val & ((1 << dci_pdu_rel15->pdsch_to_harq_feedback_timing_indicator.nbits) - 1)) << (dci_size - pos); // Antenna ports pos += dci_pdu_rel15->antenna_ports.nbits; *dci_pdu |= ((uint64_t)dci_pdu_rel15->antenna_ports.val & ((1 << dci_pdu_rel15->antenna_ports.nbits) - 1)) << (dci_size - pos); // TCI pos += dci_pdu_rel15->transmission_configuration_indication.nbits; *dci_pdu |= ((uint64_t)dci_pdu_rel15->transmission_configuration_indication.val & ((1 << dci_pdu_rel15->transmission_configuration_indication.nbits) - 1)) << (dci_size - pos); // SRS request pos += dci_pdu_rel15->srs_request.nbits; *dci_pdu |= ((uint64_t)dci_pdu_rel15->srs_request.val & ((1 << dci_pdu_rel15->srs_request.nbits) - 1)) << (dci_size - pos); // CBG transmission information pos += dci_pdu_rel15->cbgti.nbits; *dci_pdu |= ((uint64_t)dci_pdu_rel15->cbgti.val & ((1 << dci_pdu_rel15->cbgti.nbits) - 1)) << (dci_size - pos); // CBG flushing out information pos += dci_pdu_rel15->cbgfi.nbits; *dci_pdu |= ((uint64_t)dci_pdu_rel15->cbgfi.val & ((1 << dci_pdu_rel15->cbgfi.nbits) - 1)) << (dci_size - pos); // DMRS sequence init pos += 1; *dci_pdu |= ((uint64_t)dci_pdu_rel15->dmrs_sequence_initialization.val & 0x1) << (dci_size - pos); } LOG_D(NR_MAC, "DCI has %d bits and the payload is %lx\n", dci_size, *dci_pdu); } int get_spf(nfapi_nr_config_request_scf_t *cfg) { int mu = cfg->ssb_config.scs_common.value; AssertFatal(mu>=0&&mu<4,"Illegal scs %d\n",mu); return(10 * (1< 0 && tmp < (14-tmp2)) return(tmp2); else return(13-tmp2); } int extract_length(int startSymbolAndLength) { int tmp = startSymbolAndLength/14; int tmp2 = startSymbolAndLength%14; if (tmp > 0 && tmp < (14-tmp2)) return(tmp); else return(15-tmp2); } /* * Dump the UL or DL UE_info into LOG_T(MAC) */ void dump_nr_list(NR_UE_info_t **list) { UE_iterator(list, UE) { LOG_T(NR_MAC, "NR list UEs rntis %04x\n", (*list)->rnti); } } /* * Create a new NR_list */ void create_nr_list(NR_list_t *list, int len) { list->head = -1; list->next = malloc(len * sizeof(*list->next)); AssertFatal(list->next, "cannot malloc() memory for NR_list_t->next\n"); for (int i = 0; i < len; ++i) list->next[i] = -1; list->tail = -1; list->len = len; } /* * Resize an NR_list */ void resize_nr_list(NR_list_t *list, int new_len) { if (new_len == list->len) return; if (new_len > list->len) { /* list->head remains */ const int old_len = list->len; int* n = realloc(list->next, new_len * sizeof(*list->next)); AssertFatal(n, "cannot realloc() memory for NR_list_t->next\n"); list->next = n; for (int i = old_len; i < new_len; ++i) list->next[i] = -1; /* list->tail remains */ list->len = new_len; } else { /* new_len < len */ AssertFatal(list->head < new_len, "shortened list head out of index %d (new len %d)\n", list->head, new_len); AssertFatal(list->tail < new_len, "shortened list tail out of index %d (new len %d)\n", list->head, new_len); for (int i = 0; i < list->len; ++i) AssertFatal(list->next[i] < new_len, "shortened list entry out of index %d (new len %d)\n", list->next[i], new_len); /* list->head remains */ int *n = realloc(list->next, new_len * sizeof(*list->next)); AssertFatal(n, "cannot realloc() memory for NR_list_t->next\n"); list->next = n; /* list->tail remains */ list->len = new_len; } } /* * Destroy an NR_list */ void destroy_nr_list(NR_list_t *list) { free(list->next); } /* * Add an ID to an NR_list at the end, traversing the whole list. Note: * add_tail_nr_list() is a faster alternative, but this implementation ensures * we do not add an existing ID. */ void add_nr_list(NR_list_t *listP, int id) { int *cur = &listP->head; while (*cur >= 0) { AssertFatal(*cur != id, "id %d already in NR_UE_list!\n", id); cur = &listP->next[*cur]; } *cur = id; if (listP->next[id] < 0) listP->tail = id; } /* * Remove an ID from an NR_list */ void remove_nr_list(NR_list_t *listP, int id) { int *cur = &listP->head; int *prev = &listP->head; while (*cur != -1 && *cur != id) { prev = cur; cur = &listP->next[*cur]; } AssertFatal(*cur != -1, "ID %d not found in UE_list\n", id); int *next = &listP->next[*cur]; *cur = listP->next[*cur]; *next = -1; listP->tail = *prev >= 0 && listP->next[*prev] >= 0 ? listP->tail : *prev; } /* * Add an ID to the tail of the NR_list in O(1). Note that there is * corresponding remove_tail_nr_list(), as we cannot set the tail backwards and * therefore need to go through the whole list (use remove_nr_list()) */ void add_tail_nr_list(NR_list_t *listP, int id) { int *last = listP->tail < 0 ? &listP->head : &listP->next[listP->tail]; *last = id; listP->next[id] = -1; listP->tail = id; } /* * Add an ID to the front of the NR_list in O(1) */ void add_front_nr_list(NR_list_t *listP, int id) { const int ohead = listP->head; listP->head = id; listP->next[id] = ohead; if (listP->tail < 0) listP->tail = id; } /* * Remove an ID from the front of the NR_list in O(1) */ void remove_front_nr_list(NR_list_t *listP) { AssertFatal(listP->head >= 0, "Nothing to remove\n"); const int ohead = listP->head; listP->head = listP->next[ohead]; listP->next[ohead] = -1; if (listP->head < 0) listP->tail = -1; } NR_UE_info_t *find_nr_UE(NR_UEs_t *UEs, rnti_t rntiP) { UE_iterator(UEs->connected_ue_list, UE) { if (UE->rnti == rntiP) { LOG_D(NR_MAC,"Search and found rnti: %04x\n", rntiP); return UE; } } return NULL; } NR_UE_info_t *find_ra_UE(NR_UEs_t *UEs, rnti_t rntiP) { UE_iterator(UEs->access_ue_list, UE) { if (UE->rnti == rntiP) { LOG_D(NR_MAC,"Search and found rnti: %04x\n", rntiP); return UE; } } return NULL; } void delete_nr_ue_data(NR_UE_info_t *UE, NR_COMMON_channels_t *ccPtr, uid_allocator_t *uia) { ASN_STRUCT_FREE(asn_DEF_NR_CellGroupConfig, UE->CellGroup); ASN_STRUCT_FREE(asn_DEF_NR_SpCellConfig, UE->reconfigSpCellConfig); ASN_STRUCT_FREE(asn_DEF_NR_UE_NR_Capability, UE->capability); NR_UE_sched_ctrl_t *sched_ctrl = &UE->UE_sched_ctrl; seq_arr_free(&sched_ctrl->lc_config, NULL); destroy_nr_list(&sched_ctrl->available_dl_harq); destroy_nr_list(&sched_ctrl->feedback_dl_harq); destroy_nr_list(&sched_ctrl->retrans_dl_harq); destroy_nr_list(&sched_ctrl->available_ul_harq); destroy_nr_list(&sched_ctrl->feedback_ul_harq); destroy_nr_list(&sched_ctrl->retrans_ul_harq); for (int i = 0; i < NR_MAX_HARQ_PROCESSES; ++i) free_transportBlock_buffer(&sched_ctrl->harq_processes[i].transportBlock); free_sched_pucch_list(sched_ctrl); uid_linear_allocator_free(uia, UE->uid); LOG_I(NR_MAC, "Remove NR rnti 0x%04x\n", UE->rnti); free_and_zero(UE->ra); free(UE); } #define TB_SINGLE_LAYER (32 * 1024) uint8_t *allocate_transportBlock_buffer(byte_array_t *tb, uint32_t needed) { DevAssert(needed > 0); if (tb->buf != NULL && needed <= tb->len) return tb->buf; // nothing to do, current is enough uint32_t size = TB_SINGLE_LAYER; while (needed > size) size *= 2; LOG_D(NR_MAC, "allocating new TB block of size %d\n", size); free(tb->buf); tb->buf = malloc_or_fail(size); tb->len = size; return tb->buf; } void free_transportBlock_buffer(byte_array_t *tb) { free_byte_array(*tb); } void set_max_fb_time(NR_UE_UL_BWP_t *UL_BWP, const NR_UE_DL_BWP_t *DL_BWP) { UL_BWP->max_fb_time = 8; // default value // take the maximum in dl_DataToUL_ACK list if (DL_BWP->dci_format != NR_DL_DCI_FORMAT_1_0 && UL_BWP->pucch_Config) { const struct NR_PUCCH_Config__dl_DataToUL_ACK *fb_times = UL_BWP->pucch_Config->dl_DataToUL_ACK; for (int i = 0; i < fb_times->list.count; i++) { if(*fb_times->list.array[i] > UL_BWP->max_fb_time) UL_BWP->max_fb_time = *fb_times->list.array[i]; } } } void reset_sched_ctrl(NR_UE_sched_ctrl_t *sched_ctrl) { sched_ctrl->srs_feedback.ul_ri = 0; sched_ctrl->srs_feedback.tpmi = 0; sched_ctrl->srs_feedback.sri = 0; } int get_dlbw_tbslbrm(int scc_bwpsize, const NR_ServingCellConfig_t *servingCellConfig) { int bw = scc_bwpsize; if (servingCellConfig) { if (servingCellConfig->downlinkBWP_ToAddModList) { const struct NR_ServingCellConfig__downlinkBWP_ToAddModList *BWP_list = servingCellConfig->downlinkBWP_ToAddModList; for (int i = 0; i < BWP_list->list.count; i++) { NR_BWP_t genericParameters = BWP_list->list.array[i]->bwp_Common->genericParameters; int curr_bw = NRRIV2BW(genericParameters.locationAndBandwidth, MAX_BWP_SIZE); if (curr_bw > bw) bw = curr_bw; } } } return bw; } int get_ulbw_tbslbrm(int scc_bwpsize, const NR_ServingCellConfig_t *servingCellConfig) { int bw = scc_bwpsize; if (servingCellConfig) { if (servingCellConfig->uplinkConfig && servingCellConfig->uplinkConfig->uplinkBWP_ToAddModList) { const struct NR_UplinkConfig__uplinkBWP_ToAddModList *BWP_list = servingCellConfig->uplinkConfig->uplinkBWP_ToAddModList; for (int i = 0; i < BWP_list->list.count; i++) { NR_BWP_t genericParameters = BWP_list->list.array[i]->bwp_Common->genericParameters; int curr_bw = NRRIV2BW(genericParameters.locationAndBandwidth, MAX_BWP_SIZE); if (curr_bw > bw) bw = curr_bw; } } } return bw; } static void set_sched_pucch_list(NR_UE_sched_ctrl_t *sched_ctrl, const NR_UE_UL_BWP_t *ul_bwp, const NR_ServingCellConfigCommon_t *scc, const frame_structure_t *fs) { const int NTN_gNB_Koffset = get_NTN_Koffset(scc); const int n_ul_slots_period = get_ul_slots_per_period(fs); // PUCCH list size is given by the number of UL slots in the PUCCH period // the length PUCCH period is determined by max_fb_time since we may need to prepare PUCCH for ACK/NACK max_fb_time slots ahead const int list_size = n_ul_slots_period << (int)ceil(log2((ul_bwp->max_fb_time + NTN_gNB_Koffset) / fs->numb_slots_period + 1)); if (!sched_ctrl->sched_pucch) { sched_ctrl->sched_pucch = calloc_or_fail(list_size, sizeof(*sched_ctrl->sched_pucch)); sched_ctrl->sched_pucch_size = list_size; } else if (list_size > sched_ctrl->sched_pucch_size) { sched_ctrl->sched_pucch = realloc(sched_ctrl->sched_pucch, list_size * sizeof(*sched_ctrl->sched_pucch)); for (int i = sched_ctrl->sched_pucch_size; i < list_size; i++) { NR_sched_pucch_t *curr_pucch = &sched_ctrl->sched_pucch[i]; memset(curr_pucch, 0, sizeof(*curr_pucch)); } sched_ctrl->sched_pucch_size = list_size; } } static void create_dl_harq_list(NR_UE_sched_ctrl_t *sched_ctrl, const NR_UE_ServingCell_Info_t *sc_info, bool dci_00_10) { int nrofHARQ = get_nrofHARQ_ProcessesForPDSCH(sc_info); // limit the number of HARQ PIDs to 16 in case of common search space if (dci_00_10 && nrofHARQ > 16) nrofHARQ = 16; // add all available DL HARQ processes for this UE AssertFatal(sched_ctrl->available_dl_harq.len == sched_ctrl->feedback_dl_harq.len && sched_ctrl->available_dl_harq.len == sched_ctrl->retrans_dl_harq.len, "HARQ lists have different lengths (%d/%d/%d)\n", sched_ctrl->available_dl_harq.len, sched_ctrl->feedback_dl_harq.len, sched_ctrl->retrans_dl_harq.len); if (sched_ctrl->available_dl_harq.len == 0) { create_nr_list(&sched_ctrl->available_dl_harq, nrofHARQ); for (int harq = 0; harq < nrofHARQ; harq++) add_tail_nr_list(&sched_ctrl->available_dl_harq, harq); create_nr_list(&sched_ctrl->feedback_dl_harq, nrofHARQ); create_nr_list(&sched_ctrl->retrans_dl_harq, nrofHARQ); } else if (sched_ctrl->available_dl_harq.len == nrofHARQ) { LOG_D(NR_MAC, "nrofHARQ %d already configured\n", nrofHARQ); } else { const int old_nrofHARQ = sched_ctrl->available_dl_harq.len; AssertFatal(nrofHARQ > old_nrofHARQ, "cannot resize HARQ list to be smaller (nrofHARQ %d, old_nrofHARQ %d)\n", nrofHARQ, old_nrofHARQ); resize_nr_list(&sched_ctrl->available_dl_harq, nrofHARQ); for (int harq = old_nrofHARQ; harq < nrofHARQ; harq++) add_tail_nr_list(&sched_ctrl->available_dl_harq, harq); resize_nr_list(&sched_ctrl->feedback_dl_harq, nrofHARQ); resize_nr_list(&sched_ctrl->retrans_dl_harq, nrofHARQ); } } static void create_ul_harq_list(NR_UE_sched_ctrl_t *sched_ctrl, const NR_UE_ServingCell_Info_t *sc_info, bool dci_00_10) { int nrofHARQ = get_nrofHARQ_ProcessesForPUSCH(sc_info); // limit the number of HARQ PIDs to 16 in case of common search space if (dci_00_10 && nrofHARQ > 16) nrofHARQ = 16; if (sched_ctrl->available_ul_harq.len == 0) { create_nr_list(&sched_ctrl->available_ul_harq, nrofHARQ); for (int harq = 0; harq < nrofHARQ; harq++) add_tail_nr_list(&sched_ctrl->available_ul_harq, harq); create_nr_list(&sched_ctrl->feedback_ul_harq, nrofHARQ); create_nr_list(&sched_ctrl->retrans_ul_harq, nrofHARQ); } else if (sched_ctrl->available_ul_harq.len == nrofHARQ) { LOG_D(NR_MAC, "nrofHARQ %d already configured\n", nrofHARQ); } else { const int old_nrofHARQ = sched_ctrl->available_ul_harq.len; AssertFatal(nrofHARQ > old_nrofHARQ, "cannot resize HARQ list to be smaller (nrofHARQ %d, old_nrofHARQ %d)\n", nrofHARQ, old_nrofHARQ); resize_nr_list(&sched_ctrl->available_ul_harq, nrofHARQ); for (int harq = old_nrofHARQ; harq < nrofHARQ; harq++) add_tail_nr_list(&sched_ctrl->available_ul_harq, harq); resize_nr_list(&sched_ctrl->feedback_ul_harq, nrofHARQ); resize_nr_list(&sched_ctrl->retrans_ul_harq, nrofHARQ); } } // main function to configure parameters of current BWP void configure_UE_BWP(gNB_MAC_INST *nr_mac, NR_ServingCellConfigCommon_t *scc, NR_UE_info_t *UE, bool is_RA, int target_ss, int dl_bwp_switch, int ul_bwp_switch) { NR_CellGroupConfig_t *CellGroup = UE->CellGroup; NR_UE_ServingCell_Info_t *sc_info = &UE->sc_info; NR_UE_DL_BWP_t *DL_BWP = &UE->current_DL_BWP; NR_UE_UL_BWP_t *UL_BWP = &UE->current_UL_BWP; NR_BWP_Downlink_t *dl_bwp = NULL; NR_BWP_Uplink_t *ul_bwp = NULL; NR_BWP_DownlinkDedicated_t *bwpd = NULL; NR_BWP_UplinkDedicated_t *ubwpd = NULL; // number of additional BWPs (excluding initial BWP) sc_info->n_dl_bwp = 0; sc_info->n_ul_bwp = 0; int old_dl_bwp_id = DL_BWP->bwp_id; int old_ul_bwp_id = UL_BWP->bwp_id; NR_ServingCellConfig_t *servingCellConfig = NULL; if (CellGroup && CellGroup->spCellConfig && CellGroup->spCellConfig->spCellConfigDedicated) { servingCellConfig = CellGroup->spCellConfig->spCellConfigDedicated; if(dl_bwp_switch >= 0 && ul_bwp_switch >= 0) { AssertFatal(dl_bwp_switch == ul_bwp_switch, "Different UL and DL BWP not supported\n"); DL_BWP->bwp_id = dl_bwp_switch; UL_BWP->bwp_id = ul_bwp_switch; } else { // (re)configuring BWP // TODO BWP switching not via RRC reconfiguration // via RRC if firstActiveXlinkBWP_Id is NULL, MAC stays on the same BWP as before if (servingCellConfig->firstActiveDownlinkBWP_Id) DL_BWP->bwp_id = *servingCellConfig->firstActiveDownlinkBWP_Id; if (servingCellConfig->uplinkConfig->firstActiveUplinkBWP_Id) UL_BWP->bwp_id = *servingCellConfig->uplinkConfig->firstActiveUplinkBWP_Id; } const struct NR_ServingCellConfig__downlinkBWP_ToAddModList *bwpList = servingCellConfig->downlinkBWP_ToAddModList; if(bwpList) sc_info->n_dl_bwp = bwpList->list.count; if (DL_BWP->bwp_id>0) { for (int i=0; ilist.count; i++) { dl_bwp = bwpList->list.array[i]; if(dl_bwp->bwp_Id == DL_BWP->bwp_id) break; } AssertFatal(dl_bwp!=NULL,"Couldn't find DLBWP corresponding to BWP ID %ld\n",DL_BWP->bwp_id); } const struct NR_UplinkConfig__uplinkBWP_ToAddModList *ubwpList = servingCellConfig->uplinkConfig->uplinkBWP_ToAddModList; if(ubwpList) sc_info->n_ul_bwp = ubwpList->list.count; if (UL_BWP->bwp_id>0) { for (int i=0; ilist.count; i++) { ul_bwp = ubwpList->list.array[i]; if(ul_bwp->bwp_Id == UL_BWP->bwp_id) break; } AssertFatal(ul_bwp!=NULL,"Couldn't find ULBWP corresponding to BWP ID %ld\n",UL_BWP->bwp_id); } // selection of dedicated BWPs if(dl_bwp) bwpd = dl_bwp->bwp_Dedicated; else bwpd = servingCellConfig->initialDownlinkBWP; if(ul_bwp) ubwpd = ul_bwp->bwp_Dedicated; else ubwpd = servingCellConfig->uplinkConfig->initialUplinkBWP; DL_BWP->pdsch_Config = bwpd->pdsch_Config->choice.setup; UL_BWP->configuredGrantConfig = ubwpd->configuredGrantConfig ? ubwpd->configuredGrantConfig->choice.setup : NULL; UL_BWP->pusch_Config = ubwpd->pusch_Config->choice.setup; UL_BWP->pucch_Config = ubwpd->pucch_Config->choice.setup; UL_BWP->srs_Config = ubwpd->srs_Config->choice.setup; } else { DL_BWP->bwp_id = 0; UL_BWP->bwp_id = 0; DL_BWP->pdsch_Config = NULL; UL_BWP->pusch_Config = NULL; UL_BWP->pucch_Config = NULL; UL_BWP->configuredGrantConfig = NULL; } if (old_dl_bwp_id != DL_BWP->bwp_id) LOG_I(NR_MAC, "Switching to DL-BWP %li\n", DL_BWP->bwp_id); if (old_ul_bwp_id != UL_BWP->bwp_id) LOG_I(NR_MAC, "Switching to UL-BWP %li\n", UL_BWP->bwp_id); // TDA lists if (DL_BWP->bwp_id>0) DL_BWP->tdaList_Common = dl_bwp->bwp_Common->pdsch_ConfigCommon->choice.setup->pdsch_TimeDomainAllocationList; else DL_BWP->tdaList_Common = scc->downlinkConfigCommon->initialDownlinkBWP->pdsch_ConfigCommon->choice.setup->pdsch_TimeDomainAllocationList; if(UL_BWP->bwp_id>0) UL_BWP->tdaList_Common = ul_bwp->bwp_Common->pusch_ConfigCommon->choice.setup->pusch_TimeDomainAllocationList; else UL_BWP->tdaList_Common = scc->uplinkConfigCommon->initialUplinkBWP->pusch_ConfigCommon->choice.setup->pusch_TimeDomainAllocationList; // setting generic parameters NR_BWP_t dl_genericParameters = (DL_BWP->bwp_id > 0 && dl_bwp) ? dl_bwp->bwp_Common->genericParameters: scc->downlinkConfigCommon->initialDownlinkBWP->genericParameters; DL_BWP->scs = dl_genericParameters.subcarrierSpacing; DL_BWP->cyclicprefix = dl_genericParameters.cyclicPrefix; DL_BWP->BWPSize = NRRIV2BW(dl_genericParameters.locationAndBandwidth, MAX_BWP_SIZE); DL_BWP->BWPStart = NRRIV2PRBOFFSET(dl_genericParameters.locationAndBandwidth, MAX_BWP_SIZE); sc_info->initial_dl_BWPSize = NRRIV2BW(scc->downlinkConfigCommon->initialDownlinkBWP->genericParameters.locationAndBandwidth, MAX_BWP_SIZE); sc_info->initial_dl_BWPStart = NRRIV2PRBOFFSET(scc->downlinkConfigCommon->initialDownlinkBWP->genericParameters.locationAndBandwidth, MAX_BWP_SIZE); NR_BWP_t ul_genericParameters = (UL_BWP->bwp_id > 0 && ul_bwp) ? ul_bwp->bwp_Common->genericParameters: scc->uplinkConfigCommon->initialUplinkBWP->genericParameters; UL_BWP->scs = ul_genericParameters.subcarrierSpacing; UL_BWP->cyclicprefix = ul_genericParameters.cyclicPrefix; UL_BWP->BWPSize = NRRIV2BW(ul_genericParameters.locationAndBandwidth, MAX_BWP_SIZE); UL_BWP->BWPStart = NRRIV2PRBOFFSET(ul_genericParameters.locationAndBandwidth, MAX_BWP_SIZE); sc_info->initial_ul_BWPSize = NRRIV2BW(scc->uplinkConfigCommon->initialUplinkBWP->genericParameters.locationAndBandwidth, MAX_BWP_SIZE); sc_info->initial_ul_BWPStart = NRRIV2PRBOFFSET(scc->uplinkConfigCommon->initialUplinkBWP->genericParameters.locationAndBandwidth, MAX_BWP_SIZE); sc_info->dl_bw_tbslbrm = get_dlbw_tbslbrm(sc_info->initial_dl_BWPSize, servingCellConfig); sc_info->ul_bw_tbslbrm = get_ulbw_tbslbrm(sc_info->initial_ul_BWPSize, servingCellConfig); if (UL_BWP->bwp_id > 0) { UL_BWP->pucch_ConfigCommon = ul_bwp->bwp_Common->pucch_ConfigCommon ? ul_bwp->bwp_Common->pucch_ConfigCommon->choice.setup : NULL; UL_BWP->rach_ConfigCommon = ul_bwp->bwp_Common->rach_ConfigCommon ? ul_bwp->bwp_Common->rach_ConfigCommon->choice.setup : NULL; } else { UL_BWP->pucch_ConfigCommon = scc->uplinkConfigCommon->initialUplinkBWP->pucch_ConfigCommon->choice.setup; UL_BWP->rach_ConfigCommon = scc->uplinkConfigCommon->initialUplinkBWP->rach_ConfigCommon->choice.setup; } NR_UE_sched_ctrl_t *sched_ctrl = &UE->UE_sched_ctrl; // Reset required fields in sched_ctrl (e.g. ul_ri and tpmi) reset_sched_ctrl(sched_ctrl); if(!is_RA) { if (servingCellConfig) { if (servingCellConfig->csi_MeasConfig) { sc_info->csi_MeasConfig = servingCellConfig->csi_MeasConfig->choice.setup; compute_csi_bitlen(sc_info->csi_MeasConfig, UE->csi_report_template); } if (servingCellConfig->pdsch_ServingCellConfig && servingCellConfig->pdsch_ServingCellConfig->choice.setup) { NR_PDSCH_ServingCellConfig_t *pdsch_servingcellconfig = servingCellConfig->pdsch_ServingCellConfig->choice.setup; sc_info->nrofHARQ_ProcessesForPDSCH = pdsch_servingcellconfig->nrofHARQ_ProcessesForPDSCH; if (pdsch_servingcellconfig->ext1) sc_info->maxMIMO_Layers_PDSCH = pdsch_servingcellconfig->ext1->maxMIMO_Layers; if (pdsch_servingcellconfig->ext3 && pdsch_servingcellconfig->ext3->downlinkHARQ_FeedbackDisabled_r17) sc_info->downlinkHARQ_FeedbackDisabled_r17 = &pdsch_servingcellconfig->ext3->downlinkHARQ_FeedbackDisabled_r17->choice.setup; if (pdsch_servingcellconfig->codeBlockGroupTransmission && pdsch_servingcellconfig->codeBlockGroupTransmission->choice.setup) sc_info->pdsch_CGB_Transmission = pdsch_servingcellconfig->codeBlockGroupTransmission->choice.setup; if (pdsch_servingcellconfig->ext3) sc_info->nrofHARQ_ProcessesForPDSCH_v1700 = pdsch_servingcellconfig->ext3->nrofHARQ_ProcessesForPDSCH_v1700; } sc_info->crossCarrierSchedulingConfig = servingCellConfig->crossCarrierSchedulingConfig; sc_info->supplementaryUplink = servingCellConfig->supplementaryUplink; if (servingCellConfig->uplinkConfig) { if (servingCellConfig->uplinkConfig->carrierSwitching) sc_info->carrierSwitching = servingCellConfig->uplinkConfig->carrierSwitching->choice.setup; if (servingCellConfig->uplinkConfig->pusch_ServingCellConfig && servingCellConfig->uplinkConfig->pusch_ServingCellConfig->choice.setup) { NR_PUSCH_ServingCellConfig_t *pusch_servingcellconfig = servingCellConfig->uplinkConfig->pusch_ServingCellConfig->choice.setup; if (pusch_servingcellconfig->ext1) sc_info->maxMIMO_Layers_PUSCH = pusch_servingcellconfig->ext1->maxMIMO_Layers; sc_info->rateMatching_PUSCH = pusch_servingcellconfig->rateMatching; if (pusch_servingcellconfig->codeBlockGroupTransmission && pusch_servingcellconfig->codeBlockGroupTransmission->choice.setup) sc_info->pusch_CGB_Transmission = pusch_servingcellconfig->codeBlockGroupTransmission->choice.setup; if (pusch_servingcellconfig->ext3) sc_info->nrofHARQ_ProcessesForPUSCH_r17 = pusch_servingcellconfig->ext3->nrofHARQ_ProcessesForPUSCH_r17; } } } if (CellGroup && CellGroup->physicalCellGroupConfig) UE->pdsch_HARQ_ACK_Codebook = CellGroup->physicalCellGroupConfig->pdsch_HARQ_ACK_Codebook; // setting PDCCH related structures for sched_ctrl sched_ctrl->search_space = get_searchspace(scc, bwpd, target_ss); sched_ctrl->coreset = get_coreset(nr_mac, scc, bwpd, sched_ctrl->search_space, target_ss); sched_ctrl->sched_pdcch = set_pdcch_structure(nr_mac, sched_ctrl->search_space, sched_ctrl->coreset, scc, &dl_genericParameters, nr_mac->type0_PDCCH_CSS_config); // set DL DCI format DL_BWP->dci_format = (sched_ctrl->search_space->searchSpaceType && sched_ctrl->search_space->searchSpaceType->present == NR_SearchSpace__searchSpaceType_PR_ue_Specific) ? (sched_ctrl->search_space->searchSpaceType->choice.ue_Specific->dci_Formats == NR_SearchSpace__searchSpaceType__ue_Specific__dci_Formats_formats0_1_And_1_1 ? NR_DL_DCI_FORMAT_1_1 : NR_DL_DCI_FORMAT_1_0) : NR_DL_DCI_FORMAT_1_0; // set UL DCI format UL_BWP->dci_format = (sched_ctrl->search_space->searchSpaceType && sched_ctrl->search_space->searchSpaceType->present == NR_SearchSpace__searchSpaceType_PR_ue_Specific) ? (sched_ctrl->search_space->searchSpaceType->choice.ue_Specific->dci_Formats == NR_SearchSpace__searchSpaceType__ue_Specific__dci_Formats_formats0_1_And_1_1 ? NR_UL_DCI_FORMAT_0_1 : NR_UL_DCI_FORMAT_0_0) : NR_UL_DCI_FORMAT_0_0; } else { // setting PDCCH related structures for RA struct NR_PDCCH_ConfigCommon__commonSearchSpaceList *commonSearchSpaceList = NULL; NR_SearchSpaceId_t ra_SearchSpace = 0; if(dl_bwp) { commonSearchSpaceList = dl_bwp->bwp_Common->pdcch_ConfigCommon->choice.setup->commonSearchSpaceList; ra_SearchSpace = *dl_bwp->bwp_Common->pdcch_ConfigCommon->choice.setup->ra_SearchSpace; } else { commonSearchSpaceList = scc->downlinkConfigCommon->initialDownlinkBWP->pdcch_ConfigCommon->choice.setup->commonSearchSpaceList; ra_SearchSpace = *scc->downlinkConfigCommon->initialDownlinkBWP->pdcch_ConfigCommon->choice.setup->ra_SearchSpace; } AssertFatal(commonSearchSpaceList->list.count > 0, "common SearchSpace list has 0 elements\n"); for (int i = 0; i < commonSearchSpaceList->list.count; i++) { NR_SearchSpace_t *ss = commonSearchSpaceList->list.array[i]; if (ss->searchSpaceId == ra_SearchSpace) sched_ctrl->search_space = ss; } AssertFatal(sched_ctrl->search_space != NULL, "SearchSpace cannot be null for RA\n"); sched_ctrl->coreset = get_coreset(nr_mac, scc, dl_bwp, sched_ctrl->search_space, NR_SearchSpace__searchSpaceType_PR_common); NR_COMMON_channels_t *cc = &nr_mac->common_channels[0]; int ssb_index = cc->ssb_index[UE->UE_beam_index]; sched_ctrl->sched_pdcch = set_pdcch_structure(nr_mac, sched_ctrl->search_space, sched_ctrl->coreset, scc, &dl_genericParameters, &nr_mac->type0_PDCCH_CSS_config[ssb_index]); UL_BWP->dci_format = NR_UL_DCI_FORMAT_0_0; DL_BWP->dci_format = NR_DL_DCI_FORMAT_1_0; } bool format_00_10 = DL_BWP->dci_format == NR_DL_DCI_FORMAT_1_0; // if DL is 10 UL is surely 00 create_dl_harq_list(sched_ctrl, sc_info, format_00_10); create_ul_harq_list(sched_ctrl, sc_info, format_00_10); set_max_fb_time(UL_BWP, DL_BWP); set_sched_pucch_list(sched_ctrl, UL_BWP, scc, &nr_mac->frame_structure); // Set MCS tables long *dl_mcs_Table = DL_BWP->pdsch_Config ? DL_BWP->pdsch_Config->mcs_Table : NULL; DL_BWP->mcsTableIdx = get_pdsch_mcs_table(dl_mcs_Table, DL_BWP->dci_format, TYPE_C_RNTI_, target_ss); // 0 precoding enabled 1 precoding disabled UL_BWP->transform_precoding = get_transformPrecoding(UL_BWP, UL_BWP->dci_format, 0); // Set uplink MCS table long *mcs_Table = NULL; if (UL_BWP->pusch_Config) mcs_Table = UL_BWP->transform_precoding ? UL_BWP->pusch_Config->mcs_Table : UL_BWP->pusch_Config->mcs_TableTransformPrecoder; UL_BWP->mcs_table = get_pusch_mcs_table(mcs_Table, !UL_BWP->transform_precoding, UL_BWP->dci_format, TYPE_C_RNTI_, target_ss, false); } void reset_srs_stats(NR_UE_info_t *UE) { if (UE) { UE->mac_stats.srs_stats[0] = '\0'; } } static void init_bler_stats(const NR_bler_options_t *bler_options, NR_bler_stats_t *bler_stats, frame_t frame) { bler_stats->last_frame = frame; bler_stats->mcs = bler_options->min_mcs; bler_stats->bler = (float)(bler_options->lower + bler_options->upper) / 2.0f; } /* @brief returns a new UE allocated instance. * * It will be typically added to the access_ue_list, but not always (e.g., * phytest mode), so this is not done in this function (and also, to allow * error handling). Remove with delete_nr_ue_data(). */ NR_UE_info_t *get_new_nr_ue_inst(uid_allocator_t *uia, rnti_t rnti, NR_CellGroupConfig_t *CellGroup) { NR_UE_info_t *UE = calloc_or_fail(1, sizeof(NR_UE_info_t)); UE->uid = uid_linear_allocator_new(uia); UE->rnti = rnti; UE->CellGroup = CellGroup; UE->ra = calloc(1, sizeof(*UE->ra)); NR_UE_sched_ctrl_t *sched_ctrl = &UE->UE_sched_ctrl; sched_ctrl->ta_update = 31; /* set illegal time domain allocation to force recomputation of all fields */ sched_ctrl->sched_pdsch.time_domain_allocation = -1; sched_ctrl->sched_pusch.time_domain_allocation = -1; /* Set default BWPs */ AssertFatal(UE->sc_info.n_ul_bwp <= NR_MAX_NUM_BWP, "uplinkBWP_ToAddModList has %d BWP!\n", UE->sc_info.n_ul_bwp); // initialize LCID structure seq_arr_init(&sched_ctrl->lc_config, sizeof(nr_lc_config_t)); return UE; } bool add_UE_to_list(int list_size, NR_UE_info_t *list[list_size], NR_UE_info_t *UE) { for (int i = 0; i < list_size; i++) { if (!list[i]) { list[i] = UE; return true; } } return false; } NR_UE_info_t *remove_UE_from_list(int list_size, NR_UE_info_t *list[list_size], rnti_t rnti) { for (int i = 0; i < list_size; i++) { NR_UE_info_t *curr_UE = list[i]; if (!curr_UE) break; /* reached the end of the list */ if (curr_UE->rnti != rnti) continue; /* remove this UE from the list, return the pointer */ memmove(&list[i], &list[i+1], sizeof(list[0]) * (list_size - i - 1)); list[list_size - 1] = NULL; return curr_UE; } return NULL; } /** @brief Transitions a UE from access list to connected list (i.e., the RA * list to the "normal" UE context list. */ bool transition_ra_connected_nr_ue(gNB_MAC_INST *nr_mac, NR_UE_info_t *UE) { NR_UEs_t *UE_info = &nr_mac->UE_info; // remove UE from initial access list (moved to connected mode) NR_UE_info_t *r = remove_UE_from_list(NR_NB_RA_PROC_MAX, UE_info->access_ue_list, UE->rnti); DevAssert(r == UE); /* sanity check: we should have removed the current UE ptr from list */ free_and_zero(UE->ra); return add_connected_nr_ue(nr_mac, UE); } /** @brief Add a UE to the list of UEs in * connected mode. * * To remove the UE, use mac_remove_nr_ue(). */ bool add_connected_nr_ue(gNB_MAC_INST *nr_mac, NR_UE_info_t *UE) { LOG_I(NR_MAC, "Adding new UE context with RNTI 0x%04x\n", UE->rnti); NR_UEs_t *UE_info = &nr_mac->UE_info; dump_nr_list(UE_info->connected_ue_list); AssertFatal(!UE->ra, "UE in connected cannot have RA process\n"); NR_SCHED_LOCK(&UE_info->mutex); bool success = add_UE_to_list(MAX_MOBILES_PER_GNB, UE_info->connected_ue_list, UE); if (!success) { LOG_E(NR_MAC,"Try to add UE %04x but the list is full\n", UE->rnti); delete_nr_ue_data(UE, NULL, &UE_info->uid_allocator); NR_SCHED_UNLOCK(&UE_info->mutex); return false; } NR_UE_sched_ctrl_t *sched_ctrl = &UE->UE_sched_ctrl; sched_ctrl->dl_max_mcs = 28; /* do not limit MCS for individual UEs */ sched_ctrl->sched_srs.frame = -1; sched_ctrl->sched_srs.slot = -1; sched_ctrl->pdcch_cl_adjust = 0; reset_srs_stats(UE); // Initialize bler_stats init_bler_stats(&nr_mac->dl_bler, &sched_ctrl->dl_bler_stats, nr_mac->frame); init_bler_stats(&nr_mac->ul_bler, &sched_ctrl->ul_bler_stats, nr_mac->frame); NR_SCHED_UNLOCK(&UE_info->mutex); dump_nr_list(UE_info->connected_ue_list); return true; } void free_sched_pucch_list(NR_UE_sched_ctrl_t *sched_ctrl) { free(sched_ctrl->sched_pucch); } void reset_dl_harq_list(NR_UE_sched_ctrl_t *sched_ctrl) { int harq; while ((harq = sched_ctrl->feedback_dl_harq.head) >= 0) { remove_front_nr_list(&sched_ctrl->feedback_dl_harq); add_tail_nr_list(&sched_ctrl->available_dl_harq, harq); } while ((harq = sched_ctrl->retrans_dl_harq.head) >= 0) { remove_front_nr_list(&sched_ctrl->retrans_dl_harq); add_tail_nr_list(&sched_ctrl->available_dl_harq, harq); } for (int i = 0; i < NR_MAX_HARQ_PROCESSES; i++) { sched_ctrl->harq_processes[i].feedback_slot = -1; sched_ctrl->harq_processes[i].round = 0; sched_ctrl->harq_processes[i].is_waiting = false; } } void reset_ul_harq_list(NR_UE_sched_ctrl_t *sched_ctrl) { int harq; while ((harq = sched_ctrl->feedback_ul_harq.head) >= 0) { remove_front_nr_list(&sched_ctrl->feedback_ul_harq); add_tail_nr_list(&sched_ctrl->available_ul_harq, harq); } while ((harq = sched_ctrl->retrans_ul_harq.head) >= 0) { remove_front_nr_list(&sched_ctrl->retrans_ul_harq); add_tail_nr_list(&sched_ctrl->available_ul_harq, harq); } for (int i = 0; i < NR_MAX_HARQ_PROCESSES; i++) { sched_ctrl->ul_harq_processes[i].feedback_slot = -1; sched_ctrl->ul_harq_processes[i].round = 0; sched_ctrl->ul_harq_processes[i].is_waiting = false; } } void mac_remove_nr_ue(gNB_MAC_INST *nr_mac, rnti_t rnti) { /* already mutex protected */ NR_SCHED_ENSURE_LOCKED(&nr_mac->sched_lock); NR_UEs_t *UE_info = &nr_mac->UE_info; NR_SCHED_LOCK(&UE_info->mutex); NR_UE_info_t *UE = remove_UE_from_list(MAX_MOBILES_PER_GNB + 1, UE_info->connected_ue_list, rnti); NR_SCHED_UNLOCK(&UE_info->mutex); if (UE) delete_nr_ue_data(UE, nr_mac->common_channels, &UE_info->uid_allocator); else nr_release_ra_UE(nr_mac, rnti); } // all values passed to this function are in dB x10 uint8_t nr_get_tpc(int target, uint8_t cqi, int incr, int tx_power) { // al values passed to this function are x10 int snrx10 = (cqi * 5) - 640 - (tx_power * 10); LOG_D(NR_MAC, "tpc : target %d, cqi %d, snrx10 %d, tx_power %d\n", target, ((int)cqi * 5) - 640, snrx10, tx_power); if (snrx10 > target + incr) return 0; // decrease 1dB if (snrx10 < target - (3*incr)) return 3; // increase 3dB if (snrx10 < target - incr) return 2; // increase 1dB LOG_D(NR_MAC,"tpc : target %d, snrx10 %d\n",target,snrx10); return 1; // no change } /** * @brief Limits the power control commands (TPC) in NR by checking the RSSI threshold. * * This function evaluates the received signal strength indicator (RSSI) and adjusts the * transmit power control (TPC) commands accordingly to ensure they remain within acceptable * limits. It helps in maintaining the signal quality and preventing excessive power usage. * * @param rssi The received signal strength indicator value, as defined in FAPI specifications. * @param tpc The transmit power control command to be limited. * @param rssi_threshold RSSI threshold in 0.1 dBm/dBFS, range -1280 to 0 * @return The adjusted TPC command after applying the RSSI threshold check. */ uint8_t nr_limit_tpc(int tpc, int rssi, int rssi_threshold) { if (rssi == 0xFFFF) { // RSSI not available, keep tpc return tpc; } // Convert RSSI threshold to FAPI scale const int fapi_rssi_0dBm_or_0dBFS = 1280; int rssi_fapi_threshold = fapi_rssi_0dBm_or_0dBFS + rssi_threshold; // Further limit TPC if above or near RSSI threshold int tpc_to_db[] = {-1, 0, 1, 3}; if (rssi > rssi_fapi_threshold) { // RSSI above theshold, reduce power return 0; } else if (rssi + tpc_to_db[tpc] * 10 > rssi_fapi_threshold) { // Cannot apply required TPC, check 1 dB increment if (rssi + 10 > rssi_fapi_threshold) { // Still cannot apply required TPC, keep power return 1; } else { // Can apply 1dB increment, but 3 was requested return 2; } } return tpc; } int get_pdsch_to_harq_feedback(NR_PUCCH_Config_t *pucch_Config, nr_dci_format_t dci_format, uint8_t *pdsch_to_harq_feedback) { /* already mutex protected: held in nr_acknack_scheduling() */ if (dci_format == NR_DL_DCI_FORMAT_1_0) { for (int i = 0; i < 8; i++) pdsch_to_harq_feedback[i] = i + 1; return 8; } else { AssertFatal(pucch_Config != NULL && pucch_Config->dl_DataToUL_ACK != NULL,"dl_DataToUL_ACK shouldn't be null here\n"); for (int i = 0; i < pucch_Config->dl_DataToUL_ACK->list.count; i++) { pdsch_to_harq_feedback[i] = *pucch_Config->dl_DataToUL_ACK->list.array[i]; } return pucch_Config->dl_DataToUL_ACK->list.count; } } void nr_csirs_scheduling(int Mod_idP, frame_t frame, slot_t slot, 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]; int n_slots_frame = gNB_mac->frame_structure.numb_slots_frame; NR_SCHED_ENSURE_LOCKED(&gNB_mac->sched_lock); UE_info->sched_csirs = 0; UE_iterator(UE_info->connected_ue_list, UE) { NR_UE_DL_BWP_t *dl_bwp = &UE->current_DL_BWP; // CSI-RS is common to all UEs in a given BWP // therefore we need to schedule only once per BWP // the following condition verifies if CSI-RS // has been already scheduled in this BWP if (UE_info->sched_csirs & (1 << dl_bwp->bwp_id)) continue; NR_UE_sched_ctrl_t *sched_ctrl = &UE->UE_sched_ctrl; if (nr_timer_is_active(&sched_ctrl->transm_interrupt)) continue; if (!UE->sc_info.csi_MeasConfig) continue; NR_CSI_MeasConfig_t *csi_measconfig = UE->sc_info.csi_MeasConfig; // looking for the correct CSI-RS resource in current BWP NR_NZP_CSI_RS_ResourceSetId_t *nzp = NULL; for (int csi_list=0; csi_listcsi_ResourceConfigToAddModList->list.count; csi_list++) { NR_CSI_ResourceConfig_t *csires = csi_measconfig->csi_ResourceConfigToAddModList->list.array[csi_list]; if(csires->bwp_Id == dl_bwp->bwp_id && csires->csi_RS_ResourceSetList.present == NR_CSI_ResourceConfig__csi_RS_ResourceSetList_PR_nzp_CSI_RS_SSB && csires->csi_RS_ResourceSetList.choice.nzp_CSI_RS_SSB->nzp_CSI_RS_ResourceSetList) { nzp = csires->csi_RS_ResourceSetList.choice.nzp_CSI_RS_SSB->nzp_CSI_RS_ResourceSetList->list.array[0]; } } if (csi_measconfig->nzp_CSI_RS_ResourceToAddModList != NULL && nzp != NULL) { NR_NZP_CSI_RS_Resource_t *nzpcsi; int period, offset; 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]; // transmitting CSI-RS only for current BWP if (nzpcsi->nzp_CSI_RS_ResourceId != *nzp) continue; NR_CSI_RS_ResourceMapping_t resourceMapping = nzpcsi->resourceMapping; csi_period_offset(NULL, nzpcsi->periodicityAndOffset, &period, &offset); if((frame * n_slots_frame + slot - offset) % period == 0) { LOG_D(NR_MAC,"Scheduling CSI-RS in frame %d slot %d Resource ID %ld\n", frame, slot, nzpcsi->nzp_CSI_RS_ResourceId); NR_beam_alloc_t beam_csi = beam_allocation_procedure(&gNB_mac->beam_info, frame, slot, UE->UE_beam_index, n_slots_frame); AssertFatal(beam_csi.idx >= 0, "Cannot allocate CSI-RS in any available beam\n"); uint16_t *vrb_map = gNB_mac->common_channels[CC_id].vrb_map[beam_csi.idx]; UE_info->sched_csirs |= (1 << dl_bwp->bwp_id); nfapi_nr_dl_tti_request_pdu_t *dl_tti_csirs_pdu = &dl_req->dl_tti_pdu_list[dl_req->nPDUs]; memset((void*)dl_tti_csirs_pdu,0,sizeof(nfapi_nr_dl_tti_request_pdu_t)); dl_tti_csirs_pdu->PDUType = NFAPI_NR_DL_TTI_CSI_RS_PDU_TYPE; dl_tti_csirs_pdu->PDUSize = (uint8_t)(2+sizeof(nfapi_nr_dl_tti_csi_rs_pdu)); nfapi_nr_dl_tti_csi_rs_pdu_rel15_t *csirs_pdu_rel15 = &dl_tti_csirs_pdu->csi_rs_pdu.csi_rs_pdu_rel15; csirs_pdu_rel15->precodingAndBeamforming.num_prgs = 1; csirs_pdu_rel15->precodingAndBeamforming.prg_size = resourceMapping.freqBand.nrofRBs; //1 PRG of max size csirs_pdu_rel15->precodingAndBeamforming.dig_bf_interfaces = 1; csirs_pdu_rel15->precodingAndBeamforming.prgs_list[0].pm_idx = 0; csirs_pdu_rel15->precodingAndBeamforming.prgs_list[0].dig_bf_interface_list[0].beam_idx = UE->UE_beam_index; csirs_pdu_rel15->bwp_size = dl_bwp->BWPSize; csirs_pdu_rel15->bwp_start = dl_bwp->BWPStart; csirs_pdu_rel15->subcarrier_spacing = dl_bwp->scs; if (dl_bwp->cyclicprefix) csirs_pdu_rel15->cyclic_prefix = *dl_bwp->cyclicprefix; else csirs_pdu_rel15->cyclic_prefix = 0; // According to last paragraph of TS 38.214 5.2.2.3.1 if (resourceMapping.freqBand.startingRB < dl_bwp->BWPStart) { csirs_pdu_rel15->start_rb = dl_bwp->BWPStart; } else { csirs_pdu_rel15->start_rb = resourceMapping.freqBand.startingRB; } if (resourceMapping.freqBand.nrofRBs > (dl_bwp->BWPStart + dl_bwp->BWPSize - csirs_pdu_rel15->start_rb)) { csirs_pdu_rel15->nr_of_rbs = dl_bwp->BWPStart + dl_bwp->BWPSize - csirs_pdu_rel15->start_rb; } else { csirs_pdu_rel15->nr_of_rbs = resourceMapping.freqBand.nrofRBs; } AssertFatal(csirs_pdu_rel15->nr_of_rbs >= 24, "CSI-RS has %d RBs, but the minimum is 24\n", csirs_pdu_rel15->nr_of_rbs); csirs_pdu_rel15->csi_type = 1; // NZP-CSI-RS csirs_pdu_rel15->symb_l0 = resourceMapping.firstOFDMSymbolInTimeDomain; if (resourceMapping.firstOFDMSymbolInTimeDomain2) csirs_pdu_rel15->symb_l1 = *resourceMapping.firstOFDMSymbolInTimeDomain2; csirs_pdu_rel15->cdm_type = resourceMapping.cdm_Type; csirs_pdu_rel15->freq_density = resourceMapping.density.present; if ((resourceMapping.density.present == NR_CSI_RS_ResourceMapping__density_PR_dot5) && (resourceMapping.density.choice.dot5 == NR_CSI_RS_ResourceMapping__density__dot5_evenPRBs)) csirs_pdu_rel15->freq_density--; csirs_pdu_rel15->scramb_id = nzpcsi->scramblingID; csirs_pdu_rel15->power_control_offset = nzpcsi->powerControlOffset + 8; if (nzpcsi->powerControlOffsetSS) csirs_pdu_rel15->power_control_offset_ss = *nzpcsi->powerControlOffsetSS; else csirs_pdu_rel15->power_control_offset_ss = 1; // 0 dB switch(resourceMapping.frequencyDomainAllocation.present){ case NR_CSI_RS_ResourceMapping__frequencyDomainAllocation_PR_row1: csirs_pdu_rel15->row = 1; csirs_pdu_rel15->freq_domain = ((resourceMapping.frequencyDomainAllocation.choice.row1.buf[0])>>4)&0x0f; for (int rb = csirs_pdu_rel15->start_rb; rb < (csirs_pdu_rel15->start_rb + csirs_pdu_rel15->nr_of_rbs); rb++) vrb_map[rb] |= SL_to_bitmap(csirs_pdu_rel15->symb_l0, 1); break; case NR_CSI_RS_ResourceMapping__frequencyDomainAllocation_PR_row2: csirs_pdu_rel15->row = 2; csirs_pdu_rel15->freq_domain = (((resourceMapping.frequencyDomainAllocation.choice.row2.buf[1]>>4)&0x0f) | ((resourceMapping.frequencyDomainAllocation.choice.row2.buf[0]<<4)&0xff0)); for (int rb = csirs_pdu_rel15->start_rb; rb < (csirs_pdu_rel15->start_rb + csirs_pdu_rel15->nr_of_rbs); rb++) vrb_map[rb] |= SL_to_bitmap(csirs_pdu_rel15->symb_l0, 1); break; case NR_CSI_RS_ResourceMapping__frequencyDomainAllocation_PR_row4: csirs_pdu_rel15->row = 4; csirs_pdu_rel15->freq_domain = ((resourceMapping.frequencyDomainAllocation.choice.row4.buf[0])>>5)&0x07; for (int rb = csirs_pdu_rel15->start_rb; rb < (csirs_pdu_rel15->start_rb + csirs_pdu_rel15->nr_of_rbs); rb++) vrb_map[rb] |= SL_to_bitmap(csirs_pdu_rel15->symb_l0, 1); break; case NR_CSI_RS_ResourceMapping__frequencyDomainAllocation_PR_other: csirs_pdu_rel15->freq_domain = ((resourceMapping.frequencyDomainAllocation.choice.other.buf[0])>>2)&0x3f; // determining the row of table 7.4.1.5.3-1 in 38.211 switch(resourceMapping.nrofPorts){ case NR_CSI_RS_ResourceMapping__nrofPorts_p1: AssertFatal(1==0,"Resource with 1 CSI port shouldn't be within other rows\n"); break; case NR_CSI_RS_ResourceMapping__nrofPorts_p2: csirs_pdu_rel15->row = 3; for (int rb = csirs_pdu_rel15->start_rb; rb < (csirs_pdu_rel15->start_rb + csirs_pdu_rel15->nr_of_rbs); rb++) vrb_map[rb] |= SL_to_bitmap(csirs_pdu_rel15->symb_l0, 1); break; case NR_CSI_RS_ResourceMapping__nrofPorts_p4: csirs_pdu_rel15->row = 5; for (int rb = csirs_pdu_rel15->start_rb; rb < (csirs_pdu_rel15->start_rb + csirs_pdu_rel15->nr_of_rbs); rb++) vrb_map[rb] |= SL_to_bitmap(csirs_pdu_rel15->symb_l0, 2); break; case NR_CSI_RS_ResourceMapping__nrofPorts_p8: if (resourceMapping.cdm_Type == NR_CSI_RS_ResourceMapping__cdm_Type_cdm4_FD2_TD2) { csirs_pdu_rel15->row = 8; for (int rb = csirs_pdu_rel15->start_rb; rb < (csirs_pdu_rel15->start_rb + csirs_pdu_rel15->nr_of_rbs); rb++) vrb_map[rb] |= SL_to_bitmap(csirs_pdu_rel15->symb_l0, 2); } else{ int num_k = 0; for (int k=0; k<6; k++) num_k+=(((csirs_pdu_rel15->freq_domain)>>k)&0x01); if(num_k==4) { csirs_pdu_rel15->row = 6; for (int rb = csirs_pdu_rel15->start_rb; rb < (csirs_pdu_rel15->start_rb + csirs_pdu_rel15->nr_of_rbs); rb++) vrb_map[rb] |= SL_to_bitmap(csirs_pdu_rel15->symb_l0, 1); } else { csirs_pdu_rel15->row = 7; for (int rb = csirs_pdu_rel15->start_rb; rb < (csirs_pdu_rel15->start_rb + csirs_pdu_rel15->nr_of_rbs); rb++) vrb_map[rb] |= SL_to_bitmap(csirs_pdu_rel15->symb_l0, 2); } } break; case NR_CSI_RS_ResourceMapping__nrofPorts_p12: if (resourceMapping.cdm_Type == NR_CSI_RS_ResourceMapping__cdm_Type_cdm4_FD2_TD2) { csirs_pdu_rel15->row = 10; for (int rb = csirs_pdu_rel15->start_rb; rb < (csirs_pdu_rel15->start_rb + csirs_pdu_rel15->nr_of_rbs); rb++) vrb_map[rb] |= SL_to_bitmap(csirs_pdu_rel15->symb_l0, 2); } else { csirs_pdu_rel15->row = 9; for (int rb = csirs_pdu_rel15->start_rb; rb < (csirs_pdu_rel15->start_rb + csirs_pdu_rel15->nr_of_rbs); rb++) vrb_map[rb] |= SL_to_bitmap(csirs_pdu_rel15->symb_l0, 1); } break; case NR_CSI_RS_ResourceMapping__nrofPorts_p16: if (resourceMapping.cdm_Type == NR_CSI_RS_ResourceMapping__cdm_Type_cdm4_FD2_TD2) csirs_pdu_rel15->row = 12; else csirs_pdu_rel15->row = 11; for (int rb = csirs_pdu_rel15->start_rb; rb < (csirs_pdu_rel15->start_rb + csirs_pdu_rel15->nr_of_rbs); rb++) vrb_map[rb] |= SL_to_bitmap(csirs_pdu_rel15->symb_l0, 2); break; case NR_CSI_RS_ResourceMapping__nrofPorts_p24: if (resourceMapping.cdm_Type == NR_CSI_RS_ResourceMapping__cdm_Type_cdm4_FD2_TD2) { csirs_pdu_rel15->row = 14; for (int rb = csirs_pdu_rel15->start_rb; rb < (csirs_pdu_rel15->start_rb + csirs_pdu_rel15->nr_of_rbs); rb++) vrb_map[rb] |= (SL_to_bitmap(csirs_pdu_rel15->symb_l0, 2) | SL_to_bitmap(csirs_pdu_rel15->symb_l1, 2)); } else{ if (resourceMapping.cdm_Type == NR_CSI_RS_ResourceMapping__cdm_Type_cdm8_FD2_TD4) { csirs_pdu_rel15->row = 15; for (int rb = csirs_pdu_rel15->start_rb; rb < (csirs_pdu_rel15->start_rb + csirs_pdu_rel15->nr_of_rbs); rb++) vrb_map[rb] |= SL_to_bitmap(csirs_pdu_rel15->symb_l0, 3); } else { csirs_pdu_rel15->row = 13; for (int rb = csirs_pdu_rel15->start_rb; rb < (csirs_pdu_rel15->start_rb + csirs_pdu_rel15->nr_of_rbs); rb++) vrb_map[rb] |= (SL_to_bitmap(csirs_pdu_rel15->symb_l0, 2) | SL_to_bitmap(csirs_pdu_rel15->symb_l1, 2)); } } break; case NR_CSI_RS_ResourceMapping__nrofPorts_p32: if (resourceMapping.cdm_Type == NR_CSI_RS_ResourceMapping__cdm_Type_cdm4_FD2_TD2) { csirs_pdu_rel15->row = 17; for (int rb = csirs_pdu_rel15->start_rb; rb < (csirs_pdu_rel15->start_rb + csirs_pdu_rel15->nr_of_rbs); rb++) vrb_map[rb] |= (SL_to_bitmap(csirs_pdu_rel15->symb_l0, 2) | SL_to_bitmap(csirs_pdu_rel15->symb_l1, 2)); } else{ if (resourceMapping.cdm_Type == NR_CSI_RS_ResourceMapping__cdm_Type_cdm8_FD2_TD4) { csirs_pdu_rel15->row = 18; for (int rb = csirs_pdu_rel15->start_rb; rb < (csirs_pdu_rel15->start_rb + csirs_pdu_rel15->nr_of_rbs); rb++) vrb_map[rb] |= SL_to_bitmap(csirs_pdu_rel15->symb_l0, 3); } else { csirs_pdu_rel15->row = 16; for (int rb = csirs_pdu_rel15->start_rb; rb < (csirs_pdu_rel15->start_rb + csirs_pdu_rel15->nr_of_rbs); rb++) vrb_map[rb] |= (SL_to_bitmap(csirs_pdu_rel15->symb_l0, 2) | SL_to_bitmap(csirs_pdu_rel15->symb_l1, 2)); } } break; default: AssertFatal(1==0,"Invalid number of ports in CSI-RS resource\n"); } break; default: AssertFatal(1==0,"Invalid freqency domain allocation in CSI-RS resource\n"); } dl_req->nPDUs++; } } } } } void nr_measgap_scheduling(gNB_MAC_INST *nr_mac, frame_t frame, sub_frame_t slot) { NR_SCHED_ENSURE_LOCKED(&nr_mac->sched_lock); NR_UEs_t *UE_info = &nr_mac->UE_info; UE_iterator(UE_info->connected_ue_list, UE) { measgap_config_t *mgc = &UE->measgap_config; if (!mgc->enable) continue; const int slots_frame = nr_mac->frame_structure.numb_slots_frame; const frame_t f = (frame + (slot + mgc->n_slots_advance) / slots_frame) % 1024; const slot_t s = (slot + mgc->n_slots_advance) % slots_frame; // TS 38 331 - Section 5.5.2.9 Measurement gap configuration if (!(((f % (mgc->mgrp_ms / 10)) == (mgc->gapOffset / 10)) && (s == mgc->gapOffset % 10))) continue; NR_timer_t *t = &UE->UE_sched_ctrl.transm_interrupt; interrupt_followup_action_t a = nr_timer_is_active(t) ? UE->interrupt_action : FOLLOW_INSYNC; // start a timer to stop scheduling UE during MeasGap, or extend timer for // duration of measGap with existing follow-up action // TODO: if the timer is running, it might be to stop scheduling of the UE afterwards, but then it does not make sense to stop // scheduling the UE now, we should maybe just skip it? if (!nr_timer_is_active(t) || nr_timer_remaining_time(t) < mgc->mgl_slots) { nr_mac_trigger_ul_failure(&UE->UE_sched_ctrl, UE->current_DL_BWP.scs); /* set the UE to "not active" */ nr_mac_interrupt_ue_transmission(nr_mac, UE, a, mgc->mgl_slots); } } } void nr_mac_clean_cellgroup(NR_CellGroupConfig_t *cell_group) { DevAssert(cell_group != NULL); /* remove a reconfigurationWithSync, we don't need it anymore */ if (cell_group->spCellConfig && cell_group->spCellConfig->reconfigurationWithSync != NULL) { ASN_STRUCT_FREE(asn_DEF_NR_ReconfigurationWithSync, cell_group->spCellConfig->reconfigurationWithSync); cell_group->spCellConfig->reconfigurationWithSync = NULL; } /* remove the rlc_BearerToReleaseList, we don't need it anymore */ if (cell_group->rlc_BearerToReleaseList != NULL) { struct NR_CellGroupConfig__rlc_BearerToReleaseList *l = cell_group->rlc_BearerToReleaseList; asn_sequence_del(&l->list, l->list.count, /* free */ 1); free(cell_group->rlc_BearerToReleaseList); cell_group->rlc_BearerToReleaseList = NULL; } /* remove reestablishRLC, we don't need it anymore */ for (int i = 0; i < cell_group->rlc_BearerToAddModList->list.count; ++i) free_and_zero(cell_group->rlc_BearerToAddModList->list.array[i]->reestablishRLC); } int nr_mac_get_reconfig_delay_slots(NR_SubcarrierSpacing_t scs) { /* we previously assumed a specific "slot ahead" value for the PHY processing * time. However, we cannot always know it (e.g., third-party PHY), so simply * assume a tentative worst-case slot processing time */ const int sl_ahead = 10; /* 16ms seems to be the most common: See 38.331 Tab 12.1-1 */ int delay_ms = NR_RRC_RECONFIGURATION_DELAY_MS + NR_RRC_BWP_SWITCHING_DELAY_MS; return (delay_ms << scs) + sl_ahead; } int nr_mac_interrupt_ue_transmission(gNB_MAC_INST *mac, NR_UE_info_t *UE, interrupt_followup_action_t action, int slots) { DevAssert(mac != NULL); DevAssert(UE != NULL); NR_SCHED_ENSURE_LOCKED(&mac->sched_lock); nr_timer_setup(&UE->UE_sched_ctrl.transm_interrupt, slots, 1); nr_timer_start(&UE->UE_sched_ctrl.transm_interrupt); UE->interrupt_action = action; // it might happen that timing advance command should be sent during the UE // inactivity time. To prevent this, set a variable as if we would have just // sent it. This way, another TA command will for sure be sent in some // frames, after the inactivity of the UE. UE->UE_sched_ctrl.ta_frame = (mac->frame - 1 + 1024) % 1024; LOG_D(NR_MAC, "UE %04x: Interrupt UE transmission (%d slots) action %d\n", UE->rnti, slots, UE->interrupt_action); return 0; } int nr_transmission_action_indicator_stop(gNB_MAC_INST *mac, NR_UE_info_t *UE_info) { int delay = nr_mac_get_reconfig_delay_slots(UE_info->current_DL_BWP.scs); nr_mac_interrupt_ue_transmission(mac, UE_info, FOLLOW_OUTOFSYNC, delay); LOG_I(NR_MAC, "gNB-DU received the TransmissionActionIndicator with Stop value for UE %04x\n", UE_info->rnti); return 0; } void nr_mac_trigger_release_complete(gNB_MAC_INST *mac, int rnti) { // the CU might not know such UE, e.g., because we never sent a message to // it, so there might not be a corresponding entry for such UE in the look up // table. This can happen, e.g., on Msg.3 with C-RNTI, where we create a UE // MAC context, decode the PDU, find the C-RNTI MAC CE, and then throw the // newly created context away. See also in _nr_rx_sdu() and commit 93f59a3c6e56f if (!du_exists_f1_ue_data(rnti)) return; // unlock the scheduler temporarily to prevent possible deadlocks with // du_remove_f1_ue_data() (and also while sending the message to RRC) NR_SCHED_UNLOCK(&mac->sched_lock); f1_ue_data_t ue_data = du_get_f1_ue_data(rnti); f1ap_ue_context_rel_cplt_t complete = { .gNB_CU_ue_id = ue_data.secondary_ue, .gNB_DU_ue_id = rnti, }; mac->mac_rrc.ue_context_release_complete(&complete); du_remove_f1_ue_data(rnti); NR_SCHED_LOCK(&mac->sched_lock); } void nr_mac_release_ue(gNB_MAC_INST *mac, int rnti) { NR_SCHED_ENSURE_LOCKED(&mac->sched_lock); nr_rlc_remove_ue(rnti); mac_remove_nr_ue(mac, rnti); } void nr_mac_update_timers(module_id_t module_id, frame_t frame, slot_t slot) { gNB_MAC_INST *mac = RC.nrmac[module_id]; /* already mutex protected: held in gNB_dlsch_ulsch_scheduler() */ NR_SCHED_ENSURE_LOCKED(&mac->sched_lock); NR_UEs_t *UE_info = &mac->UE_info; UE_iterator(UE_info->connected_ue_list, UE) { NR_UE_sched_ctrl_t *sched_ctrl = &UE->UE_sched_ctrl; if (nr_mac_check_release(sched_ctrl, UE->rnti)) { // trigger release first as nr_mac_release_ue() invalidates UE ptr nr_mac_trigger_release_complete(mac, UE->rnti); nr_mac_release_ue(mac, UE->rnti); // go back to examine the next UE, which is at the position the // current UE was UE--; continue; } /* check if UL failure and trigger release request if necessary */ bool released = nr_mac_check_ul_failure(mac, UE->rnti, sched_ctrl); if (released) { // go back to examine the next UE, which is at the position the current UE was UE--; continue; } if (nr_timer_tick(&sched_ctrl->transm_interrupt)) { /* expired */ nr_timer_stop(&sched_ctrl->transm_interrupt); if (UE->interrupt_action == FOLLOW_OUTOFSYNC) { nr_mac_trigger_ul_failure(sched_ctrl, UE->current_DL_BWP.scs); } else { DevAssert(UE->interrupt_action == FOLLOW_INSYNC); nr_mac_reset_ul_failure(sched_ctrl); } } } } int ul_buffer_index(int frame, int slot, int slots_per_frame, int size) { const int abs_slot = frame * slots_per_frame + slot; return abs_slot % size; } void UL_tti_req_ahead_initialization(gNB_MAC_INST *gNB, int n, int CCid, frame_t frameP, int slotP) { if(gNB->UL_tti_req_ahead[CCid][1].Slot == 1) return; /* fill in slot/frame numbers: slot is fixed, frame will be updated by scheduler * consider that scheduler runs sl_ahead: the first sl_ahead slots are * already "in the past" and thus we put frame 1 instead of 0! */ for (int i = 0; i < gNB->UL_tti_req_ahead_size; ++i) { int abs_slot = frameP * n + slotP + i; nfapi_nr_ul_tti_request_t *req = &gNB->UL_tti_req_ahead[CCid][abs_slot % gNB->UL_tti_req_ahead_size]; req->SFN = (abs_slot / n) % MAX_FRAME_NUMBER; req->Slot = abs_slot % n; } } int get_fapi_beamforming_index(gNB_MAC_INST *mac, int ssb_idx) { int beam_idx = mac->fapi_beam_index[ssb_idx]; AssertFatal(beam_idx >= 0, "Invalid beamforming index %d\n", beam_idx); return beam_idx; } // TODO this is a placeholder for a possibly more complex function // for now the fapi beam index is the number of SSBs transmitted before ssb_index i void fapi_beam_index_allocation(NR_ServingCellConfigCommon_t *scc, gNB_MAC_INST *mac) { int len = 0; uint8_t* buf = NULL; switch (scc->ssb_PositionsInBurst->present) { case NR_ServingCellConfigCommon__ssb_PositionsInBurst_PR_shortBitmap: len = 4; buf = scc->ssb_PositionsInBurst->choice.shortBitmap.buf; break; case NR_ServingCellConfigCommon__ssb_PositionsInBurst_PR_mediumBitmap: len = 8; buf = scc->ssb_PositionsInBurst->choice.mediumBitmap.buf; break; case NR_ServingCellConfigCommon__ssb_PositionsInBurst_PR_longBitmap: len = 64; buf = scc->ssb_PositionsInBurst->choice.longBitmap.buf; break; default : AssertFatal(false, "Invalid configuration\n"); } int fapi_index = 0; for (int i = 0; i < len; ++i) { if ((buf[i / 8] >> (7 - i % 8)) & 0x1) mac->fapi_beam_index[i] = fapi_index++; else mac->fapi_beam_index[i] = -1; } } static inline int get_beam_index(const NR_beam_info_t *beam_info, int frame, int slot, int slots_per_frame) { return ((frame * slots_per_frame + slot) / beam_info->beam_duration) % beam_info->beam_allocation_size; } NR_beam_alloc_t beam_allocation_procedure(NR_beam_info_t *beam_info, int frame, int slot, int beam_index, int slots_per_frame) { // if no beam allocation for analog beamforming we always return beam index 0 (no multiple beams) if (!beam_info->beam_allocation) return (NR_beam_alloc_t) {.new_beam = false, .idx = 0}; const int index = get_beam_index(beam_info, frame, slot, slots_per_frame); for (int i = 0; i < beam_info->beams_per_period; i++) { NR_beam_alloc_t beam_struct = {.new_beam = false, .idx = i}; int *beam = &beam_info->beam_allocation[i][index]; if (*beam == -1) { beam_struct.new_beam = true; *beam = beam_index; } if (*beam == beam_index) { LOG_D(NR_MAC, "%d.%d Using beam structure with index %d for beam %d (%s)\n", frame, slot, beam_struct.idx, beam_index, beam_struct.new_beam ? "new beam" : "old beam"); return beam_struct; } } return (NR_beam_alloc_t) {.new_beam = false, .idx = -1}; } void reset_beam_status(NR_beam_info_t *beam_info, int frame, int slot, int beam_index, int slots_per_frame, bool new_beam) { if(!new_beam) // need to reset only if the beam was allocated specifically for this instance return; const int index = get_beam_index(beam_info, frame, slot, slots_per_frame); for (int i = 0; i < beam_info->beams_per_period; i++) { if (beam_info->beam_allocation[i][index] == beam_index) beam_info->beam_allocation[i][index] = -1; } } void beam_selection_procedures(gNB_MAC_INST *mac, NR_UE_info_t *UE) { // do not perform beam procedures if there is no beam information if (!mac->beam_info.beam_allocation) return; RSRP_report_t *rsrp_report = &UE->UE_sched_ctrl.CSI_report.ssb_rsrp_report; // simple beam switching algorithm -> we select beam with highest RSRP from CSI report int new_bf_index = get_fapi_beamforming_index(mac, rsrp_report->resource_id[0]); if (UE->UE_beam_index == new_bf_index) return; // no beam change needed LOG_I(NR_MAC, "[UE %x] Switching to beam with ID %d (SSB number %d)\n", UE->rnti, new_bf_index, rsrp_report->resource_id[0]); UE->UE_beam_index = new_bf_index; } void send_initial_ul_rrc_message(int rnti, const uint8_t *sdu, sdu_size_t sdu_len, void *data) { gNB_MAC_INST *mac = RC.nrmac[0]; NR_UE_info_t *UE = (NR_UE_info_t *)data; NR_SCHED_ENSURE_LOCKED(&mac->sched_lock); uint8_t du2cu[1024]; int encoded_len = 0; if (UE->CellGroup) encoded_len = encode_cellGroupConfig(UE->CellGroup, du2cu, sizeof(du2cu)); DevAssert(mac->f1_config.setup_req != NULL); AssertFatal(mac->f1_config.setup_req->num_cells_available == 1, "can handle only one cell\n"); const f1ap_initial_ul_rrc_message_t ul_rrc_msg = { .plmn = mac->f1_config.setup_req->cell[0].info.plmn, .nr_cellid = mac->f1_config.setup_req->cell[0].info.nr_cellid, .gNB_DU_ue_id = rnti, .crnti = rnti, .rrc_container = (uint8_t *) sdu, .rrc_container_length = sdu_len, .du2cu_rrc_container = encoded_len ? (uint8_t *) du2cu : NULL, .du2cu_rrc_container_length = encoded_len }; mac->mac_rrc.initial_ul_rrc_message_transfer(0, &ul_rrc_msg); } bool prepare_initial_ul_rrc_message(gNB_MAC_INST *mac, NR_UE_info_t *UE) { NR_SCHED_ENSURE_LOCKED(&mac->sched_lock); /* activate SRB0 */ if (!nr_rlc_activate_srb0(UE->rnti, UE, send_initial_ul_rrc_message)) return false; if (UE->uid >= MAX_MOBILES_PER_GNB) { // verifying if any UE left in the meantime and it is possible to get a valid UID uid_linear_allocator_free(&mac->UE_info.uid_allocator, UE->uid); UE->uid = uid_linear_allocator_new(&mac->UE_info.uid_allocator); if (UE->uid >= MAX_MOBILES_PER_GNB) { // if the UE list is full, we should reject the UE with an RRC reject message // to signal the upper layers to send a reject we send an empty CellGroup information element UE->CellGroup = NULL; LOG_W(NR_MAC, "Maximum number of UE alllowed at DU reached. Cannot serve UE %04x", UE->rnti); return true; } } /* create this UE's initial CellGroup */ int CC_id = 0; int srb_id = 1; const NR_ServingCellConfigCommon_t *scc = mac->common_channels[CC_id].ServingCellConfigCommon; const NR_ServingCellConfig_t *sccd = UE->is_redcap ? NULL : mac->common_channels[CC_id].pre_ServingCellConfig; NR_CellGroupConfig_t *cellGroupConfig = get_initial_cellGroupConfig(UE->uid, scc, sccd, &mac->radio_config, &mac->rlc_config); ASN_STRUCT_FREE(asn_DEF_NR_CellGroupConfig, UE->CellGroup); UE->CellGroup = cellGroupConfig; if (!cellGroupConfig) return true; /* the cellGroup sent to CU specifies there is SRB1, so create it */ DevAssert(cellGroupConfig->rlc_BearerToAddModList->list.count == 1); const NR_RLC_BearerConfig_t *bearer = cellGroupConfig->rlc_BearerToAddModList->list.array[0]; DevAssert(bearer->servedRadioBearer->choice.srb_Identity == srb_id); nr_rlc_add_srb(UE->rnti, bearer->servedRadioBearer->choice.srb_Identity, bearer); int priority = bearer->mac_LogicalChannelConfig->ul_SpecificParameters->priority; nr_lc_config_t c = {.lcid = bearer->logicalChannelIdentity, .priority = priority}; nr_mac_add_lcid(&UE->UE_sched_ctrl, &c); return true; } void nr_mac_trigger_release_timer(NR_UE_sched_ctrl_t *sched_ctrl, NR_SubcarrierSpacing_t subcarrier_spacing) { // trigger 60ms sched_ctrl->release_timer = 100 << subcarrier_spacing; } bool nr_mac_check_release(NR_UE_sched_ctrl_t *sched_ctrl, int rnti) { if (sched_ctrl->release_timer == 0) return false; sched_ctrl->release_timer--; return sched_ctrl->release_timer == 0; } #define UL_FAILURE_REQ_GRACE 10000 #define UL_FAILURE_TIMEOUT 30000 void nr_mac_trigger_ul_failure(NR_UE_sched_ctrl_t *sched_ctrl, NR_SubcarrierSpacing_t subcarrier_spacing) { if (sched_ctrl->ul_failure) { /* already running */ return; } sched_ctrl->ul_failure = true; // UL_FAILURE_TIMEOUT ms till triggering release request // UL_FAILURE_REQ_GRACE ms till automatically released after request sched_ctrl->ul_failure_timer = (UL_FAILURE_REQ_GRACE + UL_FAILURE_TIMEOUT) << subcarrier_spacing; } void nr_mac_reset_ul_failure(NR_UE_sched_ctrl_t *sched_ctrl) { sched_ctrl->ul_failure = false; sched_ctrl->ul_failure_timer = 0; sched_ctrl->pusch_consecutive_dtx_cnt = 0; } /* \brief trigger a release request towards the CU. * \returns true if release was requested, else false (in which case the CU * does not know the UE, and we can safely remove it). */ bool nr_mac_request_release_ue(const gNB_MAC_INST *nrmac, int rnti) { if (!du_exists_f1_ue_data(rnti)) { LOG_W(NR_MAC, "UE %04x: no CU-UE ID stored, cannot request release\n", rnti); return false; } f1_ue_data_t ue_data = du_get_f1_ue_data(rnti); f1ap_ue_context_rel_req_t request = { .gNB_CU_ue_id = ue_data.secondary_ue, .gNB_DU_ue_id = rnti, .cause = F1AP_CAUSE_RADIO_NETWORK, .cause_value = F1AP_CauseRadioNetwork_rl_failure_rlc, }; nrmac->mac_rrc.ue_context_release_request(&request); return true; } bool nr_mac_check_ul_failure(gNB_MAC_INST *nrmac, int rnti, NR_UE_sched_ctrl_t *sched_ctrl) { if (!sched_ctrl->ul_failure) return false; if (sched_ctrl->ul_failure_timer > 0) sched_ctrl->ul_failure_timer--; /* trigger once: trigger when ul_failure_timer == UL_FAILURE_REQ_GRACE( we * wait for a UE release command from upper layers), and UE is automatically * released when no answer after UL_FAILURE_REQ_GRACE */ if (sched_ctrl->ul_failure_timer == UL_FAILURE_REQ_GRACE) { LOG_W(MAC, "UE %04x: request release after UL failure timer expiry\n", rnti); bool requested = nr_mac_request_release_ue(nrmac, rnti); if (!requested) sched_ctrl->ul_failure_timer = 0; /* force expiry */ } if (sched_ctrl->ul_failure_timer == 0) { LOG_W(NR_MAC, "UE %04x: no answer for release request, dropping UE\n", rnti); nr_mac_release_ue(nrmac, rnti); return true; } return false; } void nr_mac_trigger_reconfiguration(const gNB_MAC_INST *nrmac, const NR_UE_info_t *UE) { DevAssert(UE->CellGroup != NULL); uint8_t buf[2048]; asn_enc_rval_t enc_rval = uper_encode_to_buffer(&asn_DEF_NR_CellGroupConfig, NULL, UE->CellGroup, buf, sizeof(buf)); AssertFatal(enc_rval.encoded > 0, "ASN1 encoding of CellGroupConfig failed, failed type %s\n", enc_rval.failed_type->name); du_to_cu_rrc_information_t du2cu = { .cellGroupConfig = buf, .cellGroupConfig_length = (enc_rval.encoded + 7) >> 3, }; f1_ue_data_t ue_data = du_get_f1_ue_data(UE->rnti); f1ap_ue_context_modif_required_t required = { .gNB_CU_ue_id = ue_data.secondary_ue, .gNB_DU_ue_id = UE->rnti, .du_to_cu_rrc_information = &du2cu, .cause = F1AP_CAUSE_RADIO_NETWORK, .cause_value = F1AP_CauseRadioNetwork_action_desirable_for_radio_reasons, }; nrmac->mac_rrc.ue_context_modification_required(&required); } /* \brief add bearers from CellGroupConfig. * * This is a kind of hack, as this should be processed through a F1 UE Context * setup request, but some modes do not use that (NSA/do-ra/phy_test). */ void process_addmod_bearers_cellGroupConfig(NR_UE_sched_ctrl_t *sched_ctrl, const struct NR_CellGroupConfig__rlc_BearerToAddModList *addmod) { if (addmod == NULL) return; /* nothing to do */ for (int i = 0; i < addmod->list.count; ++i) { const NR_RLC_BearerConfig_t *conf = addmod->list.array[i]; int lcid = conf->logicalChannelIdentity; nr_lc_config_t c = {.lcid = lcid}; nr_mac_add_lcid(sched_ctrl, &c); } } long get_lcid_from_drbid(int drb_id) { return drb_id + 3; /* LCID is DRB + 3 */ } long get_lcid_from_srbid(int srb_id) { return srb_id; } static bool eq_lcid_config(const void *vval, const void *vit) { const nr_lc_config_t *val = (const nr_lc_config_t *)vval; const nr_lc_config_t *it = (const nr_lc_config_t *)vit; return it->lcid == val->lcid; } static int cmp_lc_config(const void *va, const void *vb) { const nr_lc_config_t *a = (const nr_lc_config_t *)va; const nr_lc_config_t *b = (const nr_lc_config_t *)vb; if (a->priority < b->priority) return -1; if (a->priority == b->priority) return 0; return 1; } nr_lc_config_t *nr_mac_get_lc_config(NR_UE_sched_ctrl_t* sched_ctrl, int lcid) { nr_lc_config_t c = {.lcid = lcid}; elm_arr_t elm = find_if(&sched_ctrl->lc_config, &c, eq_lcid_config); if (elm.found) return elm.it; else return NULL; } bool nr_mac_add_lcid(NR_UE_sched_ctrl_t* sched_ctrl, const nr_lc_config_t *c) { elm_arr_t elm = find_if(&sched_ctrl->lc_config, (void *) c, eq_lcid_config); if (elm.found) { LOG_I(NR_MAC, "cannot add LCID %d: already present, updating configuration\n", c->lcid); nr_lc_config_t *exist = (nr_lc_config_t *)elm.it; *exist = *c; } else { LOG_D(NR_MAC, "Add LCID %d\n", c->lcid); seq_arr_push_back(&sched_ctrl->lc_config, (void*) c, sizeof(*c)); } void *base = seq_arr_front(&sched_ctrl->lc_config); size_t nmemb = seq_arr_size(&sched_ctrl->lc_config); size_t size = sizeof(*c); qsort(base, nmemb, size, cmp_lc_config); return true; } bool nr_mac_remove_lcid(NR_UE_sched_ctrl_t *sched_ctrl, long lcid) { nr_lc_config_t c = {.lcid = lcid}; elm_arr_t elm = find_if(&sched_ctrl->lc_config, &c, eq_lcid_config); if (!elm.found) { LOG_E(NR_MAC, "can not remove LC: no such LC with ID %ld\n", lcid); return false; } seq_arr_erase(&sched_ctrl->lc_config, elm.it); return true; } bool nr_mac_get_new_rnti(NR_UEs_t *UEs, rnti_t *rnti) { int loop = 0; bool exist_connected_ue, exist_in_pending_ra_ue; do { *rnti = (taus() % 0xffef) + 1; exist_connected_ue = find_nr_UE(UEs, *rnti) != NULL; exist_in_pending_ra_ue = find_ra_UE(UEs, *rnti) != NULL; loop++; } while (loop < 100 && (exist_connected_ue || exist_in_pending_ra_ue)); return loop < 100; // nothing found: loop count 100 } /// @brief Orders PDCCH aggregation levels so that we first check desired aggregation level according to /// pdcch_cl_adjust /// @param agg_level_search_order in/out 5-element array of aggregation levels from 0 to 4 /// @param pdcch_cl_adjust value from 0 to 1 indication channel impariments (0 - good channel, 1 - bad channel) static void determine_aggregation_level_search_order(int agg_level_search_order[NUM_PDCCH_AGG_LEVELS], float pdcch_cl_adjust) { int desired_agg_level_index = round(4 * pdcch_cl_adjust); int agg_level_search_index = 0; for (int i = desired_agg_level_index; i < NUM_PDCCH_AGG_LEVELS; i++) { agg_level_search_order[agg_level_search_index++] = i; } for (int i = desired_agg_level_index - 1; i >= 0; i--) { agg_level_search_order[agg_level_search_index++] = i; } } /// @brief Update PDCCH closed loop adjust for UE depending on detection of feedback. /// @param sched_ctrl UE scheduling control info /// @param feedback_not_detected Whether feedback (PUSCH or HARQ) was detected void nr_mac_update_pdcch_closed_loop_adjust(NR_UE_sched_ctrl_t *sched_ctrl, bool feedback_not_detected) { if (feedback_not_detected) { sched_ctrl->pdcch_cl_adjust = min(1, sched_ctrl->pdcch_cl_adjust + 0.05); } else { sched_ctrl->pdcch_cl_adjust = max(0, sched_ctrl->pdcch_cl_adjust - 0.01); } }