/* * 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 ra_procedures.c * \brief Routines for UE MAC-layer Random Access procedures (TS 38.321, Release 15) * \author R. Knopp, Navid Nikaein, Guido Casati * \date 2019 * \version 0.1 * \company Eurecom * \email: knopp@eurecom.fr navid.nikaein@eurecom.fr, guido.casati@iis.fraunhofer.de * \note * \warning */ /* RRC */ #include "RRC/NR_UE/L2_interface_ue.h" /* MAC */ #include "NR_MAC_COMMON/nr_mac.h" #include "LAYER2/NR_MAC_UE/mac_proto.h" #include #include "openair2/LAYER2/nr_rlc/nr_rlc_oai_api.h" int16_t get_prach_tx_power(NR_UE_MAC_INST_t *mac) { RA_config_t *ra = &mac->ra; int16_t pathloss = compute_nr_SSB_PL(mac); int16_t ra_preamble_rx_power = (int16_t)(ra->prach_resources.ra_preamble_rx_target_power + pathloss); return min(ra->prach_resources.Pc_max, ra_preamble_rx_power); } static void set_preambleTransMax(RA_config_t *ra, long preambleTransMax) { switch (preambleTransMax) { case 0: ra->preambleTransMax = 3; break; case 1: ra->preambleTransMax = 4; break; case 2: ra->preambleTransMax = 5; break; case 3: ra->preambleTransMax = 6; break; case 4: ra->preambleTransMax = 7; break; case 5: ra->preambleTransMax = 8; break; case 6: ra->preambleTransMax = 10; break; case 7: ra->preambleTransMax = 20; break; case 8: ra->preambleTransMax = 50; break; case 9: ra->preambleTransMax = 100; break; case 10: ra->preambleTransMax = 200; break; default: AssertFatal(false, "Invalid preambleTransMax\n"); } } static int get_Msg3SizeGroupA(long ra_Msg3SizeGroupA) { int bits = 0; switch (ra_Msg3SizeGroupA) { case NR_RACH_ConfigCommon__groupBconfigured__ra_Msg3SizeGroupA_b56: bits = 56; break; case NR_RACH_ConfigCommon__groupBconfigured__ra_Msg3SizeGroupA_b144: bits = 144; break; case NR_RACH_ConfigCommon__groupBconfigured__ra_Msg3SizeGroupA_b208: bits = 208; break; case NR_RACH_ConfigCommon__groupBconfigured__ra_Msg3SizeGroupA_b256: bits = 256; break; case NR_RACH_ConfigCommon__groupBconfigured__ra_Msg3SizeGroupA_b282: bits = 282; break; case NR_RACH_ConfigCommon__groupBconfigured__ra_Msg3SizeGroupA_b480: bits = 480; break; case NR_RACH_ConfigCommon__groupBconfigured__ra_Msg3SizeGroupA_b640: bits = 640; break; case NR_RACH_ConfigCommon__groupBconfigured__ra_Msg3SizeGroupA_b800: bits = 800; break; case NR_RACH_ConfigCommon__groupBconfigured__ra_Msg3SizeGroupA_b1000: bits = 1000; break; case NR_RACH_ConfigCommon__groupBconfigured__ra_Msg3SizeGroupA_b72: bits = 72; break; default: AssertFatal(false, "Unknown ra-Msg3SizeGroupA %lu\n", ra_Msg3SizeGroupA); } return bits / 8; // returning bytes } static int get_messagePowerOffsetGroupB(long messagePowerOffsetGroupB) { int pow_offset = 0; switch (messagePowerOffsetGroupB) { case NR_RACH_ConfigCommon__groupBconfigured__messagePowerOffsetGroupB_minusinfinity: pow_offset = INT_MIN; break; case NR_RACH_ConfigCommon__groupBconfigured__messagePowerOffsetGroupB_dB0: pow_offset = 0; break; case NR_RACH_ConfigCommon__groupBconfigured__messagePowerOffsetGroupB_dB5: pow_offset = 5; break; case NR_RACH_ConfigCommon__groupBconfigured__messagePowerOffsetGroupB_dB8: pow_offset = 8; break; case NR_RACH_ConfigCommon__groupBconfigured__messagePowerOffsetGroupB_dB10: pow_offset = 10; break; case NR_RACH_ConfigCommon__groupBconfigured__messagePowerOffsetGroupB_dB12: pow_offset = 12; break; case NR_RACH_ConfigCommon__groupBconfigured__messagePowerOffsetGroupB_dB15: pow_offset = 15; break; case NR_RACH_ConfigCommon__groupBconfigured__messagePowerOffsetGroupB_dB18: pow_offset = 18; break; default: AssertFatal(false, "Unknown messagePowerOffsetGroupB %lu\n", messagePowerOffsetGroupB); } return pow_offset; } static void select_preamble_group(NR_UE_MAC_INST_t *mac) { RA_config_t *ra = &mac->ra; // TODO if the RA_TYPE is switched from 2-stepRA to 4-stepRA if (!ra->Msg3_buffer) { // if Msg3 buffer is empty NR_RACH_ConfigCommon_t *nr_rach_ConfigCommon = mac->current_UL_BWP->rach_ConfigCommon; if (nr_rach_ConfigCommon && nr_rach_ConfigCommon->groupBconfigured) { // if Random Access Preambles group B is configured struct NR_RACH_ConfigCommon__groupBconfigured *groupB = nr_rach_ConfigCommon->groupBconfigured; // if the potential Msg3 size (UL data available for transmission plus MAC subheader(s) and, // where required, MAC CEs) is greater than ra-Msg3SizeGroupA // if the pathloss is less than PCMAX (of the Serving Cell performing the Random Access Procedure) // – preambleReceivedTargetPower – msg3-DeltaPreamble – messagePowerOffsetGroupB int groupB_pow_offset = get_messagePowerOffsetGroupB(groupB->messagePowerOffsetGroupB); int PLThreshold = ra->prach_resources.Pc_max - ra->preambleRxTargetPower - ra->msg3_deltaPreamble - groupB_pow_offset; int pathloss = compute_nr_SSB_PL(mac); // TODO if the Random Access procedure was initiated for the CCCH logical channel and the CCCH SDU size // plus MAC subheader is greater than ra-Msg3SizeGroupA if (ra->Msg3_size > get_Msg3SizeGroupA(groupB->ra_Msg3SizeGroupA) && pathloss < PLThreshold) ra->RA_GroupA = false; else ra->RA_GroupA = true; } else ra->RA_GroupA = true; } // else if Msg3 is being retransmitted, we keep what used in first transmission of Msg3 } ssb_ro_preambles_t get_ssb_ro_preambles_4step(struct NR_RACH_ConfigCommon__ssb_perRACH_OccasionAndCB_PreamblesPerSSB *config) { ssb_ro_preambles_t ret = {0}; switch (config->present) { case NR_RACH_ConfigCommon__ssb_perRACH_OccasionAndCB_PreamblesPerSSB_PR_oneEighth: ret.ssb_per_ro = 0.125; ret.preambles_per_ssb = (config->choice.oneEighth + 1) << 2; break; case NR_RACH_ConfigCommon__ssb_perRACH_OccasionAndCB_PreamblesPerSSB_PR_oneFourth: ret.ssb_per_ro = 0.25; ret.preambles_per_ssb = (config->choice.oneFourth + 1) << 2; break; case NR_RACH_ConfigCommon__ssb_perRACH_OccasionAndCB_PreamblesPerSSB_PR_oneHalf: ret.ssb_per_ro = 0.5; ret.preambles_per_ssb = (config->choice.oneHalf + 1) << 2; break; case NR_RACH_ConfigCommon__ssb_perRACH_OccasionAndCB_PreamblesPerSSB_PR_one: ret.ssb_per_ro = 1; ret.preambles_per_ssb = (config->choice.one + 1) << 2; break; case NR_RACH_ConfigCommon__ssb_perRACH_OccasionAndCB_PreamblesPerSSB_PR_two: ret.ssb_per_ro = 2; ret.preambles_per_ssb = (config->choice.two + 1) << 2; break; case NR_RACH_ConfigCommon__ssb_perRACH_OccasionAndCB_PreamblesPerSSB_PR_four: ret.ssb_per_ro = 4; ret.preambles_per_ssb = config->choice.four; break; case NR_RACH_ConfigCommon__ssb_perRACH_OccasionAndCB_PreamblesPerSSB_PR_eight: ret.ssb_per_ro = 8; ret.preambles_per_ssb = config->choice.eight; break; case NR_RACH_ConfigCommon__ssb_perRACH_OccasionAndCB_PreamblesPerSSB_PR_sixteen: ret.ssb_per_ro = 16; ret.preambles_per_ssb = config->choice.sixteen; break; default: AssertFatal(false, "Invalid ssb_perRACH_OccasionAndCB_PreamblesPerSSB\n"); } LOG_D(NR_MAC, "SSB per RO %f preambles per SSB %d\n", ret.ssb_per_ro, ret.preambles_per_ssb); return ret; } static ssb_ro_preambles_t get_ssb_ro_preambles_2step(struct NR_RACH_ConfigCommonTwoStepRA_r16__msgA_SSB_PerRACH_OccasionAndCB_PreamblesPerSSB_r16 *config) { ssb_ro_preambles_t ret = {0}; switch (config->present) { case NR_RACH_ConfigCommonTwoStepRA_r16__msgA_SSB_PerRACH_OccasionAndCB_PreamblesPerSSB_r16_PR_oneEighth : ret.ssb_per_ro = 0.125; ret.preambles_per_ssb = (config->choice.oneEighth + 1) << 2; break; case NR_RACH_ConfigCommonTwoStepRA_r16__msgA_SSB_PerRACH_OccasionAndCB_PreamblesPerSSB_r16_PR_oneFourth : ret.ssb_per_ro = 0.25; ret.preambles_per_ssb = (config->choice.oneFourth + 1) << 2; break; case NR_RACH_ConfigCommonTwoStepRA_r16__msgA_SSB_PerRACH_OccasionAndCB_PreamblesPerSSB_r16_PR_oneHalf : ret.ssb_per_ro = 0.5; ret.preambles_per_ssb = (config->choice.oneHalf + 1) << 2; break; case NR_RACH_ConfigCommonTwoStepRA_r16__msgA_SSB_PerRACH_OccasionAndCB_PreamblesPerSSB_r16_PR_one : ret.ssb_per_ro = 1; ret.preambles_per_ssb = (config->choice.one + 1) << 2; break; case NR_RACH_ConfigCommonTwoStepRA_r16__msgA_SSB_PerRACH_OccasionAndCB_PreamblesPerSSB_r16_PR_two : ret.ssb_per_ro = 2; ret.preambles_per_ssb = (config->choice.two + 1) << 2; break; case NR_RACH_ConfigCommonTwoStepRA_r16__msgA_SSB_PerRACH_OccasionAndCB_PreamblesPerSSB_r16_PR_four : ret.ssb_per_ro = 4; ret.preambles_per_ssb = config->choice.four; break; case NR_RACH_ConfigCommonTwoStepRA_r16__msgA_SSB_PerRACH_OccasionAndCB_PreamblesPerSSB_r16_PR_eight : ret.ssb_per_ro = 8; ret.preambles_per_ssb = config->choice.eight; break; case NR_RACH_ConfigCommonTwoStepRA_r16__msgA_SSB_PerRACH_OccasionAndCB_PreamblesPerSSB_r16_PR_sixteen : ret.ssb_per_ro = 16; ret.preambles_per_ssb = config->choice.sixteen; break; default : AssertFatal(false, "Invalid msgA_SSB_PerRACH_OccasionAndCB_PreamblesPerSSB_r16\n"); } return ret; } static void config_preamble_index(NR_UE_MAC_INST_t *mac) { RA_config_t *ra = &mac->ra; // Random seed generation unsigned int seed; if (IS_SOFTMODEM_IQPLAYER || IS_SOFTMODEM_IQRECORDER) { // Overwrite seed with non-random seed for IQ player/recorder seed = 1; } else { // & to truncate the int64_t and keep only the LSB bits, up to sizeof(int) seed = (unsigned int)(rdtsc_oai() & ~0); } NR_RACH_ConfigCommon_t *nr_rach_ConfigCommon = mac->current_UL_BWP->rach_ConfigCommon; int nb_of_preambles = 64; bool groupBconfigured = false; int preamb_ga = 0; if (ra->ra_type == RA_4_STEP) { ra->ssb_ro_config = mac->ssb_ro_preambles; if (nr_rach_ConfigCommon->totalNumberOfRA_Preambles) nb_of_preambles = *nr_rach_ConfigCommon->totalNumberOfRA_Preambles; // Amongst the contention-based Random Access Preambles associated with an SSB the first numberOfRA-PreamblesGroupA // included in groupBconfigured Random Access Preambles belong to Random Access Preambles group A. // The remaining Random Access Preambles associated with the SSB belong to Random Access Preambles group B (if configured) select_preamble_group(mac); if (nr_rach_ConfigCommon->groupBconfigured) { groupBconfigured = true; preamb_ga = nr_rach_ConfigCommon->groupBconfigured->numberOfRA_PreamblesGroupA; } } else { NR_RACH_ConfigCommonTwoStepRA_r16_t *twostep = &mac->current_UL_BWP->msgA_ConfigCommon_r16->rach_ConfigCommonTwoStepRA_r16; AssertFatal(twostep->groupB_ConfiguredTwoStepRA_r16 == NULL, "GroupB preambles not supported for 2-step RA\n"); ra->RA_GroupA = true; // The field is mandatory present if the 2-step random access type occasions are shared with 4-step random access type, // otherwise the field is not present bool sharedROs = twostep->msgA_CB_PreamblesPerSSB_PerSharedRO_r16 != NULL; AssertFatal(sharedROs == false, "Shared ROs between 2- and 4-step RA not supported\n"); // For Type-2 random access procedure with separate configuration of PRACH occasions with Type-1 random access procedure // configuration by msgA-SSB-PerRACH-OccasionAndCB-PreamblesPerSSB when provided; // otherwise, by ssb-perRACH-OccasionAndCB-PreamblesPerSSB if (twostep->msgA_SSB_PerRACH_OccasionAndCB_PreamblesPerSSB_r16) ra->ssb_ro_config = get_ssb_ro_preambles_2step(twostep->msgA_SSB_PerRACH_OccasionAndCB_PreamblesPerSSB_r16); else ra->ssb_ro_config = mac->ssb_ro_preambles; if (twostep->msgA_TotalNumberOfRA_Preambles_r16) nb_of_preambles = *twostep->msgA_TotalNumberOfRA_Preambles_r16; } int groupOffset = 0; if (groupBconfigured) { AssertFatal(preamb_ga < nb_of_preambles, "Nb of preambles for groupA not compatible with total number of preambles\n"); if (!ra->RA_GroupA) { // groupB groupOffset = preamb_ga; nb_of_preambles = nb_of_preambles - preamb_ga; } else { nb_of_preambles = preamb_ga; } } int pream_per_ssb = min(ra->ssb_ro_config.preambles_per_ssb, nb_of_preambles); int rand_preamb = rand_r(&seed) % pream_per_ssb; if (ra->ssb_ro_config.ssb_per_ro < 1) ra->ra_PreambleIndex = groupOffset + rand_preamb; else { int ssb_pr_idx = mac->ssb_list.nb_ssb_per_index[mac->mib_ssb] % (int)ra->ssb_ro_config.ssb_per_ro; ra->ra_PreambleIndex = groupOffset + (ssb_pr_idx * ra->ssb_ro_config.preambles_per_ssb) + rand_preamb; } } static void configure_ra_preamble(NR_UE_MAC_INST_t *mac) { RA_config_t *ra = &mac->ra; int ssb = -1; // init as not selected ra->ro_mask_index = -1; // init as not selected NR_RACH_ConfigDedicated_t *rach_ConfigDedicated = ra->rach_ConfigDedicated; if (ra->ra_type == RA_4_STEP) { // TODO if the Random Access procedure was initiated for SpCell beam failure recovery // TODO if the Random Access procedure was initiated for SI request if (ra->pdcch_order.active && ra->pdcch_order.preamble_index != 0xb000000) { // set the PREAMBLE_INDEX to the signalled ra-PreambleIndex; ra->ra_PreambleIndex = ra->pdcch_order.preamble_index; // select the SSB signalled by PDCCH ssb = ra->pdcch_order.ssb_index; ra->ro_mask_index = ra->pdcch_order.prach_mask; } else if (rach_ConfigDedicated && rach_ConfigDedicated->cfra) { NR_CFRA_t *cfra = rach_ConfigDedicated->cfra; AssertFatal(cfra->occasions == NULL, "Dedicated PRACH occasions for CFRA not supported\n"); AssertFatal(cfra->resources.present == NR_CFRA__resources_PR_ssb, "SSB-based CFRA supported only\n"); struct NR_CFRA__resources__ssb *ssb_list = cfra->resources.choice.ssb; for (int i = 0; i < ssb_list->ssb_ResourceList.list.count; i++) { NR_CFRA_SSB_Resource_t *res = ssb_list->ssb_ResourceList.list.array[i]; // TODO select an SSB with SS-RSRP above rsrp-ThresholdSSB amongst the associated SSBs if (res->ssb == mac->mib_ssb) { ssb = mac->mib_ssb; // set the PREAMBLE_INDEX to a ra-PreambleIndex corresponding to the selected SSB ra->ra_PreambleIndex = res->ra_PreambleIndex; ra->ro_mask_index = ssb_list->ra_ssb_OccasionMaskIndex; break; } } } else { // for the contention-based Random Access preamble selection // TODO if at least one of the SSBs with SS-RSRP above rsrp-ThresholdSSB is available // else select any SSB ssb = mac->mib_ssb; config_preamble_index(mac); } } else { // 2-step RA // if the contention-free 2-step RA type Resources associated with SSBs have been explicitly provided in rach-ConfigDedicated // TODO and at least one SSB with SS-RSRP above msgA-RSRP-ThresholdSSB amongst the associated SSBs is available if (ra->cfra) { AssertFatal(rach_ConfigDedicated->ext1 && rach_ConfigDedicated->ext1->cfra_TwoStep_r16, "Two-step CFRA should be configured here\n"); NR_CFRA_TwoStep_r16_t *cfra = rach_ConfigDedicated->ext1->cfra_TwoStep_r16; AssertFatal(cfra->occasionsTwoStepRA_r16 == NULL, "Dedicated PRACH occasions for CFRA not supported\n"); for (int i = 0; i < cfra->resourcesTwoStep_r16.ssb_ResourceList.list.count; i++) { NR_CFRA_SSB_Resource_t *res = cfra->resourcesTwoStep_r16.ssb_ResourceList.list.array[i]; if (res->ssb == mac->mib_ssb) { ssb = mac->mib_ssb; // set the PREAMBLE_INDEX to a ra-PreambleIndex corresponding to the selected SSB ra->ra_PreambleIndex = res->ra_PreambleIndex; ra->ro_mask_index = cfra->resourcesTwoStep_r16.ra_ssb_OccasionMaskIndex; break; } } } else { // for the contention-based Random Access Preamble selection // TODO if at least one of the SSBs with SS-RSRP above msgA-RSRP-ThresholdSSB is available // else select any SSB ssb = mac->mib_ssb; config_preamble_index(mac); } } AssertFatal(ssb >= 0, "Something wrong! RA resource selection didn't set any SSB\n"); // setting the RA ssb value as the progressive number of SSB transmitted // non-transmitted SSBs are not taken into account int ssb_idx = mac->ssb_list.nb_ssb_per_index[ssb]; ra->new_ssb = ssb != ssb_idx ? true : false; ra->ra_ssb = ssb_idx; // TODO not sure how to handle the RO mask when it is limiting the RO occasions AssertFatal(ra->ro_mask_index <= 0, "Handling of RACH occasion masking indication not implemented\n"); } static bool check_mixed_slot_prach(frame_structure_t *fs, int slot, int start_prach, int end_prach) { bool is_mixed = is_mixed_slot(slot, fs); if (is_mixed) { tdd_bitmap_t *bitmap = &fs->period_cfg.tdd_slot_bitmap[slot % fs->numb_slots_period]; if (bitmap->num_ul_symbols == 0) return false; int ul_end = NR_NUMBER_OF_SYMBOLS_PER_SLOT - 1; int ul_start = NR_NUMBER_OF_SYMBOLS_PER_SLOT - bitmap->num_ul_symbols; if (start_prach < ul_start || end_prach > ul_end) return false; } return true; } static void select_prach_occasion(RA_config_t *ra, int nb_tx_ssb, int n, prach_occasion_info_t ra_occasions_period[n], int num_ra_occasions_period) { unsigned int seed; if (IS_SOFTMODEM_IQPLAYER || IS_SOFTMODEM_IQRECORDER) { // Overwrite seed with non-random seed for IQ player/recorder seed = 1; } else { // & to truncate the int64_t and keep only the LSB bits, up to sizeof(int) seed = (unsigned int)(rdtsc_oai() & ~0); } int num_ros_per_ssb = 0; int idx_ssb = 0; int temp_idx = 0; if (ra->ssb_ro_config.ssb_per_ro < 1) { num_ros_per_ssb = (int)(1 / ra->ssb_ro_config.ssb_per_ro); idx_ssb = (rand_r(&seed) % num_ros_per_ssb); temp_idx = ra->ra_ssb * num_ros_per_ssb + idx_ssb; } else { int ssb_per_ro = nb_tx_ssb < ra->ssb_ro_config.ssb_per_ro ? nb_tx_ssb : ra->ssb_ro_config.ssb_per_ro; num_ros_per_ssb = ra->association_periods * ssb_per_ro * num_ra_occasions_period / nb_tx_ssb; idx_ssb = (rand_r(&seed) % num_ros_per_ssb); int eq_ssb = ra->ra_ssb + (idx_ssb * nb_tx_ssb); temp_idx = eq_ssb / ra->ssb_ro_config.ssb_per_ro; } int ro_index = temp_idx % num_ra_occasions_period; int ass_period_idx = temp_idx / num_ra_occasions_period; ra->sched_ro_info = ra_occasions_period[ro_index]; ra->sched_ro_info.association_period_idx = ass_period_idx; } static void configure_prach_occasions(NR_UE_MAC_INST_t *mac, int scs) { RA_config_t *ra = &mac->ra; int num_ra_occasions_period = 0; frame_structure_t *fs = &mac->frame_structure; nr_prach_info_t prach_info = get_nr_prach_occasion_info_from_index(ra->ra_config_index, mac->frequency_range, fs->frame_type); int max_num_occasions = prach_info.N_RA_sfn * prach_info.N_t_slot * prach_info.N_RA_slot * ra->num_fd_occasions; prach_occasion_info_t ra_occasions_period[max_num_occasions]; // Number of PRACH slots within a subframe (or 60kHz slot) to be taken into account only for 30 and 120kHz // as defined in 5.3.2 of 211 int prach_slots_in_sf = (scs == 1 || scs == 3) ? prach_info.N_RA_slot : 1; uint64_t temp_s_map = prach_info.s_map; int n_frames = prach_info.y2 == -1 ? 1 : 2; for (int n = 0; n < n_frames; n++) { int sf = 0; for (int s = 0; s < prach_info.N_RA_sfn; s++) { // subframe/60kHz slot occasions in period while (((temp_s_map >> sf) & 0x01) == 0) sf++; int sl = sf; for (int i = 0; i < prach_slots_in_sf; i++) { // slot per subframe/60kHz slot int add_slot = i; if (scs == 1 || scs == 3) { sl *= 2; // if only 1 slot per subframe (or 60kHz slot) in case of 30 or 120kHz it's the odd one // as defined in 5.3.2 of 211 if (((prach_info.format & 0xff) > 3) && prach_slots_in_sf == 1) add_slot = 1; } int slot = sl + add_slot; if (!is_ul_slot(slot, fs)) continue; // valid PRACH occasion only if slot is UL for (int t = 0; t < prach_info.N_t_slot; t++) { // td occasions within a slot // see 5.3.2 in 211 int temp_format = prach_info.format >> 8; // B1, B2 or B3 if A1/B1, A2/B2 or A3/B3 formats in tables if (t != prach_info.N_t_slot - 1 || temp_format == 0xff) temp_format = prach_info.format & 0xff; int start_symbol = prach_info.start_symbol + t * prach_info.N_dur; int end_symbol = start_symbol + prach_info.N_dur; // valid occasion only if PRACH symbols are UL symbols in mixed slot if (fs->frame_type == TDD && !check_mixed_slot_prach(fs, slot, start_symbol, end_symbol)) continue; for (int f = 0; f < ra->num_fd_occasions; f++) { // fd occasions ra_occasions_period[num_ra_occasions_period] = (prach_occasion_info_t){.slot = slot, .frame_info[0] = prach_info.x, .frame_info[1] = n == 0 ? prach_info.y : prach_info.y2, .start_symbol = start_symbol, .fdm = f, .format = temp_format}; LOG_D(NR_MAC, "RA occasion %d: slot %d start symbol %d fd occasion %d\n", num_ra_occasions_period, slot, start_symbol, f); num_ra_occasions_period++; } } } sf++; } } int config_period = prach_info.x; // configuration period ra->association_periods = 1; int nb_eq_ssb = mac->ssb_list.nb_tx_ssb; if (ra->ssb_ro_config.ssb_per_ro < 1) nb_eq_ssb *= (int)(1 / ra->ssb_ro_config.ssb_per_ro); int nb_eq_ro = num_ra_occasions_period; if (ra->ssb_ro_config.ssb_per_ro > 1) nb_eq_ro *= (int)ra->ssb_ro_config.ssb_per_ro; while (nb_eq_ssb > nb_eq_ro) { // not enough PRACH occasions -> need to increase association period ra->association_periods <<= 1; AssertFatal(ra->association_periods * config_period <= 16, "Cannot find an association period for %d SSB and %d RO with %f SSB per RO\n", mac->ssb_list.nb_tx_ssb, num_ra_occasions_period, ra->ssb_ro_config.ssb_per_ro); nb_eq_ro <<= 1; // doubling the association period -> doubling ROs } LOG_D(NR_MAC, "PRACH configuration period %d association period %d\n", config_period, ra->association_periods); select_prach_occasion(ra, mac->ssb_list.nb_tx_ssb, max_num_occasions, ra_occasions_period, num_ra_occasions_period); } /* TS 38.321 subclause 7.3 - return DELTA_PREAMBLE values in dB */ static int nr_get_delta_preamble(int scs, int prach_format) { int delta = 0; switch (prach_format) { case 0: case 3: delta = 0; break; case 1: delta = -3; break; case 2: delta = -6; break; case 0xa1: case 0xb1: delta = 8 + 3 * scs; break; case 0xa2: case 0xb2: case 0xc2: delta = 5 + 3 * scs; break; case 0xa3: case 0xb3: delta = 3 + 3 * scs; break; case 0xb4: delta = 3 * scs; break; case 0xc0: delta = 11 + 3 * scs; break; default: AssertFatal(false, "Invalid preamble format %x\n", prach_format); } return delta; } static int get_ra_preamble_rx_target_power(RA_config_t *ra, int scs) { NR_PRACH_RESOURCES_t *prach_resources = &ra->prach_resources; int delta_preamble = nr_get_delta_preamble(scs, ra->sched_ro_info.format); int tp = ra->preambleReceivedTargetPower_config + delta_preamble + prach_resources->power_offset_2step; tp += (prach_resources->preamble_power_ramping_cnt - 1) * prach_resources->preamble_power_ramping_step; return tp; } // 38.321 Sections 5.1.3 and .3a static void ra_preamble_msga_transmission(RA_config_t *ra, int scs) { NR_PRACH_RESOURCES_t *prach_resources = &ra->prach_resources; if (prach_resources->preamble_tx_counter > 1) { // TODO if the notification of suspending power ramping counter has not been received from lower layers (not implemented) // this happens if the L1 does not transmit PRACH of if the spacial filter changed (UE transmitting in another beam) // TODO if LBT failure indication was not received from lower layers (LBT not implemented) // if SSB or CSI-RS selected is not changed from the selection in the last Random Access Preamble transmission if (!ra->new_ssb) prach_resources->preamble_power_ramping_cnt++; } prach_resources->ra_preamble_rx_target_power = get_ra_preamble_rx_target_power(ra, scs); // 3GPP TS 38.321 Section 5.1.3 says t_id for RA-RNTI depends on mu as specified in clause 5.3.2 in TS 38.211 // so mu = 0 for prach format < 4. const int slot = ((ra->sched_ro_info.format & 0xff) < 4) ? ra->sched_ro_info.slot >> scs : ra->sched_ro_info.slot; ra->ra_rnti = nr_get_ra_rnti(ra->sched_ro_info.start_symbol, slot, ra->sched_ro_info.fdm, 0); if (ra->ra_type == RA_2_STEP) ra->MsgB_rnti = nr_get_MsgB_rnti(ra->sched_ro_info.start_symbol, slot, ra->sched_ro_info.fdm, 0); } // 38.321 Section 5.1.2 Random Access Resource selection void ra_resource_selection(NR_UE_MAC_INST_t *mac) { configure_ra_preamble(mac); const NR_UE_UL_BWP_t *current_UL_BWP = mac->current_UL_BWP; const int ul_mu = mac->current_UL_BWP->scs; const int mu = nr_get_prach_or_ul_mu(mac->current_UL_BWP->msgA_ConfigCommon_r16, current_UL_BWP->rach_ConfigCommon, ul_mu); configure_prach_occasions(mac, mu); ra_preamble_msga_transmission(&mac->ra, mu); } static int nr_get_RA_window_2Step_v17(long msgB_ResponseWindow) { switch (msgB_ResponseWindow) { case NR_RACH_ConfigGenericTwoStepRA_r16__ext1__msgB_ResponseWindow_v1700_sl240: return 240; break; case NR_RACH_ConfigGenericTwoStepRA_r16__ext1__msgB_ResponseWindow_v1700_sl640: return 640; break; case NR_RACH_ConfigGenericTwoStepRA_r16__ext1__msgB_ResponseWindow_v1700_sl960: return 960; break; case NR_RACH_ConfigGenericTwoStepRA_r16__ext1__msgB_ResponseWindow_v1700_sl1280: return 1280; break; case NR_RACH_ConfigGenericTwoStepRA_r16__ext1__msgB_ResponseWindow_v1700_sl1920: return 1920; break; case NR_RACH_ConfigGenericTwoStepRA_r16__ext1__msgB_ResponseWindow_v1700_sl2560: return 2590; default: AssertFatal(false, "illegal msgB_responseWindow value %ld\n", msgB_ResponseWindow); break; } return 0; } static int nr_get_RA_window_2Step_v16(long msgB_ResponseWindow) { switch (msgB_ResponseWindow) { case NR_RACH_ConfigGenericTwoStepRA_r16__msgB_ResponseWindow_r16_sl1: return 1; break; case NR_RACH_ConfigGenericTwoStepRA_r16__msgB_ResponseWindow_r16_sl2: return 2; break; case NR_RACH_ConfigGenericTwoStepRA_r16__msgB_ResponseWindow_r16_sl4: return 4; break; case NR_RACH_ConfigGenericTwoStepRA_r16__msgB_ResponseWindow_r16_sl8: return 8; break; case NR_RACH_ConfigGenericTwoStepRA_r16__msgB_ResponseWindow_r16_sl10: return 10; break; case NR_RACH_ConfigGenericTwoStepRA_r16__msgB_ResponseWindow_r16_sl20: return 20; break; case NR_RACH_ConfigGenericTwoStepRA_r16__msgB_ResponseWindow_r16_sl40: return 40; break; case NR_RACH_ConfigGenericTwoStepRA_r16__msgB_ResponseWindow_r16_sl80: return 80; break; case NR_RACH_ConfigGenericTwoStepRA_r16__msgB_ResponseWindow_r16_sl160: return 160; break; case NR_RACH_ConfigGenericTwoStepRA_r16__msgB_ResponseWindow_r16_sl320: return 360; break; default: AssertFatal(false, "illegal msgB_responseWindow value %ld\n", msgB_ResponseWindow); break; } return 0; } static int nr_get_RA_window_4Step_v16(long ra_ResponseWindow) { switch (ra_ResponseWindow) { case NR_RACH_ConfigGeneric__ext1__ra_ResponseWindow_v1610_sl60: return 60; case NR_RACH_ConfigGeneric__ext1__ra_ResponseWindow_v1610_sl160: return 160; default: AssertFatal(false, "illegal ra_ResponseWindow value %ld\n", ra_ResponseWindow); } return 0; } static int nr_get_RA_window_4Step_v17(long ra_ResponseWindow) { switch (ra_ResponseWindow) { case NR_RACH_ConfigGeneric__ext2__ra_ResponseWindow_v1700_sl240: return 240; case NR_RACH_ConfigGeneric__ext2__ra_ResponseWindow_v1700_sl320: return 320; case NR_RACH_ConfigGeneric__ext2__ra_ResponseWindow_v1700_sl640: return 640; case NR_RACH_ConfigGeneric__ext2__ra_ResponseWindow_v1700_sl960: return 960; case NR_RACH_ConfigGeneric__ext2__ra_ResponseWindow_v1700_sl1280: return 1280; case NR_RACH_ConfigGeneric__ext2__ra_ResponseWindow_v1700_sl1920: return 1920; case NR_RACH_ConfigGeneric__ext2__ra_ResponseWindow_v1700_sl2560: return 2590; default: AssertFatal(false, "illegal msgB_responseWindow value %ld\n", ra_ResponseWindow); } return 0; } static int nr_get_RA_window_4Step(long ra_ResponseWindow) { switch (ra_ResponseWindow) { case NR_RACH_ConfigGeneric__ra_ResponseWindow_sl1: return 1; break; case NR_RACH_ConfigGeneric__ra_ResponseWindow_sl2: return 2; break; case NR_RACH_ConfigGeneric__ra_ResponseWindow_sl4: return 4; break; case NR_RACH_ConfigGeneric__ra_ResponseWindow_sl8: return 8; break; case NR_RACH_ConfigGeneric__ra_ResponseWindow_sl10: return 10; break; case NR_RACH_ConfigGeneric__ra_ResponseWindow_sl20: return 20; break; case NR_RACH_ConfigGeneric__ra_ResponseWindow_sl40: return 40; break; case NR_RACH_ConfigGeneric__ra_ResponseWindow_sl80: return 80; break; default: AssertFatal(false, "illegal ra_ResponseWindow value %ld\n", ra_ResponseWindow); break; } return 0; } static void setup_ra_response_window(NR_UE_MAC_INST_t *mac, RA_config_t *ra, NR_RACH_ConfigGeneric_t *configGeneric, NR_RACH_ConfigGenericTwoStepRA_r16_t *twostep) { const int slots_per_frame = mac->frame_structure.numb_slots_frame; int respwind_value = 0; if (ra->ra_type == RA_2_STEP) { long *msgB_ResponseWindow = twostep->msgB_ResponseWindow_r16; if (msgB_ResponseWindow) respwind_value = nr_get_RA_window_2Step_v16(*msgB_ResponseWindow); if (twostep->ext1 && twostep->ext1->msgB_ResponseWindow_v1700) { AssertFatal(msgB_ResponseWindow == NULL, "The network does not configure msgB-ResponseWindow-r16 simultaneously with msgB-ResponseWindow-v1700\n"); msgB_ResponseWindow = twostep->ext1->msgB_ResponseWindow_v1700; if (msgB_ResponseWindow) respwind_value = nr_get_RA_window_2Step_v17(*msgB_ResponseWindow); } // The network configures a value lower than or equal to 40ms int slots_40ms = 4 * slots_per_frame; AssertFatal(respwind_value != 0 && respwind_value <= slots_40ms, "Invalid MSGB response window value\n"); } else { AssertFatal(ra->ra_type == RA_4_STEP, "Invalid RA type\n"); long *response_window = NULL; if (configGeneric->ext1 && configGeneric->ext1->ra_ResponseWindow_v1610) respwind_value = nr_get_RA_window_4Step_v16(*configGeneric->ext1->ra_ResponseWindow_v1610); if (configGeneric->ext2 && configGeneric->ext2->ra_ResponseWindow_v1700) { AssertFatal(response_window == NULL, "ra_ResponseWindow_v1610 and ra_ResponseWindow_v1700 are mutually exclusive\n"); respwind_value = nr_get_RA_window_4Step_v17(*configGeneric->ext2->ra_ResponseWindow_v1700); } if (!response_window) respwind_value = nr_get_RA_window_4Step(configGeneric->ra_ResponseWindow); // The network configures a value lower than or equal to 10ms AssertFatal(respwind_value != 0, "Invalid RAR response window value\n"); if (respwind_value > slots_per_frame) LOG_E(NR_MAC, "RA-ResponseWindow need to be configured to a value lower than or equal to 10 ms\n"); } const int ntn_ue_koffset = GET_NTN_UE_K_OFFSET(&mac->phy_config.config_req.ntn_config, mac->current_UL_BWP->scs); ra->response_window_setup_time = respwind_value + ntn_ue_koffset; } // Random Access procedure initialization as per 5.1.1 and initialization of variables specific // to Random Access type as specified in clause 5.1.1a (3GPP TS 38.321 version 16.2.1 Release 16) bool init_RA(NR_UE_MAC_INST_t *mac, int frame) { RA_config_t *ra = &mac->ra; LOG_D(NR_MAC, "Initialization of RA\n"); ra->ra_state = nrRA_GENERATE_PREAMBLE; // TODO this piece of code is required to compute MSG3_size that is used by ra_preambles_config function // Not a good implementation, it needs improvements if (!IS_SA_MODE(get_softmodem_params())) ra->Msg3_size = sizeof(uint16_t) + sizeof(NR_MAC_SUBHEADER_FIXED); // Random acces procedure initialization mac->state = UE_PERFORMING_RA; ra->RA_active = true; NR_PRACH_RESOURCES_t *prach_resources = &ra->prach_resources; fapi_nr_config_request_t *cfg = &mac->phy_config.config_req; // flush MSG3 buffer free_and_zero(ra->Msg3_buffer); // set the PREAMBLE_TRANSMISSION_COUNTER to 1 prach_resources->preamble_tx_counter = 1; // set the PREAMBLE_POWER_RAMPING_COUNTER to 1 prach_resources->preamble_power_ramping_cnt = 1; // set POWER_OFFSET_2STEP_RA to 0 dB prach_resources->power_offset_2step = 0; // perform the BWP operation as specified in clause 5.15 // if PRACH occasions are not configured for the active UL BWP if (!mac->current_UL_BWP->rach_ConfigCommon) { // switch the active UL BWP to BWP indicated by initialUplinkBWP mac->current_UL_BWP = get_ul_bwp_structure(mac, 0, false); // if the Serving Cell is an SpCell // switch the active DL BWP to BWP indicated by initialDownlinkBWP mac->current_DL_BWP = get_dl_bwp_structure(mac, 0, false); } else { // if the active DL BWP does not have the same bwp-Id as the active UL BWP if (mac->current_UL_BWP->bwp_id != mac->current_DL_BWP->bwp_id) { // switch the active DL BWP to the DL BWP with the same bwp-Id as the active UL BWP mac->current_DL_BWP = get_dl_bwp_structure(mac, 0, false); } } const NR_UE_UL_BWP_t *current_UL_BWP = mac->current_UL_BWP; NR_RACH_ConfigCommon_t *nr_rach_ConfigCommon = current_UL_BWP->rach_ConfigCommon; AssertFatal(nr_rach_ConfigCommon, "rach-ConfigCommon should be configured here\n"); // stop the bwp-InactivityTimer associated with the active DL BWP of this Serving Cell, if running // TODO bwp-InactivityTimer not implemented // if the carrier to use for the Random Access procedure is explicitly signalled (always the case for us) // PRACH shall be as specified for QPSK modulated DFT-s-OFDM of equivalent RB allocation (38.101-1) NR_SubcarrierSpacing_t prach_scs; int scs_for_pcmax; // for long prach the UL BWP SCS is used for calculating RA_PCMAX NR_RACH_ConfigGeneric_t *rach_ConfigGeneric = &nr_rach_ConfigCommon->rach_ConfigGeneric; if (nr_rach_ConfigCommon->msg1_SubcarrierSpacing) { prach_scs = *nr_rach_ConfigCommon->msg1_SubcarrierSpacing; scs_for_pcmax = prach_scs; } else { const unsigned int index = rach_ConfigGeneric->prach_ConfigurationIndex; const unsigned int unpaired = mac->phy_config.config_req.cell_config.frame_duplex_type; const unsigned int format = get_format0(index, unpaired, mac->frequency_range); prach_scs = get_delta_f_RA_long(format); scs_for_pcmax = mac->current_UL_BWP->scs; } int n_prbs = get_N_RA_RB(prach_scs, current_UL_BWP->scs); int start_prb = rach_ConfigGeneric->msg1_FrequencyStart + current_UL_BWP->BWPStart; prach_resources->Pc_max = nr_get_Pcmax(mac->p_Max, mac->nr_band, mac->frame_structure.frame_type, mac->frequency_range, current_UL_BWP->channel_bandwidth, 2, false, scs_for_pcmax, cfg->carrier_config.dl_grid_size[scs_for_pcmax], true, n_prbs, start_prb); // TODO if the Random Access procedure was initiated for SI request // and the Random Access Resources for SI request have been explicitly provided by RRC // TODO if the Random Access procedure was initiated for SpCell beam failure recovery // and if the contention-free Random Access Resources for beam failure recovery request for 4-step RA type // have been explicitly provided by RRC for the BWP selected for Random Access procedure NR_RACH_ConfigCommonTwoStepRA_r16_t *twostep_conf = NULL; NR_RACH_ConfigDedicated_t *rach_Dedicated = ra->rach_ConfigDedicated; // if the Random Access procedure is initiated by PDCCH order // and if the ra-PreambleIndex explicitly provided by PDCCH is not 0b000000 bool pdcch_order = (ra->pdcch_order.active && ra->pdcch_order.preamble_index != 0xb000000) ? true : false; // or if the Random Access procedure was initiated for reconfiguration with sync and // if the contention-free Random Access Resources for 4-step RA type have been explicitly provided // in rach-ConfigDedicated for the BWP selected for Random Access procedure if ((rach_Dedicated && rach_Dedicated->cfra) || pdcch_order) { LOG_I(MAC, "Initialization of 4-Step CFRA procedure\n"); ra->ra_type = RA_4_STEP; ra->cfra = true; } else { bool twostep_cfra = (rach_Dedicated && rach_Dedicated->ext1) ? (rach_Dedicated->ext1->cfra_TwoStep_r16 ? true : false) : false; if (twostep_cfra) { // if the Random Access procedure was initiated for reconfiguration with sync and // if the contention-free Random Access Resources for 2-step RA type have been explicitly provided in rach-ConfigDedicated LOG_I(MAC, "Initialization of 2-Step CFRA procedure\n"); ra->ra_type = RA_2_STEP; ra->cfra = true; } else { bool twostep = false; if (current_UL_BWP->msgA_ConfigCommon_r16) { twostep_conf = ¤t_UL_BWP->msgA_ConfigCommon_r16->rach_ConfigCommonTwoStepRA_r16; if (nr_rach_ConfigCommon) { // if the BWP selected for Random Access procedure is configured with both 2- and 4-step RA type Random Access Resources // and the RSRP of the downlink pathloss reference is above msgA-RSRP-Threshold AssertFatal(twostep_conf->msgA_RSRP_Threshold_r16, "msgA_RSRP_Threshold_r16 is mandatory present if both 2-step and 4-step random access types are configured\n"); // For thresholds the RSRP value is (IE value – 156) dBm except for the IE 127 in which case the actual value is infinity int rsrp_msga_thr = *twostep_conf->msgA_RSRP_Threshold_r16 - 156; if (*twostep_conf->msgA_RSRP_Threshold_r16 != 127 && mac->ssb_measurements[mac->mib_ssb].ssb_rsrp_dBm > rsrp_msga_thr) twostep = true; } else { // if the BWP selected for Random Access procedure is only configured with 2-step RA type Random Access twostep = true; } } if (twostep) { LOG_I(MAC, "Initialization of 2-Step CBRA procedure\n"); ra->ra_type = RA_2_STEP; ra->cfra = false; } else { LOG_I(MAC, "Initialization of 4-Step CBRA procedure\n"); ra->ra_type = RA_4_STEP; ra->cfra = false; } } } // need to ask RRC to send the MSG3 data to transmit if needed (CBRA originating from upper layers) if (!mac->msg3_C_RNTI && ra->cfra == false) nr_mac_rrc_msg3_ind(mac->ue_id, 0, true); NR_RACH_ConfigGenericTwoStepRA_r16_t *twostep_generic = twostep_conf ? &twostep_conf->rach_ConfigGenericTwoStepRA_r16 : NULL; if (ra->ra_type == RA_2_STEP && twostep_generic && twostep_generic->msgA_PreamblePowerRampingStep_r16) prach_resources->preamble_power_ramping_step = *twostep_generic->msgA_PreamblePowerRampingStep_r16 << 1; else prach_resources->preamble_power_ramping_step = rach_ConfigGeneric->powerRampingStep << 1; ra->scaling_factor_bi = 1; if (ra->ra_type == RA_2_STEP && twostep_generic && twostep_generic->msgA_PreambleReceivedTargetPower_r16) ra->preambleReceivedTargetPower_config = *twostep_generic->msgA_PreambleReceivedTargetPower_r16; else ra->preambleReceivedTargetPower_config = rach_ConfigGeneric->preambleReceivedTargetPower; if (ra->ra_type == RA_2_STEP && twostep_generic && twostep_generic->preambleTransMax_r16) set_preambleTransMax(ra, *twostep_generic->preambleTransMax_r16); else set_preambleTransMax(ra, rach_ConfigGeneric->preambleTransMax); if (ra->ra_type == RA_2_STEP && twostep_generic && twostep_generic->msgA_ZeroCorrelationZoneConfig_r16) ra->zeroCorrelationZoneConfig = *twostep_generic->msgA_ZeroCorrelationZoneConfig_r16; else ra->zeroCorrelationZoneConfig = rach_ConfigGeneric->zeroCorrelationZoneConfig; if (ra->ra_type == RA_2_STEP && twostep_conf && twostep_conf->msgA_RestrictedSetConfig_r16) ra->restricted_set_config = *twostep_conf->msgA_RestrictedSetConfig_r16; else ra->restricted_set_config = nr_rach_ConfigCommon->restrictedSetConfig; // TODO msgA-TransMax not implemented // TODO ra-Prioritization not implemented if (ra->ra_type == RA_2_STEP && twostep_generic && twostep_generic->msgA_PreambleReceivedTargetPower_r16) ra->preambleRxTargetPower = *twostep_generic->msgA_PreambleReceivedTargetPower_r16; else ra->preambleRxTargetPower = rach_ConfigGeneric->preambleReceivedTargetPower; if (ra->ra_type == RA_2_STEP && current_UL_BWP->msgA_ConfigCommon_r16 && current_UL_BWP->msgA_ConfigCommon_r16->msgA_PUSCH_Config_r16 && current_UL_BWP->msgA_ConfigCommon_r16->msgA_PUSCH_Config_r16->msgA_DeltaPreamble_r16) ra->msg3_deltaPreamble = *current_UL_BWP->msgA_ConfigCommon_r16->msgA_PUSCH_Config_r16->msgA_DeltaPreamble_r16 << 1; else ra->msg3_deltaPreamble = mac->current_UL_BWP->msg3_DeltaPreamble ? *mac->current_UL_BWP->msg3_DeltaPreamble << 1 : 0; ra->new_ssb = false; if (ra->ra_type == RA_2_STEP && twostep_generic && twostep_generic->msgA_PRACH_ConfigurationIndex_r16) ra->ra_config_index = *twostep_generic->msgA_PRACH_ConfigurationIndex_r16; else { if (rach_ConfigGeneric->ext1 && rach_ConfigGeneric->ext1->prach_ConfigurationIndex_v1610) ra->ra_config_index = *rach_ConfigGeneric->ext1->prach_ConfigurationIndex_v1610; else ra->ra_config_index = rach_ConfigGeneric->prach_ConfigurationIndex; } if (ra->ra_type == RA_2_STEP && twostep_generic && twostep_generic->msgA_RO_FDM_r16) ra->num_fd_occasions = 1 << *twostep_generic->msgA_RO_FDM_r16; else ra->num_fd_occasions = 1 << rach_ConfigGeneric->msg1_FDM; setup_ra_response_window(mac, ra, rach_ConfigGeneric, twostep_generic); return true; } void nr_Msg3_transmitted(NR_UE_MAC_INST_t *mac, uint8_t CC_id, frame_t frameP, slot_t slotP, uint8_t gNB_id) { RA_config_t *ra = &mac->ra; NR_RACH_ConfigCommon_t *nr_rach_ConfigCommon = mac->current_UL_BWP->rach_ConfigCommon; const int ntn_ue_koffset = GET_NTN_UE_K_OFFSET(&mac->phy_config.config_req.ntn_config, mac->current_UL_BWP->scs); const int slots_per_ms = mac->frame_structure.numb_slots_frame / 10; // start contention resolution timer const int RA_contention_resolution_timer_ms = (nr_rach_ConfigCommon->ra_ContentionResolutionTimer + 1) << 3; const int RA_contention_resolution_timer_slots = RA_contention_resolution_timer_ms * slots_per_ms; // timer step 1 slot and timer target given by ra_ContentionResolutionTimer + ta-Common-r17 nr_timer_setup(&ra->contention_resolution_timer, RA_contention_resolution_timer_slots + ntn_ue_koffset, 1); nr_timer_start(&ra->contention_resolution_timer); ra->ra_state = nrRA_WAIT_CONTENTION_RESOLUTION; } static uint8_t *fill_msg3_crnti_pdu(RA_config_t *ra, uint8_t *pdu, uint16_t crnti) { // RA triggered by UE MAC with C-RNTI in MAC CE LOG_D(NR_MAC, "Generating MAC CE with C-RNTI for MSG3 %x\n", crnti); *(NR_MAC_SUBHEADER_FIXED *)pdu = (NR_MAC_SUBHEADER_FIXED){.LCID = UL_SCH_LCID_C_RNTI, .R = 0}; pdu += sizeof(NR_MAC_SUBHEADER_FIXED); // C-RNTI MAC CE (2 octets) uint16_t rnti_pdu = ((crnti & 0xFF) << 8) | ((crnti >> 8) & 0xFF); memcpy(pdu, &rnti_pdu, sizeof(rnti_pdu)); pdu += sizeof(rnti_pdu); ra->t_crnti = crnti; return pdu; } static uint8_t *fill_msg3_pdu_from_rlc(NR_UE_MAC_INST_t *mac, uint8_t *pdu, int TBS_max) { RA_config_t *ra = &mac->ra; // regular Msg3/MsgA_PUSCH with PDU coming from higher layers *(NR_MAC_SUBHEADER_FIXED *)pdu = (NR_MAC_SUBHEADER_FIXED){.LCID = UL_SCH_LCID_CCCH_48_BITS}; pdu += sizeof(NR_MAC_SUBHEADER_FIXED); tbs_size_t len = nr_mac_rlc_data_req(mac->ue_id, mac->ue_id, false, 0, // SRB0 for messages sent in MSG3 TBS_max - sizeof(NR_MAC_SUBHEADER_FIXED), /* size of mac_ce above */ (char *)pdu); AssertFatal(len > 0, "no data for Msg3/MsgA_PUSCH\n"); // UE Contention Resolution Identity // Store the first 48 bits belonging to the uplink CCCH SDU within Msg3 to determine whether or not the // Random Access Procedure has been successful after reception of Msg4 // We copy from persisted memory to another persisted memory memcpy(ra->cont_res_id, pdu, sizeof(uint8_t) * 6); pdu += len; return pdu; } void nr_get_Msg3_MsgA_PUSCH_payload(NR_UE_MAC_INST_t *mac, uint8_t *buf, int TBS_max) { RA_config_t *ra = &mac->ra; // we already stored MSG3 in the buffer, we can use that if (ra->Msg3_buffer) { memcpy(buf, ra->Msg3_buffer, sizeof(uint8_t) * TBS_max); return; } uint8_t *pdu = buf; if (mac->msg3_C_RNTI) pdu = fill_msg3_crnti_pdu(ra, pdu, mac->crnti); else pdu = fill_msg3_pdu_from_rlc(mac, pdu, TBS_max); AssertFatal(TBS_max >= pdu - buf, "Allocated resources are not enough for Msg3/MsgA_PUSCH!\n"); // Padding: fill remainder with 0 LOG_D(NR_MAC, "Remaining %ld bytes, filling with padding\n", pdu - buf); while (pdu < buf + TBS_max - sizeof(NR_MAC_SUBHEADER_FIXED)) { *(NR_MAC_SUBHEADER_FIXED *)pdu = (NR_MAC_SUBHEADER_FIXED){.LCID = UL_SCH_LCID_PADDING}; pdu += sizeof(NR_MAC_SUBHEADER_FIXED); } ra->Msg3_buffer = calloc_or_fail(TBS_max, sizeof(uint8_t)); memcpy(ra->Msg3_buffer, buf, sizeof(uint8_t) * TBS_max); } // Handlig successful RA completion @ MAC layer // according to section 5 of 3GPP TS 38.321 version 16.2.1 Release 16 // todo: // - complete handling of received contention-based RA preamble void nr_ra_succeeded(NR_UE_MAC_INST_t *mac, const uint8_t gNB_index, const frame_t frame, const int slot) { RA_config_t *ra = &mac->ra; if (ra->cfra) { LOG_I(MAC, "[UE %d][%d.%d][RAPROC] RA procedure succeeded. CFRA: RAR successfully received.\n", mac->ue_id, frame, slot); } else { LOG_A(MAC, "[UE %d][%d.%d][RAPROC] %d-Step RA procedure succeeded. CBRA: Contention Resolution is successful.\n", mac->ue_id, frame, slot, ra->ra_type == RA_2_STEP ? 2 : 4); mac->crnti = ra->t_crnti; ra->t_crnti = 0; nr_timer_stop(&ra->contention_resolution_timer); } ra->RA_active = false; mac->msg3_C_RNTI = false; ra->ra_state = nrRA_SUCCEEDED; mac->state = UE_CONNECTED; free_and_zero(ra->Msg3_buffer); nr_mac_rrc_ra_ind(mac->ue_id, true); } void nr_ra_backoff_setting(RA_config_t *ra) { // select a random backoff time according to a uniform distribution // between 0 and the PREAMBLE_BACKOFF uint32_t seed = (unsigned int)(rdtsc_oai() & ~0); uint32_t random_backoff = ra->RA_backoff_limit ? rand_r(&seed) % ra->RA_backoff_limit : 0; // in slots nr_timer_setup(&ra->RA_backoff_timer, random_backoff, 1); nr_timer_start(&ra->RA_backoff_timer); } void nr_ra_contention_resolution_failed(NR_UE_MAC_INST_t *mac) { LOG_W(MAC, "[UE %d] Contention resolution failed\n", mac->ue_id); RA_config_t *ra = &mac->ra; // discard the TEMPORARY_C-RNTI ra->t_crnti = 0; // flush MSG3 buffer free_and_zero(ra->Msg3_buffer); // MSG3 with C-RNTI is a L2 procedure, we shouldn't send any indication to RRC if (!mac->msg3_C_RNTI) nr_mac_rrc_msg3_ind(mac->ue_id, 0, true); NR_PRACH_RESOURCES_t *prach_resources = &ra->prach_resources; prach_resources->preamble_tx_counter++; if (prach_resources->preamble_tx_counter == ra->preambleTransMax + 1) { // indicate a Random Access problem to upper layers nr_mac_rrc_ra_ind(mac->ue_id, false); } else { // TODO handle msgA-TransMax (go back to 4-step if the threshold is reached) // starting backoff time nr_ra_backoff_setting(ra); } } void nr_rar_not_successful(NR_UE_MAC_INST_t *mac) { LOG_W(MAC, "[UE %d] RAR reception failed\n", mac->ue_id); RA_config_t *ra = &mac->ra; NR_PRACH_RESOURCES_t *prach_resources = &ra->prach_resources; prach_resources->preamble_tx_counter++; bool ra_completed = false; if (prach_resources->preamble_tx_counter == ra->preambleTransMax + 1) { // if the Random Access Preamble is transmitted on the SpCell // TODO to be verified, this means SA if I'm not mistaken if (IS_SA_MODE(get_softmodem_params())) { // indicate a Random Access problem to upper layers nr_mac_rrc_ra_ind(mac->ue_id, false); } else { // if the Random Access Preamble is transmitted on an SCell: // consider the Random Access procedure unsuccessfully completed. ra_completed = true; ra->ra_state = nrRA_UE_IDLE; } } if (!ra_completed) { nr_ra_backoff_setting(ra); } } void trigger_MAC_UE_RA(NR_UE_MAC_INST_t *mac, dci_pdu_rel15_t *pdcch_order) { LOG_W(NR_MAC, "Triggering new RA procedure for UE with RNTI %x\n", mac->crnti); mac->state = UE_PERFORMING_RA; reset_ra(mac, false); RA_config_t *ra = &mac->ra; mac->msg3_C_RNTI = true; if (pdcch_order) { ra->pdcch_order.active = true; ra->pdcch_order.preamble_index = pdcch_order->ra_preamble_index; ra->pdcch_order.ssb_index = pdcch_order->ss_pbch_index; ra->pdcch_order.prach_mask = pdcch_order->prach_mask_index; } } void prepare_msg4_msgb_feedback(NR_UE_MAC_INST_t *mac, int pid, int ack_nack) { NR_UE_DL_HARQ_STATUS_t *current_harq = &mac->dl_harq_info[pid]; int sched_slot = current_harq->ul_slot; int sched_frame = current_harq->ul_frame; PUCCH_sched_t pucch = {.n_CCE = current_harq->n_CCE, .N_CCE = current_harq->N_CCE, .ack_payload = ack_nack, .n_harq = 1}; current_harq->active = false; current_harq->ack_received = false; RA_config_t *ra = &mac->ra; ra->ra_pucch = calloc_or_fail(1, sizeof(*ra->ra_pucch)); ra->ra_pucch->pucch_sched = pucch; ra->ra_pucch->sched_frame = sched_frame; ra->ra_pucch->sched_slot = sched_slot; } void reset_ra(NR_UE_MAC_INST_t *nr_mac, bool free_prach) { RA_config_t *ra = &nr_mac->ra; if (ra->rach_ConfigDedicated) asn1cFreeStruc(asn_DEF_NR_RACH_ConfigDedicated, ra->rach_ConfigDedicated); memset(ra, 0, sizeof(RA_config_t)); if (!free_prach) return; }