From 72bfd1b5c3280725b44203f697cf98ebc84cebac Mon Sep 17 00:00:00 2001 From: francescomani Date: Mon, 25 Aug 2025 19:04:22 +0200 Subject: [PATCH 01/97] switch to initial BWP if current BWP has no PRACH in gNB --- .../NR_MAC_gNB/gNB_scheduler_primitives.c | 24 ++++++++++++------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_primitives.c b/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_primitives.c index ae5305d77c..e718210e8b 100644 --- a/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_primitives.c +++ b/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_primitives.c @@ -2718,8 +2718,7 @@ void configure_UE_BWP(gNB_MAC_INST *nr_mac, AssertFatal(dl_bwp_switch == ul_bwp_switch, "Different UL and DL BWP not supported\n"); DL_BWP->bwp_id = dl_bwp_switch; UL_BWP->bwp_id = ul_bwp_switch; - } - else { + } else { // (re)configuring BWP // TODO BWP switching not via RRC reconfiguration // via RRC if firstActiveXlinkBWP_Id is NULL, MAC stays on the same BWP as before @@ -2768,8 +2767,7 @@ void configure_UE_BWP(gNB_MAC_INST *nr_mac, UL_BWP->pusch_Config = ubwpd->pusch_Config->choice.setup; UL_BWP->pucch_Config = ubwpd->pucch_Config->choice.setup; UL_BWP->srs_Config = ubwpd->srs_Config->choice.setup; - } - else { + } else { DL_BWP->bwp_id = 0; UL_BWP->bwp_id = 0; DL_BWP->pdsch_Config = NULL; @@ -2778,11 +2776,6 @@ void configure_UE_BWP(gNB_MAC_INST *nr_mac, UL_BWP->configuredGrantConfig = NULL; } - if (old_dl_bwp_id != DL_BWP->bwp_id) - LOG_I(NR_MAC, "Switching to DL-BWP %li\n", DL_BWP->bwp_id); - if (old_ul_bwp_id != UL_BWP->bwp_id) - LOG_I(NR_MAC, "Switching to UL-BWP %li\n", UL_BWP->bwp_id); - // TDA lists if (DL_BWP->bwp_id>0) DL_BWP->tdaList_Common = dl_bwp->bwp_Common->pdsch_ConfigCommon->choice.setup->pdsch_TimeDomainAllocationList; @@ -2832,6 +2825,19 @@ void configure_UE_BWP(gNB_MAC_INST *nr_mac, UL_BWP->rach_ConfigCommon = scc->uplinkConfigCommon->initialUplinkBWP->rach_ConfigCommon->choice.setup; } + // if PRACH occasions are not configured for the active UL BWP + // switch the active UL BWP to BWP indicated by initialUplinkBWP + // as described in 5.15 of 38.321 + if (is_RA && !UL_BWP->rach_ConfigCommon) { + LOG_I(NR_MAC, "Cannot perform RA in current BWP, switching to initial BWP\n"); + configure_UE_BWP(nr_mac, scc, UE, is_RA, target_ss, 0, 0); + return; + } + if (old_dl_bwp_id != DL_BWP->bwp_id) + LOG_I(NR_MAC, "Switching to DL-BWP %li\n", DL_BWP->bwp_id); + if (old_ul_bwp_id != UL_BWP->bwp_id) + LOG_I(NR_MAC, "Switching to UL-BWP %li\n", UL_BWP->bwp_id); + NR_UE_sched_ctrl_t *sched_ctrl = &UE->UE_sched_ctrl; // Reset required fields in sched_ctrl (e.g. ul_ri and tpmi) reset_sched_ctrl(sched_ctrl); From 47554f89ce19ac89ecba526b357b139d2fe9418f Mon Sep 17 00:00:00 2001 From: francescomani Date: Mon, 25 Aug 2025 16:14:56 +0200 Subject: [PATCH 02/97] fix switch to initial UL BWP if no PRACH common configuration in current BWP --- openair2/LAYER2/NR_MAC_UE/nr_ra_procedures.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/openair2/LAYER2/NR_MAC_UE/nr_ra_procedures.c b/openair2/LAYER2/NR_MAC_UE/nr_ra_procedures.c index 58269f769a..42712ebbb4 100644 --- a/openair2/LAYER2/NR_MAC_UE/nr_ra_procedures.c +++ b/openair2/LAYER2/NR_MAC_UE/nr_ra_procedures.c @@ -879,23 +879,23 @@ bool init_RA(NR_UE_MAC_INST_t *mac, int frame) // set POWER_OFFSET_2STEP_RA to 0 dB prach_resources->power_offset_2step = 0; - const NR_UE_UL_BWP_t *current_UL_BWP = mac->current_UL_BWP; // perform the BWP operation as specified in clause 5.15 // if PRACH occasions are not configured for the active UL BWP - if (!current_UL_BWP->rach_ConfigCommon) { + if (!mac->current_UL_BWP->rach_ConfigCommon) { // switch the active UL BWP to BWP indicated by initialUplinkBWP - current_UL_BWP = get_ul_bwp_structure(mac, 0, false); + mac->current_UL_BWP = get_ul_bwp_structure(mac, 0, false); // if the Serving Cell is an SpCell // switch the active DL BWP to BWP indicated by initialDownlinkBWP mac->current_DL_BWP = get_dl_bwp_structure(mac, 0, false); } else { // if the active DL BWP does not have the same bwp-Id as the active UL BWP - if (current_UL_BWP->bwp_id != mac->current_DL_BWP->bwp_id) { + if (mac->current_UL_BWP->bwp_id != mac->current_DL_BWP->bwp_id) { // switch the active DL BWP to the DL BWP with the same bwp-Id as the active UL BWP mac->current_DL_BWP = get_dl_bwp_structure(mac, 0, false); } } + const NR_UE_UL_BWP_t *current_UL_BWP = mac->current_UL_BWP; NR_RACH_ConfigCommon_t *nr_rach_ConfigCommon = current_UL_BWP->rach_ConfigCommon; AssertFatal(nr_rach_ConfigCommon, "rach-ConfigCommon should be configured here\n"); // stop the bwp-InactivityTimer associated with the active DL BWP of this Serving Cell, if running From 966bab9cca9bf9a9578ab065d27df6d90fecc423 Mon Sep 17 00:00:00 2001 From: francescomani Date: Tue, 23 Sep 2025 08:39:41 +0200 Subject: [PATCH 03/97] do not assert for mismatched TBS in phy-test mode --- openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_dlsch.c | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_dlsch.c b/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_dlsch.c index 552b1fa041..44c9072a15 100644 --- a/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_dlsch.c +++ b/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_dlsch.c @@ -1250,10 +1250,16 @@ void nr_schedule_ue_spec(module_id_t module_id, current_harq_pid, harq->round, harq->ndi); - AssertFatal(harq->sched_pdsch.tb_size == TBS, - "UE %04x mismatch between scheduled TBS and buffered TB for HARQ PID %d\n", - UE->rnti, - current_harq_pid); + if (!get_softmodem_params()->phy_test) + AssertFatal(harq->sched_pdsch.tb_size == TBS, + "UE %04x mismatch between scheduled TBS and buffered TB for HARQ PID %d\n", + UE->rnti, + current_harq_pid); + else if (harq->sched_pdsch.tb_size != TBS) + LOG_E(NR_MAC, + "Mismatch between scheduled TBS and buffered TB for HARQ PID %d. No RTX control in phy-test mode. " + "Possible causes: presence of CSI-RS or DLSCH scheduled in the mixed slot.\n", + current_harq_pid); T(T_GNB_MAC_RETRANSMISSION_DL_PDU_WITH_DATA, T_INT(module_id), T_INT(CC_id), T_INT(rnti), T_INT(frame), T_INT(slot), T_INT(current_harq_pid), T_INT(harq->round), T_BUFFER(harq->transportBlock.buf, TBS)); UE->mac_stats.dl.total_rbs_retx += sched_pdsch->rbSize; From 2d70cec6ddef08fbb0a7e3853d8a8718af819b5c Mon Sep 17 00:00:00 2001 From: Robert Schmidt Date: Fri, 25 Jul 2025 14:41:30 +0200 Subject: [PATCH 04/97] Remove autotools definitions They do not seem to be used anywhere (I grepped). --- CMakeLists.txt | 3 --- 1 file changed, 3 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 058bfd7a39..e996e633b4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -173,9 +173,6 @@ else () set(C_FLAGS_PROCESSOR "${C_FLAGS_PROCESSOR} -march=armv8.2-a") endif() -# add autotools definitions that were maybe used! -add_definitions(-DSTDC_HEADERS=1 -DHAVE_SYS_TYPES_H=1 -DHAVE_SYS_STAT_H=1 -DHAVE_STDLIB_H=1 -DHAVE_STRING_H=1 -DHAVE_MEMORY_H=1 -DHAVE_STRINGS_H=1 -DHAVE_INTTYPES_H=1 -DHAVE_STDINT_H=1 -DHAVE_UNISTD_H=1 -DHAVE_FCNTL_H=1 -DHAVE_ARPA_INET_H=1 -DHAVE_SYS_TIME_H=1 -DHAVE_SYS_SOCKET_H=1 -DHAVE_STRERROR=1 -DHAVE_SOCKET=1 -DHAVE_MEMSET=1 -DHAVE_GETTIMEOFDAY=1 -DHAVE_STDLIB_H=1 -DHAVE_MALLOC=1 -DHAVE_LIBSCTP) - # we need -rdynamic to incorporate all symbols in shared objects, see man page set(commonOpts "-pipe -fPIC -Wall -fno-strict-aliasing -rdynamic") # GNU C/C++ Compiler might throw many warnings without packed-bitfield-compat, see man page From d5bf702f10fa47ebb3c6f9892cb7b750f9b2885f Mon Sep 17 00:00:00 2001 From: Robert Schmidt Date: Fri, 25 Jul 2025 14:41:03 +0200 Subject: [PATCH 05/97] Remove CMakeLists.txt author information Author information can be inferred from git blame. --- CMakeLists.txt | 2 -- 1 file changed, 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index e996e633b4..1bbed7918b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -19,8 +19,6 @@ # * contact@openairinterface.org # */ -# Author: laurent THOMAS, Lionel GAUTHIER - cmake_minimum_required (VERSION 3.16) project (OpenAirInterface LANGUAGES C CXX) set(CMAKE_CXX_STANDARD 17) From acb6f05a6d679245279d85604008a90e6c7b2ab7 Mon Sep 17 00:00:00 2001 From: Robert Schmidt Date: Mon, 15 Sep 2025 09:17:19 +0200 Subject: [PATCH 06/97] Remove include_directories() for non-existing directories --- CMakeLists.txt | 6 ------ 1 file changed, 6 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 1bbed7918b..5f7c520a0c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -544,7 +544,6 @@ include_directories(${CMAKE_CURRENT_BINARY_DIR}) include_directories("${OPENAIR_DIR}/executables") include_directories("${OPENAIR2_DIR}/COMMON") include_directories("${OPENAIR2_DIR}/UTIL") -include_directories("${OPENAIR2_DIR}/UTIL/LOG") include_directories("${OPENAIR3_DIR}/COMMON") include_directories("${OPENAIR3_DIR}/UTILS") include_directories("${NFAPI_DIR}/nfapi/public_inc") @@ -585,12 +584,7 @@ include_directories("${OPENAIR3_DIR}/ocp-gtpu") include_directories("${OPENAIR3_DIR}/M3AP") include_directories("${OPENAIR3_DIR}/MME_APP") include_directories("${OPENAIR_DIR}/radio/COMMON") -include_directories("${OPENAIR2_DIR}/UTIL/OSA") -include_directories("${OPENAIR2_DIR}/UTIL/MEM") -include_directories("${OPENAIR2_DIR}/UTIL/LISTS") -include_directories("${OPENAIR2_DIR}/UTIL/FIFO") include_directories("${OPENAIR2_DIR}/UTIL/MATH") -include_directories("${OPENAIR2_DIR}/UTIL/TIMER") include_directories("${OPENAIR2_DIR}/UTIL/OTG") include_directories("${OPENAIR2_DIR}/UTIL/OPT") include_directories("${OPENAIR_DIR}") From ada7eb8b2a6e1c6d02833a51bb4dbaf41444d587 Mon Sep 17 00:00:00 2001 From: Robert Schmidt Date: Thu, 19 Dec 2024 21:38:22 +0100 Subject: [PATCH 07/97] Remove unused/non-compiling test stub --- CMakeLists.txt | 4 ---- openair2/NR_PHY_INTERFACE/nfapi_5g_test.c | 21 --------------------- 2 files changed, 25 deletions(-) delete mode 100644 openair2/NR_PHY_INTERFACE/nfapi_5g_test.c diff --git a/CMakeLists.txt b/CMakeLists.txt index 5f7c520a0c..d993a8d09a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1715,10 +1715,6 @@ add_library(minimal_lib ) target_link_libraries(minimal_lib PUBLIC pthread dl ${T_LIB} LOG) -add_executable(nfapi_test - ${OPENAIR_DIR}/openair2/NR_PHY_INTERFACE/nfapi_5g_test.c - ) - add_executable(measurement_display ${OPENAIR_DIR}/common/utils/threadPool/measurement_display.c) target_link_libraries (measurement_display minimal_lib) diff --git a/openair2/NR_PHY_INTERFACE/nfapi_5g_test.c b/openair2/NR_PHY_INTERFACE/nfapi_5g_test.c deleted file mode 100644 index 5d2cf18974..0000000000 --- a/openair2/NR_PHY_INTERFACE/nfapi_5g_test.c +++ /dev/null @@ -1,21 +0,0 @@ - - -//openair2/NR_PHY_INTERFACE/nfapi_5g_test.c -#include "nfapi_nr_interface_scf.h" - -#include // for printf - -int main ( int argc, char** argv) -{ - nfapi_nr_cell_param_t nfapi_nr_cell_param_test; - nfapi_nr_param_tlv_t tlvs_to_report_list; - - nfapi_nr_cell_param_test.config_tlvs_to_report_list = &tlvs_to_report_list; - nfapi_nr_cell_param_test.phy_state = 0; - nfapi_nr_cell_param_test.release_capability = 0; - nfapi_nr_cell_param_test.skip_blank_dl_config = 0; - nfapi_nr_cell_param_test.skip_blank_ul_config = 0; - - printf(" test nfapi \n"); - return 0; -} From b2fce659e8d6301d1b64e787a83cf1a02f03d464 Mon Sep 17 00:00:00 2001 From: Robert Schmidt Date: Thu, 19 Dec 2024 22:00:03 +0100 Subject: [PATCH 08/97] Fix warning for wrong printf format specifier MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit common/utils/threadPool/measurement_display.c:55:14 warning: format ‘%lu’ expects argument of type ‘long unsigned int’, but argument 3 has type ‘long long unsigned int’ [-Wformat=] --- common/utils/threadPool/measurement_display.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/utils/threadPool/measurement_display.c b/common/utils/threadPool/measurement_display.c index cf2febaf33..8e44beb949 100644 --- a/common/utils/threadPool/measurement_display.c +++ b/common/utils/threadPool/measurement_display.c @@ -52,7 +52,7 @@ int main(int argc, char *argv[]) { while ( 1 ) { if ( read(fd,&doneRequest, sizeof(doneRequest)) == sizeof(doneRequest)) { - printf("%lu" SEP "%lu" SEP "%lu" SEP "%lu" "\n", + printf("%lu" SEP "%llu" SEP "%llu" SEP "%llu" "\n", doneRequest.key, (doneRequest.startProcessingTime-doneRequest.creationTime)/cpuCyclesMicroSec, (doneRequest.endProcessingTime-doneRequest.startProcessingTime)/cpuCyclesMicroSec, From 252b19cddbbbb641bd08700ef295e793f4e00786 Mon Sep 17 00:00:00 2001 From: Robert Schmidt Date: Thu, 19 Dec 2024 22:42:38 +0100 Subject: [PATCH 09/97] Make rftest compile uniqCfg is in minimal_lib, so not necessary. Remove non-existant struct members. --- openair1/PHY/TOOLS/calibration_test.c | 5 ----- 1 file changed, 5 deletions(-) diff --git a/openair1/PHY/TOOLS/calibration_test.c b/openair1/PHY/TOOLS/calibration_test.c index f69e468e7b..1d8e64ee90 100644 --- a/openair1/PHY/TOOLS/calibration_test.c +++ b/openair1/PHY/TOOLS/calibration_test.c @@ -28,7 +28,6 @@ char *uecap_file; int read_recplayconfig(recplay_conf_t **recplay_conf, recplay_state_t **recplay_state) {return 0;} void nfapi_setmode(nfapi_mode_t nfapi_mode) {} void set_taus_seed(unsigned int seed_init){}; -configmodule_interface_t *uniqCfg = NULL; int main(int argc, char **argv) { ///static configuration for NR at the moment if ((uniqCfg = load_configmodule(argc, argv, CONFIG_ENABLECMDLINEONLY)) == NULL) { @@ -60,8 +59,6 @@ int main(int argc, char **argv) { openair0_config_t openair0_cfg= { //! Module ID for this configuration .Mod_id=0, - //! device log level - .log_level=0, //! duplexing mode .duplex_mode=0, //! number of downlink resource blocks @@ -130,8 +127,6 @@ int main(int argc, char **argv) { .recplay_conf=NULL, //! number of samples per tti .samples_per_tti=0, - //! check for threequarter sampling rate - .threequarter_fs=0, }; //----------------------- openair0_device rfdevice= { From 912463f7528e27d0659c4c5b66052972a123f92d Mon Sep 17 00:00:00 2001 From: Laurent THOMAS Date: Thu, 25 Sep 2025 09:29:44 +0200 Subject: [PATCH 10/97] modulation LUTs size was wrong, and remove inconsistent test of SSE2 enable --- openair1/PHY/MODULATION/nr_modulation.c | 33 +++++++---------------- openair1/PHY/NR_REFSIG/nr_gen_mod_table.c | 10 +++---- openair1/PHY/NR_REFSIG/nr_mod_table.h | 9 +++---- 3 files changed, 15 insertions(+), 37 deletions(-) diff --git a/openair1/PHY/MODULATION/nr_modulation.c b/openair1/PHY/MODULATION/nr_modulation.c index 40de9351d6..84f7e356e7 100644 --- a/openair1/PHY/MODULATION/nr_modulation.c +++ b/openair1/PHY/MODULATION/nr_modulation.c @@ -119,49 +119,33 @@ const char nr_W_4l_4p[5][4][4] = { void nr_modulation(const uint32_t *in, uint32_t length, uint16_t mod_order, int16_t *out) { - uint16_t mask = ((1 << mod_order) - 1); - int32_t *nr_mod_table32; + const uint16_t mask = ((1 << mod_order) - 1); int32_t *out32 = (int32_t *)out; const uint8_t *in_bytes = (const uint8_t *)in; const uint64_t *in64 = (const uint64_t *)in; int64_t *out64 = (int64_t *)out; uint32_t i = 0; -#if defined(__SSE2__) - simde__m128i *nr_mod_table128; - simde__m128i *out128; -#endif - LOG_D(PHY, "nr_modulation: length %d, mod_order %d\n", length, mod_order); switch (mod_order) { -#if defined(__SSE2__) - case 2: - nr_mod_table128 = (simde__m128i *)nr_qpsk_byte_mod_table; - out128 = (simde__m128i *)out; + case 2: { + simde__m128i *nr_mod_table128 = (simde__m128i *)nr_qpsk_byte_mod_table; + simde__m128i *out128 = (simde__m128i *)out; for (i = 0; i < length / 8; i++) out128[i] = nr_mod_table128[in_bytes[i]]; // the bits that are left out i = i * 8 / 2; - nr_mod_table32 = (int32_t *)nr_qpsk_mod_table; + int32_t *nr_mod_table32 = (int32_t *)nr_qpsk_mod_table; while (i < length / 2) { const int idx = ((in_bytes[(i * 2) / 8] >> ((i * 2) & 0x7)) & mask); out32[i] = nr_mod_table32[idx]; i++; } + } return; -#else - case 2: - nr_mod_table32 = (int32_t *)nr_qpsk_mod_table; - for (i = 0; i < length / mod_order; i++) { - const int idx = ((in[i * 2 / 32] >> ((i * 2) & 0x1f)) & mask); - out32[i] = nr_mod_table32[idx]; - } - return; -#endif case 4: - out64 = (int64_t *)out; for (i = 0; i < length / 8; i++) out64[i] = nr_16qam_byte_mod_table[in_bytes[i]]; // the bits that are left out @@ -234,10 +218,11 @@ void nr_modulation(const uint32_t *in, uint32_t length, uint16_t mod_order, int1 } return; - case 8: - nr_mod_table32 = (int32_t *)nr_256qam_mod_table; + case 8: { + int32_t *nr_mod_table32 = (int32_t *)nr_256qam_mod_table; for (i = 0; i < length / 8; i++) out32[i] = nr_mod_table32[in_bytes[i]]; + } return; default: diff --git a/openair1/PHY/NR_REFSIG/nr_gen_mod_table.c b/openair1/PHY/NR_REFSIG/nr_gen_mod_table.c index 76a89f670b..732d531db4 100644 --- a/openair1/PHY/NR_REFSIG/nr_gen_mod_table.c +++ b/openair1/PHY/NR_REFSIG/nr_gen_mod_table.c @@ -22,15 +22,11 @@ #include "nr_refsig.h" #include "nr_mod_table.h" c16_t nr_qpsk_mod_table[4]; - +simde__m128i nr_qpsk_byte_mod_table[256]; int32_t nr_16qam_mod_table[16]; -simde__m128i nr_qpsk_byte_mod_table[2048]; - -int64_t nr_16qam_byte_mod_table[1024]; - +int64_t nr_16qam_byte_mod_table[256]; int64_t nr_64qam_mod_table[4096]; - -int32_t nr_256qam_mod_table[512]; +int32_t nr_256qam_mod_table[256]; void nr_generate_modulation_table() { float sqrt2 = 0.70711; diff --git a/openair1/PHY/NR_REFSIG/nr_mod_table.h b/openair1/PHY/NR_REFSIG/nr_mod_table.h index 517d86b762..81969d9a40 100644 --- a/openair1/PHY/NR_REFSIG/nr_mod_table.h +++ b/openair1/PHY/NR_REFSIG/nr_mod_table.h @@ -29,15 +29,12 @@ #define NR_MOD_TABLE_QAM256_OFFSET 87 extern c16_t nr_qpsk_mod_table[4]; +extern simde__m128i nr_qpsk_byte_mod_table[256]; extern int32_t nr_16qam_mod_table[16]; -#if defined(__SSE2__) -extern simde__m128i nr_qpsk_byte_mod_table[2048]; -#endif - -extern int64_t nr_16qam_byte_mod_table[1024]; +extern int64_t nr_16qam_byte_mod_table[256]; extern int64_t nr_64qam_mod_table[4096]; -extern int32_t nr_256qam_mod_table[512]; +extern int32_t nr_256qam_mod_table[256]; #endif From a7257eaff1341611356a96c7e51a8395b3463f2e Mon Sep 17 00:00:00 2001 From: Guido Casati Date: Thu, 18 Sep 2025 22:12:37 +0200 Subject: [PATCH 11/97] E1 lib: fix inconsistent equality checking patterns in E1AP bearer context management Replace result &= pattern with consistent early return pattern: - Fix mixing of logical (&&) and binary (&) operators - Use consistent 'if (!eq_()) return false;' pattern throughout - Fix bug in eq_bearer_context_mod_response where drbModA was compared with itself - Remove unused 'bool result = true;' variables - Always return true at the end of equality functions This makes the code more consistent with the rest of the codebase and provides better error reporting through the existing _E1_EQ_CHECK macros. --- .../E1AP/lib/e1ap_bearer_context_management.c | 52 +++++++++++-------- 1 file changed, 31 insertions(+), 21 deletions(-) diff --git a/openair2/E1AP/lib/e1ap_bearer_context_management.c b/openair2/E1AP/lib/e1ap_bearer_context_management.c index 0f2e5972e2..fdccd94925 100644 --- a/openair2/E1AP/lib/e1ap_bearer_context_management.c +++ b/openair2/E1AP/lib/e1ap_bearer_context_management.c @@ -398,7 +398,6 @@ static bool eq_pdcp_info(const e1_pdcp_status_info_t *a, const e1_pdcp_status_in */ static bool eq_drb_to_setup(const DRB_nGRAN_to_setup_t *a, const DRB_nGRAN_to_setup_t *b) { - bool result = true; _E1_EQ_CHECK_LONG(a->id, b->id); _E1_EQ_CHECK_INT(a->numCellGroups, b->numCellGroups); for (int i = 0; i < a->numCellGroups; i++) { @@ -406,11 +405,14 @@ static bool eq_drb_to_setup(const DRB_nGRAN_to_setup_t *a, const DRB_nGRAN_to_se } _E1_EQ_CHECK_INT(a->numQosFlow2Setup, b->numQosFlow2Setup); for (int i = 0; i < a->numQosFlow2Setup; i++) { - result &= eq_qos_flow(&a->qosFlows[i], &b->qosFlows[i]); + if (!eq_qos_flow(&a->qosFlows[i], &b->qosFlows[i])) + return false; } - result &= eq_pdcp_config(&a->pdcp_config, &b->pdcp_config); - result &= eq_sdap_config(&a->sdap_config, &b->sdap_config); - return result; + if (!eq_pdcp_config(&a->pdcp_config, &b->pdcp_config)) + return false; + if (!eq_sdap_config(&a->sdap_config, &b->sdap_config)) + return false; + return true; } static void free_drb_to_setup_item(const DRB_nGRAN_to_setup_t *msg) @@ -1939,25 +1941,32 @@ static bool eq_security_ind(security_indication_t *a, security_indication_t *b) */ static bool eq_drb_to_mod(const DRB_nGRAN_to_mod_t *a, const DRB_nGRAN_to_mod_t *b) { - bool result = true; _E1_EQ_CHECK_LONG(a->id, b->id); _E1_EQ_CHECK_INT(a->numQosFlow2Setup, b->numQosFlow2Setup); for (int i = 0; i < a->numQosFlow2Setup; i++) { - result &= eq_qos_flow(&a->qosFlows[i], &b->qosFlows[i]); + if (!eq_qos_flow(&a->qosFlows[i], &b->qosFlows[i])) + return false; } _E1_EQ_CHECK_INT(a->numDlUpParam, b->numDlUpParam); for (int i = 0; i < a->numDlUpParam; i++) { _E1_EQ_CHECK_INT(a->DlUpParamList[i].cell_group_id, b->DlUpParamList[i].cell_group_id); - result &= eq_up_tl_info(&a->DlUpParamList[i].tl_info, &b->DlUpParamList[i].tl_info); + if (!eq_up_tl_info(&a->DlUpParamList[i].tl_info, &b->DlUpParamList[i].tl_info)) + return false; + } + if (a->pdcp_config && b->pdcp_config) { + if (!eq_pdcp_config(a->pdcp_config, b->pdcp_config)) + return false; + } + if (a->sdap_config && b->sdap_config) { + if (!eq_sdap_config(a->sdap_config, b->sdap_config)) + return false; } - if (a->pdcp_config && b->pdcp_config) - result &= eq_pdcp_config(a->pdcp_config, b->pdcp_config); - if (a->sdap_config && b->sdap_config) - result &= eq_sdap_config(a->sdap_config, b->sdap_config); _E1_EQ_CHECK_OPTIONAL_PTR(a, b, pdcp_status); - if (a->pdcp_status && b->pdcp_status) - result &= eq_pdcp_info(a->pdcp_status, b->pdcp_status); - return result; + if (a->pdcp_status && b->pdcp_status) { + if (!eq_pdcp_info(a->pdcp_status, b->pdcp_status)) + return false; + } + return true; } /** @brief Equality check for PDU session item to modify */ @@ -2423,8 +2432,6 @@ bool eq_bearer_context_mod_response(const e1ap_bearer_modif_resp_t *a, const e1a { if (!a || !b) return false; // Null-check both inputs - bool result = true; - // Basic members _E1_EQ_CHECK_INT(a->gNB_cu_cp_ue_id, b->gNB_cu_cp_ue_id); _E1_EQ_CHECK_INT(a->gNB_cu_up_ue_id, b->gNB_cu_up_ue_id); @@ -2440,8 +2447,10 @@ bool eq_bearer_context_mod_response(const e1ap_bearer_modif_resp_t *a, const e1a _E1_EQ_CHECK_OPTIONAL_IE(pduA, pduB, integrityProtectionIndication, _E1_EQ_CHECK_INT); _E1_EQ_CHECK_OPTIONAL_PTR(pduA, pduB, ng_DL_UP_TL_info); - if (pduA->ng_DL_UP_TL_info && pduB->ng_DL_UP_TL_info) - result &= eq_up_tl_info(pduA->ng_DL_UP_TL_info, pduB->ng_DL_UP_TL_info); + if (pduA->ng_DL_UP_TL_info && pduB->ng_DL_UP_TL_info) { + if (!eq_up_tl_info(pduA->ng_DL_UP_TL_info, pduB->ng_DL_UP_TL_info)) + return false; + } // DRB Modified List _E1_EQ_CHECK_INT(pduA->numDRBModified, pduB->numDRBModified); @@ -2455,7 +2464,8 @@ bool eq_bearer_context_mod_response(const e1ap_bearer_modif_resp_t *a, const e1a } _E1_EQ_CHECK_OPTIONAL_PTR(drbModA, drbModB, pdcp_status); if (drbModA->pdcp_status && drbModB->pdcp_status) { - result &= eq_pdcp_info(drbModA->pdcp_status, drbModA->pdcp_status); + if (!eq_pdcp_info(drbModA->pdcp_status, drbModB->pdcp_status)) + return false; } } @@ -2490,7 +2500,7 @@ bool eq_bearer_context_mod_response(const e1ap_bearer_modif_resp_t *a, const e1a } - return result; + return true; } /** From 8dcd253e60171bb0de622a49b55a5eb0735eb4e6 Mon Sep 17 00:00:00 2001 From: Guido Casati Date: Thu, 18 Sep 2025 22:15:30 +0200 Subject: [PATCH 12/97] E1 lib: Adopt _E1_EQ_CHECK_OPTIONAL_PTR macro in E1AP bearer context management - Add _E1_EQ_CHECK_OPTIONAL_PTR checks for pdcp_config, sdap_config, UP_TL_information, and securityIndication - Fix bug in eq_pdu_session_to_mod_item where a->UP_TL_information was compared with itself instead of b->UP_TL_information - Fix eq_security_ind call to properly handle return value with early return pattern - Improve consistency with existing macro usage patterns in the codebase - Provide better error reporting for optional pointer field mismatches --- openair2/E1AP/lib/e1ap_bearer_context_management.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/openair2/E1AP/lib/e1ap_bearer_context_management.c b/openair2/E1AP/lib/e1ap_bearer_context_management.c index fdccd94925..109b0008d3 100644 --- a/openair2/E1AP/lib/e1ap_bearer_context_management.c +++ b/openair2/E1AP/lib/e1ap_bearer_context_management.c @@ -1953,10 +1953,12 @@ static bool eq_drb_to_mod(const DRB_nGRAN_to_mod_t *a, const DRB_nGRAN_to_mod_t if (!eq_up_tl_info(&a->DlUpParamList[i].tl_info, &b->DlUpParamList[i].tl_info)) return false; } + _E1_EQ_CHECK_OPTIONAL_PTR(a, b, pdcp_config); if (a->pdcp_config && b->pdcp_config) { if (!eq_pdcp_config(a->pdcp_config, b->pdcp_config)) return false; } + _E1_EQ_CHECK_OPTIONAL_PTR(a, b, sdap_config); if (a->sdap_config && b->sdap_config) { if (!eq_sdap_config(a->sdap_config, b->sdap_config)) return false; @@ -1973,7 +1975,8 @@ static bool eq_drb_to_mod(const DRB_nGRAN_to_mod_t *a, const DRB_nGRAN_to_mod_t static bool eq_pdu_session_to_mod_item(const pdu_session_to_mod_t *a, const pdu_session_to_mod_t *b) { _E1_EQ_CHECK_LONG(a->sessionId, b->sessionId); - if (a->UP_TL_information && a->UP_TL_information) { + _E1_EQ_CHECK_OPTIONAL_PTR(a, b, UP_TL_information); + if (a->UP_TL_information && b->UP_TL_information) { if (!eq_up_tl_info(a->UP_TL_information, b->UP_TL_information)) return false; } @@ -1987,8 +1990,10 @@ static bool eq_pdu_session_to_mod_item(const pdu_session_to_mod_t *a, const pdu_ if (!eq_drb_to_mod(&a->DRBnGRanModList[i], &b->DRBnGRanModList[i])) return false; } + _E1_EQ_CHECK_OPTIONAL_PTR(a, b, securityIndication); if (a->securityIndication && b->securityIndication) { - eq_security_ind(a->securityIndication, b->securityIndication); + if (!eq_security_ind(a->securityIndication, b->securityIndication)) + return false; } return true; } From 50760c04a705862246fe4e19cd7bae02ea8467df Mon Sep 17 00:00:00 2001 From: Guido Casati Date: Thu, 18 Sep 2025 21:43:30 +0200 Subject: [PATCH 13/97] Fix log messages in N2 handover: print both PCI and Cell ID when available - Fixed incorrect log messages that said 'PCI=%lu' when printing nr_cell_id - Now prints both NR Cell ID and PCI when DU is found - Provides better debugging information for handover requests --- openair2/RRC/NR/rrc_gNB_NGAP.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/openair2/RRC/NR/rrc_gNB_NGAP.c b/openair2/RRC/NR/rrc_gNB_NGAP.c index 34666e3538..75e6742422 100644 --- a/openair2/RRC/NR/rrc_gNB_NGAP.c +++ b/openair2/RRC/NR/rrc_gNB_NGAP.c @@ -1129,11 +1129,10 @@ void rrc_gNB_send_NGAP_HANDOVER_FAILURE(gNB_RRC_INST *rrc, ngap_handover_failure /** @brief Process NG Handover Request message (8.4.2.2 3GPP TS 38.413) */ int rrc_gNB_process_Handover_Request(gNB_RRC_INST *rrc, instance_t instance, ngap_handover_request_t *msg) { - LOG_I(NR_RRC, "Received Handover Request (on PCI=%lu) \n", msg->nr_cell_id); struct nr_rrc_du_container_t *du = get_du_by_cell_id(rrc, msg->nr_cell_id); if (du == NULL) { /* Cell Not Found! Return HO Request Failure*/ - LOG_E(RRC, "Failed to process Handover Request: no DU found with PCI=%lu \n", msg->nr_cell_id); + LOG_E(RRC, "Failed to process Handover Request: no DU found with NR Cell ID=%lu \n", msg->nr_cell_id); ngap_handover_failure_t fail = { .amf_ue_ngap_id = msg->amf_ue_ngap_id, .cause.type = NGAP_CAUSE_RADIO_NETWORK, @@ -1142,6 +1141,8 @@ int rrc_gNB_process_Handover_Request(gNB_RRC_INST *rrc, instance_t instance, nga rrc_gNB_send_NGAP_HANDOVER_FAILURE(rrc, &fail); return -1; } + uint16_t pci = du->setup_req->cell[0].info.nr_pci; + LOG_I(NR_RRC, "Received Handover Request (on NR Cell ID=%lu, PCI=%u) \n", msg->nr_cell_id, pci); // Create UE context sctp_assoc_t curr_assoc_id = du->assoc_id; From e535fbe6a715808fe1fee7f691ec77984467923c Mon Sep 17 00:00:00 2001 From: Guido Casati Date: Tue, 23 Sep 2025 10:55:45 +0200 Subject: [PATCH 14/97] Replace impossible ID mismatch checks with DevAssert in NGAP functions - Replace error handling for impossible ID mismatches with DevAssert statements - If ngap_get_ue_context() succeeds, returned context must have matching NGAP gNB UE IDs by definition - This catches potential bugs in the lookup function immediately - Affects: handover_required, handover_notify, handover_cancel, ul_ran_status_transfer These checks were logically impossible since we looked up the context using the same IDs we were then checking for mismatch. --- openair3/NGAP/ngap_gNB.c | 23 ++++++----------------- 1 file changed, 6 insertions(+), 17 deletions(-) diff --git a/openair3/NGAP/ngap_gNB.c b/openair3/NGAP/ngap_gNB.c index 8c9ac6a973..46bcf93cad 100644 --- a/openair3/NGAP/ngap_gNB.c +++ b/openair3/NGAP/ngap_gNB.c @@ -293,12 +293,7 @@ int ngap_handover_required(instance_t instance, ngap_handover_required_t *msg) return -1; } - if ((ue_context_p->gNB_ue_ngap_id != msg->gNB_ue_ngap_id)) { - NGAP_ERROR("ue_context_p->gNB_ue_ngap_id %d does not match msg->gNB_ue_ngap_id %d\n", - ue_context_p->gNB_ue_ngap_id, - msg->gNB_ue_ngap_id); - return -1; - } + DevAssert(ue_context_p->gNB_ue_ngap_id == msg->gNB_ue_ngap_id); NGAP_NGAP_PDU_t *pdu = encode_ng_handover_required(msg); if (!pdu) { @@ -434,11 +429,7 @@ static int ngap_gNB_handover_notify(instance_t instance, const ngap_handover_not NGAP_ERROR("Failed to encode Handover Notify: no ue context associated with gNB_ue_ngap_id=%d\n", msg->gNB_ue_ngap_id); return -1; } - - if (ue_context_p->gNB_ue_ngap_id != msg->gNB_ue_ngap_id) { - NGAP_ERROR("Failed to encode Handover Notify: unknown gNB_ue_ngap_id=%d\n", msg->gNB_ue_ngap_id); - return -1; - } + DevAssert(ue_context_p->gNB_ue_ngap_id == msg->gNB_ue_ngap_id); if (ue_context_p->amf_ue_ngap_id != msg->amf_ue_ngap_id) { NGAP_ERROR("Failed to encode Handover Notify: unknown amf_ue_ngap_id=%ld\n", msg->amf_ue_ngap_id); @@ -479,11 +470,7 @@ static int ngap_gNB_handover_cancel(instance_t instance, const ngap_handover_can NGAP_ERROR("Failed to encode Handover Cancel: no UE context for gNB_ue_ngap_id=%d\n", msg->gNB_ue_ngap_id); return -1; } - - if (ue_context_p->gNB_ue_ngap_id != msg->gNB_ue_ngap_id) { - NGAP_ERROR("Failed to encode Handover Cancel: mismatched gNB_ue_ngap_id=%d\n", msg->gNB_ue_ngap_id); - return -1; - } + DevAssert(ue_context_p->gNB_ue_ngap_id == msg->gNB_ue_ngap_id); if (ue_context_p->amf_ue_ngap_id != msg->amf_ue_ngap_id) { NGAP_ERROR("Failed to encode Handover Cancel: mismatched amf_ue_ngap_id=%ld\n", msg->amf_ue_ngap_id); @@ -522,11 +509,13 @@ int ngap_gNB_handle_ul_ran_status_transfer(instance_t instance, const ngap_ran_s DevAssert(ngap_gNB_instance_p != NULL); ngap_gNB_ue_context_t *ue_context_p = ngap_get_ue_context(msg->gnb_ue_ngap_id); - if (!ue_context_p || ue_context_p->gNB_ue_ngap_id != msg->gnb_ue_ngap_id) { + if (!ue_context_p) { NGAP_ERROR("Could not find UE context for gNB_ue_ngap_id %d\n", msg->gnb_ue_ngap_id); return -1; } + DevAssert(ue_context_p->gNB_ue_ngap_id == msg->gnb_ue_ngap_id); + NGAP_NGAP_PDU_t *pdu = encode_ng_ul_ran_status_transfer(msg); if (!pdu) { NGAP_ERROR("Failed to encode UL RAN Status Transfer\n"); From 41b33e912462347768d5c4d483b4251248a5b0c7 Mon Sep 17 00:00:00 2001 From: Jaroslava Fiedlerova Date: Thu, 25 Sep 2025 15:22:10 +0200 Subject: [PATCH 15/97] CI: Update frequency in HO setup Move center frequency from 3439 MHz to 4185 MHz to avoid interference with other lab setups. --- .../gnb-du.sa.band78.51prb.usrpb210.ho-pci0.conf | 14 +++++++------- .../gnb-du.sa.band78.51prb.usrpb210.ho-pci1.conf | 14 +++++++------- ci-scripts/conf_files/neighbour-config-ho.conf | 8 ++++---- 3 files changed, 18 insertions(+), 18 deletions(-) diff --git a/ci-scripts/conf_files/gnb-du.sa.band78.51prb.usrpb210.ho-pci0.conf b/ci-scripts/conf_files/gnb-du.sa.band78.51prb.usrpb210.ho-pci0.conf index 216e2c3bd2..eb52e68566 100644 --- a/ci-scripts/conf_files/gnb-du.sa.band78.51prb.usrpb210.ho-pci0.conf +++ b/ci-scripts/conf_files/gnb-du.sa.band78.51prb.usrpb210.ho-pci0.conf @@ -26,11 +26,11 @@ gNBs = # downlinkConfigCommon #frequencyInfoDL - # 4.01 GHz - absoluteFrequencySSB = 629376; - dl_frequencyBand = 78; - # 4 GHz - dl_absoluteFrequencyPointA = 628720; + # 4186.56 MHz + absoluteFrequencySSB = 679104; + dl_frequencyBand = 77; + # 4175.82 MHz + dl_absoluteFrequencyPointA = 678388; #scs-SpecificCarrierList dl_offstToCarrier = 0; # subcarrierSpacing @@ -50,7 +50,7 @@ gNBs = #uplinkConfigCommon #frequencyInfoUL - ul_frequencyBand = 78; + ul_frequencyBand = 77; #scs-SpecificCarrierList ul_offstToCarrier = 0; # subcarrierSpacing @@ -188,7 +188,7 @@ RUs = ( nb_rx = 1 att_tx = 0; att_rx = 0; - bands = [78]; + bands = [77]; max_pdschReferenceSignalPower = -27; max_rxgain = 114; eNB_instances = [0]; diff --git a/ci-scripts/conf_files/gnb-du.sa.band78.51prb.usrpb210.ho-pci1.conf b/ci-scripts/conf_files/gnb-du.sa.band78.51prb.usrpb210.ho-pci1.conf index ff154f733f..3ba7e17b46 100644 --- a/ci-scripts/conf_files/gnb-du.sa.band78.51prb.usrpb210.ho-pci1.conf +++ b/ci-scripts/conf_files/gnb-du.sa.band78.51prb.usrpb210.ho-pci1.conf @@ -26,11 +26,11 @@ gNBs = # downlinkConfigCommon #frequencyInfoDL - # 4.01 GHz - absoluteFrequencySSB = 629376; - dl_frequencyBand = 78; - # 4 GHz - dl_absoluteFrequencyPointA = 628720; + # 4186.56 MHz + absoluteFrequencySSB = 679104; + dl_frequencyBand = 77; + # 4175.82 MHz + dl_absoluteFrequencyPointA = 678388; #scs-SpecificCarrierList dl_offstToCarrier = 0; # subcarrierSpacing @@ -50,7 +50,7 @@ gNBs = #uplinkConfigCommon #frequencyInfoUL - ul_frequencyBand = 78; + ul_frequencyBand = 77; #scs-SpecificCarrierList ul_offstToCarrier = 0; # subcarrierSpacing @@ -188,7 +188,7 @@ RUs = ( nb_rx = 1 att_tx = 0; att_rx = 0; - bands = [78]; + bands = [77]; max_pdschReferenceSignalPower = -27; max_rxgain = 114; eNB_instances = [0]; diff --git a/ci-scripts/conf_files/neighbour-config-ho.conf b/ci-scripts/conf_files/neighbour-config-ho.conf index f9d1e38ee0..2490ce1883 100644 --- a/ci-scripts/conf_files/neighbour-config-ho.conf +++ b/ci-scripts/conf_files/neighbour-config-ho.conf @@ -6,9 +6,9 @@ neighbour_list = ( gNB_ID = 0xe01; nr_cellid = 11111111; physical_cellId = 1; - absoluteFrequencySSB = 629376; + absoluteFrequencySSB = 679104; subcarrierSpacing = 1; #30 KHz - band = 78; + band = 77; plmn = { mcc = 001; mnc = 03; mnc_length = 2}; tracking_area_code = 1; } @@ -21,9 +21,9 @@ neighbour_list = ( gNB_ID = 0xe00; nr_cellid = 12345678; physical_cellId = 0; - absoluteFrequencySSB = 629376; + absoluteFrequencySSB = 679104; subcarrierSpacing = 1; #30 KHz - band = 78; + band = 77; plmn = { mcc = 001; mnc = 03; mnc_length = 2}; tracking_area_code = 1; } From a64038658f66693808f6f3dde1e8747047118d53 Mon Sep 17 00:00:00 2001 From: alexjiao Date: Fri, 13 Jun 2025 15:56:05 +0800 Subject: [PATCH 16/97] Modify nr_uci_encoding for UCI on PUSCH 38.212 section 5.3.3.1 and 5.3.3.2 describe how to encode 1-bit and 2-bit information. This is needed when typically HARQ-ACK will be transmitted on PUSCH. 38.212 section 5.4.3 also describes how to do rate matching for channel coding of small block lengths. --- .../CODING/nrSmallBlock/encodeSmallBlock.c | 3 + openair1/PHY/NR_UE_TRANSPORT/pucch_nr.c | 285 ++++++++++++------ openair1/PHY/NR_UE_TRANSPORT/pucch_nr.h | 2 + 3 files changed, 194 insertions(+), 96 deletions(-) diff --git a/openair1/PHY/CODING/nrSmallBlock/encodeSmallBlock.c b/openair1/PHY/CODING/nrSmallBlock/encodeSmallBlock.c index 3078243c8f..a3a5863542 100644 --- a/openair1/PHY/CODING/nrSmallBlock/encodeSmallBlock.c +++ b/openair1/PHY/CODING/nrSmallBlock/encodeSmallBlock.c @@ -31,11 +31,14 @@ */ #include "PHY/CODING/nrSmallBlock/nr_small_block_defs.h" +#include "common/utils/assertions.h" //input = [0 ... 0 c_K-1 ... c_2 c_1 c_0] //output = [d_31 d_30 ... d_2 d_1 d_0] uint32_t encodeSmallBlock(int in, int len) { + AssertFatal(len >= 3, "encodeSmallBlock only supports input lengths A >= 3, got A=%d", len); + uint32_t out = 0; for (int i = 0; i < len; i++) if ((in & (1 << i)) > 0) diff --git a/openair1/PHY/NR_UE_TRANSPORT/pucch_nr.c b/openair1/PHY/NR_UE_TRANSPORT/pucch_nr.c index 611572a0a3..b4c65cf28e 100644 --- a/openair1/PHY/NR_UE_TRANSPORT/pucch_nr.c +++ b/openair1/PHY/NR_UE_TRANSPORT/pucch_nr.c @@ -520,101 +520,143 @@ static inline void nr_pucch2_3_4_scrambling(uint16_t M_bit, uint16_t rnti, uint1 printf("\t\t [nr_pucch2_3_4_scrambling] scrambling M_bit=%d bits\n", M_bit); #endif } -static void nr_uci_encoding(uint64_t payload, - uint8_t nr_bit, - int fmt, - uint8_t is_pi_over_2_bpsk_enabled, - uint8_t nrofSymbols, - uint8_t nrofPRB, - uint8_t n_SF_PUCCH_s, - uint8_t intraSlotFrequencyHopping, - uint8_t add_dmrs, - uint64_t *b, - uint16_t *M_bit) { + +void nr_uci_encoding(uint64_t payload, uint8_t nr_bit, uint8_t nrofPRB, bool uci_on_pusch, uint16_t E, uint8_t Qm, uint64_t *b) +{ /* - * Implementing TS 38.212 Subclause 6.3.1.2 + * Implementing TS 38.212 Subclause 6.3.1.2 and 6.3.2 * */ // A is the payload size, to be provided in function call uint8_t A = nr_bit; // L is the CRC size - //uint8_t L; + // uint8_t L; // E is the rate matching output sequence length as given in TS 38.212 subclause 6.3.1.4.1 - uint16_t E=0,E_init; - - if (fmt == 2) E = 16*nrofSymbols*nrofPRB; - - if (fmt == 3) { - E_init = (is_pi_over_2_bpsk_enabled == 0) ? 24:12; - - if (nrofSymbols == 4) { - E = (intraSlotFrequencyHopping == 0)?(E_init*(nrofSymbols-1)*nrofPRB):((E_init*(nrofSymbols-1)*nrofPRB)); + // int I_seg; #ifdef DEBUG_NR_PUCCH_TX - printf("format 3 nrofSymbols =4 and E_init=%d,E=%d\n",E_init,E); + printf("\t\t [nr_uci_encoding] start function with encoding A=%d bits into M_bit=%d (where nrofPRB=%d)\n", A, E, nrofPRB); #endif - } - if (nrofSymbols > 4) { - E = E_init*(nrofSymbols-2)*nrofPRB; -#ifdef DEBUG_NR_PUCCH_TX - printf("format 3 nrofSymbols >4 and E_init=%d,E = %d\n",E_init,E); -#endif - } + // For A=1 case (single bit UCI) + if (A == 1) { + uint8_t uci_bit_val = payload & 1; // Extract the single UCI bit + uint64_t pattern_word; + uint8_t *b_bytes = (uint8_t *)b; // Access b as bytes to set individual special values - if (nrofSymbols > 9) { - E = (add_dmrs == 0)?(E_init*(nrofSymbols-2)*nrofPRB):((E_init*(nrofSymbols-4)*nrofPRB)); -#ifdef DEBUG_NR_PUCCH_TX - printf("format 3 nrofSymbols >9 and E_init=%d,E = %d\n",E_init,E); -#endif + if (Qm == 1) { + // For BPSK, just repeat the input bit + pattern_word = uci_bit_val ? 0xFFFFFFFFFFFFFFFFULL : 0x0000000000000000ULL; + for (int i = 0; i < 8; i++) { + b[i] = pattern_word; + } + } else { + // For higher order modulation (QPSK, etc.), use placeholder values + memset(b, 0, 8 * sizeof(uint64_t)); + + // Fill the entire output with the pattern + for (int i = 0; i < E; i++) { + switch (i % Qm) { + case 0: + // First bit in each group is actual UCI bit + b_bytes[i] = uci_bit_val; + LOG_D(PHY, + "[UCI_ENCODING_A1_QAM_LOOP] i=%d, i%%Qm=%d (case 0), writing uci_bit_val %d to b_bytes[%d]\n", + i, + i % Qm, + uci_bit_val, + i); + break; + case 1: + b_bytes[i] = NR_PUSCH_y; + LOG_D(PHY, + "[UCI_ENCODING_A1_QAM_LOOP] i=%d, i%%Qm=%d (case 1), writing NR_PUSCH_y (0x%02X) to b_bytes[%d]\n", + i, + i % Qm, + NR_PUSCH_y, + i); + break; + default: + b_bytes[i] = NR_PUSCH_x; + break; + } + } } } + // For A=2 case (two bits UCI) + else if (A == 2) { + uint8_t bit0 = (payload >> 0) & 1; + uint8_t bit1 = (payload >> 1) & 1; + uint8_t c2 = bit0 ^ bit1; // Parity bit (XOR of the two bits) + uint8_t *b_bytes = (uint8_t *)b; - if (fmt == 4) { - E_init = (is_pi_over_2_bpsk_enabled == 0) ? 24:12; + if (Qm == 1) { + // For BPSK, output is [bit0, bit1, c2] + uint64_t pattern = (bit0) | (bit1 << 1) | (c2 << 2); + // Repeat this 3-bit pattern to fill the output + pattern |= pattern << 3; + pattern |= pattern << 6; + pattern |= pattern << 12; + pattern |= pattern << 24; + pattern |= pattern << 48; - if (nrofSymbols == 4) { - E = (intraSlotFrequencyHopping == 0)?(E_init*(nrofSymbols-1)/n_SF_PUCCH_s):((E_init*(nrofSymbols-1)/n_SF_PUCCH_s)); -#ifdef DEBUG_NR_PUCCH_TX - printf("format 4 nrofSymbols =4 and E_init=%d,E=%d\n",E_init,E); -#endif + for (int i = 0; i < 8; i++) { + b[i] = pattern; + } + } else { + // For higher order modulation (Qm>=2), using patterns from Table 5.3.3.2-1 + // Pattern: 3 groups of Qm bits each = 3*Qm total length + memset(b, 0, 8 * sizeof(uint64_t)); + + for (int i = 0; i < E; i++) { + int pos_in_pattern = i % (3 * Qm); + int group = pos_in_pattern / Qm; + int pos_in_group = pos_in_pattern % Qm; + uint8_t val_to_write; + + if (pos_in_group == 0) { + if (group == 0) + val_to_write = bit0; + else if (group == 1) + val_to_write = c2; + else + val_to_write = bit1; + } else if (pos_in_group == 1) { + if (group == 0) + val_to_write = bit1; + else if (group == 1) + val_to_write = bit0; + else + val_to_write = c2; + } else { + // Positions 2 and beyond are x placeholders + val_to_write = NR_PUSCH_x; + } + + b_bytes[i] = val_to_write; + } } - - if (nrofSymbols > 4) { - E = E_init*(nrofSymbols-2)/n_SF_PUCCH_s; -#ifdef DEBUG_NR_PUCCH_TX - printf("format 4 nrofSymbols >4 and E_init=%d,E = %d\n",E_init,E); -#endif - } - - if (nrofSymbols > 9) { - E = (add_dmrs == 0)?(E_init*(nrofSymbols-2)/n_SF_PUCCH_s):((E_init*(nrofSymbols-4)/n_SF_PUCCH_s)); -#ifdef DEBUG_NR_PUCCH_TX - printf("format 4 nrofSymbols >9 and E_init=%d,E = %d\n",E_init,E); -#endif - } - } - - *M_bit = E; - //int I_seg; -#ifdef DEBUG_NR_PUCCH_TX - printf("\t\t [nr_uci_encoding] start function with fmt=%d, encoding A=%d bits into M_bit=%d (where nrofSymbols=%d,nrofPRB=%d)\n",fmt,A,*M_bit,nrofSymbols,nrofPRB); -#endif - - if (A<=11) { + } else if (A <= 11) { // procedure in subclause 6.3.1.2.2 (UCI encoded by channel coding of small block lengths -> subclause 6.3.1.3.2) // CRC bits are not attached, and coding small block lengths (subclause 5.3.3) uint64_t b0 = encodeSmallBlock(payload, A); - // repetition for rate-matching up to 16 PRB - b[0] = b0 | (b0<<32); - b[1] = b[0]; - b[2] = b[0]; - b[3] = b[0]; - b[4] = b[0]; - b[5] = b[0]; - b[6] = b[0]; - b[7] = b[0]; - AssertFatal(nrofPRB<=16,"Number of PRB >16\n"); - } else if (A>=12) { + if (uci_on_pusch) { + b[0] = b0; + for (int i = 1; i < 8; i++) { + b[i] = 0; + } + } else { + // repetition for rate-matching up to 16 PRB + b[0] = b0 | (b0<<32); + b[1] = b[0]; + b[2] = b[0]; + b[3] = b[0]; + b[4] = b[0]; + b[5] = b[0]; + b[6] = b[0]; + b[7] = b[0]; + AssertFatal(nrofPRB<=16,"Number of PRB >16\n"); + } + } else if (A >= 12) { // Encoder reversal payload = reverse_bits(payload, A); @@ -623,7 +665,52 @@ static void nr_uci_encoding(uint64_t payload, A, nrofPRB); } - + + if (uci_on_pusch) { + // Rate matching for HARQ ACK following 38.212 section 5.4.3 + uint64_t output[8] = {0}; // Assuming max 512 bits (8 words of 64 bits) + uint16_t N; + if (nr_bit <= 2) { + // For A=1 (BPSK), N=1. For A=2 (QPSK), N=3 + N = (nr_bit == 1) ? 1 : 3; + } else if (nr_bit <= 11) { + // For 3 <= A <= 11, the small block encoder produces a 32-bit codeword + N = 32; + } else { + // For polar-coded UCI, output depends on nrofPRB + N = 16 * nrofPRB; + } + + if ((nr_bit == 1 || nr_bit == 2) && Qm > 1) { + LOG_D(PHY, + "[UCI_ENCODING_RM] Bypassing bit-wise rate matching for A=%d, Qm=%d. 'b' (length %d bytes) is assumed to be already " + "final.\n", + nr_bit, + Qm, + E); + } else { + if (N == 0) { + LOG_W(PHY, "HARQ-ACK rate matching with encoded_length=0 but E_uci_ack=%d\n", E); + return; + } + + // Rate matching with single loop for both repetition and puncturing + for (int i = 0; i < E; i++) { + int src_bit = i % N; // Modulo for cyclic repetition + int src_word = src_bit / 64; + int src_bit_pos = src_bit % 64; + int dst_word = i / 64; + int dst_bit_pos = i % 64; + + if ((b[src_word] >> src_bit_pos) & 1ULL) + output[dst_word] |= (1ULL << dst_bit_pos); + } + + for (int i = 0; i < (E + 63) / 64; i++) { + b[i] = output[i]; + } + } + } } //#if 0 void nr_generate_pucch2(const PHY_VARS_NR_UE *ue, @@ -639,13 +726,8 @@ void nr_generate_pucch2(const PHY_VARS_NR_UE *ue, // b is the block of bits transmitted on the physical channel after payload coding uint64_t b[16] = {0}; // limit to 1024-bit encoded length // M_bit is the number of bits of block b (payload after encoding) - uint16_t M_bit = 0; - nr_uci_encoding(pucch_pdu->payload, - pucch_pdu->n_bit, - 2,0, - pucch_pdu->nr_of_symbols, - pucch_pdu->prb_size, - 1,0,0,&b[0],&M_bit); + uint16_t M_bit = 16 * pucch_pdu->nr_of_symbols * pucch_pdu->prb_size; + nr_uci_encoding(pucch_pdu->payload, pucch_pdu->n_bit, pucch_pdu->prb_size, false, M_bit, 0, &b[0]); /* * Implementing TS 38.211 * Subclauses 6.3.2.5.1 Scrambling (PUCCH format 2) @@ -829,7 +911,7 @@ void nr_generate_pucch3_4(const PHY_VARS_NR_UE *ue, // b is the block of bits transmitted on the physical channel after payload coding uint64_t b[16]; // M_bit is the number of bits of block b (payload after encoding) - uint16_t M_bit; + uint16_t M_bit = 0; // parameter PUCCH-F4-preDFT-OCC-length set of {2,4} -> to use table -1 or -2 // in format 4, n_SF_PUCCH_s = {2,4}, provided by higher layer parameter PUCCH-F4-preDFT-OCC-length (in format 3 n_SF_PUCCH_s=1) uint8_t n_SF_PUCCH_s; @@ -856,17 +938,28 @@ void nr_generate_pucch3_4(const PHY_VARS_NR_UE *ue, uint16_t startingPRB = pucch_pdu->prb_start + pucch_pdu->bwp_start; uint8_t add_dmrs = pucch_pdu->add_dmrs_flag; - nr_uci_encoding(pucch_pdu->payload, - pucch_pdu->n_bit, - pucch_pdu->format_type, - is_pi_over_2_bpsk_enabled, - nrofSymbols, - nrofPRB, - n_SF_PUCCH_s, - intraSlotFrequencyHopping, - add_dmrs, - b, - &M_bit); + uint16_t E_init = (is_pi_over_2_bpsk_enabled == 0) ? 24 : 12; + + if (pucch_pdu->format_type == 3) { + if (nrofSymbols == 4) { + M_bit = (intraSlotFrequencyHopping == 0) ? (E_init * (nrofSymbols - 1) * nrofPRB) : (E_init * (nrofSymbols - 1) * nrofPRB); + } else if (nrofSymbols > 4) { + M_bit = E_init * (nrofSymbols - 2) * nrofPRB; + } else if (nrofSymbols > 9) { + M_bit = (add_dmrs == 0) ? (E_init * (nrofSymbols - 2) * nrofPRB) : (E_init * (nrofSymbols - 4) * nrofPRB); + } + } else if (pucch_pdu->format_type == 4) { + if (nrofSymbols == 4) { + M_bit = (intraSlotFrequencyHopping == 0) ? (E_init * (nrofSymbols - 1) / n_SF_PUCCH_s) + : (E_init * (nrofSymbols - 1) / n_SF_PUCCH_s); + } else if (nrofSymbols > 4) { + M_bit = E_init * (nrofSymbols - 2) / n_SF_PUCCH_s; + } else if (nrofSymbols > 9) { + M_bit = (add_dmrs == 0) ? (E_init * (nrofSymbols - 2) / n_SF_PUCCH_s) : (E_init * (nrofSymbols - 4) / n_SF_PUCCH_s); + } + } + + nr_uci_encoding(pucch_pdu->payload, pucch_pdu->n_bit, nrofPRB, false, M_bit, 0, b); /* * Implementing TS 38.211 * Subclauses 6.3.2.6.1 Scrambling (PUCCH formats 3 and 4) diff --git a/openair1/PHY/NR_UE_TRANSPORT/pucch_nr.h b/openair1/PHY/NR_UE_TRANSPORT/pucch_nr.h index 97be0e274f..1f24b589f4 100644 --- a/openair1/PHY/NR_UE_TRANSPORT/pucch_nr.h +++ b/openair1/PHY/NR_UE_TRANSPORT/pucch_nr.h @@ -72,6 +72,8 @@ void nr_generate_pucch3_4(const PHY_VARS_NR_UE *ue, const int nr_slot_tx, const fapi_nr_ul_config_pucch_pdu *pucch_pdu); +void nr_uci_encoding(uint64_t payload, uint8_t nr_bit, uint8_t nrofPRB, bool uci_on_pusch, uint16_t E, uint8_t Qm, uint64_t *b); + static const uint8_t list_of_prime_numbers[46] = {2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97, 101,103,107,109,113, From 8222fa27e5a2e358e351cb522122cc8433dfc3c2 Mon Sep 17 00:00:00 2001 From: alexjiao Date: Fri, 13 Jun 2025 16:31:45 +0800 Subject: [PATCH 17/97] Add functions to calculate rate matching information for UCI on PUSCH Use betaOffset and alpha scaling etc. to get rate matching information of UCI on PUSCH (e.g. max bit capacity of encoded UCI and encoded ULSCH) according to TS 38.212 section 6.3.2.4 and 6.2.7. --- .../PHY/NR_UE_TRANSPORT/nr_transport_ue.h | 9 + openair1/PHY/NR_UE_TRANSPORT/nr_ulsch_ue.c | 169 ++++++++++++++++++ 2 files changed, 178 insertions(+) diff --git a/openair1/PHY/NR_UE_TRANSPORT/nr_transport_ue.h b/openair1/PHY/NR_UE_TRANSPORT/nr_transport_ue.h index c63754b583..7252663d41 100644 --- a/openair1/PHY/NR_UE_TRANSPORT/nr_transport_ue.h +++ b/openair1/PHY/NR_UE_TRANSPORT/nr_transport_ue.h @@ -135,4 +135,13 @@ typedef struct { // PTRS symbol index, to be updated every PTRS symbol within a slot. uint8_t ptrs_symbol_index; } NR_UE_DLSCH_t; + +typedef struct { + uint16_t Q_dash_ACK; // number of coded HARQ-ACK symbols + uint16_t E_uci_ACK; // number of coded HARQ-ACK bits + uint16_t Q_dash_ACK_rvd; // number of coded HARQ-ACK symbols reserved + uint16_t E_uci_ACK_rvd; // number of coded HARQ-ACK bits reserved + uint32_t G_ulsch; // bit capacity of ULSCH +} rate_match_info_uci_t; + #endif diff --git a/openair1/PHY/NR_UE_TRANSPORT/nr_ulsch_ue.c b/openair1/PHY/NR_UE_TRANSPORT/nr_ulsch_ue.c index 435716919a..bd51b0641c 100644 --- a/openair1/PHY/NR_UE_TRANSPORT/nr_ulsch_ue.c +++ b/openair1/PHY/NR_UE_TRANSPORT/nr_ulsch_ue.c @@ -472,6 +472,175 @@ static void map_symbols(const nr_phy_pxsch_params_t p, } } +// Function to lookup beta offset value from Table 9.3-1 in TS 38.213 +static double get_beta_offset_harq_ack(uint8_t beta_offset_index) +{ + static const double beta_offset_values[21] = { + 1.000, // Index 0 + 2.000, // Index 1 + 2.500, // Index 2 + 3.125, // Index 3 + 4.000, // Index 4 + 5.000, // Index 5 + 6.250, // Index 6 + 8.000, // Index 7 + 10.000, // Index 8 + 12.625, // Index 9 + 15.875, // Index 10 + 20.000, // Index 11 + 31.000, // Index 12 + 50.000, // Index 13 + 80.000, // Index 14 + 126.000, // Index 15 + 0.6, // Index 16 + 0.4, // Index 17 + 0.2, // Index 18 + 0.1, // Index 19 + 0.05, // Index 20 + }; + + if (beta_offset_index > 20) { + LOG_E(PHY, "Invalid beta_offset_index %d, using default value\n", beta_offset_index); + return 20.000; // Default value using index 11 + } + + return beta_offset_values[beta_offset_index]; +} + +static double get_alpha_scaling_value(uint8_t alpha_scaling) +{ + switch (alpha_scaling) { + case 0: + return 0.5; + case 1: + return 0.65; + case 2: + return 0.8; + case 3: + return 1.0; + default: + AssertFatal(false, "Invalid alpha_scaling value %d, valid range is 0-3", alpha_scaling); + return 1.0; + } +} + +/* + * This function gets the CRC size of UCI + */ +static int get_crc_uci(const uint16_t ouci) +{ + int L = 0; + if (ouci > 19) { + L = 11; + } else if (ouci > 11) { + L = 6; + } else { + L = 0; // no ACK/NACK + } + + return L; +} + +static uint16_t get_Qd(const uint16_t oack, double beta, double alpha, const uint32_t sumKr, const uint32_t s1, const uint32_t s2) +{ + if (oack == 0) + return 0; + + uint16_t first_term = ceil(((double)oack + get_crc_uci(oack)) * (double)beta * s1 / sumKr); + uint16_t second_term = ceil(alpha * s2); + + return (first_term < second_term) ? first_term : second_term; +} + +/* + * This function calculates the rate matching information for UCI multiplexing with PUSCH + */ +static rate_match_info_uci_t calc_rate_match_info_uci(const nfapi_nr_ue_pusch_pdu_t *pusch_pdu, + const NR_UL_UE_HARQ_t *harq_process_ul_ue, + const uint8_t nlqm, + unsigned int *G) +{ + // get beta offset + uint8_t beta_offset_index = pusch_pdu->pusch_uci.beta_offset_harq_ack; + double beta = get_beta_offset_harq_ack(beta_offset_index); + + // get alpha scaling value + uint8_t alpha_scaling = pusch_pdu->pusch_uci.alpha_scaling; + double alpha = get_alpha_scaling_value(alpha_scaling); + + // Calculate sumKr (total bits in all code blocks) + uint32_t sumKr = 0; + if (harq_process_ul_ue->C == 0) { + sumKr = 0; + } else if (harq_process_ul_ue->C == 1) { + sumKr = harq_process_ul_ue->K; + } else { + sumKr = harq_process_ul_ue->K * harq_process_ul_ue->C; + } + + // Calculate s1: total number of non-DMRS REs in allocation + uint16_t nb_rb = pusch_pdu->rb_size; + uint8_t start_symbol = pusch_pdu->start_symbol_index; + uint8_t number_of_symbols = pusch_pdu->nr_of_symbols; + uint16_t ul_dmrs_symb_pos = pusch_pdu->ul_dmrs_symb_pos; + + uint32_t s1 = 0; + for (int l = start_symbol; l < start_symbol + number_of_symbols; l++) { + if (!((ul_dmrs_symb_pos >> l) & 0x01)) { + s1 += nb_rb * NR_NB_SC_PER_RB; + } + } + + // Calculate s2: number of non-DMRS REs after first DMRS symbol + int first_dmrs_symbol = -1; + for (int l = start_symbol; l < start_symbol + number_of_symbols; l++) { + if ((ul_dmrs_symb_pos >> l) & 0x01) { + first_dmrs_symbol = l; + break; + } + } + int l0 = -1; + if (first_dmrs_symbol >= 0 && first_dmrs_symbol < start_symbol + number_of_symbols - 1) { + l0 = first_dmrs_symbol + 1; + } + uint32_t s2 = 0; + for (int l = l0; l < start_symbol + number_of_symbols; l++) { + if (!((ul_dmrs_symb_pos >> l) & 0x01)) { + s2 += nb_rb * NR_NB_SC_PER_RB; + } + } + + uint16_t oack = pusch_pdu->pusch_uci.harq_ack_bit_length; + uint16_t oack_rvd = (oack <= 2) ? 2 : 0; // get the reserved bits when oACK <= 2 according to TS 38.212 section 6.2.7, step 1 + + rate_match_info_uci_t rminfo = {0}; + + // get the number of coded HARQ-ACK symbols and bits, TS 38.212 section 6.3.2.4.1.1 + rminfo.Q_dash_ACK = get_Qd(oack, beta, alpha, sumKr, s1, s2); + rminfo.E_uci_ACK = rminfo.Q_dash_ACK * nlqm; + + if (oack_rvd > 0) { + rminfo.Q_dash_ACK_rvd = get_Qd(oack_rvd, beta, alpha, sumKr, s1, s2); + rminfo.E_uci_ACK_rvd = rminfo.Q_dash_ACK_rvd * nlqm; + } + + if (oack_rvd == 0) { + rminfo.G_ulsch = *G - rminfo.E_uci_ACK; + } else { + rminfo.G_ulsch = *G; + } + + *G = rminfo.G_ulsch; + LOG_D(PHY, "[UCI_RATE_MATCH] sumKr=%u, s1=%u, s2=%u, Final G_ulsch (output G): %u\n", sumKr, s1, s2, *G); + LOG_D(PHY, + "[UCI_RATE_MATCH] rate matching info returned: E_uci_ACK=%u, E_uci_ACK_rvd=%u, G_ulsch=%u\n", + rminfo.E_uci_ACK, + rminfo.E_uci_ACK_rvd, + rminfo.G_ulsch); + + return rminfo; +} + void nr_ue_ulsch_procedures(PHY_VARS_NR_UE *UE, const uint32_t frame, const uint8_t slot, From ceda9c11412a465c47b4dc092086eca4dcac0555 Mon Sep 17 00:00:00 2001 From: alexjiao Date: Fri, 13 Jun 2025 19:58:41 +0800 Subject: [PATCH 18/97] Add functions to implement UCI and data multiplexing on PUSCH Data and control multiplexing procedure is defined in 38.212 section 6.2.7. Step 3 (CSI1 and CSI2 mapping) is not implemented, only HARQ-ACK. PUSCH frequency hopping is not supported. --- .../NR_UE_TRANSPORT/nr_transport_proto_ue.h | 13 +- openair1/PHY/NR_UE_TRANSPORT/nr_ulsch_ue.c | 371 ++++++++++++++++++ 2 files changed, 382 insertions(+), 2 deletions(-) diff --git a/openair1/PHY/NR_UE_TRANSPORT/nr_transport_proto_ue.h b/openair1/PHY/NR_UE_TRANSPORT/nr_transport_proto_ue.h index 44a3611bf0..8a08b97121 100644 --- a/openair1/PHY/NR_UE_TRANSPORT/nr_transport_proto_ue.h +++ b/openair1/PHY/NR_UE_TRANSPORT/nr_transport_proto_ue.h @@ -41,6 +41,15 @@ #define NR_PUSCH_x 2 // UCI placeholder bit TS 38.212 V15.4.0 subclause 5.3.3.1 #define NR_PUSCH_y 3 // UCI placeholder bit +typedef enum { + BIT_TYPE_ULSCH = 0, // Default: UL-SCH data + BIT_TYPE_ACK = 1, // HARQ-ACK bit + BIT_TYPE_ACK_RESERVED = 2, // Reserved for HARQ-ACK (punctured) + BIT_TYPE_ACK_ULSCH = 3, + BIT_TYPE_CSI1 = 4, // CSI Part 1 bit + BIT_TYPE_CSI2 = 5 // CSI Part 2 bit +} uci_on_pusch_bit_type_t; + // Specifies the data that should be copied to the scope during PDSCH RX typedef struct pdsch_scope_req_s { bool copy_chanest_to_scope; @@ -121,8 +130,8 @@ void nr_pusch_codeword_scrambling(uint8_t *in, uint32_t Nid, uint32_t n_RNTI, bool uci_on_pusch, - uint32_t* out); - + const uci_on_pusch_bit_type_t *template, + uint32_t *out); /** \brief Alternative entry point to UE uplink shared channels procedures. It handles all the HARQ processes in only one call. diff --git a/openair1/PHY/NR_UE_TRANSPORT/nr_ulsch_ue.c b/openair1/PHY/NR_UE_TRANSPORT/nr_ulsch_ue.c index bd51b0641c..6091746ef2 100644 --- a/openair1/PHY/NR_UE_TRANSPORT/nr_ulsch_ue.c +++ b/openair1/PHY/NR_UE_TRANSPORT/nr_ulsch_ue.c @@ -49,6 +49,12 @@ #include "PHY/NR_REFSIG/ul_ref_seq_nr.h" #include #include "PHY/log_tools.h" +#include "PHY/NR_UE_TRANSPORT/pucch_nr.h" +#include + +#define MAX_RE_PER_SYMBOL_IN_ALLOC (275 * 12) +#define MAX_NLQM (4 * 8) +#define MAX_UCI_CODED_BITS 1024 //#define DEBUG_PUSCH_MAPPING //#define DEBUG_MAC_PDU @@ -641,6 +647,371 @@ static rate_match_info_uci_t calc_rate_match_info_uci(const nfapi_nr_ue_pusch_pd return rminfo; } +static int initialize_mapping_resources(const nfapi_nr_ue_pusch_pdu_t *pusch_pdu, + uint32_t *m_ulsch_initial, + uint32_t *m_ulsch_current, + uint32_t *m_uci_current, + bool *is_dmrs_symbol_flags, + uint8_t *dmrs_symbol_set_relative, + uint8_t *num_dmrs_symbols_in_set_relative) +{ + if (!pusch_pdu || !m_ulsch_initial || !m_ulsch_current || !m_uci_current || !is_dmrs_symbol_flags || !dmrs_symbol_set_relative + || !num_dmrs_symbols_in_set_relative) + return -1; + + uint8_t n_pusch_sym_all = pusch_pdu->nr_of_symbols; + uint16_t ul_dmrs_symb_pos = pusch_pdu->ul_dmrs_symb_pos; + uint8_t dmrs_type = pusch_pdu->dmrs_config_type; + uint8_t cdm_grps_no_data = pusch_pdu->num_dmrs_cdm_grps_no_data; + uint32_t res_per_symbol_non_dmrs = pusch_pdu->rb_size * 12; + + *num_dmrs_symbols_in_set_relative = 0; + + // Initialize resources per symbol for ULSCH and UCI + for (uint8_t i = 0; i < n_pusch_sym_all; i++) { + uint8_t absolute_symbol_idx = pusch_pdu->start_symbol_index + i; + + if ((ul_dmrs_symb_pos >> absolute_symbol_idx) & 0x01) { + is_dmrs_symbol_flags[i] = true; + if (*num_dmrs_symbols_in_set_relative < 14) { + dmrs_symbol_set_relative[(*num_dmrs_symbols_in_set_relative)++] = i; + } + + // Calculate available data REs on DMRS symbols based on DMRS configuration + uint32_t data_re_on_dmrs_sym_per_prb = 0; + + if (dmrs_type == 0) { // Type 1 + if (cdm_grps_no_data == 1) { + data_re_on_dmrs_sym_per_prb = 6; + } else { + data_re_on_dmrs_sym_per_prb = 0; + } + } else { // Type 2 + if (cdm_grps_no_data == 1) { + data_re_on_dmrs_sym_per_prb = 4; + } else if (cdm_grps_no_data == 2) { + data_re_on_dmrs_sym_per_prb = 2; + } else { + data_re_on_dmrs_sym_per_prb = 0; + } + } + + m_ulsch_initial[i] = pusch_pdu->rb_size * data_re_on_dmrs_sym_per_prb; + m_ulsch_current[i] = m_ulsch_initial[i]; + m_uci_current[i] = 0; // UCI is not mapped on DMRS symbols + + } else { // Not a DMRS symbol + is_dmrs_symbol_flags[i] = false; + + m_ulsch_initial[i] = res_per_symbol_non_dmrs; + m_ulsch_current[i] = res_per_symbol_non_dmrs; + m_uci_current[i] = res_per_symbol_non_dmrs; + } + } + + return 0; +} + +static uint8_t find_first_uci_symbol(uint8_t n_pusch_sym_all, + const bool *is_dmrs_symbol_flags, + uint8_t *dmrs_symbol_set_relative, + uint32_t num_dmrs_symbols_in_set_relative) +{ + uint8_t l1_c = 0; + + // Find first non-DMRS symbol + for (uint8_t i = 0; i < n_pusch_sym_all; i++) { + if (!is_dmrs_symbol_flags[i]) { + l1_c = i; + break; + } + } + + // If there are DMRS symbols, try to find a symbol after the first DMRS + if (num_dmrs_symbols_in_set_relative > 0) { + uint8_t first_dmrs_idx = dmrs_symbol_set_relative[0]; + if (l1_c <= first_dmrs_idx) { + for (uint8_t i = first_dmrs_idx + 1; i < n_pusch_sym_all; i++) { + if (!is_dmrs_symbol_flags[i]) { + l1_c = i; + break; + } + } + } + } + + return l1_c; +} + +/* + * This function builds the initial template by reserving positions for HARQ-ACK. + */ +static void build_template_reserve_ack(uci_on_pusch_bit_type_t *template, + const nfapi_nr_ue_pusch_pdu_t *pusch_pdu, + uint32_t G_ack_rvd, + uint8_t l1_c, + const uint32_t *m_uci_current, + const uint32_t *m_ulsch_initial, + uint32_t positions_by_sym[][MAX_UCI_CODED_BITS], + uint32_t *count_by_sym) +{ + const uint8_t n_symbols = pusch_pdu->nr_of_symbols; + const uint32_t nlqm = pusch_pdu->qam_mod_order * pusch_pdu->nrOfLayers; + + memset(count_by_sym, 0, n_symbols * sizeof(uint32_t)); + + uint32_t symbol_start_bit_idx[14] = {0}; + for (uint8_t s = 1; s < n_symbols; s++) { + symbol_start_bit_idx[s] = symbol_start_bit_idx[s - 1] + (m_ulsch_initial[s - 1] * nlqm); + } + + // Reserve Positions using RE-level D-Factor Distribution + uint32_t total_reserved = 0; + + for (uint8_t sym = l1_c; sym < n_symbols && total_reserved < G_ack_rvd; sym++) { + const uint32_t uci_re_on_sym = m_uci_current[sym]; + + if (uci_re_on_sym > 0) { + const uint32_t remaining_to_reserve = G_ack_rvd - total_reserved; + uint32_t d_factor_re; + const uint32_t num_re_to_select = ceil((double)remaining_to_reserve / nlqm); + if (num_re_to_select >= uci_re_on_sym) { + d_factor_re = 1; + } else { + d_factor_re = floor((double)uci_re_on_sym / num_re_to_select); + if (d_factor_re == 0) { + d_factor_re = 1; + } + } + + for (uint32_t re_offset = 0; re_offset < uci_re_on_sym && total_reserved < G_ack_rvd; re_offset += d_factor_re) { + for (uint32_t bit_in_re = 0; bit_in_re < nlqm; bit_in_re++) { + if (total_reserved >= G_ack_rvd) { + break; + } + + uint32_t bit_offset_in_sym = (re_offset * nlqm) + bit_in_re; + uint32_t cw_idx = symbol_start_bit_idx[sym] + bit_offset_in_sym; + template[cw_idx] = BIT_TYPE_ACK_RESERVED; + positions_by_sym[sym][count_by_sym[sym]] = cw_idx; + count_by_sym[sym]++; + + total_reserved++; + } + } + } + } +} + +/* + * This function maps the HARQ-ACK bits when O_ACK > 2 + */ +static void map_non_overlapped_ack(uci_on_pusch_bit_type_t *template, + const nfapi_nr_ue_pusch_pdu_t *pusch_pdu, + uint16_t G_ack, + uint8_t l1_c, + const uint32_t *m_uci_current, + const uint32_t *m_ulsch_initial) +{ + const uint8_t n_symbols = pusch_pdu->nr_of_symbols; + const uint32_t nlqm = pusch_pdu->qam_mod_order * pusch_pdu->nrOfLayers; + + uint32_t symbol_start_bit_idx[14] = {0}; + for (uint8_t s = 1; s < n_symbols; s++) { + symbol_start_bit_idx[s] = symbol_start_bit_idx[s - 1] + (m_ulsch_initial[s - 1] * nlqm); + } + + uint32_t total_placed = 0; + for (uint8_t sym = l1_c; sym < n_symbols && total_placed < G_ack; sym++) { + const uint32_t uci_re_on_sym = m_uci_current[sym]; + + if (uci_re_on_sym > 0) { + const uint32_t remaining_to_place = G_ack - total_placed; + uint32_t d_factor_re; + const uint32_t num_re_to_select = ceil((double)remaining_to_place / nlqm); + + if (num_re_to_select >= uci_re_on_sym) { + d_factor_re = 1; + } else { + d_factor_re = floor((double)uci_re_on_sym / num_re_to_select); + if (d_factor_re == 0) { + d_factor_re = 1; + } + } + + for (uint32_t re_offset = 0; re_offset < uci_re_on_sym && total_placed < G_ack; re_offset += d_factor_re) { + for (uint32_t bit_in_re = 0; bit_in_re < nlqm; bit_in_re++) { + if (total_placed >= G_ack) { + break; + } + + uint32_t bit_offset_in_sym = (re_offset * nlqm) + bit_in_re; + uint32_t cw_idx = symbol_start_bit_idx[sym] + bit_offset_in_sym; + template[cw_idx] = BIT_TYPE_ACK; + + total_placed++; + } + } + } + } +} + +/* + * This function maps the HARQ-ACK bits when O_ACK <= 2 + */ +static void map_overlapped_ack(uci_on_pusch_bit_type_t *template, + uint16_t G_ack, + uint8_t l1_c, + uint8_t n_symbols, + uint32_t positions_by_sym[][MAX_UCI_CODED_BITS], + const uint32_t *count_by_sym) +{ + uint32_t ack_bits_marked = 0; + + for (uint8_t sym_iter = l1_c; sym_iter < n_symbols && ack_bits_marked < G_ack; sym_iter++) { + const uint32_t num_reserved_bits_on_sym = count_by_sym[sym_iter]; + + if (num_reserved_bits_on_sym > 0) { + const uint32_t num_ack_remaining = G_ack - ack_bits_marked; + uint32_t d_factor; + + // This d-factor is calculated for stepping through the list of *reserved bits*. + if (num_ack_remaining >= num_reserved_bits_on_sym) { + d_factor = 1; + } else { + d_factor = floor((double)num_reserved_bits_on_sym / num_ack_remaining); + if (d_factor == 0) { + d_factor = 1; + } + } + + const uint32_t *reserved_indices_on_this_sym = positions_by_sym[sym_iter]; + + for (uint32_t i = 0; i < num_reserved_bits_on_sym && ack_bits_marked < G_ack; i += d_factor) { + uint32_t pos_to_mark = reserved_indices_on_this_sym[i]; + template[pos_to_mark] = BIT_TYPE_ACK_ULSCH; + + ack_bits_marked++; + } + } + } +} + +/* + * Applies the template to build the final codeword + */ +static void apply_template_to_codeword(uint8_t *codeword, + const uci_on_pusch_bit_type_t *template, + uint32_t codeword_len, + const uint8_t *ulsch_bits, + const uint64_t *cack, + uint16_t G_ack, + uint32_t G_ulsch) +{ + uint32_t ulsch_idx = 0; + uint32_t ack_idx = 0; + + for (uint32_t i = 0; i < codeword_len; i++) { + switch (template[i]) { + case BIT_TYPE_ACK: + if (G_ack > 0 && ack_idx < G_ack) { + uint32_t word_idx = ack_idx / 64; + uint32_t bit_in_word_idx = ack_idx % 64; + codeword[i] = (cack[word_idx] >> bit_in_word_idx) & 1; + ack_idx++; + } + break; + + case BIT_TYPE_ACK_ULSCH: + if (G_ack > 0 && ack_idx < G_ack) { + codeword[i] = ((const uint8_t *)cack)[ack_idx++]; + if (G_ulsch > 0 && ulsch_idx < G_ulsch) { + ulsch_idx++; + } + } + break; + + case BIT_TYPE_ACK_RESERVED: + case BIT_TYPE_ULSCH: + default: + if (G_ulsch > 0 && ulsch_idx < G_ulsch) { + uint32_t byte_idx = ulsch_idx / 8; + uint32_t bit_in_byte_idx = ulsch_idx % 8; + codeword[i] = (ulsch_bits[byte_idx] >> bit_in_byte_idx) & 1; + ulsch_idx++; + } + break; + } + } +} + +/* + * This function implements the UCI multiplexing on PUSCH according to TS 38.212 section 6.2.7. + */ +static uci_on_pusch_bit_type_t *nr_data_control_mapping(const nfapi_nr_ue_pusch_pdu_t *pusch_pdu, + uci_on_pusch_bit_type_t *template, + unsigned int G_ulsch, + uint16_t G_ack, + uint32_t G_ack_rvd, + uint8_t *codeword, + uint32_t codeword_len, + const uint8_t *ulsch_bits, + const uint64_t *cack) +{ + if (!pusch_pdu || !codeword || codeword_len == 0 || !template) + return NULL; + const uint8_t n_symbols = pusch_pdu->nr_of_symbols; + if (n_symbols == 0 || n_symbols > 14) + return NULL; + + uint32_t m_ulsch_initial[14] = {0}; + uint32_t m_ulsch_current[14] = {0}; + uint32_t m_uci_current[14] = {0}; // This holds RE counts, not bit counts + bool is_dmrs_symbol_flags[14] = {0}; + uint8_t dmrs_symbol_set_relative[14] = {0}; + uint8_t num_dmrs_symbols_in_set_relative = 0; + + if (initialize_mapping_resources(pusch_pdu, + m_ulsch_initial, + m_ulsch_current, + m_uci_current, + is_dmrs_symbol_flags, + dmrs_symbol_set_relative, + &num_dmrs_symbols_in_set_relative) + != 0) { + LOG_E(PHY, "Failed to initialize mapping resources\n"); + return NULL; + } + + uint8_t l1_c = find_first_uci_symbol(n_symbols, is_dmrs_symbol_flags, dmrs_symbol_set_relative, num_dmrs_symbols_in_set_relative); + + memset(template, 0, codeword_len * sizeof(uci_on_pusch_bit_type_t)); + + uint32_t positions_by_sym[14][MAX_UCI_CODED_BITS] = {0}; + uint32_t count_by_sym[14] = {0}; + + if (G_ack_rvd > 0) { + build_template_reserve_ack(template, + pusch_pdu, + G_ack_rvd, + l1_c, + m_uci_current, + m_ulsch_initial, + positions_by_sym, + count_by_sym); + } else if (G_ack > 0) { + map_non_overlapped_ack(template, pusch_pdu, G_ack, l1_c, m_uci_current, m_ulsch_initial); + } + + if (G_ack > 0 && G_ack_rvd > 0) { + map_overlapped_ack(template, G_ack, l1_c, n_symbols, positions_by_sym, count_by_sym); + } + + apply_template_to_codeword(codeword, template, codeword_len, ulsch_bits, cack, G_ack, G_ulsch); + + return template; +} + void nr_ue_ulsch_procedures(PHY_VARS_NR_UE *UE, const uint32_t frame, const uint8_t slot, From 5f3a19cb7d83dd7c83c3a801ae4952a1a8a0d7e0 Mon Sep 17 00:00:00 2001 From: Sakthivel Velumani Date: Fri, 15 Aug 2025 21:20:53 -0400 Subject: [PATCH 19/97] Separate segmentation from ULSCH encoding function Segmentation have to be called before UCI mapping to get number of segments and sumKr values. --- .../NR_UE_TRANSPORT/nr_transport_proto_ue.h | 7 + .../PHY/NR_UE_TRANSPORT/nr_transport_ue.h | 2 + .../PHY/NR_UE_TRANSPORT/nr_ulsch_coding.c | 153 +++++++++--------- openair1/PHY/NR_UE_TRANSPORT/nr_ulsch_ue.c | 4 +- openair1/SIMULATION/NR_PHY/ulschsim.c | 1 + 5 files changed, 92 insertions(+), 75 deletions(-) diff --git a/openair1/PHY/NR_UE_TRANSPORT/nr_transport_proto_ue.h b/openair1/PHY/NR_UE_TRANSPORT/nr_transport_proto_ue.h index 8a08b97121..c257726501 100644 --- a/openair1/PHY/NR_UE_TRANSPORT/nr_transport_proto_ue.h +++ b/openair1/PHY/NR_UE_TRANSPORT/nr_transport_proto_ue.h @@ -95,6 +95,13 @@ void nr_dlsch_decoding(PHY_VARS_NR_UE *phy_vars_ue, int nb_dlsch, uint8_t *DLSCH_ids); +int nr_ulsch_pre_encoding(PHY_VARS_NR_UE *ue, + const NR_UE_ULSCH_t *ulsch, + const uint32_t frame, + const uint8_t slot, + const unsigned int *G, + const int nb_ulsch, + const uint8_t *ULSCH_ids); /** \brief This is the alternative top-level entry point for ULSCH encoding in UE. It handles all the HARQ processes in only one call. The routine first computes the segmentation information, followed by LDPC encoding algorithm of the diff --git a/openair1/PHY/NR_UE_TRANSPORT/nr_transport_ue.h b/openair1/PHY/NR_UE_TRANSPORT/nr_transport_ue.h index 7252663d41..26f41b1fd3 100644 --- a/openair1/PHY/NR_UE_TRANSPORT/nr_transport_ue.h +++ b/openair1/PHY/NR_UE_TRANSPORT/nr_transport_ue.h @@ -55,6 +55,8 @@ typedef struct { uint32_t C; /// Number of bits in code segments uint32_t K; + /// + uint32_t Kb; /// Number of "Filler" bits uint32_t F; /// Number of soft channel bits diff --git a/openair1/PHY/NR_UE_TRANSPORT/nr_ulsch_coding.c b/openair1/PHY/NR_UE_TRANSPORT/nr_ulsch_coding.c index b044d06305..d68ae6e9ae 100644 --- a/openair1/PHY/NR_UE_TRANSPORT/nr_ulsch_coding.c +++ b/openair1/PHY/NR_UE_TRANSPORT/nr_ulsch_coding.c @@ -34,56 +34,27 @@ #include "common/utils/LOG/vcd_signal_dumper.h" #include "PHY/log_tools.h" -int nr_ulsch_encoding(PHY_VARS_NR_UE *ue, - NR_UE_ULSCH_t *ulsch, - const uint32_t frame, - const uint8_t slot, - unsigned int *G, - int nb_ulsch, - uint8_t *ULSCH_ids, - uint16_t number_dmrs_symbols) +int nr_ulsch_pre_encoding(PHY_VARS_NR_UE *ue, + const NR_UE_ULSCH_t *ulsch, + const uint32_t frame, + const uint8_t slot, + const unsigned int *G, + const int nb_ulsch, + const uint8_t *ULSCH_ids) { - start_meas_nr_ue_phy(ue, ULSCH_ENCODING_STATS); - VCD_SIGNAL_DUMPER_DUMP_FUNCTION_BY_NAME(VCD_SIGNAL_DUMPER_FUNCTIONS_NR_UE_ULSCH_ENCODING, VCD_FUNCTION_IN); - - nrLDPC_TB_encoding_parameters_t TBs[nb_ulsch]; - memset(TBs, 0, sizeof(TBs)); - nrLDPC_slot_encoding_parameters_t slot_parameters = { - .frame = frame, - .slot = slot, - .nb_TBs = nb_ulsch, - .threadPool = &get_nrUE_params()->Tpool, - .tinput = NULL, - .tprep = NULL, - .tparity = NULL, - .toutput = NULL, - .TBs = TBs - }; - - int max_num_segments = 0; - - for (uint8_t pusch_id = 0; pusch_id < nb_ulsch; pusch_id++) { - uint8_t ULSCH_id = ULSCH_ids[pusch_id]; - uint8_t harq_pid = ulsch[ULSCH_id].pusch_pdu.pusch_data.harq_process_id; - - nrLDPC_TB_encoding_parameters_t *TB_parameters = &TBs[pusch_id]; - /* Neither harq_pid nor ULSCH_id are unique in the instance - * but their combination is. - * Since ULSCH_id < 2 - * then 2 * harq_pid + ULSCH_id is unique. - */ - TB_parameters->harq_unique_pid = 2 * harq_pid + ULSCH_id; + for (uint_fast8_t pusch_id = 0; pusch_id < nb_ulsch; pusch_id++) { + const uint8_t ULSCH_id = ULSCH_ids[pusch_id]; + const uint8_t harq_pid = ulsch[ULSCH_id].pusch_pdu.pusch_data.harq_process_id; /////////////////////////parameters and variables initialization///////////////////////// - unsigned int crc = 1; NR_UL_UE_HARQ_t *harq_process = &ue->ul_harq_processes[harq_pid]; const nfapi_nr_ue_pusch_pdu_t *pusch_pdu = &ulsch->pusch_pdu; - uint16_t nb_rb = pusch_pdu->rb_size; - uint32_t A = pusch_pdu->pusch_data.tb_size << 3; - uint8_t Qm = pusch_pdu->qam_mod_order; + const uint16_t nb_rb = pusch_pdu->rb_size; + const uint32_t A = pusch_pdu->pusch_data.tb_size << 3; + const uint8_t Qm = pusch_pdu->qam_mod_order; // target_code_rate is in 0.1 units - float Coderate = (float)pusch_pdu->target_code_rate / 10240.0f; + const float Coderate = (float)pusch_pdu->target_code_rate / 10240.0f; LOG_D(NR_PHY, "ulsch coding nb_rb %d, Nl = %d\n", nb_rb, pusch_pdu->nrOfLayers); LOG_D(NR_PHY, "ulsch coding A %d G %d mod_order %d Coderate %f\n", A, G[pusch_id], Qm, Coderate); @@ -91,11 +62,11 @@ int nr_ulsch_encoding(PHY_VARS_NR_UE *ue, ///////////////////////// a---->| add CRC |---->b ///////////////////////// - int max_payload_bytes = MAX_NUM_NR_ULSCH_SEGMENTS_PER_LAYER * pusch_pdu->nrOfLayers * 1056; + const int max_payload_bytes = MAX_NUM_NR_ULSCH_SEGMENTS_PER_LAYER * pusch_pdu->nrOfLayers * 1056; int B; if (A > NR_MAX_PDSCH_TBS) { // Add 24-bit crc (polynomial A) to payload - crc = crc24a(harq_process->payload_AB, A) >> 8; + const unsigned int crc = crc24a(harq_process->payload_AB, A) >> 8; harq_process->payload_AB[A >> 3] = ((uint8_t *)&crc)[2]; harq_process->payload_AB[1 + (A >> 3)] = ((uint8_t *)&crc)[1]; harq_process->payload_AB[2 + (A >> 3)] = ((uint8_t *)&crc)[0]; @@ -103,7 +74,7 @@ int nr_ulsch_encoding(PHY_VARS_NR_UE *ue, AssertFatal((A / 8) + 4 <= max_payload_bytes, "A %d is too big (A/8+4 = %d > %d)\n", A, (A / 8) + 4, max_payload_bytes); } else { // Add 16-bit crc (polynomial A) to payload - crc = crc16(harq_process->payload_AB, A) >> 16; + const unsigned int crc = crc16(harq_process->payload_AB, A) >> 16; harq_process->payload_AB[A >> 3] = ((uint8_t *)&crc)[1]; harq_process->payload_AB[1 + (A >> 3)] = ((uint8_t *)&crc)[0]; B = A + 16; @@ -151,7 +122,7 @@ int nr_ulsch_encoding(PHY_VARS_NR_UE *ue, T_INT((int)ulsch->pusch_pdu.transform_precoding), // transformPrecoder_enabled = 0, transformPrecoder_disabled = 1 T_INT((int)ulsch->pusch_pdu.dmrs_config_type), // dmrs_resource_map_config: pusch_dmrs_type1 = 0, pusch_dmrs_type2 = 1 T_INT((int)ulsch->pusch_pdu.ul_dmrs_symb_pos), // used to derive the DMRS symbol positions - T_INT((int)number_dmrs_symbols), + T_INT((int)get_num_dmrs(ulsch->pusch_pdu.ul_dmrs_symb_pos)), // dmrs_start_ofdm_symbol // dmrs_duration_num_ofdm_symbols // dmrs_num_add_positions @@ -168,36 +139,55 @@ int nr_ulsch_encoding(PHY_VARS_NR_UE *ue, VCD_SIGNAL_DUMPER_DUMP_FUNCTION_BY_NAME(VCD_SIGNAL_DUMPER_FUNCTIONS_NR_SEGMENTATION, VCD_FUNCTION_IN); start_meas_nr_ue_phy(ue, ULSCH_SEGMENTATION_STATS); - TB_parameters->Kb = nr_segmentation(harq_process->payload_AB, - harq_process->c, - B, - &harq_process->C, - &harq_process->K, - &harq_process->Z, - &harq_process->F, - harq_process->BG); - TB_parameters->C = harq_process->C; - TB_parameters->K = harq_process->K; - TB_parameters->Z = harq_process->Z; - TB_parameters->F = harq_process->F; - TB_parameters->BG = harq_process->BG; - if (TB_parameters->C > MAX_NUM_NR_DLSCH_SEGMENTS_PER_LAYER * pusch_pdu->nrOfLayers) { - LOG_E(PHY, "nr_segmentation.c: too many segments %d, B %d\n", TB_parameters->C, B); + harq_process->Kb = nr_segmentation(harq_process->payload_AB, + harq_process->c, + B, + &harq_process->C, + &harq_process->K, + &harq_process->Z, + &harq_process->F, + harq_process->BG); + if (harq_process->C > MAX_NUM_NR_DLSCH_SEGMENTS_PER_LAYER * pusch_pdu->nrOfLayers) { + LOG_E(PHY, "nr_segmentation.c: too many segments %d, B %d\n", harq_process->C, B); return (-1); } stop_meas_nr_ue_phy(ue, ULSCH_SEGMENTATION_STATS); VCD_SIGNAL_DUMPER_DUMP_FUNCTION_BY_NAME(VCD_SIGNAL_DUMPER_FUNCTIONS_NR_SEGMENTATION, VCD_FUNCTION_OUT); - max_num_segments = max(max_num_segments, TB_parameters->C); - - TB_parameters->nb_rb = nb_rb; - TB_parameters->Qm = Qm; - TB_parameters->mcs = pusch_pdu->mcs_index; - TB_parameters->nb_layers = pusch_pdu->nrOfLayers; - TB_parameters->rv_index = pusch_pdu->pusch_data.rv_index; - TB_parameters->G = G[pusch_id]; - TB_parameters->tbslbrm = pusch_pdu->tbslbrm; - TB_parameters->A = A; } // pusch_id + return 0; +} + +int nr_ulsch_encoding(PHY_VARS_NR_UE *ue, + NR_UE_ULSCH_t *ulsch, + const uint32_t frame, + const uint8_t slot, + unsigned int *G, + int nb_ulsch, + uint8_t *ULSCH_ids, + uint16_t number_dmrs_symbols) +{ + start_meas_nr_ue_phy(ue, ULSCH_ENCODING_STATS); + VCD_SIGNAL_DUMPER_DUMP_FUNCTION_BY_NAME(VCD_SIGNAL_DUMPER_FUNCTIONS_NR_UE_ULSCH_ENCODING, VCD_FUNCTION_IN); + + nrLDPC_TB_encoding_parameters_t TBs[nb_ulsch]; + memset(TBs, 0, sizeof(TBs)); + nrLDPC_slot_encoding_parameters_t slot_parameters = {.frame = frame, + .slot = slot, + .nb_TBs = nb_ulsch, + .threadPool = &get_nrUE_params()->Tpool, + .tinput = NULL, + .tprep = NULL, + .tparity = NULL, + .toutput = NULL, + .TBs = TBs}; + + int max_num_segments = 0; + for (uint_fast8_t pusch_id = 0; pusch_id < nb_ulsch; pusch_id++) { + const uint8_t ULSCH_id = ULSCH_ids[pusch_id]; + const uint8_t harq_pid = ulsch[ULSCH_id].pusch_pdu.pusch_data.harq_process_id; + NR_UL_UE_HARQ_t *harq_process = &ue->ul_harq_processes[harq_pid]; + max_num_segments = max(max_num_segments, harq_process->C); + } nrLDPC_segment_encoding_parameters_t segments[nb_ulsch][max_num_segments]; memset(segments, 0, sizeof(segments)); @@ -208,10 +198,25 @@ int nr_ulsch_encoding(PHY_VARS_NR_UE *ue, nrLDPC_TB_encoding_parameters_t *TB_parameters = &TBs[pusch_id]; NR_UL_UE_HARQ_t *harq_process = &ue->ul_harq_processes[harq_pid]; + const nfapi_nr_ue_pusch_pdu_t *pusch_pdu = &ulsch[ULSCH_id].pusch_pdu; + const uint16_t nb_rb = pusch_pdu->rb_size; + TB_parameters->harq_unique_pid = 2 * harq_pid + ULSCH_id; + TB_parameters->C = harq_process->C; + TB_parameters->K = harq_process->K; + TB_parameters->Z = harq_process->Z; + TB_parameters->F = harq_process->F; + TB_parameters->BG = harq_process->BG; + TB_parameters->Kb = harq_process->Kb; + TB_parameters->nb_rb = nb_rb; + TB_parameters->Qm = pusch_pdu->qam_mod_order; + TB_parameters->mcs = pusch_pdu->mcs_index; + TB_parameters->nb_layers = pusch_pdu->nrOfLayers; + TB_parameters->rv_index = pusch_pdu->pusch_data.rv_index; + TB_parameters->G = G[pusch_id]; + TB_parameters->tbslbrm = pusch_pdu->tbslbrm; + TB_parameters->A = pusch_pdu->pusch_data.tb_size / 8; TB_parameters->segments = segments[pusch_id]; - const nfapi_nr_ue_pusch_pdu_t *pusch_pdu = &ulsch->pusch_pdu; - uint16_t nb_rb = pusch_pdu->rb_size; memset(harq_process->f, 0, 14 * nb_rb * 12 * 16); TB_parameters->output = harq_process->f; diff --git a/openair1/PHY/NR_UE_TRANSPORT/nr_ulsch_ue.c b/openair1/PHY/NR_UE_TRANSPORT/nr_ulsch_ue.c index 6091746ef2..561d357363 100644 --- a/openair1/PHY/NR_UE_TRANSPORT/nr_ulsch_ue.c +++ b/openair1/PHY/NR_UE_TRANSPORT/nr_ulsch_ue.c @@ -1092,7 +1092,9 @@ void nr_ue_ulsch_procedures(PHY_VARS_NR_UE *UE, /////////////////////////ULSCH coding///////////////////////// - if (nr_ulsch_encoding(UE, &phy_data->ulsch, frame, slot, G, 1, ULSCH_ids, number_dmrs_symbols) == -1) { + int enc_ret = nr_ulsch_pre_encoding(UE, &phy_data->ulsch, frame, slot, G, 1, ULSCH_ids); + enc_ret += nr_ulsch_encoding(UE, &phy_data->ulsch, frame, slot, G, 1, ULSCH_ids, number_dmrs_symbols); + if (enc_ret != 0) { stop_meas_nr_ue_phy(UE, PUSCH_PROC_STATS); return; } diff --git a/openair1/SIMULATION/NR_PHY/ulschsim.c b/openair1/SIMULATION/NR_PHY/ulschsim.c index 058a59feb5..64c04b900e 100644 --- a/openair1/SIMULATION/NR_PHY/ulschsim.c +++ b/openair1/SIMULATION/NR_PHY/ulschsim.c @@ -513,6 +513,7 @@ int main(int argc, char **argv) if (input_fd == NULL) { uint8_t ULSCH_ids[] = {0}; + nr_ulsch_pre_encoding(UE, ulsch_ue, 0, 0, &G, 1, ULSCH_ids); nr_ulsch_encoding(UE, ulsch_ue, 0, 0, &G, 1, ULSCH_ids, 0); } From b43dee14995c274d3f617db0039073c464d45445 Mon Sep 17 00:00:00 2001 From: Jaroslava Fiedlerova Date: Fri, 26 Sep 2025 14:58:47 +0200 Subject: [PATCH 20/97] CI: adjust Amarisoft UE RX gain Lower the RX gain used by the Amarisoft UE in CI tests to prevent DL signal saturation. This resolves an issue with low DL throughput observed in CI. RX/TX signal levels on the Amarisoft UE can be checked with "t spl" command in lteue; MAX values should not approach 0. --- ci-scripts/as_ue/config.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ci-scripts/as_ue/config.cfg b/ci-scripts/as_ue/config.cfg index 1c71b752c1..42d8eba369 100644 --- a/ci-scripts/as_ue/config.cfg +++ b/ci-scripts/as_ue/config.cfg @@ -44,4 +44,4 @@ rf_driver: { #endif tx_gain: 75.0, /* TX gain (in dB) */ -rx_gain: 55.0, /* RX gain (in dB) */ +rx_gain: 52.0, /* RX gain (in dB) */ From 03d4ce2e1e96fee1d1b2ccec8e4b1a8b669af2d1 Mon Sep 17 00:00:00 2001 From: Jaroslava Fiedlerova Date: Fri, 26 Sep 2025 15:07:16 +0200 Subject: [PATCH 21/97] CI: Attach multiple UEs at once in AW2S test --- .../xml_files/container_sa_aw2s_asue.xml | 88 +------------------ 1 file changed, 2 insertions(+), 86 deletions(-) diff --git a/ci-scripts/xml_files/container_sa_aw2s_asue.xml b/ci-scripts/xml_files/container_sa_aw2s_asue.xml index 2367dc7e5c..e5d32c181f 100644 --- a/ci-scripts/xml_files/container_sa_aw2s_asue.xml +++ b/ci-scripts/xml_files/container_sa_aw2s_asue.xml @@ -36,18 +36,6 @@ 102002 302002 102003 - 102004 - 102005 - 102006 - 102007 - 102008 - 102009 - 102010 - 102011 - 102012 - 102013 - 102014 - 102015 100100 100010 100020 @@ -118,80 +106,8 @@ Attach_UE - Attach UE 3 - amarisoft_ue_3 - - - - Attach_UE - Attach UE 4 - amarisoft_ue_4 - - - - Attach_UE - Attach (RedCap) UE 5 - amarisoft_ue_5 - - - - Attach_UE - Attach (RedCap) UE 6 - amarisoft_ue_6 - - - - Attach_UE - Attach UE 7 - amarisoft_ue_7 - - - - Attach_UE - Attach UE 8 - amarisoft_ue_8 - - - - Attach_UE - Attach UE 9 - amarisoft_ue_9 - - - - Attach_UE - Attach (RedCap) UE 10 - amarisoft_ue_10 - - - - Attach_UE - Attach (RedCap) UE 11 - amarisoft_ue_11 - - - - Attach_UE - Attach (RedCap) UE 12 - amarisoft_ue_12 - - - - Attach_UE - Attach UE 13 - amarisoft_ue_13 - - - - Attach_UE - Attach UE 14 - amarisoft_ue_14 - - - - Attach_UE - Attach (RedCap) UE 15 - amarisoft_ue_15 + Attach UE 3 - 15 + amarisoft_ue_3 amarisoft_ue_4 amarisoft_ue_5 amarisoft_ue_6 amarisoft_ue_7 amarisoft_ue_8 amarisoft_ue_9 amarisoft_ue_10 amarisoft_ue_11 amarisoft_ue_12 amarisoft_ue_13 amarisoft_ue_14 amarisoft_ue_15 From 124debc2c77cb3ad74d2630e4ed76abb59daba81 Mon Sep 17 00:00:00 2001 From: Robert Schmidt Date: Thu, 19 Dec 2024 23:51:37 +0100 Subject: [PATCH 22/97] Fix warnings in rftest executable MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit warning: ‘genericPowerPerAntena’ defined but not used [-Wunused-function] warning: ‘graph.text’ is used uninitialized [-Wuninitialized] warning: ignoring return value of ‘read’ declared with attribute ‘warn_unused_result’ [-Wunused-result] --- openair1/PHY/TOOLS/calibration_scope.c | 14 ++++++++++---- openair1/PHY/TOOLS/calibration_test.c | 9 +++++++-- 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/openair1/PHY/TOOLS/calibration_scope.c b/openair1/PHY/TOOLS/calibration_scope.c index 6d7723d6d4..c7a305ab27 100644 --- a/openair1/PHY/TOOLS/calibration_scope.c +++ b/openair1/PHY/TOOLS/calibration_scope.c @@ -99,8 +99,9 @@ static void commonGraph(OAIgraph_t *graph, int type, FL_Coord x, FL_Coord y, FL_ } static OAIgraph_t calibrationCommonGraph( void (*funct) (OAIgraph_t *graph, calibData_t *context), - int type, FL_Coord x, FL_Coord y, FL_Coord w, FL_Coord h, const char *label, FL_COLOR pointColor) { - OAIgraph_t graph; + int type, FL_Coord x, FL_Coord y, FL_Coord w, FL_Coord h, const char *label, FL_COLOR pointColor) +{ + OAIgraph_t graph = {0}; commonGraph(&graph, type, x, y, w, h, label, pointColor); graph.funct=funct; return graph; @@ -236,7 +237,9 @@ static void genericWaterFall (OAIgraph_t *graph, scopeSample_t *values, const in graph->iteration++; } -static void genericPowerPerAntena(OAIgraph_t *graph, const int nb_ant, const scopeSample_t **data, const int len) { +__attribute__((unused)) +static void genericPowerPerAntena(OAIgraph_t *graph, const int nb_ant, const scopeSample_t **data, const int len) +{ float *values, *time; oai_xygraph_getbuff(graph, &time, &values, len, 0); @@ -263,7 +266,10 @@ static void gNBfreqWaterFall (OAIgraph_t *graph, calibData_t *context) { 0, 0, "X axis:one frame in time"); } -static void timeResponse (OAIgraph_t *graph, calibData_t *context) { + +__attribute__((unused)) +static void timeResponse (OAIgraph_t *graph, calibData_t *context) +{ #if 0 const int len=2*phy_vars_gnb->frame_parms.ofdm_symbol_size; float *values, *time; diff --git a/openair1/PHY/TOOLS/calibration_test.c b/openair1/PHY/TOOLS/calibration_test.c index 1d8e64ee90..4d88ab542a 100644 --- a/openair1/PHY/TOOLS/calibration_test.c +++ b/openair1/PHY/TOOLS/calibration_test.c @@ -314,8 +314,13 @@ int main(int argc, char **argv) { rfdevice.trx_start_func(&rfdevice); while(!oai_exit) { - for (int i=0; i Date: Thu, 19 Dec 2024 22:45:50 +0100 Subject: [PATCH 23/97] CMakeLists.txt: comment out sims that don't compile --- CMakeLists.txt | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index d993a8d09a..9aeb1851d7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1930,14 +1930,14 @@ target_link_libraries(nr-uesoftmodem PRIVATE ##################################### #special case for dlim TM4, which uses its own version of phy_scope code -add_executable(dlsim_tm4 - ${OPENAIR1_DIR}/SIMULATION/LTE_PHY/dlsim_tm4.c - ${OPENAIR1_DIR}/PHY/TOOLS/lte_phy_scope_tm4.c - ) -target_link_libraries (dlsim_tm4 - -Wl,--start-group SIMU UTIL SCHED_LIB SCHED_RU_LIB PHY ITTI -Wl,--end-group - pthread m rt CONFIG_LIB ${T_LIB} - ) +#add_executable(dlsim_tm4 +# ${OPENAIR1_DIR}/SIMULATION/LTE_PHY/dlsim_tm4.c +# ${OPENAIR1_DIR}/PHY/TOOLS/lte_phy_scope_tm4.c +# ) +#target_link_libraries (dlsim_tm4 +# -Wl,--start-group SIMU UTIL SCHED_LIB SCHED_RU_LIB PHY ITTI -Wl,--end-group +# pthread m rt CONFIG_LIB ${T_LIB} +# ) add_executable(rftest ${OPENAIR_DIR}/openair1/PHY/TOOLS/calibration_test.c @@ -2062,7 +2062,9 @@ target_link_libraries(nr_ulsim PRIVATE ) target_link_libraries(nr_ulsim PRIVATE asn1_nr_rrc_hdrs asn1_lte_rrc_hdrs) -foreach(myExe dlsim dlsim_tm7 ulsim pbchsim scansim mbmssim pdcchsim pucchsim prachsim syncsim) +# these simulators do not compile: +# dlsim_tm7 pbchsim scansim mbmssim pdcchsim pucchsim prachsim syncsim +foreach(myExe dlsim ulsim) add_executable(${myExe} ${OPENAIR1_DIR}/SIMULATION/LTE_PHY/${myExe}.c From a31838acd72150f1684b6931b87704f456269d2b Mon Sep 17 00:00:00 2001 From: Robert Schmidt Date: Thu, 19 Dec 2024 22:45:59 +0100 Subject: [PATCH 24/97] CMakeLists.txt: comment out tests that don't compile --- CMakeLists.txt | 33 +++++++++++++++++---------------- 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 9aeb1851d7..66c6fe20b1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2084,22 +2084,23 @@ endforeach(myExe) #unitary tests for Core NEtwork pieces ################################# -foreach(myExe s1ap - secu_knas_encrypt_eia1 - secu_kenb - aes128_ctr - secu_knas_encrypt_eea2 - secu_knas secu_knas_encrypt_eea1 - kdf - aes128_cmac_encrypt - secu_knas_encrypt_eia2) - add_executable(test_${myExe} - ${OPENAIR3_DIR}/TEST/test_${myExe}.c - ) - target_link_libraries (test_${myExe} - -Wl,--start-group SECURITY UTIL -Wl,--end-group m rt CONFIG_LIB - ) -endforeach(myExe) +#foreach(myExe +# s1ap +# secu_knas_encrypt_eia1 +# secu_kenb +# aes128_ctr +# secu_knas_encrypt_eea2 +# secu_knas secu_knas_encrypt_eea1 +# kdf +# aes128_cmac_encrypt +# secu_knas_encrypt_eia2) +# add_executable(test_${myExe} +# ${OPENAIR3_DIR}/TEST/test_${myExe}.c +# ) +# target_link_libraries (test_${myExe} +# -Wl,--start-group SECURITY UTIL -Wl,--end-group m rt CONFIG_LIB +# ) +#endforeach(myExe) #ensure that the T header files are generated before targets depending on them if (${T_TRACER}) From d6c29b2d2c4a17f22a082df56b78de026c67ec9a Mon Sep 17 00:00:00 2001 From: Robert Schmidt Date: Tue, 16 Sep 2025 08:12:23 +0200 Subject: [PATCH 25/97] Fix E2_AGENT linking w.r.t. simulators Link E2_AGENT libraries directly into nr-softmodem/lte-softmodem instead of L2/L2_NR/F1AP. Doing that ensures that simulators, e.g., nr_dlsim/nr_ulsim do still compile. It also simplifies the code. Prior to this, compiling nr_ulsim with E2_AGENT, for instance, resulted in the following errors, fixed in this commit: FAILED: nr_ulsim /usr/bin/ld: openair2/E2AP/RAN_FUNCTION/libe2_ran_func_du_cucp_cuup.a(ran_func_rc.c.o): in function `send_aper_ric_ind': /home/oai/robert/cleanup-t-dependency-cmake/openair2/E2AP/RAN_FUNCTION/O-RAN/ran_func_rc.c:643: (.text+0xee6): undefined reference to `async_event_agent_api' /usr/bin/ld: /home/oai/robert/cleanup-t-dependency-cmake/openair2/E2AP/RAN_FUNCTION/O-RAN/ran_func_rc.c:643:(.text+0x11ca): undefined reference to `async_event_agent_api' /usr/bin/ld: /home/oai/robert/cleanup-t-dependency-cmake/openair2/E2AP/RAN_FUNCTION/O-RAN/ran_func_rc.c:643:(.text+0x1485): undefined reference to `async_event_agent_api' /usr/bin/ld: /home/oai/robert/cleanup-t-dependency-cmake/openair2/E2AP/RAN_FUNCTION/O-RAN/ran_func_rc.c:643:(.text+0x159c): undefined reference to `async_event_agent_api' /usr/bin/ld: /home/oai/robert/cleanup-t-dependency-cmake/openair2/E2AP/RAN_FUNCTION/O-RAN/ran_func_rc.c:643:(.text+0x16b1): undefined reference to `async_event_agent_api' [...] --- CMakeLists.txt | 15 ++------------- 1 file changed, 2 insertions(+), 13 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 66c6fe20b1..9a865728a9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -464,9 +464,6 @@ target_include_directories(f1ap PUBLIC F1AP_DIR) target_link_libraries(f1ap PUBLIC asn1_f1ap GTPV1U) target_link_libraries(f1ap PRIVATE ngap nr_rrc HASHTABLE f1ap_lib) target_include_directories(f1ap PRIVATE ${F1AP_DIR}/lib) -if(E2_AGENT) - target_compile_definitions(f1ap PRIVATE E2_AGENT) -endif() # LPP ############## @@ -1306,12 +1303,6 @@ add_library(L2 target_link_libraries(L2 PRIVATE x2ap s1ap lte_rrc m2ap) target_link_libraries(L2 PRIVATE asn1_lte_rrc_hdrs asn1_nr_rrc_hdrs) -if(E2_AGENT) - target_link_libraries(L2 PUBLIC e2_agent e2_agent_arg e2_ran_func_du_cucp_cuup) - target_compile_definitions(L2 PRIVATE ${E2AP_VERSION} ${KPM_VERSION} E2_AGENT) -endif() - - add_library(MAC_UE_NR ${MAC_NR_SRC_UE}) target_link_libraries(MAC_UE_NR PRIVATE asn1_lte_rrc_hdrs asn1_nr_rrc_hdrs PUBLIC nr_ue_power_procedures nr_ue_ra_procedures) @@ -1338,10 +1329,6 @@ target_link_libraries(L2_NR PRIVATE f1ap x2ap s1ap ngap nr_rrc e1ap nr_rlc nr_co if(OAI_AERIAL) target_compile_definitions(L2_NR PRIVATE ENABLE_AERIAL) endif() -if(E2_AGENT) - target_link_libraries(L2_NR PUBLIC e2_agent e2_agent_arg e2_ran_func_du_cucp_cuup) - target_compile_definitions(L2_NR PRIVATE ${E2AP_VERSION} ${KPM_VERSION} E2_AGENT) -endif() add_library(L2_LTE_NR # temporary solution until 4G/5G code completely untangled (as evidenced by deletion of the following file) @@ -1754,6 +1741,7 @@ target_link_libraries(lte-softmodem PRIVATE asn1_nr_rrc_hdrs asn1_lte_rrc_hdrs) target_link_libraries(lte-softmodem PRIVATE ${blas_LIBRARIES} ${cblas_LIBRARIES} ${lapacke_LIBRARIES} ${lapack_LIBRARIES}) if(E2_AGENT) + target_link_libraries(lte-softmodem PUBLIC e2_agent e2_agent_arg e2_ran_func_du_cucp_cuup) target_compile_definitions(lte-softmodem PRIVATE ${E2AP_VERSION} ${KPM_VERSION} E2_AGENT) endif() @@ -1857,6 +1845,7 @@ if (OAI_AERIAL) target_link_libraries(nr-softmodem PUBLIC aerial_lib) endif() if(E2_AGENT) + target_link_libraries(nr-softmodem PUBLIC e2_agent e2_agent_arg e2_ran_func_du_cucp_cuup) target_compile_definitions(nr-softmodem PRIVATE ${E2AP_VERSION} ${KPM_VERSION} E2_AGENT) endif() From 1a5e2598a3e76beda7bfe5c274cf24e2e693bfa4 Mon Sep 17 00:00:00 2001 From: Robert Schmidt Date: Fri, 12 Sep 2025 12:10:17 +0200 Subject: [PATCH 26/97] Move LTTng check to subdirectory where used --- CMakeLists.txt | 5 ----- common/utils/LOG/CMakeLists.txt | 3 +++ 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 9a865728a9..a72cc93823 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -609,11 +609,6 @@ add_library(UTIL ${OPENAIR_DIR}/common/utils/LOG/vcd_signal_dumper.c ${OPENAIR2_DIR}/UTIL/OPT/probe.c ) -if (ENABLE_LTTNG) - find_package(LTTngUST 2.3.8 EXACT REQUIRED) -else() - message(STATUS "LTTNG support disabled") -endif() pkg_check_modules(cap libcap) if (cap_FOUND) diff --git a/common/utils/LOG/CMakeLists.txt b/common/utils/LOG/CMakeLists.txt index ff224329de..6c4527f760 100644 --- a/common/utils/LOG/CMakeLists.txt +++ b/common/utils/LOG/CMakeLists.txt @@ -4,7 +4,10 @@ target_link_libraries(log_headers INTERFACE T_headers) set(log_sources log.c) if (ENABLE_LTTNG) + find_package(LTTngUST 2.3.8 EXACT REQUIRED) set(log_sources ${log_sources} lttng-tp.c) +else() + message(STATUS "LTTNG support disabled") endif() add_library(LOG ${log_sources}) target_include_directories(LOG PUBLIC .) From a68168805b3837346bd6f7d60d13961233a073d8 Mon Sep 17 00:00:00 2001 From: Robert Schmidt Date: Thu, 19 Dec 2024 23:11:44 +0100 Subject: [PATCH 27/97] Remove NB-IoT configuration Remove, because 1) it does not compile (the asn1c generated code should be prepend with LTE_ etc, nobody did that) 2) a shared module makes no sense 3) the configuration is trivial to recreate --- CMakeLists.txt | 5 - openair2/ENB_APP/NB_IoT_config.c | 252 ------------------ openair2/ENB_APP/NB_IoT_config.h | 37 --- openair2/ENB_APP/NB_IoT_paramdef.h | 405 ----------------------------- 4 files changed, 699 deletions(-) delete mode 100644 openair2/ENB_APP/NB_IoT_config.c delete mode 100644 openair2/ENB_APP/NB_IoT_config.h delete mode 100644 openair2/ENB_APP/NB_IoT_paramdef.h diff --git a/CMakeLists.txt b/CMakeLists.txt index a72cc93823..350b66afa9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1653,12 +1653,7 @@ include_directories(${NAS_SRC}UE/EMM/SAP) include_directories(${NAS_SRC}UE/ESM) include_directories(${NAS_SRC}UE/ESM/SAP) -# nbiot add_definitions("-DNUMBER_OF_UE_MAX_NB_IoT=16") -set (NBIOT_SOURCES - ${OPENAIR2_DIR}/ENB_APP/NB_IoT_config.c - ) -add_library(NB_IoT MODULE ${NBIOT_SOURCES} ) # Simulation library ########################## diff --git a/openair2/ENB_APP/NB_IoT_config.c b/openair2/ENB_APP/NB_IoT_config.c deleted file mode 100644 index e6fe6e5d71..0000000000 --- a/openair2/ENB_APP/NB_IoT_config.c +++ /dev/null @@ -1,252 +0,0 @@ -/* - * Licensed to the OpenAirInterface (OAI) Software Alliance under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The OpenAirInterface Software Alliance licenses this file to You under - * the OAI Public License, Version 1.1 (the "License"); you may not use this file - * except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.openairinterface.org/?page_id=698 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - *------------------------------------------------------------------------------- - * For more information about the OpenAirInterface (OAI) Software Alliance: - * contact@openairinterface.org - */ - -/* - nbiot_config.c - ------------------- - AUTHOR : Francois Taburet - COMPANY : NOKIA - EMAIL : francois.taburet@nokia-bell-labs.com -*/ - -#include -#include - -#include "log.h" -#include "assertions.h" -#include "intertask_interface.h" -#include "s1ap_eNB.h" -#include "sctp_eNB_task.h" -#include "SystemInformationBlockType2.h" - -#include "PHY/phy_extern.h" -#include "radio/ETHERNET/ethernet_lib.h" -#include "common/config/config_userapi.h" -#include "RRC_config_tools.h" -#include "RRC_paramsvalues.h" -#include "NB_IoT_paramdef.h" -#include "L1_paramdef.h" -#include "MACRLC_paramdef.h" -#include "LAYER2/MAC/proto_NB_IoT.h" - - -void RCconfig_NbIoTL1(void) { - paramdef_t NbIoT_L1_Params[] = L1PARAMS_DESC; - paramlist_def_t NbIoT_L1_ParamList = {NBIOT_L1LIST_CONFIG_STRING,NULL,0}; - /* No component carrier for NbIoT, ignore number of CC */ - // NbIoT_L1_Params[L1_CC_IDX ].paramflags = PARAMFLAG_DONOTREAD; - config_getlist(&NbIoT_L1_ParamList, NbIoT_L1_Params, sizeofArray(NbIoT_L1_Params), NULL); - - if (NbIoT_L1_ParamList.numelt > 0) { - if (RC.L1_NB_IoT == NULL) { - RC.L1_NB_IoT = (PHY_VARS_eNB_NB_IoT **)malloc(RC.nb_nb_iot_L1_inst*sizeof(PHY_VARS_eNB_NB_IoT *)); - LOG_I(PHY,"RC.L1_NB_IoT = %p\n",RC.L1_NB_IoT); - memset(RC.L1_NB_IoT,0,RC.nb_nb_iot_L1_inst*sizeof(PHY_VARS_eNB_NB_IoT *)); - } - - for(int j = 0; j eth_params_n.local_if_name = strdup(*(NbIoT_L1_ParamList.paramarray[j][L1_LOCAL_N_IF_NAME_IDX].strptr)); - RC.L1_NB_IoT[j]->eth_params_n.my_addr = strdup(*(NbIoT_L1_ParamList.paramarray[j][L1_LOCAL_N_ADDRESS_IDX].strptr)); - RC.L1_NB_IoT[j]->eth_params_n.remote_addr = strdup(*(NbIoT_L1_ParamList.paramarray[j][L1_REMOTE_N_ADDRESS_IDX].strptr)); - RC.L1_NB_IoT[j]->eth_params_n.my_portc = *(NbIoT_L1_ParamList.paramarray[j][L1_LOCAL_N_PORTC_IDX].iptr); - RC.L1_NB_IoT[j]->eth_params_n.remote_portc = *(NbIoT_L1_ParamList.paramarray[j][L1_REMOTE_N_PORTC_IDX].iptr); - RC.L1_NB_IoT[j]->eth_params_n.my_portd = *(NbIoT_L1_ParamList.paramarray[j][L1_LOCAL_N_PORTD_IDX].iptr); - RC.L1_NB_IoT[j]->eth_params_n.remote_portd = *(NbIoT_L1_ParamList.paramarray[j][L1_REMOTE_N_PORTD_IDX].iptr); - RC.L1_NB_IoT[j]->eth_params_n.transp_preference = ETH_UDP_MODE; - } else { // other midhaul - } - }// j=0..num_inst - - printf("Initializing northbound interface for NB-IoT L1\n"); - l1_north_init_NB_IoT(); - } else { - LOG_I(PHY,"No " NBIOT_L1LIST_CONFIG_STRING " configuration found"); - } -} - -void RCconfig_NbIoTmacrlc(void) { - paramdef_t NbIoT_MacRLC_Params[] = MACRLCPARAMS_DESC; - paramlist_def_t NbIoT_MacRLC_ParamList = {NBIOT_MACRLCLIST_CONFIG_STRING,NULL,0}; - /* No component carrier for NbIoT, ignore number of CC */ - // NbIoT_MacRLC_Params[MACRLC_CC_IDX ].paramflags = PARAMFLAG_DONOTREAD; - config_getlist(&NbIoT_MacRLC_ParamList, NbIoT_MacRLC_Params, sizeofArray(NbIoT_MacRLC_Params), NULL); - - if ( NbIoT_MacRLC_ParamList.numelt > 0) { - mac_top_init_eNB_NB_IoT(); - - for (int j=0; jeth_params_n.local_if_name = strdup(*(NbIoT_MacRLC_ParamList.paramarray[j][MACRLC_LOCAL_N_IF_NAME_IDX].strptr)); - RC.nb_iot_mac[j]->eth_params_n.my_addr = strdup(*(NbIoT_MacRLC_ParamList.paramarray[j][MACRLC_LOCAL_N_ADDRESS_IDX].strptr)); - RC.nb_iot_mac[j]->eth_params_n.remote_addr = strdup(*(NbIoT_MacRLC_ParamList.paramarray[j][MACRLC_REMOTE_N_ADDRESS_IDX].strptr)); - RC.nb_iot_mac[j]->eth_params_n.my_portc = *(NbIoT_MacRLC_ParamList.paramarray[j][MACRLC_LOCAL_N_PORTC_IDX].iptr); - RC.nb_iot_mac[j]->eth_params_n.remote_portc = *(NbIoT_MacRLC_ParamList.paramarray[j][MACRLC_REMOTE_N_PORTC_IDX].iptr); - RC.nb_iot_mac[j]->eth_params_n.my_portd = *(NbIoT_MacRLC_ParamList.paramarray[j][MACRLC_LOCAL_N_PORTD_IDX].iptr); - RC.nb_iot_mac[j]->eth_params_n.remote_portd = *(NbIoT_MacRLC_ParamList.paramarray[j][MACRLC_REMOTE_N_PORTD_IDX].iptr);; - RC.nb_iot_mac[j]->eth_params_n.transp_preference = ETH_UDP_MODE; - } else { // other midhaul - AssertFatal(1==0,"MACRLC %d: %s unknown northbound midhaul\n",j, *(NbIoT_MacRLC_ParamList.paramarray[j][MACRLC_TRANSPORT_N_PREFERENCE_IDX].strptr)); - } - - if (strcmp(*(NbIoT_MacRLC_ParamList.paramarray[j][MACRLC_TRANSPORT_S_PREFERENCE_IDX].strptr), "local_L1") == 0) { - } else if (strcmp(*(NbIoT_MacRLC_ParamList.paramarray[j][MACRLC_TRANSPORT_S_PREFERENCE_IDX].strptr), "nfapi") == 0) { - RC.nb_iot_mac[j]->eth_params_s.local_if_name = strdup(*(NbIoT_MacRLC_ParamList.paramarray[j][MACRLC_LOCAL_S_IF_NAME_IDX].strptr)); - RC.nb_iot_mac[j]->eth_params_s.my_addr = strdup(*(NbIoT_MacRLC_ParamList.paramarray[j][MACRLC_LOCAL_S_ADDRESS_IDX].strptr)); - RC.nb_iot_mac[j]->eth_params_s.remote_addr = strdup(*(NbIoT_MacRLC_ParamList.paramarray[j][MACRLC_REMOTE_S_ADDRESS_IDX].strptr)); - RC.nb_iot_mac[j]->eth_params_s.my_portc = *(NbIoT_MacRLC_ParamList.paramarray[j][MACRLC_LOCAL_S_PORTC_IDX].iptr); - RC.nb_iot_mac[j]->eth_params_s.remote_portc = *(NbIoT_MacRLC_ParamList.paramarray[j][MACRLC_REMOTE_S_PORTC_IDX].iptr); - RC.nb_iot_mac[j]->eth_params_s.my_portd = *(NbIoT_MacRLC_ParamList.paramarray[j][MACRLC_LOCAL_S_PORTD_IDX].iptr); - RC.nb_iot_mac[j]->eth_params_s.remote_portd = *(NbIoT_MacRLC_ParamList.paramarray[j][MACRLC_REMOTE_S_PORTD_IDX].iptr); - RC.nb_iot_mac[j]->eth_params_s.transp_preference = ETH_UDP_MODE; - } else { // other midhaul - AssertFatal(1==0,"MACRLC %d: %s unknown southbound midhaul\n",j,*(NbIoT_MacRLC_ParamList.paramarray[j][MACRLC_TRANSPORT_S_PREFERENCE_IDX].strptr)); - } - }// j=0..num_inst */ - } else {// MacRLC_ParamList.numelt > 0 - AssertFatal (0, - "No " NBIOT_MACRLCLIST_CONFIG_STRING " configuration found"); - } -} - - - - -int RCconfig_NbIoTRRC(MessageDef *msg_p, int nbiotrrc_id,eNB_RRC_INST_NB_IoT *nbiotrrc) { - char instprefix[MAX_OPTNAME_SIZE*3 + 32]; - checkedparam_t NBIoTCheckParams[] = NBIOT_RRCPARAMS_CHECK_DESC_0_14; - checkedparam_t NBIoTCheckParamsB[] = NBIOT_RRCPARAMS_CHECK_DESC_15_end; - paramdef_t NBIoTParams[] = NBIOTRRCPARAMS_DESC; - paramdef_t NBIoTPrachParams[] = NBIOTRRC_NPRACH_PARAMS_DESC; - checkedparam_t NBIoTPrachCheckParams[] = NBIOT_RRCLIST_NPRACHPARAMSCHECK_DESC; - paramdef_t NBIoTRRCRefParams[] = NBIOTRRCPARAMS_RRCREF_DESC; - paramdef_t NBIoTLteCCParams[] = NBIOT_LTECCPARAMS_DESC; - checkedparam_t NBIoTLteCCCheckParams[] = NBIOT_LTECCPARAMS_CHECK_DESC; - - /* map parameter checking array instances to parameter definition array instances */ - for (int i = 0; i < sizeofArray(NBIoTParams) && i < sizeofArray(NBIoTCheckParams); i++) { - NBIoTParams[i].chkPptr = &(NBIoTCheckParams[i]); - } - - for (int i = 0; i < sizeofArray(NBIoTParams) && i < sizeofArray(NBIoTCheckParamsB); i++) { - NBIoTParams[i + 15].chkPptr = &(NBIoTCheckParamsB[i]); - } - - for (int i = 0; i < sizeofArray(NBIoTPrachParams) && i < sizeofArray(NBIoTPrachCheckParams); i++) { - NBIoTPrachParams[i].chkPptr = &(NBIoTPrachCheckParams[i]); - } - - /* brut force itti message fields assignment, to be redesigned with itti replacement */ - NBIoTParams[NBIOT_RACH_RARESPONSEWINDOWSIZE_NB_IDX].uptr = (uint32_t *)&(NBIOTRRC_CONFIGURATION_REQ (msg_p).rach_raResponseWindowSize_NB); - NBIoTParams[NBIOT_RACH_MACCONTENTIONRESOLUTIONTIMER_NB_IDX].uptr = (uint32_t *)&(NBIOTRRC_CONFIGURATION_REQ (msg_p).rach_macContentionResolutionTimer_NB); - NBIoTParams[NBIOT_RACH_POWERRAMPINGSTEP_NB_IDX].uptr = (uint32_t *)&(NBIOTRRC_CONFIGURATION_REQ (msg_p).rach_powerRampingStep_NB); - NBIoTParams[NBIOT_RACH_PREAMBLEINITIALRECEIVEDTARGETPOWER_NB_IDX].uptr = (uint32_t *)&(NBIOTRRC_CONFIGURATION_REQ (msg_p).rach_preambleInitialReceivedTargetPower_NB); - NBIoTParams[NBIOT_RACH_PREAMBLETRANSMAX_CE_NB_IDX].uptr = (uint32_t *)&(NBIOTRRC_CONFIGURATION_REQ (msg_p).rach_preambleTransMax_CE_NB); - NBIoTParams[NBIOT_BCCH_MODIFICATIONPERIODCOEFF_NB_IDX].uptr = (uint32_t *)&(NBIOTRRC_CONFIGURATION_REQ (msg_p).bcch_modificationPeriodCoeff_NB); - NBIoTParams[NBIOT_PCCH_DEFAULTPAGINGCYCLE_NB_IDX].uptr = (uint32_t *)&(NBIOTRRC_CONFIGURATION_REQ (msg_p).pcch_defaultPagingCycle_NB); - NBIoTParams[NBIOT_NPRACH_CP_LENGTH_IDX].uptr = (uint32_t *)&(NBIOTRRC_CONFIGURATION_REQ (msg_p).nprach_CP_Length); - NBIoTParams[NBIOT_NPRACH_RSRP_RANGE_IDX].uptr = (uint32_t *)&(NBIOTRRC_CONFIGURATION_REQ (msg_p).nprach_rsrp_range); - NBIoTParams[NBIOT_MAXNUMPREAMBLEATTEMPTCE_NB_IDX].uptr = (uint32_t *)&(NBIOTRRC_CONFIGURATION_REQ (msg_p).maxNumPreambleAttemptCE_NB); - NBIoTParams[NBIOT_NPDSCH_NRS_POWER_IDX].uptr = (uint32_t *)&(NBIOTRRC_CONFIGURATION_REQ (msg_p).npdsch_nrs_Power); - NBIoTParams[NBIOT_NPUSCH_ACK_NACK_NUMREPETITIONS_NB_IDX].uptr = (uint32_t *)&(NBIOTRRC_CONFIGURATION_REQ (msg_p). npusch_ack_nack_numRepetitions_NB); - NBIoTParams[NBIOT_NPUSCH_SRS_SUBFRAMECONFIG_NB_IDX].uptr = (uint32_t *)&(NBIOTRRC_CONFIGURATION_REQ (msg_p). npusch_srs_SubframeConfig_NB); - NBIoTParams[NBIOT_NPUSCH_THREETONE_CYCLICSHIFT_R13_IDX].uptr = (uint32_t *)&(NBIOTRRC_CONFIGURATION_REQ (msg_p).npusch_threeTone_CyclicShift_r13); - NBIoTParams[NBIOT_NPUSCH_SIXTONE_CYCLICSHIFT_R13_IDX].uptr = (uint32_t *)&(NBIOTRRC_CONFIGURATION_REQ (msg_p).npusch_sixTone_CyclicShift_r13); - NBIoTParams[NBIOT_NPUSCH_GROUPASSIGNMENTNPUSCH_R13_IDX].uptr = (uint32_t *)&(NBIOTRRC_CONFIGURATION_REQ (msg_p).npusch_groupAssignmentNPUSCH_r13); - NBIoTParams[NBIOT_DL_GAPTHRESHOLD_NB_IDX].uptr = (uint32_t *)&(NBIOTRRC_CONFIGURATION_REQ (msg_p).dl_GapThreshold_NB); - NBIoTParams[NBIOT_DL_GAPPERIODICITY_NB_IDX].uptr = (uint32_t *)&(NBIOTRRC_CONFIGURATION_REQ (msg_p).dl_GapPeriodicity_NB); - NBIoTParams[NBIOT_NPUSCH_P0_NOMINALNPUSCH_IDX].uptr = (uint32_t *)&(NBIOTRRC_CONFIGURATION_REQ (msg_p).npusch_p0_NominalNPUSCH); - NBIoTParams[NBIOT_DELTAPREAMBLEMSG3_IDX].uptr = (uint32_t *)&(NBIOTRRC_CONFIGURATION_REQ (msg_p).deltaPreambleMsg3); - NBIoTParams[NBIOT_UE_TIMERSANDCONSTANTS_T300_NB_IDX].uptr = (uint32_t *)&(NBIOTRRC_CONFIGURATION_REQ (msg_p).ue_TimersAndConstants_t300_NB); - NBIoTParams[NBIOT_UE_TIMERSANDCONSTANTS_T301_NB_IDX].uptr = (uint32_t *)&(NBIOTRRC_CONFIGURATION_REQ (msg_p).ue_TimersAndConstants_t301_NB); - NBIoTParams[NBIOT_UE_TIMERSANDCONSTANTS_T310_NB_IDX].uptr = (uint32_t *)&(NBIOTRRC_CONFIGURATION_REQ (msg_p).ue_TimersAndConstants_t310_NB); - NBIoTParams[NBIOT_UE_TIMERSANDCONSTANTS_T311_NB_IDX].uptr = (uint32_t *)&(NBIOTRRC_CONFIGURATION_REQ (msg_p).ue_TimersAndConstants_t311_NB); - NBIoTParams[NBIOT_UE_TIMERSANDCONSTANTS_N310_NB_IDX].uptr = (uint32_t *)&(NBIOTRRC_CONFIGURATION_REQ (msg_p).ue_TimersAndConstants_n310_NB); - NBIoTParams[NBIOT_UE_TIMERSANDCONSTANTS_N311_NB_IDX].uptr = (uint32_t *)&(NBIOTRRC_CONFIGURATION_REQ (msg_p).ue_TimersAndConstants_n311_NB); - sprintf(instprefix, NBIOT_RRCLIST_CONFIG_STRING ".[%i]",nbiotrrc_id); - config_get( NBIoTParams,sizeofArray(NBIoTParams),instprefix); - NBIOTRRC_CONFIGURATION_REQ (msg_p).nprach_SubcarrierMSG3_RangeStart = config_get_processedint(config_get_if(), &(NBIoTParams[NBIOT_NPRACH_SUBCARRIERMSG3_RANGESTART_IDX]) ); - NBIOTRRC_CONFIGURATION_REQ (msg_p).npusch_groupHoppingEnabled = config_get_processedint(config_get_if(), &(NBIoTParams[NBIOT_NPUSCH_GROUPHOPPINGENABLED_IDX] ) ); - NBIOTRRC_CONFIGURATION_REQ (msg_p).dl_GapDurationCoeff_NB = config_get_processedint(config_get_if(), &(NBIoTParams[NBIOT_DL_GAPDURATIONCOEFF_NB_IDX] ) ); - NBIOTRRC_CONFIGURATION_REQ (msg_p).npusch_alpha = config_get_processedint(config_get_if(), &(NBIoTParams[NBIOT_NPUSCH_ALPHA_IDX] ) ); - - for (int i=0; inb_nb_iot_rrc_inst = NbIoT_ParamList.numelt; - config_getlist( &NbIoT_MACRLCParamList,NULL,0, NULL); - RC->nb_nb_iot_macrlc_inst = NbIoT_MACRLCParamList.numelt; - // Get num L1 instances - config_getlist( &NbIoT_L1ParamList,NULL,0, NULL); - RC->nb_nb_iot_L1_inst = NbIoT_L1ParamList.numelt; -} diff --git a/openair2/ENB_APP/NB_IoT_config.h b/openair2/ENB_APP/NB_IoT_config.h deleted file mode 100644 index 314fca8180..0000000000 --- a/openair2/ENB_APP/NB_IoT_config.h +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Licensed to the OpenAirInterface (OAI) Software Alliance under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The OpenAirInterface Software Alliance licenses this file to You under - * the OAI Public License, Version 1.1 (the "License"); you may not use this file - * except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.openairinterface.org/?page_id=698 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - *------------------------------------------------------------------------------- - * For more information about the OpenAirInterface (OAI) Software Alliance: - * contact@openairinterface.org - */ - -/* - nbiot_config.h - ------------------- - AUTHOR : Francois Taburet - COMPANY : NOKIA - EMAIL : francois.taburet@nokia-bell-labs.com -*/ -#ifndef INCLUDE_NBIOT_CONFIG_H -#define INCLUDE_NBIOT_CONFIG_H - - -extern void RCconfig_NbIoTL1(void) ; -extern void RCconfig_NbIoTmacrlc(void); -extern int RCconfig_NbIoTRRC(MessageDef *msg_p, int nbiotrrc_id,eNB_RRC_INST_NB_IoT *nbiotrrc); -extern void RCConfig_NbIoT(RAN_CONTEXT_t *RC); -#endif diff --git a/openair2/ENB_APP/NB_IoT_paramdef.h b/openair2/ENB_APP/NB_IoT_paramdef.h deleted file mode 100644 index 8778f6d2e4..0000000000 --- a/openair2/ENB_APP/NB_IoT_paramdef.h +++ /dev/null @@ -1,405 +0,0 @@ -/* - * Licensed to the OpenAirInterface (OAI) Software Alliance under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The OpenAirInterface Software Alliance licenses this file to You under - * the OAI Public License, Version 1.1 (the "License"); you may not use this file - * except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.openairinterface.org/?page_id=698 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - *------------------------------------------------------------------------------- - * For more information about the OpenAirInterface (OAI) Software Alliance: - * contact@openairinterface.org - */ - -/*! \file openair2/ENB_APP/enb_paramdef.f - * \brief definition of configuration parameters for all eNodeB modules - * \author Francois TABURET - * \date 2017 - * \version 0.1 - * \company NOKIA BellLabs France - * \email: francois.taburet@nokia-bell-labs.com - * \note - * \warning - */ - -#ifndef __ENB_APP_NB_IOT_PARAMDEF__H__ -#define __ENB_APP_NB_IOT_PARAMDEF__H__ - -#include "common/config/config_paramdesc.h" -#include "SystemInformationBlockType2.h" -#include "DL-GapConfig-NB-r13.h" -#include "NPRACH-Parameters-NB-r13.h" -#include "PowerRampingParameters.h" -#include "BCCH-Config-NB-r13.h" -#include "PCCH-Config-NB-r13.h" -#include "ACK-NACK-NumRepetitions-NB-r13.h" -#include "TDD-Config.h" - - - - - -/* - int16_t eutra_band; - uint32_t downlink_frequency; - int32_t uplink_frequency_offset; - int16_t Nid_cell;// for testing, change later - int16_t N_RB_DL;// for testing, change later -*/ -/*-------------------------------------------------------------------------------------------------------------------*/ -/* SIB1 parameters possibly coming from LTE RRC (in band deployment) */ -/*---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------*/ -/* component carriers configuration parameters */ -/* optname helpstr paramflags XXXptr defXXXval type numelt checked_param */ -/*---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------*/ -#define NBIOT_LTECCPARAMS_CHECK_DESC { \ - { .s3a= { config_checkstr_assign_integer, FRAMETYPE_OKVALUES, FRAMETYPE_MODVALUES,2}} , \ - { .s2= { config_check_intrange, TDDCONFIG_OKRANGE}}, \ - { .s2= { config_check_intrange, TDDCONFIGS_OKRANGE}}, \ - { .s3a= { config_checkstr_assign_integer, PREFIX_OKVALUES, PREFIX_MODVALUES,2}} , \ - { .s3a= { config_checkstr_assign_integer, PREFIXUL_OKVALUES, PREFIXUL_MODVALUES,2}} , \ - { .s5= {NULL }} , \ - { .s5= {NULL }} , \ - { .s5= {NULL }} , \ - { .s5= {NULL }} , \ - { .s1= { config_check_intval, NRBDL_OKVALUES,6}} , \ -} - - -#define NBIOT_LTECCPARAMS_DESC { \ -{ENB_CONFIG_STRING_FRAME_TYPE, NULL, 0, strptr:NULL, defstrval:"FDD", TYPE_STRING, 0}, \ -{ENB_CONFIG_STRING_TDD_CONFIG, NULL, 0, iptr:NULL, defintval:3, TYPE_UINT, 0}, \ -{ENB_CONFIG_STRING_TDD_CONFIG_S, NULL, 0, iptr:NULL, defintval:0, TYPE_UINT, 0}, \ -{ENB_CONFIG_STRING_PREFIX_TYPE, NULL, 0, strptr:NULL, defstrval:"NORMAL", TYPE_STRING, 0}, \ -{ENB_CONFIG_STRING_PREFIX_TYPE_UL, NULL, 0, strptr:NULL, defstrval:"NORMAL", TYPE_STRING, 0}, \ -{ENB_CONFIG_STRING_EUTRA_BAND, NULL, 0, iptr:NULL, defintval:7, TYPE_UINT, 0}, \ -{ENB_CONFIG_STRING_DOWNLINK_FREQUENCY, NULL, 0, i64ptr:NULL, defint64val:2680000000, TYPE_UINT64, 0}, \ -{ENB_CONFIG_STRING_UPLINK_FREQUENCY_OFFSET, NULL, 0, iptr:NULL, defintval:-120000000, TYPE_INT, 0}, \ -{ENB_CONFIG_STRING_NID_CELL, NULL, 0, iptr:NULL, defintval:0, TYPE_UINT, 0}, \ -{ENB_CONFIG_STRING_N_RB_DL, NULL, 0, iptr:NULL, defintval:25, TYPE_UINT, 0}, \ -} -#define LTECCPARAMS_FRAME_TYPE_IDX 0 -#define LTECCPARAMS_TDD_CONFIG_IDX 1 -#define LTECCPARAMS_TDD_CONFIG_S_IDX 2 -#define LTECCPARAMS_PREFIX_TYPE_IDX 3 -#define LTECCPARAMS_PREFIX_TYPE_UL_IDX 4 -#define LTECCPARAMS_EUTRA_BAND_IDX 5 -#define LTECCPARAMS_DOWNLINK_FREQUENCY_IDX 6 -#define LTECCPARAMS_UPLINK_FREQUENCY_OFFSET_IDX 7 -#define LTECCPARAMS_NID_CELL_IDX 8 -#define LTECCPARAMS_N_RB_DL_IDX 9 - - -/*-------------------------------------------------------------------------------------------------------------------*/ - -/* NB-Iot RRC list section name */ -#define NBIOT_RRCLIST_CONFIG_STRING "NB-IoT_RRCs" - - -#define RACH_RARESPONSEWINDOWSIZE_NB_OKVALUES {20,50,80,120,180,240,320,400} -#define PREF1(A) RACH_CE_LevelInfo_r13__ra_ResponseWindowSize_r13_ ## A -#define RACH_RARESPONSEWINDOWSIZE_NB_MODVALUES { PREF1(sf20),PREF1(sf50),PREF1(sf80),PREF1(sf120), \ - PREF1(sf180),PREF1(sf240),PREF1(sf320),PREF1(sf400) } - - -#define RACH_MACCONTENTIONRESOLUTIONTIMER_NB_OKVALUES {80,100,120,160,200,240,480,960} -#define PREF2(A) RACH_CE_LevelInfo_r13__mac_ContentionResolutionTimer_r13_ ## A -#define RACH_MACCONTENTIONRESOLUTIONTIMER_NB_MODVALUES { PREF2(sf80),PREF2(sf100),PREF2(sf120),PREF2(sf160), \ - PREF2(sf200),PREF2(sf240),PREF2(sf480),PREF2(sf960) } - - -#define RACH_POWERRAMPINGSTEP_NB_OKVALUES {0,2,4,6} -#define PREF3(A) PowerRampingParameters__powerRampingStep_ ## A -#define RACH_POWERRAMPINGSTEP_NB_MODVALUES { PREF3(dB0),PREF3(dB2),PREF3(dB4),PREF3(dB6) } - - -#define RACH_PREAMBLEINITIALRECEIVEDTARGETPOWER_NB_OKRANGE {-120, -90} - -#define RACH_PREAMBLETRANSMAX_CE_NB_OKVALUES {3,4,5,6,7,8,10,20,50,100,200} -#define PREF4(A) PreambleTransMax_ ## A -#define RACH_PREAMBLETRANSMAX_CE_NB_MODVALUES { PREF4(n3), PREF4(n4), PREF4(n5), PREF4(n6), PREF4(n7), PREF4(n8), \ - PREF4(n10),PREF4(n20),PREF4(n50),PREF4(n100),PREF4(n200) } - -#define BCCH_MODIFICATIONPERIODCOEFF_NB_OKVALUES {16,32,64,128} -#define PREF5(A) BCCH_Config_NB_r13__modificationPeriodCoeff_r13_ ## A -#define BCCH_MODIFICATIONPERIODCOEFF_NB_MODVALUES { PREF5(n16), PREF5(n32), PREF5(n64),PREF5(n128) } - -#define PCCH_DEFAULTPAGINGCYCLE_NB_OKVALUES {128,256,512,1024} -#define PREF6(A) PCCH_Config_NB_r13__defaultPagingCycle_r13_ ## A -#define PCCH_DEFAULTPAGINGCYCLE_NB_MODVALUES { PREF6(rf128), PREF6(rf256), PREF6(rf512), PREF6(rf1024) } - -#define NPRACH_CP_LENGTH_OKVALUES {0,1} -#define NPRACH_RSRP_RANGE_OKVALUES {0,96} - -#define MSG3RANGESTART_OKVALUES {"zero","oneThird","twoThird","one"} -#define MSG3RANGESTART_MODVALUES {NPRACH_Parameters_NB_r13__nprach_SubcarrierMSG3_RangeStart_r13_zero, NPRACH_Parameters_NB_r13__nprach_SubcarrierMSG3_RangeStart_r13_oneThird, \ - NPRACH_Parameters_NB_r13__nprach_SubcarrierMSG3_RangeStart_r13_twoThird, NPRACH_Parameters_NB_r13__nprach_SubcarrierMSG3_RangeStart_r13_one} - - -#define MAXNUMPREAMBLEATTEMPTCE_OKVALUES {3,4,5,6,7,8,10} -#define MAXNUMPREAMBLEATTEMPTCE_MODVALUES { NPRACH_Parameters_NB_r13__maxNumPreambleAttemptCE_r13_n3, NPRACH_Parameters_NB_r13__maxNumPreambleAttemptCE_r13_n4, \ - NPRACH_Parameters_NB_r13__maxNumPreambleAttemptCE_r13_n5, NPRACH_Parameters_NB_r13__maxNumPreambleAttemptCE_r13_n6, \ - NPRACH_Parameters_NB_r13__maxNumPreambleAttemptCE_r13_n7, NPRACH_Parameters_NB_r13__maxNumPreambleAttemptCE_r13_n8, \ - NPRACH_Parameters_NB_r13__maxNumPreambleAttemptCE_r13_n10 } - -#define NPDSCH_NRS_POWER_OKRANGE {-60,50} - -#define NPUSCH_ACK_NACK_NUMREPETITIONS_NB_OKVALUES {1,2,4,8,16,32,64,128} -#define PREF9(A) ACK_NACK_NumRepetitions_NB_r13_ ## A -#define NPUSCH_ACK_NACK_NUMREPETITIONS_NB_MODVALUES { PREF9(r1), PREF9(r2), PREF9(r4), PREF9(r8), \ - PREF9(r16), PREF9(r32), PREF9(r64), PREF9(r128) } - -#define NPUSCH_SRS_SUBFRAMECONFIG_NB_OKRANGE {0,15} -#define NPUSCH_THREETONE_CYCLICSHIFT_R13_OKRANGE {0,2} -#define NPUSCH_SIXTONE_CYCLICSHIFT_R13_OKRANGE {0,3} - -#define NPUSCH_GROUPHOPPINGENABLED_OKVALUES {"enable","disable"} -#define NPUSCH_GROUPHOPPINGENABLED_MODVALUES {1,0} - -#define NPUSCH_GROUPASSIGNMENTNPUSCH_R13_OKRANGE {0,29} - -#define DLGAPTHRESHOLD_OKVALUES {32,64,128,256} -#define DLGAPTHRESHOLD_MODVALUES { DL_GapConfig_NB_r13__dl_GapThreshold_r13_n32, DL_GapConfig_NB_r13__dl_GapThreshold_r13_n64, \ - DL_GapConfig_NB_r13__dl_GapThreshold_r13_n128, DL_GapConfig_NB_r13__dl_GapThreshold_r13_n256} \ - -#define DLGAPPERIODICITY_OKVALUES {64,128,256,512} -#define DLGAPPERIODICITY_MODVALUES { DL_GapConfig_NB_r13__dl_GapPeriodicity_r13_sf64, DL_GapConfig_NB_r13__dl_GapPeriodicity_r13_sf128, \ - DL_GapConfig_NB_r13__dl_GapPeriodicity_r13_sf256,DL_GapConfig_NB_r13__dl_GapPeriodicity_r13_sf512} - -#define DLGAPDURATION_OKVALUES {"oneEighth","oneFourth","threeEighth","oneHalf"} -#define DLGAPDURATION_MODVALUES {DL_GapConfig_NB_r13__dl_GapDurationCoeff_r13_oneEighth, DL_GapConfig_NB_r13__dl_GapDurationCoeff_r13_oneFourth, \ - DL_GapConfig_NB_r13__dl_GapDurationCoeff_r13_threeEighth, DL_GapConfig_NB_r13__dl_GapDurationCoeff_r13_oneHalf} - -#define NPUSCH_P0_NOMINALNPUSCH_OKRANGE {-126,24} - -#define NPUSCH_ALPHA_OKVALUES {"AL0","AL04","AL05","AL06","AL07","AL08","AL09","AL1"} -#define NPUSCH_ALPHA_MODVALUES { Alpha_r12_al0, Alpha_r12_al04, Alpha_r12_al05, Alpha_r12_al06, \ - Alpha_r12_al07, Alpha_r12_al08, Alpha_r12_al09, Alpha_r12_al1} - -#define DELTAPREAMBLEMSG3_OKRANGE {-1,6} - - - - - -#define NBIOT_RRCPARAMS_CHECK_DESC { \ - { .s1a= { config_check_modify_integer, RACH_RARESPONSEWINDOWSIZE_NB_OKVALUES, RACH_RARESPONSEWINDOWSIZE_NB_MODVALUES, 8}}, \ - { .s1a= { config_check_modify_integer, RACH_MACCONTENTIONRESOLUTIONTIMER_NB_OKVALUES, RACH_MACCONTENTIONRESOLUTIONTIMER_NB_MODVALUES, 8}}, \ - { .s1a= { config_check_modify_integer, RACH_POWERRAMPINGSTEP_NB_OKVALUES, RACH_POWERRAMPINGSTEP_NB_MODVALUES, 4}} , \ - { .s2= { config_check_intrange, RACH_PREAMBLEINITIALRECEIVEDTARGETPOWER_NB_OKRANGE}}, \ - { .s1a= { config_check_modify_integer, RACH_PREAMBLETRANSMAX_CE_NB_OKVALUES, RACH_PREAMBLETRANSMAX_CE_NB_MODVALUES, 11}} , \ - { .s1a= { config_check_modify_integer, BCCH_MODIFICATIONPERIODCOEFF_NB_OKVALUES, BCCH_MODIFICATIONPERIODCOEFF_NB_MODVALUES, 4}} , \ - { .s1a= { config_check_modify_integer, PCCH_DEFAULTPAGINGCYCLE_NB_OKVALUES, PCCH_DEFAULTPAGINGCYCLE_NB_MODVALUES, 4}} , \ - { .s1= { NULL, NPRACH_CP_LENGTH_OKVALUES ,4}} , \ - { .s2= { config_check_intrange, NPRACH_RSRP_RANGE_OKVALUES}} , \ - { .s3a= { config_checkstr_assign_integer, MSG3RANGESTART_OKVALUES, MSG3RANGESTART_MODVALUES, 4}} , \ - { .s1a= { config_check_modify_integer, MAXNUMPREAMBLEATTEMPTCE_OKVALUES, MAXNUMPREAMBLEATTEMPTCE_MODVALUES, 7}} , \ - { .s1= { config_check_intval, NPDSCH_NRS_POWER_OKRANGE,4}} , \ - { .s1a= { config_check_modify_integer, NPUSCH_ACK_NACK_NUMREPETITIONS_NB_OKVALUES, NPUSCH_ACK_NACK_NUMREPETITIONS_NB_MODVALUES, 8}} , \ - { .s2= { config_check_intrange, NPUSCH_SRS_SUBFRAMECONFIG_NB_OKRANGE}} , \ - { .s2= { config_check_intrange, NPUSCH_THREETONE_CYCLICSHIFT_R13_OKRANGE}} , \ - { .s2= { config_check_intrange, NPUSCH_SIXTONE_CYCLICSHIFT_R13_OKRANGE}} , \ - { .s3a= { config_checkstr_assign_integer, NPUSCH_GROUPHOPPINGENABLED_OKVALUES, NPUSCH_GROUPHOPPINGENABLED_MODVALUES, 2}} , \ - { .s2= { config_check_intrange, NPUSCH_GROUPASSIGNMENTNPUSCH_R13_OKRANGE}} , \ - { .s1a= { config_check_modify_integer, DLGAPTHRESHOLD_OKVALUES, DLGAPTHRESHOLD_MODVALUES, 4}} , \ - { .s1a= { config_check_modify_integer, DLGAPPERIODICITY_OKVALUES, DLGAPPERIODICITY_MODVALUES, 4}} , \ - { .s3a= { config_checkstr_assign_integer, DLGAPDURATION_OKVALUES, DLGAPDURATION_MODVALUES , 4}} , \ - { .s2= { config_check_intrange, NPUSCH_P0_NOMINALNPUSCH_OKRANGE}} , \ - { .s3a= { config_checkstr_assign_integer, NPUSCH_ALPHA_OKVALUES, NPUSCH_ALPHA_MODVALUES, 8}} , \ - { .s2= { config_check_intrange, DELTAPREAMBLEMSG3_OKRANGE}} , \ - { .s1a= { config_check_modify_integer, UETIMER_T300_OKVALUES, UETIMER_T300_MODVALUES,8}} , \ - { .s1a= { config_check_modify_integer, UETIMER_T301_OKVALUES, UETIMER_T301_MODVALUES,8}} , \ - { .s1a= { config_check_modify_integer, UETIMER_T310_OKVALUES, UETIMER_T310_MODVALUES,7}} , \ - { .s1a= { config_check_modify_integer, UETIMER_T311_OKVALUES, UETIMER_T311_MODVALUES,7}} , \ - { .s1a= { config_check_modify_integer, UETIMER_N310_OKVALUES, UETIMER_N310_MODVALUES,8}} , \ - { .s1a= { config_check_modify_integer, UETIMER_N311_OKVALUES, UETIMER_N311_MODVALUES,8}} , \ -} -/*-----------------------------------------------------------------------------------------------------------------------------------------*/ -/* NB-IoT RRC configuration parameters */ -/* optname helpstr paramflags XXXptr defXXXval type numelt */ -/*-----------------------------------------------------------------------------------------------------------------------------------------*/ -#define NBIOTRRCPARAMS_DESC { \ -{"rach_raResponseWindowSize_NB", NULL, 0, uptr:NULL, defintval:20, TYPE_UINT, 0}, \ -{"rach_macContentionResolutionTimer_NB", NULL, 0, uptr:NULL, defintval:80, TYPE_UINT, 0}, \ -{"rach_powerRampingStep_NB", NULL, 0, uptr:NULL, defintval:0, TYPE_UINT, 0}, \ -{"rach_preambleInitialReceivedTargetPower_NB", NULL, 0, iptr:NULL, defintval:-112, TYPE_INT32, 0}, \ -{"rach_preambleTransMax_CE_NB", NULL, 0, uptr:NULL, defintval:3, TYPE_UINT, 0}, \ -{"bcch_modificationPeriodCoeff_NB", NULL, 0, uptr:NULL, defintval:16, TYPE_UINT, 0}, \ -{"pcch_defaultPagingCycle_NB", NULL, 0, uptr:NULL, defintval:256, TYPE_UINT, 0}, \ -{"nprach_CP_Length", NULL, 0, uptr:NULL, defintval:0, TYPE_UINT, 0}, \ -{"nprach_rsrp_range", NULL, 0, uptr:NULL, defintval:0, TYPE_UINT, 0}, \ -{"nprach_SubcarrierMSG3_RangeStart", NULL, 0, strptr:NULL, defstrval:"one", TYPE_STRING, 0}, \ -{"maxNumPreambleAttemptCE_NB", NULL, 0, uptr:NULL, defintval:10, TYPE_UINT, 0}, \ -{"npdsch_nrs_Power", NULL, 0, iptr:NULL, defintval:0, TYPE_INT, 0}, \ -{"npusch_ack_nack_numRepetitions_NB", NULL, 0, uptr:NULL, defintval:1, TYPE_UINT, 0}, \ -{"npusch_srs_SubframeConfig_NB", NULL, 0, uptr:NULL, defintval:0, TYPE_UINT, 0}, \ -{"npusch_threeTone_CyclicShift_r13", NULL, 0, uptr:NULL, defintval:0, TYPE_UINT, 0}, \ -{"npusch_sixTone_CyclicShift_r13", NULL, 0, uptr:NULL, defintval:0, TYPE_UINT, 0}, \ -{"npusch_groupHoppingEnabled", NULL, 0, strptr:NULL, defstrval:"disable", TYPE_STRING, 0}, \ -{"npusch_groupAssignmentNPUSCH_r13", NULL, 0, uptr:NULL, defintval:0, TYPE_UINT, 0}, \ -{"dl_GapThreshold_NB", NULL, 0, uptr:NULL, defintval:32, TYPE_UINT, 0}, \ -{"dl_GapPeriodicity_NB", NULL, 0, uptr:NULL, defintval:64, TYPE_UINT, 0}, \ -{"dl_GapDurationCoeff_NB", NULL, 0, strptr:NULL, defstrval:"oneEighth", TYPE_STRING, 0}, \ -{"npusch_p0_NominalNPUSCH", NULL, 0, iptr:NULL, defintval:0, TYPE_INT32, 0}, \ -{"npusch_alpha", NULL, 0, strptr:NULL, defstrval:"AL0", TYPE_STRING, 0}, \ -{"deltaPreambleMsg3", NULL, 0, iptr:NULL, defintval:0, TYPE_INT32, 0}, \ -{"ue_TimersAndConstants_t300_NB", NULL, 0, uptr:NULL, defintval:1000, TYPE_UINT, 0}, \ -{"ue_TimersAndConstants_t301_NB", NULL, 0, uptr:NULL, defintval:1000, TYPE_UINT, 0}, \ -{"ue_TimersAndConstants_t310_NB", NULL, 0, uptr:NULL, defintval:1000, TYPE_UINT, 0}, \ -{"ue_TimersAndConstants_t311_NB", NULL, 0, uptr:NULL, defintval:10000, TYPE_UINT, 0}, \ -{"ue_TimersAndConstants_n310_NB", NULL, 0, uptr:NULL, defintval:20, TYPE_UINT, 0}, \ -{"ue_TimersAndConstants_n311_NB", NULL, 0, uptr:NULL, defintval:1, TYPE_UINT, 0}, \ -} - -#define NBIOT_RACH_RARESPONSEWINDOWSIZE_NB_IDX 0 -#define NBIOT_RACH_MACCONTENTIONRESOLUTIONTIMER_NB_IDX 1 -#define NBIOT_RACH_POWERRAMPINGSTEP_NB_IDX 2 -#define NBIOT_RACH_PREAMBLEINITIALRECEIVEDTARGETPOWER_NB_IDX 3 -#define NBIOT_RACH_PREAMBLETRANSMAX_CE_NB_IDX 4 -#define NBIOT_BCCH_MODIFICATIONPERIODCOEFF_NB_IDX 5 -#define NBIOT_PCCH_DEFAULTPAGINGCYCLE_NB_IDX 6 -#define NBIOT_NPRACH_CP_LENGTH_IDX 7 -#define NBIOT_NPRACH_RSRP_RANGE_IDX 8 -#define NBIOT_NPRACH_SUBCARRIERMSG3_RANGESTART_IDX 9 -#define NBIOT_MAXNUMPREAMBLEATTEMPTCE_NB_IDX 10 -#define NBIOT_NPDSCH_NRS_POWER_IDX 11 -#define NBIOT_NPUSCH_ACK_NACK_NUMREPETITIONS_NB_IDX 12 -#define NBIOT_NPUSCH_SRS_SUBFRAMECONFIG_NB_IDX 13 -#define NBIOT_NPUSCH_THREETONE_CYCLICSHIFT_R13_IDX 14 -#define NBIOT_NPUSCH_SIXTONE_CYCLICSHIFT_R13_IDX 15 -#define NBIOT_NPUSCH_GROUPHOPPINGENABLED_IDX 16 -#define NBIOT_NPUSCH_GROUPASSIGNMENTNPUSCH_R13_IDX 17 -#define NBIOT_DL_GAPTHRESHOLD_NB_IDX 18 -#define NBIOT_DL_GAPPERIODICITY_NB_IDX 19 -#define NBIOT_DL_GAPDURATIONCOEFF_NB_IDX 20 -#define NBIOT_NPUSCH_P0_NOMINALNPUSCH_IDX 21 -#define NBIOT_NPUSCH_ALPHA_IDX 22 -#define NBIOT_DELTAPREAMBLEMSG3_IDX 23 -#define NBIOT_UE_TIMERSANDCONSTANTS_T300_NB_IDX 24 -#define NBIOT_UE_TIMERSANDCONSTANTS_T301_NB_IDX 25 -#define NBIOT_UE_TIMERSANDCONSTANTS_T310_NB_IDX 26 -#define NBIOT_UE_TIMERSANDCONSTANTS_T311_NB_IDX 27 -#define NBIOT_UE_TIMERSANDCONSTANTS_N310_NB_IDX 28 -#define NBIOT_UE_TIMERSANDCONSTANTS_N311_NB_IDX 29 - -/* NB-Iot RRC: link to LTE RRC section name */ -#define NBIOT_LTERRCREF_CONFIG_STRING "LTERRC_Ref" -/*---------------------------------------------------------------------------------------------------------------*/ -/* NB-IoT RRC configuration parameters to link to a LTE RRC instance (in-guard, in-band) */ -/* optname helpstr paramflags XXXptr defXXXval type numelt */ -/*---------------------------------------------------------------------------------------------------------------*/ -#define NBIOTRRCPARAMS_RRCREF_DESC { \ -{"RRC_inst", NULL, 0, uptr:NULL, defintval:0, TYPE_UINT, 0}, \ -{"CC_inst", NULL, 0, uptr:NULL, defintval:0, TYPE_UINT, 0}, \ -} -/*--------------------------------------------------------------------------------------------------------------*/ -#define NBIOT_RRCINST_IDX 0 -#define NBIOT_CCINST_IDX 1 - -#define NBIOT_RRCLIST_NPRACHPARAMS_CONFIG_STRING "NPRACH-NB-r13" - - -#define NPRACH_PERIODICITY_OKVALUES {40,80,160,240,320,640,1280,2560} -#define NPRACH_PERIODICITY_MODVALUES { NPRACH_Parameters_NB_r13__nprach_Periodicity_r13_ms40, NPRACH_Parameters_NB_r13__nprach_Periodicity_r13_ms80, \ - NPRACH_Parameters_NB_r13__nprach_Periodicity_r13_ms160, NPRACH_Parameters_NB_r13__nprach_Periodicity_r13_ms240, \ - NPRACH_Parameters_NB_r13__nprach_Periodicity_r13_ms320, NPRACH_Parameters_NB_r13__nprach_Periodicity_r13_ms640, \ - NPRACH_Parameters_NB_r13__nprach_Periodicity_r13_ms1280, NPRACH_Parameters_NB_r13__nprach_Periodicity_r13_ms2560 } - -#define NPRACH_STARTTIME_OKVALUES {8,16,32,64,128,256,512,1024} -#define NPRACH_STARTTIME_MODVALUES { NPRACH_Parameters_NB_r13__nprach_StartTime_r13_ms8, NPRACH_Parameters_NB_r13__nprach_StartTime_r13_ms16, \ - NPRACH_Parameters_NB_r13__nprach_StartTime_r13_ms32, NPRACH_Parameters_NB_r13__nprach_StartTime_r13_ms64, \ - NPRACH_Parameters_NB_r13__nprach_StartTime_r13_ms128, NPRACH_Parameters_NB_r13__nprach_StartTime_r13_ms256, \ - NPRACH_Parameters_NB_r13__nprach_StartTime_r13_ms512, NPRACH_Parameters_NB_r13__nprach_StartTime_r13_ms1024 } - -#define NPRACH_SUBCARRIEROFFSET_OKVALUES {0,12,24,36,2,18,34} -#define NPRACH_SUBCARRIEROFFSET_MODVALUES { NPRACH_Parameters_NB_r13__nprach_SubcarrierOffset_r13_n0, NPRACH_Parameters_NB_r13__nprach_SubcarrierOffset_r13_n12, \ - NPRACH_Parameters_NB_r13__nprach_SubcarrierOffset_r13_n24, NPRACH_Parameters_NB_r13__nprach_SubcarrierOffset_r13_n36, \ - NPRACH_Parameters_NB_r13__nprach_SubcarrierOffset_r13_n2, NPRACH_Parameters_NB_r13__nprach_SubcarrierOffset_r13_n18, \ - NPRACH_Parameters_NB_r13__nprach_SubcarrierOffset_r13_n34 } - -#define NPRACH_NUMSUBCARRIERS_OKVALUES {12,24,36,48} -#define NPRACH_NUMSUBCARRIERS_MODVALUES { NPRACH_Parameters_NB_r13__nprach_NumSubcarriers_r13_n12, NPRACH_Parameters_NB_r13__nprach_NumSubcarriers_r13_n24, \ - NPRACH_Parameters_NB_r13__nprach_NumSubcarriers_r13_n36, NPRACH_Parameters_NB_r13__nprach_NumSubcarriers_r13_n48 } - - -#define NUMREPETITIONSPERPREAMBLEATTEMPT_OKVALUES {1,2,4,8,16,32,64,128} -#define NUMREPETITIONSPERPREAMBLEATTEMPT_MODVALUES { NPRACH_Parameters_NB_r13__numRepetitionsPerPreambleAttempt_r13_n1, NPRACH_Parameters_NB_r13__numRepetitionsPerPreambleAttempt_r13_n2, \ - NPRACH_Parameters_NB_r13__numRepetitionsPerPreambleAttempt_r13_n4, NPRACH_Parameters_NB_r13__numRepetitionsPerPreambleAttempt_r13_n8, \ - NPRACH_Parameters_NB_r13__numRepetitionsPerPreambleAttempt_r13_n16, NPRACH_Parameters_NB_r13__numRepetitionsPerPreambleAttempt_r13_n32, \ - NPRACH_Parameters_NB_r13__numRepetitionsPerPreambleAttempt_r13_n64, NPRACH_Parameters_NB_r13__numRepetitionsPerPreambleAttempt_r13_n128} - -#define NPDCCHNUMREPETITIONSRA_OKVALUES {1,2,4,8,16,32,64,128,256,512,1024,2048} -#define NPDCCHNUMREPETITIONSRA_MODVALUES { NPRACH_Parameters_NB_r13__npdcch_NumRepetitions_RA_r13_r1, NPRACH_Parameters_NB_r13__npdcch_NumRepetitions_RA_r13_r2, \ - NPRACH_Parameters_NB_r13__npdcch_NumRepetitions_RA_r13_r4, NPRACH_Parameters_NB_r13__npdcch_NumRepetitions_RA_r13_r8, \ - NPRACH_Parameters_NB_r13__npdcch_NumRepetitions_RA_r13_r16, NPRACH_Parameters_NB_r13__npdcch_NumRepetitions_RA_r13_r32, \ - NPRACH_Parameters_NB_r13__npdcch_NumRepetitions_RA_r13_r64, NPRACH_Parameters_NB_r13__npdcch_NumRepetitions_RA_r13_r128, \ - NPRACH_Parameters_NB_r13__npdcch_NumRepetitions_RA_r13_r256, NPRACH_Parameters_NB_r13__npdcch_NumRepetitions_RA_r13_r512, \ - NPRACH_Parameters_NB_r13__npdcch_NumRepetitions_RA_r13_r1024,NPRACH_Parameters_NB_r13__npdcch_NumRepetitions_RA_r13_r2048} - -#define NPDCCHSTARTSFCSSRA_OKVALUES {1,2,4,8,16,32,48,64} -#define NPDCCHSTARTSFCSSRA_MODVALUES { NPRACH_Parameters_NB_r13__npdcch_StartSF_CSS_RA_r13_v1dot5, NPRACH_Parameters_NB_r13__npdcch_StartSF_CSS_RA_r13_v2, \ - NPRACH_Parameters_NB_r13__npdcch_StartSF_CSS_RA_r13_v4, NPRACH_Parameters_NB_r13__npdcch_StartSF_CSS_RA_r13_v8, \ - NPRACH_Parameters_NB_r13__npdcch_StartSF_CSS_RA_r13_v16, NPRACH_Parameters_NB_r13__npdcch_StartSF_CSS_RA_r13_v32, \ - NPRACH_Parameters_NB_r13__npdcch_StartSF_CSS_RA_r13_v48, NPRACH_Parameters_NB_r13__npdcch_StartSF_CSS_RA_r13_v64} - -#define NPDCCHOFFSETRA_OKVALUES {"zero","oneEighth","oneFourth","threeEighth"} -#define NPDCCHOFFSETRA_MODVALUES { NPRACH_Parameters_NB_r13__npdcch_Offset_RA_r13_zero, NPRACH_Parameters_NB_r13__npdcch_Offset_RA_r13_oneEighth, \ - NPRACH_Parameters_NB_r13__npdcch_Offset_RA_r13_oneFourth, NPRACH_Parameters_NB_r13__npdcch_Offset_RA_r13_threeEighth} - - - -#define NBIOT_RRCLIST_NPRACHPARAMSCHECK_DESC { \ - { .s1a= { config_check_modify_integer, NPRACH_PERIODICITY_OKVALUES, NPRACH_PERIODICITY_MODVALUES, 8 }}, \ - { .s1a= { config_check_modify_integer, NPRACH_STARTTIME_OKVALUES, NPRACH_STARTTIME_MODVALUES, 8 }}, \ - { .s1a= { config_check_modify_integer, NPRACH_SUBCARRIEROFFSET_OKVALUES, NPRACH_SUBCARRIEROFFSET_MODVALUES, 7 }}, \ - { .s1a= { config_check_modify_integer, NPRACH_NUMSUBCARRIERS_OKVALUES, NPRACH_NUMSUBCARRIERS_MODVALUES, 4 }}, \ - { .s1a= { config_check_modify_integer, NUMREPETITIONSPERPREAMBLEATTEMPT_OKVALUES, NUMREPETITIONSPERPREAMBLEATTEMPT_MODVALUES,8 }}, \ - { .s1a= { config_check_modify_integer, NPDCCHNUMREPETITIONSRA_OKVALUES, NPDCCHNUMREPETITIONSRA_MODVALUES, 12}}, \ - { .s1a= { config_check_modify_integer, NPDCCHSTARTSFCSSRA_OKVALUES, NPDCCHSTARTSFCSSRA_MODVALUES, 8 }}, \ - { .s3a= { config_checkstr_assign_integer, NPDCCHOFFSETRA_OKVALUES, NPDCCHOFFSETRA_MODVALUES, 4 }}, \ -} - - - -/*------------------------------------------------------------------------------------------------------------------------------*/ -/* NB-IoT NPrach parameters, there will be three ocuurences of these parameters in each RRC instance */ - /* optname helpstr paramflags XXXptr defXXXval type numelt */ -/*------------------------------------------------------------------------------------------------------------------------------*/ -#define NBIOTRRC_NPRACH_PARAMS_DESC { \ -{"nprach_Periodicity", NULL, 0, uptr:NULL, defintval:320, TYPE_UINT, 0}, \ -{"nprach_StartTime", NULL, 0, uptr:NULL, defintval:8, TYPE_UINT, 0}, \ -{"nprach_SubcarrierOffset", NULL, 0, uptr:NULL, defintval:0, TYPE_UINT, 0}, \ -{"nprach_NumSubcarriers", NULL, 0, uptr:NULL, defintval:12, TYPE_UINT, 0}, \ -{"numRepetitionsPerPreambleAttempt", NULL, 0, uptr:NULL, defintval:2, TYPE_UINT, 0}, \ -{"npdcch_NumRepetitions_RA", NULL, 0, uptr:NULL, defintval:16, TYPE_UINT, 0}, \ -{"npdcch_StartSF_CSS_RA", NULL, 0, uptr:NULL, defintval:2, TYPE_UINT, 0}, \ -{"npdcch_Offset_RA", NULL, 0, strptr:NULL, defstrval:"zero", TYPE_STRING, 0}, \ -} - -#define NBIOT_NPRACH_PERIODICITY_IDX 0 -#define NBIOT_NPRACH_STARTTIME_IDX 1 -#define NBIOT_NPRACH_SUBCARRIEROFFSET_IDX 2 -#define NBIOT_NPRACH_NUMSUBCARRIERS_IDX 3 -#define NBIOT_NUMREPETITIONSPERPREAMBLEATTEMPT_NB_IDX 4 -#define NBIOT_NPDCCH_NUMREPETITIONS_RA_IDX 5 -#define NBIOT_NPDCCH_STARTSF_CSS_RA_IDX 6 -#define NBIOT_NPDCCH_OFFSET_RA_IDX 7 - -/*-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------*/ -/* NB IoT MACRLC configuration list section name */ -#define NBIOT_MACRLCLIST_CONFIG_STRING "NB-IoT_MACRLCs" - - -/*-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------*/ -/* NB IoT L1 configuration list section name */ -#define NBIOT_L1LIST_CONFIG_STRING "NB-IoT_L1s" - -#endif From cede4499071097c04e83071661b3835379051d44 Mon Sep 17 00:00:00 2001 From: Robert Schmidt Date: Fri, 25 Jul 2025 09:30:00 +0200 Subject: [PATCH 28/97] Remove NUMBER_OF_UE_MAX_NB_IoT and make NB_IoT compile This constant seems to be an analogon to NUMBER_OF_UE_MAX; replace with the latter, as it does the same. (NBIoT does not work, anyway) Fix dependencies of the NB_IoT target so it compiles. --- CMakeLists.txt | 2 - openair1/PHY/defs_L1_NB_IoT.h | 73 ++++++++++++++--------------- openair1/PHY/impl_defs_top_NB_IoT.h | 24 +++++----- 3 files changed, 48 insertions(+), 51 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 350b66afa9..fa7510a9f1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1653,8 +1653,6 @@ include_directories(${NAS_SRC}UE/EMM/SAP) include_directories(${NAS_SRC}UE/ESM) include_directories(${NAS_SRC}UE/ESM/SAP) -add_definitions("-DNUMBER_OF_UE_MAX_NB_IoT=16") - # Simulation library ########################## set (SIMUSRC diff --git a/openair1/PHY/defs_L1_NB_IoT.h b/openair1/PHY/defs_L1_NB_IoT.h index aba1ffb255..97f243fae8 100644 --- a/openair1/PHY/defs_L1_NB_IoT.h +++ b/openair1/PHY/defs_L1_NB_IoT.h @@ -512,20 +512,20 @@ typedef struct PHY_VARS_eNB_NB_IoT_s { NB_IoT_DL_FRAME_PARMS frame_parms; PHY_MEASUREMENTS_eNB_NB_IoT measurements[NUMBER_OF_eNB_SECTORS_MAX_NB_IoT]; /// Measurement variables NB_IoT_eNB_COMMON common_vars; - NB_IoT_eNB_SRS srs_vars[NUMBER_OF_UE_MAX_NB_IoT]; + NB_IoT_eNB_SRS srs_vars[NUMBER_OF_UE_MAX]; NB_IoT_eNB_PBCH pbch; - NB_IoT_eNB_PUSCH *pusch_vars[NUMBER_OF_UE_MAX_NB_IoT]; + NB_IoT_eNB_PUSCH *pusch_vars[NUMBER_OF_UE_MAX]; NB_IoT_eNB_PRACH prach_vars; - //LTE_eNB_DLSCH_t *dlsch[NUMBER_OF_UE_MAX_NB_IoT][2]; // Nusers times two spatial streams - NB_IoT_eNB_NULSCH_t *ulsch[NUMBER_OF_UE_MAX_NB_IoT+1]; // Nusers + number of RA (the ulsch[0] contains RAR) + //LTE_eNB_DLSCH_t *dlsch[NUMBER_OF_UE_MAX][2]; // Nusers times two spatial streams + NB_IoT_eNB_NULSCH_t *ulsch[NUMBER_OF_UE_MAX+1]; // Nusers + number of RA (the ulsch[0] contains RAR) //LTE_eNB_DLSCH_t *dlsch_SI,*dlsch_ra; //LTE_eNB_DLSCH_t *dlsch_MCH; - NB_IoT_eNB_UE_stats UE_stats[NUMBER_OF_UE_MAX_NB_IoT]; - //LTE_eNB_UE_stats *UE_stats_ptr[NUMBER_OF_UE_MAX_NB_IoT]; + NB_IoT_eNB_UE_stats UE_stats[NUMBER_OF_UE_MAX]; + //LTE_eNB_UE_stats *UE_stats_ptr[NUMBER_OF_UE_MAX]; /// cell-specific reference symbols uint32_t lte_gold_table_NB_IoT[20][2][14]; /// UE-specific reference symbols (p=5), TM 7 - uint32_t lte_gold_uespec_port5_table[NUMBER_OF_UE_MAX_NB_IoT][20][38]; + uint32_t lte_gold_uespec_port5_table[NUMBER_OF_UE_MAX][20][38]; /// UE-specific reference symbols (p=7...14), TM 8/9/10 uint32_t lte_gold_uespec_table[2][20][2][21]; /// mbsfn reference symbols @@ -539,7 +539,7 @@ typedef struct PHY_VARS_eNB_NB_IoT_s { /// char eNB_generate_rar; /// Indicator set to 0 after first SR - uint8_t first_sr[NUMBER_OF_UE_MAX_NB_IoT]; + uint8_t first_sr[NUMBER_OF_UE_MAX]; uint32_t max_peak_val; /// @@ -549,7 +549,7 @@ typedef struct PHY_VARS_eNB_NB_IoT_s { /// N0 (used for abstraction) double N0; /// - unsigned char first_run_timing_advance[NUMBER_OF_UE_MAX_NB_IoT]; + unsigned char first_run_timing_advance[NUMBER_OF_UE_MAX]; unsigned char first_run_I0_measurements; unsigned char cooperation_flag; // for cooperative communication @@ -575,37 +575,37 @@ typedef struct PHY_VARS_eNB_NB_IoT_s { DCI_ALLOC_NB_IoT_t dci_alloc[2][NUM_DCI_MAX_NB_IoT]; // dci_alloc from even/odd subframes ///////////// // PDSCH Variables - PDSCH_CONFIG_DEDICATED_NB_IoT pdsch_config_dedicated[NUMBER_OF_UE_MAX_NB_IoT]; + PDSCH_CONFIG_DEDICATED_NB_IoT pdsch_config_dedicated[NUMBER_OF_UE_MAX]; // PUSCH Variables - PUSCH_CONFIG_DEDICATED_NB_IoT pusch_config_dedicated[NUMBER_OF_UE_MAX_NB_IoT]; + PUSCH_CONFIG_DEDICATED_NB_IoT pusch_config_dedicated[NUMBER_OF_UE_MAX]; // PUCCH variables - PUCCH_CONFIG_DEDICATED_NB_IoT pucch_config_dedicated[NUMBER_OF_UE_MAX_NB_IoT]; + PUCCH_CONFIG_DEDICATED_NB_IoT pucch_config_dedicated[NUMBER_OF_UE_MAX]; // UL-POWER-Control - UL_POWER_CONTROL_DEDICATED_NB_IoT ul_power_control_dedicated[NUMBER_OF_UE_MAX_NB_IoT]; + UL_POWER_CONTROL_DEDICATED_NB_IoT ul_power_control_dedicated[NUMBER_OF_UE_MAX]; // TPC - TPC_PDCCH_CONFIG_NB_IoT tpc_pdcch_config_pucch[NUMBER_OF_UE_MAX_NB_IoT]; + TPC_PDCCH_CONFIG_NB_IoT tpc_pdcch_config_pucch[NUMBER_OF_UE_MAX]; /// - TPC_PDCCH_CONFIG_NB_IoT tpc_pdcch_config_pusch[NUMBER_OF_UE_MAX_NB_IoT]; + TPC_PDCCH_CONFIG_NB_IoT tpc_pdcch_config_pusch[NUMBER_OF_UE_MAX]; // CQI reporting - CQI_REPORT_CONFIG_NB_IoT cqi_report_config[NUMBER_OF_UE_MAX_NB_IoT]; + CQI_REPORT_CONFIG_NB_IoT cqi_report_config[NUMBER_OF_UE_MAX]; // SRS Variables - SOUNDINGRS_UL_CONFIG_DEDICATED_NB_IoT soundingrs_ul_config_dedicated[NUMBER_OF_UE_MAX_NB_IoT]; + SOUNDINGRS_UL_CONFIG_DEDICATED_NB_IoT soundingrs_ul_config_dedicated[NUMBER_OF_UE_MAX]; /// uint8_t ncs_cell[20][7]; // Scheduling Request Config - SCHEDULING_REQUEST_CONFIG_NB_IoT scheduling_request_config[NUMBER_OF_UE_MAX_NB_IoT]; + SCHEDULING_REQUEST_CONFIG_NB_IoT scheduling_request_config[NUMBER_OF_UE_MAX]; // Transmission mode per UE - uint8_t transmission_mode[NUMBER_OF_UE_MAX_NB_IoT]; + uint8_t transmission_mode[NUMBER_OF_UE_MAX]; /// cba_last successful reception for each group, used for collision detection uint8_t cba_last_reception[4]; // Pointers for active physicalConfigDedicated to be applied in current subframe - struct PhysicalConfigDedicated *physicalConfigDedicated[NUMBER_OF_UE_MAX_NB_IoT]; + struct PhysicalConfigDedicated *physicalConfigDedicated[NUMBER_OF_UE_MAX]; //Pointers for actve physicalConfigDedicated for NB-IoT to be applied in current subframe - struct PhysicalConfigDedicated_NB_r13 *phy_config_dedicated_NB_IoT[NUMBER_OF_UE_MAX_NB_IoT]; + struct PhysicalConfigDedicated_NB_r13 *phy_config_dedicated_NB_IoT[NUMBER_OF_UE_MAX]; /// uint32_t rb_mask_ul[4]; /// Information regarding TM5 - MU_MIMO_mode_NB_IoT mu_mimo_mode[NUMBER_OF_UE_MAX_NB_IoT]; + MU_MIMO_mode_NB_IoT mu_mimo_mode[NUMBER_OF_UE_MAX]; /// target_ue_dl_mcs : only for debug purposes uint32_t target_ue_dl_mcs; /// target_ue_ul_mcs : only for debug purposes @@ -660,16 +660,16 @@ typedef struct PHY_VARS_eNB_NB_IoT_s { time_stats_t ulsch_tc_ext_stats; time_stats_t ulsch_tc_intl1_stats; time_stats_t ulsch_tc_intl2_stats; - int32_t pucch1_stats_cnt[NUMBER_OF_UE_MAX_NB_IoT][10]; - int32_t pucch1_stats[NUMBER_OF_UE_MAX_NB_IoT][10*1024]; - int32_t pucch1_stats_thres[NUMBER_OF_UE_MAX_NB_IoT][10*1024]; - int32_t pucch1ab_stats_cnt[NUMBER_OF_UE_MAX_NB_IoT][10]; - int32_t pucch1ab_stats[NUMBER_OF_UE_MAX_NB_IoT][2*10*1024]; - int32_t pusch_stats_rb[NUMBER_OF_UE_MAX_NB_IoT][10240]; - int32_t pusch_stats_round[NUMBER_OF_UE_MAX_NB_IoT][10240]; - int32_t pusch_stats_mcs[NUMBER_OF_UE_MAX_NB_IoT][10240]; - int32_t pusch_stats_bsr[NUMBER_OF_UE_MAX_NB_IoT][10240]; - int32_t pusch_stats_BO[NUMBER_OF_UE_MAX_NB_IoT][10240]; + int32_t pucch1_stats_cnt[NUMBER_OF_UE_MAX][10]; + int32_t pucch1_stats[NUMBER_OF_UE_MAX][10*1024]; + int32_t pucch1_stats_thres[NUMBER_OF_UE_MAX][10*1024]; + int32_t pucch1ab_stats_cnt[NUMBER_OF_UE_MAX][10]; + int32_t pucch1ab_stats[NUMBER_OF_UE_MAX][2*10*1024]; + int32_t pusch_stats_rb[NUMBER_OF_UE_MAX][10240]; + int32_t pusch_stats_round[NUMBER_OF_UE_MAX][10240]; + int32_t pusch_stats_mcs[NUMBER_OF_UE_MAX][10240]; + int32_t pusch_stats_bsr[NUMBER_OF_UE_MAX][10240]; + int32_t pusch_stats_BO[NUMBER_OF_UE_MAX][10240]; /// RF and Interface devices per CC openair0_device rfdevice; @@ -682,7 +682,7 @@ typedef struct PHY_VARS_eNB_NB_IoT_s { //------------------------ /* - * NUMBER_OF_UE_MAX_NB_IoT maybe in the future should be dynamic because could be very large and the memory may explode + * NUMBER_OF_UE_MAX maybe in the future should be dynamic because could be very large and the memory may explode * (is almost the indication of the number of UE context that we are storing at PHY layer) * * reasoning: the following data structure (ndlsch, nulsch ecc..) are used to store the context that should be transmitted in at least n+4 subframe later @@ -710,11 +710,10 @@ typedef struct PHY_VARS_eNB_NB_IoT_s { * */ - //TODO: check what should be NUMBER_OF_UE_MAX_NB_IoT value NB_IoT_eNB_NPBCH_t *npbch; - NB_IoT_eNB_NPDCCH_t *npdcch[NUMBER_OF_UE_MAX_NB_IoT]; - NB_IoT_eNB_NDLSCH_t *ndlsch[NUMBER_OF_UE_MAX_NB_IoT][2]; - NB_IoT_eNB_NULSCH_t *nulsch[NUMBER_OF_UE_MAX_NB_IoT+1]; //nulsch[0] contains the RAR + NB_IoT_eNB_NPDCCH_t *npdcch[NUMBER_OF_UE_MAX]; + NB_IoT_eNB_NDLSCH_t *ndlsch[NUMBER_OF_UE_MAX][2]; + NB_IoT_eNB_NULSCH_t *nulsch[NUMBER_OF_UE_MAX+1]; //nulsch[0] contains the RAR NB_IoT_eNB_NDLSCH_t *ndlsch_SI,*ndlsch_ra, *ndlsch_SIB1; NB_IoT_DL_FRAME_PARMS frame_parms_NB_IoT; diff --git a/openair1/PHY/impl_defs_top_NB_IoT.h b/openair1/PHY/impl_defs_top_NB_IoT.h index f9560cb228..8b3360c087 100644 --- a/openair1/PHY/impl_defs_top_NB_IoT.h +++ b/openair1/PHY/impl_defs_top_NB_IoT.h @@ -158,30 +158,30 @@ typedef struct { short n0_subband_power_tot_dBm[100]; // eNB measurements (per user) //! estimated received spatial signal power (linear) - unsigned int rx_spatial_power[NUMBER_OF_UE_MAX_NB_IoT][2][2]; + unsigned int rx_spatial_power[NUMBER_OF_UE_MAX][2][2]; //! estimated received spatial signal power (dB) - unsigned short rx_spatial_power_dB[NUMBER_OF_UE_MAX_NB_IoT][2][2]; + unsigned short rx_spatial_power_dB[NUMBER_OF_UE_MAX][2][2]; //! estimated rssi (dBm) - short rx_rssi_dBm[NUMBER_OF_UE_MAX_NB_IoT]; + short rx_rssi_dBm[NUMBER_OF_UE_MAX]; //! estimated correlation (wideband linear) between spatial channels (computed in dlsch_demodulation) - int rx_correlation[NUMBER_OF_UE_MAX_NB_IoT][2]; + int rx_correlation[NUMBER_OF_UE_MAX][2]; //! estimated correlation (wideband dB) between spatial channels (computed in dlsch_demodulation) - int rx_correlation_dB[NUMBER_OF_UE_MAX_NB_IoT][2]; + int rx_correlation_dB[NUMBER_OF_UE_MAX][2]; /// Wideband CQI (= SINR) - int wideband_cqi[NUMBER_OF_UE_MAX_NB_IoT][NB_ANTENNAS_RX]; + int wideband_cqi[NUMBER_OF_UE_MAX][NB_ANTENNAS_RX]; /// Wideband CQI in dB (= SINR dB) - int wideband_cqi_dB[NUMBER_OF_UE_MAX_NB_IoT][NB_ANTENNAS_RX]; + int wideband_cqi_dB[NUMBER_OF_UE_MAX][NB_ANTENNAS_RX]; /// Wideband CQI (sum of all RX antennas, in dB) - char wideband_cqi_tot[NUMBER_OF_UE_MAX_NB_IoT]; + char wideband_cqi_tot[NUMBER_OF_UE_MAX]; /// Subband CQI per RX antenna and RB (= SINR) - int subband_cqi[NUMBER_OF_UE_MAX_NB_IoT][NB_ANTENNAS_RX][100]; + int subband_cqi[NUMBER_OF_UE_MAX][NB_ANTENNAS_RX][100]; /// Total Subband CQI and RB (= SINR) - int subband_cqi_tot[NUMBER_OF_UE_MAX_NB_IoT][100]; + int subband_cqi_tot[NUMBER_OF_UE_MAX][100]; /// Subband CQI in dB and RB (= SINR dB) - int subband_cqi_dB[NUMBER_OF_UE_MAX_NB_IoT][NB_ANTENNAS_RX][100]; + int subband_cqi_dB[NUMBER_OF_UE_MAX][NB_ANTENNAS_RX][100]; /// Total Subband CQI and RB - int subband_cqi_tot_dB[NUMBER_OF_UE_MAX_NB_IoT][100]; + int subband_cqi_tot_dB[NUMBER_OF_UE_MAX][100]; } PHY_MEASUREMENTS_eNB_NB_IoT; From cc094ac73789fd9340bddb190db85b24c85b850f Mon Sep 17 00:00:00 2001 From: Robert Schmidt Date: Fri, 20 Dec 2024 00:03:20 +0100 Subject: [PATCH 29/97] XNAP asn.1 cmake: ber_decoder.h is not created XNAP is asn1c compiled with -no-gen-BER. Correspondingly, no ber_decoder.h file is created. However, when running cmake followed by ninja, it notices the absence of this file: ninja explain: output openair2/XNAP/MESSAGES/ber_decoder.h doesn't exist which triggers a complete rebuild of XNAP ASN.1. Without this file, the targets are not marked as dirty. --- openair2/XNAP/MESSAGES/ASN1/xnap_R16.2.0.cmake | 1 - 1 file changed, 1 deletion(-) diff --git a/openair2/XNAP/MESSAGES/ASN1/xnap_R16.2.0.cmake b/openair2/XNAP/MESSAGES/ASN1/xnap_R16.2.0.cmake index 3cc71e8415..cb0c762983 100644 --- a/openair2/XNAP/MESSAGES/ASN1/xnap_R16.2.0.cmake +++ b/openair2/XNAP/MESSAGES/ASN1/xnap_R16.2.0.cmake @@ -833,7 +833,6 @@ set(xnap_headers asn_SEQUENCE_OF.h asn_SET_OF.h asn_system.h - ber_decoder.h ber_tlv_length.h ber_tlv_tag.h BIT_STRING.h From 4177c6d182692c913525ab227cbabeaa6a5686db Mon Sep 17 00:00:00 2001 From: Robert Schmidt Date: Fri, 25 Jul 2025 14:36:25 +0200 Subject: [PATCH 30/97] Include CMakeLists.txt using add_subdirectory(), simplify --- CMakeLists.txt | 2 -- openair1/PHY/CODING/CMakeLists.txt | 1 + openair1/PHY/CODING/nrLDPC_decoder/CMakeLists.txt | 1 + .../PHY/CODING/nrLDPC_decoder/nrLDPC_tools/CMakeLists.txt | 8 ++++---- 4 files changed, 6 insertions(+), 6 deletions(-) create mode 100644 openair1/PHY/CODING/nrLDPC_decoder/CMakeLists.txt diff --git a/CMakeLists.txt b/CMakeLists.txt index fa7510a9f1..6d16902308 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2105,8 +2105,6 @@ if (${T_TRACER}) endforeach(i) endif (${T_TRACER}) -include(${OPENAIR1_DIR}/PHY/CODING/nrLDPC_decoder/nrLDPC_tools/CMakeLists.txt) - set(ENABLE_TESTS OFF CACHE STRING "Activate build of tests") set_property(CACHE ENABLE_TESTS PROPERTY TYPE BOOL) set(ENABLE_PHYSIM_TESTS OFF CACHE STRING "Activate build of physim tests") diff --git a/openair1/PHY/CODING/CMakeLists.txt b/openair1/PHY/CODING/CMakeLists.txt index 7e07f8f2dc..4681487c85 100644 --- a/openair1/PHY/CODING/CMakeLists.txt +++ b/openair1/PHY/CODING/CMakeLists.txt @@ -68,3 +68,4 @@ if (ENABLE_LDPC_CUDA) endif() add_subdirectory(nrLDPC_coding) +add_subdirectory(nrLDPC_decoder) diff --git a/openair1/PHY/CODING/nrLDPC_decoder/CMakeLists.txt b/openair1/PHY/CODING/nrLDPC_decoder/CMakeLists.txt new file mode 100644 index 0000000000..1734260e68 --- /dev/null +++ b/openair1/PHY/CODING/nrLDPC_decoder/CMakeLists.txt @@ -0,0 +1 @@ +add_subdirectory(nrLDPC_tools) diff --git a/openair1/PHY/CODING/nrLDPC_decoder/nrLDPC_tools/CMakeLists.txt b/openair1/PHY/CODING/nrLDPC_decoder/nrLDPC_tools/CMakeLists.txt index 26ef39408f..4b8dfcb446 100644 --- a/openair1/PHY/CODING/nrLDPC_decoder/nrLDPC_tools/CMakeLists.txt +++ b/openair1/PHY/CODING/nrLDPC_decoder/nrLDPC_tools/CMakeLists.txt @@ -1,7 +1,7 @@ -add_subdirectory(${CMAKE_CURRENT_LIST_DIR}/generator_bnProc ldpc/generator_bnProc) -add_subdirectory(${CMAKE_CURRENT_LIST_DIR}/generator_bnProc_avx512 ldpc/generator_bnProc_avx512) -add_subdirectory(${CMAKE_CURRENT_LIST_DIR}/generator_cnProc ldpc/generator_cnProc) -add_subdirectory(${CMAKE_CURRENT_LIST_DIR}/generator_cnProc_avx512 ldpc/generator_cnProc_avx512) +add_subdirectory(generator_bnProc ldpc/generator_bnProc) +add_subdirectory(generator_bnProc_avx512 ldpc/generator_bnProc_avx512) +add_subdirectory(generator_cnProc ldpc/generator_cnProc) +add_subdirectory(generator_cnProc_avx512 ldpc/generator_cnProc_avx512) # custom target to build all generators add_custom_target(ldpc_generators) From 46ed4ac36d300eb8d1d9c337192431c88d97afbc Mon Sep 17 00:00:00 2001 From: Robert Schmidt Date: Thu, 11 Sep 2025 06:49:45 +0200 Subject: [PATCH 31/97] Update comment to reference right target --- common/utils/T/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/utils/T/CMakeLists.txt b/common/utils/T/CMakeLists.txt index 5425a564b1..8f58871adc 100644 --- a/common/utils/T/CMakeLists.txt +++ b/common/utils/T/CMakeLists.txt @@ -47,7 +47,7 @@ add_custom_target(generate_T DEPENDS T_IDs.h check_vcd) # even if T is off, we need the headers as many targets depend on it # for this, create an INTERFACE (header-only) library T_headers. To ensure the # headers have really been created, we make this headers library explicitly -# depend on the generated headers. +# depend on the generate_T target. add_library(T_headers INTERFACE) add_dependencies(T_headers T_IDs.h generate_T) target_include_directories(T_headers INTERFACE ${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_CURRENT_SOURCE_DIR}) From 72371ba10b53013dd5f38fbe59ece9dbbaec6758 Mon Sep 17 00:00:00 2001 From: Robert Schmidt Date: Thu, 11 Sep 2025 06:50:09 +0200 Subject: [PATCH 32/97] Remove unnecessary comment --- openair1/PHY/CODING/CMakeLists.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/openair1/PHY/CODING/CMakeLists.txt b/openair1/PHY/CODING/CMakeLists.txt index 4681487c85..f696c71213 100644 --- a/openair1/PHY/CODING/CMakeLists.txt +++ b/openair1/PHY/CODING/CMakeLists.txt @@ -7,7 +7,6 @@ add_library(coding MODULE 3gpplte_turbo_decoder.c ) set_target_properties(coding PROPERTIES LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}) -#ensure that the T header files are generated before targets depending on them target_link_libraries(coding PRIVATE log_headers) add_library(ldpc_orig MODULE From eef092c3188fa457416564015da525cf571417f2 Mon Sep 17 00:00:00 2001 From: Robert Schmidt Date: Thu, 11 Sep 2025 10:10:14 +0200 Subject: [PATCH 33/97] Omit JER compilation for NRPPA ASN.1 We don't use JER. This avoids compilation problems, such as: /usr/bin/ld: openair3/NRPPA/MESSAGES/libasn1_nrppa.a(INTEGER_jer.c.o): in function `INTEGER_decode_jer': tmp.MtrwVDhQqY/openair3/NRPPA/MESSAGES/INTEGER_jer.c:186:(.text+0x5b8): undefined reference to `jer_decode_primitive --- openair3/NRPPA/MESSAGES/ASN1/38455.cmake | 14 -------------- openair3/NRPPA/MESSAGES/CMakeLists.txt | 2 +- 2 files changed, 1 insertion(+), 15 deletions(-) diff --git a/openair3/NRPPA/MESSAGES/ASN1/38455.cmake b/openair3/NRPPA/MESSAGES/ASN1/38455.cmake index d81f3db96c..7421d09e06 100644 --- a/openair3/NRPPA/MESSAGES/ASN1/38455.cmake +++ b/openair3/NRPPA/MESSAGES/ASN1/38455.cmake @@ -4,7 +4,6 @@ set(nrppa_source ANY_aper.c ANY_ber.c ANY.c -ANY_jer.c ANY_uper.c ANY_xer.c aper_decoder.c @@ -24,7 +23,6 @@ ber_decoder.c ber_tlv_length.c ber_tlv_tag.c BIT_STRING.c -BIT_STRING_jer.c BIT_STRING_print.c BIT_STRING_rfill.c BIT_STRING_uper.c @@ -33,7 +31,6 @@ constraints.c constr_CHOICE_aper.c constr_CHOICE_ber.c constr_CHOICE.c -constr_CHOICE_jer.c constr_CHOICE_print.c constr_CHOICE_rfill.c constr_CHOICE_uper.c @@ -41,11 +38,9 @@ constr_CHOICE_xer.c constr_SEQUENCE_aper.c constr_SEQUENCE_ber.c constr_SEQUENCE.c -constr_SEQUENCE_jer.c constr_SEQUENCE_OF_aper.c constr_SEQUENCE_OF_ber.c constr_SEQUENCE_OF.c -constr_SEQUENCE_OF_jer.c constr_SEQUENCE_OF_uper.c constr_SEQUENCE_OF_xer.c constr_SEQUENCE_print.c @@ -55,7 +50,6 @@ constr_SEQUENCE_xer.c constr_SET_OF_aper.c constr_SET_OF_ber.c constr_SET_OF.c -constr_SET_OF_jer.c constr_SET_OF_print.c constr_SET_OF_rfill.c constr_SET_OF_uper.c @@ -66,21 +60,17 @@ GraphicString.c INTEGER_aper.c INTEGER_ber.c INTEGER.c -INTEGER_jer.c INTEGER_print.c INTEGER_rfill.c INTEGER_uper.c INTEGER_xer.c -jer_encoder.c NativeEnumerated_aper.c NativeEnumerated.c -NativeEnumerated_jer.c NativeEnumerated_uper.c NativeEnumerated_xer.c NativeInteger_aper.c NativeInteger_ber.c NativeInteger.c -NativeInteger_jer.c NativeInteger_print.c NativeInteger_rfill.c NativeInteger_uper.c @@ -406,21 +396,18 @@ NRPPA_WLAN-RSSI.c NULL_aper.c NULL_ber.c NULL.c -NULL_jer.c NULL_print.c NULL_rfill.c NULL_uper.c NULL_xer.c ObjectDescriptor.c OBJECT_IDENTIFIER.c -OBJECT_IDENTIFIER_jer.c OBJECT_IDENTIFIER_print.c OBJECT_IDENTIFIER_rfill.c OBJECT_IDENTIFIER_xer.c OCTET_STRING_aper.c OCTET_STRING_ber.c OCTET_STRING.c -OCTET_STRING_jer.c OCTET_STRING_print.c OCTET_STRING_rfill.c OCTET_STRING_uper.c @@ -473,7 +460,6 @@ constr_TYPE.h der_encoder.h GraphicString.h INTEGER.h -jer_encoder.h NativeEnumerated.h NativeInteger.h NRPPA_AbortTransmission.h diff --git a/openair3/NRPPA/MESSAGES/CMakeLists.txt b/openair3/NRPPA/MESSAGES/CMakeLists.txt index 997fb70a9c..6581932024 100644 --- a/openair3/NRPPA/MESSAGES/CMakeLists.txt +++ b/openair3/NRPPA/MESSAGES/CMakeLists.txt @@ -12,7 +12,7 @@ include(ASN1/38455.cmake) run_asn1c(${CMAKE_CURRENT_SOURCE_DIR}/${NRPPA_GRAMMAR} "NRPPA_" OUTPUT ${nrppa_source} ${nrppa_headers} - OPTIONS -gen-APER -gen-UPER -no-gen-OER -fcompound-names -no-gen-example -findirect-choice -fno-include-deps + OPTIONS -gen-APER -gen-UPER -no-gen-OER -no-gen-JER -fcompound-names -no-gen-example -findirect-choice -fno-include-deps COMMENT "NRPPA source files" ) From 66c3b7d192d307b18326bf92208d6e42b0682700 Mon Sep 17 00:00:00 2001 From: Robert Schmidt Date: Fri, 12 Sep 2025 19:59:28 +0200 Subject: [PATCH 34/97] Break coding_defs.h dependency on logging module coding_defs.h included defs_common.h, which pulls in many dependencies, e.g., the logging module, which pulls in T. However, it does not use anything of this. So remove, and include stdbool.h instead (because bool is used in coding_defs.h. nr_polar_defs.h relied on some includes done in coding_defs. Use includes for pthreads and SIMD to define types that are used in nr_polar_defs.h. --- openair1/PHY/CODING/3gpplte.c | 1 + openair1/PHY/CODING/coding_defs.h | 5 +---- openair1/PHY/CODING/nrPolar_tools/nr_crc_byte.c | 1 + openair1/PHY/CODING/nrPolar_tools/nr_polar_defs.h | 2 ++ 4 files changed, 5 insertions(+), 4 deletions(-) diff --git a/openair1/PHY/CODING/3gpplte.c b/openair1/PHY/CODING/3gpplte.c index 3502a1ad56..d3aec284da 100644 --- a/openair1/PHY/CODING/3gpplte.c +++ b/openair1/PHY/CODING/3gpplte.c @@ -30,6 +30,7 @@ #include #include #include "PHY/CODING/coding_defs.h" +#include "common/utils/utils.h" //#define DEBUG_TURBO_ENCODER 1 diff --git a/openair1/PHY/CODING/coding_defs.h b/openair1/PHY/CODING/coding_defs.h index 3c91892d8c..d6e1b780f7 100644 --- a/openair1/PHY/CODING/coding_defs.h +++ b/openair1/PHY/CODING/coding_defs.h @@ -28,7 +28,7 @@ #define __CODING_DEFS__H__ #include -#include +#include #define CRC24_A 0 #define CRC24_B 1 @@ -455,8 +455,5 @@ int32_t nr_segmentation(unsigned char *input_buffer, */ int nr_get_R_ldpc_decoder(int rvidx, int E, int BG, int Z, int *llrLen, int round); -decoder_if_t phy_threegpplte_turbo_decoder; -decoder_if_t phy_threegpplte_turbo_decoder8; -decoder_if_t phy_threegpplte_turbo_decoder16; /** @} */ #endif diff --git a/openair1/PHY/CODING/nrPolar_tools/nr_crc_byte.c b/openair1/PHY/CODING/nrPolar_tools/nr_crc_byte.c index 58764151c4..2bc3c8b517 100644 --- a/openair1/PHY/CODING/nrPolar_tools/nr_crc_byte.c +++ b/openair1/PHY/CODING/nrPolar_tools/nr_crc_byte.c @@ -20,6 +20,7 @@ */ #include "PHY/CODING/nrPolar_tools/nr_polar_defs.h" +#include "common/utils/utils.h" // ----- Old implementation ---- const uint8_t **crc24c_generator_matrix(uint16_t payloadSizeBits) diff --git a/openair1/PHY/CODING/nrPolar_tools/nr_polar_defs.h b/openair1/PHY/CODING/nrPolar_tools/nr_polar_defs.h index 803854c23b..519f23175c 100644 --- a/openair1/PHY/CODING/nrPolar_tools/nr_polar_defs.h +++ b/openair1/PHY/CODING/nrPolar_tools/nr_polar_defs.h @@ -38,11 +38,13 @@ #include #include #include +#include #include "PHY/CODING/nrPolar_tools/nr_polar_dci_defs.h" #include "PHY/CODING/nrPolar_tools/nr_polar_uci_defs.h" #include "PHY/CODING/nrPolar_tools/nr_polar_pbch_defs.h" #include "PHY/CODING/coding_defs.h" +#include "PHY/sse_intrin.h" // #include "SIMULATION/TOOLS/sim.h" #define NR_POLAR_DECODER_LISTSIZE 8 // uint8_t From fd37fba9b735c6c68ff367817b56fdcd03df34b3 Mon Sep 17 00:00:00 2001 From: Jaroslava Fiedlerova Date: Tue, 8 Oct 2024 10:14:32 +0200 Subject: [PATCH 35/97] Cleanup of libraries/executables from T loop These libraries/executables are not defined in OAI. --- CMakeLists.txt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 6d16902308..2f576a1919 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2087,17 +2087,17 @@ if (${T_TRACER}) nr-uesoftmodem dlsim dlsim_tm4 dlsim_tm7 ulsim pbchsim scansim mbmssim pdcchsim pucchsim prachsim syncsim nr_ulsim nr_dlsim nr_dlschsim nr_pbchsim nr_pucchsim - nr_ulschsim ldpctest polartest smallblocktest cu_test du_test + nr_ulschsim ldpctest polartest smallblocktest #all "add_library" definitions ITTI lte_rrc nr_rrc s1ap x2ap m2ap m3ap f1ap params_libconfig oai_usrpdevif oai_bladerfdevif oai_lmssdrdevif oai_iqplayer - oai_eth_transpro oai_mobipass HASHTABLE UTIL OMG_SUMO - SECURITY SCHED_LIB SCHED_NR_LIB SCHED_RU_LIB SCHED_UE_LIB SCHED_NR_UE_LIB default_sched remote_sched RAL + oai_eth_transpro HASHTABLE UTIL + SECURITY SCHED_LIB SCHED_NR_LIB SCHED_RU_LIB SCHED_UE_LIB SCHED_NR_UE_LIB NFAPI_LIB NFAPI_PNF_LIB NFAPI_VNF_LIB NFAPI_USER_LIB MISC_NFAPI_LTE_LIB MISC_NFAPI_NR_LIB PHY_COMMON PHY PHY_UE PHY_NR PHY_NR_COMMON PHY_NR_UE PHY_RU L2 L2_LTE L2_NR L2_LTE_NR L2_UE NR_L2_UE MAC_NR_COMMON MAC_UE_NR ngap - CN_UTILS GTPV1U SCTP_CLIENT MME_APP LIB_NAS_UE NB_IoT SIMU OPENAIR0_LIB + GTPV1U SCTP_CLIENT MME_APP LIB_NAS_UE SIMU dfts config_internals nr_common crc_byte) if (TARGET ${i}) add_dependencies(${i} generate_T) From 197705f232e587b97ad959224e8703457fd80063 Mon Sep 17 00:00:00 2001 From: Robert Schmidt Date: Tue, 16 Sep 2025 08:59:31 +0200 Subject: [PATCH 36/97] Provide T headers via log.h It seems that many compilation units include T.h through the config module (as evidenced when taking out T.h from config_load_configmodule.h). This is strange, because T.h is only used in the logging headers. Hence, move it to there, where it is used. --- common/config/config_load_configmodule.h | 1 - common/utils/LOG/log.h | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/common/config/config_load_configmodule.h b/common/config/config_load_configmodule.h index 3ec27d456f..d5f87d0f8b 100644 --- a/common/config/config_load_configmodule.h +++ b/common/config/config_load_configmodule.h @@ -39,7 +39,6 @@ #include #include #include "common/config/config_paramdesc.h" -#include "common/utils/T/T.h" #define CONFIG_MAX_OOPT_PARAMS 10 // maximum number of parameters in the -O option (-O :P1:P2... #define CONFIG_MAX_ALLOCATEDPTRS 32768 // maximum number of parameters that can be dynamicaly allocated in the config module diff --git a/common/utils/LOG/log.h b/common/utils/LOG/log.h index 7b9b570a06..1f7474253c 100644 --- a/common/utils/LOG/log.h +++ b/common/utils/LOG/log.h @@ -52,6 +52,7 @@ #endif #include #include +#include "common/utils/T/T.h" #if ENABLE_LTTNG #include "lttng-log.h" #endif From 2889e55cd8d4671017e689adf3036bcde27bc087 Mon Sep 17 00:00:00 2001 From: Robert Schmidt Date: Thu, 11 Sep 2025 06:49:31 +0200 Subject: [PATCH 37/97] Remove redundant target_include_directories() from LOG The same is achieved by including log_headers. --- common/utils/LOG/CMakeLists.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/common/utils/LOG/CMakeLists.txt b/common/utils/LOG/CMakeLists.txt index 6c4527f760..ade7f76dea 100644 --- a/common/utils/LOG/CMakeLists.txt +++ b/common/utils/LOG/CMakeLists.txt @@ -10,7 +10,6 @@ else() message(STATUS "LTTNG support disabled") endif() add_library(LOG ${log_sources}) -target_include_directories(LOG PUBLIC .) target_link_libraries(LOG PRIVATE CONFIG_LIB) target_link_libraries(LOG PUBLIC log_headers) if (ENABLE_LTTNG) From 1ecb03f5f81dc8ed4ee831e862ebf45bf88996ac Mon Sep 17 00:00:00 2001 From: Robert Schmidt Date: Mon, 15 Sep 2025 09:02:41 +0200 Subject: [PATCH 38/97] Make BladeRF compile Fix variable name. See also: 74662975683d ("move command line sample offset [...] at top level [...]") --- radio/BLADERF/bladerf_lib.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/radio/BLADERF/bladerf_lib.c b/radio/BLADERF/bladerf_lib.c index e834f8377e..178fc2fff2 100644 --- a/radio/BLADERF/bladerf_lib.c +++ b/radio/BLADERF/bladerf_lib.c @@ -138,7 +138,7 @@ static int trx_brf_write(openair0_device *device, brf_state_t *brf = (brf_state_t*)device->priv; /* BRF has only 1 rx/tx chaine : is it correct? */ int16_t *samples = (int16_t*)buff[0]; - timestamp -= device->openair0_cfg->command_line_sample_advance - device->openair0_cfg->tx_sample_advance; + ptimestamp -= device->openair0_cfg->command_line_sample_advance - device->openair0_cfg->tx_sample_advance; //memset(&brf->meta_tx, 0, sizeof(brf->meta_tx)); // When BLADERF_META_FLAG_TX_NOW is used the timestamp is not used, so one can't schedule a tx if (brf->meta_tx.flags == 0 ) From 2a5ed87589299dca7e9ca93cb86d068a10419672 Mon Sep 17 00:00:00 2001 From: Robert Schmidt Date: Thu, 25 Apr 2024 09:50:11 +0200 Subject: [PATCH 39/97] E2 Agent uses CONFIG_LIB for configuration Since E2 agent uses the config library, include it directly. This also makes the dependency on T obsolete, because - it is the logging headers that are needed, and - the T dependency is fulfilled through the log_headers. --- openair2/E2AP/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openair2/E2AP/CMakeLists.txt b/openair2/E2AP/CMakeLists.txt index d86dafb0b9..8cbaae73db 100644 --- a/openair2/E2AP/CMakeLists.txt +++ b/openair2/E2AP/CMakeLists.txt @@ -129,4 +129,4 @@ add_subdirectory(flexric/src/util) add_subdirectory(RAN_FUNCTION) add_library(e2_agent_arg STATIC e2_agent_arg.c) -add_dependencies(e2_agent_arg generate_T) +target_link_libraries(e2_agent_arg PRIVATE CONFIG_LIB log_headers) From 19813283fbb4dc507d005ce3b6fb8bb628252f0c Mon Sep 17 00:00:00 2001 From: Robert Schmidt Date: Thu, 11 Sep 2025 09:30:08 +0200 Subject: [PATCH 40/97] Avoid T error on radio libraries These libraries have an (indirect) dependency on T, either by using the logging module (which in turn depends on T), or through other includes. The easiest is to depend on the logging module, which will fulfil also the T dependency. It would equally be feasible to use the utils target (which in turn depends on log_headers), but this would necessitate to compile, and does not seem to be necessary. --- CMakeLists.txt | 7 +++---- radio/AW2SORI/CMakeLists.txt | 2 +- radio/BLADERF/CMakeLists.txt | 2 +- radio/ETHERNET/CMakeLists.txt | 2 +- radio/LMSSDR/CMakeLists.txt | 2 +- radio/USRP/CMakeLists.txt | 2 +- radio/emulator/CMakeLists.txt | 3 +-- radio/fhi_72/CMakeLists.txt | 3 ++- radio/fhi_72/mplane/CMakeLists.txt | 2 +- radio/rfsimulator/CMakeLists.txt | 2 +- radio/vrtsim/CMakeLists.txt | 4 ++-- 11 files changed, 15 insertions(+), 16 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 2f576a1919..30c8ee06a5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1678,9 +1678,8 @@ include_directories("${NFAPI_DIR}/nfapi/inc") include_directories("${NFAPI_DIR}/sim_common/inc") include_directories("${NFAPI_DIR}/pnf_sim/inc") -add_library(oai_iqplayer MODULE - ${OPENAIR_DIR}/radio/iqplayer/iqplayer_lib.c - ) +add_library(oai_iqplayer MODULE ${OPENAIR_DIR}/radio/iqplayer/iqplayer_lib.c) +target_link_libraries(oai_iqplayer PRIVATE log_headers) ################################# # add executables for operation @@ -2090,7 +2089,7 @@ if (${T_TRACER}) nr_ulschsim ldpctest polartest smallblocktest #all "add_library" definitions ITTI lte_rrc nr_rrc s1ap x2ap m2ap m3ap f1ap - params_libconfig oai_usrpdevif oai_bladerfdevif oai_lmssdrdevif oai_iqplayer + params_libconfig oai_eth_transpro HASHTABLE UTIL SECURITY SCHED_LIB SCHED_NR_LIB SCHED_RU_LIB SCHED_UE_LIB SCHED_NR_UE_LIB NFAPI_LIB NFAPI_PNF_LIB NFAPI_VNF_LIB NFAPI_USER_LIB diff --git a/radio/AW2SORI/CMakeLists.txt b/radio/AW2SORI/CMakeLists.txt index e02d8a2721..134ab27b81 100644 --- a/radio/AW2SORI/CMakeLists.txt +++ b/radio/AW2SORI/CMakeLists.txt @@ -9,7 +9,7 @@ endif() add_library(aw2sori_transpro MODULE oaiori.c) target_compile_options(aw2sori_transpro PRIVATE -shared -fPIC -msse4 -g -ggdb -DLITE_COMPILATION) -target_link_libraries(aw2sori_transpro PRIVATE ori oai_eth_transpro) +target_link_libraries(aw2sori_transpro PRIVATE ori oai_eth_transpro log_headers) set_target_properties(aw2sori_transpro PROPERTIES LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}) add_custom_command(TARGET aw2sori_transpro POST_BUILD COMMAND ${CMAKE_COMMAND} -E create_symlink libaw2sori_transpro.so libthirdparty_transpro.so diff --git a/radio/BLADERF/CMakeLists.txt b/radio/BLADERF/CMakeLists.txt index 5504eb4051..063c8cb21e 100644 --- a/radio/BLADERF/CMakeLists.txt +++ b/radio/BLADERF/CMakeLists.txt @@ -1,7 +1,7 @@ pkg_check_modules(bladeRF REQUIRED libbladeRF) add_library(oai_bladerfdevif MODULE bladerf_lib.c) target_link_libraries(oai_bladerfdevif PRIVATE bladeRF) -target_link_libraries(oai_bladerfdevif PRIVATE UTIL) +target_link_libraries(oai_bladerfdevif PRIVATE log_headers) set_target_properties(oai_bladerfdevif PROPERTIES LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}) add_custom_command(TARGET oai_bladerfdevif POST_BUILD COMMAND ${CMAKE_COMMAND} -E create_symlink liboai_bladerfdevif.so liboai_device.so diff --git a/radio/ETHERNET/CMakeLists.txt b/radio/ETHERNET/CMakeLists.txt index 8c6590c6e9..3a9b9dc5cb 100644 --- a/radio/ETHERNET/CMakeLists.txt +++ b/radio/ETHERNET/CMakeLists.txt @@ -3,7 +3,7 @@ add_library(oai_eth_transpro MODULE eth_udp.c eth_raw.c ) -target_link_libraries(oai_eth_transpro PRIVATE ${T_LIB}) +target_link_libraries(oai_eth_transpro PRIVATE log_headers) target_include_directories(oai_eth_transpro PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) set_target_properties(oai_eth_transpro PROPERTIES LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}) add_custom_command(TARGET oai_eth_transpro POST_BUILD diff --git a/radio/LMSSDR/CMakeLists.txt b/radio/LMSSDR/CMakeLists.txt index 2df5c9a01f..bfe2a7f99f 100644 --- a/radio/LMSSDR/CMakeLists.txt +++ b/radio/LMSSDR/CMakeLists.txt @@ -1,7 +1,7 @@ find_package(LimeSuite REQUIRED) add_library(oai_lmssdrdevif MODULE lms_lib.cpp) -target_link_libraries(oai_lmssdrdevif LimeSuite ) +target_link_libraries(oai_lmssdrdevif LimeSuite log_headers) set_target_properties(oai_lmssdrdevif PROPERTIES LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}) add_custom_command(TARGET oai_lmssdrdevif POST_BUILD COMMAND ${CMAKE_COMMAND} -E create_symlink liboai_lmssdrdevif.so liboai_device.so diff --git a/radio/USRP/CMakeLists.txt b/radio/USRP/CMakeLists.txt index 4e72d140f4..50d95ee7e5 100644 --- a/radio/USRP/CMakeLists.txt +++ b/radio/USRP/CMakeLists.txt @@ -5,7 +5,7 @@ add_library(oai_usrpdevif MODULE usrp_lib.cpp) #target_include_directories(oai_usrpdevif PRIVATE Boost::boost) target_link_libraries(oai_usrpdevif PRIVATE ${UHD_LIBRARIES}) target_include_directories(oai_usrpdevif PRIVATE ${UHD_INCLUDE_DIRS}) -target_link_libraries(oai_usrpdevif PRIVATE UTIL) +target_link_libraries(oai_usrpdevif PRIVATE log_headers) set_target_properties(oai_usrpdevif PROPERTIES LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}) add_custom_command(TARGET oai_usrpdevif POST_BUILD COMMAND ${CMAKE_COMMAND} -E create_symlink liboai_usrpdevif.so liboai_device.so diff --git a/radio/emulator/CMakeLists.txt b/radio/emulator/CMakeLists.txt index 42b196ee95..983d95986a 100644 --- a/radio/emulator/CMakeLists.txt +++ b/radio/emulator/CMakeLists.txt @@ -1,4 +1,3 @@ add_library(rf_emulator MODULE rf_emulator.c) set_target_properties(rf_emulator PROPERTIES LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}) -target_link_libraries(rf_emulator PRIVATE noise_device) -add_dependencies(rf_emulator generate_T) +target_link_libraries(rf_emulator PRIVATE noise_device log_headers) diff --git a/radio/fhi_72/CMakeLists.txt b/radio/fhi_72/CMakeLists.txt index 3b0f16d2a4..f2161d06a8 100644 --- a/radio/fhi_72/CMakeLists.txt +++ b/radio/fhi_72/CMakeLists.txt @@ -33,7 +33,8 @@ endif() add_compile_options(-Wno-packed-not-aligned) set_target_properties(oran_fhlib_5g PROPERTIES COMPILE_FLAGS "-fvisibility=hidden -march=native") -target_link_libraries(oran_fhlib_5g PRIVATE xran::xran ${dpdk_LINK_LIBRARIES} ${T_LIB} pthread dl rt m numa) +target_link_libraries(oran_fhlib_5g PRIVATE xran::xran ${dpdk_LINK_LIBRARIES} pthread dl rt m numa) +target_link_libraries(oran_fhlib_5g PRIVATE log_headers) target_include_directories(oran_fhlib_5g PRIVATE ${dpdk_INCLUDE_DIRS}) if (CMAKE_SYSTEM_PROCESSOR STREQUAL "aarch64") find_package(armral REQUIRED) diff --git a/radio/fhi_72/mplane/CMakeLists.txt b/radio/fhi_72/mplane/CMakeLists.txt index 65faa6dfbd..26bbb7939e 100644 --- a/radio/fhi_72/mplane/CMakeLists.txt +++ b/radio/fhi_72/mplane/CMakeLists.txt @@ -17,7 +17,7 @@ endif() add_compile_options(-Wno-packed-not-aligned) set_target_properties(oran_fhlib_5g_mplane PROPERTIES COMPILE_FLAGS "-fvisibility=hidden -march=native") -target_link_libraries(oran_fhlib_5g_mplane PRIVATE xran::xran ${dpdk_LINK_LIBRARIES} ${T_LIB} pthread dl rt m numa) +target_link_libraries(oran_fhlib_5g_mplane PRIVATE xran::xran ${dpdk_LINK_LIBRARIES} log_headers pthread dl rt m numa) target_include_directories(oran_fhlib_5g_mplane PRIVATE ${dpdk_INCLUDE_DIRS}) target_compile_definitions(oran_fhlib_5g_mplane PRIVATE OAI_MPLANE) diff --git a/radio/rfsimulator/CMakeLists.txt b/radio/rfsimulator/CMakeLists.txt index efba710bad..ad4d92f108 100644 --- a/radio/rfsimulator/CMakeLists.txt +++ b/radio/rfsimulator/CMakeLists.txt @@ -3,7 +3,7 @@ add_library(rfsimulator MODULE apply_channelmod.c ../../openair1/PHY/TOOLS/signal_energy.c ) -target_link_libraries(rfsimulator PRIVATE SIMU) +target_link_libraries(rfsimulator PRIVATE SIMU log_headers) set_target_properties(rfsimulator PROPERTIES LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}) add_executable(replay_node stored_node.c) diff --git a/radio/vrtsim/CMakeLists.txt b/radio/vrtsim/CMakeLists.txt index c5ee458666..998b791653 100644 --- a/radio/vrtsim/CMakeLists.txt +++ b/radio/vrtsim/CMakeLists.txt @@ -1,9 +1,9 @@ add_library(noise_device STATIC noise_device.c) +target_link_libraries(noise_device PRIVATE log_headers) add_library(vrtsim MODULE vrtsim.c) set_target_properties(vrtsim PROPERTIES LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}) -target_link_libraries(vrtsim PRIVATE SIMU shm_td_iq_channel actor noise_device) -add_dependencies(vrtsim generate_T) +target_link_libraries(vrtsim PRIVATE SIMU shm_td_iq_channel actor noise_device log_headers) add_boolean_option(OAI_VRTSIM_TAPS_CLIENT OFF "Enable taps client" ON) if (OAI_VRTSIM_TAPS_CLIENT) From eea7be3977ffa10adf4a58c3d5c0d9903a743432 Mon Sep 17 00:00:00 2001 From: Robert Schmidt Date: Thu, 11 Sep 2025 06:51:32 +0200 Subject: [PATCH 41/97] ldpc_segment: avoid T error, use log_headers for logging As parent. --- .../nrLDPC_coding/nrLDPC_coding_segment/CMakeLists.txt | 6 +----- .../CODING/nrLDPC_coding/nrLDPC_coding_xdma/CMakeLists.txt | 7 +------ 2 files changed, 2 insertions(+), 11 deletions(-) diff --git a/openair1/PHY/CODING/nrLDPC_coding/nrLDPC_coding_segment/CMakeLists.txt b/openair1/PHY/CODING/nrLDPC_coding/nrLDPC_coding_segment/CMakeLists.txt index f96a2cfaca..8c2811e3a1 100644 --- a/openair1/PHY/CODING/nrLDPC_coding/nrLDPC_coding_segment/CMakeLists.txt +++ b/openair1/PHY/CODING/nrLDPC_coding/nrLDPC_coding_segment/CMakeLists.txt @@ -3,8 +3,4 @@ add_library(ldpc_segment OBJECT nrLDPC_coding_segment_decoder.c nrLDPC_coding_segment_encoder.c ) - -#ensure that the T header files are generated before targets depending on them -if (${T_TRACER}) - add_dependencies(ldpc_segment generate_T) -endif (${T_TRACER}) +target_link_libraries(ldpc_segment PUBLIC log_headers) diff --git a/openair1/PHY/CODING/nrLDPC_coding/nrLDPC_coding_xdma/CMakeLists.txt b/openair1/PHY/CODING/nrLDPC_coding/nrLDPC_coding_xdma/CMakeLists.txt index 0325858389..0d8f5571cd 100644 --- a/openair1/PHY/CODING/nrLDPC_coding/nrLDPC_coding_xdma/CMakeLists.txt +++ b/openair1/PHY/CODING/nrLDPC_coding/nrLDPC_coding_xdma/CMakeLists.txt @@ -16,12 +16,7 @@ if (ENABLE_LDPC_XDMA) ) target_include_directories(ldpc_xdma PRIVATE ../nrLDPC_coding_segment) - #ensure that the T header files are generated before targets depending on them - if (${T_TRACER}) - add_dependencies(ldpc_xdma generate_T) - endif (${T_TRACER}) - - target_link_libraries(ldpc_xdma PRIVATE ldpc_gen_HEADERS) + target_link_libraries(ldpc_xdma PRIVATE ldpc_gen_HEADERS log_headers) set_target_properties(ldpc_xdma PROPERTIES LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}) add_dependencies(nr-softmodem ldpc_xdma) From 5716d95e978d78dbc2803068c3c6ceabdea9dc71 Mon Sep 17 00:00:00 2001 From: Robert Schmidt Date: Mon, 15 Sep 2025 07:46:56 +0000 Subject: [PATCH 42/97] nr_prachsim: remove unused include --- openair1/SIMULATION/NR_PHY/prachsim.c | 1 - 1 file changed, 1 deletion(-) diff --git a/openair1/SIMULATION/NR_PHY/prachsim.c b/openair1/SIMULATION/NR_PHY/prachsim.c index a13918a606..35e242cad3 100644 --- a/openair1/SIMULATION/NR_PHY/prachsim.c +++ b/openair1/SIMULATION/NR_PHY/prachsim.c @@ -64,7 +64,6 @@ #include "nfapi/oai_integration/vendor_ext.h" #include "nfapi_interface.h" #include "nfapi_nr_interface_scf.h" -#include "nr_common.h" #include "time_meas.h" #define NR_PRACH_DEBUG 1 From 1b81c4b09ca98b3b7216a9f603a47376252ce1af Mon Sep 17 00:00:00 2001 From: Robert Schmidt Date: Sat, 13 Sep 2025 06:26:03 +0000 Subject: [PATCH 43/97] RLC only needs headers for logging --- openair2/LAYER2/nr_rlc/CMakeLists.txt | 4 ++-- openair2/LAYER2/nr_rlc/tests/CMakeLists.txt | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/openair2/LAYER2/nr_rlc/CMakeLists.txt b/openair2/LAYER2/nr_rlc/CMakeLists.txt index 953822df10..7473b1d493 100644 --- a/openair2/LAYER2/nr_rlc/CMakeLists.txt +++ b/openair2/LAYER2/nr_rlc/CMakeLists.txt @@ -7,14 +7,14 @@ add_library(nr_rlc_core OBJECT nr_rlc_sdu.c nr_rlc_rx_manager.c) target_include_directories(nr_rlc_core PRIVATE ../../..) -target_link_libraries(nr_rlc_core PRIVATE ${T_LIB}) +target_link_libraries(nr_rlc_core PRIVATE log_headers) add_library(nr_rlc STATIC asn1_utils.c nr_rlc_oai_api.c nr_rlc_ue_manager.c ) # L2_NR: for PDCP -target_link_libraries(nr_rlc PUBLIC pthread f1ap nr_rlc_core UTIL ${T_LIB}) +target_link_libraries(nr_rlc PUBLIC pthread f1ap nr_rlc_core log_headers) target_include_directories(nr_rlc PRIVATE ../../..) target_link_libraries(nr_rlc PRIVATE asn1_lte_rrc asn1_nr_rrc) diff --git a/openair2/LAYER2/nr_rlc/tests/CMakeLists.txt b/openair2/LAYER2/nr_rlc/tests/CMakeLists.txt index c8d365ddf9..68d001ab94 100644 --- a/openair2/LAYER2/nr_rlc/tests/CMakeLists.txt +++ b/openair2/LAYER2/nr_rlc/tests/CMakeLists.txt @@ -1,7 +1,7 @@ foreach(TESTNUM RANGE 1 17) add_executable(nr_rlc_test_${TESTNUM} test.c) - target_link_libraries(nr_rlc_test_${TESTNUM} PRIVATE nr_rlc_core pthread UTIL ${T_LIB} minimal_lib) + target_link_libraries(nr_rlc_test_${TESTNUM} PRIVATE nr_rlc_core pthread utils minimal_lib) target_compile_definitions(nr_rlc_test_${TESTNUM} PRIVATE TEST="test${TESTNUM}.h") #target_include_directories(nr_rlc_test_${TESTNUM} PRIVATE LOG) #target_compile_options(nr_rlc_test_${TESTNUM} PRIVATE -g --coverage -Wall) From 1e476735eac1d5220413634fc9f10b5f9ff8356f Mon Sep 17 00:00:00 2001 From: Robert Schmidt Date: Fri, 12 Sep 2025 18:19:10 +0000 Subject: [PATCH 44/97] xforms uses logging --- openair1/PHY/TOOLS/CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/openair1/PHY/TOOLS/CMakeLists.txt b/openair1/PHY/TOOLS/CMakeLists.txt index 8a3d912caa..dad31d0ede 100644 --- a/openair1/PHY/TOOLS/CMakeLists.txt +++ b/openair1/PHY/TOOLS/CMakeLists.txt @@ -45,6 +45,7 @@ if(ENABLE_UESCOPE OR ENABLE_ENBSCOPE OR ENABLE_NRSCOPE) lte_phy_scope.c ../../../executables/stats.c) target_link_libraries(xforms_common PRIVATE asn1_nr_rrc_hdrs asn1_lte_rrc_hdrs) + target_link_libraries(xforms_common PRIVATE log_headers) add_library(enbscope MODULE lte_enb_scope.c $) target_link_libraries(enbscope PUBLIC forms PRIVATE asn1_nr_rrc_hdrs asn1_lte_rrc_hdrs UTIL) From cdbfaf835e2747c401f5cd140461db6a021664fb Mon Sep 17 00:00:00 2001 From: Robert Schmidt Date: Sat, 13 Sep 2025 06:28:03 +0000 Subject: [PATCH 45/97] params_yaml has no dependency on UTIL These libraries have no dependencies on UTIL, so it's better to omit it. However, then the test is missing symbols for the config (which likely, indirectly came through UTIL -> LOG -> CONFIG_LIB, so add it now onto the test. --- common/config/yaml/CMakeLists.txt | 4 ++-- common/config/yaml/tests/CMakeLists.txt | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/common/config/yaml/CMakeLists.txt b/common/config/yaml/CMakeLists.txt index 635887e37e..cfcc57e2ab 100644 --- a/common/config/yaml/CMakeLists.txt +++ b/common/config/yaml/CMakeLists.txt @@ -8,11 +8,11 @@ else() endif() add_library(params_yaml_static config_yaml.cpp) -target_link_libraries(params_yaml_static PUBLIC UTIL yaml-cpp::yaml-cpp) +target_link_libraries(params_yaml_static PUBLIC yaml-cpp::yaml-cpp) if (ENABLE_TESTS) add_subdirectory(tests) endif() add_library(params_yaml MODULE config_yaml.cpp) -target_link_libraries(params_yaml PUBLIC UTIL yaml-cpp::yaml-cpp) +target_link_libraries(params_yaml PUBLIC yaml-cpp::yaml-cpp) set_target_properties(params_yaml PROPERTIES LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}) diff --git a/common/config/yaml/tests/CMakeLists.txt b/common/config/yaml/tests/CMakeLists.txt index e3f1002d79..a460ccfdd4 100644 --- a/common/config/yaml/tests/CMakeLists.txt +++ b/common/config/yaml/tests/CMakeLists.txt @@ -1,5 +1,5 @@ add_executable(test_yaml_config test_yaml_config.cpp) -target_link_libraries(test_yaml_config PRIVATE params_yaml_static GTest::gtest) +target_link_libraries(test_yaml_config PRIVATE params_yaml_static GTest::gtest CONFIG_LIB) add_dependencies(tests test_yaml_config) add_test(NAME test_yaml_config COMMAND ./test_yaml_config) From 4876533e9ca9114ac0b3f3b5a714bdfc5eff030e Mon Sep 17 00:00:00 2001 From: Robert Schmidt Date: Sat, 13 Sep 2025 06:31:48 +0000 Subject: [PATCH 46/97] F1AP IDs: Remove unnecessary dependency --- openair2/F1AP/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openair2/F1AP/CMakeLists.txt b/openair2/F1AP/CMakeLists.txt index 78bc9435a2..53ab2823ed 100644 --- a/openair2/F1AP/CMakeLists.txt +++ b/openair2/F1AP/CMakeLists.txt @@ -3,7 +3,7 @@ add_subdirectory(MESSAGES) if(ENABLE_TESTS) add_executable(f1ap_ids_test f1ap_ids_test.c f1ap_ids.c) - target_link_libraries(f1ap_ids_test UTIL HASHTABLE minimal_lib) + target_link_libraries(f1ap_ids_test HASHTABLE minimal_lib) add_dependencies(tests f1ap_ids_test) add_test(NAME F1AP_ID_test COMMAND f1ap_ids_test) From 2ff949a16fe803fcffd2ea1fe720ec2c472b16f3 Mon Sep 17 00:00:00 2001 From: Robert Schmidt Date: Mon, 15 Sep 2025 13:14:46 +0000 Subject: [PATCH 47/97] Correct dependencies onto logging module These libraries depend on the logging module, so add the right dependency log_headers. Indirectly, this will fulfill the condition of T. I consider the prior usage of generate_T or {T_LIB} incorrect, because basically none of these libraries actually, actively use T, but rather only the logging module, which pulls in T. The dependency of log_headers will also take care of generating and including T headers, which is a dependency of the logging module. For conf2uedata and related, fulfill the dependency through conf2uedata_lib (on which the rest depends). --- common/utils/telnetsrv/CMakeLists.txt | 2 +- common/utils/time_manager/CMakeLists.txt | 2 +- nfapi/oai_integration/socket/CMakeLists.txt | 3 +-- openair2/LAYER2/NR_MAC_UE/CMakeLists.txt | 4 ++-- openair3/NAS/TOOLS/CMakeLists.txt | 7 +------ 5 files changed, 6 insertions(+), 12 deletions(-) diff --git a/common/utils/telnetsrv/CMakeLists.txt b/common/utils/telnetsrv/CMakeLists.txt index 1a740adbe2..a249596d97 100644 --- a/common/utils/telnetsrv/CMakeLists.txt +++ b/common/utils/telnetsrv/CMakeLists.txt @@ -44,7 +44,7 @@ add_library(telnetsrv_enb MODULE telnetsrv_enb_phycmd.c ) add_dependencies(telnetsrv telnetsrv_enb) -target_link_libraries(telnetsrv_enb PRIVATE asn1_nr_rrc_hdrs asn1_lte_rrc_hdrs) +target_link_libraries(telnetsrv_enb PRIVATE asn1_nr_rrc_hdrs asn1_lte_rrc_hdrs log_headers) message(STATUS "No specific telnet functions for gnb") diff --git a/common/utils/time_manager/CMakeLists.txt b/common/utils/time_manager/CMakeLists.txt index 3a197ae556..37cae023b0 100644 --- a/common/utils/time_manager/CMakeLists.txt +++ b/common/utils/time_manager/CMakeLists.txt @@ -7,7 +7,7 @@ add_library(time_management_core OBJECT time_client.c) target_link_libraries(time_management PUBLIC time_management_core) -target_link_libraries(time_management_core PRIVATE ${T_LIB}) +target_link_libraries(time_management_core PRIVATE log_headers) if(ENABLE_TESTS) add_subdirectory(tests) diff --git a/nfapi/oai_integration/socket/CMakeLists.txt b/nfapi/oai_integration/socket/CMakeLists.txt index 57cad36362..a909372d37 100644 --- a/nfapi/oai_integration/socket/CMakeLists.txt +++ b/nfapi/oai_integration/socket/CMakeLists.txt @@ -8,7 +8,6 @@ target_include_directories(nfapi_socket_lib PRIVATE include/) target_compile_definitions(nfapi_socket_lib PUBLIC ENABLE_SOCKET) target_link_libraries(nfapi_socket_lib PRIVATE NFAPI_LIB NFAPI_USER_LIB) -target_link_libraries(nfapi_socket_lib PRIVATE LOG) -add_dependencies(nfapi_socket_lib generate_T) +target_link_libraries(nfapi_socket_lib PRIVATE log_headers) target_link_libraries(nfapi_socket_lib PRIVATE asn1_lte_rrc_hdrs asn1_nr_rrc_hdrs) target_link_libraries(nfapi_socket_lib PRIVATE nr_fapi_p5 nr_fapi_p7) diff --git a/openair2/LAYER2/NR_MAC_UE/CMakeLists.txt b/openair2/LAYER2/NR_MAC_UE/CMakeLists.txt index aa583c8598..8a7fec829e 100644 --- a/openair2/LAYER2/NR_MAC_UE/CMakeLists.txt +++ b/openair2/LAYER2/NR_MAC_UE/CMakeLists.txt @@ -1,7 +1,7 @@ add_library(nr_ue_power_procedures nr_ue_power_procedures.c) -target_link_libraries(nr_ue_power_procedures PUBLIC asn1_nr_rrc ${T_LIB} MAC_NR_COMMON nr_common) +target_link_libraries(nr_ue_power_procedures PUBLIC asn1_nr_rrc log_headers MAC_NR_COMMON nr_common) add_library(nr_ue_ra_procedures nr_ra_procedures.c) -target_link_libraries(nr_ue_ra_procedures PUBLIC asn1_nr_rrc asn1_lte_rrc_hdrs ${T_LIB} MAC_NR_COMMON nr_common) +target_link_libraries(nr_ue_ra_procedures PUBLIC asn1_nr_rrc asn1_lte_rrc_hdrs log_headers MAC_NR_COMMON nr_common) if (ENABLE_TESTS) add_subdirectory(tests) endif() diff --git a/openair3/NAS/TOOLS/CMakeLists.txt b/openair3/NAS/TOOLS/CMakeLists.txt index e57db8255f..50058ad5bc 100755 --- a/openair3/NAS/TOOLS/CMakeLists.txt +++ b/openair3/NAS/TOOLS/CMakeLists.txt @@ -13,6 +13,7 @@ add_library(conf2uedata_lib OBJECT ${OPENAIR_DIR}/common/utils/mem/memory.c ${OPENAIR_DIR}/common/utils/utils.c ) +target_link_libraries(conf2uedata_lib PUBLIC log_headers) add_executable(conf2uedata conf2uedata.c) target_link_libraries(conf2uedata PRIVATE conf2uedata_lib) @@ -28,9 +29,3 @@ add_executable(nvram nvram.c) target_link_libraries(nvram PRIVATE conf2uedata_lib) set_target_properties(nvram PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}) target_link_libraries(nvram PRIVATE LOG minimal_lib ${libconfig_LIBRARIES}) - -if (${T_TRACER}) - add_dependencies(conf2uedata generate_T) - add_dependencies(usim generate_T) - add_dependencies(nvram generate_T) -endif (${T_TRACER}) From 60ab0a89a2d1b1024efa7a6627dbfc682c88065e Mon Sep 17 00:00:00 2001 From: Robert Schmidt Date: Tue, 16 Sep 2025 09:07:41 +0200 Subject: [PATCH 48/97] Build all targets in Cluster PhySim build --- docker/Dockerfile.phySim.rhel9 | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/docker/Dockerfile.phySim.rhel9 b/docker/Dockerfile.phySim.rhel9 index c5faab2ec7..abb85f9586 100644 --- a/docker/Dockerfile.phySim.rhel9 +++ b/docker/Dockerfile.phySim.rhel9 @@ -36,9 +36,14 @@ RUN dnf install -y libasan libubsan #run build_oai to build the target image RUN /bin/sh oaienv && \ - cd cmake_targets && \ - mkdir -p log && \ - ./build_oai --phy_simulators --ninja --sanitize --build-tool-opt -k10 --noavx512 -c --cmake-opt -DCMAKE_C_FLAGS=-Werror --cmake-opt -DCMAKE_CXX_FLAGS=-Werror --cmake-opt -DCMAKE_RUNTIME_OUTPUT_DIRECTORY=/opt/oai-physim/bin + mkdir -p cmake_targets/ran_build/build && \ + cd cmake_targets/ran_build/build && \ + cmake3 -GNinja ../../.. \ + -DAVX512=OFF -DCMAKE_C_FLAGS=-Werror -DCMAKE_CXX_FLAGS=-Werror \ + -DENABLE_PHYSIM_TESTS=ON \ + -DSANITIZE_ADDRESS=ON -DSANITIZE_UNDEFINED=ON \ + -DCMAKE_RUNTIME_OUTPUT_DIRECTORY=/opt/oai-physim/bin && \ + ninja -k10 -j36 #start from scratch for target executable FROM registry.access.redhat.com/ubi9/ubi:latest AS oai-physim From 0fb8a09b86019d0b6d482ecbcaa438cfebefb1a2 Mon Sep 17 00:00:00 2001 From: alexjiao Date: Mon, 29 Sep 2025 17:08:16 +0800 Subject: [PATCH 49/97] Remove function get_nr_RSRP() and get_nr_rx_total_gain_dB() for cleanup The function get_nr_RSRP() and get_nr_rx_total_gain_dB() is not used anywhere in the code. --- openair1/PHY/NR_UE_ESTIMATION/nr_estimation.h | 2 -- .../PHY/NR_UE_ESTIMATION/nr_ue_measurements.c | 28 ------------------- 2 files changed, 30 deletions(-) diff --git a/openair1/PHY/NR_UE_ESTIMATION/nr_estimation.h b/openair1/PHY/NR_UE_ESTIMATION/nr_estimation.h index b327327113..b1bc3ca8e1 100644 --- a/openair1/PHY/NR_UE_ESTIMATION/nr_estimation.h +++ b/openair1/PHY/NR_UE_ESTIMATION/nr_estimation.h @@ -147,8 +147,6 @@ void nr_pdsch_ptrs_processing(PHY_VARS_NR_UE *ue, uint16_t rnti, NR_UE_DLSCH_t dlsch[2]); -float_t get_nr_RSRP(module_id_t Mod_id,uint8_t CC_id,uint8_t gNB_index); - int nr_sl_psbch_rsrp_measurements(sl_nr_ue_phy_params_t *sl_phy_params, NR_DL_FRAME_PARMS *fp, c16_t rxdataF[][fp->samples_per_slot_wCP], diff --git a/openair1/PHY/NR_UE_ESTIMATION/nr_ue_measurements.c b/openair1/PHY/NR_UE_ESTIMATION/nr_ue_measurements.c index 747a000c4d..ff706af688 100644 --- a/openair1/PHY/NR_UE_ESTIMATION/nr_ue_measurements.c +++ b/openair1/PHY/NR_UE_ESTIMATION/nr_ue_measurements.c @@ -46,34 +46,6 @@ //#define DEBUG_MEAS_UE //#define DEBUG_RANK_EST -uint32_t get_nr_rx_total_gain_dB (module_id_t Mod_id,uint8_t CC_id) -{ - - PHY_VARS_NR_UE *ue = PHY_vars_UE_g[Mod_id][CC_id]; - - if (ue) - return ue->rx_total_gain_dB; - - return 0xFFFFFFFF; -} - - -float_t get_nr_RSRP(module_id_t Mod_id,uint8_t CC_id,uint8_t gNB_index) -{ - - AssertFatal(PHY_vars_UE_g!=NULL,"PHY_vars_UE_g is null\n"); - AssertFatal(PHY_vars_UE_g[Mod_id]!=NULL,"PHY_vars_UE_g[%d] is null\n",Mod_id); - AssertFatal(PHY_vars_UE_g[Mod_id][CC_id]!=NULL,"PHY_vars_UE_g[%d][%d] is null\n",Mod_id,CC_id); - - PHY_VARS_NR_UE *ue = PHY_vars_UE_g[Mod_id][CC_id]; - - if (ue) - return (10*log10(ue->measurements.rsrp[gNB_index])- - get_nr_rx_total_gain_dB(Mod_id,0) - - 10*log10(20*12)); - return -140.0; -} - void nr_ue_measurements(PHY_VARS_NR_UE *ue, const UE_nr_rxtx_proc_t *proc, NR_UE_DLSCH_t *dlsch, From 7d70617a019eecfee636c55c73d53a4da2dc5e82 Mon Sep 17 00:00:00 2001 From: calvin_peyron Date: Tue, 2 Sep 2025 11:46:54 +0200 Subject: [PATCH 50/97] Fix documentation broken links --- doc/FEATURE_SET.md | 2 +- doc/environment-variables.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/FEATURE_SET.md b/doc/FEATURE_SET.md index 9faf2c3d3b..f759ddf145 100644 --- a/doc/FEATURE_SET.md +++ b/doc/FEATURE_SET.md @@ -25,7 +25,7 @@ The following features are valid for the gNB and the 5G-NR UE. * Static TDD - - Multi TDD pattern supported refer [TDD Configuration](NR_SA_Multi_TDD_Pattern.md) + - Multi TDD pattern supported refer [TDD Configuration](MAC/mac-usage.md) * Static FDD * Normal CP * Subcarrier spacings: 15 and 30kHz (FR1), 120kHz (FR2) diff --git a/doc/environment-variables.md b/doc/environment-variables.md index 9778665afe..76730294fa 100644 --- a/doc/environment-variables.md +++ b/doc/environment-variables.md @@ -9,7 +9,7 @@ OAI uses/supports a number of environment variables, documented in the following - `OAI_CONFIGMODULE`: can be used to pass the configuration file instead of `-O` - `OAI_GDBSTACKS`: if defined when hitting an assertion (`DevAssert()`, `AssertFatal()`, ...), OAI will load `gdb` and provide a backtrace for every thread - `OAI_RNGSEED`: overwrites any seed for random number generators (RNG) in simulators -- `OAI_THREADPOOLMEASUREMENTS`: path to a file to store thread pool debugging information, see the [thread pool documentation](..common/utils/threadPool/thread-pool.md) +- `OAI_THREADPOOLMEASUREMENTS`: path to a file to store thread pool debugging information, see the [thread pool documentation](../common/utils/threadPool/thread-pool.md) - `OPENAIR_DIR`: should point to the root directory of OpenAirInterface; some code relies on this to get a filename, e.g., BLER curves for L2sim channel emulation - `USIM_DIR`: directory to read/write USIM data in (4G) `usim` tool; if not defined, will use `PWD` (working directory) From 3f9dcee5fa8735c94e05ad013e17a116cbf19bb7 Mon Sep 17 00:00:00 2001 From: Laurent THOMAS Date: Tue, 2 Sep 2025 17:02:05 +0200 Subject: [PATCH 51/97] remove a dead function --- common/utils/mem/memory.c | 46 ----------------------------------- common/utils/mem/oai_memory.h | 2 -- 2 files changed, 48 deletions(-) diff --git a/common/utils/mem/memory.c b/common/utils/mem/memory.c index d4f09279aa..44b806dbe9 100644 --- a/common/utils/mem/memory.c +++ b/common/utils/mem/memory.c @@ -25,52 +25,6 @@ #include "common/utils/mem/oai_memory.h" #include "common/utils/LOG/log.h" -/**************************************************************************** - ** ** - ** Name: memory_get_path() ** - ** ** - ** Description: Gets the absolute path of the file where non-volatile ** - ** data are located ** - ** ** - ** Inputs: dirname: The directory where data file is located ** - ** filename: The name of the data file ** - ** Others: None ** - ** ** - ** Outputs: None ** - ** Return: The absolute path of the non-volatile data ** - ** file. The returned value is a dynamically ** - ** allocated octet string that needs to be ** - ** freed after usage. ** - ** Others: None ** - ** ** - ***************************************************************************/ -char* memory_get_path(const char* dirname, const char* filename) -{ - /* Get non-volatile data directory */ - const char* path = getenv(dirname); - - if (path == NULL) { - path = getenv(DEFAULT_NAS_PATH); - } - - if (path == NULL) { - path = "."; - } - - /* Append non-volatile data file name */ - size_t size = strlen(path) + strlen(filename) + 1; - char* data_filename = (char*)malloc(size + 1); - - if (data_filename != NULL) { - if (size != sprintf(data_filename, "%s/%s", path, filename)) { - free(data_filename); - return NULL; - } - } - - return data_filename; -} - char* memory_get_path_from_ueid(const char* dirname, const char* filename, int ueid) { /* Get non-volatile data directory */ diff --git a/common/utils/mem/oai_memory.h b/common/utils/mem/oai_memory.h index 311ad6f4d0..c09fe0b072 100644 --- a/common/utils/mem/oai_memory.h +++ b/common/utils/mem/oai_memory.h @@ -35,8 +35,6 @@ typedef unsigned char uint8_t; //----------------------------------------------------------------------------- -char* memory_get_path(const char* dirname, const char* filename); - char* memory_get_path_from_ueid(const char* dirname, const char* filename, int ueid); int memory_read(const char* datafile, void* data, size_t size); From 63397ac592d8b552d7e868f8bd37adaac084cd8e Mon Sep 17 00:00:00 2001 From: calvin_peyron Date: Wed, 17 Sep 2025 11:23:21 +0200 Subject: [PATCH 52/97] Remove RFSIMULATOR environment variable --- radio/rfsimulator/simulator.c | 8 -------- 1 file changed, 8 deletions(-) diff --git a/radio/rfsimulator/simulator.c b/radio/rfsimulator/simulator.c index 1a5c94f698..13a414fed6 100644 --- a/radio/rfsimulator/simulator.c +++ b/radio/rfsimulator/simulator.c @@ -358,14 +358,6 @@ static void rfsimulator_readconfig(rfsimulator_state_t *rfsimulator) { } } - /* for compatibility keep environment variable usage */ - if (getenv("RFSIMULATOR") != NULL) { - rfsimulator->ip = getenv("RFSIMULATOR"); - LOG_W(HW, "The RFSIMULATOR environment variable is deprecated and support will be removed in the future. Instead, add parameter --rfsimulator.serveraddr %s to set the server address. Note: the default is \"server\"; for the gNB/eNB, you don't have to set any configuration.\n", rfsimulator->ip); - LOG_I(HW, "Remove RFSIMULATOR environment variable to get rid of this message and the sleep.\n"); - sleep(10); - } - if ( strncasecmp(rfsimulator->ip,"enb",3) == 0 || strncasecmp(rfsimulator->ip,"server",3) == 0 ) rfsimulator->role = SIMU_ROLE_SERVER; From 1c5af6065c83bb25bdbe553e6fe456f41b9c4e29 Mon Sep 17 00:00:00 2001 From: alexjiao Date: Fri, 13 Jun 2025 20:39:08 +0800 Subject: [PATCH 53/97] Handle UCI on PUSCH procedures at PHY Get beta offset, alpha scaling and other parameters to calculate rate matching information for UCI on PUSCH, then use the rate matching info to encode UCI and re-encode PUSCH, call nr_uci_on_pusch to get final multiplexed codeword for scrambling. --- openair1/PHY/NR_UE_TRANSPORT/nr_ulsch_ue.c | 71 ++++++++++++++++++++-- 1 file changed, 65 insertions(+), 6 deletions(-) diff --git a/openair1/PHY/NR_UE_TRANSPORT/nr_ulsch_ue.c b/openair1/PHY/NR_UE_TRANSPORT/nr_ulsch_ue.c index 561d357363..41b935221b 100644 --- a/openair1/PHY/NR_UE_TRANSPORT/nr_ulsch_ue.c +++ b/openair1/PHY/NR_UE_TRANSPORT/nr_ulsch_ue.c @@ -1034,8 +1034,11 @@ void nr_ue_ulsch_procedures(PHY_VARS_NR_UE *UE, LOG_D(PHY, "nr_ue_ulsch_procedures_slot hard_id %d %d.%d prepare for coding\n", harq_pid, frame, slot); NR_UE_ULSCH_t *ulsch_ue = &phy_data->ulsch; + NR_UE_PUCCH *pucch_ue = &phy_data->pucch_vars; NR_UL_UE_HARQ_t *harq_process_ul_ue = &UE->ul_harq_processes[harq_pid]; const nfapi_nr_ue_pusch_pdu_t *pusch_pdu = &ulsch_ue->pusch_pdu; + const fapi_nr_ul_config_pucch_pdu *pucch_pdu = &pucch_ue->pucch_pdu[0]; + uci_on_pusch_bit_type_t *uci_mapping_template = NULL; uint16_t number_dmrs_symbols = 0; @@ -1076,6 +1079,11 @@ void nr_ue_ulsch_procedures(PHY_VARS_NR_UE *UE, G[pusch_id] = nr_get_G(nb_rb, number_of_symbols, nb_dmrs_re_per_rb, number_dmrs_symbols, unav_res, mod_order, Nl); + // Capture the initial total PUSCH bits. This is the total_codeword_length for mapping. + unsigned int G_initial_total_pusch_bits = G[pusch_id]; + + uci_on_pusch_bit_type_t template_buffer[G_initial_total_pusch_bits]; + ws_trace_t tmp = {.nr = true, .direction = DIRECTION_UPLINK, .pdu_buffer = harq_process_ul_ue->payload_AB, @@ -1092,9 +1100,18 @@ void nr_ue_ulsch_procedures(PHY_VARS_NR_UE *UE, /////////////////////////ULSCH coding///////////////////////// - int enc_ret = nr_ulsch_pre_encoding(UE, &phy_data->ulsch, frame, slot, G, 1, ULSCH_ids); - enc_ret += nr_ulsch_encoding(UE, &phy_data->ulsch, frame, slot, G, 1, ULSCH_ids, number_dmrs_symbols); - if (enc_ret != 0) { + rate_match_info_uci_t rm_info = {0}; + const uint8_t nl_qm = Nl * mod_order; // product of number of layers and modulation order + if(nr_ulsch_pre_encoding(UE, &phy_data->ulsch, frame, slot, G, 1, ULSCH_ids) != 0) { + LOG_E(PHY, "Error pre-encoding\n"); + return; + } + + if (pusch_pdu->pusch_uci.harq_ack_bit_length != 0) { + rm_info = calc_rate_match_info_uci(pusch_pdu, harq_process_ul_ue, nl_qm, &G[pusch_id]); + } + + if (nr_ulsch_encoding(UE, &phy_data->ulsch, frame, slot, G, 1, ULSCH_ids, number_dmrs_symbols) == -1) { stop_meas_nr_ue_phy(UE, PUSCH_PROC_STATS); return; } @@ -1107,9 +1124,51 @@ void nr_ue_ulsch_procedures(PHY_VARS_NR_UE *UE, int N_PRB_oh = 0; // higher layer (RRC) parameter xOverhead in PUSCH-ServingCellConfig - AssertFatal(pusch_pdu->pusch_uci.harq_ack_bit_length == 0 && pusch_pdu->pusch_uci.csi_part1_bit_length == 0 - && pusch_pdu->pusch_uci.csi_part2_bit_length == 0, - "UCI on PUSCH not supported at PHY\n"); + if (pusch_pdu->pusch_uci.harq_ack_bit_length != 0) { + LOG_D(PHY, "[UCI_ON_PUSCH] Original HARQ-ACK bit length: %u\n", pusch_pdu->pusch_uci.harq_ack_bit_length); + LOG_D(PHY, "[UCI_ON_PUSCH] Initial G: %u\n", G_initial_total_pusch_bits); + // b is the block of bits transmitted on the physical channel after payload coding + uint64_t b[16] = {0}; // limit to 1024-bit encoded length + + if (pucch_pdu == NULL) { + LOG_E(PHY, "nr_ue_ulsch_procedures: pucch_pdu is NULL but HARQ-ACK is present. Cannot proceed with UCI encoding.\n"); + stop_meas_nr_ue_phy(UE, PUSCH_PROC_STATS); + return; + } + + nr_uci_encoding(pusch_pdu->pusch_uci.harq_payload, + pusch_pdu->pusch_uci.harq_ack_bit_length, + pucch_pdu->prb_size, + true, + rm_info.E_uci_ACK, + mod_order, + &b[0]); + + LOG_D(PHY, + "[UCI_ON_PUSCH] G_ulsch=%u (updated G[pusch_id]), G_ack=%u (M_bit), G_ack_rvd=%u, total_len=%u " + "(G_initial_total_pusch_bits).\n", + G[pusch_id], + rm_info.E_uci_ACK, + rm_info.E_uci_ACK_rvd, + G_initial_total_pusch_bits); + + uint8_t temp_codeword[G_initial_total_pusch_bits]; + nr_data_control_mapping(pusch_pdu, + template_buffer, + G[pusch_id], + rm_info.E_uci_ACK, + rm_info.E_uci_ACK_rvd, + temp_codeword, + G_initial_total_pusch_bits, + harq_process_ul_ue->f, + b); + + memcpy(harq_process_ul_ue->f, temp_codeword, G_initial_total_pusch_bits); + uci_mapping_template = template_buffer; + } + + AssertFatal(pusch_pdu->pusch_uci.csi_part1_bit_length == 0 && pusch_pdu->pusch_uci.csi_part2_bit_length == 0, + "UCI (CSI) on PUSCH not supported at PHY\n"); uint16_t start_rb = pusch_pdu->rb_start; uint16_t start_sc = frame_parms->first_carrier_offset + (start_rb + pusch_pdu->bwp_start) * NR_NB_SC_PER_RB; From b69b65ba6676d9fa84f4aa564322033724b1fc46 Mon Sep 17 00:00:00 2001 From: alexjiao Date: Fri, 13 Jun 2025 20:46:00 +0800 Subject: [PATCH 54/97] Handle UCI on PUSCH scrambling When the codeword has UCI multiplexed with data, we need to handle UCI scrambling according to 38.211 section 6.3.1.1. Especially when O_ACK <= 2 there are x and y placeholder bits for UCI. --- openair1/PHY/NR_UE_TRANSPORT/nr_ulsch_ue.c | 106 +++++++++++++++++---- 1 file changed, 85 insertions(+), 21 deletions(-) diff --git a/openair1/PHY/NR_UE_TRANSPORT/nr_ulsch_ue.c b/openair1/PHY/NR_UE_TRANSPORT/nr_ulsch_ue.c index 41b935221b..08cc881180 100644 --- a/openair1/PHY/NR_UE_TRANSPORT/nr_ulsch_ue.c +++ b/openair1/PHY/NR_UE_TRANSPORT/nr_ulsch_ue.c @@ -62,32 +62,84 @@ //extern int32_t uplink_counter; -void nr_pusch_codeword_scrambling_uci(uint8_t *in, uint32_t size, uint32_t Nid, uint32_t n_RNTI, uint32_t* out) +static void nr_pusch_codeword_scrambling_uci(uint8_t *in, + uint32_t size, + uint32_t Nid, + uint32_t n_RNTI, + const uci_on_pusch_bit_type_t *template, + uint32_t *out) { uint32_t *seq = gold_cache((n_RNTI << 15) + Nid, (size + 31) / 32); - for (int i=0; i> 31; + uint32_t num_words = (size + 31) / 32; + + // Step 1: Initial general scrambling + // First convert unpacked input to bit-packed words + uint32_t in_words[num_words]; + memset(in_words, 0, num_words * sizeof(uint32_t)); + + for (uint32_t i = 0; i < size; i++) { + uint32_t word_idx = i / 32; + uint32_t bit_idx = i % 32; + if (in[i] & 1) { + in_words[word_idx] |= (1U << bit_idx); + } + } + + for (uint32_t i = 0; i < num_words; i++) { + out[i] = in_words[i] ^ seq[i]; + } + + for (uint32_t i = 0; i < size; i++) { + if (template[i] == BIT_TYPE_ACK) { + // Step 2: Overwrite/Correct positions for ACK-only bits when O_ACK > 2 + uint32_t pos = i; + uint32_t idx = pos / 32; + uint32_t b_idx = pos % 32; + + // Clear the bit that was set by the initial general scrambling + out[idx] &= ~(1U << b_idx); + + uint32_t ack_bit_value = in[pos] & 1; + uint32_t scrambling_bit_for_ack = (seq[idx] >> b_idx) & 1; + out[idx] |= ((ack_bit_value ^ scrambling_bit_for_ack) << b_idx); + } else if (template[i] == BIT_TYPE_ACK_ULSCH) { + // Step 3: Overwrite/Correct positions for UCI bits including placeholders X, Y when O_ACK <= 2 + uint32_t pos = i; + uint32_t idx = pos / 32; + uint32_t b_idx = pos % 32; + + out[idx] &= ~(1U << b_idx); + + if (in[pos] == NR_PUSCH_y) { + if (b_idx > 0) { + // Y depends on the final value of the previous bit in the same word. + // This previous bit could be an ACK (already corrected) or ULSCH (from initial scramble). + out[idx] |= ((out[idx] >> (b_idx - 1)) & 1) << b_idx; + } else if (idx > 0) { + // Y depends on the last bit of the previous word. + out[idx] |= ((out[idx - 1] >> 31) & 1); + } + } else if (in[pos] == NR_PUSCH_x) { + out[idx] |= (1U << b_idx); + } else { + uint32_t ack_bit_value = in[pos] & 1; + uint32_t scrambling_bit_for_ack = (seq[idx] >> b_idx) & 1; + out[idx] |= ((ack_bit_value ^ scrambling_bit_for_ack) << b_idx); } } - else - out[idx] ^= (((in[i]) & 1) ^ ((seq[idx] >> b_idx) & 1)) << b_idx; - //printf("i %d b_idx %d in %d s 0x%08x out 0x%08x\n", i, b_idx, in[i], s, *out); } } -void nr_pusch_codeword_scrambling(uint8_t *in, uint32_t size, uint32_t Nid, uint32_t n_RNTI, bool uci_on_pusch, uint32_t* out) +void nr_pusch_codeword_scrambling(uint8_t *in, + uint32_t size, + uint32_t Nid, + uint32_t n_RNTI, + bool uci_on_pusch, + const uci_on_pusch_bit_type_t *template, + uint32_t *out) { if (uci_on_pusch) - nr_pusch_codeword_scrambling_uci(in, size, Nid, n_RNTI, out); + nr_pusch_codeword_scrambling_uci(in, size, Nid, n_RNTI, template, out); else nr_codeword_scrambling(in, size, 0, Nid, n_RNTI, out); } @@ -1201,16 +1253,28 @@ void nr_ue_ulsch_procedures(PHY_VARS_NR_UE *UE, /////////////////////////ULSCH scrambling///////////////////////// - uint32_t available_bits = G[pusch_id]; - // +1 because size can be not modulo 4 - uint32_t scrambled_output[available_bits / (8 * sizeof(uint32_t)) + 1]; + uint32_t available_bits; + bool is_uci_on_pusch = (pusch_pdu->pusch_uci.harq_ack_bit_length != 0); + + if (is_uci_on_pusch) { + // UCI on PUSCH is present, so available bits are the total codeword length + available_bits = G_initial_total_pusch_bits; + } else { + // No UCI on PUSCH, so available bits are the initial G value + available_bits = G[pusch_id]; + } + + // +1 because size can be not modulo 4 for the uint32_t array + uint32_t scrambled_output_len_u32 = (available_bits + 31) / 32; // Round up to nearest uint32_t count + uint32_t scrambled_output[scrambled_output_len_u32]; memset(scrambled_output, 0, sizeof(scrambled_output)); nr_pusch_codeword_scrambling(harq_process_ul_ue->f, available_bits, pusch_pdu->data_scrambling_id, rnti, - false, + is_uci_on_pusch, + uci_mapping_template, scrambled_output); #if T_TRACER if (T_ACTIVE(T_UE_PHY_UL_SCRAMBLED_TX_BITS)) { From ac76c6e27a533ff4289044a97843805e643a5f2c Mon Sep 17 00:00:00 2001 From: alexjiao Date: Sun, 27 Jul 2025 19:40:09 +0800 Subject: [PATCH 55/97] Add functions to calculate M_bit for PUCCH format 2/3/4 Add functions to calculate PUCCH format 2/3/4 rate matching output sequence length (M_bit), according to 3GPP TS 38.212 Table 6.3.1.4-1. --- openair1/PHY/NR_UE_TRANSPORT/pucch_nr.c | 67 +++++++++++++++++-------- 1 file changed, 46 insertions(+), 21 deletions(-) diff --git a/openair1/PHY/NR_UE_TRANSPORT/pucch_nr.c b/openair1/PHY/NR_UE_TRANSPORT/pucch_nr.c index b4c65cf28e..b6b20cefaf 100644 --- a/openair1/PHY/NR_UE_TRANSPORT/pucch_nr.c +++ b/openair1/PHY/NR_UE_TRANSPORT/pucch_nr.c @@ -485,6 +485,45 @@ void nr_generate_pucch1(const PHY_VARS_NR_UE *ue, } } +// Calculate number of DMRS symbols for PUCCH formats 3 and 4 +static uint8_t nr_pucch_get_dmrs_symbols(uint8_t nrofSymbols, uint8_t add_dmrs) +{ + if (nrofSymbols == 4) { + return 1; + } else if (nrofSymbols > 4 && nrofSymbols <= 9) { + return 2; + } else if (nrofSymbols > 9) { + return (add_dmrs == 0) ? 2 : 4; + } + return 0; +} + +// Calculate PUCCH format 2/3/4 rate matching output sequence length according to TS 38.212 Table 6.3.1.4-1 +static uint16_t nr_pucch_output_sequence_length(uint8_t format_type, + uint8_t nrofSymbols, + uint16_t nrofPRB, + uint8_t n_SF_PUCCH_s, + uint8_t is_pi_over_2_bpsk_enabled, + uint8_t add_dmrs) +{ + uint16_t M_bit = 0; + + if (format_type == 2) { + M_bit = 16 * nrofSymbols * nrofPRB; + } else if (format_type == 3) { + uint16_t E_init = (is_pi_over_2_bpsk_enabled == 0) ? 24 : 12; + uint8_t num_dmrs_symbols = nr_pucch_get_dmrs_symbols(nrofSymbols, add_dmrs); + M_bit = E_init * (nrofSymbols - num_dmrs_symbols) * nrofPRB / n_SF_PUCCH_s; + } else if (format_type == 4) { + nrofPRB = 1; + uint16_t E_init = (is_pi_over_2_bpsk_enabled == 0) ? 24 : 12; + uint8_t num_dmrs_symbols = nr_pucch_get_dmrs_symbols(nrofSymbols, add_dmrs); + M_bit = E_init * (nrofSymbols - num_dmrs_symbols) * nrofPRB / n_SF_PUCCH_s; + } + + return M_bit; +} + static inline void nr_pucch2_3_4_scrambling(uint16_t M_bit, uint16_t rnti, uint16_t n_id, uint64_t *B64, uint8_t *btilde) { // c_init=nRNTI*2^15+n_id according to TS 38.211 Subclause 6.3.2.6.1 @@ -726,7 +765,7 @@ void nr_generate_pucch2(const PHY_VARS_NR_UE *ue, // b is the block of bits transmitted on the physical channel after payload coding uint64_t b[16] = {0}; // limit to 1024-bit encoded length // M_bit is the number of bits of block b (payload after encoding) - uint16_t M_bit = 16 * pucch_pdu->nr_of_symbols * pucch_pdu->prb_size; + uint16_t M_bit = nr_pucch_output_sequence_length(pucch_pdu->format_type, pucch_pdu->nr_of_symbols, pucch_pdu->prb_size, 0, 0, 0); nr_uci_encoding(pucch_pdu->payload, pucch_pdu->n_bit, pucch_pdu->prb_size, false, M_bit, 0, &b[0]); /* * Implementing TS 38.211 @@ -938,26 +977,12 @@ void nr_generate_pucch3_4(const PHY_VARS_NR_UE *ue, uint16_t startingPRB = pucch_pdu->prb_start + pucch_pdu->bwp_start; uint8_t add_dmrs = pucch_pdu->add_dmrs_flag; - uint16_t E_init = (is_pi_over_2_bpsk_enabled == 0) ? 24 : 12; - - if (pucch_pdu->format_type == 3) { - if (nrofSymbols == 4) { - M_bit = (intraSlotFrequencyHopping == 0) ? (E_init * (nrofSymbols - 1) * nrofPRB) : (E_init * (nrofSymbols - 1) * nrofPRB); - } else if (nrofSymbols > 4) { - M_bit = E_init * (nrofSymbols - 2) * nrofPRB; - } else if (nrofSymbols > 9) { - M_bit = (add_dmrs == 0) ? (E_init * (nrofSymbols - 2) * nrofPRB) : (E_init * (nrofSymbols - 4) * nrofPRB); - } - } else if (pucch_pdu->format_type == 4) { - if (nrofSymbols == 4) { - M_bit = (intraSlotFrequencyHopping == 0) ? (E_init * (nrofSymbols - 1) / n_SF_PUCCH_s) - : (E_init * (nrofSymbols - 1) / n_SF_PUCCH_s); - } else if (nrofSymbols > 4) { - M_bit = E_init * (nrofSymbols - 2) / n_SF_PUCCH_s; - } else if (nrofSymbols > 9) { - M_bit = (add_dmrs == 0) ? (E_init * (nrofSymbols - 2) / n_SF_PUCCH_s) : (E_init * (nrofSymbols - 4) / n_SF_PUCCH_s); - } - } + M_bit = nr_pucch_output_sequence_length(pucch_pdu->format_type, + nrofSymbols, + nrofPRB, + n_SF_PUCCH_s, + is_pi_over_2_bpsk_enabled, + add_dmrs); nr_uci_encoding(pucch_pdu->payload, pucch_pdu->n_bit, nrofPRB, false, M_bit, 0, b); /* From 8de13fab97e7c2132ddb22451e3d026377f40e7e Mon Sep 17 00:00:00 2001 From: Sakthivel Velumani Date: Fri, 15 Aug 2025 23:56:39 -0400 Subject: [PATCH 56/97] Simplify code --- openair1/PHY/NR_REFSIG/dmrs_nr.c | 4 + openair1/PHY/NR_REFSIG/dmrs_nr.h | 2 + openair1/PHY/NR_UE_TRANSPORT/nr_ulsch_ue.c | 145 +++++++------------- openair1/SCHED_NR_UE/phy_procedures_nr_ue.c | 8 +- 4 files changed, 53 insertions(+), 106 deletions(-) diff --git a/openair1/PHY/NR_REFSIG/dmrs_nr.c b/openair1/PHY/NR_REFSIG/dmrs_nr.c index a5a55d685a..7dba224cdb 100644 --- a/openair1/PHY/NR_REFSIG/dmrs_nr.c +++ b/openair1/PHY/NR_REFSIG/dmrs_nr.c @@ -306,6 +306,10 @@ int8_t get_next_dmrs_symbol_in_slot(uint16_t ul_dmrs_symb_pos, uint8_t counter, return -1; } +int8_t get_num_dmrs_re_per_rb(const uint8_t dmrs_type, const uint8_t num_cdm_grp_no_data) +{ + return (dmrs_type == NFAPI_NR_DMRS_TYPE1 ? 6 * num_cdm_grp_no_data : 4 * num_cdm_grp_no_data); +} /* return the position of valid dmrs symbol in a slot for channel compensation */ int8_t get_valid_dmrs_idx_for_channel_est(uint16_t dmrs_symb_pos, uint8_t counter) diff --git a/openair1/PHY/NR_REFSIG/dmrs_nr.h b/openair1/PHY/NR_REFSIG/dmrs_nr.h index 97d61993ce..5f17478874 100644 --- a/openair1/PHY/NR_REFSIG/dmrs_nr.h +++ b/openair1/PHY/NR_REFSIG/dmrs_nr.h @@ -71,6 +71,8 @@ void nr_chest_time_domain_avg(NR_DL_FRAME_PARMS *frame_parms, uint16_t dmrs_bitmap, uint16_t num_rbs); +int8_t get_num_dmrs_re_per_rb(const uint8_t dmrs_type, const uint8_t num_cdm_grp_no_data); + static inline uint8_t is_dmrs_symbol(uint8_t l, uint16_t dmrsSymbMask) { DevAssert(l < 32); diff --git a/openair1/PHY/NR_UE_TRANSPORT/nr_ulsch_ue.c b/openair1/PHY/NR_UE_TRANSPORT/nr_ulsch_ue.c index 08cc881180..d48109440d 100644 --- a/openair1/PHY/NR_UE_TRANSPORT/nr_ulsch_ue.c +++ b/openair1/PHY/NR_UE_TRANSPORT/nr_ulsch_ue.c @@ -89,28 +89,17 @@ static void nr_pusch_codeword_scrambling_uci(uint8_t *in, out[i] = in_words[i] ^ seq[i]; } + // According to 38.211 6.3.1.1 for (uint32_t i = 0; i < size; i++) { - if (template[i] == BIT_TYPE_ACK) { - // Step 2: Overwrite/Correct positions for ACK-only bits when O_ACK > 2 + if (template[i] == BIT_TYPE_ACK_ULSCH) { + // Step 2: Overwrite/Correct positions for UCI bits including placeholders X, Y when O_ACK <= 2 uint32_t pos = i; uint32_t idx = pos / 32; uint32_t b_idx = pos % 32; - // Clear the bit that was set by the initial general scrambling - out[idx] &= ~(1U << b_idx); - - uint32_t ack_bit_value = in[pos] & 1; - uint32_t scrambling_bit_for_ack = (seq[idx] >> b_idx) & 1; - out[idx] |= ((ack_bit_value ^ scrambling_bit_for_ack) << b_idx); - } else if (template[i] == BIT_TYPE_ACK_ULSCH) { - // Step 3: Overwrite/Correct positions for UCI bits including placeholders X, Y when O_ACK <= 2 - uint32_t pos = i; - uint32_t idx = pos / 32; - uint32_t b_idx = pos % 32; - - out[idx] &= ~(1U << b_idx); - if (in[pos] == NR_PUSCH_y) { + // Clear bit + out[idx] &= ~(1U << b_idx); if (b_idx > 0) { // Y depends on the final value of the previous bit in the same word. // This previous bit could be an ACK (already corrected) or ULSCH (from initial scramble). @@ -121,10 +110,6 @@ static void nr_pusch_codeword_scrambling_uci(uint8_t *in, } } else if (in[pos] == NR_PUSCH_x) { out[idx] |= (1U << b_idx); - } else { - uint32_t ack_bit_value = in[pos] & 1; - uint32_t scrambling_bit_for_ack = (seq[idx] >> b_idx) & 1; - out[idx] |= ((ack_bit_value ^ scrambling_bit_for_ack) << b_idx); } } } @@ -139,8 +124,10 @@ void nr_pusch_codeword_scrambling(uint8_t *in, uint32_t *out) { if (uci_on_pusch) + // in buffer is in byte-packed format nr_pusch_codeword_scrambling_uci(in, size, Nid, n_RNTI, template, out); else + // in buffer is in bit-packed format nr_codeword_scrambling(in, size, 0, Nid, n_RNTI, out); } @@ -701,62 +688,31 @@ static rate_match_info_uci_t calc_rate_match_info_uci(const nfapi_nr_ue_pusch_pd static int initialize_mapping_resources(const nfapi_nr_ue_pusch_pdu_t *pusch_pdu, uint32_t *m_ulsch_initial, - uint32_t *m_ulsch_current, - uint32_t *m_uci_current, - bool *is_dmrs_symbol_flags, - uint8_t *dmrs_symbol_set_relative, - uint8_t *num_dmrs_symbols_in_set_relative) + uint32_t *m_uci_current) { - if (!pusch_pdu || !m_ulsch_initial || !m_ulsch_current || !m_uci_current || !is_dmrs_symbol_flags || !dmrs_symbol_set_relative - || !num_dmrs_symbols_in_set_relative) + if (!pusch_pdu || !m_ulsch_initial || !m_uci_current) return -1; - uint8_t n_pusch_sym_all = pusch_pdu->nr_of_symbols; - uint16_t ul_dmrs_symb_pos = pusch_pdu->ul_dmrs_symb_pos; - uint8_t dmrs_type = pusch_pdu->dmrs_config_type; - uint8_t cdm_grps_no_data = pusch_pdu->num_dmrs_cdm_grps_no_data; - uint32_t res_per_symbol_non_dmrs = pusch_pdu->rb_size * 12; - - *num_dmrs_symbols_in_set_relative = 0; + const uint8_t n_pusch_sym_all = pusch_pdu->nr_of_symbols; + const uint16_t ul_dmrs_symb_pos = pusch_pdu->ul_dmrs_symb_pos; + const uint8_t dmrs_type = pusch_pdu->dmrs_config_type; + const uint8_t cdm_grps_no_data = pusch_pdu->num_dmrs_cdm_grps_no_data; + const uint32_t res_per_symbol_non_dmrs = pusch_pdu->rb_size * NR_NB_SC_PER_RB; + const uint32_t data_re_on_dmrs_sym_per_prb = NR_NB_SC_PER_RB - get_num_dmrs_re_per_rb(dmrs_type, cdm_grps_no_data); // Initialize resources per symbol for ULSCH and UCI for (uint8_t i = 0; i < n_pusch_sym_all; i++) { uint8_t absolute_symbol_idx = pusch_pdu->start_symbol_index + i; if ((ul_dmrs_symb_pos >> absolute_symbol_idx) & 0x01) { - is_dmrs_symbol_flags[i] = true; - if (*num_dmrs_symbols_in_set_relative < 14) { - dmrs_symbol_set_relative[(*num_dmrs_symbols_in_set_relative)++] = i; - } - // Calculate available data REs on DMRS symbols based on DMRS configuration - uint32_t data_re_on_dmrs_sym_per_prb = 0; - - if (dmrs_type == 0) { // Type 1 - if (cdm_grps_no_data == 1) { - data_re_on_dmrs_sym_per_prb = 6; - } else { - data_re_on_dmrs_sym_per_prb = 0; - } - } else { // Type 2 - if (cdm_grps_no_data == 1) { - data_re_on_dmrs_sym_per_prb = 4; - } else if (cdm_grps_no_data == 2) { - data_re_on_dmrs_sym_per_prb = 2; - } else { - data_re_on_dmrs_sym_per_prb = 0; - } - } m_ulsch_initial[i] = pusch_pdu->rb_size * data_re_on_dmrs_sym_per_prb; - m_ulsch_current[i] = m_ulsch_initial[i]; m_uci_current[i] = 0; // UCI is not mapped on DMRS symbols } else { // Not a DMRS symbol - is_dmrs_symbol_flags[i] = false; m_ulsch_initial[i] = res_per_symbol_non_dmrs; - m_ulsch_current[i] = res_per_symbol_non_dmrs; m_uci_current[i] = res_per_symbol_non_dmrs; } } @@ -764,35 +720,31 @@ static int initialize_mapping_resources(const nfapi_nr_ue_pusch_pdu_t *pusch_pdu return 0; } -static uint8_t find_first_uci_symbol(uint8_t n_pusch_sym_all, - const bool *is_dmrs_symbol_flags, - uint8_t *dmrs_symbol_set_relative, - uint32_t num_dmrs_symbols_in_set_relative) +static void get_first_uci_symbol(const uint8_t start_symbol, + const uint8_t num_symbols, + const uint16_t dmrs_map, + uint8_t *first_non_dmrs_sym, + uint8_t *dmrs_p1) { - uint8_t l1_c = 0; - - // Find first non-DMRS symbol - for (uint8_t i = 0; i < n_pusch_sym_all; i++) { - if (!is_dmrs_symbol_flags[i]) { - l1_c = i; + // First non-DMRS symbol + const uint16_t last_sym = start_symbol + num_symbols; + for (uint_fast8_t s = start_symbol; s < last_sym; s++) { + if (!is_dmrs_symbol(s, dmrs_map)) { + *first_non_dmrs_sym = s; break; } } - // If there are DMRS symbols, try to find a symbol after the first DMRS - if (num_dmrs_symbols_in_set_relative > 0) { - uint8_t first_dmrs_idx = dmrs_symbol_set_relative[0]; - if (l1_c <= first_dmrs_idx) { - for (uint8_t i = first_dmrs_idx + 1; i < n_pusch_sym_all; i++) { - if (!is_dmrs_symbol_flags[i]) { - l1_c = i; - break; - } - } - } + // Symbol after first consequtive DMRS symbol + const uint8_t first_dmrs_sym = get_next_dmrs_symbol_in_slot(dmrs_map, start_symbol, last_sym); + *dmrs_p1 = first_dmrs_sym + 1; + while (is_dmrs_symbol(*dmrs_p1, dmrs_map) && *dmrs_p1 < last_sym) { + (*dmrs_p1)++; } - return l1_c; + // Return relative symbol idx + *first_non_dmrs_sym -= start_symbol; + *dmrs_p1 -= start_symbol; } /* @@ -1013,34 +965,29 @@ static uci_on_pusch_bit_type_t *nr_data_control_mapping(const nfapi_nr_ue_pusch_ if (!pusch_pdu || !codeword || codeword_len == 0 || !template) return NULL; const uint8_t n_symbols = pusch_pdu->nr_of_symbols; - if (n_symbols == 0 || n_symbols > 14) + if (n_symbols == 0 || n_symbols > NR_NUMBER_OF_SYMBOLS_PER_SLOT) return NULL; - uint32_t m_ulsch_initial[14] = {0}; - uint32_t m_ulsch_current[14] = {0}; - uint32_t m_uci_current[14] = {0}; // This holds RE counts, not bit counts - bool is_dmrs_symbol_flags[14] = {0}; - uint8_t dmrs_symbol_set_relative[14] = {0}; - uint8_t num_dmrs_symbols_in_set_relative = 0; + uint32_t m_ulsch_initial[NR_NUMBER_OF_SYMBOLS_PER_SLOT] = {0}; + uint32_t m_uci_current[NR_NUMBER_OF_SYMBOLS_PER_SLOT] = {0}; // This holds RE counts, not bit counts - if (initialize_mapping_resources(pusch_pdu, - m_ulsch_initial, - m_ulsch_current, - m_uci_current, - is_dmrs_symbol_flags, - dmrs_symbol_set_relative, - &num_dmrs_symbols_in_set_relative) - != 0) { + if (initialize_mapping_resources(pusch_pdu, m_ulsch_initial, m_uci_current) != 0) { LOG_E(PHY, "Failed to initialize mapping resources\n"); return NULL; } - uint8_t l1_c = find_first_uci_symbol(n_symbols, is_dmrs_symbol_flags, dmrs_symbol_set_relative, num_dmrs_symbols_in_set_relative); + uint8_t first_non_dmrs_sym = 0; + uint8_t l1_c = 0; + get_first_uci_symbol(pusch_pdu->start_symbol_index, + pusch_pdu->nr_of_symbols, + pusch_pdu->ul_dmrs_symb_pos, + &first_non_dmrs_sym, + &l1_c); memset(template, 0, codeword_len * sizeof(uci_on_pusch_bit_type_t)); - uint32_t positions_by_sym[14][MAX_UCI_CODED_BITS] = {0}; - uint32_t count_by_sym[14] = {0}; + uint32_t positions_by_sym[NR_NUMBER_OF_SYMBOLS_PER_SLOT][MAX_UCI_CODED_BITS] = {0}; + uint32_t count_by_sym[NR_NUMBER_OF_SYMBOLS_PER_SLOT] = {0}; if (G_ack_rvd > 0) { build_template_reserve_ack(template, diff --git a/openair1/SCHED_NR_UE/phy_procedures_nr_ue.c b/openair1/SCHED_NR_UE/phy_procedures_nr_ue.c index ce398d9a7a..75f73b0e8c 100644 --- a/openair1/SCHED_NR_UE/phy_procedures_nr_ue.c +++ b/openair1/SCHED_NR_UE/phy_procedures_nr_ue.c @@ -1214,13 +1214,7 @@ void pdsch_processing(PHY_VARS_NR_UE *ue, const UE_nr_rxtx_proc_t *proc, nr_phy_ } VCD_SIGNAL_DUMPER_DUMP_FUNCTION_BY_NAME(VCD_SIGNAL_DUMPER_FUNCTIONS_UE_SLOT_FEP_PDSCH, VCD_FUNCTION_OUT); - uint8_t nb_re_dmrs; - if (dlsch_config->dmrsConfigType == NFAPI_NR_DMRS_TYPE1) { - nb_re_dmrs = 6 * dlsch_config->n_dmrs_cdm_groups; - } - else { - nb_re_dmrs = 4 * dlsch_config->n_dmrs_cdm_groups; - } + const uint8_t nb_re_dmrs = get_num_dmrs_re_per_rb(dlsch_config->dmrsConfigType, dlsch_config->n_dmrs_cdm_groups); uint16_t dmrs_len = get_num_dmrs(dlsch_config->dlDmrsSymbPos); uint32_t unav_res = 0; if(dlsch_config->pduBitmap & 0x1) { From b2174cb8e127da301650f16b0e404852d0bf2c77 Mon Sep 17 00:00:00 2001 From: Sakthivel Velumani Date: Sat, 16 Aug 2025 12:46:11 -0400 Subject: [PATCH 57/97] Add processing stats for UCI mapping on PUSCH --- openair1/PHY/NR_UE_TRANSPORT/nr_ulsch_ue.c | 3 ++- openair1/PHY/nr_phy_common/inc/nr_ue_phy_meas.h | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/openair1/PHY/NR_UE_TRANSPORT/nr_ulsch_ue.c b/openair1/PHY/NR_UE_TRANSPORT/nr_ulsch_ue.c index d48109440d..7fca035a27 100644 --- a/openair1/PHY/NR_UE_TRANSPORT/nr_ulsch_ue.c +++ b/openair1/PHY/NR_UE_TRANSPORT/nr_ulsch_ue.c @@ -1152,6 +1152,7 @@ void nr_ue_ulsch_procedures(PHY_VARS_NR_UE *UE, G_initial_total_pusch_bits); uint8_t temp_codeword[G_initial_total_pusch_bits]; + start_meas_nr_ue_phy(UE, UCI_ON_PUSCH_MAPPING); nr_data_control_mapping(pusch_pdu, template_buffer, G[pusch_id], @@ -1161,7 +1162,7 @@ void nr_ue_ulsch_procedures(PHY_VARS_NR_UE *UE, G_initial_total_pusch_bits, harq_process_ul_ue->f, b); - + stop_meas_nr_ue_phy(UE, UCI_ON_PUSCH_MAPPING); memcpy(harq_process_ul_ue->f, temp_codeword, G_initial_total_pusch_bits); uci_mapping_template = template_buffer; } diff --git a/openair1/PHY/nr_phy_common/inc/nr_ue_phy_meas.h b/openair1/PHY/nr_phy_common/inc/nr_ue_phy_meas.h index 9f91274bae..79b0b378e8 100644 --- a/openair1/PHY/nr_phy_common/inc/nr_ue_phy_meas.h +++ b/openair1/PHY/nr_phy_common/inc/nr_ue_phy_meas.h @@ -47,6 +47,7 @@ FN(DLSCH_PROCEDURES_STATS),\ FN(PHY_PROC_TX),\ FN(PUSCH_PROC_STATS),\ + FN(UCI_ON_PUSCH_MAPPING),\ FN(ULSCH_SEGMENTATION_STATS),\ FN(ULSCH_LDPC_ENCODING_STATS),\ FN(ULSCH_RATE_MATCHING_STATS),\ From 83bf140bc3bd7e0416f09706b9681abd67a5edca Mon Sep 17 00:00:00 2001 From: Sakthivel Velumani Date: Sat, 16 Aug 2025 19:20:44 -0400 Subject: [PATCH 58/97] Add flag for Tx phase pre compensation Used in ulsim to verify waveform in MATLAB --- .../PHY/NR_UE_TRANSPORT/nr_transport_proto_ue.h | 3 ++- openair1/PHY/NR_UE_TRANSPORT/nr_ulsch_ue.c | 16 +++++++++------- openair1/PHY/defs_nr_UE.h | 3 +++ openair1/SCHED_NR_UE/phy_procedures_nr_ue.c | 3 ++- openair1/SCHED_NR_UE/phy_procedures_nr_ue_sl.c | 10 +++++++++- 5 files changed, 25 insertions(+), 10 deletions(-) diff --git a/openair1/PHY/NR_UE_TRANSPORT/nr_transport_proto_ue.h b/openair1/PHY/NR_UE_TRANSPORT/nr_transport_proto_ue.h index c257726501..0c519451a5 100644 --- a/openair1/PHY/NR_UE_TRANSPORT/nr_transport_proto_ue.h +++ b/openair1/PHY/NR_UE_TRANSPORT/nr_transport_proto_ue.h @@ -170,7 +170,8 @@ uint8_t nr_ue_pusch_common_procedures(PHY_VARS_NR_UE *UE, c16_t **txdataF, c16_t **txdata, uint32_t linktype, - bool was_symbol_used[NR_NUMBER_OF_SYMBOLS_PER_SLOT]); + bool was_symbol_used[NR_NUMBER_OF_SYMBOLS_PER_SLOT], + bool no_phase_pre_comp); void clean_UE_harq(PHY_VARS_NR_UE *UE); diff --git a/openair1/PHY/NR_UE_TRANSPORT/nr_ulsch_ue.c b/openair1/PHY/NR_UE_TRANSPORT/nr_ulsch_ue.c index 7fca035a27..ff2d67e793 100644 --- a/openair1/PHY/NR_UE_TRANSPORT/nr_ulsch_ue.c +++ b/openair1/PHY/NR_UE_TRANSPORT/nr_ulsch_ue.c @@ -1506,19 +1506,21 @@ uint8_t nr_ue_pusch_common_procedures(PHY_VARS_NR_UE *UE, c16_t **txdataF, c16_t **txdata, uint32_t linktype, - bool was_symbol_used[NR_NUMBER_OF_SYMBOLS_PER_SLOT]) + bool was_symbol_used[NR_NUMBER_OF_SYMBOLS_PER_SLOT], + bool no_phase_pre_comp) { int N_RB = (linktype == link_type_sl) ? frame_parms->N_RB_SL : frame_parms->N_RB_UL; - for (int i = 0; i < NR_NUMBER_OF_SYMBOLS_PER_SLOT; i++) { - if (was_symbol_used[i] == false) - continue; - for (int ap = 0; ap < n_antenna_ports; ap++) { - apply_nr_rotation_TX(frame_parms, txdataF[ap], frame_parms->symbol_rotation[linktype], slot, N_RB, i, 1); + if (!no_phase_pre_comp) { + for (int i = 0; i < NR_NUMBER_OF_SYMBOLS_PER_SLOT; i++) { + if (was_symbol_used[i] == false) + continue; + for (int ap = 0; ap < n_antenna_ports; ap++) { + apply_nr_rotation_TX(frame_parms, txdataF[ap], frame_parms->symbol_rotation[linktype], slot, N_RB, i, 1); + } } } - for (int ap = 0; ap < n_antenna_ports; ap++) { if (frame_parms->Ncp == 1) { // extended cyclic prefix for (int i = 0; i < NR_NUMBER_OF_SYMBOLS_PER_SLOT_EXTENDED_CP; i++) { diff --git a/openair1/PHY/defs_nr_UE.h b/openair1/PHY/defs_nr_UE.h index 6ae3ddc37a..d56355298b 100644 --- a/openair1/PHY/defs_nr_UE.h +++ b/openair1/PHY/defs_nr_UE.h @@ -499,6 +499,9 @@ typedef struct PHY_VARS_NR_UE_s { /// RF and Interface devices per CC openair0_device rfdevice; + /// Phase precompensation flag + bool no_phase_pre_comp; + void* scopeData; // Pointers to hold PDSCH data only for phy simulators void *phy_sim_rxdataF; diff --git a/openair1/SCHED_NR_UE/phy_procedures_nr_ue.c b/openair1/SCHED_NR_UE/phy_procedures_nr_ue.c index 75f73b0e8c..89b8ed91a7 100644 --- a/openair1/SCHED_NR_UE/phy_procedures_nr_ue.c +++ b/openair1/SCHED_NR_UE/phy_procedures_nr_ue.c @@ -404,7 +404,8 @@ void phy_procedures_nrUE_TX(PHY_VARS_NR_UE *ue, const UE_nr_rxtx_proc_t *proc, n (c16_t **)txdataF, txp, link_type_ul, - was_symbol_used); + was_symbol_used, + ue->no_phase_pre_comp); stop_meas_nr_ue_phy(ue, OFDM_MOD_STATS); } diff --git a/openair1/SCHED_NR_UE/phy_procedures_nr_ue_sl.c b/openair1/SCHED_NR_UE/phy_procedures_nr_ue_sl.c index bd2ac08b81..e03c0a1fae 100644 --- a/openair1/SCHED_NR_UE/phy_procedures_nr_ue_sl.c +++ b/openair1/SCHED_NR_UE/phy_procedures_nr_ue_sl.c @@ -313,7 +313,15 @@ void phy_procedures_nrUE_SL_TX(PHY_VARS_NR_UE *ue, const UE_nr_rxtx_proc_t *proc was_symbol_used[i] = true; if (tx_action) { LOG_D(NR_PHY, "Sending Uplink data \n"); - nr_ue_pusch_common_procedures(ue, proc->nr_slot_tx, fp, fp->nb_antennas_tx, txdataF, txp, link_type_sl, was_symbol_used); + nr_ue_pusch_common_procedures(ue, + proc->nr_slot_tx, + fp, + fp->nb_antennas_tx, + txdataF, + txp, + link_type_sl, + was_symbol_used, + ue->no_phase_pre_comp); } LOG_D(NR_PHY, "****** end Sidelink TX-Chain for AbsSubframe %d.%d ******\n", frame_tx, slot_tx); From 59ff9ae04e8d8826cc91df0e2989bc7ffd24f46c Mon Sep 17 00:00:00 2001 From: Sakthivel Velumani Date: Sat, 16 Aug 2025 21:04:56 -0400 Subject: [PATCH 59/97] ulsim: test UCI on PUSCH with matlab Since there is no UCI on PUSCH implementation in gNB, it is good to test UE's implementation with matlab. This commit allows to test with matlab in two ways: 1. generate OFDM waveform from ulsim and decode it in matlab using uci_on_pusch_decode.m 2. generate codeword in matlab using uci_on_pusch_encode.m read it in ulsim and compare the bits. ulsim option -o is used --- openair1/PHY/NR_UE_TRANSPORT/nr_ulsch_ue.c | 3 + openair1/PHY/defs_nr_UE.h | 1 + openair1/SIMULATION/NR_PHY/ulsim.c | 176 +++++++++++++++++- .../SIMULATION/TOOLS/uci_on_pusch_decode.m | 79 ++++++++ .../SIMULATION/TOOLS/uci_on_pusch_encode.m | 85 +++++++++ 5 files changed, 335 insertions(+), 9 deletions(-) create mode 100644 openair1/SIMULATION/TOOLS/uci_on_pusch_decode.m create mode 100644 openair1/SIMULATION/TOOLS/uci_on_pusch_encode.m diff --git a/openair1/PHY/NR_UE_TRANSPORT/nr_ulsch_ue.c b/openair1/PHY/NR_UE_TRANSPORT/nr_ulsch_ue.c index ff2d67e793..310fa7dc5f 100644 --- a/openair1/PHY/NR_UE_TRANSPORT/nr_ulsch_ue.c +++ b/openair1/PHY/NR_UE_TRANSPORT/nr_ulsch_ue.c @@ -1224,6 +1224,9 @@ void nr_ue_ulsch_procedures(PHY_VARS_NR_UE *UE, is_uci_on_pusch, uci_mapping_template, scrambled_output); + if (UE->phy_sim_test_buf) { + memcpy(UE->phy_sim_test_buf, scrambled_output, (available_bits + 7) / 8); + } #if T_TRACER if (T_ACTIVE(T_UE_PHY_UL_SCRAMBLED_TX_BITS)) { // Get Time Stamp for T-tracer messages diff --git a/openair1/PHY/defs_nr_UE.h b/openair1/PHY/defs_nr_UE.h index d56355298b..935226b238 100644 --- a/openair1/PHY/defs_nr_UE.h +++ b/openair1/PHY/defs_nr_UE.h @@ -511,6 +511,7 @@ typedef struct PHY_VARS_NR_UE_s { void *phy_sim_pdsch_dl_ch_estimates; void *phy_sim_pdsch_dl_ch_estimates_ext; uint8_t *phy_sim_dlsch_b; + uint8_t *phy_sim_test_buf; dynamic_barrier_t process_slot_tx_barriers[NUM_PROCESS_SLOT_TX_BARRIERS]; diff --git a/openair1/SIMULATION/NR_PHY/ulsim.c b/openair1/SIMULATION/NR_PHY/ulsim.c index ec4a7190fa..edab935e15 100644 --- a/openair1/SIMULATION/NR_PHY/ulsim.c +++ b/openair1/SIMULATION/NR_PHY/ulsim.c @@ -160,6 +160,108 @@ openair0_config_t openair0_cfg[MAX_CARDS]; channel_desc_t *UE2gNB[MAX_MOBILES_PER_GNB][NUMBER_OF_gNB_MAX]; +static void copy_bytes_to_packed_bits(const uint8_t *in, const uint32_t num_bits, const bool is_ulsch, uint8_t *out) +{ + if (is_ulsch) { // MATLAB computes CRC for input of MSB first + for (uint_fast32_t b = 0; b < num_bits; b++) { + out[b / 8] |= ((in[b] & 1) << (7 - (b % 8))); + } + } else { + for (uint_fast32_t b = 0; b < num_bits; b++) { + out[b / 8] |= (in[b] << (b % 8)); + } + } +} + +static void prepare_ue_pusch_pdu_from_matlab_vector(const bool uci_on_pusch, + FILE *vect_file, + nfapi_nr_ue_pusch_pdu_t *pusch_config_pdu, + uint8_t *cw_buf) +{ + if (!uci_on_pusch) + return; + + if (vect_file == NULL) + return; + + struct vect_vars { + uint32_t A; + uint32_t oack; + uint32_t ocsi1; + uint32_t ocsi2; + uint32_t cwlen; + uint32_t cwlen_scr; + } __attribute__((packed)); + + struct vect_vars var = {0}; + if (1 != fread(&var, sizeof(var), 1, vect_file)) { + printf("Error reading from matlab vector file\n"); + exit(-1); + } + + const uint16_t buff_len = var.A + var.oack + var.ocsi1 + var.ocsi2; + uint8_t vec_bits[buff_len]; + memset(vec_bits, 0, sizeof(vect_file)); + + uint8_t *p_vec_bits = vec_bits; + if (var.A != fread(p_vec_bits, sizeof(uint8_t), var.A, vect_file)) { + printf("Error reading ULSCH bits from file\n"); + exit(-1); + } + p_vec_bits += var.A; + if (var.oack != fread(p_vec_bits, sizeof(uint8_t), var.oack, vect_file)) { + printf("Error reading ACK bits from file\n"); + exit(-1); + } + p_vec_bits += var.oack; + if (var.ocsi1 != fread(p_vec_bits, sizeof(uint8_t), var.ocsi1, vect_file)) { + printf("Error reading CSI1 bits from file\n"); + exit(-1); + } + p_vec_bits += var.ocsi1; + if (var.ocsi2 != fread(p_vec_bits, sizeof(uint8_t), var.ocsi2, vect_file)) { + printf("Error reading CSI2 bits from file\n"); + exit(-1); + } + + if (var.cwlen != fread(cw_buf, sizeof(uint8_t), var.cwlen, vect_file)) { + printf("Error reading cw bits from file\n"); + exit(-1); + } + + memset(cw_buf, 0, var.cwlen_scr); + if (var.cwlen_scr != fread(cw_buf, sizeof(uint8_t), var.cwlen_scr, vect_file)) { + printf("Error reading cw bits from file\n"); + exit(-1); + } + + uint16_t tb_buf_size = (var.A + 7) / 8; + pusch_config_pdu->pusch_data.tb_size = tb_buf_size; + pusch_config_pdu->tx_request_body.pdu_length = tb_buf_size; + uint8_t *pb = pusch_config_pdu->tx_request_body.fapiTxPdu; + memset(pb, 0, tb_buf_size); + p_vec_bits = vec_bits; + copy_bytes_to_packed_bits(p_vec_bits, var.A, true, pb); + + pusch_config_pdu->pusch_uci.harq_ack_bit_length = var.oack; + pb = (uint8_t *)&pusch_config_pdu->pusch_uci.harq_payload; + memset(pb, 0, sizeof(pusch_config_pdu->pusch_uci.harq_payload)); + p_vec_bits += var.A; + copy_bytes_to_packed_bits(p_vec_bits, var.oack, false, pb); + + pusch_config_pdu->pusch_uci.csi_part1_bit_length = var.ocsi1; + pb = (uint8_t *)&pusch_config_pdu->pusch_uci.csi_part1_payload; + memset(pb, 0, sizeof(pusch_config_pdu->pusch_uci.csi_part1_payload)); + p_vec_bits += var.oack; + copy_bytes_to_packed_bits(p_vec_bits, var.ocsi1, false, pb); + + pusch_config_pdu->pusch_uci.csi_part2_bit_length = var.ocsi2; + pb = (uint8_t *)&pusch_config_pdu->pusch_uci.csi_part2_payload; + memset(pb, 0, sizeof(pusch_config_pdu->pusch_uci.csi_part2_payload)); + p_vec_bits += var.ocsi1; + copy_bytes_to_packed_bits(p_vec_bits, var.ocsi2, false, pb); +} + configmodule_interface_t *uniqCfg = NULL; int main(int argc, char *argv[]) { @@ -204,6 +306,8 @@ int main(int argc, char *argv[]) int print_perf = 0; cpuf = get_cpu_freq_GHz(); int msg3_flag = 0; + bool uci_on_pusch = false; + bool no_phase_pre_comp = false; int rv_index = 0; float roundStats; double effRate; @@ -227,6 +331,7 @@ int main(int argc, char *argv[]) UE_nr_rxtx_proc_t UE_proc; FILE *scg_fd=NULL; + FILE *uci_ulsch_matlab_vec = NULL; int file_offset = 0; double DS_TDL = .03; @@ -252,7 +357,8 @@ int main(int argc, char *argv[]) int c; bool setAffinity=false; char gNBthreads[128]="n"; - while ((c = getopt(argc, argv, "--:O:a:b:c:d:ef:g:h:i:jk:m:n:p:q:r:s:t:u:v:w:y:z:A:C:F:G:H:I:M:N:PR:S:T:U:L:ZW:E:X:Y:")) != -1) { + while ((c = getopt(argc, argv, "--:O:a:b:c:d:ef:g:h:i:jk:m:n:o::p:q:r:s:t:u:v:w:y:z:A:C:F:G:H:I:M:N:PR:S:T:U:L:ZW:E:X:Y:")) + != -1) { /* ignore long options starting with '--', option '-O' and their arguments that are handled by configmodule */ /* with this opstring getopt returns 1 for non-option arguments, refer to 'man 3 getopt' */ if (c == 1 || c == '-' || c == 'O') @@ -360,6 +466,20 @@ int main(int argc, char *argv[]) Imcs = atoi(optarg); break; + case 'o': + uci_on_pusch = true; + // UCI on PUSCH is not implemented in OAI gNB yet. + // So this flag is needed to verify in MATLAB + no_phase_pre_comp = true; + if (optarg) { // -o with file input: use matlab vector + uci_ulsch_matlab_vec = fopen(optarg, "rb"); + if (uci_ulsch_matlab_vec == NULL) { + printf("Error opening %s\n", optarg); + exit(-1); + } + } + break; + case 'W': precod_nbr_layers = atoi(optarg); break; @@ -539,6 +659,7 @@ int main(int argc, char *argv[]) printf("-k 3/4 sampling\n"); printf("-m MCS value\n"); printf("-n Number of trials to simulate\n"); + printf("-o Enable UCI on PUSCH. Optionally accepts input file (without space). This feature is not yet available in gNB so only used to verify with MATLAB generated vector\n"); printf("-p Use extended prefix mode\n"); printf("-q MCS table\n"); printf("-r Number of allocated resource blocks for PUSCH\n"); @@ -786,7 +907,8 @@ int main(int argc, char *argv[]) UE->if_inst->phy_config_request = nr_ue_phy_config_request; UE->if_inst->dl_indication = nr_ue_dl_indication; UE->if_inst->ul_indication = nr_ue_ul_indication; - + UE->no_phase_pre_comp = no_phase_pre_comp; + UE_mac->if_module = nr_ue_if_module_init(0); initFloatingCoresTpool(threadCnt, &nrUE_params.Tpool, false, "UE-tpool"); @@ -924,6 +1046,9 @@ int main(int argc, char *argv[]) } unsigned int available_bits = nr_get_G(nb_rb, nb_symb_sch, nb_re_dmrs, number_dmrs_symbols, unav_res, mod_order, precod_nbr_layers); + uint8_t cw_buf[available_bits]; + memset(cw_buf, 0, available_bits); + UE->phy_sim_test_buf = calloc(1, (available_bits + 7) / 8); printf("[ULSIM]: VALUE OF G: %u, TBS: %u\n", available_bits, TBS); int frame_length_complex_samples = gNB->frame_parms.samples_per_subframe * NR_NUMBER_OF_SUBFRAMES_PER_FRAME; @@ -1222,6 +1347,20 @@ int main(int argc, char *argv[]) // pusch_config_pdu->pdu_bit_map |= PUSCH_PDU_BITMAP_DFTS_OFDM; pusch_config_pdu->num_dmrs_cdm_grps_no_data = num_dmrs_cdm_grps_no_data; } + if (uci_on_pusch) { + const nfapi_nr_ue_pusch_uci_t pusch_uci = { + .alpha_scaling = 3, + .beta_offset_csi1 = 13, + .beta_offset_csi2 = 13, + .beta_offset_harq_ack = 11, + .harq_ack_bit_length = 3, + .harq_payload = 3, + //.csi_part1_bit_length = 4, + //.csi_part1_payload = 15 + }; + pusch_config_pdu->pusch_uci = pusch_uci; + prepare_ue_pusch_pdu_from_matlab_vector(uci_on_pusch, uci_ulsch_matlab_vec, pusch_config_pdu, cw_buf); + } if (do_SRS == 1) { fapi_nr_ul_config_request_pdu_t *ul_config1 = &ul_config.ul_config_list[1]; @@ -1457,7 +1596,7 @@ int main(int argc, char *argv[]) } if ((ulsch_gNB->last_iteration_cnt >= ulsch_gNB->max_ldpc_iterations) || ul_proc_error == 1) { - error_flag = 1; + error_flag = uci_on_pusch ? 0 : 1; n_errors[round]++; crc_status = 1; } else @@ -1474,15 +1613,29 @@ int main(int argc, char *argv[]) available_bits, (ptrsSymbPerSlot * ptrsRePerSymb * mod_order * precod_nbr_layers)); } - for (i = 0; i < available_bits; i++) { - const uint8_t current_bit = (UE->ul_harq_processes[harq_pid].f[i / 8] >> (i & 7)) & 1; - if (((current_bit == 0) && (pusch_vars->llr[i] <= 0)) || ((current_bit == 1) && (pusch_vars->llr[i] >= 0))) { - /*if(errors_scrambling == 0) - printf("\x1B[34m" "[frame %d][trial %d]\t1st bit in error in unscrambling = %d\n" "\x1B[0m", frame, trial, i);*/ - errors_scrambling[round]++; + if (uci_on_pusch) { + for (i = 0; i < available_bits; i++) { + const uint8_t current_bit = (UE->phy_sim_test_buf[i / 8] >> (i & 7)) & 1; + const uint8_t test_vector_bit = cw_buf[i] & 1; + if (current_bit != test_vector_bit) + errors_scrambling[round]++; + } + } else { + for (i = 0; i < available_bits; i++) { + const uint8_t current_bit = (UE->ul_harq_processes[harq_pid].f[i / 8] >> (i & 7)) & 1; + if (((current_bit == 0) && (pusch_vars->llr[i] <= 0)) || ((current_bit == 1) && (pusch_vars->llr[i] >= 0))) { + errors_scrambling[round]++; + } } } round++; + if (uci_on_pusch && uci_ulsch_matlab_vec && (errors_scrambling[round] == 0)) { + ret = 0; + printf("*************\n"); + printf("UCI on PUSCH test OK against MATLAB generated codeword\n"); + printf("*************\n"); + break; + } } // round if (n_trials == 1 && errors_scrambling[0] > 0) { @@ -1655,5 +1808,10 @@ int main(int argc, char *argv[]) free(filename_csv); } + if (uci_ulsch_matlab_vec) + fclose(uci_ulsch_matlab_vec); + + free_and_zero(UE->phy_sim_test_buf); + return ret; } diff --git a/openair1/SIMULATION/TOOLS/uci_on_pusch_decode.m b/openair1/SIMULATION/TOOLS/uci_on_pusch_decode.m new file mode 100644 index 0000000000..122b4912fb --- /dev/null +++ b/openair1/SIMULATION/TOOLS/uci_on_pusch_decode.m @@ -0,0 +1,79 @@ +% ULSIM command line +% ./nr_ulsim -m 27 -u 1 -R 51 -r 51 -s 300 -L 4 -o + +carrier = nrCarrierConfig; +carrier.NSizeGrid = 51; +carrier.NSlot = 8; +carrier.SubcarrierSpacing = 30; +carrier.CyclicPrefix = "normal"; + +pusch = nrPUSCHConfig; +pusch.PRBSet = 0:carrier.NSizeGrid-1; +pusch.SymbolAllocation = [0,12]; +pusch.MappingType = "B"; +pusch.NID = 0; +pusch.RNTI = 4660; +pusch.NumLayers = 1; +pusch.TransformPrecoding = false; +pusch.TransmissionScheme = "nonCodebook"; +pusch.NumAntennaPorts = 1; +pusch.TPMI = 0; +pusch.Modulation = "64QAM"; + +pusch.DMRS.DMRSConfigurationType = 1; +pusch.DMRS.DMRSTypeAPosition = 2; +pusch.DMRS.NumCDMGroupsWithoutData = 1; +pusch.DMRS.DMRSAdditionalPosition = 0; +pusch.DMRS.NIDNSCID = 0; +pusch.DMRS.NRSID = 0; +pusch.DMRS.NSCID = 0; +pusch.DMRS.GroupHopping = 0; +pusch.DMRS.SequenceHopping = 0; + +pusch.BetaOffsetACK = 20; +pusch.BetaOffsetCSI1 = 6.25; +pusch.BetaOffsetCSI2 = 1; +pusch.UCIScaling = 1; + +[puschIndices,puschIndicesInfo] = nrPUSCHIndices(carrier,pusch); + +dmrsLayerSymbols = nrPUSCHDMRS(carrier,pusch); +dmrsLayerIndices = nrPUSCHDMRSIndices(carrier,pusch); + +run("../../../cmake_targets/ran_build/build/txsig0.m"); +rxGrid = nrOFDMDemodulate(carrier,txs0); +[estChannelGrid,noiseEst] = nrChannelEstimate(carrier,rxGrid,dmrsLayerIndices,dmrsLayerSymbols); +[puschRx,puschHest] = nrExtractResources(puschIndices,rxGrid,estChannelGrid); +[puschEq,csi] = nrEqualizeMMSE(puschRx,puschHest,noiseEst); + +decodeULSCH = nrULSCHDecoder; +decodeULSCH.MultipleHARQProcesses = false; +decodeULSCH.CBGTransmission = false; +decodeULSCH.TargetCodeRate = 910 / 1024; +decodeULSCH.LDPCDecodingAlgorithm = 'Normalized min-sum'; +decodeULSCH.MaximumLDPCIterationCount = 6; +decodeULSCH.TransportBlockLength = 37896; + +oack = 3; +ocsi1 = 0; +ocsi2 = 0; +[ulschLLRs,rxSymbols] = nrPUSCHDecode(carrier,pusch,decodeULSCH.TargetCodeRate,... + decodeULSCH.TransportBlockLength,oack,ocsi1,ocsi2,puschEq,noiseEst); +rmInfo = nrULSCHInfo(pusch,decodeULSCH.TargetCodeRate,... + decodeULSCH.TransportBlockLength,oack,ocsi1,ocsi2); +[culsch,cack,ccsi1,ccsi2] = nrULSCHDemultiplex(pusch,decodeULSCH.TargetCodeRate,... + decodeULSCH.TransportBlockLength,oack,ocsi1,ocsi2,ulschLLRs); + +plot(rxSymbols,'*'); + +decodeULSCH.reset(); +rv = 0; % Redundancy version for decoding +[decodedBits,blkerr] = decodeULSCH(culsch,pusch.Modulation,pusch.NumLayers,rv); +% Display the decoded bits +disp('Block error:'); +disp(blkerr); + +ucibits = nrUCIDecode(cack,oack); +% Display the decoded UCI bits +disp('Decoded UCI bits:'); +disp(ucibits); diff --git a/openair1/SIMULATION/TOOLS/uci_on_pusch_encode.m b/openair1/SIMULATION/TOOLS/uci_on_pusch_encode.m new file mode 100644 index 0000000000..0b83d211d2 --- /dev/null +++ b/openair1/SIMULATION/TOOLS/uci_on_pusch_encode.m @@ -0,0 +1,85 @@ +% ulsim command for same pusch config +% ./nr_ulsim -m 27 -u 1 -R 51 -r 51 -s 300 -o uci_on_pusch_7.bin + +carrier = nrCarrierConfig; +carrier.NSizeGrid = 51; +carrier.NSlot = 8; +carrier.SubcarrierSpacing = 30; +carrier.CyclicPrefix = "normal"; + +pusch = nrPUSCHConfig; +pusch.PRBSet = 0:carrier.NSizeGrid-1; +pusch.SymbolAllocation = [0,12]; +pusch.MappingType = "B"; +pusch.NID = 0; +pusch.RNTI = 4660; +pusch.NumLayers = 1; +pusch.TransformPrecoding = false; +pusch.TransmissionScheme = "nonCodebook"; +pusch.NumAntennaPorts = 1; +pusch.TPMI = 0; +pusch.Modulation = "64QAM"; + +pusch.DMRS.DMRSConfigurationType = 1; +pusch.DMRS.DMRSTypeAPosition = 2; +pusch.DMRS.NumCDMGroupsWithoutData = 1; +pusch.DMRS.DMRSAdditionalPosition = 0; +pusch.DMRS.NIDNSCID = 0; +pusch.DMRS.NRSID = 0; +pusch.DMRS.NSCID = 0; +pusch.DMRS.GroupHopping = 0; +pusch.DMRS.SequenceHopping = 0; + +pusch.BetaOffsetACK = 20; +pusch.BetaOffsetCSI1 = 6.25; +pusch.BetaOffsetCSI2 = 1; +pusch.UCIScaling = 1; + +A = 37896; +rate = 910 / 1024; +rv = 0; +modulation = pusch.Modulation; +nlayers = pusch.NumLayers; % Number of layers for decoding + +oack = 7; +ocsi1 = 0; +ocsi2 = 0; +cbsInfo = nrULSCHInfo(pusch, rate, A, oack, ocsi1, ocsi2); % Get ULSCH information + +ack = randi([0 1],oack,1); +csi1 = randi([0 1],ocsi1,1); +csi2 = randi([0 1],ocsi2,1); +cack = nrUCIEncode(ack,cbsInfo.GACK,pusch.Modulation); +ccsi1 = []; +ccsi2 = []; + +tb_data = randi([0 1],A,1); +% Transport block CRC attachment +tbIn = nrCRCEncode(tb_data,cbsInfo.CRC); + +% Code block segmentation and CRC attachment +cbsIn = nrCodeBlockSegmentLDPC(tbIn,cbsInfo.BGN); + +% LDPC encoding +enc = nrLDPCEncode(cbsIn,cbsInfo.BGN); + +% Rate matching and code block concatenation +chIn = nrRateMatchLDPC(enc,cbsInfo.GULSCH,rv,modulation,nlayers); +culsch = chIn; + + +% Get the codeword and locations of each type (data and UCI) +[cw,indInfo] = nrULSCHMultiplex(pusch,rate,A,culsch,cack,ccsi1,ccsi2); +cw_scr = nrPUSCHScramble(cw,pusch.NID,pusch.RNTI); + +% Write to bin file +fid = fopen('uci_on_pusch_7.bin', 'wb'); +to_write = [A,oack,ocsi1,ocsi2,length(cw),length(cw_scr)]; +fwrite(fid, to_write, 'uint32'); % Write the data to the binary file +fwrite(fid, tb_data, "uint8"); +fwrite(fid, ack, "uint8"); +fwrite(fid, csi1, "uint8"); +fwrite(fid, csi2, "uint8"); +fwrite(fid, cw, "uint8"); +fwrite(fid, cw_scr, "uint8"); +fclose(fid); % Close the file after writing From fd50ff43ccb20bb3a7ede13f80d6ee5f1f1daf0e Mon Sep 17 00:00:00 2001 From: Guido Casati Date: Tue, 29 Jul 2025 10:54:11 +0200 Subject: [PATCH 60/97] Handle gracefully encoding failure in build_RRCReconfiguration_IEs closes #979 --- openair2/RRC/NR/MESSAGES/asn1_msg.c | 34 ++++++++++++++++++++++++----- 1 file changed, 29 insertions(+), 5 deletions(-) diff --git a/openair2/RRC/NR/MESSAGES/asn1_msg.c b/openair2/RRC/NR/MESSAGES/asn1_msg.c index 0ed1750d6b..5420348a16 100644 --- a/openair2/RRC/NR/MESSAGES/asn1_msg.c +++ b/openair2/RRC/NR/MESSAGES/asn1_msg.c @@ -545,15 +545,35 @@ static NR_RRCReconfiguration_IEs_t *build_RRCReconfiguration_IEs(const nr_rrc_re /* masterCellGroup */ if (params->cell_group_config) { - // Encode in extension IE (Master cell group) - uint8_t *buf = NULL; - ssize_t len = uper_encode_to_new_buffer(&asn_DEF_NR_CellGroupConfig, NULL, params->cell_group_config, (void **)&buf); - AssertFatal(len > 0, "ASN1 message encoding failed (%lu)!\n", len); + uint8_t temp[4096]; + asn_enc_rval_t enc = uper_encode_to_buffer(&asn_DEF_NR_CellGroupConfig, NULL, params->cell_group_config, temp, sizeof(temp)); + + if (enc.encoded <= 0) { + LOG_E(NR_RRC, "ASN.1 encoding failed for NR_CellGroupConfig (encoded=%ld)\n", enc.encoded); + if (enc.failed_type) { + LOG_E(NR_RRC, "Failed at ASN.1 type: %s\n", enc.failed_type->name); + } + if (enc.structure_ptr) { + LOG_E(NR_RRC, "Failed at structure element: %p\n", enc.structure_ptr); + } + // Print diagnostic information to help debug the issue + LOG_E(NR_RRC, "CellGroupConfig structure that failed to encode:\n"); + xer_fprint(stdout, &asn_DEF_NR_CellGroupConfig, (const void *)params->cell_group_config); + ASN_STRUCT_FREE(asn_DEF_NR_RRCReconfiguration_IEs, ie); + return NULL; + } + DevAssert(enc.encoded <= sizeof(temp) * 8); // Encoded data must fit in temp buffer + // Allocate buffer for the encoded data and copy it + // enc.encoded is in bits, convert to bytes + size_t encoded_bytes = (enc.encoded + 7) / 8; + uint8_t *buf = calloc_or_fail(1, encoded_bytes); + memcpy(buf, temp, encoded_bytes); + if (LOG_DEBUGFLAG(DEBUG_ASN1)) { xer_fprint(stdout, &asn_DEF_NR_CellGroupConfig, (const void *)params->cell_group_config); } ie->nonCriticalExtension->masterCellGroup = calloc_or_fail(1, sizeof(*ie->nonCriticalExtension->masterCellGroup)); - *ie->nonCriticalExtension->masterCellGroup = (OCTET_STRING_t){.buf = buf, .size = len}; + *ie->nonCriticalExtension->masterCellGroup = (OCTET_STRING_t){.buf = buf, .size = encoded_bytes}; } /* masterKeyUpdate */ @@ -608,6 +628,10 @@ byte_array_t do_RRCReconfiguration(const nr_rrc_reconfig_param_t *params) { byte_array_t msg = {.buf = NULL, .len = 0}; NR_RRCReconfiguration_IEs_t *ie = build_RRCReconfiguration_IEs(params); + if (!ie) { + LOG_E(NR_RRC, "%s: failed to encode RRCReconfiguration\n", __func__); + return msg; + } NR_DL_DCCH_Message_t dl_dcch_msg = {0}; dl_dcch_msg.message.present = NR_DL_DCCH_MessageType_PR_c1; From b4485ecea2ff0fed61242c449ae61009d910b9df Mon Sep 17 00:00:00 2001 From: Guido Casati Date: Thu, 18 Sep 2025 21:58:52 +0200 Subject: [PATCH 61/97] Fix UE context leak in handover failure - Add UE context cleanup before sending handover failure message - Prevents memory/resource leak when PDU session establishment fails during handover - UE context was created but never cleaned up on failure path This fixes a bug where UE contexts would accumulate over time when handovers failed after context creation. --- openair2/RRC/NR/rrc_gNB_NGAP.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/openair2/RRC/NR/rrc_gNB_NGAP.c b/openair2/RRC/NR/rrc_gNB_NGAP.c index 75e6742422..a7ed703f7d 100644 --- a/openair2/RRC/NR/rrc_gNB_NGAP.c +++ b/openair2/RRC/NR/rrc_gNB_NGAP.c @@ -1196,12 +1196,15 @@ int rrc_gNB_process_Handover_Request(gNB_RRC_INST *rrc, instance_t instance, nga if (!trigger_bearer_setup(rrc, UE, msg->nb_of_pdusessions, to_setup, msg->ue_ambr.br_dl)) { LOG_E(NR_RRC, "Failed to establish PDU session: handover failed\n"); + ngap_handover_failure_t fail = { .amf_ue_ngap_id = msg->amf_ue_ngap_id, .cause.type = NGAP_CAUSE_RADIO_NETWORK, .cause.value = NGAP_CAUSE_RADIO_NETWORK_HO_FAILURE_IN_TARGET_5GC_NGRAN_NODE_OR_TARGET_SYSTEM, }; rrc_gNB_send_NGAP_HANDOVER_FAILURE(rrc, &fail); + rrc_remove_ue(rrc, ue_context_p); + return -1; } return 0; From a38aa1bab8cfab44942dbc324cea220201e31c74 Mon Sep 17 00:00:00 2001 From: Guido Casati Date: Thu, 18 Sep 2025 22:02:47 +0200 Subject: [PATCH 62/97] Fix memory leaks in fill_removal_lists_from_source_measConfig - Add proper cleanup of allocated ASN.1 structures in all return paths - Fix early returns that were leaking NR_HandoverPreparationInformation - Fix early returns that were leaking NR_RRCReconfiguration - Add cleanup at end of successful function execution This prevents memory leaks when processing handover preparation information during N2 handover procedures. --- openair2/RRC/NR/MESSAGES/asn1_msg.c | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/openair2/RRC/NR/MESSAGES/asn1_msg.c b/openair2/RRC/NR/MESSAGES/asn1_msg.c index 0ed1750d6b..f76f3ee1d1 100644 --- a/openair2/RRC/NR/MESSAGES/asn1_msg.c +++ b/openair2/RRC/NR/MESSAGES/asn1_msg.c @@ -1231,6 +1231,7 @@ void fill_removal_lists_from_source_measConfig(NR_MeasConfig_t *currentMC, byte_ and extract the measConfig provided by the source gNB */ if (!hpi->criticalExtensions.choice.c1->choice.handoverPreparationInformation->sourceConfig) { LOG_W(NR_RRC, "Missing sourceConfig: in source gNB rrcReconfiguration\n"); + ASN_STRUCT_FREE(asn_DEF_NR_HandoverPreparationInformation, hpi); return; } NR_RRCReconfiguration_t *rrcReconf = NULL; @@ -1242,11 +1243,15 @@ void fill_removal_lists_from_source_measConfig(NR_MeasConfig_t *currentMC, byte_ sourceConfig->rrcReconfiguration.size); if (rrcReconf_dec_rval.code != RC_OK || rrcReconf_dec_rval.consumed < 0) { LOG_E(NR_RRC, "Failed to decode source gNB rrcReconfiguration!\n"); + ASN_STRUCT_FREE(asn_DEF_NR_HandoverPreparationInformation, hpi); return; } - if (rrcReconf->criticalExtensions.choice.rrcReconfiguration->measConfig == NULL) + if (rrcReconf->criticalExtensions.choice.rrcReconfiguration->measConfig == NULL) { + ASN_STRUCT_FREE(asn_DEF_NR_HandoverPreparationInformation, hpi); + ASN_STRUCT_FREE(asn_DEF_NR_RRCReconfiguration, rrcReconf); return; + } NR_MeasConfig_t *sourceMC = rrcReconf->criticalExtensions.choice.rrcReconfiguration->measConfig; @@ -1278,6 +1283,10 @@ void fill_removal_lists_from_source_measConfig(NR_MeasConfig_t *currentMC, byte_ asn1cSeqAdd(¤tMC->measIdToRemoveList->list, measId); } } + + // Clean up allocated memory + ASN_STRUCT_FREE(asn_DEF_NR_HandoverPreparationInformation, hpi); + ASN_STRUCT_FREE(asn_DEF_NR_RRCReconfiguration, rrcReconf); } int doRRCReconfiguration_from_HandoverCommand(byte_array_t *ba, const byte_array_t handoverCommand) From 12d92bc836a65b2f09f50d591564845a385b67e7 Mon Sep 17 00:00:00 2001 From: Robert Schmidt Date: Fri, 19 Sep 2025 10:33:25 +0200 Subject: [PATCH 63/97] Fix crash in get_du_for_ue when F1 UE data does not exist Add check for cu_exists_f1_ue_data() before calling cu_get_f1_ue_data() to prevent assertion failure when telnet command 'ci fetch_du_by_ue_id' is used for UEs that have been handed over via N2 (and no longer have F1 UE data). This fixes the crash: Assertion (ret == HASH_TABLE_OK && data != ((void *)0)) failed! In get_hashtable_data() /oai-ran/openair2/F1AP/f1ap_ids.c:36 element for ue_id 1 not found The function now returns NULL gracefully when F1 UE data is not available. Co-authored-by: Guido Casati --- openair2/RRC/NR/rrc_gNB_du.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/openair2/RRC/NR/rrc_gNB_du.c b/openair2/RRC/NR/rrc_gNB_du.c index db1a9cd459..e104641edc 100644 --- a/openair2/RRC/NR/rrc_gNB_du.c +++ b/openair2/RRC/NR/rrc_gNB_du.c @@ -619,6 +619,8 @@ void rrc_CU_process_f1_lost_connection(gNB_RRC_INST *rrc, f1ap_lost_connection_t nr_rrc_du_container_t *get_du_for_ue(gNB_RRC_INST *rrc, uint32_t ue_id) { + if (!cu_exists_f1_ue_data(ue_id)) + return NULL; f1_ue_data_t ue_data = cu_get_f1_ue_data(ue_id); return get_du_by_assoc_id(rrc, ue_data.du_assoc_id); } From 524a3ddf85412d3750db6db08e9c0f8806cd094a Mon Sep 17 00:00:00 2001 From: Guido Casati Date: Fri, 19 Sep 2025 15:48:09 +0200 Subject: [PATCH 64/97] Fix xer_fprint parameter in ngap_handover_required Fix incorrect xer_fprint parameter - should pass pdu instead of &pdu to properly print ASN.1 structure for debugging. --- openair3/NGAP/ngap_gNB.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openair3/NGAP/ngap_gNB.c b/openair3/NGAP/ngap_gNB.c index 46bcf93cad..4331f130a9 100644 --- a/openair3/NGAP/ngap_gNB.c +++ b/openair3/NGAP/ngap_gNB.c @@ -303,7 +303,7 @@ int ngap_handover_required(instance_t instance, ngap_handover_required_t *msg) } if (LOG_DEBUGFLAG(DEBUG_ASN1)) - xer_fprint(stdout, &asn_DEF_NGAP_NGAP_PDU, &pdu); + xer_fprint(stdout, &asn_DEF_NGAP_NGAP_PDU, pdu); byte_array_t out = { .buf = NULL, .len = 0 }; if (ngap_gNB_encode_pdu(pdu, &out.buf, (uint32_t *)&out.len) < 0) { From 235493aa07f87cdfb58c9580e55bb5cf86d5fa4d Mon Sep 17 00:00:00 2001 From: Guido Casati Date: Fri, 19 Sep 2025 15:40:22 +0200 Subject: [PATCH 65/97] Fix ASN.1 memory management ownership in NGAP functions In the current memory management the caller wasn't responsible for freeing memory they allocate. The ngap_gNB_encode_pdu function was taking ownership of PDU contents by calling ASN_STRUCT_FREE_CONTENTS_ONLY, while callers also expected to manage the same memory. This created unclear ownership semantics and potential double-free bugs. - Remove ASN_STRUCT_FREE_CONTENTS_ONLY from ngap_gNB_encode_pdu to prevent double-free - Remove unnecessary ASN_STRUCT_FREE when encode function returns NULL - Add proper ASN_STRUCT_FREE cleanup for heap-allocated PDUs in ngap_gNB.c functions - Add ASN_STRUCT_FREE_CONTENTS_ONLY cleanup for stack-allocated PDUs in NAS and context management functions - Ensure consistent memory cleanup patterns across all NGAP encoder callers - Fix potential memory leaks in all ngap_gNB_encode_pdu call sites This prevents double-free bugs and ensures proper cleanup of ASN.1 structures in both heap and stack allocation scenarios. --- openair3/NGAP/ngap_gNB.c | 16 ++++++++------ .../ngap_gNB_context_management_procedures.c | 4 ++++ openair3/NGAP/ngap_gNB_encoder.c | 1 - openair3/NGAP/ngap_gNB_nas_procedures.c | 22 ++++++++++++++++++- 4 files changed, 34 insertions(+), 9 deletions(-) diff --git a/openair3/NGAP/ngap_gNB.c b/openair3/NGAP/ngap_gNB.c index 4331f130a9..b2ba38de52 100644 --- a/openair3/NGAP/ngap_gNB.c +++ b/openair3/NGAP/ngap_gNB.c @@ -298,7 +298,6 @@ int ngap_handover_required(instance_t instance, ngap_handover_required_t *msg) NGAP_NGAP_PDU_t *pdu = encode_ng_handover_required(msg); if (!pdu) { NGAP_ERROR("Failed to encode Handover Required\n"); - ASN_STRUCT_FREE(asn_DEF_NGAP_NGAP_PDU, pdu); return -1; } @@ -307,11 +306,10 @@ int ngap_handover_required(instance_t instance, ngap_handover_required_t *msg) byte_array_t out = { .buf = NULL, .len = 0 }; if (ngap_gNB_encode_pdu(pdu, &out.buf, (uint32_t *)&out.len) < 0) { - ASN_STRUCT_FREE(asn_DEF_NGAP_NGAP_PDU, pdu); NGAP_ERROR("Failed to encode Handover Required message\n"); + ASN_STRUCT_FREE(asn_DEF_NGAP_NGAP_PDU, pdu); return -1; } - free(pdu); /* UE associated signalling -> use the allocated stream */ ngap_gNB_itti_send_sctp_data_req(ngap_gNB_instance_p->instance, @@ -320,6 +318,7 @@ int ngap_handover_required(instance_t instance, ngap_handover_required_t *msg) out.len, ue_context_p->tx_stream); + ASN_STRUCT_FREE(asn_DEF_NGAP_NGAP_PDU, pdu); return 0; } @@ -342,7 +341,6 @@ static int ngap_gNB_handover_failure(instance_t instance, const ngap_handover_fa NGAP_NGAP_PDU_t *pdu = encode_ng_handover_failure(msg); if (!pdu) { NGAP_ERROR("Failed to encode NG Handover Failure\n"); - ASN_STRUCT_FREE(asn_DEF_NGAP_NGAP_PDU, pdu); return -1; } @@ -364,7 +362,7 @@ static int ngap_gNB_handover_failure(instance_t instance, const ngap_handover_fa } ngap_gNB_itti_send_sctp_data_req(ngap_gNB_instance_p->instance, amf->assoc_id, buffer, length, amf->nextstream); - ASN_STRUCT_FREE(asn_DEF_NGAP_NGAP_PDU, &pdu); + ASN_STRUCT_FREE(asn_DEF_NGAP_NGAP_PDU, pdu); return 0; } @@ -410,7 +408,7 @@ static int ngap_gNB_handover_request_acknowledge(instance_t instance, ngap_hando /* UE associated signalling -> use the allocated stream */ ngap_gNB_itti_send_sctp_data_req(ngap->instance, ue_context_p.amf_ref->assoc_id, ba.buf, ba.len, ue_context_p.tx_stream); NGAP_INFO("Sent Handover Request Acknowledge to AMF\n"); - + ASN_STRUCT_FREE(asn_DEF_NGAP_NGAP_PDU, pdu); return 0; } @@ -450,6 +448,7 @@ static int ngap_gNB_handover_notify(instance_t instance, const ngap_handover_not ba.buf, ba.len, ue_context_p->tx_stream); + ASN_STRUCT_FREE(asn_DEF_NGAP_NGAP_PDU, pdu); return 0; } @@ -489,7 +488,6 @@ static int ngap_gNB_handover_cancel(instance_t instance, const ngap_handover_can ASN_STRUCT_FREE(asn_DEF_NGAP_NGAP_PDU, pdu); return -1; } - ASN_STRUCT_FREE(asn_DEF_NGAP_NGAP_PDU, pdu); /* UE-associated signalling -> use the UE's allocated SCTP stream */ ngap_gNB_itti_send_sctp_data_req(ngap_gNB_instance_p->instance, @@ -498,6 +496,7 @@ static int ngap_gNB_handover_cancel(instance_t instance, const ngap_handover_can ba.len, ue_context_p->tx_stream); + ASN_STRUCT_FREE(asn_DEF_NGAP_NGAP_PDU, pdu); return 0; } @@ -530,6 +529,7 @@ int ngap_gNB_handle_ul_ran_status_transfer(instance_t instance, const ngap_ran_s } ngap_gNB_itti_send_sctp_data_req(instance, ue_context_p->amf_ref->assoc_id, out.buf, out.len, ue_context_p->tx_stream); + ASN_STRUCT_FREE(asn_DEF_NGAP_NGAP_PDU, pdu); return 0; } @@ -769,11 +769,13 @@ static int ngap_gNB_generate_ng_setup_request( if (ngap_gNB_encode_pdu(&pdu, &buffer, &len) < 0) { NGAP_ERROR("Failed to encode NG setup request\n"); + ASN_STRUCT_FREE_CONTENTS_ONLY(asn_DEF_NGAP_NGAP_PDU, &pdu); return -1; } /* Non UE-Associated signalling -> stream = 0 */ ngap_gNB_itti_send_sctp_data_req(instance_p->instance, ngap_amf_data_p->assoc_id, buffer, len, 0); + ASN_STRUCT_FREE_CONTENTS_ONLY(asn_DEF_NGAP_NGAP_PDU, &pdu); return ret; } diff --git a/openair3/NGAP/ngap_gNB_context_management_procedures.c b/openair3/NGAP/ngap_gNB_context_management_procedures.c index 7abbec2592..7f114d5bf6 100644 --- a/openair3/NGAP/ngap_gNB_context_management_procedures.c +++ b/openair3/NGAP/ngap_gNB_context_management_procedures.c @@ -111,6 +111,7 @@ int ngap_ue_context_release_complete(instance_t instance, if (ngap_gNB_encode_pdu(&pdu, &buffer, &length) < 0) { /* Encode procedure has failed... */ NGAP_ERROR("Failed to encode UE context release complete\n"); + ASN_STRUCT_FREE_CONTENTS_ONLY(asn_DEF_NGAP_NGAP_PDU, &pdu); return -1; } @@ -125,6 +126,7 @@ int ngap_ue_context_release_complete(instance_t instance, ngap_gNB_ue_context_t *tmp = ngap_detach_ue_context(ue_release_complete_p->gNB_ue_ngap_id); if (tmp) free(tmp); + ASN_STRUCT_FREE_CONTENTS_ONLY(asn_DEF_NGAP_NGAP_PDU, &pdu); return 0; } @@ -207,6 +209,7 @@ int ngap_ue_context_release_req(instance_t instance, if (ngap_gNB_encode_pdu(&pdu, &buffer, &length) < 0) { /* Encode procedure has failed... */ NGAP_ERROR("Failed to encode UE context release complete\n"); + ASN_STRUCT_FREE_CONTENTS_ONLY(asn_DEF_NGAP_NGAP_PDU, &pdu); return -1; } @@ -215,6 +218,7 @@ int ngap_ue_context_release_req(instance_t instance, ue_context_p->amf_ref->assoc_id, buffer, length, ue_context_p->tx_stream); + ASN_STRUCT_FREE_CONTENTS_ONLY(asn_DEF_NGAP_NGAP_PDU, &pdu); return 0; } diff --git a/openair3/NGAP/ngap_gNB_encoder.c b/openair3/NGAP/ngap_gNB_encoder.c index 7a7ce758e1..be3945c2f0 100644 --- a/openair3/NGAP/ngap_gNB_encoder.c +++ b/openair3/NGAP/ngap_gNB_encoder.c @@ -148,6 +148,5 @@ int ngap_gNB_encode_pdu(NGAP_NGAP_PDU_t *pdu, uint8_t **buffer, uint32_t *len) NGAP_DEBUG("Unknown message outcome (%d) or not implemented", (int)pdu->present); return -1; } - ASN_STRUCT_FREE_CONTENTS_ONLY(asn_DEF_NGAP_NGAP_PDU, pdu); return ret; } diff --git a/openair3/NGAP/ngap_gNB_nas_procedures.c b/openair3/NGAP/ngap_gNB_nas_procedures.c index fe86d7c5e0..cff9cd0b72 100644 --- a/openair3/NGAP/ngap_gNB_nas_procedures.c +++ b/openair3/NGAP/ngap_gNB_nas_procedures.c @@ -259,8 +259,11 @@ int ngap_gNB_handle_nas_first_req(instance_t instance, ngap_nas_first_req_t *UEf ie->value.choice.UEContextRequest = NGAP_UEContextRequest_requested; } - if (ngap_gNB_encode_pdu(&pdu, &buffer, &length) < 0) + if (ngap_gNB_encode_pdu(&pdu, &buffer, &length) < 0) { + ASN_STRUCT_FREE_CONTENTS_ONLY(asn_DEF_NGAP_NGAP_PDU, &pdu); DevMessage("Failed to encode initial UE message\n"); + return -1; + } /* Update the current NGAP UE state */ ue_desc_p.ue_state = NGAP_UE_WAITING_CSR; @@ -288,6 +291,7 @@ int ngap_gNB_handle_nas_first_req(instance_t instance, ngap_nas_first_req_t *UEf /* Send encoded message over sctp */ ngap_gNB_itti_send_sctp_data_req(instance_p->instance, amf->assoc_id, buffer, length, ue_desc_p.tx_stream); + ASN_STRUCT_FREE_CONTENTS_ONLY(asn_DEF_NGAP_NGAP_PDU, &pdu); return 0; } @@ -454,6 +458,7 @@ int ngap_gNB_nas_uplink(instance_t instance, ngap_uplink_nas_t *ngap_uplink_nas_ if (ngap_gNB_encode_pdu(&pdu, &buffer, &length) < 0) { NGAP_ERROR("Failed to encode uplink NAS transport\n"); /* Encode procedure has failed... */ + ASN_STRUCT_FREE_CONTENTS_ONLY(asn_DEF_NGAP_NGAP_PDU, &pdu); return -1; } @@ -462,6 +467,7 @@ int ngap_gNB_nas_uplink(instance_t instance, ngap_uplink_nas_t *ngap_uplink_nas_ ue_context_p->amf_ref->assoc_id, buffer, length, ue_context_p->tx_stream); + ASN_STRUCT_FREE_CONTENTS_ONLY(asn_DEF_NGAP_NGAP_PDU, &pdu); return 0; } @@ -535,6 +541,7 @@ int ngap_gNB_nas_non_delivery_ind(instance_t instance, if (ngap_gNB_encode_pdu(&pdu, &buffer, &length) < 0) { NGAP_ERROR("Failed to encode NAS NON delivery indication\n"); /* Encode procedure has failed... */ + ASN_STRUCT_FREE_CONTENTS_ONLY(asn_DEF_NGAP_NGAP_PDU, &pdu); return -1; } @@ -543,6 +550,7 @@ int ngap_gNB_nas_non_delivery_ind(instance_t instance, ue_context_p->amf_ref->assoc_id, buffer, length, ue_context_p->tx_stream); + ASN_STRUCT_FREE_CONTENTS_ONLY(asn_DEF_NGAP_NGAP_PDU, &pdu); return 0; } @@ -704,6 +712,7 @@ int ngap_gNB_initial_ctxt_resp(instance_t instance, ngap_initial_context_setup_r if (ngap_gNB_encode_pdu(&pdu, &buffer, &length) < 0) { NGAP_ERROR("Failed to encode InitialContextSetupResponse\n"); /* Encode procedure has failed... */ + ASN_STRUCT_FREE_CONTENTS_ONLY(asn_DEF_NGAP_NGAP_PDU, &pdu); return -1; } @@ -711,6 +720,7 @@ int ngap_gNB_initial_ctxt_resp(instance_t instance, ngap_initial_context_setup_r LOG_I(NR_RRC,"Send message to sctp: NGAP_InitialContextSetupResponse\n"); ngap_gNB_itti_send_sctp_data_req(ngap_gNB_instance_p->instance, ue_context_p->amf_ref->assoc_id, buffer, length, ue_context_p->tx_stream); + ASN_STRUCT_FREE_CONTENTS_ONLY(asn_DEF_NGAP_NGAP_PDU, &pdu); return 0; } @@ -785,6 +795,7 @@ int ngap_gNB_initial_ctxt_fail(instance_t instance, ngap_initial_context_setup_f if (ngap_gNB_encode_pdu(&pdu, &buffer, &length) < 0) { NGAP_ERROR("Failed to encode InitialContextSetupFailure\n"); /* Encode procedure has failed... */ + ASN_STRUCT_FREE_CONTENTS_ONLY(asn_DEF_NGAP_NGAP_PDU, &pdu); return -1; } /* UE associated signalling -> use the allocated stream */ @@ -794,6 +805,7 @@ int ngap_gNB_initial_ctxt_fail(instance_t instance, ngap_initial_context_setup_f buffer, length, ue_context_p->tx_stream); + ASN_STRUCT_FREE_CONTENTS_ONLY(asn_DEF_NGAP_NGAP_PDU, &pdu); return 0; } @@ -866,11 +878,13 @@ int ngap_gNB_ue_capabilities(instance_t instance, ngap_ue_cap_info_ind_t *ue_cap if (ngap_gNB_encode_pdu(&pdu, &buffer, &length) < 0) { /* Encode procedure has failed... */ NGAP_ERROR("Failed to encode UE radio capabilities indication\n"); + ASN_STRUCT_FREE_CONTENTS_ONLY(asn_DEF_NGAP_NGAP_PDU, &pdu); return -1; } /* UE associated signalling -> use the allocated stream */ ngap_gNB_itti_send_sctp_data_req(ngap_gNB_instance_p->instance, ue_context_p->amf_ref->assoc_id, buffer, length, ue_context_p->tx_stream); + ASN_STRUCT_FREE_CONTENTS_ONLY(asn_DEF_NGAP_NGAP_PDU, &pdu); return 0; } @@ -990,11 +1004,13 @@ int ngap_gNB_pdusession_setup_resp(instance_t instance, ngap_pdusession_setup_re if (ngap_gNB_encode_pdu(&pdu, &buffer, &length) < 0) { NGAP_ERROR("Failed to encode uplink transport\n"); /* Encode procedure has failed... */ + ASN_STRUCT_FREE_CONTENTS_ONLY(asn_DEF_NGAP_NGAP_PDU, &pdu); return -1; } /* UE associated signalling -> use the allocated stream */ ngap_gNB_itti_send_sctp_data_req(ngap_gNB_instance_p->instance, ue_context_p->amf_ref->assoc_id, buffer, length, ue_context_p->tx_stream); + ASN_STRUCT_FREE_CONTENTS_ONLY(asn_DEF_NGAP_NGAP_PDU, &pdu); return 0; } @@ -1128,12 +1144,14 @@ int ngap_gNB_pdusession_modify_resp(instance_t instance, ngap_pdusession_modify_ if (ngap_gNB_encode_pdu(&pdu, &buffer, &length) < 0) { NGAP_ERROR("Failed to encode uplink transport\n"); /* Encode procedure has failed... */ + ASN_STRUCT_FREE_CONTENTS_ONLY(asn_DEF_NGAP_NGAP_PDU, &pdu); return -1; } /* UE associated signalling -> use the allocated stream */ ngap_gNB_itti_send_sctp_data_req(ngap_gNB_instance_p->instance, ue_context_p->amf_ref->assoc_id, buffer, length, ue_context_p->tx_stream); + ASN_STRUCT_FREE_CONTENTS_ONLY(asn_DEF_NGAP_NGAP_PDU, &pdu); return 0; } //------------------------------------------------------------------------------ @@ -1201,6 +1219,7 @@ int ngap_gNB_pdusession_release_resp(instance_t instance, ngap_pdusession_releas if (ngap_gNB_encode_pdu(&pdu, &buffer, &length) < 0) { NGAP_ERROR("Failed to encode release response\n"); /* Encode procedure has failed... */ + ASN_STRUCT_FREE_CONTENTS_ONLY(asn_DEF_NGAP_NGAP_PDU, &pdu); return -1; } @@ -1211,6 +1230,7 @@ int ngap_gNB_pdusession_release_resp(instance_t instance, ngap_pdusession_releas (uint64_t)ue_context_p->amf_ue_ngap_id, pdusession_release_resp_p->nb_of_pdusessions_released); + ASN_STRUCT_FREE_CONTENTS_ONLY(asn_DEF_NGAP_NGAP_PDU, &pdu); return 0; } From de07668b0bb07ac2f542fdced61e6153de7f4bfc Mon Sep 17 00:00:00 2001 From: Guido Casati Date: Fri, 19 Sep 2025 15:58:40 +0200 Subject: [PATCH 66/97] Fix potential memory leak in ngap_gNB_handover_request_acknowledge Move UE context creation and storage to after successful PDU encoding to prevent memory leaks if encoding fails. Also add proper PDU cleanup when AMF fetch fails and use consistent NULL check style. This ensures UE context is only stored when the operation is guaranteed to succeed, improving error handling and memory management. --- openair3/NGAP/ngap_gNB.c | 29 +++++++++++++++-------------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/openair3/NGAP/ngap_gNB.c b/openair3/NGAP/ngap_gNB.c index b2ba38de52..68e77d369c 100644 --- a/openair3/NGAP/ngap_gNB.c +++ b/openair3/NGAP/ngap_gNB.c @@ -376,20 +376,6 @@ static int ngap_gNB_handover_request_acknowledge(instance_t instance, ngap_hando DevAssert(msg != NULL); DevAssert(ngap != NULL); - // First, create and store NGAP UE context - ngap_gNB_ue_context_t ue_context_p = { - .amf_ref = ngap_gNB_get_AMF_from_instance(ngap), - .gNB_ue_ngap_id = msg->gNB_ue_ngap_id, - .amf_ue_ngap_id = msg->amf_ue_ngap_id, - .gNB_instance = ngap, - .ue_state = NGAP_UE_CONNECTED, - }; - if (ue_context_p.amf_ref == NULL) { - NGAP_ERROR("Failed to fetch AMF for current NGAP instance\n"); - return -1; - } - ngap_store_ue_context(&ue_context_p); - NGAP_NGAP_PDU_t *pdu = encode_ng_handover_request_ack(msg); if (!pdu) { NGAP_ERROR("Failed to encode NG Handover Request Acknowledge\n"); @@ -405,6 +391,21 @@ static int ngap_gNB_handover_request_acknowledge(instance_t instance, ngap_hando return -1; } + // Create and store NGAP UE context + ngap_gNB_ue_context_t ue_context_p = { + .amf_ref = ngap_gNB_get_AMF_from_instance(ngap), + .gNB_ue_ngap_id = msg->gNB_ue_ngap_id, + .amf_ue_ngap_id = msg->amf_ue_ngap_id, + .gNB_instance = ngap, + .ue_state = NGAP_UE_CONNECTED, + }; + if (!ue_context_p.amf_ref) { + NGAP_ERROR("Failed to fetch AMF for current NGAP instance\n"); + ASN_STRUCT_FREE(asn_DEF_NGAP_NGAP_PDU, pdu); + return -1; + } + ngap_store_ue_context(&ue_context_p); + /* UE associated signalling -> use the allocated stream */ ngap_gNB_itti_send_sctp_data_req(ngap->instance, ue_context_p.amf_ref->assoc_id, ba.buf, ba.len, ue_context_p.tx_stream); NGAP_INFO("Sent Handover Request Acknowledge to AMF\n"); From 13571587224ab11a191fc9751d02a868cfbce345 Mon Sep 17 00:00:00 2001 From: Guido Casati Date: Fri, 19 Sep 2025 16:51:34 +0200 Subject: [PATCH 67/97] Fix hardcoded instance 0 bug in UE context release command Use the instance parameter instead of hardcoded 0 to access the correct RRC instance. Also clean up redundant variable initialization. --- openair2/RRC/NR/rrc_gNB_NGAP.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/openair2/RRC/NR/rrc_gNB_NGAP.c b/openair2/RRC/NR/rrc_gNB_NGAP.c index a7ed703f7d..95fbf72d5d 100644 --- a/openair2/RRC/NR/rrc_gNB_NGAP.c +++ b/openair2/RRC/NR/rrc_gNB_NGAP.c @@ -1306,9 +1306,8 @@ void rrc_gNB_free_Handover_Command(ngap_handover_command_t *msg) */ int rrc_gNB_process_NGAP_UE_CONTEXT_RELEASE_COMMAND(MessageDef *msg_p, instance_t instance) { - gNB_RRC_INST *rrc = RC.nrrrc[0]; - uint32_t gNB_ue_ngap_id = 0; - gNB_ue_ngap_id = NGAP_UE_CONTEXT_RELEASE_COMMAND(msg_p).gNB_ue_ngap_id; + gNB_RRC_INST *rrc = RC.nrrrc[instance]; + uint32_t gNB_ue_ngap_id = NGAP_UE_CONTEXT_RELEASE_COMMAND(msg_p).gNB_ue_ngap_id; rrc_gNB_ue_context_t *ue_context_p = rrc_gNB_get_ue_context(RC.nrrrc[instance], gNB_ue_ngap_id); if (ue_context_p == NULL) { From 59803e221f7a5cdb9fdca6bbbe154d8049c8d019 Mon Sep 17 00:00:00 2001 From: Guido Casati Date: Mon, 22 Sep 2025 10:31:18 +0200 Subject: [PATCH 68/97] Fix duplicate handover request handling - Add early check for existing UE context by AMF UE NGAP ID at beginning of rrc_gNB_process_Handover_Request - Add dedicated function to get UE context by AMF UE NGAP ID: rrc_gNB_get_ue_context_by_amf_ue_ngap_id() - Send handover failure with appropriate cause if UE context already exists - Remove redundant ho_context NULL check that could never be true for newly created UE contexts - Prevents duplicate handover processing and resource leaks --- openair2/RRC/NR/rrc_gNB_NGAP.c | 17 +++++++++++++---- openair2/RRC/NR/rrc_gNB_UE_context.c | 12 ++++++++++++ openair2/RRC/NR/rrc_gNB_UE_context.h | 1 + 3 files changed, 26 insertions(+), 4 deletions(-) diff --git a/openair2/RRC/NR/rrc_gNB_NGAP.c b/openair2/RRC/NR/rrc_gNB_NGAP.c index 95fbf72d5d..4c5b504f2d 100644 --- a/openair2/RRC/NR/rrc_gNB_NGAP.c +++ b/openair2/RRC/NR/rrc_gNB_NGAP.c @@ -1129,6 +1129,19 @@ void rrc_gNB_send_NGAP_HANDOVER_FAILURE(gNB_RRC_INST *rrc, ngap_handover_failure /** @brief Process NG Handover Request message (8.4.2.2 3GPP TS 38.413) */ int rrc_gNB_process_Handover_Request(gNB_RRC_INST *rrc, instance_t instance, ngap_handover_request_t *msg) { + // Check if UE context already exists for this AMF UE NGAP ID + rrc_gNB_ue_context_t *existing_ue_context = rrc_gNB_get_ue_context_by_amf_ue_ngap_id(rrc, msg->amf_ue_ngap_id); + if (existing_ue_context != NULL) { + LOG_E(RRC, "UE context already exists for AMF UE NGAP ID %ld, cannot process handover request\n", msg->amf_ue_ngap_id); + ngap_handover_failure_t fail = { + .amf_ue_ngap_id = msg->amf_ue_ngap_id, + .cause.type = NGAP_CAUSE_RADIO_NETWORK, + .cause.value = NGAP_CAUSE_RADIO_NETWORK_HO_FAILURE_IN_TARGET_5GC_NGRAN_NODE_OR_TARGET_SYSTEM, + }; + rrc_gNB_send_NGAP_HANDOVER_FAILURE(rrc, &fail); + return -1; + } + struct nr_rrc_du_container_t *du = get_du_by_cell_id(rrc, msg->nr_cell_id); if (du == NULL) { /* Cell Not Found! Return HO Request Failure*/ @@ -1150,10 +1163,6 @@ int rrc_gNB_process_Handover_Request(gNB_RRC_INST *rrc, instance_t instance, nga gNB_RRC_UE_t *UE = &ue_context_p->ue_context; // allocate context for target - if (UE->ho_context != NULL) { - LOG_E(NR_RRC, "Ongoing handover for UE %d, cannot trigger new\n", UE->rrc_ue_id); - return -1; - } UE->ho_context = alloc_ho_ctx(HO_CTX_TARGET); UE->ho_context->target->ho_trigger = nr_rrc_trigger_n2_ho_target; diff --git a/openair2/RRC/NR/rrc_gNB_UE_context.c b/openair2/RRC/NR/rrc_gNB_UE_context.c index 412c5231cc..9a2c3fb6f6 100644 --- a/openair2/RRC/NR/rrc_gNB_UE_context.c +++ b/openair2/RRC/NR/rrc_gNB_UE_context.c @@ -122,6 +122,18 @@ rrc_gNB_ue_context_t *rrc_gNB_get_ue_context_by_rnti_any_du(gNB_RRC_INST *rrc_in return NULL; } +/** @brief Fetch UE Context by the unique AMF UE NGAP ID */ +rrc_gNB_ue_context_t *rrc_gNB_get_ue_context_by_amf_ue_ngap_id(gNB_RRC_INST *rrc_instance_pP, uint64_t amf_ue_ngap_id) +{ + rrc_gNB_ue_context_t *ue_context_p; + RB_FOREACH (ue_context_p, rrc_nr_ue_tree_s, &(rrc_instance_pP->rrc_ue_head)) { + if (ue_context_p->ue_context.amf_ue_ngap_id == amf_ue_ngap_id) { + return ue_context_p; + } + } + return NULL; +} + void rrc_gNB_free_mem_ue_context(rrc_gNB_ue_context_t *const ue_context_pP) //----------------------------------------------------------------------------- { diff --git a/openair2/RRC/NR/rrc_gNB_UE_context.h b/openair2/RRC/NR/rrc_gNB_UE_context.h index 9485672463..b8edd159c4 100644 --- a/openair2/RRC/NR/rrc_gNB_UE_context.h +++ b/openair2/RRC/NR/rrc_gNB_UE_context.h @@ -46,6 +46,7 @@ rrc_gNB_ue_context_t* rrc_gNB_allocate_new_ue_context(gNB_RRC_INST* rrc_instance rrc_gNB_ue_context_t* rrc_gNB_get_ue_context(gNB_RRC_INST* rrc_instance_pP, ue_id_t ue); rrc_gNB_ue_context_t* rrc_gNB_get_ue_context_by_rnti(gNB_RRC_INST* rrc_instance_pP, sctp_assoc_t assoc_id, rnti_t rntiP); rrc_gNB_ue_context_t* rrc_gNB_get_ue_context_by_rnti_any_du(gNB_RRC_INST* rrc_instance_pP, rnti_t rntiP); +rrc_gNB_ue_context_t* rrc_gNB_get_ue_context_by_amf_ue_ngap_id(gNB_RRC_INST* rrc_instance_pP, uint64_t amf_ue_ngap_id); void rrc_gNB_free_mem_ue_context(rrc_gNB_ue_context_t* const ue_context_pP); From 487de67ff8eb3b4f32ee03b6fc95e2c8b5fc984c Mon Sep 17 00:00:00 2001 From: Guido Casati Date: Tue, 23 Sep 2025 14:52:37 +0200 Subject: [PATCH 69/97] Fix measurement configuration memory leak with stack allocation - Replace heap allocation with stack allocation for seq_arr_t structures - Use proper seq_arr_free() with free functions for ASN.1 structures - Remove unnecessary malloc/free calls for sequence containers - Improve memory safety and performance with automatic cleanup Fixes 128-byte memory leak: Direct leak of 128 byte(s) in 2 object(s) allocated from: /#0 0x7e7f39ab4a57 in __interceptor_calloc ../../../../src/libsanitizer/asan/asan_malloc_linux.cpp:154 /#1 0x56df8b07312d in SEQUENCE_decode_uper /home/guido/repo/openairinterface5g/develop/cmake_targets/ran_build/build/openair2/RRC/LTE/MESSAGES/constr_SEQUENCE_uper.c:37 /#2 0x56df8b056551 in uper_decode /home/guido/repo/openairinterface5g/develop/cmake_targets/ran_build/build/openair2/RRC/LTE/MESSAGES/uper_decoder.c:83 /#3 0x56df8a896b1b in get_nr_mtc /home/guido/repo/openairinterface5g/develop/openair2/LAYER2/NR_MAC_gNB/nr_radio_config.c:4216 /#4 0x56df8a82feb8 in ue_context_setup_request /home/guido/repo/openairinterface5g/develop/openair2/LAYER2/NR_MAC_gNB/mac_rrc_dl_handler.c:635 /#5 0x56df8a66a298 in nr_initiate_handover /home/guido/repo/openairinterface5g/develop/openair2/RRC/NR/rrc_gNB_mobility.c:218 /#6 0x56df8a6720af in nr_rrc_trigger_n2_ho_target /home/guido/repo/openairinterface5g/develop/openair2/RRC/NR/rrc_gNB_mobility.c:526 /#7 0x56df8a5cf338 in rrc_gNB_process_e1_bearer_context_setup_resp /home/guido/repo/openairinterface5g/develop/openair2/RRC/NR/rrc_gNB.c:2633 /#8 0x56df8a5d28ab in rrc_gnb_task /home/guido/repo/openairinterface5g/develop/openair2/RRC/NR/rrc_gNB.c:3012 /#9 0x7e7f38494ac2 in start_thread nptl/pthread_create.c:442 --- openair2/RRC/NR/MESSAGES/asn1_msg.c | 6 +++++- openair2/RRC/NR/rrc_gNB.c | 28 +++++++++++++++++----------- 2 files changed, 22 insertions(+), 12 deletions(-) diff --git a/openair2/RRC/NR/MESSAGES/asn1_msg.c b/openair2/RRC/NR/MESSAGES/asn1_msg.c index f76f3ee1d1..d711217fbb 100644 --- a/openair2/RRC/NR/MESSAGES/asn1_msg.c +++ b/openair2/RRC/NR/MESSAGES/asn1_msg.c @@ -1121,7 +1121,11 @@ NR_MeasConfig_t *get_MeasConfig(const NR_MeasTiming_t *mt, if (rc_A3_seq) { for (int i = 0; i < rc_A3_seq->size; i++) { NR_ReportConfigToAddMod_t *rc_A3 = (NR_ReportConfigToAddMod_t *)seq_arr_at(rc_A3_seq, i); - asn1cSeqAdd(&mc->reportConfigToAddModList->list, rc_A3); + // Create a deep copy of the report config + NR_ReportConfigToAddMod_t *rc_A3_copy = NULL; + int result = asn_copy(&asn_DEF_NR_ReportConfigToAddMod, (void **)&rc_A3_copy, rc_A3); + AssertFatal(result >= 0, "error during asn_copy() of ReportConfigToAddMod\n"); + asn1cSeqAdd(&mc->reportConfigToAddModList->list, rc_A3_copy); } } diff --git a/openair2/RRC/NR/rrc_gNB.c b/openair2/RRC/NR/rrc_gNB.c index cc766b9d81..2661531c27 100644 --- a/openair2/RRC/NR/rrc_gNB.c +++ b/openair2/RRC/NR/rrc_gNB.c @@ -638,11 +638,14 @@ NR_MeasConfig_t *nr_rrc_get_measconfig(const gNB_RRC_INST *rrc, uint64_t nr_cell nr_rrc_du_container_t *du = get_du_by_cell_id((gNB_RRC_INST *)rrc, nr_cellid); DevAssert(du != NULL); f1ap_served_cell_info_t *cell_info = &du->setup_req->cell[0].info; - NR_ReportConfigToAddMod_t *rc_PER = NULL; - NR_ReportConfigToAddMod_t *rc_A2 = NULL; - seq_arr_t *rc_A3_seq = NULL; - seq_arr_t *neigh_seq = NULL; + if (du->mtc != NULL) { + NR_ReportConfigToAddMod_t *rc_PER = NULL; + NR_ReportConfigToAddMod_t *rc_A2 = NULL; + seq_arr_t rc_A3_seq = {0}; + seq_arr_t neigh_seq = {0}; + seq_arr_init(&rc_A3_seq, sizeof(NR_ReportConfigToAddMod_t)); + seq_arr_init(&neigh_seq, sizeof(nr_neighbour_cell_t)); int scs = get_ssb_scs(cell_info); int band = get_dl_band(cell_info); const NR_MeasTimingList_t *mtlist = du->mtc->criticalExtensions.choice.c1->choice.measTimingConf->measTiming; @@ -657,21 +660,17 @@ NR_MeasConfig_t *nr_rrc_get_measconfig(const gNB_RRC_INST *rrc, uint64_t nr_cell 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. */ - rc_A3_seq = malloc(sizeof(seq_arr_t)); - neigh_seq = malloc(sizeof(seq_arr_t)); - seq_arr_init(rc_A3_seq, sizeof(NR_ReportConfigToAddMod_t)); - seq_arr_init(neigh_seq, sizeof(nr_neighbour_cell_t)); 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); if (default_a3_added && neighbourCell->physicalCellId == -1) continue; - seq_arr_push_back(neigh_seq, neighbourCell, sizeof(nr_neighbour_cell_t)); + 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) { NR_ReportConfigId_t reportConfigId = neighbourCell->physicalCellId == -1 ? 3 : i + 4; - seq_arr_push_back(rc_A3_seq, prepare_a3_event_report(a3Event, reportConfigId), sizeof(NR_ReportConfigToAddMod_t)); + seq_arr_push_back(&rc_A3_seq, prepare_a3_event_report(a3Event, reportConfigId), sizeof(NR_ReportConfigToAddMod_t)); if (neighbourCell->physicalCellId == -1) default_a3_added = true; } @@ -681,7 +680,14 @@ NR_MeasConfig_t *nr_rrc_get_measconfig(const gNB_RRC_INST *rrc, uint64_t nr_cell 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); - return get_MeasConfig(mt, band, scs, cell_info->nr_pci, rc_PER, rc_A2, rc_A3_seq, neigh_seq); + + NR_MeasConfig_t *result = get_MeasConfig(mt, band, scs, cell_info->nr_pci, rc_PER, rc_A2, &rc_A3_seq, &neigh_seq); + + // Clean up sequence arrays + seq_arr_free(&rc_A3_seq, NULL); + seq_arr_free(&neigh_seq, NULL); + + return result; } return NULL; } From 915c41c127e48bd09c5905a5bc52246a60d7270c Mon Sep 17 00:00:00 2001 From: Guido Casati Date: Tue, 23 Sep 2025 16:10:48 +0200 Subject: [PATCH 70/97] Fix MTC sequence memory leak in UE context setup MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add missing ASN_STRUCT_FREE for NR_MeasurementTimingConfiguration in ue_context_setup_request - Fix 64-byte MTC sequence leak in get_nr_mtc → SEQUENCE_decode_uper AddressSanitizer backtrace: Direct leak of 64 byte(s) in 1 object(s) allocated from: /#0 0x7dcfcb8b4a57 in __interceptor_calloc ../../../../src/libsanitizer/asan/asan_malloc_linux.cpp:154 /#1 0x591fcc59b67d in SEQUENCE_decode_uper /home/guido/repo/openairinterface5g/develop/cmake_targets/ran_build/build/openair2/RRC/LTE/MESSAGES/constr_SEQUENCE_uper.c:37 /#2 0x591fcc57eaa1 in uper_decode /home/guido/repo/openairinterface5g/develop/openair2/RRC/LTE/MESSAGES/uper_decoder.c:83 /#3 0x591fcbdbedeb in get_nr_mtc /home/guido/repo/openairinterface5g/develop/openair2/LAYER2/NR_MAC_gNB/nr_radio_config.c:4216 /#4 0x591fcbd58188 in ue_context_setup_request /home/guido/repo/openairinterface5g/develop/openair2/LAYER2/NR_MAC_gNB/mac_rrc_dl_handler.c:635 /#5 0x591fcbb92568 in nr_initiate_handover /home/guido/repo/openairinterface5g/develop/openair2/RRC/NR/rrc_gNB_mobility.c:218 /#6 0x591fcbb9a37f in nr_rrc_trigger_n2_ho_target /home/guido/repo/openairinterface5g/develop/openair2/RRC/NR/rrc_gNB_mobility.c:526 /#7 0x591fcbaf7608 in rrc_gNB_process_e1_bearer_context_setup_resp /home/guido/repo/openairinterface5g/develop/openair2/RRC/NR/rrc_gNB.c:2640 /#8 0x591fcbafab7b in rrc_gnb_task /home/guido/repo/openairinterface5g/develop/openair2/RRC/NR/rrc_gNB.c:3019 /#9 0x7dcfca294ac2 in start_thread nptl/pthread_create.c:442 Root cause: ue_context_setup_request allocated NR_MeasurementTimingConfiguration via get_nr_mtc() but never freed it after creating the measurement gap configuration. The create_measgap_config function only reads from the structure and doesn't take ownership, so the caller must free it. --- openair2/LAYER2/NR_MAC_gNB/mac_rrc_dl_handler.c | 1 + 1 file changed, 1 insertion(+) diff --git a/openair2/LAYER2/NR_MAC_gNB/mac_rrc_dl_handler.c b/openair2/LAYER2/NR_MAC_gNB/mac_rrc_dl_handler.c index b3d596b524..debc0d5530 100644 --- a/openair2/LAYER2/NR_MAC_gNB/mac_rrc_dl_handler.c +++ b/openair2/LAYER2/NR_MAC_gNB/mac_rrc_dl_handler.c @@ -721,6 +721,7 @@ void ue_context_setup_request(const f1ap_ue_context_setup_req_t *req) /* free the memory we allocated above */ free_ue_context_setup_resp(&resp); ASN_STRUCT_FREE(asn_DEF_NR_CG_ConfigInfo, cg_configinfo); + ASN_STRUCT_FREE(asn_DEF_NR_MeasurementTimingConfiguration, mtc); } void ue_context_modification_request(const f1ap_ue_context_mod_req_t *req) From 5e3b4c497c54e9d31517b1873cca745ad8b86bfd Mon Sep 17 00:00:00 2001 From: Guido Casati Date: Tue, 23 Sep 2025 16:46:41 +0200 Subject: [PATCH 71/97] Refactor handover context cleanup and fix memory leak in N2 handover source gNB path - Add missing handover context cleanup in rrc_CU_process_ue_context_release_complete - Fix 16-byte handover context leak in N2 handover source gNB trigger path AddressSanitizer backtrace: Direct leak of 16 byte(s) in 1 object(s) allocated from: /#0 0x7fee67cb4a57 in __interceptor_calloc ../../../../src/libsanitizer/asan/asan_malloc_linux.cpp:154 /#1 0x58059e33454b in calloc_or_fail /home/guido/repo/openairinterface5g/develop/common/utils/utils.h:74 /#2 0x58059e33454b in alloc_ho_ctx /home/guido/repo/openairinterface5g/develop/openair2/RRC/NR/rrc_gNB_mobility.c:47 /#3 0x58059e339e4d in nr_rrc_trigger_n2_ho /home/guido/repo/openairinterface5g/develop/openair2/RRC/NR/rrc_gNB_mobility.c:556 /#4 0x58059e33ae93 in nr_HO_N2_trigger_telnet /home/guido/repo/openairinterface5g/develop/openair2/RRC/NR/rrc_gNB_mobility.c:611 Root cause: Handover context allocated in N2 handover source gNB trigger path (nr_rrc_trigger_n2_ho) for any successful N2 handover but not properly freed in all handover completion scenarios. The UE context release complete handler was missing cleanup for handover context. Solution: refactor handover context cleanup - Move nr_rrc_finalize_ho() call to rrc_delete_ue_data() for centralized cleanup - Remove redundant nr_rrc_finalize_ho() calls from handover failure paths: * rrc_CU_process_ue_context_release_request() - UE not deleted yet * invalidate_du_connections() - UE will be deleted later - Keep nr_rrc_finalize_ho() calls in handover success paths where UE continues operating - Ensures handover context is cleaned up exactly once when UE is actually deleted - Prevents potential double-free and use-after-free issues in handover cleanup This centralizes all UE data cleanup in rrc_delete_ue_data() and ensures handover context is properly managed throughout the UE lifecycle and cleaned up when UE delete happens. --- openair2/RRC/NR/rrc_gNB.c | 3 +++ openair2/RRC/NR/rrc_gNB_du.c | 4 +--- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/openair2/RRC/NR/rrc_gNB.c b/openair2/RRC/NR/rrc_gNB.c index 2661531c27..aea6fb9259 100644 --- a/openair2/RRC/NR/rrc_gNB.c +++ b/openair2/RRC/NR/rrc_gNB.c @@ -2338,6 +2338,9 @@ static void rrc_CU_process_ue_context_release_request(MessageDef *msg_p, sctp_as static void rrc_delete_ue_data(gNB_RRC_UE_t *UE) { + /* Clean up handover context if it exists */ + if (UE->ho_context) + nr_rrc_finalize_ho(UE); ASN_STRUCT_FREE(asn_DEF_NR_UE_NR_Capability, UE->UE_Capability_nr); ASN_STRUCT_FREE(asn_DEF_NR_CellGroupConfig, UE->masterCellGroup); ASN_STRUCT_FREE(asn_DEF_NR_MeasResults, UE->measResults); diff --git a/openair2/RRC/NR/rrc_gNB_du.c b/openair2/RRC/NR/rrc_gNB_du.c index e104641edc..edf6f19164 100644 --- a/openair2/RRC/NR/rrc_gNB_du.c +++ b/openair2/RRC/NR/rrc_gNB_du.c @@ -453,10 +453,8 @@ static int invalidate_du_connections(gNB_RRC_INST *rrc, sctp_assoc_t assoc_id) RB_FOREACH(ue_context_p, rrc_nr_ue_tree_s, &rrc->rrc_ue_head) { gNB_RRC_UE_t *UE = &ue_context_p->ue_context; uint32_t ue_id = UE->rrc_ue_id; - if (UE->ho_context != NULL) { + if (UE->ho_context != NULL) LOG_W(NR_RRC, "DU disconnected while handover for UE %d active\n", ue_id); - nr_rrc_finalize_ho(UE); - } f1_ue_data_t ue_data = cu_get_f1_ue_data(ue_id); if (ue_data.du_assoc_id != assoc_id) continue; /* this UE is on another DU */ From feea066c3f1922e41c23859544d287fb7a11cf5e Mon Sep 17 00:00:00 2001 From: Guido Casati Date: Tue, 23 Sep 2025 16:15:15 +0200 Subject: [PATCH 72/97] Fix measurement gap config memory leak in create_measgap_config - Add missing ASN_STRUCT_FREE for NR_MeasGapConfig in create_measgap_config - Fix 48-byte measurement gap config leak in get_gap_config_from_smtc AddressSanitizer backtrace: Direct leak of 48 byte(s) in 1 object(s) allocated from: /#0 0x7d32adab4a57 in __interceptor_calloc ../../../../src/libsanitizer/asan/asan_malloc_linux.cpp:154 /#1 0x5751f99cbb79 in calloc_or_fail /home/guido/repo/openairinterface5g/develop/common/utils/utils.h:74 /#2 0x5751f99cbb79 in get_gap_config_from_smtc /home/guido/repo/openairinterface5g/develop/openair2/LAYER2/NR_MAC_gNB/nr_radio_config.c:4164 /#3 0x5751f99cbb79 in create_measgap_config /home/guido/repo/openairinterface5g/develop/openair2/LAYER2/NR_MAC_gNB/nr_radio_config.c:4310 /#4 0x5751f9964abe in ue_context_setup_request /home/guido/repo/openairinterface5g/develop/openair2/LAYER2/NR_MAC_gNB/mac_rrc_dl_handler.c:709 /#5 0x5751f979e568 in nr_initiate_handover /home/guido/repo/openairinterface5g/develop/openair2/RRC/NR/rrc_gNB_mobility.c:218 /#6 0x5751f97a637f in nr_rrc_trigger_n2_ho_target /home/guido/repo/openairinterface5g/develop/openair2/RRC/NR/rrc_gNB_mobility.c:526 /#7 0x5751f9703608 in rrc_gNB_process_e1_bearer_context_setup_resp /home/guido/repo/openairinterface5g/develop/openair2/RRC/NR/rrc_gNB.c:2641 /#8 0x5751f9706b7b in rrc_gnb_task /home/guido/repo/openairinterface5g/develop/openair2/RRC/NR/rrc_gNB.c:3020 /#9 0x7d32ac494ac2 in start_thread nptl/pthread_create.c:442 Root cause: create_measgap_config allocated NR_MeasGapConfig via get_gap_config_from_smtc() but never freed it after extracting the needed values to populate the measgap_config_t return structure. --- openair2/LAYER2/NR_MAC_gNB/nr_radio_config.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/openair2/LAYER2/NR_MAC_gNB/nr_radio_config.c b/openair2/LAYER2/NR_MAC_gNB/nr_radio_config.c index 44cb211b1d..8d6731dbbd 100644 --- a/openair2/LAYER2/NR_MAC_gNB/nr_radio_config.c +++ b/openair2/LAYER2/NR_MAC_gNB/nr_radio_config.c @@ -4361,8 +4361,10 @@ measgap_config_t create_measgap_config(const NR_MeasurementTimingConfiguration_t const NR_SSB_MTC_t *ssb_mtc = &ft->ssb_MeasurementTimingConfiguration; NR_MeasGapConfig_t *measGapConfig = get_gap_config_from_smtc(ssb_mtc); const NR_GapConfig_t *gap_config = get_gap_config(measGapConfig); - if (!gap_config) + if (!gap_config) { + ASN_STRUCT_FREE(asn_DEF_NR_MeasGapConfig, measGapConfig); return mgc; + } mgc.mgrp_ms = get_mgrp(gap_config->mgrp); DevAssert(mgc.mgrp_ms != -1); @@ -4385,6 +4387,10 @@ measgap_config_t create_measgap_config(const NR_MeasurementTimingConfiguration_t mgc.mgl_slots = ((int)(10 * (mgc.mgl_ms + max_k2)) << scs) / 10; mgc.enable = true; + + // Free the allocated measGapConfig structure after we're done using gap_config + ASN_STRUCT_FREE(asn_DEF_NR_MeasGapConfig, measGapConfig); + return mgc; } From c2e8743a7b7ce7a0590f8db7ac1aa3f8686370b4 Mon Sep 17 00:00:00 2001 From: Guido Casati Date: Mon, 29 Sep 2025 17:34:15 +0200 Subject: [PATCH 73/97] Optimize create_measgap_config to eliminate unnecessary allocation - Replace inefficient 'allocate, extract, free' pattern with direct value extraction - Remove unused functions get_gap_config_from_smtc() and get_gap_config() - Add extract_gap_config_from_smtc() that extracts values directly without allocation - Eliminates use-after-free risk and improves performance - No allocation overhead, no memory leaks --- openair2/LAYER2/NR_MAC_gNB/nr_radio_config.c | 126 ++++++++----------- 1 file changed, 49 insertions(+), 77 deletions(-) diff --git a/openair2/LAYER2/NR_MAC_gNB/nr_radio_config.c b/openair2/LAYER2/NR_MAC_gNB/nr_radio_config.c index 8d6731dbbd..a2b7c26678 100644 --- a/openair2/LAYER2/NR_MAC_gNB/nr_radio_config.c +++ b/openair2/LAYER2/NR_MAC_gNB/nr_radio_config.c @@ -50,6 +50,11 @@ #define NR_MAX_SUPPORTED_DL_LAYERS 4 +/* Default values for measurement gap configuration */ +#define DEFAULT_MGRP NR_GapConfig__mgrp_ms160 +#define DEFAULT_MGTA NR_GapConfig__mgta_ms0dot5 +#define DEFAULT_MGL NR_GapConfig__mgl_ms6 + #define PUCCH2_SIZE 8 const uint8_t slotsperframe[5] = {10, 20, 40, 80, 160}; @@ -4211,53 +4216,6 @@ NR_ReconfigurationWithSync_t *get_reconfiguration_with_sync(rnti_t rnti, uid_t u return reconfigurationWithSync; } -static NR_MeasGapConfig_t *get_gap_config_from_smtc(const NR_SSB_MTC_t *ssb_mtc) -{ - NR_MeasGapConfig_t *measGapConfig = calloc_or_fail(1, sizeof(*measGapConfig)); - measGapConfig->ext1 = calloc_or_fail(1, sizeof(*measGapConfig->ext1)); - measGapConfig->ext1->gapUE = calloc_or_fail(1, sizeof(*measGapConfig->ext1->gapUE)); - measGapConfig->ext1->gapUE->present = NR_SetupRelease_GapConfig_PR_setup; - NR_GapConfig_t *gap_config = calloc_or_fail(1, sizeof(*gap_config)); - measGapConfig->ext1->gapUE->choice.setup = gap_config; - - // mgta = Measurement Gap Timing Advance, to provide sufficient time for the UE to re-tune its transceiver - // This allows the Measurement Gap to extend mgta ms either side of the SS/PBCH Measurement Window. - gap_config->mgta = NR_GapConfig__mgta_ms0dot5; - - // mgrp = Measurement Gap Repetition Period - // gapOffset = It defines the start of the Measurement Gaps relative to the start of the radio frame with SFN = 0 - // The Measurement Gaps need to be synchronized with the SS/PBCH transmissions which are to be measured - gap_config->mgrp = NR_GapConfig__mgrp_ms160; - switch (ssb_mtc->periodicityAndOffset.present) { - case NR_SSB_MTC__periodicityAndOffset_PR_sf20: - gap_config->gapOffset = ssb_mtc->periodicityAndOffset.choice.sf20; - break; - case NR_SSB_MTC__periodicityAndOffset_PR_sf40: - gap_config->gapOffset = ssb_mtc->periodicityAndOffset.choice.sf40; - break; - case NR_SSB_MTC__periodicityAndOffset_PR_sf80: - gap_config->gapOffset = ssb_mtc->periodicityAndOffset.choice.sf80; - break; - case NR_SSB_MTC__periodicityAndOffset_PR_sf160: - gap_config->gapOffset = ssb_mtc->periodicityAndOffset.choice.sf160; - break; - default: - LOG_W(NR_RRC, "SMTC periodicity higher than MGRP\n"); - // With this configuration, not all SSBs belong to the Measurement Gap. - if (ssb_mtc->periodicityAndOffset.present == NR_SSB_MTC__periodicityAndOffset_PR_sf5) { - gap_config->gapOffset = ssb_mtc->periodicityAndOffset.choice.sf5; - } else if (ssb_mtc->periodicityAndOffset.present == NR_SSB_MTC__periodicityAndOffset_PR_sf10) { - gap_config->gapOffset = ssb_mtc->periodicityAndOffset.choice.sf10; - } - } - - // mgl = Measurement Gap Length - // FIXME: At least the duration of the SMTC plus 2 times the mgta should be enough, - // however, at the moment it only works by setting the maximum value - gap_config->mgl = NR_GapConfig__mgl_ms6; - - return measGapConfig; -} NR_MeasurementTimingConfiguration_t *get_nr_mtc(uint8_t *buf, uint32_t len) { @@ -4330,25 +4288,37 @@ static float get_mgta(long mgta) } } -/** @brief Return Measurement Gap Configuration, from ASN.1 config */ -const NR_GapConfig_t *get_gap_config(const NR_MeasGapConfig_t *mgc) +/** @brief Extract gapOffset from SSB MTC periodicity */ +static bool extract_gap_offset_from_smtc(const NR_SSB_MTC_t *ssb_mtc, long *gapOffset) { - if (mgc->gapFR2 && mgc->gapFR2->present == NR_SetupRelease_GapConfig_PR_setup) - return mgc->gapFR2->choice.setup; + if (!ssb_mtc) + return false; - // FR1 case - if (!mgc->ext1) - return NULL; + // Extract gapOffset based on periodicity + switch (ssb_mtc->periodicityAndOffset.present) { + case NR_SSB_MTC__periodicityAndOffset_PR_sf20: + *gapOffset = ssb_mtc->periodicityAndOffset.choice.sf20; + break; + case NR_SSB_MTC__periodicityAndOffset_PR_sf40: + *gapOffset = ssb_mtc->periodicityAndOffset.choice.sf40; + break; + case NR_SSB_MTC__periodicityAndOffset_PR_sf80: + *gapOffset = ssb_mtc->periodicityAndOffset.choice.sf80; + break; + case NR_SSB_MTC__periodicityAndOffset_PR_sf160: + *gapOffset = ssb_mtc->periodicityAndOffset.choice.sf160; + break; + default: + LOG_W(NR_RRC, "SMTC periodicity higher than MGRP\n"); + if (ssb_mtc->periodicityAndOffset.present == NR_SSB_MTC__periodicityAndOffset_PR_sf5) { + *gapOffset = ssb_mtc->periodicityAndOffset.choice.sf5; + } else if (ssb_mtc->periodicityAndOffset.present == NR_SSB_MTC__periodicityAndOffset_PR_sf10) { + *gapOffset = ssb_mtc->periodicityAndOffset.choice.sf10; + } + break; + } - const NR_SetupRelease_GapConfig_t *gapUE = mgc->ext1->gapUE; - if (gapUE && gapUE->present == NR_SetupRelease_GapConfig_PR_setup) - return gapUE->choice.setup; - - const NR_SetupRelease_GapConfig_t *gapFR1 = mgc->ext1->gapFR1; - if (gapFR1 && gapFR1->present == NR_SetupRelease_GapConfig_PR_setup) - return gapFR1->choice.setup; - - return NULL; + return true; } measgap_config_t create_measgap_config(const NR_MeasurementTimingConfiguration_t *mtc, int scs, int min_rxtxtime) @@ -4359,20 +4329,25 @@ measgap_config_t create_measgap_config(const NR_MeasurementTimingConfiguration_t DevAssert(mt != NULL && mt->frequencyAndTiming != NULL); const struct NR_MeasTiming__frequencyAndTiming *ft = mt->frequencyAndTiming; const NR_SSB_MTC_t *ssb_mtc = &ft->ssb_MeasurementTimingConfiguration; - NR_MeasGapConfig_t *measGapConfig = get_gap_config_from_smtc(ssb_mtc); - const NR_GapConfig_t *gap_config = get_gap_config(measGapConfig); - if (!gap_config) { - ASN_STRUCT_FREE(asn_DEF_NR_MeasGapConfig, measGapConfig); + + // Initialize with defaults + long mgrp = DEFAULT_MGRP; + long gapOffset = 0; + long mgta = DEFAULT_MGTA; + long mgl = DEFAULT_MGL; + + // Extract gapOffset from SSB MTC periodicity + if (!extract_gap_offset_from_smtc(ssb_mtc, &gapOffset)) { return mgc; } - mgc.mgrp_ms = get_mgrp(gap_config->mgrp); + mgc.mgrp_ms = get_mgrp(mgrp); DevAssert(mgc.mgrp_ms != -1); - mgc.mgrp = gap_config->mgrp; + mgc.mgrp = mgrp; - mgc.gapOffset = gap_config->gapOffset; - mgc.mgta = gap_config->mgta; - mgc.n_slots_mgta = ((int)(10 * get_mgta(gap_config->mgta)) << scs) / 10; + mgc.gapOffset = gapOffset; + mgc.mgta = mgta; + mgc.n_slots_mgta = ((int)(10 * get_mgta(mgta)) << scs) / 10; // We start the timer K2 slots earlier to avoid scheduling feedback PUCCHs inside measGap // or depending on the current min_rxtxtime earlier @@ -4381,16 +4356,13 @@ measgap_config_t create_measgap_config(const NR_MeasurementTimingConfiguration_t mgc.n_slots_advance = mgc.n_slots_mgta + max_k2; - mgc.mgl_ms = get_mgl(gap_config->mgl); + mgc.mgl_ms = get_mgl(mgl); DevAssert(mgc.mgl_ms != -1); - mgc.mgl = gap_config->mgl; + mgc.mgl = mgl; mgc.mgl_slots = ((int)(10 * (mgc.mgl_ms + max_k2)) << scs) / 10; mgc.enable = true; - // Free the allocated measGapConfig structure after we're done using gap_config - ASN_STRUCT_FREE(asn_DEF_NR_MeasGapConfig, measGapConfig); - return mgc; } From 3e8354727e03c3d8fce493732899943ca6630c3c Mon Sep 17 00:00:00 2001 From: Guido Casati Date: Tue, 23 Sep 2025 14:15:44 +0200 Subject: [PATCH 74/97] Remove redundant NULL checks for handover encoder functions Handoer encoder functions never return NULL - they either return a valid pointer or terminate the program. Remove dead code paths. --- openair3/NGAP/ngap_gNB.c | 29 +++++++++-------------------- 1 file changed, 9 insertions(+), 20 deletions(-) diff --git a/openair3/NGAP/ngap_gNB.c b/openair3/NGAP/ngap_gNB.c index 68e77d369c..7cc8041ad5 100644 --- a/openair3/NGAP/ngap_gNB.c +++ b/openair3/NGAP/ngap_gNB.c @@ -296,10 +296,7 @@ int ngap_handover_required(instance_t instance, ngap_handover_required_t *msg) DevAssert(ue_context_p->gNB_ue_ngap_id == msg->gNB_ue_ngap_id); NGAP_NGAP_PDU_t *pdu = encode_ng_handover_required(msg); - if (!pdu) { - NGAP_ERROR("Failed to encode Handover Required\n"); - return -1; - } + DevAssert(pdu != NULL); if (LOG_DEBUGFLAG(DEBUG_ASN1)) xer_fprint(stdout, &asn_DEF_NGAP_NGAP_PDU, pdu); @@ -339,10 +336,7 @@ static int ngap_gNB_handover_failure(instance_t instance, const ngap_handover_fa DevAssert(ngap_gNB_instance_p != NULL); NGAP_NGAP_PDU_t *pdu = encode_ng_handover_failure(msg); - if (!pdu) { - NGAP_ERROR("Failed to encode NG Handover Failure\n"); - return -1; - } + DevAssert(pdu != NULL); if (ngap_gNB_encode_pdu(pdu, &buffer, &length) < 0) { NGAP_ERROR("Failed to encode HANDOVER FAILURE MESSAGE\n"); @@ -377,10 +371,7 @@ static int ngap_gNB_handover_request_acknowledge(instance_t instance, ngap_hando DevAssert(ngap != NULL); NGAP_NGAP_PDU_t *pdu = encode_ng_handover_request_ack(msg); - if (!pdu) { - NGAP_ERROR("Failed to encode NG Handover Request Acknowledge\n"); - return -1; - } + DevAssert(pdu != NULL); if (LOG_DEBUGFLAG(DEBUG_ASN1)) xer_fprint(stdout, &asn_DEF_NGAP_NGAP_PDU, pdu); @@ -436,6 +427,10 @@ static int ngap_gNB_handover_notify(instance_t instance, const ngap_handover_not } NGAP_NGAP_PDU_t *pdu = encode_ng_handover_notify(msg); + if (!pdu) { + NGAP_ERROR("Failed to encode NG Handover Notify\n"); + return -1; + } if (ngap_gNB_encode_pdu(pdu, &ba.buf, (uint32_t *)&ba.len) < 0) { NGAP_ERROR("Failed to encode NG Handover Notify message\n"); @@ -479,10 +474,7 @@ static int ngap_gNB_handover_cancel(instance_t instance, const ngap_handover_can /* Encode NGAP PDU */ NGAP_NGAP_PDU_t *pdu = encode_ng_handover_cancel(msg); - if (pdu == NULL) { - NGAP_ERROR("Failed to encode NG Handover Cancel\n"); - return -1; - } + DevAssert(pdu != NULL); if (ngap_gNB_encode_pdu(pdu, &ba.buf, (uint32_t *)&ba.len) < 0) { NGAP_ERROR("Failed to encode NG Handover Cancel message PDU\n"); @@ -517,10 +509,7 @@ int ngap_gNB_handle_ul_ran_status_transfer(instance_t instance, const ngap_ran_s DevAssert(ue_context_p->gNB_ue_ngap_id == msg->gnb_ue_ngap_id); NGAP_NGAP_PDU_t *pdu = encode_ng_ul_ran_status_transfer(msg); - if (!pdu) { - NGAP_ERROR("Failed to encode UL RAN Status Transfer\n"); - return -1; - } + DevAssert(pdu != NULL); byte_array_t out = {.buf = NULL, .len = 0}; if (ngap_gNB_encode_pdu(pdu, &out.buf, (uint32_t *)&out.len) < 0) { From 0cbed89b03c5201ae95184d0bf9519cada2d0569 Mon Sep 17 00:00:00 2001 From: Guido Casati Date: Tue, 23 Sep 2025 14:20:39 +0200 Subject: [PATCH 75/97] Replace malloc_or_fail with calloc_or_fail for safer memory allocation - Update encoder functions in ngap_gNB_mobility_management.c to use calloc_or_fail - Provides zero-initialized memory and consistent error handling - Prevents uninitialized fields and improves memory safety Also: - Update NG Setup Request to use calloc_or_fail --- openair3/NGAP/ngap_gNB.c | 18 +++++++++--------- openair3/NGAP/ngap_gNB_mobility_management.c | 14 +++++++------- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/openair3/NGAP/ngap_gNB.c b/openair3/NGAP/ngap_gNB.c index 7cc8041ad5..fb4d6c135b 100644 --- a/openair3/NGAP/ngap_gNB.c +++ b/openair3/NGAP/ngap_gNB.c @@ -677,7 +677,7 @@ static int ngap_gNB_generate_ng_setup_request( pdu.choice.initiatingMessage->value.present = NGAP_InitiatingMessage__value_PR_NGSetupRequest; out = &pdu.choice.initiatingMessage->value.choice.NGSetupRequest; /* mandatory */ - ie = (NGAP_NGSetupRequestIEs_t *)calloc(1, sizeof(NGAP_NGSetupRequestIEs_t)); + ie = calloc_or_fail(1, sizeof(NGAP_NGSetupRequestIEs_t)); ie->id = NGAP_ProtocolIE_ID_id_GlobalRANNodeID; ie->criticality = NGAP_Criticality_reject; ie->value.present = NGAP_NGSetupRequestIEs__value_PR_GlobalRANNodeID; @@ -700,7 +700,7 @@ static int ngap_gNB_generate_ng_setup_request( /* optional */ if (instance_p->gNB_name) { - ie = (NGAP_NGSetupRequestIEs_t *)calloc(1, sizeof(NGAP_NGSetupRequestIEs_t)); + ie = calloc_or_fail(1, sizeof(NGAP_NGSetupRequestIEs_t)); ie->id = NGAP_ProtocolIE_ID_id_RANNodeName; ie->criticality = NGAP_Criticality_ignore; ie->value.present = NGAP_NGSetupRequestIEs__value_PR_RANNodeName; @@ -710,28 +710,28 @@ static int ngap_gNB_generate_ng_setup_request( } /* mandatory */ - ie = (NGAP_NGSetupRequestIEs_t *)calloc(1, sizeof(NGAP_NGSetupRequestIEs_t)); + ie = calloc_or_fail(1, sizeof(NGAP_NGSetupRequestIEs_t)); ie->id = NGAP_ProtocolIE_ID_id_SupportedTAList; ie->criticality = NGAP_Criticality_reject; ie->value.present = NGAP_NGSetupRequestIEs__value_PR_SupportedTAList; { - ta = (NGAP_SupportedTAItem_t *)calloc(1, sizeof(NGAP_SupportedTAItem_t)); + ta = (NGAP_SupportedTAItem_t *)calloc_or_fail(1, sizeof(NGAP_SupportedTAItem_t)); INT24_TO_OCTET_STRING(instance_p->tac, &ta->tAC); { for (int i = 0; i < ngap_amf_data_p->broadcast_plmn_num; ++i) { - plmn = (NGAP_BroadcastPLMNItem_t *)calloc(1, sizeof(NGAP_BroadcastPLMNItem_t)); + plmn = (NGAP_BroadcastPLMNItem_t *)calloc_or_fail(1, sizeof(NGAP_BroadcastPLMNItem_t)); ngap_plmn_t *plmn_req = &instance_p->plmn[ngap_amf_data_p->broadcast_plmn_index[i]]; plmn_id_t *plmn_id = &instance_p->plmn[ngap_amf_data_p->broadcast_plmn_index[i]].plmn; MCC_MNC_TO_TBCD(plmn_id->mcc, plmn_id->mnc, plmn_id->mnc_digit_length, &plmn->pLMNIdentity); for (int si = 0; si < plmn_req->num_nssai; si++) { - ssi = (NGAP_SliceSupportItem_t *)calloc(1, sizeof(NGAP_SliceSupportItem_t)); + ssi = (NGAP_SliceSupportItem_t *)calloc_or_fail(1, sizeof(NGAP_SliceSupportItem_t)); INT8_TO_OCTET_STRING(plmn_req->s_nssai[si].sst, &ssi->s_NSSAI.sST); const uint32_t sd = plmn_req->s_nssai[si].sd; if (sd != 0xffffff) { - ssi->s_NSSAI.sD = calloc(1, sizeof(NGAP_SD_t)); - ssi->s_NSSAI.sD->buf = calloc(3, sizeof(uint8_t)); + ssi->s_NSSAI.sD = calloc_or_fail(1, sizeof(NGAP_SD_t)); + ssi->s_NSSAI.sD->buf = calloc_or_fail(3, sizeof(uint8_t)); ssi->s_NSSAI.sD->size = 3; ssi->s_NSSAI.sD->buf[0] = (sd & 0xff0000) >> 16; ssi->s_NSSAI.sD->buf[1] = (sd & 0x00ff00) >> 8; @@ -749,7 +749,7 @@ static int ngap_gNB_generate_ng_setup_request( asn1cSeqAdd(&out->protocolIEs.list, ie); /* mandatory */ - ie = (NGAP_NGSetupRequestIEs_t *)calloc(1, sizeof(NGAP_NGSetupRequestIEs_t)); + ie = calloc_or_fail(1, sizeof(NGAP_NGSetupRequestIEs_t)); ie->id = NGAP_ProtocolIE_ID_id_DefaultPagingDRX; ie->criticality = NGAP_Criticality_ignore; ie->value.present = NGAP_NGSetupRequestIEs__value_PR_PagingDRX; diff --git a/openair3/NGAP/ngap_gNB_mobility_management.c b/openair3/NGAP/ngap_gNB_mobility_management.c index a6ff4b39a1..95290f7bc3 100644 --- a/openair3/NGAP/ngap_gNB_mobility_management.c +++ b/openair3/NGAP/ngap_gNB_mobility_management.c @@ -37,7 +37,7 @@ * (9.2.3.1 of 3GPP TS 38.413) NG-RAN node → AMF */ NGAP_NGAP_PDU_t *encode_ng_handover_required(const ngap_handover_required_t *msg) { - NGAP_NGAP_PDU_t *pdu = malloc_or_fail(sizeof(*pdu)); + NGAP_NGAP_PDU_t *pdu = calloc_or_fail(1, sizeof(*pdu)); /* Prepare the NGAP message to encode */ pdu->present = NGAP_NGAP_PDU_PR_initiatingMessage; @@ -198,7 +198,7 @@ NGAP_NGAP_PDU_t *encode_ng_handover_required(const ngap_handover_required_t *msg NGAP_NGAP_PDU_t *encode_ng_handover_failure(const ngap_handover_failure_t *msg) { - NGAP_NGAP_PDU_t *pdu = malloc_or_fail(sizeof(*pdu)); + NGAP_NGAP_PDU_t *pdu = calloc_or_fail(1, sizeof(*pdu)); /* Prepare the NGAP message to encode */ pdu->present = NGAP_NGAP_PDU_PR_unsuccessfulOutcome; @@ -283,7 +283,7 @@ int decode_ng_handover_request(ngap_handover_request_t *out, const NGAP_NGAP_PDU // Mobility Restriction List (O) NGAP_FIND_PROTOCOLIE_BY_ID(NGAP_HandoverRequestIEs_t, ie, container, NGAP_ProtocolIE_ID_id_MobilityRestrictionList, false); if (ie != NULL) { - out->mobility_restriction = malloc_or_fail(sizeof(*out->mobility_restriction)); + out->mobility_restriction = calloc_or_fail(1, sizeof(*out->mobility_restriction)); *out->mobility_restriction = decode_ngap_mobility_restriction(&ie->value.choice.MobilityRestrictionList); } @@ -385,7 +385,7 @@ int decode_ng_handover_request(ngap_handover_request_t *out, const NGAP_NGAP_PDU NGAP_NGAP_PDU_t *encode_ng_handover_request_ack(ngap_handover_request_ack_t *msg) { - NGAP_NGAP_PDU_t *pdu = malloc_or_fail(sizeof(*pdu)); + NGAP_NGAP_PDU_t *pdu = calloc_or_fail(1, sizeof(*pdu)); pdu->present = NGAP_NGAP_PDU_PR_successfulOutcome; asn1cCalloc(pdu->choice.successfulOutcome, head); @@ -453,7 +453,7 @@ NGAP_NGAP_PDU_t *encode_ng_handover_request_ack(ngap_handover_request_ack_t *msg for (int j = 0; j < msg->pdusessions[pduSesIdx].ack_transfer.nb_of_qos_flow; j++) { asn1cSequenceAdd(transfer.qosFlowSetupResponseList.list, NGAP_QosFlowItemWithDataForwarding_t, qosItem); qosItem->qosFlowIdentifier = msg->pdusessions[pduSesIdx].ack_transfer.qos_setup_list[j].qfi; - qosItem->dataForwardingAccepted = malloc_or_fail(sizeof(*qosItem->dataForwardingAccepted)); + qosItem->dataForwardingAccepted = calloc_or_fail(1, sizeof(*qosItem->dataForwardingAccepted)); *qosItem->dataForwardingAccepted = NGAP_DataForwardingAccepted_data_forwarding_accepted; } @@ -565,7 +565,7 @@ void free_ng_handover_command(ngap_handover_command_t *msg) NGAP_NGAP_PDU_t *encode_ng_handover_notify(const ngap_handover_notify_t *msg) { - NGAP_NGAP_PDU_t *pdu = malloc_or_fail(sizeof(*pdu)); + NGAP_NGAP_PDU_t *pdu = calloc_or_fail(1, sizeof(*pdu)); // Message Type (M) pdu->present = NGAP_NGAP_PDU_PR_initiatingMessage; @@ -682,7 +682,7 @@ int decode_ng_handover_cancel_ack(ngap_handover_cancel_ack_t *out, const NGAP_NG /** @brief Encode NGAP UL RAN Status Transfer (9.2.3.14 of 3GPP TS 38.413) */ NGAP_NGAP_PDU_t *encode_ng_ul_ran_status_transfer(const ngap_ran_status_transfer_t *msg) { - NGAP_NGAP_PDU_t *pdu = malloc_or_fail(sizeof(*pdu)); + NGAP_NGAP_PDU_t *pdu = calloc_or_fail(1, sizeof(*pdu)); pdu->present = NGAP_NGAP_PDU_PR_initiatingMessage; asn1cCalloc(pdu->choice.initiatingMessage, head); head->procedureCode = NGAP_ProcedureCode_id_UplinkRANStatusTransfer; From c6a06dc81faeb866488cc51774337fc682ac7c5d Mon Sep 17 00:00:00 2001 From: Guido Casati Date: Fri, 19 Sep 2025 15:48:22 +0200 Subject: [PATCH 76/97] Improve error message consistency in NGAP handover functions - ngap_gNB_handover_notify: Fix misleading error message from 'Failed to encode' to 'Failed to find UE context' for better clarity - ngap_gNB_handle_ul_ran_status_transfer: Standardize error message format from 'Could not find' to 'Failed to find' for consistency across functions This ensures consistent and accurate error reporting across all NGAP handover functions. --- openair3/NGAP/ngap_gNB.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openair3/NGAP/ngap_gNB.c b/openair3/NGAP/ngap_gNB.c index fb4d6c135b..b03e153486 100644 --- a/openair3/NGAP/ngap_gNB.c +++ b/openair3/NGAP/ngap_gNB.c @@ -416,7 +416,7 @@ static int ngap_gNB_handover_notify(instance_t instance, const ngap_handover_not DevAssert(ngap_gNB_instance_p != NULL); if ((ue_context_p = ngap_get_ue_context(msg->gNB_ue_ngap_id)) == NULL) { - NGAP_ERROR("Failed to encode Handover Notify: no ue context associated with gNB_ue_ngap_id=%d\n", msg->gNB_ue_ngap_id); + NGAP_ERROR("Failed to find UE context for Handover Notify: gNB_ue_ngap_id=%d\n", msg->gNB_ue_ngap_id); return -1; } DevAssert(ue_context_p->gNB_ue_ngap_id == msg->gNB_ue_ngap_id); @@ -502,7 +502,7 @@ int ngap_gNB_handle_ul_ran_status_transfer(instance_t instance, const ngap_ran_s ngap_gNB_ue_context_t *ue_context_p = ngap_get_ue_context(msg->gnb_ue_ngap_id); if (!ue_context_p) { - NGAP_ERROR("Could not find UE context for gNB_ue_ngap_id %d\n", msg->gnb_ue_ngap_id); + NGAP_ERROR("Failed to find UE context for gNB_ue_ngap_id %d\n", msg->gnb_ue_ngap_id); return -1; } From 1fc057cb08c141674040d73b9773a5a904abf9c9 Mon Sep 17 00:00:00 2001 From: Guido Casati Date: Thu, 18 Sep 2025 22:09:51 +0200 Subject: [PATCH 77/97] Optimize PLMN identity assignment in handover procedures Replace field-by-field assignment with struct copy for plmn_identity. Both source and destination use identical plmn_id_t type, making this change safe and more maintainable. - Reduces lines of code - More readable and less error-prone - Single memory copy operation instead of multiple field assignments --- openair2/RRC/NR/rrc_gNB_NGAP.c | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/openair2/RRC/NR/rrc_gNB_NGAP.c b/openair2/RRC/NR/rrc_gNB_NGAP.c index 4c5b504f2d..b5915db574 100644 --- a/openair2/RRC/NR/rrc_gNB_NGAP.c +++ b/openair2/RRC/NR/rrc_gNB_NGAP.c @@ -1466,11 +1466,11 @@ void rrc_gNB_send_NGAP_HANDOVER_NOTIFY(gNB_RRC_INST *rrc, gNB_RRC_UE_t *UE) ho_notify->gNB_ue_ngap_id = UE->rrc_ue_id; ho_notify->amf_ue_ngap_id = UE->amf_ue_ngap_id; ho_notify->user_info.nrCellIdentity = rrc->nr_cellid; - ho_notify->user_info.target_ng_ran.tac = *du->setup_req->cell->info.tac; - ho_notify->user_info.target_ng_ran.targetgNBId = rrc->node_id; - ho_notify->user_info.target_ng_ran.plmn_identity.mcc = du->setup_req->cell->info.plmn.mcc; - ho_notify->user_info.target_ng_ran.plmn_identity.mnc = du->setup_req->cell->info.plmn.mnc; - ho_notify->user_info.target_ng_ran.plmn_identity.mnc_digit_length = du->setup_req->cell->info.plmn.mnc_digit_length; + + target_ran_node_id_t *target_ng_ran = &ho_notify->user_info.target_ng_ran; + target_ng_ran->tac = *du->setup_req->cell->info.tac; + target_ng_ran->targetgNBId = rrc->node_id; + target_ng_ran->plmn_identity = du->setup_req->cell->info.plmn; itti_send_msg_to_task(TASK_NGAP, rrc->module_id, msg_p); } @@ -1626,10 +1626,7 @@ void rrc_gNB_send_NGAP_HANDOVER_REQUIRED(gNB_RRC_INST *rrc, { LOG_I(NR_RRC, "Handover Preparation: send Handover Required (target gNB ID=%d, PCI=%d)\n", neighbour->gNB_ID, neighbour->physicalCellId); - const plmn_id_t plmn = {.mcc = neighbour->plmn.mnc, - .mnc = neighbour->plmn.mnc, - .mnc_digit_length = neighbour->plmn.mnc_digit_length}; - + const plmn_id_t plmn = neighbour->plmn; const target_ran_node_id_t target = { .plmn_identity = plmn, .tac = neighbour->tac, From 205bf7c993e59b91cea2eb4f81a5cd7289df46e9 Mon Sep 17 00:00:00 2001 From: Guido Casati Date: Fri, 19 Sep 2025 16:44:54 +0200 Subject: [PATCH 78/97] Add NULL check for DU lookup in handover notify Add early return if get_du_by_cell_id returns NULL to prevent segmentation fault when accessing DU structure members. --- openair2/RRC/NR/rrc_gNB_NGAP.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/openair2/RRC/NR/rrc_gNB_NGAP.c b/openair2/RRC/NR/rrc_gNB_NGAP.c index b5915db574..8c839304c8 100644 --- a/openair2/RRC/NR/rrc_gNB_NGAP.c +++ b/openair2/RRC/NR/rrc_gNB_NGAP.c @@ -1457,11 +1457,15 @@ void rrc_gNB_send_NGAP_HANDOVER_REQUEST_ACKNOWLEDGE(gNB_RRC_INST *rrc, gNB_RRC_U void rrc_gNB_send_NGAP_HANDOVER_NOTIFY(gNB_RRC_INST *rrc, gNB_RRC_UE_t *UE) { LOG_I(NR_RRC, "Triggering NGAP Handover Notify\n"); + nr_rrc_du_container_t *du = get_du_by_cell_id(rrc, rrc->nr_cellid); + if (du == NULL) { + LOG_E(NR_RRC, "Failed to send Handover Notify: no DU found with NR Cell ID=%lu\n", rrc->nr_cellid); + return; + } MessageDef *msg_p = itti_alloc_new_message(TASK_RRC_GNB, 0, NGAP_HANDOVER_NOTIFY); ngap_handover_notify_t *ho_notify = &NGAP_HANDOVER_NOTIFY(msg_p); memset(ho_notify, 0, sizeof(*ho_notify)); - nr_rrc_du_container_t *du = get_du_by_cell_id(rrc, rrc->nr_cellid); ho_notify->gNB_ue_ngap_id = UE->rrc_ue_id; ho_notify->amf_ue_ngap_id = UE->amf_ue_ngap_id; From 9d9f272e74beba0dfa40c5056019610b90893b7c Mon Sep 17 00:00:00 2001 From: Guido Casati Date: Fri, 19 Sep 2025 11:33:18 +0200 Subject: [PATCH 79/97] Refactor: split e1_send_bearer_updates into specialized functions Split monolithic e1_send_bearer_updates into three focused functions: - e1_send_bearer_updates(): DRB setup from F1 UE Context Modification Response - e1_request_pdcp_status(): Request PDCP status during inter-CU handover - e1_notify_pdcp_status(): Notify CU-UP with PDCP status during handover Extract common helpers: - append_e1_drb_mod_req(): Common DRB-to-mod for bearer request logic - e1_send_bearer_modification_request(): Common sending step Bug fixes: - Fix loop bounds (i < n instead of i == n) - Fix array access bounds checking - Fix DRB ID consistency Memory management: - Add missing free_e1ap_context_mod_request calls - Improve error handling with early returns In the process: - Simplify cuup_notify_reestablishment with FOR_EACH_SEQ_ARR loop - Adopted all callers to use new consolidated functions Improves maintainability and eliminates complex conditional logic. --- openair2/RRC/NR/nr_rrc_proto.h | 2 +- openair2/RRC/NR/rrc_gNB.c | 283 +++++++++++++++++++-------------- openair2/RRC/NR/rrc_gNB_NGAP.c | 2 +- 3 files changed, 166 insertions(+), 121 deletions(-) diff --git a/openair2/RRC/NR/nr_rrc_proto.h b/openair2/RRC/NR/nr_rrc_proto.h index fbd09d20a1..777ea9a8d1 100644 --- a/openair2/RRC/NR/nr_rrc_proto.h +++ b/openair2/RRC/NR/nr_rrc_proto.h @@ -127,6 +127,6 @@ void openair_rrc_gNB_configuration(gNB_RRC_INST *rrc, gNB_RrcConfigurationReq *c NR_SRB_ToAddModList_t *createSRBlist(gNB_RRC_UE_t *ue, uint8_t reestablish); NR_DRB_ToAddModList_t *createDRBlist(gNB_RRC_UE_t *ue, bool reestablish, bool do_integrity, bool do_ciphering); void activate_srb(gNB_RRC_UE_t *UE, int srb_id); -void e1_send_bearer_updates(gNB_RRC_INST *rrc, gNB_RRC_UE_t *UE, int n, f1ap_drb_setup_t *drbs, const ngap_drb_status_t *drb_status); +void e1_notify_pdcp_status(gNB_RRC_INST *rrc, gNB_RRC_UE_t *UE, const ngap_drb_status_t *drb_status); #endif diff --git a/openair2/RRC/NR/rrc_gNB.c b/openair2/RRC/NR/rrc_gNB.c index aea6fb9259..c2734c755a 100644 --- a/openair2/RRC/NR/rrc_gNB.c +++ b/openair2/RRC/NR/rrc_gNB.c @@ -879,22 +879,65 @@ static void fill_security_info(gNB_RRC_INST *rrc, gNB_RRC_UE_t *UE, security_inf nr_derive_key(UP_INT_ALG, secInfo->integrityProtectionAlgorithm, UE->kgnb, (uint8_t *)secInfo->integrityProtectionKey); } -/* \brief find existing PDU session inside E1AP Bearer Modif message, or - * point to new one. - * \param bearer_modif E1AP Bearer Modification Message - * \param pdu_id PDU session ID - * \return pointer to existing PDU session, or to new/unused one. */ -static pdu_session_to_mod_t *find_or_next_pdu_session(e1ap_bearer_mod_req_t *bearer_modif, int pdu_id) +/** @brief Add DRB modification to E1AP bearer modification request + * Finds PDU session by DRB ID, creates/updates PDU session modification entry + * and appends DRB modification + * @param req E1AP bearer modification request + * @param ue UE context + * @param drb_id DRB ID to find PDU session for + * @param drb_to_mod DRB modification data to append + * @return true if successful, false if PDU session not found */ +static bool append_e1_drb_mod_req(e1ap_bearer_mod_req_t *req, + gNB_RRC_UE_t *ue, + const int drb_id, + const DRB_nGRAN_to_mod_t *drb_to_mod) { - for (int i = 0; i < bearer_modif->numPDUSessionsMod; ++i) { - if (bearer_modif->pduSessionMod[i].sessionId == pdu_id) - return &bearer_modif->pduSessionMod[i]; + // Find PDU session by DRB ID + rrc_pdu_session_param_t *pdu = find_pduSession_from_drbId(ue, drb_id); + if (!pdu) { + LOG_E(NR_RRC, "UE %d: Failed to append E1 DRB mod, no PDU session found (DRB=%d)\n", ue->rrc_ue_id, drb_id); + return false; } - /* E1AP Bearer Modification has no PDU session to modify with that ID, create - * new entry */ - DevAssert(bearer_modif->numPDUSessionsMod < E1AP_MAX_NUM_PDU_SESSIONS - 1); - bearer_modif->numPDUSessionsMod += 1; - return &bearer_modif->pduSessionMod[bearer_modif->numPDUSessionsMod - 1]; + + int pdu_id = pdu->param.pdusession_id; + + // Find existing PDU session modification entry or create new one + pdu_session_to_mod_t *pdu_mod = NULL; + for (int i = 0; i < req->numPDUSessionsMod; ++i) { + if (req->pduSessionMod[i].sessionId == pdu_id) { + pdu_mod = &req->pduSessionMod[i]; + break; + } + } + + // Create new PDU session modification entry if not found + if (!pdu_mod) { + DevAssert(req->numPDUSessionsMod < E1AP_MAX_NUM_PDU_SESSIONS); + int new_index = req->numPDUSessionsMod++; + req->pduSessionMod[new_index].sessionId = pdu_id; + pdu_mod = &req->pduSessionMod[new_index]; + } + + // Append DRB modification + DevAssert(pdu_mod->numDRB2Modify < E1AP_MAX_NUM_DRBS); + pdu_mod->DRBnGRanModList[pdu_mod->numDRB2Modify++] = *drb_to_mod; + + return true; +} + +/** @brief Create DRB modification for reestablishment from existing DRB */ +static DRB_nGRAN_to_mod_t get_e1_drb_mod_reestablishment(const drb_t *drb, const bearer_context_pdcp_config_t *pdcp_config) +{ + DRB_nGRAN_to_mod_t drb_e1 = {0}; + drb_e1.id = drb->drb_id; + drb_e1.numDlUpParam = 1; + memcpy(&drb_e1.DlUpParamList[0].tl_info.tlAddress, &drb->du_tunnel_config.addr.buffer, sizeof(uint8_t) * 4); + drb_e1.DlUpParamList[0].tl_info.teId = drb->du_tunnel_config.teid; + /* PDCP configuration */ + drb_e1.pdcp_config = malloc_or_fail(sizeof(*drb_e1.pdcp_config)); + *drb_e1.pdcp_config = *pdcp_config; + drb_e1.pdcp_config->pDCP_Reestablishment = true; + return drb_e1; } /** @@ -907,44 +950,18 @@ static void cuup_notify_reestablishment(gNB_RRC_INST *rrc, gNB_RRC_UE_t *ue_p) .gNB_cu_up_ue_id = ue_p->rrc_ue_id, }; // Quit re-establishment notification if no CU-UP is associated - if (!is_cuup_associated(rrc)) { + if (!is_cuup_associated(rrc) || !ue_associated_to_cuup(rrc, ue_p)) { return; } - if (!ue_associated_to_cuup(rrc, ue_p)) - return; + + bool um_on_default_drb = rrc->configuration.um_on_default_drb; /* loop through active DRBs */ - for (int drb_id = 1; drb_id <= MAX_DRBS_PER_UE; drb_id++) { - drb_t *drb = get_drb(&ue_p->drbs, drb_id); - if (!drb) - continue; - /* fetch an existing PDU session for this DRB */ - rrc_pdu_session_param_t *pdu = find_pduSession_from_drbId(ue_p, drb_id); - if (pdu == NULL) { - LOG_E(RRC, "UE %d: E1 Bearer Context Modification: no PDU session for DRB ID %d\n", ue_p->rrc_ue_id, drb_id); - continue; + FOR_EACH_SEQ_ARR(drb_t *, drb, &ue_p->drbs) { + bearer_context_pdcp_config_t pdcp = set_bearer_context_pdcp_config(drb->pdcp_config, um_on_default_drb, ue_p->redcap_cap); + DRB_nGRAN_to_mod_t drb_e1 = get_e1_drb_mod_reestablishment(drb, &pdcp); + if (!append_e1_drb_mod_req(&req, ue_p, drb->drb_id, &drb_e1)) { + LOG_W(NR_RRC, "UE %d: Failed to append E1 DRB mod for reestablishment (DRB=%d)\n", ue_p->rrc_ue_id, drb->drb_id); } - /* Get pointer to existing (or new one) PDU session to modify in E1 */ - pdu_session_to_mod_t *pdu_e1 = find_or_next_pdu_session(&req, pdu->param.pdusession_id); - AssertError(pdu != NULL, - continue, - "UE %u: E1 Bearer Context Modification: PDU session %d to setup is null\n", - ue_p->rrc_ue_id, - pdu->param.pdusession_id); - /* Prepare PDU for E1 Bearear Context Modification Request */ - pdu_e1->sessionId = pdu->param.pdusession_id; - /* Fill DRB to setup with ID, DL TL and DL TEID */ - DRB_nGRAN_to_mod_t *drb_e1 = &pdu_e1->DRBnGRanModList[pdu_e1->numDRB2Modify]; - drb_e1->id = drb_id; - drb_e1->numDlUpParam = 1; - memcpy(&drb_e1->DlUpParamList[0].tl_info.tlAddress, &drb->du_tunnel_config.addr.buffer, sizeof(uint8_t) * 4); - drb_e1->DlUpParamList[0].tl_info.teId = drb->du_tunnel_config.teid; - /* PDCP configuration */ - if (!drb_e1->pdcp_config) - drb_e1->pdcp_config = malloc_or_fail(sizeof(*drb_e1->pdcp_config)); - *drb_e1->pdcp_config = set_bearer_context_pdcp_config(drb->pdcp_config, rrc->configuration.um_on_default_drb, ue_p->redcap_cap); - drb_e1->pdcp_config->pDCP_Reestablishment = true; - /* increase DRB to modify counter */ - pdu_e1->numDRB2Modify += 1; } #if 0 @@ -2094,12 +2111,14 @@ void rrc_gNB_process_dc_overall_timeout(const module_id_t gnb_mod_idP, x2ap_ENDC /* \brief fill E1 bearer modification's DRB from F1 DRB * \param drb_e1 pointer to a DRB inside an E1 bearer modification message * \param drb_f1 pointer to a DRB inside an F1 UE Ctxt modification Response */ -static void fill_e1_bearer_modif(DRB_nGRAN_to_mod_t *drb_e1, const f1ap_drb_setup_t *drb_f1) +static DRB_nGRAN_to_mod_t fill_e1_bearer_modif_from_f1(const f1ap_drb_setup_t *drb_f1) { - drb_e1->id = drb_f1->id; - drb_e1->numDlUpParam = drb_f1->up_dl_tnl_len; - drb_e1->DlUpParamList[0].tl_info.tlAddress = drb_f1->up_dl_tnl[0].tl_address; - drb_e1->DlUpParamList[0].tl_info.teId = drb_f1->up_dl_tnl[0].teid; + DRB_nGRAN_to_mod_t drb_e1 = {0}; + drb_e1.id = drb_f1->id; + drb_e1.numDlUpParam = drb_f1->up_dl_tnl_len; + drb_e1.DlUpParamList[0].tl_info.tlAddress = drb_f1->up_dl_tnl[0].tl_address; + drb_e1.DlUpParamList[0].tl_info.teId = drb_f1->up_dl_tnl[0].teid; + return drb_e1; } static gtpu_tunnel_t f1u_gtp_update(uint32_t teid, const in_addr_t addr) @@ -2126,89 +2145,113 @@ static void store_du_f1u_tunnel(const f1ap_drb_setup_t *drbs, int n, gNB_RRC_UE_ } } -static void fill_e1_bearer_modif_pdcp_status(gNB_RRC_UE_t *UE, - DRB_nGRAN_to_mod_t *drb_to_mod, - const int drb_id, - const bool um_on_default_drb, - const ngap_drb_status_t *drb_status) +static DRB_nGRAN_to_mod_t get_e1_drb_mod_pdcp_status(const drb_t *drb, + bearer_context_pdcp_config_t *pdcp_config, + const ngap_drb_status_t *drb_status) { DevAssert(drb_status); - drb_to_mod->id = drb_id; - drb_to_mod->pdcp_sn_status_requested = false; + DevAssert(pdcp_config); + DevAssert(drb); + DRB_nGRAN_to_mod_t drb_to_mod = {0}; + drb_to_mod.id = drb->drb_id; + drb_to_mod.pdcp_sn_status_requested = false; // PDCP SN Status Information - drb_to_mod->pdcp_config = calloc_or_fail(1, sizeof(*drb_to_mod->pdcp_config)); - drb_t *drb = get_drb(&UE->drbs, drb_id); - *drb_to_mod->pdcp_config = set_bearer_context_pdcp_config(drb->pdcp_config, um_on_default_drb, UE->redcap_cap); - drb_to_mod->pdcp_status = calloc_or_fail(1, sizeof(*drb_to_mod->pdcp_status)); - drb_to_mod->pdcp_status->dl_count.hfn = drb_status->dl_count.hfn; - drb_to_mod->pdcp_status->dl_count.sn = drb_status->dl_count.pdcp_sn; - drb_to_mod->pdcp_status->ul_count.hfn = drb_status->ul_count.hfn; - drb_to_mod->pdcp_status->ul_count.sn = drb_status->ul_count.pdcp_sn; + drb_to_mod.pdcp_config = calloc_or_fail(1, sizeof(*drb_to_mod.pdcp_config)); + *drb_to_mod.pdcp_config = *pdcp_config; + drb_to_mod.pdcp_status = calloc_or_fail(1, sizeof(*drb_to_mod.pdcp_status)); + drb_to_mod.pdcp_status->dl_count.hfn = drb_status->dl_count.hfn; + drb_to_mod.pdcp_status->dl_count.sn = drb_status->dl_count.pdcp_sn; + drb_to_mod.pdcp_status->ul_count.hfn = drb_status->ul_count.hfn; + drb_to_mod.pdcp_status->ul_count.sn = drb_status->ul_count.pdcp_sn; + return drb_to_mod; } -/** @brief Fill and send Bearer Context Modification Request with: - * (1) from F1 UE Context DRB to setup list, or (2) with PDCP Status Request */ -void e1_send_bearer_updates(gNB_RRC_INST *rrc, gNB_RRC_UE_t *UE, int n, f1ap_drb_setup_t *drbs, const ngap_drb_status_t *drb_status) +/** @brief Send E1 bearer context modification request to CU-UP */ +static void e1_send_bearer_modification_request(gNB_RRC_INST *rrc, gNB_RRC_UE_t *UE, e1ap_bearer_mod_req_t *req) { - // Quit bearer updates if no CU-UP is associated - if (!is_cuup_associated(rrc)) { + if (req->numPDUSessionsMod == 0) { + LOG_W(NR_RRC, "UE %d: No PDU sessions to modify in E1 bearer update\n", UE->rrc_ue_id); return; } - // we assume the same UE ID in CU-UP and CU-CP + DevAssert(req->numPDUSessions == 0); + req->secInfo = malloc_or_fail(sizeof(*req->secInfo)); + fill_security_info(rrc, UE, req->secInfo); + + sctp_assoc_t assoc_id = get_existing_cuup_for_ue(rrc, UE); + rrc->cucp_cuup.bearer_context_mod(assoc_id, req); +} + +/** @brief Send E1 bearer updates for DRBs to setup from F1 UE Context Modification Response */ +static void e1_send_bearer_updates(gNB_RRC_INST *rrc, gNB_RRC_UE_t *UE, int n, f1ap_drb_setup_t *drbs) +{ + if (!is_cuup_associated(rrc) || n <= 0) + return; + e1ap_bearer_mod_req_t req = { .gNB_cu_cp_ue_id = UE->rrc_ue_id, .gNB_cu_up_ue_id = UE->rrc_ue_id, }; - bool is_inter_cu_ho = UE->ho_context && UE->ho_context->source && !UE->ho_context->target; - int drb_index = 0; - FOR_EACH_SEQ_ARR(drb_t *, drb, &UE->drbs) { - int drb_id = drb->drb_id; - DRB_nGRAN_to_mod_t drb_to_mod = {0}; - if (n > 0) { - /* DRBs to Setup in F1 UE Context Modifcation Response */ - if (drb_index == n) - break; - const f1ap_drb_setup_t *drb_f1 = &drbs[drb_index]; - drb_id = drb_f1->id; - fill_e1_bearer_modif(&drb_to_mod, drb_f1); - } else if (is_inter_cu_ho) { - /* On-going handover: send PDCP Status Request */ - drb_t *drb = get_drb(&UE->drbs, drb_id); - if (!drb) { - LOG_E(NR_RRC, "Failed to send PDCP Status Request: DRB %d not found\n", drb_id); - continue; - } - drb_to_mod.id = drb_id; - // PDCP SN Status Request - drb_to_mod.pdcp_sn_status_requested = true; - LOG_I(NR_RRC, "PDCP Status requested (drb_id=%d)\n", drb_id); - } else if (drb_status) { - /* On-going handover: send PDCP Status */ - fill_e1_bearer_modif_pdcp_status(UE, &drb_to_mod, drb_id, rrc->configuration.um_on_default_drb, drb_status); - } + for (int i = 0; i < n; i++) { + const f1ap_drb_setup_t *drb_f1 = &drbs[i]; + int drb_id = drb_f1->id; rrc_pdu_session_param_t *pdu = find_pduSession_from_drbId(UE, drb_id); if (!pdu) continue; - pdu_session_to_mod_t *to_mod = find_or_next_pdu_session(&req, pdu->param.pdusession_id); - DevAssert(to_mod); - to_mod->sessionId = pdu->param.pdusession_id; - // Fill E1 DRB to Modify item - to_mod->DRBnGRanModList[to_mod->numDRB2Modify++] = drb_to_mod; - - drb_index++; + DRB_nGRAN_to_mod_t drb_to_mod = fill_e1_bearer_modif_from_f1(drb_f1); + if (!append_e1_drb_mod_req(&req, UE, drb_id, &drb_to_mod)) { + LOG_W(NR_RRC, "UE %d: Failed to append E1 DRB mod from F1 (DRB=%d)\n", UE->rrc_ue_id, drb_id); + } } - DevAssert(req.numPDUSessionsMod > 0); - DevAssert(req.numPDUSessions == 0); - // Always send security information - req.secInfo = malloc_or_fail(sizeof(*req.secInfo)); - fill_security_info(rrc, UE, req.secInfo); + e1_send_bearer_modification_request(rrc, UE, &req); + free_e1ap_context_mod_request(&req); +} - // send the E1 bearer modification request message to update F1-U tunnel info - sctp_assoc_t assoc_id = get_existing_cuup_for_ue(rrc, UE); - rrc->cucp_cuup.bearer_context_mod(assoc_id, &req); +/** @brief Request PDCP status from CU-UP during inter-CU handover */ +static void e1_request_pdcp_status(gNB_RRC_INST *rrc, gNB_RRC_UE_t *UE) +{ + if (!is_cuup_associated(rrc)) + return; + + e1ap_bearer_mod_req_t req = { + .gNB_cu_cp_ue_id = UE->rrc_ue_id, + .gNB_cu_up_ue_id = UE->rrc_ue_id, + }; + + FOR_EACH_SEQ_ARR(drb_t *, drb, &UE->drbs) { + DRB_nGRAN_to_mod_t drb_to_mod = {.id = drb->drb_id, .pdcp_sn_status_requested = true}; + LOG_D(NR_RRC, "PDCP Status requested (drb_id=%d)\n", drb->drb_id); + if (!append_e1_drb_mod_req(&req, UE, drb->drb_id, &drb_to_mod)) { + LOG_W(NR_RRC, "UE %d: Failed to append E1 DRB mod for PDCP status request (DRB=%d)\n", UE->rrc_ue_id, drb->drb_id); + } + } + + e1_send_bearer_modification_request(rrc, UE, &req); + free_e1ap_context_mod_request(&req); +} + +/** @brief Notify CU-UP with PDCP status during handover */ +void e1_notify_pdcp_status(gNB_RRC_INST *rrc, gNB_RRC_UE_t *UE, const ngap_drb_status_t *drb_status) +{ + if (!is_cuup_associated(rrc) || !drb_status) + return; + + e1ap_bearer_mod_req_t req = { + .gNB_cu_cp_ue_id = UE->rrc_ue_id, + .gNB_cu_up_ue_id = UE->rrc_ue_id, + }; + + FOR_EACH_SEQ_ARR(drb_t *, drb, &UE->drbs) { + LOG_I(NR_RRC, "Forward PDCP Status to CU-UP (drb_id=%d)\n", drb->drb_id); + bearer_context_pdcp_config_t pdcp_config = set_bearer_context_pdcp_config(drb->pdcp_config, rrc->configuration.um_on_default_drb, UE->redcap_cap); + DRB_nGRAN_to_mod_t drb_to_mod = get_e1_drb_mod_pdcp_status(drb, &pdcp_config, drb_status); + append_e1_drb_mod_req(&req, UE, drb->drb_id, &drb_to_mod); + } + + e1_send_bearer_modification_request(rrc, UE, &req); + free_e1ap_context_mod_request(&req); } static void rrc_CU_process_ue_context_setup_response(MessageDef *msg_p, instance_t instance) @@ -2262,7 +2305,7 @@ static void rrc_CU_process_ue_context_setup_response(MessageDef *msg_p, instance AssertFatal(UE->Srb[1].Active && UE->Srb[2].Active, "SRBs 1 and 2 must be active during DRB Establishment"); store_du_f1u_tunnel(resp->drbs, resp->drbs_len, UE); if (num_drb == 0) - e1_send_bearer_updates(rrc, UE, resp->drbs_len, resp->drbs, NULL); + e1_send_bearer_updates(rrc, UE, resp->drbs_len, resp->drbs); else cuup_notify_reestablishment(rrc, UE); } @@ -2397,9 +2440,11 @@ static void rrc_CU_process_ue_context_modification_response(MessageDef *msg_p, i gNB_RRC_UE_t *UE = &ue_context_p->ue_context; bool is_inter_cu_ho = UE->ho_context && UE->ho_context->source && !UE->ho_context->target; - if (resp->drbs_len > 0 || is_inter_cu_ho) { + if (resp->drbs_len > 0) { // DRB to setup store_du_f1u_tunnel(resp->drbs, resp->drbs_len, UE); - e1_send_bearer_updates(rrc, UE, resp->drbs_len, resp->drbs, NULL); + e1_send_bearer_updates(rrc, UE, resp->drbs_len, resp->drbs); + } else if (is_inter_cu_ho) { // PDCP status request + e1_request_pdcp_status(rrc, UE); } if (resp->du_to_cu_rrc_info) { diff --git a/openair2/RRC/NR/rrc_gNB_NGAP.c b/openair2/RRC/NR/rrc_gNB_NGAP.c index 8c839304c8..7d75384a9b 100644 --- a/openair2/RRC/NR/rrc_gNB_NGAP.c +++ b/openair2/RRC/NR/rrc_gNB_NGAP.c @@ -1725,7 +1725,7 @@ int rrc_gNB_process_NGAP_DL_RAN_STATUS_TRANSFER(MessageDef *msg_p, instance_t in s->dl_count.sn_len == NGAP_SN_LENGTH_18 ? "18-bit" : "12-bit"); // Send to PDCP layer - e1_send_bearer_updates(rrc, UE, 0, NULL, s); + e1_notify_pdcp_status(rrc, UE, s); } return 0; From 4aafd1e369f3ba1c95e24244c9d2ee997c66c3ee Mon Sep 17 00:00:00 2001 From: Guido Casati Date: Thu, 25 Sep 2025 11:28:17 +0200 Subject: [PATCH 80/97] Simplify N2 handover test to focus on N2 handover only - Remove extensive traffic testing before HO (iperf tests 030001, 030002) - Do 1 ping test after attachment - Reduce ping packet count from 100 to 50 packets for remaining connectivity test Time savings: ~2-3 minutes by removing traffic validation overhead --- ci-scripts/xml_files/container_5g_rfsim_n2_ho.xml | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/ci-scripts/xml_files/container_5g_rfsim_n2_ho.xml b/ci-scripts/xml_files/container_5g_rfsim_n2_ho.xml index 1b2e84046c..feffaedf05 100644 --- a/ci-scripts/xml_files/container_5g_rfsim_n2_ho.xml +++ b/ci-scripts/xml_files/container_5g_rfsim_n2_ho.xml @@ -31,9 +31,6 @@ 000002 000003 020001 - 020002 - 030001 - 030002 000004 040001 050001 @@ -110,7 +107,7 @@ acamas rfsim5g_ue acamas - -c 100 -i 0.25 + -c 50 -i 0.25 5 @@ -121,7 +118,7 @@ acamas rfsim5g_ext_dn acamas - -c 100 -i 0.25 + -c 50 -i 0.25 10 From dcd7ee711bbb9029803267727db41835f8bbc0ec Mon Sep 17 00:00:00 2001 From: Guido Casati Date: Thu, 18 Sep 2025 22:25:35 +0200 Subject: [PATCH 81/97] Improve RRC neighbor configuration documentation - Move 'What is a gNB neighbor?' explanation from rrc-usage.md to handover-tutorial.md where it belongs - Replace generic parameter names with actual configuration parameter names and specific descriptions (e.g. gNB_ID, nr_cellid) - Add example configuration structure showing proper syntax - Add missing 'band' parameter that was not listed before - Provide concrete examples and exact constraints for each parameter - Cross-reference between documents for better organization This addresses documentation issues where generic parameter descriptions were not useful for developers configuring neighbor cells. --- doc/RRC/rrc-usage.md | 55 ++++++++++++++++++++++++++-------------- doc/handover-tutorial.md | 17 +++++++++++++ 2 files changed, 53 insertions(+), 19 deletions(-) diff --git a/doc/RRC/rrc-usage.md b/doc/RRC/rrc-usage.md index 0eb7bc7195..0e8c94c61a 100644 --- a/doc/RRC/rrc-usage.md +++ b/doc/RRC/rrc-usage.md @@ -99,26 +99,43 @@ the [MAC configuration](../MAC/mac-usage.md) as well for SIB configuration. ### Neighbor-gNB configuration -#### What is a gNB neighbor? - -Network continuity is a key aspect of 5G. In the 5G architecture, gNB neighbors play a central role in maintaining service continuity through mechanisms such as handover and load balancing. By definition, a gNB neighbor is another gNB that can be measured and linked by the UE. If the current serving gNB is no longer optimal, the UE may connect to a neighbor gNB. - -To support this behavior, the network configuration specifies additional frequencies and cells that the UE should measure. The UE reports these measurements to the network, which then decides whether or not to initiate a handover. - -Neighbor types include: -- **Intra-gNB neighbors** - cells belonging to the same gNB -- **Inter-gNB neighbors** - cells belonging to different gNBs -- **Inter-RAT neighbors** - cells belonging to another RAT (e.g., LTE) +Refer to the [handover tutorial](../handover-tutorial.md) for detailed information about gNB neighbors and handover procedures. #### Required configuration parameters -To define a neighbor cell in the configuration file, the following parameters are typically needed: -- **gNB ID** - unique identifier of the gNB -- **Cell ID** - identifier of the cell within the gNB -- **Physical Cell ID** - identifier of the cell’s synchronization signal (PCI) -- **Absolute Frequency** - frequency used by the SSB (absoluteFrequencySSB) -- **Subcarrier Spacing** - numerology (e.g., 15 kHz, 30 kHz) -- **PLMN configuration** - MCC, MNC, and MNC length -- **Tracking Area Code (TAC)** - identifier of the tracking area +To define a neighbor cell in the configuration file, the following parameters are required: -Refer to the [handover tutorial](../handover-tutorial.md) for more information. +- `gNB_ID` - identifier of the neighbor gNB (e.g., `0xe01`) +- `nr_cellid` - cell identifier of the neighbor 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 +- `band` - 3GPP frequency band number (e.g., `78` for 3.5GHz) +- `plmn` - PLMN configuration object with: + - `mcc` - mobile country code (3 digits, e.g., `001`) + - `mnc` - mobile network code (2-3 digits, e.g., `01`) + - `mnc_length` - number of digits in MNC (must be `2` or `3`) +- `tracking_area_code` - tracking area identifier (e.g., `1`) + +Example configuration structure: +``` +neighbour_list = ( + { + nr_cellid = 12345678; + neighbour_cell_configuration = ( + { + gNB_ID = 0xe01; + nr_cellid = 11111111; + physical_cellId = 1; + absoluteFrequencySSB = 643296; + subcarrierSpacing = 1; # 30 kHz + band = 78; + plmn = { mcc = 001; mnc = 01; mnc_length = 2 }; + tracking_area_code = 1; + } + ); + } +); +``` + +Refer to the [handover tutorial](../handover-tutorial.md) for complete examples and detailed setup instructions. diff --git a/doc/handover-tutorial.md b/doc/handover-tutorial.md index 67bf99b3c2..eeb4ce09fa 100644 --- a/doc/handover-tutorial.md +++ b/doc/handover-tutorial.md @@ -13,6 +13,23 @@ same. ![F1 Handover setup](./RRC/ho.png) +# What is a gNB neighbor? + +Network continuity is a key aspect of 5G. In the 5G architecture, gNB neighbors +play a central role in maintaining service continuity through mechanisms such +as handover and load balancing. By definition, a gNB neighbor is another gNB +that can be measured and linked by the UE. If the current serving gNB is no +longer optimal, the UE may connect to a neighbor gNB. + +To support this behavior, the network configuration specifies additional frequencies +and cells that the UE should measure. The UE reports these measurements to the +network, which then decides whether or not to initiate a handover. + +Neighbor types include: +- **Intra-gNB neighbors** - cells belonging to the same gNB +- **Inter-gNB neighbors** - cells belonging to different gNBs +- **Inter-RAT neighbors** - cells belonging to another RAT (e.g., LTE) + # Steps to run F1 handover with OAI UE Measurement reporting and processing of RRC Reconfiguration for Mobility are From dcb10ffc944c82dded1b54a8a6323c80f74e34e5 Mon Sep 17 00:00:00 2001 From: Guido Casati Date: Wed, 15 Jan 2025 10:30:00 +0100 Subject: [PATCH 82/97] Handover tutorial: fix minor issues and add same-machine setup guidance - Update opening statement to reflect both F1 (intra-gNB) and N2 (inter-gNB) handover support - Fix grammar error: 'either by by decision' -> 'either a decision' - Standardize naming: 'gNB PCI1' -> 'gNB-PCI1' - Fix typo: 'neighbor call' -> 'neighbor cell' - Remove --sa flag from N2 handover target gNB command for consistency - Add network configuration notes for same-machine setups: * IP address assignment instructions to avoid network conflicts * Target gNB command options for proper network interface configuration --- doc/handover-tutorial.md | 25 +++++++++++++++++++------ 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/doc/handover-tutorial.md b/doc/handover-tutorial.md index eeb4ce09fa..b6e60746aa 100644 --- a/doc/handover-tutorial.md +++ b/doc/handover-tutorial.md @@ -1,5 +1,5 @@ -This tutorial explains how to perform handovers. For the moment, only F1 -handovers are supported. +This tutorial explains how to perform handovers. It covers both F1 handovers +(intra-gNB, within a single gNB between DUs) and N2 handovers (inter-gNB). [[_TOC_]] @@ -370,10 +370,16 @@ We assume: * Two independent gNBs connected to the same 5GC via N2 interface. * A UE initially connected to gNB-PCI0, which will be handed over to gNB-PCI1. -* Handover is triggered by either by decision based measurement event (e.g. A3) or telnet command. +* Handover is triggered by either a decision based measurement event (e.g. A3) or telnet command. ## Steps to run N2 handover with OAI UE +**Note for same-machine setup:** When running both gNBs on the same machine, you need to assign a unique IP address to the second gNB to avoid network conflicts. For example: + +```sh +sudo ip addr add 192.168.71.180/24 dev rfsim5g-public +``` + 1. Similarly to F1 handover, UE does not support any measurement reporting and handover is triggered by telnet command. Therefore, ensure that both gNBs and UE are built with telnet support: @@ -397,10 +403,17 @@ sudo ./nr-uesoftmodem -r 106 --numerology 1 --band 78 -C 3619200000 --rfsim --ui Ensure the UE successfully registers with the network. -4. Start the target gNB (gNB PCI1) e.g. +4. Start the target gNB (gNB-PCI1) e.g. ```sh -sudo ./nr-softmodem -O ../../../targets/PROJECTS/GENERIC-NR-5GC/CONF/gnb.sa.band78.fr1.106PRB.pci1.rfsim.conf --sa --rfsim --telnetsrv --telnetsrv.shrmod ci --gNBs.[0].min_rxtxtime 6 --rfsimulator.serveraddr 127.0.0.1 +sudo ./nr-softmodem -O ../../../targets/PROJECTS/GENERIC-NR-5GC/CONF/gnb.sa.band78.fr1.106PRB.pci1.rfsim.conf --rfsim --telnetsrv --telnetsrv.shrmod ci --gNBs.[0].min_rxtxtime 6 --rfsimulator.serveraddr 127.0.0.1 +``` + +**Note for same-machine setup:** When running both gNBs on the same machine, add the following network interface options to the target gNB command, e.g.: + +```sh +--gNBs.[0].NETWORK_INTERFACES.GNB_IPV4_ADDRESS_FOR_NG_AMF 192.168.71.180 +--gNBs.[0].NETWORK_INTERFACES.GNB_IPV4_ADDRESS_FOR_NGU 192.168.71.180 ``` 5. Trigger the N2 handover, e.g. @@ -410,7 +423,7 @@ From gNB-PCI0, trigger handover on target gNB with PCI 1 for UE ID 1: ```sh echo ci trigger_n2_ho 1,1 | nc 127.0.0.1 9090 && echo ``` -where the input parameters correspond to the PCI of the neighbor call and the RRC ID of the UE. +where the input parameters correspond to the PCI of the neighbor cell and the RRC ID of the UE. This will initiate the N2 handover on the source gNB. From 6193d7c7f452835bab2b0a268aaae2b10968bd69 Mon Sep 17 00:00:00 2001 From: Jaroslava Fiedlerova Date: Mon, 29 Sep 2025 17:21:45 +0200 Subject: [PATCH 83/97] Create XML for OAIUE longrun pipeline Test duration: 1 hour Test scenario: 3x DL 10 min test (20 Mbps) + 3x UL 10 min test (5 Mbps) --- .../container_sa_n310_nrue_longrun.xml | 179 ++++++++++++++++++ 1 file changed, 179 insertions(+) create mode 100644 ci-scripts/xml_files/container_sa_n310_nrue_longrun.xml diff --git a/ci-scripts/xml_files/container_sa_n310_nrue_longrun.xml b/ci-scripts/xml_files/container_sa_n310_nrue_longrun.xml new file mode 100644 index 0000000000..f0d8f4f3b6 --- /dev/null +++ b/ci-scripts/xml_files/container_sa_n310_nrue_longrun.xml @@ -0,0 +1,179 @@ + + + LONGRUN-5G-NRUE + Longrun 40 MHz TDD SA + tasks + + 060000 + 010000 010001 + 800813 + 020000 + 800814 + 020001 + 000001 + 001000 + 000100 + 001000 + 000100 + 001000 + 000100 + 001000 + 000200 + 001000 + 000200 + 001000 + 000200 + 040001 040000 + 333333 + 444444 + 060001 + + + + + DeployCoreNetwork + Initialize 5G Core + oc-cn5g + + + + Pull_Cluster_Image + Pull Images from Cluster + oai-gnb + avra + + + + Pull_Cluster_Image + Pull Images from Cluster + oai-nr-ue + caracal + + + + Create_Workspace + Create new Workspace + avra + + + + Deploy_Object + Deploy gNB (TDD/Band78/40MHz/N310) in a container + avra + ci-scripts/yaml_files/5g_sa_n310_gnb + 3 + + + + Create_Workspace + Create new Workspace + caracal + + + + Deploy_Object + Deploy nrUE (TDD/Band78/40MHz/N310) in a container + ci-scripts/yaml_files/5g_sa_n310_nrue + caracal + 3 + + + + Attach_UE + Attach OAIUE + oai_ue_caracal + + + + Iperf + iperf (DL/10Mbps/UDP)(10 min) + -u -b 10M -t 600 -i 1 -R + oai_ue_caracal + oc-cn5g + 25 + 80 + + + + Iperf + iperf (UL/2Mbps/UDP)(10 min) + -u -b 2M -t 600 -i 1 + oai_ue_caracal + oc-cn5g + 25 + 80 + + + + Ping + Ping: 20pings in 20sec + oai_ue_caracal + oc-cn5g + -c 20 + 1 + 25 + + + + Undeploy_Object + true + Undeploy gNB + avra + ci-scripts/yaml_files/5g_sa_n310_gnb + 20,100,100,100 + 20,100,100,100 + + + + Undeploy_Object + true + Undeploy nr UE + caracal + ci-scripts/yaml_files/5g_sa_n310_nrue + + + + Clean_Test_Server_Images + true + Clean Test Images on Test Server + avra + oai-gnb + + + + Clean_Test_Server_Images + true + Clean Test Images on Test Server + caracal + oai-nr-ue + + + + UndeployCoreNetwork + Terminate 5G Core + true + oc-cn5g + + + From 7935c19d21f9ef848256204b81a78360b02a1da3 Mon Sep 17 00:00:00 2001 From: Jaroslava Fiedlerova Date: Mon, 28 Jul 2025 22:31:11 +0200 Subject: [PATCH 84/97] Fix in CreateWorkspace Previously, the script attempted to directly merge ${merge}, which could f ail if the branch was not already available locally. This update explicitly fetches the branch from origin and then merges FETCH_HEAD with --ff, ensuring the merge works even when the branch is missing locally. --- ci-scripts/scripts/create_workspace.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ci-scripts/scripts/create_workspace.sh b/ci-scripts/scripts/create_workspace.sh index c8410802db..a8359a9ae4 100755 --- a/ci-scripts/scripts/create_workspace.sh +++ b/ci-scripts/scripts/create_workspace.sh @@ -22,5 +22,5 @@ git config user.name "OAI Jenkins" git config advice.detachedHead false mkdir -p cmake_targets/log git checkout -f ${ref} -[ -n "${merge}" ] && git merge --ff ${merge} -m "Temporary merge for CI" +[ -n "${merge}" ] && git fetch origin ${merge} && git merge --ff FETCH_HEAD -m "Temporary merge for CI" exit 0 From 2dde472cfd78e4f7aca2e7b4a8d1e3ce59e55483 Mon Sep 17 00:00:00 2001 From: Jaroslava Fiedlerova Date: Wed, 1 Oct 2025 10:58:39 +0200 Subject: [PATCH 85/97] Adjust gNB conf file in OAIUE test Increase prach_dtx_threshold to avoid fake PRACH detection. --- ci-scripts/conf_files/gnb.sa.band78.106prb.n310.7ds2u.conf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ci-scripts/conf_files/gnb.sa.band78.106prb.n310.7ds2u.conf b/ci-scripts/conf_files/gnb.sa.band78.106prb.n310.7ds2u.conf index 2f994ab25e..ed1c79911e 100644 --- a/ci-scripts/conf_files/gnb.sa.band78.106prb.n310.7ds2u.conf +++ b/ci-scripts/conf_files/gnb.sa.band78.106prb.n310.7ds2u.conf @@ -184,7 +184,7 @@ L1s = ( { num_cc = 1; tr_n_preference = "local_mac"; - prach_dtx_threshold = 130; + prach_dtx_threshold = 150; #pucch0_dtx_threshold = 150; } ); From 2ea4cbbd930639f46c5e85ab436e186f60bbadbb Mon Sep 17 00:00:00 2001 From: calvin_peyron Date: Wed, 17 Sep 2025 11:23:35 +0200 Subject: [PATCH 86/97] update env variable usage documentation --- doc/environment-variables.md | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/doc/environment-variables.md b/doc/environment-variables.md index 76730294fa..767afa6db4 100644 --- a/doc/environment-variables.md +++ b/doc/environment-variables.md @@ -3,14 +3,13 @@ OAI uses/supports a number of environment variables, documented in the following: - `NFAPI_TRACE_LEVEL`: set the nfapi custom logging framework's log level; can be one of `error`, `warn`, `note`, `info`, `debug`. Default is `warn`. -- `NR_AWGN_RESULTS_DIR`: directory containing BLER curves for L2simulator channel modelling in SISO case -- `NR_MIMO2x2_AWGN_RESULTS_DIR`: directory containing BLER curves for L2simulator channel modelling in 2x2 MIMO case +- `NR_AWGN_RESULTS_DIR`: directory containing BLER curves for NR L2simulator channel modelling in SISO case +- `NR_MIMO2x2_AWGN_RESULTS_DIR`: directory containing BLER curves for NR L2simulator channel modelling in 2x2 MIMO case +- `OPENAIR_DIR`: should point to the root directory of OpenAirInterface; BLER curves for LTE L2sim channel emulation - `NVRAM_DIR`: directory to read/write NVRAM data in (5G) `nvram` tool; if not defined, will use `PWD` (working directory) - `OAI_CONFIGMODULE`: can be used to pass the configuration file instead of `-O` - `OAI_GDBSTACKS`: if defined when hitting an assertion (`DevAssert()`, `AssertFatal()`, ...), OAI will load `gdb` and provide a backtrace for every thread - `OAI_RNGSEED`: overwrites any seed for random number generators (RNG) in simulators -- `OAI_THREADPOOLMEASUREMENTS`: path to a file to store thread pool debugging information, see the [thread pool documentation](../common/utils/threadPool/thread-pool.md) -- `OPENAIR_DIR`: should point to the root directory of OpenAirInterface; some code relies on this to get a filename, e.g., BLER curves for L2sim channel emulation - `USIM_DIR`: directory to read/write USIM data in (4G) `usim` tool; if not defined, will use `PWD` (working directory) Furthermore, these variables appear in code that is not maintained and maybe not even compiled anywhere: @@ -19,3 +18,5 @@ Furthermore, these variables appear in code that is not maintained and maybe not - `SSH_CLIENT`: alternative host to connect to, for CLI, if `REMADDR` is not defined - `USER`: user name in the command-line interface - `rftestInputFile`: input file for the `calibration_test` tool +- `OPENAIR2_DIR` : OMV and OMG modules, obsolete +- `TITLE`: OTG module, obsolete From a0d8baf323b169d4255c0d8f5a3998e8ff30ee52 Mon Sep 17 00:00:00 2001 From: Laurent THOMAS Date: Tue, 2 Sep 2025 17:07:28 +0200 Subject: [PATCH 87/97] update and rename TESTING_GNB_W_COTS_UE.md to describe actual content --- doc/README.md | 2 +- ..._COTS_UE.md => TESTING_OAI_NSA_COTS_UE.md} | 61 +++++++++--------- .../attach_signaling_scheme.jpg | Bin .../oai_fr1_lab.jpg | Bin .../oai_fr1_setup.jpg | Bin .../enb.conf | 0 .../gnb.conf | 0 .../oai_enb.log | 0 .../oai_fr1_setup.vsdx | Bin .../oai_gnb.log | 0 10 files changed, 33 insertions(+), 30 deletions(-) rename doc/{TESTING_GNB_W_COTS_UE.md => TESTING_OAI_NSA_COTS_UE.md} (85%) rename doc/{testing_gnb_w_cots_ue_resources => images}/attach_signaling_scheme.jpg (100%) rename doc/{testing_gnb_w_cots_ue_resources => images}/oai_fr1_lab.jpg (100%) rename doc/{testing_gnb_w_cots_ue_resources => images}/oai_fr1_setup.jpg (100%) rename doc/{testing_gnb_w_cots_ue_resources => testing_oai_nsa_w_cots_ue_resources}/enb.conf (100%) rename doc/{testing_gnb_w_cots_ue_resources => testing_oai_nsa_w_cots_ue_resources}/gnb.conf (100%) rename doc/{testing_gnb_w_cots_ue_resources => testing_oai_nsa_w_cots_ue_resources}/oai_enb.log (100%) rename doc/{testing_gnb_w_cots_ue_resources => testing_oai_nsa_w_cots_ue_resources}/oai_fr1_setup.vsdx (100%) rename doc/{testing_gnb_w_cots_ue_resources => testing_oai_nsa_w_cots_ue_resources}/oai_gnb.log (100%) diff --git a/doc/README.md b/doc/README.md index 9bd89480af..139418d817 100644 --- a/doc/README.md +++ b/doc/README.md @@ -52,7 +52,7 @@ There is some general information in the [OpenAirInterface Gitlab Wiki](https:// * IF setups and arbitrary frequencies * MIMO - [How to run OAI with O-RAN 7.2 FHI](./ORAN_FHI7.2_Tutorial.md) -- [How to run a 5G-NSA setup](./TESTING_GNB_W_COTS_UE.md) +- [How to run a 5G-NSA setup](./TESTING_OAI_NSA_COTS_UE.md) - [How to run a 4G setup using L1 simulator](./L1SIM.md) _Note: we recommend the RFsimulator_ - [How to use the L2 simulator](./L2NFAPI.md) - [How to use the OAI channel simulator](../openair1/SIMULATION/TOOLS/DOC/channel_simulation.md) diff --git a/doc/TESTING_GNB_W_COTS_UE.md b/doc/TESTING_OAI_NSA_COTS_UE.md similarity index 85% rename from doc/TESTING_GNB_W_COTS_UE.md rename to doc/TESTING_OAI_NSA_COTS_UE.md index f44e140e22..f53e7fe9a1 100644 --- a/doc/TESTING_GNB_W_COTS_UE.md +++ b/doc/TESTING_OAI_NSA_COTS_UE.md @@ -7,12 +7,12 @@ STATUS 2020/10/15 : added External Resources section and links ## External Resources Additional Resources to this page can be found here (special mention to Walter Maguire ) : -https://docs.google.com/document/d/1pL8Szm0ocGxdl5ESVp12Ff71a4PbhCb9SpvbLZzwYbo/edit?usp=sharing +[Additional Notes Supporting the OAI COTS NSA](https://docs.google.com/document/d/1pL8Szm0ocGxdl5ESVp12Ff71a4PbhCb9SpvbLZzwYbo/edit?usp=sharing) At time of writing, the openairinterface5G Commit Tag is 2020.w39 Faraday Cages can be found here : -http://www.saelig.com/MFR00066/ste2300.htm +[Faraday Cage](http://www.saelig.com/MFR00066/ste2300.htm) ## Configuration Overview @@ -37,32 +37,32 @@ Our code might not work with all 5G phones yet, but we are constantly improving * Simcom SIMCOM8200EA * Quectel RM500Q-GL -Note1: In the version we have at Eurecom, you need to set the PLMN to 50501, and you also need to change the firmware to "11.0.0 (RD1A.201105.003.B1, Nov 2020, EU carriers)" (see https://developers.google.com/android/images) +Note1: In the version we have at Eurecom, you need to set the PLMN to 50501, and you also need to change the firmware to "11.0.0 (RD1A.201105.003.B1, Nov 2020, EU carriers)" (see [Factory Images for Nexus and Pixel Devices](https://developers.google.com/android/images)) ## Repository -https://gitlab.eurecom.fr/oai/openairinterface5g/tree/develop +[OAI](https://gitlab.eurecom.fr/oai/openairinterface5g/tree/develop) ## Architecture Setup The scheme below depicts our typical setup: -![image info](./testing_gnb_w_cots_ue_resources/oai_fr1_setup.jpg) +![image info](./images/oai_fr1_setup.jpg) The photo depicts the FR1 setup part of the scheme above: -![image info](./testing_gnb_w_cots_ue_resources/oai_fr1_lab.jpg) +![image info](./images/oai_fr1_lab.jpg) ## Build and Install General guidelines to build eNB and gNB : -See https://gitlab.eurecom.fr/oai/openairinterface5g/blob/develop/doc/BUILD.md#building-ues-enodeb-and-gnodeb-executables +See [Building UE, eNB and gNb executables](https://gitlab.eurecom.fr/oai/openairinterface5g/blob/develop/doc/BUILD.md#building-ues-enodeb-and-gnodeb-executables) - **eNB** -``` +```bash cd /openairinterface5g/ source oaienv cd cmake_targets/ @@ -72,7 +72,7 @@ cd cmake_targets/ - **gNB** -``` +```bash cd /openairinterface5g/ source oaienv cd cmake_targets/ @@ -83,8 +83,7 @@ cd cmake_targets/ - **EPC** for reference: -https://github.com/OPENAIRINTERFACE/openair-epc-fed/blob/master/docs/DEPLOY_HOME.md - +[DEPLOY_HOME_MAGMA_MME](https://github.com/OPENAIRINTERFACE/openair-epc-fed/blob/master/docs/DEPLOY_HOME_MAGMA_MME.md) ## Configuration Files @@ -93,15 +92,17 @@ Each component (EPC, eNB, gNB) has its own configuration file. These config files are passed as arguments of the run command line, using the option -O \ The **REFERENCE** files for eNB and gNB, **used by the CI**, can be found here: -[enb conf file](../ci-scripts/conf_files/enb.band7.tm1.fr1.25PRB.usrpb210.conf) -[gnb conf file](../ci-scripts/conf_files/gnb.band78.tm1.fr1.106PRB.usrpb210.conf) +* [enb conf file](../ci-scripts/conf_files/enb.band7.25prb.usrpb200.tm1.conf) +* [gnb conf file](../ci-scripts/conf_files/gnb-du.sa.band78.106prb.usrpb200.conf) These files have to be updated manually to set the IP addresses and frequency. - -**ATTENTION** : an **EXTERNAL** clock is used to sync the eNB and gNB, -whether the clock is internal or external is defined in the configuration files (!! details needed !!) - +--- +>**⚠️ ATTENTION ⚠️** +> +>An **EXTERNAL** clock is used to sync the eNB and gNB, whether the clock is internal or external is defined in the configuration files (!! details needed !!) +> +--- 1- In the **eNB configuration file** : - look for MME IP address, and update the **ipv4 field** with the IP address of the **EPC** server @@ -198,14 +199,13 @@ The test takes typically a few seconds, max 10-15 seconds. If it takes more than - **EPC** (on EPC host): for reference: -https://github.com/OPENAIRINTERFACE/openair-epc-fed/blob/master/docs/DEPLOY_HOME.md - +[DEPLOY_HOME_MAGMA_MME](https://github.com/OPENAIRINTERFACE/openair-epc-fed/blob/master/docs/DEPLOY_HOME_MAGMA_MME.md) - **eNB** (on the eNB host): Execute: -``` +```bash ~/openairinterface5g/cmake_targets/ran_build/build$ sudo ./lte-softmodem -O **YOUR_ENB_CONF_FILE** | tee **YOUR_LOG_FILE** ``` @@ -213,13 +213,16 @@ Execute: - **gNB** (on the gNB host) - -**ATTENTION** : for the gNB execution, -The **-E** option is required to enable the tri-quarter sampling rate when using a B2xx serie USRP -The **-E** option is **NOT supported** when using a a N300 USRP +--- +>**⚠️ ATTENTION ⚠️** +> +> For the gNB execution: +> - The **-E** option is required to enable the tri-quarter sampling rate when using a B2xx serie USRP +> - The **-E** option is **NOT supported** when using a a N300 USRP +--- Execute: -``` +```bash ~/openairinterface5g/cmake_targets/ran_build/build$ sudo ./nr-softmodem -O **YOUR_GNB_CONF_FILE** -E --nsa | tee **YOUR_LOG_FILE** ``` @@ -229,9 +232,9 @@ Execute: The test case corresponds to the UE attachement, that is the UE connection and its initial access in 5G, as depicted below: -**Source** : https://www.sharetechnote.com/html/5G/5G_LTE_Interworking.html +**Source** : [5G/LTE interworking](https://www.sharetechnote.com/html/5G/5G_LTE_Interworking.html ) -![image info](./testing_gnb_w_cots_ue_resources/attach_signaling_scheme.jpg) +![image info](./images/attach_signaling_scheme.jpg) The test reaches step **12. E-RAB modifcation confirmation** , eventhough not all the messages will appear in the log file. @@ -239,8 +242,8 @@ The test reaches step **12. E-RAB modifcation confirmation** , eventhough not al From the log file that is generated, we can monitor several important steps, to assess that the test was successful. Log files examples can be found here: -[enb log file](https://gitlab.eurecom.fr/oai/openairinterface5g/-/blob/rh_doc_update_3/doc/testing_gnb_w_cots_ue_resources/oai_enb.log) -[gnb log file](https://gitlab.eurecom.fr/oai/openairinterface5g/-/blob/rh_doc_update_3/doc/testing_gnb_w_cots_ue_resources/oai_gnb.log) +* [enb log file](./testing_oai_nsa_w_cots_ue_resources/oai_enb.log) +* [gnb log file](./testing_oai_nsa_w_cots_ue_resources/oai_gnb.log) - eNB receives UE capabilities information, including its NR capabilites, and triggers sGNB Addition Request message: diff --git a/doc/testing_gnb_w_cots_ue_resources/attach_signaling_scheme.jpg b/doc/images/attach_signaling_scheme.jpg similarity index 100% rename from doc/testing_gnb_w_cots_ue_resources/attach_signaling_scheme.jpg rename to doc/images/attach_signaling_scheme.jpg diff --git a/doc/testing_gnb_w_cots_ue_resources/oai_fr1_lab.jpg b/doc/images/oai_fr1_lab.jpg similarity index 100% rename from doc/testing_gnb_w_cots_ue_resources/oai_fr1_lab.jpg rename to doc/images/oai_fr1_lab.jpg diff --git a/doc/testing_gnb_w_cots_ue_resources/oai_fr1_setup.jpg b/doc/images/oai_fr1_setup.jpg similarity index 100% rename from doc/testing_gnb_w_cots_ue_resources/oai_fr1_setup.jpg rename to doc/images/oai_fr1_setup.jpg diff --git a/doc/testing_gnb_w_cots_ue_resources/enb.conf b/doc/testing_oai_nsa_w_cots_ue_resources/enb.conf similarity index 100% rename from doc/testing_gnb_w_cots_ue_resources/enb.conf rename to doc/testing_oai_nsa_w_cots_ue_resources/enb.conf diff --git a/doc/testing_gnb_w_cots_ue_resources/gnb.conf b/doc/testing_oai_nsa_w_cots_ue_resources/gnb.conf similarity index 100% rename from doc/testing_gnb_w_cots_ue_resources/gnb.conf rename to doc/testing_oai_nsa_w_cots_ue_resources/gnb.conf diff --git a/doc/testing_gnb_w_cots_ue_resources/oai_enb.log b/doc/testing_oai_nsa_w_cots_ue_resources/oai_enb.log similarity index 100% rename from doc/testing_gnb_w_cots_ue_resources/oai_enb.log rename to doc/testing_oai_nsa_w_cots_ue_resources/oai_enb.log diff --git a/doc/testing_gnb_w_cots_ue_resources/oai_fr1_setup.vsdx b/doc/testing_oai_nsa_w_cots_ue_resources/oai_fr1_setup.vsdx similarity index 100% rename from doc/testing_gnb_w_cots_ue_resources/oai_fr1_setup.vsdx rename to doc/testing_oai_nsa_w_cots_ue_resources/oai_fr1_setup.vsdx diff --git a/doc/testing_gnb_w_cots_ue_resources/oai_gnb.log b/doc/testing_oai_nsa_w_cots_ue_resources/oai_gnb.log similarity index 100% rename from doc/testing_gnb_w_cots_ue_resources/oai_gnb.log rename to doc/testing_oai_nsa_w_cots_ue_resources/oai_gnb.log From acd3634d27667e8f968900a0a33d2fadad55952d Mon Sep 17 00:00:00 2001 From: calvin_peyron Date: Wed, 3 Sep 2025 11:20:21 +0200 Subject: [PATCH 88/97] Update E1-design.md --- doc/E1AP/E1-design.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/doc/E1AP/E1-design.md b/doc/E1AP/E1-design.md index 8fafd09f66..d9a7131393 100644 --- a/doc/E1AP/E1-design.md +++ b/doc/E1AP/E1-design.md @@ -1,6 +1,6 @@ [[_TOC_]] -# 1. Introduction +## 1. Introduction E1 is the interface that lies between the nodes CU Control Plane (CUCP) and CU User Plane (CUUP). Once the nodes are configured, all user plane traffic flows through CUUP. @@ -77,11 +77,11 @@ sequenceDiagram ``` More details about the E1AP procedures in OAI are available in this document: [E1 Procedures](./e1ap_procedures.md). -# 2. Running the E1 Split +## 2. Running the E1 Split The setup is assuming that all modules are running on the same machine. The user can refer to the [F1 design document](./../F1AP/F1-design.md) for local deployment of the DU. -## 2.1 Configuration File +### 2.1 Configuration File The gNB is started based on the node type that is specified in the configuration file. The following parameters must be configured accordingly. @@ -130,9 +130,9 @@ The CUUP uses the IP address specified in `gNBs.[0].local_s_address` for F1-U an Alternatively, you can use the config files `ci-scripts/conf_files/gnb-cucp.sa.f1.conf` and `ci-scripts/conf_files/gnb-cuup.sa.f1.conf`. -## 2.2 Steps to Run the Split in rfsimulator with OAI UE +### 2.2 Steps to Run the Split in rfsimulator with OAI UE -Note: A 5G core must be running at this point. Steps to start the OAI 5G core can be found [in the oai-cn5g-fed repository](https://gitlab.eurecom.fr/oai/cn5g/oai-cn5g-fed/-/blob/master/docs/DEPLOY_HOME.md) or [here](NR_SA_CN5G_gNB_USRP_COTS_UE_Tutorial.md). +Note: A 5G core must be running at this point. Steps to start the OAI 5G core can be found [in the oai-cn5g-fed repository](https://gitlab.eurecom.fr/oai/cn5g/oai-cn5g-fed/-/blob/master/docs/DEPLOY_HOME.md) or [here](../NR_SA_Tutorial_OAI_CN5G.md). 0. Open wireshark to capture the E1AP messages. You might set the capture filter to `sctp` to limit the number of captured packages. @@ -165,7 +165,7 @@ OAI UE: sudo ./nr-uesoftmodem -r 106 --numerology 1 --band 78 -C 3619200000 --rfsim --rfsimulator.serveraddr 127.0.0.1 ``` -# 3. Configuration file IP addresses of network functions +## 3. Configuration file IP addresses of network functions You can also run the nodes on different machines. If you do so please change the interface parameters accordingly and make sure the interfaces are reachable. Please refer to the following figure for an overview of all parameters. @@ -173,7 +173,7 @@ You can also run the nodes on different machines. If you do so please change the [PDF version](./images/e1-archi.pdf) | [LaTeX/TikZ version](./images/e1-archi.tex) if you want to modify to reflect your setup -# 4. Multiple CU-UP +## 4. Multiple CU-UP It is possible to connect multiple CU-UP to the same CU-CP. In the handler of the E1 Setup Request, the CU-CP verifies that the PLMN(s) (MCC, MNC) between @@ -195,6 +195,6 @@ that you have to restart the CU-CP if you want to connect the CU-UP again (e.g., after a crash). The CU-CP might also misfunction during attach if a CU-UP was connected, but disconnected in the meantime. -# 5. Abnormal conditions +## 5. Abnormal conditions * The CU-UP goes offline during normal operation (e.g. UEs have a valid PDU Session and are exchanging data on the UP): after restarting the CU-UP, the UP is not restored and the user will notice GTP errors. In this case the UEs have to reconnect. From 25918898a74984773aa8b79d941c2e628c8d78f4 Mon Sep 17 00:00:00 2001 From: calvin_peyron Date: Wed, 3 Sep 2025 11:35:32 +0200 Subject: [PATCH 89/97] Update F1-design.md --- doc/F1AP/F1-design.md | 42 +++++++++++++++++++++--------------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/doc/F1AP/F1-design.md b/doc/F1AP/F1-design.md index 34c48a640c..5290426ea3 100644 --- a/doc/F1AP/F1-design.md +++ b/doc/F1AP/F1-design.md @@ -2,7 +2,7 @@ - + @@ -14,7 +14,7 @@ [[_TOC_]] -# Introduction +## Introduction The F1 interface is the functional split of 3GPP between the CU (centralized unit: PDCP, RRC, SDAP) and the DU (distributed unit: RLC, MAC, PHY). It is @@ -33,17 +33,17 @@ F1-U: No equivalent for 4G exists. -## Control plane (F1-C) +### Control plane (F1-C) The interface F1-C is designed for the exchange of signalling messages between the Radio Network Layer (RNL) and the Transport Network Layer (TNL). It consists of F1 Application Protocol messages (F1-AP) exchanged over SCTP. -## Data plane (F1-U) +### Data plane (F1-U) F1-U uses GTP-U for information exchange. -# OAI Implementation Status +## OAI Implementation Status The implementation of F1AP messages is seamlessly integrated into OAI, supporting both Monolithic SA and CU/DU functional split modes. The F1 code is therefore always compiled with nr-softmodem. @@ -63,13 +63,13 @@ This is the current status: - Not supported: * NSA -## F1-C +### F1-C -### F1AP messages +#### F1AP messages -Refer to [FEATURE_SET.md](FEATURE_SET.md#gNB-F1AP) to learn about the current F1AP implementation status. +Refer to [FEATURE_SET.md](../FEATURE_SET.md#gnb-f1ap) to learn about the current F1AP implementation status. -### High-level F1-C code structure +#### High-level F1-C code structure The F1 interface is used internally between CU (mostly RRC) and DU (mostly MAC) to exchange information. In DL, the CU sends messages as defined by the @@ -170,7 +170,7 @@ sequenceDiagram TASK_MAC_GNB->>+TASK_RRC_GNB: raw F1AP message (ITTI) ``` -## F1-U +### F1-U Current status: @@ -178,7 +178,7 @@ Current status: * Each packet is acknowledged individually * Support of multiple DUs per CU -# How to run +## How to run As mentioned earlier, OAI uses F1 internally. It is always compiled in. To start CU/DU, you use `./nr-softmodem` with the appropriate configuration @@ -190,7 +190,7 @@ sudo cmake_targets/ran_build/build/nr-softmodem -O ci-scripts/conf_files/gnb-du. ``` These files are tested in the CI, and are configured for use in docker, -see [this `docker-compose` file](../ci-scripts/yaml_files/5g_f1_rfsimulator/docker-compose.yaml). +see [this `docker-compose` file](../../ci-scripts/yaml_files/5g_f1_rfsimulator/docker-compose.yaml). The rules to decide if a config triggers a start of a DU, CU, or monolithic gNB, are, in order: @@ -200,7 +200,7 @@ gNB, are, in order: (`tr_s_preference`), it is a CU. 3. It is a (monolithic) gNB. -## F1 IP configuration for Local network deployment of F1 +### F1 IP configuration for Local network deployment of F1 The following paragraphs explain the IP configuration for F1 in the OAI config files on the example of a a local deployment. We assume that the CU will bind @@ -242,7 +242,7 @@ the options `MACRLCs.[0].local_n_address` and `MACRLCs.[0].local_n_address_f1u`, respectively. Note that this is not foreseen for the CU; in the case of the CU, please use separate CU-UP and CU-CP. -## Configuration of multiple DUs +### Configuration of multiple DUs Upon F1 Setup Request of a new DU, the CU cross-checks that @@ -260,9 +260,9 @@ You have to of course make sure that the local interface of the DU Assuming you use RFsim, you should make the RFsimulator server side (typically the gNB) bind on different hosts (`rfsimulator.serverport`). -# Code documentation +## Code documentation -## Common multi-threading architecture +### Common multi-threading architecture The CU and DU interfaces are based on ITTI threads (see `common/utils/ocp_itti/itti.md`) adopted by all OAI upper layers to run isolated threads dedicated to one feature. @@ -301,7 +301,7 @@ All GTP-U tunnels are managed in a Linux Thread, that have partially ITTI design 3. incoming packets are sent to the tunnel creator using a C callback (the callback function is given in tunnel creation order). The callback should not block -## F1-C messages towards the CU +### F1-C messages towards the CU The CU thread starts when the CU starts. It opens listening socket on the configuration-specified IP/port by sending the appropriate message to @@ -332,7 +332,7 @@ You might also want to consult TS 38.401 regarding the message exchange. | UE Context Release Request | `F1AP_UE_CONTEXT_RELEASE_REQ` | `rrc_CU_process_ue_context_release_request()` | RRC will trigger UE release | | UE Context Release Complete | `F1AP_UE_CONTEXT_RELEASE_COMPLETE` | `rrc_CU_process_ue_context_release_complete()` | frees UE Context, signals to NGAP | -## F1-C Messages towards the DU +### F1-C Messages towards the DU The task "gNB app", after reading the configuration file, sends a message `F1AP_DU_REGISTER_REQ` to the DU task. This message contains network @@ -371,9 +371,9 @@ You might also want to consult TS 38.401 regarding the message exchange. a reconfiguration has to be triggered if the CU receives a CellGroupConfig, originating at the DU. See also flag `expect_reconfiguration`. -## F1-U messages +### F1-U messages -### General +#### General In the DU in UL, RLC checks in `deliver_sdu()` if we are operating in split mode, and either (direct) calls `pdcp_data_ind` (DRB) or (f1ap) sends a GTP @@ -390,7 +390,7 @@ In the DU in DL, assuming the GTP-U tunnel exists, GTP decapsulates the packet and calls the reception call back `nr_rlc_data_req()`, which enqueues the packet into the UE's RLC buffer. -## Tunnel Setup +### Tunnel Setup In GTP-U, TS 29.281 specifies a optional header (NR RAN Container). This extension header may be transmitted in a G-PDU over the X2-U, Xn-U and F1-U user From 0721c0fad0ca1b99afe9eefb5d3c2dfdcfc7a885 Mon Sep 17 00:00:00 2001 From: calvin_peyron Date: Wed, 3 Sep 2025 11:38:16 +0200 Subject: [PATCH 90/97] Fix image link inside F1AP-lib.md --- doc/F1AP/F1AP-lib.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/F1AP/F1AP-lib.md b/doc/F1AP/F1AP-lib.md index 46712914ed..86db97ec8b 100644 --- a/doc/F1AP/F1AP-lib.md +++ b/doc/F1AP/F1AP-lib.md @@ -2,7 +2,7 @@ - + From aa323b02f28554630c77502486328794b49b8647 Mon Sep 17 00:00:00 2001 From: calvin_peyron Date: Wed, 3 Sep 2025 13:16:25 +0200 Subject: [PATCH 91/97] Fix link in mac-usage.md --- doc/MAC/mac-usage.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/MAC/mac-usage.md b/doc/MAC/mac-usage.md index e412d4b329..f4583b7a23 100644 --- a/doc/MAC/mac-usage.md +++ b/doc/MAC/mac-usage.md @@ -183,7 +183,7 @@ See [nFAPI documentation](../nfapi.md) or [Aerial tutorial](../Aerial_FAPI_Split_Tutorial.md) for information about the (n)FAPI split. -See [F1 documentation](../F1-design.md) for information about the F1 split. +See [F1 documentation](../F1AP/F1-design.md) for information about the F1 split. ## MAC scheduler-related configuration options From 2d2f61f2530b04b85e587a966f8e76c0037ba520 Mon Sep 17 00:00:00 2001 From: calvin_peyron Date: Wed, 3 Sep 2025 13:16:54 +0200 Subject: [PATCH 92/97] Fix link in rrc-usage.md --- doc/RRC/rrc-usage.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/RRC/rrc-usage.md b/doc/RRC/rrc-usage.md index 0eb7bc7195..332c69a0eb 100644 --- a/doc/RRC/rrc-usage.md +++ b/doc/RRC/rrc-usage.md @@ -12,7 +12,7 @@ The RRC layer controls the basic connection setup of UEs as well as additional procedures. It is the fundamental building block of OAI's CU-CP, and interacts with lower layers (DU, basically MAC and RLC) through F1AP messages, and with the CU-UP through E1AP messages. More information can be found in the -respective [F1AP page](../F1-design.md) and [E1AP page](../E1AP/E1-design.md). +respective [F1AP page](../F1AP/F1-design.md) and [E1AP page](../E1AP/E1-design.md). # Periodic output and interpretation @@ -57,7 +57,7 @@ As of now, it does not print information about connected CU-UPs or AMFs. ## Split-related options (when running in a CU or CU-CP) -See [F1 documentation](../F1-design.md) for information about the F1 split. +See [F1 documentation](../F1AP/F1-design.md) for information about the F1 split. See [E1 documentation](../E1AP/E1-design.md) for information about the E1 split. ## RRC-specific configuration options From e7a45a304b3196c8afb60e9ffab8a02013aabb73 Mon Sep 17 00:00:00 2001 From: calvin_peyron Date: Wed, 3 Sep 2025 13:54:20 +0200 Subject: [PATCH 93/97] Remove unused and outdated documentation --- doc/README.md | 2 - doc/SystemX-tutorial-design.md | 143 --------------------------------- 2 files changed, 145 deletions(-) delete mode 100644 doc/SystemX-tutorial-design.md diff --git a/doc/README.md b/doc/README.md index 139418d817..e19c71268b 100644 --- a/doc/README.md +++ b/doc/README.md @@ -71,8 +71,6 @@ There is some general information in the [OpenAirInterface Gitlab Wiki](https:// Legacy unmaintained files: - [`L2NFAPI_NOS1.md`](./L2NFAPI_NOS1.md), [`L2NFAPI_S1.md`](./L2NFAPI_S1.md): old L2simulator, not valid anymore -- [`SystemX-tutorial-design.md`](./SystemX-tutorial-design.md): old, high-level - documentation - [`UL_MIMO.txt`](./UL_MIMO.txt): UL-MIMO specific notes # Designs diff --git a/doc/SystemX-tutorial-design.md b/doc/SystemX-tutorial-design.md deleted file mode 100644 index 1e099abafd..0000000000 --- a/doc/SystemX-tutorial-design.md +++ /dev/null @@ -1,143 +0,0 @@ -# OpenAirInterface for SystemX - -# Terminology - -****This document use the 5G terminology**** - -**Central Unit (CU):** It is a logical node that includes the gNB -functions like Transfer of user data, Mobility control, Radio access -network sharing, Positioning, Session Management etc., except those -functions allocated exclusively to the DU. CU controls the operation of -DUs over front-haul (Fs) interface. A central unit (CU) may also be -known as BBU/REC/RCC/C-RAN/V-RAN/VNF - -**Distributed Unit (DU):** This logical node includes a subset of the -gNB functions, depending on the functional split option. Its operation -is controlled by the CU. Distributed Unit (DU) also known with other -names like RRH/RRU/RE/RU/PNF. - -In OpenAir code, the terminology is often RU and BBU. - -# OpenAirUsage - -## EPC and general environment - -### OAI EPC - -Use the stable OAI EPC, that can run in one machine (VM or standalone) - -Draft description: - - -## Standalone 4G - -EPC+eNB on one machine, the UE can be commercial or OAI UE. - -### USRP B210 - -Main current issue: traffic is good only on coaxial link between UE and -eNB (probably power management issue). - -### Simulated RF - -Running eNB+UE both OAI can be done over a virtual RF link. - -The UE current status is that threads synchronization is implicit in -some cases. As the RF simulator is very quick, a “sleep()” is required -in the UE main loop - -(line 1744, targets/RT/USER/lte-ue.c). - -Running also the UE in the same machine is possible with simulated RF. - -Running in same machine is simpler, offers about infinite speed for -virtual RF samples transmission. - -A specific configuration is required because the EPC Sgi interface has -the same IP tunnel end point as the UE. - -So, we have to create a network namespace for the UE and to route data -in/out of the namespace. - -```bash -ip netns delete aNameSpace 2> /dev/null - -ip link delete v-eth1 2> /dev/null - -ip netns add aNameSpace - -ip link add v-eth1 type veth peer name v-peer1 - -ip link set v-peer1 netns aNameSpace - -ip addr add 10.200.1.1/24 dev v-eth1 - -ip link set v-eth1 up - -iptables -t nat -A POSTROUTING -s 10.200.1.0/255.255.255.0 -o enp0s31f6 \ --j MASQUERADE - -iptables -A FORWARD -i enp0s31f6 -o v-eth1 -j ACCEPT - -iptables -A FORWARD -o enp0s31f6 -i v-eth1 -j ACCEPT - -ip netns exec aNameSpace ip link set dev lo up - -ip netns exec aNameSpace ip addr add 10.200.1.2/24 dev v-peer1 - -ip netns exec aNameSpace ip link set v-peer1 up - -ip netns exec aNameSpace bash -``` - -After the last command, the Linux shell is in the new namespace, ready -to run the UE. - -To make user plan traffic, the traffic generator has to run in the same -namespace - -```bash -ip netns exec aNameSpace bash -``` - -The traffic genenrator has to specify the interface: - -```bash -route add default oaitun_ue1 -``` - -or specify the outgoing route in the traffic generator (like option “-I” -in ping command). - -## 5G and F1 - -Today 5G achievement is limited to physical layer. - -The available modulation is 40MHz, that require one X310 or N300 for the -gNB and a X310 or N300 for the nrUE. - -### Usage with X310 - -Linux configuration: - - -We included most of this configuration included in OAI source code. - -Remain to set the NIC (network interface card) MTU to 9000 (jumbo -frames). - -### Running 5G - -Usage with RFsimulator: - -**gNB** - -```bash -sudo ./nr-softmodem -O ../../../targets/PROJECTS/GENERIC-LTE-EPC/CONF/gnb.band78.tm1.106PRB.usrpn300.conf -``` - -**nrUE** - -```bash -sudo ./nr-uesoftmodem --numerology 1 -r 106 -C 3510000000 -d --rfsimulator.serveraddr 127.0.0.1 -``` From 2dd72d40cbf422c7cdb7f4f04f55cb834e81e1c0 Mon Sep 17 00:00:00 2001 From: calvin_peyron Date: Thu, 4 Sep 2025 10:13:44 +0200 Subject: [PATCH 94/97] Put documentation images in right folder --- doc/Doxyfile | 2 +- doc/FEATURE_SET.md | 4 ++-- doc/{ => images}/nr-ue-threads.svg | 0 doc/{ => images}/oai_enb_func_split_arch.png | Bin doc/{ => images}/oai_logo.png | Bin doc/nr-ue-design.md | 2 +- doc/oai_enb_block_diagram.png | Bin 277532 -> 0 bytes 7 files changed, 4 insertions(+), 4 deletions(-) rename doc/{ => images}/nr-ue-threads.svg (100%) rename doc/{ => images}/oai_enb_func_split_arch.png (100%) rename doc/{ => images}/oai_logo.png (100%) delete mode 100644 doc/oai_enb_block_diagram.png diff --git a/doc/Doxyfile b/doc/Doxyfile index e67078a043..e91e63386f 100644 --- a/doc/Doxyfile +++ b/doc/Doxyfile @@ -51,7 +51,7 @@ PROJECT_BRIEF = "Full experimental OpenSource LTE and NR implementation # pixels and the maximum width should not exceed 200 pixels. Doxygen will copy # the logo to the output directory. -PROJECT_LOGO = @CMAKE_SOURCE_DIR@/doc/oai_logo.png +PROJECT_LOGO = @CMAKE_SOURCE_DIR@/doc/images/oai_logo.png # The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) path # into which the generated documentation will be written. If a relative path is diff --git a/doc/FEATURE_SET.md b/doc/FEATURE_SET.md index f759ddf145..4429cad56b 100644 --- a/doc/FEATURE_SET.md +++ b/doc/FEATURE_SET.md @@ -11,12 +11,12 @@ - FAPI (IF2) : specified by Small Cell Forum (open-nFAPI implementation) - IF1 : F1 in 3GPP Release 15 -![Functional Split Architecture](./oai_enb_func_split_arch.png) +![Functional Split Architecture](./images/oai_enb_func_split_arch.png) # OpenAirInterface Block Diagram # -![Block Diagram](./oai_enb_block_diagram.png) +![Block Diagram](./images/oai_enb_block_diagram.png) # OpenAirInterface 5G-NR Feature Set # diff --git a/doc/nr-ue-threads.svg b/doc/images/nr-ue-threads.svg similarity index 100% rename from doc/nr-ue-threads.svg rename to doc/images/nr-ue-threads.svg diff --git a/doc/oai_enb_func_split_arch.png b/doc/images/oai_enb_func_split_arch.png similarity index 100% rename from doc/oai_enb_func_split_arch.png rename to doc/images/oai_enb_func_split_arch.png diff --git a/doc/oai_logo.png b/doc/images/oai_logo.png similarity index 100% rename from doc/oai_logo.png rename to doc/images/oai_logo.png diff --git a/doc/nr-ue-design.md b/doc/nr-ue-design.md index 9ca95c1a00..1648e6b7c5 100644 --- a/doc/nr-ue-design.md +++ b/doc/nr-ue-design.md @@ -15,7 +15,7 @@ The UE exits when at any point in operation it gets out of synchronization. When UE uses actors which are threads dedicated to particular activity. Sync Actor handles initial sync. DL Actors handle DLSCH PHY procedures. UL procedures are are run on the UL Actor -![design](nr-ue-threads.svg) +![design](images/nr-ue-threads.svg) ## Initial Synchronization Block ```mermaid diff --git a/doc/oai_enb_block_diagram.png b/doc/oai_enb_block_diagram.png deleted file mode 100644 index 58c8eb9e959fae8616de03cc9f56fcfe2cc0f0e7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 277532 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

hxsnh9RTqxb? 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ఝLNIPN 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

U;VjjKT4T=nly9hAsi&Hx)BXo_uBS~3 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 KT)B+JhyMlE&rUA@ From 23e0f50af0dee33d0790d3823713b18160b1a090 Mon Sep 17 00:00:00 2001 From: calvin_peyron Date: Thu, 4 Sep 2025 11:18:05 +0200 Subject: [PATCH 95/97] Change D2D emulator documentation format --- doc/README.md | 2 +- doc/d2d_emulator_setup.md | 207 +++++++++++++++++++++++++++++++++++++ doc/d2d_emulator_setup.txt | 104 ------------------- 3 files changed, 208 insertions(+), 105 deletions(-) create mode 100644 doc/d2d_emulator_setup.md delete mode 100644 doc/d2d_emulator_setup.txt diff --git a/doc/README.md b/doc/README.md index e19c71268b..c48f2b6ee8 100644 --- a/doc/README.md +++ b/doc/README.md @@ -60,7 +60,7 @@ There is some general information in the [OpenAirInterface Gitlab Wiki](https:// - [How to run OAI-VNF and OAI-PNF](./nfapi.md): how to run the FAPI/nFAPI split, including some general remarks on FAPI/nFAPI. - [How to use the positioning reference signal (PRS)](./RUN_NR_PRS.md) -- [How to use device-to-device communication (D2D, 4G)](./d2d_emulator_setup.txt) +- [How to use device-to-device communication (D2D, 4G)](./d2d_emulator_setup.md) - [How to run with E2 agent](../openair2/E2AP/README.md) - [How to run the physical simulators](./physical-simulators.md) - [How to setup OAI with Nvidia Aerial and Foxconn](./Aerial_FAPI_Split_Tutorial.md) diff --git a/doc/d2d_emulator_setup.md b/doc/d2d_emulator_setup.md new file mode 100644 index 0000000000..8b529d97fa --- /dev/null +++ b/doc/d2d_emulator_setup.md @@ -0,0 +1,207 @@ +# How to use device-to-device communication (D2D, 4G) + +--- +>**⚠️ ATTENTION ⚠️** +> +>D2D is currently unfinished, the following documentation steps are likely not enough to make it work. +--- + +## Scenario 1 : **Off-network UE2UE link** +SynchREF UE (UE1) + +```mermaid +graph LR + UE1[UE1
eth0: 10.10.10.1] --- UE2[UE2
eno1: 10.10.10.2] +``` + +### Example of /etc/network/interfaces configuration for UE1 +```text +auto eth0 + iface eth0 inet static + address 10.10.10.1 + netmask 255.255.255.0 + gateway 10.10.10.1 +``` + +### Prepare the environment: +```bash +git clone https://gitlab.eurecom.fr/matzakos/LTE-D2D.git +cd LTE-D2D +git checkout master +``` + +This branch contains all the current development for **DDPS**, including: + - **UE MAC <-> UE MAC** for **Scenario 1** (off-network communication) + - **eNB MAC <-> UE MAC** using **NFAPI Transport** (on-network communication) + - **RRC Extensions** to support on-network cases + +### NFAPI configuration (required even for Scenario 1 target) +```bash +git clone https://github.com/cisco/open-nFAPI.git +cd open-nfapi +patch -p1 --dry-run < $OPENAIR_HOME/open-nfapi.oai.patch +``` +Validate that there are no errors: + +```patch -p1 < $OPENAIR_HOME/open-nfapi.oai.patch``` + +### OAI build/execute +```bash +export NFAPI_DIR=XXX (place where NFAPI was installed) +cd cmake_targets +./build_oai --UE --ninja #If necessary, use ./build_oai -I --UE to install required packages +cd ran_build/build/ +``` + +### UE1 +``` bash +sudo ifconfig oip0 10.0.0.1 +sudo iptables -A POSTROUTING -t mangle -o oip0 -d 224.0.0.3 -j MARK --set-mark 3 +``` +--- +>**NOTE** +> +>If necessary, add a default gateway: +> +>```sudo route add default gw 10.10.10.1 eth0``` +--- + + +### UE2 +```bash +sudo ifconfig oip0 10.0.0.2 +sudo iptables -A POSTROUTING -t mangle -o oip0 -d 224.0.0.3 -j MARK --set-mark 3 +``` +--- +>**NOTE** +> +>If necessary, add a default gateway: +> +>```sudo route add default gw 10.10.10.1 eno1``` +--- + + +### UE1 and UE2: Get and build `vencore_app` from `d2d-l3-stub` (branch: `l3_stub`) +```bash +gcc -I . vencore_app.c -o vencore_app -lpthread +``` + + +## TEST ONE-TO-MANY + +### Run UE1 then UE2, for example: +- UE1: +```bash +sudo ./lte-softmodem-stub -U --emul-iface eth0 +``` +- UE2: +```bash +sudo ./lte-softmodem-stub -U --emul-iface eno1 +``` + +### Test with Ping +- Sender - UE1: +```bash +ping -I oip0 224.0.0.3 +``` +- Receiver - UE2: *using wireshark* + +### Test with Iperf +- Sender - UE1: +```bash +iperf -c 224.0.0.3 -u -b 0.1M --bind 10.0.0.1 -t 100 +``` + +- Receiver - UE2: +```bash +sudo ./mcreceive 224.0.0.3 5001 +``` + +Filter the incoming packets according to GroupL2Id: receiver (one-to-many) can discard the packets if it doesn't belong to this group. +For the moment, both sender and receiver use the same set of Ids (hardcoded) + +- UE1 (sender): + ```bash + sudo ./lte-softmodem-stub -U --emul-iface eth0 + ./vencore_app #send the sourceL2Id, groupL2Id to OAI + ping -I oip0 224.0.0.3 + ``` + +- UE2(receiver) + ```bash + sudo ./lte-softmodem-stub -U --emul-iface eno1 + #we can see the incomming packets from OAI log, however, cannot see from Wireshark -> they are discarded at MAC layer + ./vencore_app #we can see the packets appearing in Wireshark + ``` + + +## TEST PC5-S (UE1 -sender, UE2 - receiver) and PC5-U for ONE-TO-ONE scenario +### Configure UE1/UE2 + +Configure ports and routing table for UE1 and UE2 + +**UE1:** + ```bash + sudo ifconfig oip0 10.0.0.1 + sudo iptables -A POSTROUTING -t mangle -o oip0 -d 10.0.0.2 -j MARK --set-mark 3 + sudo route add default gw 10.10.10.1 eth0 + ``` +**UE2:** + ```bash + sudo ifconfig oip0 10.0.0.2 + sudo iptables -A POSTROUTING -t mangle -o oip0 -d 10.0.0.1 -j MARK --set-mark 3 + sudo route add default gw 10.10.10.1 eno1 + ``` + +#### Step 1: +Run the traffic emulated over Ethernet on UE1 using lte-softmodem-stub to test the behaviour without RF board. + +**UE1:** +```bash +sudo ./lte-softmodem-stub -U --emul-iface eth0 +``` + +#### Step 2: +Run the traffic emulated over Ethernet on UE2 and set it as listening to incoming messages from PC5-S + +**UE2:** +```bash +sudo ./lte-softmodem-stub -U --emul-iface eno1 +./vencore_app -r #listen to incomming message from PC5-S +``` + +#### Step 3: +Send a message from UE1 to UE2 + +**UE1:** +```bash +./vencore_app -s #send a message via PC5-S (e.g., DirectCommunicationRequest) +``` + +#### Generate unicast traffic +**UE1:** +```bash +ping -I oip0 10.0.0.2 +``` + + +## TEST PC5-D + +#### Step 1: +Run the traffic emulated over Ethernet on UE1 and send a discovery message via PC5-D + +**UE1:** +```bash +sudo ./lte-softmodem-stub -U --emul-iface eth0 +./vencore_app -d #send a PC5-Discovery-Announcement via PC5D +``` + +#### Step 2: +Run the traffic emulated over Ethernet on UE2 and send a discovery message via PC5-D + +**UE2:** +```bash +sudo ./lte-softmodem-stub -U --emul-iface eno1 +./vencore_app -d #send a PC5-Discovery-Announcement via PC5D +``` + diff --git a/doc/d2d_emulator_setup.txt b/doc/d2d_emulator_setup.txt deleted file mode 100644 index fe26e3f9d1..0000000000 --- a/doc/d2d_emulator_setup.txt +++ /dev/null @@ -1,104 +0,0 @@ -Scenario 1 : Off-network UE2UE link -SynchREF UE (UE1) - -UE1(eth0 - 10.10.10.1)--------UE2(eno1 - 10.10.10.2) - -Here's an example of /etc/network/interfaces configuration for UE1 -auto eth0 - iface eth0 inet static - address 10.10.10.1 - netmask 255.255.255.0 - gateway 10.10.10.1 - -Prepare the environment: - - git clone https://gitlab.eurecom.fr/matzakos/LTE-D2D.git #branch: master -This branch contains all the current development for DDPS - - UE MAC<-> UE MAC for Scenario 1 - - eNB MAC<->UE MAC (NFAPI Transport) - - RRC Extensions for “on-network” cases - -NFAPI configuration (required even for Scenario 1 target) - - git clone https://github.com/cisco/open-nFAPI.git - - cd open-nfapi - - patch -p1 --dry-run < $OPENAIR_HOME/open-nfapi.oai.patch -Validate that there are no errors - - patch -p1 < $OPENAIR_HOME/open-nfapi.oai.patch - -OAI build/execute - - export NFAPI_DIR=XXX (place where NFAPI was installed) - - cd cmake_targets - - ./build_oai --UE --ninja - (if necessary, use ./build_oai -I --UE to install required packages) - - cd ran_build/build/ - -UE1: - - sudo ifconfig oip0 10.0.0.1 - - sudo iptables -A POSTROUTING -t mangle -o oip0 -d 224.0.0.3 -j MARK --set-mark 3 - - (if necessary) sudo route add default gw 10.10.10.1 eth0 -UE2: - - sudo ifconfig oip0 10.0.0.2 - - sudo iptables -A POSTROUTING -t mangle -o oip0 -d 224.0.0.3 -j MARK --set-mark 3 - - (if necessary) sudo route add default gw 10.10.10.1 eno1 - -UE1 and UE2: Get and build vencore_app from d2d-l3-stub (branch: l3_stub) - - gcc -I . vencore_app.c -o vencore_app -lpthread - --------------------------------- -TEST ONE-TO-MANY -Run UE1 then UE2, for example: -UE1: sudo ./lte-softmodem-stub -U --emul-iface eth0 -UE2: sudo ./lte-softmodem-stub -U --emul-iface eno1 - -Test with Ping -- Sender - UE1: ping -I oip0 224.0.0.3 -- Receiver - UE2: using wireshark - -Test with Iperf -- Sender - UE1: iperf -c 224.0.0.3 -u -b 0.1M --bind 10.0.0.1 -t 100 -- Receiver - UE2: sudo ./mcreceive 224.0.0.3 5001 - -Filter the incomming packets according to GroupL2Id: receiver (one-to-many) can discard the packets if it doesn't belong to this group. -For the moment, both sender and receiver use the same set of Ids (hardcoded) - -UE1 (sender) - - sudo ./lte-softmodem-stub -U --emul-iface eth0 - - ./vencore_app #send the sourceL2Id, groupL2Id to OAI - - ping -I oip0 224.0.0.3 - UE2(receiver) - - sudo ./lte-softmodem-stub -U --emul-iface eno1 - #we can see the incomming packets from OAI log, however, cannot see from Wireshark -> they are discarded at MAC layer - - ./vencore_app #we can see the packets appearing in Wireshark - - -------------------------------------- -TEST PC5-S (UE1 -sender, UE2 - receiver) and PC5-U for ONE-TO-ONE scenario -Configure UE1/UE2 -UE1: - - sudo ifconfig oip0 10.0.0.1 - - sudo iptables -A POSTROUTING -t mangle -o oip0 -d 10.0.0.2 -j MARK --set-mark 3 - - sudo route add default gw 10.10.10.1 eth0 -UE2: - - sudo ifconfig oip0 10.0.0.2 - - sudo iptables -A POSTROUTING -t mangle -o oip0 -d 10.0.0.1 -j MARK --set-mark 3 - - sudo route add default gw 10.10.10.1 eno1 - -step 1: -- UE1: sudo ./lte-softmodem-stub -U --emul-iface eth0 -step 2: -- UE2: sudo ./lte-softmodem-stub -U --emul-iface eno1 -- UE2: ./vencore_app -r #listen to incomming message from PC5-S -step 3: -- UE1: ./vencore_app -s #send a message via PC5-S (e.g., DirectCommunicationRequest) - -Generate unicast traffic -UE1: ping -I oip0 10.0.0.2 - - - -------------------------------------- -TEST PC5-D -step 1: -- UE1: sudo ./lte-softmodem-stub -U --emul-iface eth0 -- UE1: ./vencore_app -d #send a PC5-Discovery-Announcement via PC5D -step 2: -- UE2: sudo ./lte-softmodem-stub -U --emul-iface eno1 -- UE2: ./vencore_app -d #send a PC5-Discovery-Announcement via PC5D - From 8fcea7c314f02b517e27bfaaa2ec89bd5a3b3fc5 Mon Sep 17 00:00:00 2001 From: calvin_peyron Date: Fri, 12 Sep 2025 14:40:19 +0200 Subject: [PATCH 96/97] Update UL_MIMO documentation --- doc/UL_MIMO.md | 88 +++++++++++++++++++++++++++++++++++++++++++++++++ doc/UL_MIMO.txt | 31 ----------------- 2 files changed, 88 insertions(+), 31 deletions(-) create mode 100644 doc/UL_MIMO.md delete mode 100644 doc/UL_MIMO.txt diff --git a/doc/UL_MIMO.md b/doc/UL_MIMO.md new file mode 100644 index 0000000000..6adf5f2790 --- /dev/null +++ b/doc/UL_MIMO.md @@ -0,0 +1,88 @@ +# UpLink Multiple Input Multiple Output (UL MIMO) +## What is UL MIMO ? + +UpLink MIMO refers to the use of multiple antennas at both the UE (User Equipment) as transmitter and the base station as receiver. They are used to send multiple data streams simultaneously from the user to the network. + +## How to use it ? + +## Step 1: build phy sim + +```bash +cd cmake_targets/ +sudo ./build_oai --phy_simulators -c +cd phy_simulators/build/ +``` + +## Step 2: Use the sim + +### Option 1: launch ULSCH sim +On UpLink Shared CHannel(ULSCH), multiple users share the same radio resources (time and frequency slots), the base station schedules and assigns the resources dynamically. The goal here is to simulate the sending of data from an UE to a base station. + +Example: +| Parameter | Value | Description | +|--- |:-: |--: | +| R | 106 | R_NB_UL=106| +| m | 9 | MCS=9 | +| s | 13 | Start SNR | +| n | 100 | Will simulate 100 frames | +| y | 4 | 4 antennas used in eNB | +| z | 4 | 4 antennas used in UE | +| W | 4 | Will use 4 layers | + + +```bash +sudo ./nr_ulschsim -R 106 -m9 -s13 -n100 -y4 -z4 -W4 +``` + +### Option 2: launch UL sim +On UpLink Channel, a single user is dealing with a single base station. The goal is here to simulate the sending of the data from an UE to a base station. + +It's focusing on: +- UpLink chain validation +- PUSCH (whole chain) on UE side and gNB side +- No channel model on data domain signal, which means an ideal scenario is used +- No cross-path connection, which means no signal leakage or interference are present +- PMI = 0 is only unitary precoding matrix + +The same parameters are used. + +```bash +sudo ./nr_ulsim -n100 -m9 -r106 -s13 -W4 -y4 -z4 +``` +### Option 3: Use RFSIM + +#### Build + +RF simulator allows to test OAI without an RF board. It replaces an actual RF board driver. + +```bash +sudo ./build_oai -c --gNB --nrUE -w SIMU +``` + +#### Run +##### 4x4 RANK 4 + +Example: +| Parameter | Value | Description | +|--- |:-: |--: | +| O | [...] | Use a configuration file | +| l | 2 | Set the number of layers for downlink to 2 for PHY test mode | +| L | 4 | Set the number of layers for uplink to 4 for PHY test mode | +| ue-nb-ant-rx | 4 | Set UE number of rx antennas to 4 | +| ue-nb-ant-tx | 4 | Set UE number of tx antennas to 4 | + +```bash +sudo ./nr-softmodem -O ../../../targets/PROJECTS/GENERIC-NR-5GC/CONF/gnb.sa.band78.fr1.106PRB.usrpb210.4layer.conf --rfsim --phy-test --l 2 --L 4 +sudo ./nr-uesoftmodem --rfsim --phy-test --ue-nb-ant-rx 4 --ue-nb-ant-tx 4 +``` + +##### 4x4 RANK 2 + +The same parameters are used excepted uplink number of layers set to 2. + +```bash +sudo ./nr-softmodem -O ../../../targets/PROJECTS/GENERIC-NR-5GC/CONF/gnb.sa.band78.fr1.106PRB.usrpb210.4layer.conf --rfsim --phy-test --l 2 --L 2 +sudo ./nr-uesoftmodem --rfsim --phy-test --ue-nb-ant-rx 4 --ue-nb-ant-tx 4 +``` + + diff --git a/doc/UL_MIMO.txt b/doc/UL_MIMO.txt deleted file mode 100644 index c06bc78c4c..0000000000 --- a/doc/UL_MIMO.txt +++ /dev/null @@ -1,31 +0,0 @@ -# how to build phy sim -cd cmake_targets/ -sudo ./build_oai --phy_simulators -c - -cd phy_simulators/build/ -# ULSCH sim -# bit level validation -# PUSCH encoding and decoding for 4 layers 2 CMD without data -sudo ./nr_ulschsim -R 106 -m9 -s13 -n100 -y4 -z4 -W4 - -# UL sim -# Uplink chain validation -# PUSCH on UE side and gNB side,whole chain for PUSCH -# No channel model on data domain signal -# No cross-path connection -# PMI = 0 is only unitary prcoding matrix -sudo ./nr_ulsim -n100 -m9 -r106 -s13 -W4 -y4 -z4 - -# How to build rfsim -sudo ./build_oai -c --gNB --nrUE -w SIMU - -# How to run with rfsim -# 4x4 RANK 4 -sudo ./nr-softmodem -O ../../../targets/PROJECTS/GENERIC-NR-5GC/CONF/gnb.sa.band78.fr1.106PRB.usrpb210.4layer.conf --rfsim --phy-test --l 2 --L 4 -sudo ./nr-uesoftmodem --rfsim --phy-test --ue-nb-ant-rx 4 --ue-nb-ant-tx 4 -# 4x4 RANK 2 -sudo ./nr-softmodem -O ../../../targets/PROJECTS/GENERIC-NR-5GC/CONF/gnb.sa.band78.fr1.106PRB.usrpb210.4layer.conf --rfsim --phy-test --l 2 --L 2 -sudo ./nr-uesoftmodem --rfsim --phy-test --ue-nb-ant-rx 4 --ue-nb-ant-tx 4 - - - From 7eb33a4003c202ff410f0f80a4cd5fa4f44d778a Mon Sep 17 00:00:00 2001 From: Robert Schmidt Date: Wed, 1 Oct 2025 22:13:48 +0200 Subject: [PATCH 97/97] Workaround: give pod deployment more time pods deployed through helm occasionally take more time, leading to errors such as Error from server (BadRequest): container "physim" in pod "oai-physims-4g-sczvl" is waiting to start: ContainerCreating Give a bit more time before checking the logs. We cannot wait too much, as the container will just end on FINISHED in running state, so the logs will always be there. --- ci-scripts/scripts/oc-deploy-physims.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ci-scripts/scripts/oc-deploy-physims.sh b/ci-scripts/scripts/oc-deploy-physims.sh index c209be485b..19c1940ba1 100755 --- a/ci-scripts/scripts/oc-deploy-physims.sh +++ b/ci-scripts/scripts/oc-deploy-physims.sh @@ -15,7 +15,7 @@ oc project ${OC_NS} > /dev/null oc tag oaicicd-ran/oai-physim:${IMG_TAG} ${OC_NS}/oai-physim:${IMG_TAG} helm install ${OC_RELEASE} ${OAI_DIR}/charts/${OC_RELEASE} --set global.image.version=${IMG_TAG} --wait --timeout 120s POD_ID=$(oc get pods | grep oai-${OC_RELEASE} | awk '{print $1}') -sleep 10 +sleep 30 echo "Monitoring logs for 'FINISHED' in pod '$POD_ID'" oc logs -f -n ${OC_NS} "$POD_ID" | while read -r line; do if [[ "$line" == *"FINISHED"* ]]; then