diff --git a/doc/FEATURE_SET.md b/doc/FEATURE_SET.md index 34a3f17987..36c84fab07 100644 --- a/doc/FEATURE_SET.md +++ b/doc/FEATURE_SET.md @@ -308,6 +308,7 @@ These modes of operation are supported: * Reception of NR-PSS/NR-SSS * NR-PBCH supports multiple SSBs and flexible periodicity - RSRP measurement for the strongest SSB + - RSRP measurement for neighboring cell at same SSB frequency with serving cell * Reception of NR-PDCCH (including reception of DCI, polar decoding, de-scrambling, de-modulation, RB de-mapping, etc) - common search space configured by MIB - user-specific search space configured by RRC @@ -456,6 +457,7 @@ These modes of operation are supported: - RRCReestablishmentRequest/RRC Reestablishment/Reestablishment complete - Support for master cell group configuration - Reception of UECapabilityEnquiry, encoding and transmission of UECapability + - Support for measurement report of Event A2/A3 * NTN according to 38.331 Rel.17 - Reception of ntn-Config-r17 from SIB19 or reconfigurationWithSync - Handling of ntn-UlSyncValidityDuration-r17 in SIB19 diff --git a/nfapi/open-nFAPI/nfapi/public_inc/fapi_nr_ue_interface.h b/nfapi/open-nFAPI/nfapi/public_inc/fapi_nr_ue_interface.h index dd6b8135ab..4c99da4e35 100644 --- a/nfapi/open-nFAPI/nfapi/public_inc/fapi_nr_ue_interface.h +++ b/nfapi/open-nFAPI/nfapi/public_inc/fapi_nr_ue_interface.h @@ -29,6 +29,11 @@ #define NFAPI_UE_MAX_NUM_CB 8 #define NFAPI_MAX_NUM_UL_PDU 255 #define NFAPI_MAX_NUM_CSI_RATEMATCH 4 +// Maximum number of neighboring cells that can be tracked simultaneously +// Set to 1 due to PSS search limitation: pss_search_time_nr() returns only +// the single strongest PSS correlation peak, making it impossible to reliably +// detect multiple neighbor cells in the same measurement cycle +#define NUMBER_OF_NEIGHBORING_CELLS_MAX 1 /* typedef unsigned int uint32_t; @@ -718,6 +723,16 @@ typedef struct } fapi_nr_prach_config_t; +typedef struct { + uint16_t Nid_cell; + uint8_t active; + uint32_t ssb_freq; +} fapi_nr_neighboring_cell_t; + +typedef struct { + fapi_nr_neighboring_cell_t nr_neighboring_cell[NUMBER_OF_NEIGHBORING_CELLS_MAX]; +} fapi_nr_meas_config_t; + typedef struct { int16_t target_Nid_cell; bool ssb_bw_scan; @@ -733,7 +748,7 @@ typedef struct { fapi_nr_tdd_table_t tdd_table; fapi_nr_prach_config_t prach_config; fapi_nr_ntn_config_t ntn_config; - + fapi_nr_meas_config_t meas_config; } fapi_nr_config_request_t; #endif diff --git a/openair1/PHY/NR_REFSIG/pss_nr.h b/openair1/PHY/NR_REFSIG/pss_nr.h index cf705a7ee6..8034d7f362 100644 --- a/openair1/PHY/NR_REFSIG/pss_nr.h +++ b/openair1/PHY/NR_REFSIG/pss_nr.h @@ -86,6 +86,18 @@ int pss_synchro_nr(const c16_t **rxdata, int *f_off, int *pssPeak, int *pssAvg); +int pss_search_time_nr(const c16_t **rxdata, + const NR_DL_FRAME_PARMS *frame_parms, + const c16_t pssTime[NUMBER_PSS_SEQUENCE][frame_parms->ofdm_symbol_size], + bool fo_flag, + int is, + int target_Nid_cell, + int *nid2, + int *f_off, + int *pssPeak, + int *pssAvg, + int search_start, + int search_length); void generate_pss_nr_time(const NR_DL_FRAME_PARMS *fp, const int N_ID_2, int ssbFirstSCS, c16_t pssTime[fp->ofdm_symbol_size]); int16_t *get_primary_synchro_nr2(const int nid2); diff --git a/openair1/PHY/NR_UE_ESTIMATION/nr_estimation.h b/openair1/PHY/NR_UE_ESTIMATION/nr_estimation.h index 7a47c67c52..4804f41b21 100644 --- a/openair1/PHY/NR_UE_ESTIMATION/nr_estimation.h +++ b/openair1/PHY/NR_UE_ESTIMATION/nr_estimation.h @@ -114,10 +114,23 @@ uint32_t nr_ue_calculate_ssb_rsrp(const NR_DL_FRAME_PARMS *fp, int ssb_start_subcarrier); void nr_ue_ssb_rsrp_measurements(PHY_VARS_NR_UE *ue, - uint8_t gNB_index, + int ssb_index, const UE_nr_rxtx_proc_t *proc, c16_t rxdataF[][ue->frame_parms.samples_per_slot_wCP]); +// Structure to pass data to neighboring cell measurement task +typedef struct { + UE_nr_rxtx_proc_t proc; + PHY_VARS_NR_UE *ue; + c16_t *rxdata[NB_RX_ANTENNAS_MAX]; + uint32_t rxdata_size; + c16_t rxdata_ant[]; +} nr_meas_task_args_t; + +void nr_ue_meas_neighboring_cell(void *arg); + +void do_neighboring_cell_measurements(UE_nr_rxtx_proc_t *proc, PHY_VARS_NR_UE *ue, c16_t **rxdata, uint32_t rxdata_size); + void nr_ue_rrc_measurements(PHY_VARS_NR_UE *ue, const UE_nr_rxtx_proc_t *proc, c16_t rxdataF[][ue->frame_parms.samples_per_slot_wCP]); diff --git a/openair1/PHY/NR_UE_ESTIMATION/nr_ue_measurements.c b/openair1/PHY/NR_UE_ESTIMATION/nr_ue_measurements.c index b04f595481..d06f458419 100644 --- a/openair1/PHY/NR_UE_ESTIMATION/nr_ue_measurements.c +++ b/openair1/PHY/NR_UE_ESTIMATION/nr_ue_measurements.c @@ -37,6 +37,12 @@ #include "common/utils/LOG/log.h" #include "PHY/sse_intrin.h" #include "SCHED_NR_UE/defs.h" +#include "PHY/NR_REFSIG/sss_nr.h" +#include "PHY/NR_REFSIG/pss_nr.h" +#include "PHY/NR_REFSIG/ss_pbch_nr.h" +#include "PHY/MODULATION/modulation_UE.h" +#include "PHY/NR_UE_TRANSPORT/nr_transport_proto_ue.h" +#include "PHY/NR_UE_ESTIMATION/nr_estimation.h" #define K1 ((long long int) 512) #define K2 ((long long int) (1024-K1)) @@ -184,6 +190,35 @@ uint32_t nr_ue_calculate_ssb_rsrp(const NR_DL_FRAME_PARMS *fp, return rsrp; } +// Send SSB RSRP measurement to MAC +static void send_ssb_rsrp_meas(PHY_VARS_NR_UE *ue, + const UE_nr_rxtx_proc_t *proc, + uint16_t Nid_cell, + int rsrp_dBm, + bool is_neighboring_cell, + int ssb_index, + float sinr_dB) +{ + if (!ue->if_inst || !ue->if_inst->dl_indication) + return; + + fapi_nr_l1_measurements_t l1_measurements = { + .gNB_index = proc->gNB_id, + .meas_type = NFAPI_NR_SS_MEAS, + .Nid_cell = Nid_cell, + .is_neighboring_cell = is_neighboring_cell, + .rsrp_dBm = rsrp_dBm, + .ssb_index = ssb_index, + .sinr_dB = sinr_dB, + }; + + nr_downlink_indication_t dl_indication = {0}; + fapi_nr_rx_indication_t rx_ind = {0}; + nr_fill_dl_indication(&dl_indication, NULL, &rx_ind, proc, ue, NULL); + nr_fill_rx_indication(&rx_ind, FAPI_NR_MEAS_IND, ue, NULL, NULL, 1, proc, &l1_measurements, NULL); + ue->if_inst->dl_indication(&dl_indication); +} + // This function implements: // - SS reference signal received power (SS-RSRP) as per clause 5.1.1 of 3GPP TS 38.215 version 16.3.0 Release 16 // - no Layer 3 filtering implemented (no filterCoefficient provided from RRC) @@ -227,23 +262,245 @@ void nr_ue_ssb_rsrp_measurements(PHY_VARS_NR_UE *ue, ue->measurements.ssb_sinr_dB[ssb_index]); // Send SS measurements to MAC - if (!ue->if_inst || !ue->if_inst->dl_indication) - return; + send_ssb_rsrp_meas(ue, + proc, + ue->frame_parms.Nid_cell, + ue->measurements.ssb_rsrp_dBm[ssb_index], + false, + ssb_index, + ue->measurements.ssb_sinr_dB[ssb_index]); +} - fapi_nr_l1_measurements_t l1_measurements = { - .gNB_index = proc->gNB_id, - .meas_type = NFAPI_NR_SS_MEAS, - .Nid_cell = ue->frame_parms.Nid_cell, - .rsrp_dBm = ue->measurements.ssb_rsrp_dBm[ssb_index], - .sinr_dB = ue->measurements.ssb_sinr_dB[ssb_index], - .ssb_index = ssb_index, - .is_neighboring_cell = false, +static bool search_neighboring_cell(NR_DL_FRAME_PARMS *frame_parms, + fapi_nr_neighboring_cell_t *nr_neighboring_cell, + neighboring_cell_info_t *neighboring_cell_info, + c16_t **rxdata, + uint32_t rxdata_size, + uint32_t rxdataF_sz, + c16_t rxdataF[][rxdataF_sz], + c16_t pssTime[][frame_parms->ofdm_symbol_size]) +{ + int detected_nid_cell = -1; + int ssb_offset = 0; + int freq_offset_pss = 0; + int pss_peak = 0; + int pss_avg = 0; + int32_t sss_metric = 0; + uint8_t sss_phase = 0; + int freq_offset_sss = 0; + + nr_ssb_search_params_t search_params = { + .frame_parms = frame_parms, + .rxdata = rxdata, + .rxdata_size = rxdata_size, + .ssb_start_subcarrier = frame_parms->ssb_start_subcarrier, + .target_nid_cell = -1, // Blind search + .exclude_nid_cell = frame_parms->Nid_cell, // Exclude serving cell + .apply_freq_offset = false, + .search_frame_id = 0, // Search in first frame of buffer + .fo_flag = false, + .rxdataF = rxdataF, + .pssTime = pssTime, + .detected_nid_cell = &detected_nid_cell, + .ssb_offset = &ssb_offset, + .sss_metric = &sss_metric, + .freq_offset_pss = &freq_offset_pss, + .freq_offset_sss = &freq_offset_sss, + .sss_phase = &sss_phase, + .pss_peak = &pss_peak, + .pss_avg = &pss_avg, }; - nr_downlink_indication_t dl_indication = {0}; - fapi_nr_rx_indication_t rx_ind = {0}; - nr_fill_dl_indication(&dl_indication, NULL, &rx_ind, proc, ue, NULL); - nr_fill_rx_indication(&rx_ind, FAPI_NR_MEAS_IND, ue, NULL, NULL, 1, proc, &l1_measurements, NULL); - ue->if_inst->dl_indication(&dl_indication); + + bool found = nr_search_ssb_common(&search_params); + + if (found) { + nr_neighboring_cell->Nid_cell = detected_nid_cell; + LOG_I(NR_PHY, + "Found neighbor cell PCI=%d (sss_metric=%d, ssb_offset=%d, pss_peak=%d dB, pss_avg=%d dB)\n", + detected_nid_cell, + sss_metric, + ssb_offset, + pss_peak, + pss_avg); + + // Update search window + neighboring_cell_info->pss_search_start = ssb_offset + frame_parms->nb_prefix_samples - 16; + neighboring_cell_info->pss_search_length = 32; + } + + return found; +} + +static bool validate_known_pci(NR_DL_FRAME_PARMS *frame_parms, + fapi_nr_neighboring_cell_t *nr_neighboring_cell, + neighboring_cell_info_t *neighboring_cell_info, + c16_t **rxdata, + uint32_t rxdataF_sz, + c16_t rxdataF[][rxdataF_sz], + c16_t pssTime[][frame_parms->ofdm_symbol_size]) +{ + int known_pci = nr_neighboring_cell->Nid_cell; + int pss_index = GET_NID2(known_pci); + int f_off = 0; + int pss_peak = 0; + int pss_avg = 0; + + int start = neighboring_cell_info->pss_search_start; + int length = neighboring_cell_info->pss_search_length; + + int peak_position = pss_search_time_nr((const c16_t **)rxdata, + frame_parms, + pssTime, + false, // no frequency offset estimation for tracking + 0, // first frame + known_pci, + &pss_index, + &f_off, + &pss_peak, + &pss_avg, + start, + length); + + if (peak_position < frame_parms->nb_prefix_samples) { + if (neighboring_cell_info->valid_meas) + neighboring_cell_info->consec_fail++; + LOG_D(NR_PHY, + "PSS validation failed for PCI=%d (search window: start=%d, length=%d, peak=%d dB, avg=%d dB), consec_fail=%d\n", + known_pci, + start, + length, + pss_peak, + pss_avg, + neighboring_cell_info->consec_fail); + return false; + } + + int ssb_offset = peak_position - frame_parms->nb_prefix_samples; + + uint8_t sss_symbol = SSS_SYMBOL_NB - PSS_SYMBOL_NB; + nr_slot_fep(NULL, frame_parms, 0, 0, rxdataF, link_type_dl, ssb_offset, (c16_t **)rxdata); + nr_slot_fep(NULL, frame_parms, 0, sss_symbol, rxdataF, link_type_dl, ssb_offset, (c16_t **)rxdata); + + int detected_nid_cell = -1; + int32_t sss_metric = 0; + uint8_t sss_phase = 0; + int freq_offset_sss = 0; + bool sss_detected = rx_sss_nr(frame_parms, + pss_index, + known_pci, + 0, + frame_parms->ssb_start_subcarrier, + &detected_nid_cell, + &sss_metric, + &sss_phase, + &freq_offset_sss, + rxdataF); + + if (!sss_detected) { + if (neighboring_cell_info->valid_meas) + neighboring_cell_info->consec_fail++; + LOG_D(NR_PHY, + "Known PCI validation failed for PCI=%d (metric=%d), consec_fail=%d\n", + known_pci, + sss_metric, + neighboring_cell_info->consec_fail); + return false; + } + + LOG_D(NR_PHY, "Known PCI validation completed for PCI=%d, metric=%d\n", known_pci, sss_metric); + neighboring_cell_info->consec_fail = 0; + neighboring_cell_info->valid_meas = true; + neighboring_cell_info->pss_search_start = peak_position - 16; + neighboring_cell_info->pss_search_length = 32; + + return true; +} + +void do_neighboring_cell_measurements(UE_nr_rxtx_proc_t *proc, PHY_VARS_NR_UE *ue, c16_t **rxdata, uint32_t rxdata_size) +{ + NR_DL_FRAME_PARMS *frame_parms = &ue->frame_parms; + + const uint32_t rxdataF_sz = ue->frame_parms.samples_per_slot_wCP; + + // Generate PSS time-domain sequences once for all neighbor cells + __attribute__((aligned(32))) c16_t pssTime[NUMBER_PSS_SEQUENCE][frame_parms->ofdm_symbol_size]; + for (int nid2_idx = 0; nid2_idx < NUMBER_PSS_SEQUENCE; nid2_idx++) { + generate_pss_nr_time(frame_parms, nid2_idx, frame_parms->ssb_start_subcarrier, pssTime[nid2_idx]); + } + + __attribute__((aligned(32))) c16_t rxdataF[ue->frame_parms.nb_antennas_rx][rxdataF_sz]; + + for (int cell_idx = 0; cell_idx < NUMBER_OF_NEIGHBORING_CELLS_MAX; cell_idx++) { + fapi_nr_neighboring_cell_t *neighbor_cell = &ue->nrUE_config.meas_config.nr_neighboring_cell[cell_idx]; + if (neighbor_cell->active == 0) { + continue; + } + + memset(rxdataF, 0, sizeof(rxdataF)); + neighboring_cell_info_t *neighboring_cell_info = &ue->measurements.neighboring_cell_info[cell_idx]; + + // performing the correlation on a frame length plus two symbols + // to take into account the possibility of PSS between the two frames + if (neighboring_cell_info->pss_search_length == 0) { + neighboring_cell_info->pss_search_length = frame_parms->samples_per_frame + (2 * frame_parms->ofdm_symbol_size); + } + + bool is_blind_search = (neighbor_cell->Nid_cell == (uint16_t)-1) || (neighbor_cell->Nid_cell == frame_parms->Nid_cell); + if (neighbor_cell->Nid_cell == frame_parms->Nid_cell) { + LOG_D(NR_PHY, "Neighbor cell PCI %d matches serving cell, using blind search\n", neighbor_cell->Nid_cell); + neighboring_cell_info->pss_search_start = 0; + neighboring_cell_info->pss_search_length = frame_parms->samples_per_frame + (2 * frame_parms->ofdm_symbol_size); + } + LOG_D(NR_PHY, + "Neighbor cell measurement: Nid_cell=%u, is_blind_search=%s, active=%u\n", + neighbor_cell->Nid_cell, + is_blind_search ? "true" : "false", + neighbor_cell->active); + + if (is_blind_search) { + if (!search_neighboring_cell(frame_parms, + neighbor_cell, + neighboring_cell_info, + rxdata, + rxdata_size, + rxdataF_sz, + rxdataF, + pssTime)) { + continue; + } + } else { + if (!validate_known_pci(frame_parms, neighbor_cell, neighboring_cell_info, rxdata, rxdataF_sz, rxdataF, pssTime)) { + if (neighboring_cell_info->consec_fail >= NEIGHBOR_CELL_MAX_CONSECUTIVE_FAILURES) { + LOG_D(NR_PHY, "Max consecutive failures reached for PCI=%d, resetting to full search\n", neighbor_cell->Nid_cell); + neighboring_cell_info->pss_search_start = 0; + neighboring_cell_info->pss_search_length = frame_parms->samples_per_frame + (2 * frame_parms->ofdm_symbol_size); + neighboring_cell_info->valid_meas = false; + neighboring_cell_info->consec_fail = 0; + send_ssb_rsrp_meas(ue, proc, neighbor_cell->Nid_cell, INT_MAX, true, -1, 0.0); + } + continue; + } + } + + // RSRP measurements + neighboring_cell_info->ssb_rsrp = nr_ue_calculate_ssb_rsrp(frame_parms, proc, rxdataF, 0, frame_parms->ssb_start_subcarrier); + + neighboring_cell_info->ssb_rsrp_dBm = 10 * log10(neighboring_cell_info->ssb_rsrp) + 30 - SQ15_SQUARED_NORM_FACTOR_DB + - ((int)ue->openair0_cfg[0].rx_gain[0] - (int)ue->openair0_cfg[0].rx_gain_offset[0]) + - dB_fixed(ue->frame_parms.ofdm_symbol_size); + + // Send SS measurements to MAC + send_ssb_rsrp_meas(ue, proc, neighbor_cell->Nid_cell, neighboring_cell_info->ssb_rsrp_dBm, true, -1, 0.0); + } +} + +void nr_ue_meas_neighboring_cell(void *arg) +{ + nr_meas_task_args_t *args = (nr_meas_task_args_t *)arg; + do_neighboring_cell_measurements(&args->proc, args->ue, args->rxdata, args->rxdata_size); + + args->ue->measurements.meas_request_pending = false; + free(args); } // This function computes the received noise power diff --git a/openair1/PHY/NR_UE_TRANSPORT/nr_initial_sync.c b/openair1/PHY/NR_UE_TRANSPORT/nr_initial_sync.c index da70fd50e8..e747b86528 100644 --- a/openair1/PHY/NR_UE_TRANSPORT/nr_initial_sync.c +++ b/openair1/PHY/NR_UE_TRANSPORT/nr_initial_sync.c @@ -171,6 +171,114 @@ static void compensate_freq_offset(c16_t **x, const NR_DL_FRAME_PARMS *fp, const } } +/* + * Common SSB search function used by both initial sync and neighbor cell search + */ +bool nr_search_ssb_common(nr_ssb_search_params_t *params) +{ + const NR_DL_FRAME_PARMS *fp = params->frame_parms; + + const uint32_t pssTime_sz = fp->ofdm_symbol_size; + c16_t(*pssTime)[pssTime_sz] = (c16_t(*)[pssTime_sz])params->pssTime; + + // Perform PSS search + int nid2 = -1; + int freq_offset_pss = 0; + int pss_peak = 0; + int pss_avg = 0; + const int sync_pos = pss_synchro_nr((const c16_t **)params->rxdata, + fp, + pssTime, + params->search_frame_id, + params->fo_flag, + params->target_nid_cell, + &nid2, + &freq_offset_pss, + &pss_peak, + &pss_avg); + + if (params->pss_peak) + *params->pss_peak = pss_peak; + if (params->pss_avg) + *params->pss_avg = pss_avg; + if (params->freq_offset_pss) + *params->freq_offset_pss = freq_offset_pss; + + if (sync_pos < fp->nb_prefix_samples || nid2 < 0) { + return false; + } + + const int ssb_offset = sync_pos - fp->nb_prefix_samples; + if (params->ssb_offset) + *params->ssb_offset = ssb_offset; + +#ifdef DEBUG_INITIAL_SYNCH + LOG_I(PHY, "Initial sync : Estimated PSS position %d, Nid2 %d, ssb offset %d\n", sync_pos, nid2, ssb_offset); +#endif + + // Check that SSB fits within buffer + if (ssb_offset + NR_N_SYMBOLS_SSB * (fp->ofdm_symbol_size + fp->nb_prefix_samples) >= params->rxdata_size) { + LOG_D(PHY, + "SSB extends beyond buffer boundary (sync_pos %d, ssb_offset %d, buffer_size %d)\n", + sync_pos, + ssb_offset, + params->rxdata_size); + return false; + } + + // Apply frequency offset compensation if requested + if (params->apply_freq_offset && freq_offset_pss != 0) { + compensate_freq_offset(params->rxdata, fp, freq_offset_pss, params->search_frame_id); + } + + // Extract SSB symbols to frequency domain + // Symbol ordering: 0=PSS, 1=PBCH, 2=SSS, 3=PBCH + const uint32_t rxdataF_sz = fp->samples_per_slot_wCP; + c16_t(*rxdataF)[rxdataF_sz] = (c16_t(*)[rxdataF_sz])params->rxdataF; + const int sample_offset = params->search_frame_id * fp->samples_per_frame + ssb_offset; + for (int i = 0; i < NR_N_SYMBOLS_SSB; i++) { + nr_slot_fep(NULL, fp, 0, i, rxdataF, link_type_dl, sample_offset, (c16_t **)params->rxdata); + } + + // Perform SSS detection + int detected_nid_cell = -1; + int32_t sss_metric = 0; + uint8_t sss_phase = 0; + int freq_offset_sss = 0; + + bool sss_detected = rx_sss_nr(fp, + nid2, + params->target_nid_cell, + freq_offset_pss, + params->ssb_start_subcarrier, + &detected_nid_cell, + &sss_metric, + &sss_phase, + &freq_offset_sss, + rxdataF); + + if (params->sss_metric) + *params->sss_metric = sss_metric; + if (params->sss_phase) + *params->sss_phase = sss_phase; + if (params->freq_offset_sss) + *params->freq_offset_sss = freq_offset_sss; + + if (!sss_detected || detected_nid_cell < 0) { + return false; + } + + // Check if we should exclude the serving cell + if (params->exclude_nid_cell >= 0 && detected_nid_cell == params->exclude_nid_cell) { + return false; + } + + if (params->detected_nid_cell) + *params->detected_nid_cell = detected_nid_cell; + + return true; +} + void nr_scan_ssb(void *arg) { /* Initial synchronisation @@ -196,79 +304,61 @@ void nr_scan_ssb(void *arg) for (int nid2 = 0; nid2 < pss_sequence; nid2++) generate_pss_nr_time(fp, nid2, ssbInfo->gscnInfo.ssbFirstSC, pssTime[nid2]); + const uint32_t rxdataF_sz = fp->samples_per_slot_wCP; + __attribute__((aligned(32))) c16_t rxdataF[fp->nb_antennas_rx][rxdataF_sz]; + // initial sync performed on two successive frames, if pbch passes on first frame, no need to process second frame // only one frame is used for simulation tools for (int frame_id = 0; frame_id < ssbInfo->nFrames && !ssbInfo->syncRes.cell_detected; frame_id++) { if (ssbInfo->freqOffset) compensate_freq_offset(rxdata, fp, ssbInfo->freqOffset, frame_id); - /* process pss search on received buffer */ - ssbInfo->syncRes.frame_id = frame_id; - int nid2; + int detected_nid_cell = -1; + int ssb_offset = 0; int freq_offset_pss = 0; - const int sync_pos = pss_synchro_nr((const c16_t **)rxdata, - fp, - pssTime, - frame_id, - ssbInfo->foFlag, - ssbInfo->targetNidCell, - &nid2, - &freq_offset_pss, - &ssbInfo->pssCorrPeakPower, - &ssbInfo->pssCorrAvgPower); - if (sync_pos < fp->nb_prefix_samples) - continue; - - ssbInfo->ssbOffset = sync_pos - fp->nb_prefix_samples; - -#ifdef DEBUG_INITIAL_SYNCH - LOG_I(PHY, "Initial sync : Estimated PSS position %d, Nid2 %d, ssb offset %d\n", sync_pos, nid2, ssbInfo->ssbOffset); -#endif - /* check that SSS/PBCH block is continuous inside the received buffer */ - if (ssbInfo->ssbOffset + NR_N_SYMBOLS_SSB * (fp->ofdm_symbol_size + fp->nb_prefix_samples) >= fp->samples_per_frame) { - LOG_D(PHY, "Can't try to decode SSS from PSS position, will retry (PSS circular buffer wrapping): sync_pos %d\n", sync_pos); - continue; - } - - // digital compensation of FFO for SSB symbols - if (ssbInfo->foFlag) { - compensate_freq_offset(rxdata, fp, freq_offset_pss, frame_id); - } - - /* slot_fep function works for lte and takes into account begining of frame with prefix for subframe 0 */ - /* for NR this is not the case but slot_fep is still used for computing FFT of samples */ - /* in order to achieve correct processing for NR prefix samples is forced to 0 and then restored after function call */ - /* symbol number are from beginning of SS/PBCH blocks as below: */ - /* Signal PSS PBCH SSS PBCH */ - /* symbol number 0 1 2 3 */ - /* time samples in buffer rxdata are used as input of FFT -> FFT results are stored in the frequency buffer rxdataF */ - /* rxdataF stores SS/PBCH from beginning of buffers in the same symbol order as in time domain */ - - const uint32_t rxdataF_sz = fp->samples_per_slot_wCP; - __attribute__((aligned(32))) c16_t rxdataF[fp->nb_antennas_rx][rxdataF_sz]; - for (int i = 0; i < NR_N_SYMBOLS_SSB; i++) - nr_slot_fep(NULL, fp, 0, i, rxdataF, link_type_dl, frame_id * fp->samples_per_frame + ssbInfo->ssbOffset, (c16_t **)rxdata); - int freq_offset_sss = 0; - int32_t metric_tdd_ncp = 0; - uint8_t phase_tdd_ncp; - ssbInfo->syncRes.cell_detected = rx_sss_nr(fp, - nid2, - ssbInfo->targetNidCell, - freq_offset_pss, - ssbInfo->gscnInfo.ssbFirstSC, - &ssbInfo->nidCell, - &metric_tdd_ncp, - &phase_tdd_ncp, - &freq_offset_sss, - rxdataF); + int32_t sss_metric = 0; + uint8_t sss_phase = 0; + + nr_ssb_search_params_t search_params = { + .frame_parms = fp, + .rxdata = rxdata, + .rxdata_size = fp->samples_per_frame, + .ssb_start_subcarrier = ssbInfo->gscnInfo.ssbFirstSC, + .target_nid_cell = ssbInfo->targetNidCell, + .exclude_nid_cell = -1, // No exclusion for initial sync + .apply_freq_offset = ssbInfo->foFlag, + .search_frame_id = frame_id, + .fo_flag = ssbInfo->foFlag, + .rxdataF = rxdataF, + .pssTime = pssTime, + .detected_nid_cell = &detected_nid_cell, + .ssb_offset = &ssb_offset, + .sss_metric = &sss_metric, + .freq_offset_pss = &freq_offset_pss, + .freq_offset_sss = &freq_offset_sss, + .sss_phase = &sss_phase, + .pss_peak = &ssbInfo->pssCorrPeakPower, + .pss_avg = &ssbInfo->pssCorrAvgPower, + }; + + ssbInfo->syncRes.frame_id = frame_id; + ssbInfo->syncRes.cell_detected = nr_search_ssb_common(&search_params); + + if (!ssbInfo->syncRes.cell_detected) { + continue; + } + + ssbInfo->ssbOffset = ssb_offset; + ssbInfo->nidCell = detected_nid_cell; + #ifdef DEBUG_INITIAL_SYNCH LOG_I(PHY, "TDD Normal prefix: sss detection result; %d, CellId %d metric %d, phase %d, measured offset %d\n", ssbInfo->syncRes.cell_detected, ssbInfo->nidCell, - metric_tdd_ncp, - phase_tdd_ncp, + sss_metric, + sss_phase, ssbInfo->syncRes.rx_offset); #endif ssbInfo->freqOffset += freq_offset_pss + freq_offset_sss; diff --git a/openair1/PHY/NR_UE_TRANSPORT/nr_transport_proto_ue.h b/openair1/PHY/NR_UE_TRANSPORT/nr_transport_proto_ue.h index 68904f1fa2..e47ff37969 100644 --- a/openair1/PHY/NR_UE_TRANSPORT/nr_transport_proto_ue.h +++ b/openair1/PHY/NR_UE_TRANSPORT/nr_transport_proto_ue.h @@ -246,6 +246,13 @@ nr_initial_sync_t nr_initial_sync(UE_nr_rxtx_proc_t *proc, nr_gscn_info_t gscnInfo[MAX_GSCN_BAND], int numGscn); +/*! + \brief Common SSB search function shared by initial sync and neighbor cell search + @param params Pointer to SSB search parameters structure + @return true if SSB was successfully detected, false otherwise +*/ +bool nr_search_ssb_common(nr_ssb_search_params_t *params); + /*! \brief This function gets the carrier frequencies either from FP or command-line-set global variables, depending on the availability of the latter diff --git a/openair1/PHY/NR_UE_TRANSPORT/pss_nr.c b/openair1/PHY/NR_UE_TRANSPORT/pss_nr.c index 180ca99918..08ed65945c 100644 --- a/openair1/PHY/NR_UE_TRANSPORT/pss_nr.c +++ b/openair1/PHY/NR_UE_TRANSPORT/pss_nr.c @@ -50,16 +50,18 @@ //#define DBG_PSS_NR static time_stats_t generic_time[TIME_LAST]; -static int pss_search_time_nr(const c16_t **rxdata, - const NR_DL_FRAME_PARMS *frame_parms, - const c16_t pssTime[NUMBER_PSS_SEQUENCE][frame_parms->ofdm_symbol_size], - bool fo_flag, - int is, - int target_Nid_cell, - int *nid2, - int *f_off, - int *pssPeak, - int *pssAvg); +int pss_search_time_nr(const c16_t **rxdata, + const NR_DL_FRAME_PARMS *frame_parms, + const c16_t pssTime[NUMBER_PSS_SEQUENCE][frame_parms->ofdm_symbol_size], + bool fo_flag, + int is, + int target_Nid_cell, + int *nid2, + int *f_off, + int *pssPeak, + int *pssAvg, + int search_start, + int search_length); static int16_t primary_synchro_nr2[NUMBER_PSS_SEQUENCE][LENGTH_PSS_NR] = {0}; static c16_t primary_synchro[NUMBER_PSS_SEQUENCE][LENGTH_PSS_NR] = {0}; @@ -407,7 +409,7 @@ int pss_synchro_nr(const c16_t **rxdata, VCD_SIGNAL_DUMPER_DUMP_FUNCTION_BY_NAME(VCD_SIGNAL_DUMPER_FUNCTIONS_PSS_SEARCH_TIME_NR, VCD_FUNCTION_IN); int synchro_position = - pss_search_time_nr(rxdata, frame_parms, pssTime, fo_flag, is, target_Nid_cell, nid2, f_off, pssPeak, pssAvg); + pss_search_time_nr(rxdata, frame_parms, pssTime, fo_flag, is, target_Nid_cell, nid2, f_off, pssPeak, pssAvg, -1, -1); VCD_SIGNAL_DUMPER_DUMP_FUNCTION_BY_NAME(VCD_SIGNAL_DUMPER_FUNCTIONS_PSS_SEARCH_TIME_NR, VCD_FUNCTION_OUT); @@ -496,30 +498,38 @@ int pss_synchro_nr(const c16_t **rxdata, * *********************************************************************/ -static int pss_search_time_nr(const c16_t **rxdata, - const NR_DL_FRAME_PARMS *frame_parms, - const c16_t pssTime[NUMBER_PSS_SEQUENCE][frame_parms->ofdm_symbol_size], - bool fo_flag, - int is, - int target_Nid_cell, - int *nid2, - int *f_off, - int *pssPeak, - int *pssAvg) +int pss_search_time_nr(const c16_t **rxdata, + const NR_DL_FRAME_PARMS *frame_parms, + const c16_t pssTime[NUMBER_PSS_SEQUENCE][frame_parms->ofdm_symbol_size], + bool fo_flag, + int is, + int target_Nid_cell, + int *nid2, + int *f_off, + int *pssPeak, + int *pssAvg, + int search_start, + int search_length) { unsigned int n, ar, peak_position, pss_source; int64_t peak_value; int64_t avg[NUMBER_PSS_SEQUENCE] = {0}; double ffo_est = 0; - // performing the correlation on a frame length plus one symbol for the first of the two frame - // to take into account the possibility of PSS in between the two frames - unsigned int length; - if (is==0) - length = frame_parms->samples_per_frame + (2*frame_parms->ofdm_symbol_size); - else - length = frame_parms->samples_per_frame; - + // Determine search window + unsigned int start, length; + if (search_start >= 0 && search_length > 0) { + // Use provided search window for known neighboring cells + start = search_start; + length = search_length; + } else { + // For initial cell search + start = 0; + if (is == 0) + length = frame_parms->samples_per_frame + (2 * frame_parms->ofdm_symbol_size); + else + length = frame_parms->samples_per_frame; + } AssertFatal(length>0,"illegal length %d\n",length); peak_value = 0; @@ -547,7 +557,7 @@ static int pss_search_time_nr(const c16_t **rxdata, } for (int pss_index = pss_index_start; pss_index < pss_index_end; pss_index++) { - for (n = 0; n < length; n += 4) { // + for (n = start; n < start + length; n += 4) { // int64_t pss_corr_ue=0; /* calculate dot product of primary_synchro_time_nr and rxdata[ar][n] @@ -627,7 +637,7 @@ static int pss_search_time_nr(const c16_t **rxdata, dB_fixed64(avg[pss_source]), ffo_est); - if (peak_value < 5*avg[pss_source]) + if ((search_start < 0 || search_length == 0) && peak_value < 5 * avg[pss_source]) return(-1); diff --git a/openair1/PHY/defs_nr_UE.h b/openair1/PHY/defs_nr_UE.h index a166dc13cd..e96927fe21 100644 --- a/openair1/PHY/defs_nr_UE.h +++ b/openair1/PHY/defs_nr_UE.h @@ -125,6 +125,17 @@ typedef enum { #define debug_msg if (((mac_xface->frame%100) == 0) || (mac_xface->frame < 50)) msg +#define NEIGHBOR_CELL_MAX_CONSECUTIVE_FAILURES 10 + +typedef struct { + int pss_search_start; + int pss_search_length; + uint32_t ssb_rsrp; + int ssb_rsrp_dBm; + int consec_fail; // Counter for consecutive detection failures + bool valid_meas; +} neighboring_cell_info_t; + typedef struct { uint8_t decoded_output[3]; // PBCH paylod not larger than 3B uint8_t xtra_byte; @@ -214,7 +225,9 @@ typedef struct { unsigned char nb_antennas_rx; /// DLSCH error counter // short dlsch_errors; - + /// Info about neighboring cells to perform the measurements + neighboring_cell_info_t neighboring_cell_info[NUMBER_OF_NEIGHBORING_CELLS_MAX]; + bool meas_request_pending; } PHY_NR_MEASUREMENTS; typedef struct { @@ -573,6 +586,30 @@ typedef struct { task_ans_t *ans; } nr_ue_ssb_scan_t; +// Common SSB search parameters - used by both initial sync and neighbor cell search +typedef struct { + const NR_DL_FRAME_PARMS *frame_parms; + c16_t **rxdata; + uint32_t rxdata_size; + int ssb_start_subcarrier; + int target_nid_cell; // -1 for blind search, specific PCI for targeted search + int exclude_nid_cell; // -1 for no exclusion, or serving cell PCI to exclude + bool apply_freq_offset; // whether to compensate frequency offset + int search_frame_id; // Frame index to search (0, 1, 2...) within rxdata buffer + bool fo_flag; // frequency offset estimation flag for pss_synchro_nr() + void *rxdataF; // Pre-allocated rxdataF buffer + void *pssTime; // Pre-generated PSS time sequences + // Output parameters + int *detected_nid_cell; // detected PCI + int *ssb_offset; // SSB offset in samples + int32_t *sss_metric; // SSS detection metric + int *freq_offset_pss; // PSS frequency offset estimate + int *freq_offset_sss; // SSS frequency offset estimate + uint8_t *sss_phase; // SSS phase + int *pss_peak; // PSS correlation peak power + int *pss_avg; // PSS correlation average power +} nr_ssb_search_params_t; + typedef struct nr_phy_data_tx_s { NR_UE_ULSCH_t ulsch; NR_UE_PUCCH pucch_vars; diff --git a/openair1/SCHED_NR_UE/phy_procedures_nr_ue.c b/openair1/SCHED_NR_UE/phy_procedures_nr_ue.c index c2aa0b4bd7..c21dafc1b3 100644 --- a/openair1/SCHED_NR_UE/phy_procedures_nr_ue.c +++ b/openair1/SCHED_NR_UE/phy_procedures_nr_ue.c @@ -973,6 +973,32 @@ static void nr_ue_dlsch_procedures(PHY_VARS_NR_UE *ue, } +static bool check_meas_to_perform(PHY_VARS_NR_UE *ue, int nr_slot_rx) +{ + if (nr_slot_rx != 0) { + return false; + } + + if (ue->measurements.meas_request_pending == true) { + return false; + } + + // Check if any intra-frequency neighbor cells are configured + uint32_t serving_ssb_freq = get_ssb_arfcn(&ue->frame_parms); + bool has_intra_freq_neighbors = false; + + for (int cell_idx = 0; cell_idx < NUMBER_OF_NEIGHBORING_CELLS_MAX; cell_idx++) { + fapi_nr_neighboring_cell_t *nr_neighboring_cell = &ue->nrUE_config.meas_config.nr_neighboring_cell[cell_idx]; + if (nr_neighboring_cell->active == 1) { + if (nr_neighboring_cell->ssb_freq == 0 || nr_neighboring_cell->ssb_freq == serving_ssb_freq) { + has_intra_freq_neighbors = true; + } + } + } + + return has_intra_freq_neighbors; +} + static bool is_ssb_index_transmitted(const PHY_VARS_NR_UE *ue, const int index) { if (ue->received_config_request) { @@ -1188,6 +1214,28 @@ int pbch_processing(PHY_VARS_NR_UE *ue, const UE_nr_rxtx_proc_t *proc, nr_phy_da } // for i } // for rsc_id } // for gNB_id + + PHY_NR_MEASUREMENTS *measurements = &ue->measurements; + if (check_meas_to_perform(ue, nr_slot_rx)) { + measurements->meas_request_pending = true; + + // Copy rxdata + uint32_t rxdata_size = (2 * (fp->samples_per_frame) + fp->ofdm_symbol_size); + size_t total_size = sizeof(nr_meas_task_args_t) + fp->nb_antennas_rx * rxdata_size * sizeof(c16_t); + nr_meas_task_args_t *args = malloc(total_size); + args->proc = *proc; + args->ue = ue; + args->rxdata_size = rxdata_size; + + for (int i = 0; i < fp->nb_antennas_rx; i++) { + args->rxdata[i] = &args->rxdata_ant[i * rxdata_size]; + memcpy(args->rxdata[i], ue->common_vars.rxdata[i], rxdata_size * sizeof(c16_t)); + } + + task_t t = {.func = nr_ue_meas_neighboring_cell, .args = args}; + pushTpool(&get_nrUE_params()->Tpool, t); + } + TracyCZoneEnd(ctx); return sampleShift; } diff --git a/openair2/LAYER2/NR_MAC_UE/config_ue.c b/openair2/LAYER2/NR_MAC_UE/config_ue.c index 3185d040ed..e5a1fb4e1a 100644 --- a/openair2/LAYER2/NR_MAC_UE/config_ue.c +++ b/openair2/LAYER2/NR_MAC_UE/config_ue.c @@ -2888,3 +2888,17 @@ void nr_rrc_mac_config_req_cg(module_id_t module_id, ret = pthread_mutex_unlock(&mac->if_mutex); AssertFatal(!ret, "mutex failed %d\n", ret); } + +void nr_rrc_mac_config_req_meas(module_id_t module_id, const nr_neighbor_cell_info_t *neighbor_cells, int num_neighbors) +{ + NR_UE_MAC_INST_t *mac = get_mac_inst(module_id); + + for (int i = 0; i < num_neighbors && i < NUMBER_OF_NEIGHBORING_CELLS_MAX; i++) { + fapi_nr_neighboring_cell_t *phy_cell = &mac->phy_config.config_req.meas_config.nr_neighboring_cell[i]; + phy_cell->Nid_cell = neighbor_cells[i].Nid_cell; + phy_cell->ssb_freq = neighbor_cells[i].ssb_freq; + phy_cell->active = neighbor_cells[i].active; + } + + mac->if_module->phy_config_request(&mac->phy_config); +} diff --git a/openair2/LAYER2/NR_MAC_UE/mac_proto.h b/openair2/LAYER2/NR_MAC_UE/mac_proto.h index 5ee8a62625..69ba897a21 100644 --- a/openair2/LAYER2/NR_MAC_UE/mac_proto.h +++ b/openair2/LAYER2/NR_MAC_UE/mac_proto.h @@ -97,6 +97,14 @@ void nr_rrc_mac_config_other_sib(module_id_t module_id, NR_SIB19_r17_t *sib19_r1 void nr_rrc_mac_resume_rb(module_id_t module_id, bool is_srb, int rb_id); void nr_rrc_mac_config_req_reset(module_id_t module_id, NR_UE_MAC_reset_cause_t cause); +typedef struct nr_neighbor_cell_info { + int16_t Nid_cell; // Physical Cell ID + uint32_t ssb_freq; // SSB frequency (ARFCN) + uint8_t active; // 1 = active, 0 = inactive +} nr_neighbor_cell_info_t; + +void nr_rrc_mac_config_req_meas(module_id_t module_id, const nr_neighbor_cell_info_t *neighbor_cells, int num_neighbors); + /**\brief initialization NR UE MAC instance(s)*/ NR_UE_MAC_INST_t * nr_l2_init_ue(int nb_inst); diff --git a/openair2/RRC/NR/MESSAGES/asn1_msg.c b/openair2/RRC/NR/MESSAGES/asn1_msg.c index 2c08f23f6b..e179b5f4e8 100644 --- a/openair2/RRC/NR/MESSAGES/asn1_msg.c +++ b/openair2/RRC/NR/MESSAGES/asn1_msg.c @@ -855,12 +855,15 @@ int do_RRCSetupComplete(uint8_t *buffer, return((enc_rval.encoded+7)/8); } -// TODO: This function is only implemented for event A2 +// TODO: This function is only implemented for event A2/A3 int do_nrMeasurementReport_SA(long trigger_to_measid, long trigger_quantity, long rs_type, uint16_t Nid_cell, int rsrp_index, + bool neighbor_cell_valid, + uint16_t neighbor_Nid_cell, + int neighbor_rsrp_index, uint8_t *buffer, size_t buffer_size) { @@ -877,6 +880,7 @@ int do_nrMeasurementReport_SA(long trigger_to_measid, asn1cCalloc(measurementReport->criticalExtensions.choice.measurementReport, mrIE); mrIE->measResults.measId = trigger_to_measid; + // Serving cell NR_MeasResultServMO_t *measResultServMo = calloc_or_fail(1, sizeof(*measResultServMo)); NR_MeasResultNR_t *measResultServingCell = &measResultServMo->measResultServingCell; @@ -895,6 +899,30 @@ int do_nrMeasurementReport_SA(long trigger_to_measid, } ASN_SEQUENCE_ADD(&mrIE->measResults.measResultServingMOList.list, measResultServMo); + + // Neighbor cell + if (neighbor_cell_valid) { + struct NR_MeasResults__measResultNeighCells *measResultNeighCells = calloc_or_fail(1, sizeof(*measResultNeighCells)); + mrIE->measResults.measResultNeighCells = measResultNeighCells; + measResultNeighCells->present = NR_MeasResults__measResultNeighCells_PR_measResultListNR; + NR_MeasResultListNR_t *measResultListNR = calloc_or_fail(1, sizeof(*measResultListNR)); + measResultNeighCells->choice.measResultListNR = measResultListNR; + struct NR_MeasResultNR *meas_result_neigh_cell = calloc_or_fail(1, sizeof(*meas_result_neigh_cell)); + asn1cCalloc(meas_result_neigh_cell->physCellId, neighbor_pci); + *neighbor_pci = neighbor_Nid_cell; + struct NR_MeasResultNR__measResult__cellResults *cellResults = &meas_result_neigh_cell->measResult.cellResults; + struct NR_MeasQuantityResults *neigh_mq_res = calloc_or_fail(1, sizeof(*neigh_mq_res)); + if (trigger_quantity == NR_MeasTriggerQuantityOffset_PR_rsrp) { + asn1cCalloc(neigh_mq_res->rsrp, rsrp); + *rsrp = neighbor_rsrp_index; + if (rs_type == NR_NR_RS_Type_ssb) + cellResults->resultsSSB_Cell = neigh_mq_res; + else + cellResults->resultsCSI_RS_Cell = neigh_mq_res; + } + ASN_SEQUENCE_ADD(&measResultListNR->list, meas_result_neigh_cell); + } + enc_rval = uper_encode_to_buffer(&asn_DEF_NR_UL_DCCH_Message, NULL, (void *)&ul_dcch_msg, buffer, buffer_size); AssertFatal(enc_rval.encoded > 0, "ASN1 message encoding failed (%s, %lu)!\n", enc_rval.failed_type->name, enc_rval.encoded); diff --git a/openair2/RRC/NR/MESSAGES/asn1_msg.h b/openair2/RRC/NR/MESSAGES/asn1_msg.h index f4f4987ceb..2e09397e60 100644 --- a/openair2/RRC/NR/MESSAGES/asn1_msg.h +++ b/openair2/RRC/NR/MESSAGES/asn1_msg.h @@ -131,6 +131,9 @@ int do_nrMeasurementReport_SA(long trigger_to_measid, long rs_type, uint16_t Nid_cell, int rsrp_index, + bool neighbor_cell_valid, + uint16_t neighbor_Nid_cell, + int neighbor_rsrp_index, uint8_t *buffer, size_t buffer_size); diff --git a/openair2/RRC/NR_UE/rrc_UE.c b/openair2/RRC/NR_UE/rrc_UE.c index 10c38c1ceb..9e77dcfa8f 100644 --- a/openair2/RRC/NR_UE/rrc_UE.c +++ b/openair2/RRC/NR_UE/rrc_UE.c @@ -921,6 +921,14 @@ static void nr_rrc_manage_rlc_bearers(NR_UE_RRC_INST_t *rrc, const NR_CellGroupC } } +static void nr_ue_meas_reset(meas_t *meas_cell, bool csi_meas) +{ + if (csi_meas) + meas_cell->csi_rsrp_dBm.init = false; + else + meas_cell->ss_rsrp_dBm.init = false; +} + static void nr_rrc_process_reconfigurationWithSync(NR_UE_RRC_INST_t *rrc, NR_ReconfigurationWithSync_t *reconfigurationWithSync, int gNB_index) @@ -934,6 +942,24 @@ static void nr_rrc_process_reconfigurationWithSync(NR_UE_RRC_INST_t *rrc, return; } + // Clear neighbor cell lists from measurement objects during handover + rrcPerNB_t *rrcNB = &rrc->perNB[gNB_index]; + for (int i = 0; i < MAX_MEAS_OBJ; i++) { + if (rrcNB->MeasObj[i] && rrcNB->MeasObj[i]->measObject.present == NR_MeasObjectToAddMod__measObject_PR_measObjectNR) { + NR_MeasObjectNR_t *measObjNR = rrcNB->MeasObj[i]->measObject.choice.measObjectNR; + if (measObjNR->cellsToAddModList) { + ASN_STRUCT_FREE(asn_DEF_NR_CellsToAddModList, measObjNR->cellsToAddModList); + measObjNR->cellsToAddModList = NULL; + } + } + } + + l3_measurements_t *l3m = &rrcNB->l3_measurements; + for (int i = 0; i < NUMBER_OF_NEIGHBORING_CELLS_MAX; i++) { + nr_ue_meas_reset(&l3m->neighboring_cell[i], false); + nr_ue_meas_reset(&l3m->neighboring_cell[i], true); + } + if (reconfigurationWithSync->spCellConfigCommon) { /* if the frequencyInfoDL is included, consider the target SpCell to be one on the SSB frequency indicated by the frequencyInfoDL */ @@ -1355,7 +1381,11 @@ static void handle_measid_remove(rrcPerNB_t *rrc, struct NR_MeasIdToRemoveList * } } -static void handle_measid_addmod(rrcPerNB_t *rrc, struct NR_MeasIdToAddModList *addmod_list, NR_UE_Timers_Constants_t *timers) +static void handle_measid_addmod(rrcPerNB_t *rrc, + struct NR_MeasIdToAddModList *addmod_list, + NR_UE_Timers_Constants_t *timers, + nr_neighbor_cell_info_t *neighbor_cells, + int *num_neighbors) { for (int i = 0; i < addmod_list->list.count; i++) { NR_MeasId_t id = addmod_list->list.array[i]->measId; @@ -1389,11 +1419,56 @@ static void handle_measid_addmod(rrcPerNB_t *rrc, struct NR_MeasIdToAddModList * nr_timer_start(&timers->T321); } } + // Check for A3 event and extract neighbor cell info for MAC/PHY + else if (reportNR->reportType.present == NR_ReportConfigNR__reportType_PR_eventTriggered) { + NR_EventTriggerConfig_t *eventTriggerConfig = reportNR->reportType.choice.eventTriggered; + if (eventTriggerConfig->eventId.present == NR_EventTriggerConfig__eventId_PR_eventA3) { + if (rrc->MeasObj[measObjectId] + && rrc->MeasObj[measObjectId]->measObject.present == NR_MeasObjectToAddMod__measObject_PR_measObjectNR) { + NR_MeasObjectNR_t *obj_nr = rrc->MeasObj[measObjectId]->measObject.choice.measObjectNR; + + uint32_t ssb_freq = 0; + if (obj_nr->ssbFrequency) { + ssb_freq = *obj_nr->ssbFrequency; + } + + if (obj_nr->cellsToAddModList) { + NR_CellsToAddModList_t *cellsToAddModList = obj_nr->cellsToAddModList; + for (int j = 0; j < cellsToAddModList->list.count; j++) { + if (*num_neighbors < NUMBER_OF_NEIGHBORING_CELLS_MAX) { + NR_CellsToAddMod_t *cell = cellsToAddModList->list.array[j]; + neighbor_cells[*num_neighbors].Nid_cell = cell->physCellId; + neighbor_cells[*num_neighbors].ssb_freq = ssb_freq; + neighbor_cells[*num_neighbors].active = 1; + (*num_neighbors)++; + } else { + LOG_W(NR_RRC, + "More than %d neighboring cells configured, ignoring excess cells!\n", + NUMBER_OF_NEIGHBORING_CELLS_MAX); + break; + } + } + } else { + // We do not know PCI of neighboring cells, so we do blind search + if (*num_neighbors < NUMBER_OF_NEIGHBORING_CELLS_MAX) { + neighbor_cells[*num_neighbors].Nid_cell = -1; + neighbor_cells[*num_neighbors].ssb_freq = ssb_freq; + neighbor_cells[*num_neighbors].active = 1; + (*num_neighbors)++; + } + } + } + } + } } } } -static void nr_rrc_ue_process_measConfig(rrcPerNB_t *rrc, NR_MeasConfig_t *const measConfig, NR_UE_Timers_Constants_t *timers) +static void nr_rrc_ue_process_measConfig(rrcPerNB_t *rrc, + NR_MeasConfig_t *const measConfig, + NR_UE_Timers_Constants_t *timers, + nr_neighbor_cell_info_t *neighbor_cells, + int *num_neighbors) { if (measConfig->measObjectToRemoveList) handle_measobj_remove(rrc, measConfig->measObjectToRemoveList, timers); @@ -1414,7 +1489,7 @@ static void nr_rrc_ue_process_measConfig(rrcPerNB_t *rrc, NR_MeasConfig_t *const handle_measid_remove(rrc, measConfig->measIdToRemoveList, timers); if (measConfig->measIdToAddModList) - handle_measid_addmod(rrc, measConfig->measIdToAddModList, timers); + handle_measid_addmod(rrc, measConfig->measIdToAddModList, timers, neighbor_cells, num_neighbors); LOG_W(NR_RRC, "Measurement gaps not yet supported!\n"); @@ -1487,7 +1562,12 @@ static void nr_rrc_ue_process_rrcReconfiguration(NR_UE_RRC_INST_t *rrc, int gNB_ } if (ie->measConfig) { LOG_I(NR_RRC, "RRCReconfiguration includes Measurement Configuration\n"); - nr_rrc_ue_process_measConfig(rrcNB, ie->measConfig, &rrc->timers_and_constants); + nr_neighbor_cell_info_t neighbor_cells[NUMBER_OF_NEIGHBORING_CELLS_MAX]; + int num_neighbors = 0; + nr_rrc_ue_process_measConfig(rrcNB, ie->measConfig, &rrc->timers_and_constants, neighbor_cells, &num_neighbors); + if (num_neighbors > 0) { + nr_rrc_mac_config_req_meas(rrc->ue_id, neighbor_cells, num_neighbors); + } } if (ie->lateNonCriticalExtension) { LOG_E(NR_RRC, "RRCReconfiguration includes lateNonCriticalExtension. Not handled.\n"); @@ -2625,13 +2705,10 @@ static void apply_ema(val_init_t *vi_rsrp_dBm, float filter_coeff_rsrp, int rsrp *meas_init = true; } -void nr_ue_meas_filtering(rrcPerNB_t *rrc, bool is_neighboring_cell, uint16_t Nid_cell, bool csi_meas, int rsrp_dBm) +void nr_ue_meas_filtering(rrcPerNB_t *rrc, meas_t *meas_cell, uint16_t Nid_cell, bool csi_meas, int rsrp_dBm) { - if (is_neighboring_cell) - return; - l3_measurements_t *l3_measurements = &rrc->l3_measurements; - meas_t *meas_cell = &l3_measurements->serving_cell; + if (meas_cell->Nid_cell != Nid_cell) { meas_cell->ss_rsrp_dBm.init = false; meas_cell->csi_rsrp_dBm.init = false; @@ -2676,6 +2753,179 @@ static long get_measurement_report_interval_ms(NR_ReportInterval_t interval) } } +static int get_rsrp_value(const meas_t *cell) +{ + if (cell->ss_rsrp_dBm.init) + return cell->ss_rsrp_dBm.val; + if (cell->csi_rsrp_dBm.init) + return cell->csi_rsrp_dBm.val; + return INT_MAX; +} + +static int get_meas_id(rrcPerNB_t *rrcNB, int report_config_id) +{ + for (int j = 0; j < MAX_MEAS_ID; j++) { + NR_MeasIdToAddMod_t *meas_id_toAddMod = rrcNB->MeasId[j]; + if (meas_id_toAddMod && meas_id_toAddMod->reportConfigId == report_config_id) + return meas_id_toAddMod->measId; + } + return -1; +} + +static void setup_meas_trigger(l3_measurements_t *l3_measurements, + NR_EventTriggerConfig_t *event_config, + int meas_id, + long trigger_quantity, + bool neighbor_cell_valid) +{ + l3_measurements->trigger_to_measid = meas_id; + l3_measurements->trigger_quantity = trigger_quantity; + l3_measurements->rs_type = event_config->rsType; + l3_measurements->reports_sent = 0; + l3_measurements->max_reports = + (event_config->reportAmount == NR_EventTriggerConfig__reportAmount_infinity) ? INT_MAX : (1 << event_config->reportAmount); + l3_measurements->report_interval_ms = get_measurement_report_interval_ms(event_config->reportInterval); + l3_measurements->neighbor_cell_valid = neighbor_cell_valid; +} + +static void start_meas_event(l3_measurements_t *l3_measurements, + rrcPerNB_t *rrcNB, + NR_timer_t *event_timer, + NR_EventTriggerConfig_t *event_config, + int report_config_id, + long trigger_quantity, + bool neighbor_cell_valid, + long time_to_trigger) +{ + nr_timer_setup(event_timer, get_event_time_to_trigger(time_to_trigger), 10); + nr_timer_start(event_timer); + + int meas_id = get_meas_id(rrcNB, report_config_id); + AssertFatal(meas_id > 0, "meas_id did not found for report_config_id %i\n", report_config_id); + + setup_meas_trigger(l3_measurements, event_config, meas_id, trigger_quantity, neighbor_cell_valid); +} + +static void stop_meas_event(l3_measurements_t *l3_measurements, NR_timer_t *event_timer) +{ + nr_timer_stop(event_timer); + nr_timer_stop(&l3_measurements->periodic_report_timer); + l3_measurements->reports_sent = 0; +} + +// TS 38.331 - 5.5.4.3 Event A2 (Serving becomes worse than threshold) +static void handle_event_a2(l3_measurements_t *l3_measurements, + rrcPerNB_t *rrcNB, + struct NR_EventTriggerConfig__eventId__eventA2 *event_A2, + NR_EventTriggerConfig_t *event_trigger_config, + long report_config_id) +{ + if (event_A2->a2_Threshold.present != NR_MeasTriggerQuantity_PR_rsrp) + return; + + meas_t *serving_cell = &l3_measurements->serving_cell; + int serving_cell_rsrp = get_rsrp_value(serving_cell); + if (serving_cell_rsrp == INT_MAX) { + LOG_E(NR_RRC, "There are no RSRP measurements taken for the active cell\n"); + } + + // TS 38.133 - Table 10.1.6.1-1: SS-RSRP and CSI-RSRP measurement report mapping + int rsrp_threshold = event_A2->a2_Threshold.choice.rsrp - 157; + int rsrp_hysteresis = event_A2->hysteresis >> 1; + + if (serving_cell_rsrp + rsrp_hysteresis < rsrp_threshold) { + if (!nr_timer_is_active(&l3_measurements->TA2) && (l3_measurements->reports_sent == 0)) { + start_meas_event(l3_measurements, + rrcNB, + &l3_measurements->TA2, + event_trigger_config, + report_config_id, + event_A2->a2_Threshold.present, + false, + event_A2->timeToTrigger); + + LOG_W(NR_RRC, + "(active_cell_rsrp) %i + (rsrp_hysteresis) %i < (rsrp_threshold) %i\n", + serving_cell_rsrp, + rsrp_hysteresis, + rsrp_threshold); + } + } else if (nr_timer_is_active(&l3_measurements->TA2) && (serving_cell_rsrp - rsrp_hysteresis > rsrp_threshold)) { + stop_meas_event(l3_measurements, &l3_measurements->TA2); + } +} + +// TS 38.331 - 5.5.4.4 Event A3 (Neighbour becomes offset better than SpCell) +static void handle_event_a3(l3_measurements_t *l3_measurements, + rrcPerNB_t *rrcNB, + struct NR_EventTriggerConfig__eventId__eventA3 *event_A3, + NR_EventTriggerConfig_t *event_trigger_config, + long report_config_id) +{ + if (event_A3->a3_Offset.present != NR_MeasTriggerQuantityOffset_PR_rsrp) + return; + + meas_t *serving_cell = &l3_measurements->serving_cell; + + int serving_cell_rsrp = get_rsrp_value(serving_cell); + if (serving_cell_rsrp == INT_MAX) { + LOG_D(NR_RRC, "There are no RSRP measurements taken for the serving cell\n"); + return; + } + + int rsrp_offset = event_A3->a3_Offset.choice.rsrp >> 1; + int rsrp_hysteresis = event_A3->hysteresis; + + // Check all neighboring cells for Event A3 condition + bool entry_cond_met = false; + bool above_leaving_threshold = false; + int entry_neighbor_rsrp = INT_MIN; + + for (int i = 0; i < NUMBER_OF_NEIGHBORING_CELLS_MAX; i++) { + meas_t *neighboring_cell = &l3_measurements->neighboring_cell[i]; + int neighboring_cell_rsrp = get_rsrp_value(neighboring_cell); + + if (neighboring_cell_rsrp == INT_MAX) { + LOG_D(NR_RRC, "There are no RSRP measurements taken for the neighboring cell %d\n", i); + neighboring_cell_rsrp = INT_MIN; + } + + // Check entry condition + if (neighboring_cell_rsrp > serving_cell_rsrp + rsrp_offset + rsrp_hysteresis) { + entry_cond_met = true; + entry_neighbor_rsrp = neighboring_cell_rsrp; + } + + // Check if any neighbor is still above leaving threshold + if (neighboring_cell_rsrp >= serving_cell_rsrp + rsrp_offset - rsrp_hysteresis) { + above_leaving_threshold = true; + } + } + + // Trigger event if any neighbor meets entry condition + if (entry_cond_met && !nr_timer_is_active(&l3_measurements->TA3) && (l3_measurements->reports_sent == 0)) { + start_meas_event(l3_measurements, + rrcNB, + &l3_measurements->TA3, + event_trigger_config, + report_config_id, + NR_MeasTriggerQuantityOffset_PR_rsrp, + true, + event_A3->timeToTrigger); + + LOG_W(NR_RRC, + "(neighboring_cell_rsrp) %i > (serving_cell_rsrp %i) + (rsrp_offset) %i + (rsrp_hysteresis) %i\n", + entry_neighbor_rsrp, + serving_cell_rsrp, + rsrp_offset, + rsrp_hysteresis); + } + // Stop event if all neighbors are below leaving threshold + else if (nr_timer_is_active(&l3_measurements->TA3) && !above_leaving_threshold) { + stop_meas_event(l3_measurements, &l3_measurements->TA3); + } +} + static void nr_ue_check_meas_report(NR_UE_RRC_INST_t *rrc, const uint8_t gnb_index) { rrcPerNB_t *rrcNB = rrc->perNB + gnb_index; @@ -2694,64 +2944,24 @@ static void nr_ue_check_meas_report(NR_UE_RRC_INST_t *rrc, const uint8_t gnb_ind continue; NR_EventTriggerConfig_t *event_trigger_config = report_config_nr->reportType.choice.eventTriggered; - if (event_trigger_config->eventId.present != NR_EventTriggerConfig__eventId_PR_eventA2) - continue; - struct NR_EventTriggerConfig__eventId__eventA2 *event_A2 = event_trigger_config->eventId.choice.eventA2; - if (event_A2->a2_Threshold.present != NR_MeasTriggerQuantity_PR_rsrp) - continue; - - meas_t *serving_cell = &l3_measurements->serving_cell; - int serving_cell_rsrp = INT_MAX; - if (serving_cell->ss_rsrp_dBm.init == true) { - serving_cell_rsrp = serving_cell->ss_rsrp_dBm.val; - } else if (serving_cell->csi_rsrp_dBm.init == true) { - serving_cell_rsrp = serving_cell->csi_rsrp_dBm.val; - } else { - LOG_E(NR_RRC, "There are no RSRP measurements taken for the active cell\n"); - } - - // TS 38.133 - Table 10.1.6.1-1: SS-RSRP and CSI-RSRP measurement report mapping - int rsrp_threshold = event_A2->a2_Threshold.choice.rsrp - 157; - int rsrp_hysteresis = event_A2->hysteresis >> 1; - - // TS 38.331 - 5.5.4.3 Event A2 (Serving becomes worse than threshold) - if (serving_cell_rsrp + rsrp_hysteresis < rsrp_threshold) { - if (!nr_timer_is_active(&l3_measurements->TA2) && (l3_measurements->reports_sent == 0)) { - nr_timer_setup(&l3_measurements->TA2, get_A2_event_time_to_trigger(event_A2->timeToTrigger), 10); - nr_timer_start(&l3_measurements->TA2); - int report_config_id = report_config->reportConfigId; - int meas_id = -1; - for (int j = 0; j < MAX_MEAS_ID; j++) { - NR_MeasIdToAddMod_t *meas_id_toAddMod = rrcNB->MeasId[j]; - if (meas_id_toAddMod == NULL) { - continue; - } - if (meas_id_toAddMod->reportConfigId == report_config_id) { - meas_id = meas_id_toAddMod->measId; - } - } - AssertFatal(meas_id > 0, "meas_id did not found for report_config_id %i\n", report_config_id); - - l3_measurements->trigger_to_measid = meas_id; - l3_measurements->trigger_quantity = event_A2->a2_Threshold.present; - l3_measurements->rs_type = event_trigger_config->rsType; - l3_measurements->reports_sent = 0; - l3_measurements->max_reports = (event_trigger_config->reportAmount == NR_EventTriggerConfig__reportAmount_infinity) - ? INT_MAX - : (1 << event_trigger_config->reportAmount); - l3_measurements->report_interval_ms = get_measurement_report_interval_ms(event_trigger_config->reportInterval); - - LOG_W(NR_RRC, - "(active_cell_rsrp) %i + (rsrp_hysteresis) %i < (rsrp_threshold) %i\n", - serving_cell_rsrp, - rsrp_hysteresis, - rsrp_threshold); - } - } else if (nr_timer_is_active(&l3_measurements->TA2) && (serving_cell_rsrp - rsrp_hysteresis > rsrp_threshold)) { - nr_timer_stop(&l3_measurements->TA2); - nr_timer_stop(&l3_measurements->periodic_report_timer); - l3_measurements->reports_sent = 0; + switch (event_trigger_config->eventId.present) { + case NR_EventTriggerConfig__eventId_PR_eventA2: + handle_event_a2(l3_measurements, + rrcNB, + event_trigger_config->eventId.choice.eventA2, + event_trigger_config, + report_config->reportConfigId); + break; + case NR_EventTriggerConfig__eventId_PR_eventA3: + handle_event_a3(l3_measurements, + rrcNB, + event_trigger_config->eventId.choice.eventA3, + event_trigger_config, + report_config->reportConfigId); + break; + default: + break; } } } @@ -2800,6 +3010,54 @@ static void nr_rrc_handle_ra_indication(NR_UE_RRC_INST_t *rrc, bool ra_succeeded } } +static void nr_rrc_handle_meas_indication(NR_UE_RRC_INST_t *rrc, NRRrcMacMeasDataInd *meas_ind) +{ + rrcPerNB_t *rrcNB = rrc->perNB + meas_ind->gnb_index; + l3_measurements_t *l3_measurements = &rrcNB->l3_measurements; + meas_t *meas_cell = NULL; + + if (meas_ind->is_neighboring_cell) { + uint16_t target_nid_cell = meas_ind->Nid_cell; + for (int i = 0; i < NUMBER_OF_NEIGHBORING_CELLS_MAX; i++) { + if (l3_measurements->neighboring_cell[i].Nid_cell == target_nid_cell) { + meas_cell = &l3_measurements->neighboring_cell[i]; + break; + } + } + if (!meas_cell) { + for (int i = 0; i < NUMBER_OF_NEIGHBORING_CELLS_MAX; i++) { + if (!l3_measurements->neighboring_cell[i].ss_rsrp_dBm.init && !l3_measurements->neighboring_cell[i].csi_rsrp_dBm.init) { + meas_cell = &l3_measurements->neighboring_cell[i]; + break; + } + } + } + } else { + meas_cell = &l3_measurements->serving_cell; + } + + if (!meas_cell) { + LOG_E(NR_RRC, "meas_cell not found!\n"); + return; + } + + if (meas_ind->is_neighboring_cell && meas_ind->rsrp_dBm == INT_MAX) { + LOG_W(NR_RRC, "[Nid_cell %i] Neighboring cell not detected. L3 measurements will be reset.\n", meas_ind->Nid_cell); + meas_cell->Nid_cell = meas_ind->Nid_cell; + nr_ue_meas_reset(meas_cell, meas_ind->is_csi_meas); + } else { + LOG_D(NR_RRC, + "[%s][Nid_cell %i] Received %s measurements: RSRP = %i (dBm)\n", + meas_ind->is_neighboring_cell ? "Neighboring cell" : "Active cell", + meas_ind->Nid_cell, + meas_ind->is_csi_meas ? "CSI meas" : "SSB meas", + meas_ind->rsrp_dBm); + + nr_ue_meas_filtering(rrcNB, meas_cell, meas_ind->Nid_cell, meas_ind->is_csi_meas, meas_ind->rsrp_dBm); + nr_ue_check_meas_report(rrc, meas_ind->gnb_index); + } +} + void *rrc_nrue_task(void *args_p) { itti_mark_task_ready(TASK_RRC_NRUE); @@ -2904,21 +3162,9 @@ void *rrc_nrue(void *notUsed) nr_rrc_ue_decode_NR_SBCCH_SL_BCH_Message(rrc, sbcch->gnb_index,sbcch->frame, sbcch->slot, sbcch->sdu, sbcch->sdu_size, sbcch->rx_slss_id); + break; case NR_RRC_MAC_MEAS_DATA_IND: - - LOG_D(NR_RRC, "[%s][Nid_cell %i] Received %s measurements: RSRP = %i (dBm)\n", - NR_RRC_MAC_MEAS_DATA_IND(msg_p).is_neighboring_cell? "Neighboring cell" : "Active cell", - NR_RRC_MAC_MEAS_DATA_IND(msg_p).Nid_cell, - NR_RRC_MAC_MEAS_DATA_IND(msg_p).is_csi_meas ? "CSI meas" : "SSB meas", - NR_RRC_MAC_MEAS_DATA_IND(msg_p).rsrp_dBm); - - rrcPerNB_t *rrcNB = rrc->perNB + NR_RRC_MAC_MEAS_DATA_IND(msg_p).gnb_index; - nr_ue_meas_filtering(rrcNB, - NR_RRC_MAC_MEAS_DATA_IND(msg_p).is_neighboring_cell, - NR_RRC_MAC_MEAS_DATA_IND(msg_p).Nid_cell, - NR_RRC_MAC_MEAS_DATA_IND(msg_p).is_csi_meas, - NR_RRC_MAC_MEAS_DATA_IND(msg_p).rsrp_dBm); - nr_ue_check_meas_report(rrc, NR_RRC_MAC_MEAS_DATA_IND(msg_p).gnb_index); + nr_rrc_handle_meas_indication(rrc, &NR_RRC_MAC_MEAS_DATA_IND(msg_p)); break; case NR_RRC_MAC_CCCH_DATA_IND: { @@ -3320,11 +3566,17 @@ void rrc_ue_generate_measurementReport(rrcPerNB_t *rrc, instance_t ue_id) l3_measurements_t *l3m = &rrc->l3_measurements; int rsrp_dBm = l3m->rs_type == NR_NR_RS_Type_ssb ? l3m->serving_cell.ss_rsrp_dBm.val : l3m->serving_cell.csi_rsrp_dBm.val; int rsrp_index = get_rsrp_index(rsrp_dBm); + int neighbor_rsrp_dBm = + l3m->rs_type == NR_NR_RS_Type_ssb ? l3m->neighboring_cell[0].ss_rsrp_dBm.val : l3m->neighboring_cell[0].csi_rsrp_dBm.val; + int neighbor_rsrp_index = get_rsrp_index(neighbor_rsrp_dBm); uint8_t size = do_nrMeasurementReport_SA(l3m->trigger_to_measid, l3m->trigger_quantity, l3m->rs_type, l3m->serving_cell.Nid_cell, rsrp_index, + l3m->neighbor_cell_valid, + l3m->neighboring_cell[0].Nid_cell, + neighbor_rsrp_index, buffer, sizeof(buffer)); diff --git a/openair2/RRC/NR_UE/rrc_defs.h b/openair2/RRC/NR_UE/rrc_defs.h index 336afcc63a..efe2e6e147 100644 --- a/openair2/RRC/NR_UE/rrc_defs.h +++ b/openair2/RRC/NR_UE/rrc_defs.h @@ -70,6 +70,7 @@ #define MAX_MEAS_CONFIG 64 #define MAX_MEAS_ID 64 #define MAX_QUANTITY_CONFIG 2 +#define NUMBER_OF_NEIGHBORING_CELLS_MAX 1 typedef enum { nr_SecondaryCellGroupConfig_r15=0, @@ -196,13 +197,16 @@ typedef struct l3_measurements_s { float ssb_filter_coeff_rsrp; float csi_RS_filter_coeff_rsrp; meas_t serving_cell; + meas_t neighboring_cell[NUMBER_OF_NEIGHBORING_CELLS_MAX]; long trigger_to_measid; long trigger_quantity; long rs_type; int reports_sent; int max_reports; long report_interval_ms; + bool neighbor_cell_valid; NR_timer_t TA2; + NR_timer_t TA3; NR_timer_t periodic_report_timer; } l3_measurements_t; diff --git a/openair2/RRC/NR_UE/rrc_proto.h b/openair2/RRC/NR_UE/rrc_proto.h index d8f2c0f23f..ab14f704b9 100644 --- a/openair2/RRC/NR_UE/rrc_proto.h +++ b/openair2/RRC/NR_UE/rrc_proto.h @@ -74,7 +74,7 @@ void nr_ue_rrc_timer_trigger(int module_id, int hfn, int frame, int gnb_id); void handle_t300_expiry(NR_UE_RRC_INST_t *rrc); void handle_t430_expiry(NR_UE_RRC_INST_t *rrc); -int get_A2_event_time_to_trigger(long time_to_trigger); +int get_event_time_to_trigger(long time_to_trigger); void reset_rlf_timers_and_constants(NR_UE_Timers_Constants_t *tac); void set_default_timers_and_constants(NR_UE_Timers_Constants_t *tac); void nr_rrc_set_sib1_timers_and_constants(NR_UE_Timers_Constants_t *tac, NR_SIB1_t *sib1); diff --git a/openair2/RRC/NR_UE/rrc_timers_and_constants.c b/openair2/RRC/NR_UE/rrc_timers_and_constants.c index 133ffb8d8e..d7ad5c249e 100644 --- a/openair2/RRC/NR_UE/rrc_timers_and_constants.c +++ b/openair2/RRC/NR_UE/rrc_timers_and_constants.c @@ -129,11 +129,24 @@ void handle_meas_timers(NR_UE_RRC_INST_t *rrc) l3_measurements_t *l3_measurements = &nb->l3_measurements; bool ta2_expired = nr_timer_tick(&l3_measurements->TA2); + bool ta3_expired = nr_timer_tick(&l3_measurements->TA3); if (ta2_expired && l3_measurements->trigger_quantity > 0) { rrc_ue_generate_measurementReport(nb, rrc->ue_id); l3_measurements->reports_sent = 1; - if (l3_measurements->reports_sent < l3_measurements->max_reports) { + if (l3_measurements->reports_sent < l3_measurements->max_reports + && !nr_timer_is_active(&l3_measurements->periodic_report_timer)) { + nr_timer_setup(&l3_measurements->periodic_report_timer, l3_measurements->report_interval_ms, 10); + nr_timer_start(&l3_measurements->periodic_report_timer); + } + } + + if (ta3_expired && l3_measurements->trigger_quantity > 0) { + rrc_ue_generate_measurementReport(nb, rrc->ue_id); + l3_measurements->reports_sent = 1; + + if (l3_measurements->reports_sent < l3_measurements->max_reports + && !nr_timer_is_active(&l3_measurements->periodic_report_timer)) { nr_timer_setup(&l3_measurements->periodic_report_timer, l3_measurements->report_interval_ms, 10); nr_timer_start(&l3_measurements->periodic_report_timer); } @@ -663,7 +676,7 @@ void reset_rlf_timers_and_constants(NR_UE_Timers_Constants_t *tac) tac->N311_cnt = 0; } -int get_A2_event_time_to_trigger(long time_to_trigger) +int get_event_time_to_trigger(long time_to_trigger) { switch (time_to_trigger) { case NR_TimeToTrigger_ms0: