diff --git a/openair2/GNB_APP/gnb_config.c b/openair2/GNB_APP/gnb_config.c index 5980666cff..856078e6f7 100644 --- a/openair2/GNB_APP/gnb_config.c +++ b/openair2/GNB_APP/gnb_config.c @@ -2012,6 +2012,108 @@ static nr_neighbour_cell_t *get_neighbour_by_pci(seq_arr_t *neighbour_cells, int return NULL; } +/** @brief Parse gNB-level inter-frequency list (frequency_list) into a shared array. + * This parses gNBs.[gnb_idx].frequency_list once and validates per-frequency SIB4 + * parameters. The resulting list can then be reused for all serving cells. + * @param[out] inter_freqs Sequence array of nr_inter_freq_cfg_t to populate + * @param[in] gnb_idx gNB index */ +static bool eq_inter_freq_arfcn_scs(const void *vval, const void *vit) +{ + const nr_inter_freq_cfg_t *key = (const nr_inter_freq_cfg_t *)vval; + const nr_inter_freq_cfg_t *cur = (const nr_inter_freq_cfg_t *)vit; + return key->arfcn == cur->arfcn && key->scs == cur->scs; +} + +/** @brief Insert an inter-frequency config if (ARFCN,SCS) is unique in the list. + * Fails fast if a duplicate is found. + * @param[in,out] inter_freqs Existing inter-frequency list + * @param[in] f Candidate entry to insert (by value) + * @param[in] gnb_idx gNB index for logging + */ +static void add_inter_freq(seq_arr_t *inter_freqs, const nr_inter_freq_cfg_t *f, uint8_t gnb_idx) +{ + const nr_inter_freq_cfg_t key = {.arfcn = f->arfcn, .scs = f->scs}; + elm_arr_t dup = find_if(inter_freqs, (void *)&key, eq_inter_freq_arfcn_scs); + if (dup.found) { + AssertFatal(false, "gNB %d: duplicate inter-freq carrier ARFCN %d, SCS %d in frequency_list\n", gnb_idx, f->arfcn, f->scs); + } + + seq_arr_push_back(inter_freqs, (void *)f, sizeof(*f)); +} + +static void parse_inter_freq_list(seq_arr_t *inter_freqs, const uint8_t gnb_idx) +{ + seq_arr_init(inter_freqs, sizeof(nr_inter_freq_cfg_t)); + + char freq_path[MAX_OPTNAME_SIZE + 8]; + snprintf(freq_path, sizeof(freq_path), "%s.[%i]", GNB_CONFIG_STRING_GNB_LIST, gnb_idx); + GET_PARAMS_LIST(freq_list, freq_params, GNBFREQUENCYPARAMS_DESC, GNB_CONFIG_STRING_FREQUENCY_LIST, freq_path); + + for (int i = 0; i < freq_list.numelt; ++i) { + const paramdef_t *freq_item = freq_list.paramarray[i]; + nr_inter_freq_cfg_t f = {0}; + f.arfcn = *gpd(freq_item, sizeofArray(freq_params), GNB_CONFIG_STRING_FREQUENCY_ABS_FREQ_SSB)->i64ptr; + f.scs = *gpd(freq_item, sizeofArray(freq_params), GNB_CONFIG_STRING_FREQUENCY_SCS)->uptr; + + char cfg_path[MAX_OPTNAME_SIZE + 16]; + snprintf(cfg_path, + sizeof(cfg_path), + "%s.[%i].%s.[%i]", + GNB_CONFIG_STRING_GNB_LIST, + gnb_idx, + GNB_CONFIG_STRING_FREQUENCY_LIST, + i); + GET_PARAMS(freq_cfg, GNBFREQUENCYCONFIGPARAMS_DESC, cfg_path); + + nr_neighbour_cell_sib4_freq_t *p = &f.freq_cfg; + + p->cellReselectionPriority = *freq_cfg[GNB_CONFIG_FREQUENCY_CELL_RESEL_PRIO_IDX].iptr; + p->threshX_HighP = *freq_cfg[GNB_CONFIG_FREQUENCY_THRESH_X_HIGH_P_IDX].iptr; + p->threshX_LowP = *freq_cfg[GNB_CONFIG_FREQUENCY_THRESH_X_LOW_P_IDX].iptr; + p->threshX_HighQ = *freq_cfg[GNB_CONFIG_FREQUENCY_THRESH_X_HIGH_Q_IDX].iptr; + p->threshX_LowQ = *freq_cfg[GNB_CONFIG_FREQUENCY_THRESH_X_LOW_Q_IDX].iptr; + p->q_OffsetFreq = *freq_cfg[GNB_CONFIG_FREQUENCY_Q_OFFSET_FREQ_IDX].iptr; + f.q_RxLevMin = *freq_cfg[GNB_CONFIG_FREQUENCY_Q_RXLEVMIN_IDX].iptr; + f.t_ReselectionNR = *freq_cfg[GNB_CONFIG_FREQUENCY_T_RESEL_NR_IDX].uptr; + + /* Add inter-frequency configuration to the list. */ + add_inter_freq(inter_freqs, &f, gnb_idx); + + LOG_I(GNB_APP, + "gNB %d: SIB4 inter-freq ARFCN %d, SCS %d, prio=%d, ThreshX_HighP=%d, ThreshX_LowP=%d, ThreshX_HighQ=%d, " + "ThreshX_LowQ=%d, q_OffsetFreq=%d\n", + gnb_idx, + f.arfcn, + f.scs, + f.freq_cfg.cellReselectionPriority, + f.freq_cfg.threshX_HighP, + f.freq_cfg.threshX_LowP, + f.freq_cfg.threshX_HighQ, + f.freq_cfg.threshX_LowQ, + f.freq_cfg.q_OffsetFreq); + } +} + +static void set_sib3_offsets(nr_neighbour_cell_t *n, const paramdef_t *cell_params, int n_cell_params) +{ + nr_neighbour_cell_neighbor_offset_t *sib3_off = &n->sib3.offset; + + /* SIB3 per-neighbour offsets */ + sib3_off->q_OffsetCell = *gpd(cell_params, n_cell_params, GNB_CONFIG_STRING_NEIGHBOUR_CELL_Q_OFFSET_CELL)->iptr; + sib3_off->q_RxLevMinOffsetCell = *gpd(cell_params, n_cell_params, GNB_CONFIG_STRING_NEIGHBOUR_CELL_Q_RXLEVMIN_OFFSET_CELL)->iptr; + sib3_off->q_QualMinOffsetCell = *gpd(cell_params, n_cell_params, GNB_CONFIG_STRING_NEIGHBOUR_CELL_Q_QUALMIN_OFFSET_CELL)->iptr; +} + +static void set_sib4_offsets(nr_neighbour_cell_t *n, const paramdef_t *cell_params, int n_cell_params) +{ + nr_neighbour_cell_neighbor_offset_t *sib4_off = &n->sib4.offset; + + /* SIB4 per-neighbour offsets */ + sib4_off->q_OffsetCell = *gpd(cell_params, n_cell_params, GNB_CONFIG_STRING_NEIGHBOUR_CELL_Q_OFFSET_CELL)->iptr; + sib4_off->q_RxLevMinOffsetCell = *gpd(cell_params, n_cell_params, GNB_CONFIG_STRING_NEIGHBOUR_CELL_Q_RXLEVMIN_OFFSET_CELL)->iptr; + sib4_off->q_QualMinOffsetCell = *gpd(cell_params, n_cell_params, GNB_CONFIG_STRING_NEIGHBOUR_CELL_Q_QUALMIN_OFFSET_CELL)->iptr; +} + /** @brief Parse and add neighbour cells for a given cell configuration * @param[in,out] cell Neighbour cell configuration to populate * @param[in] list Parameter list describing neighbour cells @@ -2036,6 +2138,11 @@ static void parse_neighbour_cells_list(neighbour_cell_configuration_t *cell, .absoluteFrequencySSB = *gpd(cell_params, n_cell_params, GNB_CONFIG_STRING_NEIGHBOUR_CELL_ABS_FREQ_SSB)->i64ptr, .tac = *gpd(cell_params, n_cell_params, GNB_CONFIG_STRING_NEIGHBOUR_TRACKING_ARE_CODE)->uptr}; + set_sib3_offsets(&n, cell_params, n_cell_params); + set_sib4_offsets(&n, cell_params, n_cell_params); + /* SIB4 per-frequency reselection parameters (q_OffsetFreq, thresholds, priority) + * are populated in parse_inter_freq_list() per-frequency, not per neighbour here. */ + char p[CONFIG_MAXOPTLENGTH]; // plmn path snprintf(p, sizeof(p), "%s.%s.[%i].%s", n_path, GNB_CONFIG_STRING_NEIGHBOUR_CELL_LIST, l, GNB_CONFIG_STRING_NEIGHBOUR_PLMN); GET_PARAMS(NeighbourPlmn, GNBPLMNPARAMS_DESC, p); @@ -2087,6 +2194,9 @@ static void fill_neighbour_cell_configuration(const uint8_t gnb_idx, gNB_RRC_INS if (nlist.numelt < 1) return; + /* Parse gNB-level inter-frequency configuration once and reuse it for all cells. */ + parse_inter_freq_list(&rrc->inter_freqs, gnb_idx); + rrc->neighbour_cell_configuration = malloc_or_fail(sizeof(seq_arr_t)); seq_arr_init(rrc->neighbour_cell_configuration, sizeof(neighbour_cell_configuration_t)); diff --git a/openair2/GNB_APP/gnb_paramdef.h b/openair2/GNB_APP/gnb_paramdef.h index 98e53d83e2..35a1c096d1 100644 --- a/openair2/GNB_APP/gnb_paramdef.h +++ b/openair2/GNB_APP/gnb_paramdef.h @@ -300,6 +300,11 @@ typedef enum { #define GNB_CONFIG_STRING_NEIGHBOUR_CELL_BAND "band" #define GNB_CONFIG_STRING_NEIGHBOUR_TRACKING_ARE_CODE "tracking_area_code" #define GNB_CONFIG_STRING_NEIGHBOUR_PLMN "plmn" +/* SIB3 fields (intra-frequency) - per-neighbor */ +#define GNB_CONFIG_STRING_NEIGHBOUR_CELL_Q_OFFSET_CELL "q_OffsetCell" +#define GNB_CONFIG_STRING_NEIGHBOUR_CELL_Q_RXLEVMIN_OFFSET_CELL "q_RxLevMinOffsetCell" +#define GNB_CONFIG_STRING_NEIGHBOUR_CELL_Q_QUALMIN_OFFSET_CELL "q_QualMinOffsetCell" +/* SIB4 per-frequency fields are configured in frequency_list.frequency_config */ // clang-format off #define GNBNEIGHBOURCELLPARAMS_DESC { /* optname helpstr paramflags XXXptr def val type numelt */ \ @@ -324,9 +329,82 @@ typedef enum { {GNB_CONFIG_STRING_NEIGHBOUR_TRACKING_ARE_CODE, \ "neighbour cell tracking area (0..16777215)", PARAMFLAG_MANDATORY, .uptr=NULL, .defuintval=0, TYPE_UINT, 0, \ .chkPptr = &(checkedparam_t){.s2 = {config_check_uintrange, {0, 16777215}}}}, \ + {GNB_CONFIG_STRING_NEIGHBOUR_CELL_Q_OFFSET_CELL, \ + "Q-OffsetCell for SIB3/SIB4 (Q-OffsetRange, default 0)", 0, .iptr=NULL, .defintval=0, TYPE_INT, 0, \ + .chkPptr = &(checkedparam_t){.s1 = {config_check_intval, \ + {-24, -22, -20, -18, -16, -14, -12, -10, -8, -6, -5, -4, -3, -2, -1, 0, 1, 2, 3, 4, 5, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24}, 31}}}, \ + {GNB_CONFIG_STRING_NEIGHBOUR_CELL_Q_RXLEVMIN_OFFSET_CELL, \ + "Q-RxLevMinOffsetCell for SIB3/SIB4 (1..8 or -1=disabled)", 0, .iptr=NULL, .defintval=-1, TYPE_INT, 0, \ + .chkPptr = &(checkedparam_t){.s1 = {config_check_intval, {-1, 1, 2, 3, 4, 5, 6, 7, 8}, 9}}}, \ + {GNB_CONFIG_STRING_NEIGHBOUR_CELL_Q_QUALMIN_OFFSET_CELL, \ + "Q-QualMinOffsetCell for SIB3/SIB4 (1..8 or -1=disabled)", 0, .iptr=NULL, .defintval=-1, TYPE_INT, 0, \ + .chkPptr = &(checkedparam_t){.s1 = {config_check_intval, {-1, 1, 2, 3, 4, 5, 6, 7, 8}, 9}}}, \ } // clang-format on +/* Per-frequency neighbour configuration (frequency_list) */ +#define GNB_CONFIG_STRING_FREQUENCY_LIST "frequency_list" +#define GNB_CONFIG_STRING_FREQUENCY_ABS_FREQ_SSB "absoluteFrequencySSB" +#define GNB_CONFIG_STRING_FREQUENCY_SCS "subcarrierSpacing" +#define GNB_CONFIG_STRING_FREQUENCY_BAND "band" +#define GNB_CONFIG_STRING_FREQUENCY_CONFIG "frequency_config" + +#define GNB_CONFIG_STRING_FREQUENCY_CELL_RESEL_PRIO "cellReselectionPriority" +#define GNB_CONFIG_STRING_FREQUENCY_THRESH_X_HIGH_P "threshX_HighP" +#define GNB_CONFIG_STRING_FREQUENCY_THRESH_X_LOW_P "threshX_LowP" +#define GNB_CONFIG_STRING_FREQUENCY_THRESH_X_HIGH_Q "threshX_HighQ" +#define GNB_CONFIG_STRING_FREQUENCY_THRESH_X_LOW_Q "threshX_LowQ" +#define GNB_CONFIG_STRING_FREQUENCY_Q_OFFSET_FREQ "q_OffsetFreq" +#define GNB_CONFIG_STRING_FREQUENCY_Q_RXLEVMIN "q_RxLevMin" +#define GNB_CONFIG_STRING_FREQUENCY_T_RESEL_NR "t_ReselectionNR" + +// clang-format off +#define GNBFREQUENCYPARAMS_DESC { \ +/* optname helpstr paramflags XXXptr def val type numelt */ \ + {GNB_CONFIG_STRING_FREQUENCY_ABS_FREQ_SSB, "inter-frequency abs freq ssb", PARAMFLAG_MANDATORY, .i64ptr=NULL, .defint64val=0, TYPE_INT64, 0}, \ + {GNB_CONFIG_STRING_FREQUENCY_SCS, "inter-frequency scs", PARAMFLAG_MANDATORY, .uptr=NULL, .defuintval=0, TYPE_UINT, 0}, \ + {GNB_CONFIG_STRING_FREQUENCY_BAND, "inter-frequency band", PARAMFLAG_MANDATORY, .uptr=NULL, .defuintval=78, TYPE_UINT, 0}, \ +} + +#define GNBFREQUENCYCONFIGPARAMS_DESC { \ + {GNB_CONFIG_STRING_FREQUENCY_CELL_RESEL_PRIO, \ + "SIB4 cellReselectionPriority (0..7, -1=disabled)", 0, .iptr=NULL, .defintval=-1, TYPE_INT, 0, \ + .chkPptr = &(checkedparam_t){.s2 = {config_check_intrange, {-1, 7}}}}, \ + {GNB_CONFIG_STRING_FREQUENCY_THRESH_X_HIGH_P, \ + "SIB4 threshX-HighP (0..31)", 0, .iptr=NULL, .defintval=0, TYPE_INT, 0, \ + .chkPptr = &(checkedparam_t){.s2 = {config_check_intrange, {0, 31}}}}, \ + {GNB_CONFIG_STRING_FREQUENCY_THRESH_X_LOW_P, \ + "SIB4 threshX-LowP (0..31)", 0, .iptr=NULL, .defintval=0, TYPE_INT, 0, \ + .chkPptr = &(checkedparam_t){.s2 = {config_check_intrange, {0, 31}}}}, \ + {GNB_CONFIG_STRING_FREQUENCY_THRESH_X_HIGH_Q, \ + "SIB4 threshX-HighQ (0..31, -1=disabled)", 0, .iptr=NULL, .defintval=-1, TYPE_INT, 0, \ + .chkPptr = &(checkedparam_t){.s2 = {config_check_intrange, {-1, 31}}}}, \ + {GNB_CONFIG_STRING_FREQUENCY_THRESH_X_LOW_Q, \ + "SIB4 threshX-LowQ (0..31, -1=disabled)", 0, .iptr=NULL, .defintval=-1, TYPE_INT, 0, \ + .chkPptr = &(checkedparam_t){.s2 = {config_check_intrange, {-1, 31}}}}, \ + {GNB_CONFIG_STRING_FREQUENCY_Q_OFFSET_FREQ, \ + "SIB4 q-OffsetFreq (Q-OffsetRange values, default 0)", 0, .iptr=NULL, .defintval=0, TYPE_INT, 0, \ + .chkPptr = &(checkedparam_t){.s1 = {config_check_intval, {-24, -22, -20, -18, -16, -14, -12, -10, \ + -8, -6, -5, -4, -3, -2, -1, 0, 1, 2, 3, 4, 5, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24}, 31}}}, \ + {GNB_CONFIG_STRING_FREQUENCY_Q_RXLEVMIN, \ + "SIB4 per-carrier Q-RxLevMin (-70..-22, default -22)", 0, .iptr=NULL, .defintval=-22, TYPE_INT, 0, \ + .chkPptr = &(checkedparam_t){.s2 = {config_check_intrange, {-70, -22}}}}, \ + {GNB_CONFIG_STRING_FREQUENCY_T_RESEL_NR, \ + "SIB4 per-carrier t-ReselectionNR (0..7, default 0)", 0, .uptr=NULL, .defuintval=0, TYPE_UINT, 0, \ + .chkPptr = &(checkedparam_t){.s2 = {config_check_uintrange, {0, 7}}}}, \ +} +// clang-format on + +/* Indexes into GNBFREQUENCYCONFIGPARAMS_DESC */ +#define GNB_CONFIG_FREQUENCY_CELL_RESEL_PRIO_IDX 0 +#define GNB_CONFIG_FREQUENCY_THRESH_X_HIGH_P_IDX 1 +#define GNB_CONFIG_FREQUENCY_THRESH_X_LOW_P_IDX 2 +#define GNB_CONFIG_FREQUENCY_THRESH_X_HIGH_Q_IDX 3 +#define GNB_CONFIG_FREQUENCY_THRESH_X_LOW_Q_IDX 4 +#define GNB_CONFIG_FREQUENCY_Q_OFFSET_FREQ_IDX 5 +#define GNB_CONFIG_FREQUENCY_Q_RXLEVMIN_IDX 6 +#define GNB_CONFIG_FREQUENCY_T_RESEL_NR_IDX 7 + /* New Measurement Configurations*/ #define GNB_CONFIG_STRING_MEASUREMENT_CONFIGURATION "nr_measurement_configuration" diff --git a/openair2/RRC/NR/nr_rrc_defs.h b/openair2/RRC/NR/nr_rrc_defs.h index 68104ba203..f0e212c79f 100644 --- a/openair2/RRC/NR/nr_rrc_defs.h +++ b/openair2/RRC/NR/nr_rrc_defs.h @@ -312,6 +312,72 @@ typedef struct { bool is_default_a3_configuration_exists; } nr_measurement_configuration_t; +/** @brief Per-neighbor cell-specific offsets (TS 38.331), shared by SIB3 and SIB4 + * Maps to ASN.1 IntraFreqNeighCellInfo and InterFreqNeighCellInfo + * (SIB3.IntraFreqNeighCellList, SIB4.InterFreqCarrierFreqInfo.neighCellList) */ +typedef struct { + // q-OffsetCell: (Q-OffsetRange values -24,-22,-20,-18,-16,-14,-12,-10,-8,-6,-5,-4,-3,-2,-1,0,1,2,3,4,5,6,8,10,12,14,16,18,20,22,24 dB) + // per-neighbor cell ranking + int q_OffsetCell; + // q-RxLevMinOffsetCell: Q-OffsetCellSmall (1..8): Per-neighbor RSRP minimum offset + int q_RxLevMinOffsetCell; + // q-QualMinOffsetCell: Q-OffsetCellSmall (1..8): Per-neighbor RSRQ minimum offset + int q_QualMinOffsetCell; +} nr_neighbour_cell_neighbor_offset_t; + +/** @brief SIB3 (intra-frequency) neighbor list parameters (TS 38.331) + * Per-neighbor fields for SIB3.IntraFreqNeighCellList.IntraFreqNeighCellInfo. + * SIB3 carries intra-freq neighbor offsets for ranking. */ +typedef struct { + nr_neighbour_cell_neighbor_offset_t offset; +} nr_neighbour_cell_sib3_t; + +/** @brief SIB4 inter-frequency carrier parameters (TS 38.331) + * Per-frequency fields for SIB4.InterFreqCarrierFreqList.InterFreqCarrierFreqInfo. + * Stored once per ARFCN; all neighbors on that frequency share this config. */ +typedef struct { + // cellReselectionPriority (0..7): Absolute priority of this inter-freq carrier + int cellReselectionPriority; + // threshX-HighP (0..31): RSRP threshold for reselection to a higher-priority inter-freq + int threshX_HighP; + // threshX-LowP (0..31): RSRP threshold for reselection to a lower-priority inter-freq + int threshX_LowP; + // threshX-HighQ (0..31): RSRQ threshold for higher-priority inter-freq reselection + int threshX_HighQ; + // threshX-LowQ (0..31): RSRQ threshold for lower-priority inter-freq reselection + int threshX_LowQ; + // q-OffsetFreq (Q-OffsetRange values: -24,-22,-20,-18,-16,-14,-12,-10,-8,-6,-5,-4,-3,-2,-1,0,1,2,3,4,5,6,8,10,12,14,16,18,20,22,24 dB): + // Frequency-specific offset in inter-freq cell ranking formula + int q_OffsetFreq; +} nr_neighbour_cell_sib4_freq_t; + +/** @brief SIB4 (inter-frequency) parameters per neighbor (TS 38.331) + * Combines per-frequency carrier info (InterFreqCarrierFreqInfo) and per-neighbor + * offsets (InterFreqNeighCellInfo). */ +typedef struct { + // Per-frequency carrier (InterFreqCarrierFreqInfo) + nr_neighbour_cell_sib4_freq_t sib4_freq; + // Per-neighbor offsets (InterFreqNeighCellInfo) + nr_neighbour_cell_neighbor_offset_t offset; +} nr_neighbour_cell_sib4_t; + +/** @brief Per-frequency SIB4 configuration (TS 38.331), keyed by ARFCN + * One entry per inter-freq carrier (absoluteFrequencySSB + subcarrierSpacing). + * freq_cfg holds InterFreqCarrierFreqInfo parameters. Analogous to one “carrier” slice of SIB4 + * interFreqCarrierFreqList. */ +typedef struct { + // absoluteFrequencySSB (ARFCN) + int arfcn; + // ssbSubcarrierSpacing + int scs; + // q-RxLevMin (-70..-22 dBm) + int q_RxLevMin; + // t-ReselectionNR (0..7) + int t_ReselectionNR; + // Inter-frequency carrier parameters (InterFreqCarrierFreqInfo) + nr_neighbour_cell_sib4_freq_t freq_cfg; +} nr_inter_freq_cfg_t; + typedef struct { uint32_t gNB_ID; uint64_t nrcell_id; @@ -322,6 +388,10 @@ typedef struct { plmn_id_t plmn; uint32_t tac; bool isIntraFrequencyNeighbour; + // SIB3 (intra-frequency neighbor cell-specific offsets) + nr_neighbour_cell_sib3_t sib3; + // SIB4 (inter-frequency neighbor cell-specific parameters) + nr_neighbour_cell_sib4_t sib4; } nr_neighbour_cell_t; typedef struct neighbour_cell_configuration_s { @@ -540,6 +610,9 @@ typedef struct gNB_RRC_INST_s { nr_mac_rrc_dl_if_t mac_rrc; cucp_cuup_if_t cucp_cuup; + // Per-frequency SIB4 configurations, indexed by ARFCN + seq_arr_t inter_freqs; /* array of nr_inter_freq_cfg_t */ + // Per-cell neighbour configurations, indexed by cell_id seq_arr_t *neighbour_cell_configuration; nr_measurement_configuration_t measurementConfiguration;