Merge remote-tracking branch 'origin/rrc-sibs' into integration_2026_w16 (!3983)
Add SIB 3,4 support, configurable SIB2 and refactor CU/DU SIB management
This MR makes neighbour and inter-frequency configuration drive how
SIB2/SIB3/SIB4 are built and sent from CU to DUs. It standardizes SIB
payloads as byte_array_t with typed SIB IDs across RRC, F1AP and MAC,
reducing ad‑hoc buffer handling. Neighbour parsing, validation and
lookup are tightened.
Changes
- Minor refactor to gNB neighbour parsing and storage (shared PLMN
extraction, safer allocation, etc).
- Represent SIB containers uniformly as byte_array_t plus nr_sib_type_t,
and adapt F1AP, MAC and RRC users to the new container API.
- Make SIB2 cell-reselection information fully config-driven with
explicit bounds checking and SIB2 ASN.1 building from that config.
- Generate SIB3 intra-frequency neighbours from the per-cell neighbour
list and propagate them from CU to DU over F1, with MAC
decoding/attaching them to SystemInformation.
- Generate SIB4 inter-frequency neighbours from a new frequency_list
plus neighbour SIB3/SIB4 offsets, and propagate them from CU to DU
over F1, with MAC decoding/attaching them.
- Add basic ASN.1 round-trip tests for SIB2/SIB3/SIB4 and SIB4 range
checks, and update RRC docs to describe the neighbour/inter-frequency
configuration model.
Testing:
1. in gNB conf file:
cu_sibs = (2, 3, 4);
2. Update neighbour config file with SIB3/SIB4 conf:
(see documentation)
3. run gNB and UE as usual
---
Logs & configs: see MR on Gitlab
This commit is contained in:
@@ -17,6 +17,7 @@ gNBs =
|
||||
// Tracking area code, 0x0000 and 0xfffe are reserved values
|
||||
tracking_area_code = 1;
|
||||
plmn_list = ({ mcc = 208; mnc = 99; mnc_length = 2; snssaiList = ({ sst = 1, sd = 0xffffff }) });
|
||||
cu_sibs = ( 2, 3, 4 );
|
||||
@include "neighbour-config.conf"
|
||||
|
||||
nr_cellid = 12345678L;
|
||||
|
||||
@@ -5,6 +5,57 @@
|
||||
# for the 2-cell rfsim setup (gNB_ID 0xe00 & 0xb00) #
|
||||
############################################################
|
||||
|
||||
############################################################
|
||||
# SIB2 #
|
||||
############################################################
|
||||
|
||||
sib2_config : {
|
||||
q_Hyst = 0;
|
||||
cellReselectionPriority = 0;
|
||||
threshServingLowP = 0;
|
||||
threshServingLowQ = 0;
|
||||
s_NonIntraSearchP = -1;
|
||||
s_NonIntraSearchQ = -1;
|
||||
q_RxLevMin = -56;
|
||||
q_QualMin = -1;
|
||||
s_IntraSearchP = 22;
|
||||
s_IntraSearchQ = -1;
|
||||
t_ReselectionNR = 1;
|
||||
deriveSSB_IndexFromCell = 1;
|
||||
|
||||
speed_t_Evaluation = 0;
|
||||
speed_t_HystNormal = 0;
|
||||
speed_n_CellChangeMedium = 1;
|
||||
speed_n_CellChangeHigh = 2;
|
||||
speed_sf_Medium = 1;
|
||||
speed_sf_High = 0;
|
||||
};
|
||||
|
||||
############################################################
|
||||
# Per-frequency SIB4 inter-frequency configuration #
|
||||
############################################################
|
||||
|
||||
frequency_list = (
|
||||
{
|
||||
absoluteFrequencySSB = 621312;
|
||||
subcarrierSpacing = 1; # 30 kHz
|
||||
band = 78;
|
||||
|
||||
frequency_config = (
|
||||
{
|
||||
cellReselectionPriority = 5;
|
||||
threshX_HighP = 10;
|
||||
threshX_LowP = 6;
|
||||
threshX_HighQ = -1;
|
||||
threshX_LowQ = -1;
|
||||
q_OffsetFreq = 0;
|
||||
q_RxLevMin = -56;
|
||||
t_ReselectionNR = 1;
|
||||
}
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
neighbour_list = (
|
||||
##########################################################
|
||||
# Entry USED BY gNB_ID = 0xe00 (nr_cellid = 1) #
|
||||
@@ -21,6 +72,9 @@ neighbour_list = (
|
||||
band = 78;
|
||||
plmn = { mcc = 208; mnc = 99; mnc_length = 2 };
|
||||
tracking_area_code = 1;
|
||||
q_OffsetCell = 2;
|
||||
q_RxLevMinOffsetCell= 2;
|
||||
q_QualMinOffsetCell = 1;
|
||||
}
|
||||
);
|
||||
},
|
||||
@@ -40,6 +94,9 @@ neighbour_list = (
|
||||
band = 78;
|
||||
plmn = { mcc = 208; mnc = 99; mnc_length = 2 };
|
||||
tracking_area_code = 1;
|
||||
q_OffsetCell = 2;
|
||||
q_RxLevMinOffsetCell= 2;
|
||||
q_QualMinOffsetCell = 1;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@@ -48,8 +48,8 @@ typedef union checkedparam {
|
||||
int num_okintval; /* number of valid values in the checkingval array */
|
||||
} s1a;
|
||||
struct {
|
||||
int (*f2)(configmodule_interface_t *cfg,
|
||||
paramdef_t *param); /* check an integer against an authorized range, defined by its min and max value */
|
||||
int (*f2)(const configmodule_interface_t *cfg,
|
||||
const paramdef_t *param); /* check an integer against an authorized range, defined by its min and max value */
|
||||
int okintrange[CONFIG_MAX_NUMCHECKVAL]; /* integer array, store min and max values */
|
||||
|
||||
} s2;
|
||||
|
||||
@@ -183,14 +183,35 @@ int config_check_intval(configmodule_interface_t *cfg, paramdef_t *param)
|
||||
return -1;
|
||||
}
|
||||
|
||||
for ( int i=0; i<param->chkPptr->s1.num_okintval ; i++) {
|
||||
if( *(param->uptr) == param->chkPptr->s1.okintval[i] ) {
|
||||
return 0;
|
||||
if (param->type == TYPE_INT32 || param->type == TYPE_INT) {
|
||||
if (param->iptr == NULL) {
|
||||
fprintf(stderr, "[CONFIG] config_check_intval: %s: NULL iptr\n", param->optname);
|
||||
return -1;
|
||||
}
|
||||
const int v = *param->iptr;
|
||||
for (int i = 0; i < param->chkPptr->s1.num_okintval; i++) {
|
||||
if (v == param->chkPptr->s1.okintval[i])
|
||||
return 0;
|
||||
}
|
||||
fprintf(stderr, "[CONFIG] config_check_intval: %s: %i invalid value, authorized values:\n ", param->optname, v);
|
||||
for (int i = 0; i < param->chkPptr->s1.num_okintval; i++) {
|
||||
fprintf(stderr, " %i", param->chkPptr->s1.okintval[i]);
|
||||
}
|
||||
fprintf(stderr, " \n");
|
||||
return -1;
|
||||
} else {
|
||||
if (param->uptr == NULL) {
|
||||
fprintf(stderr, "[CONFIG] config_check_intval: %s: NULL uptr\n", param->optname);
|
||||
return -1;
|
||||
}
|
||||
for (int i = 0; i < param->chkPptr->s1.num_okintval; i++) {
|
||||
if (*(param->uptr) == (uint32_t)param->chkPptr->s1.okintval[i]) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
print_intvalueerror(param, "config_check_intval", param->chkPptr->s1.okintval, param->chkPptr->s1.num_okintval);
|
||||
return -1;
|
||||
}
|
||||
|
||||
print_intvalueerror(param,"config_check_intval", param->chkPptr->s1.okintval,param->chkPptr->s1.num_okintval);
|
||||
return -1;
|
||||
}
|
||||
|
||||
int config_check_modify_integer(configmodule_interface_t *cfg, paramdef_t *param)
|
||||
@@ -211,15 +232,38 @@ int config_check_modify_integer(configmodule_interface_t *cfg, paramdef_t *param
|
||||
return -1;
|
||||
}
|
||||
|
||||
int config_check_intrange(configmodule_interface_t *cfg, paramdef_t *param)
|
||||
int config_check_intrange(const configmodule_interface_t *cfg, const paramdef_t *param)
|
||||
{
|
||||
UNUSED(cfg);
|
||||
if( *(param->iptr) >= param->chkPptr->s2.okintrange[0] && *(param->iptr) <= param->chkPptr->s2.okintrange[1] ) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
fprintf(stderr,"[CONFIG] config_check_intrange: %s: %i invalid value, authorized range: %i %i\n",
|
||||
param->optname, (int)*(param->uptr), param->chkPptr->s2.okintrange[0], param->chkPptr->s2.okintrange[1]);
|
||||
fprintf(stderr,
|
||||
"[CONFIG] config_check_intrange: %s: %i invalid value, authorized range: %i %i\n",
|
||||
param->optname,
|
||||
(int)*(param->iptr),
|
||||
param->chkPptr->s2.okintrange[0],
|
||||
param->chkPptr->s2.okintrange[1]);
|
||||
return -1;
|
||||
}
|
||||
|
||||
int config_check_uintrange(const configmodule_interface_t *cfg, const paramdef_t *param)
|
||||
{
|
||||
(void)cfg;
|
||||
const uint32_t *v = param->uptr;
|
||||
const int *range = param->chkPptr->s2.okintrange;
|
||||
|
||||
if (*v >= (uint32_t)range[0] && *v <= (uint32_t)range[1]) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
fprintf(stderr,
|
||||
"[CONFIG] config_check_uintrange: %s: %u invalid, authorized range: %i %i\n",
|
||||
param->optname,
|
||||
*v,
|
||||
range[0],
|
||||
range[1]);
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
||||
@@ -51,7 +51,8 @@ int config_get_processedint(configmodule_interface_t *cfg, paramdef_t *cfgoption
|
||||
/* functions to be used in parameters definition, to check parameters values */
|
||||
int config_check_intval(configmodule_interface_t *cfg, paramdef_t *param);
|
||||
int config_check_modify_integer(configmodule_interface_t *cfg, paramdef_t *param);
|
||||
int config_check_intrange(configmodule_interface_t *cfg, paramdef_t *param);
|
||||
int config_check_intrange(const configmodule_interface_t *cfg, const paramdef_t *param);
|
||||
int config_check_uintrange(const configmodule_interface_t *cfg, const paramdef_t *param);
|
||||
int config_check_strval(configmodule_interface_t *cfg, paramdef_t *param);
|
||||
int config_checkstr_assign_integer(configmodule_interface_t *cfg, paramdef_t *param);
|
||||
const paramdef_t *config_get_paramdef_from_name(const paramdef_t *pd, int num, const char *name);
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
#include <stdlib.h>
|
||||
#include "assertions.h"
|
||||
#include "common/utils/utils.h"
|
||||
#include "common/utils/ds/byte_array.h"
|
||||
|
||||
#define MAX_SI_GROUPS 3
|
||||
#define NR_MAX_PDSCH_TBS 3824
|
||||
@@ -79,10 +80,32 @@ static inline const char *rnti_types(nr_rnti_type_t rr)
|
||||
// the total shift is 2 * 15, in dB scale thats 10log10(2^(15*2))
|
||||
#define SQ15_SQUARED_NORM_FACTOR_DB 90.3089986992
|
||||
|
||||
typedef enum {
|
||||
NR_SIB_1 = 1,
|
||||
NR_SIB_2,
|
||||
NR_SIB_3,
|
||||
NR_SIB_4,
|
||||
NR_SIB_5,
|
||||
NR_SIB_6,
|
||||
NR_SIB_7,
|
||||
NR_SIB_8,
|
||||
NR_SIB_9,
|
||||
NR_SIB_10,
|
||||
NR_SIB_11,
|
||||
NR_SIB_12,
|
||||
NR_SIB_13,
|
||||
NR_SIB_14,
|
||||
NR_SIB_15,
|
||||
NR_SIB_16,
|
||||
NR_SIB_17,
|
||||
NR_SIB_18,
|
||||
NR_SIB_19,
|
||||
NR_SIB_20,
|
||||
NR_SIB_21,
|
||||
} nr_sib_type_t;
|
||||
|
||||
typedef struct {
|
||||
uint8_t *SIB_buffer;
|
||||
int SIB_size;
|
||||
int SIB_type;
|
||||
nr_sib_type_t SIB_type;
|
||||
} nr_SIBs_t;
|
||||
|
||||
typedef struct nr_bandentry_s {
|
||||
|
||||
+3
-3
@@ -154,7 +154,7 @@ These modes of operation are supported:
|
||||
- RedCap SIB1 v17 IEs supported
|
||||
- Coexistence of RedCap and Normal UEs
|
||||
- Handling of RedCap capability for small PDCP/RLC SN size
|
||||
- Scheduling of other SIBs (2, 19)
|
||||
- Scheduling of other SIBs (2, 3, 4, 19)
|
||||
- NTN
|
||||
- Support downlinkHARQ-FeedbackDisabled-r17
|
||||
- Support for 32 PDSCH and PUSCH HARQ processes per UE
|
||||
@@ -194,7 +194,7 @@ These modes of operation are supported:
|
||||
|
||||
- NR RRC (38.331) Rel 17 messages using new [asn1c](https://github.com/mouse07410/asn1c)
|
||||
- LTE RRC (36.331) also updated to Rel 15
|
||||
- Generation of system information (SIB2)
|
||||
- Generation of system information (SIB2, SIB3, SIB4)
|
||||
- RRC can configure PDCP and SDAP (through E1), and RLC and MAC (through F1)
|
||||
- Interface with GTP-U (tunnel creation/handling for S1-U (NSA), N3 (SA), F1 interfaces)
|
||||
- Integration of RRC messages and procedures supporting UE 5G SA connection
|
||||
@@ -380,7 +380,7 @@ These modes of operation are supported:
|
||||
- MIB processing
|
||||
- Scheduling of system information block 1 (SIB1) reception
|
||||
* Other system information
|
||||
- Scheduling of other system information blocks reception
|
||||
- Scheduling of other system information blocks reception (SIB2, SIB3, SIB4)
|
||||
* Random access procedure (needs improvement, there is still not a clear separation between MAC and PHY)
|
||||
- Mapping SSBs to multiple ROs
|
||||
- Scheduling of PRACH
|
||||
|
||||
@@ -633,6 +633,98 @@ sequenceDiagram
|
||||
Note over TargetCU: Then: encode Handover Command, send NGAP HANDOVER REQUEST ACKNOWLEDGE
|
||||
```
|
||||
|
||||
### Neighbour cells
|
||||
|
||||
#### SIB3/SIB4 and measurement-gap implementation
|
||||
|
||||
The following section documents the implementation-level control flow
|
||||
for SIB3/SIB4 and measurement-gap handling in current OAI, using the configured
|
||||
neighbor cell list as a shared input model.
|
||||
|
||||
Briefly, the three procedures are:
|
||||
- SIB3: CU derives and provides intra-frequency neighbour SI, which DU broadcasts
|
||||
and UE uses for autonomous idle/inactive intra-frequency reselection.
|
||||
- SIB4: CU derives and provides inter-frequency carrier/neighbour SI, which DU
|
||||
broadcasts and UE uses for autonomous idle/inactive inter-frequency reselection.
|
||||
- MeasGap: CU/DU coordinate dedicated `MeasGapConfig` in UE `MeasConfig`, DU
|
||||
scheduler interrupts transmission (`nr_measgap_scheduling()`) for the UE to
|
||||
apply the MeasGap configuration in connected mode for gap-based measurements that
|
||||
are sent as `MeasurementReport` and processed at CU-CP (`rrc_gNB_process_MeasurementReport()`).
|
||||
Event-driven reports (for example A3) are used by CU-side mobility logic and can
|
||||
trigger handover procedures.
|
||||
|
||||
### MeasGap
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant DM as Data model (neighbour_cell_configuration)
|
||||
participant CU as CU-CP (RRC)
|
||||
participant DU as DU (MAC/RRC)
|
||||
participant UE as UE
|
||||
|
||||
CU-->>DM: Read serving + neighbour frequency/PCI/band data
|
||||
DM-->>CU: Provide neighbour/frequency inputs for MeasConfig
|
||||
CU->>CU: nr_rrc_get_measconfig
|
||||
Note over CU: Build UE MeasConfig
|
||||
CU->>CU: get_meas_timing_config (cell.mtc, ue.measConfig)
|
||||
Note over CU: Check distinct ssbFrequency
|
||||
alt one frequency only
|
||||
Note over CU: returns NULL, no meas_timing_config sent to DU
|
||||
else multiple frequencies
|
||||
CU->>DU: F1AP UE Context Setup Request
|
||||
Note over CU,DU: Includes cu_to_du_rrc_info.meas_timing_config
|
||||
DU->>DU: create_measgap_config
|
||||
DU->>DU: encode_measgap_config
|
||||
DU-->>CU: F1AP UE Context Setup Response
|
||||
Note over DU,CU: Includes du_to_cu_rrc_info.meas_gap_config
|
||||
CU->>CU: get_meas_gap_config
|
||||
Note over CU: Decode/store gap in UE MeasConfig (UE.measConfig.measGapConfig)
|
||||
CU->>UE: RRCReconfiguration
|
||||
Note over CU,UE: Includes MeasConfig.measGapConfig
|
||||
rect rgba(210, 235, 255, 0.35)
|
||||
Note over DU,UE: Measurement-gap window
|
||||
DU->>DU: nr_measgap_scheduling
|
||||
Note over DU: Interrupt transmission for meas gap
|
||||
UE->>UE: nr_rrc_handle_meas_indication
|
||||
Note over UE: Perform gap-based neighbour/inter-frequency measurements
|
||||
UE-->>CU: MeasurementReport
|
||||
end
|
||||
Note over UE,CU: UL-DCCH MeasurementReport for configured events (e.g. A3)
|
||||
CU->>CU: rrc_gNB_process_MeasurementReport
|
||||
Note over CU: Run mobility decision logic (may trigger handover)
|
||||
end
|
||||
```
|
||||
|
||||
### SIB3/SIB4
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant DM as Data model (neighbour_cell_configuration)
|
||||
participant CU as CU-CP (RRC)
|
||||
participant DU as DU
|
||||
participant UE as UE
|
||||
|
||||
CU->>CU: cp_f1_served_cell_info_to_cell
|
||||
CU->>DM: get_cell_neighbour_list
|
||||
DM-->>CU: neighbour_cell_configuration for serving cell
|
||||
CU->>CU: get_ssb_arfcn
|
||||
Note over CU: Derive serving_ssb_arfcn from cell MTC
|
||||
loop for each configured neighbour
|
||||
Note over CU: Compare neighbour.absoluteFrequencySSB vs serving_ssb_arfcn
|
||||
alt equal
|
||||
CU->>CU: get_sib3_intra_freq_neighbors
|
||||
else different
|
||||
CU->>CU: get_sib4_inter_freq_neighbors
|
||||
end
|
||||
end
|
||||
CU-->>DU: F1AP F1 Setup Response
|
||||
DU-->>UE: BCCH-DL-SCH-Message
|
||||
Note over DU,UE: Broadcast SystemInformation including configured SIB3/SIB4
|
||||
UE->>UE: nr_rrc_ue_decode_NR_BCCH_DL_SCH_Message
|
||||
Note over UE: Evaluate idle/inactive reselection criteria and timers
|
||||
Note over UE,DU: After reselection, access/registration continues on selected cell DU as needed
|
||||
```
|
||||
|
||||
## UE Context
|
||||
|
||||
UE context information is stored in `gNB_RRC_UE_t`, which includes:
|
||||
|
||||
+158
-6
@@ -94,7 +94,81 @@ the [MAC configuration](../MAC/mac-usage.md) as well for SIB configuration.
|
||||
deactivated, a transparent SDAP header is prepended to packets, but no
|
||||
further processing is being done.
|
||||
- `cu_sibs` (default: `[]`) list of SIBs to give to the DU for transmission.
|
||||
Currently, SIB2 is supported.
|
||||
Currently supported:
|
||||
- SIB2: serving-cell reselection parameters (configured in `sib2_config`)
|
||||
- SIB3: intra-frequency neighbour cell list (neighbours on the same SSB ARFCN as the serving cell)
|
||||
- SIB4: inter-frequency carriers + neighbour lists, grouped per `(absoluteFrequencySSB, subcarrierSpacing)`. Per-frequency fields (e.g. `cellReselectionPriority`, `threshX_HighP`, `threshX_LowP`, `q_OffsetFreq`) come from `frequency_list`
|
||||
|
||||
Example activation:
|
||||
```
|
||||
cu_sibs = ( 2, 3, 4 );
|
||||
```
|
||||
SIB2 is configured per-gNB in `sib2_config` (see below). SIB3/SIB4 are derived from the neighbour configuration:
|
||||
- `neighbour_list` / `neighbour_cell_configuration`: neighbour identity + per-neighbour offsets (`q_OffsetCell`, etc.)
|
||||
- `frequency_list`: per-frequency SIB4 reselection parameters (priority/thresholds/`q_OffsetFreq`)
|
||||
|
||||
Example `gNBs.[0].sib2_config`:
|
||||
```
|
||||
cu_sibs = ( 2 );
|
||||
|
||||
sib2_config : {
|
||||
q_Hyst = 0;
|
||||
cellReselectionPriority = 0;
|
||||
threshServingLowP = 0;
|
||||
threshServingLowQ = 4;
|
||||
s_NonIntraSearchP = 10;
|
||||
s_NonIntraSearchQ = 8;
|
||||
q_RxLevMin = -56;
|
||||
q_QualMin = -18;
|
||||
s_IntraSearchP = 22;
|
||||
s_IntraSearchQ = 20;
|
||||
t_ReselectionNR = 1;
|
||||
deriveSSB_IndexFromCell = 1;
|
||||
|
||||
speed_t_Evaluation = 0;
|
||||
speed_t_HystNormal = 0;
|
||||
speed_n_CellChangeMedium = 1;
|
||||
speed_n_CellChangeHigh = 2;
|
||||
speed_sf_Medium = 1;
|
||||
speed_sf_High = 0;
|
||||
};
|
||||
```
|
||||
|
||||
#### SIB3/SIB4 and measurement gaps
|
||||
|
||||
This section summarizes how SIB3/SIB4 and measurement gaps relate in NR and how OAI currently implements them.
|
||||
|
||||
From 3GPP TS 38.331:
|
||||
|
||||
- `SIB3` carries intra-frequency reselection information (`intraFreqNeighCellList`).
|
||||
- `SIB4` carries inter-frequency reselection information (`interFreqCarrierFreqList`
|
||||
and per-carrier neighbour lists).
|
||||
- `MeasGapConfig` is part of dedicated `MeasConfig` (typically sent in
|
||||
`RRCReconfiguration`) and controls measurement gaps in connected mode.
|
||||
|
||||
In other words:
|
||||
|
||||
- SIB3/SIB4 are broadcast SI for idle/inactive (`RRC_IDLE` / `RRC_INACTIVE`)
|
||||
reselection behavior: UE performs autonomous cell reselection using broadcast
|
||||
SI (SIB3 for intra-frequency, SIB4 for inter-frequency).
|
||||
- `MeasGapConfig` is a dedicated UE measurement behavior and applies to
|
||||
connected mode (`RRC_CONNECTED`): the network configures what the UE
|
||||
measures and reports (periodic and event-based, e.g., A3), and those reports
|
||||
are used by CU-CP mobility logic (including handover decisions).
|
||||
|
||||
MeasGap does not depend on SIB3/SIB4, however they share the same underlying
|
||||
neighbour/frequency data model, which is the common source of serving +
|
||||
neighbour frequency information (see also [Neighbor-gNB configuration](#neighbour-gnb-configuration)):
|
||||
|
||||
- SIB3/SIB4 generation is done on the CU-CP side (inside `rrc_gNB_du.c`) from
|
||||
neighbour/frequency configuration and serving-cell MTC-derived ARFCN.
|
||||
- Measurement-gap configuration also starts from the same neighbour/frequency
|
||||
model: CU uses neighbour fields (frequency/PCI/band) to build UE
|
||||
`MeasConfig` measurement objects, then DU derives/encodes gap parameters
|
||||
from CU-provided timing (`meas_timing_config`) and returns `meas_gap_config`
|
||||
for CU forwarding in dedicated `RRCReconfiguration`.
|
||||
|
||||
Detailed implementation flow and sequence diagrams are documented in [`rrc-dev.md`](./rrc-dev.md).
|
||||
|
||||
#### UE-specific configuration
|
||||
|
||||
@@ -105,12 +179,64 @@ the [MAC configuration](../MAC/mac-usage.md) as well for SIB configuration.
|
||||
|
||||
Refer to the [handover tutorial](../handover-tutorial.md) for detailed information about gNB neighbors and handover procedures.
|
||||
|
||||
##### Configuration structure and key semantics
|
||||
|
||||
The neighbour configuration is a 2-level structure:
|
||||
|
||||
- Outer list: `neighbour_list`
|
||||
- Key: `nr_cellid` of the serving cell
|
||||
- One entry per serving cell
|
||||
- Inner list: `neighbour_cell_configuration`
|
||||
- Actual neighbour cells for that serving cell
|
||||
- Contains neighbour fields such as `gNB_ID`, neighbour `nr_cellid`, `physical_cellId`, frequency, PLMN, etc.
|
||||
|
||||
This same core configuration model is reused by multiple RRC procedures.
|
||||
|
||||
- SIB3/SIB4 generation: uses neighbour identity/frequency/offset fields to derive
|
||||
intra/inter-frequency SI.
|
||||
- Connected-mode measurement config: uses neighbour frequency/PCI/band fields to build
|
||||
UE `MeasConfig` measurement objects.
|
||||
- Handover-related procedures: reuse neighbour identity fields (e.g., cell ID/PCI/PLMN
|
||||
TAC, gNB ID) for target selection and for populating target-cell information carried
|
||||
in NGAP handover messages.
|
||||
|
||||
Conceptually, for each serving cell the RRC keeps:
|
||||
|
||||
- A per-frequency table (`inter_freqs`): one entry per `(absoluteFrequencySSB,
|
||||
subcarrierSpacing)` used for SIB4, containing the SIB4 per-frequency fields
|
||||
(priority, thresholds, `q_OffsetFreq`, `q_RxLevMin`, `t_ReselectionNR`).
|
||||
- A per-neighbour list (`neighbour_cells`): one entry per neighbour, with
|
||||
identity (cell ID, PCI, PLMN, TAC), frequency (`absoluteFrequencySSB`,
|
||||
`subcarrierSpacing`, `band`), and SIB3/SIB4 per-neighbour offsets
|
||||
(`q_OffsetCell`, `q_RxLevMinOffsetCell`, `q_QualMinOffsetCell`).
|
||||
- A link from neighbours to frequencies: each neighbour implicitly points to
|
||||
the matching `inter_freqs` entry via its `(absoluteFrequencySSB,
|
||||
subcarrierSpacing)`; if no such frequency exists, it is treated as having no
|
||||
SIB4 per-frequency configuration.
|
||||
|
||||
Notes:
|
||||
|
||||
- In `neighbour_list`, only `nr_cellid` is used as the key for lookup.
|
||||
- `physical_cellId` belongs to neighbour-cell entries in `neighbour_cell_configuration` (inner list).
|
||||
- `nr_cellid` entries in `neighbour_list` should be unique to avoid ambiguous lookup.
|
||||
- Intra-frequency neighbours (SIB3) are derived only from the per-cell
|
||||
`neighbour_cell_configuration` on the serving carrier.
|
||||
|
||||
At configuration time (`gnb_config.c`), neighbours are parsed into `neighbour_cells`,
|
||||
per-neighbour SIB3/SIB4 offsets are validated, and a per-frequency array `inter_freqs`
|
||||
is built by grouping neighbours by `(absoluteFrequencySSB, subcarrierSpacing)` and
|
||||
in-range SIB4 per-frequency fields (`cellReselectionPriority`, `threshX_HighP/L`,
|
||||
`q_OffsetFreq`) across neighbours on the same ARFCN. At SIB4 build time, the RRC uses
|
||||
`inter_freqs` to create one `InterFreqCarrierFreqInfo` per ARFCN for inter-frequency
|
||||
carriers (ARFCN different from the serving SSB ARFCN) and attaches all neighbours whose
|
||||
`inter_freq_idx` points to that frequency entry.
|
||||
|
||||
##### Required configuration parameters
|
||||
|
||||
To define a neighbor cell in the configuration file, the following parameters are required:
|
||||
To define a neighbour cell in the configuration file, the following parameters are required:
|
||||
|
||||
- `gNB_ID` - identifier of the neighbor gNB (e.g., `0xe01`)
|
||||
- `nr_cellid` - cell identifier of the neighbor cell (e.g., `11111111`)
|
||||
- `gNB_ID` - identifier of the neighbour gNB (e.g., `0xe01`)
|
||||
- `nr_cellid` - cell identifier of the neighbour cell (e.g., `11111111`)
|
||||
- `physical_cellId` - physical cell ID for radio identification (e.g., `1`)
|
||||
- `absoluteFrequencySSB` - SSB frequency in ARFCN notation (e.g., `643296`)
|
||||
- `subcarrierSpacing` - numerology index: 0=15kHz, 1=30kHz, 2=60kHz, 3=120kHz
|
||||
@@ -123,19 +249,45 @@ To define a neighbor cell in the configuration file, the following parameters ar
|
||||
|
||||
Example configuration structure:
|
||||
```
|
||||
# Per-frequency SIB4 configuration (one entry per ARFCN), shared by all cells
|
||||
frequency_list = (
|
||||
{
|
||||
absoluteFrequencySSB = 643296;
|
||||
subcarrierSpacing = 1; # 30 kHz
|
||||
band = 78;
|
||||
|
||||
frequency_config = (
|
||||
{
|
||||
cellReselectionPriority = 5;
|
||||
threshX_HighP = 10;
|
||||
threshX_LowP = 6;
|
||||
q_OffsetFreq = 0;
|
||||
# Optional: threshX_HighQ, threshX_LowQ, etc.
|
||||
}
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
# Per-cell neighbour configuration; neighbours reference frequency_list via ARFCN/SCS
|
||||
neighbour_list = (
|
||||
{
|
||||
nr_cellid = 12345678;
|
||||
|
||||
neighbour_cell_configuration = (
|
||||
{
|
||||
gNB_ID = 0xe01;
|
||||
nr_cellid = 11111111;
|
||||
physical_cellId = 1;
|
||||
absoluteFrequencySSB = 643296;
|
||||
subcarrierSpacing = 1; # 30 kHz
|
||||
absoluteFrequencySSB = 643296; # ARFCN used to look up matching entry in frequency_list
|
||||
subcarrierSpacing = 1; # 30 kHz
|
||||
band = 78;
|
||||
plmn = { mcc = 001; mnc = 01; mnc_length = 2 };
|
||||
tracking_area_code = 1;
|
||||
|
||||
# Example per-neighbour offsets (SIB3/SIB4)
|
||||
q_OffsetCell = 0;
|
||||
q_RxLevMinOffsetCell = -1;
|
||||
q_QualMinOffsetCell = -1;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@@ -512,8 +512,16 @@ configuration, e.g. [neighbour-config-rfsim.conf](../../ci-scripts/conf_files/ne
|
||||
This configuration can also be present in a different file and included in the
|
||||
gNB configuration file with `@include "neighbour-config-rfsim.conf"`.
|
||||
|
||||
For each gNB there is a `neighbour_cell_configuration` linked to its serving
|
||||
cell ID.
|
||||
The neighbor configuration is nested:
|
||||
|
||||
- `neighbour_list` outer entries are keyed by serving `nr_cellid`
|
||||
- each outer entry contains `neighbour_cell_configuration`, i.e., the list of neighbor cells for that serving cell
|
||||
|
||||
In this model:
|
||||
|
||||
- outer `nr_cellid` entries should be unique
|
||||
- neighbor `physical_cellId` values are defined in inner neighbor entries
|
||||
- the same serving-cell keyed neighbor mapping is used for both F1 and N2 handover logic
|
||||
|
||||
See the example above for `neighbour-config-ho.conf`. The same configuration
|
||||
is for both F1 and N2 handover.
|
||||
|
||||
@@ -161,8 +161,7 @@ typedef struct f1ap_du_register_req_s {
|
||||
|
||||
typedef struct f1ap_sib_msg_s {
|
||||
/// RRC container with system information owned by gNB-CU
|
||||
uint8_t *SI_container;
|
||||
int SI_container_length;
|
||||
byte_array_t SI_container;
|
||||
/// SIB block type, e.g. 2 for sibType2
|
||||
int SI_type;
|
||||
} f1ap_sib_msg_t;
|
||||
|
||||
@@ -627,10 +627,10 @@ static void encode_cells_to_activate(const served_cells_to_activate_t *cell, F1A
|
||||
F1AP_GNB_CUSystemInformation_t *gNB_CUSystemInformation =
|
||||
&cells_to_be_activated_itemExtIEs->extensionValue.choice.GNB_CUSystemInformation;
|
||||
const f1ap_sib_msg_t *SI_msg = &cell->SI_msg[n];
|
||||
if (SI_msg->SI_container != NULL) {
|
||||
if (SI_msg->SI_container.buf != NULL) {
|
||||
asn1cSequenceAdd(gNB_CUSystemInformation->sibtypetobeupdatedlist.list, F1AP_SibtypetobeupdatedListItem_t, sib_item);
|
||||
sib_item->sIBtype = SI_msg->SI_type;
|
||||
OCTET_STRING_fromBuf(&sib_item->sIBmessage, (const char *)SI_msg->SI_container, SI_msg->SI_container_length);
|
||||
OCTET_STRING_fromBuf(&sib_item->sIBmessage, (const char *)SI_msg->SI_container.buf, SI_msg->SI_container.len);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -670,7 +670,7 @@ static bool decode_cells_to_activate(served_cells_to_activate_t *out, const F1AP
|
||||
F1AP_SibtypetobeupdatedListItem_t *sib_item = gNB_CUSystemInformation->sibtypetobeupdatedlist.list.array[s];
|
||||
/* SI container */
|
||||
f1ap_sib_msg_t *SI_msg = &out->SI_msg[s];
|
||||
SI_msg->SI_container = cp_octet_string(&sib_item->sIBmessage, &SI_msg->SI_container_length);
|
||||
SI_msg->SI_container.buf = cp_octet_string(&sib_item->sIBmessage, (int *)&SI_msg->SI_container.len);
|
||||
/* SIB type */
|
||||
SI_msg->SI_type = sib_item->sIBtype;
|
||||
}
|
||||
@@ -1163,8 +1163,8 @@ bool eq_f1ap_setup_response(const f1ap_setup_resp_t *a, const f1ap_setup_resp_t
|
||||
for (int j = 0; j < b->cells_to_activate[i].num_SI; j++) {
|
||||
const f1ap_sib_msg_t *a_SI_msg = &a->cells_to_activate[i].SI_msg[j];
|
||||
const f1ap_sib_msg_t *b_SI_msg = &b->cells_to_activate[i].SI_msg[j];
|
||||
_EQ_CHECK_INT(*a_SI_msg->SI_container, *b_SI_msg->SI_container);
|
||||
_EQ_CHECK_INT(a_SI_msg->SI_container_length, b_SI_msg->SI_container_length);
|
||||
if (!eq_byte_array(&a_SI_msg->SI_container, &b_SI_msg->SI_container))
|
||||
return false;
|
||||
_EQ_CHECK_INT(a_SI_msg->SI_type, b_SI_msg->SI_type);
|
||||
}
|
||||
}
|
||||
@@ -1201,9 +1201,7 @@ f1ap_setup_resp_t cp_f1ap_setup_response(const f1ap_setup_resp_t *msg)
|
||||
for (int j = 0; j < cp_cell->num_SI; j++) {
|
||||
f1ap_sib_msg_t *cp_SI_msg = &cp_cell->SI_msg[j];
|
||||
const f1ap_sib_msg_t *b_SI_msg = &msg_cell->SI_msg[j];
|
||||
cp_SI_msg->SI_container_length = b_SI_msg->SI_container_length;
|
||||
cp_SI_msg->SI_container = malloc_or_fail(cp_SI_msg->SI_container_length);
|
||||
memcpy(cp_SI_msg->SI_container, b_SI_msg->SI_container, b_SI_msg->SI_container_length);
|
||||
cp_SI_msg->SI_container = copy_byte_array(b_SI_msg->SI_container);
|
||||
cp_SI_msg->SI_type = b_SI_msg->SI_type;
|
||||
}
|
||||
}
|
||||
@@ -1221,8 +1219,7 @@ void free_f1ap_setup_response(const f1ap_setup_resp_t *msg)
|
||||
free(msg->gNB_CU_name);
|
||||
for (int i = 0; i < msg->num_cells_to_activate; i++)
|
||||
for (int j = 0; j < msg->cells_to_activate[i].num_SI; j++)
|
||||
if (msg->cells_to_activate[i].SI_msg[j].SI_container_length > 0)
|
||||
free(msg->cells_to_activate[i].SI_msg[j].SI_container);
|
||||
free_byte_array(msg->cells_to_activate[i].SI_msg[j].SI_container);
|
||||
free(msg->cells_to_activate);
|
||||
}
|
||||
|
||||
@@ -1798,7 +1795,7 @@ void free_f1ap_cu_configuration_update(const f1ap_gnb_cu_configuration_update_t
|
||||
{
|
||||
for (int i = 0; i < msg->num_cells_to_activate; i++)
|
||||
for (int j = 0; j < msg->cells_to_activate[i].num_SI; j++)
|
||||
free(msg->cells_to_activate[i].SI_msg[j].SI_container);
|
||||
free_byte_array(msg->cells_to_activate[i].SI_msg[j].SI_container);
|
||||
free(msg->cells_to_activate);
|
||||
}
|
||||
|
||||
@@ -1819,8 +1816,8 @@ bool eq_f1ap_cu_configuration_update(const f1ap_gnb_cu_configuration_update_t *a
|
||||
for (int s = 0; s < a->cells_to_activate[i].num_SI; s++) {
|
||||
f1ap_sib_msg_t *a_sib_msg = &a->cells_to_activate[i].SI_msg[s];
|
||||
f1ap_sib_msg_t *b_sib_msg = &b->cells_to_activate[i].SI_msg[s];
|
||||
_EQ_CHECK_INT(*a_sib_msg->SI_container, *b_sib_msg->SI_container);
|
||||
_EQ_CHECK_INT(a_sib_msg->SI_container_length, b_sib_msg->SI_container_length);
|
||||
if (!eq_byte_array(&a_sib_msg->SI_container, &b_sib_msg->SI_container))
|
||||
return false;
|
||||
_EQ_CHECK_INT(a_sib_msg->SI_type, b_sib_msg->SI_type);
|
||||
}
|
||||
}
|
||||
@@ -1842,9 +1839,7 @@ f1ap_gnb_cu_configuration_update_t cp_f1ap_cu_configuration_update(const f1ap_gn
|
||||
for (int s = 0; s < cp.cells_to_activate[i].num_SI; s++) {
|
||||
cp.cells_to_activate[i].SI_msg[s] = msg->cells_to_activate[i].SI_msg[s];
|
||||
f1ap_sib_msg_t *SI_msg = &cp.cells_to_activate[i].SI_msg[s];
|
||||
SI_msg->SI_container = calloc_or_fail(SI_msg->SI_container_length, sizeof(*SI_msg->SI_container));
|
||||
for (int j = 0; j < SI_msg->SI_container_length; j++)
|
||||
SI_msg->SI_container[j] = msg->cells_to_activate[i].SI_msg[s].SI_container[j];
|
||||
SI_msg->SI_container = copy_byte_array(msg->cells_to_activate[i].SI_msg[s].SI_container);
|
||||
}
|
||||
}
|
||||
return cp;
|
||||
@@ -2153,8 +2148,8 @@ bool eq_f1ap_du_configuration_update_acknowledge(const f1ap_gnb_du_configuration
|
||||
return false;
|
||||
_EQ_CHECK_INT(a->cells_to_activate[i].num_SI, b->cells_to_activate[i].num_SI);
|
||||
for (int s = 0; s < a->cells_to_activate[i].num_SI; s++) {
|
||||
_EQ_CHECK_INT(*a->cells_to_activate[i].SI_msg[s].SI_container, *b->cells_to_activate[i].SI_msg[s].SI_container);
|
||||
_EQ_CHECK_INT(a->cells_to_activate[i].SI_msg[s].SI_container_length, b->cells_to_activate[i].SI_msg[s].SI_container_length);
|
||||
if (!eq_byte_array(&a->cells_to_activate[i].SI_msg[s].SI_container, &b->cells_to_activate[i].SI_msg[s].SI_container))
|
||||
return false;
|
||||
_EQ_CHECK_INT(a->cells_to_activate[i].SI_msg[s].SI_type, b->cells_to_activate[i].SI_msg[s].SI_type);
|
||||
}
|
||||
}
|
||||
@@ -2180,9 +2175,7 @@ f1ap_gnb_du_configuration_update_acknowledge_t cp_f1ap_du_configuration_update_a
|
||||
f1ap_sib_msg_t *cp_sib = &cp.cells_to_activate[i].SI_msg[s];
|
||||
const f1ap_sib_msg_t *msg_sib = &msg->cells_to_activate[i].SI_msg[s];
|
||||
cp_sib->SI_type = msg_sib->SI_type;
|
||||
cp_sib->SI_container_length = msg_sib->SI_container_length;
|
||||
cp_sib->SI_container = calloc_or_fail(cp_sib->SI_container_length, sizeof(*cp_sib->SI_container));
|
||||
memcpy(cp_sib->SI_container, msg_sib->SI_container, cp_sib->SI_container_length);
|
||||
cp_sib->SI_container = copy_byte_array(msg_sib->SI_container);
|
||||
}
|
||||
}
|
||||
return cp;
|
||||
@@ -2196,9 +2189,7 @@ void free_f1ap_du_configuration_update_acknowledge(const f1ap_gnb_du_configurati
|
||||
// SI_container
|
||||
for (int i = 0; i < msg->num_cells_to_activate; i++) {
|
||||
for (int j = 0; j < msg->cells_to_activate[i].num_SI; j++) {
|
||||
if (msg->cells_to_activate[i].SI_msg[j].SI_container) {
|
||||
free(msg->cells_to_activate[i].SI_msg[j].SI_container);
|
||||
}
|
||||
free_byte_array(msg->cells_to_activate[i].SI_msg[j].SI_container);
|
||||
}
|
||||
}
|
||||
free(msg->cells_to_activate);
|
||||
|
||||
@@ -313,9 +313,9 @@ static void test_f1ap_setup_response(void)
|
||||
int SI_container_length = strlen(s) + 1;
|
||||
orig.cells_to_activate[0].num_SI = 1;
|
||||
f1ap_sib_msg_t *SI_msg = &orig.cells_to_activate[0].SI_msg[0];
|
||||
SI_msg->SI_container = malloc_or_fail(SI_container_length);
|
||||
memcpy(SI_msg->SI_container, (uint8_t *)s, SI_container_length);
|
||||
SI_msg->SI_container_length = SI_container_length;
|
||||
SI_msg->SI_container.buf = malloc_or_fail(SI_container_length);
|
||||
memcpy(SI_msg->SI_container.buf, s, SI_container_length);
|
||||
SI_msg->SI_container.len = SI_container_length;
|
||||
SI_msg->SI_type = 7;
|
||||
}
|
||||
F1AP_F1AP_PDU_t *f1enc = encode_f1ap_setup_response(&orig);
|
||||
@@ -609,10 +609,10 @@ static void test_f1ap_du_configuration_update_acknowledge(void)
|
||||
orig.cells_to_activate[0].plmn.mnc_digit_length = 2;
|
||||
orig.cells_to_activate[0].num_SI = 1;
|
||||
orig.cells_to_activate[0].SI_msg[0].SI_type = 7;
|
||||
orig.cells_to_activate[0].SI_msg[0].SI_container_length = 10;
|
||||
orig.cells_to_activate[0].SI_msg[0].SI_container = malloc(sizeof(uint8_t) * 10);
|
||||
for (int i = 0; i < orig.cells_to_activate[0].SI_msg[0].SI_container_length; i++) {
|
||||
orig.cells_to_activate[0].SI_msg[0].SI_container[i] = i;
|
||||
orig.cells_to_activate[0].SI_msg[0].SI_container.len = 10;
|
||||
orig.cells_to_activate[0].SI_msg[0].SI_container.buf = malloc(sizeof(uint8_t) * 10);
|
||||
for (int i = 0; i < (int)orig.cells_to_activate[0].SI_msg[0].SI_container.len; i++) {
|
||||
orig.cells_to_activate[0].SI_msg[0].SI_container.buf[i] = i;
|
||||
}
|
||||
// ASN.1 enc/dec
|
||||
F1AP_F1AP_PDU_t *f1enc = encode_f1ap_du_configuration_update_acknowledge(&orig);
|
||||
@@ -655,8 +655,8 @@ static void test_f1ap_cu_configuration_update(void)
|
||||
orig.cells_to_activate[0].plmn.mnc_digit_length = 2;
|
||||
orig.cells_to_activate[0].num_SI = 1;
|
||||
orig.cells_to_activate[0].SI_msg[0].SI_type = 7;
|
||||
orig.cells_to_activate[0].SI_msg[0].SI_container_length = 10;
|
||||
orig.cells_to_activate[0].SI_msg[0].SI_container = malloc(sizeof(uint8_t) * 10);
|
||||
orig.cells_to_activate[0].SI_msg[0].SI_container.len = 10;
|
||||
orig.cells_to_activate[0].SI_msg[0].SI_container.buf = malloc(sizeof(uint8_t) * 10);
|
||||
F1AP_F1AP_PDU_t *f1enc = encode_f1ap_cu_configuration_update(&orig);
|
||||
F1AP_F1AP_PDU_t *f1dec = f1ap_encode_decode(f1enc);
|
||||
f1ap_msg_free(f1enc);
|
||||
|
||||
+257
-81
@@ -1083,7 +1083,7 @@ f1ap_gnb_du_system_info_t *get_sys_info(NR_BCCH_BCH_Message_t *mib, const NR_BCC
|
||||
nr_SIBs_t *si = (nr_SIBs_t *)seq_arr_at(du_SIBs, i);
|
||||
// other SIB in gNB-DU System Information not implemented yet
|
||||
// only DU SIB not included in this message is SIB19
|
||||
AssertFatal(si->SIB_type == 19, "Cannot handle SIB%d in gNB-DU System Information\n", si->SIB_type);
|
||||
AssertFatal(si->SIB_type == NR_SIB_19, "Cannot handle SIB%d in gNB-DU System Information\n", (int)si->SIB_type);
|
||||
}
|
||||
}
|
||||
return sys_info;
|
||||
@@ -1243,7 +1243,7 @@ static seq_arr_t *fill_du_sibs(paramdef_t *GNBparamarray)
|
||||
int sib_value = GNBparamarray[GNB_DU_SIBS_IDX].iptr[i];
|
||||
// SIB10, SIB12, SIB13, SIB14, SIB15, SIB17, SIB18, SIB19 and SIB20 are generated by the DU
|
||||
// all the rest in the DU
|
||||
AssertFatal((sib_value > 9 && sib_value < 21) && sib_value != 11 && sib_value != 16,
|
||||
AssertFatal((sib_value > NR_SIB_9 && sib_value < NR_SIB_21) && sib_value != NR_SIB_11 && sib_value != NR_SIB_16,
|
||||
"SIB%d is not a DU SIB, to be configured in DU section\n",
|
||||
sib_value);
|
||||
nr_SIBs_t *du_sib = calloc_or_fail(1, sizeof(nr_SIBs_t));
|
||||
@@ -2012,94 +2012,209 @@ static nr_neighbour_cell_t *get_neighbour_by_pci(seq_arr_t *neighbour_cells, int
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void fill_neighbour_cell_configuration(uint8_t gnb_idx, gNB_RRC_INST *rrc)
|
||||
/** @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)
|
||||
{
|
||||
char gnbpath[MAX_OPTNAME_SIZE + 8];
|
||||
snprintf(gnbpath, sizeof(gnbpath), "%s.[%i]", GNB_CONFIG_STRING_GNB_LIST, gnb_idx);
|
||||
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;
|
||||
}
|
||||
|
||||
GET_PARAMS_LIST(neighbour_list_param_list,
|
||||
neighbour_list_params,
|
||||
GNB_NEIGHBOUR_LIST_PARAM_LIST,
|
||||
GNB_CONFIG_STRING_NEIGHBOUR_LIST,
|
||||
gnbpath);
|
||||
|
||||
if (neighbour_list_param_list.numelt < 1)
|
||||
return;
|
||||
|
||||
rrc->neighbour_cell_configuration = malloc(sizeof(seq_arr_t));
|
||||
seq_arr_init(rrc->neighbour_cell_configuration, sizeof(neighbour_cell_configuration_t));
|
||||
|
||||
for (int elm = 0; elm < neighbour_list_param_list.numelt; ++elm) {
|
||||
neighbour_cell_configuration_t cell = {0};
|
||||
cell.nr_cell_id = (uint64_t)*neighbour_list_param_list.paramarray[elm][0].u64ptr;
|
||||
|
||||
char neighbourpath[MAX_OPTNAME_SIZE + 8];
|
||||
snprintf(neighbourpath, sizeof(neighbourpath), "%s.[%i].%s.[%i]", GNB_CONFIG_STRING_GNB_LIST, gnb_idx, GNB_CONFIG_STRING_NEIGHBOUR_LIST, elm);
|
||||
GET_PARAMS_LIST(NeighbourCellParamList,
|
||||
NeighbourCellParams,
|
||||
GNBNEIGHBOURCELLPARAMS_DESC,
|
||||
GNB_CONFIG_STRING_NEIGHBOUR_CELL_LIST,
|
||||
neighbourpath);
|
||||
LOG_I(GNB_APP, "Cell %ld has %d neighbours\n", cell.nr_cell_id, NeighbourCellParamList.numelt);
|
||||
if (NeighbourCellParamList.numelt < 1)
|
||||
continue;
|
||||
|
||||
cell.neighbour_cells = malloc_or_fail(sizeof(seq_arr_t));
|
||||
seq_arr_init(cell.neighbour_cells, sizeof(nr_neighbour_cell_t));
|
||||
for (int l = 0; l < NeighbourCellParamList.numelt; ++l) {
|
||||
nr_neighbour_cell_t n = {0};
|
||||
n.gNB_ID = *(NeighbourCellParamList.paramarray[l][GNB_CONFIG_N_CELL_GNB_ID_IDX].uptr);
|
||||
n.nrcell_id = (uint64_t) * (NeighbourCellParamList.paramarray[l][GNB_CONFIG_N_CELL_NR_CELLID_IDX].u64ptr);
|
||||
n.physicalCellId = *NeighbourCellParamList.paramarray[l][GNB_CONFIG_N_CELL_PHYSICAL_ID_IDX].uptr;
|
||||
n.subcarrierSpacing = *NeighbourCellParamList.paramarray[l][GNB_CONFIG_N_CELL_SCS_IDX].uptr;
|
||||
n.band = *NeighbourCellParamList.paramarray[l][GNB_CONFIG_N_CELL_BAND_IDX].uptr;
|
||||
n.absoluteFrequencySSB = *NeighbourCellParamList.paramarray[l][GNB_CONFIG_N_CELL_ABS_FREQ_SSB_IDX].i64ptr;
|
||||
n.tac = *NeighbourCellParamList.paramarray[l][GNB_CONFIG_N_CELL_TAC_IDX].uptr;
|
||||
|
||||
char neighbour_plmn_path[CONFIG_MAXOPTLENGTH];
|
||||
snprintf(neighbour_plmn_path,
|
||||
sizeof(neighbour_plmn_path),
|
||||
"%s.%s.[%i].%s",
|
||||
neighbourpath,
|
||||
GNB_CONFIG_STRING_NEIGHBOUR_CELL_LIST,
|
||||
l,
|
||||
GNB_CONFIG_STRING_NEIGHBOUR_PLMN);
|
||||
GET_PARAMS(NeighbourPlmn, GNBPLMNPARAMS_DESC, neighbour_plmn_path);
|
||||
|
||||
n.plmn.mcc = *NeighbourPlmn[GNB_MOBILE_COUNTRY_CODE_IDX].uptr;
|
||||
n.plmn.mnc = *NeighbourPlmn[GNB_MOBILE_NETWORK_CODE_IDX].uptr;
|
||||
n.plmn.mnc_digit_length = *NeighbourPlmn[GNB_MNC_DIGIT_LENGTH].uptr;
|
||||
if (get_neighbour_by_pci(cell.neighbour_cells, n.physicalCellId) != NULL) {
|
||||
LOG_E(GNB_APP,
|
||||
"Cell %ld: duplicate PCI %d in neighbour list (nrcell_id %ld)\n",
|
||||
cell.nr_cell_id,
|
||||
n.physicalCellId,
|
||||
n.nrcell_id);
|
||||
AssertFatal(false, "PCI must be unique within a cell's neighbour list\n");
|
||||
}
|
||||
seq_arr_push_back(cell.neighbour_cells, &n, sizeof(n));
|
||||
LOG_I(GNB_APP,
|
||||
" [%d] neighbor ID %d cellId %ld PCI %d SCS %d SSB ARFCN %u TAC %u PLMN %03u.%0*u\n",
|
||||
l,
|
||||
n.gNB_ID,
|
||||
n.nrcell_id,
|
||||
n.physicalCellId,
|
||||
n.subcarrierSpacing,
|
||||
n.absoluteFrequencySSB,
|
||||
n.tac,
|
||||
n.plmn.mcc,
|
||||
n.plmn.mnc_digit_length,
|
||||
n.plmn.mnc);
|
||||
}
|
||||
seq_arr_push_back(rrc->neighbour_cell_configuration, &cell, sizeof(cell));
|
||||
/** @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
|
||||
* @param[in] n_path Base path for neighbour configuration in the config tree
|
||||
* @param[in] n_cell_params Number of neighbour-cell parameters */
|
||||
static void parse_neighbour_cells_list(neighbour_cell_configuration_t *cell,
|
||||
const paramlist_def_t *list,
|
||||
const char *n_path,
|
||||
int n_cell_params)
|
||||
{
|
||||
seq_arr_init(&cell->neighbour_cells, sizeof(nr_neighbour_cell_t));
|
||||
|
||||
/* Parse each neighbour cell in the list */
|
||||
for (int l = 0; l < list->numelt; ++l) {
|
||||
const paramdef_t *cell_params = list->paramarray[l];
|
||||
nr_neighbour_cell_t n = {
|
||||
.gNB_ID = *gpd(cell_params, n_cell_params, GNB_CONFIG_STRING_GNB_ID)->uptr,
|
||||
.nrcell_id = *gpd(cell_params, n_cell_params, GNB_CONFIG_STRING_NRCELLID)->u64ptr,
|
||||
.physicalCellId = *gpd(cell_params, n_cell_params, GNB_CONFIG_STRING_NEIGHBOUR_CELL_PHYSICAL_ID)->uptr,
|
||||
.subcarrierSpacing = *gpd(cell_params, n_cell_params, GNB_CONFIG_STRING_NEIGHBOUR_CELL_SCS)->uptr,
|
||||
.band = *gpd(cell_params, n_cell_params, GNB_CONFIG_STRING_NEIGHBOUR_CELL_BAND)->uptr,
|
||||
.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);
|
||||
n.plmn = extract_plmn_from_params(NeighbourPlmn, sizeofArray(NeighbourPlmn));
|
||||
|
||||
LOG_I(GNB_APP,
|
||||
"Neighbour[%d]: cellId %ld, PLMN %03u.%0*u, gNB %d, PCI %d, SCS %d, SSB ARFCN %u, TAC %u\n",
|
||||
l,
|
||||
n.nrcell_id,
|
||||
n.plmn.mcc,
|
||||
n.plmn.mnc_digit_length,
|
||||
n.plmn.mnc,
|
||||
n.gNB_ID,
|
||||
n.physicalCellId,
|
||||
n.subcarrierSpacing,
|
||||
n.absoluteFrequencySSB,
|
||||
n.tac);
|
||||
|
||||
/* Check for duplicate PCI in the neighbour list */
|
||||
if (get_neighbour_by_pci(&cell->neighbour_cells, n.physicalCellId) != NULL) {
|
||||
LOG_E(GNB_APP, "Cell %ld: duplicate PCI %d in neighbour list (NCI=%ld)\n", cell->nr_cell_id, n.physicalCellId, n.nrcell_id);
|
||||
AssertFatal(false, "PCI must be unique within a cell's neighbour list\n");
|
||||
}
|
||||
|
||||
seq_arr_push_back(&cell->neighbour_cells, &n, sizeof(n));
|
||||
}
|
||||
}
|
||||
|
||||
/** @brief Sort neighbour cell configuration by cell ID
|
||||
* @param[in] rrc Pointer to RRC instance */
|
||||
static void sort_neighbour_configuration(gNB_RRC_INST *rrc)
|
||||
{
|
||||
void *base = seq_arr_front(rrc->neighbour_cell_configuration);
|
||||
size_t nmemb = seq_arr_size(rrc->neighbour_cell_configuration);
|
||||
size_t element_size = sizeof(neighbour_cell_configuration_t);
|
||||
|
||||
qsort(base, nmemb, element_size, sort_neighbour_cell_config_by_cell_id);
|
||||
}
|
||||
|
||||
/** @brief Fill neighbour cell configuration from config file
|
||||
* @param[in] gnb_idx gNB index
|
||||
* @param[in,out] rrc Pointer to RRC instance */
|
||||
static void fill_neighbour_cell_configuration(const uint8_t gnb_idx, gNB_RRC_INST *rrc)
|
||||
{
|
||||
char path[MAX_OPTNAME_SIZE + 8];
|
||||
snprintf(path, sizeof(path), "%s.[%i]", GNB_CONFIG_STRING_GNB_LIST, gnb_idx);
|
||||
GET_PARAMS_LIST(nlist, nparams, GNB_NEIGHBOUR_LIST_PARAM_LIST, GNB_CONFIG_STRING_NEIGHBOUR_LIST, path);
|
||||
|
||||
LOG_I(GNB_APP, "gNB %d: neighbour_list has %d serving cell(s)\n", gnb_idx, nlist.numelt);
|
||||
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));
|
||||
|
||||
for (int elm = 0; elm < nlist.numelt; ++elm) {
|
||||
neighbour_cell_configuration_t cell = {0};
|
||||
cell.nr_cell_id = *gpd(nlist.paramarray[elm], sizeofArray(nparams), GNB_CONFIG_STRING_NRCELLID)->u64ptr;
|
||||
snprintf(path, sizeof(path), "%s.[%i].%s.[%i]", GNB_CONFIG_STRING_GNB_LIST, gnb_idx, GNB_CONFIG_STRING_NEIGHBOUR_LIST, elm);
|
||||
GET_PARAMS_LIST(ncell_list, ncell_params, GNBNEIGHBOURCELLPARAMS_DESC, GNB_CONFIG_STRING_NEIGHBOUR_CELL_LIST, path);
|
||||
LOG_I(GNB_APP, "Cell %ld has %d neighbours configured\n", cell.nr_cell_id, ncell_list.numelt);
|
||||
if (ncell_list.numelt < 1)
|
||||
continue;
|
||||
parse_neighbour_cells_list(&cell, &ncell_list, path, sizeofArray(ncell_params));
|
||||
seq_arr_push_back(rrc->neighbour_cell_configuration, &cell, sizeof(cell));
|
||||
}
|
||||
|
||||
sort_neighbour_configuration(rrc);
|
||||
}
|
||||
|
||||
static void fill_measurement_configuration(uint8_t gnb_idx, gNB_RRC_INST *rrc)
|
||||
{
|
||||
char measurement_path[MAX_OPTNAME_SIZE + 8];
|
||||
@@ -2166,6 +2281,64 @@ static void fill_measurement_configuration(uint8_t gnb_idx, gNB_RRC_INST *rrc)
|
||||
}
|
||||
}
|
||||
|
||||
/** @brief Parse and populate SIB2 configuration from config file
|
||||
* @param[in] gnb_idx gNB index
|
||||
* @return populated SIB2 configuration */
|
||||
static sib2_config_t fill_sib2_configuration(uint8_t gnb_idx)
|
||||
{
|
||||
sib2_config_t sib2 = {0};
|
||||
|
||||
char sib2_path[MAX_OPTNAME_SIZE * 2 + 8];
|
||||
snprintf(sib2_path, sizeof(sib2_path), "%s.[%i].%s", GNB_CONFIG_STRING_GNB_LIST, gnb_idx, GNB_CONFIG_STRING_SIB2_CONFIG);
|
||||
|
||||
GET_PARAMS(SIB2Params, GNBSIB2PARAMS_DESC, sib2_path);
|
||||
const int n_sib2_params = sizeofArray(SIB2Params);
|
||||
|
||||
cell_reselection_info_common_t *common = &sib2.cell_reselection_info_common;
|
||||
common->q_Hyst = *gpd(SIB2Params, n_sib2_params, GNB_CONFIG_STRING_SIB2_Q_HYST)->iptr;
|
||||
/* speedStateReselectionPars */
|
||||
common->speedStateReselectionPars = calloc_or_fail(1, sizeof(*common->speedStateReselectionPars));
|
||||
sib2_speed_state_reselection_pars_t *speed = common->speedStateReselectionPars;
|
||||
|
||||
/* Cell reselection serving frequency information */
|
||||
cell_reselection_serving_freq_info_t *serv = &sib2.cell_reselection_serving_freq_info;
|
||||
serv->cellReselectionPriority = *gpd(SIB2Params, n_sib2_params, GNB_CONFIG_STRING_SIB2_CELLRESEL_PRIORITY)->iptr;
|
||||
serv->threshServingLowP = *gpd(SIB2Params, n_sib2_params, GNB_CONFIG_STRING_SIB2_THRESH_SERVING_LOW_P)->iptr;
|
||||
serv->threshServingLowQ = *gpd(SIB2Params, n_sib2_params, GNB_CONFIG_STRING_SIB2_THRESH_SERVING_LOW_Q)->iptr;
|
||||
serv->s_NonIntraSearchP = *gpd(SIB2Params, n_sib2_params, GNB_CONFIG_STRING_SIB2_S_NONINTRASEARCH_P)->iptr;
|
||||
serv->s_NonIntraSearchQ = *gpd(SIB2Params, n_sib2_params, GNB_CONFIG_STRING_SIB2_S_NONINTRASEARCH_Q)->iptr;
|
||||
|
||||
/* Intra-frequency cell reselection parameters */
|
||||
intra_freq_cell_reselection_info_t *intra = &sib2.intra_freq_cell_reselection_info;
|
||||
intra->q_RxLevMin = *gpd(SIB2Params, n_sib2_params, GNB_CONFIG_STRING_SIB2_Q_RXLEVMIN)->iptr;
|
||||
intra->q_QualMin = *gpd(SIB2Params, n_sib2_params, GNB_CONFIG_STRING_SIB2_Q_QUALMIN)->iptr;
|
||||
intra->s_IntraSearchP = *gpd(SIB2Params, n_sib2_params, GNB_CONFIG_STRING_SIB2_S_INTRASEARCH_P)->iptr;
|
||||
intra->s_IntraSearchQ = *gpd(SIB2Params, n_sib2_params, GNB_CONFIG_STRING_SIB2_S_INTRASEARCH_Q)->iptr;
|
||||
intra->t_ReselectionNR = *gpd(SIB2Params, n_sib2_params, GNB_CONFIG_STRING_SIB2_T_RESEL_NR)->iptr;
|
||||
|
||||
/* deriveSSB_IndexFromCell (0/1) */
|
||||
int derive_ssb = *gpd(SIB2Params, n_sib2_params, GNB_CONFIG_STRING_SIB2_DERIVE_SSB_IDX)->iptr;
|
||||
sib2.deriveSSB_IndexFromCell = (derive_ssb != 0);
|
||||
|
||||
/* Speed-state pars: optional parameters are omitted from the config file (default applied), not set to -1. */
|
||||
speed->t_Evaluation = *gpd(SIB2Params, n_sib2_params, GNB_CONFIG_STRING_SIB2_SPEED_T_EVAL)->iptr;
|
||||
speed->t_HystNormal = *gpd(SIB2Params, n_sib2_params, GNB_CONFIG_STRING_SIB2_SPEED_T_HYST_NORMAL)->iptr;
|
||||
speed->n_CellChangeMedium = *gpd(SIB2Params, n_sib2_params, GNB_CONFIG_STRING_SIB2_SPEED_N_CELL_CHG_MED)->iptr;
|
||||
speed->n_CellChangeHigh = *gpd(SIB2Params, n_sib2_params, GNB_CONFIG_STRING_SIB2_SPEED_N_CELL_CHG_HIGH)->iptr;
|
||||
speed->sf_Medium = *gpd(SIB2Params, n_sib2_params, GNB_CONFIG_STRING_SIB2_SPEED_SF_MEDIUM)->iptr;
|
||||
speed->sf_High = *gpd(SIB2Params, n_sib2_params, GNB_CONFIG_STRING_SIB2_SPEED_SF_HIGH)->iptr;
|
||||
|
||||
LOG_I(GNB_APP,
|
||||
"gNB %d SIB2 config: q_Hyst=%d dB, cellReselPrio=%d, q_RxLevMin=%d dBm, t_ReselectionNR=%d s\n",
|
||||
gnb_idx,
|
||||
common->q_Hyst,
|
||||
serv->cellReselectionPriority,
|
||||
intra->q_RxLevMin,
|
||||
intra->t_ReselectionNR);
|
||||
|
||||
return sib2;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Allocates and initializes RRC instances
|
||||
* Currently assuming 1 instance
|
||||
@@ -2265,6 +2438,9 @@ gNB_RRC_INST *RCconfig_NRRRC()
|
||||
}//
|
||||
}//End for (k=0; k <num_gnbs ; k++)
|
||||
openair_rrc_gNB_configuration(rrc, &nrrrc_config);
|
||||
|
||||
/* Load SIB2 configuration */
|
||||
rrc->sib2_config = fill_sib2_configuration(i);
|
||||
}//End if (num_gnbs>0)
|
||||
|
||||
if (!NODE_IS_DU(rrc->node_type))
|
||||
|
||||
@@ -31,19 +31,28 @@ uint16_t set_snssai_config(nssai_t *nssai, const int max_num_ssi, uint8_t k, uin
|
||||
return num_ssi;
|
||||
}
|
||||
|
||||
/** @brief Extract PLMN from parameter array
|
||||
* @param[in] params Parameter array containing PLMN fields
|
||||
* @return plmn_id_t structure filled from parameters */
|
||||
plmn_id_t extract_plmn_from_params(const paramdef_t *params, int n_params)
|
||||
{
|
||||
plmn_id_t plmn = {.mcc = *gpd(params, n_params, GNB_CONFIG_STRING_MOBILE_COUNTRY_CODE)->uptr,
|
||||
.mnc = *gpd(params, n_params, GNB_CONFIG_STRING_MOBILE_NETWORK_CODE)->uptr,
|
||||
.mnc_digit_length = *gpd(params, n_params, GNB_CONFIG_STRING_MNC_DIGIT_LENGTH)->uptr};
|
||||
AssertFatal((plmn.mnc_digit_length == 2) || (plmn.mnc_digit_length == 3), "BAD MNC DIGIT LENGTH %d", plmn.mnc_digit_length);
|
||||
return plmn;
|
||||
}
|
||||
|
||||
uint8_t set_plmn_config(plmn_id_t *p, uint8_t idx)
|
||||
{
|
||||
char gnbpath[MAX_OPTNAME_SIZE * 2 + 8];
|
||||
snprintf(gnbpath, sizeof(gnbpath), "%s.[%i]", GNB_CONFIG_STRING_GNB_LIST, idx);
|
||||
GET_PARAMS_LIST(PLMNParamList, PLMNParams, GNBPLMNPARAMS_DESC, GNB_CONFIG_STRING_PLMN_LIST, gnbpath, PLMNPARAMS_CHECK);
|
||||
uint8_t num_plmn = PLMNParamList.numelt;
|
||||
const int n_plmn_params = sizeofArray(PLMNParams);
|
||||
AssertFatal(num_plmn >= 1 && num_plmn <= 6, "The number of PLMN IDs must be in [1,6], but is %d\n", num_plmn);
|
||||
for (int l = 0; l < num_plmn; ++l) {
|
||||
plmn_id_t *plmn = &p[l];
|
||||
plmn->mcc = *PLMNParamList.paramarray[l][GNB_MOBILE_COUNTRY_CODE_IDX].uptr;
|
||||
plmn->mnc = *PLMNParamList.paramarray[l][GNB_MOBILE_NETWORK_CODE_IDX].uptr;
|
||||
plmn->mnc_digit_length = *PLMNParamList.paramarray[l][GNB_MNC_DIGIT_LENGTH].u8ptr;
|
||||
AssertFatal((plmn->mnc_digit_length == 2) || (plmn->mnc_digit_length == 3), "BAD MNC DIGIT LENGTH %d", plmn->mnc_digit_length);
|
||||
p[l] = extract_plmn_from_params(PLMNParamList.paramarray[l], n_plmn_params);
|
||||
}
|
||||
return num_plmn;
|
||||
}
|
||||
|
||||
@@ -4,6 +4,8 @@
|
||||
|
||||
#include <assert.h>
|
||||
#include "common/5g_platform_types.h"
|
||||
#include "common/config/config_paramdesc.h"
|
||||
|
||||
uint16_t set_snssai_config(nssai_t *nssai, const int max_num_ssi, uint8_t k, uint8_t l);
|
||||
uint8_t set_plmn_config(plmn_id_t *p, uint8_t idx);
|
||||
plmn_id_t extract_plmn_from_params(const paramdef_t *params, int n_params);
|
||||
|
||||
+190
-17
@@ -10,6 +10,7 @@
|
||||
#define __GNB_APP_GNB_PARAMDEF__H__
|
||||
|
||||
#include "common/config/config_paramdesc.h"
|
||||
#include "common/config/config_userapi.h"
|
||||
#include "common/ngran_types.h"
|
||||
#include "RRC_nr_paramsvalues.h"
|
||||
|
||||
@@ -289,7 +290,6 @@ typedef enum {
|
||||
#define GNB_NEIGHBOUR_LIST_PARAM_LIST { \
|
||||
/* optname helpstr paramflags XXXptr def val type numelt */ \
|
||||
{GNB_CONFIG_STRING_NRCELLID, "cell nrCell Id which has neighbours", PARAMFLAG_MANDATORY, .u64ptr=NULL, .defint64val=0, TYPE_UINT64, 0}, \
|
||||
{GNB_CONFIG_STRING_NEIGHBOUR_CELL_PHYSICAL_ID, "neighbour cell physical id", PARAMFLAG_MANDATORY, .uptr=NULL, .defuintval=0, TYPE_UINT, 0}, \
|
||||
}
|
||||
// clang-format on
|
||||
#define GNB_CONFIG_STRING_NEIGHBOUR_CELL_LIST "neighbour_cell_configuration"
|
||||
@@ -300,27 +300,111 @@ 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 */
|
||||
|
||||
#define GNB_CONFIG_N_CELL_GNB_ID_IDX 0
|
||||
#define GNB_CONFIG_N_CELL_NR_CELLID_IDX 1
|
||||
#define GNB_CONFIG_N_CELL_PHYSICAL_ID_IDX 2
|
||||
#define GNB_CONFIG_N_CELL_ABS_FREQ_SSB_IDX 3
|
||||
#define GNB_CONFIG_N_CELL_SCS_IDX 4
|
||||
#define GNB_CONFIG_N_CELL_BAND_IDX 5
|
||||
#define GNB_CONFIG_N_CELL_TAC_IDX 6
|
||||
// clang-format off
|
||||
#define GNBNEIGHBOURCELLPARAMS_DESC { \
|
||||
/* optname helpstr paramflags XXXptr def val type numelt */ \
|
||||
{GNB_CONFIG_STRING_GNB_ID, "neighbour cell's gNB ID", PARAMFLAG_MANDATORY, .uptr=NULL, .defintval=0, TYPE_UINT, 0}, \
|
||||
{GNB_CONFIG_STRING_NRCELLID, "neighbour cell nrCell Id", PARAMFLAG_MANDATORY, .u64ptr=NULL, .defint64val=0, TYPE_UINT64, 0}, \
|
||||
{GNB_CONFIG_STRING_NEIGHBOUR_CELL_PHYSICAL_ID, "neighbour cell physical id", PARAMFLAG_MANDATORY, .uptr=NULL, .defuintval=0, TYPE_UINT, 0}, \
|
||||
{GNB_CONFIG_STRING_NEIGHBOUR_CELL_ABS_FREQ_SSB, "neighbour cell abs freq ssb", PARAMFLAG_MANDATORY, .i64ptr=NULL, .defint64val=0, TYPE_INT64, 0}, \
|
||||
{GNB_CONFIG_STRING_NEIGHBOUR_CELL_SCS, "neighbour cell scs", PARAMFLAG_MANDATORY, .uptr=NULL, .defuintval=0, TYPE_UINT, 0}, \
|
||||
{GNB_CONFIG_STRING_NEIGHBOUR_CELL_BAND, "neighbour cell band", PARAMFLAG_MANDATORY, .uptr=NULL, .defuintval=78, TYPE_UINT, 0}, \
|
||||
{GNB_CONFIG_STRING_NEIGHBOUR_TRACKING_ARE_CODE, "neighbour cell tracking area", PARAMFLAG_MANDATORY, .uptr=NULL, .defuintval=0, TYPE_UINT, 0}, \
|
||||
#define GNBNEIGHBOURCELLPARAMS_DESC { /* optname helpstr paramflags XXXptr def val type numelt */ \
|
||||
{GNB_CONFIG_STRING_GNB_ID, \
|
||||
"neighbour cell's gNB ID", PARAMFLAG_MANDATORY, .uptr=NULL, .defintval=0, TYPE_UINT, 0, \
|
||||
.chkPptr=NULL}, \
|
||||
{GNB_CONFIG_STRING_NRCELLID, \
|
||||
"neighbour cell nrCell Id", PARAMFLAG_MANDATORY, .u64ptr=NULL, .defint64val=0, TYPE_UINT64, 0, \
|
||||
.chkPptr=NULL}, \
|
||||
{GNB_CONFIG_STRING_NEIGHBOUR_CELL_PHYSICAL_ID, \
|
||||
"neighbour cell physical id", PARAMFLAG_MANDATORY, .uptr=NULL, .defuintval=0, TYPE_UINT, 0, \
|
||||
.chkPptr = &(checkedparam_t){.s2 = {config_check_uintrange, {0, 1007}}}}, \
|
||||
{GNB_CONFIG_STRING_NEIGHBOUR_CELL_ABS_FREQ_SSB, \
|
||||
"neighbour cell absolute frequency SSB (0..3279165)", PARAMFLAG_MANDATORY, .i64ptr=NULL, .defint64val=0, TYPE_INT64, 0, \
|
||||
.chkPptr = &(checkedparam_t){.s2 = {config_check_intrange, {0, 3279165}}}}, \
|
||||
{GNB_CONFIG_STRING_NEIGHBOUR_CELL_SCS, \
|
||||
"neighbour cell scs (15/30/60/120 kHz)", PARAMFLAG_MANDATORY, .uptr=NULL, .defuintval=0, TYPE_UINT, 0, \
|
||||
.chkPptr = &(checkedparam_t){.s1 = {config_check_intval, {0, 1, 2, 3}, 4}}}, \
|
||||
{GNB_CONFIG_STRING_NEIGHBOUR_CELL_BAND, \
|
||||
"neighbour cell band (1..1024)", PARAMFLAG_MANDATORY, .uptr=NULL, .defuintval=78, TYPE_UINT, 0, \
|
||||
.chkPptr = &(checkedparam_t){.s2 = {config_check_uintrange, {1, 1024}}}}, \
|
||||
{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"
|
||||
@@ -369,6 +453,95 @@ typedef enum {
|
||||
#define MEASUREMENT_EVENTS_INCLUDE_BEAM_MEAS_IDX 1
|
||||
#define MEASUREMENT_EVENTS_MAX_RS_INDEX_TO_REPORT 2
|
||||
|
||||
/*-------------------------------------------------------------------------------------------------------------------------------------*/
|
||||
/* SIB2 cell reselection configuration parameters */
|
||||
/*-------------------------------------------------------------------------------------------------------------------------------------*/
|
||||
|
||||
#define GNB_CONFIG_STRING_SIB2_CONFIG "sib2_config"
|
||||
|
||||
/* clang-format off */
|
||||
|
||||
/* SIB2 top-level parameters (per cell) */
|
||||
#define GNB_CONFIG_STRING_SIB2_Q_HYST "q_Hyst"
|
||||
#define GNB_CONFIG_STRING_SIB2_CELLRESEL_PRIORITY "cellReselectionPriority"
|
||||
#define GNB_CONFIG_STRING_SIB2_THRESH_SERVING_LOW_P "threshServingLowP"
|
||||
#define GNB_CONFIG_STRING_SIB2_THRESH_SERVING_LOW_Q "threshServingLowQ"
|
||||
#define GNB_CONFIG_STRING_SIB2_S_NONINTRASEARCH_P "s_NonIntraSearchP"
|
||||
#define GNB_CONFIG_STRING_SIB2_S_NONINTRASEARCH_Q "s_NonIntraSearchQ"
|
||||
#define GNB_CONFIG_STRING_SIB2_Q_RXLEVMIN "q_RxLevMin"
|
||||
#define GNB_CONFIG_STRING_SIB2_Q_QUALMIN "q_QualMin"
|
||||
#define GNB_CONFIG_STRING_SIB2_S_INTRASEARCH_P "s_IntraSearchP"
|
||||
#define GNB_CONFIG_STRING_SIB2_S_INTRASEARCH_Q "s_IntraSearchQ"
|
||||
#define GNB_CONFIG_STRING_SIB2_T_RESEL_NR "t_ReselectionNR"
|
||||
#define GNB_CONFIG_STRING_SIB2_DERIVE_SSB_IDX "deriveSSB_IndexFromCell"
|
||||
|
||||
/* SIB2 speedStateReselectionPars parameters */
|
||||
#define GNB_CONFIG_STRING_SIB2_SPEED_T_EVAL "speed_t_Evaluation"
|
||||
#define GNB_CONFIG_STRING_SIB2_SPEED_T_HYST_NORMAL "speed_t_HystNormal"
|
||||
#define GNB_CONFIG_STRING_SIB2_SPEED_N_CELL_CHG_MED "speed_n_CellChangeMedium"
|
||||
#define GNB_CONFIG_STRING_SIB2_SPEED_N_CELL_CHG_HIGH "speed_n_CellChangeHigh"
|
||||
#define GNB_CONFIG_STRING_SIB2_SPEED_SF_MEDIUM "speed_sf_Medium"
|
||||
#define GNB_CONFIG_STRING_SIB2_SPEED_SF_HIGH "speed_sf_High"
|
||||
|
||||
#define GNBSIB2PARAMS_DESC { /* optname helpstr paramflags XXXptr def val type numelt */ \
|
||||
{GNB_CONFIG_STRING_SIB2_Q_HYST, \
|
||||
"SIB2 q-Hyst (0,1,2,3,4,5,6,8,10..24 dB)", 0, .iptr=NULL, .defintval=0, TYPE_INT, 0, \
|
||||
.chkPptr = &(checkedparam_t){.s1 = {config_check_intval, {0, 1, 2, 3, 4, 5, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24}, 16}}}, \
|
||||
{GNB_CONFIG_STRING_SIB2_CELLRESEL_PRIORITY, \
|
||||
"SIB2 cellReselectionPriority (0..7)", 0, .iptr=NULL, .defintval=0, TYPE_INT, 0, \
|
||||
.chkPptr = &(checkedparam_t){.s2 = {config_check_intrange, {0, 7}}}}, \
|
||||
{GNB_CONFIG_STRING_SIB2_THRESH_SERVING_LOW_P, \
|
||||
"SIB2 threshServingLowP (0..31)", 0, .iptr=NULL, .defintval=0, TYPE_INT, 0, \
|
||||
.chkPptr = &(checkedparam_t){.s2 = {config_check_intrange, {0, 31}}}}, \
|
||||
{GNB_CONFIG_STRING_SIB2_THRESH_SERVING_LOW_Q, \
|
||||
"SIB2 threshServingLowQ (0..31, -1=disabled)", 0, .iptr=NULL, .defintval=0, TYPE_INT, 0, \
|
||||
.chkPptr = &(checkedparam_t){.s2 = {config_check_intrange, {-1, 31}}}}, \
|
||||
{GNB_CONFIG_STRING_SIB2_S_NONINTRASEARCH_P, \
|
||||
"SIB2 s-NonIntraSearchP (0..31, -1=disabled)", 0, .iptr=NULL, .defintval=-1, TYPE_INT, 0, \
|
||||
.chkPptr = &(checkedparam_t){.s2 = {config_check_intrange, {-1, 31}}}}, \
|
||||
{GNB_CONFIG_STRING_SIB2_S_NONINTRASEARCH_Q, \
|
||||
"SIB2 s-NonIntraSearchQ (0..31, -1=disabled)", 0, .iptr=NULL, .defintval=-1, TYPE_INT, 0, \
|
||||
.chkPptr = &(checkedparam_t){.s2 = {config_check_intrange, {-1, 31}}}}, \
|
||||
{GNB_CONFIG_STRING_SIB2_Q_RXLEVMIN, \
|
||||
"SIB2 q-RxLevMin (-70..-22)", 0, .iptr=NULL, .defintval=-56, TYPE_INT, 0, \
|
||||
.chkPptr = &(checkedparam_t){.s2 = {config_check_intrange, {-70, -22}}}}, \
|
||||
{GNB_CONFIG_STRING_SIB2_Q_QUALMIN, \
|
||||
"SIB2 q-QualMin (-43..-12 or -1=disabled)", 0, .iptr=NULL, .defintval=-1, TYPE_INT, 0, \
|
||||
.chkPptr = &(checkedparam_t){.s1 = {config_check_intval, \
|
||||
{-1, -43, -42, -41, -40, -39, -38, -37, -36, -35, -34, -33, -32, -31, -30, -29, -28, \
|
||||
-27, -26, -25, -24, -23, -22, -21, -20, -19, -18, -17, -16, -15, -14, -13, -12}, 33}}}, \
|
||||
{GNB_CONFIG_STRING_SIB2_S_INTRASEARCH_P, \
|
||||
"SIB2 s-IntraSearchP (0..31)", 0, .iptr=NULL, .defintval=22, TYPE_INT, 0, \
|
||||
.chkPptr = &(checkedparam_t){.s2 = {config_check_intrange, {0, 31}}}}, \
|
||||
{GNB_CONFIG_STRING_SIB2_S_INTRASEARCH_Q, \
|
||||
"SIB2 s-IntraSearchQ (0..31, -1=disabled)", 0, .iptr=NULL, .defintval=-1, TYPE_INT, 0, \
|
||||
.chkPptr = &(checkedparam_t){.s2 = {config_check_intrange, {-1, 31}}}}, \
|
||||
{GNB_CONFIG_STRING_SIB2_T_RESEL_NR, \
|
||||
"SIB2 t-ReselectionNR (0..7)", 0, .iptr=NULL, .defintval=1, TYPE_INT, 0, \
|
||||
.chkPptr = &(checkedparam_t){.s2 = {config_check_intrange, {0, 7}}}}, \
|
||||
{GNB_CONFIG_STRING_SIB2_DERIVE_SSB_IDX, \
|
||||
"SIB2 deriveSSB-IndexFromCell (0/1)", PARAMFLAG_BOOL, .iptr=NULL, .defintval=1, TYPE_INT, 0}, \
|
||||
{GNB_CONFIG_STRING_SIB2_SPEED_T_EVAL, \
|
||||
"SIB2 speed t-Evaluation (0=s30,1=s60,2=s120,3=s180,4=s240)", 0, .iptr=NULL, .defintval=0, TYPE_INT, 0, \
|
||||
.chkPptr = &(checkedparam_t){.s2 = {config_check_intrange, {0, 4}}}}, \
|
||||
{GNB_CONFIG_STRING_SIB2_SPEED_T_HYST_NORMAL, \
|
||||
"SIB2 speed t-HystNormal (0=s30,1=s60,2=s120,3=s180,4=s240)", 0, .iptr=NULL, .defintval=0, TYPE_INT, 0, \
|
||||
.chkPptr = &(checkedparam_t){.s2 = {config_check_intrange, {0, 4}}}}, \
|
||||
{GNB_CONFIG_STRING_SIB2_SPEED_N_CELL_CHG_MED, \
|
||||
"SIB2 speed n-CellChangeMedium (1..16)", 0, .iptr=NULL, .defintval=1, TYPE_INT, 0, \
|
||||
.chkPptr = &(checkedparam_t){.s2 = {config_check_intrange, {1, 16}}}}, \
|
||||
{GNB_CONFIG_STRING_SIB2_SPEED_N_CELL_CHG_HIGH, \
|
||||
"SIB2 speed n-CellChangeHigh (1..16)", 0, .iptr=NULL, .defintval=2, TYPE_INT, 0, \
|
||||
.chkPptr = &(checkedparam_t){.s2 = {config_check_intrange, {1, 16}}}}, \
|
||||
{GNB_CONFIG_STRING_SIB2_SPEED_SF_MEDIUM, \
|
||||
"SIB2 speed sf-Medium (0=dB-6,1=dB-4,2=dB-2,3=dB0)", 0, .iptr=NULL, .defintval=1, TYPE_INT, 0, \
|
||||
.chkPptr = &(checkedparam_t){.s2 = {config_check_intrange, {0, 3}}}}, \
|
||||
{GNB_CONFIG_STRING_SIB2_SPEED_SF_HIGH, \
|
||||
"SIB2 speed sf-High (0=dB-6,1=dB-4,2=dB-2,3=dB0)", 0, .iptr=NULL, .defintval=0, TYPE_INT, 0, \
|
||||
.chkPptr = &(checkedparam_t){.s2 = {config_check_intrange, {0, 3}}}}, \
|
||||
}
|
||||
/* clang-format on */
|
||||
|
||||
/* PLMN ID configuration */
|
||||
|
||||
#define GNB_CONFIG_STRING_PLMN_LIST "plmn_list"
|
||||
|
||||
@@ -988,27 +988,56 @@ bool nr_mac_configure_other_sib(gNB_MAC_INST *nrmac, int num_cu_sib, const f1ap_
|
||||
NR_SystemInformation_IEs_t *sysInfov17 = calloc(1, sizeof(*sysInfov17)); // for othersibs
|
||||
for (int i = 0; i < num_cu_sib; i++) {
|
||||
config_sibs[i] = cu_sib[i].SI_type;
|
||||
const byte_array_t *container = &cu_sib[i].SI_container;
|
||||
if (!container->buf || container->len == 0) {
|
||||
LOG_W(NR_MAC, "SIB%d container is invalid (ptr=%p len=%zu), skipping\n", config_sibs[i], container->buf, container->len);
|
||||
continue;
|
||||
}
|
||||
switch (config_sibs[i]) {
|
||||
case 2: {
|
||||
case NR_SIB_2: {
|
||||
struct NR_SystemInformation_IEs__sib_TypeAndInfo__Member *type = calloc(1, sizeof(*type));
|
||||
type->present = NR_SystemInformation_IEs__sib_TypeAndInfo__Member_PR_sib2;
|
||||
// SIB2 coming from CU need to decode it
|
||||
NR_SIB2_t *sib2 = NULL;
|
||||
asn_dec_rval_t dec_rval = uper_decode(NULL,
|
||||
&asn_DEF_NR_SIB2,
|
||||
(void **)&sib2,
|
||||
cu_sib[i].SI_container,
|
||||
cu_sib[i].SI_container_length,
|
||||
0,
|
||||
0);
|
||||
asn_dec_rval_t dec_rval = uper_decode(NULL, &asn_DEF_NR_SIB2, (void **)&sib2, 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_SIB2, cu_sib[i].SI_container);
|
||||
ASN_STRUCT_FREE(asn_DEF_NR_SIB2, sib2);
|
||||
}
|
||||
type->choice.sib2 = sib2;
|
||||
add_sib_to_systeminformation(sysInfo, type);
|
||||
break;
|
||||
}
|
||||
case NR_SIB_3: {
|
||||
struct NR_SystemInformation_IEs__sib_TypeAndInfo__Member *type = calloc_or_fail(1, sizeof(*type));
|
||||
type->present = NR_SystemInformation_IEs__sib_TypeAndInfo__Member_PR_sib3;
|
||||
NR_SIB3_t *sib3 = NULL;
|
||||
asn_dec_rval_t dec_rval = uper_decode(NULL, &asn_DEF_NR_SIB3, (void **)&sib3, 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_SIB3, sib3);
|
||||
free(type);
|
||||
break;
|
||||
}
|
||||
type->choice.sib3 = sib3;
|
||||
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]);
|
||||
}
|
||||
@@ -1019,7 +1048,7 @@ bool nr_mac_configure_other_sib(gNB_MAC_INST *nrmac, int num_cu_sib, const f1ap_
|
||||
int sib_idx = i + num_cu_sib;
|
||||
config_sibs[sib_idx] = si->SIB_type;
|
||||
switch (config_sibs[sib_idx]) {
|
||||
case 19: {
|
||||
case NR_SIB_19: {
|
||||
struct NR_SystemInformation_IEs__sib_TypeAndInfo__Member *type_du = calloc(1, sizeof(*type_du));
|
||||
type_du->present = NR_SystemInformation_IEs__sib_TypeAndInfo__Member_PR_sib19_v1700;
|
||||
NR_SIB19_r17_t *sib19 = get_SIB19_NR(cc->ServingCellConfigCommon);
|
||||
|
||||
@@ -223,39 +223,67 @@ NR_UE_NR_Capability_t *decode_nr_ue_capability(int rnti, const NR_UE_CapabilityR
|
||||
return NULL;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
int do_SIB2_NR(uint8_t **msg_SIB2, NR_SSB_MTC_t *ssbmtc)
|
||||
byte_array_t do_SIB2_NR(const NR_SIB2_t *sib2)
|
||||
{
|
||||
NR_SIB2_t *sib2 = calloc(1, sizeof(*sib2));
|
||||
sib2->cellReselectionInfoCommon.q_Hyst = NR_SIB2__cellReselectionInfoCommon__q_Hyst_dB0;
|
||||
struct NR_SIB2__cellReselectionInfoCommon__speedStateReselectionPars *speed = calloc(1, sizeof(*speed));
|
||||
NR_MobilityStateParameters_t mobilityStateParameters = {0};
|
||||
mobilityStateParameters.t_Evaluation = NR_MobilityStateParameters__t_Evaluation_s30;
|
||||
mobilityStateParameters.t_HystNormal = NR_MobilityStateParameters__t_HystNormal_s30;
|
||||
mobilityStateParameters.n_CellChangeMedium = 1;
|
||||
mobilityStateParameters.n_CellChangeHigh = 2;
|
||||
speed->mobilityStateParameters = mobilityStateParameters;
|
||||
struct NR_SIB2__cellReselectionInfoCommon__speedStateReselectionPars__q_HystSF qhyst = {0};
|
||||
qhyst.sf_Medium = NR_SIB2__cellReselectionInfoCommon__speedStateReselectionPars__q_HystSF__sf_Medium_dB_4;
|
||||
qhyst.sf_High = NR_SIB2__cellReselectionInfoCommon__speedStateReselectionPars__q_HystSF__sf_High_dB_6;
|
||||
speed->q_HystSF = qhyst;
|
||||
sib2->cellReselectionInfoCommon.speedStateReselectionPars = speed;
|
||||
sib2->cellReselectionServingFreqInfo.cellReselectionPriority = 0; // INTEGER (0..7)
|
||||
sib2->cellReselectionServingFreqInfo.threshServingLowP = 0;
|
||||
NR_ReselectionThresholdQ_t *threshServingLowQ = calloc(1, sizeof(*threshServingLowQ));
|
||||
*threshServingLowQ = 0;
|
||||
sib2->cellReselectionServingFreqInfo.threshServingLowQ = threshServingLowQ;
|
||||
sib2->intraFreqCellReselectionInfo.q_RxLevMin = -56; // INTEGER (-70..-22)
|
||||
sib2->intraFreqCellReselectionInfo.s_IntraSearchP = 22; // INTEGER (0..31)
|
||||
sib2->intraFreqCellReselectionInfo.t_ReselectionNR = 1; // INTEGER (0..7)
|
||||
sib2->intraFreqCellReselectionInfo.deriveSSB_IndexFromCell = true;
|
||||
sib2->intraFreqCellReselectionInfo.smtc = ssbmtc;
|
||||
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_SIB2, sib2, errbuf, &errlen);
|
||||
if (ret != 0) {
|
||||
LOG_E(NR_RRC, "SIB2 constraint check failed: %s\n", errbuf);
|
||||
return msg;
|
||||
}
|
||||
|
||||
ssize_t size = uper_encode_to_new_buffer(&asn_DEF_NR_SIB2, NULL, (void *)sib2, (void **)msg_SIB2);
|
||||
AssertFatal (size > 0, "ASN1 message encoding failed (encoded %lu bytes)!\n", size);
|
||||
ASN_STRUCT_FREE(asn_DEF_NR_SIB2, sib2);
|
||||
return size;
|
||||
int val = uper_encode_to_new_buffer(&asn_DEF_NR_SIB2, NULL, (void *)sib2, (void **)&msg.buf);
|
||||
if (val <= 0) {
|
||||
LOG_E(NR_RRC, "Failed to encode SIB2\n");
|
||||
return msg;
|
||||
}
|
||||
|
||||
msg.len = val;
|
||||
return msg;
|
||||
}
|
||||
|
||||
byte_array_t do_SIB3_NR(const NR_SIB3_t *sib3)
|
||||
{
|
||||
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_SIB3, sib3, errbuf, &errlen);
|
||||
if (ret != 0) {
|
||||
LOG_E(NR_RRC, "SIB3 constraint check failed: %s\n", errbuf);
|
||||
return msg;
|
||||
}
|
||||
|
||||
int val = uper_encode_to_new_buffer(&asn_DEF_NR_SIB3, NULL, (void *)sib3, (void **)&msg.buf);
|
||||
if (val <= 0) {
|
||||
LOG_E(NR_RRC, "Failed to encode SIB3\n");
|
||||
return msg;
|
||||
}
|
||||
|
||||
msg.len = val;
|
||||
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)
|
||||
|
||||
@@ -33,6 +33,9 @@
|
||||
#include "NR_MeasurementTimingConfiguration.h"
|
||||
#include "NR_UE-NR-Capability.h"
|
||||
#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"
|
||||
@@ -65,7 +68,9 @@ typedef struct {
|
||||
*/
|
||||
int xer_sprint_NR(char *string, size_t string_size, struct asn_TYPE_descriptor_s *td, void *sptr);
|
||||
|
||||
int do_SIB2_NR(uint8_t **msg_SIB2, NR_SSB_MTC_t *ssbmtc);
|
||||
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);
|
||||
|
||||
|
||||
@@ -14,12 +14,61 @@ extern "C" {
|
||||
#include "NR_DRB-ToAddMod.h"
|
||||
#include "NR_DRB-ToAddModList.h"
|
||||
#include "NR_SRB-ToAddModList.h"
|
||||
#include "NR_InterFreqCarrierFreqInfo.h"
|
||||
#include "ds/byte_array.h"
|
||||
#include "NR_SIB2.h"
|
||||
#include "NR_SIB3.h"
|
||||
#include "NR_SIB4.h"
|
||||
#include "NR_IntraFreqNeighCellInfo.h"
|
||||
#include "NR_IntraFreqNeighCellList.h"
|
||||
#include "NR_InterFreqCarrierFreqInfo.h"
|
||||
#include "NR_InterFreqNeighCellInfo.h"
|
||||
#include "NR_InterFreqNeighCellList.h"
|
||||
#include "asn_application.h"
|
||||
RAN_CONTEXT_t RC;
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
static NR_SIB2_t *encode_and_decode_sib2(const NR_SIB2_t *sib2, byte_array_t *ba_out)
|
||||
{
|
||||
*ba_out = do_SIB2_NR(sib2);
|
||||
EXPECT_GT(ba_out->len, 0);
|
||||
EXPECT_NE(ba_out->buf, nullptr);
|
||||
|
||||
NR_SIB2_t *decoded = nullptr;
|
||||
asn_dec_rval_t dec = uper_decode(nullptr, &asn_DEF_NR_SIB2, (void **)&decoded, ba_out->buf, ba_out->len, 0, 0);
|
||||
EXPECT_EQ(dec.code, RC_OK);
|
||||
EXPECT_NE(decoded, nullptr);
|
||||
return decoded;
|
||||
}
|
||||
|
||||
static NR_SIB3_t *encode_and_decode_sib3(const NR_SIB3_t *sib3, byte_array_t *ba_out)
|
||||
{
|
||||
*ba_out = do_SIB3_NR(sib3);
|
||||
EXPECT_GT(ba_out->len, 0);
|
||||
EXPECT_NE(ba_out->buf, nullptr);
|
||||
|
||||
NR_SIB3_t *decoded = nullptr;
|
||||
asn_dec_rval_t dec = uper_decode(nullptr, &asn_DEF_NR_SIB3, (void **)&decoded, ba_out->buf, ba_out->len, 0, 0);
|
||||
EXPECT_EQ(dec.code, RC_OK);
|
||||
EXPECT_NE(decoded, nullptr);
|
||||
return decoded;
|
||||
}
|
||||
|
||||
static NR_SIB4_t *encode_and_decode_sib4(NR_SIB4_t *sib4, byte_array_t *ba_out)
|
||||
{
|
||||
*ba_out = do_SIB4_NR(sib4);
|
||||
EXPECT_GT(ba_out->len, 0);
|
||||
EXPECT_NE(ba_out->buf, nullptr);
|
||||
|
||||
NR_SIB4_t *decoded = nullptr;
|
||||
asn_dec_rval_t dec = uper_decode(nullptr, &asn_DEF_NR_SIB4, (void **)&decoded, ba_out->buf, ba_out->len, 0, 0);
|
||||
EXPECT_EQ(dec.code, RC_OK);
|
||||
EXPECT_NE(decoded, nullptr);
|
||||
return decoded;
|
||||
}
|
||||
|
||||
TEST(nr_asn1, rrc_reject)
|
||||
{
|
||||
unsigned char buf[1000];
|
||||
@@ -138,6 +187,167 @@ TEST(nr_asn1, rrc_reconfiguration)
|
||||
free_RRCReconfiguration_params(params);
|
||||
}
|
||||
|
||||
TEST(nr_asn1, sib2_basic_encode_decode)
|
||||
{
|
||||
NR_SIB2_t *sib2 = (NR_SIB2_t *)calloc_or_fail(1, sizeof(*sib2));
|
||||
sib2->cellReselectionInfoCommon.q_Hyst = NR_SIB2__cellReselectionInfoCommon__q_Hyst_dB0;
|
||||
sib2->cellReselectionServingFreqInfo.cellReselectionPriority = 3;
|
||||
sib2->cellReselectionServingFreqInfo.threshServingLowP = 10;
|
||||
sib2->intraFreqCellReselectionInfo.q_RxLevMin = -56;
|
||||
sib2->intraFreqCellReselectionInfo.s_IntraSearchP = 22;
|
||||
sib2->intraFreqCellReselectionInfo.t_ReselectionNR = 1;
|
||||
sib2->intraFreqCellReselectionInfo.deriveSSB_IndexFromCell = true;
|
||||
|
||||
byte_array_t ba = {};
|
||||
NR_SIB2_t *decoded = encode_and_decode_sib2(sib2, &ba);
|
||||
|
||||
EXPECT_EQ(decoded->cellReselectionServingFreqInfo.cellReselectionPriority,
|
||||
sib2->cellReselectionServingFreqInfo.cellReselectionPriority);
|
||||
EXPECT_EQ(decoded->cellReselectionServingFreqInfo.threshServingLowP, sib2->cellReselectionServingFreqInfo.threshServingLowP);
|
||||
|
||||
ASN_STRUCT_FREE(asn_DEF_NR_SIB2, sib2);
|
||||
ASN_STRUCT_FREE(asn_DEF_NR_SIB2, decoded);
|
||||
free_byte_array(ba);
|
||||
}
|
||||
|
||||
TEST(nr_asn1, sib3_basic_encode_decode)
|
||||
{
|
||||
NR_SIB3_t *sib3 = (NR_SIB3_t *)calloc_or_fail(1, sizeof(*sib3));
|
||||
sib3->intraFreqNeighCellList = (struct NR_IntraFreqNeighCellList *)calloc_or_fail(1, sizeof(*sib3->intraFreqNeighCellList));
|
||||
|
||||
NR_IntraFreqNeighCellInfo_t *cell = (NR_IntraFreqNeighCellInfo_t *)calloc_or_fail(1, sizeof(*cell));
|
||||
cell->physCellId = 100;
|
||||
cell->q_OffsetCell = NR_Q_OffsetRange_dB0;
|
||||
ASN_SEQUENCE_ADD(&sib3->intraFreqNeighCellList->list, cell);
|
||||
|
||||
byte_array_t ba = {};
|
||||
NR_SIB3_t *decoded = encode_and_decode_sib3(sib3, &ba);
|
||||
|
||||
ASSERT_NE(decoded->intraFreqNeighCellList, nullptr);
|
||||
EXPECT_EQ(decoded->intraFreqNeighCellList->list.count, 1);
|
||||
EXPECT_EQ(decoded->intraFreqNeighCellList->list.array[0]->physCellId, cell->physCellId);
|
||||
|
||||
ASN_STRUCT_FREE(asn_DEF_NR_SIB3, sib3);
|
||||
ASN_STRUCT_FREE(asn_DEF_NR_SIB3, decoded);
|
||||
free_byte_array(ba);
|
||||
}
|
||||
|
||||
TEST(nr_asn1, sib4_basic_encode_decode)
|
||||
{
|
||||
NR_SIB4_t *sib4 = (NR_SIB4_t *)calloc_or_fail(1, sizeof(*sib4));
|
||||
|
||||
NR_InterFreqCarrierFreqInfo_t *carrier = (NR_InterFreqCarrierFreqInfo_t *)calloc_or_fail(1, sizeof(*carrier));
|
||||
carrier->dl_CarrierFreq = 2000;
|
||||
carrier->ssbSubcarrierSpacing = NR_SubcarrierSpacing_kHz15;
|
||||
carrier->deriveSSB_IndexFromCell = true;
|
||||
carrier->q_RxLevMin = -56;
|
||||
carrier->t_ReselectionNR = 1;
|
||||
carrier->threshX_HighP = 5;
|
||||
carrier->threshX_LowP = 3;
|
||||
carrier->interFreqNeighCellList = (struct NR_InterFreqNeighCellList *)calloc_or_fail(1, sizeof(*carrier->interFreqNeighCellList));
|
||||
|
||||
NR_InterFreqNeighCellInfo_t *ncell = (NR_InterFreqNeighCellInfo_t *)calloc_or_fail(1, sizeof(*ncell));
|
||||
ncell->physCellId = 200;
|
||||
ncell->q_OffsetCell = NR_Q_OffsetRange_dB0;
|
||||
ASN_SEQUENCE_ADD(&carrier->interFreqNeighCellList->list, ncell);
|
||||
ASN_SEQUENCE_ADD(&sib4->interFreqCarrierFreqList.list, carrier);
|
||||
|
||||
byte_array_t ba = {};
|
||||
NR_SIB4_t *decoded = encode_and_decode_sib4(sib4, &ba);
|
||||
|
||||
ASSERT_GT(decoded->interFreqCarrierFreqList.list.count, 0);
|
||||
EXPECT_EQ(decoded->interFreqCarrierFreqList.list.array[0]->dl_CarrierFreq, carrier->dl_CarrierFreq);
|
||||
|
||||
ASN_STRUCT_FREE(asn_DEF_NR_SIB4, sib4);
|
||||
ASN_STRUCT_FREE(asn_DEF_NR_SIB4, decoded);
|
||||
free_byte_array(ba);
|
||||
}
|
||||
|
||||
TEST(nr_asn1, inter_freq_carrier_freq_info_ranges)
|
||||
{
|
||||
NR_SIB4_t *sib4 = (NR_SIB4_t *)calloc_or_fail(1, sizeof(*sib4));
|
||||
ASN_SEQUENCE_ADD(&sib4->interFreqCarrierFreqList.list,
|
||||
(NR_InterFreqCarrierFreqInfo_t *)calloc_or_fail(1, sizeof(NR_InterFreqCarrierFreqInfo_t)));
|
||||
|
||||
NR_InterFreqCarrierFreqInfo_t *carrier = sib4->interFreqCarrierFreqList.list.array[0];
|
||||
carrier->dl_CarrierFreq = 640000;
|
||||
carrier->ssbSubcarrierSpacing = NR_SubcarrierSpacing_kHz30;
|
||||
carrier->deriveSSB_IndexFromCell = 1;
|
||||
carrier->threshX_HighP = 0;
|
||||
carrier->threshX_LowP = 0;
|
||||
|
||||
NR_Q_OffsetRange_t *qoff = (NR_Q_OffsetRange_t *)calloc_or_fail(1, sizeof(*qoff));
|
||||
*qoff = 15; // arbitrary valid Q-OffsetRange enum
|
||||
carrier->q_OffsetFreq = qoff;
|
||||
|
||||
// Optional threshX_Q struct
|
||||
carrier->threshX_Q = (decltype(carrier->threshX_Q))calloc_or_fail(1, sizeof(*carrier->threshX_Q));
|
||||
carrier->threshX_Q->threshX_HighQ = 0;
|
||||
carrier->threshX_Q->threshX_LowQ = 0;
|
||||
|
||||
auto try_encode = [](NR_SIB4_t *s, bool expect_success) {
|
||||
byte_array_t ba = do_SIB4_NR(s);
|
||||
int len = (int)ba.len;
|
||||
if (expect_success) {
|
||||
EXPECT_GT(len, 0);
|
||||
EXPECT_NE(ba.buf, nullptr);
|
||||
} else {
|
||||
EXPECT_LE(len, 0);
|
||||
}
|
||||
free_byte_array(ba);
|
||||
};
|
||||
|
||||
// Valid lower bounds for all constrained scalars
|
||||
carrier->q_RxLevMin = -70;
|
||||
carrier->t_ReselectionNR = 0;
|
||||
carrier->threshX_HighP = 0;
|
||||
carrier->threshX_LowP = 0;
|
||||
carrier->threshX_Q->threshX_HighQ = 0;
|
||||
carrier->threshX_Q->threshX_LowQ = 0;
|
||||
*qoff = 15;
|
||||
try_encode(sib4, true);
|
||||
|
||||
// Invalid q_RxLevMin (below ASN.1 range)
|
||||
carrier->q_RxLevMin = -80;
|
||||
carrier->t_ReselectionNR = 0;
|
||||
try_encode(sib4, false);
|
||||
|
||||
// Invalid t_ReselectionNR (above ASN.1 range)
|
||||
carrier->q_RxLevMin = -70;
|
||||
carrier->t_ReselectionNR = 8;
|
||||
try_encode(sib4, false);
|
||||
|
||||
// Restore valid q_RxLevMin / t_ReselectionNR
|
||||
carrier->q_RxLevMin = -70;
|
||||
carrier->t_ReselectionNR = 0;
|
||||
|
||||
// Invalid threshX_HighP (below 0)
|
||||
carrier->threshX_HighP = -1;
|
||||
try_encode(sib4, false);
|
||||
carrier->threshX_HighP = 0;
|
||||
|
||||
// Invalid threshX_LowP (below 0)
|
||||
carrier->threshX_LowP = -1;
|
||||
try_encode(sib4, false);
|
||||
carrier->threshX_LowP = 0;
|
||||
|
||||
// Invalid threshX_HighQ (below 0)
|
||||
carrier->threshX_Q->threshX_HighQ = -1;
|
||||
try_encode(sib4, false);
|
||||
carrier->threshX_Q->threshX_HighQ = 0;
|
||||
|
||||
// Invalid threshX_LowQ (below 0)
|
||||
carrier->threshX_Q->threshX_LowQ = -1;
|
||||
try_encode(sib4, false);
|
||||
carrier->threshX_Q->threshX_LowQ = 0;
|
||||
|
||||
// Invalid q_OffsetFreq (outside [-24,24] enum range)
|
||||
*qoff = 99;
|
||||
try_encode(sib4, false);
|
||||
|
||||
ASN_STRUCT_FREE(asn_DEF_NR_SIB4, sib4);
|
||||
}
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
logInit();
|
||||
|
||||
@@ -312,21 +312,107 @@ 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;
|
||||
|
||||
/** @brief Neighbor cell configuration structure
|
||||
* Single source of truth for neighbor cell information, used across multiple protocols/scopes:
|
||||
* - Handover (NGAP/XnAP): for target gNB (ID, plmn, tac, nrcell_id, physicalCellId)
|
||||
* - Measurement (MeasConfig): physicalCellId, absoluteFrequencySSB, band
|
||||
* - DU validation: all fields validated against actual cell information
|
||||
* - SIB3/SIB4 generation: physicalCellId, absoluteFrequencySSB, subcarrierSpacing, band
|
||||
* References:
|
||||
* - 3GPP TS 38.331 (RRC) for SIB3/SIB4 and MeasConfig
|
||||
* - 3GPP TS 38.413 (NGAP) / TS 38.423 (XnAP) for target cell identification */
|
||||
typedef struct {
|
||||
// gNB identifier (for target node in NGAP/XnAP handover)
|
||||
uint32_t gNB_ID;
|
||||
// NR Cell Global Identifier (for NGAP/XnAP handover, DU validation)
|
||||
uint64_t nrcell_id;
|
||||
// Physical Cell ID (PCI) (used in HandoverPreparationInformation and MeasObjectNR)
|
||||
int physicalCellId;
|
||||
// SSB absolute frequency (ARFCN) (for MeasObjectNR, intra/inter-frequency determination)
|
||||
int absoluteFrequencySSB;
|
||||
// SSB subcarrier spacing (for MeasObjectNR/SIB4)
|
||||
int subcarrierSpacing;
|
||||
// Frequency band indicator (for MeasObjectNR/SIB4)
|
||||
int band;
|
||||
// PLMN identity (for target node in NGAP/XnAP handover)
|
||||
plmn_id_t plmn;
|
||||
// Tracking Area Code (for target node in NGAP/XnAP handover)
|
||||
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 {
|
||||
uint64_t nr_cell_id;
|
||||
seq_arr_t *neighbour_cells;
|
||||
seq_arr_t neighbour_cells;
|
||||
} neighbour_cell_configuration_t;
|
||||
|
||||
typedef struct nr_mac_rrc_dl_if_s {
|
||||
@@ -418,6 +504,101 @@ typedef struct nr_rrc_cuup_container_t {
|
||||
sctp_assoc_t assoc_id;
|
||||
} nr_rrc_cuup_container_t;
|
||||
|
||||
/**Timers for SIB2 MobilityStateParameters (TS 38.331)
|
||||
* Value set shared by T-Evaluation and T-HystNormal: (s30, s60, s120, s180, s240). */
|
||||
typedef enum sib2_mobility_state_timer_e {
|
||||
SIB2_MOBILITY_STATE_TIMER_S30,
|
||||
SIB2_MOBILITY_STATE_TIMER_S60,
|
||||
SIB2_MOBILITY_STATE_TIMER_S120,
|
||||
SIB2_MOBILITY_STATE_TIMER_S180,
|
||||
SIB2_MOBILITY_STATE_TIMER_S240,
|
||||
} sib2_mobility_state_timer_t;
|
||||
|
||||
/** Q-HystSF scaling factors for Q_hyst (TS 38.331)
|
||||
* Speed-dependent offsets (-6, -4, -2, 0 dB) applied to the base Q_hyst
|
||||
* when the UE is in medium or high mobility state. */
|
||||
typedef enum sib2_q_hystsf_e {
|
||||
SIB2_Q_HYSTSF_DB_6,
|
||||
SIB2_Q_HYSTSF_DB_4,
|
||||
SIB2_Q_HYSTSF_DB_2,
|
||||
SIB2_Q_HYSTSF_DB0,
|
||||
} sib2_q_hystsf_t;
|
||||
|
||||
/** SIB2 speedStateReselectionPars configuration
|
||||
* Mirrors TS 38.331 MobilityStateParameters and Q-hystSF. */
|
||||
typedef struct sib2_speed_state_reselection_pars_s {
|
||||
/* t-Evaluation: duration for evaluating allowed reselections
|
||||
* to enter mobility states (s30, s60, s120, s180, s240) */
|
||||
sib2_mobility_state_timer_t t_Evaluation;
|
||||
/* t-HystNormal: additional time period to evaluate reselection criteria
|
||||
* before returning to normal mobility (s30, s60, s120, s180, s240) */
|
||||
sib2_mobility_state_timer_t t_HystNormal;
|
||||
/* n-CellChangeMedium: max number of cell reselections to enter medium mobility state (1..16) */
|
||||
int n_CellChangeMedium;
|
||||
/* n-CellChangeHigh: max number of cell reselections to enter high mobility state (1..16) */
|
||||
int n_CellChangeHigh;
|
||||
/* sf-Medium: speed-dependent scaling factor for Qhyst in medium state (-6, -4, -2, 0 dB) */
|
||||
sib2_q_hystsf_t sf_Medium;
|
||||
/* sf-High: speed-dependent scaling factor for Qhyst in high state (-6, -4, -2, 0 dB) */
|
||||
sib2_q_hystsf_t sf_High;
|
||||
} sib2_speed_state_reselection_pars_t;
|
||||
|
||||
/** SIB2 cellReselectionServingFreqInfo (TS 38.331) */
|
||||
typedef struct {
|
||||
/* s-NonIntraSearchP: Srxlev threshold to trigger NR inter-freq / inter-RAT measurements (0..31)*2 dB.
|
||||
* Set to -1 to omit (optional ASN.1 field). */
|
||||
int s_NonIntraSearchP;
|
||||
/* s-NonIntraSearchQ: Squal threshold to trigger NR inter-freq / inter-RAT measurements (0..31)*2 dB */
|
||||
int s_NonIntraSearchQ;
|
||||
/* threshServingLowP: Srxlev threshold (dB) used by the UE on the serving cell
|
||||
* when reselecting towards a lower-priority RAT/frequency (0..31)*2 dB */
|
||||
int threshServingLowP;
|
||||
/* threshServingLowQ: Squal threshold (dB) used by the UE on the serving cell
|
||||
* when reselecting towards a lower-priority RAT/frequency (0..31)*2 dB.
|
||||
* Set to -1 to omit (optional ASN.1 field). */
|
||||
int threshServingLowQ;
|
||||
/* cellReselectionPriority: absolute priority of serving NR frequency (0..7) */
|
||||
int cellReselectionPriority;
|
||||
} cell_reselection_serving_freq_info_t;
|
||||
|
||||
/** SIB2 cellReselectionInfoCommon (TS 38.331) */
|
||||
typedef struct {
|
||||
/* q-Hyst: hysteresis added to serving-cell ranking R_s (0..24 dB) */
|
||||
int q_Hyst;
|
||||
/* speedStateReselectionPars: MobilityStateParameters + q-HystSF */
|
||||
sib2_speed_state_reselection_pars_t *speedStateReselectionPars;
|
||||
} cell_reselection_info_common_t;
|
||||
|
||||
/** SIB2 intraFreqCellReselectionInfo (TS 38.331) */
|
||||
typedef struct {
|
||||
/* q-RxLevMin: minimum required RX level in the cell (dBm) */
|
||||
int q_RxLevMin;
|
||||
/* q-QualMin: minimum required quality level in the cell (dB); */
|
||||
int q_QualMin;
|
||||
/* s-IntraSearchP: Srxlev threshold for intra-freq meas (0..31) */
|
||||
int s_IntraSearchP;
|
||||
/* s-IntraSearchQ: Squal threshold for intra-freq meas (0..31) */
|
||||
int s_IntraSearchQ;
|
||||
/* t-ReselectionNR: NR cell reselection timer (0..7) */
|
||||
int t_ReselectionNR;
|
||||
} intra_freq_cell_reselection_info_t;
|
||||
|
||||
/** SIB2 configuration for idle/inactive cell reselection
|
||||
* (maps to SIB2 reselection parameters in TS 38.304/38.331, per cell).
|
||||
* @note Squal = Cell selection quality value (dB)
|
||||
* Srxlev = Cell selection RX level value (dB) */
|
||||
typedef struct sib2_config_s {
|
||||
// cellReselectionInfoCommon
|
||||
cell_reselection_info_common_t cell_reselection_info_common;
|
||||
// cellReselectionServingFreqInfo
|
||||
cell_reselection_serving_freq_info_t cell_reselection_serving_freq_info;
|
||||
// intraFreqCellReselectionInfo
|
||||
intra_freq_cell_reselection_info_t intra_freq_cell_reselection_info;
|
||||
/* deriveSSB_IndexFromCell: SIB2 deriveSSB-IndexFromCell - whether UE may
|
||||
* assume SFN/SSB alignment across cells on serving freq (per TS 38.304/38.133) */
|
||||
bool deriveSSB_IndexFromCell;
|
||||
} sib2_config_t;
|
||||
|
||||
//---NR---(completely change)---------------------
|
||||
typedef struct gNB_RRC_INST_s {
|
||||
|
||||
@@ -432,6 +613,8 @@ typedef struct gNB_RRC_INST_s {
|
||||
// RRC configuration
|
||||
nr_rrc_config_t configuration;
|
||||
seq_arr_t *SIBs;
|
||||
// SIB2 configuration for cell reselection parameters
|
||||
sib2_config_t sib2_config;
|
||||
|
||||
// gNB N3 GTPU instance
|
||||
instance_t e1_inst;
|
||||
@@ -443,6 +626,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;
|
||||
|
||||
|
||||
+13
-14
@@ -263,10 +263,8 @@ static bool eq_pci(const void *vval, const void *vit)
|
||||
|
||||
const nr_neighbour_cell_t *get_neighbour_cell_by_pci(const neighbour_cell_configuration_t *cell, int pci)
|
||||
{
|
||||
seq_arr_t *head = cell->neighbour_cells;
|
||||
DevAssert(head != NULL);
|
||||
LOG_D(NR_RRC, "Number of neighbour cells: %ld\n", head->size);
|
||||
elm_arr_t e = find_if(head, &pci, eq_pci);
|
||||
DevAssert(cell);
|
||||
elm_arr_t e = find_if((seq_arr_t *)&cell->neighbour_cells, &pci, eq_pci);
|
||||
if (e.found) {
|
||||
const nr_neighbour_cell_t *neighbour = (const nr_neighbour_cell_t *)e.it;
|
||||
LOG_D(NR_RRC, "Found matching neighbour cell with PCI %d and Cell ID %ld\n", neighbour->physicalCellId, neighbour->nrcell_id);
|
||||
@@ -772,23 +770,23 @@ NR_MeasConfig_t *nr_rrc_get_measconfig(const gNB_RRC_INST *rrc, uint64_t nr_cell
|
||||
const NR_MeasTimingList_t *mtlist = cell->mtc->criticalExtensions.choice.c1->choice.measTimingConf->measTiming;
|
||||
const NR_MeasTiming_t *mt = mtlist->list.array[0];
|
||||
const neighbour_cell_configuration_t *neighbour_config = get_neighbour_cell_config(rrc, cell->info.cell_id);
|
||||
seq_arr_t *neighbour_cells = NULL;
|
||||
if (neighbour_config)
|
||||
neighbour_cells = neighbour_config->neighbour_cells;
|
||||
const seq_arr_t *neighbour_cells = neighbour_config ? &neighbour_config->neighbour_cells : NULL;
|
||||
|
||||
int *neigh_a3_id = NULL;
|
||||
if (neighbour_cells && neighbour_cells->size)
|
||||
neigh_a3_id = calloc_or_fail(neighbour_cells->size, sizeof(int));
|
||||
|
||||
if (neighbour_cells && rrc->measurementConfiguration.a3_event_list && rrc->measurementConfiguration.a3_event_list->size > 0) {
|
||||
const nr_measurement_configuration_t *meas_cfg = &rrc->measurementConfiguration;
|
||||
const seq_arr_t *a3_event_list = meas_cfg->a3_event_list;
|
||||
if (neighbour_cells && a3_event_list && a3_event_list->size > 0) {
|
||||
/* Loop through neighbours and find related A3 configuration
|
||||
If no related A3 but there is default add the default one.
|
||||
If default one added once as a report, no need to add it again && duplication.
|
||||
*/
|
||||
LOG_D(NR_RRC, "Preparing A3 Event Measurement Configuration!\n");
|
||||
bool default_a3_added = false; // To ensure that the default configuration is only added once
|
||||
for (int i = 0; i < neighbour_cells->size; i++) {
|
||||
nr_neighbour_cell_t *neighbourCell = (nr_neighbour_cell_t *)seq_arr_at(neighbour_cells, i);
|
||||
int i = 0;
|
||||
FOR_EACH_SEQ_ARR(nr_neighbour_cell_t *, neighbourCell, neighbour_cells) {
|
||||
seq_arr_push_back(&neigh_seq, neighbourCell, sizeof(nr_neighbour_cell_t));
|
||||
const nr_a3_event_t *a3Event = get_a3_configuration((gNB_RRC_INST *)rrc, neighbourCell->physicalCellId);
|
||||
if (!a3Event) {
|
||||
@@ -814,12 +812,13 @@ NR_MeasConfig_t *nr_rrc_get_measconfig(const gNB_RRC_INST *rrc, uint64_t nr_cell
|
||||
}
|
||||
NR_ReportConfigId_t reportConfigId = neigh_a3_id[i];
|
||||
seq_arr_push_back(&rc_A3_seq, prepare_a3_event_report(a3Event, reportConfigId), sizeof(NR_ReportConfigToAddMod_t));
|
||||
i++;
|
||||
}
|
||||
}
|
||||
if (rrc->measurementConfiguration.per_event)
|
||||
rc_PER = prepare_periodic_event_report(rrc->measurementConfiguration.per_event);
|
||||
if (rrc->measurementConfiguration.a2_event)
|
||||
rc_A2 = prepare_a2_event_report(rrc->measurementConfiguration.a2_event);
|
||||
if (meas_cfg->per_event)
|
||||
rc_PER = prepare_periodic_event_report(meas_cfg->per_event);
|
||||
if (meas_cfg->a2_event)
|
||||
rc_A2 = prepare_a2_event_report(meas_cfg->a2_event);
|
||||
|
||||
NR_MeasConfig_t *result = get_MeasConfig(mt, band, cell->info.pci, rc_PER, rc_A2, &rc_A3_seq, &neigh_seq, neigh_a3_id);
|
||||
|
||||
|
||||
+450
-56
@@ -23,6 +23,11 @@
|
||||
#include "f1ap_messages_types.h"
|
||||
#include "ngap_messages_types.h"
|
||||
#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"
|
||||
#include "openair2/F1AP/f1ap_common.h"
|
||||
#include "openair2/F1AP/f1ap_ids.h"
|
||||
@@ -33,6 +38,7 @@
|
||||
#include "s1ap_messages_types.h"
|
||||
#include "tree.h"
|
||||
#include "uper_decoder.h"
|
||||
#include "common/utils/oai_asn1.h"
|
||||
#include "utils.h"
|
||||
#include "xer_encoder.h"
|
||||
|
||||
@@ -55,6 +61,375 @@ static NR_SSB_MTC_t *get_ssb_mtc(const NR_MeasurementTimingConfiguration_t *mtc)
|
||||
return ssb_mtc;
|
||||
}
|
||||
|
||||
/** @brief Map config q_Hyst (dB: 0,1,2,3,4,5,6,8,10..24) to ASN.1 q-Hyst enum index (0..15).
|
||||
* ASN.1 NR_SIB2 cellReselectionInfoCommon.q-Hyst is encoded with 16 values. */
|
||||
static long get_q_hyst_asn1(int q_hyst_db)
|
||||
{
|
||||
switch (q_hyst_db) {
|
||||
case 0:
|
||||
return NR_SIB2__cellReselectionInfoCommon__q_Hyst_dB0;
|
||||
case 1:
|
||||
return NR_SIB2__cellReselectionInfoCommon__q_Hyst_dB1;
|
||||
case 2:
|
||||
return NR_SIB2__cellReselectionInfoCommon__q_Hyst_dB2;
|
||||
case 3:
|
||||
return NR_SIB2__cellReselectionInfoCommon__q_Hyst_dB3;
|
||||
case 4:
|
||||
return NR_SIB2__cellReselectionInfoCommon__q_Hyst_dB4;
|
||||
case 5:
|
||||
return NR_SIB2__cellReselectionInfoCommon__q_Hyst_dB5;
|
||||
case 6:
|
||||
return NR_SIB2__cellReselectionInfoCommon__q_Hyst_dB6;
|
||||
case 8:
|
||||
return NR_SIB2__cellReselectionInfoCommon__q_Hyst_dB8;
|
||||
case 10:
|
||||
return NR_SIB2__cellReselectionInfoCommon__q_Hyst_dB10;
|
||||
case 12:
|
||||
return NR_SIB2__cellReselectionInfoCommon__q_Hyst_dB12;
|
||||
case 14:
|
||||
return NR_SIB2__cellReselectionInfoCommon__q_Hyst_dB14;
|
||||
case 16:
|
||||
return NR_SIB2__cellReselectionInfoCommon__q_Hyst_dB16;
|
||||
case 18:
|
||||
return NR_SIB2__cellReselectionInfoCommon__q_Hyst_dB18;
|
||||
case 20:
|
||||
return NR_SIB2__cellReselectionInfoCommon__q_Hyst_dB20;
|
||||
case 22:
|
||||
return NR_SIB2__cellReselectionInfoCommon__q_Hyst_dB22;
|
||||
case 24:
|
||||
return NR_SIB2__cellReselectionInfoCommon__q_Hyst_dB24;
|
||||
default:
|
||||
AssertFatal(false, "unexpected q-Hyst value %d (configuration should have been validated)\n", q_hyst_db);
|
||||
}
|
||||
}
|
||||
|
||||
/** @brief Build NR_SIB2 from validated SIB2 configuration.
|
||||
* @param cfg Validated SIB2 configuration from config file
|
||||
* @param ssbmtc SSB-MTC instance to attach to intraFreqCellReselectionInfo.smtc. */
|
||||
static NR_SIB2_t *get_sib2_from_cfg(const sib2_config_t *cfg, NR_SSB_MTC_t *ssbmtc)
|
||||
{
|
||||
NR_SIB2_t *sib2 = calloc_or_fail(1, sizeof(*sib2));
|
||||
typeof(sib2->cellReselectionInfoCommon) *common = &sib2->cellReselectionInfoCommon;
|
||||
const cell_reselection_info_common_t *cfg_common = &cfg->cell_reselection_info_common;
|
||||
|
||||
/* q-Hyst: ASN.1 enum (0..15) converted from config dB (0,1,2,3,4,5,6,8,10..24). */
|
||||
common->q_Hyst = get_q_hyst_asn1(cfg_common->q_Hyst);
|
||||
|
||||
/* speedStateReselectionPars */
|
||||
if (cfg_common->speedStateReselectionPars != NULL) {
|
||||
struct NR_SIB2__cellReselectionInfoCommon__speedStateReselectionPars *speed = calloc_or_fail(1, sizeof(*speed));
|
||||
NR_MobilityStateParameters_t mobilityStateParameters = {0};
|
||||
const sib2_speed_state_reselection_pars_t *pars = cfg_common->speedStateReselectionPars;
|
||||
/* t-Evaluation / t-HystNormal: (0..7) */
|
||||
mobilityStateParameters.t_Evaluation = pars->t_Evaluation;
|
||||
mobilityStateParameters.t_HystNormal = pars->t_HystNormal;
|
||||
/* n-CellChangeMedium / n-CellChangeHigh: (1..16) */
|
||||
mobilityStateParameters.n_CellChangeMedium = pars->n_CellChangeMedium;
|
||||
mobilityStateParameters.n_CellChangeHigh = pars->n_CellChangeHigh;
|
||||
speed->mobilityStateParameters = mobilityStateParameters;
|
||||
|
||||
/* q-HystSF: sf-Medium/sf-High (0..3) */
|
||||
struct NR_SIB2__cellReselectionInfoCommon__speedStateReselectionPars__q_HystSF qhyst = {0};
|
||||
qhyst.sf_Medium = pars->sf_Medium;
|
||||
qhyst.sf_High = pars->sf_High;
|
||||
speed->q_HystSF = qhyst;
|
||||
|
||||
common->speedStateReselectionPars = speed;
|
||||
}
|
||||
|
||||
/* cellReselectionServingFreqInfo */
|
||||
typeof(sib2->cellReselectionServingFreqInfo) *serv = &sib2->cellReselectionServingFreqInfo;
|
||||
const cell_reselection_serving_freq_info_t *cfg_serv = &cfg->cell_reselection_serving_freq_info;
|
||||
/* cellReselectionPriority: CellReselectionPriority (0..7) */
|
||||
serv->cellReselectionPriority = cfg_serv->cellReselectionPriority;
|
||||
/* threshServingLowP: ReselectionThreshold (0..31) */
|
||||
serv->threshServingLowP = cfg_serv->threshServingLowP;
|
||||
/* threshServingLowQ: ReselectionThresholdQ (0..31) */
|
||||
if (cfg_serv->threshServingLowQ != -1)
|
||||
asn1cCallocOne(serv->threshServingLowQ, cfg_serv->threshServingLowQ);
|
||||
/* s-NonIntraSearchP/Q: ReselectionThreshold/ReselectionThresholdQ (0..31) */
|
||||
if (cfg_serv->s_NonIntraSearchP != -1)
|
||||
asn1cCallocOne(serv->s_NonIntraSearchP, cfg_serv->s_NonIntraSearchP);
|
||||
if (cfg_serv->s_NonIntraSearchQ != -1)
|
||||
asn1cCallocOne(serv->s_NonIntraSearchQ, cfg_serv->s_NonIntraSearchQ);
|
||||
|
||||
/* intraFreqCellReselectionInfo */
|
||||
typeof(sib2->intraFreqCellReselectionInfo) *intra = &sib2->intraFreqCellReselectionInfo;
|
||||
const intra_freq_cell_reselection_info_t *cfg_intra = &cfg->intra_freq_cell_reselection_info;
|
||||
/* q-RxLevMin: Q-RxLevMin (-70..-22) */
|
||||
intra->q_RxLevMin = cfg_intra->q_RxLevMin;
|
||||
/* q-QualMin: Q-QualMin (-43..-12) */
|
||||
if (cfg_intra->q_QualMin != -1)
|
||||
asn1cCallocOne(intra->q_QualMin, cfg_intra->q_QualMin);
|
||||
/* s-IntraSearchP: ReselectionThreshold (0..31) */
|
||||
intra->s_IntraSearchP = cfg_intra->s_IntraSearchP;
|
||||
/* s-IntraSearchQ: ReselectionThresholdQ (0..31) */
|
||||
if (cfg_intra->s_IntraSearchQ != -1)
|
||||
asn1cCallocOne(intra->s_IntraSearchQ, cfg_intra->s_IntraSearchQ);
|
||||
/* t-ReselectionNR: (0..7) */
|
||||
intra->t_ReselectionNR = cfg_intra->t_ReselectionNR;
|
||||
/* deriveSSB-IndexFromCell (bool) */
|
||||
intra->deriveSSB_IndexFromCell = cfg->deriveSSB_IndexFromCell;
|
||||
/* smtc */
|
||||
intra->smtc = ssbmtc;
|
||||
|
||||
return sib2;
|
||||
}
|
||||
|
||||
/** @brief Map internal int enum to ASN.1 Q-OffsetRange value. */
|
||||
static long get_q_offset_asn1(int q_offset_db)
|
||||
{
|
||||
switch (q_offset_db) {
|
||||
case -24:
|
||||
return NR_Q_OffsetRange_dB_24;
|
||||
case -22:
|
||||
return NR_Q_OffsetRange_dB_22;
|
||||
case -20:
|
||||
return NR_Q_OffsetRange_dB_20;
|
||||
case -18:
|
||||
return NR_Q_OffsetRange_dB_18;
|
||||
case -16:
|
||||
return NR_Q_OffsetRange_dB_16;
|
||||
case -14:
|
||||
return NR_Q_OffsetRange_dB_14;
|
||||
case -12:
|
||||
return NR_Q_OffsetRange_dB_12;
|
||||
case -10:
|
||||
return NR_Q_OffsetRange_dB_10;
|
||||
case -8:
|
||||
return NR_Q_OffsetRange_dB_8;
|
||||
case -6:
|
||||
return NR_Q_OffsetRange_dB_6;
|
||||
case -5:
|
||||
return NR_Q_OffsetRange_dB_5;
|
||||
case -4:
|
||||
return NR_Q_OffsetRange_dB_4;
|
||||
case -3:
|
||||
return NR_Q_OffsetRange_dB_3;
|
||||
case -2:
|
||||
return NR_Q_OffsetRange_dB_2;
|
||||
case -1:
|
||||
return NR_Q_OffsetRange_dB_1;
|
||||
case 0:
|
||||
return NR_Q_OffsetRange_dB0;
|
||||
case 1:
|
||||
return NR_Q_OffsetRange_dB1;
|
||||
case 2:
|
||||
return NR_Q_OffsetRange_dB2;
|
||||
case 3:
|
||||
return NR_Q_OffsetRange_dB3;
|
||||
case 4:
|
||||
return NR_Q_OffsetRange_dB4;
|
||||
case 5:
|
||||
return NR_Q_OffsetRange_dB5;
|
||||
case 6:
|
||||
return NR_Q_OffsetRange_dB6;
|
||||
case 8:
|
||||
return NR_Q_OffsetRange_dB8;
|
||||
case 10:
|
||||
return NR_Q_OffsetRange_dB10;
|
||||
case 12:
|
||||
return NR_Q_OffsetRange_dB12;
|
||||
case 14:
|
||||
return NR_Q_OffsetRange_dB14;
|
||||
case 16:
|
||||
return NR_Q_OffsetRange_dB16;
|
||||
case 18:
|
||||
return NR_Q_OffsetRange_dB18;
|
||||
case 20:
|
||||
return NR_Q_OffsetRange_dB20;
|
||||
case 22:
|
||||
return NR_Q_OffsetRange_dB22;
|
||||
case 24:
|
||||
return NR_Q_OffsetRange_dB24;
|
||||
default:
|
||||
AssertFatal(false, "Invalid q_offset_db value %d\n", q_offset_db);
|
||||
}
|
||||
}
|
||||
|
||||
/** @brief Build a SIB3 (intra-frequency neighbour cell list) from the configured neighbour cells.
|
||||
* Only neighbours whose SSB ARFCN matches @param serving_ssb_arfcn are included.
|
||||
* @param neighbours list of configured neighbour cells
|
||||
* @param serving_ssb_arfcn SSB ARFCN of the serving cell, used to filter intra-frequency neighbours
|
||||
* @returns allocated NR_SIB3_t on success, NULL if no matching neighbours were found */
|
||||
static NR_SIB3_t *get_sib3_intra_freq_neighbors(const seq_arr_t *neighbours, uint32_t serving_ssb_arfcn)
|
||||
{
|
||||
DevAssert(neighbours);
|
||||
|
||||
NR_SIB3_t *sib3 = calloc_or_fail(1, sizeof(*sib3));
|
||||
sib3->intraFreqNeighCellList = calloc_or_fail(1, sizeof(*sib3->intraFreqNeighCellList));
|
||||
|
||||
int intra_count = 0;
|
||||
FOR_EACH_SEQ_ARR (const nr_neighbour_cell_t *, neigh, neighbours) {
|
||||
if (neigh->absoluteFrequencySSB != serving_ssb_arfcn)
|
||||
continue;
|
||||
|
||||
LOG_D(NR_RRC,
|
||||
"SIB3: intra-frequency neighbour candidate: cell ID %lu, PCI %d (SSB ARFCN %d)\n",
|
||||
neigh->nrcell_id,
|
||||
neigh->physicalCellId,
|
||||
neigh->absoluteFrequencySSB);
|
||||
|
||||
NR_IntraFreqNeighCellInfo_t *cell = calloc_or_fail(1, sizeof(*cell));
|
||||
cell->physCellId = neigh->physicalCellId;
|
||||
|
||||
const nr_neighbour_cell_neighbor_offset_t *off = &neigh->sib3.offset;
|
||||
/* q-OffsetCell: NR_Q-OffsetRange (-24..24 dB), 0 dB default */
|
||||
cell->q_OffsetCell = get_q_offset_asn1(off->q_OffsetCell);
|
||||
|
||||
/* q-RxLevMinOffsetCell / q-QualMinOffsetCell: (1..8) */
|
||||
if (off->q_RxLevMinOffsetCell != -1)
|
||||
asn1cCallocOne(cell->q_RxLevMinOffsetCell, off->q_RxLevMinOffsetCell);
|
||||
if (off->q_QualMinOffsetCell != -1)
|
||||
asn1cCallocOne(cell->q_QualMinOffsetCell, off->q_QualMinOffsetCell);
|
||||
|
||||
/* Add to SIB3 intra-frequency neighbour list */
|
||||
if (sib3->intraFreqNeighCellList->list.count >= NR_maxCellIntra) {
|
||||
LOG_W(NR_RRC, "SIB3: too many intra-frequency neighbors (max %d), skipping cell %ld\n", NR_maxCellIntra, neigh->nrcell_id);
|
||||
asn1cFreeStruc(asn_DEF_NR_IntraFreqNeighCellInfo, cell);
|
||||
break;
|
||||
}
|
||||
asn1cSeqAdd(&sib3->intraFreqNeighCellList->list, cell);
|
||||
++intra_count;
|
||||
}
|
||||
|
||||
if (intra_count == 0) {
|
||||
LOG_W(NR_RRC,
|
||||
"SIB3: no intra-frequency neighbours found for serving SSB ARFCN %u "
|
||||
"(configured neighbours %u), not building SIB3\n",
|
||||
serving_ssb_arfcn,
|
||||
(unsigned)seq_arr_size(neighbours));
|
||||
asn1cFreeStruc(asn_DEF_NR_SIB3, sib3);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
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) */
|
||||
@@ -152,7 +527,7 @@ 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 nr_rrc_cell_container_t *cell)
|
||||
static const 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);
|
||||
@@ -160,42 +535,7 @@ static neighbour_cell_configuration_t *get_cell_neighbour_list(const gNB_RRC_INS
|
||||
|
||||
void *it = bsearch((void *)&cell->info.cell_id, base, nmemb, size, nr_cell_id_match);
|
||||
|
||||
return (neighbour_cell_configuration_t *)it;
|
||||
}
|
||||
|
||||
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 Pointer to cell container
|
||||
*/
|
||||
static void label_intra_frequency_neighbours(gNB_RRC_INST *rrc, 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);
|
||||
if (!neighbour_cell_config)
|
||||
return;
|
||||
|
||||
uint32_t ssb_arfcn = get_ssb_arfcn(cell);
|
||||
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);
|
||||
return (const neighbour_cell_configuration_t *)it;
|
||||
}
|
||||
|
||||
static bool valid_du_in_neighbour_configs(const seq_arr_t *neighbour_cell_configuration, const f1ap_served_cell_info_t *cell)
|
||||
@@ -206,10 +546,8 @@ static bool valid_du_in_neighbour_configs(const seq_arr_t *neighbour_cell_config
|
||||
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++) {
|
||||
const nr_neighbour_cell_t *nc = seq_arr_at(neighbour_config->neighbour_cells, ni);
|
||||
FOR_EACH_SEQ_ARR(const neighbour_cell_configuration_t *, neighbour_config, neighbour_cell_configuration) {
|
||||
FOR_EACH_SEQ_ARR(const nr_neighbour_cell_t *, nc, &neighbour_config->neighbour_cells) {
|
||||
if (nc->nrcell_id != cell->nr_cellid)
|
||||
continue;
|
||||
// current cell is in the nc config, check that config matches
|
||||
@@ -299,6 +637,17 @@ static void cp_f1_served_cell_info_to_cell(nr_rrc_cell_container_t *dst, const f
|
||||
}
|
||||
}
|
||||
|
||||
static void add_si_msg(served_cells_to_activate_t *cell, int sib_type, const byte_array_t *enc)
|
||||
{
|
||||
AssertFatal(cell->num_SI < F1AP_MAX_NO_SIB_TYPES,
|
||||
"Too many SI messages (%u), max supported is %u\n",
|
||||
cell->num_SI,
|
||||
F1AP_MAX_NO_SIB_TYPES);
|
||||
cell->SI_msg[cell->num_SI].SI_container = copy_byte_array(*enc);
|
||||
cell->SI_msg[cell->num_SI].SI_type = sib_type;
|
||||
cell->num_SI++;
|
||||
}
|
||||
|
||||
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");
|
||||
@@ -427,16 +776,68 @@ void rrc_gNB_process_f1_setup_req(f1ap_setup_req_t *req, sctp_assoc_t assoc_id)
|
||||
// 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);
|
||||
FOR_EACH_SEQ_ARR (nr_SIBs_t *, sib, sibs) {
|
||||
switch (sib->SIB_type) {
|
||||
case 2: {
|
||||
case NR_SIB_2: {
|
||||
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;
|
||||
cell.SI_msg[cell.num_SI].SI_type = sib->SIB_type;
|
||||
cell.num_SI++;
|
||||
NR_SIB2_t *sib2 = get_sib2_from_cfg(&rrc->sib2_config, ssbmtc);
|
||||
byte_array_t enc = do_SIB2_NR(sib2);
|
||||
ASN_STRUCT_FREE(asn_DEF_NR_SIB2, sib2);
|
||||
if (!enc.buf || enc.len == 0) {
|
||||
free_byte_array(enc);
|
||||
LOG_E(NR_RRC, "SIB2 encoding failed\n");
|
||||
break;
|
||||
}
|
||||
add_si_msg(&cell, sib->SIB_type, &enc);
|
||||
free_byte_array(enc);
|
||||
LOG_I(NR_RRC, "DU %ld: added SIB2 to F1 Setup Response (cell %ld)\n", du->gNB_DU_id, new->info.cell_id);
|
||||
} break;
|
||||
case NR_SIB_3: {
|
||||
if (!rrc->neighbour_cell_configuration)
|
||||
break;
|
||||
const neighbour_cell_configuration_t *neighbour_config = get_cell_neighbour_list(rrc, new);
|
||||
if (!neighbour_config)
|
||||
break;
|
||||
const seq_arr_t *neigh_cells = &neighbour_config->neighbour_cells;
|
||||
|
||||
NR_SIB3_t *sib3 = get_sib3_intra_freq_neighbors(neigh_cells, get_ssb_arfcn(new));
|
||||
if (!sib3)
|
||||
break;
|
||||
|
||||
byte_array_t enc = do_SIB3_NR(sib3);
|
||||
ASN_STRUCT_FREE(asn_DEF_NR_SIB3, sib3);
|
||||
if (!enc.buf || enc.len == 0) {
|
||||
free_byte_array(enc);
|
||||
LOG_E(NR_RRC, "SIB3 encoding failed\n");
|
||||
break;
|
||||
}
|
||||
add_si_msg(&cell, sib->SIB_type, &enc);
|
||||
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);
|
||||
@@ -444,9 +845,6 @@ void rrc_gNB_process_f1_setup_req(f1ap_setup_req_t *req, sctp_assoc_t assoc_id)
|
||||
}
|
||||
}
|
||||
|
||||
if (new->mib != NULL &&new->sib1 != NULL)
|
||||
label_intra_frequency_neighbours(rrc, new);
|
||||
|
||||
resp.cells_to_activate[i] = cell;
|
||||
}
|
||||
|
||||
@@ -662,10 +1060,6 @@ void rrc_gNB_process_f1_du_configuration_update(f1ap_gnb_du_configuration_update
|
||||
LOG_I(NR_RRC, "update system information of DU %ld\n", du->gNB_DU_id);
|
||||
}
|
||||
}
|
||||
|
||||
if (cell && cell->mib != NULL && cell->sib1 != NULL && cell->assoc_id == du->assoc_id) {
|
||||
label_intra_frequency_neighbours(rrc, cell);
|
||||
}
|
||||
}
|
||||
|
||||
if (conf_up->num_cells_to_delete > 0) {
|
||||
|
||||
@@ -607,7 +607,6 @@ void nr_HO_N2_trigger_telnet(gNB_RRC_INST *rrc, uint32_t neighbour_pci, uint32_t
|
||||
if (neighbour_pci == scell_pci) {
|
||||
LOG_I(NR_RRC, "UE %d: trigger handover on the same cell PCI=%d\n", rrc_ue_id, neighbour_pci);
|
||||
nr_neighbour_cell_t neighbourConfig = {
|
||||
.isIntraFrequencyNeighbour = true,
|
||||
.gNB_ID = du->gNB_DU_id,
|
||||
.nrcell_id = pcell->info.cell_id,
|
||||
.physicalCellId = pcell->info.pci,
|
||||
|
||||
Reference in New Issue
Block a user