diff --git a/ci-scripts/conf_files/gnb-cu.sa.band78.106prb.conf b/ci-scripts/conf_files/gnb-cu.sa.band78.106prb.conf index 015d77cacc..8460983d2b 100644 --- a/ci-scripts/conf_files/gnb-cu.sa.band78.106prb.conf +++ b/ci-scripts/conf_files/gnb-cu.sa.band78.106prb.conf @@ -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; diff --git a/ci-scripts/conf_files/neighbour-config.conf b/ci-scripts/conf_files/neighbour-config.conf index fe91ff2f08..57aa55f772 100644 --- a/ci-scripts/conf_files/neighbour-config.conf +++ b/ci-scripts/conf_files/neighbour-config.conf @@ -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; } ); } diff --git a/common/config/config_paramdesc.h b/common/config/config_paramdesc.h index 51c8626116..efb5c956d2 100644 --- a/common/config/config_paramdesc.h +++ b/common/config/config_paramdesc.h @@ -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; diff --git a/common/config/config_userapi.c b/common/config/config_userapi.c index 76f9722d0a..c009187800 100644 --- a/common/config/config_userapi.c +++ b/common/config/config_userapi.c @@ -183,14 +183,35 @@ int config_check_intval(configmodule_interface_t *cfg, paramdef_t *param) return -1; } - for ( int i=0; ichkPptr->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; } diff --git a/common/config/config_userapi.h b/common/config/config_userapi.h index fafbd81019..b959cb57fe 100644 --- a/common/config/config_userapi.h +++ b/common/config/config_userapi.h @@ -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); diff --git a/common/utils/nr/nr_common.h b/common/utils/nr/nr_common.h index 8580a0491b..228e51feb2 100644 --- a/common/utils/nr/nr_common.h +++ b/common/utils/nr/nr_common.h @@ -13,6 +13,7 @@ #include #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 { diff --git a/doc/FEATURE_SET.md b/doc/FEATURE_SET.md index 95aa32166f..bc6ba6b4a3 100644 --- a/doc/FEATURE_SET.md +++ b/doc/FEATURE_SET.md @@ -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 diff --git a/doc/RRC/rrc-dev.md b/doc/RRC/rrc-dev.md index eee1b861fa..a218997d3a 100644 --- a/doc/RRC/rrc-dev.md +++ b/doc/RRC/rrc-dev.md @@ -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: diff --git a/doc/RRC/rrc-usage.md b/doc/RRC/rrc-usage.md index 46ff9f061c..e828001d1b 100644 --- a/doc/RRC/rrc-usage.md +++ b/doc/RRC/rrc-usage.md @@ -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; } ); } diff --git a/doc/handover-tutorial.md b/doc/handover-tutorial.md index 68e685c83d..02736da931 100644 --- a/doc/handover-tutorial.md +++ b/doc/handover-tutorial.md @@ -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. diff --git a/openair2/COMMON/f1ap_messages_types.h b/openair2/COMMON/f1ap_messages_types.h index 37c4a7ee83..2df2c783bf 100644 --- a/openair2/COMMON/f1ap_messages_types.h +++ b/openair2/COMMON/f1ap_messages_types.h @@ -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; diff --git a/openair2/F1AP/lib/f1ap_interface_management.c b/openair2/F1AP/lib/f1ap_interface_management.c index bb2015b33e..815a9f1b82 100644 --- a/openair2/F1AP/lib/f1ap_interface_management.c +++ b/openair2/F1AP/lib/f1ap_interface_management.c @@ -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); diff --git a/openair2/F1AP/tests/f1ap_lib_test.c b/openair2/F1AP/tests/f1ap_lib_test.c index 8f315ff8ae..1140e3c39b 100644 --- a/openair2/F1AP/tests/f1ap_lib_test.c +++ b/openair2/F1AP/tests/f1ap_lib_test.c @@ -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); diff --git a/openair2/GNB_APP/gnb_config.c b/openair2/GNB_APP/gnb_config.c index a73a77b0ee..856078e6f7 100644 --- a/openair2/GNB_APP/gnb_config.c +++ b/openair2/GNB_APP/gnb_config.c @@ -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 sib2_config = fill_sib2_configuration(i); }//End if (num_gnbs>0) if (!NODE_IS_DU(rrc->node_type)) diff --git a/openair2/GNB_APP/gnb_config_common.c b/openair2/GNB_APP/gnb_config_common.c index 395af98750..aca2457d08 100644 --- a/openair2/GNB_APP/gnb_config_common.c +++ b/openair2/GNB_APP/gnb_config_common.c @@ -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; } diff --git a/openair2/GNB_APP/gnb_config_common.h b/openair2/GNB_APP/gnb_config_common.h index ae2ae4e799..bfa875c13d 100644 --- a/openair2/GNB_APP/gnb_config_common.h +++ b/openair2/GNB_APP/gnb_config_common.h @@ -4,6 +4,8 @@ #include #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); diff --git a/openair2/GNB_APP/gnb_paramdef.h b/openair2/GNB_APP/gnb_paramdef.h index 8265303969..35a1c096d1 100644 --- a/openair2/GNB_APP/gnb_paramdef.h +++ b/openair2/GNB_APP/gnb_paramdef.h @@ -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" diff --git a/openair2/LAYER2/NR_MAC_gNB/config.c b/openair2/LAYER2/NR_MAC_gNB/config.c index 202590f9fc..1d217b7c72 100644 --- a/openair2/LAYER2/NR_MAC_gNB/config.c +++ b/openair2/LAYER2/NR_MAC_gNB/config.c @@ -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); diff --git a/openair2/RRC/NR/MESSAGES/asn1_msg.c b/openair2/RRC/NR/MESSAGES/asn1_msg.c index 8d5d00c975..d818229054 100644 --- a/openair2/RRC/NR/MESSAGES/asn1_msg.c +++ b/openair2/RRC/NR/MESSAGES/asn1_msg.c @@ -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) diff --git a/openair2/RRC/NR/MESSAGES/asn1_msg.h b/openair2/RRC/NR/MESSAGES/asn1_msg.h index 7aeb2b4ed0..384096ee16 100644 --- a/openair2/RRC/NR/MESSAGES/asn1_msg.h +++ b/openair2/RRC/NR/MESSAGES/asn1_msg.h @@ -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); diff --git a/openair2/RRC/NR/MESSAGES/tests/test_asn1_msg.cpp b/openair2/RRC/NR/MESSAGES/tests/test_asn1_msg.cpp index 4867d7f8ab..c14e9bdba0 100644 --- a/openair2/RRC/NR/MESSAGES/tests/test_asn1_msg.cpp +++ b/openair2/RRC/NR/MESSAGES/tests/test_asn1_msg.cpp @@ -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(); diff --git a/openair2/RRC/NR/nr_rrc_defs.h b/openair2/RRC/NR/nr_rrc_defs.h index 606eb86327..5a4063c3f2 100644 --- a/openair2/RRC/NR/nr_rrc_defs.h +++ b/openair2/RRC/NR/nr_rrc_defs.h @@ -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; diff --git a/openair2/RRC/NR/rrc_gNB.c b/openair2/RRC/NR/rrc_gNB.c index 096dc98eea..4c47e6a23b 100644 --- a/openair2/RRC/NR/rrc_gNB.c +++ b/openair2/RRC/NR/rrc_gNB.c @@ -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); diff --git a/openair2/RRC/NR/rrc_gNB_du.c b/openair2/RRC/NR/rrc_gNB_du.c index ec139e684d..6b60e5b6b9 100644 --- a/openair2/RRC/NR/rrc_gNB_du.c +++ b/openair2/RRC/NR/rrc_gNB_du.c @@ -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) { diff --git a/openair2/RRC/NR/rrc_gNB_mobility.c b/openair2/RRC/NR/rrc_gNB_mobility.c index e36e6ee684..fdbc2d66af 100644 --- a/openair2/RRC/NR/rrc_gNB_mobility.c +++ b/openair2/RRC/NR/rrc_gNB_mobility.c @@ -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,