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.
This commit is contained in:
Guido Casati
2026-04-10 10:22:50 +02:00
parent 817d1a6cb5
commit 6f9a716eb9
4 changed files with 187 additions and 0 deletions
+15
View File
@@ -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]);
}
+21
View File
@@ -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;
+2
View File
@@ -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);
+149
View File
@@ -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);
}