Files
openairinterface5g/openair2/RRC/NR/rrc_gNB_du.c
T

708 lines
28 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*
* Licensed to the OpenAirInterface (OAI) Software Alliance under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The OpenAirInterface Software Alliance licenses this file to You under
* the OAI Public License, Version 1.1 (the "License"); you may not use this file
* except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.openairinterface.org/?page_id=698
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*-------------------------------------------------------------------------------
* For more information about the OpenAirInterface (OAI) Software Alliance:
* contact@openairinterface.org
*/
#include "rrc_gNB_du.h"
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
#include "F1AP_CauseMisc.h"
#include "F1AP_CauseProtocol.h"
#include "F1AP_CauseRadioNetwork.h"
#include "T.h"
#include "asn_codecs.h"
#include "assertions.h"
#include "common/ran_context.h"
#include "common/utils/nr/nr_common.h"
#include "common/utils/T/T.h"
#include "common/utils/alg/foreach.h"
#include "common/utils/ds/seq_arr.h"
#include "constr_TYPE.h"
#include "executables/softmodem-common.h"
#include "f1ap_messages_types.h"
#include "ngap_messages_types.h"
#include "nr_rrc_defs.h"
#include "rrc_gNB_mobility.h"
#include "openair2/F1AP/f1ap_common.h"
#include "openair2/F1AP/f1ap_ids.h"
#include "lib/f1ap_interface_management.h"
#include "rrc_gNB_NGAP.h"
#include "rrc_gNB_UE_context.h"
#include "rrc_messages_types.h"
#include "s1ap_messages_types.h"
#include "tree.h"
#include "uper_decoder.h"
#include "utils.h"
#include "xer_encoder.h"
int get_dl_band(const struct f1ap_served_cell_info_t *cell_info)
{
return cell_info->mode == F1AP_MODE_TDD ? cell_info->tdd.freqinfo.band : cell_info->fdd.dl_freqinfo.band;
}
int get_ssb_scs(const struct f1ap_served_cell_info_t *cell_info)
{
return cell_info->mode == F1AP_MODE_TDD ? cell_info->tdd.tbw.scs : cell_info->fdd.dl_tbw.scs;
}
static NR_SSB_MTC_t *get_ssb_mtc(const NR_MeasurementTimingConfiguration_t *mtc)
{
// TODO verify which element of the list to pick
NR_MeasTimingList_t *mtlist = mtc->criticalExtensions.choice.c1->choice.measTimingConf->measTiming;
NR_SSB_MTC_t *ssb_mtc = calloc(1, sizeof(*ssb_mtc));
*ssb_mtc = mtlist->list.array[0]->frequencyAndTiming->ssb_MeasurementTimingConfiguration;
return ssb_mtc;
}
static int ssb_arfcn_mtc(const NR_MeasurementTimingConfiguration_t *mtc)
{
/* format has been verified when accepting MeasurementTimingConfiguration */
NR_MeasTimingList_t *mtlist = mtc->criticalExtensions.choice.c1->choice.measTimingConf->measTiming;
return mtlist->list.array[0]->frequencyAndTiming->carrierFreq;
}
int get_ssb_arfcn(const struct nr_rrc_du_container_t *du)
{
DevAssert(du != NULL && du->mtc != NULL);
return ssb_arfcn_mtc(du->mtc);
}
static int du_compare(const nr_rrc_du_container_t *a, const nr_rrc_du_container_t *b)
{
if (a->assoc_id > b->assoc_id)
return 1;
if (a->assoc_id == b->assoc_id)
return 0;
return -1; /* a->assoc_id < b->assoc_id */
}
/* Tree management functions */
RB_GENERATE/*_STATIC*/(rrc_du_tree, nr_rrc_du_container_t, entries, du_compare);
static bool rrc_gNB_plmn_matches(const gNB_RRC_INST *rrc, const f1ap_served_cell_info_t *info)
{
const gNB_RrcConfigurationReq *conf = &rrc->configuration;
return conf->num_plmn == 1 // F1 supports only one
&& conf->plmn[0].mcc == info->plmn.mcc && conf->plmn[0].mnc == info->plmn.mnc;
}
static bool extract_sys_info(const f1ap_gnb_du_system_info_t *sys_info, NR_MIB_t **mib, NR_SIB1_t **sib1)
{
DevAssert(sys_info != NULL);
DevAssert(mib != NULL);
DevAssert(sib1 != NULL);
asn_dec_rval_t dec_rval = uper_decode_complete(NULL, &asn_DEF_NR_MIB, (void **)mib, sys_info->mib, sys_info->mib_length);
if (dec_rval.code != RC_OK) {
LOG_E(NR_RRC, "Failed to decode NR_MIB (%zu bits) of DU\n", dec_rval.consumed);
ASN_STRUCT_FREE(asn_DEF_NR_MIB, *mib);
return false;
}
if (sys_info->sib1) {
dec_rval = uper_decode_complete(NULL, &asn_DEF_NR_SIB1, (void **)sib1, sys_info->sib1, sys_info->sib1_length);
if (dec_rval.code != RC_OK) {
ASN_STRUCT_FREE(asn_DEF_NR_MIB, *mib);
ASN_STRUCT_FREE(asn_DEF_NR_SIB1, *sib1);
LOG_E(NR_RRC, "Failed to decode NR_SIB1 (%zu bits), rejecting DU\n", dec_rval.consumed);
return false;
}
}
if (LOG_DEBUGFLAG(DEBUG_ASN1)) {
xer_fprint(stdout, &asn_DEF_NR_MIB, *mib);
xer_fprint(stdout, &asn_DEF_NR_SIB1, *sib1);
}
return true;
}
static NR_MeasurementTimingConfiguration_t *extract_mtc(uint8_t *buf, int buf_len)
{
NR_MeasurementTimingConfiguration_t *mtc = NULL;
asn_dec_rval_t dec_rval = uper_decode_complete(NULL, &asn_DEF_NR_MeasurementTimingConfiguration, (void **)&mtc, buf, buf_len);
if (dec_rval.code != RC_OK) {
ASN_STRUCT_FREE(asn_DEF_NR_MeasurementTimingConfiguration, mtc);
return NULL;
}
/* verify that it has the format we need */
if (mtc->criticalExtensions.present != NR_MeasurementTimingConfiguration__criticalExtensions_PR_c1
|| mtc->criticalExtensions.choice.c1 == NULL
|| mtc->criticalExtensions.choice.c1->present != NR_MeasurementTimingConfiguration__criticalExtensions__c1_PR_measTimingConf
|| mtc->criticalExtensions.choice.c1->choice.measTimingConf == NULL
|| mtc->criticalExtensions.choice.c1->choice.measTimingConf->measTiming == NULL
|| mtc->criticalExtensions.choice.c1->choice.measTimingConf->measTiming->list.count == 0) {
LOG_E(NR_RRC, "error: measurementTimingConfiguration does not have expected format (at least one measTiming entry\n");
if (LOG_DEBUGFLAG(DEBUG_ASN1))
xer_fprint(stdout, &asn_DEF_NR_MeasurementTimingConfiguration, mtc);
ASN_STRUCT_FREE(asn_DEF_NR_MeasurementTimingConfiguration, mtc);
return NULL;
}
return mtc;
}
static int nr_cell_id_match(const void *key, const void *element)
{
const int *key_id = (const int *)key;
const neighbour_cell_configuration_t *config_element = (const neighbour_cell_configuration_t *)element;
if (*key_id < config_element->nr_cell_id) {
return -1;
} else if (*key_id == config_element->nr_cell_id) {
return 0;
}
return 1;
}
static neighbour_cell_configuration_t *get_cell_neighbour_list(const gNB_RRC_INST *rrc, const f1ap_served_cell_info_t *cell_info)
{
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);
return (neighbour_cell_configuration_t *)it;
}
const struct f1ap_served_cell_info_t *get_cell_information_by_phycellId(int phyCellId)
{
gNB_RRC_INST *rrc = RC.nrrrc[0];
nr_rrc_du_container_t *it = NULL;
RB_FOREACH (it, rrc_du_tree, &rrc->dus) {
for (int cellIdx = 0; cellIdx < it->setup_req->num_cells_available; cellIdx++) {
const f1ap_served_cell_info_t *cell_info = &(it->setup_req->cell[cellIdx].info);
if (cell_info->nr_pci == phyCellId) {
LOG_D(NR_RRC, "HO LOG: Found cell with phyCellId %d\n", phyCellId);
return cell_info;
}
}
}
return NULL;
}
static void is_intra_frequency_neighbour(void *ssb_arfcn, void *neighbour_cell)
{
uint32_t *ssb_arfcn_ptr = (uint32_t *)ssb_arfcn;
nr_neighbour_cell_t *neighbour_cell_ptr = (nr_neighbour_cell_t *)neighbour_cell;
if (*ssb_arfcn_ptr == neighbour_cell_ptr->absoluteFrequencySSB) {
LOG_D(NR_RRC, "HO LOG: found intra frequency neighbour %lu!\n", neighbour_cell_ptr->nrcell_id);
neighbour_cell_ptr->isIntraFrequencyNeighbour = true;
}
}
/**
* @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
*/
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)
{
if (!rrc->neighbour_cell_configuration)
return;
neighbour_cell_configuration_t *neighbour_cell_config = get_cell_neighbour_list(rrc, cell_info);
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);
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)
{
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++) {
const nr_neighbour_cell_t *nc = seq_arr_at(neighbour_config->neighbour_cells, ni);
if (nc->nrcell_id != cell->nr_cellid)
continue;
// current cell is in the nc config, check that config matches
if (nc->physicalCellId != cell->nr_pci) {
LOG_W(NR_RRC, "Cell %ld in neighbour config: PCI mismatch (%d vs %d)\n", cell->nr_cellid, cell->nr_pci, nc->physicalCellId);
return false;
}
if (cell->tac && nc->tac != *cell->tac) {
LOG_W(NR_RRC, "Cell %ld in neighbour config: TAC mismatch (%d vs %d)\n", cell->nr_cellid, *cell->tac, nc->tac);
return false;
}
if (ssb_arfcn != nc->absoluteFrequencySSB) {
LOG_W(NR_RRC,
"Cell %ld in neighbour config: SSB ARFCN mismatch (%d vs %d)\n",
cell->nr_cellid,
ssb_arfcn,
nc->absoluteFrequencySSB);
return false;
}
if (get_ssb_scs(cell) != nc->subcarrierSpacing) {
LOG_W(NR_RRC,
"Cell %ld in neighbour config: SCS mismatch (%d vs %d)\n",
cell->nr_cellid,
get_ssb_scs(cell),
nc->subcarrierSpacing);
return false;
}
if (cell->plmn.mcc != nc->plmn.mcc || cell->plmn.mnc != nc->plmn.mnc
|| cell->plmn.mnc_digit_length != nc->plmn.mnc_digit_length) {
LOG_W(NR_RRC,
"Cell %ld in neighbour config: PLMN mismatch (%03d.%0*d vs %03d.%0*d)\n",
cell->nr_cellid,
cell->plmn.mcc,
cell->plmn.mnc_digit_length,
cell->plmn.mnc,
nc->plmn.mcc,
nc->plmn.mnc_digit_length,
nc->plmn.mnc);
return false;
}
LOG_D(NR_RRC, "Cell %ld is neighbor of cell %ld\n", cell->nr_cellid, neighbour_config->nr_cell_id);
}
}
return true;
}
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");
gNB_RRC_INST *rrc = RC.nrrrc[0];
DevAssert(rrc);
LOG_I(NR_RRC, "Received F1 Setup Request from gNB_DU %lu (%s) on assoc_id %d\n", req->gNB_DU_id, req->gNB_DU_name, assoc_id);
// pre-fill F1 Setup Failure message
f1ap_setup_failure_t fail = {.transaction_id = F1AP_get_next_transaction_identifier(0, 0)};
// check:
// - it is one cell
// - PLMN and Cell ID matches
// - no previous DU with the same ID
// else reject
if (req->num_cells_available != 1) {
LOG_E(NR_RRC, "can only handle on DU cell, but gNB_DU %ld has %d\n", req->gNB_DU_id, req->num_cells_available);
fail.cause = F1AP_CauseRadioNetwork_gNB_CU_Cell_Capacity_Exceeded;
rrc->mac_rrc.f1_setup_failure(assoc_id, &fail);
return;
}
f1ap_served_cell_info_t *cell_info = &req->cell[0].info;
if (!rrc_gNB_plmn_matches(rrc, cell_info)) {
LOG_E(NR_RRC,
"PLMN mismatch: CU %03d.%0*d, DU %03d%0*d\n",
rrc->configuration.plmn[0].mcc,
rrc->configuration.plmn[0].mnc_digit_length,
rrc->configuration.plmn[0].mnc,
cell_info->plmn.mcc,
cell_info->plmn.mnc_digit_length,
cell_info->plmn.mnc);
fail.cause = F1AP_CauseRadioNetwork_plmn_not_served_by_the_gNB_CU;
rrc->mac_rrc.f1_setup_failure(assoc_id, &fail);
return;
}
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) {
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->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);
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))) {
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;
}
const f1ap_gnb_du_system_info_t *sys_info = req->cell[0].sys_info;
NR_MIB_t *mib = NULL;
NR_SIB1_t *sib1 = NULL;
if (sys_info != NULL && sys_info->mib != NULL && !(sys_info->sib1 == NULL && IS_SA_MODE(get_softmodem_params()))) {
if (!extract_sys_info(sys_info, &mib, &sib1)) {
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;
/* 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->setup_req = calloc(1,sizeof(*du->setup_req));
AssertFatal(du->setup_req, "out of memory\n");
// Copy F1AP message
*du->setup_req = cp_f1ap_setup_request(req);
// 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++;
served_cells_to_activate_t cell = {
.plmn = cell_info->plmn,
.nr_cellid = cell_info->nr_cellid,
.nrpci = cell_info->nr_pci,
.num_SI = 0,
};
// Encode CU SIBs and configure setup response with sysinfo
seq_arr_t *sibs = rrc->SIBs;
if (sibs) {
for (int i = 0; i < sibs->size; i++) {
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);
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;
cell.SI_msg[cell.num_SI].SI_type = sib->SIB_type;
cell.num_SI++;
} break;
default:
AssertFatal(false, "SIB%d not handled yet\n", sib->SIB_type);
}
}
}
if (du->mib != NULL && du->sib1 != NULL)
label_intra_frequency_neighbours(rrc, du, cell_info);
f1ap_setup_resp_t resp = {.transaction_id = req->transaction_id,
.num_cells_to_activate = 1,
.cells_to_activate[0] = cell};
int num = read_version(TO_STRING(NR_RRC_VERSION), &resp.rrc_ver[0], &resp.rrc_ver[1], &resp.rrc_ver[2]);
AssertFatal(num == 3, "could not read RRC version string %s\n", TO_STRING(NR_RRC_VERSION));
if (rrc->node_name != NULL)
resp.gNB_CU_name = strdup(rrc->node_name);
rrc->mac_rrc.f1_setup_response(assoc_id, &resp);
/* we need to setup one default UE for phy-test and do-ra modes in the MAC */
if (get_softmodem_params()->phy_test > 0 || get_softmodem_params()->do_ra > 0)
rrc_add_nsa_user(rrc, NULL, assoc_id);
}
static int invalidate_du_connections(gNB_RRC_INST *rrc, sctp_assoc_t assoc_id)
{
int count = 0;
seq_arr_t ue_context_to_remove;
seq_arr_init(&ue_context_to_remove, sizeof(rrc_gNB_ue_context_t *));
rrc_gNB_ue_context_t *ue_context_p = NULL;
RB_FOREACH(ue_context_p, rrc_nr_ue_tree_s, &rrc->rrc_ue_head) {
gNB_RRC_UE_t *UE = &ue_context_p->ue_context;
uint32_t ue_id = UE->rrc_ue_id;
if (UE->ho_context != NULL) {
LOG_W(NR_RRC, "DU disconnected while handover for UE %d active\n", ue_id);
nr_rrc_finalize_ho(UE);
}
f1_ue_data_t ue_data = cu_get_f1_ue_data(ue_id);
if (ue_data.du_assoc_id == assoc_id && IS_SA_MODE(get_softmodem_params())) {
/* this UE belongs to the DU that disconnected, set du_assoc_id to 0,
* meaning DU is offline, then trigger release request */
ue_data.du_assoc_id = 0;
bool success = cu_update_f1_ue_data(ue_id, &ue_data);
DevAssert(success);
ngap_cause_t cause = {.type = NGAP_CAUSE_RADIO_NETWORK, .value = NGAP_CAUSE_RADIO_NETWORK_RADIO_CONNECTION_WITH_UE_LOST};
rrc_gNB_send_NGAP_UE_CONTEXT_RELEASE_REQ(0, ue_context_p, cause);
count++;
} else {
seq_arr_push_back(&ue_context_to_remove, &ue_context_p, sizeof(ue_context_p));
}
}
for (int i = 0; i < seq_arr_size(&ue_context_to_remove); ++i) {
/* we retrieve a pointer (=iterator) to the UE context pointer
* (ue_context_p), so dereference once */
rrc_gNB_ue_context_t *p = *(rrc_gNB_ue_context_t **)seq_arr_at(&ue_context_to_remove, i);
rrc_remove_nsa_user_context(rrc, p);
}
seq_arr_free(&ue_context_to_remove, NULL);
return count;
}
static void update_cell_info(nr_rrc_du_container_t *du, const f1ap_served_cell_info_t *new_ci)
{
DevAssert(du != NULL);
DevAssert(new_ci != NULL);
AssertFatal(du->setup_req->num_cells_available == 1, "expected 1 cell for DU, but has %d\n", du->setup_req->num_cells_available);
f1ap_served_cell_info_t *ci = &du->setup_req->cell[0].info;
// make sure no memory is allocated
free_f1ap_cell(ci, NULL);
*ci = copy_f1ap_served_cell_info(new_ci);
NR_MeasurementTimingConfiguration_t *new_mtc =
extract_mtc(new_ci->measurement_timing_config, new_ci->measurement_timing_config_len);
if (new_mtc != NULL) {
ASN_STRUCT_FREE(asn_DEF_NR_MeasurementTimingConfiguration, du->mtc);
du->mtc = new_mtc;
} else {
LOG_E(NR_RRC, "error decoding MeasurementTimingConfiguration during cell update, ignoring new config\n");
ASN_STRUCT_FREE(asn_DEF_NR_MeasurementTimingConfiguration, new_mtc);
}
}
void rrc_gNB_process_f1_du_configuration_update(f1ap_gnb_du_configuration_update_t *conf_up, sctp_assoc_t assoc_id)
{
AssertFatal(assoc_id != 0, "illegal assoc_id == 0: should be -1 (monolithic) or >0 (split)\n");
gNB_RRC_INST *rrc = RC.nrrrc[0];
DevAssert(rrc);
// check:
// - it is one cell
// - PLMN and Cell ID matches
// - no previous DU with the same ID
// else reject
nr_rrc_du_container_t *du = get_du_by_assoc_id(rrc, assoc_id);
AssertError(du != NULL, return, "no DU found for assoc_id %d\n", assoc_id);
const f1ap_served_cell_info_t *info = &du->setup_req->cell[0].info;
if (conf_up->num_cells_to_add > 0) {
// Here we check if the number of cell limit is respectet, otherwise send failure
LOG_W(NR_RRC, "du_configuration_update->cells_to_add_list is not supported yet");
}
if (conf_up->num_cells_to_modify > 0) {
// here the old nrcgi is used to find the cell information, if it exist then we modify consequently otherwise we fail
AssertFatal(conf_up->num_cells_to_modify == 1, "cannot handle more than one cell!\n");
if (info->nr_cellid != conf_up->cell_to_modify[0].old_nr_cellid) {
LOG_W(NR_RRC, "no cell with ID %ld found, ignoring gNB-DU configuration update\n", conf_up->cell_to_modify[0].old_nr_cellid);
return;
}
// verify the new plmn of the cell
if (!rrc_gNB_plmn_matches(rrc, &conf_up->cell_to_modify[0].info)) {
LOG_W(NR_RRC, "PLMN does not match, ignoring gNB-DU configuration update\n");
return;
}
update_cell_info(du, &conf_up->cell_to_modify[0].info);
const f1ap_gnb_du_system_info_t *sys_info = conf_up->cell_to_modify[0].sys_info;
if (sys_info != NULL && sys_info->mib != NULL && !(sys_info->sib1 == NULL && IS_SA_MODE(get_softmodem_params()))) {
// MIB is mandatory, so will be overwritten. SIB1 is optional, so will
// only be overwritten if present in sys_info
ASN_STRUCT_FREE(asn_DEF_NR_MIB, du->mib);
if (sys_info->sib1 != NULL) {
ASN_STRUCT_FREE(asn_DEF_NR_SIB1, du->sib1);
du->sib1 = NULL;
}
NR_MIB_t *mib = NULL;
if (!extract_sys_info(sys_info, &mib, &du->sib1)) {
LOG_W(NR_RRC, "cannot update sys_info for DU %ld\n", du->setup_req->gNB_DU_id);
} else {
DevAssert(mib != NULL);
du->mib = mib;
LOG_I(NR_RRC, "update system information of DU %ld\n", du->setup_req->gNB_DU_id);
}
}
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);
}
}
if (conf_up->num_cells_to_delete > 0) {
// delete the cell and send cell to desactive IE in the response.
LOG_W(NR_RRC, "du_configuration_update->cells_to_delete_list is not supported yet");
}
for (int i = 0; i < conf_up->num_status; ++i) {
const f1ap_cell_status_t *cs = &conf_up->status[i];
const char *status = cs->service_state == F1AP_STATE_IN_SERVICE ? "in service" : "out of service";
const plmn_id_t *p = &cs->plmn;
LOG_I(NR_RRC, "cell PLMN %03d.%0*d Cell ID %ld is %s\n", p->mcc, p->mnc_digit_length, p->mnc, cs->nr_cellid, status);
}
/* Send DU Configuration Acknowledgement */
f1ap_gnb_du_configuration_update_acknowledge_t ack = {.transaction_id = conf_up->transaction_id};
rrc->mac_rrc.gnb_du_configuration_update_acknowledge(assoc_id, &ack);
}
void rrc_CU_process_f1_lost_connection(gNB_RRC_INST *rrc, f1ap_lost_connection_t *lc, sctp_assoc_t assoc_id)
{
AssertFatal(assoc_id != 0, "illegal assoc_id == 0: should be -1 (monolithic) or >0 (split)\n");
(void) lc; // unused for the moment
nr_rrc_du_container_t e = {.assoc_id = assoc_id};
nr_rrc_du_container_t *du = RB_FIND(rrc_du_tree, &rrc->dus, &e);
if (du == NULL) {
LOG_W(NR_RRC, "no DU connected or not found for assoc_id %d: F1 Setup Failed?\n", assoc_id);
return;
}
f1ap_setup_req_t *req = du->setup_req;
LOG_I(NR_RRC, "releasing DU ID %ld (%s) on assoc_id %d\n", req->gNB_DU_id, req->gNB_DU_name, assoc_id);
ASN_STRUCT_FREE(asn_DEF_NR_MIB, du->mib);
ASN_STRUCT_FREE(asn_DEF_NR_SIB1, du->sib1);
ASN_STRUCT_FREE(asn_DEF_NR_MeasurementTimingConfiguration, du->mtc);
/* TODO: free setup request */
nr_rrc_du_container_t *removed = RB_REMOVE(rrc_du_tree, &rrc->dus, du);
DevAssert(removed != NULL);
rrc->num_dus--;
int num = invalidate_du_connections(rrc, assoc_id);
if (num > 0) {
LOG_I(NR_RRC, "%d UEs lost through DU disconnect\n", num);
}
}
nr_rrc_du_container_t *get_du_for_ue(gNB_RRC_INST *rrc, uint32_t ue_id)
{
f1_ue_data_t ue_data = cu_get_f1_ue_data(ue_id);
return get_du_by_assoc_id(rrc, ue_data.du_assoc_id);
}
nr_rrc_du_container_t *get_du_by_assoc_id(gNB_RRC_INST *rrc, sctp_assoc_t assoc_id)
{
nr_rrc_du_container_t e = {.assoc_id = assoc_id};
return RB_FIND(rrc_du_tree, &rrc->dus, &e);
}
/* \brief find DU by cell ID. Note: currently the CU is limited to one cell per
* DU, hence here, DU == cell. Modify this to look up a specific cell. */
nr_rrc_du_container_t *get_du_by_cell_id(gNB_RRC_INST *rrc, uint64_t cell_id)
{
nr_rrc_du_container_t *du = NULL;
RB_FOREACH(du, rrc_du_tree, &rrc->dus) {
if (cell_id == du->setup_req->cell[0].info.nr_cellid)
return du;
}
return NULL;
}
void dump_du_info(const gNB_RRC_INST *rrc, FILE *f)
{
fprintf(f, "%ld connected DUs \n", rrc->num_dus);
int i = 1;
nr_rrc_du_container_t *du = NULL;
/* cast is necessary to eliminate warning "discards ‘const’ qualifier" */
RB_FOREACH(du, rrc_du_tree, &((gNB_RRC_INST *)rrc)->dus) {
const f1ap_setup_req_t *sr = du->setup_req;
fprintf(f, "[%d] DU ID %ld (%s) ", i++, sr->gNB_DU_id, sr->gNB_DU_name);
if (du->assoc_id == -1) {
fprintf(f, "integrated DU-CU");
} else {
fprintf(f, "assoc_id %d", du->assoc_id);
}
const f1ap_served_cell_info_t *info = &sr->cell[0].info;
fprintf(f, ": nrCellID %ld, PCI %d, SSB ARFCN %d\n", info->nr_cellid, info->nr_pci, get_ssb_arfcn(du));
if (info->mode == F1AP_MODE_TDD) {
const f1ap_nr_frequency_info_t *fi = &info->tdd.freqinfo;
const f1ap_transmission_bandwidth_t *tb = &info->tdd.tbw;
fprintf(f, " TDD: band %d ARFCN %d SCS %d (kHz) PRB %d\n", fi->band, fi->arfcn, 15 * (1 << tb->scs), tb->nrb);
} else {
const f1ap_nr_frequency_info_t *dfi = &info->fdd.dl_freqinfo;
const f1ap_transmission_bandwidth_t *dtb = &info->fdd.dl_tbw;
fprintf(f, " FDD: DL band %d ARFCN %d SCS %d (kHz) PRB %d\n", dfi->band, dfi->arfcn, 15 * (1 << dtb->scs), dtb->nrb);
const f1ap_nr_frequency_info_t *ufi = &info->fdd.ul_freqinfo;
const f1ap_transmission_bandwidth_t *utb = &info->fdd.ul_tbw;
fprintf(f, " UL band %d ARFCN %d SCS %d (kHz) PRB %d\n", ufi->band, ufi->arfcn, 15 * (1 << utb->scs), utb->nrb);
}
}
}
nr_rrc_du_container_t *find_target_du(gNB_RRC_INST *rrc, sctp_assoc_t source_assoc_id)
{
nr_rrc_du_container_t *target_du = NULL;
nr_rrc_du_container_t *it = NULL;
bool next_du = false;
RB_FOREACH (it, rrc_du_tree, &rrc->dus) {
if (next_du == false && source_assoc_id != it->assoc_id) {
continue;
} else if (source_assoc_id == it->assoc_id) {
next_du = true;
} else {
target_du = it;
break;
}
}
if (target_du == NULL) {
RB_FOREACH (it, rrc_du_tree, &rrc->dus) {
if (source_assoc_id == it->assoc_id) {
continue;
} else {
target_du = it;
break;
}
}
}
return target_du;
}
void trigger_f1_reset(gNB_RRC_INST *rrc, sctp_assoc_t du_assoc_id)
{
f1ap_reset_t reset = {
.transaction_id = F1AP_get_next_transaction_identifier(0, 0),
.cause = F1AP_CAUSE_TRANSPORT,
.cause_value = 1, // F1AP_CauseTransport_transport_resource_unavailable
.reset_type = F1AP_RESET_ALL, // DU does not support partial reset yet
};
rrc->mac_rrc.f1_reset(du_assoc_id, &reset);
}