/* * SPDX-License-Identifier: LicenseRef-CSSL-1.0 */ /*! * \brief gNB configuration performed by RRC or as a consequence of RRC procedures */ #include #include #include #include #include #include #include "BIT_STRING.h" #include "LAYER2/NR_MAC_COMMON/nr_mac_common.h" #include "LAYER2/NR_MAC_gNB/mac_proto.h" #include "GNB_APP/gnb_config.h" #include "NR_MIB.h" #include "NR_MAC_gNB/nr_mac_gNB.h" #include "NR_BCCH-BCH-Message.h" #include "NR_ServingCellConfigCommon.h" #include "NR_MIB.h" #include "SCHED_NR/phy_frame_config_nr.h" #include "T.h" #include "asn_internal.h" #include "assertions.h" #include "common/ran_context.h" #include "common/utils/T/T.h" #include "common/utils/nr/nr_common.h" #include "executables/softmodem-common.h" #include "f1ap_messages_types.h" #include "nfapi/oai_integration/vendor_ext.h" #include "nfapi_interface.h" #include "nfapi_nr_interface.h" #include "nfapi_nr_interface_scf.h" #include "utils.h" c16_t convert_precoder_weight(double complex c_in) { return (c16_t) {.r = round(SHRT_MAX*creal(c_in)), .i = round(SHRT_MAX*cimag(c_in))}; } static void free_dbt_config(nfapi_nr_dbt_pdu_t *dbt) { if (!dbt || !dbt->dig_beam_list) return; for (int i = 0; i < dbt->num_dig_beams; ++i) free(dbt->dig_beam_list[i].txru_list); free(dbt->dig_beam_list); dbt->dig_beam_list = NULL; dbt->num_dig_beams = 0; dbt->num_txrus = 0; } static void log_dbt_table(const nr_beam_table_t *bt) { if (!bt || bt->num_beams <= 0 || bt->num_weights_per_beam <= 0 || !bt->beam_weights) return; LOG_I(NR_MAC, "DBT: num_beams=%d num_weights_per_beam=%d\n", bt->num_beams, bt->num_weights_per_beam); for (int b = 0; b < bt->num_beams; ++b) { uint16_t beam_id = bt->beam_ids ? bt->beam_ids[b] : b; const int max_line = 2048; char line[max_line]; int off = snprintf(line, sizeof(line), "DBT beam[%d] id=%u:", b, beam_id); for (int w = 0; w < bt->num_weights_per_beam && off > -1 && off < max_line - 1; ++w) off += snprintf(line + off, sizeof(line) - off, " w[%d]=(%0.6f,%0.6f)", w, creal(bt->beam_weights[b][w]), cimag(bt->beam_weights[b][w])); LOG_D(NR_MAC, "%s\n", line); } } void get_K1_K2(int N1, int N2, int *K1, int *K2, int layers) { // num of allowed k1 and k2 according to 5.2.2.2.1-3 and -4 in 38.214 switch (layers) { case 1: *K1 = 1; *K2 = 1; break; case 2: *K2 = N2 == 1 ? 1 : 2; if(N2 == N1 || N1 == 2) *K1 = 2; else if (N2 == 1) *K1 = 4; else *K1 = 3; break; case 3: case 4: *K2 = N2 == 1 ? 1 : 2; if (N1 == 6) *K1 = 5; else *K1 = N1; break; default: AssertFatal(false, "Number of layers %d not supported\n", layers); } } int get_NTN_Koffset(const NR_ServingCellConfigCommon_t *scc) { if (scc->ext2 && scc->ext2->ntn_Config_r17 && scc->ext2->ntn_Config_r17->cellSpecificKoffset_r17) return *scc->ext2->ntn_Config_r17->cellSpecificKoffset_r17 << *scc->ssbSubcarrierSpacing; return 0; } int precoding_weigths_generation(nfapi_nr_pm_list_t *mat, int pmiq, int L, int N1, int N2, int O1, int O2, int num_antenna_ports, double complex theta_n[4], double complex v_lm[N1 * O1 + 4 * O1][N2 * O2 + O2][N2 * N1]) { // Table 5.2.2.2.1-X: // Codebook for L-layer CSI reporting using antenna ports 3000 to 2999+PCSI-RS // pmi=1,...,pmi_size are computed as follows int K1 = 0, K2 = 0; get_K1_K2(N1, N2, &K1, &K2, L); int I2 = L == 1 ? 4 : 2; for (int k2 = 0; k2 < K2; k2++) { for (int k1 = 0; k1 < K1; k1++) { for (int mm = 0; mm < N2 * O2; mm++) { // i_1_2 for (int ll = 0; ll < N1 * O1; ll++) { // i_1_1 for (int nn = 0; nn < I2; nn++) { // i_2 mat->pmi_pdu[pmiq].pm_idx = pmiq + 1; // index 0 is the identity matrix mat->pmi_pdu[pmiq].numLayers = L; mat->pmi_pdu[pmiq].num_ant_ports = num_antenna_ports; LOG_D(PHY, "layer %d Codebook pmiq = %d\n", L, pmiq); for (int j_col = 0; j_col < L; j_col++) { int llc = ll + (k1 * O1 * (j_col & 1)); int mmc = mm + (k2 * O2 * (j_col & 1)); double complex phase_sign = j_col <= ((L - 1) / 2) ? 1 : -1; double complex res_code; for (int i_rows = 0; i_rows < N1 * N2; i_rows++) { nfapi_nr_pm_weights_t *weights = &mat->pmi_pdu[pmiq].weights[j_col][i_rows]; res_code = sqrt(1 / (double)L) * v_lm[llc][mmc][i_rows]; c16_t precoder_weight = convert_precoder_weight(res_code); *weights = precoder_weight; LOG_D(PHY, "%d Layer Precoding Matrix[pmi %d][antPort %d][layerIdx %d]= %f+j %f -> Fixed Point %d+j %d \n", L, pmiq, i_rows, j_col, creal(res_code), cimag(res_code), weights->r, weights->i); } for (int i_rows = N1 * N2; i_rows < 2 * N1 * N2; i_rows++) { nfapi_nr_pm_weights_t *weights = &mat->pmi_pdu[pmiq].weights[j_col][i_rows]; res_code = sqrt(1 / (double)L) * (phase_sign)*theta_n[nn] * v_lm[llc][mmc][i_rows - N1 * N2]; c16_t precoder_weight = convert_precoder_weight(res_code); *weights = precoder_weight; LOG_D(PHY, "%d Layer Precoding Matrix[pmi %d][antPort %d][layerIdx %d]= %f+j %f -> Fixed Point %d+j %d \n", L, pmiq, i_rows, j_col, creal(res_code), cimag(res_code), weights->r, weights->i); } } pmiq++; } } } } } return pmiq; } static int precoding_weights_generation_4x1_high_rank(nfapi_nr_pm_list_t *mat, int pmiq, int layers) { const uint8_t i11_count = nr_type1_sp_4x1_i11_count(layers); for (uint8_t i11 = 0; i11 < i11_count; i11++) { for (uint8_t i2 = 0; i2 < 2; i2++) { nfapi_nr_pm_pdu_t *pdu = &mat->pmi_pdu[pmiq]; pdu->pm_idx = pmiq + 1; // index zero is the identity matrix pdu->numLayers = layers; pdu->num_ant_ports = 8; AssertFatal(nr_type1_sp_4x1_weights(layers, i11, 0, i2, pdu->weights), "Invalid rank-%d Type-I single-panel PMI i11=%u i2=%u\n", layers, i11, i2); pmiq++; } } return pmiq; } nfapi_nr_pm_list_t init_DL_MIMO_codebook(gNB_MAC_INST *gNB, nr_pdsch_AntennaPorts_t antenna_ports) { int num_antenna_ports = antenna_ports.N1 * antenna_ports.N2 * antenna_ports.XP; if (num_antenna_ports < 2) return (nfapi_nr_pm_list_t) {0}; //NR Codebook Generation for codebook type1 SinglePanel int N1 = antenna_ports.N1; int N2 = antenna_ports.N2; //Uniform Planner Array: UPA // X X X X ... X // X X X X ... X // N2 . . . . ... . // X X X X ... X // |<-----N1---->| //Get the uniform planar array parameters // To be confirmed int O2 = N2 > 1 ? 4 : 1; //Vertical beam oversampling (1 or 4) int O1 = num_antenna_ports > 2 ? 4 : 1; //Horizontal beam oversampling (1 or 4) const int max_mimo_layers = min(num_antenna_ports, NR_MAX_NB_LAYERS); const int low_codebook_layers = min(max_mimo_layers, 4); const bool high_rank_4x1 = num_antenna_ports == 8 && antenna_ports.XP == 2 && N1 == 4 && N2 == 1; AssertFatal(num_antenna_ports < 16, "Max number of antenna ports supported is currently 16\n"); int K1 = 0, K2 = 0; nfapi_nr_pm_list_t mat = {.num_pm_idx = 0}; memset(gNB->precoding_matrix_size, 0, sizeof(gNB->precoding_matrix_size)); for (int i = 0; i < low_codebook_layers; i++) { get_K1_K2(N1, N2, &K1, &K2, i + 1); int i2_size = i == 0 ? 4 : 2; gNB->precoding_matrix_size[i] = i2_size * N1 * O1 * N2 * O2 * K1 * K2; mat.num_pm_idx += gNB->precoding_matrix_size[i]; } if (max_mimo_layers > 4 && !high_rank_4x1) { LOG_W(NR_MAC, "Rank-5--8 PMI is only implemented for the eight-port Type-I single-panel 4x1 geometry; " "unsupported geometry XP=%d N1=%d N2=%d will use identity precoding\n", antenna_ports.XP, N1, N2); } else if (high_rank_4x1) { for (int layers = 5; layers <= max_mimo_layers; layers++) { gNB->precoding_matrix_size[layers - 1] = 2 * nr_type1_sp_4x1_i11_count(layers); mat.num_pm_idx += gNB->precoding_matrix_size[layers - 1]; } } mat.pmi_pdu = malloc16(mat.num_pm_idx * sizeof(*mat.pmi_pdu)); AssertFatal(mat.pmi_pdu != NULL, "out of memory\n"); // Generation of codebook Type1 with codebookMode 1 (num_antenna_ports < 16) // Generate DFT vertical beams // ll: index of a vertical beams vector (represented by i1_1 in TS 38.214) const int max_l = N1 * O1 + 4 * O1; // max k1 is 4*O1 double complex v[max_l][N1]; for (int ll = 0; ll < max_l; ll++) { // i1_1 for (int nn = 0; nn < N1; nn++) { v[ll][nn] = cexp(I * (2 * M_PI * nn * ll) / (N1 * O1)); LOG_D(PHY, "v[%d][%d] = %f +j %f\n", ll, nn, creal(v[ll][nn]), cimag(v[ll][nn])); } } // Generate DFT Horizontal beams // mm: index of a Horizontal beams vector (represented by i1_2 in TS 38.214) const int max_m = N2 * O2 + O2; // max k2 is O2 double complex u[max_m][N2]; for (int mm = 0; mm < max_m; mm++) { // i1_2 for (int nn = 0; nn < N2; nn++) { u[mm][nn] = cexp(I * (2 * M_PI * nn * mm) / (N2 * O2)); LOG_D(PHY, "u[%d][%d] = %f +j %f\n", mm, nn, creal(u[mm][nn]), cimag(u[mm][nn])); } } // Generate co-phasing angles // i_2: index of a co-phasing vector // i1_1, i1_2, and i_2 are reported from UEs double complex theta_n[4]; for (int nn = 0; nn < 4; nn++) { theta_n[nn] = cexp(I * M_PI * nn / 2); LOG_D(PHY, "theta_n[%d] = %f +j %f\n", nn, creal(theta_n[nn]), cimag(theta_n[nn])); } // Kronecker product v_lm double complex v_lm[max_l][max_m][N2 * N1]; // v_ll_mm_codebook denotes the elements of a precoding matrix W_i1,1_i_1,2 for (int ll = 0; ll < max_l; ll++) { // i_1_1 for (int mm = 0; mm < max_m; mm++) { // i_1_2 for (int nn1 = 0; nn1 < N1; nn1++) { for (int nn2 = 0; nn2 < N2; nn2++) { v_lm[ll][mm][nn1 * N2 + nn2] = v[ll][nn1] * u[mm][nn2]; LOG_D(PHY, "v_lm[%d][%d][%d] = %f +j %f\n", ll, mm, nn1 * N2 + nn2, creal(v_lm[ll][mm][nn1 * N2 + nn2]), cimag(v_lm[ll][mm][nn1 * N2 + nn2])); } } } } int pmiq = 0; for (int layers = 1; layers <= low_codebook_layers; layers++) pmiq = precoding_weigths_generation(&mat, pmiq, layers, N1, N2, O1, O2, num_antenna_ports, theta_n, v_lm); if (high_rank_4x1) for (int layers = 5; layers <= max_mimo_layers; layers++) pmiq = precoding_weights_generation_4x1_high_rank(&mat, pmiq, layers); AssertFatal(pmiq == mat.num_pm_idx, "Generated %d precoding matrices but allocated %d\n", pmiq, mat.num_pm_idx); return mat; } /** * @brief Get the first UL slot index in period * @param fs frame structure * @param mixed indicates whether to include in the count also mixed slot with UL symbols or only full UL slot * @return slot index */ int get_first_ul_slot(const frame_structure_t *fs, bool mixed) { DevAssert(fs); if (fs->frame_type == TDD) { for (int i = 0; i < fs->numb_slots_period; i++) { if ((mixed && is_ul_slot(i, fs)) || fs->period_cfg.tdd_slot_bitmap[i].slot_type == TDD_NR_UPLINK_SLOT) { return i; } } } return 0; // FDD } /** * @brief Get number of DL slots per period (full DL slots + mixed slots with DL symbols) */ int get_dl_slots_per_period(const frame_structure_t *fs) { return fs->frame_type == TDD ? fs->period_cfg.num_dl_slots : fs->numb_slots_frame; } /** * @brief Get number of UL slots per period (full UL slots + mixed slots with UL symbols) */ int get_ul_slots_per_period(const frame_structure_t *fs) { return fs->frame_type == TDD ? fs->period_cfg.num_ul_slots : fs->numb_slots_frame; } /** * @brief Get number of full UL slots per period * @param fs Pointer to the frame structure * @return Number of full UL slots */ int get_full_ul_slots_per_period(const frame_structure_t *fs) { DevAssert(fs); if (fs->frame_type == FDD) return fs->numb_slots_frame; int count = 0; for (int i = 0; i < fs->numb_slots_period; i++) if (fs->period_cfg.tdd_slot_bitmap[i].slot_type == TDD_NR_UPLINK_SLOT) count++; LOG_D(NR_MAC, "Full UL slots in TDD period: %d\n", count); return count; } /** * @brief Get number of full DL slots per period * @param fs Pointer to the frame structure * @return Number of full DL slots */ int get_full_dl_slots_per_period(const frame_structure_t *fs) { DevAssert(fs); if (fs->frame_type == FDD) return fs->numb_slots_frame; int count = 0; for (int i = 0; i < fs->numb_slots_period; i++) if (fs->period_cfg.tdd_slot_bitmap[i].slot_type == TDD_NR_DOWNLINK_SLOT) count++; LOG_D(NR_MAC, "Full DL slots in TDD period: %d\n", count); return count; } /** * @brief Get number of UL slots per frame */ int get_ul_slots_per_frame(const frame_structure_t *fs) { return fs->frame_type == TDD ? fs->numb_period_frame * get_ul_slots_per_period(fs) : fs->numb_slots_frame; } /** * @brief Get the nth UL slot offset for UE index idx in a TDD period using the frame structure bitmap * @param fs frame structure * @param idx UE index * @param count_mixed indicates whether counting mixed slot with UL symbols (e.g. for SRS) or only full UL slots * @return slot index offset */ int get_ul_slot_offset(const frame_structure_t *fs, int idx, bool count_mixed) { DevAssert(fs); // FDD if (fs->frame_type == FDD) return idx; // UL slots indexes in period int ul_slot_idxs[fs->numb_slots_period]; int ul_slot_count = 0; /* Populate the indices of UL slots in the TDD period from the bitmap count also mixed slots with UL symbols if flag count_mixed is present */ for (int i = 0; i < fs->numb_slots_period; i++) { if ((count_mixed && is_ul_slot(i, fs)) || fs->period_cfg.tdd_slot_bitmap[i].slot_type == TDD_NR_UPLINK_SLOT) { ul_slot_idxs[ul_slot_count++] = i; } } // Compute slot index offset int period_idx = idx / ul_slot_count; // wrap up the count of complete TDD periods spanned by the index int ul_slot_idx_in_period = idx % ul_slot_count; // wrap up the UL slot index within the current TDD period return ul_slot_idxs[ul_slot_idx_in_period] + period_idx * fs->numb_slots_period; } static void config_common(gNB_MAC_INST *nrmac, const nr_mac_config_t *config, NR_ServingCellConfigCommon_t *scc) { nfapi_nr_config_request_scf_t *cfg = &nrmac->config[0]; nrmac->common_channels[0].ServingCellConfigCommon = scc; free_dbt_config(&cfg->dbt_config); if (config->bt.num_beams > 0) { AssertFatal(config->bt.beam_weights != NULL, "DBT config invalid: num_beams=%d but beam_weights is NULL\n", config->bt.num_beams); AssertFatal(config->bt.num_weights_per_beam > 0, "DBT config invalid: num_weights_per_beam=%d\n", config->bt.num_weights_per_beam); AssertFatal(config->bt.num_weights_per_beam <= UINT16_MAX, "DBT num_weights_per_beam %d exceeds uint16 max\n", config->bt.num_weights_per_beam); AssertFatal(config->bt.num_beams <= UINT16_MAX, "DBT num_beams %d exceeds uint16 max\n", config->bt.num_beams); cfg->dbt_config.num_dig_beams = (uint16_t)config->bt.num_beams; cfg->dbt_config.num_txrus = (uint16_t)config->bt.num_weights_per_beam; cfg->dbt_config.dig_beam_list = calloc_or_fail(cfg->dbt_config.num_dig_beams, sizeof(*cfg->dbt_config.dig_beam_list)); for (uint16_t b = 0; b < cfg->dbt_config.num_dig_beams; ++b) { nfapi_nr_dig_beam_t *beam = &cfg->dbt_config.dig_beam_list[b]; beam->beam_idx = config->bt.beam_ids ? config->bt.beam_ids[b] : b; beam->txru_list = calloc_or_fail(cfg->dbt_config.num_txrus, sizeof(*beam->txru_list)); for (uint16_t w = 0; w < cfg->dbt_config.num_txrus; ++w) { float re = crealf(config->bt.beam_weights[b][w]); float im = cimagf(config->bt.beam_weights[b][w]); AssertFatal(re >= -1.0f && re <= 1.0f, "DBT real weight out of range [-1,1]: %f\n", re); AssertFatal(im >= -1.0f && im <= 1.0f, "DBT imag weight out of range [-1,1]: %f\n", im); c16_t q15 = convert_precoder_weight(config->bt.beam_weights[b][w]); beam->txru_list[w].dig_beam_weight_Re = (uint16_t)q15.r; beam->txru_list[w].dig_beam_weight_Im = (uint16_t)q15.i; } } LOG_I(NR_MAC, "Loaded DBT: num_beams=%d num_weights_per_beam=%d\n", config->bt.num_beams, config->bt.num_weights_per_beam); log_dbt_table(&config->bt); } // Carrier configuration NR_FrequencyInfoDL_t *frequencyInfoDL = scc->downlinkConfigCommon->frequencyInfoDL; NR_FreqBandIndicatorNR_t nr_band = *frequencyInfoDL->frequencyBandList.list.array[0]; frequency_range_t frequency_range = get_freq_range_from_band(nr_band); int bw_index = get_supported_band_index(frequencyInfoDL->scs_SpecificCarrierList.list.array[0]->subcarrierSpacing, frequency_range, frequencyInfoDL->scs_SpecificCarrierList.list.array[0]->carrierBandwidth); cfg->carrier_config.dl_bandwidth.value = get_supported_bw_mhz(frequency_range, bw_index); cfg->carrier_config.dl_bandwidth.tl.tag = NFAPI_NR_CONFIG_DL_BANDWIDTH_TAG; // temporary cfg->num_tlv++; cfg->carrier_config.dl_frequency.value = from_nrarfcn(nr_band, *scc->ssbSubcarrierSpacing, frequencyInfoDL->absoluteFrequencyPointA) / 1000; // freq in kHz cfg->carrier_config.dl_frequency.tl.tag = NFAPI_NR_CONFIG_DL_FREQUENCY_TAG; cfg->num_tlv++; for (int i = 0; i < 5; i++) { if (i == frequencyInfoDL->scs_SpecificCarrierList.list.array[0]->subcarrierSpacing) { cfg->carrier_config.dl_grid_size[i].value = frequencyInfoDL->scs_SpecificCarrierList.list.array[0]->carrierBandwidth; cfg->carrier_config.dl_k0[i].value = frequencyInfoDL->scs_SpecificCarrierList.list.array[0]->offsetToCarrier; cfg->carrier_config.dl_grid_size[i].tl.tag = NFAPI_NR_CONFIG_DL_GRID_SIZE_TAG; cfg->carrier_config.dl_k0[i].tl.tag = NFAPI_NR_CONFIG_DL_K0_TAG; cfg->num_tlv++; cfg->num_tlv++; } else { cfg->carrier_config.dl_grid_size[i].value = 0; cfg->carrier_config.dl_k0[i].value = 0; } } struct NR_FrequencyInfoUL *frequencyInfoUL = scc->uplinkConfigCommon->frequencyInfoUL; int scs = frequencyInfoUL->scs_SpecificCarrierList.list.array[0]->subcarrierSpacing; frequency_range = get_freq_range_from_band(*frequencyInfoUL->frequencyBandList->list.array[0]); bw_index = get_supported_band_index(scs, frequency_range, frequencyInfoUL->scs_SpecificCarrierList.list.array[0]->carrierBandwidth); cfg->carrier_config.uplink_bandwidth.value = get_supported_bw_mhz(frequency_range, bw_index); cfg->carrier_config.uplink_bandwidth.tl.tag = NFAPI_NR_CONFIG_UPLINK_BANDWIDTH_TAG; // temporary cfg->num_tlv++; int UL_pointA; if (frequencyInfoUL->absoluteFrequencyPointA == NULL) UL_pointA = frequencyInfoDL->absoluteFrequencyPointA; else UL_pointA = *frequencyInfoUL->absoluteFrequencyPointA; cfg->carrier_config.uplink_frequency.value = from_nrarfcn(*frequencyInfoUL->frequencyBandList->list.array[0], *scc->ssbSubcarrierSpacing, UL_pointA) / 1000; // freq in kHz cfg->carrier_config.uplink_frequency.tl.tag = NFAPI_NR_CONFIG_UPLINK_FREQUENCY_TAG; cfg->num_tlv++; for (int i = 0; i < 5; i++) { if (i == scs) { cfg->carrier_config.ul_grid_size[i].value = frequencyInfoUL->scs_SpecificCarrierList.list.array[0]->carrierBandwidth; cfg->carrier_config.ul_k0[i].value = frequencyInfoUL->scs_SpecificCarrierList.list.array[0]->offsetToCarrier; cfg->carrier_config.ul_grid_size[i].tl.tag = NFAPI_NR_CONFIG_UL_GRID_SIZE_TAG; cfg->carrier_config.ul_k0[i].tl.tag = NFAPI_NR_CONFIG_UL_K0_TAG; cfg->num_tlv++; cfg->num_tlv++; } else { cfg->carrier_config.ul_grid_size[i].value = 0; cfg->carrier_config.ul_k0[i].value = 0; } } frame_type_t frame_type = get_frame_type(nr_band, *scc->ssbSubcarrierSpacing); nrmac->common_channels[0].frame_type = frame_type; // Cell configuration cfg->cell_config.phy_cell_id.value = *scc->physCellId; cfg->cell_config.phy_cell_id.tl.tag = NFAPI_NR_CONFIG_PHY_CELL_ID_TAG; cfg->num_tlv++; cfg->cell_config.frame_duplex_type.value = frame_type; cfg->cell_config.frame_duplex_type.tl.tag = NFAPI_NR_CONFIG_FRAME_DUPLEX_TYPE_TAG; cfg->num_tlv++; // SSB configuration cfg->ssb_config.ss_pbch_power.value = scc->ss_PBCH_BlockPower; cfg->ssb_config.ss_pbch_power.tl.tag = NFAPI_NR_CONFIG_SS_PBCH_POWER_TAG; cfg->num_tlv++; cfg->ssb_config.bch_payload.value = 1; cfg->ssb_config.bch_payload.tl.tag = NFAPI_NR_CONFIG_BCH_PAYLOAD_TAG; cfg->num_tlv++; cfg->ssb_config.scs_common.value = *scc->ssbSubcarrierSpacing; cfg->ssb_config.scs_common.tl.tag = NFAPI_NR_CONFIG_SCS_COMMON_TAG; cfg->num_tlv++; // PRACH configuration uint8_t nb_preambles = 64; NR_RACH_ConfigCommon_t *rach_ConfigCommon = scc->uplinkConfigCommon->initialUplinkBWP->rach_ConfigCommon->choice.setup; if (rach_ConfigCommon->totalNumberOfRA_Preambles != NULL) nb_preambles = *rach_ConfigCommon->totalNumberOfRA_Preambles; cfg->prach_config.prach_sequence_length.value = rach_ConfigCommon->prach_RootSequenceIndex.present - 1; cfg->prach_config.prach_sequence_length.tl.tag = NFAPI_NR_CONFIG_PRACH_SEQUENCE_LENGTH_TAG; cfg->num_tlv++; cfg->prach_config.prach_ConfigurationIndex.value = rach_ConfigCommon->rach_ConfigGeneric.prach_ConfigurationIndex; cfg->prach_config.prach_ConfigurationIndex.tl.tag = NFAPI_NR_CONFIG_PRACH_CONFIG_INDEX_TAG; cfg->num_tlv++; if (rach_ConfigCommon->msg1_SubcarrierSpacing) cfg->prach_config.prach_sub_c_spacing.value = *rach_ConfigCommon->msg1_SubcarrierSpacing; else { // If absent, use SCS as derived from the prach-ConfigurationIndex (for 839) int config_index = rach_ConfigCommon->rach_ConfigGeneric.prach_ConfigurationIndex; int frame_type = get_frame_type(nr_band, frequencyInfoUL->scs_SpecificCarrierList.list.array[0]->subcarrierSpacing); int format = get_nr_prach_format_from_index(config_index, UL_pointA, frame_type) & 0xff; cfg->prach_config.prach_sub_c_spacing.value = get_delta_f_RA_long(format); } cfg->prach_config.prach_sub_c_spacing.tl.tag = NFAPI_NR_CONFIG_PRACH_SUB_C_SPACING_TAG; cfg->num_tlv++; cfg->prach_config.restricted_set_config.value = rach_ConfigCommon->restrictedSetConfig; cfg->prach_config.restricted_set_config.tl.tag = NFAPI_NR_CONFIG_RESTRICTED_SET_CONFIG_TAG; cfg->num_tlv++; switch (rach_ConfigCommon->rach_ConfigGeneric.msg1_FDM) { case 0: cfg->prach_config.num_prach_fd_occasions.value = 1; break; case 1: cfg->prach_config.num_prach_fd_occasions.value = 2; break; case 2: cfg->prach_config.num_prach_fd_occasions.value = 4; break; case 3: cfg->prach_config.num_prach_fd_occasions.value = 8; break; default: AssertFatal(1 == 0, "msg1 FDM identifier %ld undefined (0,1,2,3) \n", rach_ConfigCommon->rach_ConfigGeneric.msg1_FDM); } cfg->prach_config.num_prach_fd_occasions.tl.tag = NFAPI_NR_CONFIG_NUM_PRACH_FD_OCCASIONS_TAG; cfg->num_tlv++; cfg->prach_config.num_prach_fd_occasions_list = (nfapi_nr_num_prach_fd_occasions_t *)malloc( cfg->prach_config.num_prach_fd_occasions.value * sizeof(nfapi_nr_num_prach_fd_occasions_t)); for (int i = 0; i < cfg->prach_config.num_prach_fd_occasions.value; i++) { nfapi_nr_num_prach_fd_occasions_t *prach_fd_occasion = &cfg->prach_config.num_prach_fd_occasions_list[i]; // prach_fd_occasion->num_prach_fd_occasions = i; if (cfg->prach_config.prach_sequence_length.value) prach_fd_occasion->prach_root_sequence_index.value = rach_ConfigCommon->prach_RootSequenceIndex.choice.l139; else prach_fd_occasion->prach_root_sequence_index.value = rach_ConfigCommon->prach_RootSequenceIndex.choice.l839; prach_fd_occasion->prach_root_sequence_index.tl.tag = NFAPI_NR_CONFIG_PRACH_ROOT_SEQUENCE_INDEX_TAG; cfg->num_tlv++; prach_fd_occasion->k1.value = NRRIV2PRBOFFSET(scc->uplinkConfigCommon->initialUplinkBWP->genericParameters.locationAndBandwidth, MAX_BWP_SIZE) + rach_ConfigCommon->rach_ConfigGeneric.msg1_FrequencyStart + (get_N_RA_RB(cfg->prach_config.prach_sub_c_spacing.value, scs) * i); if (IS_SA_MODE(get_softmodem_params())) { prach_fd_occasion->k1.value = NRRIV2PRBOFFSET(scc->uplinkConfigCommon->initialUplinkBWP->genericParameters.locationAndBandwidth, MAX_BWP_SIZE) + rach_ConfigCommon->rach_ConfigGeneric.msg1_FrequencyStart + (get_N_RA_RB(cfg->prach_config.prach_sub_c_spacing.value, scs) * i); } else { prach_fd_occasion->k1.value = rach_ConfigCommon->rach_ConfigGeneric.msg1_FrequencyStart + (get_N_RA_RB(cfg->prach_config.prach_sub_c_spacing.value, scs) * i); } prach_fd_occasion->k1.tl.tag = NFAPI_NR_CONFIG_K1_TAG; cfg->num_tlv++; prach_fd_occasion->prach_zero_corr_conf.value = rach_ConfigCommon->rach_ConfigGeneric.zeroCorrelationZoneConfig; prach_fd_occasion->prach_zero_corr_conf.tl.tag = NFAPI_NR_CONFIG_PRACH_ZERO_CORR_CONF_TAG; cfg->num_tlv++; prach_fd_occasion->num_root_sequences.value = compute_nr_root_seq(rach_ConfigCommon, nb_preambles, frame_type, frequency_range); prach_fd_occasion->num_root_sequences.tl.tag = NFAPI_NR_CONFIG_NUM_ROOT_SEQUENCES_TAG; cfg->num_tlv++; prach_fd_occasion->num_unused_root_sequences.tl.tag = NFAPI_NR_CONFIG_NUM_UNUSED_ROOT_SEQUENCES_TAG; prach_fd_occasion->num_unused_root_sequences.value = 0; cfg->num_tlv++; } cfg->prach_config.ssb_per_rach.value = rach_ConfigCommon->ssb_perRACH_OccasionAndCB_PreamblesPerSSB->present - 1; cfg->prach_config.ssb_per_rach.tl.tag = NFAPI_NR_CONFIG_SSB_PER_RACH_TAG; cfg->num_tlv++; // compute and store prach duration in slots from rach_ConfigCommon NR_RACH_ConfigGeneric_t *rachConfig = &scc->uplinkConfigCommon->initialUplinkBWP->rach_ConfigCommon->choice.setup->rach_ConfigGeneric; NR_COMMON_channels_t *cc = nrmac->common_channels; const uint32_t pointA = scc->downlinkConfigCommon->frequencyInfoDL->absoluteFrequencyPointA; const int prach_fmt = (get_nr_prach_format_from_index(rachConfig->prach_ConfigurationIndex, pointA, cc->frame_type) & 0xff); cc->prach_len = (prach_fmt < 4) ? get_long_prach_dur(prach_fmt, *scc->ssbSubcarrierSpacing) : 1; // SSB Table Configuration cfg->ssb_table.ssb_offset_point_a.value = get_ssb_offset_to_pointA(*scc->downlinkConfigCommon->frequencyInfoDL->absoluteFrequencySSB, scc->downlinkConfigCommon->frequencyInfoDL->absoluteFrequencyPointA, *scc->ssbSubcarrierSpacing, frequency_range); cfg->ssb_table.ssb_offset_point_a.tl.tag = NFAPI_NR_CONFIG_SSB_OFFSET_POINT_A_TAG; cfg->num_tlv++; cfg->ssb_table.ssb_period.value = *scc->ssb_periodicityServingCell; cfg->ssb_table.ssb_period.tl.tag = NFAPI_NR_CONFIG_SSB_PERIOD_TAG; cfg->num_tlv++; cfg->ssb_table.ssb_subcarrier_offset.value = get_ssb_subcarrier_offset(*scc->downlinkConfigCommon->frequencyInfoDL->absoluteFrequencySSB, scc->downlinkConfigCommon->frequencyInfoDL->absoluteFrequencyPointA, *scc->ssbSubcarrierSpacing); cfg->ssb_table.ssb_subcarrier_offset.tl.tag = NFAPI_NR_CONFIG_SSB_SUBCARRIER_OFFSET_TAG; cfg->num_tlv++; cfg->ssb_table.case_v3.value = set_ssb_case(*scc->ssbSubcarrierSpacing, nr_band); cfg->ssb_table.case_v3.tl.tag = NFAPI_NR_FAPI_SSB_CASE_VENDOR_EXTENSION_TAG; cfg->num_tlv++; uint8_t *mib_payload = nrmac->common_channels[0].MIB_pdu; uint32_t mib = (mib_payload[2] << 16) | (mib_payload[1] << 8) | mib_payload[0]; cfg->ssb_table.MIB.tl.tag = NFAPI_NR_CONFIG_MIB_TAG; cfg->ssb_table.MIB.value = mib; cfg->num_tlv++; nrmac->ssb_SubcarrierOffset = cfg->ssb_table.ssb_subcarrier_offset.value; nrmac->ssb_OffsetPointA = cfg->ssb_table.ssb_offset_point_a.value; LOG_D(NR_MAC, "ssb_OffsetPointA %d, ssb_SubcarrierOffset %d\n", cfg->ssb_table.ssb_offset_point_a.value, cfg->ssb_table.ssb_subcarrier_offset.value); switch (scc->ssb_PositionsInBurst->present) { case 1: cfg->ssb_table.ssb_mask_list[0].ssb_mask.value = ((uint32_t)scc->ssb_PositionsInBurst->choice.shortBitmap.buf[0]) << 24; cfg->ssb_table.ssb_mask_list[1].ssb_mask.value = 0; break; case 2: cfg->ssb_table.ssb_mask_list[0].ssb_mask.value = ((uint32_t)scc->ssb_PositionsInBurst->choice.mediumBitmap.buf[0]) << 24; cfg->ssb_table.ssb_mask_list[1].ssb_mask.value = 0; break; case 3: cfg->ssb_table.ssb_mask_list[0].ssb_mask.value = 0; cfg->ssb_table.ssb_mask_list[1].ssb_mask.value = 0; for (int i = 0; i < 4; i++) { cfg->ssb_table.ssb_mask_list[0].ssb_mask.value += (uint32_t)scc->ssb_PositionsInBurst->choice.longBitmap.buf[3 - i] << i * 8; cfg->ssb_table.ssb_mask_list[1].ssb_mask.value += (uint32_t)scc->ssb_PositionsInBurst->choice.longBitmap.buf[7 - i] << i * 8; } break; default: AssertFatal(1 == 0, "SSB bitmap size value %d undefined (allowed values 1,2,3) \n", scc->ssb_PositionsInBurst->present); } cfg->ssb_table.ssb_mask_list[0].ssb_mask.tl.tag = NFAPI_NR_CONFIG_SSB_MASK_TAG; cfg->ssb_table.ssb_mask_list[1].ssb_mask.tl.tag = NFAPI_NR_CONFIG_SSB_MASK_TAG; cfg->num_tlv += 2; // logical antenna ports nr_pdsch_AntennaPorts_t pdsch_AntennaPorts = config->pdsch_AntennaPorts; int num_pdsch_antenna_ports = pdsch_AntennaPorts.N1 * pdsch_AntennaPorts.N2 * pdsch_AntennaPorts.XP; cfg->carrier_config.num_tx_ant.value = num_pdsch_antenna_ports; AssertFatal(num_pdsch_antenna_ports > 0 && num_pdsch_antenna_ports < 33, "pdsch_AntennaPorts in 1...32\n"); cfg->carrier_config.num_tx_ant.tl.tag = NFAPI_NR_CONFIG_NUM_TX_ANT_TAG; int num_ssb = 0; for (int i = 0; i < 32; i++) { cfg->ssb_table.ssb_beam_id_list[i].beam_id.tl.tag = NFAPI_NR_CONFIG_BEAM_ID_TAG; if ((cfg->ssb_table.ssb_mask_list[0].ssb_mask.value >> (31 - i)) & 1) { cfg->ssb_table.ssb_beam_id_list[i].beam_id.value = num_ssb; num_ssb++; } cfg->num_tlv++; } for (int i = 0; i < 32; i++) { cfg->ssb_table.ssb_beam_id_list[32 + i].beam_id.tl.tag = NFAPI_NR_CONFIG_BEAM_ID_TAG; if ((cfg->ssb_table.ssb_mask_list[1].ssb_mask.value >> (31 - i)) & 1) { cfg->ssb_table.ssb_beam_id_list[32 + i].beam_id.value = num_ssb; num_ssb++; } cfg->num_tlv++; } int pusch_AntennaPorts = config->pusch_AntennaPorts; cfg->carrier_config.num_rx_ant.value = pusch_AntennaPorts; AssertFatal(pusch_AntennaPorts > 0 && pusch_AntennaPorts < 13, "pusch_AntennaPorts in 1...12\n"); cfg->carrier_config.num_rx_ant.tl.tag = NFAPI_NR_CONFIG_NUM_RX_ANT_TAG; LOG_I(NR_MAC, "Set TX antenna number to %d, Set RX antenna number to %d (num ssb %d: %x,%x)\n", cfg->carrier_config.num_tx_ant.value, cfg->carrier_config.num_rx_ant.value, num_ssb, cfg->ssb_table.ssb_mask_list[0].ssb_mask.value, cfg->ssb_table.ssb_mask_list[1].ssb_mask.value); AssertFatal(cfg->carrier_config.num_tx_ant.value > 0, "carrier_config.num_tx_ant.value %d!\n", cfg->carrier_config.num_tx_ant.value); cfg->num_tlv++; cfg->num_tlv++; #ifdef ENABLE_AERIAL if (nrmac->beam_info.beam_mode == PRECONFIGURED_BEAM_IDX) { // if we are doing BF in Aerial we need these Custom TLV cfg->carrier_config.num_rx_ant.value = 64; //TOOD: Read number of baseband ports (phy ant) from Config? cfg->carrier_config.num_tx_ant.value = 64; //TOOD: Read number of baseband ports (phy ant) from Config? }else{ // In CAT-A Mode these are equal to num_rx_ant (and Aerial ignores the value) cfg->carrier_config.num_rx_port.value = pusch_AntennaPorts; cfg->carrier_config.num_rx_port.tl.tag = NFAPI_NR_CONFIG_NUM_RX_PORT_TAG; cfg->carrier_config.num_tx_port.value = num_pdsch_antenna_ports; cfg->carrier_config.num_tx_port.tl.tag = NFAPI_NR_CONFIG_NUM_TX_PORT_TAG; } #endif // Frame structure configuration uint8_t mu = frequencyInfoUL->scs_SpecificCarrierList.list.array[0]->subcarrierSpacing; if (cfg->cell_config.frame_duplex_type.value == TDD) { cfg->tdd_table.tdd_period.tl.tag = NFAPI_NR_CONFIG_TDD_PERIOD_TAG; cfg->num_tlv++; cfg->tdd_table.tdd_period.value = get_tdd_period_idx(scc->tdd_UL_DL_ConfigurationCommon); LOG_D(NR_MAC, "Setting TDD configuration period to %d\n", cfg->tdd_table.tdd_period.value); } frame_structure_t *fs = &nrmac->frame_structure; config_frame_structure(mu, scc->tdd_UL_DL_ConfigurationCommon, cfg->tdd_table.tdd_period.value, cfg->cell_config.frame_duplex_type.value, fs); if (cfg->cell_config.frame_duplex_type.value == TDD) set_tdd_config_nr(cfg, fs); // precoding matrix configuration (to be improved) cfg->pmi_list = init_DL_MIMO_codebook(nrmac, pdsch_AntennaPorts); if (nrmac->beam_info.beam_mode != NO_BEAM_MODE) { LOG_I(NR_MAC, "Configuring analog beamforming in config_request message\n"); cfg->analog_beamforming_ve.num_beams_period_vendor_ext.tl.tag = NFAPI_NR_FAPI_NUM_BEAMS_PERIOD_VENDOR_EXTENSION_TAG; cfg->analog_beamforming_ve.num_beams_period_vendor_ext.value = nrmac->beam_info.beams_per_period; cfg->num_tlv++; cfg->analog_beamforming_ve.analog_bf_vendor_ext.tl.tag = NFAPI_NR_FAPI_ANALOG_BF_VENDOR_EXTENSION_TAG; cfg->analog_beamforming_ve.analog_bf_vendor_ext.value = 1; // analog BF enabled cfg->num_tlv++; } else { cfg->analog_beamforming_ve.analog_bf_vendor_ext.value = 0; // analog BF disabled if (NFAPI_MODE == NFAPI_MONOLITHIC) { cfg->analog_beamforming_ve.analog_bf_vendor_ext.tl.tag = NFAPI_NR_FAPI_ANALOG_BF_VENDOR_EXTENSION_TAG; cfg->num_tlv++; } } if (IS_SA_MODE(get_softmodem_params())) { int bw = frequencyInfoDL->scs_SpecificCarrierList.list.array[0]->carrierBandwidth; uint64_t bw_khz = (12 * bw) * (15 << mu); uint64_t carr_dl = (cfg->carrier_config.dl_frequency.value + bw_khz / 2) * 1000; uint64_t carr_ul = (cfg->carrier_config.uplink_frequency.value + bw_khz / 2) * 1000; int prb_offset = (frequency_range == FR1) ? cfg->ssb_table.ssb_offset_point_a.value >> scs : cfg->ssb_table.ssb_offset_point_a.value >> (scs - 2); int sc_offset = (frequency_range == FR1) ? cfg->ssb_table.ssb_subcarrier_offset.value >> scs : cfg->ssb_table.ssb_subcarrier_offset.value; if (cfg->carrier_config.dl_frequency.value != cfg->carrier_config.uplink_frequency.value) { LOG_I(NR_MAC, "Command line parameters for OAI UE: -C %lu --CO %ld -r %d --numerology %d --band %ld --ssb %d %s\n", carr_dl, carr_ul - carr_dl, bw, mu, nr_band, 12 * prb_offset + sc_offset, get_softmodem_params()->threequarter_fs ? "-E" : ""); } else { LOG_I(NR_MAC, "Command line parameters for OAI UE: -C %lu -r %d --numerology %d --band %ld --ssb %d %s\n", carr_dl, bw, mu, nr_band, 12 * prb_offset + sc_offset, get_softmodem_params()->threequarter_fs ? "-E" : ""); } } } static void initialize_beam_information(NR_beam_info_t *beam_info, int mu, int slots_per_frame) { if (beam_info->beam_mode == NO_BEAM_MODE) return; int size = mu == 0 ? slots_per_frame << 1 : slots_per_frame; // slots in beam duration gives the number of consecutive slots tied the the same beam AssertFatal(size % beam_info->beam_duration == 0, "Beam duration %d should be divider of number of slots per frame %d\n", beam_info->beam_duration, slots_per_frame); beam_info->beam_allocation_size = size / beam_info->beam_duration; for (int i = 0; i < beam_info->beams_per_period; i++) { beam_info->beam_allocation[i] = malloc16(beam_info->beam_allocation_size * sizeof(*beam_info->beam_allocation)); for (int j = 0; j < beam_info->beam_allocation_size; j++) beam_info->beam_allocation[i][j] = -1; } } static void config_sched_ctrlSIB1(gNB_MAC_INST *nr_mac) { const NR_MIB_t *mib = nr_mac->common_channels[0].mib->message.choice.mib; NR_ServingCellConfigCommon_t *scc = nr_mac->common_channels[0].ServingCellConfigCommon; NR_sched_ctrl_sib1_t *sched_ctrlCommon = calloc_or_fail(1, sizeof(*sched_ctrlCommon)); nr_mac->sched_ctrlSIB1 = sched_ctrlCommon; NR_SubcarrierSpacing_t scs = *scc->ssbSubcarrierSpacing; const long band = *scc->downlinkConfigCommon->frequencyInfoDL->frequencyBandList.list.array[0]; const int bw = scc->downlinkConfigCommon->frequencyInfoDL->scs_SpecificCarrierList.list.array[0]->carrierBandwidth; int8_t ssb_period = *scc->ssb_periodicityServingCell; uint8_t ssb_frame_periodicity = 1; if (ssb_period > 1) ssb_frame_periodicity = 1 << (ssb_period - 1); const int8_t numb_slots_frame = nr_mac->frame_structure.numb_slots_frame; frequency_range_t frequency_range = scc->ssb_PositionsInBurst->present == 3 ? FR2 : FR1; const int prb_offset = frequency_range == FR1 ? nr_mac->ssb_OffsetPointA >> scs : nr_mac->ssb_OffsetPointA >> (scs - 2); NR_Type0_PDCCH_CSS_config_t type0_PDCCH_CSS_config = {0}; for (int i = 0; i < get_max_ssbs(scc); i++) { if (is_ssb_configured(scc, i)) { uint16_t ssb_start_symbol = get_ssb_start_symbol(band, scs, i); get_type0_PDCCH_CSS_config_parameters(&type0_PDCCH_CSS_config, 0, mib, numb_slots_frame, nr_mac->ssb_SubcarrierOffset, ssb_start_symbol, scs, frequency_range, band, bw, i, ssb_frame_periodicity, prb_offset); fill_searchSpaceZero(&sched_ctrlCommon->search_space[i], numb_slots_frame, &type0_PDCCH_CSS_config); } } // CSET0 doesn't depend on SSB index fill_coresetZero(&sched_ctrlCommon->coreset, &type0_PDCCH_CSS_config); nr_mac->cset0_bwp_start = type0_PDCCH_CSS_config.cset_start_rb; nr_mac->cset0_bwp_size = type0_PDCCH_CSS_config.num_rbs; if (type0_PDCCH_CSS_config.type0_pdcch_ss_mux_pattern > 1) { int bwp_start = NRRIV2PRBOFFSET(scc->downlinkConfigCommon->initialDownlinkBWP->genericParameters.locationAndBandwidth, MAX_BWP_SIZE); int bwp_size = NRRIV2BW(scc->downlinkConfigCommon->initialDownlinkBWP->genericParameters.locationAndBandwidth, MAX_BWP_SIZE); // we need to configure a commonControlResourceSet != 0 // because CSET0 would start from a symbol != 0 and that's unwanted for anything but SIB1 // The network configures the commonControlResourceSet in SIB1 so that it is contained in the bandwidth of CSET0 bool do_TCI = nr_mac->radio_config.do_TCI; configure_coreset_for_mux23(scc, nr_mac->cset0_bwp_start - bwp_start, nr_mac->cset0_bwp_size, bwp_start, bwp_size, do_TCI); } } /** * \brief Initializes the UL TDA information at the scheduler, as used for * every UE in UL. This just copies the information from the SCC into a smaller * list. Note that this list should not be modified, as get_num_ul_tda() and * nr_rrc_config_ul_tda() are designed to work in sync (get_num_ul_tda() * assumes an ordering, output by nr_rrc_config_ul_tda()). */ static void init_ul_tda_info(const NR_PUSCH_TimeDomainResourceAllocationList_t *l, seq_arr_t *sa_tda) { DevAssert(l->list.count > 0 && l->list.count <= 16); DevAssert(seq_arr_size(sa_tda) == 0); for (int i = 0; i < l->list.count; ++i) { NR_PUSCH_TimeDomainResourceAllocation_t *t = l->list.array[i]; DevAssert(t->k2); NR_tda_info_t tda = { .mapping_type = typeB, .k2 = *t->k2, .valid_tda = true }; SLIV2SL(t->startSymbolAndLength, &tda.startSymbolIndex, &tda.nrOfSymbols); LOG_I(NR_MAC, "TDA index %d: start %d length %d k2 %ld\n", i, tda.startSymbolIndex, tda.nrOfSymbols, tda.k2); seq_arr_push_back(sa_tda, &tda, sizeof(tda)); } } void nr_mac_config_scc(gNB_MAC_INST *nrmac, NR_ServingCellConfigCommon_t *scc, const nr_mac_config_t *config) { DevAssert(nrmac != NULL); DevAssert(scc != NULL); DevAssert(config != NULL); AssertFatal(scc->ssb_PositionsInBurst->present > 0 && scc->ssb_PositionsInBurst->present < 4, "SSB Bitmap type %d is not valid\n", scc->ssb_PositionsInBurst->present); const int NTN_gNB_Koffset = get_NTN_Koffset(scc); const int n = get_slots_per_frame_from_scs(*scc->ssbSubcarrierSpacing); const int size = n << (int)ceil(log2((NTN_gNB_Koffset + 13) / n + 1)); // 13 is upper limit for max_fb_time nrmac->vrb_map_UL_size = size; int num_beams = 1; if(nrmac->beam_info.beam_mode != NO_BEAM_MODE) num_beams = nrmac->beam_info.beams_per_period; for (int i = 0; i < num_beams; i++) { nrmac->common_channels[0].vrb_map_UL[i] = calloc(size * MAX_BWP_SIZE, sizeof(uint16_t)); AssertFatal(nrmac->common_channels[0].vrb_map_UL[i], "could not allocate memory for RC.nrmac[]->common_channels[0].vrb_map_UL[%d]\n", i); } nrmac->UL_tti_req_ahead_size = size; nrmac->UL_tti_req_ahead[0] = calloc(size, sizeof(nfapi_nr_ul_tti_request_t)); AssertFatal(nrmac->UL_tti_req_ahead[0], "could not allocate memory for nrmac->UL_tti_req_ahead[0]\n"); initialize_beam_information(&nrmac->beam_info, *scc->ssbSubcarrierSpacing, n); LOG_D(NR_MAC, "Configuring common parameters from NR ServingCellConfig\n"); config_common(nrmac, config, scc); fill_beam_index_list(scc, config, nrmac); if (NFAPI_MODE == NFAPI_MONOLITHIC) { // nothing to be sent in the other cases NR_PHY_Config_t phycfg = {.Mod_id = 0, .CC_id = 0, .cfg = &nrmac->config[0]}; DevAssert(nrmac->if_inst->NR_PHY_config_req); nrmac->if_inst->NR_PHY_config_req(&phycfg); } find_SSB_and_RO_available(nrmac); if (IS_SA_MODE(get_softmodem_params())) config_sched_ctrlSIB1(nrmac); seq_arr_init(&nrmac->ul_tda, sizeof(NR_tda_info_t)); init_ul_tda_info(scc->uplinkConfigCommon->initialUplinkBWP->pusch_ConfigCommon->choice.setup->pusch_TimeDomainAllocationList, &nrmac->ul_tda); } bool nr_mac_configure_other_sib(gNB_MAC_INST *nrmac, int num_cu_sib, const f1ap_sib_msg_t cu_sib[num_cu_sib]) { NR_COMMON_channels_t *cc = &nrmac->common_channels[0]; seq_arr_t *du_SIBs = nrmac->common_channels[0].du_SIBs; int num_du_sib = 0; if (du_SIBs) num_du_sib = du_SIBs->size; if (num_cu_sib + num_du_sib == 0) return false; /* no updates */ int config_sibs[num_cu_sib + num_du_sib]; NR_SystemInformation_IEs_t *sysInfo = calloc(1, sizeof(*sysInfo)); //for SIB up to 14 included NR_SystemInformation_IEs_t *sysInfov17 = calloc(1, sizeof(*sysInfov17)); // for othersibs for (int i = 0; i < num_cu_sib; i++) { config_sibs[i] = cu_sib[i].SI_type; const byte_array_t *container = &cu_sib[i].SI_container; if (!container->buf || container->len == 0) { LOG_W(NR_MAC, "SIB%d container is invalid (ptr=%p len=%zu), skipping\n", config_sibs[i], container->buf, container->len); continue; } switch (config_sibs[i]) { case NR_SIB_2: { struct NR_SystemInformation_IEs__sib_TypeAndInfo__Member *type = calloc(1, sizeof(*type)); type->present = NR_SystemInformation_IEs__sib_TypeAndInfo__Member_PR_sib2; // SIB2 coming from CU need to decode it NR_SIB2_t *sib2 = NULL; asn_dec_rval_t dec_rval = uper_decode(NULL, &asn_DEF_NR_SIB2, (void **)&sib2, container->buf, container->len, 0, 0); if (dec_rval.code != RC_OK) { LOG_E(NR_MAC, "cannot decode SIB%d from CU\n", config_sibs[i]); ASN_STRUCT_FREE(asn_DEF_NR_SIB2, sib2); } type->choice.sib2 = sib2; add_sib_to_systeminformation(sysInfo, type); break; } case NR_SIB_3: { struct NR_SystemInformation_IEs__sib_TypeAndInfo__Member *type = calloc_or_fail(1, sizeof(*type)); type->present = NR_SystemInformation_IEs__sib_TypeAndInfo__Member_PR_sib3; NR_SIB3_t *sib3 = NULL; asn_dec_rval_t dec_rval = uper_decode(NULL, &asn_DEF_NR_SIB3, (void **)&sib3, container->buf, container->len, 0, 0); if (dec_rval.code != RC_OK) { LOG_E(NR_MAC, "cannot decode SIB%d from CU\n", config_sibs[i]); ASN_STRUCT_FREE(asn_DEF_NR_SIB3, sib3); free(type); break; } type->choice.sib3 = sib3; add_sib_to_systeminformation(sysInfo, type); break; } case NR_SIB_4: { struct NR_SystemInformation_IEs__sib_TypeAndInfo__Member *type = calloc_or_fail(1, sizeof(*type)); type->present = NR_SystemInformation_IEs__sib_TypeAndInfo__Member_PR_sib4; NR_SIB4_t *sib4 = NULL; asn_dec_rval_t dec_rval = uper_decode(NULL, &asn_DEF_NR_SIB4, (void **)&sib4, container->buf, container->len, 0, 0); if (dec_rval.code != RC_OK) { LOG_E(NR_MAC, "cannot decode SIB%d from CU\n", config_sibs[i]); ASN_STRUCT_FREE(asn_DEF_NR_SIB4, sib4); free(type); break; } type->choice.sib4 = sib4; add_sib_to_systeminformation(sysInfo, type); break; } default : AssertFatal(false, "Invalid or not supported SIB%d\n", config_sibs[i]); } } for (int i = 0; i < num_du_sib; i++) { nr_SIBs_t *si = (nr_SIBs_t *)seq_arr_at(du_SIBs, i); int sib_idx = i + num_cu_sib; config_sibs[sib_idx] = si->SIB_type; switch (config_sibs[sib_idx]) { case NR_SIB_19: { struct NR_SystemInformation_IEs__sib_TypeAndInfo__Member *type_du = calloc(1, sizeof(*type_du)); type_du->present = NR_SystemInformation_IEs__sib_TypeAndInfo__Member_PR_sib19_v1700; NR_SIB19_r17_t *sib19 = get_SIB19_NR(cc->ServingCellConfigCommon); type_du->choice.sib19_v1700 = sib19; add_sib_to_systeminformation(sysInfov17, type_du); break; } default : AssertFatal(false, "Invalid or not supported SIB%d\n", sib_idx); } } update_SIB1_NR_SI(cc->sib1, num_cu_sib + num_du_sib, config_sibs); cc->sib1_bcch_length = encode_SIB_NR(cc->sib1, cc->sib1_bcch_pdu, sizeof(cc->sib1_bcch_pdu)); AssertFatal(cc->sib1_bcch_length > 0, "could not encode SIB1\n"); // generate otherSIB payloads cc->other_sib_bcch_length[0] = encode_sysinfo_ie(sysInfo, cc->other_sib_bcch_pdu[0], sizeof(cc->other_sib_bcch_pdu[0])); cc->other_sib_bcch_length[1] = encode_sysinfo_ie(sysInfov17, cc->other_sib_bcch_pdu[1], sizeof(cc->other_sib_bcch_pdu[1])); ASN_STRUCT_FREE(asn_DEF_NR_SystemInformation_IEs, sysInfo); ASN_STRUCT_FREE(asn_DEF_NR_SystemInformation_IEs, sysInfov17); return true; } bool nr_update_sib19(const gnb_sat_position_update_t *sat_position) { gNB_MAC_INST *nrmac = RC.nrmac[0]; NR_COMMON_channels_t *cc = &nrmac->common_channels[0]; NR_ServingCellConfigCommon_t *scc = cc->ServingCellConfigCommon; if (!scc || !scc->ext2 || !scc->ext2->ntn_Config_r17) return false; const vector_t *pos = &sat_position->position; const vector_t *vel = &sat_position->velocity; LOG_D(NR_MAC, "SFN = %d, SubFrame = %d\n", sat_position->sfn, sat_position->subframe); LOG_D(NR_MAC, "TA_Common: value %d, %f msec\n", sat_position->delay, sat_position->delay * 4.072e-6); LOG_D(NR_MAC, "TA_CommonDrift: value %d, = %f µsec/sec\n", sat_position->drift, sat_position->drift * 0.2e-3); LOG_D(NR_MAC, "TA_CommonDriftVariant: value %d, %f µsec/sec^2\n", sat_position->accel, sat_position->accel * 0.2e-4); LOG_D(NR_MAC, "SAT Position: values %d/%d/%d, %.3f/%3f/%3f metres in X/Y/Z\n", pos->X, pos->Y, pos->Z, pos->X * 1.3, pos->Y * 1.3, pos->Z * 1.3); LOG_D(NR_MAC, "SAT Velocity: values %d/%d/%d, %.3f/%3f/%3f m/s in X/Y/Z\n", vel->X, vel->Y, vel->Z, vel->X * 0.06, vel->Y * 0.06, vel->Z * 0.06); NR_SCHED_LOCK(&nrmac->sched_lock); if (!scc->ext2->ntn_Config_r17->epochTime_r17) scc->ext2->ntn_Config_r17->epochTime_r17 = calloc (1, sizeof(*scc->ext2->ntn_Config_r17->epochTime_r17)); NR_EpochTime_r17_t *epoch_time_r17 = scc->ext2->ntn_Config_r17->epochTime_r17; epoch_time_r17->sfn_r17 = sat_position->sfn; epoch_time_r17->subFrameNR_r17 = sat_position->subframe; if (!scc->ext2->ntn_Config_r17->ta_Info_r17) scc->ext2->ntn_Config_r17->ta_Info_r17 = calloc(1, sizeof(*scc->ext2->ntn_Config_r17->ta_Info_r17)); NR_TA_Info_r17_t *sib19_ta_info = scc->ext2->ntn_Config_r17->ta_Info_r17; // SIB19 provides Round trip delay on feeder link (between gNB and SAT). sib19_ta_info->ta_Common_r17 = sat_position->delay; if (sat_position->drift) { if (!sib19_ta_info->ta_CommonDrift_r17) sib19_ta_info->ta_CommonDrift_r17 = calloc(1, sizeof(*sib19_ta_info->ta_CommonDrift_r17)); *sib19_ta_info->ta_CommonDrift_r17 = sat_position->drift; } else free_and_zero(sib19_ta_info->ta_CommonDrift_r17); if (sat_position->accel) { if (!sib19_ta_info->ta_CommonDriftVariant_r17) sib19_ta_info->ta_CommonDriftVariant_r17 = calloc(1, sizeof(*sib19_ta_info->ta_CommonDriftVariant_r17)); *sib19_ta_info->ta_CommonDriftVariant_r17 = sat_position->accel; } else free_and_zero(sib19_ta_info->ta_CommonDriftVariant_r17); // Currently PositionVelocity is supported and not yet the OrbitalParams if (!scc->ext2->ntn_Config_r17->ephemerisInfo_r17) { scc->ext2->ntn_Config_r17->ephemerisInfo_r17 = calloc(1, sizeof(*scc->ext2->ntn_Config_r17->ephemerisInfo_r17)); scc->ext2->ntn_Config_r17->ephemerisInfo_r17->present = NR_EphemerisInfo_r17_PR_NOTHING; } if (scc->ext2->ntn_Config_r17->ephemerisInfo_r17->present == NR_EphemerisInfo_r17_PR_orbital_r17) { ASN_STRUCT_FREE(asn_DEF_NR_Orbital_r17, scc->ext2->ntn_Config_r17->ephemerisInfo_r17->choice.orbital_r17); scc->ext2->ntn_Config_r17->ephemerisInfo_r17->choice.orbital_r17 = NULL; scc->ext2->ntn_Config_r17->ephemerisInfo_r17->present = NR_EphemerisInfo_r17_PR_NOTHING; } if (!scc->ext2->ntn_Config_r17->ephemerisInfo_r17->choice.positionVelocity_r17) scc->ext2->ntn_Config_r17->ephemerisInfo_r17->choice.positionVelocity_r17 = calloc(1, sizeof(*scc->ext2->ntn_Config_r17->ephemerisInfo_r17->choice.positionVelocity_r17)); scc->ext2->ntn_Config_r17->ephemerisInfo_r17->present = NR_EphemerisInfo_r17_PR_positionVelocity_r17; NR_PositionVelocity_r17_t *sib19_PosVel = scc->ext2->ntn_Config_r17->ephemerisInfo_r17->choice.positionVelocity_r17; sib19_PosVel->positionX_r17 = pos->X; sib19_PosVel->positionY_r17 = pos->Y; sib19_PosVel->positionZ_r17 = pos->Z; sib19_PosVel->velocityVX_r17 = vel->X; sib19_PosVel->velocityVY_r17 = vel->Y; sib19_PosVel->velocityVZ_r17 = vel->Z; NR_SystemInformation_IEs_t *sysInfov17 = calloc(1, sizeof(*sysInfov17)); // for othersibs struct NR_SystemInformation_IEs__sib_TypeAndInfo__Member *type_du = calloc(1, sizeof(*type_du)); type_du->present = NR_SystemInformation_IEs__sib_TypeAndInfo__Member_PR_sib19_v1700; NR_SIB19_r17_t *sib19 = get_SIB19_NR(cc->ServingCellConfigCommon); type_du->choice.sib19_v1700 = sib19; add_sib_to_systeminformation(sysInfov17, type_du); cc->other_sib_bcch_length[1] = encode_sysinfo_ie(sysInfov17, cc->other_sib_bcch_pdu[1], sizeof(cc->other_sib_bcch_pdu[1])); AssertFatal(cc->other_sib_bcch_length[1] > 0, "could not encode SIB19\n"); ASN_STRUCT_FREE(asn_DEF_NR_SystemInformation_IEs, sysInfov17); NR_SCHED_UNLOCK(&nrmac->sched_lock); return true; } bool nr_trigger_bwp_switch(uint16_t rnti, int bwp_id) { gNB_MAC_INST *nrmac = RC.nrmac[0]; NR_SCHED_LOCK(&nrmac->sched_lock); NR_UE_info_t *UE = find_nr_UE(&nrmac->UE_info, rnti); bool success = false; if (!UE) { LOG_W(NR_MAC, "could not find UE for RNTI %04x\n", rnti); } else if (UE->current_DL_BWP.bwp_id == bwp_id) { LOG_W(NR_MAC, "UE %04x is already on BWP ID %d, not triggering reconfiguration\n", rnti, bwp_id); } else { // UE != NULL && current_DL_BWP.bwp_id != bwp_id nr_mac_trigger_reconfiguration(nrmac, UE, bwp_id, false); success = true; } NR_SCHED_UNLOCK(&nrmac->sched_lock); return success; } void prepare_du_configuration_update(gNB_MAC_INST *mac, f1ap_served_cell_info_t *info, NR_BCCH_BCH_Message_t *mib, const NR_BCCH_DL_SCH_Message_t *sib1) { /* send gNB-DU configuration update to RRC */ f1ap_gnb_du_configuration_update_t update = { .transaction_id = 1, .num_status = 1, .status = calloc_or_fail(1, sizeof(f1ap_cell_status_t)), }; update.status[0].plmn = info->plmn; update.status[0].nr_cellid = info->nr_cellid; update.status[0].service_state = F1AP_STATE_IN_SERVICE; if (mib && sib1) { update.num_cells_to_modify = 1; update.cell_to_modify = calloc_or_fail(1, sizeof(*update.cell_to_modify)); update.cell_to_modify[0].old_nr_cellid = info->nr_cellid; update.cell_to_modify[0].info = *info; update.cell_to_modify[0].sys_info = get_sys_info(mib, sib1, NULL); } mac->mac_rrc.gnb_du_configuration_update(&update); } void nr_mac_configure_sib1(gNB_MAC_INST *nrmac, const plmn_id_t *plmn, uint64_t cellID, int tac) { AssertFatal(IS_SA_MODE(get_softmodem_params()), "error: SIB1 only applicable for SA\n"); NR_COMMON_channels_t *cc = &nrmac->common_channels[0]; NR_ServingCellConfigCommon_t *scc = cc->ServingCellConfigCommon; NR_BCCH_DL_SCH_Message_t *sib1 = get_SIB1_NR(scc, plmn, cellID, tac, &nrmac->radio_config); cc->sib1 = sib1; cc->sib1_bcch_length = encode_SIB_NR(sib1, cc->sib1_bcch_pdu, sizeof(cc->sib1_bcch_pdu)); AssertFatal(cc->sib1_bcch_length > 0, "could not encode SIB1\n"); } static bool process_addmod_bearers_cellGroupConfig(NR_UE_sched_ctrl_t *sched_ctrl, const NR_RLC_BearerConfig_t *conf) { int priority = conf->mac_LogicalChannelConfig->ul_SpecificParameters->priority; nr_lc_config_t c = {.lcid = conf->logicalChannelIdentity, .priority = priority}; return nr_mac_add_lcid(sched_ctrl, &c); } bool nr_mac_add_test_ue(gNB_MAC_INST *nrmac, uint32_t rnti, NR_CellGroupConfig_t *CellGroup) { /* ideally, instead of this function, "users" of this function should call * the ue context setup request function in mac_rrc_dl_handler.c */ DevAssert(nrmac != NULL); DevAssert(CellGroup != NULL); DevAssert(get_softmodem_params()->phy_test); NR_SCHED_LOCK(&nrmac->sched_lock); NR_UE_info_t *UE = get_new_nr_ue_inst(&nrmac->UE_info.uid_allocator, rnti, CellGroup, &nrmac->radio_config); DevAssert(UE->uid < MAX_MOBILES_PER_GNB); // physical simulators: we assume we can always create a UE free_and_zero(UE->ra); // physical simulators: UE will not do RA UE->local_bwp_id = 1; // for physical simulators bool res = add_connected_nr_ue(nrmac, UE); if (!res) { LOG_E(NR_MAC, "Error adding UE %04x\n", rnti); delete_nr_ue_data(UE, &nrmac->UE_info.uid_allocator); NR_SCHED_UNLOCK(&nrmac->sched_lock); return false; } int ss_type = NR_SearchSpace__searchSpaceType_PR_ue_Specific; configure_UE_BWP(nrmac, nrmac->common_channels[0].ServingCellConfigCommon, UE, false, ss_type, -1, -1); const struct NR_CellGroupConfig__rlc_BearerToAddModList *l = CellGroup->rlc_BearerToAddModList; for (int i = 0; l != NULL && i < l->list.count; ++i) process_addmod_bearers_cellGroupConfig(&UE->UE_sched_ctrl, CellGroup->rlc_BearerToAddModList->list.array[i]); AssertFatal(CellGroup->rlc_BearerToReleaseList == NULL, "cannot release bearers while adding new UEs\n"); NR_SCHED_UNLOCK(&nrmac->sched_lock); LOG_I(NR_MAC, "Added new UE %x with initial CellGroup\n", rnti); return true; } void nr_mac_prepare_ra_ue(gNB_MAC_INST *nrmac, NR_UE_info_t *UE) { DevAssert(nrmac != NULL); NR_SCHED_ENSURE_LOCKED(&nrmac->sched_lock); NR_RA_t *ra = UE->ra; ra->cfra = true; NR_CellGroupConfig_t *CellGroup = UE->CellGroup; DevAssert(CellGroup != NULL); struct NR_CFRA *cfra = CellGroup->spCellConfig->reconfigurationWithSync->rach_ConfigDedicated->choice.uplink->cfra; uint8_t num_preamble = cfra->resources.choice.ssb->ssb_ResourceList.list.count; ra->preambles.num_preambles = num_preamble; NR_COMMON_channels_t *cc = &nrmac->common_channels[0]; char buf[200]; int idx = 0; for (int i = 0; i < cc->num_active_ssb; i++) { for (int j = 0; j < num_preamble; j++) { if (cc->ssb_index[i] == cfra->resources.choice.ssb->ssb_ResourceList.list.array[j]->ssb) { // one dedicated preamble for each beam ra->preambles.preamble_list[i] = cfra->resources.choice.ssb->ssb_ResourceList.list.array[j]->ra_PreambleIndex; if (idx < sizeof(buf) - 1) idx += snprintf(buf + idx, sizeof(buf) - idx, " %d", ra->preambles.preamble_list[i]); break; } } } LOG_I(NR_MAC, "Added new %s process for UE RNTI %04x with initial CellGroup and %d preamble(s): %s\n", ra->cfra ? "CFRA" : "CBRA", UE->rnti, num_preamble, buf); }