From 6f9a716eb9eb854d9ac693b00dae38dbd8a3f6bd Mon Sep 17 00:00:00 2001 From: Guido Casati Date: Wed, 11 Feb 2026 10:56:24 +0100 Subject: [PATCH] RRC/MAC: add SIB4 inter-frequency neighbour support Add SIB4 encode/decode helpers and populate SIB4 from configured inter-frequency carriers and neighbour cells, wiring it into DU F1 Setup handling and MAC SystemInformation. Changes: - Add do_SIB4_NR() and NR_SIB4.h inclusion in asn1_msg.c/asn1_msg.h to UPER-encode NR_SIB4_t into a byte_array_t. - Extend nr_mac_configure_other_sib() in NR_MAC_gNB/config.c with an NR_SIB_4 case that validates the SIB container, decodes NR_SIB4_t via uper_decode, logs failures, and appends SIB4 to SystemInformation. - Introduce build_inter_freq_carrier_from_cfg() and get_sib4_inter_freq_neighbors() in rrc_gNB_du.c to build NR_InterFreqCarrierFreqInfo_t and NR_SIB4_t from inter-frequency and neighbour cell configuration, skipping invalid or empty carriers. - Handle NR_SIB_4 in rrc_gNB_process_f1_setup_req() by deriving SIB4 from neighbour/inter-frequency configuration, encoding it with do_SIB4_NR(), validating the resulting byte_array_t and adding the SI message for the DU cell when encoding succeeds. --- openair2/LAYER2/NR_MAC_gNB/config.c | 15 +++ openair2/RRC/NR/MESSAGES/asn1_msg.c | 21 ++++ openair2/RRC/NR/MESSAGES/asn1_msg.h | 2 + openair2/RRC/NR/rrc_gNB_du.c | 149 ++++++++++++++++++++++++++++ 4 files changed, 187 insertions(+) diff --git a/openair2/LAYER2/NR_MAC_gNB/config.c b/openair2/LAYER2/NR_MAC_gNB/config.c index f38eebd326..1d217b7c72 100644 --- a/openair2/LAYER2/NR_MAC_gNB/config.c +++ b/openair2/LAYER2/NR_MAC_gNB/config.c @@ -1023,6 +1023,21 @@ bool nr_mac_configure_other_sib(gNB_MAC_INST *nrmac, int num_cu_sib, const f1ap_ add_sib_to_systeminformation(sysInfo, type); break; } + case NR_SIB_4: { + struct NR_SystemInformation_IEs__sib_TypeAndInfo__Member *type = calloc_or_fail(1, sizeof(*type)); + type->present = NR_SystemInformation_IEs__sib_TypeAndInfo__Member_PR_sib4; + NR_SIB4_t *sib4 = NULL; + asn_dec_rval_t dec_rval = uper_decode(NULL, &asn_DEF_NR_SIB4, (void **)&sib4, container->buf, container->len, 0, 0); + if (dec_rval.code != RC_OK) { + LOG_E(NR_MAC, "cannot decode SIB%d from CU\n", config_sibs[i]); + ASN_STRUCT_FREE(asn_DEF_NR_SIB4, sib4); + free(type); + break; + } + type->choice.sib4 = sib4; + add_sib_to_systeminformation(sysInfo, type); + break; + } default : AssertFatal(false, "Invalid or not supported SIB%d\n", config_sibs[i]); } diff --git a/openair2/RRC/NR/MESSAGES/asn1_msg.c b/openair2/RRC/NR/MESSAGES/asn1_msg.c index 80235ebdb7..d818229054 100644 --- a/openair2/RRC/NR/MESSAGES/asn1_msg.c +++ b/openair2/RRC/NR/MESSAGES/asn1_msg.c @@ -265,6 +265,27 @@ byte_array_t do_SIB3_NR(const NR_SIB3_t *sib3) return msg; } +byte_array_t do_SIB4_NR(NR_SIB4_t *sib4) +{ + byte_array_t msg = {.buf = NULL, .len = 0}; + char errbuf[256] = {0}; + size_t errlen = sizeof(errbuf); + int ret = asn_check_constraints(&asn_DEF_NR_SIB4, sib4, errbuf, &errlen); + if (ret != 0) { + LOG_E(NR_RRC, "SIB4 constraint check failed: %s\n", errbuf); + return msg; + } + + int val = uper_encode_to_new_buffer(&asn_DEF_NR_SIB4, NULL, (void *)sib4, (void **)&msg.buf); + if (val <= 0) { + LOG_E(NR_RRC, "Failed to encode SIB4\n"); + return msg; + } + + msg.len = val; + return msg; +} + int do_RRCReject(uint8_t *const buffer) { asn_enc_rval_t enc_rval; diff --git a/openair2/RRC/NR/MESSAGES/asn1_msg.h b/openair2/RRC/NR/MESSAGES/asn1_msg.h index 11963780f1..384096ee16 100644 --- a/openair2/RRC/NR/MESSAGES/asn1_msg.h +++ b/openair2/RRC/NR/MESSAGES/asn1_msg.h @@ -35,6 +35,7 @@ #include "NR_UE-CapabilityRAT-ContainerList.h" #include "NR_SIB2.h" #include "NR_SIB3.h" +#include "NR_SIB4.h" #include "ds/seq_arr.h" #include "ds/byte_array.h" #include "openair2/LAYER2/nr_pdcp/nr_pdcp_configuration.h" @@ -69,6 +70,7 @@ int xer_sprint_NR(char *string, size_t string_size, struct asn_TYPE_descriptor_s byte_array_t do_SIB2_NR(const NR_SIB2_t *sib2); byte_array_t do_SIB3_NR(const NR_SIB3_t *sib3); +byte_array_t do_SIB4_NR(NR_SIB4_t *sib4); int do_RRCReject(uint8_t *const buffer); diff --git a/openair2/RRC/NR/rrc_gNB_du.c b/openair2/RRC/NR/rrc_gNB_du.c index 43692ec742..9843d02b83 100644 --- a/openair2/RRC/NR/rrc_gNB_du.c +++ b/openair2/RRC/NR/rrc_gNB_du.c @@ -25,6 +25,7 @@ #include "nr_rrc_defs.h" #include "NR_SIB2.h" #include "NR_SIB3.h" +#include "NR_SIB4.h" #include "NR_Q-OffsetRange.h" #include "openair2/RRC/NR/MESSAGES/asn1_msg.h" #include "rrc_gNB_mobility.h" @@ -305,6 +306,130 @@ static NR_SIB3_t *get_sib3_intra_freq_neighbors(const seq_arr_t *neighbours, uin return sib3; } +/** @brief Build an NR_InterFreqCarrierFreqInfo structure from a nr_inter_freq_cfg_t configuration. + * @param cfg Configuration to build the carrier from + * @return Allocated NR_InterFreqCarrierFreqInfo_t on success, NULL if the configuration is invalid */ +static NR_InterFreqCarrierFreqInfo_t *build_inter_freq_carrier_from_cfg(const nr_inter_freq_cfg_t *cfg) +{ + DevAssert(cfg); + + const nr_neighbour_cell_sib4_freq_t *f = &cfg->freq_cfg; + + NR_InterFreqCarrierFreqInfo_t *carrier = calloc_or_fail(1, sizeof(*carrier)); + /* dl_CarrierFreq (ARFCN) */ + carrier->dl_CarrierFreq = cfg->arfcn; + /* ssbSubcarrierSpacing: (15/30/60/120 kHz) */ + carrier->ssbSubcarrierSpacing = cfg->scs; + carrier->deriveSSB_IndexFromCell = true; + /* Q-RxLevMin (-70..-22 dBm) */ + carrier->q_RxLevMin = cfg->q_RxLevMin; + /* T-Reselection (0..7) */ + carrier->t_ReselectionNR = cfg->t_ReselectionNR; + /* threshX_HighP/LowP: Reselection threshold (0..31) */ + carrier->threshX_HighP = f->threshX_HighP; + carrier->threshX_LowP = f->threshX_LowP; + /* threshX-Q: Optional RSRQ thresholds; emit only when both are provided. */ + if (f->threshX_HighQ != -1 && f->threshX_LowQ != -1) { + carrier->threshX_Q = calloc_or_fail(1, sizeof(*carrier->threshX_Q)); + carrier->threshX_Q->threshX_HighQ = f->threshX_HighQ; + carrier->threshX_Q->threshX_LowQ = f->threshX_LowQ; + } else if (f->threshX_HighQ != -1 || f->threshX_LowQ != -1) { + LOG_W(NR_RRC, + "SIB4: partial threshX-Q for ARFCN %d (HighQ=%d LowQ=%d), omitting threshX-Q\n", + cfg->arfcn, + f->threshX_HighQ, + f->threshX_LowQ); + } + /* CellReselectionPriority (0..7) */ + if (f->cellReselectionPriority != -1) + asn1cCallocOne(carrier->cellReselectionPriority, f->cellReselectionPriority); + /* q-OffsetFreq (-24..24 dB), 0 dB default */ + asn1cCallocOne(carrier->q_OffsetFreq, get_q_offset_asn1(f->q_OffsetFreq)); + /* InterFreqNeighCellList */ + carrier->interFreqNeighCellList = calloc_or_fail(1, sizeof(*carrier->interFreqNeighCellList)); + return carrier; +} + +/** + * @brief Build an NR_SIB4 structure populated with inter-frequency neighbour cell entries. + * + * Iterates over all configured inter-frequency carriers (excluding the serving cell ARFCN) + * and, for each carrier, adds the matching neighbour cells from @p neighbour_config. + * Carriers with no matching neighbours are discarded. If no valid carrier is found, + * the function returns NULL. + * + * @param neighbour_config Neighbour cell configuration (cells only). + * @param inter_freqs gNB-level inter-frequency configuration. + * @param serving_ssb_arfcn SSB ARFCN of the serving cell, used to skip the serving carrier. + * @return Allocated NR_SIB4_t on success, NULL if no inter-frequency neighbours are available. + */ +static NR_SIB4_t *get_sib4_inter_freq_neighbors(const neighbour_cell_configuration_t *neighbour_config, + const seq_arr_t *inter_freqs, + uint32_t serving_ssb_arfcn) +{ + DevAssert(neighbour_config); + + NR_SIB4_t *sib4 = calloc_or_fail(1, sizeof(*sib4)); + + const seq_arr_t *neigh = &neighbour_config->neighbour_cells; + + FOR_EACH_SEQ_ARR (nr_inter_freq_cfg_t *, cfg, inter_freqs) { + if (cfg->arfcn == serving_ssb_arfcn) + continue; + + NR_InterFreqCarrierFreqInfo_t *carrier = build_inter_freq_carrier_from_cfg(cfg); + if (!carrier) + continue; + + NR_InterFreqNeighCellList_t *neigh_list = carrier->interFreqNeighCellList; + + FOR_EACH_SEQ_ARR (nr_neighbour_cell_t *, nc, neigh) { + if (nc->absoluteFrequencySSB != cfg->arfcn || nc->subcarrierSpacing != cfg->scs) + continue; + + if (neigh_list->list.count >= NR_maxCellInter) { + LOG_W(NR_RRC, + "SIB4: too many inter-frequency neighbors for ARFCN %ld (max %d), skipping neighbour\n", + carrier->dl_CarrierFreq, + NR_maxCellInter); + continue; + } + + const nr_neighbour_cell_neighbor_offset_t *offset = &nc->sib4.offset; + LOG_I(NR_RRC, + "SIB4: added inter-frequency neighbour cell: cellId %ld (PCI %d), ARFCN %d, SCS %d\n", + nc->nrcell_id, + nc->physicalCellId, + cfg->arfcn, + cfg->scs); + asn1cSequenceAdd(neigh_list->list, NR_InterFreqNeighCellInfo_t, cell); + cell->physCellId = nc->physicalCellId; + + /* q_OffsetCell: 0 dB is default */ + cell->q_OffsetCell = get_q_offset_asn1(offset->q_OffsetCell); + + /* q_RxLevMinOffsetCell / q_QualMinOffsetCell: range 1..8 */ + if (offset->q_RxLevMinOffsetCell != -1) + asn1cCallocOne(cell->q_RxLevMinOffsetCell, offset->q_RxLevMinOffsetCell); + if (offset->q_QualMinOffsetCell != -1) + asn1cCallocOne(cell->q_QualMinOffsetCell, offset->q_QualMinOffsetCell); + } + + if (neigh_list->list.count > 0) { + asn1cSeqAdd(&sib4->interFreqCarrierFreqList.list, carrier); + } else { + ASN_STRUCT_FREE(asn_DEF_NR_InterFreqCarrierFreqInfo, carrier); + } + } + + if (sib4->interFreqCarrierFreqList.list.count == 0) { + ASN_STRUCT_FREE(asn_DEF_NR_SIB4, sib4); + return NULL; + } + + return sib4; +} + /** @brief Return the frequency of the SS block of the cell for which this message is included, * or of other SS blocks within the same carrier. MeasurementTimingConfiguration message is * used to convey assistance information for measurement timing between nodes (e.g F1 setup) */ @@ -725,6 +850,30 @@ void rrc_gNB_process_f1_setup_req(f1ap_setup_req_t *req, sctp_assoc_t assoc_id) free_byte_array(enc); LOG_I(NR_RRC, "DU %ld: added SIB3 to F1 Setup Response (cell %ld)\n", du->gNB_DU_id, new->info.cell_id); } break; + case NR_SIB_4: { + if (!rrc->neighbour_cell_configuration) + break; + const neighbour_cell_configuration_t *neighbour_cfg = get_cell_neighbour_list(rrc, new); + if (!neighbour_cfg) + break; + + NR_SIB4_t *sib4 = get_sib4_inter_freq_neighbors(neighbour_cfg, &rrc->inter_freqs, get_ssb_arfcn(new)); + if (!sib4) { + LOG_W(NR_RRC, "SIB4: could not build from neighbours, skipping\n"); + break; + } + + byte_array_t enc = do_SIB4_NR(sib4); + ASN_STRUCT_FREE(asn_DEF_NR_SIB4, sib4); + if (!enc.buf || enc.len == 0) { + free_byte_array(enc); + LOG_E(NR_RRC, "SIB4 encoding failed\n"); + break; + } + add_si_msg(&cell, sib->SIB_type, &enc); + free_byte_array(enc); + LOG_I(NR_RRC, "DU %ld: added SIB4 to F1 Setup Response (cell %ld)\n", du->gNB_DU_id, new->info.cell_id); + } break; default: AssertFatal(false, "SIB%d not handled yet\n", sib->SIB_type); }