RRC multi-cell: integrate cell containers into F1 setup flow

Refactor F1 setup processing to create and manage cell containers during DU
initialization, establishing the foundation for multi-cell architecture where
cells are managed independently from DUs.

Why: Cell information is currently tied to F1AP structures (f1ap_served_cell_info_t),
which complicates multi-cell support. Introducing cell containers at F1 setup
decouples RRC cell management from F1AP and allows cells to be managed
independently while keeping existing code paths working.

Key changes:

1. Cell container creation (cp_f1_served_cell_info_to_cell):
   New helper copying F1AP served cell info into nr_rrc_cell_container_t (cell_id,
   pci, plmn, tac, TDD/FDD frequency info). Extracts and stores MTC in the cell.

2. Function signature updates:
   get_cell_neighbour_list() and label_intra_frequency_neighbours() now take
   nr_rrc_cell_container_t* and use cell->info; improved logging (cell_id, PCI,
   SSB ARFCN).

3. valid_du_in_neighbour_configs:
   Removed ssb_arfcn parameter; MTC is extracted and freed inside the function.

4. F1 Setup (rrc_gNB_process_f1_setup_req):
   Initialize DU cell tree (RB_INIT(&du->cells)). Populate DU-only fields
   (gNB_DU_id, gNB_DU_name, rrc_ver).
   Flow:
   - create cell with cp_f1_served_cell_info_to_cell
   - add to global tree via rrc_add_cell()
   - add DU via rrc_add_du()
   - attach cell to DU with rrc_add_cell_to_du().
   Store MIB/SIB1/MTC in cell.
   Use cell container for neighbour labeling and SIB encoding.

5. Duplicate cell detection:
   Use rrc_add_cell() for global cell tree insert; collision return indicates
   duplicate cell_id. Clearer error messages for Cell ID vs PCI conflicts.
   Use F1AP_CauseRadioNetwork_cell_not_available for cell conflicts.

6. DU configuration update:
   Use get_cell_by_cell_id() to find cell, check assoc_id, pass cell container
   to label_intra_frequency_neighbours().

7. DU disconnection:
   Use rrc_cleanup_du() to remove cells from global tree and free resources;
   logging uses DU fields (gNB_DU_id, gNB_DU_name).

8. Global cell tree:
   Initialize rrc->cells in openair_rrc_gNB_configuration().

