diff --git a/CMakeLists.txt b/CMakeLists.txt index fde8abf2e..646a40455 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -30,7 +30,7 @@ endif(${CMAKE_SOURCE_DIR} STREQUAL ${CMAKE_BINARY_DIR}) ######################################################################## # Project setup ######################################################################## -cmake_minimum_required(VERSION 3.10) +cmake_minimum_required(VERSION 3.5) project( SRSRAN ) message( STATUS "CMAKE_SYSTEM: " ${CMAKE_SYSTEM} ) message( STATUS "CMAKE_SYSTEM_PROCESSOR: " ${CMAKE_SYSTEM_PROCESSOR} ) @@ -69,6 +69,7 @@ option(DISABLE_SIMD "Disable SIMD instructions" OFF) option(AUTO_DETECT_ISA "Autodetect supported ISA extensions" ON) option(ENABLE_GUI "Enable GUI (using srsGUI)" ON) +option(ENABLE_RF_PLUGINS "Enable RF plugins" ON) option(ENABLE_UHD "Enable UHD" ON) option(ENABLE_BLADERF "Enable BladeRF" ON) option(ENABLE_SOAPYSDR "Enable SoapySDR" ON) @@ -509,8 +510,12 @@ if(CMAKE_C_COMPILER_ID MATCHES "GNU" OR CMAKE_C_COMPILER_ID MATCHES "Clang") endif () if (ENABLE_ASAN) - set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fsanitize=address -fno-omit-frame-pointer") - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=address -fno-omit-frame-pointer") + # Note: When using ASAN, we need to ensure the use of RPATH instead of RUNPATH via "-Wl,--disable-new-dtags" + # While RPATH is default, some systems (e.g. Ubuntu 18.04 and 20.04) use RUNPATH by default, which is non-transitive. + # Since ASAN intercepts dlopen(), by which it replaces the dynamic string token "$ORIGIN" to its own location, + # the RF plugins won't be found by "libsrsran_rf.so" when using ASAN + RUNPATH in the top-level executable. + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fsanitize=address -fno-omit-frame-pointer -Wl,--disable-new-dtags") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=address -fno-omit-frame-pointer -Wl,--disable-new-dtags") endif (ENABLE_ASAN) if (ENABLE_TSAN) diff --git a/cmake/modules/FindMKL.cmake b/cmake/modules/FindMKL.cmake index 22de689e6..4d2c61b86 100644 --- a/cmake/modules/FindMKL.cmake +++ b/cmake/modules/FindMKL.cmake @@ -28,31 +28,45 @@ find_path(MKL_INCLUDE_DIR NAMES mkl.h HINTS $ENV{MKL_DIR}/include + /opt/intel/oneapi/mkl/latest/include + /opt/intel/mkl/include + /usr/include/mkl PATHS) find_path(MKL_FFTW_INCLUDE_DIR NAMES fftw3.h HINTS $ENV{MKL_DIR}/include/fftw + /opt/intel/oneapi/mkl/latest/include/fftw + /opt/intel/mkl/include/fftw + /usr/include/mkl/fftw PATHS) find_library(MKL_LIBRARIES NAMES mkl_rt HINTS $ENV{MKL_DIR}/lib/intel64 + /opt/intel/oneapi/mkl/latest/lib/intel64 + /opt/intel/mkl/lib/intel64 PATHS) find_library(MKL_CORE NAMES libmkl_core.a HINTS $ENV{MKL_DIR}/lib/intel64 + /opt/intel/oneapi/mkl/latest/lib/intel64/ + /opt/intel/mkl/lib/intel64 PATHS) find_library(MKL_ILP NAMES libmkl_intel_ilp64.a HINTS $ENV{MKL_DIR}/lib/intel64 + /opt/intel/oneapi/mkl/latest/lib/intel64/ + /opt/intel/mkl/lib/intel64 PATHS) find_library(MKL_SEQ NAMES libmkl_sequential.a HINTS $ENV{MKL_DIR}/lib/intel64 + /opt/intel/oneapi/mkl/latest/lib/intel64/ + /opt/intel/mkl/lib/intel64 PATHS) set(MKL_STATIC_LIBRARIES -Wl,--start-group ${MKL_CORE} ${MKL_ILP} ${MKL_SEQ} -Wl,--end-group -lpthread -lm -ldl) @@ -61,7 +75,7 @@ set(MKL_INCLUDE_DIRS ${MKL_INCLUDE_DIR} ${MKL_FFTW_INCLUDE_DIR}) include(FindPackageHandleStandardArgs) # handle the QUIETLY and REQUIRED arguments and set MKL_FOUND to TRUE # if all listed variables are TRUE -find_package_handle_standard_args(mkl DEFAULT_MSG +find_package_handle_standard_args(MKL DEFAULT_MSG MKL_LIBRARIES MKL_CORE MKL_ILP MKL_SEQ MKL_INCLUDE_DIRS) if(MKL_FOUND) diff --git a/cmake/modules/FindSKIQ.cmake b/cmake/modules/FindSKIQ.cmake index 74bf873ea..cf6f472cf 100644 --- a/cmake/modules/FindSKIQ.cmake +++ b/cmake/modules/FindSKIQ.cmake @@ -18,7 +18,7 @@ # and at http://www.gnu.org/licenses/. # -INCLUDE(FindPkgConfig) +FIND_PACKAGE(PkgConfig REQUIRED) #PKG_CHECK_MODULES(SKIQ SKIQ) IF(NOT SKIQ_FOUND) @@ -26,7 +26,7 @@ FIND_PATH( SKIQ_INCLUDE_DIRS NAMES sidekiq_api.h HINTS $ENV{SKIQ_DIR}/inc - $ENV{SKIQ_DIR}/sidekiq_core/inc + $ENV{SKIQ_DIR}/sidekiq_core/inc PATHS /usr/local/include /usr/include ) diff --git a/cmake/modules/FindSoapySDR.cmake b/cmake/modules/FindSoapySDR.cmake index 1ac1968e2..a6bb2960c 100644 --- a/cmake/modules/FindSoapySDR.cmake +++ b/cmake/modules/FindSoapySDR.cmake @@ -23,11 +23,11 @@ if(NOT SOAPYSDR_FOUND) pkg_check_modules (SOAPYSDR_PKG SoapySDR) find_path(SOAPYSDR_INCLUDE_DIRS - NAMES Device.h + NAMES SoapySDR/Device.h HINTS $ENV{SOAPY_DIR}/include PATHS ${SOAPYSDR_PKG_INCLUDE_DIRS} - /usr/include/SoapySDR - /usr/local/include/SoapySDR + /usr/include + /usr/local/include ) find_library(SOAPYSDR_LIBRARIES diff --git a/cmake/modules/SRSRANbuildinfo.cmake.in b/cmake/modules/SRSRANbuildinfo.cmake.in index 8cbea7325..e2ce71273 100644 --- a/cmake/modules/SRSRANbuildinfo.cmake.in +++ b/cmake/modules/SRSRANbuildinfo.cmake.in @@ -1,4 +1,3 @@ -cmake_minimum_required(VERSION 2.6) execute_process( COMMAND git rev-parse --abbrev-ref HEAD diff --git a/lib/examples/CMakeLists.txt b/lib/examples/CMakeLists.txt index 2581ede59..ea1826d40 100644 --- a/lib/examples/CMakeLists.txt +++ b/lib/examples/CMakeLists.txt @@ -66,7 +66,7 @@ endif(SRSGUI_FOUND) if (ZEROMQ_FOUND) add_executable(zmq_remote_rx zmq_remote_rx.c) - target_link_libraries(zmq_remote_rx srsran_phy srsran_rf) + target_link_libraries(zmq_remote_rx srsran_phy srsran_rf ${ZEROMQ_LIBRARIES}) endif (ZEROMQ_FOUND) ################################################################# diff --git a/lib/examples/pdsch_enodeb.c b/lib/examples/pdsch_enodeb.c index 238eaece8..77c92f43d 100644 --- a/lib/examples/pdsch_enodeb.c +++ b/lib/examples/pdsch_enodeb.c @@ -86,10 +86,6 @@ static bool enable_256qam = false; static float output_file_snr = +INFINITY; static bool use_standard_lte_rate = false; -// CFR type test args -static char cfr_manual_str[] = "manual"; -static char cfr_auto_cma_str[] = "auto_cma"; -static char cfr_auto_ema_str[] = "auto_ema"; // CFR runtime control flags static bool cfr_thr_inc = false; @@ -105,7 +101,7 @@ typedef struct { } cfr_args_t; static cfr_args_t cfr_args = {.enable = 0, - .mode = cfr_manual_str, + .mode = "manual", .manual_thres = 1.0f, .strength = 1.0f, .auto_target_papr = 8.0f, @@ -301,14 +297,9 @@ static int parse_cfr_args() cfr_config.alpha = cfr_args.strength; cfr_config.ema_alpha = cfr_args.ema_alpha; - if (!strcmp(cfr_args.mode, cfr_manual_str)) { - cfr_config.cfr_mode = SRSRAN_CFR_THR_MANUAL; - } else if (!strcmp(cfr_args.mode, cfr_auto_cma_str)) { - cfr_config.cfr_mode = SRSRAN_CFR_THR_AUTO_CMA; - } else if (!strcmp(cfr_args.mode, cfr_auto_ema_str)) { - cfr_config.cfr_mode = SRSRAN_CFR_THR_AUTO_EMA; - } else { - ERROR("CFR mode is not recognised"); + cfr_config.cfr_mode = srsran_cfr_str2mode(cfr_args.mode); + if (cfr_config.cfr_mode == SRSRAN_CFR_THR_INVALID) { + ERROR("CFR mode not recognised"); return SRSRAN_ERROR; } diff --git a/lib/examples/pssch_ue.c b/lib/examples/pssch_ue.c index d45ccb428..1c7334009 100644 --- a/lib/examples/pssch_ue.c +++ b/lib/examples/pssch_ue.c @@ -303,7 +303,7 @@ int main(int argc, char** argv) cf_t* equalized_sf_buffer = srsran_vec_malloc(sizeof(cf_t) * sf_n_re); // RX - srsran_ofdm_t fft[SRSRAN_MAX_PORTS]; + srsran_ofdm_t fft[SRSRAN_MAX_PORTS] = {}; srsran_ofdm_cfg_t ofdm_cfg = {}; ofdm_cfg.nof_prb = cell_sl.nof_prb; ofdm_cfg.cp = SRSRAN_CP_NORM; diff --git a/lib/include/srsran/asn1/nas_5g_ies.h b/lib/include/srsran/asn1/nas_5g_ies.h index ec8a82d72..482185a87 100644 --- a/lib/include/srsran/asn1/nas_5g_ies.h +++ b/lib/include/srsran/asn1/nas_5g_ies.h @@ -50,7 +50,7 @@ public: reserved = 0b111, } value; - const char* to_string(); + const char* to_string() const; }; typedef nas_enumerated registration_type_type; @@ -60,7 +60,7 @@ public: follow_on_request_pending = 0b1, } value; - const char* to_string(); + const char* to_string() const; }; typedef nas_enumerated follow_on_request_bit_type; @@ -83,7 +83,7 @@ public: mapped_security_context = 0b1, } value; - const char* to_string(); + const char* to_string() const; }; typedef nas_enumerated security_context_flag_type; @@ -92,7 +92,7 @@ public: no_key_is_available_or_reserved = 0b111, } value; - const char* to_string(); + const char* to_string() const; }; typedef nas_enumerated nas_key_set_identifier_type; @@ -122,7 +122,7 @@ public: eui_64 = 0b111, } value; - const char* to_string(); + const char* to_string() const; }; typedef nas_enumerated identity_types; @@ -140,7 +140,7 @@ public: gli = 0b100, } value; - const char* to_string(); + const char* to_string() const; }; typedef nas_enumerated supi_format_type; @@ -151,7 +151,7 @@ public: ecies_scheme_profile_b = 0b0010, } value; - const char* to_string(); + const char* to_string() const; }; typedef nas_enumerated protection_scheme_id_type; @@ -424,7 +424,7 @@ public: sst_sd_mapped_hplmn_sst_and_mapped_hplmn_sd = 0b00001000, } value; - const char* to_string(); + const char* to_string() const; }; typedef nas_enumerated SST_type; @@ -655,7 +655,7 @@ public: data_centric = 0b1, } value; - const char* to_string(); + const char* to_string() const; }; typedef nas_enumerated UE_usage_setting_type; @@ -680,7 +680,7 @@ public: drx_cycle_parameter_t_256 = 0b0100, } value; - const char* to_string(); + const char* to_string() const; }; typedef nas_enumerated drx_value_type; @@ -746,7 +746,7 @@ public: multiple_payloads = 0b1111, } value; - const char* to_string(); + const char* to_string() const; }; typedef nas_enumerated Payload_container_type_type; @@ -793,7 +793,7 @@ public: sms_over_nas_supported = 0b1, } value; - const char* to_string(); + const char* to_string() const; }; typedef nas_enumerated SMS_requested_type; @@ -803,7 +803,7 @@ public: ue_radio_capability_update_needed = 0b1, } value; - const char* to_string(); + const char* to_string() const; }; typedef nas_enumerated NG_RAN_RCU_type; @@ -815,7 +815,7 @@ public: reserved = 0b11, } value; - const char* to_string(); + const char* to_string() const; }; typedef nas_enumerated PNB_5GS_CIoT_type; @@ -827,7 +827,7 @@ public: reserved = 0b11, } value; - const char* to_string(); + const char* to_string() const; }; typedef nas_enumerated PNB_EPS_CIoT_type; @@ -925,7 +925,7 @@ public: seconds_20 = 0b1111, } value; - const char* to_string(); + const char* to_string() const; }; typedef nas_enumerated Paging_Time_Window_type; @@ -949,7 +949,7 @@ public: second_20_48 = 0b1111, } value; - const char* to_string(); + const char* to_string() const; }; typedef nas_enumerated eDRX_value_type; @@ -978,7 +978,7 @@ public: value_indicates_that_the_timer_is_deactivated = 0b111, } value; - const char* to_string(); + const char* to_string() const; }; typedef nas_enumerated Unit_type; @@ -1062,7 +1062,7 @@ public: drx_cycle_parameter_t_1024 = 0b0111, } value; - const char* to_string(); + const char* to_string() const; }; typedef nas_enumerated nb_n1_mode_drx_value_type; @@ -1084,7 +1084,7 @@ public: registered_for_emergency_services = 0b1, } value; - const char* to_string(); + const char* to_string() const; }; typedef nas_enumerated Emergency_registered_type; @@ -1094,7 +1094,7 @@ public: nssaa_is_to_be_performed = 0b1, } value; - const char* to_string(); + const char* to_string() const; }; typedef nas_enumerated NSSAA_to_be_performed_type; @@ -1104,7 +1104,7 @@ public: sms_over_nas_allowed = 0b1, } value; - const char* to_string(); + const char* to_string() const; }; typedef nas_enumerated SMS_allowed_type; @@ -1116,7 +1116,7 @@ public: reserved = 0b111, } value; - const char* to_string(); + const char* to_string() const; }; typedef nas_enumerated registration_result_type; @@ -1154,7 +1154,7 @@ public: reserved = 0b11, } value; - const char* to_string(); + const char* to_string() const; }; typedef nas_enumerated type_of_list_type; @@ -1307,7 +1307,7 @@ public: nssai_inclusion_mode_d = 0b11, } value; - const char* to_string(); + const char* to_string() const; }; typedef nas_enumerated NSSAI_inclusion_mode_type; @@ -1351,7 +1351,7 @@ public: network_assigned_ue_radio_capability_i_ds_deletion_requested = 0b001, } value; - const char* to_string(); + const char* to_string() const; }; typedef nas_enumerated Deletion_request_type; @@ -1447,7 +1447,7 @@ public: protocol_error_unspecified = 0b01101111, } value; - const char* to_string(); + const char* to_string() const; }; typedef nas_enumerated cause_5gmm_type; @@ -1469,7 +1469,7 @@ public: switch_off = 0b1, } value; - const char* to_string(); + const char* to_string() const; }; typedef nas_enumerated switch_off_type; @@ -1479,7 +1479,7 @@ public: re_registration_required = 0b1, } value; - const char* to_string(); + const char* to_string() const; }; typedef nas_enumerated re_registration_required_type; @@ -1490,7 +1490,7 @@ public: access_3_gpp_and_non_3_gpp_access = 0b11, } value; - const char* to_string(); + const char* to_string() const; }; typedef nas_enumerated access_type_type; @@ -1535,7 +1535,7 @@ public: unused_shall_be_interpreted_as_data_2 = 0b1011, } value; - const char* to_string(); + const char* to_string() const; }; typedef nas_enumerated Service_type_value_type; @@ -1559,7 +1559,7 @@ public: emergency_services_fallback = 0b100, } value; - const char* to_string(); + const char* to_string() const; }; typedef nas_enumerated control_plane_service_type_value_type; @@ -1624,7 +1624,7 @@ public: reserved = 0b11, } value; - const char* to_string(); + const char* to_string() const; }; typedef nas_enumerated value_type; @@ -1658,7 +1658,7 @@ public: release_of_n1_nas_signalling_connection_not_required = 0b1, } value; - const char* to_string(); + const char* to_string() const; }; typedef nas_enumerated SCMR_type; @@ -1745,7 +1745,7 @@ public: eui_64 = 0b111, } value; - const char* to_string(); + const char* to_string() const; }; typedef nas_enumerated identity_types; @@ -1773,7 +1773,7 @@ public: ia7_5g = 0b0111, } value; - const char* to_string(); + const char* to_string() const; }; typedef nas_enumerated integrity_protection_algorithm_type; @@ -1789,7 +1789,7 @@ public: ea7_5g = 0b0111, } value; - const char* to_string(); + const char* to_string() const; }; typedef nas_enumerated ciphering_algorithm_type; @@ -1813,7 +1813,7 @@ public: imeisv_requested = 0b001, } value; - const char* to_string(); + const char* to_string() const; }; typedef nas_enumerated imeisv_request_type; @@ -1841,7 +1841,7 @@ public: eia7 = 0b111, } value; - const char* to_string(); + const char* to_string() const; }; typedef nas_enumerated integrity_protection_algorithm_type; @@ -1857,7 +1857,7 @@ public: eea7 = 0b111, } value; - const char* to_string(); + const char* to_string() const; }; typedef nas_enumerated ciphering_algorithm_type; @@ -1943,7 +1943,7 @@ public: non_3_gpp_access = 0b10, } value; - const char* to_string(); + const char* to_string() const; }; typedef nas_enumerated Access_type_value_type; @@ -1982,7 +1982,7 @@ public: reserved = 0b111, } value; - const char* to_string(); + const char* to_string() const; }; typedef nas_enumerated Request_type_value_type; @@ -2015,7 +2015,7 @@ public: ma_pdu_session_network_upgrade_is_allowed = 0b0001, } value; - const char* to_string(); + const char* to_string() const; }; typedef nas_enumerated MA_PDU_session_information_value_type; @@ -2040,7 +2040,7 @@ public: reserved = 0b11, } value; - const char* to_string(); + const char* to_string() const; }; typedef nas_enumerated Downlink_data_expected_type; @@ -2064,7 +2064,7 @@ public: full_data_rate = 0b11111111, } value; - const char* to_string(); + const char* to_string() const; }; typedef nas_enumerated max_data_rate_UPIP_uplink_type; @@ -2075,7 +2075,7 @@ public: full_data_rate = 0b11111111, } value; - const char* to_string(); + const char* to_string() const; }; typedef nas_enumerated max_data_rate_UPIP_downlink_type; @@ -2102,7 +2102,7 @@ public: reserved = 0b111, } value; - const char* to_string(); + const char* to_string() const; }; typedef nas_enumerated PDU_session_type_value_type; @@ -2129,7 +2129,7 @@ public: reserved = 0b111, } value; - const char* to_string(); + const char* to_string() const; }; typedef nas_enumerated SSC_mode_value_type; @@ -2254,7 +2254,7 @@ public: bits_15 = 0b10, } value; - const char* to_string(); + const char* to_string() const; }; typedef nas_enumerated CID_Length_type; @@ -2277,7 +2277,7 @@ public: ipv4v6 = 0b011, } value; - const char* to_string(); + const char* to_string() const; }; typedef nas_enumerated PDU_session_type_value_type; @@ -2340,7 +2340,7 @@ public: inc_by_256_pbps = 0b00011001, } value; - const char* to_string(); + const char* to_string() const; }; typedef nas_enumerated unit_session_AMBR_type; @@ -2430,7 +2430,7 @@ public: value_indicates_that_the_timer_is_deactivated = 0b111, } value; - const char* to_string(); + const char* to_string() const; }; typedef nas_enumerated Unit_type; @@ -2485,7 +2485,7 @@ public: ethernet_pdn_type_in_s1_mode_supported = 0b1, } value; - const char* to_string(); + const char* to_string() const; }; typedef nas_enumerated EPT_S1_type; @@ -2557,7 +2557,7 @@ public: the_back_off_timer_is_applied_in_all_plm_ns = 0b1, } value; - const char* to_string(); + const char* to_string() const; }; typedef nas_enumerated abo_type; diff --git a/lib/include/srsran/asn1/rrc_nr_utils.h b/lib/include/srsran/asn1/rrc_nr_utils.h index b4db92817..548ccef70 100644 --- a/lib/include/srsran/asn1/rrc_nr_utils.h +++ b/lib/include/srsran/asn1/rrc_nr_utils.h @@ -103,6 +103,7 @@ bool make_phy_tdd_cfg(const srsran_duplex_config_nr_t& srsran_duplex_config bool make_phy_harq_ack_cfg(const asn1::rrc_nr::phys_cell_group_cfg_s& phys_cell_group_cfg, srsran_harq_ack_cfg_hl_t* srsran_ue_dl_nr_harq_ack_cfg); bool make_phy_coreset_cfg(const asn1::rrc_nr::ctrl_res_set_s& ctrl_res_set, srsran_coreset_t* srsran_coreset); +void make_phy_search_space0_cfg(srsran_search_space_t* in_srsran_search_space); bool make_phy_search_space_cfg(const asn1::rrc_nr::search_space_s& search_space, srsran_search_space_t* srsran_search_space); bool make_phy_csi_report(const asn1::rrc_nr::csi_report_cfg_s& csi_report_cfg, diff --git a/lib/include/srsran/common/bearer_manager.h b/lib/include/srsran/common/bearer_manager.h index 849c2e7b8..45b882dcf 100644 --- a/lib/include/srsran/common/bearer_manager.h +++ b/lib/include/srsran/common/bearer_manager.h @@ -45,6 +45,7 @@ public: srsran::srsran_rat_t rat; uint32_t lcid; uint32_t eps_bearer_id; + uint32_t five_qi = 0; bool is_valid() const { return rat != srsran_rat_t::nulltype; } }; static const radio_bearer_t invalid_rb; @@ -59,9 +60,11 @@ public: bool has_active_radio_bearer(uint32_t eps_bearer_id); - radio_bearer_t get_radio_bearer(uint32_t eps_bearer_id); + radio_bearer_t get_radio_bearer(uint32_t eps_bearer_id) const; - radio_bearer_t get_eps_bearer_id_for_lcid(uint32_t lcid); + radio_bearer_t get_eps_bearer_id_for_lcid(uint32_t lcid) const; + + bool set_five_qi(uint32_t eps_bearer_id, uint16_t five_qi); private: using eps_rb_map_t = std::map; @@ -156,7 +159,8 @@ public: void rem_user(uint16_t rnti); bool has_active_radio_bearer(uint16_t rnti, uint32_t eps_bearer_id); radio_bearer_t get_radio_bearer(uint16_t rnti, uint32_t eps_bearer_id); - radio_bearer_t get_lcid_bearer(uint16_t rnti, uint32_t lcid); + radio_bearer_t get_lcid_bearer(uint16_t rnti, uint32_t lcid) const; + bool set_five_qi(uint16_t rnti, uint32_t eps_bearer_id, uint16_t five_qi); private: srslog::basic_logger& logger; @@ -166,4 +170,4 @@ private: } // namespace srsenb -#endif // SRSRAN_BEARER_MANAGER_H \ No newline at end of file +#endif // SRSRAN_BEARER_MANAGER_H diff --git a/lib/include/srsran/common/common.h b/lib/include/srsran/common/common.h index c341b24c3..c077a11af 100644 --- a/lib/include/srsran/common/common.h +++ b/lib/include/srsran/common/common.h @@ -89,7 +89,7 @@ inline const char* enum_to_text(const char* const array[], uint32_t nof_types, u } template -ItemType enum_to_number(ItemType* array, uint32_t nof_types, uint32_t enum_val) +constexpr ItemType enum_to_number(ItemType* array, uint32_t nof_types, uint32_t enum_val) { return enum_val >= nof_types ? -1 : array[enum_val]; } diff --git a/lib/include/srsran/common/phy_cfg_nr.h b/lib/include/srsran/common/phy_cfg_nr.h index e176b5a6c..7fe974eeb 100644 --- a/lib/include/srsran/common/phy_cfg_nr.h +++ b/lib/include/srsran/common/phy_cfg_nr.h @@ -54,7 +54,8 @@ struct phy_cfg_nr_t { srsran_harq_ack_cfg_hl_t harq_ack = {}; srsran_csi_hl_cfg_t csi = {}; srsran_carrier_nr_t carrier = {}; - ssb_cfg_t ssb; + ssb_cfg_t ssb = {}; + uint32_t t_offset = 0; ///< n-TimingAdvanceOffset phy_cfg_nr_t() {} diff --git a/lib/include/srsran/interfaces/enb_rrc_interface_types.h b/lib/include/srsran/interfaces/enb_rrc_interface_types.h index 7ebf1a43e..3e416c8a7 100644 --- a/lib/include/srsran/interfaces/enb_rrc_interface_types.h +++ b/lib/include/srsran/interfaces/enb_rrc_interface_types.h @@ -43,6 +43,7 @@ struct meas_cell_cfg_t { asn1::rrc::q_offset_range_e cell_individual_offset; uint32_t allowed_meas_bw; bool direct_forward_path_available; + int tac; }; // neigh measurement Cell info diff --git a/lib/include/srsran/interfaces/enb_s1ap_interfaces.h b/lib/include/srsran/interfaces/enb_s1ap_interfaces.h index cc4a45807..476c15760 100644 --- a/lib/include/srsran/interfaces/enb_s1ap_interfaces.h +++ b/lib/include/srsran/interfaces/enb_s1ap_interfaces.h @@ -89,6 +89,7 @@ public: */ virtual bool send_ho_required(uint16_t rnti, uint32_t target_eci, + uint16_t target_tac, srsran::plmn_id_t target_plmn, srsran::span fwd_erabs, srsran::unique_byte_buffer_t rrc_container, diff --git a/lib/include/srsran/interfaces/enb_x2_interfaces.h b/lib/include/srsran/interfaces/enb_x2_interfaces.h index a5addce72..6e9da4d0d 100644 --- a/lib/include/srsran/interfaces/enb_x2_interfaces.h +++ b/lib/include/srsran/interfaces/enb_x2_interfaces.h @@ -39,6 +39,7 @@ class rrc_nr_interface_rrc public: struct sgnb_addition_req_params_t { uint32_t eps_bearer_id; + uint32_t five_qi; // add configuration check // E-RAB Parameters, Tunnel address (IP address, TEID) // QCI, security, etc diff --git a/lib/include/srsran/interfaces/gnb_rrc_nr_interfaces.h b/lib/include/srsran/interfaces/gnb_rrc_nr_interfaces.h index ed63dca96..b2d01e474 100644 --- a/lib/include/srsran/interfaces/gnb_rrc_nr_interfaces.h +++ b/lib/include/srsran/interfaces/gnb_rrc_nr_interfaces.h @@ -29,17 +29,20 @@ namespace srsenb { class rrc_interface_ngap_nr { public: - virtual int ue_set_security_cfg_key(uint16_t rnti, const asn1::fixed_bitstring<256, false, true>& key) = 0; - virtual int ue_set_bitrates(uint16_t rnti, const asn1::ngap::ue_aggregate_maximum_bit_rate_s& rates) = 0; - virtual int set_aggregate_max_bitrate(uint16_t rnti, const asn1::ngap::ue_aggregate_maximum_bit_rate_s& rates) = 0; - virtual int ue_set_security_cfg_capabilities(uint16_t rnti, const asn1::ngap::ue_security_cap_s& caps) = 0; - virtual int start_security_mode_procedure(uint16_t rnti, srsran::unique_byte_buffer_t nas_pdu) = 0; - virtual int - establish_rrc_bearer(uint16_t rnti, uint16_t pdu_session_id, srsran::const_byte_span nas_pdu, uint32_t lcid) = 0; - virtual int allocate_lcid(uint16_t rnti) = 0; - virtual int release_bearers(uint16_t rnti) = 0; - virtual void release_user(uint16_t rnti) = 0; - virtual void write_dl_info(uint16_t rnti, srsran::unique_byte_buffer_t sdu) = 0; + virtual int ue_set_security_cfg_key(uint16_t rnti, const asn1::fixed_bitstring<256, false, true>& key) = 0; + virtual int ue_set_bitrates(uint16_t rnti, const asn1::ngap::ue_aggregate_maximum_bit_rate_s& rates) = 0; + virtual int set_aggregate_max_bitrate(uint16_t rnti, const asn1::ngap::ue_aggregate_maximum_bit_rate_s& rates) = 0; + virtual int ue_set_security_cfg_capabilities(uint16_t rnti, const asn1::ngap::ue_security_cap_s& caps) = 0; + virtual int start_security_mode_procedure(uint16_t rnti, srsran::unique_byte_buffer_t nas_pdu) = 0; + virtual int establish_rrc_bearer(uint16_t rnti, + uint16_t pdu_session_id, + srsran::const_byte_span nas_pdu, + uint32_t lcid, + uint32_t five_qi) = 0; + virtual int allocate_lcid(uint16_t rnti) = 0; + virtual int release_bearers(uint16_t rnti) = 0; + virtual void release_user(uint16_t rnti) = 0; + virtual void write_dl_info(uint16_t rnti, srsran::unique_byte_buffer_t sdu) = 0; }; } // namespace srsenb diff --git a/lib/include/srsran/interfaces/rlc_interface_types.h b/lib/include/srsran/interfaces/rlc_interface_types.h index f3cb743fb..8e24ede0c 100644 --- a/lib/include/srsran/interfaces/rlc_interface_types.h +++ b/lib/include/srsran/interfaces/rlc_interface_types.h @@ -73,11 +73,28 @@ inline std::string to_string(const rlc_am_nr_sn_size_t& sn_size) constexpr static const char* options[] = {"12 bits", "18 bits"}; return enum_to_text(options, (uint32_t)rlc_mode_t::nulltype, (uint32_t)sn_size); } -inline uint16_t to_number(const rlc_am_nr_sn_size_t& sn_size) +constexpr uint16_t to_number(const rlc_am_nr_sn_size_t& sn_size) { - constexpr static uint16_t options[] = {12, 18}; + constexpr uint16_t options[] = {12, 18}; return enum_to_number(options, (uint32_t)rlc_mode_t::nulltype, (uint32_t)sn_size); } +/** + * @brief Value range of the serial numbers + * @param sn_size Length of the serial number field in bits + * @return cardianlity + */ +constexpr uint32_t cardinality(const rlc_am_nr_sn_size_t& sn_size) +{ + return (1 << to_number(sn_size)); +} +/**************************************************************************** + * Tx constants + * Ref: 3GPP TS 38.322 version 16.2.0 Section 7.2 + ***************************************************************************/ +constexpr uint32_t am_window_size(const rlc_am_nr_sn_size_t& sn_size) +{ + return cardinality(sn_size) / 2; +} struct rlc_am_config_t { /**************************************************************************** @@ -228,14 +245,25 @@ public: rlc_cnfg.am.t_poll_retx = 5; return rlc_cnfg; } - static rlc_config_t default_rlc_am_nr_config() + static rlc_config_t default_rlc_am_nr_config(uint32_t sn_size = 12) { - rlc_config_t rlc_cnfg = {}; - rlc_cnfg.rat = srsran_rat_t::nr; - rlc_cnfg.rlc_mode = rlc_mode_t::am; + rlc_config_t rlc_cnfg = {}; + rlc_cnfg.rat = srsran_rat_t::nr; + rlc_cnfg.rlc_mode = rlc_mode_t::am; + if (sn_size == 12) { + rlc_cnfg.am_nr.tx_sn_field_length = srsran::rlc_am_nr_sn_size_t::size12bits; + rlc_cnfg.am_nr.rx_sn_field_length = srsran::rlc_am_nr_sn_size_t::size12bits; + } else if (sn_size == 18) { + rlc_cnfg.am_nr.tx_sn_field_length = srsran::rlc_am_nr_sn_size_t::size18bits; + rlc_cnfg.am_nr.rx_sn_field_length = srsran::rlc_am_nr_sn_size_t::size18bits; + } else { + return {}; + } rlc_cnfg.am_nr.t_status_prohibit = 8; + rlc_cnfg.am_nr.max_retx_thresh = 4; rlc_cnfg.am_nr.t_reassembly = 35; rlc_cnfg.am_nr.poll_pdu = 4; + rlc_cnfg.am_nr.t_poll_retx = 45; return rlc_cnfg; } static rlc_config_t default_rlc_um_nr_config(uint32_t sn_size = 6) diff --git a/lib/include/srsran/interfaces/ue_nr_interfaces.h b/lib/include/srsran/interfaces/ue_nr_interfaces.h index a579b90a4..6b291d463 100644 --- a/lib/include/srsran/interfaces/ue_nr_interfaces.h +++ b/lib/include/srsran/interfaces/ue_nr_interfaces.h @@ -206,6 +206,9 @@ public: // RRC informs MAC about new UE identity for contention-free RA virtual bool set_crnti(const uint16_t crnti) = 0; + + // RRC informs MAC to start/stop search for BCCH messages + virtual void bcch_search(bool enabled) = 0; }; struct phy_args_nr_t { @@ -264,6 +267,12 @@ public: const float preamble_received_target_power, const float ta_base_sec = 0.0f) = 0; + /// Apply TA command after RAR + virtual void set_timeadv_rar(uint32_t tti, uint32_t ta_cmd) = 0; + + /// Apply TA command after MAC CE + virtual void set_timeadv(uint32_t tti, uint32_t ta_cmd) = 0; + /** * @brief Query PHY if there is a valid PUCCH SR resource configured for a given SR identifier * @param sr_id SR identifier diff --git a/lib/include/srsran/interfaces/ue_pdcp_interfaces.h b/lib/include/srsran/interfaces/ue_pdcp_interfaces.h index 33b1be4fe..fab61d0fb 100644 --- a/lib/include/srsran/interfaces/ue_pdcp_interfaces.h +++ b/lib/include/srsran/interfaces/ue_pdcp_interfaces.h @@ -68,6 +68,13 @@ public: virtual bool is_lcid_enabled(uint32_t lcid) = 0; }; +// SDAP interface +class pdcp_interface_sdap_nr +{ +public: + virtual void write_sdu(uint32_t lcid, srsran::unique_byte_buffer_t pdu) = 0; +}; + // STACK interface for GW (based on EPS-bearer IDs) class stack_interface_gw { diff --git a/lib/include/srsran/interfaces/ue_phy_interfaces.h b/lib/include/srsran/interfaces/ue_phy_interfaces.h index ccbd18126..1ea4aebbc 100644 --- a/lib/include/srsran/interfaces/ue_phy_interfaces.h +++ b/lib/include/srsran/interfaces/ue_phy_interfaces.h @@ -35,6 +35,15 @@ namespace srsue { +struct cfr_args_t { + bool enable = false; + srsran_cfr_mode_t mode = SRSRAN_CFR_THR_MANUAL; + float manual_thres = 2.0f; + float strength = 1.0f; + float auto_target_papr = 7.0f; + float ema_alpha = 1.0f / (float)SRSRAN_CP_NORM_NSYMB; +}; + struct phy_args_t { std::string type = "lte"; srsran::phy_log_args_t log; @@ -105,6 +114,8 @@ struct phy_args_t { srsran::channel::args_t dl_channel_args; srsran::channel::args_t ul_channel_args; + + cfr_args_t cfr_args; ///< Stores user-defined CFR configuration }; /* RAT agnostic Interface MAC -> PHY */ diff --git a/lib/include/srsran/interfaces/ue_sdap_interfaces.h b/lib/include/srsran/interfaces/ue_sdap_interfaces.h new file mode 100644 index 000000000..25b3179c6 --- /dev/null +++ b/lib/include/srsran/interfaces/ue_sdap_interfaces.h @@ -0,0 +1,47 @@ +/** + * + * \section COPYRIGHT + * + * Copyright 2013-2021 Software Radio Systems Limited + * + * By using this file, you agree to the terms and conditions set + * forth in the LICENSE file which can be found at the top level of + * the distribution. + * + */ + +/****************************************************************************** + * File: ue_sdap_interfaces.h + * Description: Abstract base class interfaces for SDAP layer + *****************************************************************************/ + +#ifndef SRSRAN_UE_SDAP_INTERFACES_H +#define SRSRAN_UE_SDAP_INTERFACES_H + +/***************************** + * SDAP INTERFACES + ****************************/ +class sdap_interface_pdcp_nr +{ +public: + virtual void write_pdu(uint32_t lcid, srsran::unique_byte_buffer_t pdu) = 0; +}; +class sdap_interface_gw_nr +{ +public: + virtual void write_sdu(uint32_t lcid, srsran::unique_byte_buffer_t pdu) = 0; +}; + +class sdap_interface_rrc +{ +public: + struct bearer_cfg_t { + bool is_data; + bool add_downlink_header; + bool add_uplink_header; + uint32_t qfi; + }; + virtual bool set_bearer_cfg(uint32_t lcid, const bearer_cfg_t& cfg) = 0; +}; + +#endif // SRSRAN_UE_SDAP_INTERFACES_H diff --git a/lib/include/srsran/phy/cfr/cfr.h b/lib/include/srsran/phy/cfr/cfr.h index e4cca2719..2cb3fa72f 100644 --- a/lib/include/srsran/phy/cfr/cfr.h +++ b/lib/include/srsran/phy/cfr/cfr.h @@ -29,12 +29,14 @@ #define CFR_EMA_INIT_AVG_PWR 0.1 /** - * @brief CFR manual threshold or PAPR limiting with Moving Average or EMA power averaging + * @brief CFR manual threshold or PAPR limiting with CMA or EMA power averaging */ typedef enum SRSRAN_API { - SRSRAN_CFR_THR_MANUAL = 1, - SRSRAN_CFR_THR_AUTO_CMA = 2, - SRSRAN_CFR_THR_AUTO_EMA = 3 + SRSRAN_CFR_THR_INVALID, + SRSRAN_CFR_THR_MANUAL, + SRSRAN_CFR_THR_AUTO_CMA, + SRSRAN_CFR_THR_AUTO_EMA, + SRSRAN_CFR_NOF_MODES } srsran_cfr_mode_t; /** @@ -124,4 +126,14 @@ SRSRAN_API int srsran_cfr_set_threshold(srsran_cfr_t* q, float thres); */ SRSRAN_API int srsran_cfr_set_papr(srsran_cfr_t* q, float papr); +/** + * @brief Converts a string representing a CFR mode from the config files into srsran_cfr_mode_t type + * + * @param[in] mode_str the cfr.mode string coming from the config file + * @return SRSRAN_CFR_THR_INVALID if mode_str is empty, + * SRSRAN_CFR_THR_INVALID if mode_str is not recognised, + * otherwise it returns the corresponding srsran_cfr_mode_t value. + */ +SRSRAN_API srsran_cfr_mode_t srsran_cfr_str2mode(const char* mode_str); + #endif // SRSRAN_CFR_H diff --git a/lib/include/srsran/phy/ch_estimation/chest_ul.h b/lib/include/srsran/phy/ch_estimation/chest_ul.h index cca034e17..86b7609cb 100644 --- a/lib/include/srsran/phy/ch_estimation/chest_ul.h +++ b/lib/include/srsran/phy/ch_estimation/chest_ul.h @@ -49,7 +49,7 @@ typedef struct SRSRAN_API { cf_t* ce; uint32_t nof_re; float noise_estimate; - float noise_estimate_dbm; + float noise_estimate_dbFs; float rsrp; float rsrp_dBfs; float epre; diff --git a/lib/include/srsran/phy/phch/harq_ack_cfg.h b/lib/include/srsran/phy/phch/harq_ack_cfg.h index abc41f7ec..b655b0af0 100644 --- a/lib/include/srsran/phy/phch/harq_ack_cfg.h +++ b/lib/include/srsran/phy/phch/harq_ack_cfg.h @@ -42,6 +42,7 @@ typedef struct { uint32_t pid; ///< HARQ process identifier uint16_t rnti; uint32_t pucch_resource_id; + uint32_t n_cce; } srsran_harq_ack_resource_t; /** diff --git a/lib/include/srsran/phy/phch/pucch.h b/lib/include/srsran/phy/phch/pucch.h index 711ec0bc1..3fd44b5ea 100644 --- a/lib/include/srsran/phy/phch/pucch.h +++ b/lib/include/srsran/phy/phch/pucch.h @@ -84,6 +84,8 @@ typedef struct SRSRAN_API { srsran_uci_value_t uci_data; float dmrs_correlation; float snr_db; + float rssi_dbFs; + float ni_dbFs; float correlation; bool detected; diff --git a/lib/include/srsran/phy/phch/ra_ul_nr.h b/lib/include/srsran/phy/phch/ra_ul_nr.h index 6ddecd0d4..dadca55f8 100644 --- a/lib/include/srsran/phy/phch/ra_ul_nr.h +++ b/lib/include/srsran/phy/phch/ra_ul_nr.h @@ -128,11 +128,13 @@ SRSRAN_API int srsran_ra_ul_nr_freq(const srsran_carrier_nr_t* carrier, * @brief Selects a valid PUCCH resource for transmission * @param pucch_cfg PUCCH configuration from upper layers * @param uci_cfg Uplink Control information configuration (and PDCCH context) + * @param N_bwp_sz Uplink BWP size in nof_prb * @param[out] resource Selected resource for transmitting PUCCH * @return SRSRAN_SUCCESS if provided configuration is valid, SRSRAN_ERROR code otherwise */ SRSRAN_API int srsran_ra_ul_nr_pucch_resource(const srsran_pucch_nr_hl_cfg_t* pucch_cfg, const srsran_uci_cfg_nr_t* uci_cfg, + uint32_t N_bwp_sz, srsran_pucch_nr_resource_t* resource); /** diff --git a/lib/include/srsran/phy/rf/rf.h b/lib/include/srsran/phy/rf/rf.h index 44891a6fe..b298cdcbb 100644 --- a/lib/include/srsran/phy/rf/rf.h +++ b/lib/include/srsran/phy/rf/rf.h @@ -72,6 +72,73 @@ typedef struct { typedef void (*srsran_rf_error_handler_t)(void* arg, srsran_rf_error_t error); +/* RF frontend API */ +typedef struct { + const char* name; + const char* (*srsran_rf_devname)(void* h); + int (*srsran_rf_start_rx_stream)(void* h, bool now); + int (*srsran_rf_stop_rx_stream)(void* h); + void (*srsran_rf_flush_buffer)(void* h); + bool (*srsran_rf_has_rssi)(void* h); + float (*srsran_rf_get_rssi)(void* h); + void (*srsran_rf_suppress_stdout)(void* h); + void (*srsran_rf_register_error_handler)(void* h, srsran_rf_error_handler_t error_handler, void* arg); + int (*srsran_rf_open)(char* args, void** h); + int (*srsran_rf_open_multi)(char* args, void** h, uint32_t nof_channels); + int (*srsran_rf_close)(void* h); + double (*srsran_rf_set_rx_srate)(void* h, double freq); + int (*srsran_rf_set_rx_gain)(void* h, double gain); + int (*srsran_rf_set_rx_gain_ch)(void* h, uint32_t ch, double gain); + int (*srsran_rf_set_tx_gain)(void* h, double gain); + int (*srsran_rf_set_tx_gain_ch)(void* h, uint32_t ch, double gain); + double (*srsran_rf_get_rx_gain)(void* h); + double (*srsran_rf_get_tx_gain)(void* h); + srsran_rf_info_t* (*srsran_rf_get_info)(void* h); + double (*srsran_rf_set_rx_freq)(void* h, uint32_t ch, double freq); + double (*srsran_rf_set_tx_srate)(void* h, double freq); + double (*srsran_rf_set_tx_freq)(void* h, uint32_t ch, double freq); + void (*srsran_rf_get_time)(void* h, time_t* secs, double* frac_secs); + void (*srsran_rf_sync_pps)(void* h); + int (*srsran_rf_recv_with_time)(void* h, + void* data, + uint32_t nsamples, + bool blocking, + time_t* secs, + double* frac_secs); + int (*srsran_rf_recv_with_time_multi)(void* h, + void** data, + uint32_t nsamples, + bool blocking, + time_t* secs, + double* frac_secs); + int (*srsran_rf_send_timed)(void* h, + void* data, + int nsamples, + time_t secs, + double frac_secs, + bool has_time_spec, + bool blocking, + bool is_start_of_burst, + bool is_end_of_burst); + int (*srsran_rf_send_timed_multi)(void* h, + void** data, + int nsamples, + time_t secs, + double frac_secs, + bool has_time_spec, + bool blocking, + bool is_start_of_burst, + bool is_end_of_burst); +} rf_dev_t; + +typedef struct { + const char* plugin_name; + void* dl_handle; + rf_dev_t* rf_api; +} srsran_rf_plugin_t; + +SRSRAN_API int srsran_rf_load_plugins(); + SRSRAN_API int srsran_rf_open(srsran_rf_t* h, char* args); SRSRAN_API int srsran_rf_open_multi(srsran_rf_t* h, char* args, uint32_t nof_channels); diff --git a/lib/include/srsran/phy/ue/ue_ul.h b/lib/include/srsran/phy/ue/ue_ul.h index 03f883d6e..194ce8c68 100644 --- a/lib/include/srsran/phy/ue/ue_ul.h +++ b/lib/include/srsran/phy/ue/ue_ul.h @@ -108,6 +108,8 @@ typedef struct SRSRAN_API { srsran_ra_ul_pusch_hopping_t hopping; + srsran_cfr_cfg_t cfr_config; + cf_t* out_buffer; cf_t* refsignal; cf_t* srs_signal; @@ -121,6 +123,8 @@ SRSRAN_API void srsran_ue_ul_free(srsran_ue_ul_t* q); SRSRAN_API int srsran_ue_ul_set_cell(srsran_ue_ul_t* q, srsran_cell_t cell); +SRSRAN_API int srsran_ue_ul_set_cfr(srsran_ue_ul_t* q, const srsran_cfr_cfg_t* cfr); + SRSRAN_API int srsran_ue_ul_pregen_signals(srsran_ue_ul_t* q, srsran_ue_ul_cfg_t* cfg); SRSRAN_API int srsran_ue_ul_dci_to_pusch_grant(srsran_ue_ul_t* q, diff --git a/lib/include/srsran/phy/utils/vector.h b/lib/include/srsran/phy/utils/vector.h index fe9876d38..47fe8535e 100644 --- a/lib/include/srsran/phy/utils/vector.h +++ b/lib/include/srsran/phy/utils/vector.h @@ -70,6 +70,15 @@ extern "C" { // Proportional moving average #define SRSRAN_VEC_PMA(average1, n1, average2, n2) (((average1) * (n1) + (average2) * (n2)) / ((n1) + (n2))) +// Safe Proportional moving average +#ifdef __cplusplus +#define SRSRAN_VEC_SAFE_PMA(average1, n1, average2, n2) \ + (std::isnormal((n1) + (n2)) ? SRSRAN_VEC_PMA(average1, n1, average2, n2) : (0)) +#else +#define SRSRAN_VEC_SAFE_PMA(average1, n1, average2, n2) \ + (isnormal((n1) + (n2)) ? SRSRAN_VEC_PMA(average1, n1, average2, n2) : (0)) +#endif + // Exponential moving average #define SRSRAN_VEC_EMA(data, average, alpha) ((alpha) * (data) + (1 - alpha) * (average)) diff --git a/lib/include/srsran/rlc/rlc_am_base.h b/lib/include/srsran/rlc/rlc_am_base.h index f2f2cb97c..2244c2e36 100644 --- a/lib/include/srsran/rlc/rlc_am_base.h +++ b/lib/include/srsran/rlc/rlc_am_base.h @@ -143,7 +143,6 @@ public: virtual void get_buffer_state(uint32_t& tx_queue, uint32_t& prio_tx_queue) = 0; virtual void reestablish() = 0; virtual void empty_queue() = 0; - virtual void discard_sdu(uint32_t pdcp_sn) = 0; virtual bool sdu_queue_is_full() = 0; virtual bool has_data() = 0; virtual void stop() = 0; @@ -151,6 +150,7 @@ public: void set_bsr_callback(bsr_callback_t callback); int write_sdu(unique_byte_buffer_t sdu); + virtual void discard_sdu(uint32_t pdcp_sn); virtual uint32_t read_pdu(uint8_t* payload, uint32_t nof_bytes) = 0; bool tx_enabled = false; diff --git a/lib/include/srsran/rlc/rlc_am_data_structs.h b/lib/include/srsran/rlc/rlc_am_data_structs.h index 284f64202..e2e33a1f5 100644 --- a/lib/include/srsran/rlc/rlc_am_data_structs.h +++ b/lib/include/srsran/rlc/rlc_am_data_structs.h @@ -204,25 +204,41 @@ public: const_iterator end() const { return list.end(); } }; +template +struct rlc_ringbuffer_base { + virtual ~rlc_ringbuffer_base() = default; + virtual T& add_pdu(size_t sn) = 0; + virtual void remove_pdu(size_t sn) = 0; + virtual T& operator[](size_t sn) = 0; + virtual size_t size() const = 0; + virtual bool empty() const = 0; + virtual bool full() const = 0; + virtual void clear() = 0; + virtual bool has_sn(uint32_t sn) const = 0; +}; + template -struct rlc_ringbuffer_t { - T& add_pdu(size_t sn) +struct rlc_ringbuffer_t : public rlc_ringbuffer_base { + ~rlc_ringbuffer_t() = default; + + T& add_pdu(size_t sn) override { srsran_expect(not has_sn(sn), "The same SN=%zd should not be added twice", sn); window.overwrite(sn, T(sn)); return window[sn]; } - void remove_pdu(size_t sn) + void remove_pdu(size_t sn) override { srsran_expect(has_sn(sn), "The removed SN=%zd is not in the window", sn); window.erase(sn); } - T& operator[](size_t sn) { return window[sn]; } - size_t size() const { return window.size(); } - bool empty() const { return window.empty(); } - void clear() { window.clear(); } + T& operator[](size_t sn) override { return window[sn]; } + size_t size() const override { return window.size(); } + bool full() const override { return window.full(); } + bool empty() const override { return window.empty(); } + void clear() override { window.clear(); } - bool has_sn(uint32_t sn) const { return window.contains(sn); } + bool has_sn(uint32_t sn) const override { return window.contains(sn); } // Return the sum data bytes of all active PDUs (check PDU is non-null) uint32_t get_buffered_bytes() @@ -309,41 +325,108 @@ private: uint32_t count = 0; }; -struct rlc_amd_retx_t { - uint32_t sn; - bool is_segment; - uint32_t so_start; - uint32_t so_end; - uint32_t current_so; +struct rlc_amd_retx_base_t { + const static uint32_t invalid_rlc_sn = std::numeric_limits::max(); + + uint32_t sn; ///< sequence number + bool is_segment; ///< flag whether this is a segment or not + uint32_t so_start; ///< offset to first byte of this segment + // so_end or segment_length are different for LTE and NR, hence are defined in subclasses + uint32_t current_so; ///< stores progressing SO during segmentation of this object + + rlc_amd_retx_base_t() : sn(invalid_rlc_sn), is_segment(false), so_start(0), current_so(0) {} + virtual ~rlc_amd_retx_base_t() = default; + + /** + * @brief overlaps implements a check whether the range of this retransmission object includes + * the given segment offset + * @param so the segment offset to check + * @return true if the segment offset is covered by the retransmission object. Otherwise false + */ + virtual bool overlaps(uint32_t so) const = 0; +}; + +struct rlc_amd_retx_lte_t : public rlc_amd_retx_base_t { + uint32_t so_end; ///< offset to first byte beyond the end of this segment + + rlc_amd_retx_lte_t() : rlc_amd_retx_base_t(), so_end(0) {} + bool overlaps(uint32_t segment_offset) const override + { + return (segment_offset >= so_start) && (segment_offset < so_end); + } +}; + +struct rlc_amd_retx_nr_t : public rlc_amd_retx_base_t { + uint32_t segment_length; ///< number of bytes contained in this segment + + rlc_amd_retx_nr_t() : rlc_amd_retx_base_t(), segment_length(0) {} + bool overlaps(uint32_t segment_offset) const override + { + return (segment_offset >= so_start) && (segment_offset < current_so + segment_length); + } +}; + +template +class pdu_retx_queue_base +{ +public: + virtual ~pdu_retx_queue_base() = default; + virtual T& push() = 0; + virtual void pop() = 0; + virtual T& front() = 0; + virtual void clear() = 0; + virtual size_t size() const = 0; + virtual bool empty() const = 0; + virtual bool full() const = 0; + + virtual T& operator[](size_t idx) = 0; + virtual const T& operator[](size_t idx) const = 0; + + virtual bool has_sn(uint32_t sn) const = 0; + virtual bool has_sn(uint32_t sn, uint32_t so) const = 0; }; -template -class pdu_retx_queue +template +class pdu_retx_queue : public pdu_retx_queue_base { public: - rlc_amd_retx_t& push() + ~pdu_retx_queue() = default; + + T& push() override { assert(not full()); - rlc_amd_retx_t& p = buffer[wpos]; - wpos = (wpos + 1) % WINDOW_SIZE; + T& p = buffer[wpos]; + wpos = (wpos + 1) % WINDOW_SIZE; return p; } - void pop() { rpos = (rpos + 1) % WINDOW_SIZE; } + void pop() override { rpos = (rpos + 1) % WINDOW_SIZE; } - rlc_amd_retx_t& front() + T& front() override { assert(not empty()); return buffer[rpos]; } - void clear() + T& operator[](size_t idx) override + { + srsran_assert(idx < size(), "Out-of-bounds access to element idx=%zd", idx); + return buffer[(rpos + idx) % WINDOW_SIZE]; + } + + const T& operator[](size_t idx) const override + { + srsran_assert(idx < size(), "Out-of-bounds access to element idx=%zd", idx); + return buffer[(rpos + idx) % WINDOW_SIZE]; + } + + void clear() override { wpos = 0; rpos = 0; } - bool has_sn(uint32_t sn) const + bool has_sn(uint32_t sn) const override { for (size_t i = rpos; i != wpos; i = (i + 1) % WINDOW_SIZE) { if (buffer[i].sn == sn) { @@ -353,14 +436,26 @@ public: return false; } - size_t size() const { return (wpos >= rpos) ? wpos - rpos : WINDOW_SIZE + wpos - rpos; } - bool empty() const { return wpos == rpos; } - bool full() const { return size() == WINDOW_SIZE - 1; } + bool has_sn(uint32_t sn, uint32_t so) const override + { + for (size_t i = rpos; i != wpos; i = (i + 1) % WINDOW_SIZE) { + if (buffer[i].sn == sn) { + if (buffer[i].overlaps(so)) { + return true; + } + } + } + return false; + } + + size_t size() const override { return (wpos >= rpos) ? wpos - rpos : WINDOW_SIZE + wpos - rpos; } + bool empty() const override { return wpos == rpos; } + bool full() const override { return size() == WINDOW_SIZE - 1; } private: - std::array buffer; - size_t wpos = 0; - size_t rpos = 0; + std::array buffer; + size_t wpos = 0; + size_t rpos = 0; }; } // namespace srsran diff --git a/lib/include/srsran/rlc/rlc_am_lte.h b/lib/include/srsran/rlc/rlc_am_lte.h index 9b6e4caf9..6ab706f6d 100644 --- a/lib/include/srsran/rlc/rlc_am_lte.h +++ b/lib/include/srsran/rlc/rlc_am_lte.h @@ -68,7 +68,6 @@ public: void stop(); uint32_t read_pdu(uint8_t* payload, uint32_t nof_bytes); - void discard_sdu(uint32_t discard_sn); bool sdu_queue_is_full(); bool has_data(); @@ -89,11 +88,11 @@ private: int build_status_pdu(uint8_t* payload, uint32_t nof_bytes); int build_retx_pdu(uint8_t* payload, uint32_t nof_bytes); - int build_segment(uint8_t* payload, uint32_t nof_bytes, rlc_amd_retx_t retx); + int build_segment(uint8_t* payload, uint32_t nof_bytes, rlc_amd_retx_lte_t retx); int build_data_pdu(uint8_t* payload, uint32_t nof_bytes); void update_notification_ack_info(uint32_t rlc_sn); - int required_buffer_size(const rlc_amd_retx_t& retx); + int required_buffer_size(const rlc_amd_retx_lte_t& retx); void retransmit_pdu(uint32_t sn); // Helpers @@ -147,7 +146,7 @@ private: // Tx windows rlc_ringbuffer_t, RLC_AM_WINDOW_SIZE> tx_window; - pdu_retx_queue retx_queue; + pdu_retx_queue retx_queue; pdcp_sn_vector_t notify_info_vec; // Mutexes diff --git a/lib/include/srsran/rlc/rlc_am_lte_packing.h b/lib/include/srsran/rlc/rlc_am_lte_packing.h index 93f0bf81f..03bc86396 100644 --- a/lib/include/srsran/rlc/rlc_am_lte_packing.h +++ b/lib/include/srsran/rlc/rlc_am_lte_packing.h @@ -62,7 +62,7 @@ int rlc_am_write_status_pdu(rlc_status_pdu_t* status, uint8_t* payload); uint32_t rlc_am_packed_length(rlc_amd_pdu_header_t* header); uint32_t rlc_am_packed_length(rlc_status_pdu_t* status); -uint32_t rlc_am_packed_length(rlc_amd_retx_t retx); +uint32_t rlc_am_packed_length(rlc_amd_retx_lte_t retx); bool rlc_am_is_pdu_segment(uint8_t* payload); bool rlc_am_is_valid_status_pdu(const rlc_status_pdu_t& status, uint32_t rx_win_min = 0); bool rlc_am_start_aligned(const uint8_t fi); diff --git a/lib/include/srsran/rlc/rlc_am_nr.h b/lib/include/srsran/rlc/rlc_am_nr.h index 72bab16ea..61929575e 100644 --- a/lib/include/srsran/rlc/rlc_am_nr.h +++ b/lib/include/srsran/rlc/rlc_am_nr.h @@ -25,6 +25,7 @@ #include "srsran/common/buffer_pool.h" #include "srsran/common/common.h" #include "srsran/common/timers.h" +#include "srsran/interfaces/pdcp_interface_types.h" #include "srsran/rlc/rlc_am_base.h" #include "srsran/rlc/rlc_am_data_structs.h" #include "srsran/rlc/rlc_am_nr_packing.h" @@ -81,13 +82,12 @@ struct rlc_am_nr_tx_state_t { struct rlc_amd_tx_pdu_nr { const uint32_t rlc_sn = INVALID_RLC_SN; - const uint32_t pdcp_sn = INVALID_RLC_SN; + uint32_t pdcp_sn = INVALID_RLC_SN; rlc_am_nr_pdu_header_t header = {}; unique_byte_buffer_t sdu_buf = nullptr; - uint32_t retx_count = 0; + uint32_t retx_count = RETX_COUNT_NOT_STARTED; struct pdu_segment { uint32_t so = 0; - uint32_t retx_count = 0; uint32_t payload_len = 0; }; std::list segment_list; @@ -105,22 +105,23 @@ public: uint32_t read_pdu(uint8_t* payload, uint32_t nof_bytes) final; void handle_control_pdu(uint8_t* payload, uint32_t nof_bytes) final; - void discard_sdu(uint32_t discard_sn) final; bool sdu_queue_is_full() final; void reestablish() final; + void stop() final; int write_sdu(unique_byte_buffer_t sdu); void empty_queue() final; + void empty_queue_no_lock(); // Data PDU helpers uint32_t build_new_pdu(uint8_t* payload, uint32_t nof_bytes); uint32_t build_new_sdu_segment(rlc_amd_tx_pdu_nr& tx_pdu, uint8_t* payload, uint32_t nof_bytes); uint32_t build_continuation_sdu_segment(rlc_amd_tx_pdu_nr& tx_pdu, uint8_t* payload, uint32_t nof_bytes); uint32_t build_retx_pdu(uint8_t* payload, uint32_t nof_bytes); - uint32_t build_retx_pdu_without_segmentation(rlc_amd_retx_t& retx, uint8_t* payload, uint32_t nof_bytes); - uint32_t build_retx_pdu_with_segmentation(rlc_amd_retx_t& retx, uint8_t* payload, uint32_t nof_bytes); - bool is_retx_segmentation_required(const rlc_amd_retx_t& retx, uint32_t nof_bytes); - uint32_t get_retx_expected_hdr_len(const rlc_amd_retx_t& retx); + uint32_t build_retx_pdu_without_segmentation(rlc_amd_retx_nr_t retx, uint8_t* payload, uint32_t nof_bytes); + uint32_t build_retx_pdu_with_segmentation(rlc_amd_retx_nr_t& retx, uint8_t* payload, uint32_t nof_bytes); + bool is_retx_segmentation_required(const rlc_amd_retx_nr_t& retx, uint32_t nof_bytes); + uint32_t get_retx_expected_hdr_len(const rlc_amd_retx_nr_t& retx); // Buffer State bool has_data() final; @@ -132,18 +133,21 @@ public: uint32_t build_status_pdu(byte_buffer_t* payload, uint32_t nof_bytes); // Polling - uint8_t get_pdu_poll(); + uint8_t get_pdu_poll(uint32_t sn, bool is_retx, uint32_t sdu_bytes); - void stop() final; + // Timers + void timer_expired(uint32_t timeout_id); + // Window helpers bool inside_tx_window(uint32_t sn) const; private: rlc_am* parent = nullptr; rlc_am_nr_rx* rx = nullptr; - uint32_t mod_nr = 4096; + uint32_t mod_nr = cardinality(rlc_am_nr_sn_size_t()); inline uint32_t tx_mod_base_nr(uint32_t sn) const; + void check_sn_reached_max_retx(uint32_t sn); /**************************************************************************** * Configurable parameters @@ -155,26 +159,41 @@ private: * Tx state variables * Ref: 3GPP TS 38.322 version 16.2.0 Section 7.1 ***************************************************************************/ - struct rlc_am_nr_tx_state_t st = {}; - rlc_ringbuffer_t tx_window; + struct rlc_am_nr_tx_state_t st = {}; + std::unique_ptr > tx_window; - // Queues and buffers - pdu_retx_queue retx_queue; - uint32_t sdu_under_segmentation_sn = INVALID_RLC_SN; // SN of the SDU currently being segmented. + // Queues, buffers and container + std::unique_ptr > retx_queue; + uint32_t sdu_under_segmentation_sn = INVALID_RLC_SN; // SN of the SDU currently being segmented. + pdcp_sn_vector_t notify_info_vec; // Helper constants - uint32_t min_hdr_size = 2; + uint32_t min_hdr_size = 2; // Pre-initialized for 12 bit SN, updated by configure() uint32_t so_size = 2; - uint32_t max_hdr_size = 4; + uint32_t max_hdr_size = 4; // Pre-initialized for 12 bit SN, updated by configure() + + /**************************************************************************** + * Tx constants + * Ref: 3GPP TS 38.322 version 16.2.0 Section 7.2 + ***************************************************************************/ + inline uint32_t tx_window_size() const; + + /**************************************************************************** + * TX timers + * Ref: 3GPP TS 38.322 version 16.2.0 Section 7.3 + ***************************************************************************/ + srsran::timer_handler::unique_timer poll_retransmit_timer; public: // Getters/Setters - void set_tx_state(const rlc_am_nr_tx_state_t& st_) { st = st_; } // This should only be used for testing. - rlc_am_nr_tx_state_t get_tx_state() { return st; } // This should only be used for testing. - uint32_t get_tx_window_size() { return tx_window.size(); } // This should only be used for testing. + void set_tx_state(const rlc_am_nr_tx_state_t& st_) { st = st_; } // This should only be used for testing. + rlc_am_nr_tx_state_t get_tx_state() { return st; } // This should only be used for testing. + uint32_t get_tx_window_utilization() { return tx_window->size(); } // This should only be used for testing. - // Debug Helper + // Debug Helpers void debug_state() const; + void info_state() const; + void debug_window() const; }; /**************************************************************************** @@ -231,7 +250,12 @@ public: bool inside_rx_window(uint32_t sn); void write_to_upper_layers(uint32_t lcid, unique_byte_buffer_t sdu); void insert_received_segment(rlc_amd_rx_pdu_nr segment, rlc_amd_rx_sdu_nr_t::segment_list_t& segment_list) const; - bool have_all_segments_been_received(const rlc_amd_rx_sdu_nr_t::segment_list_t& segment_list) const; + /** + * @brief update_segment_inventory This function updates the flags has_gap and fully_received of an SDU + * according to the current inventory of received SDU segments + * @param rx_sdu The SDU to operate on + */ + void update_segment_inventory(rlc_amd_rx_sdu_nr_t& rx_sdu) const; // Metrics uint32_t get_sdu_rx_latency_ms() final; @@ -242,17 +266,18 @@ public: // Helpers void debug_state() const; + void debug_window() const; private: rlc_am* parent = nullptr; rlc_am_nr_tx* tx = nullptr; byte_buffer_pool* pool = nullptr; - uint32_t mod_nr = 4096; + uint32_t mod_nr = cardinality(rlc_am_nr_sn_size_t()); uint32_t rx_mod_base_nr(uint32_t sn) const; // RX Window - rlc_ringbuffer_t rx_window; + std::unique_ptr > rx_window; // Mutexes std::mutex mutex; @@ -271,16 +296,22 @@ private: rlc_am_nr_config_t cfg = {}; /**************************************************************************** - * Tx state variables + * Rx state variables * Ref: 3GPP TS 38.322 version 16.2.0 Section 7.1 ***************************************************************************/ struct rlc_am_nr_rx_state_t st = {}; + /**************************************************************************** + * Rx constants + * Ref: 3GPP TS 38.322 version 16.2.0 Section 7.2 + ***************************************************************************/ + inline uint32_t rx_window_size() const; + public: // Getters/Setters - void set_rx_state(const rlc_am_nr_rx_state_t& st_) { st = st_; } // This should only be used for testing. - rlc_am_nr_rx_state_t get_rx_state() { return st; } // This should only be used for testing. - uint32_t get_rx_window_size() { return rx_window.size(); } // This should only be used for testing. + void set_rx_state(const rlc_am_nr_rx_state_t& st_) { st = st_; } // This should only be used for testing. + rlc_am_nr_rx_state_t get_rx_state() { return st; } // This should only be used for testing. + uint32_t get_rx_window_size() { return rx_window->size(); } // This should only be used for testing. }; } // namespace srsran diff --git a/lib/include/srsran/rlc/rlc_am_nr_packing.h b/lib/include/srsran/rlc/rlc_am_nr_packing.h index 92b9f16e4..b9b9ce2e9 100644 --- a/lib/include/srsran/rlc/rlc_am_nr_packing.h +++ b/lib/include/srsran/rlc/rlc_am_nr_packing.h @@ -28,7 +28,8 @@ namespace srsran { -const uint32_t INVALID_RLC_SN = 0xFFFFFFFF; +const uint32_t INVALID_RLC_SN = 0xFFFFFFFF; +const uint32_t RETX_COUNT_NOT_STARTED = 0xFFFFFFFF; ///< AM NR PDU header struct rlc_am_nr_pdu_header_t { @@ -69,6 +70,7 @@ struct rlc_amd_rx_pdu_nr_cmp { struct rlc_amd_rx_sdu_nr_t { uint32_t rlc_sn = 0; bool fully_received = false; + bool has_gap = false; unique_byte_buffer_t buf; using segment_list_t = std::set; segment_list_t segments; @@ -85,14 +87,43 @@ struct rlc_amd_tx_sdu_nr_t { explicit rlc_amd_tx_sdu_nr_t(uint32_t rlc_sn_) : rlc_sn(rlc_sn_) {} }; -///< AM NR Status PDU header (perhaps merge with LTE version) -typedef struct { - rlc_am_nr_control_pdu_type_t cpt; - uint32_t ack_sn; ///< SN of the next not received RLC Data PDU - uint16_t N_nack; ///< number of NACKs - uint8_t nack_range; ///< number of consecutively lost RLC SDUs starting from and including NACK_SN - rlc_status_nack_t nacks[RLC_AM_WINDOW_SIZE]; -} rlc_am_nr_status_pdu_t; +constexpr uint32_t rlc_am_nr_status_pdu_sizeof_header_ack_sn = 3; ///< header fixed part and ACK SN +constexpr uint32_t rlc_am_nr_status_pdu_sizeof_nack_sn_ext_12bit_sn = 2; ///< NACK SN and extension fields (12 bit SN) +constexpr uint32_t rlc_am_nr_status_pdu_sizeof_nack_sn_ext_18bit_sn = 3; ///< NACK SN and extension fields (18 bit SN) +constexpr uint32_t rlc_am_nr_status_pdu_sizeof_nack_so = 4; ///< NACK segment offsets (start and end) +constexpr uint32_t rlc_am_nr_status_pdu_sizeof_nack_range = 1; ///< NACK range (nof consecutively lost SDUs) + +/// AM NR Status PDU header +class rlc_am_nr_status_pdu_t +{ +private: + /// Stored SN size required to compute the packed size + rlc_am_nr_sn_size_t sn_size = rlc_am_nr_sn_size_t::nulltype; + /// Internal NACK container; keep in sync with packed_size_ + std::vector nacks_ = {}; + /// Stores the current packed size; sync on each change of nacks_ + uint32_t packed_size_ = rlc_am_nr_status_pdu_sizeof_header_ack_sn; + + void refresh_packed_size(); + uint32_t nack_size(const rlc_status_nack_t& nack) const; + +public: + /// CPT header + rlc_am_nr_control_pdu_type_t cpt = rlc_am_nr_control_pdu_type_t::status_pdu; + /// SN of the next not received RLC Data PDU + uint32_t ack_sn = INVALID_RLC_SN; + /// Read-only reference to NACKs + const std::vector& nacks = nacks_; + /// Read-only reference to packed size + const uint32_t& packed_size = packed_size_; + + rlc_am_nr_status_pdu_t(rlc_am_nr_sn_size_t sn_size); + void reset(); + void push_nack(const rlc_status_nack_t& nack); + const std::vector& get_nacks() const { return nacks_; } + uint32_t get_packed_size() const { return packed_size; } + bool trim(uint32_t max_packed_size); +}; /**************************************************************************** * Header pack/unpack helper functions for NR @@ -112,6 +143,10 @@ uint32_t rlc_am_nr_write_data_pdu_header(const rlc_am_nr_pdu_header_t& header, b uint32_t rlc_am_nr_packed_length(const rlc_am_nr_pdu_header_t& header); +/**************************************************************************** + * Status PDU pack/unpack helper functions for NR + * Ref: 3GPP TS 38.322 v16.2.0 Section 6.2.2.5 + ***************************************************************************/ uint32_t rlc_am_nr_read_status_pdu(const byte_buffer_t* pdu, const rlc_am_nr_sn_size_t sn_size, rlc_am_nr_status_pdu_t* status); @@ -119,10 +154,16 @@ uint32_t rlc_am_nr_read_status_pdu(const uint8_t* payload, const uint32_t nof_bytes, const rlc_am_nr_sn_size_t sn_size, rlc_am_nr_status_pdu_t* status); +uint32_t +rlc_am_nr_read_status_pdu_12bit_sn(const uint8_t* payload, const uint32_t nof_bytes, rlc_am_nr_status_pdu_t* status); +uint32_t +rlc_am_nr_read_status_pdu_18bit_sn(const uint8_t* payload, const uint32_t nof_bytes, rlc_am_nr_status_pdu_t* status); int32_t rlc_am_nr_write_status_pdu(const rlc_am_nr_status_pdu_t& status_pdu, const rlc_am_nr_sn_size_t sn_size, byte_buffer_t* pdu); +int32_t rlc_am_nr_write_status_pdu_12bit_sn(const rlc_am_nr_status_pdu_t& status_pdu, byte_buffer_t* pdu); +int32_t rlc_am_nr_write_status_pdu_18bit_sn(const rlc_am_nr_status_pdu_t& status_pdu, byte_buffer_t* pdu); /** * Logs Status PDU into provided log channel, using fmt_str as format string @@ -131,21 +172,35 @@ template void log_rlc_am_nr_status_pdu_to_string(srslog::log_channel& log_ch, const char* fmt_str, rlc_am_nr_status_pdu_t* status, + const std::string& rb_name, Args&&... args) { if (not log_ch.enabled()) { return; } fmt::memory_buffer buffer; - fmt::format_to(buffer, "ACK_SN = {}, N_nack = {}", status->ack_sn, status->N_nack); - if (status->N_nack > 0) { + fmt::format_to(buffer, "ACK_SN = {}, N_nack = {}", status->ack_sn, status->nacks.size()); + if (status->nacks.size() > 0) { fmt::format_to(buffer, ", NACK_SN = "); - for (uint32_t i = 0; i < status->N_nack; ++i) { - if (status->nacks[i].has_so) { - fmt::format_to( - buffer, "[{} {}:{}]", status->nacks[i].nack_sn, status->nacks[i].so_start, status->nacks[i].so_end); + for (uint32_t i = 0; i < status->nacks.size(); ++i) { + if (status->nacks[i].has_nack_range) { + if (status->nacks[i].has_so) { + fmt::format_to(buffer, + "[{} {}:{} r{}]", + status->nacks[i].nack_sn, + status->nacks[i].so_start, + status->nacks[i].so_end, + status->nacks[i].nack_range); + } else { + fmt::format_to(buffer, "[{} r{}]", status->nacks[i].nack_sn, status->nacks[i].nack_range); + } } else { - fmt::format_to(buffer, "[{}]", status->nacks[i].nack_sn); + if (status->nacks[i].has_so) { + fmt::format_to( + buffer, "[{} {}:{}]", status->nacks[i].nack_sn, status->nacks[i].so_start, status->nacks[i].so_end); + } else { + fmt::format_to(buffer, "[{}]", status->nacks[i].nack_sn); + } } } } @@ -155,18 +210,21 @@ void log_rlc_am_nr_status_pdu_to_string(srslog::log_channel& log_ch, /* * Log NR AMD PDUs */ -inline void log_rlc_am_nr_pdu_header_to_string(srslog::log_channel& log_ch, const rlc_am_nr_pdu_header_t& header) +inline void log_rlc_am_nr_pdu_header_to_string(srslog::log_channel& log_ch, + const rlc_am_nr_pdu_header_t& header, + const std::string& rb_name) { if (not log_ch.enabled()) { return; } fmt::memory_buffer buffer; fmt::format_to(buffer, - "[{}, P={}, SI={}, SN_SIZE={}, SN={}, SO={}", + "{}: [{}, P={}, SI={}, SN_SIZE={}, SN={}, SO={}", + rb_name, rlc_dc_field_text[header.dc], (header.p ? "1" : "0"), to_string_short(header.si), - header.sn, + to_string(header.sn_size), header.sn, header.so); fmt::format_to(buffer, "]"); diff --git a/lib/include/srsran/rlc/rlc_common.h b/lib/include/srsran/rlc/rlc_common.h index 08d249832..6da930160 100644 --- a/lib/include/srsran/rlc/rlc_common.h +++ b/lib/include/srsran/rlc/rlc_common.h @@ -43,6 +43,9 @@ namespace srsran { #define RLC_MAX_SDU_SIZE ((1 << 11) - 1) // Length of LI field is 11bits #define RLC_AM_MIN_DATA_PDU_SIZE (3) // AMD PDU with 10 bit SN (length of LI field is 11 bits) (No LI) +#define RLC_AM_NR_TYP_NACKS 512 // Expected number of NACKs in status PDU before expanding space by alloc +#define RLC_AM_NR_MAX_NACKS 2048 // Maximum number of NACKs in status PDU + #define RlcDebug(fmt, ...) logger.debug("%s: " fmt, rb_name, ##__VA_ARGS__) #define RlcInfo(fmt, ...) logger.info("%s: " fmt, rb_name, ##__VA_ARGS__) #define RlcWarning(fmt, ...) logger.warning("%s: " fmt, rb_name, ##__VA_ARGS__) @@ -171,17 +174,21 @@ public: // NACK helper (for LTE and NR) struct rlc_status_nack_t { - uint32_t nack_sn; - bool has_so; - uint16_t so_start; - uint16_t so_end; + uint32_t nack_sn; // Sequence Number (SN) of first missing SDU + bool has_so; // NACKs continuous sequence of bytes [so_start..so_end] + uint16_t so_start; // First missing byte in SDU with SN=nack_sn + uint16_t so_end; // Last missing byte in SDU with SN=nack_sn or SN=nack_sn+nack_range-1 if has_nack_range. + bool has_nack_range; // NACKs continuous sequence of SDUs + uint8_t nack_range; // Number of SDUs being NACKed (including SN=nack_sn) rlc_status_nack_t() { - has_so = false; - nack_sn = 0; - so_start = 0; - so_end = 0; + has_so = false; + nack_sn = 0; + so_start = 0; + so_end = 0; + has_nack_range = false; + nack_range = 0; } }; diff --git a/lib/include/srsran/rlc/rlc_um_lte.h b/lib/include/srsran/rlc/rlc_um_lte.h index 7e121c6a5..cb72d944c 100644 --- a/lib/include/srsran/rlc/rlc_um_lte.h +++ b/lib/include/srsran/rlc/rlc_um_lte.h @@ -58,6 +58,7 @@ private: bool configure(const rlc_config_t& cfg, std::string rb_name); uint32_t build_data_pdu(unique_byte_buffer_t pdu, uint8_t* payload, uint32_t nof_bytes); + void discard_sdu(uint32_t discard_sn); uint32_t get_buffer_state(); bool sdu_queue_is_full(); diff --git a/lib/include/srsran/rlc/rlc_um_nr.h b/lib/include/srsran/rlc/rlc_um_nr.h index 2c5f80f19..b41a8d287 100644 --- a/lib/include/srsran/rlc/rlc_um_nr.h +++ b/lib/include/srsran/rlc/rlc_um_nr.h @@ -62,6 +62,7 @@ private: bool configure(const rlc_config_t& cfg, std::string rb_name); uint32_t build_data_pdu(unique_byte_buffer_t pdu, uint8_t* payload, uint32_t nof_bytes); + void discard_sdu(uint32_t discard_sn); uint32_t get_buffer_state(); private: diff --git a/lib/include/srsran/upper/pdcp_entity_lte.h b/lib/include/srsran/upper/pdcp_entity_lte.h index 4cb0a6d1d..7d40d48fa 100644 --- a/lib/include/srsran/upper/pdcp_entity_lte.h +++ b/lib/include/srsran/upper/pdcp_entity_lte.h @@ -42,7 +42,7 @@ namespace srsran { class undelivered_sdus_queue { public: - explicit undelivered_sdus_queue(srsran::task_sched_handle task_sched); + explicit undelivered_sdus_queue(srsran::task_sched_handle task_sched, uint32_t sn_mod); bool empty() const { return count == 0; } bool is_full() const { return count >= capacity; } @@ -82,8 +82,9 @@ public: private: const static uint32_t capacity = 4096; const static uint32_t invalid_sn = -1; + uint32_t sn_mod = 0; - static uint32_t increment_sn(uint32_t sn) { return (sn + 1) % capacity; } + uint32_t increment_sn(uint32_t sn) { return (sn + 1) % sn_mod; } struct sdu_data { srsran::unique_byte_buffer_t sdu; diff --git a/lib/src/asn1/CMakeLists.txt b/lib/src/asn1/CMakeLists.txt index abdff4f03..be1cb6cd3 100644 --- a/lib/src/asn1/CMakeLists.txt +++ b/lib/src/asn1/CMakeLists.txt @@ -28,7 +28,7 @@ add_library(srsran_asn1 STATIC # ASN1 utils add_library(asn1_utils STATIC asn1_utils.cc) target_link_libraries(asn1_utils srsran_common) -INSTALL(TARGETS asn1_utils DESTINATION ${LIBRARY_DIR}) +install(TARGETS asn1_utils DESTINATION ${LIBRARY_DIR} OPTIONAL) # RRC ASN1 lib add_library(rrc_asn1 STATIC @@ -54,29 +54,29 @@ add_library(rrc_asn1 STATIC # Compile RRC ASN1 optimized for size target_compile_options(rrc_asn1 PRIVATE "-Os") target_link_libraries(rrc_asn1 asn1_utils srsran_common) -INSTALL(TARGETS rrc_asn1 DESTINATION ${LIBRARY_DIR}) +install(TARGETS rrc_asn1 DESTINATION ${LIBRARY_DIR} OPTIONAL) # S1AP ASN1 lib add_library(s1ap_asn1 STATIC s1ap.cc s1ap_utils.cc) target_compile_options(s1ap_asn1 PRIVATE "-Os") target_link_libraries(s1ap_asn1 asn1_utils srsran_common) -INSTALL(TARGETS s1ap_asn1 DESTINATION ${LIBRARY_DIR}) +install(TARGETS s1ap_asn1 DESTINATION ${LIBRARY_DIR} OPTIONAL) # RRC NR ASN1 add_library(rrc_nr_asn1 STATIC rrc_nr.cc rrc_nr_utils.cc) target_compile_options(rrc_nr_asn1 PRIVATE "-Os") target_link_libraries(rrc_nr_asn1 asn1_utils srsran_common) -INSTALL(TARGETS rrc_nr_asn1 DESTINATION ${LIBRARY_DIR}) +install(TARGETS rrc_nr_asn1 DESTINATION ${LIBRARY_DIR} OPTIONAL) # NGAP ASN1 add_library(ngap_nr_asn1 STATIC ngap.cc) target_compile_options(ngap_nr_asn1 PRIVATE "-Os") target_link_libraries(ngap_nr_asn1 asn1_utils srsran_common) -INSTALL(TARGETS ngap_nr_asn1 DESTINATION ${LIBRARY_DIR}) +install(TARGETS ngap_nr_asn1 DESTINATION ${LIBRARY_DIR} OPTIONAL) # NAS 5G add_library(nas_5g_msg STATIC nas_5g_msg.cc nas_5g_ies.cc nas_5g_utils.cc) target_compile_options(nas_5g_msg PRIVATE "-Os") target_link_libraries(nas_5g_msg asn1_utils srsran_common) -INSTALL(TARGETS nas_5g_msg DESTINATION ${LIBRARY_DIR}) +install(TARGETS nas_5g_msg DESTINATION ${LIBRARY_DIR} OPTIONAL) diff --git a/lib/src/asn1/asn1_utils.cc b/lib/src/asn1/asn1_utils.cc index 062106b41..815ae897e 100644 --- a/lib/src/asn1/asn1_utils.cc +++ b/lib/src/asn1/asn1_utils.cc @@ -195,7 +195,7 @@ SRSASN_CODE unpack_bits(T& val, Ptr& ptr, uint8_t& offset, const uint8_t* max_pt n_bits = 0; } else { auto mask = static_cast((1u << (8u - offset)) - 1u); - val += ((uint32_t)((*ptr) & mask)) << (n_bits - 8 + offset); + val += static_cast((*ptr) & mask) << (n_bits - 8 + offset); n_bits -= 8 - offset; offset = 0; ptr++; diff --git a/lib/src/asn1/nas_5g_ies.cc b/lib/src/asn1/nas_5g_ies.cc index a3df0b376..665243676 100644 --- a/lib/src/asn1/nas_5g_ies.cc +++ b/lib/src/asn1/nas_5g_ies.cc @@ -51,6 +51,34 @@ SRSASN_CODE registration_type_5gs_t::unpack(asn1::cbit_ref& bref) return SRSASN_SUCCESS; } +const char* registration_type_5gs_t::registration_type_type_::to_string() const +{ + switch (value) { + case registration_type_type_::initial_registration: + return "Initial Registration"; + case registration_type_type_::mobility_registration_updating: + return "Mobility Registration Updating"; + case registration_type_type_::periodic_registration_updating: + return "Periodic Registration Updating"; + case registration_type_type_::emergency_registration: + return "Emergency Registration"; + case registration_type_type_::reserved: + return "Reserved"; + default: + return "Invalid Choice"; + } +} +const char* registration_type_5gs_t::follow_on_request_bit_type_::to_string() const +{ + switch (value) { + case follow_on_request_bit_type_::no_follow_on_request_pending: + return "no_follow_on_request_pending"; + case follow_on_request_bit_type_::follow_on_request_pending: + return "follow_on_request_pending"; + default: + return "Invalid Choice"; + } +} // IE: key set identifier // Reference: 9.11.3.32 SRSASN_CODE key_set_identifier_t::pack(asn1::bit_ref& bref) @@ -69,6 +97,26 @@ SRSASN_CODE key_set_identifier_t::unpack(asn1::cbit_ref& bref) return SRSASN_SUCCESS; } +const char* key_set_identifier_t::security_context_flag_type_::to_string() const +{ + switch (value) { + case security_context_flag_type_::native_security_context: + return "native security context"; + case security_context_flag_type_::mapped_security_context: + return "mapped security context"; + default: + return "Invalid Choice"; + } +} +const char* key_set_identifier_t::nas_key_set_identifier_type_::to_string() const +{ + switch (value) { + case nas_key_set_identifier_type_::no_key_is_available_or_reserved: + return "no key is available or reserved"; + default: + return "Invalid Choice"; + } +} // IE: 5GS mobile identity // Reference: 9.11.3.4 SRSASN_CODE mobile_identity_5gs_t::pack(asn1::bit_ref& bref) @@ -189,7 +237,7 @@ SRSASN_CODE mobile_identity_5gs_t::unpack(asn1::cbit_ref& bref) return SRSASN_SUCCESS; } -const char* mobile_identity_5gs_t::identity_types_::to_string() +const char* mobile_identity_5gs_t::identity_types_::to_string() const { switch (value) { case identity_types_::no_identity: @@ -1182,6 +1230,17 @@ SRSASN_CODE ue_usage_setting_t::unpack(asn1::cbit_ref& bref) return SRSASN_SUCCESS; } +const char* ue_usage_setting_t::UE_usage_setting_type_::to_string() const +{ + switch (value) { + case UE_usage_setting_type_::voice_centric: + return "voice centric"; + case UE_usage_setting_type_::data_centric: + return "data centric"; + default: + return "Invalid Choice"; + } +} // IE: 5GS DRX parameters // Reference: 9.11.3.2A SRSASN_CODE drx_parameters_5gs_t::pack(asn1::bit_ref& bref) @@ -1222,6 +1281,23 @@ SRSASN_CODE drx_parameters_5gs_t::unpack(asn1::cbit_ref& bref) return SRSASN_SUCCESS; } +const char* drx_parameters_5gs_t::drx_value_type_::to_string() const +{ + switch (value) { + case drx_value_type_::drx_value_not_specified: + return "DRX value not specified"; + case drx_value_type_::drx_cycle_parameter_t_32: + return "DRX cycle parameter T 32"; + case drx_value_type_::drx_cycle_parameter_t_64: + return "DRX cycle parameter T 64"; + case drx_value_type_::drx_cycle_parameter_t_128: + return "DRX cycle parameter T 128"; + case drx_value_type_::drx_cycle_parameter_t_256: + return "DRX cycle parameter T 256"; + default: + return "Invalid Choice"; + } +} // IE: EPS NAS message container // Reference: 9.11.3.24 SRSASN_CODE eps_nas_message_container_t::pack(asn1::bit_ref& bref) @@ -1309,6 +1385,31 @@ SRSASN_CODE payload_container_type_t::unpack(asn1::cbit_ref& bref) return SRSASN_SUCCESS; } +const char* payload_container_type_t::Payload_container_type_type_::to_string() const +{ + switch (value) { + case Payload_container_type_type_::n1_sm_information: + return "N1 SM information"; + case Payload_container_type_type_::sms: + return "SMS"; + case Payload_container_type_type_::lte_positioning_protocol_lpp_message_container: + return "LTE Positioning Protocol LPP message container"; + case Payload_container_type_type_::sor_transparent_container: + return "SOR transparent container"; + case Payload_container_type_type_::ue_policy_container: + return "UE policy container"; + case Payload_container_type_type_::ue_parameters_update_transparent_container: + return "UE parameters update transparent container"; + case Payload_container_type_type_::location_services_message_container: + return "Location services message container"; + case Payload_container_type_type_::c_io_t_user_data_container: + return "CIoT user data container"; + case Payload_container_type_type_::multiple_payloads: + return "Multiple payloads"; + default: + return "Invalid Choice"; + } +} // IE: Payload container // Reference: 9.11.3.39 SRSASN_CODE payload_container_t::pack(asn1::bit_ref& bref) @@ -1404,6 +1505,58 @@ SRSASN_CODE update_type_5gs_t::unpack(asn1::cbit_ref& bref) return SRSASN_SUCCESS; } +const char* update_type_5gs_t::SMS_requested_type_::to_string() const +{ + switch (value) { + case SMS_requested_type_::sms_over_nas_not_supported: + return "SMS over NAS not supported"; + case SMS_requested_type_::sms_over_nas_supported: + return "SMS over NAS supported"; + default: + return "Invalid Choice"; + } +} +const char* update_type_5gs_t::NG_RAN_RCU_type_::to_string() const +{ + switch (value) { + case NG_RAN_RCU_type_::ue_radio_capability_update_not_needed: + return "UE radio capability update not needed"; + case NG_RAN_RCU_type_::ue_radio_capability_update_needed: + return "UE radio capability update needed"; + default: + return "Invalid Choice"; + } +} +const char* update_type_5gs_t::PNB_5GS_CIoT_type_::to_string() const +{ + switch (value) { + case PNB_5GS_CIoT_type_::no_additional_information: + return "no additional information"; + case PNB_5GS_CIoT_type_::control_plane_c_io_t_5gs_optimization: + return "control plane CIoT 5GS optimization"; + case PNB_5GS_CIoT_type_::user_plane_c_io_t_5gs_optimization: + return "user plane CIoT 5GS optimization"; + case PNB_5GS_CIoT_type_::reserved: + return "reserved"; + default: + return "Invalid Choice"; + } +} +const char* update_type_5gs_t::PNB_EPS_CIoT_type_::to_string() const +{ + switch (value) { + case PNB_EPS_CIoT_type_::no_additional_information: + return "no additional information"; + case PNB_EPS_CIoT_type_::control_plane_c_io_t_eps_optimization: + return "control plane CIoT EPS optimization"; + case PNB_EPS_CIoT_type_::user_plane_c_io_t_eps_optimization: + return "user plane CIoT EPS optimization"; + case PNB_EPS_CIoT_type_::reserved: + return "reserved"; + default: + return "Invalid Choice"; + } +} // IE: Mobile station classmark 2 // Reference: 9.11.3.31C SRSASN_CODE mobile_station_classmark_2_t::pack(asn1::bit_ref& bref) @@ -1624,6 +1777,84 @@ SRSASN_CODE extended_drx_parameters_t::unpack(asn1::cbit_ref& bref) return SRSASN_SUCCESS; } +const char* extended_drx_parameters_t::Paging_Time_Window_type_::to_string() const +{ + switch (value) { + case Paging_Time_Window_type_::seconds_0: + return "seconds_0"; + case Paging_Time_Window_type_::second_1: + return "second_1"; + case Paging_Time_Window_type_::seconds_2: + return "seconds_2"; + case Paging_Time_Window_type_::seconds_3: + return "seconds_3"; + case Paging_Time_Window_type_::seconds_4: + return "seconds_4"; + case Paging_Time_Window_type_::seconds_5: + return "seconds_5"; + case Paging_Time_Window_type_::seconds_6: + return "seconds_6"; + case Paging_Time_Window_type_::seconds_7: + return "seconds_7"; + case Paging_Time_Window_type_::seconds_8: + return "seconds_8"; + case Paging_Time_Window_type_::seconds_9: + return "seconds_9"; + case Paging_Time_Window_type_::seconds_10: + return "seconds_10"; + case Paging_Time_Window_type_::seconds_12: + return "seconds_12"; + case Paging_Time_Window_type_::seconds_14: + return "seconds_14"; + case Paging_Time_Window_type_::seconds_16: + return "seconds_16"; + case Paging_Time_Window_type_::seconds_18: + return "seconds_18"; + case Paging_Time_Window_type_::seconds_20: + return "seconds_20"; + default: + return "Invalid Choice"; + } +} +const char* extended_drx_parameters_t::eDRX_value_type_::to_string() const +{ + switch (value) { + case eDRX_value_type_::second_1_28: + return "second_1_28"; + case eDRX_value_type_::second_2_56: + return "second_2_56"; + case eDRX_value_type_::second_3_84: + return "second_3_84"; + case eDRX_value_type_::second_5_12: + return "second_5_12"; + case eDRX_value_type_::second_6_4: + return "second_6_4"; + case eDRX_value_type_::second_7_68: + return "second_7_68"; + case eDRX_value_type_::second_8_96: + return "second_8_96"; + case eDRX_value_type_::second_10_24: + return "second_10_24"; + case eDRX_value_type_::second_11_52: + return "second_11_52"; + case eDRX_value_type_::second_12_8: + return "second_12_8"; + case eDRX_value_type_::second_14_08: + return "second_14_08"; + case eDRX_value_type_::second_15_36: + return "second_15_36"; + case eDRX_value_type_::second_16_64: + return "second_16_64"; + case eDRX_value_type_::second_17_92: + return "second_17_92"; + case eDRX_value_type_::second_19_20: + return "second_19_20"; + case eDRX_value_type_::second_20_48: + return "second_20_48"; + default: + return "Invalid Choice"; + } +} // IE: GPRS timer 3 // Reference: 9.11.2.5 SRSASN_CODE gprs_timer_3_t::pack(asn1::bit_ref& bref) @@ -1661,6 +1892,29 @@ SRSASN_CODE gprs_timer_3_t::unpack(asn1::cbit_ref& bref) return SRSASN_SUCCESS; } +const char* gprs_timer_3_t::Unit_type_::to_string() const +{ + switch (value) { + case Unit_type_::value_is_incremented_in_multiples_of_10_minutes: + return "value is incremented in multiples of 10 minutes"; + case Unit_type_::value_is_incremented_in_multiples_of_1_hour: + return "value is incremented in multiples of 1 hour"; + case Unit_type_::value_is_incremented_in_multiples_of_10_hours: + return "value is incremented in multiples of 10 hours"; + case Unit_type_::value_is_incremented_in_multiples_of_2_seconds: + return "value is incremented in multiples of 2 seconds"; + case Unit_type_::value_is_incremented_in_multiples_of_30_seconds: + return "value is incremented in multiples of 30 seconds"; + case Unit_type_::value_is_incremented_in_multiples_of_1_minute: + return "value is incremented in multiples of 1 minute"; + case Unit_type_::value_is_incremented_in_multiples_of_320_hours: + return "value is incremented in multiples of 320 hours"; + case Unit_type_::value_indicates_that_the_timer_is_deactivated: + return "value indicates that the timer is deactivated"; + default: + return "Invalid Choice"; + } +} // IE: UE radio capability ID // Reference: 9.11.3.68 SRSASN_CODE ue_radio_capability_id_t::pack(asn1::bit_ref& bref) @@ -1856,6 +2110,27 @@ SRSASN_CODE nb_n1_mode_drx_parameters_t::unpack(asn1::cbit_ref& bref) return SRSASN_SUCCESS; } +const char* nb_n1_mode_drx_parameters_t::nb_n1_mode_drx_value_type_::to_string() const +{ + switch (value) { + case nb_n1_mode_drx_value_type_::drx_value_not_specified: + return "DRX value not specified"; + case nb_n1_mode_drx_value_type_::drx_cycle_parameter_t_32: + return "DRX cycle parameter T 32"; + case nb_n1_mode_drx_value_type_::drx_cycle_parameter_t_64: + return "DRX cycle parameter T 64"; + case nb_n1_mode_drx_value_type_::drx_cycle_parameter_t_128: + return "DRX cycle parameter T 128"; + case nb_n1_mode_drx_value_type_::drx_cycle_parameter_t_256: + return "DRX cycle parameter T 256"; + case nb_n1_mode_drx_value_type_::drx_cycle_parameter_t_512: + return "DRX cycle parameter T 512"; + case nb_n1_mode_drx_value_type_::drx_cycle_parameter_t_1024: + return "DRX cycle parameter T 1024"; + default: + return "Invalid Choice"; + } +} // IE: 5GS registration result // Reference: 9.11.3.6 SRSASN_CODE registration_result_5gs_t::pack(asn1::bit_ref& bref) @@ -1902,6 +2177,54 @@ SRSASN_CODE registration_result_5gs_t::unpack(asn1::cbit_ref& bref) return SRSASN_SUCCESS; } +const char* registration_result_5gs_t::Emergency_registered_type_::to_string() const +{ + switch (value) { + case Emergency_registered_type_::not_registered_for_emergency_services: + return "Not registered for emergency services"; + case Emergency_registered_type_::registered_for_emergency_services: + return "Registered for emergency services"; + default: + return "Invalid Choice"; + } +} +const char* registration_result_5gs_t::NSSAA_to_be_performed_type_::to_string() const +{ + switch (value) { + case NSSAA_to_be_performed_type_::nssaa_is_not_to_be_performed: + return "NSSAA is not to be performed"; + case NSSAA_to_be_performed_type_::nssaa_is_to_be_performed: + return "NSSAA is to be performed"; + default: + return "Invalid Choice"; + } +} +const char* registration_result_5gs_t::SMS_allowed_type_::to_string() const +{ + switch (value) { + case SMS_allowed_type_::sms_over_nas_not_allowed: + return "SMS over NAS not allowed"; + case SMS_allowed_type_::sms_over_nas_allowed: + return "SMS over NAS allowed"; + default: + return "Invalid Choice"; + } +} +const char* registration_result_5gs_t::registration_result_type_::to_string() const +{ + switch (value) { + case registration_result_type_::access_3_gpp: + return "access 3GPP"; + case registration_result_type_::non_3_gpp_access: + return "Non-3GPP access"; + case registration_result_type_::access_3_gpp_and_non_3_gpp_access: + return "access 3GPP and non-3GPP access"; + case registration_result_type_::reserved: + return "reserved"; + default: + return "Invalid Choice"; + } +} // IE: PLMN list // Reference: 9.11.3.45 SRSASN_CODE plmn_list_t::pack(asn1::bit_ref& bref) @@ -1970,6 +2293,21 @@ SRSASN_CODE tracking_area_identity_list_5gs_t::unpack(asn1::cbit_ref& bref) return SRSASN_SUCCESS; } +const char* tracking_area_identity_list_5gs_t::type_of_list_type_::to_string() const +{ + switch (value) { + case type_of_list_type_::list_of_ta_cs_belonging_to_one_plmn_or_snpn_with_non_consecutive_tac_values: + return "list of TACs belonging to one PLMN or SNPN, with non-consecutive TAC values"; + case type_of_list_type_::list_of_ta_cs_belonging_to_one_plmn_or_snpn_with_consecutive_tac_values: + return "list of TACs belonging to one PLMN or SNPN, with consecutive TAC values"; + case type_of_list_type_::list_of_ta_is_belonging_to_different_plm_ns: + return "list of TAIs belonging to different PLMNs"; + case type_of_list_type_::reserved: + return "Reserved"; + default: + return "Invalid Choice"; + } +} // IE: Rejected NSSAI // Reference: 9.11.3.46 SRSASN_CODE rejected_nssai_t::pack(asn1::bit_ref& bref) @@ -2432,6 +2770,21 @@ SRSASN_CODE nssai_inclusion_mode_t::unpack(asn1::cbit_ref& bref) return SRSASN_SUCCESS; } +const char* nssai_inclusion_mode_t::NSSAI_inclusion_mode_type_::to_string() const +{ + switch (value) { + case NSSAI_inclusion_mode_type_::nssai_inclusion_mode_a: + return "NSSAI inclusion mode A"; + case NSSAI_inclusion_mode_type_::nssai_inclusion_mode_b: + return "NSSAI inclusion mode B"; + case NSSAI_inclusion_mode_type_::nssai_inclusion_mode_c: + return "NSSAI inclusion mode C"; + case NSSAI_inclusion_mode_type_::nssai_inclusion_mode_d: + return "NSSAI inclusion mode D"; + default: + return "Invalid Choice"; + } +} // IE: Operator-defined access category definitions // Reference: 9.11.3.38 SRSASN_CODE operator_defined_access_category_definitions_t::pack(asn1::bit_ref& bref) @@ -2500,6 +2853,17 @@ SRSASN_CODE ue_radio_capability_id_deletion_indication_t::unpack(asn1::cbit_ref& return SRSASN_SUCCESS; } +const char* ue_radio_capability_id_deletion_indication_t::Deletion_request_type_::to_string() const +{ + switch (value) { + case Deletion_request_type_::ue_radio_capability_id_deletion_not_requested: + return "UE radio capability ID deletion not requested"; + case Deletion_request_type_::network_assigned_ue_radio_capability_i_ds_deletion_requested: + return "Network-assigned UE radio capability IDs deletion requested"; + default: + return "Invalid Choice"; + } +} // IE: Ciphering key data // Reference: 9.11.3.18C SRSASN_CODE ciphering_key_data_t::pack(asn1::bit_ref& bref) @@ -2624,6 +2988,97 @@ SRSASN_CODE cause_5gmm_t::unpack(asn1::cbit_ref& bref) return SRSASN_SUCCESS; } +const char* cause_5gmm_t::cause_5gmm_type_::to_string() const +{ + switch (value) { + case cause_5gmm_type_::illegal_ue: + return "Illegal UE"; + case cause_5gmm_type_::pei_not_accepted: + return "PEI not accepted"; + case cause_5gmm_type_::illegal_me: + return "Illegal ME"; + case cause_5gmm_type_::services_not_allowed_5gs: + return "Services not allowed 5GS"; + case cause_5gmm_type_::ue_identity_cannot_be_derived_by_the_network: + return "UE identity cannot be derived by the network"; + case cause_5gmm_type_::implicitly_de_registered: + return "Implicitly de-registered"; + case cause_5gmm_type_::plmn_not_allowed: + return "PLMN not allowed"; + case cause_5gmm_type_::tracking_area_not_allowed: + return "Tracking area not allowed"; + case cause_5gmm_type_::roaming_not_allowed_in_this_tracking_area: + return "Roaming not allowed in this tracking area"; + case cause_5gmm_type_::no_suitable_cells_in_tracking_area: + return "No suitable cells in tracking area"; + case cause_5gmm_type_::mac_failure: + return "MAC failure"; + case cause_5gmm_type_::synch_failure: + return "Synch failure"; + case cause_5gmm_type_::congestion: + return "Congestion"; + case cause_5gmm_type_::ue_security_capabilities_mismatch: + return "UE security capabilities mismatch"; + case cause_5gmm_type_::security_mode_rejected_unspecified: + return "Security mode rejected, unspecified"; + case cause_5gmm_type_::non_5g_authentication_unacceptable: + return "Non-5G authentication unacceptable"; + case cause_5gmm_type_::n1_mode_not_allowed: + return "N1 mode not allowed"; + case cause_5gmm_type_::restricted_service_area: + return "Restricted service area"; + case cause_5gmm_type_::redirection_to_epc_required: + return "Redirection to EPC required"; + case cause_5gmm_type_::ladn_not_available: + return "LADN not available"; + case cause_5gmm_type_::no_network_slices_available: + return "No network slices available"; + case cause_5gmm_type_::maximum_number_of_pdu_sessions_reached_: + return "Maximum number of PDU sessions reached"; + case cause_5gmm_type_::insufficient_resources_for_specific_slice_and_dnn: + return "Insufficient resources for specific slice and DNN"; + case cause_5gmm_type_::insufficient_resources_for_specific_slice: + return "Insufficient resources for specific slice"; + case cause_5gmm_type_::ng_ksi_already_in_use: + return "ngKSI already in use"; + case cause_5gmm_type_::non_3_gpp_access_to_5gcn_not_allowed: + return "Non-3GPP access to 5GCN not allowed"; + case cause_5gmm_type_::serving_network_not_authorized: + return "Serving network not authorized"; + case cause_5gmm_type_::temporarily_not_authorized_for_this_snpn: + return "Temporarily not authorized for this SNPN"; + case cause_5gmm_type_::permanently_not_authorized_for_this_snpn: + return "Permanently not authorized for this SNPN"; + case cause_5gmm_type_::not_authorized_for_this_cag_or_authorized_for_cag_cells_only: + return "Not authorized for this CAG or authorized for CAG cells only"; + case cause_5gmm_type_::wireline_access_area_not_allowed: + return "Wireline access area not allowed"; + case cause_5gmm_type_::payload_was_not_forwarded: + return "Payload was not forwarded"; + case cause_5gmm_type_::dnn_not_supported_or_not_subscribed_in_the_slice: + return "DNN not supported or not subscribed in the slice"; + case cause_5gmm_type_::insufficient_user_plane_resources_for_the_pdu_session: + return "Insufficient user-plane resources for the PDU session"; + case cause_5gmm_type_::semantically_incorrect_message: + return "Semantically incorrect message"; + case cause_5gmm_type_::invalid_mandatory_information: + return "Invalid mandatory information"; + case cause_5gmm_type_::message_type_non_existent_or_not_implemented: + return "Message type non-existent or not implemented"; + case cause_5gmm_type_::message_type_not_compatible_with_the_protocol_state: + return "Message type not compatible with the protocol state"; + case cause_5gmm_type_::information_element_non_existent_or_not_implemented: + return "Information element non-existent or not implemented"; + case cause_5gmm_type_::conditional_ie_error: + return "Conditional IE error"; + case cause_5gmm_type_::message_not_compatible_with_the_protocol_state: + return "Message not compatible with the protocol state"; + case cause_5gmm_type_::protocol_error_unspecified: + return "Protocol error, unspecified"; + default: + return "Invalid Choice"; + } +} // IE: De-registration type // Reference: 9.11.3.20 SRSASN_CODE de_registration_type_t::pack(asn1::bit_ref& bref) @@ -2644,6 +3099,41 @@ SRSASN_CODE de_registration_type_t::unpack(asn1::cbit_ref& bref) return SRSASN_SUCCESS; } +const char* de_registration_type_t::switch_off_type_::to_string() const +{ + switch (value) { + case switch_off_type_::normal_de_registration: + return "Normal de-registration"; + case switch_off_type_::switch_off: + return "Switch Off"; + default: + return "Invalid Choice"; + } +} +const char* de_registration_type_t::re_registration_required_type_::to_string() const +{ + switch (value) { + case re_registration_required_type_::re_registration_not_required: + return "re-registration not required"; + case re_registration_required_type_::re_registration_required: + return "re-registration required"; + default: + return "Invalid Choice"; + } +} +const char* de_registration_type_t::access_type_type_::to_string() const +{ + switch (value) { + case access_type_type_::access_3_gpp: + return "access 3GPP"; + case access_type_type_::non_3_gpp_access: + return "Non-3GPP access"; + case access_type_type_::access_3_gpp_and_non_3_gpp_access: + return "access 3GPP and non-3GPP access"; + default: + return "Invalid Choice"; + } +} // IE: Spare half octet // Reference: 9.5 SRSASN_CODE spare_half_octet_t::pack(asn1::bit_ref& bref) @@ -2678,6 +3168,37 @@ SRSASN_CODE service_type_t::unpack(asn1::cbit_ref& bref) return SRSASN_SUCCESS; } +const char* service_type_t::Service_type_value_type_::to_string() const +{ + switch (value) { + case Service_type_value_type_::signalling: + return "signalling"; + case Service_type_value_type_::data: + return "data"; + case Service_type_value_type_::mobile_terminated_services: + return "mobile terminated services"; + case Service_type_value_type_::emergency_services: + return "emergency services"; + case Service_type_value_type_::emergency_services_fallback: + return "emergency services fallback"; + case Service_type_value_type_::high_priority_access: + return "high priority access"; + case Service_type_value_type_::elevated_signalling: + return "elevated signalling"; + case Service_type_value_type_::unused_shall_be_interpreted_as_signalling: + return "unused shall be interpreted as signalling"; + case Service_type_value_type_::unused_shall_be_interpreted_as_signalling_1: + return "unused shall be interpreted as signalling_1"; + case Service_type_value_type_::unused_shall_be_interpreted_as_data: + return "unused shall be interpreted as data"; + case Service_type_value_type_::unused_shall_be_interpreted_as_data_1: + return "unused shall be interpreted as data_1"; + case Service_type_value_type_::unused_shall_be_interpreted_as_data_2: + return "unused shall be interpreted as data_2"; + default: + return "Invalid Choice"; + } +} // IE: Configuration update indication // Reference: 9.11.3.18 SRSASN_CODE configuration_update_indication_t::pack(asn1::bit_ref& bref) @@ -2698,6 +3219,21 @@ SRSASN_CODE configuration_update_indication_t::unpack(asn1::cbit_ref& bref) return SRSASN_SUCCESS; } +const char* configuration_update_indication_t::control_plane_service_type_value_type_::to_string() const +{ + switch (value) { + case control_plane_service_type_value_type_::mobile_originating_request: + return "mobile originating request"; + case control_plane_service_type_value_type_::mobile_terminating_request: + return "mobile terminating request"; + case control_plane_service_type_value_type_::emergency_services: + return "emergency services"; + case control_plane_service_type_value_type_::emergency_services_fallback: + return "emergency services fallback"; + default: + return "Invalid Choice"; + } +} // IE: Network name // Reference: 9.11.3.35 SRSASN_CODE network_name_t::pack(asn1::bit_ref& bref) @@ -2806,6 +3342,21 @@ SRSASN_CODE daylight_saving_time_t::unpack(asn1::cbit_ref& bref) return SRSASN_SUCCESS; } +const char* daylight_saving_time_t::value_type_::to_string() const +{ + switch (value) { + case value_type_::no_adjustment_for_daylight_saving_time: + return "No adjustment for Daylight Saving Time"; + case value_type_::hour_1_adjustment_for_daylight_saving_time: + return "hour 1 adjustment for Daylight Saving Time"; + case value_type_::hours_2_adjustment_for_daylight_saving_time: + return "hours 2 adjustment for Daylight Saving Time"; + case value_type_::reserved: + return "Reserved"; + default: + return "Invalid Choice"; + } +} // IE: SMS indication // Reference: 9.11.3.50A SRSASN_CODE sms_indication_t::pack(asn1::bit_ref& bref) @@ -2846,6 +3397,17 @@ SRSASN_CODE additional_configuration_indication_t::unpack(asn1::cbit_ref& bref) return SRSASN_SUCCESS; } +const char* additional_configuration_indication_t::SCMR_type_::to_string() const +{ + switch (value) { + case SCMR_type_::no_additional_information: + return "no additional information"; + case SCMR_type_::release_of_n1_nas_signalling_connection_not_required: + return "release of N1 NAS signalling connection not required"; + default: + return "Invalid Choice"; + } +} // IE: ABBA // Reference: 9.11.3.10 SRSASN_CODE abba_t::pack(asn1::bit_ref& bref) @@ -3031,6 +3593,27 @@ SRSASN_CODE identity_type_5gs_t::unpack(asn1::cbit_ref& bref) return SRSASN_SUCCESS; } +const char* identity_type_5gs_t::identity_types_::to_string() const +{ + switch (value) { + case identity_types_::suci: + return "SUCI"; + case identity_types_::guti_5g: + return "5G-GUTI"; + case identity_types_::imei: + return "IMEI"; + case identity_types_::s_tmsi_5g: + return "5G-S-TMSI"; + case identity_types_::imeisv: + return "IMEISV"; + case identity_types_::mac_address: + return "MAC address"; + case identity_types_::eui_64: + return "EUI-64"; + default: + return "Invalid Choice"; + } +} // IE: security algorithms // Reference: 9.11.3.34 SRSASN_CODE security_algorithms_t::pack(asn1::bit_ref& bref) @@ -3049,6 +3632,52 @@ SRSASN_CODE security_algorithms_t::unpack(asn1::cbit_ref& bref) return SRSASN_SUCCESS; } +const char* security_algorithms_t::integrity_protection_algorithm_type_::to_string() const +{ + switch (value) { + case integrity_protection_algorithm_type_::ia0_5g: + return "IA0-5G"; + case integrity_protection_algorithm_type_::ia1_128_5g: + return "IA1-128-5G"; + case integrity_protection_algorithm_type_::ia2_128_5g: + return "IA2-128-5G"; + case integrity_protection_algorithm_type_::ia3_128_5g: + return "IA3-128-5G"; + case integrity_protection_algorithm_type_::ia4_5g: + return "IA4-5G"; + case integrity_protection_algorithm_type_::ia5_5g: + return "IA5-5G"; + case integrity_protection_algorithm_type_::ia6_5g: + return "IA6-5G"; + case integrity_protection_algorithm_type_::ia7_5g: + return "IA7-5G"; + default: + return "Invalid Choice"; + } +} +const char* security_algorithms_t::ciphering_algorithm_type_::to_string() const +{ + switch (value) { + case ciphering_algorithm_type_::ea0_5g: + return "EA0-5G"; + case ciphering_algorithm_type_::ea1_128_5g: + return "EA1-128-5G"; + case ciphering_algorithm_type_::ea2_128_5g: + return "EA2-128-5G"; + case ciphering_algorithm_type_::ea3_128_5g: + return "EA3-128-5G"; + case ciphering_algorithm_type_::ea4_5g: + return "EA4-5G"; + case ciphering_algorithm_type_::ea5_5g: + return "EA5-5G"; + case ciphering_algorithm_type_::ea6_5g: + return "EA6-5G"; + case ciphering_algorithm_type_::ea7_5g: + return "EA7-5G"; + default: + return "Invalid Choice"; + } +} // IE: IMEISV request // Reference: 9.11.3.28 SRSASN_CODE imeisv_request_t::pack(asn1::bit_ref& bref) @@ -3069,6 +3698,17 @@ SRSASN_CODE imeisv_request_t::unpack(asn1::cbit_ref& bref) return SRSASN_SUCCESS; } +const char* imeisv_request_t::imeisv_request_type_::to_string() const +{ + switch (value) { + case imeisv_request_type_::imeisv_not_requested: + return "IMEISV not requested"; + case imeisv_request_type_::imeisv_requested: + return "IMEISV requested"; + default: + return "Invalid Choice"; + } +} // IE: EPS NAS security algorithms // Reference: 9.11.3.25 SRSASN_CODE eps_nas_security_algorithms_t::pack(asn1::bit_ref& bref) @@ -3096,6 +3736,52 @@ SRSASN_CODE eps_nas_security_algorithms_t::unpack(asn1::cbit_ref& bref) return SRSASN_SUCCESS; } +const char* eps_nas_security_algorithms_t::integrity_protection_algorithm_type_::to_string() const +{ + switch (value) { + case integrity_protection_algorithm_type_::eia0: + return "EIA0"; + case integrity_protection_algorithm_type_::eia1_128: + return "EIA1-128"; + case integrity_protection_algorithm_type_::eia2_128: + return "EIA2-128"; + case integrity_protection_algorithm_type_::eia3_128: + return "EIA3-128"; + case integrity_protection_algorithm_type_::eia4: + return "EIA4"; + case integrity_protection_algorithm_type_::eia5: + return "EIA5"; + case integrity_protection_algorithm_type_::eia6: + return "EIA6"; + case integrity_protection_algorithm_type_::eia7: + return "EIA7"; + default: + return "Invalid Choice"; + } +} +const char* eps_nas_security_algorithms_t::ciphering_algorithm_type_::to_string() const +{ + switch (value) { + case ciphering_algorithm_type_::eea0: + return "EEA0"; + case ciphering_algorithm_type_::eea1_128: + return "EEA1-128"; + case ciphering_algorithm_type_::eea2_128: + return "EEA2-128"; + case ciphering_algorithm_type_::eea3_128: + return "EEA3-128"; + case ciphering_algorithm_type_::eea4: + return "EEA4"; + case ciphering_algorithm_type_::eea5: + return "EEA5"; + case ciphering_algorithm_type_::eea6: + return "EEA6"; + case ciphering_algorithm_type_::eea7: + return "EEA7"; + default: + return "Invalid Choice"; + } +} // IE: Additional 5G security information // Reference: 9.11.3.12 SRSASN_CODE additional_5g_security_information_t::pack(asn1::bit_ref& bref) @@ -3296,6 +3982,17 @@ SRSASN_CODE access_type_t::unpack(asn1::cbit_ref& bref) return SRSASN_SUCCESS; } +const char* access_type_t::Access_type_value_type_::to_string() const +{ + switch (value) { + case Access_type_value_type_::access_3_gpp: + return "access_3GPP"; + case Access_type_value_type_::non_3_gpp_access: + return "Non_3GPP_access"; + default: + return "Invalid Choice"; + } +} // IE: PDU session identity 2 // Reference: 9.11.3.41 SRSASN_CODE pdu_session_identity_2_t::pack(asn1::bit_ref& bref) @@ -3332,6 +4029,27 @@ SRSASN_CODE request_type_t::unpack(asn1::cbit_ref& bref) return SRSASN_SUCCESS; } +const char* request_type_t::Request_type_value_type_::to_string() const +{ + switch (value) { + case Request_type_value_type_::initial_request: + return "initial request"; + case Request_type_value_type_::existing_pdu_session: + return "existing PDU session"; + case Request_type_value_type_::initial_emergency_request: + return "initial emergency request"; + case Request_type_value_type_::existing_emergency_pdu_session: + return "existing emergency PDU session"; + case Request_type_value_type_::modification_request: + return "modification request"; + case Request_type_value_type_::ma_pdu_request: + return "MA PDU request"; + case Request_type_value_type_::reserved: + return "reserved"; + default: + return "Invalid Choice"; + } +} // IE: S-NSSAI // Reference: 9.11.2.8 SRSASN_CODE s_nssai_t::pack(asn1::bit_ref& bref) @@ -3403,6 +4121,23 @@ SRSASN_CODE s_nssai_t::unpack(asn1::cbit_ref& bref) return SRSASN_SUCCESS; } +const char* s_nssai_t::SST_type_::to_string() const +{ + switch (value) { + case SST_type_::sst: + return "SST"; + case SST_type_::sst_and_mapped_hplmn_sst: + return "SST and mapped HPLMN SST"; + case SST_type_::sst_and_sd: + return "SST and SD"; + case SST_type_::sst_sd_and_mapped_hplmn_sst: + return "SST, SD and mapped HPLMN SST"; + case SST_type_::sst_sd_mapped_hplmn_sst_and_mapped_hplmn_sd: + return "SST, SD, mapped HPLMN SST and mapped HPLMN SD"; + default: + return "Invalid Choice"; + } +} // IE: DNN // Reference: 9.11.2.1B SRSASN_CODE dnn_t::pack(asn1::bit_ref& bref) @@ -3490,6 +4225,15 @@ SRSASN_CODE ma_pdu_session_information_t::unpack(asn1::cbit_ref& bref) return SRSASN_SUCCESS; } +const char* ma_pdu_session_information_t::MA_PDU_session_information_value_type_::to_string() const +{ + switch (value) { + case MA_PDU_session_information_value_type_::ma_pdu_session_network_upgrade_is_allowed: + return "MA PDU session network upgrade is allowed"; + default: + return "Invalid Choice"; + } +} // IE: Release assistance indication // Reference: 9.11.3.46A SRSASN_CODE release_assistance_indication_t::pack(asn1::bit_ref& bref) @@ -3510,6 +4254,21 @@ SRSASN_CODE release_assistance_indication_t::unpack(asn1::cbit_ref& bref) return SRSASN_SUCCESS; } +const char* release_assistance_indication_t::Downlink_data_expected_type_::to_string() const +{ + switch (value) { + case Downlink_data_expected_type_::no_information_regarding_ddx_is_conveyed: + return "No information regarding DDX is conveyed"; + case Downlink_data_expected_type_::no_further_uplink_and_no_further_downlink_data: + return "No further uplink and no further downlink data"; + case Downlink_data_expected_type_::only_a_single_downlink_data_transmission: + return "Only a single downlink data transmission"; + case Downlink_data_expected_type_::reserved: + return "reserved"; + default: + return "Invalid Choice"; + } +} // IE: Integrity protection maximum data rate // Reference: 9.11.4.7 SRSASN_CODE integrity_protection_maximum_data_rate_t::pack(asn1::bit_ref& bref) @@ -3528,6 +4287,32 @@ SRSASN_CODE integrity_protection_maximum_data_rate_t::unpack(asn1::cbit_ref& bre return SRSASN_SUCCESS; } +const char* integrity_protection_maximum_data_rate_t::max_data_rate_UPIP_uplink_type_::to_string() const +{ + switch (value) { + case max_data_rate_UPIP_uplink_type_::kbps_64: + return "kbps 64"; + case max_data_rate_UPIP_uplink_type_::null: + return "NULL"; + case max_data_rate_UPIP_uplink_type_::full_data_rate: + return "Full data rate"; + default: + return "Invalid Choice"; + } +} +const char* integrity_protection_maximum_data_rate_t::max_data_rate_UPIP_downlink_type_::to_string() const +{ + switch (value) { + case max_data_rate_UPIP_downlink_type_::kbps_64: + return "kbps 64"; + case max_data_rate_UPIP_downlink_type_::null: + return "NULL"; + case max_data_rate_UPIP_downlink_type_::full_data_rate: + return "Full data rate"; + default: + return "Invalid Choice"; + } +} // IE: PDU session type // Reference: 9.11.4.11 SRSASN_CODE pdu_session_type_t::pack(asn1::bit_ref& bref) @@ -3548,6 +4333,25 @@ SRSASN_CODE pdu_session_type_t::unpack(asn1::cbit_ref& bref) return SRSASN_SUCCESS; } +const char* pdu_session_type_t::PDU_session_type_value_type_::to_string() const +{ + switch (value) { + case PDU_session_type_value_type_::ipv4: + return "ipv4"; + case PDU_session_type_value_type_::ipv6: + return "ipv6"; + case PDU_session_type_value_type_::ipv4v6: + return "ipv4v6"; + case PDU_session_type_value_type_::unstructured: + return "Unstructured"; + case PDU_session_type_value_type_::ethernet: + return "Ethernet"; + case PDU_session_type_value_type_::reserved: + return "reserved"; + default: + return "Invalid Choice"; + } +} // IE: SSC mode // Reference: 9.11.4.16 SRSASN_CODE ssc_mode_t::pack(asn1::bit_ref& bref) @@ -3568,6 +4372,27 @@ SRSASN_CODE ssc_mode_t::unpack(asn1::cbit_ref& bref) return SRSASN_SUCCESS; } +const char* ssc_mode_t::SSC_mode_value_type_::to_string() const +{ + switch (value) { + case SSC_mode_value_type_::ssc_mode_1: + return "SSC mode 1"; + case SSC_mode_value_type_::ssc_mode_2: + return "SSC mode 2"; + case SSC_mode_value_type_::ssc_mode_3: + return "SSC mode 3"; + case SSC_mode_value_type_::unused_or_ssc_mode_1: + return "unused or SSC mode 1"; + case SSC_mode_value_type_::unused_or_ssc_mode_2: + return "unused or SSC mode 2"; + case SSC_mode_value_type_::unused_or_ssc_mode_3: + return "unused or SSC mode 3"; + case SSC_mode_value_type_::reserved: + return "reserved"; + default: + return "Invalid Choice"; + } +} // IE: 5GSM capability // Reference: 9.11.4.1 SRSASN_CODE capability_5gsm_t::pack(asn1::bit_ref& bref) @@ -3930,6 +4755,19 @@ SRSASN_CODE ethernet_header_compression_configuration_t::unpack(asn1::cbit_ref& return SRSASN_SUCCESS; } +const char* ethernet_header_compression_configuration_t::CID_Length_type_::to_string() const +{ + switch (value) { + case CID_Length_type_::ethernet_header_compression_not_used: + return "Ethernet header compression not used"; + case CID_Length_type_::bits_7: + return "bits_7"; + case CID_Length_type_::bits_15: + return "bits_15"; + default: + return "Invalid Choice"; + } +} // IE: PDU address // Reference: 9.11.4.10 SRSASN_CODE pdu_address_t::pack(asn1::bit_ref& bref) @@ -4008,6 +4846,19 @@ SRSASN_CODE pdu_address_t::unpack(asn1::cbit_ref& bref) return SRSASN_SUCCESS; } +const char* pdu_address_t::PDU_session_type_value_type_::to_string() const +{ + switch (value) { + case PDU_session_type_value_type_::ipv4: + return "ipv4"; + case PDU_session_type_value_type_::ipv6: + return "ipv6"; + case PDU_session_type_value_type_::ipv4v6: + return "ipv4v6"; + default: + return "Invalid Choice"; + } +} // IE: QoS rules // Reference: 9.11.4.13 SRSASN_CODE qo_s_rules_t::pack(asn1::bit_ref& bref) @@ -4084,6 +4935,65 @@ SRSASN_CODE session_ambr_t::unpack(asn1::cbit_ref& bref) return SRSASN_SUCCESS; } +const char* session_ambr_t::unit_session_AMBR_type_::to_string() const +{ + switch (value) { + case unit_session_AMBR_type_::not_used: + return "not used"; + case unit_session_AMBR_type_::inc_by_1_kbps: + return "inc by 1 Kbps"; + case unit_session_AMBR_type_::inc_by_4_kbps: + return "inc by 4 Kbps"; + case unit_session_AMBR_type_::inc_by_16_kbps: + return "inc by 16 Kbps"; + case unit_session_AMBR_type_::inc_by_64_kbps: + return "inc by 64 Kbps"; + case unit_session_AMBR_type_::inc_by_256_kbps: + return "inc by 256 kbps"; + case unit_session_AMBR_type_::inc_by_1_mbps: + return "inc by 1 Mbps"; + case unit_session_AMBR_type_::inc_by_4_mbps: + return "inc by 4 Mbps"; + case unit_session_AMBR_type_::inc_by_16_mbps: + return "inc by 16 Mbps"; + case unit_session_AMBR_type_::inc_by_64_mbps: + return "inc by 64 Mbps"; + case unit_session_AMBR_type_::inc_by_256_mbps: + return "inc by 256 Mbps"; + case unit_session_AMBR_type_::inc_by_1_gbps: + return "inc by 1 Gbps"; + case unit_session_AMBR_type_::inc_by_4_gbps: + return "inc by 4 Gbps"; + case unit_session_AMBR_type_::inc_by_16_gbps: + return "inc by 16 Gbps"; + case unit_session_AMBR_type_::inc_by_64_gbps: + return "inc by 64 Gbps"; + case unit_session_AMBR_type_::inc_by_256_gbps: + return "inc by 256 Gbps"; + case unit_session_AMBR_type_::inc_by_1_tbps: + return "inc by 1 Tbps"; + case unit_session_AMBR_type_::inc_by_4_tbps: + return "inc by 4 Tbps"; + case unit_session_AMBR_type_::inc_by_16_tbps: + return "inc by 16 Tbps"; + case unit_session_AMBR_type_::inc_by_64_tbps: + return "inc by 64 Tbps"; + case unit_session_AMBR_type_::inc_by_256_tbps: + return "inc by 256 Tbps"; + case unit_session_AMBR_type_::inc_by_1_pbps: + return "inc by 1 Pbps"; + case unit_session_AMBR_type_::inc_by_4_pbps: + return "inc by 4 Pbps"; + case unit_session_AMBR_type_::inc_by_16_pbps: + return "inc by 16 Pbps"; + case unit_session_AMBR_type_::inc_by_64_pbps: + return "inc by 64 Pbps"; + case unit_session_AMBR_type_::inc_by_256_pbps: + return "inc by 256 Pbps"; + default: + return "Invalid Choice"; + } +} // IE: 5GSM cause // Reference: 9.11.4.2 SRSASN_CODE cause_5gsm_t::pack(asn1::bit_ref& bref) @@ -4193,7 +5103,6 @@ const char* cause_5gsm_t::cause_value_type_::to_string() const return "Invalid Choice"; } } - // IE: GPRS timer // Reference: 9.11.2.3 SRSASN_CODE gprs_timer_t::pack(asn1::bit_ref& bref) @@ -4212,6 +5121,21 @@ SRSASN_CODE gprs_timer_t::unpack(asn1::cbit_ref& bref) return SRSASN_SUCCESS; } +const char* gprs_timer_t::Unit_type_::to_string() const +{ + switch (value) { + case Unit_type_::value_is_incremented_in_multiples_of_2_seconds: + return "value is incremented in multiples of 2 seconds"; + case Unit_type_::value_is_incremented_in_multiples_of_1_minute: + return "value is incremented in multiples of 1 minute"; + case Unit_type_::value_is_incremented_in_multiples_of_decihours: + return "value is incremented in multiples of decihours"; + case Unit_type_::value_indicates_that_the_timer_is_deactivated: + return "value indicates that the timer is deactivated"; + default: + return "Invalid Choice"; + } +} // IE: Always-on PDU session indication // Reference: 9.11.4.3 SRSASN_CODE always_on_pdu_session_indication_t::pack(asn1::bit_ref& bref) @@ -4359,6 +5283,17 @@ SRSASN_CODE network_feature_support_5gsm_t::unpack(asn1::cbit_ref& bref) return SRSASN_SUCCESS; } +const char* network_feature_support_5gsm_t::EPT_S1_type_::to_string() const +{ + switch (value) { + case EPT_S1_type_::ethernet_pdn_type_in_s1_mode_not_supported: + return "Ethernet PDN type in S1 mode not supported"; + case EPT_S1_type_::ethernet_pdn_type_in_s1_mode_supported: + return "Ethernet PDN type in S1 mode supported"; + default: + return "Invalid Choice"; + } +} // IE: Serving PLMN rate control // Reference: 9.11.4.20 SRSASN_CODE serving_plmn_rate_control_t::pack(asn1::bit_ref& bref) @@ -4490,6 +5425,17 @@ SRSASN_CODE congestion_re_attempt_indicator_5gsm_t::unpack(asn1::cbit_ref& bref) return SRSASN_SUCCESS; } +const char* congestion_re_attempt_indicator_5gsm_t::abo_type_::to_string() const +{ + switch (value) { + case abo_type_::the_back_off_timer_is_applied_in_the_registered_plmn: + return "The back-off timer is applied in the registered PLMN"; + case abo_type_::the_back_off_timer_is_applied_in_all_plm_ns: + return "The back-off timer is applied in all PLMNs"; + default: + return "Invalid Choice"; + } +} // IE: Re-attempt indicator // Reference: 9.11.4.17 SRSASN_CODE re_attempt_indicator_t::pack(asn1::bit_ref& bref) diff --git a/lib/src/asn1/rrc_nr_utils.cc b/lib/src/asn1/rrc_nr_utils.cc index 20b601971..4645d2572 100644 --- a/lib/src/asn1/rrc_nr_utils.cc +++ b/lib/src/asn1/rrc_nr_utils.cc @@ -512,6 +512,21 @@ bool make_phy_harq_ack_cfg(const phys_cell_group_cfg_s& phys_cell_group_cfg, return true; } +void make_phy_search_space0_cfg(srsran_search_space_t* in_srsran_search_space) +{ + in_srsran_search_space->id = 0; + in_srsran_search_space->coreset_id = 0; + in_srsran_search_space->type = srsran_search_space_type_common_0; + in_srsran_search_space->nof_candidates[0] = 0; + in_srsran_search_space->nof_candidates[1] = 0; + in_srsran_search_space->nof_candidates[2] = 4; + in_srsran_search_space->nof_candidates[3] = 2; + in_srsran_search_space->nof_candidates[4] = 0; + in_srsran_search_space->nof_formats = 1; + in_srsran_search_space->formats[0] = srsran_dci_format_nr_1_0; + in_srsran_search_space->duration = 1; +} + bool make_phy_search_space_cfg(const search_space_s& search_space, srsran_search_space_t* in_srsran_search_space) { srsran_search_space_t srsran_search_space = {}; diff --git a/lib/src/common/CMakeLists.txt b/lib/src/common/CMakeLists.txt index c951c536c..b09edbd48 100644 --- a/lib/src/common/CMakeLists.txt +++ b/lib/src/common/CMakeLists.txt @@ -63,6 +63,6 @@ target_include_directories(srsran_common PUBLIC ${SEC_INCLUDE_DIRS} ${CMAKE_SOUR target_link_libraries(srsran_common srsran_phy support srslog ${SEC_LIBRARIES} ${BACKWARD_LIBRARIES} ${SCTP_LIBRARIES}) target_compile_definitions(srsran_common PRIVATE ${BACKWARD_DEFINITIONS}) -INSTALL(TARGETS srsran_common DESTINATION ${LIBRARY_DIR}) +install(TARGETS srsran_common DESTINATION ${LIBRARY_DIR} OPTIONAL) add_subdirectory(test) diff --git a/lib/src/common/bearer_manager.cc b/lib/src/common/bearer_manager.cc index 5ee4d1d21..ccbfcf9bd 100644 --- a/lib/src/common/bearer_manager.cc +++ b/lib/src/common/bearer_manager.cc @@ -61,18 +61,28 @@ bool ue_bearer_manager_impl::has_active_radio_bearer(uint32_t eps_bearer_id) return bearers.count(eps_bearer_id) > 0; } -ue_bearer_manager_impl::radio_bearer_t ue_bearer_manager_impl::get_radio_bearer(uint32_t eps_bearer_id) +ue_bearer_manager_impl::radio_bearer_t ue_bearer_manager_impl::get_radio_bearer(uint32_t eps_bearer_id) const { auto it = bearers.find(eps_bearer_id); return it != bearers.end() ? it->second : invalid_rb; } -ue_bearer_manager_impl::radio_bearer_t ue_bearer_manager_impl::get_eps_bearer_id_for_lcid(uint32_t lcid) +ue_bearer_manager_impl::radio_bearer_t ue_bearer_manager_impl::get_eps_bearer_id_for_lcid(uint32_t lcid) const { auto lcid_it = lcid_to_eps_bearer_id.find(lcid); return lcid_it != lcid_to_eps_bearer_id.end() ? bearers.at(lcid_it->second) : invalid_rb; } +bool ue_bearer_manager_impl::set_five_qi(uint32_t eps_bearer_id, uint16_t five_qi) +{ + auto it = bearers.find(eps_bearer_id); + if (it == bearers.end()) { + return false; + } + it->second.five_qi = five_qi; + return true; +} + } // namespace detail } // namespace srsran @@ -185,7 +195,7 @@ bool enb_bearer_manager::has_active_radio_bearer(uint16_t rnti, uint32_t eps_bea return user_it->second.has_active_radio_bearer(eps_bearer_id); } -enb_bearer_manager::radio_bearer_t enb_bearer_manager::get_lcid_bearer(uint16_t rnti, uint32_t lcid) +enb_bearer_manager::radio_bearer_t enb_bearer_manager::get_lcid_bearer(uint16_t rnti, uint32_t lcid) const { auto user_it = users_map.find(rnti); if (user_it == users_map.end()) { @@ -203,4 +213,13 @@ enb_bearer_manager::radio_bearer_t enb_bearer_manager::get_radio_bearer(uint16_t return user_it->second.get_radio_bearer(eps_bearer_id); } -} // namespace srsenb \ No newline at end of file +bool enb_bearer_manager::set_five_qi(uint16_t rnti, uint32_t eps_bearer_id, uint16_t five_qi) +{ + auto user_it = users_map.find(rnti); + if (user_it == users_map.end()) { + return false; + } + return user_it->second.set_five_qi(eps_bearer_id, five_qi); +} + +} // namespace srsenb diff --git a/lib/src/common/phy_cfg_nr.cc b/lib/src/common/phy_cfg_nr.cc index aa3195d62..4de89b31e 100644 --- a/lib/src/common/phy_cfg_nr.cc +++ b/lib/src/common/phy_cfg_nr.cc @@ -330,7 +330,7 @@ bool phy_cfg_nr_t::get_pucch_uci_cfg(const srsran_slot_cfg_t& slot_cfg, srsran_pucch_nr_resource_t& resource) const { // Select PUCCH resource - if (srsran_ra_ul_nr_pucch_resource(&pucch, &uci_cfg, &resource) < SRSRAN_SUCCESS) { + if (srsran_ra_ul_nr_pucch_resource(&pucch, &uci_cfg, carrier.nof_prb, &resource) < SRSRAN_SUCCESS) { ERROR("Selecting PUCCH resource"); return false; } diff --git a/lib/src/gtpu/CMakeLists.txt b/lib/src/gtpu/CMakeLists.txt index 859e63898..57fb87f1f 100644 --- a/lib/src/gtpu/CMakeLists.txt +++ b/lib/src/gtpu/CMakeLists.txt @@ -22,4 +22,4 @@ set(SOURCES gtpu.cc) add_library(srsran_gtpu STATIC ${SOURCES}) target_link_libraries(srsran_gtpu srsran_common srsran_asn1 ${ATOMIC_LIBS}) -INSTALL(TARGETS srsran_gtpu DESTINATION ${LIBRARY_DIR}) +install(TARGETS srsran_gtpu DESTINATION ${LIBRARY_DIR} OPTIONAL) diff --git a/lib/src/mac/CMakeLists.txt b/lib/src/mac/CMakeLists.txt index c16807597..1c4e42722 100644 --- a/lib/src/mac/CMakeLists.txt +++ b/lib/src/mac/CMakeLists.txt @@ -22,6 +22,6 @@ SET(SOURCES pdu.cc pdu_queue.cc mac_sch_pdu_nr.cc mac_rar_pdu_nr.cc) add_library(srsran_mac STATIC ${SOURCES}) target_link_libraries(srsran_mac srsran_common) -INSTALL(TARGETS srsran_mac DESTINATION ${LIBRARY_DIR}) +install(TARGETS srsran_mac DESTINATION ${LIBRARY_DIR} OPTIONAL) add_subdirectory(test) \ No newline at end of file diff --git a/lib/src/pdcp/CMakeLists.txt b/lib/src/pdcp/CMakeLists.txt index 46a40ae2e..73a5c83e6 100644 --- a/lib/src/pdcp/CMakeLists.txt +++ b/lib/src/pdcp/CMakeLists.txt @@ -25,4 +25,4 @@ set(SOURCES pdcp.cc add_library(srsran_pdcp STATIC ${SOURCES}) target_link_libraries(srsran_pdcp srsran_common srsran_asn1 ${ATOMIC_LIBS}) -INSTALL(TARGETS srsran_pdcp DESTINATION ${LIBRARY_DIR}) +install(TARGETS srsran_pdcp DESTINATION ${LIBRARY_DIR} OPTIONAL) diff --git a/lib/src/pdcp/pdcp_entity_lte.cc b/lib/src/pdcp/pdcp_entity_lte.cc index 16e1183e2..f7690f1fe 100644 --- a/lib/src/pdcp/pdcp_entity_lte.cc +++ b/lib/src/pdcp/pdcp_entity_lte.cc @@ -100,7 +100,7 @@ bool pdcp_entity_lte::configure(const pdcp_config_t& cnfg_) logger.info("Status Report Required: %s", cfg.status_report_required ? "True" : "False"); if (is_drb() and not rlc->rb_is_um(lcid)) { - undelivered_sdus = std::unique_ptr(new undelivered_sdus_queue(task_sched)); + undelivered_sdus = std::unique_ptr(new undelivered_sdus_queue(task_sched, maximum_pdcp_sn)); rx_counts_info.reserve(reordering_window); } @@ -230,6 +230,10 @@ void pdcp_entity_lte::write_sdu(unique_byte_buffer_t sdu, int upper_sn) // Pass PDU to lower layers metrics.num_tx_pdus++; metrics.num_tx_pdu_bytes += sdu->N_bytes; + // Count TX'd bytes as if they were ACK'd if RLC is UM + if (rlc->rb_is_um(lcid)) { + metrics.num_tx_acked_bytes = metrics.num_tx_pdu_bytes; + } rlc->write_sdu(lcid, std::move(sdu)); } @@ -867,7 +871,7 @@ void pdcp_entity_lte::reset_metrics() /**************************************************************************** * Undelivered SDUs queue helpers ***************************************************************************/ -undelivered_sdus_queue::undelivered_sdus_queue(srsran::task_sched_handle task_sched) +undelivered_sdus_queue::undelivered_sdus_queue(srsran::task_sched_handle task_sched, uint32_t sn_mod) : sn_mod(sn_mod) { for (auto& e : sdus) { e.discard_timer = task_sched.get_unique_timer(); diff --git a/lib/src/phy/CMakeLists.txt b/lib/src/phy/CMakeLists.txt index 247fe2943..aaec436dc 100644 --- a/lib/src/phy/CMakeLists.txt +++ b/lib/src/phy/CMakeLists.txt @@ -59,4 +59,4 @@ set(srsran_srcs $ add_library(srsran_phy STATIC ${srsran_srcs} ) target_link_libraries(srsran_phy pthread m ${FFT_LIBRARIES}) -INSTALL(TARGETS srsran_phy DESTINATION ${LIBRARY_DIR}) +install(TARGETS srsran_phy DESTINATION ${LIBRARY_DIR} OPTIONAL) diff --git a/lib/src/phy/cfr/cfr.c b/lib/src/phy/cfr/cfr.c index 0f9f8e1a8..c7927b23c 100644 --- a/lib/src/phy/cfr/cfr.c +++ b/lib/src/phy/cfr/cfr.c @@ -162,6 +162,10 @@ int srsran_cfr_init(srsran_cfr_t* q, srsran_cfr_cfg_t* cfg) ERROR("Error, invalid configuration"); goto clean_exit; } + if (cfg->cfr_mode == SRSRAN_CFR_THR_INVALID) { + ERROR("Error, invalid CFR mode"); + goto clean_exit; + } if (cfg->cfr_mode == SRSRAN_CFR_THR_MANUAL && cfg->manual_thr <= 0) { ERROR("Error, invalid configuration for manual threshold"); goto clean_exit; @@ -330,6 +334,9 @@ bool srsran_cfr_params_valid(srsran_cfr_cfg_t* cfr_conf) if (cfr_conf == NULL) { return false; } + if (cfr_conf->cfr_mode == SRSRAN_CFR_THR_INVALID) { + return false; + } if (cfr_conf->alpha < 0 || cfr_conf->alpha > 1) { return false; } @@ -374,3 +381,22 @@ int srsran_cfr_set_papr(srsran_cfr_t* q, float papr) q->max_papr_lin = srsran_convert_dB_to_power(q->cfg.max_papr_db); return SRSRAN_SUCCESS; } + +srsran_cfr_mode_t srsran_cfr_str2mode(const char* mode_str) +{ + srsran_cfr_mode_t ret; + if (strcmp(mode_str, "")) { + if (!strcmp(mode_str, "manual")) { + ret = SRSRAN_CFR_THR_MANUAL; + } else if (!strcmp(mode_str, "auto_cma")) { + ret = SRSRAN_CFR_THR_AUTO_CMA; + } else if (!strcmp(mode_str, "auto_ema")) { + ret = SRSRAN_CFR_THR_AUTO_EMA; + } else { + ret = SRSRAN_CFR_THR_INVALID; // mode_str is not recognised + } + } else { + ret = SRSRAN_CFR_THR_INVALID; // mode_str is empty + } + return ret; +} diff --git a/lib/src/phy/cfr/test/cfr_test.c b/lib/src/phy/cfr/test/cfr_test.c index bb9c2516f..72f5045b4 100644 --- a/lib/src/phy/cfr/test/cfr_test.c +++ b/lib/src/phy/cfr/test/cfr_test.c @@ -30,13 +30,9 @@ #define MAX_ACPR_DB -100 -// CFR type test args -static char cfr_manual_str[] = "manual"; -static char cfr_auto_cma_str[] = "auto_cma"; -static char cfr_auto_ema_str[] = "auto_ema"; // Default CFR type -static char* cfr_type_arg = cfr_manual_str; +static char* cfr_mode_str = "manual"; static int nof_prb = -1; static srsran_cp_t cp = SRSRAN_CP_NORM; @@ -69,7 +65,7 @@ static void usage(char* prog) printf("\t-e extended cyclic prefix [Default Normal]\n"); printf("\t-f Number of frames [Default %d]\n", nof_frames); printf("\t-r Number of repetitions [Default %d]\n", nof_repetitions); - printf("\t-m CFR mode: %s, %s, %s [Default %s]\n", cfr_manual_str, cfr_auto_cma_str, cfr_auto_ema_str, cfr_type_arg); + printf("\t-m CFR mode: manual, auto_cma, auto_ema [Default %s]\n", cfr_mode_str); printf("\t-d Use DC subcarrier: [Default DC empty]\n"); printf("\t-a CFR alpha: [Default %.2f]\n", alpha); printf("\t-t CFR manual threshold: [Default %.2f]\n", thr_manual); @@ -98,7 +94,7 @@ static int parse_args(int argc, char** argv) nof_frames = (int)strtol(argv[optind], NULL, 10); break; case 'm': - cfr_type_arg = argv[optind]; + cfr_mode_str = argv[optind]; break; case 'a': alpha = strtof(argv[optind], NULL); @@ -149,13 +145,8 @@ int main(int argc, char** argv) goto clean_exit; } - if (!strcmp(cfr_type_arg, cfr_manual_str)) { - cfr_mode = SRSRAN_CFR_THR_MANUAL; - } else if (!strcmp(cfr_type_arg, cfr_auto_cma_str)) { - cfr_mode = SRSRAN_CFR_THR_AUTO_CMA; - } else if (!strcmp(cfr_type_arg, cfr_auto_ema_str)) { - cfr_mode = SRSRAN_CFR_THR_AUTO_EMA; - } else { + cfr_mode = srsran_cfr_str2mode(cfr_mode_str); + if (cfr_mode == SRSRAN_CFR_THR_INVALID) { ERROR("CFR mode is not recognised"); goto clean_exit; } @@ -202,6 +193,11 @@ int main(int argc, char** argv) cfr_tx_cfg.ema_alpha = ema_alpha; cfr_tx_cfg.dc_sc = dc_empty; + if (!srsran_cfr_params_valid(&cfr_tx_cfg)) { + ERROR("Invalid CFR configuration"); + goto clean_exit; + } + if (srsran_cfr_init(&cfr, &cfr_tx_cfg)) { ERROR("Error initializing CFR"); goto clean_exit; diff --git a/lib/src/phy/ch_estimation/chest_ul.c b/lib/src/phy/ch_estimation/chest_ul.c index 819aabe1c..5005ac613 100644 --- a/lib/src/phy/ch_estimation/chest_ul.c +++ b/lib/src/phy/ch_estimation/chest_ul.c @@ -355,16 +355,30 @@ static void chest_ul_estimate(srsran_chest_ul_t* q, } } - // Estimate received pilot power + // Measure reference signal RE average power + cf_t corr = srsran_vec_acc_cc(q->pilot_recv_signal, nslots * nrefs_sym) / (nslots * nrefs_sym); + float rsrp_avg = __real__ corr * __real__ corr + __imag__ corr * __imag__ corr; + + // Measure EPRE + float epre = srsran_vec_avg_power_cf(q->pilot_recv_signal, nslots * nrefs_sym); + + // RSRP shall not be greater than EPRE + rsrp_avg = SRSRAN_MIN(rsrp_avg, epre); + + // Calculate SNR if (isnormal(res->noise_estimate)) { - res->snr = srsran_vec_avg_power_cf(q->pilot_recv_signal, nslots * nrefs_sym) / res->noise_estimate; + res->snr = epre / res->noise_estimate; } else { res->snr = NAN; } - // Convert measurements in logarithm scale - res->snr_db = srsran_convert_power_to_dB(res->snr); - res->noise_estimate_dbm = srsran_convert_power_to_dBm(res->noise_estimate); + // Set EPRE and RSRP + res->epre = epre; + res->epre_dBfs = srsran_convert_power_to_dB(res->epre); + res->rsrp = rsrp_avg; + res->rsrp_dBfs = srsran_convert_power_to_dB(res->rsrp); + res->snr_db = srsran_convert_power_to_dB(res->snr); + res->noise_estimate_dbFs = srsran_convert_power_to_dBm(res->noise_estimate); } int srsran_chest_ul_estimate_pusch(srsran_chest_ul_t* q, @@ -479,15 +493,12 @@ int srsran_chest_ul_estimate_pucch(srsran_chest_ul_t* q, srsran_vec_prod_conj_ccc(q->pilot_recv_signal, q->pilot_known_signal, q->pilot_estimates, nrefs_sf); } - // Measure power - float rsrp_avg = 0.0f; - for (int ns = 0; ns < SRSRAN_NOF_SLOTS_PER_SF; ns++) { - for (int i = 0; i < n_rs; i++) { - cf_t corr = srsran_vec_acc_cc(q->pilot_estimates, SRSRAN_NOF_SLOTS_PER_SF * SRSRAN_NRE * n_rs) / (SRSRAN_NRE); - rsrp_avg += __real__ corr * __real__ corr + __imag__ corr * __imag__ corr; - } - } - rsrp_avg /= SRSRAN_NOF_SLOTS_PER_SF * n_rs; + // Measure reference signal RE average power + cf_t corr = srsran_vec_acc_cc(q->pilot_estimates, SRSRAN_NOF_SLOTS_PER_SF * SRSRAN_NRE * n_rs) / + (SRSRAN_NOF_SLOTS_PER_SF * SRSRAN_NRE * n_rs); + float rsrp_avg = __real__ corr * __real__ corr + __imag__ corr * __imag__ corr; + + // Measure EPRE float epre = srsran_vec_avg_power_cf(q->pilot_estimates, SRSRAN_NOF_SLOTS_PER_SF * SRSRAN_NRE * n_rs); // RSRP shall not be greater than EPRE @@ -562,11 +573,11 @@ int srsran_chest_ul_estimate_pucch(srsran_chest_ul_t* q, if (fpclassify(res->noise_estimate) == FP_ZERO) { res->noise_estimate = FLT_MIN; } - res->noise_estimate_dbm = srsran_convert_power_to_dBm(res->noise_estimate); + res->noise_estimate_dbFs = srsran_convert_power_to_dBm(res->noise_estimate); // Estimate SINR if (isnormal(res->noise_estimate)) { - res->snr = res->rsrp / res->noise_estimate; + res->snr = res->epre / res->noise_estimate; res->snr_db = srsran_convert_power_to_dB(res->snr); } else { res->snr = NAN; diff --git a/lib/src/phy/ch_estimation/dmrs_pucch.c b/lib/src/phy/ch_estimation/dmrs_pucch.c index bb0047b10..96226ddb2 100644 --- a/lib/src/phy/ch_estimation/dmrs_pucch.c +++ b/lib/src/phy/ch_estimation/dmrs_pucch.c @@ -120,43 +120,58 @@ int srsran_dmrs_pucch_format1_put(const srsran_pucch_nr_t* q, return SRSRAN_ERROR; } - uint32_t n_pucch = dmrs_pucch_format1_n_pucch(resource, 0); - if (n_pucch == 0) { - ERROR("Error getting number of symbols"); - return SRSRAN_ERROR; - } - + // First symbol index uint32_t l_prime = resource->start_symbol_idx; - for (uint32_t m = 0; m < n_pucch; m++) { - // Clause 6.4.1.3.1.2 specifies l=0,2,4... - uint32_t l = m * 2; - - // Get start of the sequence in resource grid - cf_t* slot_symbols_ptr = &slot_symbols[(q->carrier.nof_prb * (l + l_prime) + resource->starting_prb) * SRSRAN_NRE]; - - // Get Alpha index - uint32_t alpha_idx = 0; - if (srsran_pucch_nr_alpha_idx(carrier, cfg, slot, l, l_prime, resource->initial_cyclic_shift, 0, &alpha_idx) < - SRSRAN_SUCCESS) { - ERROR("Calculating alpha"); - } - // get r_uv sequence from LUT object - const cf_t* r_uv = srsran_zc_sequence_lut_get(&q->r_uv_1prb, u, v, alpha_idx); - if (r_uv == NULL) { - ERROR("Getting r_uv sequence"); + // Clause 6.4.1.3.1.2 specifies l=0,2,4... + for (uint32_t m_prime = 0, l = 0; m_prime < (resource->intra_slot_hopping ? 2 : 1); m_prime++) { + // Get number of symbols carrying DMRS + uint32_t n_pucch = dmrs_pucch_format1_n_pucch(resource, m_prime); + if (n_pucch == 0) { + ERROR("Error getting number of symbols"); return SRSRAN_ERROR; } - // Get w_i_m - cf_t w_i_m = srsran_pucch_nr_format1_w(q, n_pucch, resource->time_domain_occ, m); + // Get the starting PRB + uint32_t starting_prb = (m_prime == 0) ? resource->starting_prb : resource->second_hop_prb; + + for (uint32_t m = 0; m < n_pucch; m++, l += 2) { + // Get start of the sequence in resource grid + cf_t* slot_symbols_ptr = &slot_symbols[(q->carrier.nof_prb * (l + l_prime) + starting_prb) * SRSRAN_NRE]; + + // Get Alpha index + uint32_t alpha_idx = 0; + if (srsran_pucch_nr_alpha_idx(carrier, cfg, slot, l, l_prime, resource->initial_cyclic_shift, 0, &alpha_idx) < + SRSRAN_SUCCESS) { + ERROR("Calculating alpha"); + } + + // get r_uv sequence from LUT object + const cf_t* r_uv = srsran_zc_sequence_lut_get(&q->r_uv_1prb, u, v, alpha_idx); + if (r_uv == NULL) { + ERROR("Getting r_uv sequence"); + return SRSRAN_ERROR; + } + + // Get w_i_m + cf_t w_i_m = srsran_pucch_nr_format1_w(q, n_pucch, resource->time_domain_occ, m); - // Compute z(n) = w(i) * r_uv(n) - cf_t z[SRSRAN_NRE]; - srsran_vec_sc_prod_ccc(r_uv, w_i_m, z, SRSRAN_NRE); + // Compute z(n) = w(i) * r_uv(n) + cf_t z[SRSRAN_NRE]; + srsran_vec_sc_prod_ccc(r_uv, w_i_m, z, SRSRAN_NRE); + + if (SRSRAN_DEBUG_ENABLED && get_srsran_verbose_level() >= SRSRAN_VERBOSE_INFO && !is_handler_registered()) { + printf("[PUCCH Format 1 DMRS TX] m_prime=%d; m=%d; w_i_m=%+.3f%+.3f; z=", + m_prime, + m, + __real__ w_i_m, + __imag__ w_i_m); + srsran_vec_fprint_c(stdout, z, SRSRAN_NRE); + } - // Put z in the grid - srsran_vec_cf_copy(slot_symbols_ptr, z, SRSRAN_NRE); + // Put z in the grid + srsran_vec_cf_copy(slot_symbols_ptr, z, SRSRAN_NRE); + } } return SRSRAN_SUCCESS; @@ -186,50 +201,71 @@ int srsran_dmrs_pucch_format1_estimate(const srsran_pucch_nr_t* q, return SRSRAN_ERROR; } - uint32_t n_pucch = dmrs_pucch_format1_n_pucch(resource, 0); - if (n_pucch == 0) { - ERROR("Error getting number of symbols"); - return SRSRAN_ERROR; - } - - cf_t ce[SRSRAN_PUCCH_NR_FORMAT1_N_MAX][SRSRAN_NRE]; - - // Prevent ce[m] overflow - assert(n_pucch <= SRSRAN_PUCCH_NR_FORMAT1_N_MAX); + uint32_t start_rb_idx[SRSRAN_PUCCH_NR_FORMAT1_N_MAX]; + uint32_t symbol_idx[SRSRAN_PUCCH_NR_FORMAT1_N_MAX]; + cf_t ce[SRSRAN_PUCCH_NR_FORMAT1_N_MAX][SRSRAN_NRE]; + // First symbol index uint32_t l_prime = resource->start_symbol_idx; - for (uint32_t m = 0; m < n_pucch; m++) { - // Clause 6.4.1.3.1.2 specifies l=0,2,4... - uint32_t l = m * 2; - - // Get start of the sequence in resource grid - const cf_t* slot_symbols_ptr = - &slot_symbols[(q->carrier.nof_prb * (l + l_prime) + resource->starting_prb) * SRSRAN_NRE]; - - // Get Alpha index - uint32_t alpha_idx = 0; - if (srsran_pucch_nr_alpha_idx(&q->carrier, cfg, slot, l, l_prime, resource->initial_cyclic_shift, 0, &alpha_idx) < - SRSRAN_SUCCESS) { - ERROR("Calculating alpha"); - } - // get r_uv sequence from LUT object - const cf_t* r_uv = srsran_zc_sequence_lut_get(&q->r_uv_1prb, u, v, alpha_idx); - if (r_uv == NULL) { - ERROR("Getting r_uv sequence"); + uint32_t n_pucch_sum = 0; + for (uint32_t m_prime = 0, l = 0; m_prime < (resource->intra_slot_hopping ? 2 : 1); m_prime++) { + // Get number of symbols carrying DMRS + uint32_t n_pucch = dmrs_pucch_format1_n_pucch(resource, m_prime); + if (n_pucch == 0) { + ERROR("Error getting number of symbols"); return SRSRAN_ERROR; } - // Get w_i_m - cf_t w_i_m = srsran_pucch_nr_format1_w(q, n_pucch, resource->time_domain_occ, m); + // Prevent ce[m] overflow + assert(n_pucch <= SRSRAN_PUCCH_NR_FORMAT1_N_MAX); + + // Get the starting PRB + uint32_t starting_prb = (m_prime == 0) ? resource->starting_prb : resource->second_hop_prb; + start_rb_idx[n_pucch_sum] = starting_prb; + + for (uint32_t m = 0; m < n_pucch; m++, l += 2) { // Clause 6.4.1.3.1.2 specifies l=0,2,4... + symbol_idx[n_pucch_sum] = l + l_prime; - // Compute z(n) = w(i) * r_uv(n) - cf_t z[SRSRAN_NRE]; - srsran_vec_sc_prod_ccc(r_uv, w_i_m, z, SRSRAN_NRE); + // Get start of the sequence in resource grid + const cf_t* slot_symbols_ptr = &slot_symbols[(q->carrier.nof_prb * (l + l_prime) + starting_prb) * SRSRAN_NRE]; + + // Get Alpha index + uint32_t alpha_idx = 0; + if (srsran_pucch_nr_alpha_idx(&q->carrier, cfg, slot, l, l_prime, resource->initial_cyclic_shift, 0, &alpha_idx) < + SRSRAN_SUCCESS) { + ERROR("Calculating alpha"); + } + + // get r_uv sequence from LUT object + const cf_t* r_uv = srsran_zc_sequence_lut_get(&q->r_uv_1prb, u, v, alpha_idx); + if (r_uv == NULL) { + ERROR("Getting r_uv sequence"); + return SRSRAN_ERROR; + } - // TODO: can ce[m] overflow? - // Calculate least square estimates for this symbol - srsran_vec_prod_conj_ccc(slot_symbols_ptr, z, ce[m], SRSRAN_NRE); + // Get w_i_m + cf_t w_i_m = srsran_pucch_nr_format1_w(q, n_pucch, resource->time_domain_occ, m); + + // Compute z(n) = w(i) * r_uv(n) + cf_t z[SRSRAN_NRE]; + srsran_vec_sc_prod_ccc(r_uv, w_i_m, z, SRSRAN_NRE); + + if (SRSRAN_DEBUG_ENABLED && get_srsran_verbose_level() >= SRSRAN_VERBOSE_INFO && !is_handler_registered()) { + INFO("[PUCCH Format 1 DMRS RX] m_prime=%d; m=%d; w_i_m=%+.3f%+.3f", m_prime, m, __real__ w_i_m, __imag__ w_i_m); + srsran_vec_fprint_c(stdout, z, SRSRAN_NRE); + } + + // TODO: can ce[m] overflow? + // Calculate least square estimates for this symbol + srsran_vec_prod_conj_ccc(slot_symbols_ptr, z, ce[n_pucch_sum], SRSRAN_NRE); + + if (SRSRAN_DEBUG_ENABLED && get_srsran_verbose_level() >= SRSRAN_VERBOSE_INFO && !is_handler_registered()) { + printf("[PUCCH Format 1 DMRS RX] ce[%d]=", n_pucch_sum); + srsran_vec_fprint_c(stdout, ce[n_pucch_sum], SRSRAN_NRE); + } + n_pucch_sum++; + } } // Perform measurements @@ -237,7 +273,7 @@ int srsran_dmrs_pucch_format1_estimate(const srsran_pucch_nr_t* q, float epre = 0.0f; float ta_err = 0.0f; cf_t corr[SRSRAN_PUCCH_NR_FORMAT1_N_MAX] = {}; - for (uint32_t m = 0; m < n_pucch; m++) { + for (uint32_t m = 0; m < n_pucch_sum; m++) { corr[m] = srsran_vec_acc_cc(ce[m], SRSRAN_NRE) / SRSRAN_NRE; rsrp += SRSRAN_CSQABS(corr[m]); epre += srsran_vec_avg_power_cf(ce[m], SRSRAN_NRE); @@ -245,9 +281,9 @@ int srsran_dmrs_pucch_format1_estimate(const srsran_pucch_nr_t* q, } // Average measurements - rsrp /= n_pucch; - epre /= n_pucch; - ta_err /= n_pucch; + rsrp /= n_pucch_sum; + epre /= n_pucch_sum; + ta_err /= n_pucch_sum; // Set power measures rsrp = SRSRAN_MIN(rsrp, epre); @@ -256,7 +292,7 @@ int srsran_dmrs_pucch_format1_estimate(const srsran_pucch_nr_t* q, res->epre = epre; res->epre_dBfs = srsran_convert_power_to_dB(epre); res->noise_estimate = SRSRAN_MAX(epre - rsrp, 1e-6f); - res->noise_estimate_dbm = srsran_convert_power_to_dB(res->noise_estimate); + res->noise_estimate_dbFs = srsran_convert_power_to_dB(res->noise_estimate); res->snr = rsrp / res->noise_estimate; res->snr_db = srsran_convert_power_to_dB(res->snr); @@ -271,16 +307,16 @@ int srsran_dmrs_pucch_format1_estimate(const srsran_pucch_nr_t* q, } // Measure CFO - if (n_pucch > 1) { + if (n_pucch_sum > 1) { float cfo_avg_hz = 0.0f; - for (uint32_t m = 0; m < n_pucch - 1; m++) { + for (uint32_t m = 0; m < n_pucch_sum - 1; m++) { uint32_t l0 = resource->start_symbol_idx + m * 2; uint32_t l1 = resource->start_symbol_idx + (m + 1) * 2; float time_diff = srsran_symbol_distance_s(l0, l1, q->carrier.scs); float phase_diff = cargf(corr[m + 1] * conjf(corr[m])); if (isnormal(time_diff)) { - cfo_avg_hz += phase_diff / (2.0f * M_PI * time_diff * (n_pucch - 1)); + cfo_avg_hz += phase_diff / (2.0f * M_PI * time_diff * (n_pucch_sum - 1)); } } res->cfo_hz = cfo_avg_hz; @@ -292,11 +328,10 @@ int srsran_dmrs_pucch_format1_estimate(const srsran_pucch_nr_t* q, // ... Not implemented // Interpolates between DMRS symbols - for (uint32_t m = 0; m < n_pucch; m++) { - uint32_t l = m * 2 + 1; - cf_t* ce_ptr = &res->ce[(q->carrier.nof_prb * (l + l_prime) + resource->starting_prb) * SRSRAN_NRE]; + for (uint32_t m = 0; m < n_pucch_sum; m++) { + cf_t* ce_ptr = &res->ce[m * SRSRAN_NRE]; - if (m != n_pucch - 1) { + if (m != n_pucch_sum - 1) { // If it is not the last symbol with DMRS, average between srsran_vec_sum_ccc(ce[m], ce[m + 1], ce_ptr, SRSRAN_NRE); srsran_vec_sc_prod_cfc(ce_ptr, 0.5f, ce_ptr, SRSRAN_NRE); @@ -306,7 +341,7 @@ int srsran_dmrs_pucch_format1_estimate(const srsran_pucch_nr_t* q, srsran_vec_sub_ccc(ce_ptr, ce[m - 1], ce_ptr, SRSRAN_NRE); srsran_vec_sc_prod_cfc(ce_ptr, 0.5f, ce_ptr, SRSRAN_NRE); } else { - // Simply copy the + // Simply copy the estimated channel srsran_vec_cf_copy(ce_ptr, ce[m], SRSRAN_NRE); } } @@ -432,7 +467,7 @@ int srsran_dmrs_pucch_format2_estimate(const srsran_pucch_nr_t* q, res->epre = epre; res->epre_dBfs = srsran_convert_power_to_dB(epre); res->noise_estimate = SRSRAN_MAX(epre - rsrp, 1e-6f); - res->noise_estimate_dbm = srsran_convert_power_to_dB(res->noise_estimate); + res->noise_estimate_dbFs = srsran_convert_power_to_dB(res->noise_estimate); res->snr = rsrp / res->noise_estimate; res->snr_db = srsran_convert_power_to_dB(res->snr); diff --git a/lib/src/phy/ch_estimation/test/chest_test_srs.c b/lib/src/phy/ch_estimation/test/chest_test_srs.c index 91b2bb2d5..c1361aad6 100644 --- a/lib/src/phy/ch_estimation/test/chest_test_srs.c +++ b/lib/src/phy/ch_estimation/test/chest_test_srs.c @@ -159,12 +159,12 @@ int srs_test_context_run(srs_test_context_t* q) INFO("RESULTS: tti=%d; snr_db=%+.1f; noise_estimate_dbm=%+.1f; ta_us=%+.1f;", ul_sf_cfg.tti, q->chest_ul_res.snr_db, - q->chest_ul_res.noise_estimate_dbm, + q->chest_ul_res.noise_estimate_dbFs, q->chest_ul_res.ta_us); // Assert SRS measurements TESTASSERT(fabsf(q->chest_ul_res.snr_db - snr_db) < CHEST_TEST_SRS_SNR_DB_TOLERANCE); - TESTASSERT(fabsf(q->chest_ul_res.noise_estimate_dbm - n0_dbm) < CHEST_TEST_SRS_SNR_DB_TOLERANCE); + TESTASSERT(fabsf(q->chest_ul_res.noise_estimate_dbFs - n0_dbm) < CHEST_TEST_SRS_SNR_DB_TOLERANCE); TESTASSERT(fabsf(q->chest_ul_res.ta_us) < CHEST_TEST_SRS_TA_US_TOLERANCE); return SRSRAN_SUCCESS; diff --git a/lib/src/phy/common/test/CMakeLists.txt b/lib/src/phy/common/test/CMakeLists.txt index 0259f2f2f..34f74a930 100644 --- a/lib/src/phy/common/test/CMakeLists.txt +++ b/lib/src/phy/common/test/CMakeLists.txt @@ -37,3 +37,12 @@ target_link_libraries(sliv_test srsran_phy) add_test(sliv_test_14 sliv_test 14) add_test(sliv_test_52 sliv_test 48) add_test(sliv_test_52 sliv_test 52) + +######################################################################## +# PHY COMMON TEST +######################################################################## + +add_executable(phy_common_test phy_common_test.c) +target_link_libraries(phy_common_test srsran_phy) + +add_test(phy_common_test phy_common_test) \ No newline at end of file diff --git a/lib/src/phy/common/test/phy_common_test.c b/lib/src/phy/common/test/phy_common_test.c new file mode 100644 index 000000000..5465ffdc4 --- /dev/null +++ b/lib/src/phy/common/test/phy_common_test.c @@ -0,0 +1,50 @@ +/** + * + * \section COPYRIGHT + * + * Copyright 2013-2021 Software Radio Systems Limited + * + * By using this file, you agree to the terms and conditions set + * forth in the LICENSE file which can be found at the top level of + * the distribution. + * + */ +#include "srsran/common/test_common.h" +#include "srsran/phy/common/phy_common.h" + +int srsran_default_rates_test() +{ + // Verify calculated sample rates for all valid PRB sizes. + // By default we use the reduced 3/4 sampling to save bandwidth on the fronthaul. +#ifdef FORCE_STANDARD_RATE + srsran_use_standard_symbol_size(false); +#endif + TESTASSERT(srsran_sampling_freq_hz(6) == 1920000); + TESTASSERT(srsran_sampling_freq_hz(15) == 3840000); + TESTASSERT(srsran_sampling_freq_hz(25) == 5760000); + TESTASSERT(srsran_sampling_freq_hz(50) == 11520000); + TESTASSERT(srsran_sampling_freq_hz(75) == 15360000); // need to use default rate for 15 MHz BW + TESTASSERT(srsran_sampling_freq_hz(100) == 23040000); + return SRSRAN_SUCCESS; +} + +int lte_standard_rates_test() +{ + // Verify calculated sample rates for all valid PRB sizes. + // Enable standard LTE rates (required by some RF HW). + srsran_use_standard_symbol_size(true); + TESTASSERT(srsran_sampling_freq_hz(6) == 1920000); + TESTASSERT(srsran_sampling_freq_hz(15) == 3840000); + TESTASSERT(srsran_sampling_freq_hz(25) == 7680000); + TESTASSERT(srsran_sampling_freq_hz(50) == 15360000); + TESTASSERT(srsran_sampling_freq_hz(75) == 23040000); + TESTASSERT(srsran_sampling_freq_hz(100) == 30720000); + return SRSRAN_SUCCESS; +} + +int main(int argc, char** argv) +{ + TESTASSERT(srsran_default_rates_test() == SRSRAN_SUCCESS); + TESTASSERT(lte_standard_rates_test() == SRSRAN_SUCCESS); + return SRSRAN_SUCCESS; +} \ No newline at end of file diff --git a/lib/src/phy/dft/ofdm.c b/lib/src/phy/dft/ofdm.c index d7cfcadac..8df421bfe 100644 --- a/lib/src/phy/dft/ofdm.c +++ b/lib/src/phy/dft/ofdm.c @@ -711,7 +711,8 @@ int srsran_ofdm_set_cfr(srsran_ofdm_t* q, srsran_cfr_cfg_t* cfr) q->cfg.cfr_tx_cfg.symbol_sz = q->cfg.symbol_sz; q->cfg.cfr_tx_cfg.symbol_bw = q->nof_re; - // in the DL, the DC carrier is empty but still counts when designing the filter BW + // in the LTE DL, the DC carrier is empty but still counts when designing the filter BW + // in the LTE UL, the DC carrier is used q->cfg.cfr_tx_cfg.dc_sc = (!q->cfg.keep_dc) && (!isnormal(q->cfg.freq_shift_f)); if (q->cfg.cfr_tx_cfg.cfr_enable) { if (srsran_cfr_init(&q->tx_cfr, &q->cfg.cfr_tx_cfg) < SRSRAN_SUCCESS) { diff --git a/lib/src/phy/enb/enb_ul.c b/lib/src/phy/enb/enb_ul.c index 39ef882dc..0fe6b9dba 100644 --- a/lib/src/phy/enb/enb_ul.c +++ b/lib/src/phy/enb/enb_ul.c @@ -193,7 +193,9 @@ static int get_pucch(srsran_enb_ul_t* q, srsran_ul_sf_cfg_t* ul_sf, srsran_pucch ERROR("Error estimating PUCCH DMRS"); return SRSRAN_ERROR; } - pucch_res.snr_db = q->chest_res.snr_db; + pucch_res.snr_db = q->chest_res.snr_db; + pucch_res.rssi_dbFs = q->chest_res.epre_dBfs; + pucch_res.ni_dbFs = q->chest_res.noise_estimate_dbFs; ret = srsran_pucch_decode(&q->pucch, ul_sf, cfg, &q->chest_res, q->sf_symbols, &pucch_res); if (ret < SRSRAN_SUCCESS) { diff --git a/lib/src/phy/gnb/gnb_ul.c b/lib/src/phy/gnb/gnb_ul.c index 23a9f764f..86a19b48a 100644 --- a/lib/src/phy/gnb/gnb_ul.c +++ b/lib/src/phy/gnb/gnb_ul.c @@ -317,7 +317,7 @@ int srsran_gnb_ul_get_pucch(srsran_gnb_ul_t* q, meas->epre = q->chest_pucch.epre; meas->epre_dB = q->chest_pucch.epre_dBfs; meas->n0 = q->chest_pucch.noise_estimate; - meas->n0_dB = q->chest_pucch.noise_estimate_dbm; + meas->n0_dB = q->chest_pucch.noise_estimate_dbFs; meas->snr_dB = q->chest_pucch.snr_db; meas->cfo_hz = q->chest_pucch.cfo_hz; meas->cfo_hz_max = NAN; // Unavailable diff --git a/lib/src/phy/phch/harq_ack.c b/lib/src/phy/phch/harq_ack.c index 4751e32e5..60bba894f 100644 --- a/lib/src/phy/phch/harq_ack.c +++ b/lib/src/phy/phch/harq_ack.c @@ -88,6 +88,7 @@ static int harq_ack_gen_ack_type2(const srsran_harq_ack_cfg_hl_t* cfg, if (ack->present) { // Load ACK resource data into UCI info uci_cfg->pucch.resource_id = ack_info->cc[c].m[m].resource.pucch_resource_id; + uci_cfg->pucch.n_cce_0 = ack_info->cc[c].m[m].resource.n_cce; uci_cfg->pucch.rnti = ack_info->cc[c].m[m].resource.rnti; if (V_DL_CDAI <= V_temp) { @@ -183,6 +184,7 @@ int srsran_harq_ack_resource(const srsran_harq_ack_cfg_hl_t* cfg, pdsch_ack_resource->v_dai_dl = dci_dl->dai; pdsch_ack_resource->rnti = dci_dl->ctx.rnti; pdsch_ack_resource->pucch_resource_id = dci_dl->pucch_resource; + pdsch_ack_resource->n_cce = dci_dl->ctx.location.ncce; pdsch_ack_resource->pid = dci_dl->pid; return SRSRAN_SUCCESS; diff --git a/lib/src/phy/phch/pucch_cfg_nr.c b/lib/src/phy/phch/pucch_cfg_nr.c index c55446126..2a4c73347 100644 --- a/lib/src/phy/phch/pucch_cfg_nr.c +++ b/lib/src/phy/phch/pucch_cfg_nr.c @@ -169,7 +169,8 @@ int srsran_pucch_nr_cfg_resource_valid(const srsran_pucch_nr_resource_t* resourc return SRSRAN_ERROR; } - if (resource->intra_slot_hopping) { + // Frequency hopping is only possible with Format 1 + if (resource->intra_slot_hopping && resource->format != SRSRAN_PUCCH_NR_FORMAT_1) { ERROR("Intra-slot hopping is not implemented"); return SRSRAN_ERROR; } diff --git a/lib/src/phy/phch/pucch_nr.c b/lib/src/phy/phch/pucch_nr.c index 920eec721..3f40eb8f0 100644 --- a/lib/src/phy/phch/pucch_nr.c +++ b/lib/src/phy/phch/pucch_nr.c @@ -406,6 +406,8 @@ int srsran_pucch_nr_format1_encode(const srsran_pucch_nr_t* q, srsran_mod_modulate(&q->qpsk, b, d, 2); } + INFO("[PUCCH Format 1 Data TX] d=%+.3f%+.3f", __real__ d[0], __imag__ d[0]); + // Get group sequence uint32_t u = 0; uint32_t v = 0; @@ -414,41 +416,60 @@ int srsran_pucch_nr_format1_encode(const srsran_pucch_nr_t* q, return SRSRAN_ERROR; } - // Calculate number of symbols carrying PUCCH (No DMRS) - uint32_t n_pucch = pucch_nr_format1_n_pucch(resource, 0); - + // First symbol of this PUCCH transmission uint32_t l_prime = resource->start_symbol_idx; - for (uint32_t l = 1, m = 0; l < resource->nof_symbols; l += 2, m++) { - // Get start of the sequence in resource grid - cf_t* slot_symbols_ptr = &slot_symbols[(q->carrier.nof_prb * (l + l_prime) + resource->starting_prb) * SRSRAN_NRE]; - // Get Alpha index - uint32_t alpha_idx = 0; - if (srsran_pucch_nr_alpha_idx(&q->carrier, cfg, slot, l, l_prime, resource->initial_cyclic_shift, 0, &alpha_idx) < - SRSRAN_SUCCESS) { - return SRSRAN_ERROR; - } + // For each hop + for (uint32_t m_prime = 0, l = 1; m_prime < (resource->intra_slot_hopping ? 2 : 1); m_prime++) { + // Calculate number of symbols carrying PUCCH (No DMRS) + uint32_t n_pucch = pucch_nr_format1_n_pucch(resource, m_prime); - // get r_uv sequence from LUT object - const cf_t* r_uv = srsran_zc_sequence_lut_get(&q->r_uv_1prb, u, v, alpha_idx); - if (r_uv == NULL) { - ERROR("Getting r_uv sequence"); - return SRSRAN_ERROR; - } + // Get the starting PRB + uint32_t starting_prb = (m_prime == 0) ? resource->starting_prb : resource->second_hop_prb; - // Compute y = d(0) * r_uv - cf_t y[SRSRAN_NRE]; - srsran_vec_sc_prod_ccc(r_uv, d[0], y, SRSRAN_NRE); + // For each symbol carrying PUCCH data + for (uint32_t m = 0; m < n_pucch; m++, l += 2) { + // Get start of the sequence in resource grid + cf_t* slot_symbols_ptr = &slot_symbols[(q->carrier.nof_prb * (l + l_prime) + starting_prb) * SRSRAN_NRE]; - // Get w_i_m - cf_t w_i_m = srsran_pucch_nr_format1_w(q, n_pucch, resource->time_domain_occ, m); + // Get Alpha index + uint32_t alpha_idx = 0; + if (srsran_pucch_nr_alpha_idx(&q->carrier, cfg, slot, l, l_prime, resource->initial_cyclic_shift, 0, &alpha_idx) < + SRSRAN_SUCCESS) { + return SRSRAN_ERROR; + } + + // get r_uv sequence from LUT object + const cf_t* r_uv = srsran_zc_sequence_lut_get(&q->r_uv_1prb, u, v, alpha_idx); + if (r_uv == NULL) { + ERROR("Getting r_uv sequence"); + return SRSRAN_ERROR; + } + + // Get w_i_m + cf_t w_i_m = srsran_pucch_nr_format1_w(q, n_pucch, resource->time_domain_occ, m); - // Compute z(n) = w(i) * y(n) - cf_t z[SRSRAN_NRE]; - srsran_vec_sc_prod_ccc(y, w_i_m, z, SRSRAN_NRE); + // Compute z(n) = w(i) * r_uv(n) + cf_t z[SRSRAN_NRE]; + srsran_vec_sc_prod_ccc(r_uv, w_i_m, z, SRSRAN_NRE); - // Put z in the grid - srsran_vec_cf_copy(slot_symbols_ptr, z, SRSRAN_NRE); + if (SRSRAN_DEBUG_ENABLED && get_srsran_verbose_level() >= SRSRAN_VERBOSE_INFO && !is_handler_registered()) { + printf("[PUCCH Format 1 Data TX] m_prime=%d; m=%d; w_i_m=%+.3f%+.3f z=", + m_prime, + m, + __real__ w_i_m, + __imag__ w_i_m); + srsran_vec_fprint_c(stdout, z, SRSRAN_NRE); + } + + // Put z in the grid + srsran_vec_sc_prod_ccc(z, d[0], slot_symbols_ptr, SRSRAN_NRE); + + if (SRSRAN_DEBUG_ENABLED && get_srsran_verbose_level() >= SRSRAN_VERBOSE_INFO && !is_handler_registered()) { + printf("[PUCCH Format 1 TX] l=%d; x=", l + l_prime); + srsran_vec_fprint_c(stdout, slot_symbols_ptr, SRSRAN_NRE); + } + } } return SRSRAN_SUCCESS; @@ -493,46 +514,79 @@ int srsran_pucch_nr_format1_decode(srsran_pucch_nr_t* q, return SRSRAN_ERROR; } - // Calculate number of symbols carrying PUCCH (No DMRS) - uint32_t n_pucch = pucch_nr_format1_n_pucch(resource, 0); - + // First symbol of this PUCCH transmission uint32_t l_prime = resource->start_symbol_idx; - for (uint32_t l = 1, m = 0; l < resource->nof_symbols; l += 2, m++) { - // Get start of the sequence in resource grid - cf_t* slot_symbols_ptr = &slot_symbols[(q->carrier.nof_prb * (l + l_prime) + resource->starting_prb) * SRSRAN_NRE]; - cf_t* ce_ptr = &chest_res->ce[(q->carrier.nof_prb * (l + l_prime) + resource->starting_prb) * SRSRAN_NRE]; - // Equalise x = w(i) * d' * r_uv(n) - cf_t x[SRSRAN_NRE]; - srsran_predecoding_single(slot_symbols_ptr, ce_ptr, x, NULL, SRSRAN_NRE, 1.0f, chest_res->noise_estimate); + // For each hop + uint32_t n_pucch_sum = 0; + for (uint32_t m_prime = 0, l = 1; m_prime < (resource->intra_slot_hopping ? 2 : 1); m_prime++) { + // Calculate number of symbols carrying PUCCH (No DMRS) + uint32_t n_pucch = pucch_nr_format1_n_pucch(resource, m_prime); + + // Get the starting PRB + uint32_t starting_prb = (m_prime == 0) ? resource->starting_prb : resource->second_hop_prb; + + // For each symbol carrying PUCCH data + for (uint32_t m = 0; m < n_pucch; m++, l += 2) { + // Get start of the sequence in resource grid + cf_t* slot_symbols_ptr = &slot_symbols[(q->carrier.nof_prb * (l + l_prime) + starting_prb) * SRSRAN_NRE]; + cf_t* ce_ptr = &chest_res->ce[SRSRAN_NRE * n_pucch_sum]; + n_pucch_sum++; + + if (SRSRAN_DEBUG_ENABLED && get_srsran_verbose_level() >= SRSRAN_VERBOSE_INFO && !is_handler_registered()) { + printf("[PUCCH Format 1 CE RX] ce="); + srsran_vec_fprint_c(stdout, ce_ptr, SRSRAN_NRE); + } - // Get Alpha index - uint32_t alpha_idx = 0; - if (srsran_pucch_nr_alpha_idx( - &q->carrier, cfg, slot, l, l_prime, resource->initial_cyclic_shift, m_cs, &alpha_idx) < SRSRAN_SUCCESS) { - return SRSRAN_ERROR; - } + // Equalise x = w(i) * d' * r_uv(n) + cf_t x[SRSRAN_NRE]; + srsran_predecoding_single(slot_symbols_ptr, ce_ptr, x, NULL, SRSRAN_NRE, 1.0f, chest_res->noise_estimate); - // get r_uv sequence from LUT object - const cf_t* r_uv = srsran_zc_sequence_lut_get(&q->r_uv_1prb, u, v, alpha_idx); - if (r_uv == NULL) { - ERROR("Getting r_uv sequence"); - return SRSRAN_ERROR; - } - // Get w_i_m - cf_t w_i_m = srsran_pucch_nr_format1_w(q, n_pucch, resource->time_domain_occ, m); + if (SRSRAN_DEBUG_ENABLED && get_srsran_verbose_level() >= SRSRAN_VERBOSE_INFO && !is_handler_registered()) { + printf("[PUCCH Format 1 RX] l=%d; x=", l + l_prime); + srsran_vec_fprint_c(stdout, x, SRSRAN_NRE); + } - // Compute z(n) = w(i) * r_uv(n) - cf_t z[SRSRAN_NRE]; - srsran_vec_sc_prod_ccc(r_uv, w_i_m, z, SRSRAN_NRE); + // Get Alpha index + uint32_t alpha_idx = 0; + if (srsran_pucch_nr_alpha_idx( + &q->carrier, cfg, slot, l, l_prime, resource->initial_cyclic_shift, m_cs, &alpha_idx) < SRSRAN_SUCCESS) { + return SRSRAN_ERROR; + } + + // get r_uv sequence from LUT object + const cf_t* r_uv = srsran_zc_sequence_lut_get(&q->r_uv_1prb, u, v, alpha_idx); + if (r_uv == NULL) { + ERROR("Getting r_uv sequence"); + return SRSRAN_ERROR; + } - // Compute d = sum(x * conj(w(i) * r_uv(n))) = sum(w(i) * d' * r_uv(n) * conj(w(i) * r_uv(n))) = d' - d += srsran_vec_dot_prod_conj_ccc(x, z, SRSRAN_NRE) / SRSRAN_NRE; + // Get w_i_m + cf_t w_i_m = srsran_pucch_nr_format1_w(q, n_pucch, resource->time_domain_occ, m); - // Compute and accumulate average symbol power - pwr_acc += srsran_vec_avg_power_cf(x, SRSRAN_NRE); + // Compute z(n) = w(i) * r_uv(n) + cf_t z[SRSRAN_NRE]; + srsran_vec_sc_prod_ccc(r_uv, w_i_m, z, SRSRAN_NRE); + + if (SRSRAN_DEBUG_ENABLED && get_srsran_verbose_level() >= SRSRAN_VERBOSE_INFO && !is_handler_registered()) { + printf("[PUCCH Format 1 Data RX] m_prime=%d; m=%d; w_i_m=%+.3f%+.3f z=", + m_prime, + m, + __real__ w_i_m, + __imag__ w_i_m); + srsran_vec_fprint_c(stdout, z, SRSRAN_NRE); + } + + // Compute d = sum(x * conj(w(i) * r_uv(n))) = sum(w(i) * d' * r_uv(n) * conj(w(i) * r_uv(n))) = d' + d += srsran_vec_dot_prod_conj_ccc(x, z, SRSRAN_NRE) / SRSRAN_NRE; + + // Compute and accumulate average symbol power + pwr_acc += srsran_vec_avg_power_cf(x, SRSRAN_NRE); + } } + INFO("[PUCCH Format 1 Data RX] d=%+.3f%+.3f", __real__ d, __imag__ d); + // Demodulate d float llr[SRSRAN_PUCCH_NR_FORMAT1_MAX_NOF_BITS]; srsran_demod_soft_demodulate((nof_bits == 1) ? SRSRAN_MOD_BPSK : SRSRAN_MOD_QPSK, &d, llr, 1); diff --git a/lib/src/phy/phch/ra_ul_nr.c b/lib/src/phy/phch/ra_ul_nr.c index 4fe51f4de..49e606835 100644 --- a/lib/src/phy/phch/ra_ul_nr.c +++ b/lib/src/phy/phch/ra_ul_nr.c @@ -479,11 +479,89 @@ int srsran_ra_ul_nr_freq(const srsran_carrier_nr_t* carrier, return SRSRAN_ERROR; } +typedef struct { + srsran_pucch_nr_format_t format; + uint32_t start_symbol; + uint32_t rb_offset; + uint32_t N_cs; +} pucch_res_common_cfg_t; + +// Table 9.2.1-1 +#define PUCCH_RES_COMMON_CFG_IDX_MAX 15 +static pucch_res_common_cfg_t pucch_res_common_cfg[PUCCH_RES_COMMON_CFG_IDX_MAX + 1] = { + {SRSRAN_PUCCH_NR_FORMAT_0, 12, 0, 2}, + {SRSRAN_PUCCH_NR_FORMAT_0, 12, 0, 3}, + {SRSRAN_PUCCH_NR_FORMAT_0, 12, 3, 3}, + {SRSRAN_PUCCH_NR_FORMAT_1, 10, 0, 2}, + {SRSRAN_PUCCH_NR_FORMAT_1, 10, 0, 3}, + {SRSRAN_PUCCH_NR_FORMAT_1, 10, 2, 3}, + {SRSRAN_PUCCH_NR_FORMAT_1, 10, 4, 3}, + {SRSRAN_PUCCH_NR_FORMAT_1, 4, 0, 2}, + {SRSRAN_PUCCH_NR_FORMAT_1, 4, 0, 3}, + {SRSRAN_PUCCH_NR_FORMAT_1, 4, 2, 3}, + {SRSRAN_PUCCH_NR_FORMAT_1, 4, 4, 3}, + {SRSRAN_PUCCH_NR_FORMAT_1, 0, 0, 2}, + {SRSRAN_PUCCH_NR_FORMAT_1, 0, 0, 4}, + {SRSRAN_PUCCH_NR_FORMAT_1, 0, 2, 4}, + {SRSRAN_PUCCH_NR_FORMAT_1, 0, 4, 4}, + {SRSRAN_PUCCH_NR_FORMAT_1, 0, 0, 4}}; + // Implements TS 38.213 Table 9.2.1-1: PUCCH resource sets before dedicated PUCCH resource configuration -static int ra_ul_nr_pucch_resource_default(uint32_t r_pucch, srsran_pucch_nr_resource_t* resource) +static int ra_ul_nr_pucch_resource_default(uint32_t pucch_res_common_idx, + uint32_t N_size_bwp, + uint32_t r_pucch, + srsran_pucch_nr_resource_t* resource) { - ERROR("Not implemented"); - return SRSRAN_ERROR; + if (pucch_res_common_idx > PUCCH_RES_COMMON_CFG_IDX_MAX) { + ERROR("Invalid pucch_res_common_idx value (%d)\n", pucch_res_common_idx); + return SRSRAN_ERROR; + } + + uint32_t cs_v_2[2] = {0, 6}; + uint32_t cs_v_3[3] = {0, 4, 8}; + uint32_t cs_v_4[4] = {0, 3, 6, 9}; + + // Table 9.2.1-1 + resource->format = pucch_res_common_cfg[pucch_res_common_idx].format; + resource->start_symbol_idx = pucch_res_common_cfg[pucch_res_common_idx].start_symbol; + resource->nof_symbols = 14 - resource->start_symbol_idx; + uint32_t rb_offset_bwp = pucch_res_common_cfg[pucch_res_common_idx].rb_offset; + uint32_t N_cs = pucch_res_common_cfg[pucch_res_common_idx].N_cs; + + // Special case for cs_v_2 value + if (pucch_res_common_idx == 0) { + cs_v_2[1] = 3; + } + + // Special case for rb_offset + if (pucch_res_common_idx == 15) { + rb_offset_bwp = N_size_bwp / 4; + } + + uint32_t csv_idx = 0; + if (r_pucch / 8 == 0) { + resource->starting_prb = rb_offset_bwp + SRSRAN_FLOOR(r_pucch, N_cs); + resource->second_hop_prb = N_size_bwp - 1 - resource->starting_prb; + csv_idx = r_pucch % N_cs; + } else { + resource->second_hop_prb = rb_offset_bwp + SRSRAN_FLOOR(r_pucch - 8, N_cs); + resource->starting_prb = N_size_bwp - 1 - resource->second_hop_prb; + csv_idx = (r_pucch - 8) % N_cs; + } + + switch (N_cs) { + case 2: + resource->initial_cyclic_shift = cs_v_2[csv_idx]; + break; + case 3: + resource->initial_cyclic_shift = cs_v_3[csv_idx]; + break; + case 4: + resource->initial_cyclic_shift = cs_v_4[csv_idx]; + break; + } + + return SRSRAN_SUCCESS; } static int ra_ul_nr_pucch_resource_hl(const srsran_pucch_nr_hl_cfg_t* cfg, @@ -531,6 +609,7 @@ static int ra_ul_nr_pucch_resource_hl(const srsran_pucch_nr_hl_cfg_t* cfg, int srsran_ra_ul_nr_pucch_resource(const srsran_pucch_nr_hl_cfg_t* pucch_cfg, const srsran_uci_cfg_nr_t* uci_cfg, + uint32_t N_bwp_sz, srsran_pucch_nr_resource_t* resource) { if (pucch_cfg == NULL || uci_cfg == NULL || resource == NULL) { @@ -632,8 +711,9 @@ int srsran_ra_ul_nr_pucch_resource(const srsran_pucch_nr_hl_cfg_t* pucch_cfg, // a PUCCH resource set is provided by pucch-ResourceCommon through an index to a row of Table 9.2.1-1 for size // transmission of HARQ-ACK information on PUCCH in an initial UL BWP of N BWP PRBs. if (!pucch_cfg->enabled) { - uint32_t r_pucch = (2 * uci_cfg->pucch.n_cce_0) + 2 * uci_cfg->pucch.resource_id; - return ra_ul_nr_pucch_resource_default(r_pucch, resource); + uint32_t N_cce = SRSRAN_FLOOR(N_bwp_sz, 6); + uint32_t r_pucch = ((2 * uci_cfg->pucch.n_cce_0) / N_cce) + 2 * uci_cfg->pucch.resource_id; + return ra_ul_nr_pucch_resource_default(pucch_cfg->common.resource_common, N_bwp_sz, r_pucch, resource); } return ra_ul_nr_pucch_resource_hl(pucch_cfg, uci_cfg, uci_cfg->pucch.resource_id, resource); } diff --git a/lib/src/phy/phch/test/CMakeLists.txt b/lib/src/phy/phch/test/CMakeLists.txt index c59d71b1a..9f347414b 100644 --- a/lib/src/phy/phch/test/CMakeLists.txt +++ b/lib/src/phy/phch/test/CMakeLists.txt @@ -57,15 +57,15 @@ add_executable(psbch_file_test psbch_file_test.c) target_link_libraries(psbch_file_test srsran_phy) # TM2 file tests -add_lte_test(psbch_file_test_ideal_tm2_p6_c0 psbch_file_test -p 6 -c 0 -d -i ${CMAKE_HOME_DIRECTORY}/lib/src/phy/phch/test/signal_sidelink_ideal_tm2_p6_c0_s1.92e6.dat) -add_lte_test(psbch_file_test_ideal_tm2_p15_c84 psbch_file_test -p 15 -c 84 -d -i ${CMAKE_HOME_DIRECTORY}/lib/src/phy/phch/test/signal_sidelink_ideal_tm2_p15_c84_s3.84e6.dat) -add_lte_test(psbch_file_test_ideal_tm2_p25_c168 psbch_file_test -p 25 -c 168 -d -i ${CMAKE_HOME_DIRECTORY}/lib/src/phy/phch/test/signal_sidelink_ideal_tm2_p25_c168_s7.68e6.dat) -add_lte_test(psbch_file_test_ideal_tm2_p50_c252 psbch_file_test -p 50 -c 252 -d -i ${CMAKE_HOME_DIRECTORY}/lib/src/phy/phch/test/signal_sidelink_ideal_tm2_p50_c252_s15.36e6.dat) -add_lte_test(psbch_file_test_ideal_tm2_p100_c335 psbch_file_test -p 100 -c 335 -d -i ${CMAKE_HOME_DIRECTORY}/lib/src/phy/phch/test/signal_sidelink_ideal_tm2_p100_c335_s30.72e6.dat) -add_lte_test(psbch_file_test_ideal_tm2_p50_c252_ext psbch_file_test -p 50 -c 252 -e -d -i ${CMAKE_HOME_DIRECTORY}/lib/src/phy/phch/test/signal_sidelink_ideal_tm2_p50_c252_s15.36e6_ext.dat) +add_lte_test(psbch_file_test_ideal_tm2_p6_c0 psbch_file_test -p 6 -c 0 -d -i ${CMAKE_CURRENT_SOURCE_DIR}/signal_sidelink_ideal_tm2_p6_c0_s1.92e6.dat) +add_lte_test(psbch_file_test_ideal_tm2_p15_c84 psbch_file_test -p 15 -c 84 -d -i ${CMAKE_CURRENT_SOURCE_DIR}/signal_sidelink_ideal_tm2_p15_c84_s3.84e6.dat) +add_lte_test(psbch_file_test_ideal_tm2_p25_c168 psbch_file_test -p 25 -c 168 -d -i ${CMAKE_CURRENT_SOURCE_DIR}/signal_sidelink_ideal_tm2_p25_c168_s7.68e6.dat) +add_lte_test(psbch_file_test_ideal_tm2_p50_c252 psbch_file_test -p 50 -c 252 -d -i ${CMAKE_CURRENT_SOURCE_DIR}/signal_sidelink_ideal_tm2_p50_c252_s15.36e6.dat) +add_lte_test(psbch_file_test_ideal_tm2_p100_c335 psbch_file_test -p 100 -c 335 -d -i ${CMAKE_CURRENT_SOURCE_DIR}/signal_sidelink_ideal_tm2_p100_c335_s30.72e6.dat) +add_lte_test(psbch_file_test_ideal_tm2_p50_c252_ext psbch_file_test -p 50 -c 252 -e -d -i ${CMAKE_CURRENT_SOURCE_DIR}/signal_sidelink_ideal_tm2_p50_c252_s15.36e6_ext.dat) # TM4 file tests -add_lte_test(psbch_file_test_cmw_tm4_p50_c169 psbch_file_test -p 50 -c 169 -t 4 -i ${CMAKE_HOME_DIRECTORY}/lib/src/phy/phch/test/signal_sidelink_cmw500_f5.92e9_s11.52e6_50prb_slss_id169.dat) +add_lte_test(psbch_file_test_cmw_tm4_p50_c169 psbch_file_test -p 50 -c 169 -t 4 -i ${CMAKE_CURRENT_SOURCE_DIR}/signal_sidelink_cmw500_f5.92e9_s11.52e6_50prb_slss_id169.dat) ######################################################################## # PSCCH TEST @@ -123,38 +123,38 @@ add_executable(pssch_pscch_file_test pssch_pscch_file_test.c) target_link_libraries(pssch_pscch_file_test srsran_phy) # TM2 file tests -add_lte_test(pssch_pscch_file_test_ideal_tm2_p100 pssch_pscch_file_test -p 100 -d -i ${CMAKE_HOME_DIRECTORY}/lib/src/phy/phch/test/signal_sidelink_ideal_tm2_p100_c335_s30.72e6.dat) +add_lte_test(pssch_pscch_file_test_ideal_tm2_p100 pssch_pscch_file_test -p 100 -d -i ${CMAKE_CURRENT_SOURCE_DIR}/signal_sidelink_ideal_tm2_p100_c335_s30.72e6.dat) set_property(TEST pssch_pscch_file_test_ideal_tm2_p100 PROPERTY PASS_REGULAR_EXPRESSION "num_decoded_sci=[2,3] num_decoded_tb=1") # TM4 file tests (first SF is sf_idx = 6 such that the PSSCH sf_idx=0) -add_lte_test(pssch_pscch_file_test_ideal_tm4_p100 pssch_pscch_file_test -p 100 -t 4 -s 10 -n 10 -d -m 6 -i ${CMAKE_HOME_DIRECTORY}/lib/src/phy/phch/test/signal_sidelink_ideal_tm4_p100_c335_size10_num10_cshift0_s30.72e6.dat) +add_lte_test(pssch_pscch_file_test_ideal_tm4_p100 pssch_pscch_file_test -p 100 -t 4 -s 10 -n 10 -d -m 6 -i ${CMAKE_CURRENT_SOURCE_DIR}/signal_sidelink_ideal_tm4_p100_c335_size10_num10_cshift0_s30.72e6.dat) set_property(TEST pssch_pscch_file_test_ideal_tm4_p100 PROPERTY PASS_REGULAR_EXPRESSION "num_decoded_sci=1") -add_lte_test(pssch_pscch_test_tm4_p50_qc pssch_pscch_file_test -p 50 -t 4 -d -i ${CMAKE_HOME_DIRECTORY}/lib/src/phy/phch/test/signal_sidelink_qc9150_f5.92e9_s15.36e6_50prb_20offset.dat) +add_lte_test(pssch_pscch_test_tm4_p50_qc pssch_pscch_file_test -p 50 -t 4 -d -i ${CMAKE_CURRENT_SOURCE_DIR}/signal_sidelink_qc9150_f5.92e9_s15.36e6_50prb_20offset.dat) set_property(TEST pssch_pscch_test_tm4_p50_qc PROPERTY PASS_REGULAR_EXPRESSION "num_decoded_sci=1 num_decoded_tb=1") # Capture has a SFO offset of ~64 samples, but offsetting by 20 is enough to decode it -add_lte_test(pssch_pscch_test_tm4_p50_cmw pssch_pscch_file_test -p 50 -t 4 -o 20 -i ${CMAKE_HOME_DIRECTORY}/lib/src/phy/phch/test/signal_sidelink_cmw500_f5.92e9_s11.52e6_50prb_0offset_1ms.dat) +add_lte_test(pssch_pscch_test_tm4_p50_cmw pssch_pscch_file_test -p 50 -t 4 -o 20 -i ${CMAKE_CURRENT_SOURCE_DIR}/signal_sidelink_cmw500_f5.92e9_s11.52e6_50prb_0offset_1ms.dat) set_property(TEST pssch_pscch_test_tm4_p50_cmw PROPERTY PASS_REGULAR_EXPRESSION "num_decoded_sci=1 num_decoded_tb=1") # With PHY retransmission (3 TTI offset) first SF at sf_idx=5 -add_lte_test(pssch_pscch_test_tm4_p50_huawei pssch_pscch_file_test -p 50 -t 4 -m 5 -i ${CMAKE_HOME_DIRECTORY}/lib/src/phy/phch/test/signal_sidelink_huawei_s11.52e6_50prb_10prb_offset_with_retx.dat) +add_lte_test(pssch_pscch_test_tm4_p50_huawei pssch_pscch_file_test -p 50 -t 4 -m 5 -i ${CMAKE_CURRENT_SOURCE_DIR}/signal_sidelink_huawei_s11.52e6_50prb_10prb_offset_with_retx.dat) set_property(TEST pssch_pscch_test_tm4_p50_huawei PROPERTY PASS_REGULAR_EXPRESSION "num_decoded_sci=2 num_decoded_tb=2") # With PHY ReTx (0 TTI offset?) -add_lte_test(pssch_pscch_test_tm4_p50_uxm1 pssch_pscch_file_test -p 50 -d -t 4 -s 5 -n 10 -i ${CMAKE_HOME_DIRECTORY}/lib/src/phy/phch/test/signal_sidelink_uxm_s15.36e6_50prb_0prb_offset_mcs12.dat) +add_lte_test(pssch_pscch_test_tm4_p50_uxm1 pssch_pscch_file_test -p 50 -d -t 4 -s 5 -n 10 -i ${CMAKE_CURRENT_SOURCE_DIR}/signal_sidelink_uxm_s15.36e6_50prb_0prb_offset_mcs12.dat) set_property(TEST pssch_pscch_test_tm4_p50_uxm1 PROPERTY PASS_REGULAR_EXPRESSION "mcs=12.*num_decoded_sci=2 num_decoded_tb=2") # 100 PRB startOffset 1 MCS12 MAC padding, first SF is index 0 -add_lte_test(pssch_pscch_test_tm4_p100_uxm2 pssch_pscch_file_test -p 100 -t 4 -s 10 -n 10 -i ${CMAKE_HOME_DIRECTORY}/lib/src/phy/phch/test/signal_sidelink_uxm_s23.04e6_100prb_1prb_offset_mcs12_padding.dat) +add_lte_test(pssch_pscch_test_tm4_p100_uxm2 pssch_pscch_file_test -p 100 -t 4 -s 10 -n 10 -i ${CMAKE_CURRENT_SOURCE_DIR}/signal_sidelink_uxm_s23.04e6_100prb_1prb_offset_mcs12_padding.dat) set_property(TEST pssch_pscch_test_tm4_p100_uxm2 PROPERTY PASS_REGULAR_EXPRESSION "mcs=12.*num_decoded_sci=4") # 100 PRB LTE sampling rate, startOffset1 MCS12 ITS data, first SF is index 6 -add_lte_test(pssch_pscch_test_tm4_p100_uxm3 pssch_pscch_file_test -p 100 -d -t 4 -s 10 -n 10 -m 6 -i ${CMAKE_HOME_DIRECTORY}/lib/src/phy/phch/test/signal_sidelink_uxm_s30.72e6_100prb_1prb_offset_mcs12_its.dat) +add_lte_test(pssch_pscch_test_tm4_p100_uxm3 pssch_pscch_file_test -p 100 -d -t 4 -s 10 -n 10 -m 6 -i ${CMAKE_CURRENT_SOURCE_DIR}/signal_sidelink_uxm_s30.72e6_100prb_1prb_offset_mcs12_its.dat) set_property(TEST pssch_pscch_test_tm4_p100_uxm3 PROPERTY PASS_REGULAR_EXPRESSION "mcs=12.*num_decoded_sci=1") # 50 PRB LTE sampling rate, startOffset0 MCS28 MAC padding, first SF is index 1 -add_lte_test(pssch_pscch_test_tm4_p50_uxm4 pssch_pscch_file_test -p 50 -d -t 4 -s 5 -n 10 -m 1 -i ${CMAKE_HOME_DIRECTORY}/lib/src/phy/phch/test/signal_sidelink_uxm_s15.36e6_50prb_0prb_offset_mcs28_padding_5ms.dat) +add_lte_test(pssch_pscch_test_tm4_p50_uxm4 pssch_pscch_file_test -p 50 -d -t 4 -s 5 -n 10 -m 1 -i ${CMAKE_CURRENT_SOURCE_DIR}/signal_sidelink_uxm_s15.36e6_50prb_0prb_offset_mcs28_padding_5ms.dat) set_property(TEST pssch_pscch_test_tm4_p50_uxm4 PROPERTY PASS_REGULAR_EXPRESSION "mcs=28.*num_decoded_sci=5") ######################################################################## diff --git a/lib/src/phy/phch/test/pucch_nr_test.c b/lib/src/phy/phch/test/pucch_nr_test.c index 5b843f6df..b68efd262 100644 --- a/lib/src/phy/phch/test/pucch_nr_test.c +++ b/lib/src/phy/phch/test/pucch_nr_test.c @@ -88,63 +88,71 @@ static int test_pucch_format0(srsran_pucch_nr_t* pucch, const srsran_pucch_nr_co static int test_pucch_format1(srsran_pucch_nr_t* pucch, const srsran_pucch_nr_common_cfg_t* cfg, srsran_chest_ul_res_t* chest_res, - cf_t* slot_symbols) + cf_t* slot_symbols, + bool enable_intra_slot_hopping) { srsran_slot_cfg_t slot = {}; srsran_pucch_nr_resource_t resource = {}; resource.format = SRSRAN_PUCCH_NR_FORMAT_1; + resource.intra_slot_hopping = enable_intra_slot_hopping; for (slot.idx = 0; slot.idx < SRSRAN_NSLOTS_PER_FRAME_NR(carrier.scs); slot.idx++) { for (resource.starting_prb = 0; resource.starting_prb < carrier.nof_prb; resource.starting_prb += starting_prb_stride) { - for (resource.nof_symbols = SRSRAN_PUCCH_NR_FORMAT1_MIN_NSYMB; - resource.nof_symbols <= SRSRAN_PUCCH_NR_FORMAT1_MAX_NSYMB; - resource.nof_symbols++) { - for (resource.start_symbol_idx = 0; - resource.start_symbol_idx <= - SRSRAN_MIN(SRSRAN_PUCCH_NR_FORMAT1_MAX_STARTSYMB, SRSRAN_NSYMB_PER_SLOT_NR - resource.nof_symbols); - resource.start_symbol_idx += starting_symbol_stride) { - for (resource.time_domain_occ = 0; resource.time_domain_occ <= SRSRAN_PUCCH_NR_FORMAT1_MAX_TOCC; - resource.time_domain_occ++) { - for (resource.initial_cyclic_shift = 0; resource.initial_cyclic_shift <= SRSRAN_PUCCH_NR_FORMAT1_MAX_CS; - resource.initial_cyclic_shift++) { - for (uint32_t nof_bits = 1; nof_bits <= SRSRAN_PUCCH_NR_FORMAT1_MAX_NOF_BITS; nof_bits++) { - for (uint32_t word = 0; word < (1U << nof_bits); word++) { - // Generate bits - uint8_t b[SRSRAN_PUCCH_NR_FORMAT1_MAX_NOF_BITS] = {}; - for (uint32_t i = 0; i < nof_bits; i++) { - b[i] = (word >> i) & 1U; - } - - // Encode PUCCH - TESTASSERT(srsran_pucch_nr_format1_encode(pucch, cfg, &slot, &resource, b, nof_bits, slot_symbols) == - SRSRAN_SUCCESS); - - // Put DMRS - TESTASSERT(srsran_dmrs_pucch_format1_put(pucch, &carrier, cfg, &slot, &resource, slot_symbols) == - SRSRAN_SUCCESS); - - // Apply AWGN - srsran_channel_awgn_run_c( - &awgn, slot_symbols, slot_symbols, carrier.nof_prb * SRSRAN_NRE * SRSRAN_NSYMB_PER_SLOT_NR); - - // Estimate channel - TESTASSERT(srsran_dmrs_pucch_format1_estimate( - pucch, cfg, &slot, &resource, slot_symbols, chest_res) == SRSRAN_SUCCESS); - - TESTASSERT(fabsf(chest_res->rsrp_dBfs - 0.0f) < 3.0f); - TESTASSERT(fabsf(chest_res->epre_dBfs - 0.0f) < 3.0f); - TESTASSERT(fabsf(chest_res->snr_db - snr_db) < 10.0f); - - // Decode PUCCH - uint8_t b_rx[SRSRAN_PUCCH_NR_FORMAT1_MAX_NOF_BITS]; - TESTASSERT(srsran_pucch_nr_format1_decode( - pucch, cfg, &slot, &resource, chest_res, slot_symbols, b_rx, nof_bits, NULL) == - SRSRAN_SUCCESS); - - // Check received bits - for (uint32_t i = 0; i < nof_bits; i++) { - TESTASSERT(b[i] == b_rx[i]); + for (resource.second_hop_prb = 0; resource.second_hop_prb < (enable_intra_slot_hopping) ? carrier.nof_prb : 0; + resource.second_hop_prb += starting_prb_stride) { + for (resource.nof_symbols = SRSRAN_PUCCH_NR_FORMAT1_MIN_NSYMB; + resource.nof_symbols <= SRSRAN_PUCCH_NR_FORMAT1_MAX_NSYMB; + resource.nof_symbols++) { + for (resource.start_symbol_idx = 0; + resource.start_symbol_idx <= + SRSRAN_MIN(SRSRAN_PUCCH_NR_FORMAT1_MAX_STARTSYMB, SRSRAN_NSYMB_PER_SLOT_NR - resource.nof_symbols); + resource.start_symbol_idx += starting_symbol_stride) { + for (resource.time_domain_occ = 0; resource.time_domain_occ <= SRSRAN_PUCCH_NR_FORMAT1_MAX_TOCC; + resource.time_domain_occ++) { + for (resource.initial_cyclic_shift = 0; resource.initial_cyclic_shift <= SRSRAN_PUCCH_NR_FORMAT1_MAX_CS; + resource.initial_cyclic_shift++) { + for (uint32_t nof_bits = 1; nof_bits <= SRSRAN_PUCCH_NR_FORMAT1_MAX_NOF_BITS; nof_bits++) { + for (uint32_t word = 0; word < (1U << nof_bits); word++) { + // Generate bits + uint8_t b[SRSRAN_PUCCH_NR_FORMAT1_MAX_NOF_BITS] = {}; + for (uint32_t i = 0; i < nof_bits; i++) { + b[i] = (word >> i) & 1U; + } + + // Encode PUCCH + TESTASSERT(srsran_pucch_nr_format1_encode( + pucch, cfg, &slot, &resource, b, nof_bits, slot_symbols) == SRSRAN_SUCCESS); + + // Put DMRS + TESTASSERT(srsran_dmrs_pucch_format1_put(pucch, &carrier, cfg, &slot, &resource, slot_symbols) == + SRSRAN_SUCCESS); + + // Apply AWGN + srsran_channel_awgn_run_c( + &awgn, slot_symbols, slot_symbols, carrier.nof_prb * SRSRAN_NRE * SRSRAN_NSYMB_PER_SLOT_NR); + + // Estimate channel + TESTASSERT(srsran_dmrs_pucch_format1_estimate( + pucch, cfg, &slot, &resource, slot_symbols, chest_res) == SRSRAN_SUCCESS); + + TESTASSERT(fabsf(chest_res->rsrp_dBfs - 0.0f) < 3.0f); + TESTASSERT(fabsf(chest_res->epre_dBfs - 0.0f) < 3.0f); + TESTASSERT(fabsf(chest_res->snr_db - snr_db) < 10.0f); + + // Decode PUCCH + uint8_t b_rx[SRSRAN_PUCCH_NR_FORMAT1_MAX_NOF_BITS]; + TESTASSERT(srsran_pucch_nr_format1_decode( + pucch, cfg, &slot, &resource, chest_res, slot_symbols, b_rx, nof_bits, NULL) == + SRSRAN_SUCCESS); + + // Check received bits + for (uint32_t i = 0; i < nof_bits; i++) { + if (b[i] != b_rx[i]) { + printf("aaa"); + } + TESTASSERT(b[i] == b_rx[i]); + } } } } @@ -345,9 +353,13 @@ int main(int argc, char** argv) } } - // Test Format 1 + // Test Format 1 with and without intra slot frequency hopping if (format < 0 || format == 1) { - if (test_pucch_format1(&pucch, &common_cfg, &chest_res, slot_symb) < SRSRAN_SUCCESS) { + if (test_pucch_format1(&pucch, &common_cfg, &chest_res, slot_symb, false) < SRSRAN_SUCCESS) { + ERROR("Failed PUCCH format 1"); + goto clean_exit; + } + if (test_pucch_format1(&pucch, &common_cfg, &chest_res, slot_symb, true) < SRSRAN_SUCCESS) { ERROR("Failed PUCCH format 1"); goto clean_exit; } diff --git a/lib/src/phy/rf/CMakeLists.txt b/lib/src/phy/rf/CMakeLists.txt index ae865b5ac..36c144841 100644 --- a/lib/src/phy/rf/CMakeLists.txt +++ b/lib/src/phy/rf/CMakeLists.txt @@ -23,13 +23,32 @@ if(RF_FOUND) add_library(srsran_rf_utils STATIC rf_utils.c) target_link_libraries(srsran_rf_utils srsran_phy) - # Include common RF files + # Top-level RF library sources set(SOURCES_RF "") list(APPEND SOURCES_RF rf_imp.c) - if (UHD_FOUND) + # Lists of static (builtin) and dynamic RF plugins + set(STATIC_PLUGINS "") + set(DYNAMIC_PLUGINS "") + + if (ENABLE_RF_PLUGINS) + add_definitions(-DENABLE_RF_PLUGINS) + endif (ENABLE_RF_PLUGINS) + + # RF plugins + if (UHD_FOUND AND ENABLE_UHD) add_definitions(-DENABLE_UHD) - list(APPEND SOURCES_RF rf_uhd_imp.cc) + set(SOURCES_UHD rf_uhd_imp.cc) + if (ENABLE_RF_PLUGINS) + add_library(srsran_rf_uhd SHARED ${SOURCES_UHD}) + set_target_properties(srsran_rf_uhd PROPERTIES VERSION ${SRSRAN_VERSION_STRING} SOVERSION ${SRSRAN_SOVERSION}) + list(APPEND DYNAMIC_PLUGINS srsran_rf_uhd) + else (ENABLE_RF_PLUGINS) + add_library(srsran_rf_uhd STATIC ${SOURCES_UHD}) + list(APPEND STATIC_PLUGINS srsran_rf_uhd) + endif (ENABLE_RF_PLUGINS) + target_link_libraries(srsran_rf_uhd srsran_rf_utils srsran_phy ${UHD_LIBRARIES} ${Boost_LIBRARIES}) + install(TARGETS srsran_rf_uhd DESTINATION ${LIBRARY_DIR} OPTIONAL) # If found, add a macro to inform the UHD driver about the available feature if (UHD_ENABLE_X300_FW_RESET) @@ -41,72 +60,117 @@ if(RF_FOUND) if (UHD_ENABLE_CUSTOM_RFNOC) add_definitions(-DUHD_ENABLE_CUSTOM_RFNOC) endif(UHD_ENABLE_CUSTOM_RFNOC) - endif (UHD_FOUND) + endif (UHD_FOUND AND ENABLE_UHD) - if (UHD_FOUND AND UHD_ENABLE_CUSTOM_RFNOC) - add_executable(rfnoc_test rfnoc_test.cc) - target_link_libraries(rfnoc_test srsran_rf ${UHD_LIBRARIES} ${Boost_LIBRARIES} /usr/lib/x86_64-linux-gnu/libboost_system.so) - message(info ${Boost_LIBRARIES}) - endif (UHD_FOUND AND UHD_ENABLE_CUSTOM_RFNOC) - - if (BLADERF_FOUND) + if (BLADERF_FOUND AND ENABLE_BLADERF) add_definitions(-DENABLE_BLADERF) - list(APPEND SOURCES_RF rf_blade_imp.c) - endif (BLADERF_FOUND) + set(SOURCES_BLADE rf_blade_imp.c) + if (ENABLE_RF_PLUGINS) + add_library(srsran_rf_blade SHARED ${SOURCES_BLADE}) + set_target_properties(srsran_rf_blade PROPERTIES VERSION ${SRSRAN_VERSION_STRING} SOVERSION ${SRSRAN_SOVERSION}) + list(APPEND DYNAMIC_PLUGINS srsran_rf_blade) + else (ENABLE_RF_PLUGINS) + add_library(srsran_rf_blade STATIC ${SOURCES_BLADE}) + list(APPEND STATIC_PLUGINS srsran_rf_blade) + endif (ENABLE_RF_PLUGINS) + target_link_libraries(srsran_rf_blade srsran_rf_utils srsran_phy ${BLADERF_LIBRARIES}) + install(TARGETS srsran_rf_blade DESTINATION ${LIBRARY_DIR} OPTIONAL) + endif (BLADERF_FOUND AND ENABLE_BLADERF) if (SOAPYSDR_FOUND AND ENABLE_SOAPYSDR) add_definitions(-DENABLE_SOAPYSDR) - list(APPEND SOURCES_RF rf_soapy_imp.c) + set(SOURCES_SOAPY rf_soapy_imp.c) + if (ENABLE_RF_PLUGINS) + add_library(srsran_rf_soapy SHARED ${SOURCES_SOAPY}) + set_target_properties(srsran_rf_soapy PROPERTIES VERSION ${SRSRAN_VERSION_STRING} SOVERSION ${SRSRAN_SOVERSION}) + list(APPEND DYNAMIC_PLUGINS srsran_rf_soapy) + else (ENABLE_RF_PLUGINS) + add_library(srsran_rf_soapy STATIC ${SOURCES_SOAPY}) + list(APPEND STATIC_PLUGINS srsran_rf_soapy) + endif (ENABLE_RF_PLUGINS) + target_link_libraries(srsran_rf_soapy srsran_rf_utils srsran_phy ${SOAPYSDR_LIBRARIES}) + install(TARGETS srsran_rf_soapy DESTINATION ${LIBRARY_DIR} OPTIONAL) endif (SOAPYSDR_FOUND AND ENABLE_SOAPYSDR) - if(SKIQ_FOUND) + if(SKIQ_FOUND AND ENABLE_SKIQ) add_executable(skiq_pps_test skiq_pps_test.c) target_link_libraries(skiq_pps_test ${SKIQ_LIBRARIES} rt pthread m) add_definitions(-DENABLE_SIDEKIQ) - list(APPEND SOURCES_RF rf_skiq_imp.c rf_skiq_imp_card.c rf_skiq_imp_port.c) - endif(SKIQ_FOUND) - - if (ZEROMQ_FOUND) + set(SOURCES_SKIQ rf_skiq_imp.c rf_skiq_imp_card.c rf_skiq_imp_port.c) + if (ENABLE_RF_PLUGINS) + add_library(srsran_rf_skiq SHARED ${SOURCES_SKIQ}) + set_target_properties(srsran_rf_skiq PROPERTIES VERSION ${SRSRAN_VERSION_STRING} SOVERSION ${SRSRAN_SOVERSION}) + list(APPEND DYNAMIC_PLUGINS srsran_rf_skiq) + else (ENABLE_RF_PLUGINS) + add_library(srsran_rf_skiq STATIC ${SOURCES_SKIQ}) + list(APPEND STATIC_PLUGINS srsran_rf_skiq) + endif (ENABLE_RF_PLUGINS) + target_link_libraries(srsran_rf_skiq srsran_rf_utils srsran_phy ${SKIQ_LIBRARIES} rt) + install(TARGETS srsran_rf_skiq DESTINATION ${LIBRARY_DIR} OPTIONAL) + endif(SKIQ_FOUND AND ENABLE_SKIQ) + + if (ZEROMQ_FOUND AND ENABLE_ZEROMQ) add_definitions(-DENABLE_ZEROMQ) - list(APPEND SOURCES_RF rf_zmq_imp.c rf_zmq_imp_tx.c rf_zmq_imp_rx.c) - endif (ZEROMQ_FOUND) - + set(SOURCES_ZMQ rf_zmq_imp.c rf_zmq_imp_tx.c rf_zmq_imp_rx.c) + if (ENABLE_RF_PLUGINS) + add_library(srsran_rf_zmq SHARED ${SOURCES_ZMQ}) + set_target_properties(srsran_rf_zmq PROPERTIES VERSION ${SRSRAN_VERSION_STRING} SOVERSION ${SRSRAN_SOVERSION}) + list(APPEND DYNAMIC_PLUGINS srsran_rf_zmq) + else (ENABLE_RF_PLUGINS) + add_library(srsran_rf_zmq STATIC ${SOURCES_ZMQ}) + list(APPEND STATIC_PLUGINS srsran_rf_zmq) + endif (ENABLE_RF_PLUGINS) + target_link_libraries(srsran_rf_zmq srsran_rf_utils srsran_phy ${ZEROMQ_LIBRARIES}) + install(TARGETS srsran_rf_zmq DESTINATION ${LIBRARY_DIR} OPTIONAL) + endif (ZEROMQ_FOUND AND ENABLE_ZEROMQ) + + # Add sources of file-based RF directly to the RF library (not as a plugin) list(APPEND SOURCES_RF rf_file_imp.c rf_file_imp_tx.c rf_file_imp_rx.c) + # Top-level RF library add_library(srsran_rf_object OBJECT ${SOURCES_RF}) set_property(TARGET srsran_rf_object PROPERTY POSITION_INDEPENDENT_CODE 1) - - add_library(srsran_rf STATIC $) - add_library(srsran_rf_shared SHARED $) - - target_link_libraries(srsran_rf srsran_rf_utils srsran_phy) - set_target_properties(srsran_rf PROPERTIES VERSION ${SRSRAN_VERSION_STRING} SOVERSION ${SRSRAN_SOVERSION}) - target_link_libraries(srsran_rf_shared srsran_rf_utils srsran_phy) - set_target_properties(srsran_rf_shared PROPERTIES VERSION ${SRSRAN_VERSION_STRING} SOVERSION ${SRSRAN_SOVERSION}) - - if (UHD_FOUND) - target_link_libraries(srsran_rf ${UHD_LIBRARIES} ${Boost_LIBRARIES}) # Ubuntu 18.04 requires 'system' from Boost_LIBRARIES - target_link_libraries(srsran_rf_shared ${UHD_LIBRARIES} ${Boost_LIBRARIES}) - endif (UHD_FOUND) - - if (BLADERF_FOUND) - target_link_libraries(srsran_rf ${BLADERF_LIBRARIES}) - target_link_libraries(srsran_rf_shared ${BLADERF_LIBRARIES}) - endif (BLADERF_FOUND) - - if (SOAPYSDR_FOUND AND ENABLE_SOAPYSDR) - target_link_libraries(srsran_rf ${SOAPYSDR_LIBRARIES}) - target_link_libraries(srsran_rf_shared ${SOAPYSDR_LIBRARIES}) - endif (SOAPYSDR_FOUND AND ENABLE_SOAPYSDR) - - if(SKIQ_FOUND) - target_link_libraries(srsran_rf ${SKIQ_LIBRARIES} rt) - target_link_libraries(srsran_rf_shared ${SKIQ_LIBRARIES} rt) - endif(SKIQ_FOUND) + set(TOP_RF_LIBS) + if (ENABLE_RF_PLUGINS) + # Build as shared library with optional RF plugins (optional dependencies) + if (DYNAMIC_PLUGINS) + add_dependencies(srsran_rf_object ${DYNAMIC_PLUGINS}) + endif (DYNAMIC_PLUGINS) + add_library(srsran_rf SHARED $) + target_link_libraries(srsran_rf dl) + list(APPEND TOP_RF_LIBS srsran_rf) + + # Add $ORIGIN (i.e. current location of this library) to rpath of srsran_rf. + # This ensures that it will find the plugins that reside in the same directory as the library + set_target_properties(srsran_rf PROPERTIES BUILD_RPATH "\$ORIGIN/") + set_target_properties(srsran_rf PROPERTIES INSTALL_RPATH "\$ORIGIN/") + else (ENABLE_RF_PLUGINS) + # Build as static library with built-in RF plugins (mandatory dependencies) + add_library(srsran_rf STATIC $) + target_link_libraries(srsran_rf ${STATIC_PLUGINS}) + list(APPEND TOP_RF_LIBS srsran_rf) + + # Also build as shared library with built-in RF plugins (mandatory dependencies) + add_library(srsran_rf_shared SHARED $) + target_link_libraries(srsran_rf_shared ${STATIC_PLUGINS}) + list(APPEND TOP_RF_LIBS srsran_rf_shared) + set_target_properties(srsran_rf_shared PROPERTIES OUTPUT_NAME srsran_rf) + endif (ENABLE_RF_PLUGINS) + + foreach (TOP_RF_LIB ${TOP_RF_LIBS}) + target_link_libraries(${TOP_RF_LIB} srsran_rf_utils srsran_phy) + set_target_properties(${TOP_RF_LIB} PROPERTIES VERSION ${SRSRAN_VERSION_STRING} SOVERSION ${SRSRAN_SOVERSION}) + install(TARGETS ${TOP_RF_LIB} DESTINATION ${LIBRARY_DIR} OPTIONAL) + endforeach () + + # Tests + if (UHD_FOUND AND UHD_ENABLE_CUSTOM_RFNOC) + add_executable(rfnoc_test rfnoc_test.cc) + target_link_libraries(rfnoc_test srsran_rf ${UHD_LIBRARIES} ${Boost_LIBRARIES} /usr/lib/x86_64-linux-gnu/libboost_system.so) + message(info ${Boost_LIBRARIES}) + endif (UHD_FOUND AND UHD_ENABLE_CUSTOM_RFNOC) if (ZEROMQ_FOUND) - target_link_libraries(srsran_rf ${ZEROMQ_LIBRARIES}) - target_link_libraries(srsran_rf_shared ${ZEROMQ_LIBRARIES}) add_executable(rf_zmq_test rf_zmq_test.c) target_link_libraries(rf_zmq_test srsran_rf) #add_test(rf_zmq_test rf_zmq_test) @@ -115,6 +179,4 @@ if(RF_FOUND) add_executable(rf_file_test rf_file_test.c) target_link_libraries(rf_file_test srsran_rf) add_test(rf_file_test rf_file_test) - - INSTALL(TARGETS srsran_rf DESTINATION ${LIBRARY_DIR}) endif(RF_FOUND) diff --git a/lib/src/phy/rf/rf_blade_imp.c b/lib/src/phy/rf/rf_blade_imp.c index 52cdbc8b7..301e41bbc 100644 --- a/lib/src/phy/rf/rf_blade_imp.c +++ b/lib/src/phy/rf/rf_blade_imp.c @@ -24,6 +24,7 @@ #include #include "rf_blade_imp.h" +#include "rf_plugin.h" #include "srsran/phy/common/timestamp.h" #include "srsran/phy/utils/debug.h" #include "srsran/phy/utils/vector.h" @@ -544,3 +545,44 @@ int rf_blade_send_timed(void* h, return nsamples; } + +rf_dev_t srsran_rf_dev_blade = {"bladeRF", + rf_blade_devname, + rf_blade_start_rx_stream, + rf_blade_stop_rx_stream, + rf_blade_flush_buffer, + rf_blade_has_rssi, + rf_blade_get_rssi, + rf_blade_suppress_stdout, + rf_blade_register_error_handler, + rf_blade_open, + .srsran_rf_open_multi = rf_blade_open_multi, + rf_blade_close, + rf_blade_set_rx_srate, + rf_blade_set_rx_gain, + rf_blade_set_rx_gain_ch, + rf_blade_set_tx_gain, + rf_blade_set_tx_gain_ch, + rf_blade_get_rx_gain, + rf_blade_get_tx_gain, + rf_blade_get_info, + rf_blade_set_rx_freq, + rf_blade_set_tx_srate, + rf_blade_set_tx_freq, + rf_blade_get_time, + NULL, + rf_blade_recv_with_time, + rf_blade_recv_with_time_multi, + rf_blade_send_timed, + .srsran_rf_send_timed_multi = rf_blade_send_timed_multi}; + +#ifdef ENABLE_RF_PLUGINS +int register_plugin(rf_dev_t** rf_api) +{ + if (rf_api == NULL) { + return SRSRAN_ERROR; + } + *rf_api = &srsran_rf_dev_blade; + return SRSRAN_SUCCESS; +} +#endif /* ENABLE_RF_PLUGINS */ diff --git a/lib/src/phy/rf/rf_blade_imp.h b/lib/src/phy/rf/rf_blade_imp.h index e20a7b0bb..1aa38d819 100644 --- a/lib/src/phy/rf/rf_blade_imp.h +++ b/lib/src/phy/rf/rf_blade_imp.h @@ -19,11 +19,16 @@ * */ +#ifndef SRSRAN_RF_BLADE_IMP_H_ +#define SRSRAN_RF_BLADE_IMP_H_ + #include "srsran/config.h" #include "srsran/phy/rf/rf.h" #define DEVNAME "bladerf" +extern rf_dev_t srsran_rf_dev_blade; + SRSRAN_API int rf_blade_open(char* args, void** handler); SRSRAN_API int rf_blade_open_multi(char* args, void** handler, uint32_t nof_channels); @@ -99,3 +104,5 @@ SRSRAN_API int rf_blade_send_timed(void* h, bool blocking, bool is_start_of_burst, bool is_end_of_burst); + +#endif /* SRSRAN_RF_BLADE_IMP_H_ */ diff --git a/lib/src/phy/rf/rf_dev.h b/lib/src/phy/rf/rf_dev.h index 92a6b0ad7..e6f8f68df 100644 --- a/lib/src/phy/rf/rf_dev.h +++ b/lib/src/phy/rf/rf_dev.h @@ -22,276 +22,58 @@ #include "srsran/phy/rf/rf.h" #include -/* RF frontend API */ -typedef struct { - const char* name; - const char* (*srsran_rf_devname)(void* h); - int (*srsran_rf_start_rx_stream)(void* h, bool now); - int (*srsran_rf_stop_rx_stream)(void* h); - void (*srsran_rf_flush_buffer)(void* h); - bool (*srsran_rf_has_rssi)(void* h); - float (*srsran_rf_get_rssi)(void* h); - void (*srsran_rf_suppress_stdout)(void* h); - void (*srsran_rf_register_error_handler)(void* h, srsran_rf_error_handler_t error_handler, void* arg); - int (*srsran_rf_open)(char* args, void** h); - int (*srsran_rf_open_multi)(char* args, void** h, uint32_t nof_channels); - int (*srsran_rf_close)(void* h); - double (*srsran_rf_set_rx_srate)(void* h, double freq); - int (*srsran_rf_set_rx_gain)(void* h, double gain); - int (*srsran_rf_set_rx_gain_ch)(void* h, uint32_t ch, double gain); - int (*srsran_rf_set_tx_gain)(void* h, double gain); - int (*srsran_rf_set_tx_gain_ch)(void* h, uint32_t ch, double gain); - double (*srsran_rf_get_rx_gain)(void* h); - double (*srsran_rf_get_tx_gain)(void* h); - srsran_rf_info_t* (*srsran_rf_get_info)(void* h); - double (*srsran_rf_set_rx_freq)(void* h, uint32_t ch, double freq); - double (*srsran_rf_set_tx_srate)(void* h, double freq); - double (*srsran_rf_set_tx_freq)(void* h, uint32_t ch, double freq); - void (*srsran_rf_get_time)(void* h, time_t* secs, double* frac_secs); - void (*srsran_rf_sync_pps)(void* h); - int (*srsran_rf_recv_with_time)(void* h, - void* data, - uint32_t nsamples, - bool blocking, - time_t* secs, - double* frac_secs); - int (*srsran_rf_recv_with_time_multi)(void* h, - void** data, - uint32_t nsamples, - bool blocking, - time_t* secs, - double* frac_secs); - int (*srsran_rf_send_timed)(void* h, - void* data, - int nsamples, - time_t secs, - double frac_secs, - bool has_time_spec, - bool blocking, - bool is_start_of_burst, - bool is_end_of_burst); - int (*srsran_rf_send_timed_multi)(void* h, - void** data, - int nsamples, - time_t secs, - double frac_secs, - bool has_time_spec, - bool blocking, - bool is_start_of_burst, - bool is_end_of_burst); -} rf_dev_t; - /* Define implementation for UHD */ #ifdef ENABLE_UHD - +#ifdef ENABLE_RF_PLUGINS +static srsran_rf_plugin_t plugin_uhd = {"libsrsran_rf_uhd.so", NULL, NULL}; +#else #include "rf_uhd_imp.h" - -static rf_dev_t dev_uhd = {"UHD", - rf_uhd_devname, - rf_uhd_start_rx_stream, - rf_uhd_stop_rx_stream, - rf_uhd_flush_buffer, - rf_uhd_has_rssi, - rf_uhd_get_rssi, - rf_uhd_suppress_stdout, - rf_uhd_register_error_handler, - rf_uhd_open, - .srsran_rf_open_multi = rf_uhd_open_multi, - rf_uhd_close, - rf_uhd_set_rx_srate, - rf_uhd_set_rx_gain, - rf_uhd_set_rx_gain_ch, - rf_uhd_set_tx_gain, - rf_uhd_set_tx_gain_ch, - rf_uhd_get_rx_gain, - rf_uhd_get_tx_gain, - rf_uhd_get_info, - rf_uhd_set_rx_freq, - rf_uhd_set_tx_srate, - rf_uhd_set_tx_freq, - rf_uhd_get_time, - rf_uhd_sync_pps, - rf_uhd_recv_with_time, - rf_uhd_recv_with_time_multi, - rf_uhd_send_timed, - .srsran_rf_send_timed_multi = rf_uhd_send_timed_multi}; +static srsran_rf_plugin_t plugin_uhd = {"", NULL, &srsran_rf_dev_uhd}; +#endif #endif /* Define implementation for bladeRF */ #ifdef ENABLE_BLADERF - +#ifdef ENABLE_RF_PLUGINS +static srsran_rf_plugin_t plugin_blade = {"libsrsran_rf_blade.so", NULL, NULL}; +#else #include "rf_blade_imp.h" - -static rf_dev_t dev_blade = {"bladeRF", - rf_blade_devname, - rf_blade_start_rx_stream, - rf_blade_stop_rx_stream, - rf_blade_flush_buffer, - rf_blade_has_rssi, - rf_blade_get_rssi, - rf_blade_suppress_stdout, - rf_blade_register_error_handler, - rf_blade_open, - .srsran_rf_open_multi = rf_blade_open_multi, - rf_blade_close, - rf_blade_set_rx_srate, - rf_blade_set_rx_gain, - rf_blade_set_rx_gain_ch, - rf_blade_set_tx_gain, - rf_blade_set_tx_gain_ch, - rf_blade_get_rx_gain, - rf_blade_get_tx_gain, - rf_blade_get_info, - rf_blade_set_rx_freq, - rf_blade_set_tx_srate, - rf_blade_set_tx_freq, - rf_blade_get_time, - NULL, - rf_blade_recv_with_time, - rf_blade_recv_with_time_multi, - rf_blade_send_timed, - .srsran_rf_send_timed_multi = rf_blade_send_timed_multi}; +static srsran_rf_plugin_t plugin_blade = {"", NULL, &srsran_rf_dev_blade}; +#endif #endif +/* Define implementation for SoapySDR */ #ifdef ENABLE_SOAPYSDR - +#ifdef ENABLE_RF_PLUGINS +static srsran_rf_plugin_t plugin_soapy = {"libsrsran_rf_soapy.so", NULL, NULL}; +#else #include "rf_soapy_imp.h" - -static rf_dev_t dev_soapy = {"soapy", - rf_soapy_devname, - rf_soapy_start_rx_stream, - rf_soapy_stop_rx_stream, - rf_soapy_flush_buffer, - rf_soapy_has_rssi, - rf_soapy_get_rssi, - rf_soapy_suppress_stdout, - rf_soapy_register_error_handler, - rf_soapy_open, - rf_soapy_open_multi, - rf_soapy_close, - rf_soapy_set_rx_srate, - rf_soapy_set_rx_gain, - rf_soapy_set_rx_gain_ch, - rf_soapy_set_tx_gain, - rf_soapy_set_tx_gain_ch, - rf_soapy_get_rx_gain, - rf_soapy_get_tx_gain, - rf_soapy_get_info, - rf_soapy_set_rx_freq, - rf_soapy_set_tx_srate, - rf_soapy_set_tx_freq, - rf_soapy_get_time, - NULL, - rf_soapy_recv_with_time, - rf_soapy_recv_with_time_multi, - rf_soapy_send_timed, - .srsran_rf_send_timed_multi = rf_soapy_send_timed_multi}; - +static srsran_rf_plugin_t plugin_soapy = {"", NULL, &srsran_rf_dev_soapy}; +#endif #endif -/* Define implementation for UHD */ +/* Define implementation for ZeroMQ */ #ifdef ENABLE_ZEROMQ - +#ifdef ENABLE_RF_PLUGINS +static srsran_rf_plugin_t plugin_zmq = {"libsrsran_rf_zmq.so", NULL, NULL}; +#else #include "rf_zmq_imp.h" - -static rf_dev_t dev_zmq = {"zmq", - rf_zmq_devname, - rf_zmq_start_rx_stream, - rf_zmq_stop_rx_stream, - rf_zmq_flush_buffer, - rf_zmq_has_rssi, - rf_zmq_get_rssi, - rf_zmq_suppress_stdout, - rf_zmq_register_error_handler, - rf_zmq_open, - .srsran_rf_open_multi = rf_zmq_open_multi, - rf_zmq_close, - rf_zmq_set_rx_srate, - rf_zmq_set_rx_gain, - rf_zmq_set_rx_gain_ch, - rf_zmq_set_tx_gain, - rf_zmq_set_tx_gain_ch, - rf_zmq_get_rx_gain, - rf_zmq_get_tx_gain, - rf_zmq_get_info, - rf_zmq_set_rx_freq, - rf_zmq_set_tx_srate, - rf_zmq_set_tx_freq, - rf_zmq_get_time, - NULL, - rf_zmq_recv_with_time, - rf_zmq_recv_with_time_multi, - rf_zmq_send_timed, - .srsran_rf_send_timed_multi = rf_zmq_send_timed_multi}; +static srsran_rf_plugin_t plugin_zmq = {"", NULL, &srsran_rf_dev_zmq}; +#endif #endif /* Define implementation for file-based RF */ - #include "rf_file_imp.h" - -static rf_dev_t dev_file = {"file", - rf_file_devname, - rf_file_start_rx_stream, - rf_file_stop_rx_stream, - rf_file_flush_buffer, - rf_file_has_rssi, - rf_file_get_rssi, - rf_file_suppress_stdout, - rf_file_register_error_handler, - rf_file_open, - .srsran_rf_open_multi = rf_file_open_multi, - rf_file_close, - rf_file_set_rx_srate, - rf_file_set_rx_gain, - rf_file_set_rx_gain_ch, - rf_file_set_tx_gain, - rf_file_set_tx_gain_ch, - rf_file_get_rx_gain, - rf_file_get_tx_gain, - rf_file_get_info, - rf_file_set_rx_freq, - rf_file_set_tx_srate, - rf_file_set_tx_freq, - rf_file_get_time, - NULL, - rf_file_recv_with_time, - rf_file_recv_with_time_multi, - rf_file_send_timed, - .srsran_rf_send_timed_multi = rf_file_send_timed_multi}; +static srsran_rf_plugin_t plugin_file = {"", NULL, &srsran_rf_dev_file}; /* Define implementation for Sidekiq */ #ifdef ENABLE_SIDEKIQ - +#ifdef ENABLE_RF_PLUGINS +static srsran_rf_plugin_t plugin_skiq = {"libsrsran_rf_skiq.so", NULL, NULL}; +#else #include "rf_skiq_imp.h" - -static rf_dev_t dev_skiq = {.name = "Sidekiq", - .srsran_rf_devname = rf_skiq_devname, - .srsran_rf_start_rx_stream = rf_skiq_start_rx_stream, - .srsran_rf_stop_rx_stream = rf_skiq_stop_rx_stream, - .srsran_rf_flush_buffer = rf_skiq_flush_buffer, - .srsran_rf_has_rssi = rf_skiq_has_rssi, - .srsran_rf_get_rssi = rf_skiq_get_rssi, - .srsran_rf_suppress_stdout = rf_skiq_suppress_stdout, - .srsran_rf_register_error_handler = rf_skiq_register_error_handler, - .srsran_rf_open = rf_skiq_open, - .srsran_rf_open_multi = rf_skiq_open_multi, - .srsran_rf_close = rf_skiq_close, - .srsran_rf_set_rx_srate = rf_skiq_set_rx_srate, - .srsran_rf_set_tx_srate = rf_skiq_set_tx_srate, - .srsran_rf_set_rx_gain = rf_skiq_set_rx_gain, - .srsran_rf_set_tx_gain = rf_skiq_set_tx_gain, - .srsran_rf_set_tx_gain_ch = rf_skiq_set_tx_gain_ch, - .srsran_rf_set_rx_gain_ch = rf_skiq_set_rx_gain_ch, - .srsran_rf_get_rx_gain = rf_skiq_get_rx_gain, - .srsran_rf_get_tx_gain = rf_skiq_get_tx_gain, - .srsran_rf_get_info = rf_skiq_get_info, - .srsran_rf_set_rx_freq = rf_skiq_set_rx_freq, - .srsran_rf_set_tx_freq = rf_skiq_set_tx_freq, - .srsran_rf_get_time = rf_skiq_get_time, - .srsran_rf_recv_with_time = rf_skiq_recv_with_time, - .srsran_rf_recv_with_time_multi = rf_skiq_recv_with_time_multi, - .srsran_rf_send_timed = rf_skiq_send_timed, - .srsran_rf_send_timed_multi = rf_skiq_send_timed_multi}; +static srsran_rf_plugin_t plugin_skiq = {"", NULL, &srsran_rf_dev_skiq}; +#endif #endif //#define ENABLE_DUMMY_DEV @@ -304,34 +86,36 @@ int dummy_rcv() } void dummy_fnc() {} -static rf_dev_t dev_dummy = {"dummy", dummy_fnc, dummy_fnc, dummy_fnc, dummy_fnc, dummy_fnc, dummy_fnc, dummy_fnc, - dummy_fnc, dummy_fnc, dummy_fnc, dummy_fnc, dummy_fnc, dummy_fnc, dummy_fnc, dummy_fnc, - dummy_fnc, dummy_fnc, dummy_fnc, dummy_fnc, dummy_fnc, dummy_fnc, dummy_fnc, dummy_rcv, - dummy_fnc, dummy_fnc, dummy_fnc, dummy_fnc, dummy_fnc}; +static rf_dev_t srsran_rf_dev_dummy = { + "dummy", dummy_fnc, dummy_fnc, dummy_fnc, dummy_fnc, dummy_fnc, dummy_fnc, dummy_fnc, dummy_fnc, dummy_fnc, + dummy_fnc, dummy_fnc, dummy_fnc, dummy_fnc, dummy_fnc, dummy_fnc, dummy_fnc, dummy_fnc, dummy_fnc, dummy_fnc, + dummy_fnc, dummy_fnc, dummy_fnc, dummy_rcv, dummy_fnc, dummy_fnc, dummy_fnc, dummy_fnc, dummy_fnc}; +static srsran_rf_plugin_t plugin_dummy = {"", NULL, &srsran_rf_dev_dummy}; + #endif /** - * Collection of all currently supported RF devices + * Collection of all currently available RF plugins */ -static rf_dev_t* available_devices[] = { +static srsran_rf_plugin_t* rf_plugins[] = { #ifdef ENABLE_UHD - &dev_uhd, + &plugin_uhd, #endif #ifdef ENABLE_SOAPYSDR - &dev_soapy, + &plugin_soapy, #endif #ifdef ENABLE_BLADERF - &dev_blade, + &plugin_blade, #endif #ifdef ENABLE_ZEROMQ - &dev_zmq, + &plugin_zmq, #endif #ifdef ENABLE_SIDEKIQ - &dev_skiq, + &plugin_skiq, #endif #ifdef ENABLE_DUMMY_DEV - &dev_dummy, + &plugin_dummy, #endif - &dev_file, + &plugin_file, NULL}; diff --git a/lib/src/phy/rf/rf_file_imp.c b/lib/src/phy/rf/rf_file_imp.c index 778899aef..0e8a3c0cb 100644 --- a/lib/src/phy/rf/rf_file_imp.c +++ b/lib/src/phy/rf/rf_file_imp.c @@ -19,9 +19,6 @@ * */ -#ifndef SRSRAN_RF_IMP_TRX_H -#define SRSRAN_RF_IMP_TRX_H - #include "rf_file_imp.h" #include "rf_file_imp_trx.h" #include "rf_helper.h" @@ -865,4 +862,32 @@ clean_exit: return ret; } -#endif +rf_dev_t srsran_rf_dev_file = {"file", + rf_file_devname, + rf_file_start_rx_stream, + rf_file_stop_rx_stream, + rf_file_flush_buffer, + rf_file_has_rssi, + rf_file_get_rssi, + rf_file_suppress_stdout, + rf_file_register_error_handler, + rf_file_open, + .srsran_rf_open_multi = rf_file_open_multi, + rf_file_close, + rf_file_set_rx_srate, + rf_file_set_rx_gain, + rf_file_set_rx_gain_ch, + rf_file_set_tx_gain, + rf_file_set_tx_gain_ch, + rf_file_get_rx_gain, + rf_file_get_tx_gain, + rf_file_get_info, + rf_file_set_rx_freq, + rf_file_set_tx_srate, + rf_file_set_tx_freq, + rf_file_get_time, + NULL, + rf_file_recv_with_time, + rf_file_recv_with_time_multi, + rf_file_send_timed, + .srsran_rf_send_timed_multi = rf_file_send_timed_multi}; diff --git a/lib/src/phy/rf/rf_file_imp.h b/lib/src/phy/rf/rf_file_imp.h index 7e06bdb9c..fb34896d7 100644 --- a/lib/src/phy/rf/rf_file_imp.h +++ b/lib/src/phy/rf/rf_file_imp.h @@ -32,6 +32,8 @@ #define PARAM_LEN (128) #define PARAM_LEN_SHORT (PARAM_LEN / 2) +extern rf_dev_t srsran_rf_dev_file; + SRSRAN_API const char* rf_file_devname(void* h); SRSRAN_API int rf_file_start_rx_stream(void* h, bool now); @@ -136,6 +138,7 @@ SRSRAN_API int rf_file_send_timed_multi(void* h, * @param[in] base_srate Sample rate of RX and TX files * @return SRSRAN_SUCCESS on success, otherwise error code */ -SRSRAN_API int rf_file_open_file(void** h, FILE** rx_files, FILE** tx_files, uint32_t nof_channels, uint32_t base_srate); +SRSRAN_API int +rf_file_open_file(void** h, FILE** rx_files, FILE** tx_files, uint32_t nof_channels, uint32_t base_srate); #endif // SRSRAN_RF_FILE_IMP_H diff --git a/lib/src/phy/rf/rf_imp.c b/lib/src/phy/rf/rf_imp.c index 2c9c1e109..17a840708 100644 --- a/lib/src/phy/rf/rf_imp.c +++ b/lib/src/phy/rf/rf_imp.c @@ -19,17 +19,19 @@ * */ -#include - #include "rf_dev.h" #include "srsran/phy/rf/rf.h" #include "srsran/phy/utils/debug.h" +#include +#include int rf_get_available_devices(char** devnames, int max_strlen) { int i = 0; - while (available_devices[i]->name) { - strncpy(devnames[i], available_devices[i]->name, max_strlen); + while (rf_plugins[i] != NULL) { + if (rf_plugins[i]->rf_api != NULL) { + strncpy(devnames[i], rf_plugins[i]->rf_api->name, max_strlen); + } i++; } return i; @@ -104,20 +106,22 @@ int srsran_rf_open_devname(srsran_rf_t* rf, const char* devname, char* args, uin rf->thread_gain_run = false; bool no_rf_devs_detected = true; - printf("Available RF device list:"); - for (unsigned int i = 0; available_devices[i]; i++) { + printf("Supported RF device list:"); + for (unsigned int i = 0; rf_plugins[i] && rf_plugins[i]->rf_api; i++) { no_rf_devs_detected = false; - printf(" %s ", available_devices[i]->name); + printf(" %s", rf_plugins[i]->rf_api->name); } printf("%s\n", no_rf_devs_detected ? " " : ""); // Try to open the device if name is provided if (devname && devname[0] != '\0') { int i = 0; - while (available_devices[i] != NULL) { - if (!strcasecmp(available_devices[i]->name, devname)) { - rf->dev = available_devices[i]; - return available_devices[i]->srsran_rf_open_multi(args, &rf->handler, nof_channels); + while (rf_plugins[i] != NULL) { + if (rf_plugins[i]->rf_api) { + if (!strcasecmp(rf_plugins[i]->rf_api->name, devname)) { + rf->dev = rf_plugins[i]->rf_api; + return rf_plugins[i]->rf_api->srsran_rf_open_multi(args, &rf->handler, nof_channels); + } } i++; } @@ -129,16 +133,16 @@ int srsran_rf_open_devname(srsran_rf_t* rf, const char* devname, char* args, uin return SRSRAN_ERROR; } - // auto-mode, try to open in order of apperance in available_devices[] array + // auto-mode, try to open in order of apperance in rf_plugins[] array int i = 0; - while (available_devices[i] != NULL) { - printf("Trying to open RF device '%s'\n", available_devices[i]->name); - if (!available_devices[i]->srsran_rf_open_multi(args, &rf->handler, nof_channels)) { - rf->dev = available_devices[i]; - printf("RF device '%s' successfully opened\n", available_devices[i]->name); + while (rf_plugins[i] != NULL && rf_plugins[i]->rf_api != NULL) { + printf("Trying to open RF device '%s'\n", rf_plugins[i]->rf_api->name); + if (!rf_plugins[i]->rf_api->srsran_rf_open_multi(args, &rf->handler, nof_channels)) { + rf->dev = rf_plugins[i]->rf_api; + printf("RF device '%s' successfully opened\n", rf_plugins[i]->rf_api->name); return SRSRAN_SUCCESS; } - printf("Unable to open RF device '%s'\n", available_devices[i]->name); + printf("Unable to open RF device '%s'\n", rf_plugins[i]->rf_api->name); i++; } @@ -150,7 +154,7 @@ int srsran_rf_open_devname(srsran_rf_t* rf, const char* devname, char* args, uin int srsran_rf_open_file(srsran_rf_t* rf, FILE** rx_files, FILE** tx_files, uint32_t nof_channels, uint32_t base_srate) { - rf->dev = &dev_file; + rf->dev = &srsran_rf_dev_file; // file abstraction has custom "open" function with file-related args return rf_file_open_file(&rf->handler, rx_files, tx_files, nof_channels, base_srate); @@ -400,3 +404,93 @@ int srsran_rf_send_timed2(srsran_rf_t* rf, { return srsran_rf_send_timed3(rf, data, nsamples, secs, frac_secs, true, true, is_start_of_burst, is_end_of_burst); } + +#ifdef ENABLE_RF_PLUGINS +static void unload_plugin(srsran_rf_plugin_t* rf_plugin) +{ + if (rf_plugin == NULL) { + return; + } + if (rf_plugin->dl_handle != NULL) { + rf_plugin->rf_api = NULL; + dlclose(rf_plugin->dl_handle); + rf_plugin->dl_handle = NULL; + } +} + +static int load_plugin(srsran_rf_plugin_t* rf_plugin) +{ + if (rf_plugin->rf_api != NULL) { + // already loaded + return SRSRAN_SUCCESS; + } + + rf_plugin->dl_handle = dlopen(rf_plugin->plugin_name, RTLD_NOW); + if (rf_plugin->dl_handle == NULL) { + // Not an error, if loading failed due to missing dependencies. + // Mark this plugin as not available and return SUCCESS. + INFO("Failed to load RF plugin %s: %s", rf_plugin->plugin_name, dlerror()); + rf_plugin->rf_api = NULL; + return SRSRAN_SUCCESS; + } + + // clear errors + dlerror(); + char* err = NULL; + + // load symbols + int (*register_plugin)(rf_dev_t * *rf_api) = dlsym(rf_plugin->dl_handle, "register_plugin"); + if ((err = dlerror()) != NULL) { + ERROR("Error loading symbol '%s': %s", "register_plugin", err); + goto clean_exit; + } + + // register plugin + int ret = register_plugin(&rf_plugin->rf_api); + if (ret != SRSRAN_SUCCESS) { + ERROR("Failed to register RF API for plugin %s", rf_plugin->plugin_name); + goto clean_exit; + } + return SRSRAN_SUCCESS; +clean_exit: + unload_plugin(rf_plugin); + return SRSRAN_ERROR; +} +#endif /* ENABLE_RF_PLUGINS */ + +int srsran_rf_load_plugins() +{ +#ifdef ENABLE_RF_PLUGINS + for (unsigned int i = 0; rf_plugins[i]; i++) { + if (load_plugin(rf_plugins[i]) != SRSRAN_SUCCESS) { + return SRSRAN_ERROR; + } + } + + printf("Active RF plugins:"); + for (unsigned int i = 0; rf_plugins[i]; i++) { + if (rf_plugins[i]->dl_handle != NULL) { + printf(" %s", rf_plugins[i]->plugin_name); + } + } + printf("\n"); + + printf("Inactive RF plugins:"); + for (unsigned int i = 0; rf_plugins[i]; i++) { + if (rf_plugins[i]->dl_handle == NULL) { + printf(" %s", rf_plugins[i]->plugin_name); + } + } + printf("\n"); + +#endif /* ENABLE_RF_PLUGINS */ + return SRSRAN_SUCCESS; +} + +// Search and load plugins when this library is loaded (shared) or right before main (static) +void __attribute__((constructor)) init() +{ + if (srsran_rf_load_plugins() != SRSRAN_SUCCESS) { + ERROR("Failed to load RF plugins"); + } +} diff --git a/lib/src/phy/rf/rf_plugin.h b/lib/src/phy/rf/rf_plugin.h new file mode 100644 index 000000000..177f4eef0 --- /dev/null +++ b/lib/src/phy/rf/rf_plugin.h @@ -0,0 +1,28 @@ +/** + * + * \section COPYRIGHT + * + * Copyright 2013-2022 Software Radio Systems Limited + * + * By using this file, you agree to the terms and conditions set + * forth in the LICENSE file which can be found at the top level of + * the distribution. + * + */ + +#ifndef SRSRAN_RF_PLUGIN_H +#define SRSRAN_RF_PLUGIN_H + +#include "srsran/phy/rf/rf.h" + +#ifdef __cplusplus +extern "C" { +#endif + +SRSRAN_API int register_plugin(rf_dev_t** rf_api); + +#ifdef __cplusplus +} +#endif + +#endif /* SRSRAN_RF_PLUGIN_H */ diff --git a/lib/src/phy/rf/rf_skiq_imp.c b/lib/src/phy/rf/rf_skiq_imp.c index bb61d893b..43bca3c44 100644 --- a/lib/src/phy/rf/rf_skiq_imp.c +++ b/lib/src/phy/rf/rf_skiq_imp.c @@ -24,6 +24,7 @@ #include #include "rf_helper.h" +#include "rf_plugin.h" #include "rf_skiq_imp.h" #include "rf_skiq_imp_card.h" @@ -948,3 +949,43 @@ int rf_skiq_send_timed_multi(void* h_, return (int)rpm; } + +rf_dev_t srsran_rf_dev_skiq = {.name = "Sidekiq", + .srsran_rf_devname = rf_skiq_devname, + .srsran_rf_start_rx_stream = rf_skiq_start_rx_stream, + .srsran_rf_stop_rx_stream = rf_skiq_stop_rx_stream, + .srsran_rf_flush_buffer = rf_skiq_flush_buffer, + .srsran_rf_has_rssi = rf_skiq_has_rssi, + .srsran_rf_get_rssi = rf_skiq_get_rssi, + .srsran_rf_suppress_stdout = rf_skiq_suppress_stdout, + .srsran_rf_register_error_handler = rf_skiq_register_error_handler, + .srsran_rf_open = rf_skiq_open, + .srsran_rf_open_multi = rf_skiq_open_multi, + .srsran_rf_close = rf_skiq_close, + .srsran_rf_set_rx_srate = rf_skiq_set_rx_srate, + .srsran_rf_set_tx_srate = rf_skiq_set_tx_srate, + .srsran_rf_set_rx_gain = rf_skiq_set_rx_gain, + .srsran_rf_set_tx_gain = rf_skiq_set_tx_gain, + .srsran_rf_set_tx_gain_ch = rf_skiq_set_tx_gain_ch, + .srsran_rf_set_rx_gain_ch = rf_skiq_set_rx_gain_ch, + .srsran_rf_get_rx_gain = rf_skiq_get_rx_gain, + .srsran_rf_get_tx_gain = rf_skiq_get_tx_gain, + .srsran_rf_get_info = rf_skiq_get_info, + .srsran_rf_set_rx_freq = rf_skiq_set_rx_freq, + .srsran_rf_set_tx_freq = rf_skiq_set_tx_freq, + .srsran_rf_get_time = rf_skiq_get_time, + .srsran_rf_recv_with_time = rf_skiq_recv_with_time, + .srsran_rf_recv_with_time_multi = rf_skiq_recv_with_time_multi, + .srsran_rf_send_timed = rf_skiq_send_timed, + .srsran_rf_send_timed_multi = rf_skiq_send_timed_multi}; + +#ifdef ENABLE_RF_PLUGINS +int register_plugin(rf_dev_t** rf_api) +{ + if (rf_api == NULL) { + return SRSRAN_ERROR; + } + *rf_api = &srsran_rf_dev_skiq; + return SRSRAN_SUCCESS; +} +#endif /* ENABLE_RF_PLUGINS */ diff --git a/lib/src/phy/rf/rf_skiq_imp.h b/lib/src/phy/rf/rf_skiq_imp.h index 97a5a983d..98ec06e44 100644 --- a/lib/src/phy/rf/rf_skiq_imp.h +++ b/lib/src/phy/rf/rf_skiq_imp.h @@ -25,6 +25,8 @@ #include "srsran/config.h" #include "srsran/phy/rf/rf.h" +extern rf_dev_t srsran_rf_dev_skiq; + SRSRAN_API int rf_skiq_open(char* args, void** handler); SRSRAN_API int rf_skiq_open_multi(char* args, void** handler, uint32_t nof_rx_antennas); diff --git a/lib/src/phy/rf/rf_soapy_imp.c b/lib/src/phy/rf/rf_soapy_imp.c index 1c613dcf0..b28bcd391 100644 --- a/lib/src/phy/rf/rf_soapy_imp.c +++ b/lib/src/phy/rf/rf_soapy_imp.c @@ -25,6 +25,7 @@ #include #include "rf_helper.h" +#include "rf_plugin.h" #include "rf_soapy_imp.h" #include "srsran/phy/common/phy_common.h" #include "srsran/phy/utils/debug.h" @@ -34,8 +35,8 @@ #include #include #include -#include #include +#include #define HAVE_ASYNC_THREAD 0 @@ -862,7 +863,7 @@ int rf_soapy_recv_with_time_multi(void* h, int rf_soapy_recv_with_time(void* h, void* data, uint32_t nsamples, bool blocking, time_t* secs, double* frac_secs) { void* data_multi[SRSRAN_MAX_PORTS] = {NULL}; - data_multi[0] = data; + data_multi[0] = data; return rf_soapy_recv_with_time_multi(h, data_multi, nsamples, blocking, secs, frac_secs); } @@ -1003,3 +1004,44 @@ int rf_soapy_send_timed_multi(void* h, return n; } + +rf_dev_t srsran_rf_dev_soapy = {"soapy", + rf_soapy_devname, + rf_soapy_start_rx_stream, + rf_soapy_stop_rx_stream, + rf_soapy_flush_buffer, + rf_soapy_has_rssi, + rf_soapy_get_rssi, + rf_soapy_suppress_stdout, + rf_soapy_register_error_handler, + rf_soapy_open, + rf_soapy_open_multi, + rf_soapy_close, + rf_soapy_set_rx_srate, + rf_soapy_set_rx_gain, + rf_soapy_set_rx_gain_ch, + rf_soapy_set_tx_gain, + rf_soapy_set_tx_gain_ch, + rf_soapy_get_rx_gain, + rf_soapy_get_tx_gain, + rf_soapy_get_info, + rf_soapy_set_rx_freq, + rf_soapy_set_tx_srate, + rf_soapy_set_tx_freq, + rf_soapy_get_time, + NULL, + rf_soapy_recv_with_time, + rf_soapy_recv_with_time_multi, + rf_soapy_send_timed, + .srsran_rf_send_timed_multi = rf_soapy_send_timed_multi}; + +#ifdef ENABLE_RF_PLUGINS +int register_plugin(rf_dev_t** rf_api) +{ + if (rf_api == NULL) { + return SRSRAN_ERROR; + } + *rf_api = &srsran_rf_dev_soapy; + return SRSRAN_SUCCESS; +} +#endif /* ENABLE_RF_PLUGINS */ diff --git a/lib/src/phy/rf/rf_soapy_imp.h b/lib/src/phy/rf/rf_soapy_imp.h index bbaa38135..a1c76fb64 100644 --- a/lib/src/phy/rf/rf_soapy_imp.h +++ b/lib/src/phy/rf/rf_soapy_imp.h @@ -29,6 +29,8 @@ #include #define DEVNAME_SOAPY "soapy" +extern rf_dev_t srsran_rf_dev_soapy; + SRSRAN_API int rf_soapy_open(char* args, void** handler); SRSRAN_API int rf_soapy_open_multi(char* args, void** handler, uint32_t num_requested_channels); diff --git a/lib/src/phy/rf/rf_uhd_imp.cc b/lib/src/phy/rf/rf_uhd_imp.cc index daed73062..8cf431a24 100644 --- a/lib/src/phy/rf/rf_uhd_imp.cc +++ b/lib/src/phy/rf/rf_uhd_imp.cc @@ -29,6 +29,7 @@ #include #include "rf_helper.h" +#include "rf_plugin.h" #include "srsran/phy/utils/debug.h" #include "srsran/phy/utils/vector.h" @@ -1544,3 +1545,44 @@ int rf_uhd_send_timed_multi(void* h, return nsamples; } + +rf_dev_t srsran_rf_dev_uhd = {"UHD", + rf_uhd_devname, + rf_uhd_start_rx_stream, + rf_uhd_stop_rx_stream, + rf_uhd_flush_buffer, + rf_uhd_has_rssi, + rf_uhd_get_rssi, + rf_uhd_suppress_stdout, + rf_uhd_register_error_handler, + rf_uhd_open, + rf_uhd_open_multi, + rf_uhd_close, + rf_uhd_set_rx_srate, + rf_uhd_set_rx_gain, + rf_uhd_set_rx_gain_ch, + rf_uhd_set_tx_gain, + rf_uhd_set_tx_gain_ch, + rf_uhd_get_rx_gain, + rf_uhd_get_tx_gain, + rf_uhd_get_info, + rf_uhd_set_rx_freq, + rf_uhd_set_tx_srate, + rf_uhd_set_tx_freq, + rf_uhd_get_time, + rf_uhd_sync_pps, + rf_uhd_recv_with_time, + rf_uhd_recv_with_time_multi, + rf_uhd_send_timed, + rf_uhd_send_timed_multi}; + +#ifdef ENABLE_RF_PLUGINS +int register_plugin(rf_dev_t** rf_api) +{ + if (rf_api == NULL) { + return SRSRAN_ERROR; + } + *rf_api = &srsran_rf_dev_uhd; + return SRSRAN_SUCCESS; +} +#endif /* ENABLE_RF_PLUGINS */ diff --git a/lib/src/phy/rf/rf_uhd_imp.h b/lib/src/phy/rf/rf_uhd_imp.h index 925a5d787..4a34c988e 100644 --- a/lib/src/phy/rf/rf_uhd_imp.h +++ b/lib/src/phy/rf/rf_uhd_imp.h @@ -38,6 +38,8 @@ extern "C" { #define DEVNAME_E3X0 "uhd_e3x0" #define DEVNAME_UNKNOWN "uhd_unknown" +extern rf_dev_t srsran_rf_dev_uhd; + SRSRAN_API int rf_uhd_open(char* args, void** handler); SRSRAN_API int rf_uhd_open_multi(char* args, void** handler, uint32_t nof_channels); diff --git a/lib/src/phy/rf/rf_zmq_imp.c b/lib/src/phy/rf/rf_zmq_imp.c index 2c415a684..ceebaa874 100644 --- a/lib/src/phy/rf/rf_zmq_imp.c +++ b/lib/src/phy/rf/rf_zmq_imp.c @@ -21,6 +21,7 @@ #include "rf_zmq_imp.h" #include "rf_helper.h" +#include "rf_plugin.h" #include "rf_zmq_imp_trx.h" #include #include @@ -970,7 +971,7 @@ int rf_zmq_send_timed_multi(void* h, } // Scale according to current gain - srsran_vec_sc_prod_cfc(buf, tx_gain, buf, nsamples_baseband); + srsran_vec_sc_prod_cfc(buf, tx_gain, buf, nsamples_baseband); // Finally, transmit baseband int n = rf_zmq_tx_baseband(&handler->transmitter[i], buf, nsamples_baseband); @@ -992,3 +993,44 @@ clean_exit: return ret; } + +rf_dev_t srsran_rf_dev_zmq = {"zmq", + rf_zmq_devname, + rf_zmq_start_rx_stream, + rf_zmq_stop_rx_stream, + rf_zmq_flush_buffer, + rf_zmq_has_rssi, + rf_zmq_get_rssi, + rf_zmq_suppress_stdout, + rf_zmq_register_error_handler, + rf_zmq_open, + .srsran_rf_open_multi = rf_zmq_open_multi, + rf_zmq_close, + rf_zmq_set_rx_srate, + rf_zmq_set_rx_gain, + rf_zmq_set_rx_gain_ch, + rf_zmq_set_tx_gain, + rf_zmq_set_tx_gain_ch, + rf_zmq_get_rx_gain, + rf_zmq_get_tx_gain, + rf_zmq_get_info, + rf_zmq_set_rx_freq, + rf_zmq_set_tx_srate, + rf_zmq_set_tx_freq, + rf_zmq_get_time, + NULL, + rf_zmq_recv_with_time, + rf_zmq_recv_with_time_multi, + rf_zmq_send_timed, + .srsran_rf_send_timed_multi = rf_zmq_send_timed_multi}; + +#ifdef ENABLE_RF_PLUGINS +int register_plugin(rf_dev_t** rf_api) +{ + if (rf_api == NULL) { + return SRSRAN_ERROR; + } + *rf_api = &srsran_rf_dev_zmq; + return SRSRAN_SUCCESS; +} +#endif /* ENABLE_RF_PLUGINS */ diff --git a/lib/src/phy/rf/rf_zmq_imp.h b/lib/src/phy/rf/rf_zmq_imp.h index 0df0da872..cf30b54da 100644 --- a/lib/src/phy/rf/rf_zmq_imp.h +++ b/lib/src/phy/rf/rf_zmq_imp.h @@ -30,6 +30,8 @@ #define DEVNAME_ZMQ "ZeroMQ" +extern rf_dev_t srsran_rf_dev_zmq; + SRSRAN_API int rf_zmq_open(char* args, void** handler); SRSRAN_API int rf_zmq_open_multi(char* args, void** handler, uint32_t nof_channels); diff --git a/lib/src/phy/rf/rf_zmq_test.c b/lib/src/phy/rf/rf_zmq_test.c index 5de78991f..6665abc68 100644 --- a/lib/src/phy/rf/rf_zmq_test.c +++ b/lib/src/phy/rf/rf_zmq_test.c @@ -102,12 +102,11 @@ void enb_tx_function(const char* tx_args, bool timed_tx) // send data subframe per subframe uint32_t num_txed_samples = 0; - // initial transmission without ts void* data_ptr[SRSRAN_MAX_PORTS] = {NULL}; - cf_t tx_buffer[NOF_RX_ANT][SF_LEN]; + cf_t tx_buffer[NOF_RX_ANT][SF_LEN]; for (int c = 0; c < NOF_RX_ANT; c++) { - memcpy(&tx_buffer[c], &enb_tx_buffer[c][num_txed_samples], SF_LEN * sizeof (cf_t)); + memcpy(&tx_buffer[c], &enb_tx_buffer[c][num_txed_samples], SF_LEN * sizeof(cf_t)); data_ptr[c] = &tx_buffer[c][0]; } int ret = srsran_rf_send_multi(&enb_radio, (void**)data_ptr, SF_LEN, true, true, false); @@ -125,7 +124,7 @@ void enb_tx_function(const char* tx_args, bool timed_tx) // prepare data buffer for (int c = 0; c < NOF_RX_ANT; c++) { - memcpy(&tx_buffer[c], &enb_tx_buffer[c][num_txed_samples], SF_LEN * sizeof (cf_t)); + memcpy(&tx_buffer[c], &enb_tx_buffer[c][num_txed_samples], SF_LEN * sizeof(cf_t)); data_ptr[c] = &tx_buffer[c][0]; } @@ -243,34 +242,34 @@ int param_test(const char* args_param, const int num_channels) int main() { -// // two Rx ports -// if (param_test("rx_port=ipc://dl0,rx_port1=ipc://dl1", 2)) { -// fprintf(stderr, "Param test failed!\n"); -// return SRSRAN_ERROR; -// } - -// // multiple rx ports, no channel index provided -// if (param_test("rx_port=ipc://dl0,rx_port=ipc://dl1,rx_port=ipc://dl2,rx_port=ipc://dl3,base_srate=1.92e6", 4)) { -// fprintf(stderr, "Param test failed!\n"); -// return SRSRAN_ERROR; -// } - -// // One Rx, one Tx and all generic options -// if (param_test("rx_port0=tcp://" -// "localhost:2000,rx_format=sc16,tx_format=sc16,tx_type=pub,rx_type=sub,base_srate=1.92e6,id=test", -// 1)) { -// fprintf(stderr, "Param test failed!\n"); -// return SRSRAN_ERROR; -// } - -// // 1 port, 2 antennas, MIMO freq config -// if (param_test( -// "tx_port0=tcp://*:2001,tx_port1=tcp://*:2003,rx_port0=tcp://localhost:2000,rx_port1=tcp://" -// "localhost:2002,id=ue,base_srate=23.04e6,tx_freq0=2510e6,tx_freq1=2510e6,rx_freq0=2630e6,,rx_freq1=2630e6", -// 2)) { -// fprintf(stderr, "Param test failed!\n"); -// return SRSRAN_ERROR; -// } + // // two Rx ports + // if (param_test("rx_port=ipc://dl0,rx_port1=ipc://dl1", 2)) { + // fprintf(stderr, "Param test failed!\n"); + // return SRSRAN_ERROR; + // } + + // // multiple rx ports, no channel index provided + // if (param_test("rx_port=ipc://dl0,rx_port=ipc://dl1,rx_port=ipc://dl2,rx_port=ipc://dl3,base_srate=1.92e6", 4)) { + // fprintf(stderr, "Param test failed!\n"); + // return SRSRAN_ERROR; + // } + + // // One Rx, one Tx and all generic options + // if (param_test("rx_port0=tcp://" + // "localhost:2000,rx_format=sc16,tx_format=sc16,tx_type=pub,rx_type=sub,base_srate=1.92e6,id=test", + // 1)) { + // fprintf(stderr, "Param test failed!\n"); + // return SRSRAN_ERROR; + // } + + // // 1 port, 2 antennas, MIMO freq config + // if (param_test( + // "tx_port0=tcp://*:2001,tx_port1=tcp://*:2003,rx_port0=tcp://localhost:2000,rx_port1=tcp://" + // "localhost:2002,id=ue,base_srate=23.04e6,tx_freq0=2510e6,tx_freq1=2510e6,rx_freq0=2630e6,,rx_freq1=2630e6", + // 2)) { + // fprintf(stderr, "Param test failed!\n"); + // return SRSRAN_ERROR; + // } #if NOF_RX_ANT == 1 // single tx, single rx with continuous transmissions (no timed tx) using IPC transport diff --git a/lib/src/phy/sync/test/CMakeLists.txt b/lib/src/phy/sync/test/CMakeLists.txt index 7422f3e7d..19481d599 100644 --- a/lib/src/phy/sync/test/CMakeLists.txt +++ b/lib/src/phy/sync/test/CMakeLists.txt @@ -95,13 +95,13 @@ add_executable(psss_file_test psss_file_test.c) target_link_libraries(psss_file_test srsran_phy) # SL TM 2 -add_test(sync_sl_test_tm2_p6_c_0 sync_sl_test -p 6 -d -i ${CMAKE_HOME_DIRECTORY}/lib/src/phy/phch/test/signal_sidelink_ideal_tm2_p6_c0_s1.92e6.dat) -add_test(sync_sl_test_tm2_p15_c_84 sync_sl_test -p 15 -d -i ${CMAKE_HOME_DIRECTORY}/lib/src/phy/phch/test/signal_sidelink_ideal_tm2_p15_c84_s3.84e6.dat) -add_test(sync_sl_test_tm2_p25_c_168 sync_sl_test -p 25 -d -i ${CMAKE_HOME_DIRECTORY}/lib/src/phy/phch/test/signal_sidelink_ideal_tm2_p25_c168_s7.68e6.dat) -add_test(sync_sl_test_tm2_p50_c_252 sync_sl_test -p 50 -d -i ${CMAKE_HOME_DIRECTORY}/lib/src/phy/phch/test/signal_sidelink_ideal_tm2_p50_c252_s15.36e6.dat) -add_test(sync_sl_test_tm2_p100_c_335 sync_sl_test -p 100 -d -i ${CMAKE_HOME_DIRECTORY}/lib/src/phy/phch/test/signal_sidelink_ideal_tm2_p100_c335_s30.72e6.dat) +add_test(sync_sl_test_tm2_p6_c_0 sync_sl_test -p 6 -d -i ${CMAKE_CURRENT_SOURCE_DIR}/../../phch/test/signal_sidelink_ideal_tm2_p6_c0_s1.92e6.dat) +add_test(sync_sl_test_tm2_p15_c_84 sync_sl_test -p 15 -d -i ${CMAKE_CURRENT_SOURCE_DIR}/../../phch/test/signal_sidelink_ideal_tm2_p15_c84_s3.84e6.dat) +add_test(sync_sl_test_tm2_p25_c_168 sync_sl_test -p 25 -d -i ${CMAKE_CURRENT_SOURCE_DIR}/../../phch/test/signal_sidelink_ideal_tm2_p25_c168_s7.68e6.dat) +add_test(sync_sl_test_tm2_p50_c_252 sync_sl_test -p 50 -d -i ${CMAKE_CURRENT_SOURCE_DIR}/../../phch/test/signal_sidelink_ideal_tm2_p50_c252_s15.36e6.dat) +add_test(sync_sl_test_tm2_p100_c_335 sync_sl_test -p 100 -d -i ${CMAKE_CURRENT_SOURCE_DIR}/../../phch/test/signal_sidelink_ideal_tm2_p100_c335_s30.72e6.dat) # Sample offset -add_test(sync_sl_test_tm2_p25_c_168_so sync_sl_test -p 25 -d -o 300 -i ${CMAKE_HOME_DIRECTORY}/lib/src/phy/phch/test/signal_sidelink_ideal_tm2_p25_c168_s7.68e6.dat) +add_test(sync_sl_test_tm2_p25_c_168_so sync_sl_test -p 25 -d -o 300 -i ${CMAKE_CURRENT_SOURCE_DIR}/../../phch/test/signal_sidelink_ideal_tm2_p25_c168_s7.68e6.dat) # Self-test add_test(sync_sl_test_tm2_self_test_p25_c_168 sync_sl_test -p 25 -c 168 -d) # Self-test with frequency offset @@ -110,13 +110,13 @@ add_test(sync_sl_test_tm2_self_test_p25_c_168_fo sync_sl_test -p 25 -c 168 -d -f add_test(sync_sl_test_tm2_self_test_p25_c_168_fo_so sync_sl_test -p 25 -c 168 -d -f 100 -o 3600) # SL TM 4 -add_test(sync_sl_test_tm4_p6_c_0 sync_sl_test -p 6 -t 4 -d -i ${CMAKE_HOME_DIRECTORY}/lib/src/phy/phch/test/signal_sidelink_ideal_tm4_p6_c0_size6_num1_cshift0_s1.92e6.dat) -add_test(sync_sl_test_tm4_p15_c_84 sync_sl_test -p 15 -t 4 -d -i ${CMAKE_HOME_DIRECTORY}/lib/src/phy/phch/test/signal_sidelink_ideal_tm4_p15_c84_size5_num3_cshift0_s3.84e6.dat) -add_test(sync_sl_test_tm4_p25_c_168 sync_sl_test -p 25 -t 4 -d -i ${CMAKE_HOME_DIRECTORY}/lib/src/phy/phch/test/signal_sidelink_ideal_tm4_p25_c168_size5_num5_cshift0_s7.68e6.dat) -add_test(sync_sl_test_tm4_p50_c_252 sync_sl_test -p 50 -t 4 -d -i ${CMAKE_HOME_DIRECTORY}/lib/src/phy/phch/test/signal_sidelink_ideal_tm4_p50_c252_size10_num5_cshift0_s15.36e6.dat) -#add_test(sync_sl_test_tm4_p100_c_335 sync_sl_test -p 100 -t 4 -d -i ${CMAKE_HOME_DIRECTORY}/lib/src/phy/phch/test/signal_sidelink_ideal_tm4_p100_c335_size10_num10_cshift0_s30.72e6.dat) +add_test(sync_sl_test_tm4_p6_c_0 sync_sl_test -p 6 -t 4 -d -i ${CMAKE_CURRENT_SOURCE_DIR}/../../phch/test/signal_sidelink_ideal_tm4_p6_c0_size6_num1_cshift0_s1.92e6.dat) +add_test(sync_sl_test_tm4_p15_c_84 sync_sl_test -p 15 -t 4 -d -i ${CMAKE_CURRENT_SOURCE_DIR}/../../phch/test/signal_sidelink_ideal_tm4_p15_c84_size5_num3_cshift0_s3.84e6.dat) +add_test(sync_sl_test_tm4_p25_c_168 sync_sl_test -p 25 -t 4 -d -i ${CMAKE_CURRENT_SOURCE_DIR}/../../phch/test/signal_sidelink_ideal_tm4_p25_c168_size5_num5_cshift0_s7.68e6.dat) +add_test(sync_sl_test_tm4_p50_c_252 sync_sl_test -p 50 -t 4 -d -i ${CMAKE_CURRENT_SOURCE_DIR}/../../phch/test/signal_sidelink_ideal_tm4_p50_c252_size10_num5_cshift0_s15.36e6.dat) +#add_test(sync_sl_test_tm4_p100_c_335 sync_sl_test -p 100 -t 4 -d -i ${CMAKE_CURRENT_SOURCE_DIR}/../../phch/test/signal_sidelink_ideal_tm4_p100_c335_size10_num10_cshift0_s30.72e6.dat) # Sample offset -add_test(sync_sl_test_tm4_p25_c_168_so sync_sl_test -p 25 -t 4 -d -o 300 -i ${CMAKE_HOME_DIRECTORY}/lib/src/phy/phch/test/signal_sidelink_ideal_tm4_p25_c168_size5_num5_cshift0_s7.68e6.dat ) +add_test(sync_sl_test_tm4_p25_c_168_so sync_sl_test -p 25 -t 4 -d -o 300 -i ${CMAKE_CURRENT_SOURCE_DIR}/../../phch/test/signal_sidelink_ideal_tm4_p25_c168_size5_num5_cshift0_s7.68e6.dat ) # Self-test add_test(sync_sl_test_self_test_tm4_p25_c_168 sync_sl_test -p 25 -t 4 -c 168 -d) # Self-test with frequency offset @@ -185,4 +185,4 @@ target_link_libraries(ssb_file_test srsran_phy) # Captured with command: lib/examples/usrp_capture -a type=x300,clock=external,sampling_rate=46.08e6,rx_subdev_spec=B:0 -g 20 -r 46.08e6 -n 460800 -f 3502.8e6 -o /tmp/n78.fo35028.fs2304M.data add_nr_test(ssb_file_test_tdd ssb_file_test -i ${CMAKE_CURRENT_SOURCE_DIR}/n78.fo35028.fs4608M.data -v -r 46.08e6 -f 3502.8e6 -F 3512.64e6 -n 460800 -A 500 357802 2 0 1 0) # Capture with third-party gNB on band n3 (FDD) 15kHz SSB SCS, f_s=15.36e6, f_c=1842.5e6, f_c_ssb=1842.05e6, PCI=500 -add_nr_test(ssb_file_test_fdd ssb_file_test -i ${CMAKE_CURRENT_SOURCE_DIR}/../../ue/test/ue_dl_nr_pci500_rb52_si_coreset0_idx6_s15.36e6.dat -v -r 15.36e6 -f 1842.5e6 -F 1842.05e6 -n 15360 -d fdd -s 15 -A 500 2200 0 0 0 0) \ No newline at end of file +add_nr_test(ssb_file_test_fdd ssb_file_test -i ${CMAKE_CURRENT_SOURCE_DIR}/../../ue/test/ue_dl_nr_pci500_rb52_si_coreset0_idx6_s15.36e6.dat -v -r 15.36e6 -f 1842.5e6 -F 1842.05e6 -n 15360 -d fdd -s 15 -A 500 2200 0 0 0 0) diff --git a/lib/src/phy/ue/ue_sync.c b/lib/src/phy/ue/ue_sync.c index e255e9e40..d45ba3608 100644 --- a/lib/src/phy/ue/ue_sync.c +++ b/lib/src/phy/ue/ue_sync.c @@ -339,8 +339,7 @@ int srsran_ue_sync_set_cell(srsran_ue_sync_t* q, srsran_cell_t cell) q->cell = cell; q->fft_size = srsran_symbol_sz(q->cell.nof_prb); q->sf_len = SRSRAN_SF_LEN(q->fft_size); - srsran_sync_set_cp(&q->sfind, q->cell.cp); - srsran_sync_set_cp(&q->strack, q->cell.cp); + if (cell.id == 1000) { /* If the cell is unkown, we search PSS/SSS in 5 ms */ q->nof_recv_sf = 5; @@ -376,6 +375,10 @@ int srsran_ue_sync_set_cell(srsran_ue_sync_t* q, srsran_cell_t cell) } } + // Set CP for find and track objects + srsran_sync_set_cp(&q->sfind, cell.cp); + srsran_sync_set_cp(&q->strack, cell.cp); + // When Cell ID is 1000, ue_sync receives nof_avg_find_frames frames in find state and does not go to tracking // state and is used to search a cell if (cell.id == 1000) { @@ -393,9 +396,6 @@ int srsran_ue_sync_set_cell(srsran_ue_sync_t* q, srsran_cell_t cell) srsran_sync_set_cfo_ema_alpha(&q->strack, 0.1); } else { - q->sfind.cp = cell.cp; - q->strack.cp = cell.cp; - srsran_sync_set_frame_type(&q->sfind, cell.frame_type); srsran_sync_set_frame_type(&q->strack, cell.frame_type); diff --git a/lib/src/phy/ue/ue_ul.c b/lib/src/phy/ue/ue_ul.c index 959438840..5864c1ab0 100644 --- a/lib/src/phy/ue/ue_ul.c +++ b/lib/src/phy/ue/ue_ul.c @@ -174,6 +174,25 @@ int srsran_ue_ul_set_cell(srsran_ue_ul_t* q, srsran_cell_t cell) return ret; } +int srsran_ue_ul_set_cfr(srsran_ue_ul_t* q, const srsran_cfr_cfg_t* cfr) +{ + if (q == NULL || cfr == NULL) { + ERROR("Error, invalid inputs"); + return SRSRAN_ERROR_INVALID_INPUTS; + } + + // Copy the cfr config into the UE + q->cfr_config = *cfr; + + // Set the cfr for the fft's + if (srsran_ofdm_set_cfr(&q->fft, &q->cfr_config) < SRSRAN_SUCCESS) { + ERROR("Error setting the CFR for the fft"); + return SRSRAN_ERROR; + } + + return SRSRAN_SUCCESS; +} + int srsran_ue_ul_pregen_signals(srsran_ue_ul_t* q, srsran_ue_ul_cfg_t* cfg) { if (q->signals_pregenerated) { diff --git a/lib/src/radio/CMakeLists.txt b/lib/src/radio/CMakeLists.txt index 77b6570ca..f2923d99c 100644 --- a/lib/src/radio/CMakeLists.txt +++ b/lib/src/radio/CMakeLists.txt @@ -21,7 +21,7 @@ if(RF_FOUND) add_library(srsran_radio STATIC radio.cc channel_mapping.cc) target_link_libraries(srsran_radio srsran_rf srsran_common) - INSTALL(TARGETS srsran_radio DESTINATION ${LIBRARY_DIR}) + install(TARGETS srsran_radio DESTINATION ${LIBRARY_DIR} OPTIONAL) endif(RF_FOUND) add_subdirectory(test) diff --git a/lib/src/radio/radio.cc b/lib/src/radio/radio.cc index 9324ebf1d..0a43a6938 100644 --- a/lib/src/radio/radio.cc +++ b/lib/src/radio/radio.cc @@ -665,11 +665,11 @@ void radio::set_rx_freq(const uint32_t& carrier_idx, const double& freq) // Map carrier index to physical channel if (rx_channel_mapping.allocate_freq(carrier_idx, freq)) { channel_mapping::device_mapping_t device_mapping = rx_channel_mapping.get_device_mapping(carrier_idx); - if (device_mapping.carrier_idx >= nof_channels_x_dev) { - logger.error("Invalid mapping RF channel %d to logical carrier %d on f_rx=%.1f MHz", - device_mapping.carrier_idx, + if (device_mapping.channel_idx >= nof_channels_x_dev) { + logger.error("Invalid mapping physical channel %d to logical carrier %d on f_rx=%.1f MHz (nof_channels_x_dev=%d, device_idx=%d)", + device_mapping.channel_idx, carrier_idx, - freq / 1e6); + freq / 1e6, nof_channels_x_dev, device_mapping.device_idx); return; } @@ -684,7 +684,7 @@ void radio::set_rx_freq(const uint32_t& carrier_idx, const double& freq) cur_rx_freqs[device_mapping.carrier_idx] = freq; for (uint32_t i = 0; i < nof_antennas; i++) { channel_mapping::device_mapping_t dm = rx_channel_mapping.get_device_mapping(carrier_idx, i); - if (dm.device_idx >= rf_devices.size() or dm.carrier_idx >= nof_channels_x_dev) { + if (dm.device_idx >= rf_devices.size() or dm.channel_idx >= nof_channels_x_dev) { logger.error("Invalid port mapping %d:%d to logical carrier %d on f_rx=%.1f MHz", dm.device_idx, dm.channel_idx, @@ -804,9 +804,9 @@ void radio::set_tx_freq(const uint32_t& carrier_idx, const double& freq) // Map carrier index to physical channel if (tx_channel_mapping.allocate_freq(carrier_idx, freq)) { channel_mapping::device_mapping_t device_mapping = tx_channel_mapping.get_device_mapping(carrier_idx); - if (device_mapping.carrier_idx >= nof_channels_x_dev) { - logger.error("Invalid mapping RF channel %d to logical carrier %d on f_tx=%.1f MHz", - device_mapping.carrier_idx, + if (device_mapping.channel_idx >= nof_channels_x_dev) { + logger.error("Invalid mapping physical channel %d to logical carrier %d on f_tx=%.1f MHz", + device_mapping.channel_idx, carrier_idx, freq / 1e6); return; @@ -823,7 +823,7 @@ void radio::set_tx_freq(const uint32_t& carrier_idx, const double& freq) cur_tx_freqs[device_mapping.carrier_idx] = freq; for (uint32_t i = 0; i < nof_antennas; i++) { device_mapping = tx_channel_mapping.get_device_mapping(carrier_idx, i); - if (device_mapping.device_idx >= rf_devices.size() or device_mapping.carrier_idx >= nof_channels_x_dev) { + if (device_mapping.device_idx >= rf_devices.size() or device_mapping.channel_idx >= nof_channels_x_dev) { logger.error("Invalid port mapping %d:%d to logical carrier %d on f_rx=%.1f MHz", device_mapping.device_idx, device_mapping.channel_idx, diff --git a/lib/src/rlc/CMakeLists.txt b/lib/src/rlc/CMakeLists.txt index 2df086b55..5c379467a 100644 --- a/lib/src/rlc/CMakeLists.txt +++ b/lib/src/rlc/CMakeLists.txt @@ -32,4 +32,4 @@ set(SOURCES rlc.cc add_library(srsran_rlc STATIC ${SOURCES}) target_link_libraries(srsran_rlc srsran_common ${ATOMIC_LIBS}) -INSTALL(TARGETS srsran_rlc DESTINATION ${LIBRARY_DIR}) +install(TARGETS srsran_rlc DESTINATION ${LIBRARY_DIR} OPTIONAL) diff --git a/lib/src/rlc/rlc_am_base.cc b/lib/src/rlc/rlc_am_base.cc index 67a8e6bec..3834c4497 100644 --- a/lib/src/rlc/rlc_am_base.cc +++ b/lib/src/rlc/rlc_am_base.cc @@ -86,14 +86,32 @@ bool rlc_am::configure(const rlc_config_t& cfg_) return false; } - RlcInfo("configured - t_poll_retx=%d, poll_pdu=%d, poll_byte=%d, max_retx_thresh=%d, " - "t_reordering=%d, t_status_prohibit=%d", - cfg.am.t_poll_retx, - cfg.am.poll_pdu, - cfg.am.poll_byte, - cfg.am.max_retx_thresh, - cfg.am.t_reordering, - cfg.am.t_status_prohibit); + if (cfg.rat == srsran_rat_t::lte) { + RlcInfo("AM LTE configured - t_poll_retx=%d, poll_pdu=%d, poll_byte=%d, max_retx_thresh=%d, " + "t_reordering=%d, t_status_prohibit=%d, tx_queue_length=%d", + cfg.am.t_poll_retx, + cfg.am.poll_pdu, + cfg.am.poll_byte, + cfg.am.max_retx_thresh, + cfg.am.t_reordering, + cfg.am.t_status_prohibit, + cfg.tx_queue_length); + } else if (cfg.rat == srsran_rat_t::nr) { + RlcInfo("AM NR configured - tx_sn_field_length=%d, rx_sn_field_length=%d, " + "t_poll_retx=%d, poll_pdu=%d, poll_byte=%d, " + "max_retx_thresh=%d, t_reassembly=%d, t_status_prohibit=%di, tx_queue_length=%d", + to_number(cfg.am_nr.tx_sn_field_length), + to_number(cfg.am_nr.rx_sn_field_length), + cfg.am_nr.t_poll_retx, + cfg.am_nr.poll_pdu, + cfg.am_nr.poll_byte, + cfg.am_nr.max_retx_thresh, + cfg.am_nr.t_reassembly, + cfg.am_nr.t_status_prohibit, + cfg.tx_queue_length); + } else { + RlcError("Invalid RAT at entity configuration"); + } return true; } @@ -244,6 +262,25 @@ int rlc_am::rlc_am_base_tx::write_sdu(unique_byte_buffer_t sdu) return SRSRAN_SUCCESS; } +void rlc_am::rlc_am_base_tx::discard_sdu(uint32_t discard_sn) +{ + std::lock_guard lock(mutex); + + if (!tx_enabled) { + return; + } + bool discarded = tx_sdu_queue.apply_first([&discard_sn, this](unique_byte_buffer_t& sdu) { + if (sdu != nullptr && sdu->md.pdcp_sn == discard_sn) { + tx_sdu_queue.queue.pop_func(sdu); + sdu = nullptr; + } + return false; + }); + + // Discard fails when the PDCP PDU is already in Tx window. + RlcInfo("%s PDU with PDCP_SN=%d", discarded ? "Discarding" : "Couldn't discard", discard_sn); +} + void rlc_am::rlc_am_base_tx::set_bsr_callback(bsr_callback_t callback) { bsr_callback = callback; diff --git a/lib/src/rlc/rlc_am_lte.cc b/lib/src/rlc/rlc_am_lte.cc index ab5a7bd08..612eac611 100644 --- a/lib/src/rlc/rlc_am_lte.cc +++ b/lib/src/rlc/rlc_am_lte.cc @@ -232,7 +232,7 @@ void rlc_am_lte_tx::get_buffer_state_nolock(uint32_t& n_bytes_newtx, uint32_t& n // Bytes needed for retx if (not retx_queue.empty()) { - rlc_amd_retx_t& retx = retx_queue.front(); + rlc_amd_retx_lte_t& retx = retx_queue.front(); RlcDebug("Buffer state - retx - SN=%d, Segment: %s, %d:%d", retx.sn, retx.is_segment ? "true" : "false", @@ -277,24 +277,6 @@ void rlc_am_lte_tx::get_buffer_state_nolock(uint32_t& n_bytes_newtx, uint32_t& n } } -void rlc_am_lte_tx::discard_sdu(uint32_t discard_sn) -{ - if (!tx_enabled) { - return; - } - - bool discarded = tx_sdu_queue.apply_first([&discard_sn, this](unique_byte_buffer_t& sdu) { - if (sdu != nullptr && sdu->md.pdcp_sn == discard_sn) { - tx_sdu_queue.queue.pop_func(sdu); - sdu = nullptr; - } - return false; - }); - - // Discard fails when the PDCP PDU is already in Tx window. - RlcInfo("%s PDU with PDCP_SN=%d", discarded ? "Discarding" : "Couldn't discard", discard_sn); -} - bool rlc_am_lte_tx::sdu_queue_is_full() { return tx_sdu_queue.is_full(); @@ -380,11 +362,11 @@ void rlc_am_lte_tx::retransmit_pdu(uint32_t sn) RlcInfo("Schedule SN=%d for retx", pdu.rlc_sn); - rlc_amd_retx_t& retx = retx_queue.push(); - retx.is_segment = false; - retx.so_start = 0; - retx.so_end = pdu.buf->N_bytes; - retx.sn = pdu.rlc_sn; + rlc_amd_retx_lte_t& retx = retx_queue.push(); + retx.is_segment = false; + retx.so_start = 0; + retx.so_end = pdu.buf->N_bytes; + retx.sn = pdu.rlc_sn; } /**************************************************************************** @@ -470,7 +452,7 @@ int rlc_am_lte_tx::build_retx_pdu(uint8_t* payload, uint32_t nof_bytes) return -1; } - rlc_amd_retx_t retx = retx_queue.front(); + rlc_amd_retx_lte_t retx = retx_queue.front(); // Sanity check - drop any retx SNs not present in tx_window while (not tx_window.has_sn(retx.sn)) { @@ -543,7 +525,7 @@ int rlc_am_lte_tx::build_retx_pdu(uint8_t* payload, uint32_t nof_bytes) return (ptr - payload) + tx_window[retx.sn].buf->N_bytes; } -int rlc_am_lte_tx::build_segment(uint8_t* payload, uint32_t nof_bytes, rlc_amd_retx_t retx) +int rlc_am_lte_tx::build_segment(uint8_t* payload, uint32_t nof_bytes, rlc_amd_retx_lte_t retx) { if (tx_window[retx.sn].buf == NULL) { RlcError("In build_segment: retx.sn=%d has null buffer", retx.sn); @@ -983,7 +965,7 @@ void rlc_am_lte_tx::handle_control_pdu(uint8_t* payload, uint32_t nof_bytes) pdu.retx_count++; check_sn_reached_max_retx(i); - rlc_amd_retx_t& retx = retx_queue.push(); + rlc_amd_retx_lte_t& retx = retx_queue.push(); srsran_expect(tx_window[i].rlc_sn == i, "Incorrect RLC SN=%d!=%d being accessed", tx_window[i].rlc_sn, i); retx.sn = i; retx.is_segment = false; @@ -1107,7 +1089,7 @@ void rlc_am_lte_tx::debug_state() RlcDebug("vt_a = %d, vt_ms = %d, vt_s = %d, poll_sn = %d", vt_a, vt_ms, vt_s, poll_sn); } -int rlc_am_lte_tx::required_buffer_size(const rlc_amd_retx_t& retx) +int rlc_am_lte_tx::required_buffer_size(const rlc_amd_retx_lte_t& retx) { if (!retx.is_segment) { if (tx_window.has_sn(retx.sn)) { diff --git a/lib/src/rlc/rlc_am_nr.cc b/lib/src/rlc/rlc_am_nr.cc index 087d06a14..e63fcdd5c 100644 --- a/lib/src/rlc/rlc_am_nr.cc +++ b/lib/src/rlc/rlc_am_nr.cc @@ -27,8 +27,7 @@ #include "srsran/rlc/rlc_am_nr_packing.h" #include "srsran/srslog/event_trace.h" #include - -#define RLC_AM_NR_WINDOW_SIZE 2048 +#include namespace srsran { @@ -42,18 +41,15 @@ const static uint32_t so_end_of_sdu = 0xFFFF; /*************************************************************************** * Tx subclass implementation ***************************************************************************/ -rlc_am_nr_tx::rlc_am_nr_tx(rlc_am* parent_) : parent(parent_), rlc_am_base_tx(parent_->logger) {} +rlc_am_nr_tx::rlc_am_nr_tx(rlc_am* parent_) : + parent(parent_), rlc_am_base_tx(parent_->logger), poll_retransmit_timer(parent->timers->get_unique_timer()) +{} bool rlc_am_nr_tx::configure(const rlc_config_t& cfg_) { cfg = cfg_.am_nr; rb_name = parent->rb_name; - if (cfg.tx_sn_field_length != rlc_am_nr_sn_size_t::size12bits) { - RlcWarning("RLC AM NR only supports 12 bit SN length."); - return false; - } - if (cfg_.tx_queue_length > max_tx_queue_size) { RlcError("configuring tx queue length of %d PDUs too big. Maximum value is %d.", cfg_.tx_queue_length, @@ -61,21 +57,55 @@ bool rlc_am_nr_tx::configure(const rlc_config_t& cfg_) return false; } - mod_nr = cfg.tx_sn_field_length == rlc_am_nr_sn_size_t::size12bits ? 4096 : 262144; + mod_nr = cardinality(cfg.tx_sn_field_length); + switch (cfg.tx_sn_field_length) { + case rlc_am_nr_sn_size_t::size12bits: + min_hdr_size = 2; + tx_window = std::unique_ptr >( + new rlc_ringbuffer_t); + retx_queue = std::unique_ptr >( + new pdu_retx_queue); + break; + case rlc_am_nr_sn_size_t::size18bits: + min_hdr_size = 3; + tx_window = std::unique_ptr >( + new rlc_ringbuffer_t); + retx_queue = std::unique_ptr >( + new pdu_retx_queue); + break; + default: + RlcError("attempt to configure unsupported tx_sn_field_length %s", to_string(cfg.tx_sn_field_length)); + return false; + } - min_hdr_size = cfg.tx_sn_field_length == rlc_am_nr_sn_size_t::size12bits ? 2 : 3; max_hdr_size = min_hdr_size + so_size; + // make sure Tx queue is empty before attempting to resize + empty_queue_no_lock(); + tx_sdu_queue.resize(cfg_.tx_queue_length); + + // Check timers are valid + if (not poll_retransmit_timer.is_valid()) { + RlcError("Configuring TX: timers not configured"); + return false; + } + + // Configure t_poll_retransmission timer + if (cfg.t_poll_retx > 0) { + poll_retransmit_timer.set(static_cast(cfg.t_poll_retx), + [this](uint32_t timerid) { timer_expired(timerid); }); + } + tx_enabled = true; - RlcDebug("RLC AM NR tx entity configured."); + RlcDebug("RLC AM NR configured tx entity."); return true; } bool rlc_am_nr_tx::has_data() { - return do_status() || // if we have a status PDU to transmit - tx_sdu_queue.get_n_sdus() != 1; // or if there is a SDU queued up for transmission + return do_status() || // if we have a status PDU to transmit + tx_sdu_queue.get_n_sdus() != 0 || !retx_queue->empty(); // or if there is a SDU queued up for transmission } /** @@ -98,7 +128,7 @@ uint32_t rlc_am_nr_tx::read_pdu(uint8_t* payload, uint32_t nof_bytes) RlcDebug("RLC entity not active. Not generating PDU."); return 0; } - RlcDebug("MAC opportunity - bytes=%d, tx_window size=%zu PDUs", nof_bytes, tx_window.size()); + RlcDebug("MAC opportunity - bytes=%d, tx_window size=%zu PDUs", nof_bytes, tx_window->size()); // Tx STATUS if requested if (do_status()) { @@ -114,20 +144,20 @@ uint32_t rlc_am_nr_tx::read_pdu(uint8_t* payload, uint32_t nof_bytes) } // Retransmit if required - if (not retx_queue.empty()) { - RlcInfo("Re-transmission required. Retransmission queue size: %d", retx_queue.size()); + if (not retx_queue->empty()) { + RlcInfo("Re-transmission required. Retransmission queue size: %d", retx_queue->size()); return build_retx_pdu(payload, nof_bytes); } // Send remaining segment, if it exists if (sdu_under_segmentation_sn != INVALID_RLC_SN) { - if (not tx_window.has_sn(sdu_under_segmentation_sn)) { + if (not tx_window->has_sn(sdu_under_segmentation_sn)) { sdu_under_segmentation_sn = INVALID_RLC_SN; RlcError("SDU currently being segmented does not exist in tx_window. Aborting segmentation SN=%d", sdu_under_segmentation_sn); return 0; } - return build_continuation_sdu_segment(tx_window[sdu_under_segmentation_sn], payload, nof_bytes); + return build_continuation_sdu_segment((*tx_window)[sdu_under_segmentation_sn], payload, nof_bytes); } // Check whether there is something to TX @@ -171,7 +201,8 @@ uint32_t rlc_am_nr_tx::build_new_pdu(uint8_t* payload, uint32_t nof_bytes) // insert newly assigned SN into window and use reference for in-place operations // NOTE: from now on, we can't return from this function anymore before increasing tx_next - rlc_amd_tx_pdu_nr& tx_pdu = tx_window.add_pdu(st.tx_next); + rlc_amd_tx_pdu_nr& tx_pdu = tx_window->add_pdu(st.tx_next); + tx_pdu.pdcp_sn = tx_sdu->md.pdcp_sn; tx_pdu.sdu_buf = srsran::make_byte_buffer(); if (tx_pdu.sdu_buf == nullptr) { RlcError("Couldn't allocate PDU in %s().", __FUNCTION__); @@ -191,12 +222,12 @@ uint32_t rlc_am_nr_tx::build_new_pdu(uint8_t* payload, uint32_t nof_bytes) // Prepare header rlc_am_nr_pdu_header_t hdr = {}; hdr.dc = RLC_DC_FIELD_DATA_PDU; - hdr.p = get_pdu_poll(); + hdr.p = get_pdu_poll(st.tx_next, false, tx_sdu->N_bytes); hdr.si = rlc_nr_si_field_t::full_sdu; - hdr.sn_size = rlc_am_nr_sn_size_t::size12bits; + hdr.sn_size = cfg.tx_sn_field_length; hdr.sn = st.tx_next; tx_pdu.header = hdr; - log_rlc_am_nr_pdu_header_to_string(logger.info, hdr); + log_rlc_am_nr_pdu_header_to_string(logger.info, hdr, rb_name); // Write header uint32_t len = rlc_am_nr_write_data_pdu_header(hdr, tx_sdu.get()); @@ -205,7 +236,7 @@ uint32_t rlc_am_nr_tx::build_new_pdu(uint8_t* payload, uint32_t nof_bytes) } // Update TX Next - st.tx_next = (st.tx_next + 1) % MOD; + st.tx_next = (st.tx_next + 1) % mod_nr; memcpy(payload, tx_sdu->msg, tx_sdu->N_bytes); RlcDebug("wrote RLC PDU - %d bytes", tx_sdu->N_bytes); @@ -238,21 +269,30 @@ uint32_t rlc_am_nr_tx::build_new_sdu_segment(rlc_amd_tx_pdu_nr& tx_pdu, uint8_t* // Sanity check: can this SDU be sent considering header overhead? if (nof_bytes <= min_hdr_size) { // Small header as SO is not present - RlcError("cannot build new sdu_segment, there are not enough bytes allocated to tx header plus data. nof_bytes=%d", - nof_bytes); + RlcError("cannot build new sdu_segment, there are not enough bytes allocated to tx header plus data. nof_bytes=%d, " + "min_hdr_size=%d", + nof_bytes, + min_hdr_size); return 0; } + uint32_t segment_payload_len = nof_bytes - min_hdr_size; + + // Save SDU currently being segmented + // This needs to be done before calculating the polling bit + // To make sure we check correctly that the buffers are empty. + sdu_under_segmentation_sn = st.tx_next; + // Prepare header rlc_am_nr_pdu_header_t hdr = {}; hdr.dc = RLC_DC_FIELD_DATA_PDU; - hdr.p = get_pdu_poll(); + hdr.p = get_pdu_poll(st.tx_next, false, segment_payload_len); hdr.si = rlc_nr_si_field_t::first_segment; - hdr.sn_size = rlc_am_nr_sn_size_t::size12bits; + hdr.sn_size = cfg.tx_sn_field_length; hdr.sn = st.tx_next; hdr.so = 0; tx_pdu.header = hdr; - log_rlc_am_nr_pdu_header_to_string(logger.info, hdr); + log_rlc_am_nr_pdu_header_to_string(logger.info, hdr, rb_name); // Write header uint32_t hdr_len = rlc_am_nr_write_data_pdu_header(hdr, payload); @@ -262,13 +302,9 @@ uint32_t rlc_am_nr_tx::build_new_sdu_segment(rlc_amd_tx_pdu_nr& tx_pdu, uint8_t* } // Copy PDU to payload - uint32_t segment_payload_len = nof_bytes - hdr_len; srsran_assert((hdr_len + segment_payload_len) <= nof_bytes, "Error calculating hdr_len and segment_payload_len"); memcpy(&payload[hdr_len], tx_pdu.sdu_buf->msg, segment_payload_len); - // Save SDU currently being segmented - sdu_under_segmentation_sn = st.tx_next; - // Store Segment Info rlc_amd_tx_pdu_nr::pdu_segment segment_info; segment_info.payload_len = segment_payload_len; @@ -305,9 +341,11 @@ uint32_t rlc_am_nr_tx::build_continuation_sdu_segment(rlc_amd_tx_pdu_nr& tx_pdu, } // Sanity check: can this SDU be sent considering header overhead? - if ((max_hdr_size + 1) < nof_bytes) { // Larger header size, as SO is present - RlcError("cannot build new sdu_segment, there are not enough bytes allocated to tx header plus data. nof_bytes=%d", - nof_bytes); + if (nof_bytes <= max_hdr_size) { // Larger header size, as SO is present + RlcError("cannot build new sdu_segment, there are not enough bytes allocated to tx header plus data. nof_bytes=%d, " + "max_header_size=%d", + nof_bytes, + max_hdr_size); return 0; } @@ -340,19 +378,20 @@ uint32_t rlc_am_nr_tx::build_continuation_sdu_segment(rlc_amd_tx_pdu_nr& tx_pdu, "SDU bytes left %d, nof_bytes %d, ", segment_payload_full_len, nof_bytes); - si = rlc_nr_si_field_t::last_segment; + si = rlc_nr_si_field_t::last_segment; + sdu_under_segmentation_sn = INVALID_RLC_SN; } // Prepare header rlc_am_nr_pdu_header_t hdr = {}; hdr.dc = RLC_DC_FIELD_DATA_PDU; - hdr.p = get_pdu_poll(); + hdr.p = get_pdu_poll(st.tx_next, false, segment_payload_len); hdr.si = si; - hdr.sn_size = rlc_am_nr_sn_size_t::size12bits; + hdr.sn_size = cfg.tx_sn_field_length; hdr.sn = st.tx_next; hdr.so = last_byte; tx_pdu.header = hdr; - log_rlc_am_nr_pdu_header_to_string(logger.info, hdr); + log_rlc_am_nr_pdu_header_to_string(logger.info, hdr, rb_name); // Write header uint32_t hdr_len = rlc_am_nr_write_data_pdu_header(hdr, payload); @@ -377,9 +416,8 @@ uint32_t rlc_am_nr_tx::build_continuation_sdu_segment(rlc_amd_tx_pdu_nr& tx_pdu, } else { RlcInfo("grant is large enough for full SDU." "Removing current SDU info"); - sdu_under_segmentation_sn = INVALID_RLC_SN; - // SDU is fully TX'ed. Increment TX_NEXt - st.tx_next++; + // SDU is fully TX'ed. Increment TX_NEXT + st.tx_next = (st.tx_next + 1) % mod_nr; } return hdr_len + segment_payload_len; @@ -402,31 +440,31 @@ uint32_t rlc_am_nr_tx::build_continuation_sdu_segment(rlc_amd_tx_pdu_nr& tx_pdu, uint32_t rlc_am_nr_tx::build_retx_pdu(uint8_t* payload, uint32_t nof_bytes) { // Check there is at least 1 element before calling front() - if (retx_queue.empty()) { + if (retx_queue->empty()) { RlcError("in build_retx_pdu(): retx_queue is empty"); return 0; } - rlc_amd_retx_t& retx = retx_queue.front(); + rlc_amd_retx_nr_t& retx = retx_queue->front(); // Sanity check - drop any retx SNs not present in tx_window - while (not tx_window.has_sn(retx.sn)) { + while (not tx_window->has_sn(retx.sn)) { RlcWarning("SN=%d not in tx window. Ignoring retx.", retx.sn); - retx_queue.pop(); - if (!retx_queue.empty()) { - retx = retx_queue.front(); + retx_queue->pop(); + if (!retx_queue->empty()) { + retx = retx_queue->front(); } else { RlcWarning("empty retx queue, cannot provide retx PDU"); return 0; } } - RlcDebug("RETX - SN=%d, is_segment=%s, current_so=%d, so_start=%d, so_end=%d", + RlcDebug("RETX - SN=%d, is_segment=%s, current_so=%d, so_start=%d, segment_length=%d", retx.sn, retx.is_segment ? "true" : "false", retx.current_so, retx.so_start, - retx.so_end); + retx.segment_length); // Is segmentation/re-segmentation required? bool segmentation_required = is_retx_segmentation_required(retx, nof_bytes); @@ -442,7 +480,8 @@ uint32_t rlc_am_nr_tx::build_retx_pdu(uint8_t* payload, uint32_t nof_bytes) * * The RETX PDU may be transporting a full SDU or an SDU segment. * - * \param [retx] is the retx info contained in the retx_queue. + * \param [retx] is the retx info contained in the retx_queue. This is passed by copy, to avoid + * issues when using retx after pop'ing it from the queue. * \param [payload] is a pointer to the MAC buffer that will hold the PDU segment. * \param [nof_bytes] is the number of bytes the RLC is allowed to fill. * @@ -450,19 +489,21 @@ uint32_t rlc_am_nr_tx::build_retx_pdu(uint8_t* payload, uint32_t nof_bytes) * \remark this function will not update the SI. This means that if the retx is of the last * SDU segment, the SI should already be of the `last_segment` type. */ -uint32_t rlc_am_nr_tx::build_retx_pdu_without_segmentation(rlc_amd_retx_t& retx, uint8_t* payload, uint32_t nof_bytes) +uint32_t +rlc_am_nr_tx::build_retx_pdu_without_segmentation(const rlc_amd_retx_nr_t retx, uint8_t* payload, uint32_t nof_bytes) { - srsran_assert(tx_window.has_sn(retx.sn), "Called %s without checking retx SN", __FUNCTION__); + srsran_assert(tx_window->has_sn(retx.sn), "Called %s without checking retx SN", __FUNCTION__); srsran_assert(not is_retx_segmentation_required(retx, nof_bytes), "Called %s without checking if segmentation was required", __FUNCTION__); // Get tx_pdu info from tx_window - rlc_amd_tx_pdu_nr& tx_pdu = tx_window[retx.sn]; + rlc_amd_tx_pdu_nr& tx_pdu = (*tx_window)[retx.sn]; // Get expected header and payload len uint32_t expected_hdr_len = get_retx_expected_hdr_len(retx); - uint32_t retx_payload_len = retx.is_segment ? (retx.so_end - retx.current_so) : tx_window[retx.sn].sdu_buf->N_bytes; + uint32_t retx_payload_len = retx.is_segment ? (retx.so_start + retx.segment_length - retx.current_so) + : (*tx_window)[retx.sn].sdu_buf->N_bytes; srsran_assert(nof_bytes >= (expected_hdr_len + retx_payload_len), "Called %s but segmentation is required. nof_bytes=%d, expeced_hdr_len=%d, retx_payload_len=%d", __FUNCTION__, @@ -472,18 +513,17 @@ uint32_t rlc_am_nr_tx::build_retx_pdu_without_segmentation(rlc_amd_retx_t& retx, // Log RETX info RlcDebug("SDU%scan be fully re-transmitted. SN=%d, nof_bytes=%d, expected_hdr_len=%d, " - "current_so=%d, so_start=%d, so_end=%d", + "current_so=%d, so_start=%d, segment_length=%d", retx.is_segment ? " segment " : " ", retx.sn, nof_bytes, expected_hdr_len, retx.current_so, retx.so_start, - retx.so_end); + retx.segment_length); - // Update & write header - uint32_t current_so = 0; - rlc_nr_si_field_t si = rlc_nr_si_field_t::full_sdu; + // Get RETX SN, current SO and SI + rlc_nr_si_field_t si = rlc_nr_si_field_t::full_sdu; if (retx.is_segment) { if (retx.current_so == 0) { si = rlc_nr_si_field_t::first_segment; @@ -492,40 +532,44 @@ uint32_t rlc_am_nr_tx::build_retx_pdu_without_segmentation(rlc_amd_retx_t& retx, } else { si = rlc_nr_si_field_t::last_segment; } - current_so = retx.current_so; } - rlc_am_nr_pdu_header_t new_header = tx_pdu.header; - new_header.p = 0; - new_header.si = si; - new_header.so = current_so; - uint32_t hdr_len = rlc_am_nr_write_data_pdu_header(new_header, payload); - // Write payload into PDU - uint32_t pdu_bytes = 0; + // Get RETX PDU payload size uint32_t retx_pdu_payload_size = 0; if (not retx.is_segment) { // RETX full SDU - retx_pdu_payload_size = tx_window[retx.sn].sdu_buf->N_bytes; - pdu_bytes = hdr_len + tx_window[retx.sn].sdu_buf->N_bytes; + retx_pdu_payload_size = (*tx_window)[retx.sn].sdu_buf->N_bytes; } else { // RETX SDU segment - uint32_t retx_pdu_payload_size = (retx.so_end - retx.current_so); - pdu_bytes = hdr_len + retx_pdu_payload_size; + retx_pdu_payload_size = (retx.so_start + retx.segment_length - retx.current_so); } + + // Update RETX queue. This must be done before calculating + // the polling bit, to make sure the poll bit is calculated correctly + retx_queue->pop(); + + // Write header to payload + rlc_am_nr_pdu_header_t new_header = tx_pdu.header; + new_header.si = si; + new_header.so = retx.current_so; + new_header.p = get_pdu_poll(retx.sn, true, 0); + uint32_t hdr_len = rlc_am_nr_write_data_pdu_header(new_header, payload); + + // Write SDU/SDU segment to payload + uint32_t pdu_bytes = hdr_len + retx_pdu_payload_size; srsran_assert(pdu_bytes <= nof_bytes, "Error calculating hdr_len and pdu_payload_len"); memcpy(&payload[hdr_len], &tx_pdu.sdu_buf->msg[retx.current_so], retx_pdu_payload_size); - // Update RETX queue and log - retx_queue.pop(); - RlcHexInfo(tx_window[retx.sn].sdu_buf->msg, - tx_window[retx.sn].sdu_buf->N_bytes, + // Log RETX + RlcHexInfo((*tx_window)[retx.sn].sdu_buf->msg, + (*tx_window)[retx.sn].sdu_buf->N_bytes, "Original SDU SN=%d (%d B) (attempt %d/%d)", retx.sn, - tx_window[retx.sn].sdu_buf->N_bytes, - tx_window[retx.sn].retx_count + 1, + (*tx_window)[retx.sn].sdu_buf->N_bytes, + (*tx_window)[retx.sn].retx_count + 1, cfg.max_retx_thresh); - RlcHexInfo(payload, nof_bytes, "retx PDU SN=%d (%d B)", retx.sn, nof_bytes); - log_rlc_am_nr_pdu_header_to_string(logger.debug, new_header); + RlcHexInfo(payload, pdu_bytes, "RETX PDU SN=%d (%d B)", retx.sn, pdu_bytes); + log_rlc_am_nr_pdu_header_to_string(logger.debug, new_header, rb_name); debug_state(); return pdu_bytes; @@ -541,15 +585,15 @@ uint32_t rlc_am_nr_tx::build_retx_pdu_without_segmentation(rlc_amd_retx_t& retx, * \returns the number of bytes written to the payload buffer. * \remark: This functions assumes that the SDU has already been copied to tx_pdu.sdu_buf. */ -uint32_t rlc_am_nr_tx::build_retx_pdu_with_segmentation(rlc_amd_retx_t& retx, uint8_t* payload, uint32_t nof_bytes) +uint32_t rlc_am_nr_tx::build_retx_pdu_with_segmentation(rlc_amd_retx_nr_t& retx, uint8_t* payload, uint32_t nof_bytes) { // Get tx_pdu info from tx_window - srsran_assert(tx_window.has_sn(retx.sn), "Called %s without checking retx SN", __FUNCTION__); + srsran_assert(tx_window->has_sn(retx.sn), "Called %s without checking retx SN", __FUNCTION__); srsran_assert(is_retx_segmentation_required(retx, nof_bytes), "Called %s without checking if segmentation was not required", __FUNCTION__); - rlc_amd_tx_pdu_nr& tx_pdu = tx_window[retx.sn]; + rlc_amd_tx_pdu_nr& tx_pdu = (*tx_window)[retx.sn]; // Is this an SDU segment or a full SDU? if (not retx.is_segment) { @@ -559,11 +603,11 @@ uint32_t rlc_am_nr_tx::build_retx_pdu_with_segmentation(rlc_amd_retx_t& retx, ui nof_bytes); } else { - RlcDebug("Creating SDU segment from SDU segment. SN=%d, current_so=%d, so_start=%d, so_end=%d", + RlcDebug("Creating SDU segment from SDU segment. SN=%d, current_so=%d, so_start=%d, segment_length=%d", retx.sn, retx.current_so, retx.so_start, - retx.so_end); + retx.segment_length); } uint32_t expected_hdr_len = min_hdr_size; @@ -590,65 +634,106 @@ uint32_t rlc_am_nr_tx::build_retx_pdu_with_segmentation(rlc_amd_retx_t& retx, ui // Write header rlc_am_nr_pdu_header_t hdr = tx_pdu.header; + hdr.p = get_pdu_poll(retx.sn, true, 0); hdr.so = retx.current_so; hdr.si = si; uint32_t hdr_len = rlc_am_nr_write_data_pdu_header(hdr, payload); if (hdr_len >= nof_bytes || hdr_len != expected_hdr_len) { - log_rlc_am_nr_pdu_header_to_string(logger.error, hdr); + log_rlc_am_nr_pdu_header_to_string(logger.error, hdr, rb_name); RlcError("Error writing AMD PDU header. nof_bytes=%d, hdr_len=%d", nof_bytes, hdr_len); return 0; } - log_rlc_am_nr_pdu_header_to_string(logger.info, hdr); + log_rlc_am_nr_pdu_header_to_string(logger.info, hdr, rb_name); // Copy SDU segment into payload srsran_assert((hdr_len + retx_pdu_payload_size) <= nof_bytes, "Error calculating hdr_len and segment_payload_len"); - memcpy(&payload[hdr_len], tx_pdu.sdu_buf->msg, retx_pdu_payload_size); + memcpy(&payload[hdr_len], &tx_pdu.sdu_buf->msg[retx.current_so], retx_pdu_payload_size); + + // Store PDU segment info into tx_window + RlcDebug("Updating RETX segment info. SN=%d, is_segment=%s", retx.sn, retx.is_segment ? "true" : "false"); + if (!retx.is_segment) { + // Retx is not a segment yet + rlc_amd_tx_pdu_nr::pdu_segment seg1 = {}; + seg1.so = retx.current_so; + seg1.payload_len = retx_pdu_payload_size; + rlc_amd_tx_pdu_nr::pdu_segment seg2 = {}; + seg2.so = retx.current_so + retx_pdu_payload_size; + seg2.payload_len = retx.segment_length - retx_pdu_payload_size; + tx_pdu.segment_list.push_back(seg1); + tx_pdu.segment_list.push_back(seg2); + RlcDebug("New segment: SN=%d, SO=%d len=%d", retx.sn, seg1.so, seg1.payload_len); + RlcDebug("New segment: SN=%d, SO=%d len=%d", retx.sn, seg2.so, seg2.payload_len); + } else { + // Retx is already a segment + // Find current segment in segment list. + std::list::iterator it; + for (it = tx_pdu.segment_list.begin(); it != tx_pdu.segment_list.end(); ++it) { + if (it->so == retx.current_so) { + break; + } + } + if (it != tx_pdu.segment_list.end()) { + rlc_amd_tx_pdu_nr::pdu_segment seg1 = {}; + seg1.so = it->so; + seg1.payload_len = retx_pdu_payload_size; + rlc_amd_tx_pdu_nr::pdu_segment seg2 = {}; + seg2.so = it->so + retx_pdu_payload_size; + seg2.payload_len = it->payload_len - retx_pdu_payload_size; + + std::list::iterator begin_it = tx_pdu.segment_list.erase(it); + std::list::iterator insert_it = tx_pdu.segment_list.insert(begin_it, seg2); + std::list::iterator insert_it2 = tx_pdu.segment_list.insert(insert_it, seg1); + RlcDebug("Old segment SN=%d, SO=%d len=%d", retx.sn, retx.current_so, retx.segment_length); + RlcDebug("New segment SN=%d, SO=%d len=%d", retx.sn, seg1.so, seg1.payload_len); + RlcDebug("New segment SN=%d, SO=%d len=%d", retx.sn, seg2.so, seg2.payload_len); + } else { + RlcDebug("Could not find segment. SN=%d, SO=%d length=%d", retx.sn, retx.current_so, retx.segment_length); + } + } // Update retx queue retx.is_segment = true; retx.current_so = retx.current_so + retx_pdu_payload_size; - RlcDebug("Updated RETX info. is_segment=%s, current_so=%d, so_start=%d, so_end=%d", + RlcDebug("Updated RETX info. is_segment=%s, current_so=%d, so_start=%d, segment_length=%d", retx.is_segment ? "true" : "false", retx.current_so, retx.so_start, - retx.so_end); + retx.segment_length); if (retx.current_so >= tx_pdu.sdu_buf->N_bytes) { RlcError("Current SO larger or equal to SDU size when creating SDU segment. SN=%d, current SO=%d, SO_start=%d, " - "SO_end=%d", + "segment_length=%d", retx.sn, retx.current_so, retx.so_start, - retx.so_end); + retx.segment_length); return 0; } - if (retx.current_so >= retx.so_end) { - RlcError("Current SO larger than SO end. SN=%d, current SO=%d, SO_start=%d, SO_end=%s", + if (retx.current_so >= retx.so_start + retx.segment_length) { + RlcError("Current SO larger than SO_start + segment_length. SN=%d, current SO=%d, SO_start=%d, segment_length=%s", retx.sn, retx.current_so, retx.so_start, - retx.so_end); + retx.segment_length); return 0; } - // Update SDU segment info - // TODO return hdr_len + retx_pdu_payload_size; } -bool rlc_am_nr_tx::is_retx_segmentation_required(const rlc_amd_retx_t& retx, uint32_t nof_bytes) +bool rlc_am_nr_tx::is_retx_segmentation_required(const rlc_amd_retx_nr_t& retx, uint32_t nof_bytes) { bool segmentation_required = false; if (retx.is_segment) { uint32_t expected_hdr_size = retx.current_so == 0 ? min_hdr_size : max_hdr_size; - if (nof_bytes < ((retx.so_end - retx.current_so) + expected_hdr_size)) { + if (nof_bytes < ((retx.so_start + retx.segment_length - retx.current_so) + expected_hdr_size)) { RlcInfo("Re-segmentation required for RETX. SN=%d", retx.sn); segmentation_required = true; } } else { - if (nof_bytes < (tx_window[retx.sn].sdu_buf->N_bytes + min_hdr_size)) { + if (nof_bytes < ((*tx_window)[retx.sn].sdu_buf->N_bytes + min_hdr_size)) { RlcInfo("Segmentation required for RETX. SN=%d", retx.sn); segmentation_required = true; } @@ -656,7 +741,7 @@ bool rlc_am_nr_tx::is_retx_segmentation_required(const rlc_amd_retx_t& retx, uin return segmentation_required; } -uint32_t rlc_am_nr_tx::get_retx_expected_hdr_len(const rlc_amd_retx_t& retx) +uint32_t rlc_am_nr_tx::get_retx_expected_hdr_len(const rlc_amd_retx_nr_t& retx) { uint32_t expected_hdr_len = min_hdr_size; if (retx.is_segment && retx.current_so != 0) { @@ -668,15 +753,15 @@ uint32_t rlc_am_nr_tx::get_retx_expected_hdr_len(const rlc_amd_retx_t& retx) uint32_t rlc_am_nr_tx::build_status_pdu(byte_buffer_t* payload, uint32_t nof_bytes) { RlcInfo("generating status PDU. Bytes available:%d", nof_bytes); - rlc_am_nr_status_pdu_t tx_status; - int pdu_len = rx->get_status_pdu(&tx_status, nof_bytes); + rlc_am_nr_status_pdu_t status(cfg.rx_sn_field_length); // carries status of RX entity, hence use SN length of RX + int pdu_len = rx->get_status_pdu(&status, nof_bytes); if (pdu_len == SRSRAN_ERROR) { RlcDebug("deferred status PDU. Cause: Failed to acquire rx lock"); pdu_len = 0; } else if (pdu_len > 0 && nof_bytes >= static_cast(pdu_len)) { RlcDebug("generated status PDU. Bytes:%d", pdu_len); - log_rlc_am_nr_status_pdu_to_string(logger.info, "%s tx status PDU - %s", &tx_status, rb_name); - pdu_len = rlc_am_nr_write_status_pdu(tx_status, rlc_am_nr_sn_size_t::size12bits, payload); + log_rlc_am_nr_status_pdu_to_string(logger.info, "TX status PDU - %s", &status, rb_name); + pdu_len = rlc_am_nr_write_status_pdu(status, cfg.tx_sn_field_length, payload); } else { RlcInfo("cannot tx status PDU - %d bytes available, %d bytes required", nof_bytes, pdu_len); pdu_len = 0; @@ -691,89 +776,209 @@ void rlc_am_nr_tx::handle_control_pdu(uint8_t* payload, uint32_t nof_bytes) return; } - rlc_am_nr_status_pdu_t status = {}; + std::lock_guard lock(mutex); + rlc_am_nr_status_pdu_t status(cfg.tx_sn_field_length); RlcHexDebug(payload, nof_bytes, "%s Rx control PDU", parent->rb_name); - rlc_am_nr_read_status_pdu(payload, nof_bytes, rlc_am_nr_sn_size_t::size12bits, &status); - log_rlc_am_nr_status_pdu_to_string(logger.info, "%s Rx Status PDU: %s", &status, parent->rb_name); - // Local variables for handling Status PDU will be updated with lock + rlc_am_nr_read_status_pdu(payload, nof_bytes, cfg.tx_sn_field_length, &status); + log_rlc_am_nr_status_pdu_to_string(logger.info, "RX status PDU: %s", &status, parent->rb_name); + + /** + * Section 5.3.3.3: Reception of a STATUS report + * - if the STATUS report comprises a positive or negative acknowledgement for the RLC SDU with sequence + * number equal to POLL_SN: + * - if t-PollRetransmit is running: + * - stop and reset t-PollRetransmit. + * + */ + if (tx_mod_base_nr(st.poll_sn) <= tx_mod_base_nr(status.ack_sn)) { + if (poll_retransmit_timer.is_running()) { + RlcDebug("Received ACK or NACK for POLL_SN=%d. Stopping t-PollRetransmit", st.poll_sn); + poll_retransmit_timer.stop(); + } else { + RlcDebug("Received ACK or NACK for POLL_SN=%d. t-PollRetransmit already stopped", st.poll_sn); + } + } else { + RlcDebug("POLL_SN=%d > ACK_SN=%d. Not stopping t-PollRetransmit ", st.poll_sn, status.ack_sn); + } + /* * - if the SN of the corresponding RLC SDU falls within the range * TX_Next_Ack <= SN < = the highest SN of the AMD PDU among the AMD PDUs submitted to lower layer: * - consider the RLC SDU or the RLC SDU segment for which a negative acknowledgement was received for * retransmission. */ - // Process ACKs - uint32_t stop_sn = status.N_nack == 0 + uint32_t stop_sn = status.nacks.size() == 0 ? status.ack_sn : status.nacks[0].nack_sn; // Stop processing ACKs at the first NACK, if it exists. - if (stop_sn > st.tx_next) { - RlcError("Received ACK or NACK larger than TX_NEXT. Ignoring status report"); + if (tx_mod_base_nr(stop_sn) > tx_mod_base_nr(st.tx_next)) { + RlcError("Received ACK or NACK with SN=%d larger than TX_NEXT=%d. Ignoring status report", stop_sn, st.tx_next); + info_state(); return; } - for (uint32_t sn = st.tx_next_ack; sn < stop_sn; sn++) { - if (tx_window.has_sn(sn)) { - tx_window.remove_pdu(sn); - st.tx_next_ack = sn + 1; - // TODO notify PDCP + + // Process ACKs + for (uint32_t sn = st.tx_next_ack; tx_mod_base_nr(sn) < tx_mod_base_nr(stop_sn); sn = (sn + 1) % mod_nr) { + if (tx_window->has_sn(sn)) { + notify_info_vec.push_back((*tx_window)[sn].pdcp_sn); + tx_window->remove_pdu(sn); + st.tx_next_ack = (sn + 1) % mod_nr; } else { RlcError("Missing ACKed SN from TX window"); break; } } + RlcDebug("Processed status report ACKs. ACK_SN=%d. Tx_Next_Ack=%d", status.ack_sn, st.tx_next_ack); - // Process N_acks - for (uint32_t nack_idx = 0; nack_idx < status.N_nack; nack_idx++) { - if (st.tx_next_ack <= status.nacks[nack_idx].nack_sn && status.nacks[nack_idx].nack_sn <= st.tx_next) { + // Process N_nacks + std::set retx_sn_set; // Set of PDU SNs added for retransmission (no duplicates) + for (uint32_t nack_idx = 0; nack_idx < status.nacks.size(); nack_idx++) { + if (status.nacks[nack_idx].has_nack_range) { + RlcError("Handling NACK ranges is not yet implemented. Ignoring NACK across %d SDU(s) starting from SN=%d", + status.nacks[nack_idx].nack_range, + status.nacks[nack_idx].nack_sn); + continue; + } + if (tx_mod_base_nr(st.tx_next_ack) <= tx_mod_base_nr(status.nacks[nack_idx].nack_sn) && + tx_mod_base_nr(status.nacks[nack_idx].nack_sn) <= tx_mod_base_nr(st.tx_next)) { + RlcDebug("Handling NACK for SN=%d", status.nacks[nack_idx].nack_sn); auto nack = status.nacks[nack_idx]; uint32_t nack_sn = nack.nack_sn; - if (tx_window.has_sn(nack_sn)) { - auto& pdu = tx_window[nack_sn]; + if (tx_window->has_sn(nack_sn)) { + auto& pdu = (*tx_window)[nack_sn]; if (nack.has_so) { // NACK'ing missing bytes in SDU segment. // Retransmit all SDU segments within those missing bytes. if (pdu.segment_list.empty()) { - RlcError("Received NACK with SO, but there is no segment information"); + RlcError("Received NACK with SO, but there is no segment information. SN=%d", nack_sn); + } + bool segment_found = false; + for (const rlc_amd_tx_pdu_nr::pdu_segment& segm : pdu.segment_list) { + if (segm.so >= nack.so_start && segm.so <= nack.so_end) { + if (not retx_queue->has_sn(nack_sn, segm.so)) { + rlc_amd_retx_nr_t& retx = retx_queue->push(); + retx.sn = nack_sn; + retx.is_segment = true; + retx.so_start = segm.so; + retx.current_so = segm.so; + retx.segment_length = segm.payload_len; + retx_sn_set.insert(nack_sn); + RlcInfo("Scheduled RETX of SDU segment SN=%d, so_start=%d, segment_length=%d", + retx.sn, + retx.so_start, + retx.segment_length); + } else { + RlcInfo("Skip already scheduled RETX of SDU segment SN=%d, so_start=%d, segment_length=%d", + nack_sn, + segm.so, + segm.payload_len); + } + segment_found = true; + } } - for (std::list::iterator segm = pdu.segment_list.begin(); - segm != pdu.segment_list.end(); - segm++) { - if (segm->so >= nack.so_start && segm->so < nack.so_end) { - rlc_amd_retx_t& retx = retx_queue.push(); - retx.sn = nack_sn; - retx.is_segment = true; - retx.so_start = segm->so; - retx.current_so = segm->so; - retx.so_end = segm->so + segm->payload_len; + if (!segment_found) { + RlcWarning("Could not find segment for NACK_SN=%d. SO_start=%d, SO_end=%d", + status.nacks[nack_idx].nack_sn, + nack.so_start, + nack.so_end); + for (const rlc_amd_tx_pdu_nr::pdu_segment& segm : pdu.segment_list) { + RlcDebug( + "Segments for SN=%d. SO=%d, SO_end=%d", status.nacks[nack_idx].nack_sn, segm.so, segm.payload_len); } } } else { // NACK'ing full SDU. // add to retx queue if it's not already there - if (not retx_queue.has_sn(nack_sn)) { - // increment Retx counter and inform upper layers if needed - pdu.retx_count++; - - // check_sn_reached_max_retx(nack_sn); - rlc_amd_retx_t& retx = retx_queue.push(); - retx.sn = nack_sn; - retx.is_segment = false; - retx.so_start = 0; - retx.current_so = 0; - retx.so_end = pdu.sdu_buf->N_bytes; + if (not retx_queue->has_sn(nack_sn)) { + // Have we segmented the SDU already? + if ((*tx_window)[nack_sn].segment_list.empty()) { + rlc_amd_retx_nr_t& retx = retx_queue->push(); + retx.sn = nack_sn; + retx.is_segment = false; + retx.so_start = 0; + retx.current_so = 0; + retx.segment_length = pdu.sdu_buf->N_bytes; + retx_sn_set.insert(nack_sn); + RlcInfo("Scheduled RETX of SDU SN=%d", retx.sn); + } else { + RlcInfo("Scheduled RETX of SDU SN=%d", nack_sn); + retx_sn_set.insert(nack_sn); + for (auto segm : (*tx_window)[nack_sn].segment_list) { + rlc_amd_retx_nr_t& retx = retx_queue->push(); + retx.sn = nack_sn; + retx.is_segment = true; + retx.so_start = segm.so; + retx.current_so = segm.so; + retx.segment_length = segm.payload_len; + RlcInfo("Scheduled RETX of SDU Segment. SN=%d, SO=%d, len=%d", retx.sn, segm.so, segm.payload_len); + } + } + } else { + RlcInfo("RETX queue already has NACK_SN. SDU SN=%d, Tx_Next_Ack=%d, Tx_Next=%d", + status.nacks[nack_idx].nack_sn, + st.tx_next_ack, + st.tx_next); } } - } + } else { + RlcInfo("TX window does not contain NACK_SN. SDU SN=%d, Tx_Next_Ack=%d, Tx_Next=%d", + status.nacks[nack_idx].nack_sn, + st.tx_next_ack, + st.tx_next); + } // TX window containts NACK SN + } else { + RlcInfo("RETX not in expected range. SDU SN=%d, Tx_Next_Ack=%d, Tx_Next=%d", + status.nacks[nack_idx].nack_sn, + st.tx_next_ack, + st.tx_next); + } // NACK SN within expected range + } // NACK loop + + // Process retx_count and inform upper layers if needed + for (uint32_t retx_sn : retx_sn_set) { + auto& pdu = (*tx_window)[retx_sn]; + // Increment retx_count + if (pdu.retx_count == RETX_COUNT_NOT_STARTED) { + // Set retx_count = 0 on first RE-transmission of associated SDU (38.322 Sec. 5.3.2) + pdu.retx_count = 0; + } else { + // Increment otherwise + pdu.retx_count++; } + + // Inform upper layers if needed + check_sn_reached_max_retx(retx_sn); } - /** - * Section 5.3.3.3: Reception of a STATUS report - * - if the STATUS report comprises a positive or negative acknowledgement for the RLC SDU with sequence - * number equal to POLL_SN: - * - if t-PollRetransmit is running: - * - stop and reset t-PollRetransmit. - */ + // Notify PDCP + if (not notify_info_vec.empty()) { + parent->pdcp->notify_delivery(parent->lcid, notify_info_vec); + } + notify_info_vec.clear(); +} + +/** + * Helper to check if a SN has reached the max reTx threshold + * + * Caller _must_ hold the mutex when calling the function. + * If the retx has been reached for a SN the upper layers (i.e. RRC/PDCP) will be informed. + * The SN is _not_ removed from the Tx window, so retransmissions of that SN can still occur. + * + * + * @param sn The SN of the PDU to check + */ +void rlc_am_nr_tx::check_sn_reached_max_retx(uint32_t sn) +{ + if ((*tx_window)[sn].retx_count == cfg.max_retx_thresh) { + RlcWarning("Signaling max number of reTx=%d for SN=%d", (*tx_window)[sn].retx_count, sn); + parent->rrc->max_retx_attempted(); + srsran::pdcp_sn_vector_t pdcp_sns; + pdcp_sns.push_back((*tx_window)[sn].pdcp_sn); + parent->pdcp->notify_failure(parent->lcid, pdcp_sns); + + std::lock_guard lock(parent->metrics_mutex); + parent->metrics.num_lost_pdus++; + } } uint32_t rlc_am_nr_tx::get_buffer_state() @@ -789,6 +994,11 @@ void rlc_am_nr_tx::get_buffer_state(uint32_t& n_bytes_new, uint32_t& n_bytes_pri std::lock_guard lock(mutex); RlcDebug("buffer state - do_status=%s", do_status() ? "yes" : "no"); + if (!tx_enabled) { + RlcError("get_buffer_state() failed: TX is not enabled."); + return; + } + // Bytes needed for status report if (do_status()) { n_bytes_prio += rx->get_status_pdu_length(); @@ -796,19 +1006,19 @@ void rlc_am_nr_tx::get_buffer_state(uint32_t& n_bytes_new, uint32_t& n_bytes_pri } // Bytes needed for retx - if (not retx_queue.empty()) { - rlc_amd_retx_t& retx = retx_queue.front(); + size_t n_retx = retx_queue->size(); + for (size_t i = 0; i < n_retx; i++) { + rlc_amd_retx_nr_t& retx = (*retx_queue)[i]; RlcDebug("buffer state - retx - SN=%d, Segment: %s, %d:%d", retx.sn, retx.is_segment ? "true" : "false", retx.so_start, - retx.so_end); - if (tx_window.has_sn(retx.sn)) { - int req_bytes = retx.so_end - retx.so_start; - int hdr_req_bytes = retx.is_segment ? max_hdr_size : min_hdr_size; // Segmentation not supported yet + retx.so_start + retx.segment_length - 1); + if (tx_window->has_sn(retx.sn)) { + int req_bytes = retx.segment_length; + int hdr_req_bytes = (retx.is_segment && retx.current_so != 0) ? max_hdr_size : min_hdr_size; if (req_bytes <= 0) { - RlcError("in get_buffer_state(): Removing retx with SN=%d from queue", retx.sn); - retx_queue.pop(); + RlcError("buffer state - retx - invalid length=%d for SN=%d", req_bytes, retx.sn); } else { n_bytes_prio += (req_bytes + hdr_req_bytes); RlcDebug("buffer state - retx: %d bytes", n_bytes_prio); @@ -816,7 +1026,36 @@ void rlc_am_nr_tx::get_buffer_state(uint32_t& n_bytes_new, uint32_t& n_bytes_pri } } - // Bytes needed for tx SDUs + // Bytes needed for tx of the rest of the SDU that is currently under segmentation (if any) + if (sdu_under_segmentation_sn != INVALID_RLC_SN) { + if (tx_window->has_sn(sdu_under_segmentation_sn)) { + rlc_amd_tx_pdu_nr& seg_pdu = (*tx_window)[sdu_under_segmentation_sn]; + if (not seg_pdu.segment_list.empty()) { + // obtain amount of already transmitted Bytes + const rlc_amd_tx_pdu_nr::pdu_segment& seg = seg_pdu.segment_list.back(); + uint32_t last_byte = seg.so + seg.payload_len; + if (last_byte <= seg_pdu.sdu_buf->N_bytes) { + // compute remaining bytes pending for transmission + uint32_t remaining_bytes = seg_pdu.sdu_buf->N_bytes - last_byte; + n_bytes_new += remaining_bytes + max_hdr_size; + } else { + RlcError( + "buffer state - last segment of SDU under segmentation exceeds SDU len. SDU len=%d B, last_byte=%d B", + seg_pdu.sdu_buf->N_bytes, + last_byte); + } + } else { + RlcError("buffer state - SDU under segmentation has empty segment list. Ignoring SN=%d", + sdu_under_segmentation_sn); + } + } else { + sdu_under_segmentation_sn = INVALID_RLC_SN; + RlcError("buffer state - SDU under segmentation does not exist in tx_window. Aborting segmentation SN=%d", + sdu_under_segmentation_sn); + } + } + + // Bytes needed for tx SDUs in queue uint32_t n_sdus = tx_sdu_queue.get_n_sdus(); n_bytes_new += tx_sdu_queue.size_bytes(); @@ -830,15 +1069,81 @@ void rlc_am_nr_tx::get_buffer_state(uint32_t& n_bytes_new, uint32_t& n_bytes_pri } } -uint8_t rlc_am_nr_tx::get_pdu_poll() +/* + * Check whether the polling bit needs to be set, as specified in + * TS 38.322, section 5.3.3.2 + */ +uint8_t rlc_am_nr_tx::get_pdu_poll(uint32_t sn, bool is_retx, uint32_t sdu_bytes) { + RlcDebug("Checking poll bit requirements for PDU. SN=%d, retx=%s, sdu_bytes=%d, POLL_SN=%d", + sn, + is_retx ? "true" : "false", + sdu_bytes, + st.poll_sn); + /* For each AMD PDU or AMD PDU segment that has not been previoulsy tranmitted: + * - increment PDU_WITHOUT_POLL by one; + * - increment BYTE_WITHOUT_POLL by every new byte of Data field element that it maps to the Data field of the AMD + * PDU; + * - if PDU_WITHOUT_POLL >= pollPDU; or + * - if BYTE_WITHOUT_POLL >= pollByte: + * - include a poll in the AMD PDU as described below. + */ uint8_t poll = 0; - if (cfg.poll_pdu > 0) { - if (st.pdu_without_poll >= (uint32_t)cfg.poll_pdu) { - poll = 1; - st.pdu_without_poll = 0; - } else { - st.pdu_without_poll++; + if (!is_retx) { + st.pdu_without_poll++; + st.byte_without_poll += sdu_bytes; + if (cfg.poll_pdu > 0 && st.pdu_without_poll >= (uint32_t)cfg.poll_pdu) { + poll = 1; + RlcDebug("Setting poll bit due to PollPDU. SN=%d, POLL_SN=%d", sn, st.poll_sn); + } + if (cfg.poll_byte > 0 && st.byte_without_poll >= (uint32_t)cfg.poll_byte) { + poll = 1; + RlcDebug("Setting poll bit due to PollBYTE. SN=%d, POLL_SN=%d", sn, st.poll_sn); + } + } + + /* + * - if both the transmission buffer and the retransmission buffer becomes empty + * (excluding transmitted RLC SDUs or RLC SDU segments awaiting acknowledgements) + * after the transmission of the AMD PDU; or + * - if no new RLC SDU can be transmitted after the transmission of the AMD PDU (e.g. due to window stalling); + * - include a poll in the AMD PDU as described below. + */ + if ((tx_sdu_queue.is_empty() && retx_queue->empty() && sdu_under_segmentation_sn == INVALID_RLC_SN) || + tx_window->full()) { + RlcDebug("Setting poll bit due to empty buffers/inablity to TX. SN=%d, POLL_SN=%d", sn, st.poll_sn); + poll = 1; + } + + /* + * - If poll bit is included: + * - set PDU_WITHOUT_POLL to 0; + * - set BYTE_WITHOUT_POLL to 0. + */ + if (poll == 1) { + st.pdu_without_poll = 0; + st.byte_without_poll = 0; + /* + * - set POLL_SN to the highest SN of the AMD PDU among the AMD PDUs submitted to lower layer; + * - if t-PollRetransmit is not running: + * - start t-PollRetransmit. + * - else: + * - restart t-PollRetransmit. + */ + if (!is_retx) { + // This is not an RETX, but a new transmission + // As such it should be the highest SN submitted to the lower layers + st.poll_sn = sn; + RlcDebug("Setting new POLL_SN. POLL_SN=%d", sn); + } + if (cfg.t_poll_retx > 0) { + if (not poll_retransmit_timer.is_running()) { + poll_retransmit_timer.run(); + } else { + poll_retransmit_timer.stop(); + poll_retransmit_timer.run(); + } + RlcInfo("Started t-PollRetransmit. POLL_SN=%d", st.poll_sn); } } return poll; @@ -854,17 +1159,86 @@ void rlc_am_nr_tx::reestablish() stop(); } -void rlc_am_nr_tx::discard_sdu(uint32_t discard_sn) {} - bool rlc_am_nr_tx::sdu_queue_is_full() { return false; } -void rlc_am_nr_tx::empty_queue() {} +void rlc_am_nr_tx::empty_queue() +{ + std::lock_guard lock(mutex); + empty_queue_no_lock(); +} +void rlc_am_nr_tx::empty_queue_no_lock() +{ + // deallocate all SDUs in transmit queue + while (tx_sdu_queue.size() > 0) { + unique_byte_buffer_t buf = tx_sdu_queue.read(); + } +} void rlc_am_nr_tx::stop() {} +void rlc_am_nr_tx::timer_expired(uint32_t timeout_id) +{ + std::unique_lock lock(mutex); + + // t-PollRetransmit + if (poll_retransmit_timer.is_valid() && poll_retransmit_timer.id() == timeout_id) { + RlcDebug("Poll retransmission timer expired after %dms", poll_retransmit_timer.duration()); + debug_state(); + /* + * - if both the transmission buffer and the retransmission buffer are empty + * (excluding transmitted RLC SDU or RLC SDU segment awaiting acknowledgements); or + * - if no new RLC SDU or RLC SDU segment can be transmitted (e.g. due to window stalling): + * - consider the RLC SDU with the highest SN among the RLC SDUs submitted to lower layer for + * retransmission; or + * - consider any RLC SDU which has not been positively acknowledged for retransmission. + * - include a poll in an AMD PDU as described in section 5.3.3.2. + */ + if ((tx_sdu_queue.is_empty() && retx_queue->empty()) || tx_window->full()) { + if (tx_window->empty()) { + RlcError("t-PollRetransmit expired, but the tx_window is empty. POLL_SN=%d, Tx_Next_Ack=%d, tx_window_size=%d", + st.poll_sn, + st.tx_next_ack, + tx_window->size()); + return; + } + if (not tx_window->has_sn(st.tx_next_ack)) { + RlcError("t-PollRetransmit expired, but Tx_Next_Ack is not in the tx_widow. POLL_SN=%d, Tx_Next_Ack=%d, " + "tx_window_size=%d", + st.poll_sn, + st.tx_next_ack, + tx_window->size()); + return; + } + // RETX first RLC SDU that has not been ACKed + // or first SDU segment of the first RLC SDU + // that has not been acked + rlc_amd_retx_nr_t& retx = retx_queue->push(); + retx.sn = st.tx_next_ack; + if ((*tx_window)[st.tx_next_ack].segment_list.empty()) { + // Full SDU + retx.is_segment = false; + retx.so_start = 0; + retx.segment_length = (*tx_window)[st.tx_next_ack].sdu_buf->N_bytes; + retx.current_so = 0; + } else { + // To make sure we do not mess up the segment list + // We RETX an SDU segment instead of the full SDU + // if the SDU has been segmented before. + // As we cannot know which segments have been ACKed before + // we simply RETX the first one. + retx.is_segment = true; + retx.so_start = 0; + retx.current_so = 0; + retx.segment_length = (*tx_window)[st.tx_next_ack].segment_list.begin()->payload_len; + } + } + return; + } +} + /* * Window helpers */ @@ -873,10 +1247,15 @@ uint32_t rlc_am_nr_tx::tx_mod_base_nr(uint32_t sn) const return (sn - st.tx_next_ack) % mod_nr; } +uint32_t rlc_am_nr_tx::tx_window_size() const +{ + return am_window_size(cfg.tx_sn_field_length); +} + bool rlc_am_nr_tx::inside_tx_window(uint32_t sn) const { // TX_Next_Ack <= SN < TX_Next_Ack + AM_Window_Size - return tx_mod_base_nr(sn) < RLC_AM_NR_WINDOW_SIZE; + return tx_mod_base_nr(sn) < tx_window_size(); } /* @@ -884,13 +1263,29 @@ bool rlc_am_nr_tx::inside_tx_window(uint32_t sn) const */ void rlc_am_nr_tx::debug_state() const { - RlcDebug("TX entity state: Tx_Next %d, Rx_Next_Ack %d, POLL_SN %d, PDU_WITHOUT_POLL %d, BYTE_WITHOUT_POLL %d", - st.tx_next, + RlcDebug("TX entity state: Tx_Next_Ack=%d, Tx_Next=%d, POLL_SN=%d, PDU_WITHOUT_POLL=%d, BYTE_WITHOUT_POLL=%d", st.tx_next_ack, + st.tx_next, st.poll_sn, st.pdu_without_poll, st.byte_without_poll); } + +void rlc_am_nr_tx::info_state() const +{ + RlcInfo("TX window state: SDUs %d", tx_window->size()); + RlcInfo("TX entity state: Tx_Next_Ack=%d, Tx_Next=%d, POLL_SN=%d, PDU_WITHOUT_POLL=%d, BYTE_WITHOUT_POLL=%d", + st.tx_next_ack, + st.tx_next, + st.poll_sn, + st.pdu_without_poll, + st.byte_without_poll); +} + +void rlc_am_nr_tx::debug_window() const +{ + RlcDebug("TX window state: Tx_Next_Ack=%d, Tx_Next=%d, SDUs=%d", st.tx_next_ack, st.tx_next, tx_window->size()); +} /**************************************************************************** * Rx subclass implementation ***************************************************************************/ @@ -915,10 +1310,22 @@ bool rlc_am_nr_rx::configure(const rlc_config_t& cfg_) // Configure t_reassembly timer if (cfg.t_reassembly > 0) { reassembly_timer.set(static_cast(cfg.t_reassembly), [this](uint32_t timerid) { timer_expired(timerid); }); - RlcInfo("configured reassembly timer. t-Reassembly=%d ms", cfg.t_reassembly); } - mod_nr = (cfg.rx_sn_field_length == rlc_am_nr_sn_size_t::size12bits) ? 4096 : 262144; + mod_nr = cardinality(cfg.rx_sn_field_length); + switch (cfg.rx_sn_field_length) { + case rlc_am_nr_sn_size_t::size12bits: + rx_window = std::unique_ptr >( + new rlc_ringbuffer_t); + break; + case rlc_am_nr_sn_size_t::size18bits: + rx_window = std::unique_ptr >( + new rlc_ringbuffer_t); + break; + default: + RlcError("attempt to configure unsupported rx_sn_field_length %s", to_string(cfg.rx_sn_field_length)); + return false; + } RlcDebug("RLC AM NR configured rx entity."); @@ -934,21 +1341,23 @@ void rlc_am_nr_rx::reestablish() void rlc_am_nr_rx::handle_data_pdu(uint8_t* payload, uint32_t nof_bytes) { + std::lock_guard lock(mutex); + // Get AMD PDU Header - rlc_am_nr_pdu_header_t header = {}; - uint32_t hdr_len = rlc_am_nr_read_data_pdu_header(payload, nof_bytes, rlc_am_nr_sn_size_t::size12bits, &header); + rlc_am_nr_pdu_header_t header = {}; + uint32_t hdr_len = rlc_am_nr_read_data_pdu_header(payload, nof_bytes, cfg.rx_sn_field_length, &header); RlcHexInfo(payload, nof_bytes, "Rx data PDU SN=%d (%d B)", header.sn, nof_bytes); - log_rlc_am_nr_pdu_header_to_string(logger.debug, header); + log_rlc_am_nr_pdu_header_to_string(logger.debug, header, rb_name); // Check whether SDU is within Rx Window if (!inside_rx_window(header.sn)) { - RlcInfo("SN=%d outside rx window [%d:%d] - discarding", header.sn, st.rx_next, st.rx_next + RLC_AM_NR_WINDOW_SIZE); + RlcInfo("SN=%d outside rx window [%d:%d] - discarding", header.sn, st.rx_next, st.rx_next + rx_window_size()); return; } // Section 5.2.3.2.2, discard duplicate PDUs - if (rx_window.has_sn(header.sn) && rx_window[header.sn].fully_received) { + if (rx_window->has_sn(header.sn) && (*rx_window)[header.sn].fully_received) { RlcInfo("discarding duplicate SN=%d", header.sn); return; } @@ -970,63 +1379,75 @@ void rlc_am_nr_rx::handle_data_pdu(uint8_t* payload, uint32_t nof_bytes) if (header.p) { RlcInfo("status packet requested through polling bit"); do_status = true; - status_prohibit_timer.stop(); } debug_state(); // 5.2.3.2.3 Actions when an AMD PDU is placed in the reception buffer - // Update Rx_Next_Highest + /* + * - if x >= RX_Next_Highest + * - update RX_Next_Highest to x+ 1. + */ if (rx_mod_base_nr(header.sn) >= rx_mod_base_nr(st.rx_next_highest)) { - st.rx_next_highest = (header.sn + 1) % MOD; + st.rx_next_highest = (header.sn + 1) % mod_nr; } - // Update RX_Highest_Status /* - * - if x = RX_Highest_Status, - * - update RX_Highest_Status to the SN of the first RLC SDU with SN > current RX_Highest_Status for which not - * all bytes have been received. + * - if all bytes of the RLC SDU with SN = x are received: */ - if (rx_mod_base_nr(header.sn) == rx_mod_base_nr(st.rx_highest_status)) { - uint32_t sn_upd = 0; - uint32_t window_top = st.rx_next + RLC_AM_WINDOW_SIZE; - for (sn_upd = st.rx_highest_status; sn_upd < window_top; ++sn_upd) { - if (rx_window.has_sn(sn_upd)) { - if (not rx_window[sn_upd].fully_received) { + if (rx_window->has_sn(header.sn) && (*rx_window)[header.sn].fully_received) { + /* + * - reassemble the RLC SDU from AMD PDU(s) with SN = x, remove RLC headers when doing so and deliver + * the reassembled RLC SDU to upper layer; + */ + write_to_upper_layers(parent->lcid, std::move((*rx_window)[header.sn].buf)); + + /* + * - if x = RX_Highest_Status, + * - update RX_Highest_Status to the SN of the first RLC SDU with SN > current RX_Highest_Status for which not + * all bytes have been received. + */ + if (rx_mod_base_nr(header.sn) == rx_mod_base_nr(st.rx_highest_status)) { + uint32_t sn_upd = 0; + for (sn_upd = (st.rx_highest_status + 1) % mod_nr; rx_mod_base_nr(sn_upd) < rx_mod_base_nr(st.rx_next_highest); + sn_upd = (sn_upd + 1) % mod_nr) { + if (rx_window->has_sn(sn_upd)) { + if (not(*rx_window)[sn_upd].fully_received) { + break; // first SDU not fully received + } + } else { break; // first SDU not fully received } - } else { - break; // first SDU not fully received } + // Update to the SN of the first SDU with missing bytes. + // If it not exists, update to the end of the rx_window. + st.rx_highest_status = sn_upd; } - // Update to the SN of the first SDU with missing bytes. - // If it not exists, update to the end of the rx_window. - st.rx_highest_status = sn_upd; - } - - /* - * - if x = RX_Next: - * - update RX_Next to the SN of the first RLC SDU with SN > current RX_Next for which not all bytes - * have been received. - */ - if (rx_mod_base_nr(header.sn) == rx_mod_base_nr(st.rx_next)) { - uint32_t sn_upd = 0; - uint32_t window_top = st.rx_next + RLC_AM_WINDOW_SIZE; - for (sn_upd = st.rx_next; sn_upd < window_top; ++sn_upd) { - if (rx_window.has_sn(sn_upd)) { - if (not rx_window[sn_upd].fully_received) { + /* + * - if x = RX_Next: + * - update RX_Next to the SN of the first RLC SDU with SN > current RX_Next for which not all bytes + * have been received. + */ + if (rx_mod_base_nr(header.sn) == rx_mod_base_nr(st.rx_next)) { + uint32_t sn_upd = 0; + // move rx_next forward and remove all fully received SDUs from rx_window + for (sn_upd = (st.rx_next) % mod_nr; rx_mod_base_nr(sn_upd) < rx_mod_base_nr(st.rx_next_highest); + sn_upd = (sn_upd + 1) % mod_nr) { + if (rx_window->has_sn(sn_upd)) { + if (not(*rx_window)[sn_upd].fully_received) { + break; // first SDU not fully received + } + // RX_Next serves as the lower edge of the receiving window + // As such, we remove any SDU from the window if we update this value + rx_window->remove_pdu(sn_upd); + } else { break; // first SDU not fully received } - // RX_Next serves as the lower edge of the receiving window - // As such, we remove any SDU from the window if we update this value - rx_window.remove_pdu(sn_upd); - } else { - break; // first SDU not fully received } + // Update to the SN of the first SDU with missing bytes. + // If it not exists, update to the end of the rx_window. + st.rx_next = sn_upd; } - // Update to the SN of the first SDU with missing bytes. - // If it not exists, update to the end of the rx_window. - st.rx_next = sn_upd; } if (reassembly_timer.is_running()) { @@ -1039,7 +1460,25 @@ void rlc_am_nr_rx::handle_data_pdu(uint8_t* payload, uint32_t nof_bytes) * to RX_Next + AM_Window_Size: * - stop and reset t-Reassembly. */ - } else { + bool stop_reassembly_timer = false; + if (st.rx_next_status_trigger == st.rx_next) { + stop_reassembly_timer = true; + } + if (rx_mod_base_nr(st.rx_next_status_trigger) == rx_mod_base_nr(st.rx_next + 1)) { + if (not(*rx_window)[st.rx_next].has_gap) { + stop_reassembly_timer = true; + } + } + if (not inside_rx_window(st.rx_next_status_trigger)) { + stop_reassembly_timer = true; + } + if (stop_reassembly_timer) { + reassembly_timer.stop(); + } + } + + if (not reassembly_timer.is_running()) { + // if t-Reassembly is not running (includes the case t-Reassembly is stopped due to actions above): /* * - if RX_Next_Highest> RX_Next +1; or * - if RX_Next_Highest = RX_Next + 1 and there is at least one missing byte segment of the SDU associated @@ -1048,12 +1487,13 @@ void rlc_am_nr_rx::handle_data_pdu(uint8_t* payload, uint32_t nof_bytes) * - set RX_Next_Status_Trigger to RX_Next_Highest. */ bool restart_reassembly_timer = false; - if (st.rx_next_highest > st.rx_next + 1) { + if (rx_mod_base_nr(st.rx_next_highest) > rx_mod_base_nr(st.rx_next + 1)) { restart_reassembly_timer = true; } - if (st.rx_next_highest == st.rx_next + 1 && rx_window.has_sn(st.rx_next + 1) && - not rx_window[st.rx_next + 1].fully_received) { - restart_reassembly_timer = true; + if (rx_mod_base_nr(st.rx_next_highest) == rx_mod_base_nr(st.rx_next + 1)) { + if (rx_window->has_sn(st.rx_next) && (*rx_window)[st.rx_next].has_gap) { + restart_reassembly_timer = true; + } } if (restart_reassembly_timer) { reassembly_timer.run(); @@ -1069,11 +1509,11 @@ int rlc_am_nr_rx::handle_full_data_sdu(const rlc_am_nr_pdu_header_t& header, con { uint32_t hdr_len = rlc_am_nr_packed_length(header); // Full SDU received. Add SDU to Rx Window and copy full PDU into SDU buffer. - rlc_amd_rx_sdu_nr_t& rx_sdu = rx_window.add_pdu(header.sn); + rlc_amd_rx_sdu_nr_t& rx_sdu = rx_window->add_pdu(header.sn); rx_sdu.buf = srsran::make_byte_buffer(); if (rx_sdu.buf == nullptr) { RlcError("fatal error. Couldn't allocate PDU in %s.", __FUNCTION__); - rx_window.remove_pdu(header.sn); + rx_window->remove_pdu(header.sn); return SRSRAN_ERROR; } rx_sdu.buf->set_timestamp(); @@ -1081,13 +1521,13 @@ int rlc_am_nr_rx::handle_full_data_sdu(const rlc_am_nr_pdu_header_t& header, con // check available space for payload if (nof_bytes > rx_sdu.buf->get_tailroom()) { RlcError("discarding SN=%d of size %d B (available space %d B)", header.sn, nof_bytes, rx_sdu.buf->get_tailroom()); - rx_window.remove_pdu(header.sn); + rx_window->remove_pdu(header.sn); return SRSRAN_ERROR; } memcpy(rx_sdu.buf->msg, payload + hdr_len, nof_bytes - hdr_len); // Don't copy header rx_sdu.buf->N_bytes = nof_bytes - hdr_len; rx_sdu.fully_received = true; - write_to_upper_layers(parent->lcid, std::move(rx_window[header.sn].buf)); + rx_sdu.has_gap = false; return SRSRAN_SUCCESS; } @@ -1112,7 +1552,7 @@ int rlc_am_nr_rx::handle_segment_data_sdu(const rlc_am_nr_pdu_header_t& header, } // Add a new SDU to the RX window if necessary - rlc_amd_rx_sdu_nr_t& rx_sdu = rx_window.has_sn(header.sn) ? rx_window[header.sn] : rx_window.add_pdu(header.sn); + rlc_amd_rx_sdu_nr_t& rx_sdu = rx_window->has_sn(header.sn) ? (*rx_window)[header.sn] : rx_window->add_pdu(header.sn); // Create PDU segment info, to be stored later rlc_amd_rx_pdu_nr pdu_segment = {}; @@ -1129,20 +1569,20 @@ int rlc_am_nr_rx::handle_segment_data_sdu(const rlc_am_nr_pdu_header_t& header, insert_received_segment(std::move(pdu_segment), rx_sdu.segments); // Check weather all segments have been received - rx_sdu.fully_received = have_all_segments_been_received(rx_sdu.segments); + update_segment_inventory(rx_sdu); if (rx_sdu.fully_received) { RlcInfo("Fully received segmented SDU. SN=%d.", header.sn); rx_sdu.buf = srsran::make_byte_buffer(); if (rx_sdu.buf == nullptr) { RlcError("fatal error. Couldn't allocate PDU in %s.", __FUNCTION__); - rx_window.remove_pdu(header.sn); + rx_window->remove_pdu(header.sn); return SRSRAN_ERROR; } + // Assemble SDU from segments for (const auto& it : rx_sdu.segments) { memcpy(&rx_sdu.buf->msg[rx_sdu.buf->N_bytes], it.buf->msg, it.buf->N_bytes); rx_sdu.buf->N_bytes += it.buf->N_bytes; } - write_to_upper_layers(parent->lcid, std::move(rx_window[header.sn].buf)); } return SRSRAN_SUCCESS; } @@ -1157,53 +1597,83 @@ uint32_t rlc_am_nr_rx::get_status_pdu(rlc_am_nr_status_pdu_t* status, uint32_t m return SRSRAN_ERROR; } - status->N_nack = 0; - status->ack_sn = st.rx_next; // Start with the lower end of the window - byte_buffer_t tmp_buf; + status->reset(); - uint32_t i = status->ack_sn; - while (rx_mod_base_nr(i) <= rx_mod_base_nr(st.rx_highest_status)) { - if ((rx_window.has_sn(i) && rx_window[i].fully_received) || i == st.rx_highest_status) { - // only update ACK_SN if this SN has been fully received, or if we reached the maximum possible SN - status->ack_sn = i; + /* + * - for the RLC SDUs with SN such that RX_Next <= SN < RX_Highest_Status that has not been completely + * received yet, in increasing SN order of RLC SDUs and increasing byte segment order within RLC SDUs, + * starting with SN = RX_Next up to the point where the resulting STATUS PDU still fits to the total size of RLC + * PDU(s) indicated by lower layer: + */ + RlcDebug("Generating status PDU"); + for (uint32_t i = st.rx_next; rx_mod_base_nr(i) < rx_mod_base_nr(st.rx_highest_status); i = (i + 1) % mod_nr) { + if ((rx_window->has_sn(i) && (*rx_window)[i].fully_received)) { + RlcDebug("SDU SN=%d is fully received", i); } else { - if (not rx_window.has_sn(i)) { + if (not rx_window->has_sn(i)) { // No segment received, NACK the whole SDU - status->nacks[status->N_nack].nack_sn = i; - status->N_nack++; - } else if (not rx_window[i].fully_received) { + RlcDebug("Adding NACK for full SDU. NACK SN=%d", i); + rlc_status_nack_t nack; + nack.nack_sn = i; + nack.has_so = false; + status->push_nack(nack); + } else if (not(*rx_window)[i].fully_received) { // Some segments were received, but not all. // NACK non consecutive missing bytes + RlcDebug("Adding NACKs for segmented SDU. NACK SN=%d", i); uint32_t last_so = 0; bool last_segment_rx = false; - for (auto segm = rx_window[i].segments.begin(); segm != rx_window[i].segments.end(); segm++) { + for (auto segm = (*rx_window)[i].segments.begin(); segm != (*rx_window)[i].segments.end(); segm++) { if (segm->header.so != last_so) { // Some bytes were not received - status->nacks[status->N_nack].nack_sn = i; - status->nacks[status->N_nack].has_so = true; - status->nacks[status->N_nack].so_start = last_so; - status->nacks[status->N_nack].so_end = segm->header.so; - status->N_nack++; + rlc_status_nack_t nack; + nack.nack_sn = i; + nack.has_so = true; + nack.so_start = last_so; + nack.so_end = segm->header.so - 1; // set to last missing byte + status->push_nack(nack); + RlcDebug("First/middle segment missing. NACK_SN=%d. SO_start=%d, SO_end=%d", + nack.nack_sn, + nack.so_start, + nack.so_end); + srsran_assert(nack.so_start <= nack.so_end, "Error: SO_start > SO_end. NACK_SN=%d", nack.nack_sn); } if (segm->header.si == rlc_nr_si_field_t::last_segment) { last_segment_rx = true; } last_so = segm->header.so + segm->buf->N_bytes; - } + } // Segment loop if (not last_segment_rx) { - status->nacks[status->N_nack].nack_sn = i; - status->nacks[status->N_nack].has_so = true; - status->nacks[status->N_nack].so_start = last_so; - status->nacks[status->N_nack].so_end = so_end_of_sdu; - status->N_nack++; + rlc_status_nack_t nack; + nack.nack_sn = i; + nack.has_so = true; + nack.so_start = last_so; + nack.so_end = so_end_of_sdu; + status->push_nack(nack); + RlcDebug( + "Final segment missing. NACK_SN=%d. SO_start=%d, SO_end=%d", nack.nack_sn, nack.so_start, nack.so_end); + srsran_assert(nack.so_start <= nack.so_end, "Error: SO_start > SO_end. NACK_SN=%d", nack.nack_sn); } } } + } // NACK loop - // make sure we don't exceed grant size (FIXME) - rlc_am_nr_write_status_pdu(*status, rlc_am_nr_sn_size_t::size12bits, &tmp_buf); - // TODO - i = (i + 1) % MOD; + /* + * - set the ACK_SN to the SN of the next not received RLC SDU which is not + * indicated as missing in the resulting STATUS PDU. + */ + status->ack_sn = st.rx_highest_status; + + // trim PDU if necessary + if (status->packed_size > max_len) { + RlcInfo("Trimming status PDU with %d NACKs and packed_size=%d into max_len=%d", + status->nacks.size(), + status->packed_size, + max_len); + log_rlc_am_nr_status_pdu_to_string(logger.debug, "Untrimmed status PDU - %s", status, rb_name); + if (not status->trim(max_len)) { + RlcError("Failed to trim status PDU into provided space: max_len=%d", max_len); + } } if (max_len != UINT32_MAX) { @@ -1211,14 +1681,17 @@ uint32_t rlc_am_nr_rx::get_status_pdu(rlc_am_nr_status_pdu_t* status, uint32_t m if (status_prohibit_timer.is_valid()) { status_prohibit_timer.run(); } + do_status = false; } - return tmp_buf.N_bytes; + + return status->packed_size; } uint32_t rlc_am_nr_rx::get_status_pdu_length() { - rlc_am_nr_status_pdu_t tmp_status; - return get_status_pdu(&tmp_status, UINT32_MAX); + rlc_am_nr_status_pdu_t tmp_status(cfg.rx_sn_field_length); + get_status_pdu(&tmp_status, UINT32_MAX); + return tmp_status.get_packed_size(); } bool rlc_am_nr_rx::get_do_status() @@ -1249,19 +1722,28 @@ void rlc_am_nr_rx::timer_expired(uint32_t timeout_id) * - start t-Reassembly; * - set RX_Next_Status_Trigger to RX_Next_Highest. */ - for (uint32_t tmp_sn = st.rx_next_status_trigger; tmp_sn < st.rx_next_status_trigger + RLC_AM_WINDOW_SIZE; - tmp_sn++) { - if (not rx_window.has_sn(tmp_sn)) { - st.rx_highest_status = tmp_sn; + uint32_t sn_upd = {}; + for (sn_upd = st.rx_next_status_trigger; rx_mod_base_nr(sn_upd) < rx_mod_base_nr(st.rx_next_highest); + sn_upd = (sn_upd + 1) % mod_nr) { + if (not rx_window->has_sn(sn_upd) || (rx_window->has_sn(sn_upd) && not(*rx_window)[sn_upd].fully_received)) { break; } } + st.rx_highest_status = sn_upd; + if (not inside_rx_window(st.rx_highest_status)) { + RlcError("Rx_Highest_Status not inside RX window"); + debug_state(); + } + srsran_assert(inside_rx_window(st.rx_highest_status), "Error: rx_highest_status assigned outside rx window"); + bool restart_reassembly_timer = false; - if (st.rx_next_highest > st.rx_highest_status + 1) { + if (rx_mod_base_nr(st.rx_next_highest) > rx_mod_base_nr(st.rx_highest_status + 1)) { restart_reassembly_timer = true; } - if (st.rx_next_highest == st.rx_highest_status + 1 && not rx_window[st.rx_next_highest].fully_received) { - restart_reassembly_timer = true; + if (rx_mod_base_nr(st.rx_next_highest) == rx_mod_base_nr(st.rx_highest_status + 1)) { + if (rx_window->has_sn(st.rx_highest_status) && (*rx_window)[st.rx_highest_status].has_gap) { + restart_reassembly_timer = true; + } } if (restart_reassembly_timer) { reassembly_timer.run(); @@ -1274,6 +1756,8 @@ void rlc_am_nr_rx::timer_expired(uint32_t timeout_id) * triggered, but the STATUS report shall be triggered after RX_Highest_Status is updated. */ do_status = true; + debug_state(); + debug_window(); return; } } @@ -1295,10 +1779,15 @@ uint32_t rlc_am_nr_rx::rx_mod_base_nr(uint32_t sn) const return (sn - st.rx_next) % mod_nr; } +uint32_t rlc_am_nr_rx::rx_window_size() const +{ + return am_window_size(cfg.rx_sn_field_length); +} + bool rlc_am_nr_rx::inside_rx_window(uint32_t sn) { // RX_Next <= SN < RX_Next + AM_Window_Size - return rx_mod_base_nr(sn) < RLC_AM_NR_WINDOW_SIZE; + return rx_mod_base_nr(sn) < rx_window_size(); } /* @@ -1320,27 +1809,34 @@ void rlc_am_nr_rx::insert_received_segment(rlc_amd_rx_pdu_nr segment_list.insert(std::move(segment)); } -bool rlc_am_nr_rx::have_all_segments_been_received( - const std::set& segment_list) const +void rlc_am_nr_rx::update_segment_inventory(rlc_amd_rx_sdu_nr_t& rx_sdu) const { - if (segment_list.empty()) { - return false; - } - - // Check if we have received the last segment - if (segment_list.rbegin()->header.si != rlc_nr_si_field_t::last_segment) { - return false; + if (rx_sdu.segments.empty()) { + rx_sdu.fully_received = false; + rx_sdu.has_gap = false; + return; } - // Check if all segments have been received + // Check for gaps and if all segments have been received uint32_t next_byte = 0; - for (const auto& it : segment_list) { + for (const auto& it : rx_sdu.segments) { if (it.header.so != next_byte) { - return false; + // Found gap: set flags and return + rx_sdu.has_gap = true; + rx_sdu.fully_received = false; + return; + } + if (it.header.si == rlc_nr_si_field_t::last_segment) { + // Reached last segment without any gaps: set flags and return + rx_sdu.has_gap = false; + rx_sdu.fully_received = true; + return; } next_byte += it.buf->N_bytes; } - return true; + // No gaps, but last segment not yet received + rx_sdu.has_gap = false; + rx_sdu.fully_received = false; } /* @@ -1348,10 +1844,16 @@ bool rlc_am_nr_rx::have_all_segments_been_received( */ void rlc_am_nr_rx::debug_state() const { - RlcDebug("RX entity state: Rx_Next %d, Rx_Next_Status_Trigger %d, Rx_Highest_Status %d, Rx_Next_Highest", + RlcDebug("RX entity state: Rx_Next=%d, Rx_Next_Status_Trigger=%d, Rx_Highest_Status=%d, Rx_Next_Highest=%d", st.rx_next, st.rx_next_status_trigger, st.rx_highest_status, st.rx_next_highest); } + +void rlc_am_nr_rx::debug_window() const +{ + RlcDebug( + "RX window state: Rx_Next=%d, Rx_Next_Highest=%d, SDUs %d", st.rx_next, st.rx_next_highest, rx_window->size()); +} } // namespace srsran diff --git a/lib/src/rlc/rlc_am_nr_packing.cc b/lib/src/rlc/rlc_am_nr_packing.cc index 0ac5a1412..e11a9019d 100644 --- a/lib/src/rlc/rlc_am_nr_packing.cc +++ b/lib/src/rlc/rlc_am_nr_packing.cc @@ -24,6 +24,74 @@ namespace srsran { +/**************************************************************************** + * Container implementation for pack/unpack functions + ***************************************************************************/ + +rlc_am_nr_status_pdu_t::rlc_am_nr_status_pdu_t(rlc_am_nr_sn_size_t sn_size) : sn_size(sn_size) +{ + nacks_.reserve(RLC_AM_NR_TYP_NACKS); +} + +void rlc_am_nr_status_pdu_t::reset() +{ + cpt = rlc_am_nr_control_pdu_type_t::status_pdu; + ack_sn = INVALID_RLC_SN; + nacks_.clear(); + packed_size_ = rlc_am_nr_status_pdu_sizeof_header_ack_sn; +} + +void rlc_am_nr_status_pdu_t::push_nack(const rlc_status_nack_t& nack) +{ + nacks_.push_back(nack); + packed_size_ += nack_size(nack); +} + +bool rlc_am_nr_status_pdu_t::trim(uint32_t max_packed_size) +{ + if (max_packed_size >= packed_size_) { + // no trimming required + return true; + } + if (max_packed_size < rlc_am_nr_status_pdu_sizeof_header_ack_sn) { + // too little space for smallest possible status PDU (only header + ACK). + return false; + } + + // remove NACKs (starting from the back) until it fits into given space + // note: when removing a NACK for a segment, we have to remove all other NACKs with the same SN as well, + // see TS 38.322 Sec. 5.3.4: + // "set the ACK_SN to the SN of the next not received RLC SDU + // which is not indicated as missing in the resulting STATUS PDU." + while (nacks_.size() > 0 && (max_packed_size < packed_size_ || nacks_.back().nack_sn == ack_sn)) { + packed_size_ -= nack_size(nacks_.back()); + ack_sn = nacks_.back().nack_sn; + nacks_.pop_back(); + } + return true; +} + +void rlc_am_nr_status_pdu_t::refresh_packed_size() +{ + uint32_t packed_size = rlc_am_nr_status_pdu_sizeof_header_ack_sn; + for (auto nack : nacks_) { + packed_size += nack_size(nack); + } +} + +uint32_t rlc_am_nr_status_pdu_t::nack_size(const rlc_status_nack_t& nack) const +{ + uint32_t result = sn_size == rlc_am_nr_sn_size_t::size12bits ? rlc_am_nr_status_pdu_sizeof_nack_sn_ext_12bit_sn + : rlc_am_nr_status_pdu_sizeof_nack_sn_ext_18bit_sn; + if (nack.has_so) { + result += rlc_am_nr_status_pdu_sizeof_nack_so; + } + if (nack.has_nack_range) { + result += rlc_am_nr_status_pdu_sizeof_nack_range; + } + return result; +} + /**************************************************************************** * Header pack/unpack helper functions * Ref: 3GPP TS 38.322 v15.3.0 Section 6.2.2.4 @@ -150,6 +218,11 @@ uint32_t rlc_am_nr_write_data_pdu_header(const rlc_am_nr_pdu_header_t& header, b return len; } +/**************************************************************************** + * Status PDU pack/unpack helper functions + * Ref: 3GPP TS 38.322 v16.2.0 Section 6.2.2.5 + ***************************************************************************/ + uint32_t rlc_am_nr_read_status_pdu(const byte_buffer_t* pdu, const rlc_am_nr_sn_size_t sn_size, rlc_am_nr_status_pdu_t* status) { @@ -160,8 +233,19 @@ uint32_t rlc_am_nr_read_status_pdu(const uint8_t* payload, const uint32_t nof_bytes, const rlc_am_nr_sn_size_t sn_size, rlc_am_nr_status_pdu_t* status) +{ + if (sn_size == rlc_am_nr_sn_size_t::size12bits) { + return rlc_am_nr_read_status_pdu_12bit_sn(payload, nof_bytes, status); + } else { // 18bit SN + return rlc_am_nr_read_status_pdu_18bit_sn(payload, nof_bytes, status); + } +} + +uint32_t +rlc_am_nr_read_status_pdu_12bit_sn(const uint8_t* payload, const uint32_t nof_bytes, rlc_am_nr_status_pdu_t* status) { uint8_t* ptr = const_cast(payload); + status->reset(); // fixed part status->cpt = (rlc_am_nr_control_pdu_type_t)((*ptr >> 4) & 0x07); // 3 bits CPT @@ -172,58 +256,144 @@ uint32_t rlc_am_nr_read_status_pdu(const uint8_t* payload, return 0; } - if (sn_size == rlc_am_nr_sn_size_t::size12bits) { - status->ack_sn = (*ptr & 0x0F) << 8; // first 4 bits SN - ptr++; + status->ack_sn = (*ptr & 0x0F) << 8; // first 4 bits SN + ptr++; + + status->ack_sn |= (*ptr & 0xFF); // last 8 bits SN + ptr++; + + // read E1 flag + uint8_t e1 = *ptr & 0x80; - status->ack_sn |= (*ptr & 0xFF); // last 8 bits SN + // sanity check for reserved bits + if ((*ptr & 0x7f) != 0) { + fprintf(stderr, "Malformed PDU, reserved bits are set.\n"); + return 0; + } + + // all good, continue with next byte depending on E1 + ptr++; + + while (e1 != 0) { + // E1 flag set, read a NACK_SN + rlc_status_nack_t nack = {}; + nack.nack_sn = (*ptr & 0xff) << 4; ptr++; - // read E1 flag - uint8_t e1 = *ptr & 0x80; + e1 = *ptr & 0x08; // 1 = further NACKs follow + uint8_t e2 = *ptr & 0x04; // 1 = set of {so_start, so_end} follows + uint8_t e3 = *ptr & 0x02; // 1 = NACK range follows (i.e. NACK across multiple SNs) // sanity check for reserved bits - if ((*ptr & 0x7f) != 0) { + if ((*ptr & 0x01) != 0) { fprintf(stderr, "Malformed PDU, reserved bits are set.\n"); return 0; } + nack.nack_sn |= (*ptr & 0xF0) >> 4; - // all good, continue with next byte depending on E1 ptr++; + if (e2 != 0) { + nack.has_so = true; + nack.so_start = (*ptr) << 8; + ptr++; + nack.so_start |= (*ptr); + ptr++; + nack.so_end = (*ptr) << 8; + ptr++; + nack.so_end |= (*ptr); + ptr++; + } + if (e3 != 0) { + nack.has_nack_range = true; + nack.nack_range = (*ptr); + ptr++; + } + status->push_nack(nack); + if (uint32_t(ptr - payload) > nof_bytes) { + fprintf(stderr, "Malformed PDU, trying to read more bytes than it is available\n"); + return 0; + } + } - // reset number of acks - status->N_nack = 0; + return SRSRAN_SUCCESS; +} - while (e1 != 0) { - // E1 flag set, read a NACK_SN - rlc_status_nack_t nack = {}; - nack.nack_sn = (*ptr & 0xff) << 4; - ptr++; +uint32_t +rlc_am_nr_read_status_pdu_18bit_sn(const uint8_t* payload, const uint32_t nof_bytes, rlc_am_nr_status_pdu_t* status) +{ + uint8_t* ptr = const_cast(payload); + status->reset(); + + // fixed part + status->cpt = (rlc_am_nr_control_pdu_type_t)((*ptr >> 4) & 0x07); // 3 bits CPT + + // sanity check + if (status->cpt != rlc_am_nr_control_pdu_type_t::status_pdu) { + fprintf(stderr, "Malformed PDU, reserved bits are set.\n"); + return 0; + } + + status->ack_sn = (*ptr & 0x0F) << 14; // upper 4 bits of SN + ptr++; + + status->ack_sn |= (*ptr & 0xFF) << 6; // center 8 bits of SN + ptr++; + + status->ack_sn |= (*ptr & 0xFC) >> 2; // lower 6 bits of SN + + // read E1 flag + uint8_t e1 = *ptr & 0x02; + + // sanity check for reserved bits + if ((*ptr & 0x01) != 0) { + fprintf(stderr, "Malformed PDU, reserved bit is set.\n"); + return 0; + } + + // all good, continue with next byte depending on E1 + ptr++; + + while (e1 != 0) { + // E1 flag set, read a NACK_SN + rlc_status_nack_t nack = {}; + + nack.nack_sn = (*ptr & 0xFF) << 10; // upper 8 bits of SN + ptr++; + nack.nack_sn |= (*ptr & 0xFF) << 2; // center 8 bits of SN + ptr++; + nack.nack_sn |= (*ptr & 0xC0) >> 6; // lower 2 bits of SN - e1 = *ptr & 0x08; - uint8_t e2 = *ptr & 0x04; + e1 = *ptr & 0x20; // 1 = further NACKs follow + uint8_t e2 = *ptr & 0x10; // 1 = set of {so_start, so_end} follows + uint8_t e3 = *ptr & 0x08; // 1 = NACK range follows (i.e. NACK across multiple SNs) - // uint8_t len2 = (*ptr & 0xF0) >> 4; - nack.nack_sn |= (*ptr & 0xF0) >> 4; - status->nacks[status->N_nack] = nack; + // sanity check for reserved bits + if ((*ptr & 0x07) != 0) { + fprintf(stderr, "Malformed PDU, reserved bits are set.\n"); + return 0; + } + ptr++; + if (e2 != 0) { + nack.has_so = true; + nack.so_start = (*ptr) << 8; + ptr++; + nack.so_start |= (*ptr); + ptr++; + nack.so_end = (*ptr) << 8; + ptr++; + nack.so_end |= (*ptr); ptr++; - if (e2 != 0) { - status->nacks[status->N_nack].has_so = true; - status->nacks[status->N_nack].so_start = (*ptr) << 8; - ptr++; - status->nacks[status->N_nack].so_start |= (*ptr); - ptr++; - status->nacks[status->N_nack].so_end = (*ptr) << 8; - ptr++; - status->nacks[status->N_nack].so_end |= (*ptr); - ptr++; - } - status->N_nack++; - if ((ptr - payload) > nof_bytes) { - fprintf(stderr, "Malformed PDU, trying to read more bytes than it is available\n"); - return 0; - } + } + if (e3 != 0) { + nack.has_nack_range = true; + nack.nack_range = (*ptr); + ptr++; + } + status->push_nack(nack); + if (uint32_t(ptr - payload) > nof_bytes) { + fprintf(stderr, "Malformed PDU, trying to read more bytes than it is available\n"); + return 0; } } @@ -239,64 +409,134 @@ uint32_t rlc_am_nr_read_status_pdu(const uint8_t* payload, int32_t rlc_am_nr_write_status_pdu(const rlc_am_nr_status_pdu_t& status_pdu, const rlc_am_nr_sn_size_t sn_size, byte_buffer_t* pdu) +{ + if (sn_size == rlc_am_nr_sn_size_t::size12bits) { + return rlc_am_nr_write_status_pdu_12bit_sn(status_pdu, pdu); + } else { // 18bit SN + return rlc_am_nr_write_status_pdu_18bit_sn(status_pdu, pdu); + } +} + +int32_t rlc_am_nr_write_status_pdu_12bit_sn(const rlc_am_nr_status_pdu_t& status_pdu, byte_buffer_t* pdu) { uint8_t* ptr = pdu->msg; // fixed header part *ptr = 0; ///< 1 bit D/C field and 3bit CPT are all zero - if (sn_size == rlc_am_nr_sn_size_t::size12bits) { - // write first 4 bit of ACK_SN - *ptr |= (status_pdu.ack_sn >> 8) & 0x0f; // 4 bit ACK_SN - ptr++; - *ptr = status_pdu.ack_sn & 0xff; // remaining 8 bit of SN - ptr++; + // write first 4 bit of ACK_SN + *ptr |= (status_pdu.ack_sn >> 8) & 0x0f; // 4 bit ACK_SN + ptr++; + *ptr = status_pdu.ack_sn & 0xff; // remaining 8 bit of SN + ptr++; - // write E1 flag in octet 3 - if (status_pdu.N_nack > 0) { - *ptr = 0x80; - } else { - *ptr = 0x00; - } - ptr++; + // write E1 flag in octet 3 + if (status_pdu.nacks.size() > 0) { + *ptr = 0x80; + } else { + *ptr = 0x00; + } + ptr++; + + if (status_pdu.nacks.size() > 0) { + for (uint32_t i = 0; i < status_pdu.nacks.size(); i++) { + // write first 8 bit of NACK_SN + *ptr = (status_pdu.nacks[i].nack_sn >> 4) & 0xff; + ptr++; + + // write remaining 4 bits of NACK_SN + *ptr = (status_pdu.nacks[i].nack_sn & 0x0f) << 4; + // Set E1 if necessary + if (i < (uint32_t)(status_pdu.nacks.size() - 1)) { + *ptr |= 0x08; + } - if (status_pdu.N_nack > 0) { - for (uint32_t i = 0; i < status_pdu.N_nack; i++) { - // write first 8 bit of NACK_SN - *ptr = (status_pdu.nacks[i].nack_sn >> 4) & 0xff; + if (status_pdu.nacks[i].has_so) { + // Set E2 + *ptr |= 0x04; + } + + if (status_pdu.nacks[i].has_nack_range) { + // Set E3 + *ptr |= 0x02; + } + + ptr++; + if (status_pdu.nacks[i].has_so) { + (*ptr) = status_pdu.nacks[i].so_start >> 8; ptr++; + (*ptr) = status_pdu.nacks[i].so_start; + ptr++; + (*ptr) = status_pdu.nacks[i].so_end >> 8; + ptr++; + (*ptr) = status_pdu.nacks[i].so_end; + ptr++; + } + if (status_pdu.nacks[i].has_nack_range) { + (*ptr) = status_pdu.nacks[i].nack_range; + ptr++; + } + } + } + + pdu->N_bytes = ptr - pdu->msg; + + return SRSRAN_SUCCESS; +} + +int32_t rlc_am_nr_write_status_pdu_18bit_sn(const rlc_am_nr_status_pdu_t& status_pdu, byte_buffer_t* pdu) +{ + uint8_t* ptr = pdu->msg; + + // fixed header part + *ptr = 0; ///< 1 bit D/C field and 3bit CPT are all zero + + *ptr |= (status_pdu.ack_sn >> 14) & 0x0F; // upper 4 bits of SN + ptr++; + *ptr = (status_pdu.ack_sn >> 6) & 0xFF; // center 8 bits of SN + ptr++; + *ptr = (status_pdu.ack_sn << 2) & 0xFC; // lower 6 bits of SN - // write remaining 4 bits of NACK_SN - *ptr = (status_pdu.nacks[i].nack_sn & 0x0f) << 4; - // Set E1 if necessary - if (i < (uint32_t)(status_pdu.N_nack - 1)) { - *ptr |= 0x08; - } - - if (status_pdu.nacks[i].has_so) { - // Set E2 - *ptr |= 0x04; - - ptr++; - (*ptr) = status_pdu.nacks[i].so_start >> 8; - ptr++; - (*ptr) = status_pdu.nacks[i].so_start; - ptr++; - (*ptr) = status_pdu.nacks[i].so_end >> 8; - ptr++; - (*ptr) = status_pdu.nacks[i].so_end; - } + // set E1 flag if necessary + if (status_pdu.nacks.size() > 0) { + *ptr |= 0x02; + } + ptr++; + + if (status_pdu.nacks.size() > 0) { + for (uint32_t i = 0; i < status_pdu.nacks.size(); i++) { + *ptr = (status_pdu.nacks[i].nack_sn >> 10) & 0xFF; // upper 8 bits of SN + ptr++; + *ptr = (status_pdu.nacks[i].nack_sn >> 2) & 0xFF; // center 8 bits of SN + ptr++; + *ptr = (status_pdu.nacks[i].nack_sn << 6) & 0xC0; // lower 2 bits of SN + + if (i < (uint32_t)(status_pdu.nacks.size() - 1)) { + *ptr |= 0x20; // Set E1 + } + if (status_pdu.nacks[i].has_so) { + *ptr |= 0x10; // Set E2 + } + if (status_pdu.nacks[i].has_nack_range) { + *ptr |= 0x08; // Set E3 + } + + ptr++; + if (status_pdu.nacks[i].has_so) { + (*ptr) = status_pdu.nacks[i].so_start >> 8; + ptr++; + (*ptr) = status_pdu.nacks[i].so_start; + ptr++; + (*ptr) = status_pdu.nacks[i].so_end >> 8; + ptr++; + (*ptr) = status_pdu.nacks[i].so_end; + ptr++; + } + if (status_pdu.nacks[i].has_nack_range) { + (*ptr) = status_pdu.nacks[i].nack_range; ptr++; } } - } else { - // 18bit SN - *ptr |= (status_pdu.ack_sn >> 14) & 0x0f; // 4 bit ACK_SN - ptr++; - *ptr = status_pdu.ack_sn >> 8; // bit 3 - 10 of SN - ptr++; - *ptr = (status_pdu.ack_sn & 0xff); // remaining 6 bit of SN - ptr++; } pdu->N_bytes = ptr - pdu->msg; diff --git a/lib/src/rlc/rlc_um_base.cc b/lib/src/rlc/rlc_um_base.cc index 0cdc8258b..b386d0c2d 100644 --- a/lib/src/rlc/rlc_um_base.cc +++ b/lib/src/rlc/rlc_um_base.cc @@ -307,7 +307,18 @@ int rlc_um_base::rlc_um_base_tx::try_write_sdu(unique_byte_buffer_t sdu) void rlc_um_base::rlc_um_base_tx::discard_sdu(uint32_t discard_sn) { - RlcWarning("RLC UM: Discard SDU not implemented yet."); + std::lock_guard lock(mutex); + + bool discarded = tx_sdu_queue.apply_first([&discard_sn, this](unique_byte_buffer_t& sdu) { + if (sdu != nullptr && sdu->md.pdcp_sn == discard_sn) { + tx_sdu_queue.queue.pop_func(sdu); + sdu = nullptr; + } + return false; + }); + + // Discard fails when the PDCP PDU is already in Tx window. + RlcInfo("%s PDU with PDCP_SN=%d", discarded ? "Discarding" : "Couldn't discard", discard_sn); } bool rlc_um_base::rlc_um_base_tx::sdu_queue_is_full() diff --git a/lib/src/srslog/CMakeLists.txt b/lib/src/srslog/CMakeLists.txt index 635d064be..ef958801b 100644 --- a/lib/src/srslog/CMakeLists.txt +++ b/lib/src/srslog/CMakeLists.txt @@ -42,4 +42,4 @@ find_package(Threads REQUIRED) add_library(srslog STATIC ${SOURCES}) target_link_libraries(srslog ${CMAKE_THREAD_LIBS_INIT}) -INSTALL(TARGETS srslog DESTINATION ${LIBRARY_DIR}) +install(TARGETS srslog DESTINATION ${LIBRARY_DIR} OPTIONAL) diff --git a/lib/src/support/emergency_handlers.cc b/lib/src/support/emergency_handlers.cc index 1768cb778..f4564a3c6 100644 --- a/lib/src/support/emergency_handlers.cc +++ b/lib/src/support/emergency_handlers.cc @@ -35,7 +35,7 @@ struct handler_instance { // Handlers are added in a thread safe manner without using locks to avoid possible issues if a signal is emitted while // modifying the callback array. -static constexpr unsigned max_handlers = 12; +static constexpr unsigned max_handlers = 256; static handler_instance registered_handlers[max_handlers]; static std::atomic num_handlers; diff --git a/lib/test/asn1/ngap_test.cc b/lib/test/asn1/ngap_test.cc index a1b2c8300..cbcd6e1f5 100644 --- a/lib/test/asn1/ngap_test.cc +++ b/lib/test/asn1/ngap_test.cc @@ -225,6 +225,39 @@ int test_dl_nas_transport() return 0; } +// DL NAS transport with AMF-UE-NGAP-ID larger than 32bit +int test_dl_nas_transport2() +{ + uint8_t ngap_msg[] = {0x00, 0x04, 0x40, 0x42, 0x00, 0x00, 0x03, 0x00, 0x0a, 0x00, 0x06, 0x80, 0x03, 0x03, + 0xcf, 0x37, 0xd0, 0x00, 0x55, 0x00, 0x02, 0x00, 0x01, 0x00, 0x26, 0x00, 0x2b, 0x2a, + 0x7e, 0x00, 0x56, 0x00, 0x02, 0x00, 0x00, 0x21, 0xbc, 0x8d, 0xe5, 0x61, 0xf5, 0xb4, + 0xa7, 0x05, 0x8f, 0xdb, 0xe2, 0x3b, 0x4e, 0x21, 0xda, 0x45, 0x20, 0x10, 0x5a, 0xb8, + 0xd1, 0xdb, 0x13, 0x76, 0x80, 0x00, 0x1b, 0x1a, 0x8d, 0x3c, 0x98, 0x4c, 0x01, 0x06}; + // 00044042000003000a0006800303cf37d00055000200010026002b2a7e00560002000021bc8de561f5b4a7058fdbe23b4e21da4520105ab8d1db137680001b1a8d3c984c0106 + + cbit_ref bref(ngap_msg, sizeof(ngap_msg)); + ngap_pdu_c pdu; + TESTASSERT(pdu.unpack(bref) == SRSASN_SUCCESS); + + // Check Fields + TESTASSERT(pdu.type().value == ngap_pdu_c::types_opts::init_msg); + TESTASSERT(pdu.init_msg().proc_code == ASN1_NGAP_ID_DL_NAS_TRANSPORT); + TESTASSERT(pdu.init_msg().crit.value == crit_opts::ignore); + TESTASSERT(pdu.init_msg().value.type().value == ngap_elem_procs_o::init_msg_c::types_opts::dl_nas_transport); + + auto& dl_nas = pdu.init_msg().value.dl_nas_transport(); + // Field 0 + TESTASSERT_EQ(12948813776, dl_nas->amf_ue_ngap_id.value); + + // ... + // Field 2 + TESTASSERT(dl_nas->nas_pdu.value.size() == 42); + + TESTASSERT(ceil(bref.distance(ngap_msg) / 8.0) == sizeof(ngap_msg)); + TESTASSERT(test_pack_unpack_consistency(pdu) == SRSASN_SUCCESS); + return 0; +} + int test_ul_ran_status_transfer() { uint8_t ngap_msg[] = {0x00, 0x2e, 0x40, 0x3c, 0x00, 0x00, 0x04, 0x00, 0x0a, 0x00, 0x02, 0x00, 0x01, 0x00, 0x55, 0x00, @@ -355,6 +388,7 @@ int main() test_ngsetup_response(); test_init_ue_msg(); test_dl_nas_transport(); + test_dl_nas_transport2(); test_ul_ran_status_transfer(); test_ue_context_release(); test_ue_context_release_complete(); diff --git a/lib/test/rlc/rlc_am_nr_pdu_test.cc b/lib/test/rlc/rlc_am_nr_pdu_test.cc index 5437a65e0..4aaee0800 100644 --- a/lib/test/rlc/rlc_am_nr_pdu_test.cc +++ b/lib/test/rlc/rlc_am_nr_pdu_test.cc @@ -230,11 +230,11 @@ int rlc_am_nr_pdu_test6() return SRSRAN_SUCCESS; } -///< Control PDU tests +///< Control PDU tests (12bit SN) // Status PDU for 12bit SN with ACK_SN=2065 and no further NACK_SN (E1 bit not set) -int rlc_am_nr_control_pdu_test1() +int rlc_am_nr_control_pdu_12bit_sn_test1() { - test_delimit_logger delimiter("Control PDU test 1"); + test_delimit_logger delimiter("Control PDU (12bit SN) test 1"); const int len = 3; std::array tv = {0x08, 0x11, 0x00}; srsran::byte_buffer_t pdu = make_pdu_and_log(tv); @@ -242,10 +242,10 @@ int rlc_am_nr_control_pdu_test1() TESTASSERT(rlc_am_is_control_pdu(pdu.msg) == true); // unpack PDU - rlc_am_nr_status_pdu_t status_pdu = {}; + rlc_am_nr_status_pdu_t status_pdu(srsran::rlc_am_nr_sn_size_t::size12bits); TESTASSERT(rlc_am_nr_read_status_pdu(&pdu, srsran::rlc_am_nr_sn_size_t::size12bits, &status_pdu) == SRSRAN_SUCCESS); TESTASSERT(status_pdu.ack_sn == 2065); - TESTASSERT(status_pdu.N_nack == 0); + TESTASSERT(status_pdu.nacks.size() == 0); // reset status PDU pdu.clear(); @@ -262,9 +262,9 @@ int rlc_am_nr_control_pdu_test1() } // Status PDU for 12bit SN with ACK_SN=2065 and NACK_SN=273 (E1 bit set) -int rlc_am_nr_control_pdu_test2() +int rlc_am_nr_control_pdu_12bit_sn_test2() { - test_delimit_logger delimiter("Control PDU test 2"); + test_delimit_logger delimiter("Control PDU (12bit SN) test 2"); const int len = 5; std::array tv = {0x08, 0x11, 0x80, 0x11, 0x10}; srsran::byte_buffer_t pdu = make_pdu_and_log(tv); @@ -272,10 +272,10 @@ int rlc_am_nr_control_pdu_test2() TESTASSERT(rlc_am_is_control_pdu(pdu.msg) == true); // unpack PDU - rlc_am_nr_status_pdu_t status_pdu = {}; + rlc_am_nr_status_pdu_t status_pdu(srsran::rlc_am_nr_sn_size_t::size12bits); TESTASSERT(rlc_am_nr_read_status_pdu(&pdu, srsran::rlc_am_nr_sn_size_t::size12bits, &status_pdu) == SRSRAN_SUCCESS); TESTASSERT(status_pdu.ack_sn == 2065); - TESTASSERT(status_pdu.N_nack == 1); + TESTASSERT(status_pdu.nacks.size() == 1); TESTASSERT(status_pdu.nacks[0].nack_sn == 273); // reset status PDU @@ -283,7 +283,7 @@ int rlc_am_nr_control_pdu_test2() // pack again TESTASSERT(rlc_am_nr_write_status_pdu(status_pdu, srsran::rlc_am_nr_sn_size_t::size12bits, &pdu) == SRSRAN_SUCCESS); - // TESTASSERT(pdu.N_bytes == tv.size()); + TESTASSERT(pdu.N_bytes == tv.size()); write_pdu_to_pcap(4, pdu.msg, pdu.N_bytes); @@ -294,9 +294,9 @@ int rlc_am_nr_control_pdu_test2() // Status PDU for 12bit SN with ACK_SN=2065, NACK_SN=273, SO_START=2, SO_END=5, NACK_SN=275, SO_START=5, SO_END=0xFFFF // E1 and E2 bit set on first NACK, only E2 on second. -int rlc_am_nr_control_pdu_test3() +int rlc_am_nr_control_pdu_12bit_sn_test3() { - test_delimit_logger delimiter("Control PDU test 3"); + test_delimit_logger delimiter("Control PDU (12bit SN) test 3"); const int len = 15; std::array tv = { 0x08, 0x11, 0x80, 0x11, 0x1c, 0x00, 0x02, 0x00, 0x05, 0x11, 0x34, 0x00, 0x05, 0xFF, 0xFF}; @@ -305,10 +305,10 @@ int rlc_am_nr_control_pdu_test3() TESTASSERT(rlc_am_is_control_pdu(pdu.msg) == true); // unpack PDU - rlc_am_nr_status_pdu_t status_pdu = {}; + rlc_am_nr_status_pdu_t status_pdu(srsran::rlc_am_nr_sn_size_t::size12bits); TESTASSERT(rlc_am_nr_read_status_pdu(&pdu, srsran::rlc_am_nr_sn_size_t::size12bits, &status_pdu) == SRSRAN_SUCCESS); TESTASSERT(status_pdu.ack_sn == 2065); - TESTASSERT(status_pdu.N_nack == 2); + TESTASSERT(status_pdu.nacks.size() == 2); TESTASSERT(status_pdu.nacks[0].nack_sn == 273); TESTASSERT(status_pdu.nacks[0].so_start == 2); TESTASSERT(status_pdu.nacks[0].so_end == 5); @@ -331,9 +331,9 @@ int rlc_am_nr_control_pdu_test3() // Status PDU for 12bit SN with ACK_SN=2065, NACK_SN=273, SO_START=2, SO_END=5, NACK_SN=275 // E1 and E2 bit set on first NACK, neither E1 or E2 on the second. -int rlc_am_nr_control_pdu_test4() +int rlc_am_nr_control_pdu_12bit_sn_test4() { - test_delimit_logger delimiter("Control PDU test 4"); + test_delimit_logger delimiter("Control PDU (12bit SN) test 4"); const int len = 11; std::array tv = {0x08, 0x11, 0x80, 0x11, 0x1c, 0x00, 0x02, 0x00, 0x05, 0x11, 0x30}; srsran::byte_buffer_t pdu = make_pdu_and_log(tv); @@ -341,10 +341,10 @@ int rlc_am_nr_control_pdu_test4() TESTASSERT(rlc_am_is_control_pdu(pdu.msg) == true); // unpack PDU - rlc_am_nr_status_pdu_t status_pdu = {}; + rlc_am_nr_status_pdu_t status_pdu(srsran::rlc_am_nr_sn_size_t::size12bits); TESTASSERT(rlc_am_nr_read_status_pdu(&pdu, srsran::rlc_am_nr_sn_size_t::size12bits, &status_pdu) == SRSRAN_SUCCESS); TESTASSERT(status_pdu.ack_sn == 2065); - TESTASSERT(status_pdu.N_nack == 2); + TESTASSERT(status_pdu.nacks.size() == 2); TESTASSERT(status_pdu.nacks[0].nack_sn == 273); TESTASSERT(status_pdu.nacks[0].has_so == true); TESTASSERT(status_pdu.nacks[0].so_start == 2); @@ -368,9 +368,9 @@ int rlc_am_nr_control_pdu_test4() // Malformed Status PDU, with E1 still set at the end of the PDU // 12bit SN with ACK_SN=2065, NACK_SN=273, SO_START=2, SO_END=5, NACK_SN=275, SO_START=5, SO_END=0xFFFF // E1 and E2 bit set on first NACK, only E2 on second. -int rlc_am_nr_control_pdu_test5() +int rlc_am_nr_control_pdu_12bit_sn_test5() { - test_delimit_logger delimiter("Control PDU test 5"); + test_delimit_logger delimiter("Control PDU (12bit SN) test 5"); const int len = 15; std::array tv = { 0x08, 0x11, 0x80, 0x11, 0x1c, 0x00, 0x02, 0x00, 0x05, 0x11, 0x3c, 0x00, 0x05, 0xFF, 0xFF}; @@ -379,12 +379,562 @@ int rlc_am_nr_control_pdu_test5() TESTASSERT(rlc_am_is_control_pdu(pdu.msg) == true); // unpack PDU - rlc_am_nr_status_pdu_t status_pdu = {}; + rlc_am_nr_status_pdu_t status_pdu(srsran::rlc_am_nr_sn_size_t::size12bits); TESTASSERT(rlc_am_nr_read_status_pdu(&pdu, srsran::rlc_am_nr_sn_size_t::size12bits, &status_pdu) == 0); return SRSRAN_SUCCESS; } +// Status PDU for 12bit SN with ACK_SN=2065, +// NACK range0: 3 full SDUs, NACK_SN=273..275 +// NACK range1: missing segment sequence across 4 SDUs +// starting at NACK_SN=276, SO_START=2, +// ending at NACK_SN=279, SO_END=5 +// E1 and E3 bit set on first NACK, E2 and E3 bit set on the second. +int rlc_am_nr_control_pdu_12bit_sn_test_nack_range() +{ + test_delimit_logger delimiter("Control PDU (12bit SN) test NACK range"); + const int len = 13; + std::array tv = {0x08, // D/C | 3CPT | 4ACK_SN_upper + 0x11, // 8ACK_SN_lower + 0x80, // E1 | 7R + 0x11, // 8NACK_SN_upper + 0x1a, // 4NACK_SN_lower | E1 | E2 | E3 | R + 0x03, // 8NACK_range + 0x11, // 8NACK_SN_upper + 0x46, // 4NACK_SN_lower | E1 | E2 | E3 | R + 0x00, // 8SO_START_upper + 0x02, // 8SO_START_lower + 0x00, // 8SO_END_upper + 0x05, // 8SO_END_lower + 0x04}; // 8NACK_range + srsran::byte_buffer_t pdu = make_pdu_and_log(tv); + + TESTASSERT(rlc_am_is_control_pdu(pdu.msg) == true); + + // unpack PDU + rlc_am_nr_status_pdu_t status_pdu(srsran::rlc_am_nr_sn_size_t::size12bits); + TESTASSERT(rlc_am_nr_read_status_pdu(&pdu, srsran::rlc_am_nr_sn_size_t::size12bits, &status_pdu) == SRSRAN_SUCCESS); + TESTASSERT(status_pdu.ack_sn == 2065); + TESTASSERT(status_pdu.nacks.size() == 2); + TESTASSERT(status_pdu.nacks[0].nack_sn == 273); + TESTASSERT(status_pdu.nacks[0].has_so == false); + TESTASSERT(status_pdu.nacks[0].has_nack_range == true); + TESTASSERT(status_pdu.nacks[0].nack_range == 3); + + TESTASSERT(status_pdu.nacks[1].nack_sn == 276); + TESTASSERT(status_pdu.nacks[1].has_so == true); + TESTASSERT(status_pdu.nacks[1].so_start == 2); + TESTASSERT(status_pdu.nacks[1].so_end == 5); + TESTASSERT(status_pdu.nacks[1].has_nack_range == true); + TESTASSERT(status_pdu.nacks[1].nack_range == 4); + + // reset status PDU + pdu.clear(); + + // pack again + TESTASSERT(rlc_am_nr_write_status_pdu(status_pdu, srsran::rlc_am_nr_sn_size_t::size12bits, &pdu) == SRSRAN_SUCCESS); + TESTASSERT(pdu.N_bytes == tv.size()); + + write_pdu_to_pcap(4, pdu.msg, pdu.N_bytes); + TESTASSERT(memcmp(pdu.msg, tv.data(), pdu.N_bytes) == 0); + + return SRSRAN_SUCCESS; +} + +// Test status PDU for correct trimming and estimation of packed size +// 1) Test init, copy and reset +// 2) Test step-wise growth and trimming of status PDU while covering several corner cases +int rlc_am_nr_control_pdu_test_trimming(rlc_am_nr_sn_size_t sn_size) +{ + test_delimit_logger delimiter("Control PDU ({} bit SN) test trimming", to_number(sn_size)); + + // status PDU with no NACKs + { + constexpr uint32_t min_size = 3; + srsran::byte_buffer_t pdu; + rlc_am_nr_status_pdu_t status_pdu(sn_size); + + status_pdu.ack_sn = 99; + TESTASSERT_EQ(status_pdu.packed_size, min_size); // minimum size + TESTASSERT_EQ(rlc_am_nr_write_status_pdu(status_pdu, sn_size, &pdu), SRSRAN_SUCCESS); + TESTASSERT_EQ(pdu.N_bytes, min_size); + + rlc_am_nr_status_pdu_t status_pdu_copy = status_pdu; + TESTASSERT_EQ(status_pdu_copy.ack_sn, 99); + TESTASSERT_EQ(status_pdu_copy.packed_size, min_size); // minimum size + TESTASSERT_EQ(rlc_am_nr_write_status_pdu(status_pdu_copy, sn_size, &pdu), SRSRAN_SUCCESS); + TESTASSERT_EQ(pdu.N_bytes, min_size); + + status_pdu.reset(); + + status_pdu.ack_sn = 77; + TESTASSERT_EQ(status_pdu.packed_size, min_size); // should still have minimum size + TESTASSERT_EQ(rlc_am_nr_write_status_pdu(status_pdu, sn_size, &pdu), SRSRAN_SUCCESS); + TESTASSERT_EQ(pdu.N_bytes, min_size); + + TESTASSERT_EQ(status_pdu_copy.ack_sn, 99); // shouldn't have changed + TESTASSERT_EQ(status_pdu_copy.packed_size, min_size); // minimum size + TESTASSERT_EQ(rlc_am_nr_write_status_pdu(status_pdu_copy, sn_size, &pdu), SRSRAN_SUCCESS); + TESTASSERT_EQ(pdu.N_bytes, min_size); + } + + // status PDU with multiple NACKs + // expect: ACK=77, NACKs=[12][14][17 50:99][17 150:199][17 250:299][19][21 333:111 r5][27 444:666 r3] + { + constexpr uint32_t min_size = 3; + const uint32_t nack_size = sn_size == rlc_am_nr_sn_size_t::size12bits ? 2 : 3; + constexpr uint32_t so_size = 4; + constexpr uint32_t range_size = 1; + uint32_t expected_size = min_size; + srsran::byte_buffer_t pdu; + rlc_am_nr_status_pdu_t status_pdu(sn_size); + + status_pdu.ack_sn = 77; + { + rlc_status_nack_t nack; + nack.nack_sn = 12; + status_pdu.push_nack(nack); + } + expected_size += nack_size; + TESTASSERT_EQ(status_pdu.packed_size, expected_size); + TESTASSERT_EQ(rlc_am_nr_write_status_pdu(status_pdu, sn_size, &pdu), SRSRAN_SUCCESS); + TESTASSERT_EQ(pdu.N_bytes, expected_size); + { + rlc_status_nack_t nack; + nack.nack_sn = 14; + status_pdu.push_nack(nack); + } + expected_size += nack_size; + TESTASSERT_EQ(status_pdu.packed_size, expected_size); + TESTASSERT_EQ(rlc_am_nr_write_status_pdu(status_pdu, sn_size, &pdu), SRSRAN_SUCCESS); + TESTASSERT_EQ(pdu.N_bytes, expected_size); + { + rlc_status_nack_t nack; + nack.nack_sn = 17; + nack.has_so = true; + nack.so_start = 50; + nack.so_end = 99; + status_pdu.push_nack(nack); + } + expected_size += nack_size + so_size; + TESTASSERT_EQ(status_pdu.packed_size, expected_size); + TESTASSERT_EQ(rlc_am_nr_write_status_pdu(status_pdu, sn_size, &pdu), SRSRAN_SUCCESS); + TESTASSERT_EQ(pdu.N_bytes, expected_size); + { + rlc_status_nack_t nack; + nack.nack_sn = 17; + nack.has_so = true; + nack.so_start = 150; + nack.so_end = 199; + status_pdu.push_nack(nack); + } + expected_size += nack_size + so_size; + TESTASSERT_EQ(status_pdu.packed_size, expected_size); + TESTASSERT_EQ(rlc_am_nr_write_status_pdu(status_pdu, sn_size, &pdu), SRSRAN_SUCCESS); + TESTASSERT_EQ(pdu.N_bytes, expected_size); + { + rlc_status_nack_t nack; + nack.nack_sn = 17; + nack.has_so = true; + nack.so_start = 250; + nack.so_end = 299; + status_pdu.push_nack(nack); + } + expected_size += nack_size + so_size; + TESTASSERT_EQ(status_pdu.packed_size, expected_size); + TESTASSERT_EQ(rlc_am_nr_write_status_pdu(status_pdu, sn_size, &pdu), SRSRAN_SUCCESS); + TESTASSERT_EQ(pdu.N_bytes, expected_size); + { + rlc_status_nack_t nack; + nack.nack_sn = 19; + status_pdu.push_nack(nack); + } + expected_size += nack_size; + TESTASSERT_EQ(status_pdu.packed_size, expected_size); + TESTASSERT_EQ(rlc_am_nr_write_status_pdu(status_pdu, sn_size, &pdu), SRSRAN_SUCCESS); + TESTASSERT_EQ(pdu.N_bytes, expected_size); + { + rlc_status_nack_t nack; + nack.nack_sn = 21; + nack.has_so = true; + nack.so_start = 333; + nack.so_end = 111; + nack.has_nack_range = true; + nack.nack_range = 5; + status_pdu.push_nack(nack); + } + expected_size += nack_size + so_size + range_size; + TESTASSERT_EQ(status_pdu.packed_size, expected_size); + TESTASSERT_EQ(rlc_am_nr_write_status_pdu(status_pdu, sn_size, &pdu), SRSRAN_SUCCESS); + TESTASSERT_EQ(pdu.N_bytes, expected_size); + { + rlc_status_nack_t nack; + nack.nack_sn = 27; + nack.has_so = true; + nack.so_start = 444; + nack.so_end = 666; + nack.has_nack_range = true; + nack.nack_range = 3; + status_pdu.push_nack(nack); + } + expected_size += nack_size + so_size + range_size; + TESTASSERT_EQ(status_pdu.ack_sn, 77); + TESTASSERT_EQ(status_pdu.packed_size, expected_size); + TESTASSERT_EQ(rlc_am_nr_write_status_pdu(status_pdu, sn_size, &pdu), SRSRAN_SUCCESS); + TESTASSERT_EQ(pdu.N_bytes, expected_size); + + // current state: ACK=77, NACKs=[12][14][17 50:99][17 150:199][17 250:299][19][21 333:111 r5][27 444:666 r3] + + // create a copy, check content + rlc_am_nr_status_pdu_t status_pdu_copy = status_pdu; + TESTASSERT_EQ(status_pdu_copy.ack_sn, 77); + TESTASSERT_EQ(status_pdu_copy.packed_size, expected_size); + TESTASSERT_EQ(rlc_am_nr_write_status_pdu(status_pdu_copy, sn_size, &pdu), SRSRAN_SUCCESS); + TESTASSERT_EQ(pdu.N_bytes, expected_size); + + // current state: ACK=77, NACKs=[12][14][17 50:99][17 150:199][17 250:299][19][21 333:111 r5][27 444:666 r3] + + // trim to much larger size: nothing should change + TESTASSERT_EQ(status_pdu.trim(expected_size * 2), true); + TESTASSERT_EQ(status_pdu.ack_sn, 77); + TESTASSERT_EQ(status_pdu.packed_size, expected_size); + TESTASSERT_EQ(rlc_am_nr_write_status_pdu(status_pdu, sn_size, &pdu), SRSRAN_SUCCESS); + TESTASSERT_EQ(pdu.N_bytes, expected_size); + + // trim to exact size: nothing should change + TESTASSERT_EQ(status_pdu.trim(expected_size), true); + TESTASSERT_EQ(status_pdu.ack_sn, 77); + TESTASSERT_EQ(status_pdu.packed_size, expected_size); + TESTASSERT_EQ(rlc_am_nr_write_status_pdu(status_pdu, sn_size, &pdu), SRSRAN_SUCCESS); + TESTASSERT_EQ(pdu.N_bytes, expected_size); + + // trim to (expected_size - 1): this should remove the last NACK and update ACK accordingly + TESTASSERT_EQ(status_pdu.trim(expected_size - 1), true); + expected_size -= nack_size + so_size + range_size; + TESTASSERT_EQ(status_pdu.ack_sn, 27); + TESTASSERT_EQ(status_pdu.packed_size, expected_size); + TESTASSERT_EQ(rlc_am_nr_write_status_pdu(status_pdu, sn_size, &pdu), SRSRAN_SUCCESS); + TESTASSERT_EQ(pdu.N_bytes, expected_size); + + // current state: ACK=27, NACKs=[12][14][17 50:99][17 150:199][17 250:299][19][21 333:111 r5] + + // trim to (expected_size - last two NACKs): this should remove the last NACK and update ACK accordingly + TESTASSERT_EQ(status_pdu.trim(expected_size - (2 * nack_size + so_size + range_size)), true); + expected_size -= 2 * nack_size + so_size + range_size; + TESTASSERT_EQ(status_pdu.ack_sn, 19); + TESTASSERT_EQ(status_pdu.packed_size, expected_size); + TESTASSERT_EQ(rlc_am_nr_write_status_pdu(status_pdu, sn_size, &pdu), SRSRAN_SUCCESS); + TESTASSERT_EQ(pdu.N_bytes, expected_size); + + // current state: ACK=19, NACKs=[12][14][17 50:99][17 150:199][17 250:299] + + // trim to (expected_size - 1): this should remove the last NACK and all other NACKs with the same SN + TESTASSERT_EQ(status_pdu.trim(expected_size - 1), true); + expected_size -= 3 * (nack_size + so_size); + TESTASSERT_EQ(status_pdu.ack_sn, 17); + TESTASSERT_EQ(status_pdu.packed_size, expected_size); + TESTASSERT_EQ(rlc_am_nr_write_status_pdu(status_pdu, sn_size, &pdu), SRSRAN_SUCCESS); + TESTASSERT_EQ(pdu.N_bytes, expected_size); + + // current state: ACK=17, NACKs=[12][14] + + // trim to impossible size = 1: this should report a failure without changes of the PDU + TESTASSERT_EQ(status_pdu.trim(1), false); + TESTASSERT_EQ(status_pdu.ack_sn, 17); + TESTASSERT_EQ(status_pdu.packed_size, expected_size); + TESTASSERT_EQ(rlc_am_nr_write_status_pdu(status_pdu, sn_size, &pdu), SRSRAN_SUCCESS); + TESTASSERT_EQ(pdu.N_bytes, expected_size); + + // current state: ACK=17, NACKs=[12][14] + + // trim to minimum size: this should remove all NACKs and update ACK to the SN of the first NACK + expected_size = min_size; + TESTASSERT_EQ(status_pdu.trim(expected_size), true); + TESTASSERT_EQ(status_pdu.ack_sn, 12); + TESTASSERT_EQ(status_pdu.packed_size, expected_size); + TESTASSERT_EQ(rlc_am_nr_write_status_pdu(status_pdu, sn_size, &pdu), SRSRAN_SUCCESS); + TESTASSERT_EQ(pdu.N_bytes, expected_size); + + // current state: ACK=12, NACKs empty + + // check the copy again - should be unchanged if not a shallow copy + TESTASSERT_EQ(status_pdu_copy.ack_sn, 77); + TESTASSERT_EQ(status_pdu_copy.packed_size, expected_size); + TESTASSERT_EQ(rlc_am_nr_write_status_pdu(status_pdu_copy, sn_size, &pdu), SRSRAN_SUCCESS); + TESTASSERT_EQ(pdu.N_bytes, expected_size); + } + + return SRSRAN_SUCCESS; +} + +///< Control PDU tests (18bit SN) +// Status PDU for 18bit SN with ACK_SN=235929=0x39999=0b11 1001 1001 1001 1001 and no further NACK_SN (E1 bit not set) +int rlc_am_nr_control_pdu_18bit_sn_test1() +{ + test_delimit_logger delimiter("Control PDU (18bit SN) test 1"); + const int len = 3; + std::array tv = {0x0E, 0x66, 0x64}; + srsran::byte_buffer_t pdu = make_pdu_and_log(tv); + + TESTASSERT(rlc_am_is_control_pdu(pdu.msg) == true); + + // unpack PDU + rlc_am_nr_status_pdu_t status_pdu(srsran::rlc_am_nr_sn_size_t::size18bits); + TESTASSERT(rlc_am_nr_read_status_pdu(&pdu, srsran::rlc_am_nr_sn_size_t::size18bits, &status_pdu) == SRSRAN_SUCCESS); + TESTASSERT(status_pdu.ack_sn == 235929); + TESTASSERT(status_pdu.nacks.size() == 0); + + // reset status PDU + pdu.clear(); + + // pack again + TESTASSERT(rlc_am_nr_write_status_pdu(status_pdu, srsran::rlc_am_nr_sn_size_t::size18bits, &pdu) == SRSRAN_SUCCESS); + TESTASSERT(pdu.N_bytes == tv.size()); + + write_pdu_to_pcap(4, pdu.msg, pdu.N_bytes); + + TESTASSERT(memcmp(pdu.msg, tv.data(), pdu.N_bytes) == 0); + + return SRSRAN_SUCCESS; +} + +// Status PDU for 18bit SN with ACK_SN=235929=0x39999=0b11 1001 1001 1001 1001 (E1 bit set) +// and NACK_SN=222822=0x36666=0b11 0110 0110 0110 0110 +int rlc_am_nr_control_pdu_18bit_sn_test2() +{ + test_delimit_logger delimiter("Control PDU (18bit SN) test 2"); + const int len = 6; + std::array tv = {0x0E, 0x66, 0x66, 0xD9, 0x99, 0x80}; + srsran::byte_buffer_t pdu = make_pdu_and_log(tv); + + TESTASSERT(rlc_am_is_control_pdu(pdu.msg) == true); + + // unpack PDU + rlc_am_nr_status_pdu_t status_pdu(srsran::rlc_am_nr_sn_size_t::size18bits); + TESTASSERT(rlc_am_nr_read_status_pdu(&pdu, srsran::rlc_am_nr_sn_size_t::size18bits, &status_pdu) == SRSRAN_SUCCESS); + TESTASSERT(status_pdu.ack_sn == 235929); + TESTASSERT(status_pdu.nacks.size() == 1); + TESTASSERT(status_pdu.nacks[0].nack_sn == 222822); + + // reset status PDU + pdu.clear(); + + // pack again + TESTASSERT(rlc_am_nr_write_status_pdu(status_pdu, srsran::rlc_am_nr_sn_size_t::size18bits, &pdu) == SRSRAN_SUCCESS); + TESTASSERT(pdu.N_bytes == tv.size()); + + write_pdu_to_pcap(4, pdu.msg, pdu.N_bytes); + + TESTASSERT(memcmp(pdu.msg, tv.data(), pdu.N_bytes) == 0); + + return SRSRAN_SUCCESS; +} + +// Status PDU for 18bit SN with ACK_SN=235929=0x39999=0b11 1001 1001 1001 1001 (E1 bit set), +// NACK_SN=222822=0x36666=0b11 0110 0110 0110 0110 (E1 and E2 bit set), +// SO_START=2, SO_END=5, +// NACK_SN=222975=0x366ff=0b11 0110 0110 1111 1111 (E2 bit set), +// SO_START=5, SO_END=0xFFFF +int rlc_am_nr_control_pdu_18bit_sn_test3() +{ + test_delimit_logger delimiter("Control PDU (18bit SN) test 3"); + const int len = 17; + std::array tv = {0b00001110, // D/C | 3CPT | 4ACK_SN_upper + 0b01100110, // 8ACK_SN_center + 0b01100110, // 6ACK_SN_lower | E1 | R + 0b11011001, // 8NACK_SN_upper + 0b10011001, // 8NACK_SN_center + 0b10110000, // 2NACK_SN_lower | E1 | E2 | E3 | 3R + 0x00, // 8SO_START_upper + 0x02, // 8SO_START_lower + 0x00, // 8SO_END_upper + 0x05, // 8SO_END_lower + 0b11011001, // 8NACK_SN_upper + 0b10111111, // 8NACK_SN_center + 0b11010000, // 2NACK_SN_lower | E1 | E2 | E3 | 3R + 0x00, // 8SO_START_upper + 0x05, // 8SO_START_lower + 0xFF, // 8SO_END_upper + 0xFF}; // 8SO_END_lower + srsran::byte_buffer_t pdu = make_pdu_and_log(tv); + + TESTASSERT(rlc_am_is_control_pdu(pdu.msg) == true); + + // unpack PDU + rlc_am_nr_status_pdu_t status_pdu(srsran::rlc_am_nr_sn_size_t::size18bits); + TESTASSERT(rlc_am_nr_read_status_pdu(&pdu, srsran::rlc_am_nr_sn_size_t::size18bits, &status_pdu) == SRSRAN_SUCCESS); + TESTASSERT(status_pdu.ack_sn == 235929); + TESTASSERT(status_pdu.nacks.size() == 2); + TESTASSERT(status_pdu.nacks[0].nack_sn == 222822); + TESTASSERT(status_pdu.nacks[0].has_so == true); + TESTASSERT(status_pdu.nacks[0].so_start == 2); + TESTASSERT(status_pdu.nacks[0].so_end == 5); + TESTASSERT(status_pdu.nacks[1].nack_sn == 222975); + TESTASSERT(status_pdu.nacks[1].has_so == true); + TESTASSERT(status_pdu.nacks[1].so_start == 5); + TESTASSERT(status_pdu.nacks[1].so_end == 0xFFFF); + + // reset status PDU + pdu.clear(); + + // pack again + TESTASSERT(rlc_am_nr_write_status_pdu(status_pdu, srsran::rlc_am_nr_sn_size_t::size18bits, &pdu) == SRSRAN_SUCCESS); + TESTASSERT(pdu.N_bytes == tv.size()); + + write_pdu_to_pcap(4, pdu.msg, pdu.N_bytes); + TESTASSERT(memcmp(pdu.msg, tv.data(), pdu.N_bytes) == 0); + + return SRSRAN_SUCCESS; +} + +// Status PDU for 18bit SN with ACK_SN=235929=0x39999=0b11 1001 1001 1001 1001 (E1 bit set), +// NACK_SN=222822=0x36666=0b11 0110 0110 0110 0110 (E1 and E2 bit set), +// SO_START=2, SO_END=5, +// NACK_SN=222975=0x366ff=0b11 0110 0110 1111 1111 (E1 and E2 bit not set), +int rlc_am_nr_control_pdu_18bit_sn_test4() +{ + test_delimit_logger delimiter("Control PDU (18bit SN) test 4"); + const int len = 13; + std::array tv = {0b00001110, // D/C | 3CPT | 4ACK_SN_upper + 0b01100110, // 8ACK_SN_center + 0b01100110, // 6ACK_SN_lower | E1 | R + 0b11011001, // 8NACK_SN_upper + 0b10011001, // 8NACK_SN_center + 0b10110000, // 2NACK_SN_lower | E1 | E2 | E3 | 3R + 0x00, // 8SO_START_upper + 0x02, // 8SO_START_lower + 0x00, // 8SO_END_upper + 0x05, // 8SO_END_lower + 0b11011001, // 8NACK_SN_upper + 0b10111111, // 8NACK_SN_center + 0b11000000}; // 2NACK_SN_lower | E1 | E2 | E3 | 3R + srsran::byte_buffer_t pdu = make_pdu_and_log(tv); + + TESTASSERT(rlc_am_is_control_pdu(pdu.msg) == true); + + // unpack PDU + rlc_am_nr_status_pdu_t status_pdu(srsran::rlc_am_nr_sn_size_t::size18bits); + TESTASSERT(rlc_am_nr_read_status_pdu(&pdu, srsran::rlc_am_nr_sn_size_t::size18bits, &status_pdu) == SRSRAN_SUCCESS); + TESTASSERT(status_pdu.ack_sn == 235929); + TESTASSERT(status_pdu.nacks.size() == 2); + TESTASSERT(status_pdu.nacks[0].nack_sn == 222822); + TESTASSERT(status_pdu.nacks[0].has_so == true); + TESTASSERT(status_pdu.nacks[0].so_start == 2); + TESTASSERT(status_pdu.nacks[0].so_end == 5); + TESTASSERT(status_pdu.nacks[1].nack_sn == 222975); + TESTASSERT(status_pdu.nacks[1].has_so == false); + + // reset status PDU + pdu.clear(); + + // pack again + TESTASSERT(rlc_am_nr_write_status_pdu(status_pdu, srsran::rlc_am_nr_sn_size_t::size18bits, &pdu) == SRSRAN_SUCCESS); + TESTASSERT(pdu.N_bytes == tv.size()); + + write_pdu_to_pcap(4, pdu.msg, pdu.N_bytes); + TESTASSERT(memcmp(pdu.msg, tv.data(), pdu.N_bytes) == 0); + + return SRSRAN_SUCCESS; +} + +// Malformed Status PDU, similar to test3 but with E1 still set at the end of the PDU +// Status PDU for 18bit SN with ACK_SN=235929=0x39999=0b11 1001 1001 1001 1001 (E1 bit set), +// NACK_SN=222822=0x36666=0b11 0110 0110 0110 0110 (E1 and E2 bit set), +// SO_START=2, SO_END=5, +// NACK_SN=222975=0x366ff=0b11 0110 0110 1111 1111 ([!E1!] and E2 bit set), +// SO_START=5, SO_END=0xFFFF +int rlc_am_nr_control_pdu_18bit_sn_test5() +{ + test_delimit_logger delimiter("Control PDU (18bit SN) test 5"); + const int len = 17; + std::array tv = {0b00001110, // D/C | 3CPT | 4ACK_SN_upper + 0b01100110, // 8ACK_SN_center + 0b01100110, // 6ACK_SN_lower | E1 | R + 0b11011001, // 8NACK_SN_upper + 0b10011001, // 8NACK_SN_center + 0b10110000, // 2NACK_SN_lower | E1 | E2 | E3 | 3R + 0x00, // 8SO_START_upper + 0x02, // 8SO_START_lower + 0x00, // 8SO_END_upper + 0x05, // 8SO_END_lower + 0b11011001, // 8NACK_SN_upper + 0b10111111, // 8NACK_SN_center + 0b11110000, // 2NACK_SN_lower | [!E1!] | E2 | E3 | 3R + 0x00, // 8SO_START_upper + 0x05, // 8SO_START_lower + 0xFF, // 8SO_END_upper + 0xFF}; // 8SO_END_lower + srsran::byte_buffer_t pdu = make_pdu_and_log(tv); + + TESTASSERT(rlc_am_is_control_pdu(pdu.msg) == true); + + // unpack PDU + rlc_am_nr_status_pdu_t status_pdu(srsran::rlc_am_nr_sn_size_t::size18bits); + TESTASSERT(rlc_am_nr_read_status_pdu(&pdu, srsran::rlc_am_nr_sn_size_t::size18bits, &status_pdu) == 0); + + return SRSRAN_SUCCESS; +} + +// Status PDU for 18bit SN with ACK_SN=200977=0x31111=0b11 0001 0001 0001 0001, +// NACK range0: 3 full SDUs, NACK_SN=69905=0x11111=0b01 0001 0001 0001 0001 +// NACK range1: missing segment sequence across 4 SDUs +// starting at NACK_SN=69913=0x11119=0b01 0001 0001 0001 1001, SO_START=2, +// ending at NACK_SN=69916, SO_END=5 +// E1 and E3 bit set on first NACK, E2 and E3 bit set on the second. +int rlc_am_nr_control_pdu_18bit_sn_test_nack_range() +{ + test_delimit_logger delimiter("Control PDU (18bit SN) test NACK range"); + const int len = 15; + std::array tv = {0b00001100, // D/C | 3CPT | 4ACK_SN_upper + 0b01000100, // 8ACK_SN_center + 0b01000110, // 6ACK_SN_lower | E1 | R + 0b01000100, // 8NACK_SN_upper + 0b01000100, // 8NACK_SN_center + 0b01101000, // 2NACK_SN_lower | E1 | E2 | E3 | 3R + 0x03, // 8NACK_range + 0b01000100, // 8NACK_SN_upper + 0b01000110, // 8NACK_SN_center + 0b01011000, // 2NACK_SN_lower | E1 | E2 | E3 | 3R + 0x00, // 8SO_START_upper + 0x02, // 8SO_START_lower + 0x00, // 8SO_END_upper + 0x05, // 8SO_END_lower + 0x04}; // 8NACK_range + srsran::byte_buffer_t pdu = make_pdu_and_log(tv); + + TESTASSERT(rlc_am_is_control_pdu(pdu.msg) == true); + + // unpack PDU + rlc_am_nr_status_pdu_t status_pdu(srsran::rlc_am_nr_sn_size_t::size18bits); + TESTASSERT(rlc_am_nr_read_status_pdu(&pdu, srsran::rlc_am_nr_sn_size_t::size18bits, &status_pdu) == SRSRAN_SUCCESS); + TESTASSERT(status_pdu.ack_sn == 200977); + TESTASSERT(status_pdu.nacks.size() == 2); + TESTASSERT(status_pdu.nacks[0].nack_sn == 69905); + TESTASSERT(status_pdu.nacks[0].has_so == false); + TESTASSERT(status_pdu.nacks[0].has_nack_range == true); + TESTASSERT(status_pdu.nacks[0].nack_range == 3); + + TESTASSERT(status_pdu.nacks[1].nack_sn == 69913); + TESTASSERT(status_pdu.nacks[1].has_so == true); + TESTASSERT(status_pdu.nacks[1].so_start == 2); + TESTASSERT(status_pdu.nacks[1].so_end == 5); + TESTASSERT(status_pdu.nacks[1].has_nack_range == true); + TESTASSERT(status_pdu.nacks[1].nack_range == 4); + + // reset status PDU + pdu.clear(); + + // pack again + TESTASSERT(rlc_am_nr_write_status_pdu(status_pdu, srsran::rlc_am_nr_sn_size_t::size18bits, &pdu) == SRSRAN_SUCCESS); + TESTASSERT(pdu.N_bytes == tv.size()); + + write_pdu_to_pcap(4, pdu.msg, pdu.N_bytes); + TESTASSERT(memcmp(pdu.msg, tv.data(), pdu.N_bytes) == 0); + + return SRSRAN_SUCCESS; +} + int main(int argc, char** argv) { static const struct option long_options[] = {{"pcap", no_argument, nullptr, 'p'}, {nullptr, 0, nullptr, 0}}; @@ -441,28 +991,73 @@ int main(int argc, char** argv) return SRSRAN_ERROR; } - if (rlc_am_nr_control_pdu_test1()) { - fprintf(stderr, "rlc_am_nr_control_pdu_test1() failed.\n"); + if (rlc_am_nr_control_pdu_12bit_sn_test1()) { + fprintf(stderr, "rlc_am_nr_control_pdu_12bit_sn_test1() failed.\n"); + return SRSRAN_ERROR; + } + + if (rlc_am_nr_control_pdu_12bit_sn_test2()) { + fprintf(stderr, "rlc_am_nr_control_pdu_12bit_sn_test2() failed.\n"); + return SRSRAN_ERROR; + } + + if (rlc_am_nr_control_pdu_12bit_sn_test3()) { + fprintf(stderr, "rlc_am_nr_control_pdu_12bit_sn_test3() failed.\n"); + return SRSRAN_ERROR; + } + + if (rlc_am_nr_control_pdu_12bit_sn_test4()) { + fprintf(stderr, "rlc_am_nr_control_pdu_12bit_sn_test4() failed.\n"); + return SRSRAN_ERROR; + } + + if (rlc_am_nr_control_pdu_12bit_sn_test5()) { + fprintf(stderr, "rlc_am_nr_control_pdu_12bit_sn_test5() failed.\n"); + return SRSRAN_ERROR; + } + + if (rlc_am_nr_control_pdu_12bit_sn_test_nack_range()) { + fprintf(stderr, "rlc_am_nr_control_pdu_12bit_sn_test_nack_range() failed.\n"); + return SRSRAN_ERROR; + } + + if (rlc_am_nr_control_pdu_test_trimming(rlc_am_nr_sn_size_t::size12bits)) { + fprintf(stderr, "rlc_am_nr_control_pdu_test_trimming(size12bits) failed.\n"); + return SRSRAN_ERROR; + } + + if (rlc_am_nr_control_pdu_18bit_sn_test1()) { + fprintf(stderr, "rlc_am_nr_control_pdu_18bit_sn_test1() failed.\n"); + return SRSRAN_ERROR; + } + + if (rlc_am_nr_control_pdu_18bit_sn_test2()) { + fprintf(stderr, "rlc_am_nr_control_pdu_18bit_sn_test2() failed.\n"); + return SRSRAN_ERROR; + } + + if (rlc_am_nr_control_pdu_18bit_sn_test3()) { + fprintf(stderr, "rlc_am_nr_control_pdu_18bit_sn_test3() failed.\n"); return SRSRAN_ERROR; } - if (rlc_am_nr_control_pdu_test2()) { - fprintf(stderr, "rlc_am_nr_control_pdu_test2() failed.\n"); + if (rlc_am_nr_control_pdu_18bit_sn_test4()) { + fprintf(stderr, "rlc_am_nr_control_pdu_18bit_sn_test4() failed.\n"); return SRSRAN_ERROR; } - if (rlc_am_nr_control_pdu_test3()) { - fprintf(stderr, "rlc_am_nr_control_pdu_test3() failed.\n"); + if (rlc_am_nr_control_pdu_18bit_sn_test5()) { + fprintf(stderr, "rlc_am_nr_control_pdu_18bit_sn_test5() failed.\n"); return SRSRAN_ERROR; } - if (rlc_am_nr_control_pdu_test4()) { - fprintf(stderr, "rlc_am_nr_control_pdu_test4() failed.\n"); + if (rlc_am_nr_control_pdu_18bit_sn_test_nack_range()) { + fprintf(stderr, "rlc_am_nr_control_pdu_18bit_sn_test_nack_range() failed.\n"); return SRSRAN_ERROR; } - if (rlc_am_nr_control_pdu_test5()) { - fprintf(stderr, "rlc_am_nr_control_pdu_test5() failed.\n"); + if (rlc_am_nr_control_pdu_test_trimming(rlc_am_nr_sn_size_t::size18bits)) { + fprintf(stderr, "rlc_am_nr_control_pdu_test_trimming(size18bits) failed.\n"); return SRSRAN_ERROR; } diff --git a/lib/test/rlc/rlc_am_nr_test.cc b/lib/test/rlc/rlc_am_nr_test.cc index 6e2bca76e..1773a3fe1 100644 --- a/lib/test/rlc/rlc_am_nr_test.cc +++ b/lib/test/rlc/rlc_am_nr_test.cc @@ -35,25 +35,29 @@ using namespace srsue; using namespace srsran; -int basic_test_tx(rlc_am* rlc, byte_buffer_t pdu_bufs[NBUFS]) +int basic_test_tx(rlc_am* rlc, byte_buffer_t pdu_bufs[NBUFS], rlc_am_nr_sn_size_t sn_size) { // Push 5 SDUs into RLC1 unique_byte_buffer_t sdu_bufs[NBUFS]; + constexpr uint32_t payload_size = 1; // Give each buffer a size of 1 byte for (int i = 0; i < NBUFS; i++) { sdu_bufs[i] = srsran::make_byte_buffer(); - sdu_bufs[i]->msg[0] = i; // Write the index into the buffer - sdu_bufs[i]->N_bytes = 1; // Give each buffer a size of 1 byte - sdu_bufs[i]->md.pdcp_sn = i; // PDCP SN for notifications + sdu_bufs[i]->msg[0] = i; // Write the index into the buffer + sdu_bufs[i]->N_bytes = payload_size; // Give each buffer a size of 1 byte + sdu_bufs[i]->md.pdcp_sn = i; // PDCP SN for notifications rlc->write_sdu(std::move(sdu_bufs[i])); } - TESTASSERT_EQ(15, rlc->get_buffer_state()); // 2 Bytes * NBUFFS (header size) + NBUFFS (data) = 15 + uint32_t header_size = sn_size == rlc_am_nr_sn_size_t::size12bits ? 2 : 3; + uint32_t data_pdu_size = header_size + payload_size; + uint32_t expect_buffer_state = NBUFS * data_pdu_size; + TESTASSERT_EQ(expect_buffer_state, rlc->get_buffer_state()); // Read 5 PDUs from RLC1 (1 byte each) for (int i = 0; i < NBUFS; i++) { - uint32_t len = rlc->read_pdu(pdu_bufs[i].msg, 3); // 2 bytes for header + 1 byte payload + uint32_t len = rlc->read_pdu(pdu_bufs[i].msg, data_pdu_size); pdu_bufs[i].N_bytes = len; - TESTASSERT_EQ(3, len); + TESTASSERT_EQ(data_pdu_size, len); } TESTASSERT_EQ(0, rlc->get_buffer_state()); @@ -62,30 +66,29 @@ int basic_test_tx(rlc_am* rlc, byte_buffer_t pdu_bufs[NBUFS]) /* * Test the limits of the TX/RX window checkers - * */ -int window_checker_test() +int window_checker_test(rlc_am_nr_sn_size_t sn_size) { rlc_am_tester tester; timer_handler timers(8); auto& test_logger = srslog::fetch_basic_logger("TESTER "); - test_delimit_logger delimiter("window checkers"); + test_delimit_logger delimiter("window checkers ({} bit SN)", to_number(sn_size)); rlc_am rlc1(srsran_rat_t::nr, srslog::fetch_basic_logger("RLC_AM_1"), 1, &tester, &tester, &timers); rlc_am_nr_tx* tx = dynamic_cast(rlc1.get_tx()); rlc_am_nr_rx* rx = dynamic_cast(rlc1.get_rx()); - if (not rlc1.configure(rlc_config_t::default_rlc_am_nr_config())) { + if (not rlc1.configure(rlc_config_t::default_rlc_am_nr_config(to_number(sn_size)))) { return SRSRAN_ERROR; } { // RLC1 RX_NEXT == 0 and RLC2 TX_NEXT_ACK == 0 uint32_t sn_inside_below = 0; - uint32_t sn_inside_above = 2047; - uint32_t sn_outside_below = 4095; - uint32_t sn_outside_above = 2048; + uint32_t sn_inside_above = cardinality(sn_size) / 2 - 1; + uint32_t sn_outside_below = cardinality(sn_size) - 1; + uint32_t sn_outside_above = cardinality(sn_size) / 2; TESTASSERT_EQ(true, rx->inside_rx_window(sn_inside_below)); TESTASSERT_EQ(true, rx->inside_rx_window(sn_inside_above)); TESTASSERT_EQ(false, rx->inside_rx_window(sn_outside_below)); @@ -97,9 +100,11 @@ int window_checker_test() } rlc_am_nr_rx_state_t rx_st = {}; - rx_st.rx_next = 4095; + rx_st.rx_next = cardinality(sn_size) - 1; + ; rlc_am_nr_tx_state_t tx_st = {}; - tx_st.tx_next_ack = 4095; + tx_st.tx_next_ack = cardinality(sn_size) - 1; + ; rx->set_rx_state(rx_st); tx->set_tx_state(tx_st); @@ -107,9 +112,9 @@ int window_checker_test() { // RX_NEXT == 4095 TX_NEXT_ACK == 4095 uint32_t sn_inside_below = 0; - uint32_t sn_inside_above = 2046; - uint32_t sn_outside_below = 4094; - uint32_t sn_outside_above = 2048; + uint32_t sn_inside_above = cardinality(sn_size) / 2 - 2; + uint32_t sn_outside_below = cardinality(sn_size) - 2; + uint32_t sn_outside_above = cardinality(sn_size) / 2; TESTASSERT_EQ(true, rx->inside_rx_window(sn_inside_below)); TESTASSERT_EQ(true, rx->inside_rx_window(sn_inside_above)); TESTASSERT_EQ(false, rx->inside_rx_window(sn_outside_below)); @@ -126,19 +131,19 @@ int window_checker_test() * Test is retx_segmentation required * */ -int retx_segmentation_required_checker_test() +int retx_segmentation_required_checker_test(rlc_am_nr_sn_size_t sn_size) { rlc_am_tester tester; timer_handler timers(8); auto& test_logger = srslog::fetch_basic_logger("TESTER "); - test_delimit_logger delimiter("retx segmentation required checkers"); + test_delimit_logger delimiter("retx segmentation required checkers ({} bit SN)", to_number(sn_size)); rlc_am rlc1(srsran_rat_t::nr, srslog::fetch_basic_logger("RLC_AM_1"), 1, &tester, &tester, &timers); rlc_am_nr_tx* tx = dynamic_cast(rlc1.get_tx()); rlc_am_nr_rx* rx = dynamic_cast(rlc1.get_rx()); - if (not rlc1.configure(rlc_config_t::default_rlc_am_nr_config())) { + if (not rlc1.configure(rlc_config_t::default_rlc_am_nr_config(to_number(sn_size)))) { return SRSRAN_ERROR; } @@ -156,10 +161,10 @@ int retx_segmentation_required_checker_test() // Test full SDU retx { - uint32_t nof_bytes = 8; - rlc_amd_retx_t retx = {}; - retx.sn = 0; - retx.is_segment = false; + uint32_t nof_bytes = 8; + rlc_amd_retx_nr_t retx = {}; + retx.sn = 0; + retx.is_segment = false; tx->is_retx_segmentation_required(retx, nof_bytes); TESTASSERT_EQ(false, tx->is_retx_segmentation_required(retx, nof_bytes)); @@ -167,8 +172,8 @@ int retx_segmentation_required_checker_test() // Test SDU retx segmentation required { - uint32_t nof_bytes = 4; - rlc_amd_retx_t retx; + uint32_t nof_bytes = 4; + rlc_amd_retx_nr_t retx; retx.sn = 0; retx.is_segment = false; @@ -178,12 +183,12 @@ int retx_segmentation_required_checker_test() // Test full SDU segment retx { - uint32_t nof_bytes = 40; - rlc_amd_retx_t retx = {}; - retx.sn = 0; - retx.is_segment = true; - retx.so_start = 4; - retx.so_end = 6; + uint32_t nof_bytes = 40; + rlc_amd_retx_nr_t retx = {}; + retx.sn = 0; + retx.is_segment = true; + retx.so_start = 4; + retx.segment_length = 2; tx->is_retx_segmentation_required(retx, nof_bytes); TESTASSERT_EQ(false, tx->is_retx_segmentation_required(retx, nof_bytes)); @@ -191,12 +196,12 @@ int retx_segmentation_required_checker_test() // Test SDU segment retx segmentation required { - uint32_t nof_bytes = 4; - rlc_amd_retx_t retx = {}; - retx.sn = 0; - retx.is_segment = true; - retx.so_start = 4; - retx.so_end = 6; + uint32_t nof_bytes = 4; + rlc_amd_retx_nr_t retx = {}; + retx.sn = 0; + retx.is_segment = true; + retx.so_start = 4; + retx.segment_length = 2; tx->is_retx_segmentation_required(retx, nof_bytes); TESTASSERT_EQ(true, tx->is_retx_segmentation_required(retx, nof_bytes)); @@ -212,14 +217,14 @@ int retx_segmentation_required_checker_test() * will trigger the status report. * Poll PDU is configured to 4, so the 5th PDU should set the polling bit. */ -int basic_test() +int basic_test(rlc_am_nr_sn_size_t sn_size) { rlc_am_tester tester; timer_handler timers(8); byte_buffer_t pdu_bufs[NBUFS]; auto& test_logger = srslog::fetch_basic_logger("TESTER "); - test_delimit_logger delimiter("basic tx/rx"); + test_delimit_logger delimiter("basic tx/rx ({} bit SN)", to_number(sn_size)); rlc_am rlc1(srsran_rat_t::nr, srslog::fetch_basic_logger("RLC_AM_1"), 1, &tester, &tester, &timers); rlc_am rlc2(srsran_rat_t::nr, srslog::fetch_basic_logger("RLC_AM_2"), 1, &tester, &tester, &timers); @@ -228,18 +233,18 @@ int basic_test() rlc_am_nr_tx* tx2 = dynamic_cast(rlc2.get_tx()); rlc_am_nr_rx* rx2 = dynamic_cast(rlc2.get_rx()); - // before configuring entity - TESTASSERT_EQ(0, rlc1.get_buffer_state()); - - if (not rlc1.configure(rlc_config_t::default_rlc_am_nr_config())) { + if (not rlc1.configure(rlc_config_t::default_rlc_am_nr_config(to_number(sn_size)))) { return -1; } - if (not rlc2.configure(rlc_config_t::default_rlc_am_nr_config())) { + if (not rlc2.configure(rlc_config_t::default_rlc_am_nr_config(to_number(sn_size)))) { return -1; } - basic_test_tx(&rlc1, pdu_bufs); + // after configuring entity + TESTASSERT_EQ(0, rlc1.get_buffer_state()); + + basic_test_tx(&rlc1, pdu_bufs, sn_size); // Write 5 PDUs into RLC2 for (int i = 0; i < NBUFS; i++) { @@ -255,8 +260,8 @@ int basic_test() TESTASSERT_EQ(0, rlc2.get_buffer_state()); // Assert status is correct - rlc_am_nr_status_pdu_t status_check = {}; - rlc_am_nr_read_status_pdu(&status_buf, rlc_am_nr_sn_size_t::size12bits, &status_check); + rlc_am_nr_status_pdu_t status_check(sn_size); + rlc_am_nr_read_status_pdu(&status_buf, sn_size, &status_check); TESTASSERT_EQ(5, status_check.ack_sn); // 5 is the last SN that was not received. // Write status PDU to RLC1 @@ -265,12 +270,27 @@ int basic_test() // Check TX_NEXT_ACK rlc_am_nr_tx_state_t st = tx1->get_tx_state(); TESTASSERT_EQ(5, st.tx_next_ack); - TESTASSERT_EQ(0, tx1->get_tx_window_size()); + TESTASSERT_EQ(0, tx1->get_tx_window_utilization()); + + // Check PDCP notifications + TESTASSERT_EQ(5, tester.notified_counts.size()); + for (uint16_t i = 0; i < tester.sdus.size(); i++) { + TESTASSERT_EQ(1, tester.sdus[i]->N_bytes); + TESTASSERT_EQ(i, *(tester.sdus[i]->msg)); + TESTASSERT_EQ(1, tester.notified_counts[i]); + } // Check statistics rlc_bearer_metrics_t metrics1 = rlc1.get_metrics(); rlc_bearer_metrics_t metrics2 = rlc2.get_metrics(); + constexpr uint32_t payload_size = 1; + uint32_t header_size = sn_size == rlc_am_nr_sn_size_t::size12bits ? 2 : 3; + uint32_t data_pdu_size = header_size + payload_size; + constexpr uint32_t status_pdu_size = 3; + uint32_t total_tx_pdu_bytes = NBUFS * data_pdu_size; // NBUFS * PDU size + uint32_t total_rx_pdu_bytes = status_pdu_size; // One status PDU + // RLC1 PDU metrics TESTASSERT_EQ(5, metrics1.num_tx_sdus); TESTASSERT_EQ(0, metrics1.num_rx_sdus); @@ -279,10 +299,10 @@ int basic_test() TESTASSERT_EQ(0, metrics1.num_lost_sdus); // RLC1 SDU metrics TESTASSERT_EQ(5, metrics1.num_tx_pdus); - TESTASSERT_EQ(1, metrics1.num_rx_pdus); // One status PDU - TESTASSERT_EQ(15, metrics1.num_tx_pdu_bytes); // 2 Bytes * NBUFFS (header size) + NBUFFS (data) = 15 - TESTASSERT_EQ(3, metrics1.num_rx_pdu_bytes); // One status PDU - TESTASSERT_EQ(0, metrics1.num_lost_sdus); // No lost SDUs + TESTASSERT_EQ(1, metrics1.num_rx_pdus); // One status PDU + TESTASSERT_EQ(total_tx_pdu_bytes, metrics1.num_tx_pdu_bytes); // NBUFS * PDU size + TESTASSERT_EQ(total_rx_pdu_bytes, metrics1.num_rx_pdu_bytes); // One status PDU + TESTASSERT_EQ(0, metrics1.num_lost_sdus); // No lost SDUs // RLC2 PDU metrics TESTASSERT_EQ(0, metrics2.num_tx_sdus); @@ -291,11 +311,11 @@ int basic_test() TESTASSERT_EQ(5, metrics2.num_rx_sdu_bytes); TESTASSERT_EQ(0, metrics2.num_lost_sdus); // RLC2 SDU metrics - TESTASSERT_EQ(1, metrics2.num_tx_pdus); // One status PDU - TESTASSERT_EQ(5, metrics2.num_rx_pdus); // 5 SDUs - TESTASSERT_EQ(3, metrics2.num_tx_pdu_bytes); // One status PDU - TESTASSERT_EQ(15, metrics2.num_rx_pdu_bytes); // 2 Bytes * NBUFFS (header size) + NBUFFS (data) = 15 - TESTASSERT_EQ(0, metrics2.num_lost_sdus); // No lost SDUs + TESTASSERT_EQ(1, metrics2.num_tx_pdus); // One status PDU + TESTASSERT_EQ(5, metrics2.num_rx_pdus); // 5 SDUs + TESTASSERT_EQ(total_rx_pdu_bytes, metrics2.num_tx_pdu_bytes); // One status PDU + TESTASSERT_EQ(total_tx_pdu_bytes, metrics2.num_rx_pdu_bytes); // NBUFS * PDU size + TESTASSERT_EQ(0, metrics2.num_lost_sdus); // No lost SDUs return SRSRAN_SUCCESS; } @@ -303,8 +323,9 @@ int basic_test() * Test the loss of a single PDU. * NACK should be visible in the status report. * Retx after NACK should be present too. + * No further status reports shall be issued. */ -int lost_pdu_test() +int lost_pdu_test(rlc_am_nr_sn_size_t sn_size) { rlc_am_tester tester; timer_handler timers(8); @@ -313,20 +334,26 @@ int lost_pdu_test() auto& test_logger = srslog::fetch_basic_logger("TESTER "); rlc_am rlc1(srsran_rat_t::nr, srslog::fetch_basic_logger("RLC_AM_1"), 1, &tester, &tester, &timers); rlc_am rlc2(srsran_rat_t::nr, srslog::fetch_basic_logger("RLC_AM_2"), 1, &tester, &tester, &timers); - test_delimit_logger delimiter("lost PDU"); + test_delimit_logger delimiter("lost PDU ({} bit SN)", to_number(sn_size)); - // before configuring entity - TESTASSERT(0 == rlc1.get_buffer_state()); + constexpr uint32_t payload_size = 1; + uint32_t header_size = sn_size == rlc_am_nr_sn_size_t::size12bits ? 2 : 3; + uint32_t data_pdu_size = header_size + payload_size; + uint32_t expect_buffer_state = NBUFS * data_pdu_size; - if (not rlc1.configure(rlc_config_t::default_rlc_am_nr_config())) { + if (not rlc1.configure(rlc_config_t::default_rlc_am_nr_config(to_number(sn_size)))) { return -1; } - if (not rlc2.configure(rlc_config_t::default_rlc_am_nr_config())) { + rlc_config_t rlc2_config = rlc_config_t::default_rlc_am_nr_config(to_number(sn_size)); + if (not rlc2.configure(rlc2_config)) { return -1; } - basic_test_tx(&rlc1, pdu_bufs); + // after configuring entity + TESTASSERT(0 == rlc1.get_buffer_state()); + + basic_test_tx(&rlc1, pdu_bufs, sn_size); // Write 5 PDUs into RLC2 for (int i = 0; i < NBUFS; i++) { @@ -346,8 +373,8 @@ int lost_pdu_test() TESTASSERT(0 == rlc2.get_buffer_state()); // Assert status is correct - rlc_am_nr_status_pdu_t status_check = {}; - rlc_am_nr_read_status_pdu(&status_buf, rlc_am_nr_sn_size_t::size12bits, &status_check); + rlc_am_nr_status_pdu_t status_check(sn_size); + rlc_am_nr_read_status_pdu(&status_buf, sn_size, &status_check); TESTASSERT_EQ(3, status_check.ack_sn); // 3 is the next expected SN (i.e. the lost packet.) // Write status PDU to RLC1 @@ -360,45 +387,84 @@ int lost_pdu_test() } // t-reassembly has expired. There should be a NACK in the status report. - TESTASSERT_EQ(5, rlc2.get_buffer_state()); + constexpr uint32_t status_pdu_ack_size = 3; + uint32_t status_pdu_nack_size = sn_size == rlc_am_nr_sn_size_t::size12bits ? 2 : 3; + TESTASSERT_EQ(status_pdu_ack_size + status_pdu_nack_size, rlc2.get_buffer_state()); { // Read status PDU from RLC2 byte_buffer_t status_buf; - int len = rlc2.read_pdu(status_buf.msg, 5); + uint32_t len = rlc2.read_pdu(status_buf.msg, status_pdu_ack_size + status_pdu_nack_size); status_buf.N_bytes = len; TESTASSERT_EQ(0, rlc2.get_buffer_state()); // Assert status is correct - rlc_am_nr_status_pdu_t status_check = {}; - rlc_am_nr_read_status_pdu(&status_buf, rlc_am_nr_sn_size_t::size12bits, &status_check); + rlc_am_nr_status_pdu_t status_check(sn_size); + rlc_am_nr_read_status_pdu(&status_buf, sn_size, &status_check); TESTASSERT_EQ(5, status_check.ack_sn); // 5 is the next expected SN. - TESTASSERT_EQ(1, status_check.N_nack); // We lost one PDU. + TESTASSERT_EQ(1, status_check.nacks.size()); // We lost one PDU. TESTASSERT_EQ(3, status_check.nacks[0].nack_sn); // Lost PDU SN=3. // Write status PDU to RLC1 rlc1.write_pdu(status_buf.msg, status_buf.N_bytes); // Check there is an Retx of SN=3 - TESTASSERT_EQ(3, rlc1.get_buffer_state()); + TESTASSERT_EQ(data_pdu_size, rlc1.get_buffer_state()); } { // Check correct re-transmission byte_buffer_t retx_buf; - int len = rlc1.read_pdu(retx_buf.msg, 3); + uint32_t len = rlc1.read_pdu(retx_buf.msg, data_pdu_size); retx_buf.N_bytes = len; - TESTASSERT_EQ(3, len); + TESTASSERT_EQ(data_pdu_size, len); + // Polling bit on the RETX should be required, as the buffers are not empty. rlc2.write_pdu(retx_buf.msg, retx_buf.N_bytes); + TESTASSERT_EQ(0, rlc2.get_buffer_state()); // t-StatusProhibit is still running + } + + // Step timers until t-StatusProhibit expires + for (int cnt = 0; cnt < 8; cnt++) { + timers.step_all(); + } + TESTASSERT_EQ(3, rlc2.get_buffer_state()); // t-StatusProhibit no longer running + + { + // Double check status report + byte_buffer_t status_buf; + int len = rlc2.read_pdu(status_buf.msg, 3); + status_buf.N_bytes = len; + TESTASSERT_EQ(0, rlc2.get_buffer_state()); + + // Assert status is correct + rlc_am_nr_status_pdu_t status_check(sn_size); + rlc_am_nr_read_status_pdu(&status_buf, sn_size, &status_check); + TESTASSERT_EQ(5, status_check.ack_sn); // 5 is the next expected SN. + TESTASSERT_EQ(0, status_check.nacks.size()); // All PDUs are acked now + } + + { + // rlc2 should not issue further status PDUs as time passes (even after expiry of t_status_prohibit) + int32_t checktime = 2 * rlc2_config.am_nr.t_status_prohibit; + for (int cnt = 0; cnt < checktime; cnt++) { + timers.step_all(); + TESTASSERT_EQ(0, rlc2.get_buffer_state()); + } } // Check statistics rlc_bearer_metrics_t metrics1 = rlc1.get_metrics(); rlc_bearer_metrics_t metrics2 = rlc2.get_metrics(); + uint32_t total_tx_pdu_bytes1 = (NBUFS + 1) * data_pdu_size; // (NBUFS + 1 RETX) * PDU size + uint32_t total_rx_pdu_bytes1 = 2 * status_pdu_ack_size + status_pdu_nack_size; // Two status PDU (one with a NACK) + uint32_t total_tx_pdu_bytes2 = + 3 * status_pdu_ack_size + status_pdu_nack_size; // Three status PDU (one with a NACK, two without) + uint32_t total_rx_pdu_bytes2 = (NBUFS)*data_pdu_size; // (NBUFS - 1 Lost + 1 RETX) * PDU size + // SDU metrics TESTASSERT_EQ(5, metrics1.num_tx_sdus); TESTASSERT_EQ(0, metrics1.num_rx_sdus); @@ -406,11 +472,11 @@ int lost_pdu_test() TESTASSERT_EQ(0, metrics1.num_rx_sdu_bytes); TESTASSERT_EQ(0, metrics1.num_lost_sdus); // PDU metrics - TESTASSERT_EQ(5 + 1, metrics1.num_tx_pdus); // One re-transmission - TESTASSERT_EQ(2, metrics1.num_rx_pdus); // One status PDU - TESTASSERT_EQ(18, metrics1.num_tx_pdu_bytes); // 2 Bytes * NBUFFS (header size) + NBUFFS (data) + 1 rext (3) = 18 - TESTASSERT_EQ(3 + 5, metrics1.num_rx_pdu_bytes); // Two status PDU (one with a NACK) - TESTASSERT_EQ(0, metrics1.num_lost_sdus); // No lost SDUs + TESTASSERT_EQ(5 + 1, metrics1.num_tx_pdus); // One re-transmission + TESTASSERT_EQ(2, metrics1.num_rx_pdus); // One status PDU + TESTASSERT_EQ(total_tx_pdu_bytes1, metrics1.num_tx_pdu_bytes); // (NBUFS + 1 RETX) * PDU size + TESTASSERT_EQ(total_rx_pdu_bytes1, metrics1.num_rx_pdu_bytes); // Two status PDU (one with a NACK) + TESTASSERT_EQ(0, metrics1.num_lost_sdus); // No lost SDUs // PDU metrics TESTASSERT_EQ(0, metrics2.num_tx_sdus); @@ -419,97 +485,22 @@ int lost_pdu_test() TESTASSERT_EQ(5, metrics2.num_rx_sdu_bytes); TESTASSERT_EQ(0, metrics2.num_lost_sdus); // SDU metrics - TESTASSERT_EQ(2, metrics2.num_tx_pdus); // Two status PDUs - TESTASSERT_EQ(5, metrics2.num_rx_pdus); // 5 PDUs (6 tx'ed, but one was lost) - TESTASSERT_EQ(5 + 3, metrics2.num_tx_pdu_bytes); // Two status PDU (one with a NACK) - TESTASSERT_EQ(15, metrics2.num_rx_pdu_bytes); // 2 Bytes * NBUFFS (header size) + NBUFFS (data) = 15 - TESTASSERT_EQ(0, metrics2.num_lost_sdus); // No lost SDUs + TESTASSERT_EQ(3, metrics2.num_tx_pdus); // Three status PDUs + TESTASSERT_EQ(5, metrics2.num_rx_pdus); // 5 PDUs (6 tx'ed, but one was lost) + TESTASSERT_EQ(total_tx_pdu_bytes2, metrics2.num_tx_pdu_bytes); // Three status PDU (one with a NACK, two without) + TESTASSERT_EQ(total_rx_pdu_bytes2, metrics2.num_rx_pdu_bytes); // (NBUFS - 1 Lost + 1 RETX) * PDU size + TESTASSERT_EQ(0, metrics2.num_lost_sdus); // No lost SDUs return SRSRAN_SUCCESS; } /* - * Test the basic segmentation of a single SDU. - * A single SDU of 3 bytes is segmented into 3 PDUs + * Test the loss of a single PDU with NACK duplicate + * NACK should be visible in the status report. + * + * Retx after NACK should be present too. + * No further status reports shall be issued. */ -int basic_segmentation_test() -{ - rlc_am_tester tester; - timer_handler timers(8); - auto& test_logger = srslog::fetch_basic_logger("TESTER "); - test_delimit_logger delimiter("basic segmentation"); - rlc_am rlc1(srsran_rat_t::nr, srslog::fetch_basic_logger("RLC_AM_1"), 1, &tester, &tester, &timers); - rlc_am rlc2(srsran_rat_t::nr, srslog::fetch_basic_logger("RLC_AM_2"), 1, &tester, &tester, &timers); - - rlc_am_nr_tx* tx1 = dynamic_cast(rlc1.get_tx()); - rlc_am_nr_rx* rx1 = dynamic_cast(rlc1.get_rx()); - rlc_am_nr_tx* tx2 = dynamic_cast(rlc2.get_tx()); - rlc_am_nr_rx* rx2 = dynamic_cast(rlc2.get_rx()); - - // before configuring entity - TESTASSERT_EQ(0, rlc1.get_buffer_state()); - - if (not rlc1.configure(rlc_config_t::default_rlc_am_nr_config())) { - return -1; - } - - if (not rlc2.configure(rlc_config_t::default_rlc_am_nr_config())) { - return -1; - } - - // Push 1 SDU into RLC1 - unique_byte_buffer_t sdu; - sdu = srsran::make_byte_buffer(); - TESTASSERT(nullptr != sdu); - sdu->msg[0] = 0; // Write the index into the buffer - sdu->N_bytes = 3; // Give the SDU the size of 3 bytes - sdu->md.pdcp_sn = 0; // PDCP SN for notifications - rlc1.write_sdu(std::move(sdu)); - - // Read 3 PDUs - constexpr uint16_t n_pdus = 3; - unique_byte_buffer_t pdu_bufs[n_pdus]; - for (int i = 0; i < 3; i++) { - pdu_bufs[i] = srsran::make_byte_buffer(); - TESTASSERT(nullptr != pdu_bufs[i]); - if (i == 0) { - pdu_bufs[i]->N_bytes = rlc1.read_pdu(pdu_bufs[i]->msg, 3); - TESTASSERT_EQ(3, pdu_bufs[i]->N_bytes); - } else { - pdu_bufs[i]->N_bytes = rlc1.read_pdu(pdu_bufs[i]->msg, 5); - TESTASSERT_EQ(5, pdu_bufs[i]->N_bytes); - } - } - - // Write 5 PDUs into RLC2 - for (int i = 0; i < n_pdus; i++) { - rlc2.write_pdu(pdu_bufs[i]->msg, pdu_bufs[i]->N_bytes); // Don't write RLC_SN=3. - } - - // Check statistics - rlc_bearer_metrics_t metrics1 = rlc1.get_metrics(); - rlc_bearer_metrics_t metrics2 = rlc2.get_metrics(); - - // SDU metrics - TESTASSERT_EQ(0, metrics2.num_tx_sdus); - TESTASSERT_EQ(1, metrics2.num_rx_sdus); - TESTASSERT_EQ(0, metrics2.num_tx_sdu_bytes); - TESTASSERT_EQ(3, metrics2.num_rx_sdu_bytes); - TESTASSERT_EQ(0, metrics2.num_lost_sdus); - // PDU metrics - TESTASSERT_EQ(0, metrics2.num_tx_pdus); - TESTASSERT_EQ(3, metrics2.num_rx_pdus); // 5 PDUs (6 tx'ed, but one was lost) - TESTASSERT_EQ(0, metrics2.num_tx_pdu_bytes); // Two status PDU (one with a NACK) - TESTASSERT_EQ(13, metrics2.num_rx_pdu_bytes); // 1 PDU (No SO) + 2 PDUs (with SO) = 3 + 2*5 - TESTASSERT_EQ(0, metrics2.num_lost_sdus); // No lost SDUs - - // Check state - rlc_am_nr_tx_state_t state1_tx = tx1->get_tx_state(); - TESTASSERT_EQ(1, state1_tx.tx_next); - - return SRSRAN_SUCCESS; -} - -int segment_retx_test() +int lost_pdu_duplicated_nack_test(rlc_am_nr_sn_size_t sn_size) { rlc_am_tester tester; timer_handler timers(8); @@ -518,44 +509,26 @@ int segment_retx_test() auto& test_logger = srslog::fetch_basic_logger("TESTER "); rlc_am rlc1(srsran_rat_t::nr, srslog::fetch_basic_logger("RLC_AM_1"), 1, &tester, &tester, &timers); rlc_am rlc2(srsran_rat_t::nr, srslog::fetch_basic_logger("RLC_AM_2"), 1, &tester, &tester, &timers); - test_delimit_logger delimiter("segment retx PDU"); - - rlc_am_nr_tx* tx1 = dynamic_cast(rlc1.get_tx()); - rlc_am_nr_rx* rx1 = dynamic_cast(rlc1.get_rx()); - rlc_am_nr_tx* tx2 = dynamic_cast(rlc2.get_tx()); - rlc_am_nr_rx* rx2 = dynamic_cast(rlc2.get_rx()); + test_delimit_logger delimiter("lost PDU with NACK duplicate ({} bit SN)", to_number(sn_size)); - // before configuring entity - TESTASSERT_EQ(0, rlc1.get_buffer_state()); + constexpr uint32_t payload_size = 1; + uint32_t header_size = sn_size == rlc_am_nr_sn_size_t::size12bits ? 2 : 3; + uint32_t data_pdu_size = header_size + payload_size; + uint32_t expect_buffer_state = NBUFS * data_pdu_size; - if (not rlc1.configure(rlc_config_t::default_rlc_am_nr_config())) { + if (not rlc1.configure(rlc_config_t::default_rlc_am_nr_config(to_number(sn_size)))) { return -1; } - if (not rlc2.configure(rlc_config_t::default_rlc_am_nr_config())) { + rlc_config_t rlc2_config = rlc_config_t::default_rlc_am_nr_config(to_number(sn_size)); + if (not rlc2.configure(rlc2_config)) { return -1; } - // Push 5 SDUs into RLC1 - unique_byte_buffer_t sdu_bufs[NBUFS]; - for (int i = 0; i < NBUFS; i++) { - sdu_bufs[i] = srsran::make_byte_buffer(); - sdu_bufs[i]->msg[0] = i; // Write the index into the buffer - sdu_bufs[i]->N_bytes = 3; // Give each buffer a size of 3 bytes - sdu_bufs[i]->md.pdcp_sn = i; // PDCP SN for notifications - rlc1.write_sdu(std::move(sdu_bufs[i])); - } - - TESTASSERT_EQ(25, rlc1.get_buffer_state()); // 2 Bytes * NBUFFS (header size) + NBUFFS * 3 (data) = 25 - - // Read 5 PDUs from RLC1 (1 byte each) - for (int i = 0; i < NBUFS; i++) { - uint32_t len = rlc1.read_pdu(pdu_bufs[i].msg, 5); // 2 bytes for header + 3 byte payload - pdu_bufs[i].N_bytes = len; - TESTASSERT_EQ(5, len); - } + // after configuring entity + TESTASSERT(0 == rlc1.get_buffer_state()); - TESTASSERT_EQ(0, rlc1.get_buffer_state()); + basic_test_tx(&rlc1, pdu_bufs, sn_size); // Write 5 PDUs into RLC2 for (int i = 0; i < NBUFS; i++) { @@ -572,15 +545,21 @@ int segment_retx_test() int len = rlc2.read_pdu(status_buf.msg, 5); status_buf.N_bytes = len; - TESTASSERT_EQ(0, rlc2.get_buffer_state()); + TESTASSERT(0 == rlc2.get_buffer_state()); // Assert status is correct - rlc_am_nr_status_pdu_t status_check = {}; - rlc_am_nr_read_status_pdu(&status_buf, rlc_am_nr_sn_size_t::size12bits, &status_check); + rlc_am_nr_status_pdu_t status_check(sn_size); + rlc_am_nr_read_status_pdu(&status_buf, sn_size, &status_check); TESTASSERT_EQ(3, status_check.ack_sn); // 3 is the next expected SN (i.e. the lost packet.) // Write status PDU to RLC1 rlc1.write_pdu(status_buf.msg, status_buf.N_bytes); + + // Write duplicated status PDU to RLC1 + rlc1.write_pdu(status_buf.msg, status_buf.N_bytes); + + // Check there is nothing pending in RLC1 + TESTASSERT_EQ(0, rlc1.get_buffer_state()); } // Step timers until reassambly timeout expires @@ -589,162 +568,157 @@ int segment_retx_test() } // t-reassembly has expired. There should be a NACK in the status report. - TESTASSERT_EQ(5, rlc2.get_buffer_state()); + constexpr uint32_t status_pdu_ack_size = 3; + uint32_t status_pdu_nack_size = sn_size == rlc_am_nr_sn_size_t::size12bits ? 2 : 3; + TESTASSERT_EQ(status_pdu_ack_size + status_pdu_nack_size, rlc2.get_buffer_state()); { // Read status PDU from RLC2 byte_buffer_t status_buf; - int len = rlc2.read_pdu(status_buf.msg, 5); + uint32_t len = rlc2.read_pdu(status_buf.msg, status_pdu_ack_size + status_pdu_nack_size); status_buf.N_bytes = len; TESTASSERT_EQ(0, rlc2.get_buffer_state()); // Assert status is correct - rlc_am_nr_status_pdu_t status_check = {}; - rlc_am_nr_read_status_pdu(&status_buf, rlc_am_nr_sn_size_t::size12bits, &status_check); + rlc_am_nr_status_pdu_t status_check(sn_size); + rlc_am_nr_read_status_pdu(&status_buf, sn_size, &status_check); TESTASSERT_EQ(5, status_check.ack_sn); // 5 is the next expected SN. - TESTASSERT_EQ(1, status_check.N_nack); // We lost one PDU. + TESTASSERT_EQ(1, status_check.nacks.size()); // We lost one PDU. TESTASSERT_EQ(3, status_check.nacks[0].nack_sn); // Lost PDU SN=3. // Write status PDU to RLC1 rlc1.write_pdu(status_buf.msg, status_buf.N_bytes); - // Check there is an Retx of SN=3 - TESTASSERT_EQ(5, rlc1.get_buffer_state()); + // Write duplicated status PDU to RLC1 + rlc1.write_pdu(status_buf.msg, status_buf.N_bytes); + + // Check there is only one Retx of SN=3 + TESTASSERT_EQ(data_pdu_size, rlc1.get_buffer_state()); } { - // Re-transmit PDU in 3 segments - for (int i = 0; i < 3; i++) { - byte_buffer_t retx_buf; - uint32_t len = 0; - if (i == 0) { - len = rlc1.read_pdu(retx_buf.msg, 3); - TESTASSERT_EQ(3, len); - } else { - len = rlc1.read_pdu(retx_buf.msg, 5); - TESTASSERT_EQ(5, len); - } - retx_buf.N_bytes = len; + // Check correct re-transmission + byte_buffer_t retx_buf; + uint32_t len = rlc1.read_pdu(retx_buf.msg, data_pdu_size); + retx_buf.N_bytes = len; + TESTASSERT_EQ(data_pdu_size, len); - rlc_am_nr_pdu_header_t header_check = {}; - uint32_t hdr_len = rlc_am_nr_read_data_pdu_header(&retx_buf, rlc_am_nr_sn_size_t::size12bits, &header_check); - // Double check header. - TESTASSERT_EQ(3, header_check.sn); // Double check RETX SN - if (i == 0) { - TESTASSERT_EQ(rlc_nr_si_field_t::first_segment, header_check.si); - } else if (i == 1) { - TESTASSERT_EQ(rlc_nr_si_field_t::neither_first_nor_last_segment, header_check.si); - } else { - TESTASSERT_EQ(rlc_nr_si_field_t::last_segment, header_check.si); - } + rlc2.write_pdu(retx_buf.msg, retx_buf.N_bytes); - rlc2.write_pdu(retx_buf.msg, retx_buf.N_bytes); + TESTASSERT_EQ(0, rlc2.get_buffer_state()); // Status report shoud be required, as the TX buffers are now empty. + } + + // Step timers until t-StatusProhibit expires + for (int cnt = 0; cnt < 8; cnt++) { + timers.step_all(); + } + TESTASSERT_EQ(3, rlc2.get_buffer_state()); // t-StatusProhibit no longer running + + { + // Double check status report + byte_buffer_t status_buf; + int len = rlc2.read_pdu(status_buf.msg, 3); + status_buf.N_bytes = len; + + TESTASSERT_EQ(0, rlc2.get_buffer_state()); + + // Assert status is correct + rlc_am_nr_status_pdu_t status_check(sn_size); + rlc_am_nr_read_status_pdu(&status_buf, sn_size, &status_check); + TESTASSERT_EQ(5, status_check.ack_sn); // 5 is the next expected SN. + TESTASSERT_EQ(0, status_check.nacks.size()); // All PDUs are acked now + } + + { + // rlc2 should not issue further status PDUs as time passes (even after expiry of t_status_prohibit) + int32_t checktime = 2 * rlc2_config.am_nr.t_status_prohibit; + for (int cnt = 0; cnt < checktime; cnt++) { + timers.step_all(); + TESTASSERT_EQ(0, rlc2.get_buffer_state()); } - TESTASSERT(0 == rlc1.get_buffer_state()); } // Check statistics rlc_bearer_metrics_t metrics1 = rlc1.get_metrics(); rlc_bearer_metrics_t metrics2 = rlc2.get_metrics(); + uint32_t total_tx_pdu_bytes1 = (NBUFS + 1) * data_pdu_size; // (NBUFS + 1 RETX) * PDU size + uint32_t total_rx_pdu_bytes1 = 4 * status_pdu_ack_size + 2 * status_pdu_nack_size; // 4 status PDU (2 with a NACK) + uint32_t total_tx_pdu_bytes2 = + 3 * status_pdu_ack_size + status_pdu_nack_size; // Three status PDU (one with a NACK, two without) + uint32_t total_rx_pdu_bytes2 = (NBUFS)*data_pdu_size; // (NBUFS - 1 Lost + 1 RETX) * PDU size + // SDU metrics TESTASSERT_EQ(5, metrics1.num_tx_sdus); TESTASSERT_EQ(0, metrics1.num_rx_sdus); - TESTASSERT_EQ(15, metrics1.num_tx_sdu_bytes); + TESTASSERT_EQ(5, metrics1.num_tx_sdu_bytes); TESTASSERT_EQ(0, metrics1.num_rx_sdu_bytes); TESTASSERT_EQ(0, metrics1.num_lost_sdus); // PDU metrics - TESTASSERT_EQ(5 + 3, metrics1.num_tx_pdus); // 3 re-transmissions - TESTASSERT_EQ(2, metrics1.num_rx_pdus); // Two status PDU - TESTASSERT_EQ(38, metrics1.num_tx_pdu_bytes); // 2 Bytes * NBUFFS (header size) + NBUFFS * 3 (data) + - // 3 (1 retx no SO) + 2 * 5 (2 retx with SO) = 38 - TESTASSERT_EQ(3 + 5, metrics1.num_rx_pdu_bytes); // Two status PDU (one with a NACK) - TESTASSERT_EQ(0, metrics1.num_lost_sdus); // No lost SDUs + TESTASSERT_EQ(5 + 1, metrics1.num_tx_pdus); // One re-transmission + TESTASSERT_EQ(4, metrics1.num_rx_pdus); // 4 status PDUs + TESTASSERT_EQ(total_tx_pdu_bytes1, metrics1.num_tx_pdu_bytes); // (NBUFS + 1 RETX) * PDU size + TESTASSERT_EQ(total_rx_pdu_bytes1, metrics1.num_rx_pdu_bytes); // Two status PDU (one with a NACK) + TESTASSERT_EQ(0, metrics1.num_lost_sdus); // No lost SDUs // PDU metrics TESTASSERT_EQ(0, metrics2.num_tx_sdus); TESTASSERT_EQ(5, metrics2.num_rx_sdus); TESTASSERT_EQ(0, metrics2.num_tx_sdu_bytes); - TESTASSERT_EQ(15, metrics2.num_rx_sdu_bytes); // 5 SDUs, 3 bytes each + TESTASSERT_EQ(5, metrics2.num_rx_sdu_bytes); TESTASSERT_EQ(0, metrics2.num_lost_sdus); // SDU metrics - TESTASSERT_EQ(2, metrics2.num_tx_pdus); // Two status PDUs - TESTASSERT_EQ(7, metrics2.num_rx_pdus); // 7 PDUs (8 tx'ed, but one was lost) - TESTASSERT_EQ(5 + 3, metrics2.num_tx_pdu_bytes); // Two status PDU (one with a NACK) - TESTASSERT_EQ(33, metrics2.num_rx_pdu_bytes); // 2 Bytes * (NBUFFS-1) (header size) + (NBUFFS-1) * 3 (data) - // 3 (1 retx no SO) + 2 * 5 (2 retx with SO) = 33 - TESTASSERT_EQ(0, metrics2.num_lost_sdus); // No lost SDUs - - // Check state - rlc_am_nr_rx_state_t state2_rx = rx2->get_rx_state(); - TESTASSERT_EQ(5, state2_rx.rx_next); + TESTASSERT_EQ(3, metrics2.num_tx_pdus); // Three status PDUs + TESTASSERT_EQ(5, metrics2.num_rx_pdus); // 5 PDUs (6 tx'ed, but one was lost) + TESTASSERT_EQ(total_tx_pdu_bytes2, metrics2.num_tx_pdu_bytes); // Three status PDU (one with a NACK, two without) + TESTASSERT_EQ(total_rx_pdu_bytes2, metrics2.num_rx_pdu_bytes); // (NBUFS - 1 Lost + 1 RETX) * PDU size + TESTASSERT_EQ(0, metrics2.num_lost_sdus); // No lost SDUs return SRSRAN_SUCCESS; } -int retx_segment_test() +/* + * Test the loss of multiple PDUs. + * NACKs for all missing PDUs should be visible in buffer state -- but we enforce + * a trimmed status PDU by providing little space for the whole status PDU. + * Retx after NACK should be present too. + * Further status report shall contain the trimmed NACK. + * Another Retx after NACK should be present. + * No further status reports shall be issued. + */ +int lost_pdus_trimmed_nack_test(rlc_am_nr_sn_size_t sn_size) { rlc_am_tester tester; timer_handler timers(8); + byte_buffer_t pdu_bufs[NBUFS]; auto& test_logger = srslog::fetch_basic_logger("TESTER "); rlc_am rlc1(srsran_rat_t::nr, srslog::fetch_basic_logger("RLC_AM_1"), 1, &tester, &tester, &timers); rlc_am rlc2(srsran_rat_t::nr, srslog::fetch_basic_logger("RLC_AM_2"), 1, &tester, &tester, &timers); - test_delimit_logger delimiter("retx segment PDU"); + test_delimit_logger delimiter("lost PDUs and trimmed NACKs ({} bit SN)", to_number(sn_size)); - rlc_am_nr_tx* tx1 = dynamic_cast(rlc1.get_tx()); - rlc_am_nr_rx* rx1 = dynamic_cast(rlc1.get_rx()); - rlc_am_nr_tx* tx2 = dynamic_cast(rlc2.get_tx()); - rlc_am_nr_rx* rx2 = dynamic_cast(rlc2.get_rx()); + constexpr uint32_t payload_size = 1; + uint32_t header_size = sn_size == rlc_am_nr_sn_size_t::size12bits ? 2 : 3; + uint32_t data_pdu_size = header_size + payload_size; + uint32_t expect_buffer_state = NBUFS * data_pdu_size; - // before configuring entity - TESTASSERT(0 == rlc1.get_buffer_state()); - - if (not rlc1.configure(rlc_config_t::default_rlc_am_nr_config())) { + if (not rlc1.configure(rlc_config_t::default_rlc_am_nr_config(to_number(sn_size)))) { return -1; } - if (not rlc2.configure(rlc_config_t::default_rlc_am_nr_config())) { + rlc_config_t rlc2_config = rlc_config_t::default_rlc_am_nr_config(to_number(sn_size)); + if (not rlc2.configure(rlc2_config)) { return -1; } - int n_sdu_bufs = 5; - int n_pdu_bufs = 15; - - // Push 5 SDUs into RLC1 - std::vector sdu_bufs(n_sdu_bufs); - for (int i = 0; i < n_sdu_bufs; i++) { - sdu_bufs[i] = srsran::make_byte_buffer(); - sdu_bufs[i]->msg[0] = i; // Write the index into the buffer - sdu_bufs[i]->N_bytes = 3; // Give each buffer a size of 3 bytes - sdu_bufs[i]->md.pdcp_sn = i; // PDCP SN for notifications - rlc1.write_sdu(std::move(sdu_bufs[i])); - } - - TESTASSERT(25 == rlc1.get_buffer_state()); // 2 Bytes * NBUFFS (header size) + NBUFFS * 3 (data) = 25 - - // Read 15 PDUs from RLC1 - std::vector pdu_bufs(n_pdu_bufs); - for (int i = 0; i < n_pdu_bufs; i++) { - pdu_bufs[i] = srsran::make_byte_buffer(); - if (i == 0 || i == 3 || i == 6 || i == 9 || i == 12) { - // First segment, no SO - uint32_t len = rlc1.read_pdu(pdu_bufs[i]->msg, 3); // 2 bytes for header + 1 byte payload - pdu_bufs[i]->N_bytes = len; - TESTASSERT_EQ(3, len); - } else { - // Middle or last segment, SO present - uint32_t len = rlc1.read_pdu(pdu_bufs[i]->msg, 5); // 4 bytes for header + 1 byte payload - pdu_bufs[i]->N_bytes = len; - TESTASSERT_EQ(5, len); - } - } + // after configuring entity + TESTASSERT(0 == rlc1.get_buffer_state()); - TESTASSERT_EQ(0, rlc1.get_buffer_state()); + basic_test_tx(&rlc1, pdu_bufs, sn_size); - // Write 15 - 3 PDUs into RLC2 - for (int i = 0; i < n_pdu_bufs; i++) { - if (i != 3 && i != 7 && i != 11) { - rlc2.write_pdu(pdu_bufs[i]->msg, pdu_bufs[i]->N_bytes); // Lose first segment of RLC_SN=1. + // Write 5 PDUs into RLC2 + for (int i = 0; i < NBUFS; i++) { + if (i != 1 && i != 3) { + rlc2.write_pdu(pdu_bufs[i].msg, pdu_bufs[i].N_bytes); // Don't write RLC_SN=1 and 3. } } @@ -756,11 +730,11 @@ int retx_segment_test() int len = rlc2.read_pdu(status_buf.msg, 5); status_buf.N_bytes = len; - TESTASSERT_EQ(0, rlc2.get_buffer_state()); + TESTASSERT(0 == rlc2.get_buffer_state()); // Assert status is correct - rlc_am_nr_status_pdu_t status_check = {}; - rlc_am_nr_read_status_pdu(&status_buf, rlc_am_nr_sn_size_t::size12bits, &status_check); + rlc_am_nr_status_pdu_t status_check(sn_size); + rlc_am_nr_read_status_pdu(&status_buf, sn_size, &status_check); TESTASSERT_EQ(1, status_check.ack_sn); // 1 is the next expected SN (i.e. the first lost packet.) // Write status PDU to RLC1 @@ -772,64 +746,971 @@ int retx_segment_test() timers.step_all(); } - // t-reassembly has expired. There should be a NACK in the status report. - // There should be 3 NACKs with SO_start and SO_end - TESTASSERT_EQ(21, rlc2.get_buffer_state()); // 3 bytes for fixed header (ACK+E1) + 3 * 6 for NACK with SO = 21. + // Step timers until reassambly timeout expires + for (int cnt = 0; cnt < 35; cnt++) { + timers.step_all(); + } + + // t-reassembly has expired. There should be two NACKs in the status report. + constexpr uint32_t status_pdu_ack_size = 3; + uint32_t status_pdu_nack_size = sn_size == rlc_am_nr_sn_size_t::size12bits ? 2 : 3; + uint32_t expected_size = status_pdu_ack_size + 2 * status_pdu_nack_size; + TESTASSERT_EQ(expected_size, rlc2.get_buffer_state()); { - // Read status PDU from RLC2 + // Read status PDU from RLC2, enforce to trimming by providing little space (expected_size - 1) + // to drop the second NACK. byte_buffer_t status_buf; - int len = rlc2.read_pdu(status_buf.msg, 21); + uint32_t len = rlc2.read_pdu(status_buf.msg, expected_size - 1); status_buf.N_bytes = len; - - TESTASSERT_EQ(0, rlc2.get_buffer_state()); + expected_size = status_pdu_ack_size + 1 * status_pdu_nack_size; // only one NACK left + TESTASSERT_EQ(len, expected_size); // Assert status is correct - rlc_am_nr_status_pdu_t status_check = {}; - rlc_am_nr_read_status_pdu(&status_buf, rlc_am_nr_sn_size_t::size12bits, &status_check); - TESTASSERT_EQ(5, status_check.ack_sn); // 5 is the next expected SN. - TESTASSERT_EQ(3, status_check.N_nack); // We lost one PDU. - TESTASSERT_EQ(1, status_check.nacks[0].nack_sn); // Lost SDU on SN=1. - TESTASSERT_EQ(true, status_check.nacks[0].has_so); // Lost SDU on SN=1. - TESTASSERT_EQ(0, status_check.nacks[0].so_start); // Lost SDU on SN=1. - TESTASSERT_EQ(1, status_check.nacks[0].so_end); // Lost SDU on SN=1. - TESTASSERT_EQ(2, status_check.nacks[1].nack_sn); // Lost SDU on SN=1. - TESTASSERT_EQ(true, status_check.nacks[1].has_so); // Lost SDU on SN=1. - TESTASSERT_EQ(1, status_check.nacks[1].so_start); // Lost SDU on SN=1. - TESTASSERT_EQ(2, status_check.nacks[1].so_end); // Lost SDU on SN=1. - TESTASSERT_EQ(3, status_check.nacks[2].nack_sn); // Lost SDU on SN=1. - TESTASSERT_EQ(true, status_check.nacks[2].has_so); // Lost SDU on SN=1. - TESTASSERT_EQ(2, status_check.nacks[2].so_start); // Lost SDU on SN=1. - TESTASSERT_EQ(0xFFFF, status_check.nacks[2].so_end); // Lost SDU on SN=1. + rlc_am_nr_status_pdu_t status_check(sn_size); + rlc_am_nr_read_status_pdu(&status_buf, sn_size, &status_check); + TESTASSERT_EQ(3, status_check.ack_sn); // 3 is the next expected SN (after trimming) + TESTASSERT_EQ(1, status_check.nacks.size()); // Expect only one NACK left + TESTASSERT_EQ(1, status_check.nacks[0].nack_sn); // The NACK'ed SN is 1. // Write status PDU to RLC1 rlc1.write_pdu(status_buf.msg, status_buf.N_bytes); - // Check there is an Retx of SN=3 - TESTASSERT_EQ(5, rlc1.get_buffer_state()); + // Check there is an Retx of SN=1 + TESTASSERT_EQ(data_pdu_size, rlc1.get_buffer_state()); } { - // Re-transmit the 3 lost segments - for (int i = 0; i < 3; i++) { - byte_buffer_t retx_buf; - uint32_t len = 0; - if (i == 0) { - len = rlc1.read_pdu(retx_buf.msg, 3); - TESTASSERT_EQ(3, len); - } else { - len = rlc1.read_pdu(retx_buf.msg, 5); - TESTASSERT_EQ(5, len); - } - retx_buf.N_bytes = len; + // Check correct re-transmission + byte_buffer_t retx_buf; + uint32_t len = rlc1.read_pdu(retx_buf.msg, data_pdu_size); + retx_buf.N_bytes = len; + TESTASSERT_EQ(data_pdu_size, len); - rlc_am_nr_pdu_header_t header_check = {}; - uint32_t hdr_len = rlc_am_nr_read_data_pdu_header(&retx_buf, rlc_am_nr_sn_size_t::size12bits, &header_check); - // Double check header. - if (i == 0) { - TESTASSERT_EQ(1, header_check.sn); // Double check RETX SN - TESTASSERT_EQ(rlc_nr_si_field_t::first_segment, header_check.si); - } else if (i == 1) { - TESTASSERT_EQ(2, header_check.sn); // Double check RETX SN + rlc2.write_pdu(retx_buf.msg, retx_buf.N_bytes); + + expected_size = status_pdu_ack_size + 1 * status_pdu_nack_size; + TESTASSERT_EQ(0, rlc2.get_buffer_state()); // Status report should now include the chopped NACK + } + + // Step timers until t-StatusProhibit expires + for (int cnt = 0; cnt < 8; cnt++) { + timers.step_all(); + } + TESTASSERT_EQ(expected_size, rlc2.get_buffer_state()); // t-StatusProhibit no longer running + + { + // Double check status report + byte_buffer_t status_buf; + uint32_t len = rlc2.read_pdu(status_buf.msg, expected_size); + status_buf.N_bytes = len; + expected_size = status_pdu_ack_size + 1 * status_pdu_nack_size; // the remaining NACK + TESTASSERT_EQ(len, expected_size); + + // Nothing else pending + TESTASSERT_EQ(0, rlc2.get_buffer_state()); + + // Assert status is correct + rlc_am_nr_status_pdu_t status_check(sn_size); + rlc_am_nr_read_status_pdu(&status_buf, sn_size, &status_check); + TESTASSERT_EQ(5, status_check.ack_sn); // 5 is the next expected SN. + TESTASSERT_EQ(1, status_check.nacks.size()); // Expect only the second NACK + TESTASSERT_EQ(3, status_check.nacks[0].nack_sn); // The NACK'ed SN is 3. + + // Write status PDU to RLC1 + rlc1.write_pdu(status_buf.msg, status_buf.N_bytes); + + // Check there is an Retx of SN=3 + TESTASSERT_EQ(data_pdu_size, rlc1.get_buffer_state()); + } + { + // Check correct re-transmission + byte_buffer_t retx_buf; + uint32_t len = rlc1.read_pdu(retx_buf.msg, data_pdu_size); + retx_buf.N_bytes = len; + TESTASSERT_EQ(data_pdu_size, len); + + rlc2.write_pdu(retx_buf.msg, retx_buf.N_bytes); + + expected_size = status_pdu_ack_size; + TESTASSERT_EQ(0, rlc2.get_buffer_state()); // Status report should have no NACKs + } + // Step timers until t-StatusProhibit expires + for (int cnt = 0; cnt < 8; cnt++) { + timers.step_all(); + } + TESTASSERT_EQ(expected_size, rlc2.get_buffer_state()); // t-StatusProhibit no longer running + { + // Double check status report + byte_buffer_t status_buf; + int len = rlc2.read_pdu(status_buf.msg, expected_size); + status_buf.N_bytes = len; + + TESTASSERT_EQ(0, rlc2.get_buffer_state()); + + // Assert status is correct + rlc_am_nr_status_pdu_t status_check(sn_size); + rlc_am_nr_read_status_pdu(&status_buf, sn_size, &status_check); + TESTASSERT_EQ(5, status_check.ack_sn); // 5 is the next expected SN. + TESTASSERT_EQ(0, status_check.nacks.size()); // All PDUs are acked now + } + + { + // rlc2 should not issue further status PDUs as time passes (even after expiry of t_status_prohibit) + int32_t checktime = 2 * rlc2_config.am_nr.t_status_prohibit; + for (int cnt = 0; cnt < checktime; cnt++) { + timers.step_all(); + TESTASSERT_EQ(0, rlc2.get_buffer_state()); + } + } + + // Check statistics + rlc_bearer_metrics_t metrics1 = rlc1.get_metrics(); + rlc_bearer_metrics_t metrics2 = rlc2.get_metrics(); + + uint32_t total_tx_pdu_bytes1 = (NBUFS + 2) * data_pdu_size; // (NBUFS + 2 RETX) * PDU size + uint32_t total_rx_pdu_bytes1 = 3 * status_pdu_ack_size + 2 * status_pdu_nack_size; // 3 status PDUs (2 with one NACK) + uint32_t total_tx_pdu_bytes2 = + 4 * status_pdu_ack_size + 2 * status_pdu_nack_size; // 4 status PDUs (2 with one NACK, two without) + uint32_t total_rx_pdu_bytes2 = (NBUFS)*data_pdu_size; // (NBUFS - 2 Lost + 2 RETX) * PDU size + + // SDU metrics + TESTASSERT_EQ(5, metrics1.num_tx_sdus); + TESTASSERT_EQ(0, metrics1.num_rx_sdus); + TESTASSERT_EQ(5, metrics1.num_tx_sdu_bytes); + TESTASSERT_EQ(0, metrics1.num_rx_sdu_bytes); + TESTASSERT_EQ(0, metrics1.num_lost_sdus); + // PDU metrics + TESTASSERT_EQ(5 + 2, metrics1.num_tx_pdus); // One re-transmission + TESTASSERT_EQ(3, metrics1.num_rx_pdus); // 3 status PDUs + TESTASSERT_EQ(total_tx_pdu_bytes1, metrics1.num_tx_pdu_bytes); // (NBUFS + 2 RETX) * PDU size + TESTASSERT_EQ(total_rx_pdu_bytes1, metrics1.num_rx_pdu_bytes); // Two status PDU (one with a NACK) + TESTASSERT_EQ(0, metrics1.num_lost_sdus); // No lost SDUs + + // PDU metrics + TESTASSERT_EQ(0, metrics2.num_tx_sdus); + TESTASSERT_EQ(5, metrics2.num_rx_sdus); + TESTASSERT_EQ(0, metrics2.num_tx_sdu_bytes); + TESTASSERT_EQ(5, metrics2.num_rx_sdu_bytes); + TESTASSERT_EQ(0, metrics2.num_lost_sdus); + // SDU metrics + TESTASSERT_EQ(4, metrics2.num_tx_pdus); // 4 status PDUs + TESTASSERT_EQ(5, metrics2.num_rx_pdus); // 5 PDUs (7 tx'ed, but 2 were lost) + TESTASSERT_EQ(total_tx_pdu_bytes2, metrics2.num_tx_pdu_bytes); // Three status PDU (one with a NACK, two without) + TESTASSERT_EQ(total_rx_pdu_bytes2, metrics2.num_rx_pdu_bytes); // (NBUFS - 2 Lost + 2 RETX) * PDU size + TESTASSERT_EQ(0, metrics2.num_lost_sdus); // No lost SDUs + return SRSRAN_SUCCESS; +} + +/* + * Test the basic segmentation of a single SDU. + * A single SDU of 3 bytes is segmented into 3 PDUs + */ +int basic_segmentation_test(rlc_am_nr_sn_size_t sn_size) +{ + rlc_am_tester tester; + timer_handler timers(8); + auto& test_logger = srslog::fetch_basic_logger("TESTER "); + test_delimit_logger delimiter("basic segmentation ({} bit SN)", to_number(sn_size)); + rlc_am rlc1(srsran_rat_t::nr, srslog::fetch_basic_logger("RLC_AM_1"), 1, &tester, &tester, &timers); + rlc_am rlc2(srsran_rat_t::nr, srslog::fetch_basic_logger("RLC_AM_2"), 1, &tester, &tester, &timers); + + rlc_am_nr_tx* tx1 = dynamic_cast(rlc1.get_tx()); + rlc_am_nr_rx* rx1 = dynamic_cast(rlc1.get_rx()); + rlc_am_nr_tx* tx2 = dynamic_cast(rlc2.get_tx()); + rlc_am_nr_rx* rx2 = dynamic_cast(rlc2.get_rx()); + + if (not rlc1.configure(rlc_config_t::default_rlc_am_nr_config(to_number(sn_size)))) { + return -1; + } + + if (not rlc2.configure(rlc_config_t::default_rlc_am_nr_config(to_number(sn_size)))) { + return -1; + } + + // after configuring entity + TESTASSERT_EQ(0, rlc1.get_buffer_state()); + + // Push 1 SDU into RLC1 + unique_byte_buffer_t sdu; + constexpr uint32_t payload_size = 3; // Give the SDU the size of 3 bytes + sdu = srsran::make_byte_buffer(); + TESTASSERT(nullptr != sdu); + sdu->msg[0] = 0; // Write the index into the buffer + sdu->N_bytes = payload_size; // Give the SDU the size of 3 bytes + sdu->md.pdcp_sn = 0; // PDCP SN for notifications + rlc1.write_sdu(std::move(sdu)); + + // Read 3 PDUs + constexpr uint16_t n_pdus = 3; + unique_byte_buffer_t pdu_bufs[n_pdus]; + uint32_t header_size = sn_size == rlc_am_nr_sn_size_t::size12bits ? 2 : 3; + constexpr uint32_t so_size = 2; + constexpr uint32_t segment_size = 1; + uint32_t pdu_size_first = header_size + segment_size; + uint32_t pdu_size_continued = header_size + so_size + segment_size; + for (int i = 0; i < n_pdus; i++) { + pdu_bufs[i] = srsran::make_byte_buffer(); + TESTASSERT(nullptr != pdu_bufs[i]); + if (i == 0) { + pdu_bufs[i]->N_bytes = rlc1.read_pdu(pdu_bufs[i]->msg, pdu_size_first); + TESTASSERT_EQ(pdu_size_first, pdu_bufs[i]->N_bytes); + } else { + pdu_bufs[i]->N_bytes = rlc1.read_pdu(pdu_bufs[i]->msg, pdu_size_continued); + TESTASSERT_EQ(pdu_size_continued, pdu_bufs[i]->N_bytes); + } + } + + // Write 3 PDUs into RLC2 + for (int i = 0; i < n_pdus; i++) { + rlc2.write_pdu(pdu_bufs[i]->msg, pdu_bufs[i]->N_bytes); + } + + // Check statistics + rlc_bearer_metrics_t metrics1 = rlc1.get_metrics(); + rlc_bearer_metrics_t metrics2 = rlc2.get_metrics(); + + uint32_t total_rx_pdu_bytes = pdu_size_first + (n_pdus - 1) * pdu_size_continued; // 1 PDU (No SO) + 2 PDUs (with SO) + + // SDU metrics + TESTASSERT_EQ(0, metrics2.num_tx_sdus); + TESTASSERT_EQ(1, metrics2.num_rx_sdus); + TESTASSERT_EQ(0, metrics2.num_tx_sdu_bytes); + TESTASSERT_EQ(payload_size, metrics2.num_rx_sdu_bytes); + TESTASSERT_EQ(0, metrics2.num_lost_sdus); + // PDU metrics + TESTASSERT_EQ(0, metrics2.num_tx_pdus); + TESTASSERT_EQ(n_pdus, metrics2.num_rx_pdus); // 3 PDUs + TESTASSERT_EQ(0, metrics2.num_tx_pdu_bytes); + TESTASSERT_EQ(total_rx_pdu_bytes, metrics2.num_rx_pdu_bytes); // 1 PDU (No SO) + 2 PDUs (with SO) + TESTASSERT_EQ(0, metrics2.num_lost_sdus); // No lost SDUs + + // Check state + rlc_am_nr_tx_state_t state1_tx = tx1->get_tx_state(); + TESTASSERT_EQ(1, state1_tx.tx_next); + + return SRSRAN_SUCCESS; +} + +// This tests correct behaviour of the following flow: +// - Transmit 5 SDUs as whole PDUs +// - Loose 3rd PDU +// - Receive NACK for missing PDU +// - Retransmit lost PDU in 3 segments +// - Check metrics and state +int segment_retx_test(rlc_am_nr_sn_size_t sn_size) +{ + rlc_am_tester tester; + timer_handler timers(8); + byte_buffer_t pdu_bufs[NBUFS]; + + auto& test_logger = srslog::fetch_basic_logger("TESTER "); + rlc_am rlc1(srsran_rat_t::nr, srslog::fetch_basic_logger("RLC_AM_1"), 1, &tester, &tester, &timers); + rlc_am rlc2(srsran_rat_t::nr, srslog::fetch_basic_logger("RLC_AM_2"), 1, &tester, &tester, &timers); + test_delimit_logger delimiter("segment retx PDU ({} bit SN)", to_number(sn_size)); + + rlc_am_nr_tx* tx1 = dynamic_cast(rlc1.get_tx()); + rlc_am_nr_rx* rx1 = dynamic_cast(rlc1.get_rx()); + rlc_am_nr_tx* tx2 = dynamic_cast(rlc2.get_tx()); + rlc_am_nr_rx* rx2 = dynamic_cast(rlc2.get_rx()); + + auto rlc_cnfg = rlc_config_t::default_rlc_am_nr_config(to_number(sn_size)); + rlc_cnfg.am_nr.t_poll_retx = -1; + if (not rlc1.configure(rlc_cnfg)) { + return -1; + } + + if (not rlc2.configure(rlc_cnfg)) { + return -1; + } + + // after configuring entity + TESTASSERT_EQ(0, rlc1.get_buffer_state()); + + // Push 5 SDUs into RLC1 + unique_byte_buffer_t sdu_bufs[NBUFS]; + constexpr uint32_t payload_size = 3; // Give the SDU the size of 3 bytes + uint32_t header_size = sn_size == rlc_am_nr_sn_size_t::size12bits ? 2 : 3; + for (int i = 0; i < NBUFS; i++) { + sdu_bufs[i] = srsran::make_byte_buffer(); + sdu_bufs[i]->msg[0] = i; // Write the index into the buffer + sdu_bufs[i]->N_bytes = payload_size; // Give each buffer a size of 3 bytes + sdu_bufs[i]->md.pdcp_sn = i; // PDCP SN for notifications + rlc1.write_sdu(std::move(sdu_bufs[i])); + } + + uint32_t expected_buffer_state = (header_size + payload_size) * NBUFS; + TESTASSERT_EQ(expected_buffer_state, rlc1.get_buffer_state()); + + // Read 5 PDUs from RLC1 (1 byte each) + for (int i = 0; i < NBUFS; i++) { + uint32_t len = rlc1.read_pdu(pdu_bufs[i].msg, header_size + payload_size); + pdu_bufs[i].N_bytes = len; + TESTASSERT_EQ(header_size + payload_size, len); + } + + TESTASSERT_EQ(0, rlc1.get_buffer_state()); + + // Write 5 PDUs into RLC2 + for (int i = 0; i < NBUFS; i++) { + if (i != 3) { + rlc2.write_pdu(pdu_bufs[i].msg, pdu_bufs[i].N_bytes); // Don't write RLC_SN=3. + } + } + + // Only after t-reassembly has expired, will the status report include NACKs. + TESTASSERT_EQ(3, rlc2.get_buffer_state()); + { + // Read status PDU from RLC2 + byte_buffer_t status_buf; + int len = rlc2.read_pdu(status_buf.msg, 5); + status_buf.N_bytes = len; + + TESTASSERT_EQ(0, rlc2.get_buffer_state()); + + // Assert status is correct + rlc_am_nr_status_pdu_t status_check(sn_size); + rlc_am_nr_read_status_pdu(&status_buf, sn_size, &status_check); + TESTASSERT_EQ(3, status_check.ack_sn); // 3 is the next expected SN (i.e. the lost packet.) + + // Write status PDU to RLC1 + rlc1.write_pdu(status_buf.msg, status_buf.N_bytes); + } + + // Step timers until reassambly timeout expires + for (int cnt = 0; cnt < 35; cnt++) { + timers.step_all(); + } + + // t-reassembly has expired. There should be a NACK in the status report. + constexpr uint32_t status_pdu_ack_size = 3; + uint32_t status_pdu_nack_size = sn_size == rlc_am_nr_sn_size_t::size12bits ? 2 : 3; + TESTASSERT_EQ(status_pdu_ack_size + status_pdu_nack_size, rlc2.get_buffer_state()); + { + // Read status PDU from RLC2 + byte_buffer_t status_buf; + int len = rlc2.read_pdu(status_buf.msg, status_pdu_ack_size + status_pdu_nack_size); + status_buf.N_bytes = len; + + TESTASSERT_EQ(0, rlc2.get_buffer_state()); + + // Assert status is correct + rlc_am_nr_status_pdu_t status_check(sn_size); + rlc_am_nr_read_status_pdu(&status_buf, sn_size, &status_check); + TESTASSERT_EQ(5, status_check.ack_sn); // 5 is the next expected SN. + TESTASSERT_EQ(1, status_check.nacks.size()); // We lost one PDU. + TESTASSERT_EQ(3, status_check.nacks[0].nack_sn); // Lost PDU SN=3. + + // Write status PDU to RLC1 + rlc1.write_pdu(status_buf.msg, status_buf.N_bytes); + + // Check there is an Retx of SN=3 + TESTASSERT_EQ(header_size + payload_size, rlc1.get_buffer_state()); + } + + constexpr uint32_t so_size = 2; + constexpr uint32_t segment_size = 1; + uint32_t pdu_size_first = header_size + segment_size; + uint32_t pdu_size_continued = header_size + so_size + segment_size; + { + // Re-transmit PDU in 3 segments + for (int i = 0; i < 3; i++) { + byte_buffer_t retx_buf; + uint32_t len = 0; + if (i == 0) { + len = rlc1.read_pdu(retx_buf.msg, pdu_size_first); + TESTASSERT_EQ(pdu_size_first, len); + } else { + len = rlc1.read_pdu(retx_buf.msg, pdu_size_continued); + TESTASSERT_EQ(pdu_size_continued, len); + } + retx_buf.N_bytes = len; + + rlc_am_nr_pdu_header_t header_check = {}; + uint32_t hdr_len = rlc_am_nr_read_data_pdu_header(&retx_buf, sn_size, &header_check); + // Double check header. + TESTASSERT_EQ(3, header_check.sn); // Double check RETX SN + if (i == 0) { + TESTASSERT_EQ(rlc_nr_si_field_t::first_segment, header_check.si); + } else if (i == 1) { + TESTASSERT_EQ(rlc_nr_si_field_t::neither_first_nor_last_segment, header_check.si); + } else { + TESTASSERT_EQ(rlc_nr_si_field_t::last_segment, header_check.si); + } + + rlc2.write_pdu(retx_buf.msg, retx_buf.N_bytes); + } + TESTASSERT(0 == rlc1.get_buffer_state()); + } + + // Check statistics + rlc_bearer_metrics_t metrics1 = rlc1.get_metrics(); + rlc_bearer_metrics_t metrics2 = rlc2.get_metrics(); + + uint32_t data_pdu_size = header_size + payload_size; + uint32_t total_tx_pdu_bytes1 = NBUFS * data_pdu_size + pdu_size_first + 2 * pdu_size_continued; + uint32_t total_rx_pdu_bytes1 = 2 * status_pdu_ack_size + status_pdu_nack_size; // Two status PDU (one with a NACK) + uint32_t total_tx_pdu_bytes2 = total_rx_pdu_bytes1; + uint32_t total_rx_pdu_bytes2 = (NBUFS - 1) * data_pdu_size + pdu_size_first + 2 * pdu_size_continued; + + // SDU metrics + TESTASSERT_EQ(5, metrics1.num_tx_sdus); + TESTASSERT_EQ(0, metrics1.num_rx_sdus); + TESTASSERT_EQ(15, metrics1.num_tx_sdu_bytes); + TESTASSERT_EQ(0, metrics1.num_rx_sdu_bytes); + TESTASSERT_EQ(0, metrics1.num_lost_sdus); + // PDU metrics + TESTASSERT_EQ(5 + 3, metrics1.num_tx_pdus); // 3 re-transmissions + TESTASSERT_EQ(2, metrics1.num_rx_pdus); // Two status PDU + TESTASSERT_EQ(total_tx_pdu_bytes1, metrics1.num_tx_pdu_bytes); + TESTASSERT_EQ(total_rx_pdu_bytes1, metrics1.num_rx_pdu_bytes); + TESTASSERT_EQ(0, metrics1.num_lost_sdus); // No lost SDUs + + // PDU metrics + TESTASSERT_EQ(0, metrics2.num_tx_sdus); + TESTASSERT_EQ(5, metrics2.num_rx_sdus); + TESTASSERT_EQ(0, metrics2.num_tx_sdu_bytes); + TESTASSERT_EQ(15, metrics2.num_rx_sdu_bytes); // 5 SDUs, 3 bytes each + TESTASSERT_EQ(0, metrics2.num_lost_sdus); + // SDU metrics + TESTASSERT_EQ(2, metrics2.num_tx_pdus); // Two status PDUs + TESTASSERT_EQ(7, metrics2.num_rx_pdus); // 7 PDUs (8 tx'ed, but one was lost) + TESTASSERT_EQ(total_tx_pdu_bytes2, metrics2.num_tx_pdu_bytes); + TESTASSERT_EQ(total_rx_pdu_bytes2, + metrics2.num_rx_pdu_bytes); // 2 Bytes * (NBUFFS-1) (header size) + (NBUFFS-1) * 3 (data) + // 3 (1 retx no SO) + 2 * 5 (2 retx with SO) = 33 + TESTASSERT_EQ(0, metrics2.num_lost_sdus); // No lost SDUs + + // Check state + rlc_am_nr_rx_state_t state2_rx = rx2->get_rx_state(); + TESTASSERT_EQ(5, state2_rx.rx_next); + return SRSRAN_SUCCESS; +} + +// This tests correct behaviour of the following flow: +// - Transmit 5 SDUs as whole PDUs +// - Loose 3rd PDU +// - Receive NACK for missing PDU +// - Retransmit lost PDU in 3 segments +// - Loose first and last segment +// - Receive NACKs for missing segments +// - Receive duplicate of previous NACKs +// - Retransmit missing segments again, but only once! +// - Check metrics and state +int segment_retx_and_loose_segments_test(rlc_am_nr_sn_size_t sn_size) +{ + rlc_am_tester tester; + timer_handler timers(8); + byte_buffer_t pdu_bufs[NBUFS]; + + auto& test_logger = srslog::fetch_basic_logger("TESTER "); + rlc_am rlc1(srsran_rat_t::nr, srslog::fetch_basic_logger("RLC_AM_1"), 1, &tester, &tester, &timers); + rlc_am rlc2(srsran_rat_t::nr, srslog::fetch_basic_logger("RLC_AM_2"), 1, &tester, &tester, &timers); + test_delimit_logger delimiter("segment retx PDU and loose some segments ({} bit SN)", to_number(sn_size)); + + rlc_am_nr_tx* tx1 = dynamic_cast(rlc1.get_tx()); + rlc_am_nr_rx* rx1 = dynamic_cast(rlc1.get_rx()); + rlc_am_nr_tx* tx2 = dynamic_cast(rlc2.get_tx()); + rlc_am_nr_rx* rx2 = dynamic_cast(rlc2.get_rx()); + + if (not rlc1.configure(rlc_config_t::default_rlc_am_nr_config(to_number(sn_size)))) { + return -1; + } + + if (not rlc2.configure(rlc_config_t::default_rlc_am_nr_config(to_number(sn_size)))) { + return -1; + } + + // after configuring entity + TESTASSERT_EQ(0, rlc1.get_buffer_state()); + + // Push 5 SDUs into RLC1 + unique_byte_buffer_t sdu_bufs[NBUFS]; + constexpr uint32_t payload_size = 3; // Give the SDU the size of 3 bytes + uint32_t header_size = sn_size == rlc_am_nr_sn_size_t::size12bits ? 2 : 3; + for (int i = 0; i < NBUFS; i++) { + sdu_bufs[i] = srsran::make_byte_buffer(); + sdu_bufs[i]->msg[0] = i; // Write the index into the buffer + sdu_bufs[i]->N_bytes = payload_size; // Give each buffer a size of 3 bytes + sdu_bufs[i]->md.pdcp_sn = i; // PDCP SN for notifications + rlc1.write_sdu(std::move(sdu_bufs[i])); + } + + uint32_t expected_buffer_state = (header_size + payload_size) * NBUFS; + TESTASSERT_EQ(expected_buffer_state, rlc1.get_buffer_state()); + + // Read 5 PDUs from RLC1 (each with a full SDU) + for (int i = 0; i < NBUFS; i++) { + uint32_t len = rlc1.read_pdu(pdu_bufs[i].msg, header_size + payload_size); + pdu_bufs[i].N_bytes = len; + TESTASSERT_EQ(header_size + payload_size, len); + } + + TESTASSERT_EQ(0, rlc1.get_buffer_state()); + + // Write 5 - 1 PDUs into RLC2 + for (int i = 0; i < NBUFS; i++) { + if (i != 3) { + rlc2.write_pdu(pdu_bufs[i].msg, pdu_bufs[i].N_bytes); // Don't write RLC_SN=3. + } + } + + // Only after t-reassembly has expired, will the status report include NACKs. + TESTASSERT_EQ(3, rlc2.get_buffer_state()); + { + // Read status PDU from RLC2 + byte_buffer_t status_buf; + int len = rlc2.read_pdu(status_buf.msg, 5); + status_buf.N_bytes = len; + + TESTASSERT_EQ(0, rlc2.get_buffer_state()); + + // Assert status is correct + rlc_am_nr_status_pdu_t status_check(sn_size); + rlc_am_nr_read_status_pdu(&status_buf, sn_size, &status_check); + TESTASSERT_EQ(3, status_check.ack_sn); // 3 is the next expected SN (i.e. the lost packet.) + + // Write status PDU to RLC1 + rlc1.write_pdu(status_buf.msg, status_buf.N_bytes); + } + + // Step timers until reassambly timeout expires + for (int cnt = 0; cnt < 35; cnt++) { + timers.step_all(); + } + + // t-reassembly has expired. There should be a NACK in the status report. + constexpr uint32_t status_pdu_ack_size = 3; + uint32_t status_pdu_nack_size = sn_size == rlc_am_nr_sn_size_t::size12bits ? 2 : 3; + TESTASSERT_EQ(status_pdu_ack_size + status_pdu_nack_size, rlc2.get_buffer_state()); + { + // Read status PDU from RLC2 + byte_buffer_t status_buf; + int len = rlc2.read_pdu(status_buf.msg, status_pdu_ack_size + status_pdu_nack_size); + status_buf.N_bytes = len; + + TESTASSERT_EQ(0, rlc2.get_buffer_state()); + + // Assert status is correct + rlc_am_nr_status_pdu_t status_check(sn_size); + rlc_am_nr_read_status_pdu(&status_buf, sn_size, &status_check); + TESTASSERT_EQ(5, status_check.ack_sn); // 5 is the next expected SN. + TESTASSERT_EQ(1, status_check.nacks.size()); // We lost one PDU. + TESTASSERT_EQ(3, status_check.nacks[0].nack_sn); // Lost PDU SN=3. + + // Write status PDU to RLC1 + rlc1.write_pdu(status_buf.msg, status_buf.N_bytes); + + // Check there is an Retx of SN=3 + TESTASSERT_EQ(header_size + payload_size, rlc1.get_buffer_state()); + } + + constexpr uint32_t so_size = 2; + constexpr uint32_t segment_size = 1; + uint32_t pdu_size_first = header_size + segment_size; + uint32_t pdu_size_continued = header_size + so_size + segment_size; + { + // Re-transmit PDU in 3 segments + for (int i = 0; i < 3; i++) { + byte_buffer_t retx_buf; + uint32_t len = 0; + if (i == 0) { + len = rlc1.read_pdu(retx_buf.msg, pdu_size_first); + TESTASSERT_EQ(pdu_size_first, len); + } else { + len = rlc1.read_pdu(retx_buf.msg, pdu_size_continued); + TESTASSERT_EQ(pdu_size_continued, len); + } + retx_buf.N_bytes = len; + + rlc_am_nr_pdu_header_t header_check = {}; + uint32_t hdr_len = rlc_am_nr_read_data_pdu_header(&retx_buf, sn_size, &header_check); + // Double check header. + TESTASSERT_EQ(3, header_check.sn); // Double check RETX SN + if (i == 0) { + TESTASSERT_EQ(rlc_nr_si_field_t::first_segment, header_check.si); + } else if (i == 1) { + TESTASSERT_EQ(rlc_nr_si_field_t::neither_first_nor_last_segment, header_check.si); + } else { + TESTASSERT_EQ(rlc_nr_si_field_t::last_segment, header_check.si); + } + + // We loose the first and the last segment + if (i != 0 && i != 2) { + rlc2.write_pdu(retx_buf.msg, retx_buf.N_bytes); + } + } + TESTASSERT(0 == rlc1.get_buffer_state()); + } + + // Step timers until reassambly timeout expires + for (int cnt = 0; cnt < 35; cnt++) { + timers.step_all(); + } + + // t-reassembly has expired. There should be another NACK in the status report. + constexpr uint32_t status_pdu_so_size = 4; + TESTASSERT_EQ(status_pdu_ack_size + 2 * status_pdu_nack_size + 2 * status_pdu_so_size, rlc2.get_buffer_state()); + { + // Read status PDU from RLC2 + byte_buffer_t status_buf; + int len = rlc2.read_pdu(status_buf.msg, status_pdu_ack_size + 2 * status_pdu_nack_size + 2 * status_pdu_so_size); + status_buf.N_bytes = len; + + TESTASSERT_EQ(0, rlc2.get_buffer_state()); + + // Assert status is correct + rlc_am_nr_status_pdu_t status_check(sn_size); + rlc_am_nr_read_status_pdu(&status_buf, sn_size, &status_check); + TESTASSERT_EQ(5, status_check.ack_sn); // 5 is the next expected SN. + TESTASSERT_EQ(2, status_check.nacks.size()); // We lost two PDU segments. + TESTASSERT_EQ(3, status_check.nacks[0].nack_sn); // Lost PDU SN=3. + TESTASSERT_EQ(true, status_check.nacks[0].has_so); // This is a segment missing. + TESTASSERT_EQ(0, status_check.nacks[0].so_start); // Segment offset should be 0 here + TESTASSERT_EQ(0, status_check.nacks[0].so_end); // Segment end should be 0 here + TESTASSERT_EQ(true, status_check.nacks[1].has_so); // This is a segment missing. + TESTASSERT_EQ(2, status_check.nacks[1].so_start); // Segment offset should be 2 here + TESTASSERT_EQ(0xFFFF, status_check.nacks[1].so_end); // Segment end should be 0xFFFF here + + // Write status PDU to RLC1 + rlc1.write_pdu(status_buf.msg, status_buf.N_bytes); + + // Write status PDU duplicate to RLC1 + rlc1.write_pdu(status_buf.msg, status_buf.N_bytes); + + // Check there are two Retx segments (a first one and a continued one) + TESTASSERT_EQ(pdu_size_first + pdu_size_continued, rlc1.get_buffer_state()); + } + + { + // Re-transmit the lost 2 segments + for (int i = 0; i < 2; i++) { + byte_buffer_t retx_buf; + uint32_t len = 0; + if (i == 0) { + len = rlc1.read_pdu(retx_buf.msg, pdu_size_first); + TESTASSERT_EQ(pdu_size_first, len); + } else { + len = rlc1.read_pdu(retx_buf.msg, pdu_size_continued); + TESTASSERT_EQ(pdu_size_continued, len); + } + retx_buf.N_bytes = len; + + rlc_am_nr_pdu_header_t header_check = {}; + uint32_t hdr_len = rlc_am_nr_read_data_pdu_header(&retx_buf, sn_size, &header_check); + // Double check header. + TESTASSERT_EQ(3, header_check.sn); // Double check RETX SN + if (i == 0) { + TESTASSERT_EQ(rlc_nr_si_field_t::first_segment, header_check.si); + } else { + TESTASSERT_EQ(rlc_nr_si_field_t::last_segment, header_check.si); + } + + rlc2.write_pdu(retx_buf.msg, retx_buf.N_bytes); + } + TESTASSERT(0 == rlc1.get_buffer_state()); + } + + // Check statistics + rlc_bearer_metrics_t metrics1 = rlc1.get_metrics(); + rlc_bearer_metrics_t metrics2 = rlc2.get_metrics(); + + uint32_t data_pdu_size = header_size + payload_size; + uint32_t total_tx_pdu_bytes1 = NBUFS * data_pdu_size + 2 * pdu_size_first + 3 * pdu_size_continued; + uint32_t total_rx_pdu_bytes1 = status_pdu_ack_size + // ACK, no NACK + (status_pdu_ack_size + status_pdu_nack_size) + // ACK + NACK full SDU + 2 * (status_pdu_ack_size + 2 * status_pdu_nack_size + // 2 * (ACK + NACK two segments) + 2 * status_pdu_so_size); + uint32_t total_tx_pdu_bytes2 = status_pdu_ack_size + // ACK, no NACK + (status_pdu_ack_size + status_pdu_nack_size) + // ACK + NACK full SDU + 1 * (status_pdu_ack_size + 2 * status_pdu_nack_size + // 1 * (ACK + NACK two segments) + 2 * status_pdu_so_size); + uint32_t total_rx_pdu_bytes2 = (NBUFS - 1) * data_pdu_size + pdu_size_first + 2 * pdu_size_continued; + + // SDU metrics + TESTASSERT_EQ(5, metrics1.num_tx_sdus); + TESTASSERT_EQ(0, metrics1.num_rx_sdus); + TESTASSERT_EQ(15, metrics1.num_tx_sdu_bytes); + TESTASSERT_EQ(0, metrics1.num_rx_sdu_bytes); + TESTASSERT_EQ(0, metrics1.num_lost_sdus); + // PDU metrics + TESTASSERT_EQ(5 + 3 + 2, metrics1.num_tx_pdus); // 5 + (3 + 2) re-transmissions + TESTASSERT_EQ(4, metrics1.num_rx_pdus); // 4 status PDU + TESTASSERT_EQ(total_tx_pdu_bytes1, metrics1.num_tx_pdu_bytes); + TESTASSERT_EQ(total_rx_pdu_bytes1, metrics1.num_rx_pdu_bytes); + TESTASSERT_EQ(0, metrics1.num_lost_sdus); // No lost SDUs + + // PDU metrics + TESTASSERT_EQ(0, metrics2.num_tx_sdus); + TESTASSERT_EQ(5, metrics2.num_rx_sdus); + TESTASSERT_EQ(0, metrics2.num_tx_sdu_bytes); + TESTASSERT_EQ(15, metrics2.num_rx_sdu_bytes); // 5 SDUs, 3 bytes each + TESTASSERT_EQ(0, metrics2.num_lost_sdus); + // SDU metrics + TESTASSERT_EQ(3, metrics2.num_tx_pdus); // 3 status PDUs + TESTASSERT_EQ(7, metrics2.num_rx_pdus); // 7 PDUs (10 tx'ed, but 3 were lost) + TESTASSERT_EQ(total_tx_pdu_bytes2, metrics2.num_tx_pdu_bytes); + TESTASSERT_EQ(total_rx_pdu_bytes2, + metrics2.num_rx_pdu_bytes); // 2 Bytes * (NBUFFS-1) (header size) + (NBUFFS-1) * 3 (data) + // 3 (1 retx no SO) + 2 * 5 (2 retx with SO) = 33 + TESTASSERT_EQ(0, metrics2.num_lost_sdus); // No lost SDUs + + // Check state + rlc_am_nr_rx_state_t state2_rx = rx2->get_rx_state(); + TESTASSERT_EQ(5, state2_rx.rx_next); + return SRSRAN_SUCCESS; +} + +int retx_segment_test(rlc_am_nr_sn_size_t sn_size) +{ + rlc_am_tester tester; + timer_handler timers(8); + + auto& test_logger = srslog::fetch_basic_logger("TESTER "); + rlc_am rlc1(srsran_rat_t::nr, srslog::fetch_basic_logger("RLC_AM_1"), 1, &tester, &tester, &timers); + rlc_am rlc2(srsran_rat_t::nr, srslog::fetch_basic_logger("RLC_AM_2"), 1, &tester, &tester, &timers); + std::string str = "retx segment PDU (" + std::to_string(to_number(sn_size)) + " bit SN)"; + test_delimit_logger delimiter(str.c_str()); + + rlc_am_nr_tx* tx1 = dynamic_cast(rlc1.get_tx()); + rlc_am_nr_rx* rx1 = dynamic_cast(rlc1.get_rx()); + rlc_am_nr_tx* tx2 = dynamic_cast(rlc2.get_tx()); + rlc_am_nr_rx* rx2 = dynamic_cast(rlc2.get_rx()); + + auto rlc_cnfg = rlc_config_t::default_rlc_am_nr_config(to_number(sn_size)); + rlc_cnfg.am_nr.t_poll_retx = -1; + if (not rlc1.configure(rlc_cnfg)) { + return -1; + } + + if (not rlc2.configure(rlc_cnfg)) { + return -1; + } + + // after configuring entity + TESTASSERT(0 == rlc1.get_buffer_state()); + + int n_sdu_bufs = 5; + int n_pdu_bufs = 15; + + // Push 5 SDUs into RLC1 + std::vector sdu_bufs(n_sdu_bufs); + constexpr uint32_t payload_size = 3; // Give the SDU the size of 3 bytes + uint32_t header_size = sn_size == rlc_am_nr_sn_size_t::size12bits ? 2 : 3; + for (int i = 0; i < n_sdu_bufs; i++) { + sdu_bufs[i] = srsran::make_byte_buffer(); + sdu_bufs[i]->msg[0] = i; // Write the index into the buffer + sdu_bufs[i]->N_bytes = payload_size; // Give each buffer a size of 3 bytes + sdu_bufs[i]->md.pdcp_sn = i; // PDCP SN for notifications + rlc1.write_sdu(std::move(sdu_bufs[i])); + } + + uint32_t expected_buffer_state = (header_size + payload_size) * n_sdu_bufs; + TESTASSERT_EQ(expected_buffer_state, rlc1.get_buffer_state()); + + constexpr uint32_t so_size = 2; + constexpr uint32_t segment_size = 1; + uint32_t pdu_size_first = header_size + segment_size; + uint32_t pdu_size_continued = header_size + so_size + segment_size; + + // Read 15 PDUs from RLC1 + std::vector pdu_bufs(n_pdu_bufs); + for (int i = 0; i < n_pdu_bufs; i++) { + // First also test buffer state + uint32_t remaining_total_bytes = (payload_size * n_sdu_bufs) - (i * segment_size); + uint32_t remaining_full_sdus = remaining_total_bytes / payload_size; + uint32_t remaining_seg_bytes = remaining_total_bytes % payload_size; + + uint32_t buffer_state_full_sdus = (header_size + payload_size) * remaining_full_sdus; + uint32_t buffer_state_seg_sdu = remaining_seg_bytes == 0 ? 0 : (header_size + so_size + remaining_seg_bytes); + expected_buffer_state = buffer_state_full_sdus + buffer_state_seg_sdu; + TESTASSERT_EQ(expected_buffer_state, rlc1.get_buffer_state()); + + pdu_bufs[i] = srsran::make_byte_buffer(); + if (i == 0 || i == 3 || i == 6 || i == 9 || i == 12) { + // First segment, no SO + uint32_t len = rlc1.read_pdu(pdu_bufs[i]->msg, pdu_size_first); // 2 bytes for header + 1 byte payload + pdu_bufs[i]->N_bytes = len; + TESTASSERT_EQ(pdu_size_first, len); + } else { + // Middle or last segment, SO present + uint32_t len = rlc1.read_pdu(pdu_bufs[i]->msg, pdu_size_continued); // 4 bytes for header + 1 byte payload + pdu_bufs[i]->N_bytes = len; + TESTASSERT_EQ(pdu_size_continued, len); + } + } + + TESTASSERT_EQ(0, rlc1.get_buffer_state()); + + // Write 15 - 3 PDUs into RLC2 + for (int i = 0; i < n_pdu_bufs; i++) { + if (i != 3 && i != 7 && i != 11) { + // Lose first segment of RLC_SN=1. + // Lose middle segment of RLC_SN=2. + // Lose last segment of RLC_SN=3. + rlc2.write_pdu(pdu_bufs[i]->msg, pdu_bufs[i]->N_bytes); + } + } + + { + // Double check rx state + rlc_am_nr_rx_state_t st = rx2->get_rx_state(); + TESTASSERT_EQ(1, st.rx_next); + TESTASSERT_EQ(1, st.rx_highest_status); + TESTASSERT_EQ(2, st.rx_next_status_trigger); // Rx_Next_Highest + 1, when the t-Reordering was started + TESTASSERT_EQ(5, st.rx_next_highest); // Highest SN received + 1 + } + + // Only after t-reassembly has expired, will the status report include NACKs. + // RX_Highest_Status will be updated to to the SN + // of the first RLC SDU with SN >= RX_Next_Status_Trigger + TESTASSERT_EQ(3, rlc2.get_buffer_state()); + { + // Read status PDU from RLC2 + byte_buffer_t status_buf; + int len = rlc2.read_pdu(status_buf.msg, 5); + status_buf.N_bytes = len; + + TESTASSERT_EQ(0, rlc2.get_buffer_state()); + + // Assert status is correct + rlc_am_nr_status_pdu_t status_check(sn_size); + rlc_am_nr_read_status_pdu(&status_buf, sn_size, &status_check); + TESTASSERT_EQ(1, status_check.ack_sn); // 1 is the next expected SN (i.e. the first lost packet.) + + // Write status PDU to RLC1 + rlc1.write_pdu(status_buf.msg, status_buf.N_bytes); + } + + // Step timers until reassambly timeout expires + for (int cnt = 0; cnt < 35; cnt++) { + timers.step_all(); + } + + // After the t-Reassembly expires: + // - RX_Highest_Status is updated to the SN of the first RLC SDU with SN >= RX_Next_Status_Trigger, i.e., SN=2 + // - Because RX_Next_Highest> RX_Highest_Status +1: + // - t-Reassembly is restarted, and + // - RX_Next_Status_Trigger is set to RX_Next_Highest. + { + // Double check rx state + rlc_am_nr_rx_state_t st = rx2->get_rx_state(); + TESTASSERT_EQ(1, st.rx_next); + TESTASSERT_EQ(2, st.rx_highest_status); + TESTASSERT_EQ(5, st.rx_next_status_trigger); // Rx_Next_Highest + 1, when the t-Reassembly was started + TESTASSERT_EQ(5, st.rx_next_highest); // Highest SN received + 1 + } + + // t-reassembly has expired. Becuse RX_Highest_Status is 2 + // There should be an ACK of SN=2 and a NACK of SN=1 + constexpr uint32_t status_pdu_ack_size = 3; + uint32_t status_pdu_nack_size = sn_size == rlc_am_nr_sn_size_t::size12bits ? 2 : 3; + constexpr uint32_t status_pdu_so_size = 4; + TESTASSERT_EQ(status_pdu_ack_size + status_pdu_nack_size + status_pdu_so_size, + rlc2.get_buffer_state()); // 3 bytes for fixed header (ACK+E1) + 6 for NACK with SO = 9. + { + // Read status PDU from RLC2 + byte_buffer_t status_buf; + int len = rlc2.read_pdu(status_buf.msg, status_pdu_ack_size + status_pdu_nack_size + status_pdu_so_size); + status_buf.N_bytes = len; + + TESTASSERT_EQ(0, rlc2.get_buffer_state()); + + // Assert status is correct + rlc_am_nr_status_pdu_t status_check(sn_size); + rlc_am_nr_read_status_pdu(&status_buf, sn_size, &status_check); + TESTASSERT_EQ(2, status_check.ack_sn); // 5 is the next expected SN. + TESTASSERT_EQ(1, status_check.nacks.size()); // We lost one PDU. + TESTASSERT_EQ(1, status_check.nacks[0].nack_sn); // Lost SDU on SN=1. + TESTASSERT_EQ(true, status_check.nacks[0].has_so); // It's a segment. + TESTASSERT_EQ(0, status_check.nacks[0].so_start); // First byte missing is 0. + TESTASSERT_EQ(0, status_check.nacks[0].so_end); // Last byte of the segment. + } + + // Step timers until reassambly timeout expires + for (int cnt = 0; cnt < 35; cnt++) { + timers.step_all(); + } + + // After the t-Reassembly expires: + // - RX_Highest_Status is updated to the SN of the first RLC SDU with SN >= RX_Next_Status_Trigger, i.e., SN=2 + // - Because RX_Next_Highest> RX_Highest_Status +1: + // - t-Reassembly is restarted, and + // - RX_Next_Status_Trigger is set to RX_Next_Highest. + { + // Double check rx state + rlc_am_nr_rx_state_t st = rx2->get_rx_state(); + TESTASSERT_EQ(1, st.rx_next); + TESTASSERT_EQ(5, st.rx_highest_status); + TESTASSERT_EQ(5, st.rx_next_status_trigger); // Rx_Next_Highest + 1, when the t-Reordering was started + TESTASSERT_EQ(5, st.rx_next_highest); // Highest SN received + 1 + } + + // t-reassembly has expired. There should be a NACK in the status report. + // There should be 3 NACKs with SO_start and SO_end + TESTASSERT_EQ(status_pdu_ack_size + 3 * (status_pdu_nack_size + status_pdu_so_size), rlc2.get_buffer_state()); + { + // Read status PDU from RLC2 + byte_buffer_t status_buf; + int len = rlc2.read_pdu(status_buf.msg, status_pdu_ack_size + 3 * (status_pdu_nack_size + status_pdu_so_size)); + status_buf.N_bytes = len; + + TESTASSERT_EQ(0, rlc2.get_buffer_state()); + + // Assert status is correct + rlc_am_nr_status_pdu_t status_check(sn_size); + rlc_am_nr_read_status_pdu(&status_buf, sn_size, &status_check); + TESTASSERT_EQ(5, status_check.ack_sn); // 5 is the next expected SN. + TESTASSERT_EQ(3, status_check.nacks.size()); // We lost one PDU. + TESTASSERT_EQ(1, status_check.nacks[0].nack_sn); // Lost SDU on SN=1. + TESTASSERT_EQ(true, status_check.nacks[0].has_so); // Lost SDU on SN=1. + TESTASSERT_EQ(0, status_check.nacks[0].so_start); // Lost SDU on SN=1. + TESTASSERT_EQ(0, status_check.nacks[0].so_end); // Lost SDU on SN=1. + TESTASSERT_EQ(2, status_check.nacks[1].nack_sn); // Lost SDU on SN=1. + TESTASSERT_EQ(true, status_check.nacks[1].has_so); // Lost SDU on SN=1. + TESTASSERT_EQ(1, status_check.nacks[1].so_start); // Lost SDU on SN=1. + TESTASSERT_EQ(1, status_check.nacks[1].so_end); // Lost SDU on SN=1. + TESTASSERT_EQ(3, status_check.nacks[2].nack_sn); // Lost SDU on SN=1. + TESTASSERT_EQ(true, status_check.nacks[2].has_so); // Lost SDU on SN=1. + TESTASSERT_EQ(2, status_check.nacks[2].so_start); // Lost SDU on SN=1. + TESTASSERT_EQ(0xFFFF, status_check.nacks[2].so_end); // Lost SDU on SN=1. + + // Write status PDU to RLC1 + rlc1.write_pdu(status_buf.msg, status_buf.N_bytes); + + // Check there are 3 Retx segments (a first one and two continued ones) + TESTASSERT_EQ(pdu_size_first + 2 * pdu_size_continued, rlc1.get_buffer_state()); + } + + { + // Re-transmit the 3 lost segments + for (int i = 0; i < 3; i++) { + // First also test buffer state + uint32_t remaining_segments = 3 - i; + expected_buffer_state = remaining_segments * (header_size + so_size + segment_size); + if (i == 0) { // subtract so_size, because in this setup the first retx is a "first_segment" without SO. + expected_buffer_state -= so_size; + } + TESTASSERT_EQ(expected_buffer_state, rlc1.get_buffer_state()); + + byte_buffer_t retx_buf; + uint32_t len = 0; + if (i == 0) { + len = rlc1.read_pdu(retx_buf.msg, pdu_size_first); + TESTASSERT_EQ(pdu_size_first, len); + } else { + len = rlc1.read_pdu(retx_buf.msg, pdu_size_continued); + TESTASSERT_EQ(pdu_size_continued, len); + } + retx_buf.N_bytes = len; + + rlc_am_nr_pdu_header_t header_check = {}; + uint32_t hdr_len = rlc_am_nr_read_data_pdu_header(&retx_buf, sn_size, &header_check); + // Double check header. + if (i == 0) { + TESTASSERT_EQ(1, header_check.sn); // Double check RETX SN + TESTASSERT_EQ(rlc_nr_si_field_t::first_segment, header_check.si); + } else if (i == 1) { + TESTASSERT_EQ(2, header_check.sn); // Double check RETX SN TESTASSERT_EQ(rlc_nr_si_field_t::neither_first_nor_last_segment, header_check.si); } else { TESTASSERT_EQ(3, header_check.sn); // Double check RETX SN @@ -845,6 +1726,12 @@ int retx_segment_test() rlc_bearer_metrics_t metrics1 = rlc1.get_metrics(); rlc_bearer_metrics_t metrics2 = rlc2.get_metrics(); + uint32_t data_pdu_size = header_size + payload_size; + uint32_t total_tx_pdu_bytes1 = 5 * pdu_size_first + 10 * pdu_size_continued + pdu_size_first + 2 * pdu_size_continued; + uint32_t total_rx_pdu_bytes1 = 2 * status_pdu_ack_size + 3 * (status_pdu_nack_size + status_pdu_so_size); + uint32_t total_tx_pdu_bytes2 = 3 * status_pdu_ack_size + 4 * (status_pdu_nack_size + status_pdu_so_size); + uint32_t total_rx_pdu_bytes2 = 4 * pdu_size_first + 8 * pdu_size_continued + pdu_size_first + 2 * pdu_size_continued; + // SDU metrics TESTASSERT_EQ(5, metrics1.num_tx_sdus); TESTASSERT_EQ(0, metrics1.num_rx_sdus); @@ -853,13 +1740,15 @@ int retx_segment_test() TESTASSERT_EQ(0, metrics1.num_lost_sdus); // PDU metrics - TESTASSERT_EQ(15 + 3, metrics1.num_tx_pdus); // 15 PDUs + 3 re-transmissions - TESTASSERT_EQ(2, metrics1.num_rx_pdus); // Two status PDU - TESTASSERT_EQ(78, metrics1.num_tx_pdu_bytes); // 3 Bytes * 5 (5 PDUs without SO) + 10 * 5 (10 PDUs with SO) - // 3 (1 retx no SO) + 2 * 5 (2 retx with SO) = 78 - TESTASSERT_EQ(24, metrics1.num_rx_pdu_bytes); // Two status PDU. One with just an ack (3 bytes) - // Another with 3 NACKs all with SO. (3 + 3*6 bytes) - TESTASSERT_EQ(0, metrics1.num_lost_sdus); // No lost SDUs + TESTASSERT_EQ(15 + 3, metrics1.num_tx_pdus); // 15 PDUs + 3 re-transmissions + TESTASSERT_EQ(2, metrics1.num_rx_pdus); // Two status PDU + TESTASSERT_EQ(total_tx_pdu_bytes1, + metrics1.num_tx_pdu_bytes); // 3 Bytes * 5 (5 PDUs without SO) + 10 * 5 (10 PDUs with SO) + // 3 (1 retx no SO) + 2 * 5 (2 retx with SO) = 78 + TESTASSERT_EQ(total_rx_pdu_bytes1, + metrics1.num_rx_pdu_bytes); // Two status PDU. One with just an ack (3 bytes) + // Another with 3 NACKs all with SO. (3 + 3*6 bytes) = 24 + TESTASSERT_EQ(0, metrics1.num_lost_sdus); // No lost SDUs // PDU metrics TESTASSERT_EQ(0, metrics2.num_tx_sdus); @@ -868,14 +1757,15 @@ int retx_segment_test() TESTASSERT_EQ(15, metrics2.num_rx_sdu_bytes); // 5 SDUs, 3 bytes each TESTASSERT_EQ(0, metrics2.num_lost_sdus); // SDU metrics - TESTASSERT_EQ(2, metrics2.num_tx_pdus); // Two status PDUs - TESTASSERT_EQ(15, metrics2.num_rx_pdus); // 15 PDUs (18 tx'ed, but three were lost) - TESTASSERT_EQ(24, metrics2.num_tx_pdu_bytes); // Two status PDU. One with just an ack (3 bytes) - // Another with 3 NACKs all with SO. (3 + 3*6 bytes) - TESTASSERT_EQ(65, metrics2.num_rx_pdu_bytes); // 3 Bytes (header + data size, without SO) * 5 (N PDUs without SO) - // 5 bytes (header + data size, with SO) * 10 (N PDUs with SO) - // = 81 bytes - TESTASSERT_EQ(0, metrics2.num_lost_sdus); // No lost SDUs + TESTASSERT_EQ(3, metrics2.num_tx_pdus); // 3 status PDUs + TESTASSERT_EQ(15, metrics2.num_rx_pdus); // 15 PDUs (18 tx'ed, but three were lost) + TESTASSERT_EQ(total_tx_pdu_bytes2, // Three status PDU. One with just an ack + metrics2.num_tx_pdu_bytes); // Another with 1 NACK with SO. + // Another with 3 NACKs all with SO. + TESTASSERT_EQ(total_rx_pdu_bytes2, // 3 Bytes (header + data size, without SO) * 5 (N PDUs without SO) + metrics2.num_rx_pdu_bytes); // 5 bytes (header + data size, with SO) * 10 (N PDUs with SO) + // = 81 bytes + TESTASSERT_EQ(0, metrics2.num_lost_sdus); // No lost SDUs // Check state rlc_am_nr_rx_state_t state2_rx = rx2->get_rx_state(); @@ -884,6 +1774,703 @@ int retx_segment_test() return SRSRAN_SUCCESS; } +// This test checks whether RLC informs upper layer when max retransmission has been reached +// due to lost SDUs as a whole +int max_retx_lost_sdu_test(rlc_am_nr_sn_size_t sn_size) +{ + rlc_am_tester tester; + timer_handler timers(8); + int len = 0; + + auto& test_logger = srslog::fetch_basic_logger("TESTER "); + rlc_am rlc1(srsran_rat_t::nr, srslog::fetch_basic_logger("RLC_AM_1"), 1, &tester, &tester, &timers); + srslog::fetch_basic_logger("RLC_AM_1").set_hex_dump_max_size(100); + srslog::fetch_basic_logger("RLC").set_hex_dump_max_size(100); + test_delimit_logger delimiter("max retx lost SDU ({} bit SN)", to_number(sn_size)); + + const rlc_config_t rlc_cfg = rlc_config_t::default_rlc_am_nr_config(to_number(sn_size)); + if (not rlc1.configure(rlc_cfg)) { + return SRSRAN_ERROR; + } + + // Push 2 SDUs into RLC1 + const uint32_t n_sdus = 2; + unique_byte_buffer_t sdu_bufs[n_sdus]; + constexpr uint32_t payload_size = 1; // Give each buffer a size of 1 byte + uint32_t header_size = sn_size == rlc_am_nr_sn_size_t::size12bits ? 2 : 3; + for (uint32_t i = 0; i < n_sdus; i++) { + sdu_bufs[i] = srsran::make_byte_buffer(); + sdu_bufs[i]->msg[0] = i; // Write the index into the buffer + sdu_bufs[i]->N_bytes = payload_size; // Give each buffer a size of 1 byte + sdu_bufs[i]->md.pdcp_sn = i; // PDCP SN for notifications + rlc1.write_sdu(std::move(sdu_bufs[i])); + } + + uint32_t pdu_size = header_size + payload_size; + + // Read 2 PDUs from RLC1 (1 byte each) + const uint32_t n_pdus = 2; + byte_buffer_t pdu_bufs[n_pdus]; + for (uint32_t i = 0; i < n_pdus; i++) { + len = rlc1.read_pdu(pdu_bufs[i].msg, pdu_size); // 2 byte header + 1 byte payload + pdu_bufs[i].N_bytes = len; + } + + TESTASSERT(0 == rlc1.get_buffer_state()); + + // Fake status PDU that ack SN=1 and nack SN=0 + rlc_am_nr_status_pdu_t fake_status(sn_size); + fake_status.ack_sn = 2; // delivered up to SN=1 + rlc_status_nack_t nack; // one SN was lost + nack.nack_sn = 0; // it was SN=0 that was lost + fake_status.push_nack(nack); + + // pack into PDU + byte_buffer_t status_pdu; + rlc_am_nr_write_status_pdu(fake_status, rlc_cfg.am_nr.tx_sn_field_length, &status_pdu); + + // Exceed the number of tolerated retransmissions by one additional retransmission + // to trigger notification of the higher protocol layers. Note that the initial transmission + // (before starting retransmissions) does not count. See TS 38.322 Sec. 5.3.2 + for (uint32_t retx_count = 0; retx_count < rlc_cfg.am_nr.max_retx_thresh + 1; ++retx_count) { + // we've not yet reached max attempts + TESTASSERT(tester.max_retx_triggered == false); + + // Write status PDU to RLC1 + rlc1.write_pdu(status_pdu.msg, status_pdu.N_bytes); + + byte_buffer_t pdu_buf; + len = rlc1.read_pdu(pdu_buf.msg, pdu_size); // 2 byte header + 1 byte payload + } + + // Now maxRetx should have been triggered + TESTASSERT(tester.max_retx_triggered == true); + + return SRSRAN_SUCCESS; +} + +// This test checks whether RLC informs upper layer when max retransmission has been reached +// due to lost SDU segments +int max_retx_lost_segments_test(rlc_am_nr_sn_size_t sn_size) +{ + rlc_am_tester tester; + timer_handler timers(8); + int len = 0; + + auto& test_logger = srslog::fetch_basic_logger("TESTER "); + rlc_am rlc1(srsran_rat_t::nr, srslog::fetch_basic_logger("RLC_AM_1"), 1, &tester, &tester, &timers); + srslog::fetch_basic_logger("RLC_AM_1").set_hex_dump_max_size(100); + srslog::fetch_basic_logger("RLC").set_hex_dump_max_size(100); + test_delimit_logger delimiter("max retx lost SDU segment ({} bit SN)", to_number(sn_size)); + + const rlc_config_t rlc_cfg = rlc_config_t::default_rlc_am_nr_config(to_number(sn_size)); + if (not rlc1.configure(rlc_cfg)) { + return SRSRAN_ERROR; + } + + // Push 2 SDUs into RLC1 + const uint32_t n_sdus = 2; + unique_byte_buffer_t sdu_bufs[n_sdus]; + constexpr uint32_t payload_size = 20; // Give each buffer a size of 20 bytes + uint32_t header_size = sn_size == rlc_am_nr_sn_size_t::size12bits ? 2 : 3; + for (uint32_t i = 0; i < n_sdus; i++) { + sdu_bufs[i] = srsran::make_byte_buffer(); + sdu_bufs[i]->msg[0] = i; // Write the index into the buffer + sdu_bufs[i]->N_bytes = payload_size; // Give each buffer a size of 20 bytes + sdu_bufs[i]->md.pdcp_sn = i; // PDCP SN for notifications + rlc1.write_sdu(std::move(sdu_bufs[i])); + } + + constexpr uint32_t so_size = 2; + constexpr uint32_t segment_size_first = 13; + constexpr uint32_t segment_size_continued = 7; + uint32_t pdu_size_first = header_size + segment_size_first; + uint32_t pdu_size_continued = header_size + so_size + segment_size_continued; + + // Read 2*2=4 PDUs from RLC1 and limit to 15 byte to force segmentation in two parts: + // Segment 1: 2 byte header + 13 byte payload; space fully used + // Segment 2: 4 byte header + 7 byte payload; space not fully used, 4 bytes left over + const uint32_t n_pdus = 4; + byte_buffer_t pdu_bufs[n_pdus]; + for (uint32_t i = 0; i < n_pdus; i++) { + len = rlc1.read_pdu(pdu_bufs[i].msg, pdu_size_first); + pdu_bufs[i].N_bytes = len; + } + + TESTASSERT(0 == rlc1.get_buffer_state()); + + // Fake status PDU that ack SN=1 and nack {SN=0 segment 0, SN=0 segment 1} + rlc_am_nr_status_pdu_t status_lost_both_segments(sn_size); + status_lost_both_segments.ack_sn = 2; // delivered up to SN=1 + + // two segments lost + { + rlc_status_nack_t nack; + nack.nack_sn = 0; // it was SN=0 that was lost + nack.has_so = true; // this NACKs a segment + nack.so_start = 0; // segment starts at (and includes) byte 0 + nack.so_end = 12; // segment ends at (and includes) byte 12 + status_lost_both_segments.push_nack(nack); + } + + { + rlc_status_nack_t nack; + nack.nack_sn = 0; // it was SN=0 that was lost + nack.has_so = true; // this NACKs a segment + nack.so_start = 13; // segment starts at (and includes) byte 13 + nack.so_end = 19; // segment ends at (and includes) byte 19 + status_lost_both_segments.push_nack(nack); + } + + // pack into PDU + byte_buffer_t status_pdu_lost_both_segments; + rlc_am_nr_write_status_pdu( + status_lost_both_segments, rlc_cfg.am_nr.tx_sn_field_length, &status_pdu_lost_both_segments); + + // Fake status PDU that ack SN=1 and nack {SN=0 segment 1} + rlc_am_nr_status_pdu_t status_lost_second_segment(sn_size); + status_lost_second_segment.ack_sn = 2; // delivered up to SN=1 + + // one SN was lost + { + rlc_status_nack_t nack; + nack.nack_sn = 0; // it was SN=0 that was lost + nack.has_so = true; // this NACKs a segment + nack.so_start = 13; // segment starts at (and includes) byte 13 + nack.so_end = 19; // segment ends at (and includes) byte 19 + status_lost_second_segment.push_nack(nack); + } + + // pack into PDU + byte_buffer_t status_pdu_lost_second_segment; + rlc_am_nr_write_status_pdu( + status_lost_second_segment, rlc_cfg.am_nr.tx_sn_field_length, &status_pdu_lost_second_segment); + + // Exceed the number of tolerated retransmissions by one additional retransmission + // to trigger notification of the higher protocol layers. Note that the initial transmission + // (before starting retransmissions) does not count. See TS 38.322 Sec. 5.3.2 + for (uint32_t retx_count = 0; retx_count < rlc_cfg.am_nr.max_retx_thresh + 1; ++retx_count) { + byte_buffer_t pdu_buf; + + // we've not yet reached max attempts + TESTASSERT(tester.max_retx_triggered == false); + + if (retx_count < rlc_cfg.am_nr.max_retx_thresh / 2) { + // Send NACK for segment 1 and segment 2 + // Although two segments, this must count as one retransmission, + // because both segments NACK the same SDU in the same status message. + rlc1.write_pdu(status_pdu_lost_both_segments.msg, status_pdu_lost_both_segments.N_bytes); + + // read the retransmitted PDUs + len = rlc1.read_pdu(pdu_buf.msg, pdu_size_first); // 2 byte header + 13 byte payload + len = rlc1.read_pdu(pdu_buf.msg, pdu_size_first); // 4 byte header + 7 byte payload + } else { + // Send NACK for segment 2 (assume at least segment 1 was finally received) + rlc1.write_pdu(status_pdu_lost_second_segment.msg, status_pdu_lost_second_segment.N_bytes); + + // read the retransmitted PDUs + len = rlc1.read_pdu(pdu_buf.msg, pdu_size_first); // 4 byte header + 7 byte payload + } + } + + // Now maxRetx should have been triggered + TESTASSERT(tester.max_retx_triggered == true); + + return SRSRAN_SUCCESS; +} + +// This test checks the correct functioning of RLC discard functionality +int discard_test(rlc_am_nr_sn_size_t sn_size) +{ + rlc_am_tester tester; + timer_handler timers(8); + + auto& test_logger = srslog::fetch_basic_logger("TESTER "); + rlc_am rlc1(srsran_rat_t::nr, srslog::fetch_basic_logger("RLC_AM_1"), 1, &tester, &tester, &timers); + rlc_am rlc2(srsran_rat_t::nr, srslog::fetch_basic_logger("RLC_AM_2"), 1, &tester, &tester, &timers); + test_delimit_logger delimiter("discard test ({} bit SN)", to_number(sn_size)); + + srslog::fetch_basic_logger("RLC_AM_1").set_hex_dump_max_size(100); + srslog::fetch_basic_logger("RLC_AM_2").set_hex_dump_max_size(100); + srslog::fetch_basic_logger("RLC").set_hex_dump_max_size(100); + + if (not rlc1.configure(rlc_config_t::default_rlc_am_nr_config(to_number(sn_size)))) { + return SRSRAN_ERROR; + } + + if (not rlc2.configure(rlc_config_t::default_rlc_am_nr_config(to_number(sn_size)))) { + return SRSRAN_ERROR; + } + + uint32_t num_tx_sdus = 1; + uint32_t header_size = sn_size == rlc_am_nr_sn_size_t::size12bits ? 2 : 3; + uint32_t payload_size = 5; // Give each buffer a size of 5 bytes + // Test discarding the single SDU from the queue + { + for (uint32_t i = 0; i < num_tx_sdus; ++i) { + // Write SDU + unique_byte_buffer_t sdu = srsran::make_byte_buffer(); + TESTASSERT(sdu != nullptr); + sdu->N_bytes = payload_size; + for (uint32_t k = 0; k < sdu->N_bytes; ++k) { + sdu->msg[k] = i; // Write the index into the buffer + } + sdu->md.pdcp_sn = i; + rlc1.write_sdu(std::move(sdu)); + } + } + rlc1.discard_sdu(0); // Try to discard PDCP_SN=0 + TESTASSERT(rlc1.has_data() == false); + + num_tx_sdus = 10; + payload_size = 7; // Give each buffer a size of 7 bytes + // Test discarding two SDUs in the middle (SN=3) and end (SN=9) of the queue and read PDUs after + { + for (uint32_t i = 0; i < num_tx_sdus; ++i) { + // Write SDU + unique_byte_buffer_t sdu = srsran::make_byte_buffer(); + TESTASSERT(sdu != nullptr); + sdu->N_bytes = payload_size; + for (uint32_t k = 0; k < sdu->N_bytes; ++k) { + sdu->msg[k] = i; // Write the index into the buffer + } + sdu->md.pdcp_sn = i; + rlc1.write_sdu(std::move(sdu)); + } + } + TESTASSERT(rlc1.get_buffer_state() == num_tx_sdus * (header_size + payload_size)); // 10 * (2B Header + 7B Payload) + rlc1.discard_sdu(3); // Try to discard PDCP_SN=3 + TESTASSERT(rlc1.has_data() == true); + TESTASSERT(rlc1.get_buffer_state() == (num_tx_sdus - 1) * (header_size + payload_size)); + rlc1.discard_sdu(9); // Try to discard PDCP_SN=9 + TESTASSERT(rlc1.has_data() == true); + TESTASSERT(rlc1.get_buffer_state() == (num_tx_sdus - 2) * (header_size + payload_size)); + + num_tx_sdus = 8; + { + for (uint32_t i = 0; i < num_tx_sdus; ++i) { + unique_byte_buffer_t pdu = srsran::make_byte_buffer(); + uint32_t len = rlc1.read_pdu(pdu->msg, 50); // sufficient space to read without segmentation + pdu->N_bytes = len; + TESTASSERT((header_size + payload_size) == len); + // Check that we don't have any SN gaps + rlc_am_nr_pdu_header_t header = {}; + rlc_am_nr_read_data_pdu_header(pdu.get(), sn_size, &header); + TESTASSERT(header.sn == i); + } + } + TESTASSERT(rlc1.has_data() == false); + srslog::fetch_basic_logger("TEST").info("Received %zd SDUs", tester.sdus.size()); + + num_tx_sdus = 3; + payload_size = 7; // Give each buffer a size of 7 bytes + // Test discarding non-existing SDU from the queue + { + for (uint32_t i = 0; i < num_tx_sdus; ++i) { + // Write SDU + unique_byte_buffer_t sdu = srsran::make_byte_buffer(); + TESTASSERT(sdu != nullptr); + sdu->N_bytes = payload_size; + for (uint32_t k = 0; k < sdu->N_bytes; ++k) { + sdu->msg[k] = i; // Write the index into the buffer + } + sdu->md.pdcp_sn = i; + rlc1.write_sdu(std::move(sdu)); + } + } + TESTASSERT(rlc1.get_buffer_state() == num_tx_sdus * (header_size + payload_size)); // 3 * (2B Header + 7B Payload) + rlc1.discard_sdu(8); // Try to discard PDCP_SN=8, which doesn't exist + TESTASSERT(rlc1.get_buffer_state() == num_tx_sdus * (header_size + payload_size)); // 3 * (2B Header + 7B Payload) + + return SRSRAN_SUCCESS; +} + +// Test p bit set on new TX with PollPDU +int poll_pdu(rlc_am_nr_sn_size_t sn_size) +{ + rlc_am_tester tester; + timer_handler timers(8); + + auto& test_logger = srslog::fetch_basic_logger("TESTER "); + test_delimit_logger delimiter("pollPDU test ({} bit SN)", to_number(sn_size)); + + srslog::fetch_basic_logger("RLC_AM_1").set_hex_dump_max_size(100); + + rlc_config_t rlc_cnfg = {}; + rlc_cnfg.rat = srsran_rat_t::nr; + rlc_cnfg.rlc_mode = rlc_mode_t::am; + rlc_cnfg.am_nr.tx_sn_field_length = sn_size; // Number of bits used for tx (UL) sequence number + rlc_cnfg.am_nr.rx_sn_field_length = sn_size; // Number of bits used for rx (DL) sequence number + rlc_cnfg.am_nr.poll_pdu = 4; + rlc_cnfg.am_nr.poll_byte = 3000; + rlc_cnfg.am_nr.t_status_prohibit = 8; + rlc_cnfg.am_nr.max_retx_thresh = 8; + rlc_cnfg.am_nr.t_reassembly = 35; + + // Test p bit set on new TX with PollPDU + { + rlc_am rlc1(srsran_rat_t::nr, srslog::fetch_basic_logger("RLC_AM_1"), 1, &tester, &tester, &timers); + if (not rlc1.configure(rlc_cnfg)) { + return SRSRAN_ERROR; + } + // pollPDU == 4 + uint32_t num_tx_sdus = 6; + for (uint32_t i = 0; i < num_tx_sdus; ++i) { + // Write SDU + unique_byte_buffer_t sdu = srsran::make_byte_buffer(); + TESTASSERT(sdu != nullptr); + sdu->N_bytes = 1; + sdu->md.pdcp_sn = i; + rlc1.write_sdu(std::move(sdu)); + } + uint32_t num_tx_pdus = 6; + uint32_t pdu_size = sn_size == rlc_am_nr_sn_size_t::size12bits ? 3 : 4; + for (uint32_t i = 0; i < num_tx_pdus; ++i) { + unique_byte_buffer_t pdu = srsran::make_byte_buffer(); + TESTASSERT(pdu != nullptr); + pdu->N_bytes = rlc1.read_pdu(pdu->msg, pdu_size); + rlc_am_nr_pdu_header_t hdr; + rlc_am_nr_read_data_pdu_header(pdu.get(), sn_size, &hdr); + if (i != 3 && i != 5) { // P bit set for PollPDU and for empty TX queue + TESTASSERT_EQ(0, hdr.p); + } else { + TESTASSERT_EQ(1, hdr.p); + } + } + } + return SRSRAN_SUCCESS; +} + +// Test p bit set on new TX with PollBYTE +int poll_byte(rlc_am_nr_sn_size_t sn_size) +{ + rlc_am_tester tester; + timer_handler timers(8); + + auto& test_logger = srslog::fetch_basic_logger("TESTER "); + test_delimit_logger delimiter("pollBYTE test ({} bit SN)", to_number(sn_size)); + + srslog::fetch_basic_logger("RLC_AM_1").set_hex_dump_max_size(100); + + rlc_config_t rlc_cnfg = {}; + rlc_cnfg.rat = srsran_rat_t::nr; + rlc_cnfg.rlc_mode = rlc_mode_t::am; + rlc_cnfg.am_nr.tx_sn_field_length = sn_size; // Number of bits used for tx (UL) sequence number + rlc_cnfg.am_nr.rx_sn_field_length = sn_size; // Number of bits used for rx (DL) sequence number + rlc_cnfg.am_nr.poll_pdu = 4; + rlc_cnfg.am_nr.poll_byte = 3000; + rlc_cnfg.am_nr.t_status_prohibit = 8; + rlc_cnfg.am_nr.max_retx_thresh = 8; + rlc_cnfg.am_nr.t_reassembly = 35; + + rlc_am rlc1(srsran_rat_t::nr, srslog::fetch_basic_logger("RLC_AM_1"), 1, &tester, &tester, &timers); + if (not rlc1.configure(rlc_cnfg)) { + return SRSRAN_ERROR; + } + // pollByte == 3000 + uint32_t num_tx_sdus = 4; + for (uint32_t i = 0; i < num_tx_sdus; ++i) { + // Write SDU + unique_byte_buffer_t sdu = srsran::make_byte_buffer(); + TESTASSERT(sdu != nullptr); + sdu->N_bytes = i == 0 ? 2999 : 1; + sdu->md.pdcp_sn = i; + rlc1.write_sdu(std::move(sdu)); + } + uint32_t num_tx_pdus = num_tx_sdus; + uint32_t small_pdu_size = sn_size == rlc_am_nr_sn_size_t::size12bits ? 3 : 4; + uint32_t large_pdu_size = sn_size == rlc_am_nr_sn_size_t::size12bits ? 3001 : 3002; + for (uint32_t i = 0; i < num_tx_pdus; ++i) { + unique_byte_buffer_t pdu = srsran::make_byte_buffer(); + TESTASSERT(pdu != nullptr); + uint32_t nof_bytes = i == 0 ? large_pdu_size : small_pdu_size; + pdu->N_bytes = rlc1.read_pdu(pdu->msg, nof_bytes); + TESTASSERT_EQ(nof_bytes, pdu->N_bytes); + rlc_am_nr_pdu_header_t hdr; + rlc_am_nr_read_data_pdu_header(pdu.get(), rlc_am_nr_sn_size_t::size18bits, &hdr); + if (i != 1 && i != 3) { + TESTASSERT_EQ(0, hdr.p); + } else { + TESTASSERT_EQ(1, hdr.p); + } + } + return SRSRAN_SUCCESS; +} + +// Test p bit set on RETXes that cause an empty retx queue. +int poll_retx(rlc_am_nr_sn_size_t sn_size) +{ + rlc_am_tester tester; + timer_handler timers(8); + + auto& test_logger = srslog::fetch_basic_logger("TESTER "); + test_delimit_logger delimiter("poll retx test ({} bit SN)", to_number(sn_size)); + + srslog::fetch_basic_logger("RLC_AM_1").set_hex_dump_max_size(100); + + rlc_config_t rlc_cnfg = {}; + rlc_cnfg.rat = srsran_rat_t::nr; + rlc_cnfg.rlc_mode = rlc_mode_t::am; + rlc_cnfg.am_nr.tx_sn_field_length = sn_size; // Number of bits used for tx (UL) sequence number + rlc_cnfg.am_nr.rx_sn_field_length = sn_size; // Number of bits used for rx (DL) sequence number + rlc_cnfg.am_nr.poll_pdu = 4; + rlc_cnfg.am_nr.poll_byte = 3000; + rlc_cnfg.am_nr.t_status_prohibit = 8; + rlc_cnfg.am_nr.max_retx_thresh = 8; + rlc_cnfg.am_nr.t_reassembly = 35; + + rlc_am rlc1(srsran_rat_t::nr, srslog::fetch_basic_logger("RLC_AM_1"), 1, &tester, &tester, &timers); + if (not rlc1.configure(rlc_cnfg)) { + return SRSRAN_ERROR; + } + + // pollPDU == 4 + { + uint32_t num_tx_sdus = 5; + for (uint32_t i = 0; i < num_tx_sdus; ++i) { + // Write SDU + unique_byte_buffer_t sdu = srsran::make_byte_buffer(); + TESTASSERT(sdu != nullptr); + sdu->N_bytes = 1; + sdu->md.pdcp_sn = i; + rlc1.write_sdu(std::move(sdu)); + } + } + { + // Read 3 PDUs and NACK the second one + uint32_t num_tx_pdus = 3; + uint32_t pdu_size = sn_size == rlc_am_nr_sn_size_t::size12bits ? 3 : 4; + for (uint32_t i = 0; i < num_tx_pdus; ++i) { + unique_byte_buffer_t pdu = srsran::make_byte_buffer(); + TESTASSERT(pdu != nullptr); + pdu->N_bytes = rlc1.read_pdu(pdu->msg, pdu_size); + rlc_am_nr_pdu_header_t hdr; + rlc_am_nr_read_data_pdu_header(pdu.get(), sn_size, &hdr); + TESTASSERT_EQ(0, hdr.p); + } + } + { + unique_byte_buffer_t status_pdu = srsran::make_byte_buffer(); + TESTASSERT(status_pdu != nullptr); + rlc_am_nr_status_pdu_t status(rlc_am_nr_sn_size_t::size12bits); + status.ack_sn = 2; + { + rlc_status_nack_t nack; + nack.nack_sn = 1; // SN=1 needs RETX + status.push_nack(nack); + } + rlc_am_nr_write_status_pdu(status, rlc_cnfg.am_nr.tx_sn_field_length, status_pdu.get()); + rlc1.write_pdu(status_pdu->msg, status_pdu->N_bytes); + } + { + // Read 2 PDUs, + uint32_t num_tx_pdus = 3; + uint32_t pdu_size = sn_size == rlc_am_nr_sn_size_t::size12bits ? 3 : 4; + for (uint32_t i = 0; i < num_tx_pdus; ++i) { + unique_byte_buffer_t pdu = srsran::make_byte_buffer(); + TESTASSERT(pdu != nullptr); + pdu->N_bytes = rlc1.read_pdu(pdu->msg, pdu_size); + TESTASSERT_EQ(pdu_size, pdu->N_bytes); + rlc_am_nr_pdu_header_t hdr; + rlc_am_nr_read_data_pdu_header(pdu.get(), sn_size, &hdr); + if (i == 0) { + TESTASSERT_EQ(0, hdr.p); // No poll since pollPDU is not incremented for RETX + TESTASSERT_EQ(1, hdr.sn); + } else { + TESTASSERT_EQ(1, hdr.p); // poll set because of pollPDU for SN=3 and empty buffer on SN=4 + } + } + } + { + unique_byte_buffer_t status_pdu = srsran::make_byte_buffer(); + TESTASSERT(status_pdu != nullptr); + rlc_am_nr_status_pdu_t status(rlc_am_nr_sn_size_t::size12bits); + status.ack_sn = 4; + { + rlc_status_nack_t nack; + nack.nack_sn = 1; // SN=1 needs RETX + status.push_nack(nack); + } + rlc_am_nr_write_status_pdu(status, rlc_cnfg.am_nr.tx_sn_field_length, status_pdu.get()); + rlc1.write_pdu(status_pdu->msg, status_pdu->N_bytes); + } + { + // Read 1 RETX PDU. Empty retx buffer, so poll should be set + uint32_t num_tx_pdus = 1; + uint32_t pdu_size = sn_size == rlc_am_nr_sn_size_t::size12bits ? 3 : 4; + for (uint32_t i = 0; i < num_tx_pdus; ++i) { + unique_byte_buffer_t pdu = srsran::make_byte_buffer(); + TESTASSERT(pdu != nullptr); + pdu->N_bytes = rlc1.read_pdu(pdu->msg, pdu_size); + TESTASSERT_EQ(pdu_size, pdu->N_bytes); + rlc_am_nr_pdu_header_t hdr; + rlc_am_nr_read_data_pdu_header(pdu.get(), sn_size, &hdr); + if (i == 0) { + TESTASSERT_EQ(1, hdr.p); // Poll set because of empty retx buffer + TESTASSERT_EQ(1, hdr.sn); + } + } + } + return SRSRAN_SUCCESS; +} + +// This test checks whether re-transmissions are triggered correctly in case the t-PollRetranmission expires. +// It checks if the poll retx timer is re-armed upon receiving an ACK for POLL_SN +bool poll_retx_expiry(rlc_am_nr_sn_size_t sn_size) +{ + rlc_am_tester tester; + timer_handler timers(8); + + auto& test_logger = srslog::fetch_basic_logger("TESTER "); + test_delimit_logger delimiter("poll retx expiry test ({} bit SN)", to_number(sn_size)); + + srslog::fetch_basic_logger("RLC_AM_1").set_hex_dump_max_size(100); + srslog::fetch_basic_logger("RLC_AM_2").set_hex_dump_max_size(100); + + rlc_config_t rlc_cnfg = rlc_config_t::default_rlc_am_nr_config(); + + rlc_cnfg.am_nr.tx_sn_field_length = sn_size; // Number of bits used for tx (UL) sequence number + rlc_cnfg.am_nr.rx_sn_field_length = sn_size; // Number of bits used for rx (DL) sequence number + rlc_cnfg.am_nr.t_poll_retx = 65; + rlc_cnfg.am_nr.poll_pdu = -1; + rlc_cnfg.am_nr.poll_byte = -1; + rlc_cnfg.am_nr.max_retx_thresh = 6; + rlc_cnfg.am_nr.t_status_prohibit = 55; + + rlc_am rlc1(srsran_rat_t::nr, srslog::fetch_basic_logger("RLC_AM_1"), 1, &tester, &tester, &timers); + if (not rlc1.configure(rlc_cnfg)) { + return SRSRAN_ERROR; + } + + rlc_am rlc2(srsran_rat_t::nr, srslog::fetch_basic_logger("RLC_AM_2"), 1, &tester, &tester, &timers); + if (not rlc2.configure(rlc_cnfg)) { + return SRSRAN_ERROR; + } + + unsigned hdr_no_so = sn_size == rlc_am_nr_sn_size_t::size12bits ? 2 : 3; + unsigned hdr_with_so = sn_size == rlc_am_nr_sn_size_t::size12bits ? 4 : 5; + unsigned ack_size = 3; + unsigned nack_size_no_so = sn_size == rlc_am_nr_sn_size_t::size12bits ? 2 : 3; + unsigned nack_size_with_so = sn_size == rlc_am_nr_sn_size_t::size12bits ? (2 + 4) : (3 + 4); + // Tx SDU with 135 B of data + // Read it in two PDU segments, so=0 (89B of data) + // and so=89 (46B of data) + { + // TX a single SDU + unique_byte_buffer_t sdu = srsran::make_byte_buffer(); + TESTASSERT(sdu != nullptr); + sdu->N_bytes = 135; + for (uint32_t k = 0; k < sdu->N_bytes; ++k) { + sdu->msg[k] = 0; // Write the index into the buffer + } + sdu->md.pdcp_sn = 0; + rlc1.write_sdu(std::move(sdu)); + + // Read two PDUs. The last PDU should trigger polling, as it + // is the last SDU segment in the buffer. + uint32_t pdu1_size = 89 + hdr_no_so; + unique_byte_buffer_t pdu1 = srsran::make_byte_buffer(); + TESTASSERT(pdu1 != nullptr); + pdu1->N_bytes = rlc1.read_pdu(pdu1->msg, pdu1_size); // 89 bytes payload + + uint32_t pdu2_size = 46 + hdr_with_so; + unique_byte_buffer_t pdu2 = srsran::make_byte_buffer(); + TESTASSERT(pdu2 != nullptr); + pdu2->N_bytes = rlc1.read_pdu(pdu2->msg, pdu2_size); // 46 bytes payload + + // Deliver PDU2 to RLC2. PDU1 is lost + rlc2.write_pdu(pdu2->msg, pdu2->N_bytes); + + // Double-check polling status in PDUs + rlc_am_nr_pdu_header_t hdr1 = {}; + rlc_am_nr_read_data_pdu_header(pdu1.get(), sn_size, &hdr1); + rlc_am_nr_pdu_header_t hdr2 = {}; + rlc_am_nr_read_data_pdu_header(pdu2.get(), sn_size, &hdr2); + TESTASSERT_EQ(0, hdr1.p); + TESTASSERT_EQ(1, hdr2.p); + } + + // Step timers until t-PollRetransmit timer expires on RLC1 + // t-PollRetransmit will schedule SN=0, so=0, payload_len=89 for RETX + // t-Reordering timer also will expire on RLC2, meaning we will also get a status report. + TESTASSERT_EQ(false, rlc1.has_data()); + for (int cnt = 0; cnt < 65; cnt++) { + timers.step_all(); + } + + // Make sure that the SDU segment was scheduled for RETX + TESTASSERT_EQ(89 + hdr_no_so, rlc1.get_buffer_state()); + + // Further segment RETX segment + // First SDU segment (81B of data) + { + unique_byte_buffer_t pdu = srsran::make_byte_buffer(); + TESTASSERT(pdu != nullptr); + pdu->N_bytes = rlc1.read_pdu(pdu->msg, 81 + hdr_no_so); + } + // Second SDU segment (8B of data) + { + unique_byte_buffer_t pdu = srsran::make_byte_buffer(); + TESTASSERT(pdu != nullptr); + pdu->N_bytes = rlc1.read_pdu(pdu->msg, 8 + hdr_with_so); + } + TESTASSERT_EQ(0, rlc1.get_buffer_state()); + + // Read status PDU from RLC2 (triggered previously from t-Reordering) + // ACK=1, NACKs=1 + // NACK_SN[0].sn=0, NACK_SN[0].so_start=0, NACK_SN[0].so_end=89 + uint32_t status_size = rlc2.get_buffer_state(); + TESTASSERT_EQ(ack_size + nack_size_with_so, status_size); + + // Read status PDU from RLC2 + unique_byte_buffer_t status_buf = srsran::make_byte_buffer(); + TESTASSERT(status_buf != nullptr); + int len = rlc2.read_pdu(status_buf->msg, status_size); + status_buf->N_bytes = len; + + TESTASSERT(0 == rlc2.get_buffer_state()); + + // Assert status is correct + rlc_am_nr_status_pdu_t status_check(sn_size); + rlc_am_nr_read_status_pdu(status_buf.get(), sn_size, &status_check); + TESTASSERT(status_check.ack_sn == 1); // SN=1 is first SN missing without a NACK + TESTASSERT(status_check.nacks.size() == 1); // 1 PDU lost + TESTASSERT(status_check.nacks[0].nack_sn == 0); // SN=0 + TESTASSERT(status_check.nacks[0].so_start == 0); // SN=0 + TESTASSERT_EQ(88, status_check.nacks[0].so_end); // SN=0 + + TESTASSERT_EQ(0, rlc1.get_buffer_state()); + // Deliver status PDU after ReTX to RLC1. This should restart t-PollRetransmission + // It NACKs SDU segment 0:81 and 81:89 + TESTASSERT_EQ(false, rlc1.has_data()); + rlc1.write_pdu(status_buf->msg, status_buf->N_bytes); + TESTASSERT_EQ(true, rlc1.has_data()); + + // [I] SRB1 Retx SDU segment (81 B of data) + // [I] SRB1 Retx PDU segment (8 B of data) + { + unique_byte_buffer_t pdu1 = srsran::make_byte_buffer(); + TESTASSERT(pdu1 != nullptr); + pdu1->N_bytes = rlc1.read_pdu(pdu1->msg, 81 + hdr_no_so); + + unique_byte_buffer_t pdu2 = srsran::make_byte_buffer(); + TESTASSERT(pdu2 != nullptr); + pdu2->N_bytes = rlc1.read_pdu(pdu2->msg, 8 + hdr_with_so); + } + + TESTASSERT_EQ(false, rlc1.has_data()); // We don't have any more data + + // Step timers until t-PollRetransmission timer expires on RLC1 + // [I] SRB1 Schedule SN=3 for reTx + for (int cnt = 0; cnt < 66; cnt++) { + timers.step_all(); + } + TESTASSERT_EQ(81 + hdr_no_so, rlc1.get_buffer_state()); + srslog::fetch_basic_logger("TEST").info("t-PollRetransmssion successfully restarted."); + + return SRSRAN_SUCCESS; +} + int main() { // Setup the log message spy to intercept error and warning log entries from RLC @@ -908,12 +2495,26 @@ int main() // start log back-end srslog::init(); - TESTASSERT(window_checker_test() == SRSRAN_SUCCESS); - TESTASSERT(retx_segmentation_required_checker_test() == SRSRAN_SUCCESS); - TESTASSERT(basic_test() == SRSRAN_SUCCESS); - TESTASSERT(lost_pdu_test() == SRSRAN_SUCCESS); - TESTASSERT(basic_segmentation_test() == SRSRAN_SUCCESS); - TESTASSERT(segment_retx_test() == SRSRAN_SUCCESS); - TESTASSERT(retx_segment_test() == SRSRAN_SUCCESS); + std::initializer_list sn_sizes = {rlc_am_nr_sn_size_t::size12bits, + rlc_am_nr_sn_size_t::size18bits}; + for (auto sn_size : sn_sizes) { + TESTASSERT(window_checker_test(sn_size) == SRSRAN_SUCCESS); + TESTASSERT(retx_segmentation_required_checker_test(sn_size) == SRSRAN_SUCCESS); + TESTASSERT(basic_test(sn_size) == SRSRAN_SUCCESS); + TESTASSERT(lost_pdu_test(sn_size) == SRSRAN_SUCCESS); + TESTASSERT(lost_pdu_duplicated_nack_test(sn_size) == SRSRAN_SUCCESS); + TESTASSERT(lost_pdus_trimmed_nack_test(sn_size) == SRSRAN_SUCCESS); + TESTASSERT(basic_segmentation_test(sn_size) == SRSRAN_SUCCESS); + TESTASSERT(segment_retx_test(sn_size) == SRSRAN_SUCCESS); + TESTASSERT(segment_retx_and_loose_segments_test(sn_size) == SRSRAN_SUCCESS); + TESTASSERT(retx_segment_test(sn_size) == SRSRAN_SUCCESS); + TESTASSERT(max_retx_lost_sdu_test(sn_size) == SRSRAN_SUCCESS); + TESTASSERT(max_retx_lost_segments_test(sn_size) == SRSRAN_SUCCESS); + TESTASSERT(discard_test(sn_size) == SRSRAN_SUCCESS); + TESTASSERT(poll_pdu(sn_size) == SRSRAN_SUCCESS); + TESTASSERT(poll_byte(sn_size) == SRSRAN_SUCCESS); + TESTASSERT(poll_retx(sn_size) == SRSRAN_SUCCESS); + TESTASSERT(poll_retx_expiry(sn_size) == SRSRAN_SUCCESS); + } return SRSRAN_SUCCESS; } diff --git a/lib/test/rlc/rlc_stress_test.cc b/lib/test/rlc/rlc_stress_test.cc index 911073ca5..66337acee 100644 --- a/lib/test/rlc/rlc_stress_test.cc +++ b/lib/test/rlc/rlc_stress_test.cc @@ -19,6 +19,7 @@ * */ +#include "rlc_stress_test.h" #include "srsran/common/block_queue.h" #include "srsran/common/crash_handler.h" #include "srsran/common/rlc_pcap.h" @@ -34,458 +35,244 @@ #include #include -#define LOG_HEX_LIMIT (-1) - -#define PCAP_CRNTI (0x1001) -#define PCAP_TTI (666) - #include "srsran/common/mac_pcap.h" #include "srsran/mac/mac_sch_pdu_nr.h" -static std::unique_ptr pcap_handle = nullptr; -int write_pdu_to_pcap(const bool is_dl, const uint32_t lcid, const uint8_t* payload, const uint32_t len) +static std::unique_ptr pcap_handle = nullptr; +/*********************** + * MAC tester class + ***********************/ +void mac_dummy::run_thread() { - if (pcap_handle) { - srsran::byte_buffer_t tx_buffer; - srsran::mac_sch_pdu_nr tx_pdu; - tx_pdu.init_tx(&tx_buffer, len + 10); - tx_pdu.add_sdu(lcid, payload, len); - tx_pdu.pack(); - if (is_dl) { - pcap_handle->write_dl_crnti_nr(tx_buffer.msg, tx_buffer.N_bytes, PCAP_CRNTI, true, PCAP_TTI); - } else { - pcap_handle->write_ul_crnti_nr(tx_buffer.msg, tx_buffer.N_bytes, PCAP_CRNTI, true, PCAP_TTI); - } + srsran::move_task_t task; + while (run_enable) { + // Downlink direction first (RLC1->RLC2) + run_tti(rlc1, rlc2, true); - return SRSRAN_SUCCESS; - } - return SRSRAN_ERROR; -} + // UL direction (RLC2->RLC1) + run_tti(rlc2, rlc1, false); -using namespace std; -using namespace srsue; -using namespace srsran; -namespace bpo = boost::program_options; - -#define MIN_SDU_SIZE (5) -#define MAX_SDU_SIZE (1500) - -typedef struct { - std::string rat; - std::string mode; - int32_t sdu_size; - uint32_t test_duration_sec; - float pdu_drop_rate; - float pdu_cut_rate; - float pdu_duplicate_rate; - uint32_t sdu_gen_delay_usec; - uint32_t pdu_tx_delay_usec; - uint32_t log_level; - bool single_tx; - bool write_pcap; - uint32_t avg_opp_size; - bool random_opp; - bool zero_seed; - uint32_t nof_pdu_tti; - uint32_t max_retx; -} stress_test_args_t; - -void parse_args(stress_test_args_t* args, int argc, char* argv[]) -{ - // Command line only options - bpo::options_description general("General options"); - - general.add_options()("help,h", "Produce help message")("version,v", "Print version information and exit"); - - // clang-format off - - // Command line or config file options - bpo::options_description common("Configuration options"); - common.add_options() - ("rat", bpo::value(&args->rat)->default_value("LTE"), "The RLC version to use (LTE/NR)") - ("mode", bpo::value(&args->mode)->default_value("AM"), "Whether to test RLC acknowledged or unacknowledged mode (AM/UM for LTE) (UM6/UM12 for NR)") - ("duration", bpo::value(&args->test_duration_sec)->default_value(5), "Duration (sec)") - ("sdu_size", bpo::value(&args->sdu_size)->default_value(-1), "Size of SDUs (-1 means random)") - ("random_opp", bpo::value(&args->random_opp)->default_value(true), "Whether to generate random MAC opportunities") - ("avg_opp_size", bpo::value(&args->avg_opp_size)->default_value(1505), "Size of the MAC opportunity (if not random)") - ("sdu_gen_delay", bpo::value(&args->sdu_gen_delay_usec)->default_value(0), "SDU generation delay (usec)") - ("pdu_tx_delay", bpo::value(&args->pdu_tx_delay_usec)->default_value(0), "Delay in MAC for transfering PDU from tx'ing RLC to rx'ing RLC (usec)") - ("pdu_drop_rate", bpo::value(&args->pdu_drop_rate)->default_value(0.1), "Rate at which RLC PDUs are dropped") - ("pdu_cut_rate", bpo::value(&args->pdu_cut_rate)->default_value(0.0), "Rate at which RLC PDUs are chopped in length") - ("pdu_duplicate_rate", bpo::value(&args->pdu_duplicate_rate)->default_value(0.0), "Rate at which RLC PDUs are duplicated") - ("loglevel", bpo::value(&args->log_level)->default_value((int)srslog::basic_levels::debug), "Log level (1=Error,2=Warning,3=Info,4=Debug)") - ("singletx", bpo::value(&args->single_tx)->default_value(false), "If set to true, only one node is generating data") - ("pcap", bpo::value(&args->write_pcap)->default_value(false), "Whether to write all RLC PDU to PCAP file") - ("zeroseed", bpo::value(&args->zero_seed)->default_value(false), "Whether to initialize random seed to zero") - ("max_retx", bpo::value(&args->max_retx)->default_value(32), "Maximum number of RLC retransmission attempts") - ("nof_pdu_tti", bpo::value(&args->nof_pdu_tti)->default_value(1), "Number of PDUs processed in a TTI"); - // clang-format on - - // these options are allowed on the command line - bpo::options_description cmdline_options; - cmdline_options.add(common).add(general); - - // parse the command line and store result in vm - bpo::variables_map vm; - bpo::store(bpo::command_line_parser(argc, argv).options(cmdline_options).run(), vm); - bpo::notify(vm); - - // help option was given - print usage and exit - if (vm.count("help") > 0) { - cout << "Usage: " << argv[0] << " [OPTIONS] config_file" << endl << endl; - cout << common << endl << general << endl; - exit(0); - } + // step timer + timers->step_all(); - if (args->log_level > 4) { - args->log_level = 4; - printf("Set log level to %d (%s)\n", - args->log_level, - srslog::basic_level_to_string(static_cast(args->log_level))); + if (pending_tasks.try_pop(&task)) { + task(); + } } - - // convert mode to upper case - for (auto& c : args->mode) { - c = toupper(c); + if (pending_tasks.try_pop(&task)) { + task(); } } -class mac_dummy : public srsran::thread +void mac_dummy::run_tti(srsue::rlc_interface_mac* tx_rlc, srsue::rlc_interface_mac* rx_rlc, bool is_dl) { -public: - mac_dummy(rlc_interface_mac* rlc1_, - rlc_interface_mac* rlc2_, - stress_test_args_t args_, - uint32_t lcid_, - timer_handler* timers_, - rlc_pcap* pcap_, - uint32_t seed_) : - run_enable(true), - rlc1(rlc1_), - rlc2(rlc2_), - args(args_), - pcap(pcap_), - lcid(lcid_), - timers(timers_), - logger(srslog::fetch_basic_logger("MAC", false)), - thread("MAC_DUMMY"), - real_dist(0.0, 1.0), - mt19937(seed_) - { - logger.set_level(static_cast(args.log_level)); - logger.set_hex_dump_max_size(LOG_HEX_LIMIT); - } + std::vector pdu_list; - void stop() - { - run_enable = false; - wait_thread_finish(); - } - - void enqueue_task(srsran::move_task_t task) { pending_tasks.push(std::move(task)); } - -private: - void run_tx_tti(rlc_interface_mac* tx_rlc, rlc_interface_mac* rx_rlc, std::vector& pdu_list) - { - // Generate A number of MAC PDUs - for (uint32_t i = 0; i < args.nof_pdu_tti; i++) { - // Create PDU unique buffer - unique_byte_buffer_t pdu = srsran::make_byte_buffer(); - if (!pdu) { - printf("Fatal Error: Could not allocate PDU in mac_reader::run_thread\n"); - exit(-1); - } + // Run Tx + run_tx_tti(tx_rlc, rx_rlc, pdu_list); - // Get MAC PDU size - float factor = 1.0f; - if (args.random_opp) { - factor = 0.5f + real_dist(mt19937); - } - int opp_size = static_cast(args.avg_opp_size * factor); + // Reverse PDUs + std::reverse(pdu_list.begin(), pdu_list.end()); - // Request data to transmit - uint32_t buf_state = tx_rlc->get_buffer_state(lcid); - if (buf_state > 0) { - pdu->N_bytes = tx_rlc->read_pdu(lcid, pdu->msg, opp_size); + // Run Rx + run_rx_tti(tx_rlc, rx_rlc, is_dl, pdu_list); +} - // Push PDU in the list - pdu_list.push_back(std::move(pdu)); - } +void mac_dummy::run_tx_tti(srsue::rlc_interface_mac* tx_rlc, + srsue::rlc_interface_mac* rx_rlc, + std::vector& pdu_list) +{ + // Generate A number of MAC PDUs + for (uint32_t i = 0; i < args.nof_pdu_tti; i++) { + // Create PDU unique buffer + srsran::unique_byte_buffer_t pdu = srsran::make_byte_buffer(); + if (!pdu) { + printf("Fatal Error: Could not allocate PDU in %s\n", __FUNCTION__); + exit(-1); } - } - void run_rx_tti(rlc_interface_mac* tx_rlc, - rlc_interface_mac* rx_rlc, - bool is_dl, - std::vector& pdu_list) - { - // Sleep if necessary - if (args.pdu_tx_delay_usec > 0) { - std::this_thread::sleep_for(std::chrono::microseconds(args.pdu_tx_delay_usec)); + // Get MAC PDU size + float factor = 1.0f; + if (args.random_opp) { + factor = 0.5f + real_dist(mt19937); } + int opp_size = static_cast(args.avg_opp_size * factor); - auto it = pdu_list.begin(); // PDU iterator - bool skip_action = false; // Avoid discarding a duplicated or duplicating a discarded - - while (it != pdu_list.end()) { - // Get PDU unique buffer - unique_byte_buffer_t& pdu = *it; - - // Drop - float rnd = real_dist(mt19937); - if (std::isnan(rnd) || (((rnd > args.pdu_drop_rate) || skip_action) && pdu->N_bytes > 0)) { - uint32_t pdu_len = pdu->N_bytes; - - // Cut - if ((real_dist(mt19937) < args.pdu_cut_rate)) { - int cut_pdu_len = static_cast(pdu_len * real_dist(mt19937)); - logger.info("Cutting MAC PDU len (%d B -> %d B)", pdu_len, cut_pdu_len); - pdu_len = cut_pdu_len; - } - - // Write PDU in RX - rx_rlc->write_pdu(lcid, pdu->msg, pdu_len); - - // Write PCAP - write_pdu_to_pcap(is_dl, 4, pdu->msg, pdu_len); // Only handles NR rat - if (is_dl) { - pcap->write_dl_ccch(pdu->msg, pdu_len); - } else { - pcap->write_ul_ccch(pdu->msg, pdu_len); - } - } else { - logger.info(pdu->msg, pdu->N_bytes, "Dropping RLC PDU (%d B)", pdu->N_bytes); - skip_action = true; // Avoid drop duplicating this PDU - } + // Request data to transmit + uint32_t buf_state = tx_rlc->get_buffer_state(lcid); + if (buf_state > 0) { + pdu->N_bytes = tx_rlc->read_pdu(lcid, pdu->msg, opp_size); - // Duplicate - if (real_dist(mt19937) > args.pdu_duplicate_rate || skip_action) { - it++; - skip_action = false; // Allow action on the next PDU - } else { - logger.info(pdu->msg, pdu->N_bytes, "Duplicating RLC PDU (%d B)", pdu->N_bytes); - skip_action = true; // Avoid drop of this PDU - } + // Push PDU in the list + pdu_list.push_back(std::move(pdu)); } } +} - void run_tti(rlc_interface_mac* tx_rlc, rlc_interface_mac* rx_rlc, bool is_dl) - { - std::vector pdu_list; - - // Run Tx - run_tx_tti(tx_rlc, rx_rlc, pdu_list); +void mac_dummy::run_rx_tti(srsue::rlc_interface_mac* tx_rlc, + srsue::rlc_interface_mac* rx_rlc, + bool is_dl, + std::vector& pdu_list) +{ + // Sleep if necessary + if (args.pdu_tx_delay_usec > 0) { + std::this_thread::sleep_for(std::chrono::microseconds(args.pdu_tx_delay_usec)); + } - // Reverse PDUs - std::reverse(pdu_list.begin(), pdu_list.end()); + auto it = pdu_list.begin(); // PDU iterator + bool skip_action = false; // Avoid discarding a duplicated or duplicating a discarded - // Run Rx - run_rx_tti(tx_rlc, rx_rlc, is_dl, pdu_list); - } + while (it != pdu_list.end()) { + // Get PDU unique buffer + srsran::unique_byte_buffer_t& pdu = *it; - void run_thread() override - { - srsran::move_task_t task; - while (run_enable) { - // Downlink direction first (RLC1->RLC2) - run_tti(rlc1, rlc2, true); + // Drop + float rnd = real_dist(mt19937); + if (std::isnan(rnd) || (((rnd > args.pdu_drop_rate) || skip_action) && pdu->N_bytes > 0)) { + uint32_t pdu_len = pdu->N_bytes; - // UL direction (RLC2->RLC1) - run_tti(rlc2, rlc1, false); + // Cut + if ((real_dist(mt19937) < args.pdu_cut_rate)) { + int cut_pdu_len = static_cast(pdu_len * real_dist(mt19937)); + logger.info("Cutting MAC PDU len (%d B -> %d B)", pdu_len, cut_pdu_len); + pdu_len = cut_pdu_len; + } - // step timer - timers->step_all(); + // Write PDU in RX + rx_rlc->write_pdu(lcid, pdu->msg, pdu_len); - if (pending_tasks.try_pop(&task)) { - task(); + // Write PCAP + write_pdu_to_pcap(pcap_handle, is_dl, 4, pdu->msg, pdu_len); // Only handles NR rat + if (is_dl) { + pcap->write_dl_ccch(pdu->msg, pdu_len); + } else { + pcap->write_ul_ccch(pdu->msg, pdu_len); } + } else { + logger.info(pdu->msg, pdu->N_bytes, "Dropping RLC PDU (%d B)", pdu->N_bytes); + skip_action = true; // Avoid drop duplicating this PDU } - if (pending_tasks.try_pop(&task)) { - task(); + + // Duplicate + if (real_dist(mt19937) > args.pdu_duplicate_rate || skip_action) { + it++; + skip_action = false; // Allow action on the next PDU + } else { + logger.info(pdu->msg, pdu->N_bytes, "Duplicating RLC PDU (%d B)", pdu->N_bytes); + skip_action = true; // Avoid drop of this PDU } } +} - rlc_interface_mac* rlc1 = nullptr; - rlc_interface_mac* rlc2 = nullptr; - - std::atomic run_enable = {false}; - stress_test_args_t args = {}; - rlc_pcap* pcap = nullptr; - uint32_t lcid = 0; - srslog::basic_logger& logger; - srsran::timer_handler* timers = nullptr; - - srsran::block_queue pending_tasks; - - std::mt19937 mt19937; - std::uniform_real_distribution real_dist; -}; - -class rlc_tester : public pdcp_interface_rlc, public rrc_interface_rlc, public srsran::thread +/*********************** + * RLC tester class + ***********************/ +// PDCP interface +void rlc_tester::write_pdu(uint32_t rx_lcid, srsran::unique_byte_buffer_t sdu) { -public: - rlc_tester(rlc_interface_pdcp* rlc_pdcp_, - std::string name_, - stress_test_args_t args_, - uint32_t lcid_, - uint32_t seed_) : - logger(srslog::fetch_basic_logger(name_.c_str(), false)), - rlc_pdcp(rlc_pdcp_), - name(name_), - args(args_), - lcid(lcid_), - thread("RLC_TESTER"), - int_dist(MIN_SDU_SIZE, MAX_SDU_SIZE), - mt19937(seed_) - { - logger.set_level(srslog::basic_levels::error); - logger.set_hex_dump_max_size(LOG_HEX_LIMIT); + assert(rx_lcid == lcid); + if (args.mode != "AM") { + // Only AM will guarantee to deliver SDUs, take first byte as reference for other modes + next_expected_sdu = sdu->msg[0]; } - void stop() - { - run_enable = false; - wait_thread_finish(); + // check SDU content (consider faster alternative) + for (uint32_t i = 0; i < sdu->N_bytes; ++i) { + if (sdu->msg[i] != next_expected_sdu) { + logger.error(sdu->msg, + sdu->N_bytes, + "Received malformed SDU with size %d, expected data 0x%X", + sdu->N_bytes, + next_expected_sdu); + fprintf(stderr, "Received malformed SDU with size %d, expected data 0x%X\n", sdu->N_bytes, next_expected_sdu); + fprintf(stdout, "Received malformed SDU with size %d, expected data 0x%X\n", sdu->N_bytes, next_expected_sdu); + + std::this_thread::sleep_for(std::chrono::seconds(1)); // give some time to flush logs + exit(-1); + } } + next_expected_sdu += 1; + rx_pdus++; +} - // PDCP interface - void write_pdu(uint32_t rx_lcid, unique_byte_buffer_t sdu) - { - assert(rx_lcid == lcid); - if (args.mode != "AM") { - // Only AM will guarantee to deliver SDUs, take first byte as reference for other modes - next_expected_sdu = sdu->msg[0]; +void rlc_tester::run_thread() +{ + uint32_t pdcp_sn = 0; + uint32_t sdu_size = 0; + uint8_t payload = 0x0; // increment for each SDU + while (run_enable) { + // SDU queue is full, don't assign PDCP SN + if (rlc_pdcp->sdu_queue_is_full(lcid)) { + continue; } - // check SDU content (consider faster alternative) - for (uint32_t i = 0; i < sdu->N_bytes; ++i) { - if (sdu->msg[i] != next_expected_sdu) { - logger.error(sdu->msg, - sdu->N_bytes, - "Received malformed SDU with size %d, expected data 0x%X", - sdu->N_bytes, - next_expected_sdu); - fprintf(stderr, "Received malformed SDU with size %d\n", sdu->N_bytes); - fprintf(stdout, "Received malformed SDU with size %d\n", sdu->N_bytes); - std::this_thread::sleep_for(std::chrono::seconds(1)); // give some time to flush logs - exit(-1); - } + srsran::unique_byte_buffer_t pdu = srsran::make_byte_buffer(); + if (pdu == nullptr) { + printf("Error: Could not allocate PDU in rlc_tester::run_thread\n\n\n"); + // backoff for a bit + std::this_thread::sleep_for(std::chrono::milliseconds(10)); + continue; } - next_expected_sdu += 1; - rx_pdus++; - } - void write_pdu_bcch_bch(unique_byte_buffer_t sdu) {} - void write_pdu_bcch_dlsch(unique_byte_buffer_t sdu) {} - void write_pdu_pcch(unique_byte_buffer_t sdu) {} - void write_pdu_mch(uint32_t lcid_, srsran::unique_byte_buffer_t sdu) {} - void notify_delivery(uint32_t lcid_, const srsran::pdcp_sn_vector_t& pdcp_sns) {} - void notify_failure(uint32_t lcid_, const srsran::pdcp_sn_vector_t& pdcp_sns) {} - - // RRC interface - void max_retx_attempted() - { - logger.error( - "Maximum number of RLC retransmission reached. Consider increasing threshold or lowering channel drop rate."); - std::this_thread::sleep_for(std::chrono::seconds(1)); - exit(1); - } - void protocol_failure() - { - logger.error("RLC protocol error detected."); - std::this_thread::sleep_for(std::chrono::seconds(1)); - exit(1); - } - const char* get_rb_name(uint32_t rx_lcid) { return "DRB1"; } - - int get_nof_rx_pdus() { return rx_pdus; } - -private: - const static size_t max_pdcp_sn = 262143u; // 18bit SN - void run_thread() - { - uint32_t pdcp_sn = 0; - uint32_t sdu_size = 0; - uint8_t payload = 0x0; // increment for each SDU - while (run_enable) { - // SDU queue is full, don't assign PDCP SN - if (rlc_pdcp->sdu_queue_is_full(lcid)) { - continue; - } - - unique_byte_buffer_t pdu = srsran::make_byte_buffer(); - if (pdu == NULL) { - printf("Error: Could not allocate PDU in rlc_tester::run_thread\n\n\n"); - // backoff for a bit - std::this_thread::sleep_for(std::chrono::milliseconds(1)); - continue; - } - pdu->md.pdcp_sn = pdcp_sn; + pdu->md.pdcp_sn = pdcp_sn; - // random or fixed SDU size - if (args.sdu_size < 1) { - sdu_size = int_dist(mt19937); - } else { - sdu_size = args.sdu_size; - } + // random or fixed SDU size + if (args.sdu_size < 1) { + sdu_size = int_dist(mt19937); + } else { + sdu_size = args.sdu_size; + } - for (uint32_t i = 0; i < sdu_size; i++) { - pdu->msg[i] = payload; - } - pdu->N_bytes = sdu_size; - payload++; + for (uint32_t i = 0; i < sdu_size; i++) { + pdu->msg[i] = payload; + } + pdu->N_bytes = sdu_size; + payload++; - rlc_pdcp->write_sdu(lcid, std::move(pdu)); - pdcp_sn = (pdcp_sn + 1) % max_pdcp_sn; - if (args.sdu_gen_delay_usec > 0) { - std::this_thread::sleep_for(std::chrono::microseconds(args.sdu_gen_delay_usec)); - } + rlc_pdcp->write_sdu(lcid, std::move(pdu)); + pdcp_sn = (pdcp_sn + 1) % max_pdcp_sn; + if (args.sdu_gen_delay_usec > 0) { + std::this_thread::sleep_for(std::chrono::microseconds(args.sdu_gen_delay_usec)); } } - - std::atomic run_enable = {true}; - - /// Tx uses thread-local PDCP SN to set SDU content, the Rx uses this variable to check received SDUs - uint8_t next_expected_sdu = 0; - uint64_t rx_pdus = 0; - uint32_t lcid = 0; - srslog::basic_logger& logger; - - std::string name; - - stress_test_args_t args = {}; - - rlc_interface_pdcp* rlc_pdcp = nullptr; // used by run_thread to push PDCP SDUs to RLC - - std::mt19937 mt19937; - std::uniform_int_distribution<> int_dist; -}; +} void stress_test(stress_test_args_t args) { + auto log_sink = + (args.log_filename == "stdout") ? srslog::create_stdout_sink() : srslog::create_file_sink(args.log_filename); + if (!log_sink) { + return; + } + srslog::log_channel* chan = srslog::create_log_channel("main_channel", *log_sink); + if (!chan) { + return; + } + srslog::set_default_sink(*log_sink); + auto& log1 = srslog::fetch_basic_logger("RLC_1", false); log1.set_level(static_cast(args.log_level)); - log1.set_hex_dump_max_size(LOG_HEX_LIMIT); + log1.set_hex_dump_max_size(args.log_hex_limit); auto& log2 = srslog::fetch_basic_logger("RLC_2", false); log2.set_level(static_cast(args.log_level)); - log2.set_hex_dump_max_size(LOG_HEX_LIMIT); + log2.set_hex_dump_max_size(args.log_hex_limit); - rlc_pcap pcap; - uint32_t lcid = 1; + srsran::rlc_pcap pcap; + uint32_t lcid = 1; - rlc_config_t cnfg_ = {}; + srsran::rlc_config_t cnfg_ = {}; if (args.rat == "LTE") { if (args.mode == "AM") { // config RLC AM bearer - cnfg_ = rlc_config_t::default_rlc_am_config(); + cnfg_ = srsran::rlc_config_t::default_rlc_am_config(); cnfg_.am.max_retx_thresh = args.max_retx; } else if (args.mode == "UM") { // config UM bearer - cnfg_ = rlc_config_t::default_rlc_um_config(); + cnfg_ = srsran::rlc_config_t::default_rlc_um_config(); } else if (args.mode == "TM") { // use default LCID in TM lcid = 0; } else { - cout << "Unsupported RLC mode " << args.mode << ", exiting." << endl; + std::cout << "Unsupported RLC mode " << args.mode << ", exiting." << std::endl; exit(-1); } @@ -494,13 +281,17 @@ void stress_test(stress_test_args_t args) } } else if (args.rat == "NR") { if (args.mode == "UM6") { - cnfg_ = rlc_config_t::default_rlc_um_nr_config(6); + cnfg_ = srsran::rlc_config_t::default_rlc_um_nr_config(6); } else if (args.mode == "UM12") { - cnfg_ = rlc_config_t::default_rlc_um_nr_config(12); + cnfg_ = srsran::rlc_config_t::default_rlc_um_nr_config(12); } else if (args.mode == "AM12") { - cnfg_ = rlc_config_t::default_rlc_am_nr_config(); + cnfg_ = srsran::rlc_config_t::default_rlc_am_nr_config(12); + cnfg_.am_nr.max_retx_thresh = args.max_retx; + } else if (args.mode == "AM18") { + cnfg_ = srsran::rlc_config_t::default_rlc_am_nr_config(18); + cnfg_.am_nr.max_retx_thresh = args.max_retx; } else { - cout << "Unsupported RLC mode " << args.mode << ", exiting." << endl; + std::cout << "Unsupported RLC mode " << args.mode << ", exiting." << std::endl; exit(-1); } @@ -509,7 +300,7 @@ void stress_test(stress_test_args_t args) pcap_handle->open("rlc_stress_test_nr.pcap"); } } else { - cout << "Unsupported RAT mode " << args.rat << ", exiting." << endl; + std::cout << "Unsupported RAT mode " << args.rat << ", exiting." << std::endl; exit(-1); } @@ -522,8 +313,8 @@ void stress_test(stress_test_args_t args) srsran::timer_handler timers(8); - rlc rlc1(log1.id().c_str()); - rlc rlc2(log2.id().c_str()); + srsran::rlc rlc1(log1.id().c_str()); + srsran::rlc rlc2(log2.id().c_str()); rlc_tester tester1(&rlc1, "tester1", args, lcid, seed); rlc_tester tester2(&rlc2, "tester2", args, lcid, seed); @@ -538,7 +329,7 @@ void stress_test(stress_test_args_t args) rlc2.add_bearer(lcid, cnfg_); } - printf("Starting test ..\n"); + printf("Starting test ... Seed: %u\n", seed); tester1.start(7); if (!args.single_tx) { @@ -549,6 +340,8 @@ void stress_test(stress_test_args_t args) // wait until test is over std::this_thread::sleep_for(std::chrono::seconds(args.test_duration_sec)); + srslog::flush(); + fflush(stdout); printf("Test finished, tearing down ..\n"); // Stop RLC instances first to release blocking writers @@ -558,7 +351,6 @@ void stress_test(stress_test_args_t args) }); printf("RLC entities stopped.\n"); - // Stop upper layer writers tester1.stop(); tester2.stop(); @@ -570,10 +362,10 @@ void stress_test(stress_test_args_t args) pcap.close(); } - rlc_metrics_t metrics = {}; + srsran::rlc_metrics_t metrics = {}; rlc1.get_metrics(metrics, 1); - printf("RLC1 received %d SDUs in %ds (%.2f/s), Tx=%" PRIu64 " B, Rx=%" PRIu64 " B\n", + printf("RLC1 received %" PRIu64 " SDUs in %ds (%.2f/s), Tx=%" PRIu64 " B, Rx=%" PRIu64 " B\n", tester1.get_nof_rx_pdus(), args.test_duration_sec, static_cast(tester1.get_nof_rx_pdus() / args.test_duration_sec), @@ -582,7 +374,7 @@ void stress_test(stress_test_args_t args) rlc_bearer_metrics_print(metrics.bearer[lcid]); rlc2.get_metrics(metrics, 1); - printf("RLC2 received %d SDUs in %ds (%.2f/s), Tx=%" PRIu64 " B, Rx=%" PRIu64 " B\n", + printf("RLC2 received %" PRIu64 " SDUs in %ds (%.2f/s), Tx=%" PRIu64 " B, Rx=%" PRIu64 " B\n", tester2.get_nof_rx_pdus(), args.test_duration_sec, static_cast(tester2.get_nof_rx_pdus() / args.test_duration_sec), diff --git a/lib/test/rlc/rlc_stress_test.h b/lib/test/rlc/rlc_stress_test.h new file mode 100644 index 000000000..da824c02d --- /dev/null +++ b/lib/test/rlc/rlc_stress_test.h @@ -0,0 +1,288 @@ +/** + * + * \section COPYRIGHT + * + * Copyright 2013-2021 Software Radio Systems Limited + * + * By using this file, you agree to the terms and conditions set + * forth in the LICENSE file which can be found at the top level of + * the distribution. + * + */ + +#include "srsran/common/block_queue.h" +#include "srsran/common/crash_handler.h" +#include "srsran/common/rlc_pcap.h" +#include "srsran/common/test_common.h" +#include "srsran/common/threads.h" +#include "srsran/common/tsan_options.h" +#include "srsran/rlc/rlc.h" +#include +#include +#include +#include +#include +#include +#include + +#include "srsran/common/mac_pcap.h" +#include "srsran/mac/mac_sch_pdu_nr.h" + +inline int write_pdu_to_pcap(const std::unique_ptr& pcap_handle, + const bool is_dl, + const uint32_t lcid, + const uint8_t* payload, + const uint32_t len) +{ + const uint32_t PCAP_CRNTI = 0x1001; + const uint32_t PCAP_TTI = 666; + if (pcap_handle) { + srsran::byte_buffer_t tx_buffer; + srsran::mac_sch_pdu_nr tx_pdu; + tx_pdu.init_tx(&tx_buffer, len + 10); + tx_pdu.add_sdu(lcid, payload, len); + tx_pdu.pack(); + if (is_dl) { + pcap_handle->write_dl_crnti_nr(tx_buffer.msg, tx_buffer.N_bytes, PCAP_CRNTI, true, PCAP_TTI); + } else { + pcap_handle->write_ul_crnti_nr(tx_buffer.msg, tx_buffer.N_bytes, PCAP_CRNTI, true, PCAP_TTI); + } + + return SRSRAN_SUCCESS; + } + return SRSRAN_ERROR; +} + +typedef struct { + std::string rat; + std::string mode; + int32_t sdu_size; + uint32_t test_duration_sec; + float pdu_drop_rate; + float pdu_cut_rate; + float pdu_duplicate_rate; + uint32_t sdu_gen_delay_usec; + uint32_t pdu_tx_delay_usec; + uint32_t log_level; + bool single_tx; + bool write_pcap; + uint32_t avg_opp_size; + bool random_opp; + bool zero_seed; + uint32_t seed; + uint32_t nof_pdu_tti; + uint32_t max_retx; + int32_t log_hex_limit; + std::string log_filename; + uint32_t min_sdu_size; + uint32_t max_sdu_size; +} stress_test_args_t; + +void parse_args(stress_test_args_t* args, int argc, char* argv[]) +{ + namespace bpo = boost::program_options; + // Command line only options + bpo::options_description general("General options"); + + general.add_options()("help,h", "Produce help message")("version,v", "Print version information and exit"); + + // clang-format off + + // Command line or config file options + bpo::options_description common("Configuration options"); + common.add_options() + ("rat", bpo::value(&args->rat)->default_value("LTE"), "The RLC version to use (LTE/NR)") + ("mode", bpo::value(&args->mode)->default_value("AM"), "Whether to test RLC acknowledged or unacknowledged mode (AM/UM for LTE) (UM6/UM12/AM12/AM18 for NR)") + ("duration", bpo::value(&args->test_duration_sec)->default_value(5), "Duration (sec)") + ("sdu_size", bpo::value(&args->sdu_size)->default_value(-1), "Size of SDUs (-1 means random)") + ("random_opp", bpo::value(&args->random_opp)->default_value(true), "Whether to generate random MAC opportunities") + ("avg_opp_size", bpo::value(&args->avg_opp_size)->default_value(1505), "Size of the MAC opportunity (if not random)") + ("sdu_gen_delay", bpo::value(&args->sdu_gen_delay_usec)->default_value(0), "SDU generation delay (usec)") + ("pdu_tx_delay", bpo::value(&args->pdu_tx_delay_usec)->default_value(0), "Delay in MAC for transfering PDU from tx'ing RLC to rx'ing RLC (usec)") + ("pdu_drop_rate", bpo::value(&args->pdu_drop_rate)->default_value(0.1), "Rate at which RLC PDUs are dropped") + ("pdu_cut_rate", bpo::value(&args->pdu_cut_rate)->default_value(0.0), "Rate at which RLC PDUs are chopped in length") + ("pdu_duplicate_rate", bpo::value(&args->pdu_duplicate_rate)->default_value(0.0), "Rate at which RLC PDUs are duplicated") + ("loglevel", bpo::value(&args->log_level)->default_value((int)srslog::basic_levels::debug), "Log level (1=Error,2=Warning,3=Info,4=Debug)") + ("log_filename", bpo::value(&args->log_filename)->default_value("stdout"), "Filename to save log to") + ("singletx", bpo::value(&args->single_tx)->default_value(false), "If set to true, only one node is generating data") + ("pcap", bpo::value(&args->write_pcap)->default_value(false), "Whether to write all RLC PDU to PCAP file") + ("zeroseed", bpo::value(&args->zero_seed)->default_value(false), "Whether to initialize random seed to zero") + ("seed", bpo::value(&args->seed)->default_value(0), "Seed to use in run. 0 means the seed is randomly generated") + ("max_retx", bpo::value(&args->max_retx)->default_value(32), "Maximum number of RLC retransmission attempts") + ("nof_pdu_tti", bpo::value(&args->nof_pdu_tti)->default_value(1), "Number of PDUs processed in a TTI") + ("log_hex_limit", bpo::value(&args->log_hex_limit)->default_value(-1), "Maximum bytes in hex log") + ("min_sdu_size", bpo::value(&args->min_sdu_size)->default_value(5), "Minimum SDU size") + ("max_sdu_size", bpo::value(&args->max_sdu_size)->default_value(1500), "Maximum SDU size"); + // clang-format on + + // these options are allowed on the command line + bpo::options_description cmdline_options; + cmdline_options.add(common).add(general); + + // parse the command line and store result in vm + bpo::variables_map vm; + bpo::store(bpo::command_line_parser(argc, argv).options(cmdline_options).run(), vm); + bpo::notify(vm); + + // help option was given - print usage and exit + if (vm.count("help") > 0) { + std::cout << "Usage: " << argv[0] << " [OPTIONS] config_file" << std::endl << std::endl; + std::cout << common << std::endl << general << std::endl; + exit(0); + } + + if (args->log_level > 4) { + args->log_level = 4; + printf("Set log level to %d (%s)\n", + args->log_level, + srslog::basic_level_to_string(static_cast(args->log_level))); + } + + // convert mode to upper case + for (auto& c : args->mode) { + c = toupper(c); + } +} + +class mac_dummy : public srsran::thread +{ +public: + mac_dummy(srsue::rlc_interface_mac* rlc1_, + srsue::rlc_interface_mac* rlc2_, + stress_test_args_t args_, + uint32_t lcid_, + srsran::timer_handler* timers_, + srsran::rlc_pcap* pcap_, + uint32_t seed_) : + run_enable(true), + rlc1(rlc1_), + rlc2(rlc2_), + args(args_), + pcap(pcap_), + lcid(lcid_), + timers(timers_), + logger(srslog::fetch_basic_logger("MAC", false)), + thread("MAC_DUMMY"), + real_dist(0.0, 1.0), + mt19937(seed_) + { + logger.set_level(static_cast(args.log_level)); + logger.set_hex_dump_max_size(args.log_hex_limit); + } + + void stop() + { + run_enable = false; + wait_thread_finish(); + } + + void enqueue_task(srsran::move_task_t task) { pending_tasks.push(std::move(task)); } + +private: + void run_thread() final; + + void run_tti(srsue::rlc_interface_mac* tx_rlc, srsue::rlc_interface_mac* rx_rlc, bool is_dl); + + void run_tx_tti(srsue::rlc_interface_mac* tx_rlc, + srsue::rlc_interface_mac* rx_rlc, + std::vector& pdu_list); + + void run_rx_tti(srsue::rlc_interface_mac* tx_rlc, + srsue::rlc_interface_mac* rx_rlc, + bool is_dl, + std::vector& pdu_list); + srsue::rlc_interface_mac* rlc1 = nullptr; + srsue::rlc_interface_mac* rlc2 = nullptr; + + std::atomic run_enable = {false}; + stress_test_args_t args = {}; + srsran::rlc_pcap* pcap = nullptr; + uint32_t lcid = 0; + srslog::basic_logger& logger; + srsran::timer_handler* timers = nullptr; + + srsran::block_queue pending_tasks; + + std::mt19937 mt19937; + std::uniform_real_distribution real_dist; +}; + +class rlc_tester : public srsue::pdcp_interface_rlc, public srsue::rrc_interface_rlc, public srsran::thread +{ +public: + rlc_tester(srsue::rlc_interface_pdcp* rlc_pdcp_, + std::string name_, + stress_test_args_t args_, + uint32_t lcid_, + uint32_t seed_) : + logger(srslog::fetch_basic_logger(name_.c_str(), false)), + rlc_pdcp(rlc_pdcp_), + name(name_), + args(args_), + lcid(lcid_), + thread("RLC_TESTER"), + int_dist(args_.min_sdu_size, args_.max_sdu_size), + mt19937(seed_) + { + logger.set_level(static_cast(args.log_level)); + logger.set_hex_dump_max_size(args_.log_hex_limit); + } + + void stop() + { + run_enable = false; + wait_thread_finish(); + } + + // PDCP interface + void write_pdu(uint32_t rx_lcid, srsran::unique_byte_buffer_t sdu) final; + void write_pdu_bcch_bch(srsran::unique_byte_buffer_t sdu) final {} + void write_pdu_bcch_dlsch(srsran::unique_byte_buffer_t sdu) final {} + void write_pdu_pcch(srsran::unique_byte_buffer_t sdu) final {} + void write_pdu_mch(uint32_t lcid_, srsran::unique_byte_buffer_t sdu) final {} + void notify_delivery(uint32_t lcid_, const srsran::pdcp_sn_vector_t& pdcp_sns) final {} + void notify_failure(uint32_t lcid_, const srsran::pdcp_sn_vector_t& pdcp_sns) final {} + + // RRC interface + void max_retx_attempted() final + { + fprintf( + stderr, + "Maximum number of RLC retransmission reached. Consider increasing threshold or lowering channel drop rate."); + logger.error( + "Maximum number of RLC retransmission reached. Consider increasing threshold or lowering channel drop rate."); + std::this_thread::sleep_for(std::chrono::seconds(1)); + exit(1); + } + void protocol_failure() final + { + logger.error("RLC protocol error detected."); + std::this_thread::sleep_for(std::chrono::seconds(1)); + exit(1); + } + const char* get_rb_name(uint32_t lcid) final { return "DRB1"; } + + uint64_t get_nof_rx_pdus() const { return rx_pdus; } + +private: + const static size_t max_pdcp_sn = 262143U; // 18bit SN + void run_thread() final; + + std::atomic run_enable = {true}; + + /// Tx uses thread-local PDCP SN to set SDU content, the Rx uses this variable to check received SDUs + uint8_t next_expected_sdu = 0; + uint64_t rx_pdus = 0; + uint32_t lcid = 0; + srslog::basic_logger& logger; + + std::string name; + + stress_test_args_t args = {}; + + srsue::rlc_interface_pdcp* rlc_pdcp = nullptr; // used by run_thread to push PDCP SDUs to RLC + + std::mt19937 mt19937; + std::uniform_int_distribution<> int_dist; +}; + diff --git a/srsenb/enb.conf.example b/srsenb/enb.conf.example index 4d5cce198..505771a05 100644 --- a/srsenb/enb.conf.example +++ b/srsenb/enb.conf.example @@ -217,7 +217,7 @@ enable = false #init_dl_cqi=5 #max_sib_coderate=0.3 #pdcch_cqi_offset=0 -#nr_pdsch_mcs=28 +nr_pdsch_mcs=28 #nr_pusch_mcs=28 ##################################################################### @@ -387,7 +387,7 @@ enable = false # rlf_release_timer_ms: Time taken by eNB to release UE context after it detects a RLF # rlf_min_ul_snr_estim: SNR threshold in dB below which the enb is notified with RLF ko # s1_setup_max_retries: Maximum amount of retries to setup the S1AP connection. If this value is exceeded, an alarm is written to the log. -1 means infinity. -# +# rx_gain_offset: RX Gain offset to add to rx_gain to calibrate RSRP readings ##################################################################### [expert] #pusch_max_its = 8 # These are half iterations @@ -423,3 +423,4 @@ enable = false #rlf_release_timer_ms = 4000 #rlf_min_ul_snr_estim = -2 #s1_setup_max_retries = -1 +#rx_gain_offset = 62 diff --git a/srsenb/hdr/phy/lte/cc_worker.h b/srsenb/hdr/phy/lte/cc_worker.h index 03b47f5fd..59175cf31 100644 --- a/srsenb/hdr/phy/lte/cc_worker.h +++ b/srsenb/hdr/phy/lte/cc_worker.h @@ -109,7 +109,7 @@ private: void metrics_read(phy_metrics_t* metrics); void metrics_dl(uint32_t mcs); void metrics_ul(uint32_t mcs, float rssi, float sinr, float turbo_iters); - void metrics_ul_pucch(float sinr); + void metrics_ul_pucch(float rssi, float ni, float sinr); uint32_t get_rnti() const { return rnti; } private: diff --git a/srsenb/hdr/phy/phy_interfaces.h b/srsenb/hdr/phy/phy_interfaces.h index 2d59c6e88..ae050a5bc 100644 --- a/srsenb/hdr/phy/phy_interfaces.h +++ b/srsenb/hdr/phy/phy_interfaces.h @@ -59,6 +59,7 @@ struct phy_args_t { std::string type; srsran::phy_log_args_t log; + float rx_gain_offset = 62; float max_prach_offset_us = 10; uint32_t pusch_max_its = 10; uint32_t nr_pusch_max_its = 10; diff --git a/srsenb/hdr/phy/phy_metrics.h b/srsenb/hdr/phy/phy_metrics.h index cca2344ab..d4ee72f4b 100644 --- a/srsenb/hdr/phy/phy_metrics.h +++ b/srsenb/hdr/phy/phy_metrics.h @@ -29,28 +29,22 @@ namespace srsenb { // PHY metrics per user struct ul_metrics_t { - float n; - float pusch_sinr; - // Initialize this member with an invalid value as this field is optional. - float pusch_rssi = std::numeric_limits::quiet_NaN(); - // Initialize this member with an invalid value as this field is optional. - int64_t pusch_tpc = 0; + float n; + float pusch_sinr; + float pusch_rssi; + int64_t pusch_tpc; float pucch_sinr; - // Initialize this member with an invalid value as this field is optional. - float pucch_rssi = std::numeric_limits::quiet_NaN(); - // Initialize this member with an invalid value as this field is optional. - float pucch_ni = std::numeric_limits::quiet_NaN(); - float rssi; - float turbo_iters; - float mcs; - int n_samples; - int n_samples_pucch; + float pucch_rssi; + float pucch_ni; + float turbo_iters; + float mcs; + int n_samples; + int n_samples_pucch; }; struct dl_metrics_t { float mcs; - // Initialize this member with an invalid value as this field is optional. - int64_t pucch_tpc = 0; + int64_t pucch_tpc; int n_samples; }; diff --git a/srsenb/hdr/stack/rrc/rrc_endc.h b/srsenb/hdr/stack/rrc/rrc_endc.h index f79ba48cf..8a8cbf763 100644 --- a/srsenb/hdr/stack/rrc/rrc_endc.h +++ b/srsenb/hdr/stack/rrc/rrc_endc.h @@ -110,7 +110,6 @@ private: asn1::rrc::rrc_conn_recfg_complete_s pending_recfg_complete; // fixed ENDC variables - const uint32_t eutra_drb_id = 1; // The DRB ID that converted to NR const uint32_t lcid_drb_nr = 4; // internal events diff --git a/srsenb/hdr/stack/rrc/rrc_mobility.h b/srsenb/hdr/stack/rrc/rrc_mobility.h index a3b679764..0a3d17af1 100644 --- a/srsenb/hdr/stack/rrc/rrc_mobility.h +++ b/srsenb/hdr/stack/rrc/rrc_mobility.h @@ -66,7 +66,8 @@ private: asn1::rrc::meas_cfg_s* diff_meas_cfg); // Handover from source cell - bool start_ho_preparation(uint32_t target_eci, uint8_t measobj_id, bool fwd_direct_path_available); + bool + start_ho_preparation(uint32_t target_eci, uint16_t target_tac, uint8_t measobj_id, bool fwd_direct_path_available); // Handover to target cell void fill_mobility_reconf_common(asn1::rrc::dl_dcch_msg_s& msg, @@ -90,6 +91,7 @@ private: // events struct ho_meas_report_ev { uint32_t target_eci = 0; + uint16_t target_tac = 0; const asn1::rrc::meas_obj_to_add_mod_s* meas_obj = nullptr; bool direct_fwd_path = false; }; diff --git a/srsenb/hdr/stack/s1ap/s1ap.h b/srsenb/hdr/stack/s1ap/s1ap.h index d03ce8d60..56703c5e3 100644 --- a/srsenb/hdr/stack/s1ap/s1ap.h +++ b/srsenb/hdr/stack/s1ap/s1ap.h @@ -90,6 +90,7 @@ public: bool is_mme_connected() override; bool send_ho_required(uint16_t rnti, uint32_t target_eci, + uint16_t target_tac, srsran::plmn_id_t target_plmn, srsran::span fwd_erabs, srsran::unique_byte_buffer_t rrc_container, @@ -116,7 +117,7 @@ public: // Stack interface bool - handle_mme_rx_msg(srsran::unique_byte_buffer_t pdu, const sockaddr_in& from, const sctp_sndrcvinfo& sri, int flags); + handle_mme_rx_msg(srsran::unique_byte_buffer_t pdu, const sockaddr_in& from, const sctp_sndrcvinfo& sri, int flags); void start_pcap(srsran::s1ap_pcap* pcap_); private: @@ -221,6 +222,7 @@ private: struct ts1_reloc_prep_expired {}; ho_prep_proc_t(s1ap::ue* ue_); srsran::proc_outcome_t init(uint32_t target_eci_, + uint16_t target_tac_, srsran::plmn_id_t target_plmn_, srsran::span fwd_erabs, srsran::unique_byte_buffer_t rrc_container, @@ -237,6 +239,7 @@ private: s1ap* s1ap_ptr = nullptr; uint32_t target_eci = 0; + uint16_t target_tac = 0; srsran::plmn_id_t target_plmn; srsran::unique_byte_buffer_t rrc_container; const asn1::s1ap::ho_cmd_s* ho_cmd_msg = nullptr; @@ -269,7 +272,7 @@ private: bool was_uectxtrelease_requested() const { return release_requested; } void - set_state(s1ap_proc_id_t state, const erab_id_list& erabs_updated, const erab_item_list& erabs_failed_to_update); + set_state(s1ap_proc_id_t state, const erab_id_list& erabs_updated, const erab_item_list& erabs_failed_to_update); s1ap_proc_id_t get_state() const { return current_state; } ue_ctxt_t ctxt = {}; @@ -277,6 +280,7 @@ private: private: bool send_ho_required(uint32_t target_eci_, + uint16_t target_tac_, srsran::plmn_id_t target_plmn_, srsran::span fwd_erabs, srsran::unique_byte_buffer_t rrc_container, diff --git a/srsenb/rb.conf.example b/srsenb/rb.conf.example index de1cb8f33..8cab02bb6 100644 --- a/srsenb/rb.conf.example +++ b/srsenb/rb.conf.example @@ -1,5 +1,7 @@ // All times are in ms. Use -1 for infinity, where available +// 4G Section + // srb1_config = { // rlc_config = { // ul_am = { @@ -37,9 +39,8 @@ // } qci_config = ( - { - qci=7; + qci = 7; pdcp_config = { discard_timer = -1; pdcp_sn_size = 12; @@ -64,7 +65,7 @@ qci_config = ( }; }, { - qci=9; + qci = 9; pdcp_config = { discard_timer = 150; status_report_required = true; @@ -91,5 +92,58 @@ qci_config = ( dl_max_retx_thresh = 32; }; } +); +// 5G Section +five_qi_config = ( +{ + five_qi = 7; + pdcp_nr_config = { + drb = { + discard_timer = 50; + pdcp_sn_size_ul = 18; + pdcp_sn_size_dl = 18; + }; + t_reordering = 50; + }; + rlc_config = { + um_bi_dir = { + ul_um = { + sn_field_len = 12; + }; + dl_um = { + sn_field_len = 12; + t_reassembly = 50; + }; + }; + }; +}, +{ + five_qi = 9; + pdcp_nr_config = { + drb = { + discard_timer = 50; + pdcp_sn_size_ul = 18; + pdcp_sn_size_dl = 18; + }; + t_reordering = 50; + }; + rlc_config = { + am = { + ul_am = { + sn_field_len = 12; + t_poll_retx = 50; + poll_pdu = 4; + poll_byte = 3000; + max_retx_thres = 4; + }; + dl_am = { + sn_field_len = 12; + t_reassembly = 50; + t_status_prohibit = 50; + }; + }; + }; +} ); + diff --git a/srsenb/src/CMakeLists.txt b/srsenb/src/CMakeLists.txt index d1c507d98..3dc53e5bf 100644 --- a/srsenb/src/CMakeLists.txt +++ b/srsenb/src/CMakeLists.txt @@ -66,4 +66,4 @@ else(NOT ${BUILDENB_CMD} STREQUAL "") message(STATUS "No post-build-ENB command defined") endif (NOT ${BUILDENB_CMD} STREQUAL "") -install(TARGETS srsenb DESTINATION ${RUNTIME_DIR}) +install(TARGETS srsenb DESTINATION ${RUNTIME_DIR} OPTIONAL) diff --git a/srsenb/src/enb_cfg_parser.cc b/srsenb/src/enb_cfg_parser.cc index 962f2afe8..67de7e13a 100644 --- a/srsenb/src/enb_cfg_parser.cc +++ b/srsenb/src/enb_cfg_parser.cc @@ -644,6 +644,181 @@ int field_qci::parse(libconfig::Setting& root) return 0; } +int field_five_qi::parse(libconfig::Setting& root) +{ + uint32_t nof_five_qi = (uint32_t)root.getLength(); + for (uint32_t i = 0; i < nof_five_qi; i++) { + libconfig::Setting& q = root[i]; + + uint32_t five_qi = q["five_qi"]; + + rrc_nr_cfg_five_qi_t five_qi_cfg; + + // Parse PDCP section + if (!q.exists("pdcp_nr_config")) { + fprintf(stderr, "Error section pdcp_nr_config not found for 5qi=%d\n", five_qi); + return SRSRAN_ERROR; + } + libconfig::Setting& pdcp_nr = q["pdcp_nr_config"]; + asn1::rrc_nr::pdcp_cfg_s* pdcp_cfg = &five_qi_cfg.pdcp_cfg; + + // Get PDCP-NR DRB configs + if (!pdcp_nr.exists("drb")) { + fprintf(stderr, "Error section drb not found for 5QI=%d\n", five_qi); + return SRSRAN_ERROR; + } + libconfig::Setting& drb = pdcp_nr["drb"]; + asn1::rrc_nr::pdcp_cfg_s::drb_s_* drb_cfg = &pdcp_cfg->drb; + pdcp_cfg->drb_present = true; + + // Discard timer + field_asn1_enum_number discard_timer("discard_timer", + &drb_cfg->discard_timer); + if (discard_timer.parse(drb) == -1) { + drb_cfg->discard_timer_present = false; + } else { + drb_cfg->discard_timer_present = true; + } + + // PDCP SN size UL + field_asn1_enum_number pdcp_sn_size_ul( + "pdcp_sn_size_ul", &drb_cfg->pdcp_sn_size_ul); + if (pdcp_sn_size_ul.parse(drb) == SRSRAN_ERROR) { + drb_cfg->pdcp_sn_size_ul_present = false; + } else { + drb_cfg->pdcp_sn_size_ul_present = true; + } + + // PDCP SN size DL + field_asn1_enum_number pdcp_sn_size_dl( + "pdcp_sn_size_dl", &drb_cfg->pdcp_sn_size_dl); + if (pdcp_sn_size_dl.parse(drb) == SRSRAN_ERROR) { + drb_cfg->pdcp_sn_size_dl_present = false; + } else { + drb_cfg->pdcp_sn_size_dl_present = true; + } + + parser::field status_report_required("status_report_required", &drb_cfg->status_report_required_present); + status_report_required.parse(drb); + + parser::field out_of_order_delivery("out_of_order_delivery", &drb_cfg->out_of_order_delivery_present); + out_of_order_delivery.parse(drb); + + parser::field integrity_protection("integrity_protection", &drb_cfg->integrity_protection_present); + integrity_protection.parse(drb); + + drb_cfg->hdr_compress.set_not_used(); + // Finish DRB config + + // t_Reordering + field_asn1_enum_number t_reordering("t_reordering", + &pdcp_cfg->t_reordering); + if (t_reordering.parse(pdcp_nr) == SRSRAN_ERROR) { + pdcp_cfg->t_reordering_present = false; + } else { + pdcp_cfg->t_reordering_present = true; + } + + // Parse RLC section + if (!q.exists("rlc_config")) { + fprintf(stderr, "Error section rlc_config not found for 5qi=%d\n", five_qi); + return SRSRAN_ERROR; + } + libconfig::Setting& rlc = q["rlc_config"]; + asn1::rrc_nr::rlc_cfg_c* rlc_cfg = &five_qi_cfg.rlc_cfg; + if (rlc.exists("um_uni_dir_ul") || rlc.exists("um_uni_dir_dl")) { + // Sanity check: RLC UM uni-directional is not supported. + fprintf(stderr, "Error uni-directional UM not supported. 5QI=%d\n", five_qi); + return SRSRAN_ERROR; + } + + if (rlc.exists("am")) { + rlc_cfg->set_am(); + } else if (rlc.exists("um_bi_dir")) { + rlc_cfg->set_um_bi_dir(); + } else { + fprintf(stderr, "Invalid combination of UL/DL UM/AM for 5QI=%d\n", five_qi); + return SRSRAN_ERROR; + } + + // Parse RLC-AM section + if (rlc_cfg->type() == asn1::rrc_nr::rlc_cfg_c::types::am) { + libconfig::Setting& rlc_am = rlc["am"]; + libconfig::Setting& rlc_am_ul = rlc_am["ul_am"]; + libconfig::Setting& rlc_am_dl = rlc_am["dl_am"]; + asn1::rrc_nr::ul_am_rlc_s& ul_am_cfg = rlc_cfg->am().ul_am_rlc; + asn1::rrc_nr::dl_am_rlc_s& dl_am_cfg = rlc_cfg->am().dl_am_rlc; + + // RLC AM UL + // SN length + field_asn1_enum_number rlc_sn_size_ul("sn_field_len", &ul_am_cfg.sn_field_len); + if (rlc_sn_size_ul.parse(rlc_am_ul) == SRSRAN_ERROR) { + ul_am_cfg.sn_field_len_present = false; + } else { + ul_am_cfg.sn_field_len_present = true; + } + // t-PollRetx + field_asn1_enum_number rlc_t_poll_retx("t_poll_retx", &ul_am_cfg.t_poll_retx); + rlc_t_poll_retx.parse(rlc_am_ul); + // pollPDU + field_asn1_enum_number rlc_poll_pdu("poll_pdu", &ul_am_cfg.poll_pdu); + rlc_poll_pdu.parse(rlc_am_ul); + // pollBYTE + field_asn1_enum_number rlc_poll_bytes("poll_byte", &ul_am_cfg.poll_byte); + rlc_poll_bytes.parse(rlc_am_ul); + // maxRetxThreshold + field_asn1_enum_number rlc_max_retx_thres( + "max_retx_thres", &ul_am_cfg.max_retx_thres); + rlc_max_retx_thres.parse(rlc_am_ul); + + // RLC AM DL + // SN length + field_asn1_enum_number rlc_sn_size_dl("sn_field_len", &dl_am_cfg.sn_field_len); + if (rlc_sn_size_dl.parse(rlc_am_dl) == SRSRAN_ERROR) { + dl_am_cfg.sn_field_len_present = false; + } else { + dl_am_cfg.sn_field_len_present = true; + } + // t-reassembly + field_asn1_enum_number rlc_t_reassembly("t_reassembly", &dl_am_cfg.t_reassembly); + rlc_t_reassembly.parse(rlc_am_dl); + // t-statusProhibit + field_asn1_enum_number rlc_status_prohibit("t_status_prohibit", + &dl_am_cfg.t_status_prohibit); + rlc_status_prohibit.parse(rlc_am_dl); + } else if (rlc_cfg->type() == asn1::rrc_nr::rlc_cfg_c::types::um_bi_dir) { + libconfig::Setting& rlc_um = rlc["um_bi_dir"]; + libconfig::Setting& rlc_um_ul = rlc_um["ul_um"]; + libconfig::Setting& rlc_um_dl = rlc_um["dl_um"]; + asn1::rrc_nr::ul_um_rlc_s& ul_um_cfg = rlc_cfg->um_bi_dir().ul_um_rlc; + asn1::rrc_nr::dl_um_rlc_s& dl_um_cfg = rlc_cfg->um_bi_dir().dl_um_rlc; + + // RLC UM UL + // SN field length + field_asn1_enum_number rlc_sn_size_ul("sn_field_len", &ul_um_cfg.sn_field_len); + if (rlc_sn_size_ul.parse(rlc_um_ul) == SRSRAN_ERROR) { + ul_um_cfg.sn_field_len_present = false; + } else { + ul_um_cfg.sn_field_len_present = true; + } + + // RLC UM DL + // SN field length + field_asn1_enum_number rlc_sn_size_dl("sn_field_len", &dl_um_cfg.sn_field_len); + if (rlc_sn_size_dl.parse(rlc_um_dl) == SRSRAN_ERROR) { + dl_um_cfg.sn_field_len_present = false; + } else { + dl_um_cfg.sn_field_len_present = true; + } + // t-reassembly + field_asn1_enum_number rlc_t_reassembly_dl("t_reassembly", &dl_um_cfg.t_reassembly); + rlc_t_reassembly_dl.parse(rlc_um_dl); + } + + cfg.insert(std::make_pair(five_qi, five_qi_cfg)); + } + return 0; +} namespace rr_sections { int parse_rr(all_args_t* args_, rrc_cfg_t* rrc_cfg_, rrc_nr_cfg_t* rrc_nr_cfg_) @@ -785,6 +960,7 @@ static int parse_meas_cell_list(rrc_meas_cfg_t* meas_cfg, Setting& root) parse_default_field(cell.allowed_meas_bw, root[i], "allowed_meas_bw", 6u); asn1_parsers::default_number_to_enum( cell.cell_individual_offset, root[i], "cell_individual_offset", asn1::rrc::q_offset_range_opts::db0); + parse_default_field(cell.tac, root[i], "tac", -1); srsran_assert(srsran::is_lte_cell_nof_prb(cell.allowed_meas_bw), "Invalid measurement Bandwidth"); } return 0; @@ -1231,15 +1407,15 @@ int parse_cfg_files(all_args_t* args_, rrc_cfg_t* rrc_cfg_, rrc_nr_cfg_t* rrc_nr } try { - if (drb_sections::parse_drb(args_, rrc_cfg_) != SRSRAN_SUCCESS) { - fprintf(stderr, "Error parsing DRB configuration\n"); + if (rb_sections::parse_rb(args_, rrc_cfg_, rrc_nr_cfg_) != SRSRAN_SUCCESS) { + fprintf(stderr, "Error parsing RB configuration\n"); return SRSRAN_ERROR; } } catch (const SettingTypeException& stex) { - fprintf(stderr, "Error parsing DRB configuration: %s\n", stex.getPath()); + fprintf(stderr, "Error parsing RB configuration: %s\n", stex.getPath()); return SRSRAN_ERROR; } catch (const ConfigException& cex) { - fprintf(stderr, "Error parsing DRB configuration\n"); + fprintf(stderr, "Error parsing RB configuration\n"); return SRSRAN_ERROR; } @@ -1356,7 +1532,7 @@ int set_derived_args(all_args_t* args_, rrc_cfg_t* rrc_cfg_, phy_cfg_t* phy_cfg_ // Create dedicated cell configuration from RRC configuration for (auto it = rrc_cfg_->cell_list.begin(); it != rrc_cfg_->cell_list.end(); ++it) { - auto& cfg = *it; + cell_cfg_t& cfg = *it; phy_cell_cfg_t phy_cell_cfg = {}; phy_cell_cfg.cell = cell_cfg_; phy_cell_cfg.cell.id = cfg.pci; @@ -1405,6 +1581,13 @@ int set_derived_args(all_args_t* args_, rrc_cfg_t* rrc_cfg_, phy_cfg_t* phy_cfg_ } } + for (meas_cell_cfg_t& meas_cell : cfg.meas_cfg.meas_cells) { + if (meas_cell.tac < 0) { + // if meas cell TAC was not set, use current cell TAC. + meas_cell.tac = cfg.tac; + } + } + // Check if the enb cells PCIs won't lead to PSS detection issues auto is_pss_collision = [&cfg](const cell_cfg_t& c) { return c.pci % 3 == cfg.pci % 3 and c.dl_earfcn == cfg.dl_earfcn; @@ -2103,9 +2286,9 @@ int parse_sibs(all_args_t* args_, rrc_cfg_t* rrc_cfg_, srsenb::phy_cfg_t* phy_co } // namespace sib_sections -namespace drb_sections { +namespace rb_sections { -int parse_drb(all_args_t* args_, rrc_cfg_t* rrc_cfg_) +int parse_rb(all_args_t* args_, rrc_cfg_t* rrc_cfg_, rrc_nr_cfg_t* rrc_nr_cfg_) { parser::section srb1("srb1_config"); bool srb1_present = false; @@ -2126,11 +2309,15 @@ int parse_drb(all_args_t* args_, rrc_cfg_t* rrc_cfg_) parser::section qci("qci_config"); qci.add_field(new field_qci(rrc_cfg_->qci_cfg)); + parser::section five_qi("five_qi_config"); + five_qi.add_field(new field_five_qi(rrc_nr_cfg_->five_qi_cfg)); + // Run parser with two sections parser p(args_->enb_files.rb_config); p.add_section(&srb1); p.add_section(&srb2); p.add_section(&qci); + p.add_section(&five_qi); int ret = p.parse(); if (not srb1_present) { @@ -2143,6 +2330,6 @@ int parse_drb(all_args_t* args_, rrc_cfg_t* rrc_cfg_) return ret; } -} // namespace drb_sections +} // namespace rb_sections } // namespace srsenb diff --git a/srsenb/src/enb_cfg_parser.h b/srsenb/src/enb_cfg_parser.h index 193594dbf..df21f51d9 100644 --- a/srsenb/src/enb_cfg_parser.h +++ b/srsenb/src/enb_cfg_parser.h @@ -31,6 +31,7 @@ #include #include "srsenb/hdr/stack/rrc/rrc.h" +#include "srsgnb/hdr/stack/rrc/rrc_nr_config.h" #include "srsran/asn1/asn1_utils.h" namespace srsenb { @@ -67,11 +68,12 @@ int parse_sibs(all_args_t* args_, rrc_cfg_t* rrc_cfg_, srsenb::phy_cfg_t* phy_co } // namespace sib_sections -// drb.conf parsing -namespace drb_sections { +// rb.conf parsing +namespace rb_sections { -int parse_drb(all_args_t* args, rrc_cfg_t* rrc_cfg); -} // namespace drb_sections +int parse_rb(all_args_t* args, rrc_cfg_t* rrc_cfg, rrc_nr_cfg_t* rrc_nr_cfg); + +} // namespace rb_sections // rr.conf parsing namespace rr_sections { @@ -190,7 +192,7 @@ class field_qci final : public parser::field_itf { public: explicit field_qci(std::map& cfg_) : cfg(cfg_) {} - const char* get_name() override { return "field_cqi"; } + const char* get_name() override { return "field_qci"; } int parse(Setting& root) override; @@ -198,6 +200,17 @@ private: std::map& cfg; }; +class field_five_qi final : public parser::field_itf +{ +public: + explicit field_five_qi(std::map& cfg_) : cfg(cfg_) {} + const char* get_name() override { return "field_five_qi"; } + + int parse(Setting& root) override; + +private: + std::map& cfg; +}; // ASN1 parsers class field_asn1 : public parser::field_itf diff --git a/srsenb/src/main.cc b/srsenb/src/main.cc index 7ee96eb53..b9bd09027 100644 --- a/srsenb/src/main.cc +++ b/srsenb/src/main.cc @@ -268,6 +268,7 @@ void parse_args(all_args_t* args, int argc, char* argv[]) ("expert.ts1_reloc_overall_timeout", bpo::value(&args->stack.s1ap.ts1_reloc_overall_timeout)->default_value(10000), "S1AP TS 36.413 TS1RelocOverall Expiry Timeout value in milliseconds.") ("expert.rlf_min_ul_snr_estim", bpo::value(&args->stack.mac.rlf_min_ul_snr_estim)->default_value(-2), "SNR threshold in dB below which the eNB is notified with rlf ko.") ("expert.max_s1_setup_retries", bpo::value(&args->stack.s1ap.max_s1_setup_retries)->default_value(-1), "Max S1 setup retries") + ("expert.rx_gain_offset", bpo::value(&args->phy.rx_gain_offset)->default_value(62), "RX Gain offset to add to rx_gain to calibrate RSRP readings") // eMBMS section ("embms.enable", bpo::value(&args->stack.embms.enable)->default_value(false), "Enables MBMS in the eNB") @@ -387,18 +388,11 @@ void parse_args(all_args_t* args, int argc, char* argv[]) exit(1); } - // convert CFR mode - if (!cfr_mode.empty()) { - if (cfr_mode == "manual") { - args->phy.cfr_args.mode = SRSRAN_CFR_THR_MANUAL; - } else if (cfr_mode == "auto_cma") { - args->phy.cfr_args.mode = SRSRAN_CFR_THR_AUTO_CMA; - } else if (cfr_mode == "auto_ema") { - args->phy.cfr_args.mode = SRSRAN_CFR_THR_AUTO_EMA; - } else { - cout << "Error, invalid CFR mode: " << cfr_mode << endl; - exit(1); - } + // parse the CFR mode string + args->phy.cfr_args.mode = srsran_cfr_str2mode(cfr_mode.c_str()); + if (args->phy.cfr_args.mode == SRSRAN_CFR_THR_INVALID) { + cout << "Error, invalid CFR mode: " << cfr_mode << endl; + exit(1); } // Apply all_level to any unset layers @@ -475,7 +469,7 @@ void parse_args(all_args_t* args, int argc, char* argv[]) } if (!config_exists(args->enb_files.rb_config, "rb.conf")) { - cout << "Failed to read DRB configuration file " << args->enb_files.rb_config << " - exiting" << endl; + cout << "Failed to read RB configuration file " << args->enb_files.rb_config << " - exiting" << endl; exit(1); } diff --git a/srsenb/src/parser.cc b/srsenb/src/parser.cc index ae51ab8a6..ff4e301b8 100644 --- a/srsenb/src/parser.cc +++ b/srsenb/src/parser.cc @@ -118,7 +118,7 @@ int parser::section::parse(Setting& root) *enabled_value = false; return 0; } else { - std::cerr << "Error section " << name.c_str() << " not found." << std::endl; + std::cerr << "Error in section " << name.c_str() << ". " << ex.getPath() << " not found." << std::endl; return -1; } } diff --git a/srsenb/src/phy/lte/cc_worker.cc b/srsenb/src/phy/lte/cc_worker.cc index 6ddfa9a7e..27862da9c 100644 --- a/srsenb/src/phy/lte/cc_worker.cc +++ b/srsenb/src/phy/lte/cc_worker.cc @@ -372,7 +372,10 @@ bool cc_worker::decode_pusch_rnti(stack_interface_phy_lte::ul_sched_grant_t& ul_ // Save statistics only if data was provided if (ul_grant.data != nullptr) { // Save metrics stats - ue_db[rnti]->metrics_ul(ul_grant.dci.tb.mcs_idx, 0, enb_ul.chest_res.snr_db, pusch_res.avg_iterations_block); + ue_db[rnti]->metrics_ul(ul_grant.dci.tb.mcs_idx, + enb_ul.chest_res.epre_dBfs - phy->params.rx_gain_offset, + enb_ul.chest_res.snr_db, + pusch_res.avg_iterations_block); } return true; } @@ -460,7 +463,11 @@ int cc_worker::decode_pucch() } // Save metrics - ue_db[rnti]->metrics_ul_pucch(pucch_res.snr_db); + if (pucch_res.detected) { + ue_db[rnti]->metrics_ul_pucch(pucch_res.rssi_dbFs - phy->params.rx_gain_offset, + pucch_res.ni_dbFs - -phy->params.rx_gain_offset, + pucch_res.snr_db); + } } } } @@ -675,15 +682,23 @@ void cc_worker::ue::metrics_dl(uint32_t mcs) void cc_worker::ue::metrics_ul(uint32_t mcs, float rssi, float sinr, float turbo_iters) { + if (isnan(rssi)) { + rssi = 0; + } metrics.ul.mcs = SRSRAN_VEC_CMA((float)mcs, metrics.ul.mcs, metrics.ul.n_samples); metrics.ul.pusch_sinr = SRSRAN_VEC_CMA((float)sinr, metrics.ul.pusch_sinr, metrics.ul.n_samples); - metrics.ul.rssi = SRSRAN_VEC_CMA((float)rssi, metrics.ul.rssi, metrics.ul.n_samples); + metrics.ul.pusch_rssi = SRSRAN_VEC_CMA((float)rssi, metrics.ul.pusch_rssi, metrics.ul.n_samples); metrics.ul.turbo_iters = SRSRAN_VEC_CMA((float)turbo_iters, metrics.ul.turbo_iters, metrics.ul.n_samples); metrics.ul.n_samples++; } -void cc_worker::ue::metrics_ul_pucch(float sinr) +void cc_worker::ue::metrics_ul_pucch(float rssi, float ni, float sinr) { + if (isnan(rssi)) { + rssi = 0; + } + metrics.ul.pucch_rssi = SRSRAN_VEC_CMA((float)rssi, metrics.ul.pucch_rssi, metrics.ul.n_samples_pucch); + metrics.ul.pucch_ni = SRSRAN_VEC_CMA((float)ni, metrics.ul.pucch_ni, metrics.ul.n_samples_pucch); metrics.ul.pucch_sinr = SRSRAN_VEC_CMA((float)sinr, metrics.ul.pucch_sinr, metrics.ul.n_samples_pucch); metrics.ul.n_samples_pucch++; } diff --git a/srsenb/src/phy/lte/sf_worker.cc b/srsenb/src/phy/lte/sf_worker.cc index f0eda324f..005d7a8e1 100644 --- a/srsenb/src/phy/lte/sf_worker.cc +++ b/srsenb/src/phy/lte/sf_worker.cc @@ -277,15 +277,19 @@ uint32_t sf_worker::get_metrics(std::vector& metrics) for (uint32_t r = 0; r < cnt; r++) { phy_metrics_t* m = &metrics[r]; phy_metrics_t* m_ = &metrics_[r]; - m->dl.mcs = SRSRAN_VEC_PMA(m->dl.mcs, m->dl.n_samples, m_->dl.mcs, m_->dl.n_samples); + m->dl.mcs = SRSRAN_VEC_SAFE_PMA(m->dl.mcs, m->dl.n_samples, m_->dl.mcs, m_->dl.n_samples); m->dl.n_samples += m_->dl.n_samples; - m->ul.n = SRSRAN_VEC_PMA(m->ul.n, m->ul.n_samples, m_->ul.n, m_->ul.n_samples); - m->ul.pusch_sinr = SRSRAN_VEC_PMA(m->ul.pusch_sinr, m->ul.n_samples, m_->ul.pusch_sinr, m_->ul.n_samples); + m->ul.n = SRSRAN_VEC_SAFE_PMA(m->ul.n, m->ul.n_samples, m_->ul.n, m_->ul.n_samples); + m->ul.pusch_sinr = SRSRAN_VEC_SAFE_PMA(m->ul.pusch_sinr, m->ul.n_samples, m_->ul.pusch_sinr, m_->ul.n_samples); m->ul.pucch_sinr = - SRSRAN_VEC_PMA(m->ul.pucch_sinr, m->ul.n_samples_pucch, m_->ul.pucch_sinr, m_->ul.n_samples_pucch); - m->ul.mcs = SRSRAN_VEC_PMA(m->ul.mcs, m->ul.n_samples, m_->ul.mcs, m_->ul.n_samples); - m->ul.rssi = SRSRAN_VEC_PMA(m->ul.rssi, m->ul.n_samples, m_->ul.rssi, m_->ul.n_samples); - m->ul.turbo_iters = SRSRAN_VEC_PMA(m->ul.turbo_iters, m->ul.n_samples, m_->ul.turbo_iters, m_->ul.n_samples); + SRSRAN_VEC_SAFE_PMA(m->ul.pucch_sinr, m->ul.n_samples_pucch, m_->ul.pucch_sinr, m_->ul.n_samples_pucch); + m->ul.mcs = SRSRAN_VEC_SAFE_PMA(m->ul.mcs, m->ul.n_samples, m_->ul.mcs, m_->ul.n_samples); + m->ul.pusch_rssi = SRSRAN_VEC_SAFE_PMA(m->ul.pusch_rssi, m->ul.n_samples, m_->ul.pusch_rssi, m_->ul.n_samples); + m->ul.pucch_rssi = + SRSRAN_VEC_SAFE_PMA(m->ul.pucch_rssi, m->ul.n_samples_pucch, m_->ul.pucch_rssi, m_->ul.n_samples_pucch); + m->ul.pucch_ni = + SRSRAN_VEC_SAFE_PMA(m->ul.pucch_ni, m->ul.n_samples_pucch, m_->ul.pucch_ni, m_->ul.n_samples_pucch); + m->ul.turbo_iters = SRSRAN_VEC_SAFE_PMA(m->ul.turbo_iters, m->ul.n_samples, m_->ul.turbo_iters, m_->ul.n_samples); m->ul.n_samples += m_->ul.n_samples; m->ul.n_samples_pucch += m_->ul.n_samples_pucch; } diff --git a/srsenb/src/phy/phy.cc b/srsenb/src/phy/phy.cc index a96b25f39..2de3952d1 100644 --- a/srsenb/src/phy/phy.cc +++ b/srsenb/src/phy/phy.cc @@ -261,20 +261,28 @@ void phy::get_metrics(std::vector& metrics) metrics[j].ul.n_samples_pucch += metrics_tmp[j].ul.n_samples_pucch; metrics[j].ul.mcs += metrics_tmp[j].ul.n_samples * metrics_tmp[j].ul.mcs; metrics[j].ul.n += metrics_tmp[j].ul.n_samples * metrics_tmp[j].ul.n; - metrics[j].ul.rssi += metrics_tmp[j].ul.n_samples * metrics_tmp[j].ul.rssi; + metrics[j].ul.pusch_rssi += metrics_tmp[j].ul.n_samples * metrics_tmp[j].ul.pusch_rssi; metrics[j].ul.pusch_sinr += metrics_tmp[j].ul.n_samples * metrics_tmp[j].ul.pusch_sinr; + metrics[j].ul.pucch_rssi += metrics_tmp[j].ul.n_samples_pucch * metrics_tmp[j].ul.pucch_rssi; + metrics[j].ul.pucch_ni += metrics_tmp[j].ul.n_samples_pucch * metrics_tmp[j].ul.pucch_ni; metrics[j].ul.pucch_sinr += metrics_tmp[j].ul.n_samples_pucch * metrics_tmp[j].ul.pucch_sinr; metrics[j].ul.turbo_iters += metrics_tmp[j].ul.n_samples * metrics_tmp[j].ul.turbo_iters; } } for (uint32_t j = 0; j < metrics.size(); j++) { - metrics[j].dl.mcs /= metrics[j].dl.n_samples; - metrics[j].ul.mcs /= metrics[j].ul.n_samples; - metrics[j].ul.n /= metrics[j].ul.n_samples; - metrics[j].ul.rssi /= metrics[j].ul.n_samples; - metrics[j].ul.pusch_sinr /= metrics[j].ul.n_samples; - metrics[j].ul.pucch_sinr /= metrics[j].ul.n_samples_pucch; - metrics[j].ul.turbo_iters /= metrics[j].ul.n_samples; + if (metrics[j].dl.n_samples > 0) { + metrics[j].dl.mcs /= metrics[j].dl.n_samples; + } + if (metrics[j].ul.n_samples > 0) { + metrics[j].ul.mcs /= metrics[j].ul.n_samples; + metrics[j].ul.n /= metrics[j].ul.n_samples; + metrics[j].ul.pusch_rssi /= metrics[j].ul.n_samples; + metrics[j].ul.pusch_sinr /= metrics[j].ul.n_samples; + metrics[j].ul.pucch_rssi /= metrics[j].ul.n_samples_pucch; + metrics[j].ul.pucch_ni /= metrics[j].ul.n_samples_pucch; + metrics[j].ul.pucch_sinr /= metrics[j].ul.n_samples_pucch; + metrics[j].ul.turbo_iters /= metrics[j].ul.n_samples; + } } } diff --git a/srsenb/src/stack/rrc/rrc_endc.cc b/srsenb/src/stack/rrc/rrc_endc.cc index fbeebc60e..8ee732605 100644 --- a/srsenb/src/stack/rrc/rrc_endc.cc +++ b/srsenb/src/stack/rrc/rrc_endc.cc @@ -319,8 +319,27 @@ void rrc::ue::rrc_endc::start_sgnb_addition() { // Start EN-DC activation using EPS bearer of EUTRA DRB1 rrc_nr_interface_rrc::sgnb_addition_req_params_t params = {}; - params.eps_bearer_id = - rrc_enb->bearer_manager.get_lcid_bearer(rrc_ue->rnti, drb_to_lcid((lte_drb)eutra_drb_id)).eps_bearer_id; + + const auto& drb_list = rrc_ue->bearer_list.get_established_drbs(); + if (drb_list.size() > 0) { + // move first establised DRB to NR cell + const auto& drb1 = drb_list[0]; + const auto& erab_list = rrc_ue->bearer_list.get_erabs(); + auto erab_it = erab_list.find(drb1.eps_bearer_id); + if (erab_it != erab_list.end()) { + params.eps_bearer_id = drb1.eps_bearer_id; + params.five_qi = erab_it->second.qos_params.qci; // use QCI as 5QI + } else { + logger.error("Couldn't find ERAB config for DRB%d. Aborting SgNB addition for E-UTRA rnti=0x%x", + drb1.drb_id, + rrc_ue->rnti); + return; + } + } else { + logger.error("No LTE DRB established. Aborting SgNB addition for E-UTRA rnti=0x%x", rrc_ue->rnti); + return; + } + logger.info("Triggering SgNB addition for E-UTRA rnti=0x%x", rrc_ue->rnti); rrc_enb->rrc_nr->sgnb_addition_request(rrc_ue->rnti, params); diff --git a/srsenb/src/stack/rrc/rrc_mobility.cc b/srsenb/src/stack/rrc/rrc_mobility.cc index 6a9eaf559..5e9d4e3b0 100644 --- a/srsenb/src/stack/rrc/rrc_mobility.cc +++ b/srsenb/src/stack/rrc/rrc_mobility.cc @@ -282,9 +282,11 @@ void rrc::ue::rrc_mobility::handle_ue_meas_report(const meas_report_s& msg, srsr const enb_cell_common* c = rrc_enb->cell_common_list->get_pci(e.pci); if (meas_it != meas_list_cfg.end()) { meas_ev.target_eci = meas_it->eci; + meas_ev.target_tac = meas_it->tac; meas_ev.direct_fwd_path = meas_it->direct_forward_path_available; } else if (c != nullptr) { meas_ev.target_eci = (rrc_enb->cfg.enb_id << 8u) + c->cell_cfg.cell_id; + meas_ev.target_tac = pcell->cell_common->cell_cfg.tac; } else { logger.warning("The PCI=%d inside the MeasReport is not recognized.", e.pci); continue; @@ -305,6 +307,7 @@ void rrc::ue::rrc_mobility::handle_ue_meas_report(const meas_report_s& msg, srsr * - This struct goes in a transparent container to the S1AP */ bool rrc::ue::rrc_mobility::start_ho_preparation(uint32_t target_eci, + uint16_t target_tac, uint8_t measobj_id, bool fwd_direct_path_available) { @@ -414,7 +417,7 @@ bool rrc::ue::rrc_mobility::start_ho_preparation(uint32_t target_eci, } return rrc_enb->s1ap->send_ho_required( - rrc_ue->rnti, target_eci, target_plmn, fwd_erabs, std::move(buffer), fwd_direct_path_available); + rrc_ue->rnti, target_eci, target_tac, target_plmn, fwd_erabs, std::move(buffer), fwd_direct_path_available); } /** @@ -662,7 +665,8 @@ void rrc::ue::rrc_mobility::s1_source_ho_st::enter(rrc_mobility* f, const ho_mea logger.info("Starting S1 Handover of rnti=0x%x to cellid=0x%x.", rrc_ue->rnti, ev.target_eci); report = ev; - if (not parent_fsm()->start_ho_preparation(report.target_eci, report.meas_obj->meas_obj_id, ev.direct_fwd_path)) { + if (not parent_fsm()->start_ho_preparation( + report.target_eci, report.target_tac, report.meas_obj->meas_obj_id, ev.direct_fwd_path)) { trigger(srsran::failure_ev{}); } } diff --git a/srsenb/src/stack/s1ap/s1ap.cc b/srsenb/src/stack/s1ap/s1ap.cc index d253cd820..79dfb0cdf 100644 --- a/srsenb/src/stack/s1ap/s1ap.cc +++ b/srsenb/src/stack/s1ap/s1ap.cc @@ -127,6 +127,7 @@ void fill_erab_failed_setup_list(OutList& output_list, const s1ap::erab_item_lis s1ap::ue::ho_prep_proc_t::ho_prep_proc_t(s1ap::ue* ue_) : ue_ptr(ue_), s1ap_ptr(ue_->s1ap_ptr) {} srsran::proc_outcome_t s1ap::ue::ho_prep_proc_t::init(uint32_t target_eci_, + uint16_t target_tac_, srsran::plmn_id_t target_plmn_, srsran::span fwd_erabs, srsran::unique_byte_buffer_t rrc_container_, @@ -134,11 +135,12 @@ srsran::proc_outcome_t s1ap::ue::ho_prep_proc_t::init(uint32_t { ho_cmd_msg = nullptr; target_eci = target_eci_; + target_tac = target_tac_; target_plmn = target_plmn_; procInfo("Sending HandoverRequired to MME id=%d", ue_ptr->ctxt.mme_ue_s1ap_id.value()); if (not ue_ptr->send_ho_required( - target_eci, target_plmn, fwd_erabs, std::move(rrc_container_), has_direct_fwd_path)) { + target_eci, target_tac, target_plmn, fwd_erabs, std::move(rrc_container_), has_direct_fwd_path)) { procError("Failed to send HORequired to cell 0x%x", target_eci); return srsran::proc_outcome_t::error; } @@ -1830,6 +1832,7 @@ void s1ap::ue::get_erab_addr(uint16_t erab_id, transp_addr_t& transp_addr, asn1: bool s1ap::send_ho_required(uint16_t rnti, uint32_t target_eci, + uint16_t target_tac, srsran::plmn_id_t target_plmn, srsran::span fwd_erabs, srsran::unique_byte_buffer_t rrc_container, @@ -1844,7 +1847,8 @@ bool s1ap::send_ho_required(uint16_t rnti, } // launch procedure - if (not u->ho_prep_proc.launch(target_eci, target_plmn, fwd_erabs, std::move(rrc_container), has_direct_fwd_path)) { + if (not u->ho_prep_proc.launch( + target_eci, target_tac, target_plmn, fwd_erabs, std::move(rrc_container), has_direct_fwd_path)) { logger.error("Failed to initiate an HandoverPreparation procedure for user rnti=0x%x", u->ctxt.rnti); return false; } @@ -2110,6 +2114,7 @@ s1ap::ue::ue(s1ap* s1ap_ptr_) : s1ap_ptr(s1ap_ptr_), ho_prep_proc(this), logger( } bool s1ap::ue::send_ho_required(uint32_t target_eci, + uint16_t target_tac, srsran::plmn_id_t target_plmn, srsran::span fwd_erabs, srsran::unique_byte_buffer_t rrc_container, @@ -2140,7 +2145,7 @@ bool s1ap::ue::send_ho_required(uint32_t target_eci, // set PLMN and TAI of target // NOTE: Only HO without TAU supported. uint16_t tmp16; - tmp16 = htons(s1ap_ptr->args.tac); + tmp16 = htons(target_tac); memcpy(targetenb.sel_tai.tac.data(), &tmp16, sizeof(uint16_t)); target_plmn.to_s1ap_plmn_bytes(targetenb.sel_tai.plm_nid.data()); // NOTE: Only HO to different Macro eNB is supported. diff --git a/srsenb/test/common/dummy_classes.h b/srsenb/test/common/dummy_classes.h index b7d63e16e..8a0080f43 100644 --- a/srsenb/test/common/dummy_classes.h +++ b/srsenb/test/common/dummy_classes.h @@ -76,6 +76,7 @@ public: bool is_mme_connected() override { return true; } bool send_ho_required(uint16_t rnti, uint32_t target_eci, + uint16_t target_tac, srsran::plmn_id_t target_plmn, srsran::span fwd_erabs, srsran::unique_byte_buffer_t rrc_container, diff --git a/srsenb/test/rrc/test_helpers.h b/srsenb/test/rrc/test_helpers.h index 6ed60cbc2..bf78543a5 100644 --- a/srsenb/test/rrc/test_helpers.h +++ b/srsenb/test/rrc/test_helpers.h @@ -90,6 +90,7 @@ public: bool send_ho_required(uint16_t rnti, uint32_t target_eci, + uint16_t target_tac, srsran::plmn_id_t target_plmn, srsran::span fwd_erabs, srsran::unique_byte_buffer_t rrc_container, diff --git a/srsepc/src/CMakeLists.txt b/srsepc/src/CMakeLists.txt index 8c05a87f2..8e187e149 100644 --- a/srsepc/src/CMakeLists.txt +++ b/srsepc/src/CMakeLists.txt @@ -76,5 +76,5 @@ else(NOT ${BUILDEPC_CMD} STREQUAL "") message(STATUS "No post-build-EPC command defined") endif (NOT ${BUILDEPC_CMD} STREQUAL "") -install(TARGETS srsepc DESTINATION ${RUNTIME_DIR}) -install(TARGETS srsmbms DESTINATION ${RUNTIME_DIR}) +install(TARGETS srsepc DESTINATION ${RUNTIME_DIR} OPTIONAL) +install(TARGETS srsmbms DESTINATION ${RUNTIME_DIR} OPTIONAL) diff --git a/srsgnb/hdr/stack/mac/sched_nr_ue.h b/srsgnb/hdr/stack/mac/sched_nr_ue.h index 18da7b5f5..494f44092 100644 --- a/srsgnb/hdr/stack/mac/sched_nr_ue.h +++ b/srsgnb/hdr/stack/mac/sched_nr_ue.h @@ -70,7 +70,8 @@ public: struct pdu_builder { pdu_builder() = default; explicit pdu_builder(uint32_t cc_, ue_buffer_manager& parent_) : cc(cc_), parent(&parent_) {} - void alloc_subpdus(uint32_t rem_bytes, sched_nr_interface::dl_pdu_t& pdu); + bool alloc_subpdus(uint32_t rem_bytes, sched_nr_interface::dl_pdu_t& pdu); + uint32_t pending_bytes(uint32_t lcid) const { return parent->get_dl_tx(lcid); } private: uint32_t cc = SRSRAN_MAX_CARRIERS; @@ -189,11 +190,13 @@ public: ul_harq_proc* find_empty_ul_harq() { return ue->harq_ent.find_empty_ul_harq(); } /// Build PDU with MAC CEs and MAC SDUs - void build_pdu(uint32_t rem_bytes, sched_nr_interface::dl_pdu_t& pdu) + bool build_pdu(uint32_t rem_bytes, sched_nr_interface::dl_pdu_t& pdu, bool reset_buf_states = false) { - ue->pdu_builder.alloc_subpdus(rem_bytes, pdu); + return ue->pdu_builder.alloc_subpdus(rem_bytes, pdu); } + bool get_pending_bytes(uint32_t lcid) const { return ue->pdu_builder.pending_bytes(lcid); } + /// Channel Information Getters uint32_t dl_cqi() const { return ue->dl_cqi; } uint32_t ul_cqi() const { return ue->ul_cqi; } diff --git a/srsgnb/hdr/stack/ngap/ngap.h b/srsgnb/hdr/stack/ngap/ngap.h index 7be69a18b..7a5685dda 100644 --- a/srsgnb/hdr/stack/ngap/ngap.h +++ b/srsgnb/hdr/stack/ngap/ngap.h @@ -163,7 +163,7 @@ private: ue* find_ue_rnti(uint16_t rnti); ue* find_ue_gnbid(uint32_t gnbid); - ue* find_ue_amfid(uint32_t amfid); + ue* find_ue_amfid(uint64_t amfid); ue* add_user(value_type user); void erase(ue* ue_ptr); iterator begin() { return users.begin(); } @@ -199,7 +199,7 @@ private: ngap* ngap_ptr = nullptr; }; - ue* handle_ngapmsg_ue_id(uint32_t gnb_id, uint32_t amf_id); + ue* handle_ngapmsg_ue_id(uint32_t gnb_id, uint64_t amf_id); srsran::proc_t ngsetup_proc; diff --git a/srsgnb/hdr/stack/ngap/ngap_ue_proc.h b/srsgnb/hdr/stack/ngap/ngap_ue_proc.h index 64ca4403a..a8ee9e9a8 100644 --- a/srsgnb/hdr/stack/ngap/ngap_ue_proc.h +++ b/srsgnb/hdr/stack/ngap/ngap_ue_proc.h @@ -37,8 +37,33 @@ namespace srsenb { -// TS 38.413 - Section 8.3 - UE Context Management Procedures +/* + * TS 38.413 - Section 8.2 - PDU Session Management Procedures + */ +// TS 38.413 - Section 8.2.1 PDU Session Resource Setup +class ngap_ue_pdu_session_res_setup_proc +{ +public: + explicit ngap_ue_pdu_session_res_setup_proc(ngap_interface_ngap_proc* parent_, + rrc_interface_ngap_nr* rrc_, + ngap_ue_ctxt_t* ue_ctxt, + ngap_ue_bearer_manager* bearer_manager, + srslog::basic_logger& logger_); + srsran::proc_outcome_t init(const asn1::ngap::pdu_session_res_setup_request_s& msg); + srsran::proc_outcome_t step(); + static const char* name() { return "UE PDU Session Resource Setup"; } +private: + ngap_ue_ctxt_t* ue_ctxt; + ngap_interface_ngap_proc* parent; + ngap_ue_bearer_manager* bearer_manager; + rrc_interface_ngap_nr* rrc = nullptr; + srslog::basic_logger& logger; +}; + +/* + * TS 38.413 - Section 8.3 - UE Context Management Procedures + */ // TS 38.413 - Section 8.3.1 - Initial Context Setup class ngap_ue_initial_context_setup_proc { @@ -80,39 +105,6 @@ private: srslog::basic_logger& logger; }; -// TS 38.413 - Section 8.3.4 - UE Context Modification -class ngap_ue_ue_context_modification_proc -{ -public: - explicit ngap_ue_ue_context_modification_proc(ngap_interface_ngap_proc* parent_, srslog::basic_logger& logger_); - srsran::proc_outcome_t init(); - srsran::proc_outcome_t step(); - static const char* name() { return "UE Context Modification"; } - -private: - ngap_interface_ngap_proc* parent; -}; - -class ngap_ue_pdu_session_res_setup_proc -{ -public: - explicit ngap_ue_pdu_session_res_setup_proc(ngap_interface_ngap_proc* parent_, - rrc_interface_ngap_nr* rrc_, - ngap_ue_ctxt_t* ue_ctxt, - ngap_ue_bearer_manager* bearer_manager, - srslog::basic_logger& logger_); - srsran::proc_outcome_t init(const asn1::ngap::pdu_session_res_setup_request_s& msg); - srsran::proc_outcome_t step(); - static const char* name() { return "UE PDU Session Resource Setup"; } - -private: - ngap_ue_ctxt_t* ue_ctxt; - ngap_interface_ngap_proc* parent; - ngap_ue_bearer_manager* bearer_manager; - rrc_interface_ngap_nr* rrc = nullptr; - srslog::basic_logger& logger; -}; - } // namespace srsenb -#endif \ No newline at end of file +#endif diff --git a/srsgnb/hdr/stack/ngap/ngap_ue_utils.h b/srsgnb/hdr/stack/ngap/ngap_ue_utils.h index 2f657b53b..6374420fe 100644 --- a/srsgnb/hdr/stack/ngap/ngap_ue_utils.h +++ b/srsgnb/hdr/stack/ngap/ngap_ue_utils.h @@ -33,7 +33,7 @@ struct ngap_ue_ctxt_t { uint16_t rnti = SRSRAN_INVALID_RNTI; uint32_t ran_ue_ngap_id = invalid_gnb_id; - srsran::optional amf_ue_ngap_id; + srsran::optional amf_ue_ngap_id; uint32_t gnb_cc_idx = 0; struct timeval init_timestamp = {}; diff --git a/srsgnb/hdr/stack/rrc/cell_asn1_config.h b/srsgnb/hdr/stack/rrc/cell_asn1_config.h index 11174ad53..228028566 100644 --- a/srsgnb/hdr/stack/rrc/cell_asn1_config.h +++ b/srsgnb/hdr/stack/rrc/cell_asn1_config.h @@ -24,6 +24,7 @@ #include "rrc_nr_config.h" #include "srsran/asn1/rrc_nr.h" +#include "srsran/common/bearer_manager.h" #include "srsran/common/common_nr.h" namespace srsenb { @@ -53,6 +54,8 @@ bool compute_diff_radio_bearer_cfg(const rrc_nr_cfg_t& cfg, /// Apply radioBearerConfig updates to CellGroupConfig void fill_cellgroup_with_radio_bearer_cfg(const rrc_nr_cfg_t& cfg, + uint32_t rnti, + const enb_bearer_manager& bearer_mapper, const asn1::rrc_nr::radio_bearer_cfg_s& bearers, asn1::rrc_nr::cell_group_cfg_s& out); diff --git a/srsgnb/hdr/stack/rrc/rrc_nr.h b/srsgnb/hdr/stack/rrc/rrc_nr.h index c6a06a49b..5a11d751f 100644 --- a/srsgnb/hdr/stack/rrc/rrc_nr.h +++ b/srsgnb/hdr/stack/rrc/rrc_nr.h @@ -111,7 +111,8 @@ public: int establish_rrc_bearer(uint16_t rnti, uint16_t pdu_session_id, srsran::const_byte_span nas_pdu, - uint32_t lcid) final; + uint32_t lcid, + uint32_t five_qi) final; int release_bearers(uint16_t rnti) final; void release_user(uint16_t rnti) final; void write_dl_info(uint16_t rnti, srsran::unique_byte_buffer_t sdu) final; diff --git a/srsgnb/hdr/stack/rrc/rrc_nr_config.h b/srsgnb/hdr/stack/rrc/rrc_nr_config.h index f96a9de72..3511e3a36 100644 --- a/srsgnb/hdr/stack/rrc/rrc_nr_config.h +++ b/srsgnb/hdr/stack/rrc/rrc_nr_config.h @@ -54,6 +54,12 @@ struct rrc_cell_cfg_nr_t { typedef std::vector rrc_cell_list_nr_t; +struct rrc_nr_cfg_five_qi_t { + bool configured = false; + asn1::rrc_nr::pdcp_cfg_s pdcp_cfg; + asn1::rrc_nr::rlc_cfg_c rlc_cfg; +}; + struct rrc_nr_cfg_t { rrc_cell_list_nr_t cell_list; uint32_t inactivity_timeout_ms = 100000; @@ -62,6 +68,8 @@ struct rrc_nr_cfg_t { uint16_t mnc; bool is_standalone; + std::map five_qi_cfg; + std::array nea_preference_list; std::array nia_preference_list; diff --git a/srsgnb/hdr/stack/rrc/rrc_nr_ue.h b/srsgnb/hdr/stack/rrc/rrc_nr_ue.h index 1caf6fb4f..ca641a8d7 100644 --- a/srsgnb/hdr/stack/rrc/rrc_nr_ue.h +++ b/srsgnb/hdr/stack/rrc/rrc_nr_ue.h @@ -81,6 +81,9 @@ public: /** TS 38.331 - 5.3.8 Connection Release */ void send_rrc_release(); + /* TS 38.331 - 5.6.1 UE capability transfer */ + void handle_ue_capability_information(const asn1::rrc_nr::ue_cap_info_s& msg); + /** TS 38.331 - 5.7.1 DL information transfer */ void send_dl_information_transfer(srsran::unique_byte_buffer_t sdu); @@ -88,7 +91,7 @@ public: void handle_ul_information_transfer(const asn1::rrc_nr::ul_info_transfer_s& msg); // NGAP interface - void establish_eps_bearer(uint32_t pdu_session_id, srsran::const_byte_span nas_pdu, uint32_t lcid); + void establish_eps_bearer(uint32_t pdu_session_id, srsran::const_byte_span nas_pdu, uint32_t lcid, uint32_t five_qi); /* TS 38.331 - 5.3.4 Initial AS security activation */ void send_security_mode_command(srsran::unique_byte_buffer_t nas_pdu); @@ -96,6 +99,9 @@ public: /* TS 38.331 - 5.3.5 RRC reconfiguration */ void send_rrc_reconfiguration(); + /* TS 38.331 - 5.6.1 UE capability transfer */ + int send_ue_capability_enquiry(); + private: int send_dl_ccch(const asn1::rrc_nr::dl_ccch_msg_s& dl_ccch_msg); int send_dl_dcch(srsran::nr_srb srb, const asn1::rrc_nr::dl_dcch_msg_s& dl_dcch_msg); @@ -123,7 +129,6 @@ private: int pack_rrc_reconfiguration(asn1::dyn_octstring& packed_rrc_reconfig); int pack_secondary_cell_group_cfg(asn1::dyn_octstring& packed_secondary_cell_config); - int pack_secondary_cell_group_rlc_cfg(asn1::rrc_nr::cell_group_cfg_s& cell_group_cfg_pack); int pack_secondary_cell_group_mac_cfg(asn1::rrc_nr::cell_group_cfg_s& cell_group_cfg_pack); int pack_secondary_cell_group_sp_cell_cfg(asn1::rrc_nr::cell_group_cfg_s& cell_group_cfg_pack); @@ -158,7 +163,7 @@ private: int pack_nr_radio_bearer_config(asn1::dyn_octstring& packed_nr_bearer_config); - int add_drb(); + int add_drb(uint32_t five_qi); bool init_pucch(); diff --git a/srsgnb/src/stack/mac/mac_nr.cc b/srsgnb/src/stack/mac/mac_nr.cc index f896ee7ff..45a6a2a41 100644 --- a/srsgnb/src/stack/mac/mac_nr.cc +++ b/srsgnb/src/stack/mac/mac_nr.cc @@ -348,7 +348,10 @@ void mac_nr::rach_detected(const rach_info_t& rach_info) uint16_t rnti = alloc_ue(enb_cc_idx); // Log this event. - ++detected_rachs[enb_cc_idx]; + { + srsran::rwlock_write_guard lock(rwmutex); + ++detected_rachs[enb_cc_idx]; + } // Trigger scheduler RACH srsenb::sched_nr_interface::rar_info_t rar_info = {}; @@ -684,7 +687,7 @@ srsran::byte_buffer_t* mac_nr::assemble_rar(srsran::const_span= 0, "Dynamic DL MCS not supported"); srsran_assert(sched_cfg.fixed_ul_mcs >= 0, "Dynamic DL MCS not supported"); } diff --git a/srsgnb/src/stack/mac/sched_nr_grant_allocator.cc b/srsgnb/src/stack/mac/sched_nr_grant_allocator.cc index 0997aaccd..8eec56878 100644 --- a/srsgnb/src/stack/mac/sched_nr_grant_allocator.cc +++ b/srsgnb/src/stack/mac/sched_nr_grant_allocator.cc @@ -22,6 +22,7 @@ #include "srsgnb/hdr/stack/mac/sched_nr_grant_allocator.h" #include "srsgnb/hdr/stack/mac/sched_nr_bwp.h" #include "srsgnb/hdr/stack/mac/sched_nr_helpers.h" +#include "srsran/mac/mac_sch_pdu_nr.h" namespace srsenb { namespace sched_nr_impl { @@ -335,9 +336,29 @@ alloc_result bwp_slot_allocator::alloc_pdsch(slot_ue& ue, uint32_t ss_id, const // Allocate PDSCH pdsch_t& pdsch = bwp_pdcch_slot.pdschs.alloc_ue_pdsch_unchecked(ss_id, dci_fmt, dl_grant, ue.cfg(), pdcch.dci); - // Allocate HARQ - int mcs = ue->fixed_pdsch_mcs(); + // Select MCS and Allocate HARQ + int mcs = ue->fixed_pdsch_mcs(); + const static int min_MCS_ccch = 4; if (ue.h_dl->empty()) { + if (mcs < 0) { + mcs = srsran_ra_nr_cqi_to_mcs(/* cqi */ ue.dl_cqi(), + /* cqi_table_idx */ ue.cfg().phy().csi.reports->cqi_table, + /* mcs_table */ pdsch.sch.sch_cfg.mcs_table, + /* dci_format */ pdcch.dci.ctx.format, + /* search_space_type*/ pdcch.dci.ctx.ss_type, + /* rnti_type */ rnti_type); + if (mcs < 0) { + logger.warning("SCHED: UE rnti=0x%x reported CQI=0 - Using lowest MCS=0", ue->rnti); + mcs = 0; + } + } + // Overwrite MCS if there are pending bytes for LCID. The optimal way would be to verify that there are pending + // bytes and that the MAC SDU for CCCH gets segmented. But since the event of segmentation happens at most a couple + // of times (e.g., to send msg4/RRCSetup), we opt for the less optimal but simpler approach. + if (ue.get_pending_bytes(srsran::mac_sch_subpdu_nr::nr_lcid_sch_t::CCCH) and mcs < min_MCS_ccch) { + mcs = min_MCS_ccch; + logger.info("SCHED: MCS increased to min value %d to allocate SRB0/CCCH for rnti=0x%x", min_MCS_ccch, ue->rnti); + } bool success = ue.h_dl->new_tx(ue.pdsch_slot, ue.uci_slot, dl_grant, mcs, 4, pdcch.dci); srsran_assert(success, "Failed to allocate DL HARQ"); } else { @@ -350,6 +371,9 @@ alloc_result bwp_slot_allocator::alloc_pdsch(slot_ue& ue, uint32_t ss_id, const slot_cfg.idx = ue.pdsch_slot.to_uint(); // Value 0.95 is from TS 38.214 v15.14.00, Section 5.1.3, page 17 const static float max_R = 0.95; + double R_prime; + // The purpose of the internal loop is to decrease the MCS if the effective coderate is too high. This loop + // only affects the high MCS values while (true) { // Generate PDSCH bool success = ue->phy().get_pdsch_cfg(slot_cfg, pdcch.dci, pdsch.sch); @@ -357,16 +381,19 @@ alloc_result bwp_slot_allocator::alloc_pdsch(slot_ue& ue, uint32_t ss_id, const if (ue.h_dl->nof_retx() != 0) { srsran_assert(pdsch.sch.grant.tb[0].tbs == (int)ue.h_dl->tbs(), "The TBS did not remain constant in retx"); } - if (ue.h_dl->nof_retx() > 0 or pdsch.sch.grant.tb[0].R_prime < max_R or mcs <= 0) { + R_prime = pdsch.sch.grant.tb[0].R_prime; + if (ue.h_dl->nof_retx() > 0 or R_prime < max_R or mcs <= 0 or + (ue.get_pending_bytes(srsran::mac_sch_subpdu_nr::nr_lcid_sch_t::CCCH) and mcs <= min_MCS_ccch)) { break; } // Decrease MCS if first tx and rate is too high mcs--; pdcch.dci.mcs = mcs; } - if (mcs == 0) { + if (R_prime >= max_R and mcs == 0) { logger.warning("Couldn't find mcs that leads to R<0.95"); } + ue.h_dl->set_mcs(mcs); ue.h_dl->set_tbs(pdsch.sch.grant.tb[0].tbs); // set HARQ TBS pdsch.sch.grant.tb[0].softbuffer.tx = ue.h_dl->get_softbuffer().get(); @@ -374,7 +401,11 @@ alloc_result bwp_slot_allocator::alloc_pdsch(slot_ue& ue, uint32_t ss_id, const // Select scheduled LCIDs and update UE buffer state bwp_pdsch_slot.dl.data.emplace_back(); - ue.build_pdu(ue.h_dl->tbs(), bwp_pdsch_slot.dl.data.back()); + // NOTE: ue.h_dl->tbs() has to be converted from bits to bytes + bool segmented_ccch_pdu = not ue.build_pdu(ue.h_dl->tbs() / 8, bwp_pdsch_slot.dl.data.back()); + if (segmented_ccch_pdu) { + logger.error("SCHED: Insufficient resources to allocate SRB0/CCCH for rnti=0x%x", min_MCS_ccch, ue->rnti); + } // Generate PUCCH bwp_uci_slot.pending_acks.emplace_back(); diff --git a/srsgnb/src/stack/mac/sched_nr_ue.cc b/srsgnb/src/stack/mac/sched_nr_ue.cc index d6bbfef34..4eea90480 100644 --- a/srsgnb/src/stack/mac/sched_nr_ue.cc +++ b/srsgnb/src/stack/mac/sched_nr_ue.cc @@ -36,8 +36,19 @@ int ue_buffer_manager::get_dl_tx_total() const return total_bytes; } -void ue_buffer_manager::pdu_builder::alloc_subpdus(uint32_t rem_bytes, sched_nr_interface::dl_pdu_t& pdu) +/** + * @brief Allocates LCIDs and update US buffer states depending on available resources and checks if there is SRB0/CCCH + MAC PDU segmentation + + * @param rem_bytes TBS to be filled with MAC CEs and MAC SDUs [in bytes] + * @param reset_buf_states If true, when there is SRB0/CCCH MAC PDU segmentation, restore the UE buffers and scheduled + LCIDs as before running this function + * @return true if there is no SRB0/CCCH MAC PDU segmentation, false otherwise + */ +bool ue_buffer_manager::pdu_builder::alloc_subpdus(uint32_t rem_bytes, sched_nr_interface::dl_pdu_t& pdu) { + // First step: allocate MAC CEs until resources allow + srsran::deque restore_ces; for (ce_t ce : parent->pending_ces) { if (ce.cc == cc) { // Note: This check also avoids thread collisions across UE carriers @@ -51,13 +62,21 @@ void ue_buffer_manager::pdu_builder::alloc_subpdus(uint32_t rem_bytes, sched_nr_ } } + // Second step: allocate the remaining LCIDs (LCIDs for MAC CEs are addressed above) for (uint32_t lcid = 0; rem_bytes > 0 and is_lcid_valid(lcid); ++lcid) { uint32_t pending_lcid_bytes = parent->get_dl_tx_total(lcid); + // Return false if the TBS is too small to store the entire CCCH buffer without segmentation + if (lcid == srsran::mac_sch_subpdu_nr::nr_lcid_sch_t::CCCH and pending_lcid_bytes > rem_bytes) { + pdu.subpdus.push_back(lcid); + return false; + } if (pending_lcid_bytes > 0) { rem_bytes -= std::min(rem_bytes, pending_lcid_bytes); pdu.subpdus.push_back(lcid); } } + + return true; } /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/srsgnb/src/stack/ngap/ngap.cc b/srsgnb/src/stack/ngap/ngap.cc index e8a5f8e70..4f187f5d0 100644 --- a/srsgnb/src/stack/ngap/ngap.cc +++ b/srsgnb/src/stack/ngap/ngap.cc @@ -311,7 +311,7 @@ ngap::ue* ngap::user_list::find_ue_gnbid(uint32_t gnbid) return (it != users.end()) ? it->second.get() : nullptr; } -ngap::ue* ngap::user_list::find_ue_amfid(uint32_t amfid) +ngap::ue* ngap::user_list::find_ue_amfid(uint64_t amfid) { auto it = std::find_if(users.begin(), users.end(), [amfid](const user_list::pair_type& v) { return v.second->ctxt.amf_ue_ngap_id == amfid; @@ -767,7 +767,7 @@ bool ngap::sctp_send_ngap_pdu(const asn1::ngap::ngap_pdu_c& tx_pdu, uint32_t rnt * @param amf_id amf_ue_ngap_id value stored in NGAP message * @return pointer to user if it has been found */ -ngap::ue* ngap::handle_ngapmsg_ue_id(uint32_t gnb_id, uint32_t amf_id) +ngap::ue* ngap::handle_ngapmsg_ue_id(uint32_t gnb_id, uint64_t amf_id) { ue* user_ptr = users.find_ue_gnbid(gnb_id); ue* user_amf_ptr = nullptr; diff --git a/srsgnb/src/stack/ngap/ngap_ue_proc.cc b/srsgnb/src/stack/ngap/ngap_ue_proc.cc index 4900d51e9..4e9282bf6 100644 --- a/srsgnb/src/stack/ngap/ngap_ue_proc.cc +++ b/srsgnb/src/stack/ngap/ngap_ue_proc.cc @@ -25,82 +25,9 @@ using namespace srsran; namespace srsenb { -ngap_ue_initial_context_setup_proc::ngap_ue_initial_context_setup_proc(ngap_interface_ngap_proc* parent_, - rrc_interface_ngap_nr* rrc_, - ngap_ue_ctxt_t* ue_ctxt_, - srslog::basic_logger& logger_) : - logger(logger_), parent(parent_), rrc(rrc_), ue_ctxt(ue_ctxt_){}; - -proc_outcome_t ngap_ue_initial_context_setup_proc::init(const asn1::ngap::init_context_setup_request_s& msg) -{ - ue_ctxt->amf_pointer = msg->guami.value.amf_pointer.to_number(); - ue_ctxt->amf_set_id = msg->guami.value.amf_set_id.to_number(); - ue_ctxt->amf_region_id = msg->guami.value.amf_region_id.to_number(); - - if (msg->ue_aggregate_maximum_bit_rate_present == true) { - rrc->ue_set_bitrates(ue_ctxt->rnti, msg->ue_aggregate_maximum_bit_rate.value); - } - rrc->ue_set_security_cfg_capabilities(ue_ctxt->rnti, msg->ue_security_cap.value); - rrc->ue_set_security_cfg_key(ue_ctxt->rnti, msg->security_key.value); - - if (msg->nas_pdu_present) { - srsran::unique_byte_buffer_t pdu = srsran::make_byte_buffer(); - if (pdu == nullptr) { - logger.error("Fatal Error: Couldn't allocate buffer in %s.", __FUNCTION__); - return proc_outcome_t::error; - } - memcpy(pdu->msg, msg->nas_pdu.value.data(), msg->nas_pdu.value.size()); - pdu->N_bytes = msg->nas_pdu.value.size(); - rrc->start_security_mode_procedure(ue_ctxt->rnti, std::move(pdu)); - } else { - rrc->start_security_mode_procedure(ue_ctxt->rnti, nullptr); - } - - return proc_outcome_t::yield; -}; - -proc_outcome_t ngap_ue_initial_context_setup_proc::react(bool rrc_reconf_outcome) -{ - if (rrc_reconf_outcome == true) { - parent->send_initial_ctxt_setup_response(); - return proc_outcome_t::success; - } - - return proc_outcome_t::error; -} - -proc_outcome_t ngap_ue_initial_context_setup_proc::step() -{ - return proc_outcome_t::yield; -} - -ngap_ue_ue_context_release_proc::ngap_ue_ue_context_release_proc(ngap_interface_ngap_proc* parent_, - rrc_interface_ngap_nr* rrc_, - ngap_ue_ctxt_t* ue_ctxt_, - ngap_ue_bearer_manager* bearer_manager_, - srslog::basic_logger& logger_) : - logger(logger_) -{ - parent = parent_; - rrc = rrc_; - ue_ctxt = ue_ctxt_; - bearer_manager = bearer_manager_; -}; - -proc_outcome_t ngap_ue_ue_context_release_proc::init(const asn1::ngap::ue_context_release_cmd_s& msg) -{ - logger.info("Started %s", name()); - bearer_manager->reset_pdu_sessions(ue_ctxt->rnti); - rrc->release_user(ue_ctxt->rnti); - parent->send_ue_ctxt_release_complete(); - return proc_outcome_t::success; -} - -proc_outcome_t ngap_ue_ue_context_release_proc::step() -{ - return proc_outcome_t::success; -} - +/* + * TS 38.413 - Section 8.2.1 PDU Session Resource Setup + */ ngap_ue_pdu_session_res_setup_proc::ngap_ue_pdu_session_res_setup_proc(ngap_interface_ngap_proc* parent_, rrc_interface_ngap_nr* rrc_, ngap_ue_ctxt_t* ue_ctxt_, @@ -168,15 +95,19 @@ proc_outcome_t ngap_ue_pdu_session_res_setup_proc::init(const asn1::ngap::pdu_se return proc_outcome_t::error; } - logger.info("Added PDU Session with LCID %d, teid_out %d, teid_in %d, addr_in %s", + uint16_t five_qi = pdu_ses_res_setup_req_trans->qos_flow_setup_request_list.value[0] + .qos_flow_level_qos_params.qos_characteristics.non_dynamic5_qi() + .five_qi; + + logger.info("Added PDU Session with LCID %d, 5QI %d, teid_out %d, teid_in %d, addr_in %s", lcid, + five_qi, teid_out, teid_in, addr_in.to_string()); - // QoS parameter mapping in config in LTE enb if (su_req.pdu_session_nas_pdu.size() > 0) { - if (rrc->establish_rrc_bearer(ue_ctxt->rnti, su_req.pdu_session_id, su_req.pdu_session_nas_pdu, lcid) == + if (rrc->establish_rrc_bearer(ue_ctxt->rnti, su_req.pdu_session_id, su_req.pdu_session_nas_pdu, lcid, five_qi) == SRSRAN_SUCCESS) { parent->send_pdu_session_resource_setup_response(su_req.pdu_session_id, teid_in, addr_in); return proc_outcome_t::success; @@ -191,4 +122,86 @@ proc_outcome_t ngap_ue_pdu_session_res_setup_proc::step() return proc_outcome_t::success; } +/* + * TS 38.413 - Section 8.3.1 - Initial Context Setup + */ +ngap_ue_initial_context_setup_proc::ngap_ue_initial_context_setup_proc(ngap_interface_ngap_proc* parent_, + rrc_interface_ngap_nr* rrc_, + ngap_ue_ctxt_t* ue_ctxt_, + srslog::basic_logger& logger_) : + logger(logger_), parent(parent_), rrc(rrc_), ue_ctxt(ue_ctxt_){}; + +proc_outcome_t ngap_ue_initial_context_setup_proc::init(const asn1::ngap::init_context_setup_request_s& msg) +{ + ue_ctxt->amf_pointer = msg->guami.value.amf_pointer.to_number(); + ue_ctxt->amf_set_id = msg->guami.value.amf_set_id.to_number(); + ue_ctxt->amf_region_id = msg->guami.value.amf_region_id.to_number(); + + if (msg->ue_aggregate_maximum_bit_rate_present == true) { + rrc->ue_set_bitrates(ue_ctxt->rnti, msg->ue_aggregate_maximum_bit_rate.value); + } + rrc->ue_set_security_cfg_capabilities(ue_ctxt->rnti, msg->ue_security_cap.value); + rrc->ue_set_security_cfg_key(ue_ctxt->rnti, msg->security_key.value); + + if (msg->nas_pdu_present) { + srsran::unique_byte_buffer_t pdu = srsran::make_byte_buffer(); + if (pdu == nullptr) { + logger.error("Fatal Error: Couldn't allocate buffer in %s.", __FUNCTION__); + return proc_outcome_t::error; + } + memcpy(pdu->msg, msg->nas_pdu.value.data(), msg->nas_pdu.value.size()); + pdu->N_bytes = msg->nas_pdu.value.size(); + rrc->start_security_mode_procedure(ue_ctxt->rnti, std::move(pdu)); + } else { + rrc->start_security_mode_procedure(ue_ctxt->rnti, nullptr); + } + + return proc_outcome_t::yield; +}; + +proc_outcome_t ngap_ue_initial_context_setup_proc::react(bool rrc_reconf_outcome) +{ + if (rrc_reconf_outcome == true) { + parent->send_initial_ctxt_setup_response(); + return proc_outcome_t::success; + } + + return proc_outcome_t::error; +} + +proc_outcome_t ngap_ue_initial_context_setup_proc::step() +{ + return proc_outcome_t::yield; +} + +/* + * TS 38.413 - Section 8.3.2 - UE Context Release Request (NG-RAN node initiated) + */ +ngap_ue_ue_context_release_proc::ngap_ue_ue_context_release_proc(ngap_interface_ngap_proc* parent_, + rrc_interface_ngap_nr* rrc_, + ngap_ue_ctxt_t* ue_ctxt_, + ngap_ue_bearer_manager* bearer_manager_, + srslog::basic_logger& logger_) : + logger(logger_) +{ + parent = parent_; + rrc = rrc_; + ue_ctxt = ue_ctxt_; + bearer_manager = bearer_manager_; +}; + +proc_outcome_t ngap_ue_ue_context_release_proc::init(const asn1::ngap::ue_context_release_cmd_s& msg) +{ + logger.info("Started %s", name()); + bearer_manager->reset_pdu_sessions(ue_ctxt->rnti); + rrc->release_user(ue_ctxt->rnti); + parent->send_ue_ctxt_release_complete(); + return proc_outcome_t::success; +} + +proc_outcome_t ngap_ue_ue_context_release_proc::step() +{ + return proc_outcome_t::success; +} + } // namespace srsenb diff --git a/srsgnb/src/stack/ngap/test/ngap_test.cc b/srsgnb/src/stack/ngap/test/ngap_test.cc index 6993c7521..57351de6a 100644 --- a/srsgnb/src/stack/ngap/test/ngap_test.cc +++ b/srsgnb/src/stack/ngap/test/ngap_test.cc @@ -117,7 +117,11 @@ public: sec_mod_proc_started = true; return SRSRAN_SUCCESS; } - int establish_rrc_bearer(uint16_t rnti, uint16_t pdu_session_id, srsran::const_byte_span nas_pdu, uint32_t lcid) + int establish_rrc_bearer(uint16_t rnti, + uint16_t pdu_session_id, + srsran::const_byte_span nas_pdu, + uint32_t lcid, + uint32_t five_qi) { rrc_logger.info("Establish RRC bearer"); return SRSRAN_SUCCESS; diff --git a/srsgnb/src/stack/rrc/cell_asn1_config.cc b/srsgnb/src/stack/rrc/cell_asn1_config.cc index e70564781..398c02fd0 100644 --- a/srsgnb/src/stack/rrc/cell_asn1_config.cc +++ b/srsgnb/src/stack/rrc/cell_asn1_config.cc @@ -182,7 +182,7 @@ int fill_csi_report_from_enb_cfg(const rrc_nr_cfg_t& cfg, csi_meas_cfg_s& csi_me csi_report.group_based_beam_report.set_disabled(); // Skip CQI table (optional) csi_report.cqi_table_present = true; - csi_report.cqi_table = asn1::rrc_nr::csi_report_cfg_s::cqi_table_opts::table2; + csi_report.cqi_table = asn1::rrc_nr::csi_report_cfg_s::cqi_table_opts::table1; csi_report.subband_size = asn1::rrc_nr::csi_report_cfg_s::subband_size_opts::value1; if (cfg.cell_list[0].duplex_mode == SRSRAN_DUPLEX_MODE_FDD) { @@ -528,6 +528,11 @@ void fill_pdsch_cfg_from_enb_cfg(const rrc_nr_cfg_t& cfg, uint32_t cc, pdsch_cfg out.prb_bundling_type.static_bundling().bundle_size = pdsch_cfg_s::prb_bundling_type_c_::static_bundling_s_::bundle_size_opts::wideband; + // MCS Table + // NOTE: For Table 1 or QAM64, set false and comment value + // out.mcs_table_present = true; + // out.mcs_table.value = pdsch_cfg_s::mcs_table_opts::qam256; + // ZP-CSI out.zp_csi_rs_res_to_add_mod_list.resize(1); out.zp_csi_rs_res_to_add_mod_list[0].zp_csi_rs_res_id = 0; @@ -982,20 +987,17 @@ void fill_srb(const rrc_nr_cfg_t& cfg, srsran::nr_srb srb_id, asn1::rrc_nr::rlc_ } /// Fill DRB with parameters derived from cfg -void fill_drb(const rrc_nr_cfg_t& cfg, uint32_t lcid, srsran::nr_drb drb_id, asn1::rrc_nr::rlc_bearer_cfg_s& out) +void fill_drb(const rrc_nr_cfg_t& cfg, + const enb_bearer_manager::radio_bearer_t& rb, + srsran::nr_drb drb_id, + asn1::rrc_nr::rlc_bearer_cfg_s& out) { - out.lc_ch_id = lcid; + out.lc_ch_id = rb.lcid; out.served_radio_bearer_present = true; out.served_radio_bearer.set_drb_id() = (uint8_t)drb_id; - out.rlc_cfg_present = true; - auto& ul_um = out.rlc_cfg.set_um_bi_dir().ul_um_rlc; - ul_um.sn_field_len_present = true; - ul_um.sn_field_len.value = sn_field_len_um_opts::size12; - auto& dl_um = out.rlc_cfg.um_bi_dir().dl_um_rlc; - dl_um.sn_field_len_present = true; - dl_um.sn_field_len.value = sn_field_len_um_opts::size12; - dl_um.t_reassembly.value = t_reassembly_opts::ms50; + out.rlc_cfg_present = true; + out.rlc_cfg = cfg.five_qi_cfg.at(rb.five_qi).rlc_cfg; // MAC logical channel config out.mac_lc_ch_cfg_present = true; @@ -1323,6 +1325,8 @@ bool compute_diff_radio_bearer_cfg(const rrc_nr_cfg_t& cfg, } void fill_cellgroup_with_radio_bearer_cfg(const rrc_nr_cfg_t& cfg, + const uint32_t rnti, + const enb_bearer_manager& bearer_mapper, const asn1::rrc_nr::radio_bearer_cfg_s& bearers, asn1::rrc_nr::cell_group_cfg_s& out) { @@ -1337,8 +1341,9 @@ void fill_cellgroup_with_radio_bearer_cfg(const rrc_nr_cfg_t& // Add DRBs for (const drb_to_add_mod_s& drb : bearers.drb_to_add_mod_list) { out.rlc_bearer_to_add_mod_list.push_back({}); - uint32_t lcid = drb.drb_id + (int)srsran::nr_srb::count - 1; - fill_drb(cfg, lcid, (srsran::nr_drb)drb.drb_id, out.rlc_bearer_to_add_mod_list.back()); + uint32_t lcid = drb.drb_id + (int)srsran::nr_srb::count - 1; + enb_bearer_manager::radio_bearer_t rb = bearer_mapper.get_lcid_bearer(rnti, lcid); + fill_drb(cfg, rb, (srsran::nr_drb)drb.drb_id, out.rlc_bearer_to_add_mod_list.back()); } // Release DRBs diff --git a/srsgnb/src/stack/rrc/rrc_nr.cc b/srsgnb/src/stack/rrc/rrc_nr.cc index 03d07bca9..5a13e9479 100644 --- a/srsgnb/src/stack/rrc/rrc_nr.cc +++ b/srsgnb/src/stack/rrc/rrc_nr.cc @@ -102,14 +102,26 @@ int rrc_nr::init(const rrc_nr_cfg_t& cfg_, config_phy(); // if PHY is not yet initialized, config will be stored and applied on initialization config_mac(); - logger.debug("NIA preference list: NIA%d, NIA%d, NIA%d", - cfg.nia_preference_list[0], - cfg.nia_preference_list[1], - cfg.nia_preference_list[2]); - logger.debug("NEA preference list: NEA%d, NEA%d, NEA%d", - cfg.nea_preference_list[0], - cfg.nea_preference_list[1], - cfg.nea_preference_list[2]); + logger.info("Number of 5QI %d", cfg.five_qi_cfg.size()); + for (const std::pair& five_qi_cfg : cfg.five_qi_cfg) { + logger.info("5QI configuration. 5QI=%d", five_qi_cfg.first); + if (logger.info.enabled()) { + asn1::json_writer js{}; + five_qi_cfg.second.pdcp_cfg.to_json(js); + logger.info("PDCP NR configuration: %s", js.to_string().c_str()); + js = {}; + five_qi_cfg.second.rlc_cfg.to_json(js); + logger.info("RLC NR configuration: %s", js.to_string().c_str()); + } + } + logger.info("NIA preference list: NIA%d, NIA%d, NIA%d", + cfg.nia_preference_list[0], + cfg.nia_preference_list[1], + cfg.nia_preference_list[2]); + logger.info("NEA preference list: NEA%d, NEA%d, NEA%d", + cfg.nea_preference_list[0], + cfg.nea_preference_list[1], + cfg.nea_preference_list[2]); running = true; return SRSRAN_SUCCESS; @@ -551,6 +563,9 @@ void rrc_nr::handle_ul_dcch(uint16_t rnti, uint32_t lcid, srsran::const_byte_spa case ul_dcch_msg_type_c::c1_c_::types_opts::rrc_reest_complete: u.handle_rrc_reestablishment_complete(ul_dcch_msg.msg.c1().rrc_reest_complete()); break; + case ul_dcch_msg_type_c::c1_c_::types_opts::ue_cap_info: + u.handle_ue_capability_information(ul_dcch_msg.msg.c1().ue_cap_info()); + break; default: log_rx_pdu_fail(rnti, srb_to_lcid(lte_srb::srb0), pdu, "Unsupported UL-CCCH message type", false); // TODO Remove user @@ -645,14 +660,18 @@ int rrc_nr::start_security_mode_procedure(uint16_t rnti, srsran::unique_byte_buf user_it->second->send_security_mode_command(std::move(nas_pdu)); return SRSRAN_SUCCESS; } -int rrc_nr::establish_rrc_bearer(uint16_t rnti, uint16_t pdu_session_id, srsran::const_byte_span nas_pdu, uint32_t lcid) +int rrc_nr::establish_rrc_bearer(uint16_t rnti, + uint16_t pdu_session_id, + srsran::const_byte_span nas_pdu, + uint32_t lcid, + uint32_t five_qi) { if (not users.contains(rnti)) { logger.error("Establishing RRC bearers for inexistent rnti=0x%x", rnti); return SRSRAN_ERROR; } - users[rnti]->establish_eps_bearer(pdu_session_id, nas_pdu, lcid); + users[rnti]->establish_eps_bearer(pdu_session_id, nas_pdu, lcid, five_qi); // TODO: verify whether this is the best place where to call the RRCReconfig users[rnti]->send_rrc_reconfiguration(); diff --git a/srsgnb/src/stack/rrc/rrc_nr_ue.cc b/srsgnb/src/stack/rrc/rrc_nr_ue.cc index 0d643e3f0..31b0c7a59 100644 --- a/srsgnb/src/stack/rrc/rrc_nr_ue.cc +++ b/srsgnb/src/stack/rrc/rrc_nr_ue.cc @@ -172,55 +172,6 @@ int rrc_nr::ue::send_dl_dcch(srsran::nr_srb srb, const asn1::rrc_nr::dl_dcch_msg return SRSRAN_SUCCESS; } -int rrc_nr::ue::pack_secondary_cell_group_rlc_cfg(asn1::rrc_nr::cell_group_cfg_s& cell_group_cfg_pack) -{ - // RLC for DRB1 (with fixed LCID) - cell_group_cfg_pack.rlc_bearer_to_add_mod_list.resize(1); - auto& rlc_bearer = cell_group_cfg_pack.rlc_bearer_to_add_mod_list[0]; - rlc_bearer.lc_ch_id = drb1_lcid; - rlc_bearer.served_radio_bearer_present = true; - rlc_bearer.served_radio_bearer.set_drb_id(); - rlc_bearer.served_radio_bearer.drb_id() = 1; - rlc_bearer.rlc_cfg_present = true; - -#ifdef USE_RLC_AM_NR - rlc_bearer.rlc_cfg.set_am(); - rlc_bearer.rlc_cfg.am().ul_am_rlc.sn_field_len_present = true; - rlc_bearer.rlc_cfg.am().ul_am_rlc.sn_field_len = sn_field_len_am_opts::size12; - rlc_bearer.rlc_cfg.am().dl_am_rlc.sn_field_len_present = true; - rlc_bearer.rlc_cfg.am().dl_am_rlc.sn_field_len = sn_field_len_am_opts::size12; - - rlc_bearer.rlc_cfg.am().ul_am_rlc.t_poll_retx = t_poll_retx_opts::ms500; - rlc_bearer.rlc_cfg.am().ul_am_rlc.poll_pdu = poll_pdu_opts::p8; - rlc_bearer.rlc_cfg.am().ul_am_rlc.poll_byte = poll_byte_opts::infinity; - rlc_bearer.rlc_cfg.am().ul_am_rlc.max_retx_thres = ul_am_rlc_s::max_retx_thres_opts::t8; - rlc_bearer.rlc_cfg.am().dl_am_rlc.t_reassembly = t_reassembly_opts::ms50; - rlc_bearer.rlc_cfg.am().dl_am_rlc.t_status_prohibit = t_status_prohibit_opts::ms50; -#else - rlc_bearer.rlc_cfg.set_um_bi_dir(); - rlc_bearer.rlc_cfg.um_bi_dir().ul_um_rlc.sn_field_len_present = true; - rlc_bearer.rlc_cfg.um_bi_dir().ul_um_rlc.sn_field_len = sn_field_len_um_opts::size12; - rlc_bearer.rlc_cfg.um_bi_dir().dl_um_rlc.sn_field_len_present = true; - rlc_bearer.rlc_cfg.um_bi_dir().dl_um_rlc.sn_field_len = sn_field_len_um_opts::size12; - rlc_bearer.rlc_cfg.um_bi_dir().dl_um_rlc.t_reassembly = t_reassembly_opts::ms50; -#endif - - // MAC logical channel config - rlc_bearer.mac_lc_ch_cfg_present = true; - rlc_bearer.mac_lc_ch_cfg.ul_specific_params_present = true; - rlc_bearer.mac_lc_ch_cfg.ul_specific_params.prio = 11; - rlc_bearer.mac_lc_ch_cfg.ul_specific_params.prioritised_bit_rate = - asn1::rrc_nr::lc_ch_cfg_s::ul_specific_params_s_::prioritised_bit_rate_opts::kbps0; - rlc_bearer.mac_lc_ch_cfg.ul_specific_params.bucket_size_dur = - asn1::rrc_nr::lc_ch_cfg_s::ul_specific_params_s_::bucket_size_dur_opts::ms100; - rlc_bearer.mac_lc_ch_cfg.ul_specific_params.lc_ch_group_present = true; - rlc_bearer.mac_lc_ch_cfg.ul_specific_params.lc_ch_group = 6; - rlc_bearer.mac_lc_ch_cfg.ul_specific_params.sched_request_id_present = true; - rlc_bearer.mac_lc_ch_cfg.ul_specific_params.sched_request_id = 0; - - return SRSRAN_SUCCESS; -} - int rrc_nr::ue::pack_secondary_cell_group_mac_cfg(asn1::rrc_nr::cell_group_cfg_s& cell_group_cfg_pack) { // mac-CellGroup-Config for BSR and SR @@ -633,7 +584,6 @@ int rrc_nr::ue::pack_secondary_cell_group_cfg(asn1::dyn_octstring& packed_second { auto& cell_group_cfg_pack = cell_group_cfg; - pack_secondary_cell_group_rlc_cfg(cell_group_cfg_pack); pack_secondary_cell_group_mac_cfg(cell_group_cfg_pack); pack_secondary_cell_group_sp_cell_cfg(cell_group_cfg_pack); @@ -709,7 +659,7 @@ int rrc_nr::ue::pack_nr_radio_bearer_config(asn1::dyn_octstring& packed_nr_beare int rrc_nr::ue::handle_sgnb_addition_request(uint16_t eutra_rnti_, const sgnb_addition_req_params_t& req_params) { // Add DRB1 to RLC and PDCP - if (add_drb() != SRSRAN_SUCCESS) { + if (add_drb(req_params.five_qi) != SRSRAN_SUCCESS) { parent->logger.error("Failed to configure DRB"); parent->rrc_eutra->sgnb_addition_reject(eutra_rnti_); return SRSRAN_ERROR; @@ -782,10 +732,16 @@ void rrc_nr::ue::crnti_ce_received() * The function sets and configures all relavant fields for the DRB configuration (MAC, RLC, PDCP) in the * cellGroupConfig and also adds the bearer to the local RLC and PDCP entities. * + * @param int 5QI of the DRB to be added * @return int SRSRAN_SUCCESS on success */ -int rrc_nr::ue::add_drb() +int rrc_nr::ue::add_drb(uint32_t five_qi) { + if (parent->cfg.five_qi_cfg.find(five_qi) == parent->cfg.five_qi_cfg.end()) { + parent->logger.error("No bearer config for 5QI %d present. Aborting DRB addition.", five_qi); + return SRSRAN_ERROR; + } + // RLC for DRB1 (with fixed LCID) inside cell_group_cfg auto& cell_group_cfg_pack = cell_group_cfg; @@ -796,21 +752,7 @@ int rrc_nr::ue::add_drb() rlc_bearer.served_radio_bearer.set_drb_id(); rlc_bearer.served_radio_bearer.drb_id() = 1; rlc_bearer.rlc_cfg_present = true; - -#ifdef USE_RLC_AM_NR - rlc_bearer.rlc_cfg.set_am(); - rlc_bearer.rlc_cfg.am().ul_am_rlc.sn_field_len_present = true; - rlc_bearer.rlc_cfg.am().ul_am_rlc.sn_field_len = sn_field_len_am_opts::size12; - rlc_bearer.rlc_cfg.am().dl_am_rlc.sn_field_len_present = true; - rlc_bearer.rlc_cfg.am().dl_am_rlc.sn_field_len = sn_field_len_am_opts::size12; -#else - rlc_bearer.rlc_cfg.set_um_bi_dir(); - rlc_bearer.rlc_cfg.um_bi_dir().ul_um_rlc.sn_field_len_present = true; - rlc_bearer.rlc_cfg.um_bi_dir().ul_um_rlc.sn_field_len = sn_field_len_um_opts::size12; - rlc_bearer.rlc_cfg.um_bi_dir().dl_um_rlc.sn_field_len_present = true; - rlc_bearer.rlc_cfg.um_bi_dir().dl_um_rlc.sn_field_len = sn_field_len_um_opts::size12; - rlc_bearer.rlc_cfg.um_bi_dir().dl_um_rlc.t_reassembly = t_reassembly_opts::ms50; -#endif + rlc_bearer.rlc_cfg = parent->cfg.five_qi_cfg[five_qi].rlc_cfg; // add RLC bearer srsran::rlc_config_t rlc_cfg; @@ -847,17 +789,7 @@ int rrc_nr::ue::add_drb() drb_item.cn_assoc_present = true; drb_item.cn_assoc.set_eps_bearer_id() = 5; drb_item.pdcp_cfg_present = true; - drb_item.pdcp_cfg.ciphering_disabled_present = true; - drb_item.pdcp_cfg.drb_present = true; - drb_item.pdcp_cfg.drb.pdcp_sn_size_dl_present = true; - drb_item.pdcp_cfg.drb.pdcp_sn_size_dl = asn1::rrc_nr::pdcp_cfg_s::drb_s_::pdcp_sn_size_dl_opts::len18bits; - drb_item.pdcp_cfg.drb.pdcp_sn_size_ul_present = true; - drb_item.pdcp_cfg.drb.pdcp_sn_size_ul = asn1::rrc_nr::pdcp_cfg_s::drb_s_::pdcp_sn_size_ul_opts::len18bits; - drb_item.pdcp_cfg.drb.discard_timer_present = true; - drb_item.pdcp_cfg.drb.discard_timer = asn1::rrc_nr::pdcp_cfg_s::drb_s_::discard_timer_opts::ms100; - drb_item.pdcp_cfg.drb.hdr_compress.set_not_used(); - drb_item.pdcp_cfg.t_reordering_present = true; - drb_item.pdcp_cfg.t_reordering = asn1::rrc_nr::pdcp_cfg_s::t_reordering_opts::ms0; + drb_item.pdcp_cfg = parent->cfg.five_qi_cfg[five_qi].pdcp_cfg; // Add DRB1 to PDCP srsran::pdcp_config_t pdcp_cnfg = srsran::make_drb_pdcp_config_t(drb_item.drb_id, false, drb_item.pdcp_cfg); @@ -977,7 +909,8 @@ void rrc_nr::ue::handle_rrc_reestablishment_request(const asn1::rrc_nr::rrc_rees // compute config and create SRB1 for new user asn1::rrc_nr::radio_bearer_cfg_s dummy_radio_bearer_cfg; // just to compute difference, it's never sent to UE compute_diff_radio_bearer_cfg(parent->cfg, radio_bearer_cfg, next_radio_bearer_cfg, dummy_radio_bearer_cfg); - fill_cellgroup_with_radio_bearer_cfg(parent->cfg, dummy_radio_bearer_cfg, next_cell_group_cfg); + fill_cellgroup_with_radio_bearer_cfg( + parent->cfg, old_rnti, *parent->bearer_mapper, dummy_radio_bearer_cfg, next_cell_group_cfg); // send RRC Reestablishment message and restore bearer configuration send_connection_reest(old_ue->sec_ctx.get_ncc()); @@ -1070,7 +1003,8 @@ void rrc_nr::ue::send_rrc_setup() // - Setup masterCellGroup // - Derive master cell group config bearers - fill_cellgroup_with_radio_bearer_cfg(parent->cfg, setup_ies.radio_bearer_cfg, next_cell_group_cfg); + fill_cellgroup_with_radio_bearer_cfg( + parent->cfg, rnti, *parent->bearer_mapper, setup_ies.radio_bearer_cfg, next_cell_group_cfg); // - Pack masterCellGroup into container srsran::unique_byte_buffer_t pdu = parent->pack_into_pdu(next_cell_group_cfg, __FUNCTION__); if (pdu == nullptr) { @@ -1191,13 +1125,7 @@ void rrc_nr::ue::handle_security_mode_complete(const asn1::rrc_nr::security_mode // finally, also enable ciphering on SRB1 update_as_security(srb_to_lcid(srsran::nr_srb::srb1), false, true); - send_rrc_reconfiguration(); - // Note: Skip UE capabilities - - // Send RRCReconfiguration if necessary - if (not nas_pdu_queue.empty()) { - send_rrc_reconfiguration(); - } + send_ue_capability_enquiry(); } /// TS 38.331, RRCReconfiguration @@ -1218,7 +1146,8 @@ void rrc_nr::ue::send_rrc_reconfiguration() // Fill masterCellGroup cell_group_cfg_s master_cell_group; master_cell_group.cell_group_id = 0; - fill_cellgroup_with_radio_bearer_cfg(parent->cfg, ies.radio_bearer_cfg, master_cell_group); + fill_cellgroup_with_radio_bearer_cfg( + parent->cfg, rnti, *parent->bearer_mapper, ies.radio_bearer_cfg, master_cell_group); // Pack masterCellGroup into container srsran::unique_byte_buffer_t pdu = parent->pack_into_pdu(master_cell_group, __FUNCTION__); @@ -1263,6 +1192,57 @@ void rrc_nr::ue::send_rrc_reconfiguration() } } +int rrc_nr::ue::send_ue_capability_enquiry() +{ + dl_dcch_msg_s dl_dcch_msg; + dl_dcch_msg.msg.set_c1().set_ue_cap_enquiry().rrc_transaction_id = (uint8_t)((transaction_id++) % 4); + ue_cap_enquiry_ies_s& ies = dl_dcch_msg.msg.c1().ue_cap_enquiry().crit_exts.set_ue_cap_enquiry(); + + // ue-CapabilityRAT-RequestList + ue_cap_rat_request_s cap_rat_request; + cap_rat_request.rat_type.value = rat_type_opts::nr; + + // capabilityRequestFilter + ue_cap_request_filt_nr_s request_filter; + + // frequencyBandListFilter + freq_band_info_c freq_band_info; + freq_band_info_nr_s& freq_band_info_nr = freq_band_info.set_band_info_nr(); + + // Iterate through cell list and assign bandInformationNR items + for (auto& iter : parent->cfg.cell_list) { + freq_band_info_nr.band_nr = iter.band; + request_filter.freq_band_list_filt.push_back(freq_band_info); + } + + // Pack capabilityRequestFilter + cap_rat_request.cap_request_filt.resize(128); + asn1::bit_ref bref_pack(cap_rat_request.cap_request_filt.data(), cap_rat_request.cap_request_filt.size()); + if (request_filter.pack(bref_pack) != asn1::SRSASN_SUCCESS) { + logger.error("Failed to pack capabilityRequestFilter in UE Capability Enquiry"); + return SRSRAN_ERROR; + } + cap_rat_request.cap_request_filt.resize(bref_pack.distance_bytes()); + + ies.ue_cap_rat_request_list.push_back(cap_rat_request); + + send_dl_dcch(srsran::nr_srb::srb1, dl_dcch_msg); + + return SRSRAN_SUCCESS; +} + +void rrc_nr::ue::handle_ue_capability_information(const asn1::rrc_nr::ue_cap_info_s& msg) +{ + logger.info("UECapabilityInformation transaction ID: %d", msg.rrc_transaction_id); + + send_rrc_reconfiguration(); + + // Send RRCReconfiguration if necessary + if (not nas_pdu_queue.empty()) { + send_rrc_reconfiguration(); + } +} + void rrc_nr::ue::handle_rrc_reconfiguration_complete(const asn1::rrc_nr::rrc_recfg_complete_s& msg) { update_mac(next_cell_group_cfg, true); @@ -1325,7 +1305,10 @@ void rrc_nr::ue::handle_ul_information_transfer(const asn1::rrc_nr::ul_info_tran parent->ngap->write_pdu(rnti, msg.crit_exts.ul_info_transfer().ded_nas_msg); } -void rrc_nr::ue::establish_eps_bearer(uint32_t pdu_session_id, srsran::const_byte_span nas_pdu, uint32_t lcid) +void rrc_nr::ue::establish_eps_bearer(uint32_t pdu_session_id, + srsran::const_byte_span nas_pdu, + uint32_t lcid, + uint32_t five_qi) { // Enqueue NAS PDU srsran::unique_byte_buffer_t pdu = srsran::make_byte_buffer(); @@ -1352,23 +1335,15 @@ void rrc_nr::ue::establish_eps_bearer(uint32_t pdu_session_id, srsran::const_byt drb.cn_assoc.sdap_cfg().mapped_qos_flows_to_add.resize(1); drb.cn_assoc.sdap_cfg().mapped_qos_flows_to_add[0] = 1; - drb.drb_id = 1; - drb.pdcp_cfg_present = true; - drb.pdcp_cfg.drb_present = true; - drb.pdcp_cfg.drb.discard_timer_present = true; - drb.pdcp_cfg.drb.discard_timer.value = pdcp_cfg_s::drb_s_::discard_timer_opts::ms100; - drb.pdcp_cfg.drb.pdcp_sn_size_ul_present = true; - drb.pdcp_cfg.drb.pdcp_sn_size_ul.value = asn1::rrc_nr::pdcp_cfg_s::drb_s_::pdcp_sn_size_ul_opts::len18bits; - drb.pdcp_cfg.drb.pdcp_sn_size_dl_present = true; - drb.pdcp_cfg.drb.pdcp_sn_size_dl.value = asn1::rrc_nr::pdcp_cfg_s::drb_s_::pdcp_sn_size_dl_opts::len18bits; - drb.pdcp_cfg.drb.hdr_compress.set_not_used(); - drb.pdcp_cfg.t_reordering_present = true; - drb.pdcp_cfg.t_reordering.value = asn1::rrc_nr::pdcp_cfg_s::t_reordering_opts::ms0; + drb.drb_id = 1; + drb.pdcp_cfg_present = true; + drb.pdcp_cfg = parent->cfg.five_qi_cfg[five_qi].pdcp_cfg; next_radio_bearer_cfg.drb_to_add_mod_list.push_back(drb); parent->bearer_mapper->add_eps_bearer( rnti, lcid - 3, srsran::srsran_rat_t::nr, lcid); // TODO: configurable bearer id <-> lcid mapping + parent->bearer_mapper->set_five_qi(rnti, lcid - 3, five_qi); logger.info("Established EPS bearer for LCID %u and RNTI 0x%x", lcid, rnti); } @@ -1515,7 +1490,7 @@ int rrc_nr::ue::update_mac(const cell_group_cfg_s& cell_group_config, bool is_co void rrc_nr::ue::deactivate_bearers() { // Iterate over the bearers (MAC LC CH) and set each of them to IDLE - for (uint32_t lcid = 0; lcid < SCHED_NR_MAX_LCID; ++lcid) { + for (uint32_t lcid = 1; lcid < SCHED_NR_MAX_LCID; ++lcid) { uecfg.lc_ch_to_rem.push_back(lcid); } diff --git a/srsgnb/src/stack/rrc/test/rrc_nr_test.cc b/srsgnb/src/stack/rrc/test/rrc_nr_test.cc index 985017b9e..4bae59fd8 100644 --- a/srsgnb/src/stack/rrc/test/rrc_nr_test.cc +++ b/srsgnb/src/stack/rrc/test/rrc_nr_test.cc @@ -156,6 +156,11 @@ void test_rrc_sa_connection() rrc_nr rrc_obj(&task_sched); + // Dummy RLC/PDCP configs + asn1::rrc_nr::rlc_cfg_c rlc_cfg; + rlc_cfg.set_um_bi_dir(); + rlc_cfg.um_bi_dir().dl_um_rlc.t_reassembly = t_reassembly_e::ms50; + // set cfg rrc_nr_cfg_t rrc_cfg_nr; rrc_cfg_nr.cell_list.emplace_back(); @@ -167,6 +172,9 @@ void test_rrc_sa_connection() rrc_cfg_nr.cell_list[0].duplex_mode = SRSRAN_DUPLEX_MODE_FDD; rrc_cfg_nr.is_standalone = true; rrc_cfg_nr.enb_id = 0x19B; + rrc_cfg_nr.five_qi_cfg[9].configured = true; + rrc_cfg_nr.five_qi_cfg[9].rlc_cfg = rlc_cfg; + rrc_cfg_nr.five_qi_cfg[9].pdcp_cfg = {}; srsran::string_to_mcc("001", &rrc_cfg_nr.mcc); srsran::string_to_mnc("01", &rrc_cfg_nr.mnc); set_derived_nr_cell_params(rrc_cfg_nr.is_standalone, rrc_cfg_nr.cell_list[0]); @@ -181,6 +189,7 @@ void test_rrc_sa_connection() test_rrc_nr_connection_establishment(task_sched, rrc_obj, rlc_obj, mac_obj, ngap_obj, 0x4601); test_rrc_nr_info_transfer(task_sched, rrc_obj, pdcp_obj, ngap_obj, 0x4601); test_rrc_nr_security_mode_cmd(task_sched, rrc_obj, pdcp_obj, 0x4601); + test_rrc_nr_ue_capability_enquiry(task_sched, rrc_obj, pdcp_obj, 0x4601); test_rrc_nr_reconfiguration(task_sched, rrc_obj, pdcp_obj, ngap_obj, 0x4601); test_rrc_nr_2nd_reconfiguration(task_sched, rrc_obj, pdcp_obj, ngap_obj, 0x4601); } diff --git a/srsgnb/src/stack/rrc/test/rrc_nr_test_helpers.cc b/srsgnb/src/stack/rrc/test/rrc_nr_test_helpers.cc index 0b8aae4d8..cb5e090bb 100644 --- a/srsgnb/src/stack/rrc/test/rrc_nr_test_helpers.cc +++ b/srsgnb/src/stack/rrc/test/rrc_nr_test_helpers.cc @@ -109,7 +109,7 @@ void test_rrc_nr_connection_establishment(srsran::task_scheduler& task_sched, complete_ies.guami_type.value = rrc_setup_complete_ies_s::guami_type_opts::native; std::string NAS_msg_str = "7E01280E534C337E004109000BF200F110800101347B80802E02F07071002D7E004109000BF200F11080010134" "7B80801001002E02F0702F0201015200F11000006418010174000090530101"; - auto& ded_nas_msg = complete_ies.ded_nas_msg.from_string(NAS_msg_str); + auto& ded_nas_msg = complete_ies.ded_nas_msg.from_string(NAS_msg_str); { pdu = srsran::make_byte_buffer(); @@ -257,6 +257,41 @@ void test_rrc_nr_security_mode_cmd(srsran::task_scheduler& task_sched, rrc_obj.write_pdu(rnti, 1, std::move(pdu)); } +void test_rrc_nr_ue_capability_enquiry(srsran::task_scheduler& task_sched, + rrc_nr& rrc_obj, + pdcp_nr_rrc_tester& pdcp, + uint16_t rnti) +{ + dl_dcch_msg_s dl_dcch_msg; + { + asn1::cbit_ref bref{pdcp.last_sdu->data(), pdcp.last_sdu->size()}; + TESTASSERT_SUCCESS(dl_dcch_msg.unpack(bref)); + } + + // Check if unpacked message is correct (ueCapabilityEnquiry | gNB -> UE) + TESTASSERT_EQ(dl_dcch_msg_type_c::types_opts::c1, dl_dcch_msg.msg.type().value); + TESTASSERT_EQ(dl_dcch_msg_type_c::c1_c_::types_opts::ue_cap_enquiry, dl_dcch_msg.msg.c1().type().value); + TESTASSERT_EQ(ue_cap_enquiry_s::crit_exts_c_::types_opts::ue_cap_enquiry, + dl_dcch_msg.msg.c1().ue_cap_enquiry().crit_exts.type().value); + + // Send response (ueCapabilityInformation | UE -> gNB) + ul_dcch_msg_s ul_dcch_msg; + auto& ue_capability_information = ul_dcch_msg.msg.set_c1().set_ue_cap_info(); + ue_capability_information.rrc_transaction_id = dl_dcch_msg.msg.c1().ue_cap_enquiry().rrc_transaction_id; + ue_capability_information.crit_exts.set_ue_cap_info(); + + srsran::unique_byte_buffer_t pdu; + { + pdu = srsran::make_byte_buffer(); + asn1::bit_ref bref{pdu->data(), pdu->get_tailroom()}; + TESTASSERT_SUCCESS(ul_dcch_msg.pack(bref)); + pdu->N_bytes = bref.distance_bytes(); + } + + // send message to RRC + rrc_obj.write_pdu(rnti, 1, std::move(pdu)); +} + void test_rrc_nr_reconfiguration(srsran::task_scheduler& task_sched, rrc_nr& rrc_obj, pdcp_nr_rrc_tester& pdcp, @@ -323,14 +358,14 @@ void test_rrc_nr_2nd_reconfiguration(srsran::task_scheduler& task_sched, asn1::unbounded_octstring NAS_msg; NAS_msg.from_string("c574defc80ba722bffb8eacb6f8a163e3222cf1542ac529f6980bb15e0bf12d9f2b29f11fb458ec9"); - // STEP 2 - Trigger and send RRCReconfiguration command (gNB -> UE) - rrc_obj.establish_rrc_bearer(rnti, 1, NAS_msg, srsran::srb_to_lcid(srsran::nr_srb::srb1)); - // Test whether there exists the SRB1 initiated in the Connection Establishment // We test this as the SRB1 was set up in a different function TESTASSERT_EQ(rnti, pdcp.last_sdu_rnti); TESTASSERT_EQ(srsran::srb_to_lcid(srsran::nr_srb::srb1), pdcp.last_sdu_lcid); + // STEP 2 - Trigger and send RRCReconfiguration command (gNB -> UE) + rrc_obj.establish_rrc_bearer(rnti, 1, NAS_msg, 4, 9); + dl_dcch_msg_s dl_dcch_msg; { asn1::cbit_ref bref{pdcp.last_sdu->data(), pdcp.last_sdu->size()}; diff --git a/srsgnb/src/stack/rrc/test/rrc_nr_test_helpers.h b/srsgnb/src/stack/rrc/test/rrc_nr_test_helpers.h index e45a6e59b..9f4ed91d3 100644 --- a/srsgnb/src/stack/rrc/test/rrc_nr_test_helpers.h +++ b/srsgnb/src/stack/rrc/test/rrc_nr_test_helpers.h @@ -115,6 +115,11 @@ void test_rrc_nr_security_mode_cmd(srsran::task_scheduler& task_sched, pdcp_nr_rrc_tester& pdcp, uint16_t rnti); +void test_rrc_nr_ue_capability_enquiry(srsran::task_scheduler& task_sched, + rrc_nr& rrc_obj, + pdcp_nr_rrc_tester& pdcp, + uint16_t rnti); + void test_rrc_nr_reconfiguration(srsran::task_scheduler& task_sched, rrc_nr& rrc_obj, pdcp_nr_rrc_tester& pdcp, diff --git a/srsue/hdr/phy/nr/sync_sa.h b/srsue/hdr/phy/nr/sync_sa.h index e67b10f92..69784f759 100644 --- a/srsue/hdr/phy/nr/sync_sa.h +++ b/srsue/hdr/phy/nr/sync_sa.h @@ -88,6 +88,10 @@ public: void worker_end(const worker_context_t& w_ctx, const bool& tx_enable, srsran::rf_buffer_t& buffer) override; + void add_ta_cmd_rar(uint32_t tti, uint32_t ta_cmd); + void add_ta_cmd_new(uint32_t tti, uint32_t ta_cmd); + void add_ta_offset(uint32_t ta_offset); + private: stack_interface_phy_nr* stack = nullptr; ///< Stand-Alone RRC interface srsran::radio_interface_phy* radio = nullptr; ///< Radio object @@ -116,6 +120,9 @@ private: cell_search searcher; slot_sync slot_synchronizer; + // Time Aligment Controller, internal thread safe + ta_control ta; + // FSM States bool wait_idle(); void run_state_idle(); diff --git a/srsue/hdr/phy/phy_common.h b/srsue/hdr/phy/phy_common.h index 51d0dd250..9a40ddbc2 100644 --- a/srsue/hdr/phy/phy_common.h +++ b/srsue/hdr/phy/phy_common.h @@ -315,6 +315,8 @@ public: } } + srsran_cfr_cfg_t get_cfr_config() { return cfr_config; } + private: std::mutex meas_mutex; @@ -332,6 +334,8 @@ private: std::array avg_noise = {}; std::array avg_rsrp_neigh = {}; + srsran_cfr_cfg_t cfr_config = {}; + static constexpr uint32_t pcell_report_period = 20; static constexpr uint32_t update_rxgain_period = 10; diff --git a/srsue/hdr/phy/phy_nr_sa.h b/srsue/hdr/phy/phy_nr_sa.h index 3c6e154d1..9716e338a 100644 --- a/srsue/hdr/phy/phy_nr_sa.h +++ b/srsue/hdr/phy/phy_nr_sa.h @@ -59,6 +59,9 @@ public: const int preamble_index, const float preamble_received_target_power, const float ta_base_sec) final; + void set_timeadv_rar(uint32_t tti, uint32_t ta_cmd) final; + void set_timeadv(uint32_t tti, uint32_t ta_cmd) final; + void set_earfcn(std::vector earfcns); bool has_valid_sr_resource(uint32_t sr_id) final; void clear_pending_grants() final; diff --git a/srsue/hdr/phy/ta_control.h b/srsue/hdr/phy/ta_control.h index 9d97f736b..acf57ff4a 100644 --- a/srsue/hdr/phy/ta_control.h +++ b/srsue/hdr/phy/ta_control.h @@ -104,6 +104,19 @@ public: next_base_sec * 1e6f); } + void add_ta_offset(uint32_t ta_offset) + { + std::lock_guard lock(mutex); + + // Assuming numerology 0 + next_base_nta = ta_offset / 64; + + // Update base in seconds + next_base_sec = static_cast(next_base_nta) * SRSRAN_LTE_TS; + + logger.info("PHY: Set TA offset: n_ta_offset: %d, ta_usec: %.1f", next_base_nta, next_base_sec * 1e6f); + } + /** * Increments (delta) the next base time according to time alignment command from a Random Access Response (RAR). * diff --git a/srsue/hdr/stack/mac_nr/demux_nr.h b/srsue/hdr/stack/mac_nr/demux_nr.h index 31fad3ced..1e7e139f5 100644 --- a/srsue/hdr/stack/mac_nr/demux_nr.h +++ b/srsue/hdr/stack/mac_nr/demux_nr.h @@ -24,6 +24,7 @@ #include "mac_nr_interfaces.h" #include "srsran/common/block_queue.h" +#include "srsran/interfaces/ue_nr_interfaces.h" #include "srsran/interfaces/ue_rlc_interfaces.h" namespace srsue { @@ -44,11 +45,12 @@ public: demux_nr(srslog::basic_logger& logger_); ~demux_nr(); - int32_t init(rlc_interface_mac* rlc_); + int32_t init(rlc_interface_mac* rlc_, phy_interface_mac_nr* phy_); void process_pdus(); /// Called by MAC to process received PDUs // HARQ interface + void push_bcch(srsran::unique_byte_buffer_t pdu); void push_pdu(srsran::unique_byte_buffer_t pdu, uint32_t tti); void push_pdu_temp_crnti(srsran::unique_byte_buffer_t pdu, uint32_t tti); uint64_t get_received_crueid(); @@ -59,11 +61,13 @@ private: srslog::basic_logger& logger; rlc_interface_mac* rlc = nullptr; + phy_interface_mac_nr* phy = nullptr; uint64_t received_crueid = 0; - ///< currently only DCH PDUs supported (add BCH, PCH, etc) + ///< currently only DCH & BCH PDUs supported (add PCH, etc) srsran::block_queue pdu_queue; + srsran::block_queue bcch_queue; srsran::mac_sch_pdu_nr rx_pdu; srsran::mac_sch_pdu_nr rx_pdu_tcrnti; diff --git a/srsue/hdr/stack/mac_nr/mac_nr.h b/srsue/hdr/stack/mac_nr/mac_nr.h index a2826e394..c2969a0ce 100644 --- a/srsue/hdr/stack/mac_nr/mac_nr.h +++ b/srsue/hdr/stack/mac_nr/mac_nr.h @@ -101,6 +101,8 @@ public: uint16_t get_crnti(); uint16_t get_temp_crnti(); uint16_t get_csrnti() { return SRSRAN_INVALID_RNTI; }; // SPS not supported + void set_temp_crnti(uint16_t temp_crnti); + void set_crnti_to_temp(); /// procedure sr nr interface void start_ra() { proc_ra.start_by_mac(); } @@ -118,6 +120,7 @@ public: /// RRC void rrc_ra_problem(); void rrc_ra_completed(); + void bcch_search(bool enabled); /// stack interface void process_pdus(); @@ -138,6 +141,7 @@ private: bool is_paging_opportunity(); bool has_crnti(); + bool has_temp_crnti(); bool is_valid_crnti(const uint16_t crnti); std::vector logical_channels; // stores the raw configs provide by upper layers @@ -158,6 +162,9 @@ private: std::atomic started = {false}; + // Boolean to determine if need to decode SI-RNTI + std::atomic search_bcch = {false}; + ue_rnti rntis; // thread-safe helper to store RNTIs, contention ID, etc bool contention_res_successful; diff --git a/srsue/hdr/stack/mac_nr/mac_nr_interfaces.h b/srsue/hdr/stack/mac_nr/mac_nr_interfaces.h index 54056b667..cbc8bd8e7 100644 --- a/srsue/hdr/stack/mac_nr/mac_nr_interfaces.h +++ b/srsue/hdr/stack/mac_nr/mac_nr_interfaces.h @@ -35,6 +35,8 @@ public: // Functions for identity handling, e.g., contention id and c-rnti virtual uint16_t get_crnti() = 0; virtual bool set_crnti(uint16_t c_rnti) = 0; + virtual void set_temp_crnti(uint16_t c_rnti) = 0; + virtual void set_crnti_to_temp() = 0; // Functions for msg3 manipulation which shall be transparent to the procedure virtual bool msg3_is_transmitted() = 0; @@ -102,6 +104,7 @@ class demux_interface_harq_nr { public: /// Inform demux unit about a newly decoded TB. + virtual void push_bcch(srsran::unique_byte_buffer_t pdu) = 0; virtual void push_pdu(srsran::unique_byte_buffer_t pdu, uint32_t tti) = 0; virtual void push_pdu_temp_crnti(srsran::unique_byte_buffer_t pdu, uint32_t tti) = 0; virtual uint64_t get_received_crueid() = 0; diff --git a/srsue/hdr/stack/mac_nr/proc_ra_nr.h b/srsue/hdr/stack/mac_nr/proc_ra_nr.h index 855db5780..bd18616eb 100644 --- a/srsue/hdr/stack/mac_nr/proc_ra_nr.h +++ b/srsue/hdr/stack/mac_nr/proc_ra_nr.h @@ -49,6 +49,7 @@ public: uint16_t get_rar_rnti(); bool has_temp_crnti(); uint16_t get_temp_crnti(); + void set_crnti_to_temp(); void received_contention_resolution(bool is_successful); // PHY interfaces @@ -73,8 +74,6 @@ private: int ra_window_length = -1, ra_window_start = -1; uint16_t rar_rnti = SRSRAN_INVALID_RNTI; - uint16_t temp_crnti = SRSRAN_INVALID_RNTI; - uint16_t transmitted_crnti = SRSRAN_INVALID_RNTI; std::mutex mutex; srsran::rach_cfg_nr_t rach_cfg = {}; diff --git a/srsue/hdr/stack/rrc/rrc_cell.h b/srsue/hdr/stack/rrc/rrc_cell.h index f3485d33a..929fea986 100644 --- a/srsue/hdr/stack/rrc/rrc_cell.h +++ b/srsue/hdr/stack/rrc/rrc_cell.h @@ -256,6 +256,10 @@ public: // serving cell handling int set_serving_cell(phy_cell_t phy_cell, bool discard_serving); + // Set serving cell and earfcn for each cc_idx + void set_scell_cc_idx(uint32_t cc_idx, uint32_t earfcn, uint32_t pci); + bool get_scell_cc_idx(uint32_t earfcn, uint32_t& pci); + T& serving_cell() { return *serv_cell; } const T& serving_cell() const { return *serv_cell; } @@ -272,6 +276,9 @@ private: unique_meas_cell serv_cell; std::vector neighbour_cells; + + // store serving pci and earfcn for each carrier + std::array, SRSRAN_MAX_CARRIERS> current_cell_pci_earfcn = {}; }; } // namespace srsue diff --git a/srsue/hdr/stack/rrc_nr/rrc_nr.h b/srsue/hdr/stack/rrc_nr/rrc_nr.h index 9a1e1bffa..40e4ce13d 100644 --- a/srsue/hdr/stack/rrc_nr/rrc_nr.h +++ b/srsue/hdr/stack/rrc_nr/rrc_nr.h @@ -37,6 +37,7 @@ #include "srsran/interfaces/ue_nas_interfaces.h" #include "srsran/interfaces/ue_nr_interfaces.h" #include "srsran/interfaces/ue_rrc_interfaces.h" +#include "srsran/interfaces/ue_sdap_interfaces.h" #include "srsue/hdr/stack/upper/gw.h" namespace srsue { @@ -61,6 +62,7 @@ public: mac_interface_rrc_nr* mac_, rlc_interface_rrc* rlc_, pdcp_interface_rrc* pdcp_, + sdap_interface_rrc* sdap_, gw_interface_rrc* gw_, nas_5g_interface_rrc_nr* nas_, rrc_eutra_interface_rrc_nr* rrc_eutra_, @@ -145,6 +147,7 @@ private: void send_setup_request(srsran::nr_establishment_cause_t cause); void send_con_setup_complete(srsran::unique_byte_buffer_t nas_msg); void send_rrc_reconfig_complete(); + int send_ue_capability_info(const asn1::rrc_nr::ue_cap_enquiry_s& msg); void send_ul_info_transfer(srsran::unique_byte_buffer_t nas_msg); void send_ul_ccch_msg(const asn1::rrc_nr::ul_ccch_msg_s& msg); void send_ul_dcch_msg(uint32_t lcid, const asn1::rrc_nr::ul_dcch_msg_s& msg); @@ -155,6 +158,7 @@ private: void handle_sib1(const asn1::rrc_nr::sib1_s& sib1); bool handle_rrc_setup(const asn1::rrc_nr::rrc_setup_s& setup); void handle_rrc_reconfig(const asn1::rrc_nr::rrc_recfg_s& reconfig); + void handle_ue_capability_enquiry(const asn1::rrc_nr::ue_cap_enquiry_s& ue_cap_enquiry); void handle_dl_info_transfer(const asn1::rrc_nr::dl_info_transfer_s& dl_info_transfer); void handle_security_mode_command(const asn1::rrc_nr::security_mode_cmd_s& smc); void handle_rrc_release(const asn1::rrc_nr::rrc_release_s& rrc_release); @@ -178,6 +182,7 @@ private: mac_interface_rrc_nr* mac = nullptr; rlc_interface_rrc* rlc = nullptr; pdcp_interface_rrc* pdcp = nullptr; + sdap_interface_rrc* sdap = nullptr; gw_interface_rrc* gw = nullptr; nas_5g_interface_rrc_nr* nas = nullptr; rrc_eutra_interface_rrc_nr* rrc_eutra = nullptr; @@ -206,6 +211,7 @@ private: // Stores the state of the PHY configuration setting enum { PHY_CFG_STATE_NONE = 0, + PHY_CFG_STATE_SA_MIB_CFG, PHY_CFG_STATE_SA_SIB_CFG, PHY_CFG_STATE_SA_FULL_CFG, PHY_CFG_STATE_NSA_APPLY_SP_CELL, diff --git a/srsue/hdr/stack/rrc_nr/rrc_nr_procedures.h b/srsue/hdr/stack/rrc_nr/rrc_nr_procedures.h index 4980bd60c..cfa7d8443 100644 --- a/srsue/hdr/stack/rrc_nr/rrc_nr_procedures.h +++ b/srsue/hdr/stack/rrc_nr/rrc_nr_procedures.h @@ -34,7 +34,7 @@ namespace srsue { class rrc_nr::cell_selection_proc { public: - enum class state_t { phy_cell_search, phy_cell_select }; + enum class state_t { phy_cell_search, phy_cell_select, sib_acquire }; using cell_selection_complete_ev = srsran::proc_result_t; explicit cell_selection_proc(rrc_nr& parent_); @@ -42,6 +42,7 @@ public: srsran::proc_outcome_t step(); srsran::proc_outcome_t react(const rrc_interface_phy_nr::cell_search_result_t& event); srsran::proc_outcome_t react(const rrc_interface_phy_nr::cell_select_result_t& event); + srsran::proc_outcome_t react(const bool sib1_found); void then(const cell_selection_complete_ev& proc_result) const; diff --git a/srsue/hdr/stack/ue_stack_lte.h b/srsue/hdr/stack/ue_stack_lte.h index 707ea7d7d..9e3d104c7 100644 --- a/srsue/hdr/stack/ue_stack_lte.h +++ b/srsue/hdr/stack/ue_stack_lte.h @@ -45,6 +45,7 @@ #include "ue_stack_base.h" #include "upper/nas.h" #include "upper/nas_5g.h" +#include "upper/sdap.h" #include "upper/usim.h" #include #include @@ -55,6 +56,28 @@ namespace srsue { class phy_interface_stack_lte; +class sdap_pdcp_adapter : public pdcp_interface_sdap_nr, public gw_interface_pdcp +{ +public: + sdap_pdcp_adapter(pdcp* parent_pdcp_, sdap* parent_sdap_) : parent_pdcp(parent_pdcp_), parent_sdap(parent_sdap_) {} + void write_sdu(uint32_t lcid, srsran::unique_byte_buffer_t pdu) final + { + parent_pdcp->write_sdu(lcid, std::move(pdu)); + } + void write_pdu(uint32_t lcid, srsran::unique_byte_buffer_t pdu) final + { + parent_sdap->write_pdu(lcid, std::move(pdu)); + } + void write_pdu_mch(uint32_t lcid, srsran::unique_byte_buffer_t pdu) final + { + // not implemented + } + +private: + pdcp* parent_pdcp = nullptr; + sdap* parent_sdap = nullptr; +}; + class ue_stack_lte final : public ue_stack_base, public stack_interface_phy_lte, public stack_interface_phy_nr, @@ -240,6 +263,10 @@ private: srsue::nas_5g nas_5g; std::unique_ptr usim; + // SDAP only applies to NR + srsue::sdap sdap; + sdap_pdcp_adapter sdap_pdcp; + ue_bearer_manager bearers; // helper to manage mapping between EPS and radio bearers // Metrics helper diff --git a/srsue/hdr/stack/ue_stack_nr.h b/srsue/hdr/stack/ue_stack_nr.h index 59488cad1..0bdafc598 100644 --- a/srsue/hdr/stack/ue_stack_nr.h +++ b/srsue/hdr/stack/ue_stack_nr.h @@ -32,6 +32,7 @@ #include "srsran/rlc/rlc.h" #include "srsran/upper/pdcp.h" #include "upper/nas.h" +#include "upper/sdap.h" #include "upper/usim.h" #include "srsran/common/buffer_pool.h" @@ -144,6 +145,7 @@ private: std::unique_ptr rrc; std::unique_ptr rlc; std::unique_ptr pdcp; + std::unique_ptr sdap; // RAT-specific interfaces phy_interface_stack_nr* phy = nullptr; diff --git a/srsue/hdr/stack/upper/sdap.h b/srsue/hdr/stack/upper/sdap.h new file mode 100644 index 000000000..a5975ea6a --- /dev/null +++ b/srsue/hdr/stack/upper/sdap.h @@ -0,0 +1,56 @@ +/** + * + * \section COPYRIGHT + * + * Copyright 2013-2021 Software Radio Systems Limited + * + * By using this file, you agree to the terms and conditions set + * forth in the LICENSE file which can be found at the top level of + * the distribution. + * + */ + +#ifndef SRSUE_SDAP_H +#define SRSUE_SDAP_H + +#include "srsran/common/buffer_pool.h" +#include "srsran/common/common.h" +#include "srsran/common/common_nr.h" +#include "srsran/interfaces/ue_gw_interfaces.h" +#include "srsran/interfaces/ue_pdcp_interfaces.h" +#include "srsran/interfaces/ue_sdap_interfaces.h" + +namespace srsue { + +class sdap final : public sdap_interface_pdcp_nr, public sdap_interface_gw_nr, public sdap_interface_rrc +{ +public: + explicit sdap(const char* logname); + bool init(pdcp_interface_sdap_nr* pdcp_, srsue::gw_interface_pdcp* gw_); + void stop(); + + // Interface for GW + void write_pdu(uint32_t lcid, srsran::unique_byte_buffer_t pdu) final; + + // Interface for PDCP + void write_sdu(uint32_t lcid, srsran::unique_byte_buffer_t pdu) final; + + // Interface for RRC + bool set_bearer_cfg(uint32_t lcid, const sdap_interface_rrc::bearer_cfg_t& cfg) final; + +private: + pdcp_interface_sdap_nr* m_pdcp = nullptr; + gw_interface_pdcp* m_gw = nullptr; + + // state + bool running = false; + + // configuration + std::array bearers = {}; + + srslog::basic_logger& logger; +}; + +} // namespace srsue + +#endif // SRSUE_SDAP_H diff --git a/srsue/src/CMakeLists.txt b/srsue/src/CMakeLists.txt index e856c0270..e638da8a3 100644 --- a/srsue/src/CMakeLists.txt +++ b/srsue/src/CMakeLists.txt @@ -76,4 +76,4 @@ else(NOT ${BUILDUE_CMD} STREQUAL "") message(STATUS "No post-build-UE command defined") endif (NOT ${BUILDUE_CMD} STREQUAL "") -install(TARGETS srsue DESTINATION ${RUNTIME_DIR}) +install(TARGETS srsue DESTINATION ${RUNTIME_DIR} OPTIONAL) diff --git a/srsue/src/main.cc b/srsue/src/main.cc index 314b99b6a..56c9e5621 100644 --- a/srsue/src/main.cc +++ b/srsue/src/main.cc @@ -68,8 +68,9 @@ string config_file; static int parse_args(all_args_t* args, int argc, char* argv[]) { - bool use_standard_lte_rates = false; + bool use_standard_lte_rates = false; std::string scs_khz, ssb_scs_khz; // temporary value to store integer + std::string cfr_mode; // Command line only options bpo::options_description general("General options"); @@ -248,6 +249,14 @@ static int parse_args(all_args_t* args, int argc, char* argv[]) ("channel.ul.hst.fd_hz", bpo::value(&args->phy.ul_channel_args.hst_fd_hz)->default_value(+750.0f), "Doppler frequency in Hz") ("channel.ul.hst.init_time_s", bpo::value(&args->phy.ul_channel_args.hst_init_time_s)->default_value(0), "Initial time in seconds") + /* CFR section */ + ("cfr.enable", bpo::value(&args->phy.cfr_args.enable)->default_value(args->phy.cfr_args.enable), "CFR enable") + ("cfr.mode", bpo::value(&cfr_mode)->default_value("manual"), "CFR mode") + ("cfr.manual_thres", bpo::value(&args->phy.cfr_args.manual_thres)->default_value(args->phy.cfr_args.manual_thres), "Fixed manual clipping threshold for CFR manual mode") + ("cfr.strength", bpo::value(&args->phy.cfr_args.strength)->default_value(args->phy.cfr_args.strength), "CFR ratio between amplitude-limited vs original signal (0 to 1)") + ("cfr.auto_target_papr", bpo::value(&args->phy.cfr_args.auto_target_papr)->default_value(args->phy.cfr_args.auto_target_papr), "Signal PAPR target (in dB) in CFR auto modes") + ("cfr.ema_alpha", bpo::value(&args->phy.cfr_args.ema_alpha)->default_value(args->phy.cfr_args.ema_alpha), "Alpha coefficient for the power average in auto_ema mode (0 to 1)") + /* PHY section */ ("phy.worker_cpu_mask", bpo::value(&args->phy.worker_cpu_mask)->default_value(-1), @@ -557,6 +566,13 @@ static int parse_args(all_args_t* args, int argc, char* argv[]) args->stack.usim.using_op = vm.count("usim.op"); } + // parse the CFR mode string + args->phy.cfr_args.mode = srsran_cfr_str2mode(cfr_mode.c_str()); + if (args->phy.cfr_args.mode == SRSRAN_CFR_THR_INVALID) { + cout << "Error, invalid CFR mode: " << cfr_mode << endl; + exit(1); + } + // Apply all_level to any unset layers if (vm.count("log.all_level")) { if (!vm.count("log.rf_level")) { diff --git a/srsue/src/phy/lte/cc_worker.cc b/srsue/src/phy/lte/cc_worker.cc index 5ad9d24db..ad9f59579 100644 --- a/srsue/src/phy/lte/cc_worker.cc +++ b/srsue/src/phy/lte/cc_worker.cc @@ -56,6 +56,8 @@ cc_worker::cc_worker(uint32_t cc_idx_, uint32_t max_prb, srsue::phy_common* phy_ cc_idx = cc_idx_; phy = phy_; + srsran_cfr_cfg_t cfr_config = phy->get_cfr_config(); + signal_buffer_max_samples = 3 * SRSRAN_SF_LEN_PRB(max_prb); for (uint32_t i = 0; i < phy->args->nof_rx_ant; i++) { @@ -81,6 +83,11 @@ cc_worker::cc_worker(uint32_t cc_idx_, uint32_t max_prb, srsue::phy_common* phy_ return; } + if (srsran_ue_ul_set_cfr(&ue_ul, &cfr_config) < SRSRAN_SUCCESS) { + Error("Setting the CFR"); + return; + } + phy->set_ue_dl_cfg(&ue_dl_cfg); phy->set_ue_ul_cfg(&ue_ul_cfg); phy->set_pdsch_cfg(&ue_dl_cfg.cfg.pdsch); diff --git a/srsue/src/phy/nr/cc_worker.cc b/srsue/src/phy/nr/cc_worker.cc index e3c3cb609..302f4ebe0 100644 --- a/srsue/src/phy/nr/cc_worker.cc +++ b/srsue/src/phy/nr/cc_worker.cc @@ -205,15 +205,15 @@ void cc_worker::decode_pdcch_ul() } // Search for grants - int n_dl = + int n_ul = srsran_ue_dl_nr_find_ul_dci(&ue_dl, &dl_slot_cfg, rnti.id, rnti.type, dci_rx.data(), (uint32_t)dci_rx.size()); - if (n_dl < SRSRAN_SUCCESS) { + if (n_ul < SRSRAN_SUCCESS) { logger.error("Error decoding UL NR-PDCCH"); return; } // Iterate over all received grants - for (int i = 0; i < n_dl; i++) { + for (int i = 0; i < n_ul; i++) { // Log found DCI if (logger.info.enabled()) { std::array str; @@ -405,6 +405,7 @@ bool cc_worker::measure_csi() logger.error("PBCH-MIB: NR SFN (%d) does not match current SFN (%d)", mib.sfn, dl_slot_cfg.idx / SRSRAN_NSLOTS_PER_FRAME_NR(cfg.carrier.scs)); + dl_slot_cfg.idx = mib.sfn * SRSRAN_NSLOTS_PER_FRAME_NR(cfg.carrier.scs); } // Log MIB information @@ -678,24 +679,9 @@ bool cc_worker::work_ul() phy.set_ul_metrics(ul_m); } else if (srsran_uci_nr_total_bits(&uci_data.cfg) > 0) { - // Currently, default PUCCH is not supported, in this case log it and pretend no UCI was available - if (not cfg.pucch.enabled) { - if (logger.info.enabled()) { - std::array str; - srsran_uci_nr_info(&uci_data, str.data(), str.size()); - logger.info( - "PUCCH: No PUCCH resource to transmit UCI cc=%d, %s, tti_tx=%d", cc_idx, str.data(), ul_slot_cfg.idx); - } - - // No NR signal shall be transmitted - srsran_vec_cf_zero(tx_buffer[0], ue_ul.ifft.sf_sz); - - return true; - } - // Get PUCCH resource srsran_pucch_nr_resource_t resource = {}; - if (srsran_ra_ul_nr_pucch_resource(&cfg.pucch, &uci_data.cfg, &resource) < SRSRAN_SUCCESS) { + if (srsran_ra_ul_nr_pucch_resource(&cfg.pucch, &uci_data.cfg, cfg.carrier.nof_prb, &resource) < SRSRAN_SUCCESS) { ERROR("Selecting PUCCH resource"); return false; } diff --git a/srsue/src/phy/phy.cc b/srsue/src/phy/phy.cc index 527a38bcc..b705d76d8 100644 --- a/srsue/src/phy/phy.cc +++ b/srsue/src/phy/phy.cc @@ -470,7 +470,7 @@ bool phy::set_config(const srsran::phy_cfg_t& config_, uint32_t cc_idx) if (cc_idx >= args.nof_lte_carriers) { srsran::console("Received SCell configuration for index %d but there are not enough CC workers available\n", cc_idx); - return false; + return true; } Info("Setting configuration"); diff --git a/srsue/src/phy/phy_common.cc b/srsue/src/phy/phy_common.cc index a48d580a8..1e0fad71d 100644 --- a/srsue/src/phy/phy_common.cc +++ b/srsue/src/phy/phy_common.cc @@ -65,6 +65,14 @@ void phy_common::init(phy_args_t* _args, ul_channel = srsran::channel_ptr( new srsran::channel(args->ul_channel_args, args->nof_lte_carriers * args->nof_rx_ant, logger)); } + + // Init the CFR config struct with the CFR args + cfr_config.cfr_enable = args->cfr_args.enable; + cfr_config.cfr_mode = args->cfr_args.mode; + cfr_config.alpha = args->cfr_args.strength; + cfr_config.manual_thr = args->cfr_args.manual_thres; + cfr_config.max_papr_db = args->cfr_args.auto_target_papr; + cfr_config.ema_alpha = args->cfr_args.ema_alpha; } void phy_common::set_ue_dl_cfg(srsran_ue_dl_cfg_t* ue_dl_cfg) diff --git a/srsue/src/phy/phy_nr_sa.cc b/srsue/src/phy/phy_nr_sa.cc index 367bfc290..289d5e9e9 100644 --- a/srsue/src/phy/phy_nr_sa.cc +++ b/srsue/src/phy/phy_nr_sa.cc @@ -112,10 +112,6 @@ void phy_nr_sa::init_background() } workers.init(args, sync, stack); - // Set fix Tx and Rx sampling rates - radio->set_tx_srate(args.srate_hz); - radio->set_rx_srate(args.srate_hz); - is_configured = true; } @@ -243,6 +239,16 @@ void phy_nr_sa::send_prach(const uint32_t prach_occasion, workers.send_prach(prach_occasion, preamble_index, preamble_received_target_power); } +void phy_nr_sa::set_timeadv_rar(uint32_t tti, uint32_t ta_cmd) +{ + sync.add_ta_cmd_rar(tti, ta_cmd); +} + +void phy_nr_sa::set_timeadv(uint32_t tti, uint32_t ta_cmd) +{ + sync.add_ta_cmd_new(tti, ta_cmd); +} + int phy_nr_sa::set_rar_grant(uint32_t rar_slot_idx, std::array packed_ul_grant, uint16_t rnti, @@ -261,6 +267,9 @@ bool phy_nr_sa::set_config(const srsran::phy_cfg_nr_t& cfg) // Set UE configuration bool ret = workers.set_config(config_nr); + // Pass n_ta_offset to sync + sync.add_ta_offset(config_nr.t_offset); + // Notify PHY config completion if (stack != nullptr) { stack->set_phy_config_complete(ret); diff --git a/srsue/src/phy/sync.cc b/srsue/src/phy/sync.cc index 19d8e98bd..9fd72dcab 100644 --- a/srsue/src/phy/sync.cc +++ b/srsue/src/phy/sync.cc @@ -137,17 +137,19 @@ sync::~sync() void sync::stop() { + running = false; + std::lock_guard lock(intra_freq_cfg_mutex); - worker_com->semaphore.wait_all(); for (auto& q : intra_freq_meas) { q->stop(); } - running = false; - - wait_thread_finish(); // Reset (stop Rx stream) as soon as possible to avoid base-band Rx buffer overflow radio_h->reset(); + + // let the sync FSM finish before waiting for workers semaphores to avoid workers locking + wait_thread_finish(); + worker_com->semaphore.wait_all(); } void sync::reset() diff --git a/srsue/src/phy/sync_sa.cc b/srsue/src/phy/sync_sa.cc index a8219ee66..56ad4703c 100644 --- a/srsue/src/phy/sync_sa.cc +++ b/srsue/src/phy/sync_sa.cc @@ -25,7 +25,7 @@ namespace srsue { namespace nr { sync_sa::sync_sa(srslog::basic_logger& logger_, worker_pool& workers_) : - logger(logger_), workers(workers_), slot_synchronizer(logger_), searcher(logger_), srsran::thread("SYNC") + logger(logger_), workers(workers_), slot_synchronizer(logger_), searcher(logger_), ta(logger_), srsran::thread("SYNC") {} sync_sa::~sync_sa() @@ -87,10 +87,26 @@ bool sync_sa::reset() { // Wait worker pool to finish any processing tti_semaphore.wait_all(); + ta.set_base_sec(0); return true; } +void sync_sa::add_ta_cmd_rar(uint32_t tti_, uint32_t ta_cmd) +{ + ta.add_ta_cmd_rar(tti_, ta_cmd); +} + +void sync_sa::add_ta_cmd_new(uint32_t tti_, uint32_t ta_cmd) +{ + ta.add_ta_cmd_new(tti_, ta_cmd); +} + +void sync_sa::add_ta_offset(uint32_t ta_offset) +{ + ta.add_ta_offset(ta_offset); +} + void sync_sa::cell_go_idle() { std::unique_lock ul(rrc_mutex); @@ -302,6 +318,8 @@ void sync_sa::run_state_cell_camping() context.last = true; // Set last if standalone last_rx_time.add(FDD_HARQ_DELAY_DL_MS * 1e-3); context.tx_time.copy(last_rx_time); + // Apply current TA + context.tx_time.sub((double)ta.get_sec()); nr_worker->set_context(context); diff --git a/srsue/src/stack/mac_nr/demux_nr.cc b/srsue/src/stack/mac_nr/demux_nr.cc index a31354a01..28a0d9efa 100644 --- a/srsue/src/stack/mac_nr/demux_nr.cc +++ b/srsue/src/stack/mac_nr/demux_nr.cc @@ -30,9 +30,10 @@ demux_nr::demux_nr(srslog::basic_logger& logger_) : logger(logger_) {} demux_nr::~demux_nr() {} -int32_t demux_nr::init(rlc_interface_mac* rlc_) +int32_t demux_nr::init(rlc_interface_mac* rlc_, phy_interface_mac_nr* phy_) { rlc = rlc_; + phy = phy_; return SRSRAN_SUCCESS; } @@ -47,6 +48,11 @@ void demux_nr::push_pdu(srsran::unique_byte_buffer_t pdu, uint32_t tti) pdu_queue.push(std::move(pdu)); } +void demux_nr::push_bcch(srsran::unique_byte_buffer_t pdu) +{ + bcch_queue.push(std::move(pdu)); +} + /* Demultiplexing of MAC PDU associated with a Temporal C-RNTI. The PDU will * remain in buffer until demultiplex_pending_pdu() is called. * This features is provided to enable the Random Access Procedure to decide @@ -64,6 +70,13 @@ void demux_nr::push_pdu_temp_crnti(srsran::unique_byte_buffer_t pdu, uint32_t tt void demux_nr::process_pdus() { + // Handle first BCCH + while (not bcch_queue.empty()) { + srsran::unique_byte_buffer_t pdu = bcch_queue.wait_pop(); + logger.debug(pdu->msg, pdu->N_bytes, "Handling MAC BCCH PDU (%d B)", pdu->N_bytes); + rlc->write_pdu_bcch_dlsch(pdu->msg, pdu->N_bytes); + } + // Then user PDUs while (not pdu_queue.empty()) { srsran::unique_byte_buffer_t pdu = pdu_queue.wait_pop(); handle_pdu(rx_pdu, std::move(pdu)); @@ -101,7 +114,8 @@ void demux_nr::handle_pdu(srsran::mac_sch_pdu_nr& pdu_buffer, srsran::unique_byt logger.info("DRX CE not implemented."); break; case srsran::mac_sch_subpdu_nr::nr_lcid_sch_t::TA_CMD: - logger.info("Timing Advance CE not implemented."); + logger.info("Received TA=%d.", subpdu.get_ta().ta_command); + phy->set_timeadv(0, subpdu.get_ta().ta_command); break; case srsran::mac_sch_subpdu_nr::nr_lcid_sch_t::CON_RES_ID: received_crueid = subpdu.get_ue_con_res_id_ce_packed(); diff --git a/srsue/src/stack/mac_nr/dl_harq_nr.cc b/srsue/src/stack/mac_nr/dl_harq_nr.cc index 829c44aa7..bffbe94e7 100644 --- a/srsue/src/stack/mac_nr/dl_harq_nr.cc +++ b/srsue/src/stack/mac_nr/dl_harq_nr.cc @@ -35,6 +35,14 @@ dl_harq_entity_nr::dl_harq_entity_nr(uint8_t cc_idx_, // Init broadcast HARQ process bcch_proc.init(-1); pthread_rwlock_init(&rwlock, NULL); + + // Create default number of processes + for (uint32_t i = 0; i < cfg.nof_procs; i++) { + harq_procs[i] = std::unique_ptr(new dl_harq_process_nr(this)); + if (!harq_procs.at(i)->init(i)) { + logger.error("Error while initializing DL-HARQ process %d", i); + } + } } dl_harq_entity_nr::~dl_harq_entity_nr() @@ -51,17 +59,19 @@ int32_t dl_harq_entity_nr::set_config(const srsran::dl_harq_cfg_nr_t& cfg_) return SRSRAN_ERROR; } - // clear old processees - for (auto& proc : harq_procs) { - proc = nullptr; - } - - // Allocate and init configured HARQ processes - for (uint32_t i = 0; i < cfg.nof_procs; i++) { - harq_procs[i] = std::unique_ptr(new dl_harq_process_nr(this)); - if (!harq_procs.at(i)->init(i)) { - logger.error("Error while initializing DL-HARQ process %d", i); - return SRSRAN_ERROR; + if (cfg_.nof_procs < cfg.nof_procs) { + // clear old processes if not needed + for (uint32_t i = cfg.nof_procs - 1; i < cfg_.nof_procs; i++) { + harq_procs[i] = nullptr; + } + } else { + // Add new processes + for (uint32_t i = cfg.nof_procs; i < cfg_.nof_procs; i++) { + harq_procs[i] = std::unique_ptr(new dl_harq_process_nr(this)); + if (!harq_procs.at(i)->init(i)) { + logger.error("Error while initializing DL-HARQ process %d", i); + return SRSRAN_ERROR; + } } } @@ -228,8 +238,7 @@ void dl_harq_entity_nr::dl_harq_process_nr::tb_decoded(const mac_nr_grant_dl_t& if (acked and result.payload != nullptr) { if (is_bcch) { - logger.warning("Delivering PDU=%d bytes to Dissassemble and Demux unit (BCCH) not implemented", grant.tbs); - reset(); + harq_entity->demux_unit->push_bcch(std::move(result.payload)); } else { if (grant.rnti == harq_entity->mac->get_temp_crnti()) { logger.debug("Delivering PDU=%d bytes to Dissassemble and Demux unit (Temporal C-RNTI) not implemented", diff --git a/srsue/src/stack/mac_nr/mac_nr.cc b/srsue/src/stack/mac_nr/mac_nr.cc index bd3ad6af5..f4ba9565d 100644 --- a/srsue/src/stack/mac_nr/mac_nr.cc +++ b/srsue/src/stack/mac_nr/mac_nr.cc @@ -74,7 +74,7 @@ int mac_nr::init(const mac_nr_args_t& args_, return SRSRAN_ERROR; } - if (demux.init(rlc) != SRSRAN_SUCCESS) { + if (demux.init(rlc, phy) != SRSRAN_SUCCESS) { logger.error("Couldn't initialize demux unit."); return SRSRAN_ERROR; } @@ -112,6 +112,16 @@ void mac_nr::reset() proc_sr.reset(); proc_ra.reset(); mux.reset(); + for (const auto& cc : dl_harq) { + if (cc != nullptr) { + cc->reset(); + } + } + for (const auto& cc : ul_harq) { + if (cc != nullptr) { + cc->reset(); + } + } } void mac_nr::run_tti(const uint32_t tti) @@ -162,13 +172,16 @@ void mac_nr::update_buffer_states() mac_interface_phy_nr::sched_rnti_t mac_nr::get_ul_sched_rnti_nr(const uint32_t tti) { + if (has_temp_crnti() && has_crnti() == false) { + logger.debug("SCHED: Searching temp C-RNTI=0x%x (proc_ra)", rntis.get_temp_rnti()); + return {rntis.get_temp_rnti(), srsran_rnti_type_c}; + } return {rntis.get_crnti(), srsran_rnti_type_c}; } bool mac_nr::is_si_opportunity() { - // TODO: ask RRC if we need SI - return false; + return search_bcch; } bool mac_nr::is_paging_opportunity() @@ -191,9 +204,9 @@ mac_interface_phy_nr::sched_rnti_t mac_nr::get_dl_sched_rnti_nr(const uint32_t t return {proc_ra.get_rar_rnti(), srsran_rnti_type_ra}; } - if (proc_ra.has_temp_crnti() && has_crnti() == false) { - logger.debug("SCHED: Searching temp C-RNTI=0x%x (proc_ra)", proc_ra.get_temp_crnti()); - return {proc_ra.get_temp_crnti(), srsran_rnti_type_c}; + if (has_temp_crnti() && has_crnti() == false) { + logger.debug("SCHED: Searching temp C-RNTI=0x%x (proc_ra)", rntis.get_temp_rnti()); + return {rntis.get_temp_rnti(), srsran_rnti_type_c}; } if (has_crnti()) { @@ -205,6 +218,26 @@ mac_interface_phy_nr::sched_rnti_t mac_nr::get_dl_sched_rnti_nr(const uint32_t t return {SRSRAN_INVALID_RNTI, srsran_rnti_type_c}; } +bool mac_nr::has_temp_crnti() +{ + return rntis.get_temp_rnti() != SRSRAN_INVALID_RNTI; +} + +uint16_t mac_nr::get_temp_crnti() +{ + return rntis.get_temp_rnti(); +} + +void mac_nr::set_temp_crnti(uint16_t temp_crnti) +{ + rntis.set_temp_rnti(temp_crnti); +} + +void mac_nr::set_crnti_to_temp() +{ + rntis.set_crnti_to_temp(); +} + bool mac_nr::has_crnti() { return rntis.get_crnti() != SRSRAN_INVALID_RNTI; @@ -215,11 +248,6 @@ uint16_t mac_nr::get_crnti() return rntis.get_crnti(); } -uint16_t mac_nr::get_temp_crnti() -{ - return proc_ra.get_temp_crnti(); -} - srsran::mac_sch_subpdu_nr::lcg_bsr_t mac_nr::generate_sbsr() { return proc_bsr.generate_sbsr(); @@ -305,7 +333,7 @@ void mac_nr::new_grant_dl(const uint32_t cc_idx, const mac_nr_grant_dl_t& grant, void mac_nr::tb_decoded(const uint32_t cc_idx, const mac_nr_grant_dl_t& grant, tb_action_dl_result_t result) { - logger.debug("tb_decoded(): cc_idx=%d, tti=%d, rnti=%d, pid=%d, tbs=%d, ndi=%d, rv=%d, result=%s", + logger.debug("tb_decoded(): cc_idx=%d, tti=%d, rnti=0x%X, pid=%d, tbs=%d, ndi=%d, rv=%d, result=%s", cc_idx, grant.tti, grant.rnti, @@ -332,14 +360,14 @@ void mac_nr::tb_decoded(const uint32_t cc_idx, const mac_nr_grant_dl_t& grant, t } // If proc ra is in contention resolution (RA connection request procedure) - if (proc_ra.is_contention_resolution() && grant.rnti == get_temp_crnti()) { + if (proc_ra.is_contention_resolution() && grant.rnti == rntis.get_temp_rnti()) { proc_ra.received_contention_resolution(contention_res_successful); } } void mac_nr::new_grant_ul(const uint32_t cc_idx, const mac_nr_grant_ul_t& grant, tb_action_ul_t* action) { - logger.debug("new_grant_ul(): cc_idx=%d, tti=%d, rnti=%d, pid=%d, tbs=%d, ndi=%d, rv=%d, is_rar=%d", + logger.debug("new_grant_ul(): cc_idx=%d, tti=%d, rnti=0x%X, pid=%d, tbs=%d, ndi=%d, rv=%d, is_rar=%d", cc_idx, grant.tti, grant.rnti, @@ -456,6 +484,11 @@ void mac_nr::set_contention_id(uint64_t ue_identity) rntis.set_contention_id(ue_identity); } +void mac_nr::bcch_search(bool enabled) +{ + search_bcch = enabled; +} + bool mac_nr::set_crnti(const uint16_t c_rnti_) { if (is_valid_crnti(c_rnti_)) { diff --git a/srsue/src/stack/mac_nr/mux_nr.cc b/srsue/src/stack/mac_nr/mux_nr.cc index b6c24f664..651fbe56f 100644 --- a/srsue/src/stack/mac_nr/mux_nr.cc +++ b/srsue/src/stack/mac_nr/mux_nr.cc @@ -85,7 +85,7 @@ srsran::unique_byte_buffer_t mux_nr::get_pdu(uint32_t max_pdu_len) // Pack normal UL data PDU int32_t remaining_len = tx_pdu.get_remaing_len(); // local variable to reserve space for CEs - if (add_bsr_ce == sbsr_ce) { + if (!msg3_is_pending() && add_bsr_ce == sbsr_ce) { // reserve space for SBSR remaining_len -= 2; } diff --git a/srsue/src/stack/mac_nr/proc_ra_nr.cc b/srsue/src/stack/mac_nr/proc_ra_nr.cc index 03eed3bb9..6654b5c29 100644 --- a/srsue/src/stack/mac_nr/proc_ra_nr.cc +++ b/srsue/src/stack/mac_nr/proc_ra_nr.cc @@ -124,18 +124,6 @@ bool proc_ra_nr::has_rar_rnti() return false; } -bool proc_ra_nr::has_temp_crnti() -{ - std::lock_guard lock(mutex); - return temp_crnti != SRSRAN_INVALID_RNTI; -} - -uint16_t proc_ra_nr::get_temp_crnti() -{ - std::lock_guard lock(mutex); - return temp_crnti; -} - void proc_ra_nr::received_contention_resolution(bool is_successful) { std::lock_guard lock(mutex); @@ -225,18 +213,18 @@ void proc_ra_nr::ra_response_reception(const mac_interface_phy_nr::tb_action_dl_ for (auto& subpdu : pdu.get_subpdus()) { if (subpdu.has_rapid() && subpdu.get_rapid() == preamble_index) { logger.debug("PROC RA NR: Setting UL grant and prepare Msg3"); - temp_crnti = subpdu.get_temp_crnti(); - - // Save transmitted C-RNTI (if any) - transmitted_crnti = mac.get_crnti(); + mac.set_temp_crnti(subpdu.get_temp_crnti()); // Set Temporary-C-RNTI if provided, otherwise C-RNTI is ok - phy->set_rar_grant(tb.rx_slot_idx, subpdu.get_ul_grant(), temp_crnti, srsran_rnti_type_ra); + phy->set_rar_grant(tb.rx_slot_idx, subpdu.get_ul_grant(), subpdu.get_temp_crnti(), srsran_rnti_type_ra); + + // Apply TA CMD + current_ta = subpdu.get_ta(); + phy->set_timeadv_rar(tb.rx_slot_idx, current_ta); // reset all parameters that are used before rar rar_rnti = SRSRAN_INVALID_RNTI; mac.msg3_prepare(); - current_ta = subpdu.get_ta(); // Set Backoff parameter if (subpdu.has_backoff()) { @@ -286,12 +274,17 @@ void proc_ra_nr::ra_completion() srsran::enum_to_text(state_str_nr, (uint32_t)ra_state_t::MAX_RA_STATES, WAITING_FOR_COMPLETION)); return; } + + // Start looking for PDCCH CRNTI + if (!mac.get_crnti()) { + // promote temp RNTI to new C-RNTI + mac.set_crnti_to_temp(); + mac.set_temp_crnti(SRSRAN_INVALID_RNTI); + } + srsran::console("Random Access Complete. c-rnti=0x%x, ta=%d\n", mac.get_crnti(), current_ta); logger.info("Random Access Complete. c-rnti=0x%x, ta=%d", mac.get_crnti(), current_ta); - if (!transmitted_crnti) { - mac.set_crnti(temp_crnti); - } - temp_crnti = SRSRAN_INVALID_RNTI; + mac.rrc_ra_completed(); reset(); } @@ -299,9 +292,9 @@ void proc_ra_nr::ra_completion() void proc_ra_nr::ra_error() { std::lock_guard lock(mutex); - temp_crnti = SRSRAN_INVALID_RNTI; preamble_transmission_counter++; contention_resolution_timer.stop(); + mac.set_temp_crnti(SRSRAN_INVALID_RNTI); uint32_t backoff_wait; bool ra_procedure_completed = false; // true = (unsuccessfully) completed, false = uncompleted @@ -395,10 +388,10 @@ void proc_ra_nr::reset() { state = IDLE; started_by = initiators_t::initiators_t_NULLTYPE; + mac.set_temp_crnti(SRSRAN_INVALID_RNTI); prach_send_timer.stop(); rar_timeout_timer.stop(); contention_resolution_timer.stop(); - transmitted_crnti = SRSRAN_INVALID_RNTI; } } // namespace srsue diff --git a/srsue/src/stack/mac_nr/test/mac_nr_test.cc b/srsue/src/stack/mac_nr/test/mac_nr_test.cc index 9a7017a80..bafcc3698 100644 --- a/srsue/src/stack/mac_nr/test/mac_nr_test.cc +++ b/srsue/src/stack/mac_nr/test/mac_nr_test.cc @@ -61,6 +61,9 @@ public: bool has_valid_sr_resource(uint32_t sr_id) override { return false; } void clear_pending_grants() override {} + void set_timeadv_rar(uint32_t tti, uint32_t ta_cmd) final{}; + void set_timeadv(uint32_t tti, uint32_t ta_cmd) final{}; + private: uint32_t prach_occasion = 0; uint32_t preamble_index = 0; diff --git a/srsue/src/stack/mac_nr/test/proc_ra_nr_test.cc b/srsue/src/stack/mac_nr/test/proc_ra_nr_test.cc index 8c849fa88..9b6c8b94e 100644 --- a/srsue/src/stack/mac_nr/test/proc_ra_nr_test.cc +++ b/srsue/src/stack/mac_nr/test/proc_ra_nr_test.cc @@ -55,6 +55,8 @@ public: { return -1; } + void set_timeadv_rar(uint32_t tti, uint32_t ta_cmd) final {} + void set_timeadv(uint32_t tti, uint32_t ta_cmd) final {} private: uint32_t prach_occasion = 0; @@ -73,6 +75,8 @@ public: crnti = c_rnti; return true; } + void set_temp_crnti(uint16_t c_rnti) {} + void set_crnti_to_temp() {} bool msg3_is_transmitted() { return true; } void msg3_flush() {} diff --git a/srsue/src/stack/mac_nr/test/proc_sr_nr_test.cc b/srsue/src/stack/mac_nr/test/proc_sr_nr_test.cc index afbd1aafe..08ea86768 100644 --- a/srsue/src/stack/mac_nr/test/proc_sr_nr_test.cc +++ b/srsue/src/stack/mac_nr/test/proc_sr_nr_test.cc @@ -56,6 +56,9 @@ public: bool has_valid_sr_resource(uint32_t sr_id) override { return false; } void clear_pending_grants() override {} + void set_timeadv_rar(uint32_t tti, uint32_t ta_cmd) final{}; + void set_timeadv(uint32_t tti, uint32_t ta_cmd) final{}; + private: uint32_t prach_occasion = 0; uint32_t preamble_index = 0; diff --git a/srsue/src/stack/mac_nr/ul_harq_nr.cc b/srsue/src/stack/mac_nr/ul_harq_nr.cc index 2f7eea359..e29c33060 100644 --- a/srsue/src/stack/mac_nr/ul_harq_nr.cc +++ b/srsue/src/stack/mac_nr/ul_harq_nr.cc @@ -215,7 +215,7 @@ void ul_harq_entity_nr::ul_harq_process_nr::new_grant_ul(const mac_interface_phy // retransmission if (harq_buffer == nullptr) { // ignore the UL grant - logger.info("UL %d: HARQ buffer empty. Ignoring grant."); + logger.info("UL %d: HARQ buffer empty. Ignoring grant.", pid); return; } diff --git a/srsue/src/stack/rrc/rrc.cc b/srsue/src/stack/rrc/rrc.cc index 18a1eafec..7fcb25901 100644 --- a/srsue/src/stack/rrc/rrc.cc +++ b/srsue/src/stack/rrc/rrc.cc @@ -466,7 +466,8 @@ void rrc::process_new_cell_meas(const std::vector& meas) bool neighbour_added = meas_cells.process_new_cell_meas(meas, filter); // Instruct measurements subclass to update phy with new cells to measure based on strongest neighbours - if (state == RRC_STATE_CONNECTED && neighbour_added) { + // Avoid updating PHY while HO procedure is busy + if (state == RRC_STATE_CONNECTED && neighbour_added && !ho_handler.is_busy()) { measurements->update_phy(); } } @@ -2491,6 +2492,8 @@ void rrc::apply_phy_scell_config(const scell_to_add_mod_r10_s& scell_config, boo logger.error("Adding SCell cc_idx=%d", scell_config.scell_idx_r10); } else if (!phy_ctrl->set_cell_config(scell_cfg, scell_config.scell_idx_r10)) { logger.error("Setting SCell configuration for cc_idx=%d", scell_config.scell_idx_r10); + } else { + meas_cells.set_scell_cc_idx(scell_config.scell_idx_r10, earfcn, scell.id); } } diff --git a/srsue/src/stack/rrc/rrc_cell.cc b/srsue/src/stack/rrc/rrc_cell.cc index 5ed18e0b6..e4d0ac6d3 100644 --- a/srsue/src/stack/rrc/rrc_cell.cc +++ b/srsue/src/stack/rrc/rrc_cell.cc @@ -443,6 +443,25 @@ int meas_cell_list::set_serving_cell(phy_cell_t phy_cell, bool discard_servin return SRSRAN_SUCCESS; } +template +void meas_cell_list::set_scell_cc_idx(uint32_t cc_idx, uint32_t earfcn, uint32_t pci) +{ + current_cell_pci_earfcn[cc_idx].first = earfcn; + current_cell_pci_earfcn[cc_idx].second = pci; +} + +template +bool meas_cell_list::get_scell_cc_idx(uint32_t earfcn, uint32_t& pci) +{ + for (auto& cell : current_cell_pci_earfcn) { + if (cell.first == earfcn) { + pci = cell.second; + return true; + } + } + return false; +} + template bool meas_cell_list::process_new_cell_meas(const std::vector& meas, const std::function& filter_meas) diff --git a/srsue/src/stack/rrc/rrc_meas.cc b/srsue/src/stack/rrc/rrc_meas.cc index de065c725..4d4577854 100644 --- a/srsue/src/stack/rrc/rrc_meas.cc +++ b/srsue/src/stack/rrc/rrc_meas.cc @@ -127,7 +127,6 @@ bool rrc::rrc_meas::parse_meas_config(const rrc_conn_recfg_r8_ies_s* mob_reconf_ void rrc::rrc_meas::ho_reest_actions(const uint32_t src_earfcn, const uint32_t dst_earfcn) { meas_cfg.ho_reest_finish(src_earfcn, dst_earfcn); - update_phy(); } void rrc::rrc_meas::run_tti() @@ -233,12 +232,10 @@ void rrc::rrc_meas::var_meas_report_list::generate_report_eutra(meas_results_s* for (auto& cell : var_meas.cell_triggered_list) { // report neighbour cells only if (cell.pci == serv_cell->get_pci() && cell.earfcn == serv_cell->get_earfcn()) { - logger.info("MEAS: skipping serving cell in report neighbour=%d, pci=%d, earfcn=%d, rsrp=%+.1f, rsrq=%+.1f", + logger.info("MEAS: skipping serving cell in report neighbour=%d, pci=%d, earfcn=%d", neigh_list.size(), cell.pci, - var_meas.carrier_freq, - rrc_ptr->get_cell_rsrp(var_meas.carrier_freq, cell.pci), - rrc_ptr->get_cell_rsrq(var_meas.carrier_freq, cell.pci)); + var_meas.carrier_freq); continue; } if (neigh_list.size() <= var_meas.report_cfg_eutra.max_report_cells) { @@ -776,16 +773,31 @@ void rrc::rrc_meas::var_meas_cfg::eval_triggers_eutra(uint32_t meas_i } }; + eutra_event_s::event_id_c_ event_id = report_cfg.trigger_type.event().event_id; + + // For A1/A2 events, get serving cell from current carrier + if (event_id.type().value < eutra_event_s::event_id_c_::types::event_a3 && + meas_obj.carrier_freq != serv_cell->get_earfcn()) { + uint32_t scell_pci = 0; + if (!rrc_ptr->meas_cells.get_scell_cc_idx(meas_obj.carrier_freq, scell_pci)) { + logger.error("MEAS: Could not find serving cell for carrier earfcn=%d", meas_obj.carrier_freq); + return; + } + serv_cell = rrc_ptr->meas_cells.get_neighbour_cell_handle(meas_obj.carrier_freq, scell_pci); + if (!serv_cell) { + logger.error( + "MEAS: Could not find serving cell for carrier earfcn=%d and pci=%d", meas_obj.carrier_freq, scell_pci); + return; + } + } + double hyst = 0.5 * report_cfg.trigger_type.event().hysteresis; float Ms = is_rsrp(report_cfg.trigger_quant.value) ? serv_cell->get_rsrp() : serv_cell->get_rsrq(); - if (!std::isnormal(Ms)) { logger.debug("MEAS: Serving cell Ms=%f invalid when evaluating triggers", Ms); return; } - eutra_event_s::event_id_c_ event_id = report_cfg.trigger_type.event().event_id; - if (report_cfg.trigger_type.type() == report_cfg_eutra_s::trigger_type_c_::types::event) { // A1 & A2 are for serving cell only if (event_id.type().value < eutra_event_s::event_id_c_::types::event_a3) { diff --git a/srsue/src/stack/rrc/rrc_procedures.cc b/srsue/src/stack/rrc/rrc_procedures.cc index f496db851..66d9824ec 100644 --- a/srsue/src/stack/rrc/rrc_procedures.cc +++ b/srsue/src/stack/rrc/rrc_procedures.cc @@ -1564,6 +1564,8 @@ srsran::proc_outcome_t rrc::connection_reest_proc::react(const asn1::rrc::rrc_co // 1> perform the measurement related actions as specified in 5.5.6.1; rrc_ptr->measurements->ho_reest_actions(rrc_ptr->get_serving_cell()->get_earfcn(), rrc_ptr->get_serving_cell()->get_earfcn()); + // Update PHY measurements after HO Reestablishment actions. + rrc_ptr->measurements->update_phy(); // 1> submit the RRCConnectionReestablishmentComplete message to lower layers for transmission, upon which the // procedure ends; @@ -1747,6 +1749,8 @@ srsran::proc_outcome_t rrc::ho_proc::init(const asn1::rrc::rrc_conn_recfg_s& rrc // perform the measurement related actions as specified in 5.5.6.1; rrc_ptr->measurements->ho_reest_actions(ho_src_cell.earfcn, target_earfcn); + // Do not update PHY measurements here since it will be updated after the HO procedure finishes + // Note: We delay the enqueuing of RRC Reconf Complete message to avoid that the message goes in an UL grant // directed at the old RNTI. rrc_ptr->task_sched.defer_callback(4, [this]() { @@ -1805,6 +1809,8 @@ void rrc::ho_proc::then(const srsran::proc_state_t& result) srsran::console("HO %ssuccessful\n", result.is_success() ? "" : "un"); rrc_ptr->t304.stop(); + + rrc_ptr->measurements->update_phy(); } } // namespace srsue diff --git a/srsue/src/stack/rrc_nr/rrc_nr.cc b/srsue/src/stack/rrc_nr/rrc_nr.cc index a6eb945c0..48773e17d 100644 --- a/srsue/src/stack/rrc_nr/rrc_nr.cc +++ b/srsue/src/stack/rrc_nr/rrc_nr.cc @@ -54,6 +54,7 @@ int rrc_nr::init(phy_interface_rrc_nr* phy_, mac_interface_rrc_nr* mac_, rlc_interface_rrc* rlc_, pdcp_interface_rrc* pdcp_, + sdap_interface_rrc* sdap_, gw_interface_rrc* gw_, nas_5g_interface_rrc_nr* nas_, rrc_eutra_interface_rrc_nr* rrc_eutra_, @@ -65,6 +66,7 @@ int rrc_nr::init(phy_interface_rrc_nr* phy_, phy = phy_; rlc = rlc_; pdcp = pdcp_; + sdap = sdap_; gw = gw_; nas = nas_; mac = mac_; @@ -333,6 +335,11 @@ void rrc_nr::decode_dl_dcch(uint32_t lcid, unique_byte_buffer_t pdu) task_sched.defer_task([this, rrc_release]() { handle_rrc_release(rrc_release); }); break; } + case dl_dcch_msg_type_c::c1_c_::types::ue_cap_enquiry: { + ue_cap_enquiry_s ue_cap_enquiry = c1->ue_cap_enquiry(); + task_sched.defer_task([this, ue_cap_enquiry]() { handle_ue_capability_enquiry(ue_cap_enquiry); }); + break; + } default: logger.error("The provided DL-DCCH message type is not recognized or supported."); break; @@ -393,6 +400,11 @@ void rrc_nr::set_phy_default_config() void rrc_nr::handle_sib1(const sib1_s& sib1) { + if (meas_cells.serving_cell().has_sib1()) { + logger.info("SIB1 already processed"); + return; + } + meas_cells.serving_cell().set_sib1(sib1); logger.info("SIB1 received, CellID=%d", meas_cells.serving_cell().get_cell_id() & 0xfff); @@ -483,11 +495,34 @@ void rrc_nr::handle_sib1(const sib1_s& sib1) // Apply SSB Config fill_phy_ssb_cfg(sib1.serving_cell_cfg_common, &phy_cfg.ssb); + // Apply n-TimingAdvanceOffset + if (sib1.serving_cell_cfg_common.n_timing_advance_offset_present) { + switch (sib1.serving_cell_cfg_common.n_timing_advance_offset.value) { + case serving_cell_cfg_common_sib_s::n_timing_advance_offset_opts::n0: + phy_cfg.t_offset = 0; + break; + case serving_cell_cfg_common_sib_s::n_timing_advance_offset_opts::n25600: + phy_cfg.t_offset = 25600; + break; + case serving_cell_cfg_common_sib_s::n_timing_advance_offset_opts::n39936: + phy_cfg.t_offset = 39936; + break; + default: + logger.error("Invalid n_ta_offset option"); + break; + } + } else { + phy_cfg.t_offset = 25600; + } + phy_cfg_state = PHY_CFG_STATE_SA_SIB_CFG; if (not phy->set_config(phy_cfg)) { logger.warning("Could not set phy config."); return; } + + // Notify cell selector of successful SIB1 reception + cell_selector.trigger(true); } void rrc_nr::write_pdu_pcch(srsran::unique_byte_buffer_t pdu) {} @@ -673,6 +708,114 @@ void rrc_nr::send_rrc_reconfig_complete() send_ul_dcch_msg(srb_to_lcid(nr_srb::srb1), ul_dcch_msg); } +int rrc_nr::send_ue_capability_info(const asn1::rrc_nr::ue_cap_enquiry_s& msg) +{ + transaction_id = msg.rrc_transaction_id; + + ue_cap_enquiry_ies_s ue_cap_enquiry_ies = msg.crit_exts.ue_cap_enquiry(); + + asn1::rrc_nr::ul_dcch_msg_s ul_dcch_msg; + + auto& ue_cap_info = ul_dcch_msg.msg.set_c1().set_ue_cap_info().crit_exts.set_ue_cap_info(); + ul_dcch_msg.msg.c1().ue_cap_info().rrc_transaction_id = msg.rrc_transaction_id; + + for (auto ue_cap_rat_request : ue_cap_enquiry_ies.ue_cap_rat_request_list) { + if (ue_cap_rat_request.rat_type.value == rat_type_opts::nr) { + ue_cap_info.ue_cap_rat_container_list_present = true; + ue_cap_rat_container_s ue_cap_rat_container; + ue_cap_rat_container.rat_type.value = rat_type_opts::nr; + + ue_nr_cap_s ue_cap; + ue_cap.access_stratum_release = access_stratum_release_opts::rel15; + + // RLC params + ue_cap.rlc_params_present = true; + ue_cap.rlc_params.am_with_short_sn_present = true; + ue_cap.rlc_params.um_with_short_sn_present = true; + ue_cap.rlc_params.um_with_long_sn_present = true; + + // PDCP parameters + ue_cap.pdcp_params.supported_rohc_profiles.profile0x0000 = false; + ue_cap.pdcp_params.supported_rohc_profiles.profile0x0001 = false; + ue_cap.pdcp_params.supported_rohc_profiles.profile0x0002 = false; + ue_cap.pdcp_params.supported_rohc_profiles.profile0x0003 = false; + ue_cap.pdcp_params.supported_rohc_profiles.profile0x0004 = false; + ue_cap.pdcp_params.supported_rohc_profiles.profile0x0006 = false; + ue_cap.pdcp_params.supported_rohc_profiles.profile0x0101 = false; + ue_cap.pdcp_params.supported_rohc_profiles.profile0x0102 = false; + ue_cap.pdcp_params.supported_rohc_profiles.profile0x0103 = false; + ue_cap.pdcp_params.supported_rohc_profiles.profile0x0104 = false; + ue_cap.pdcp_params.max_num_rohc_context_sessions.value = pdcp_params_s::max_num_rohc_context_sessions_opts::cs2; + + if (args.pdcp_short_sn_support) { + ue_cap.pdcp_params.short_sn_present = true; + } + // PHY Parameters + ue_cap.phy_params.phy_params_common_present = true; + + // RF Parameters + for (const auto band : args.supported_bands_nr) { + band_nr_s band_nr; + band_nr.band_nr = band; + ue_cap.rf_params.supported_band_list_nr.push_back(band_nr); + + // supportedBandCombinationList + band_combination_s band_combination; + band_params_c band_params; + band_params.set_nr().band_nr = band; + band_combination.band_list.push_back(band_params); + ue_cap.rf_params.supported_band_combination_list.push_back(band_combination); + } + // featureSets + ue_cap.feature_sets_present = true; + feature_set_dl_per_cc_s feature_set_dl_per_cc; + feature_set_ul_per_cc_s feature_set_ul_per_cc; + + feature_set_dl_per_cc.supported_bw_dl.set_fr1().value = supported_bw_c::fr1_opts::mhz10; + feature_set_ul_per_cc.supported_bw_ul.set_fr1().value = supported_bw_c::fr1_opts::mhz10; + + switch (args.scs) { + case srsran_subcarrier_spacing_15kHz: + feature_set_dl_per_cc.supported_subcarrier_spacing_dl = subcarrier_spacing_opts::khz15; + feature_set_ul_per_cc.supported_subcarrier_spacing_ul = subcarrier_spacing_opts::khz15; + break; + case srsran_subcarrier_spacing_30kHz: + feature_set_dl_per_cc.supported_subcarrier_spacing_dl = subcarrier_spacing_opts::khz30; + feature_set_ul_per_cc.supported_subcarrier_spacing_ul = subcarrier_spacing_opts::khz30; + break; + default: + logger.warning("Unsupported subcarrier spacing value"); + } + + ue_cap.feature_sets.feature_sets_dl_per_cc.push_back(feature_set_dl_per_cc); + ue_cap.feature_sets.feature_sets_ul_per_cc.push_back(feature_set_ul_per_cc); + +#if 1 + ue_cap_rat_container.ue_cap_rat_container.resize(512); + asn1::bit_ref bref_pack(ue_cap_rat_container.ue_cap_rat_container.data(), + ue_cap_rat_container.ue_cap_rat_container.size()); + + if (ue_cap.pack(bref_pack) != asn1::SRSASN_SUCCESS) { + logger.error("Failed to pack UE NR Capabilities in UE Capability Info"); + return SRSRAN_ERROR; + } + ue_cap_rat_container.ue_cap_rat_container.resize(bref_pack.distance_bytes()); +#else + // hard-coded capabilities from third-party + ue_cap_rat_container.ue_cap_rat_container.from_string("E1A01000074F5A03020000C0A0241262C001206A0609B00C39F30C7942" + "C0E098040623809506C4DD608D21A08107CA01165B262A87813E43" + "9F40CF88E3C639F30C7942C0E070F09C0013C0070004F0001601C00140" + "A836036B04690D04083E500892D931541439F11C78C73E618F2858" + "1C0E1E04FE0000003F80000000A00E05"); +#endif + + ue_cap_info.ue_cap_rat_container_list.push_back(ue_cap_rat_container); + } + } + send_ul_dcch_msg(srb_to_lcid(nr_srb::srb1), ul_dcch_msg); + return SRSASN_SUCCESS; +} + // EUTRA-RRC interface int rrc_nr::get_eutra_nr_capabilities(srsran::byte_buffer_t* eutra_nr_caps_pdu) { @@ -1900,12 +2043,26 @@ bool rrc_nr::apply_drb_add_mod(const drb_to_add_mod_s& drb_cfg) stack->add_eps_bearer(eps_bearer_id, srsran::srsran_rat_t::nr, lcid); } else if (drb_cfg.cn_assoc.type() == drb_to_add_mod_s::cn_assoc_c_::types_opts::sdap_cfg) { const auto& sdap_cfg = drb_cfg.cn_assoc.sdap_cfg(); - if (sdap_cfg.sdap_hdr_dl == asn1::rrc_nr::sdap_cfg_s::sdap_hdr_dl_opts::present || - sdap_cfg.sdap_hdr_ul == asn1::rrc_nr::sdap_cfg_s::sdap_hdr_ul_opts::present) { - logger.error("SDAP currently not supported."); + + // Check supported configuration + if (sdap_cfg.sdap_hdr_dl.value == sdap_cfg_s::sdap_hdr_dl_opts::present || !sdap_cfg.default_drb || + sdap_cfg.mapped_qos_flows_to_add.size() != 1) { + logger.error( + "Configuring SDAP: only UL headder is supported. Default DRB must be set and number of QoS flows must be 1"); return false; } - // TODO: configure SDAP accordingly + + sdap_interface_rrc::bearer_cfg_t sdap_bearer_cfg = {}; + sdap_bearer_cfg.add_downlink_header = sdap_cfg.sdap_hdr_dl.value == sdap_cfg_s::sdap_hdr_dl_opts::present; + sdap_bearer_cfg.add_uplink_header = sdap_cfg.sdap_hdr_ul.value == sdap_cfg_s::sdap_hdr_ul_opts::present; + sdap_bearer_cfg.is_data = true; + sdap_bearer_cfg.qfi = sdap_cfg.mapped_qos_flows_to_add[0]; + + if (not sdap->set_bearer_cfg(lcid, sdap_bearer_cfg)) { + logger.error("Configuring SDAP"); + return false; + } + uint32_t pdu_session_id = drb_cfg.cn_assoc.sdap_cfg().pdu_session; // Register PDU session as "EPS bearer" in bearer manager stack->add_eps_bearer(pdu_session_id, srsran::srsran_rat_t::nr, lcid); @@ -2040,6 +2197,11 @@ void rrc_nr::handle_rrc_reconfig(const rrc_recfg_s& reconfig) } callback_list.add_proc(conn_recfg_proc); } +void rrc_nr::handle_ue_capability_enquiry(const ue_cap_enquiry_s& ue_cap_enquiry) +{ + logger.info("Received UE Capability Enquiry"); + send_ue_capability_info(ue_cap_enquiry); +} void rrc_nr::handle_dl_info_transfer(const dl_info_transfer_s& dl_info_transfer) { @@ -2161,6 +2323,9 @@ void rrc_nr::set_phy_config_complete(bool status) case PHY_CFG_STATE_NONE: logger.warning("PHY configuration completed without a clear state."); break; + case PHY_CFG_STATE_SA_MIB_CFG: + logger.info("PHY configuration with MIB parameters completed."); + break; case PHY_CFG_STATE_SA_SIB_CFG: logger.info("PHY configuration with SIB parameters completed."); break; diff --git a/srsue/src/stack/rrc_nr/rrc_nr_procedures.cc b/srsue/src/stack/rrc_nr/rrc_nr_procedures.cc index c82e9d15f..842256c14 100644 --- a/srsue/src/stack/rrc_nr/rrc_nr_procedures.cc +++ b/srsue/src/stack/rrc_nr/rrc_nr_procedures.cc @@ -393,12 +393,12 @@ proc_outcome_t rrc_nr::cell_selection_proc::init() return proc_outcome_t::yield; } -// Skipping SI acquisition procedure proc_outcome_t rrc_nr::cell_selection_proc::step() { switch (state) { case state_t::phy_cell_search: case state_t::phy_cell_select: + case state_t::sib_acquire: // Waits for cell select/search to complete return proc_outcome_t::yield; } @@ -409,84 +409,101 @@ proc_outcome_t rrc_nr::cell_selection_proc::step() proc_outcome_t rrc_nr::cell_selection_proc::handle_cell_search_result(const rrc_interface_phy_nr::cell_search_result_t& result) { - if (result.cell_found) { - // Convert Cell measurement in Text - std::array csi_info_str = {}; - srsran_csi_meas_info_short(&result.measurements, csi_info_str.data(), (uint32_t)csi_info_str.size()); - - // Unpack MIB and convert to text - srsran_mib_nr_t mib = {}; - std::array mib_info_str = {}; - if (srsran_pbch_msg_nr_mib_unpack(&result.pbch_msg, &mib) == SRSASN_SUCCESS) { - // Convert to text - srsran_pbch_msg_nr_mib_info(&mib, mib_info_str.data(), (uint32_t)mib_info_str.size()); - } else { - // It could be the PBCH does not carry MIB - strcpy(mib_info_str.data(), "No MIB found"); - } + if (!result.cell_found) { + Info("Cell search did not find any cell."); + return proc_outcome_t::error; + } - // Logs the PCI, cell measurements and decoded MIB - Info("Cell search found ARFCN=%d PCI=%d %s %s", - result.ssb_arfcn, - result.pci, - csi_info_str.data(), - mib_info_str.data()); - - // Transition to cell selection ignoring the cell search result - state = state_t::phy_cell_select; - - // until cell selection is done, update PHY config to take the last found PCI - rrc_handle.phy_cfg.carrier.pci = result.pci; - - phy_interface_rrc_nr::cell_select_args_t cs_args = {}; - cs_args.carrier = rrc_handle.phy_cfg.carrier; - cs_args.ssb_cfg = rrc_handle.phy_cfg.get_ssb_cfg(); - - { - // Coreset0 configuration - srsran::phy_cfg_nr_t& phy_cfg = rrc_handle.phy_cfg; - - // Get pointA and SSB absolute frequencies - double pointA_abs_freq_Hz = - phy_cfg.carrier.dl_center_frequency_hz - - phy_cfg.carrier.nof_prb * SRSRAN_NRE * SRSRAN_SUBC_SPACING_NR(phy_cfg.carrier.scs) / 2; - double ssb_abs_freq_Hz = phy_cfg.carrier.ssb_center_freq_hz; - // Calculate integer SSB to pointA frequency offset in Hz - uint32_t ssb_pointA_freq_offset_Hz = - (ssb_abs_freq_Hz > pointA_abs_freq_Hz) ? (uint32_t)(ssb_abs_freq_Hz - pointA_abs_freq_Hz) : 0; - - if (srsran_coreset_zero(phy_cfg.carrier.pci, - ssb_pointA_freq_offset_Hz, - phy_cfg.ssb.scs, - phy_cfg.carrier.scs, - 6, - &phy_cfg.pdcch.coreset[0])) { - fprintf(stderr, "Error generating coreset0\n"); - } - phy_cfg.pdcch.coreset_present[0] = true; - } + // Convert Cell measurement in Text + std::array csi_info_str = {}; + srsran_csi_meas_info_short(&result.measurements, csi_info_str.data(), (uint32_t)csi_info_str.size()); - // Until SI acquisition is implemented, provide hard-coded SIB for now - uint8_t msg[] = {0x74, 0x81, 0x01, 0x70, 0x10, 0x40, 0x04, 0x02, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x33, 0x60, 0x38, - 0x05, 0x01, 0x00, 0x40, 0x1a, 0x00, 0x00, 0x06, 0x6c, 0x6d, 0x92, 0x21, 0xf3, 0x70, 0x40, 0x20, - 0x00, 0x00, 0x80, 0x80, 0x00, 0x41, 0x06, 0x80, 0xa0, 0x90, 0x9c, 0x20, 0x08, 0x55, 0x19, 0x40, - 0x00, 0x00, 0x33, 0xa1, 0xc6, 0xd9, 0x22, 0x40, 0x00, 0x00, 0x20, 0xb8, 0x94, 0x63, 0xc0, 0x09, - 0x28, 0x44, 0x1b, 0x7e, 0xad, 0x8e, 0x1d, 0x00, 0x9e, 0x2d, 0xa3, 0x0a}; - srsran::unique_byte_buffer_t pdu = srsran::make_byte_buffer(); - memcpy(pdu->msg, msg, sizeof(msg)); - pdu->N_bytes = sizeof(msg); - rrc_handle.write_pdu_bcch_dlsch(std::move(pdu)); - - if (not rrc_handle.phy->start_cell_select(cs_args)) { - Error("Could not set start cell search."); - return proc_outcome_t::error; - } - return proc_outcome_t::yield; + // Unpack MIB and convert to text + srsran_mib_nr_t mib = {}; + std::array mib_info_str = {}; + if (srsran_pbch_msg_nr_mib_unpack(&result.pbch_msg, &mib) == SRSASN_SUCCESS) { + // Convert to text + srsran_pbch_msg_nr_mib_info(&mib, mib_info_str.data(), (uint32_t)mib_info_str.size()); } else { - Info("Cell search did not find any cell."); + // It could be the PBCH does not carry MIB + strcpy(mib_info_str.data(), "No MIB found"); + Error("No MIB found\n"); + return proc_outcome_t::error; } - return proc_outcome_t::error; + // Check unsupported settings + if (mib.cell_barred) { + Error("Cell barred"); + return proc_outcome_t::error; + } + if (mib.scs_common != srsran_subcarrier_spacing_15kHz) { + Error("Unsupported SCS %s", srsran_subcarrier_spacing_to_str(mib.scs_common)); + return proc_outcome_t::error; + } + // TODO: calculate SSB offset + if (mib.ssb_offset != 6) { + Error("Unsupported SSB offset %d", mib.ssb_offset); + return proc_outcome_t::error; + } + + // Logs the PCI, cell measurements and decoded MIB + Info("Cell search found ARFCN=%d PCI=%d %s %s", + result.ssb_arfcn, + result.pci, + csi_info_str.data(), + mib_info_str.data()); + + // Apply MIB settings + srsran::phy_cfg_nr_t& phy_cfg = rrc_handle.phy_cfg; + phy_cfg.pdsch.typeA_pos = mib.dmrs_typeA_pos; + phy_cfg.pdsch.scs_cfg = mib.scs_common; + phy_cfg.carrier.pci = result.pci; + + // Get pointA and SSB absolute frequencies + double pointA_abs_freq_Hz = phy_cfg.carrier.dl_center_frequency_hz - + phy_cfg.carrier.nof_prb * SRSRAN_NRE * SRSRAN_SUBC_SPACING_NR(phy_cfg.carrier.scs) / 2; + double ssb_abs_freq_Hz = phy_cfg.carrier.ssb_center_freq_hz; + // Calculate integer SSB to pointA frequency offset in Hz + uint32_t ssb_pointA_freq_offset_Hz = + (ssb_abs_freq_Hz > pointA_abs_freq_Hz) ? (uint32_t)(ssb_abs_freq_Hz - pointA_abs_freq_Hz) : 0; + + // Create coreset0 + if (srsran_coreset_zero(phy_cfg.carrier.pci, + ssb_pointA_freq_offset_Hz, + phy_cfg.ssb.scs, + phy_cfg.carrier.scs, + mib.coreset0_idx, + &phy_cfg.pdcch.coreset[0])) { + Error("Error generating coreset0"); + return proc_outcome_t::error; + } + phy_cfg.pdcch.coreset_present[0] = true; + + // Create SearchSpace0 + make_phy_search_space0_cfg(&phy_cfg.pdcch.search_space[0]); + phy_cfg.pdcch.search_space_present[0] = true; + + // Set dummy offset to pass PRACH config check, real value is provided in SIB1 + phy_cfg.prach.freq_offset = 1; + + // Update PHY configuration + rrc_handle.phy_cfg_state = PHY_CFG_STATE_SA_MIB_CFG; + if (not rrc_handle.phy->set_config(phy_cfg)) { + Error("Setting PHY configuration"); + return proc_outcome_t::error; + } + + phy_interface_rrc_nr::cell_select_args_t cs_args = {}; + cs_args.carrier = rrc_handle.phy_cfg.carrier; + cs_args.ssb_cfg = rrc_handle.phy_cfg.get_ssb_cfg(); + + // Transition to cell selection ignoring the cell search result + state = state_t::phy_cell_select; + if (not rrc_handle.phy->start_cell_select(cs_args)) { + Error("Could not set start cell search."); + return proc_outcome_t::error; + } + return proc_outcome_t::yield; } proc_outcome_t rrc_nr::cell_selection_proc::react(const rrc_interface_phy_nr::cell_select_result_t& event) @@ -506,7 +523,24 @@ proc_outcome_t rrc_nr::cell_selection_proc::react(const rrc_interface_phy_nr::ce rrc_search_result = rrc_nr::rrc_cell_search_result_t::same_cell; // PHY is now camping on serving cell - Info("Cell search completed."); + Info("Cell selection completed. Starting SIB1 acquisition"); + + // Transition to cell selection ignoring the cell search result + state = state_t::sib_acquire; + rrc_handle.mac->bcch_search(true); + return proc_outcome_t::yield; +} + +proc_outcome_t rrc_nr::cell_selection_proc::react(const bool sib1_found) +{ + if (state != state_t::sib_acquire) { + Warning("Received unexpected cell select result"); + return proc_outcome_t::yield; + } + + Info("SIB1 acquired successfully"); + rrc_handle.mac->bcch_search(false); + return proc_outcome_t::success; } diff --git a/srsue/src/stack/rrc_nr/test/ue_rrc_nr_test.cc b/srsue/src/stack/rrc_nr/test/ue_rrc_nr_test.cc index f8fbc9fd1..c7c945407 100644 --- a/srsue/src/stack/rrc_nr/test/ue_rrc_nr_test.cc +++ b/srsue/src/stack/rrc_nr/test/ue_rrc_nr_test.cc @@ -50,6 +50,7 @@ class dummy_mac : public mac_interface_rrc_nr int add_tag_config(const srsran::tag_cfg_nr_t& tag_cfg) { return SRSRAN_SUCCESS; } int set_config(const srsran::phr_cfg_nr_t& phr_cfg) { return SRSRAN_SUCCESS; } int remove_tag_config(const uint32_t tag_id) { return SRSRAN_SUCCESS; } + void bcch_search(bool) {} void start_ra_procedure() {} @@ -102,6 +103,13 @@ class dummy_pdcp : public pdcp_interface_rrc void send_status_report(uint32_t lcid){}; }; +class dummy_sdap : public sdap_interface_pdcp_nr, public sdap_interface_gw_nr, public sdap_interface_rrc +{ + void write_pdu(uint32_t lcid, srsran::unique_byte_buffer_t pdu) final{}; + void write_sdu(uint32_t lcid, srsran::unique_byte_buffer_t pdu) final{}; + bool set_bearer_cfg(uint32_t lcid, const sdap_interface_rrc::bearer_cfg_t& cfg) final { return true; }; +}; + class dummy_gw : public gw_interface_rrc { void add_mch_port(uint32_t lcid, uint32_t port){}; @@ -152,6 +160,7 @@ int rrc_nr_cap_request_test() dummy_mac dummy_mac; dummy_rlc dummy_rlc; dummy_pdcp dummy_pdcp; + dummy_sdap dummy_sdap; dummy_gw dummy_gw; dummy_nas dummy_nas; dummy_eutra dummy_eutra; @@ -166,6 +175,7 @@ int rrc_nr_cap_request_test() &dummy_mac, &dummy_rlc, &dummy_pdcp, + &dummy_sdap, &dummy_gw, &dummy_nas, &dummy_eutra, @@ -192,6 +202,7 @@ int rrc_nsa_reconfig_tdd_test() dummy_mac dummy_mac; dummy_rlc dummy_rlc; dummy_pdcp dummy_pdcp; + dummy_sdap dummy_sdap; dummy_gw dummy_gw; dummy_nas dummy_nas; dummy_eutra dummy_eutra; @@ -202,6 +213,7 @@ int rrc_nsa_reconfig_tdd_test() &dummy_mac, &dummy_rlc, &dummy_pdcp, + &dummy_sdap, &dummy_gw, &dummy_nas, &dummy_eutra, @@ -293,6 +305,7 @@ int rrc_nsa_reconfig_fdd_test() dummy_mac dummy_mac; dummy_rlc dummy_rlc; dummy_pdcp dummy_pdcp; + dummy_sdap dummy_sdap; dummy_gw dummy_gw; dummy_nas dummy_nas; dummy_eutra dummy_eutra; @@ -303,6 +316,7 @@ int rrc_nsa_reconfig_fdd_test() &dummy_mac, &dummy_rlc, &dummy_pdcp, + &dummy_sdap, &dummy_gw, &dummy_nas, &dummy_eutra, @@ -395,6 +409,7 @@ int rrc_nr_setup_request_test() dummy_mac dummy_mac; dummy_rlc dummy_rlc; dummy_pdcp dummy_pdcp; + dummy_sdap dummy_sdap; dummy_gw dummy_gw; dummy_nas dummy_nas; dummy_eutra dummy_eutra; @@ -408,6 +423,7 @@ int rrc_nr_setup_request_test() &dummy_mac, &dummy_rlc, &dummy_pdcp, + &dummy_sdap, &dummy_gw, &dummy_nas, &dummy_eutra, @@ -437,6 +453,7 @@ int rrc_nr_sib1_decoding_test() dummy_mac dummy_mac; dummy_rlc dummy_rlc; dummy_pdcp dummy_pdcp; + dummy_sdap dummy_sdap; dummy_gw dummy_gw; dummy_nas dummy_nas; dummy_eutra dummy_eutra; @@ -447,6 +464,7 @@ int rrc_nr_sib1_decoding_test() &dummy_mac, &dummy_rlc, &dummy_pdcp, + &dummy_sdap, &dummy_gw, &dummy_nas, &dummy_eutra, @@ -484,6 +502,7 @@ int rrc_nr_setup_test() dummy_mac dummy_mac; dummy_rlc dummy_rlc; dummy_pdcp dummy_pdcp; + dummy_sdap dummy_sdap; dummy_gw dummy_gw; dummy_nas dummy_nas; dummy_eutra dummy_eutra; @@ -494,6 +513,7 @@ int rrc_nr_setup_test() &dummy_mac, &dummy_rlc, &dummy_pdcp, + &dummy_sdap, &dummy_gw, &dummy_nas, &dummy_eutra, @@ -537,6 +557,7 @@ int rrc_nr_reconfig_test() dummy_mac dummy_mac; dummy_rlc dummy_rlc; dummy_pdcp dummy_pdcp; + dummy_sdap dummy_sdap; dummy_gw dummy_gw; dummy_nas dummy_nas; dummy_eutra dummy_eutra; @@ -547,6 +568,7 @@ int rrc_nr_reconfig_test() &dummy_mac, &dummy_rlc, &dummy_pdcp, + &dummy_sdap, &dummy_gw, &dummy_nas, &dummy_eutra, diff --git a/srsue/src/stack/ue_stack_lte.cc b/srsue/src/stack/ue_stack_lte.cc index 264789734..b9955e75f 100644 --- a/srsue/src/stack/ue_stack_lte.cc +++ b/srsue/src/stack/ue_stack_lte.cc @@ -56,6 +56,8 @@ ue_stack_lte::ue_stack_lte() : rrc_nr(&task_sched), pdcp(&task_sched, "PDCP"), pdcp_nr(&task_sched, "PDCP-NR"), + sdap("SDAP-NR"), + sdap_pdcp(&pdcp_nr, &sdap), nas(srslog::fetch_basic_logger("NAS", false), &task_sched), nas_5g(srslog::fetch_basic_logger("NAS5G", false), &task_sched), thread("STACK"), @@ -217,9 +219,15 @@ int ue_stack_lte::init(const stack_args_t& args_) mac.init(phy, &rlc, &rrc); rlc.init(&pdcp, &rrc, task_sched.get_timer_handler(), 0 /* RB_ID_SRB0 */); - pdcp.init(&rlc, &rrc, gw); nas.init(usim.get(), &rrc, gw, args.nas); + if (!args.sa_mode) { + pdcp.init(&rlc, &rrc, gw); + } else { + pdcp.init(&rlc, &rrc, &sdap_pdcp); + sdap.init(&sdap_pdcp, gw); + } + mac_nr_args_t mac_nr_args = {}; mac_nr.init(mac_nr_args, phy_nr, &rlc_nr, &rrc_nr); rlc_nr.init(&pdcp_nr, &rrc_nr, task_sched.get_timer_handler(), 0 /* RB_ID_SRB0 */); @@ -228,6 +236,7 @@ int ue_stack_lte::init(const stack_args_t& args_) &mac_nr, &rlc_nr, &pdcp_nr, + &sdap, gw, &nas_5g, args.sa_mode ? nullptr : &rrc, @@ -409,12 +418,17 @@ void ue_stack_lte::remove_eps_bearer(uint8_t eps_bearer_id) void ue_stack_lte::write_sdu(uint32_t eps_bearer_id, srsran::unique_byte_buffer_t sdu) { auto bearer = bearers.get_radio_bearer(eps_bearer_id); + auto task = [this, eps_bearer_id, bearer](srsran::unique_byte_buffer_t& sdu) { // route SDU to PDCP entity if (bearer.rat == srsran_rat_t::lte) { pdcp.write_sdu(bearer.lcid, std::move(sdu)); } else if (bearer.rat == srsran_rat_t::nr) { - pdcp_nr.write_sdu(bearer.lcid, std::move(sdu)); + if (args.sa_mode) { + sdap.write_sdu(bearer.lcid, std::move(sdu)); + } else { + pdcp_nr.write_sdu(bearer.lcid, std::move(sdu)); + } } else { stack_logger.warning("Can't deliver SDU for EPS bearer %d. Dropping it.", eps_bearer_id); } diff --git a/srsue/src/stack/ue_stack_nr.cc b/srsue/src/stack/ue_stack_nr.cc index ceb5b033a..ae86def80 100644 --- a/srsue/src/stack/ue_stack_nr.cc +++ b/srsue/src/stack/ue_stack_nr.cc @@ -89,6 +89,7 @@ int ue_stack_nr::init(const stack_args_t& args_) mac.get(), rlc.get(), pdcp.get(), + sdap.get(), gw, nullptr, nullptr, diff --git a/srsue/src/stack/upper/CMakeLists.txt b/srsue/src/stack/upper/CMakeLists.txt index 701dbbdde..e076a9f80 100644 --- a/srsue/src/stack/upper/CMakeLists.txt +++ b/srsue/src/stack/upper/CMakeLists.txt @@ -20,7 +20,7 @@ add_subdirectory(test) -set(SOURCES nas.cc nas_emm_state.cc nas_idle_procedures.cc gw.cc usim_base.cc usim.cc tft_packet_filter.cc nas_base.cc nas_5g_procedures.cc nas_5g.cc nas_5gmm_state.cc) +set(SOURCES nas.cc nas_emm_state.cc nas_idle_procedures.cc gw.cc usim_base.cc usim.cc tft_packet_filter.cc nas_base.cc nas_5g_procedures.cc nas_5g.cc nas_5gmm_state.cc sdap.cc) if(HAVE_PCSC) list(APPEND SOURCES "pcsc_usim.cc") diff --git a/srsue/src/stack/upper/nas_5g.cc b/srsue/src/stack/upper/nas_5g.cc index 39223cc1c..b180f4c34 100644 --- a/srsue/src/stack/upper/nas_5g.cc +++ b/srsue/src/stack/upper/nas_5g.cc @@ -585,9 +585,8 @@ int nas_5g::send_pdu_session_establishment_request(uint32_t tran ul_nas_msg.request_type.request_type_value = request_type_t::Request_type_value_type_::options::initial_request; ul_nas_msg.s_nssai_present = true; - ul_nas_msg.s_nssai.type = s_nssai_t::SST_type_::options::sst_and_sd; + ul_nas_msg.s_nssai.type = s_nssai_t::SST_type_::options::sst; ul_nas_msg.s_nssai.sst = 1; - ul_nas_msg.s_nssai.sd = 0; ul_nas_msg.dnn_present = true; ul_nas_msg.dnn.dnn_value.resize(pdu_session_cfg.apn_name.size() + 1); @@ -845,11 +844,8 @@ int nas_5g::handle_authentication_request(authentication_request_t& authenticati logger.info("Handling Authentication Request"); ctxt_base.rx_count++; // Generate authentication response using RAND, AUTN & KSI-ASME - uint16 mcc, mnc; - mcc = rrc_nr->get_mcc(); - mnc = rrc_nr->get_mnc(); plmn_id_t plmn_id; - plmn_id.from_number(mcc, mnc); + usim->get_home_plmn_id(&plmn_id); if (authentication_request.authentication_parameter_rand_present == false) { logger.error("authentication_parameter_rand_present is not present"); @@ -1368,4 +1364,4 @@ int nas_5g::add_pdu_session(uint16_t pdu_session_id, return SRSRAN_SUCCESS; } -} // namespace srsue \ No newline at end of file +} // namespace srsue diff --git a/srsue/src/stack/upper/sdap.cc b/srsue/src/stack/upper/sdap.cc new file mode 100644 index 000000000..74f0c65df --- /dev/null +++ b/srsue/src/stack/upper/sdap.cc @@ -0,0 +1,76 @@ +/** + * + * \section COPYRIGHT + * + * Copyright 2013-2021 Software Radio Systems Limited + * + * By using this file, you agree to the terms and conditions set + * forth in the LICENSE file which can be found at the top level of + * the distribution. + * + */ + +#include "srsue/hdr/stack/upper/sdap.h" + +namespace srsue { + +sdap::sdap(const char* logname) : logger(srslog::fetch_basic_logger(logname)) {} + +bool sdap::init(pdcp_interface_sdap_nr* pdcp_, srsue::gw_interface_pdcp* gw_) +{ + m_pdcp = pdcp_; + m_gw = gw_; + + running = true; + return true; +} + +void sdap::stop() +{ + if (running) { + running = false; + } +} + +void sdap::write_pdu(uint32_t lcid, srsran::unique_byte_buffer_t pdu) +{ + if (!running) { + return; + } + m_gw->write_pdu(lcid, std::move(pdu)); +} + +void sdap::write_sdu(uint32_t lcid, srsran::unique_byte_buffer_t pdu) +{ + if (!running) { + return; + } + if (lcid < bearers.size()) { + if (bearers[lcid].add_uplink_header) { + if (pdu->get_headroom() > 1) { + pdu->msg -= 1; + pdu->N_bytes += 1; + pdu->msg[0] = ((bearers[lcid].is_data ? 1 : 0) << 7) | (bearers[lcid].qfi & 0x3f); + } else { + logger.error("Not enough headroom in PDU to add header\n"); + } + } + } + m_pdcp->write_sdu(lcid, std::move(pdu)); +} + +bool sdap::set_bearer_cfg(uint32_t lcid, const sdap_interface_rrc::bearer_cfg_t& cfg) +{ + if (lcid >= bearers.size()) { + logger.error("Error setting configuration: invalid lcid=%d\n", lcid); + return false; + } + if (cfg.add_downlink_header) { + logger.error("Error setting configuration: downlink header not supported\n"); + return false; + } + bearers[lcid] = cfg; + return true; +} + +} // namespace srsue diff --git a/srsue/src/ue.cc b/srsue/src/ue.cc index 8ced08c5a..4958e37db 100644 --- a/srsue/src/ue.cc +++ b/srsue/src/ue.cc @@ -293,6 +293,33 @@ int ue::parse_args(const all_args_t& args_) args.stack.nas_5g.pdu_session_cfgs.push_back({args.stack.nas.apn_name}); } + // Validate the CFR args + srsran_cfr_cfg_t cfr_test_cfg = {}; + cfr_test_cfg.cfr_enable = args.phy.cfr_args.enable; + cfr_test_cfg.cfr_mode = args.phy.cfr_args.mode; + cfr_test_cfg.alpha = args.phy.cfr_args.strength; + cfr_test_cfg.manual_thr = args.phy.cfr_args.manual_thres; + cfr_test_cfg.max_papr_db = args.phy.cfr_args.auto_target_papr; + cfr_test_cfg.ema_alpha = args.phy.cfr_args.ema_alpha; + + if (!srsran_cfr_params_valid(&cfr_test_cfg)) { + srsran::console("Invalid CFR parameters: cfr_mode=%d, alpha=%.2f, manual_thr=%.2f, \n " + "max_papr_db=%.2f, ema_alpha=%.2f\n", + cfr_test_cfg.cfr_mode, + cfr_test_cfg.alpha, + cfr_test_cfg.manual_thr, + cfr_test_cfg.max_papr_db, + cfr_test_cfg.ema_alpha); + + logger.error("Invalid CFR parameters: cfr_mode=%d, alpha=%.2f, manual_thr=%.2f, max_papr_db=%.2f, ema_alpha=%.2f\n", + cfr_test_cfg.cfr_mode, + cfr_test_cfg.alpha, + cfr_test_cfg.manual_thr, + cfr_test_cfg.max_papr_db, + cfr_test_cfg.ema_alpha); + return SRSRAN_ERROR; + } + return SRSRAN_SUCCESS; } diff --git a/srsue/ue.conf.example b/srsue/ue.conf.example index 59f36dacd..746c45979 100644 --- a/srsue/ue.conf.example +++ b/srsue/ue.conf.example @@ -389,6 +389,33 @@ enable = false [phy.nr] #store_pdsch_ko = false +##################################################################### +# CFR configuration options +# +# The CFR module provides crest factor reduction for the transmitted signal. +# +# enable: Enable or disable the CFR. Default: disabled +# +# mode: manual: CFR threshold is set by cfr_manual_thres (default). +# auto_ema: CFR threshold is adaptive based on the signal PAPR. Power avg. with Exponential Moving Average. +# The time constant of the averaging can be tweaked with the ema_alpha parameter. +# auto_cma: CFR threshold is adaptive based on the signal PAPR. Power avg. with Cumulative Moving Average. +# Use with care, as CMA's increasingly slow response may be unsuitable for most use cases. +# +# strength: Ratio between amplitude-limited vs unprocessed signal (0 to 1). Default: 1 +# manual_thres: Fixed manual clipping threshold for CFR manual mode. Default: 2 +# auto_target_papr: Signal PAPR target (in dB) in CFR auto modes. output PAPR can be higher due to peak smoothing. Default: 7 +# ema_alpha: Alpha coefficient for the power average in auto_ema mode. Default: 1/7 +# +##################################################################### +[cfr] +#enable = false +#mode = manual +#manual_thres = 2.0 +#strength = 1.0 +#auto_target_papr = 7.0 +#ema_alpha = 0.0143 + ##################################################################### # Simulation configuration options #