Backward compatibility: setup_req still built via cp_f1ap_setup_request();
du->mib, du->sib1, du->mtc remain set (shared with cell container).
This commit is contained in:
Guido Casati
2026-02-05 11:44:09 +01:00
parent c453c342ed
commit 95d35b9b7d
2 changed files with 107 additions and 55 deletions
+1
View File
@@ -370,6 +370,7 @@ void openair_rrc_gNB_configuration(gNB_RRC_INST *rrc, gNB_RrcConfigurationReq *c
RB_INIT(&rrc->rrc_ue_head);
RB_INIT(&rrc->cuups);
RB_INIT(&rrc->dus);
RB_INIT(&rrc->cells);
rrc->configuration = *configuration;
}
+106 -55
View File
@@ -161,13 +161,13 @@ static int nr_cell_id_match(const void *key, const void *element)
return 1;
}
static neighbour_cell_configuration_t *get_cell_neighbour_list(const gNB_RRC_INST *rrc, const f1ap_served_cell_info_t *cell_info)
static neighbour_cell_configuration_t *get_cell_neighbour_list(const gNB_RRC_INST *rrc, const nr_rrc_cell_container_t *cell)
{
void *base = seq_arr_front(rrc->neighbour_cell_configuration);
size_t nmemb = seq_arr_size(rrc->neighbour_cell_configuration);
size_t size = sizeof(neighbour_cell_configuration_t);
void *it = bsearch((void *)&cell_info->nr_cellid, base, nmemb, size, nr_cell_id_match);
void *it = bsearch((void *)&cell->info.cell_id, base, nmemb, size, nr_cell_id_match);
return (neighbour_cell_configuration_t *)it;
}
@@ -201,28 +201,39 @@ static void is_intra_frequency_neighbour(void *ssb_arfcn, void *neighbour_cell)
/**
* @brief Labels neighbour cells if they are intra frequency to prepare meas config only for intra frequency ho
* @param[in] rrc Pointer to RRC instance
* @param[in] cell_info Pointer to cell information
* @param[in] du Pointer to DU container
* @param[in] cell Pointer to cell container
*/
static void label_intra_frequency_neighbours(gNB_RRC_INST *rrc,
const nr_rrc_du_container_t *du,
const f1ap_served_cell_info_t *cell_info)
const nr_rrc_cell_container_t *cell)
{
if (!rrc->neighbour_cell_configuration)
return;
neighbour_cell_configuration_t *neighbour_cell_config = get_cell_neighbour_list(rrc, cell_info);
neighbour_cell_configuration_t *neighbour_cell_config = get_cell_neighbour_list(rrc, cell);
if (!neighbour_cell_config)
return;
LOG_D(NR_RRC, "HO LOG: Cell: %lu has neighbour cell configuration!\n", cell_info->nr_cellid);
uint32_t ssb_arfcn = get_ssb_arfcn(du);
LOG_D(NR_RRC,
"Cell %lu (PCI %d, SSB ARFCN %u) has neighbour cell configuration, labeling intra-frequency neighbours\n",
cell->info.cell_id,
cell->info.pci,
ssb_arfcn);
seq_arr_t *cell_neighbour_list = neighbour_cell_config->neighbour_cells;
for_each(cell_neighbour_list, (void *)&ssb_arfcn, is_intra_frequency_neighbour);
}
static bool valid_du_in_neighbour_configs(const seq_arr_t *neighbour_cell_configuration, const f1ap_served_cell_info_t *cell, int ssb_arfcn)
static bool valid_du_in_neighbour_configs(const seq_arr_t *neighbour_cell_configuration, const f1ap_served_cell_info_t *cell)
{
// MTC is mandatory, but some DUs don't send it in the F1 Setup Request, so
// "tolerate" this behavior, despite it being mandatory
NR_MeasurementTimingConfiguration_t *mtc = extract_mtc(cell->measurement_timing_config, cell->measurement_timing_config_len);
int ssb_arfcn = ssb_arfcn_mtc(mtc);
ASN_STRUCT_FREE(asn_DEF_NR_MeasurementTimingConfiguration, mtc);
for (int c = 0; c < neighbour_cell_configuration->size; c++) {
const neighbour_cell_configuration_t *neighbour_config = seq_arr_at(neighbour_cell_configuration, c);
for (int ni = 0; ni < neighbour_config->neighbour_cells->size; ni++) {
@@ -278,6 +289,44 @@ static bool valid_du_in_neighbour_configs(const seq_arr_t *neighbour_cell_config
return true;
}
static void cp_f1_served_cell_info_to_cell(nr_rrc_cell_container_t *dst, const f1ap_served_cell_info_t *src)
{
DevAssert(dst != NULL);
DevAssert(src != NULL);
// Copy basic cell information
dst->info.cell_id = src->nr_cellid;
dst->info.pci = src->nr_pci;
dst->info.plmn = src->plmn;
dst->info.tac = src->tac ? *src->tac : 0;
// Copy frequency information based on mode
if (src->mode == F1AP_MODE_TDD) {
dst->info.mode = NR_MODE_TDD;
dst->info.tdd.dlul.band = src->tdd.freqinfo.band;
dst->info.tdd.dlul.arfcn = src->tdd.freqinfo.arfcn;
dst->info.tdd.dlul.scs = src->tdd.tbw.scs;
dst->info.tdd.dlul.nrb = src->tdd.tbw.nrb;
} else {
dst->info.mode = NR_MODE_FDD;
/* DL */
dst->info.fdd.dl.band = src->fdd.dl_freqinfo.band;
dst->info.fdd.dl.arfcn = src->fdd.dl_freqinfo.arfcn;
dst->info.fdd.dl.scs = src->fdd.dl_tbw.scs;
dst->info.fdd.dl.nrb = src->fdd.dl_tbw.nrb;
/* UL */
dst->info.fdd.ul.band = src->fdd.ul_freqinfo.band;
dst->info.fdd.ul.arfcn = src->fdd.ul_freqinfo.arfcn;
dst->info.fdd.ul.scs = src->fdd.ul_tbw.scs;
dst->info.fdd.ul.nrb = src->fdd.ul_tbw.nrb;
}
// Extract and store MTC
if (src->measurement_timing_config_len > 0) {
dst->mtc = extract_mtc(src->measurement_timing_config, src->measurement_timing_config_len);
}
}
void rrc_gNB_process_f1_setup_req(f1ap_setup_req_t *req, sctp_assoc_t assoc_id)
{
AssertFatal(assoc_id != 0, "illegal assoc_id == 0: should be -1 (monolithic) or >0 (split)\n");
@@ -315,46 +364,22 @@ void rrc_gNB_process_f1_setup_req(f1ap_setup_req_t *req, sctp_assoc_t assoc_id)
}
nr_rrc_du_container_t *it = NULL;
RB_FOREACH(it, rrc_du_tree, &rrc->dus) {
if (it->setup_req->gNB_DU_id == req->gNB_DU_id) {
if (it->gNB_DU_id == req->gNB_DU_id) {
LOG_E(NR_RRC,
"gNB-DU ID: existing DU %s on assoc_id %d already has ID %ld, rejecting requesting gNB-DU\n",
it->setup_req->gNB_DU_name,
it->gNB_DU_name,
it->assoc_id,
it->setup_req->gNB_DU_id);
fail.cause = F1AP_CauseMisc_unspecified;
rrc->mac_rrc.f1_setup_failure(assoc_id, &fail);
return;
}
// note: we assume that each DU contains only one cell; otherwise, we would
// need to check every cell in the requesting DU to any existing cell.
const f1ap_served_cell_info_t *exist_info = &it->setup_req->cell[0].info;
const f1ap_served_cell_info_t *new_info = &req->cell[0].info;
if (exist_info->nr_cellid == new_info->nr_cellid || exist_info->nr_pci == new_info->nr_pci) {
LOG_E(NR_RRC,
"existing DU %s on assoc_id %d already has cellID %ld/physCellId %d, rejecting requesting gNB-DU with cellID %ld/physCellId %d\n",
it->setup_req->gNB_DU_name,
it->assoc_id,
exist_info->nr_cellid,
exist_info->nr_pci,
new_info->nr_cellid,
new_info->nr_pci);
it->gNB_DU_id);
fail.cause = F1AP_CauseMisc_unspecified;
rrc->mac_rrc.f1_setup_failure(assoc_id, &fail);
return;
}
}
// MTC is mandatory, but some DUs don't send it in the F1 Setup Request, so
// "tolerate" this behavior, despite it being mandatory
NR_MeasurementTimingConfiguration_t *mtc =
extract_mtc(cell_info->measurement_timing_config, cell_info->measurement_timing_config_len);
if (rrc->neighbour_cell_configuration
&& !valid_du_in_neighbour_configs(rrc->neighbour_cell_configuration, cell_info, ssb_arfcn_mtc(mtc))) {
if (rrc->neighbour_cell_configuration && !valid_du_in_neighbour_configs(rrc->neighbour_cell_configuration, cell_info)) {
LOG_E(NR_RRC, "problem with DU %ld in neighbor configuration, rejecting DU\n", req->gNB_DU_id);
f1ap_setup_failure_t fail = {.cause = F1AP_CauseMisc_unspecified};
rrc->mac_rrc.f1_setup_failure(assoc_id, &fail);
ASN_STRUCT_FREE(asn_DEF_NR_MeasurementTimingConfiguration, mtc);
return;
}
@@ -367,21 +392,41 @@ void rrc_gNB_process_f1_setup_req(f1ap_setup_req_t *req, sctp_assoc_t assoc_id)
LOG_W(NR_RRC, "rejecting DU ID %ld\n", req->gNB_DU_id);
fail.cause = F1AP_CauseProtocol_semantic_error;
rrc->mac_rrc.f1_setup_failure(assoc_id, &fail);
ASN_STRUCT_FREE(asn_DEF_NR_MeasurementTimingConfiguration, mtc);
return;
}
}
LOG_I(NR_RRC, "Accepting DU %ld (%s), sending F1 Setup Response\n", req->gNB_DU_id, req->gNB_DU_name);
LOG_I(NR_RRC, "DU uses RRC version %u.%u.%u\n", req->rrc_ver[0], req->rrc_ver[1], req->rrc_ver[2]);
// we accept the DU
nr_rrc_du_container_t *du = calloc(1, sizeof(*du));
AssertFatal(du, "out of memory\n");
du->assoc_id = assoc_id;
/* Create cell and add to global tree */
nr_rrc_cell_container_t *new = calloc_or_fail(1, sizeof(*new));
new->assoc_id = assoc_id;
cp_f1_served_cell_info_to_cell(new, cell_info);
new->mib = mib;
new->sib1 = sib1;
// Add cell to DU's cell array
nr_rrc_cell_container_t *collision = rrc_add_cell(rrc, new);
if (collision != NULL) {
nr_rrc_du_container_t *existing_du = get_du_by_assoc_id(rrc, collision->assoc_id);
const char *du_name = existing_du ? existing_du->gNB_DU_name : "unknown";
LOG_E(NR_RRC,
"Cell ID %lu already exists in DU %s (assoc_id %d), rejecting requesting gNB-DU\n",
new->info.cell_id,
du_name,
collision->assoc_id);
rrc_free_cell_container(new);
fail.cause = F1AP_CauseRadioNetwork_cell_not_available;
rrc->mac_rrc.f1_setup_failure(assoc_id, &fail);
return;
}
/* ITTI will free the setup request message via free(). So the memory
* "inside" of the message will remain, but the "outside" container no, so
* allocate memory and copy it in */
// DU is accepted, add it to tree and add cell to DU's cell array
nr_rrc_du_container_t *du = calloc_or_fail(1, sizeof(*du));
du->mtc = new->mtc; // TODO: remove in upcoming commit (kept for backward compatibility)
du->assoc_id = assoc_id;
du->gNB_DU_id = req->gNB_DU_id;
du->gNB_DU_name = strdup(req->gNB_DU_name ? req->gNB_DU_name : "N/A");
memcpy(du->rrc_ver, req->rrc_ver, sizeof(du->rrc_ver));
du->setup_req = calloc(1,sizeof(*du->setup_req));
AssertFatal(du->setup_req, "out of memory\n");
// Copy F1AP message
@@ -389,9 +434,14 @@ void rrc_gNB_process_f1_setup_req(f1ap_setup_req_t *req, sctp_assoc_t assoc_id)
// MIB can be null and configured later via DU Configuration Update
du->mib = mib;
du->sib1 = sib1;
du->mtc = mtc;
RB_INSERT(rrc_du_tree, &rrc->dus, du);
rrc->num_dus++;
// Initialize cell array for this DU
seq_arr_init(&du->cells, sizeof(nr_rrc_cell_container_t *));
nr_rrc_du_container_t *du_collision = rrc_add_du(rrc, du);
AssertFatal(du_collision == NULL, "rrc_add_du should succeed for new DU (assoc_id %d)", assoc_id);
nr_rrc_cell_container_t *added = rrc_add_cell_to_du(&du->cells, new);
AssertFatal(added != NULL, "Failed to add cell %ld to DU %ld\n", new->info.cell_id, du->gNB_DU_id);
LOG_I(NR_RRC, "DU %ld: Added cell %ld\n", du->gNB_DU_id, new->info.cell_id);
served_cells_to_activate_t cell = {
.plmn = cell_info->plmn,
@@ -407,7 +457,7 @@ void rrc_gNB_process_f1_setup_req(f1ap_setup_req_t *req, sctp_assoc_t assoc_id)
nr_SIBs_t *sib = (nr_SIBs_t *)seq_arr_at(sibs, i);
switch (sib->SIB_type) {
case 2: {
NR_SSB_MTC_t *ssbmtc = get_ssb_mtc(mtc);
NR_SSB_MTC_t *ssbmtc = get_ssb_mtc(new->mtc);
sib->SIB_size = do_SIB2_NR(&sib->SIB_buffer, ssbmtc);
cell.SI_msg[cell.num_SI].SI_container = sib->SIB_buffer;
cell.SI_msg[cell.num_SI].SI_container_length = sib->SIB_size;
@@ -420,8 +470,8 @@ void rrc_gNB_process_f1_setup_req(f1ap_setup_req_t *req, sctp_assoc_t assoc_id)
}
}
if (du->mib != NULL && du->sib1 != NULL)
label_intra_frequency_neighbours(rrc, du, cell_info);
if (new->mib != NULL &&new->sib1 != NULL)
label_intra_frequency_neighbours(rrc, du, new);
f1ap_setup_resp_t resp = {.transaction_id = req->transaction_id,
.num_cells_to_activate = 1,
@@ -557,8 +607,10 @@ void rrc_gNB_process_f1_du_configuration_update(f1ap_gnb_du_configuration_update
}
}
if (du->mib != NULL && du->sib1 != NULL) {
const f1ap_served_cell_info_t *cell_info = &du->setup_req->cell[0].info;
label_intra_frequency_neighbours(rrc, du, cell_info);
nr_rrc_cell_container_t *cell = get_cell_by_cell_id(&rrc->cells, conf_up->cell_to_modify[0].old_nr_cellid);
if (cell != NULL && cell->assoc_id == du->assoc_id) {
label_intra_frequency_neighbours(rrc, du, cell);
}
}
}
@@ -599,10 +651,9 @@ void rrc_CU_process_f1_lost_connection(gNB_RRC_INST *rrc, f1ap_lost_connection_t
if (du->setup_req)
free_f1ap_setup_request(du->setup_req);
free(du->setup_req);
nr_rrc_du_container_t *removed = RB_REMOVE(rrc_du_tree, &rrc->dus, du);
DevAssert(removed != NULL);
free(du);
rrc->num_dus--;
// Clean up DU and associated cell resources
rrc_cleanup_du(rrc, du);
int num = invalidate_du_connections(rrc, assoc_id);
if (num > 0) {